mirror of https://github.com/usememos/memos.git
refactor: unify components
This commit is contained in:
parent
50a41a39a6
commit
493832aeb4
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "src/style.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
|
|
@ -15,11 +15,20 @@
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@github/relative-time-element": "^4.4.8",
|
"@github/relative-time-element": "^4.4.8",
|
||||||
"@matejmazur/react-katex": "^3.1.3",
|
"@matejmazur/react-katex": "^3.1.3",
|
||||||
"@mui/joy": "5.0.0-beta.52",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.14",
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
|
"@radix-ui/react-radio-group": "^1.3.7",
|
||||||
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"@radix-ui/react-switch": "^1.2.5",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"@usememos/mui": "0.1.0-20250607013227",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
|
|
@ -70,6 +79,7 @@
|
||||||
"nice-grpc-web": "^3.3.7",
|
"nice-grpc-web": "^3.3.7",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"terser": "^5.40.0",
|
"terser": "^5.40.0",
|
||||||
|
"tw-animate-css": "^1.3.4",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.33.0",
|
"typescript-eslint": "^8.33.0",
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.3.5"
|
||||||
|
|
|
||||||
1870
web/pnpm-lock.yaml
1870
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,5 @@
|
||||||
import { useColorScheme } from "@mui/joy";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import { getSystemColorScheme } from "./helpers/utils";
|
import { getSystemColorScheme } from "./helpers/utils";
|
||||||
|
|
@ -10,7 +9,7 @@ import { userStore, workspaceStore } from "./store/v2";
|
||||||
const App = observer(() => {
|
const App = observer(() => {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const { mode, setMode } = useColorScheme();
|
const [mode, setMode] = useState<"light" | "dark">("light");
|
||||||
const workspaceProfile = workspaceStore.state.profile;
|
const workspaceProfile = workspaceStore.state.profile;
|
||||||
const userSetting = userStore.state.userSetting;
|
const userSetting = userStore.state.userSetting;
|
||||||
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
|
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { memo, useMemo } from "react";
|
import { memo, useMemo } from "react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import type { ActivityCalendarProps, CalendarDay } from "@/types/statistics";
|
import type { ActivityCalendarProps, CalendarDay } from "@/types/statistics";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
const getCellOpacity = (ratio: number): string => {
|
const getCellOpacity = (ratio: number): string => {
|
||||||
|
|
@ -59,9 +59,16 @@ const CalendarCell = memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip className="shrink-0" title={tooltipText} placement="top" arrow>
|
<TooltipProvider>
|
||||||
{cellContent}
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger asChild>
|
||||||
|
<div className="shrink-0">{cellContent}</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{tooltipText}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Option, Select } from "@mui/joy";
|
|
||||||
import { SunIcon, MoonIcon, SmileIcon } from "lucide-react";
|
import { SunIcon, MoonIcon, SmileIcon } from "lucide-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -31,21 +31,20 @@ const AppearanceSelect: FC<Props> = (props: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select value={value} onValueChange={handleSelectChange}>
|
||||||
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
|
<SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
|
||||||
value={value}
|
<SelectValue placeholder="Select appearance" />
|
||||||
onChange={(_, appearance) => {
|
</SelectTrigger>
|
||||||
if (appearance) {
|
<SelectContent>
|
||||||
handleSelectChange(appearance);
|
{appearanceList.map((item) => (
|
||||||
}
|
<SelectItem key={item} value={item} className="whitespace-nowrap">
|
||||||
}}
|
<div className="flex items-center gap-2">
|
||||||
startDecorator={getPrefixIcon(value)}
|
{getPrefixIcon(item)}
|
||||||
>
|
{t(`setting.appearance-option.${item}`)}
|
||||||
{appearanceList.map((item) => (
|
</div>
|
||||||
<Option key={item} value={item} className="whitespace-nowrap">
|
</SelectItem>
|
||||||
{t(`setting.appearance-option.${item}`)}
|
))}
|
||||||
</Option>
|
</SelectContent>
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import {
|
||||||
SheetIcon,
|
SheetIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
|
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
|
||||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||||
import SquareDiv from "./kit/SquareDiv";
|
import SquareDiv from "./kit/SquareDiv";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import AppearanceSelect from "./AppearanceSelect";
|
import AppearanceSelect from "./AppearanceSelect";
|
||||||
import LocaleSelect from "./LocaleSelect";
|
import LocaleSelect from "./LocaleSelect";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { User } from "@/types/proto/api/v1/user_service";
|
import { User } from "@/types/proto/api/v1/user_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
@ -69,7 +70,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
||||||
<p>
|
<p>
|
||||||
{t("setting.account-section.change-password")} ({user.displayName})
|
{t("setting.account-section.change-password")} ({user.displayName})
|
||||||
</p>
|
</p>
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,7 +92,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
|
||||||
onChange={handleNewPasswordAgainChanged}
|
onChange={handleNewPasswordAgainChanged}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-row justify-end items-center mt-4 w-full gap-x-2">
|
<div className="flex flex-row justify-end items-center mt-4 w-full gap-x-2">
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" onClick={handleSaveBtnClick}>
|
<Button color="primary" onClick={handleSaveBtnClick}>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Radio, RadioGroup } from "@mui/joy";
|
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { userServiceClient } from "@/grpcweb";
|
import { userServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
|
|
@ -56,9 +58,9 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleRoleInputChange = (value: string) => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
expiration: Number(e.target.value),
|
expiration: Number(value),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,7 +91,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center w-full mb-4 gap-2">
|
<div className="flex flex-row justify-between items-center w-full mb-4 gap-2">
|
||||||
<p>{t("setting.access-token-section.create-dialog.create-access-token")}</p>
|
<p>{t("setting.access-token-section.create-dialog.create-access-token")}</p>
|
||||||
<Button variant="plain" onClick={() => destroy()}>
|
<Button variant="ghost" onClick={() => destroy()}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -113,18 +115,21 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
|
||||||
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
|
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
|
||||||
</span>
|
</span>
|
||||||
<div className="w-full flex flex-row justify-start items-center text-base">
|
<div className="w-full flex flex-row justify-start items-center text-base">
|
||||||
<RadioGroup orientation="horizontal" value={state.expiration} onChange={handleRoleInputChange}>
|
<RadioGroup value={state.expiration.toString()} onValueChange={handleRoleInputChange} className="flex flex-row gap-4">
|
||||||
{expirationOptions.map((option) => (
|
{expirationOptions.map((option) => (
|
||||||
<Radio key={option.value} value={option.value} checked={state.expiration === option.value} label={option.label} />
|
<div key={option.value} className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={option.value.toString()} id={`expiration-${option.value}`} />
|
||||||
|
<Label htmlFor={`expiration-${option.value}`}>{option.label}</Label>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
|
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
|
<Button disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
|
||||||
{t("common.create")}
|
{t("common.create")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Divider, Option, Select, Typography } from "@mui/joy";
|
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { identityProviderServiceClient } from "@/grpcweb";
|
import { identityProviderServiceClient } from "@/grpcweb";
|
||||||
import { absolutifyLink } from "@/helpers/utils";
|
import { absolutifyLink } from "@/helpers/utils";
|
||||||
import { FieldMapping, IdentityProvider, IdentityProvider_Type, OAuth2Config } from "@/types/proto/api/v1/idp_service";
|
import { FieldMapping, IdentityProvider, IdentityProvider_Type, OAuth2Config } from "@/types/proto/api/v1/idp_service";
|
||||||
|
|
@ -245,42 +247,48 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p>{t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")}</p>
|
<p>{t(isCreating ? "setting.sso-section.create-sso" : "setting.sso-section.update-sso")}</p>
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-start items-start w-80">
|
<div className="flex flex-col justify-start items-start w-80">
|
||||||
{isCreating && (
|
{isCreating && (
|
||||||
<>
|
<>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1!">{t("common.type")}</p>
|
||||||
{t("common.type")}
|
<Select value={String(type)} onValueChange={(value) => setType(parseInt(value) as unknown as IdentityProvider_Type)}>
|
||||||
</Typography>
|
<SelectTrigger className="w-full mb-4">
|
||||||
<Select className="w-full mb-4" value={type} onChange={(_, e) => setType(e ?? type)}>
|
<SelectValue />
|
||||||
{identityProviderTypes.map((kind) => (
|
</SelectTrigger>
|
||||||
<Option key={kind} value={kind}>
|
<SelectContent>
|
||||||
{kind}
|
{identityProviderTypes.map((kind) => (
|
||||||
</Option>
|
<SelectItem key={kind} value={String(kind)}>
|
||||||
))}
|
{IdentityProvider_Type[kind] || kind}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Typography className="mb-2" level="body-md">
|
<p className="mb-2 text-sm font-medium">{t("setting.sso-section.template")}</p>
|
||||||
{t("setting.sso-section.template")}
|
<Select value={selectedTemplate} onValueChange={(value) => setSelectedTemplate(value)}>
|
||||||
</Typography>
|
<SelectTrigger className="mb-1 h-auto w-full">
|
||||||
<Select className="mb-1 h-auto w-full" value={selectedTemplate} onChange={(_, e) => setSelectedTemplate(e ?? selectedTemplate)}>
|
<SelectValue />
|
||||||
{templateList.map((template) => (
|
</SelectTrigger>
|
||||||
<Option key={template.title} value={template.title}>
|
<SelectContent>
|
||||||
{template.title}
|
{templateList.map((template) => (
|
||||||
</Option>
|
<SelectItem key={template.title} value={template.title}>
|
||||||
))}
|
{template.title}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Divider className="my-2!" />
|
<Separator className="my-2" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("common.name")}
|
{t("common.name")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("common.name")}
|
placeholder={t("common.name")}
|
||||||
value={basicInfo.title}
|
value={basicInfo.title}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
@ -289,13 +297,10 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||||
title: e.target.value,
|
title: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">{t("setting.sso-section.identifier-filter")}</p>
|
||||||
{t("setting.sso-section.identifier-filter")}
|
|
||||||
</Typography>
|
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.identifier-filter")}
|
placeholder={t("setting.sso-section.identifier-filter")}
|
||||||
value={basicInfo.identifierFilter}
|
value={basicInfo.identifierFilter}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
@ -304,9 +309,8 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||||
identifierFilter: e.target.value,
|
identifierFilter: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Divider className="my-2!" />
|
<Separator className="my-2" />
|
||||||
{type === "OAUTH2" && (
|
{type === "OAUTH2" && (
|
||||||
<>
|
<>
|
||||||
{isCreating && (
|
{isCreating && (
|
||||||
|
|
@ -314,129 +318,113 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||||
{t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")}
|
{t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.client-id")}
|
{t("setting.sso-section.client-id")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.client-id")}
|
placeholder={t("setting.sso-section.client-id")}
|
||||||
value={oauth2Config.clientId}
|
value={oauth2Config.clientId}
|
||||||
onChange={(e) => setPartialOAuth2Config({ clientId: e.target.value })}
|
onChange={(e) => setPartialOAuth2Config({ clientId: e.target.value })}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.client-secret")}
|
{t("setting.sso-section.client-secret")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.client-secret")}
|
placeholder={t("setting.sso-section.client-secret")}
|
||||||
value={oauth2Config.clientSecret}
|
value={oauth2Config.clientSecret}
|
||||||
onChange={(e) => setPartialOAuth2Config({ clientSecret: e.target.value })}
|
onChange={(e) => setPartialOAuth2Config({ clientSecret: e.target.value })}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.authorization-endpoint")}
|
{t("setting.sso-section.authorization-endpoint")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.authorization-endpoint")}
|
placeholder={t("setting.sso-section.authorization-endpoint")}
|
||||||
value={oauth2Config.authUrl}
|
value={oauth2Config.authUrl}
|
||||||
onChange={(e) => setPartialOAuth2Config({ authUrl: e.target.value })}
|
onChange={(e) => setPartialOAuth2Config({ authUrl: e.target.value })}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.token-endpoint")}
|
{t("setting.sso-section.token-endpoint")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.token-endpoint")}
|
placeholder={t("setting.sso-section.token-endpoint")}
|
||||||
value={oauth2Config.tokenUrl}
|
value={oauth2Config.tokenUrl}
|
||||||
onChange={(e) => setPartialOAuth2Config({ tokenUrl: e.target.value })}
|
onChange={(e) => setPartialOAuth2Config({ tokenUrl: e.target.value })}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.user-endpoint")}
|
{t("setting.sso-section.user-endpoint")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.user-endpoint")}
|
placeholder={t("setting.sso-section.user-endpoint")}
|
||||||
value={oauth2Config.userInfoUrl}
|
value={oauth2Config.userInfoUrl}
|
||||||
onChange={(e) => setPartialOAuth2Config({ userInfoUrl: e.target.value })}
|
onChange={(e) => setPartialOAuth2Config({ userInfoUrl: e.target.value })}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.scopes")}
|
{t("setting.sso-section.scopes")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.scopes")}
|
placeholder={t("setting.sso-section.scopes")}
|
||||||
value={oauth2Scopes}
|
value={oauth2Scopes}
|
||||||
onChange={(e) => setOAuth2Scopes(e.target.value)}
|
onChange={(e) => setOAuth2Scopes(e.target.value)}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Divider className="my-2!" />
|
<Separator className="my-2" />
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">
|
||||||
{t("setting.sso-section.identifier")}
|
{t("setting.sso-section.identifier")}
|
||||||
<span className="text-red-600">*</span>
|
<span className="text-red-600">*</span>
|
||||||
</Typography>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.identifier")}
|
placeholder={t("setting.sso-section.identifier")}
|
||||||
value={oauth2Config.fieldMapping!.identifier}
|
value={oauth2Config.fieldMapping!.identifier}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping })
|
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, identifier: e.target.value } as FieldMapping })
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">{t("setting.sso-section.display-name")}</p>
|
||||||
{t("setting.sso-section.display-name")}
|
|
||||||
</Typography>
|
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("setting.sso-section.display-name")}
|
placeholder={t("setting.sso-section.display-name")}
|
||||||
value={oauth2Config.fieldMapping!.displayName}
|
value={oauth2Config.fieldMapping!.displayName}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping })
|
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, displayName: e.target.value } as FieldMapping })
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">{t("common.email")}</p>
|
||||||
{t("common.email")}
|
|
||||||
</Typography>
|
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={t("common.email")}
|
placeholder={t("common.email")}
|
||||||
value={oauth2Config.fieldMapping!.email}
|
value={oauth2Config.fieldMapping!.email}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping })
|
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, email: e.target.value } as FieldMapping })
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<Typography className="mb-1!" level="body-md">
|
<p className="mb-1 text-sm font-medium">Avatar URL</p>
|
||||||
Avatar URL
|
|
||||||
</Typography>
|
|
||||||
<Input
|
<Input
|
||||||
className="mb-2"
|
className="mb-2 w-full"
|
||||||
placeholder={"Avatar URL"}
|
placeholder={"Avatar URL"}
|
||||||
value={oauth2Config.fieldMapping!.avatarUrl}
|
value={oauth2Config.fieldMapping!.avatarUrl}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping })
|
setPartialOAuth2Config({ fieldMapping: { ...oauth2Config.fieldMapping, avatarUrl: e.target.value } as FieldMapping })
|
||||||
}
|
}
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
|
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" onClick={handleConfirmBtnClick} disabled={!allowConfirmAction()}>
|
<Button onClick={handleConfirmBtnClick} disabled={!allowConfirmAction()}>
|
||||||
{t(isCreating ? "common.create" : "common.update")}
|
{t(isCreating ? "common.create" : "common.update")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { Input, Textarea, Button } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { shortcutServiceClient } from "@/grpcweb";
|
import { shortcutServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
|
|
@ -74,7 +76,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.shortcuts")}`}</p>
|
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.shortcuts")}`}</p>
|
||||||
<Button variant="plain" onClick={() => destroy()}>
|
<Button variant="ghost" onClick={() => destroy()}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -84,8 +86,8 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||||
<Input className="w-full" type="text" placeholder="" value={shortcut.title} onChange={onShortcutTitleChange} />
|
<Input className="w-full" type="text" placeholder="" value={shortcut.title} onChange={onShortcutTitleChange} />
|
||||||
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.filter")}</span>
|
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.filter")}</span>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
className="w-full"
|
||||||
rows={3}
|
rows={3}
|
||||||
fullWidth
|
|
||||||
placeholder={t("common.shortcut-filter")}
|
placeholder={t("common.shortcut-filter")}
|
||||||
value={shortcut.filter}
|
value={shortcut.filter}
|
||||||
onChange={onShortcutFilterChange}
|
onChange={onShortcutFilterChange}
|
||||||
|
|
@ -115,7 +117,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
|
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
|
||||||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
|
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
|
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Radio, RadioGroup } from "@mui/joy";
|
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { userServiceClient } from "@/grpcweb";
|
import { userServiceClient } from "@/grpcweb";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { User, User_Role } from "@/types/proto/api/v1/user_service";
|
import { User, User_Role } from "@/types/proto/api/v1/user_service";
|
||||||
|
|
@ -66,7 +68,7 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.user")}`}</p>
|
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.user")}`}</p>
|
||||||
<Button variant="plain" onClick={() => destroy()}>
|
<Button variant="ghost" onClick={() => destroy()}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,16 +101,22 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.role")}</span>
|
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.role")}</span>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
orientation="horizontal"
|
value={user.role}
|
||||||
defaultValue={user.role}
|
onValueChange={(value) => setPartialUser({ role: value as User_Role })}
|
||||||
onChange={(e) => setPartialUser({ role: e.target.value as User_Role })}
|
className="flex flex-row gap-4"
|
||||||
>
|
>
|
||||||
<Radio value={User_Role.USER} label={t("setting.member-section.user")} />
|
<div className="flex items-center space-x-2">
|
||||||
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} />
|
<RadioGroupItem value={User_Role.USER} id="user" />
|
||||||
|
<Label htmlFor="user">{t("setting.member-section.user")}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={User_Role.ADMIN} id="admin" />
|
||||||
|
<Label htmlFor="admin">{t("setting.member-section.admin")}</Label>
|
||||||
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
|
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
|
||||||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
|
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
|
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { webhookServiceClient } from "@/grpcweb";
|
import { webhookServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
|
|
@ -108,7 +109,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
|
||||||
<p className="title-text">
|
<p className="title-text">
|
||||||
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
|
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
|
||||||
</p>
|
</p>
|
||||||
<Button variant="plain" onClick={() => destroy()}>
|
<Button variant="ghost" onClick={() => destroy()}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -142,7 +143,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
|
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
|
||||||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
|
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
|
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const DATE_TIME_FORMAT = "M/D/YYYY, H:mm:ss";
|
const DATE_TIME_FORMAT = "M/D/YYYY, H:mm:ss";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { CssVarsProvider } from "@mui/joy";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import dialogStore from "@/store/v2/dialog";
|
import dialogStore from "@/store/v2/dialog";
|
||||||
import theme from "@/theme";
|
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
interface DialogConfig {
|
interface DialogConfig {
|
||||||
dialogName: string;
|
dialogName: string;
|
||||||
|
|
@ -90,11 +88,9 @@ export function generateDialog<T extends DialogProps>(
|
||||||
} as T;
|
} as T;
|
||||||
|
|
||||||
const Fragment = observer(() => (
|
const Fragment = observer(() => (
|
||||||
<CssVarsProvider theme={theme}>
|
<BaseDialog destroy={cbs.destroy} clickSpaceDestroy={true} {...config}>
|
||||||
<BaseDialog destroy={cbs.destroy} clickSpaceDestroy={true} {...config}>
|
<DialogComponent {...dialogProps} />
|
||||||
<DialogComponent {...dialogProps} />
|
</BaseDialog>
|
||||||
</BaseDialog>
|
|
||||||
</CssVarsProvider>
|
|
||||||
));
|
));
|
||||||
|
|
||||||
dialog.render(<Fragment />);
|
dialog.render(<Fragment />);
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import { matchPath, useLocation } from "react-router-dom";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { memoStore, userStore } from "@/store/v2";
|
import { memoStore, userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import MemoFilters from "../MemoFilters";
|
import MemoFilters from "../MemoFilters";
|
||||||
import StatisticsView from "../StatisticsView";
|
import StatisticsView from "../StatisticsView";
|
||||||
import ShortcutsSection from "./ShortcutsSection";
|
import ShortcutsSection from "./ShortcutsSection";
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Drawer } from "@mui/joy";
|
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { MenuIcon } from "lucide-react";
|
import { MenuIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
import HomeSidebar from "./HomeSidebar";
|
import HomeSidebar from "./HomeSidebar";
|
||||||
|
|
||||||
const HomeSidebarDrawer = () => {
|
const HomeSidebarDrawer = () => {
|
||||||
|
|
@ -13,25 +13,17 @@ const HomeSidebarDrawer = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
|
||||||
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(inOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
|
<SheetTrigger asChild>
|
||||||
<MenuIcon className="w-6 h-auto dark:text-gray-400" />
|
<Button variant="ghost" className="bg-transparent! px-2">
|
||||||
</Button>
|
<MenuIcon className="w-6 h-auto dark:text-gray-400" />
|
||||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
</Button>
|
||||||
<div className="w-full h-full bg-zinc-100 dark:bg-zinc-900">
|
</SheetTrigger>
|
||||||
<HomeSidebar className="px-4 py-4" />
|
<SheetContent side="right" className="w-full sm:w-80 bg-zinc-100 dark:bg-zinc-900">
|
||||||
</div>
|
<HomeSidebar className="px-4 py-4" />
|
||||||
</Drawer>
|
</SheetContent>
|
||||||
</>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
|
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { shortcutServiceClient } from "@/grpcweb";
|
import { shortcutServiceClient } from "@/grpcweb";
|
||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import memoFilterStore from "@/store/v2/memoFilter";
|
import memoFilterStore from "@/store/v2/memoFilter";
|
||||||
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showCreateShortcutDialog from "../CreateShortcutDialog";
|
import showCreateShortcutDialog from "../CreateShortcutDialog";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
|
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
|
||||||
|
|
||||||
|
|
@ -40,9 +40,16 @@ const ShortcutsSection = observer(() => {
|
||||||
<div className="w-full flex flex-col justify-start items-start mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
<div className="w-full flex flex-col justify-start items-start mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||||
<div className="flex flex-row justify-between items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none">
|
<div className="flex flex-row justify-between items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none">
|
||||||
<span>{t("common.shortcuts")}</span>
|
<span>{t("common.shortcuts")}</span>
|
||||||
<Tooltip title={t("common.create")} placement="top">
|
<TooltipProvider>
|
||||||
<PlusIcon className="w-4 h-auto" onClick={() => showCreateShortcutDialog({})} />
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger asChild>
|
||||||
|
<PlusIcon className="w-4 h-auto cursor-pointer" onClick={() => showCreateShortcutDialog({})} />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("common.create")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
|
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
|
||||||
{shortcuts.map((shortcut) => {
|
{shortcuts.map((shortcut) => {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { Switch } from "@usememos/mui";
|
|
||||||
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
|
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
|
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showRenameTagDialog from "../RenameTagDialog";
|
import showRenameTagDialog from "../RenameTagDialog";
|
||||||
import TagTree from "../TagTree";
|
import TagTree from "../TagTree";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -58,7 +58,7 @@ const TagsSection = observer((props: Props) => {
|
||||||
<PopoverContent align="end" alignOffset={-12}>
|
<PopoverContent align="end" alignOffset={-12}>
|
||||||
<div className="w-auto flex flex-row justify-between items-center gap-2 p-1">
|
<div className="w-auto flex flex-row justify-between items-center gap-2 p-1">
|
||||||
<span className="text-sm shrink-0 dark:text-zinc-400">{t("common.tree-mode")}</span>
|
<span className="text-sm shrink-0 dark:text-zinc-400">{t("common.tree-mode")}</span>
|
||||||
<Switch size="sm" checked={treeMode} onChange={(event) => setTreeMode(event.target.checked)} />
|
<Switch checked={treeMode} onCheckedChange={(checked) => setTreeMode(checked)} />
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react";
|
import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { activityServiceClient } from "@/grpcweb";
|
import { activityServiceClient } from "@/grpcweb";
|
||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { activityNamePrefix } from "@/store/common";
|
import { activityNamePrefix } from "@/store/common";
|
||||||
import { memoStore, userStore } from "@/store/v2";
|
import { memoStore, userStore } from "@/store/v2";
|
||||||
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
|
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { User } from "@/types/proto/api/v1/user_service";
|
import { User } from "@/types/proto/api/v1/user_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -79,9 +79,16 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
|
||||||
: "border-gray-500 text-gray-500 bg-gray-50 dark:bg-zinc-800",
|
: "border-gray-500 text-gray-500 bg-gray-50 dark:bg-zinc-800",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tooltip title={"Comment"} placement="bottom">
|
<TooltipProvider>
|
||||||
<MessageCircleIcon className="w-4 sm:w-5 h-auto" />
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger>
|
||||||
|
<MessageCircleIcon className="w-4 sm:w-5 h-auto" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Comment</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -95,12 +102,19 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
|
||||||
<span className="text-sm text-gray-500">{inbox.createTime?.toLocaleString()}</span>
|
<span className="text-sm text-gray-500">{inbox.createTime?.toLocaleString()}</span>
|
||||||
<div>
|
<div>
|
||||||
{inbox.status === Inbox_Status.UNREAD && (
|
{inbox.status === Inbox_Status.UNREAD && (
|
||||||
<Tooltip title={t("common.archive")} placement="top">
|
<TooltipProvider>
|
||||||
<InboxIcon
|
<Tooltip>
|
||||||
className="w-4 h-auto cursor-pointer text-gray-400 hover:text-blue-600"
|
<TooltipTrigger>
|
||||||
onClick={() => handleArchiveMessage()}
|
<InboxIcon
|
||||||
/>
|
className="w-4 h-auto cursor-pointer text-gray-400 hover:text-blue-600"
|
||||||
</Tooltip>
|
onClick={() => handleArchiveMessage()}
|
||||||
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("common.archive")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { ExternalLinkIcon } from "lucide-react";
|
import { ExternalLinkIcon } from "lucide-react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -13,11 +13,18 @@ const LearnMore: React.FC<Props> = (props: Props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={title ?? t("common.learn-more")} placement="top">
|
<TooltipProvider>
|
||||||
<a className={`text-gray-500 dark:text-gray-400 hover:text-blue-600 ${className}`} href={url} target="_blank">
|
<Tooltip>
|
||||||
<ExternalLinkIcon className="w-4 h-auto" />
|
<TooltipTrigger asChild>
|
||||||
</a>
|
<a className={`text-gray-500 dark:text-gray-400 hover:text-blue-600 ${className}`} href={url} target="_blank">
|
||||||
</Tooltip>
|
<ExternalLinkIcon className="w-4 h-auto" />
|
||||||
|
</a>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{title ?? t("common.learn-more")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Option, Select } from "@mui/joy";
|
|
||||||
import { GlobeIcon } from "lucide-react";
|
import { GlobeIcon } from "lucide-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { locales } from "@/i18n";
|
import { locales } from "@/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -17,32 +17,35 @@ const LocaleSelect: FC<Props> = (props: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select value={value} onValueChange={handleSelectChange}>
|
||||||
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
|
<SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
|
||||||
startDecorator={<GlobeIcon className="w-4 h-auto" />}
|
<div className="flex items-center gap-2">
|
||||||
value={value}
|
<GlobeIcon className="w-4 h-auto" />
|
||||||
onChange={(_, value) => handleSelectChange(value as Locale)}
|
<SelectValue placeholder="Select language" />
|
||||||
>
|
</div>
|
||||||
{locales.map((locale) => {
|
</SelectTrigger>
|
||||||
try {
|
<SelectContent>
|
||||||
const languageName = new Intl.DisplayNames([locale], { type: "language" }).of(locale);
|
{locales.map((locale) => {
|
||||||
if (languageName) {
|
try {
|
||||||
return (
|
const languageName = new Intl.DisplayNames([locale], { type: "language" }).of(locale);
|
||||||
<Option key={locale} value={locale}>
|
if (languageName) {
|
||||||
{languageName.charAt(0).toUpperCase() + languageName.slice(1)}
|
return (
|
||||||
</Option>
|
<SelectItem key={locale} value={locale}>
|
||||||
);
|
{languageName.charAt(0).toUpperCase() + languageName.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nth
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Option key={locale} value={locale}>
|
<SelectItem key={locale} value={locale}>
|
||||||
{locale}
|
{locale}
|
||||||
</Option>
|
</SelectItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
memoList: Memo[];
|
memoList: Memo[];
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ import toast from "react-hot-toast";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore, userStore } from "@/store/v2";
|
import { memoStore, userStore } from "@/store/v2";
|
||||||
import { State } from "@/types/proto/api/v1/common";
|
import { State } from "@/types/proto/api/v1/common";
|
||||||
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
memo: Memo;
|
memo: Memo;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
|
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
|
||||||
import MemoAttachment from "./MemoAttachment";
|
import MemoAttachment from "./MemoAttachment";
|
||||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import hljs from "highlight.js";
|
||||||
import { CopyIcon } from "lucide-react";
|
import { CopyIcon } from "lucide-react";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import MermaidBlock from "./MermaidBlock";
|
import MermaidBlock from "./MermaidBlock";
|
||||||
import { BaseProps } from "./types";
|
import { BaseProps } from "./types";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { observer } from "mobx-react-lite";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
|
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { attachmentStore } from "@/store/v2";
|
import { attachmentStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import Error from "./Error";
|
import Error from "./Error";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import toast from "react-hot-toast";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
|
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { extractMemoIdFromName } from "@/store/common";
|
import { extractMemoIdFromName } from "@/store/common";
|
||||||
import { memoStore } from "@/store/v2";
|
import { memoStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import MemoContent from "..";
|
import MemoContent from "..";
|
||||||
import { RendererContext } from "../types";
|
import { RendererContext } from "../types";
|
||||||
import Error from "./Error";
|
import Error from "./Error";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Divider } from "@mui/joy";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { BaseProps } from "./types";
|
import { BaseProps } from "./types";
|
||||||
|
|
||||||
interface Props extends BaseProps {
|
interface Props extends BaseProps {
|
||||||
|
|
@ -6,7 +6,7 @@ interface Props extends BaseProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const HorizontalRule: React.FC<Props> = () => {
|
const HorizontalRule: React.FC<Props> = () => {
|
||||||
return <Divider className="my-3!" />;
|
return <Separator className="my-3!" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HorizontalRule;
|
export default HorizontalRule;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Link as MLink, Tooltip } from "@mui/joy";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { LinkMetadata, Node } from "@/types/proto/api/v1/markdown_service";
|
import { LinkMetadata, Node } from "@/types/proto/api/v1/markdown_service";
|
||||||
|
|
@ -43,33 +43,38 @@ const Link: React.FC<Props> = ({ content, url }: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<TooltipProvider>
|
||||||
variant="outlined"
|
<Tooltip open={showTooltip}>
|
||||||
title={
|
<TooltipTrigger asChild>
|
||||||
linkMetadata && (
|
<a
|
||||||
<div className="w-full max-w-64 sm:max-w-96 p-1 flex flex-col">
|
className="underline text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
<div className="w-full flex flex-row justify-start items-center gap-1">
|
target="_blank"
|
||||||
<img className="w-5 h-5 rounded" src={getFaviconWithGoogleS2(url)} alt={linkMetadata?.title} />
|
href={url}
|
||||||
<h3 className="text-base truncate dark:opacity-90">{linkMetadata?.title}</h3>
|
rel="noopener noreferrer"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={() => setShowTooltip(false)}
|
||||||
|
>
|
||||||
|
{content ? content.map((child, index) => <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />) : url}
|
||||||
|
</a>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{linkMetadata && (
|
||||||
|
<TooltipContent className="w-full max-w-64 sm:max-w-96 p-1">
|
||||||
|
<div className="w-full flex flex-col">
|
||||||
|
<div className="w-full flex flex-row justify-start items-center gap-1">
|
||||||
|
<img className="w-5 h-5 rounded" src={getFaviconWithGoogleS2(url)} alt={linkMetadata?.title} />
|
||||||
|
<h3 className="text-base truncate dark:opacity-90">{linkMetadata?.title}</h3>
|
||||||
|
</div>
|
||||||
|
{linkMetadata.description && (
|
||||||
|
<p className="mt-1 w-full text-sm leading-snug opacity-80 line-clamp-3">{linkMetadata.description}</p>
|
||||||
|
)}
|
||||||
|
{linkMetadata.image && (
|
||||||
|
<img className="mt-1 w-full h-32 object-cover rounded" src={linkMetadata.image} alt={linkMetadata.title} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{linkMetadata.description && (
|
</TooltipContent>
|
||||||
<p className="mt-1 w-full text-sm leading-snug opacity-80 line-clamp-3">{linkMetadata.description}</p>
|
)}
|
||||||
)}
|
</Tooltip>
|
||||||
{linkMetadata.image && (
|
</TooltipProvider>
|
||||||
<img className="mt-1 w-full h-32 object-cover rounded" src={linkMetadata.image} alt={linkMetadata.title} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
open={showTooltip}
|
|
||||||
arrow
|
|
||||||
>
|
|
||||||
<MLink underline="always" target="_blank" href={url} rel="noopener noreferrer">
|
|
||||||
<span onMouseEnter={handleMouseEnter} onMouseLeave={() => setShowTooltip(false)}>
|
|
||||||
{content ? content.map((child, index) => <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />) : url}
|
|
||||||
</span>
|
|
||||||
</MLink>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { head } from "lodash-es";
|
import { head } from "lodash-es";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service";
|
import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import Renderer from "./Renderer";
|
import Renderer from "./Renderer";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import TeX from "@matejmazur/react-katex";
|
import TeX from "@matejmazur/react-katex";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,32 @@
|
||||||
import { useColorScheme } from "@mui/joy";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MermaidBlock: React.FC<Props> = ({ content }: Props) => {
|
const MermaidBlock: React.FC<Props> = ({ content }: Props) => {
|
||||||
const { mode: colorMode } = useColorScheme();
|
const [colorMode, setColorMode] = useState<"light" | "dark">("light");
|
||||||
const mermaidDockBlock = useRef<null>(null);
|
const mermaidDockBlock = useRef<null>(null);
|
||||||
|
|
||||||
|
// Simple dark mode detection
|
||||||
|
useEffect(() => {
|
||||||
|
const updateMode = () => {
|
||||||
|
const isDark = document.documentElement.classList.contains("dark");
|
||||||
|
setColorMode(isDark ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMode();
|
||||||
|
|
||||||
|
// Watch for changes to the dark class
|
||||||
|
const observer = new MutationObserver(updateMode);
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class"],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Dynamically import mermaid to ensure compatibility with Vite
|
// Dynamically import mermaid to ensure compatibility with Vite
|
||||||
const initializeMermaid = async () => {
|
const initializeMermaid = async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import { observer } from "mobx-react-lite";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { memoFilterStore } from "@/store/v2";
|
import { memoFilterStore } from "@/store/v2";
|
||||||
import { stringifyFilters, MemoFilter } from "@/store/v2/memoFilter";
|
import { stringifyFilters, MemoFilter } from "@/store/v2/memoFilter";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { RendererContext } from "./types";
|
import { RendererContext } from "./types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Checkbox } from "@usememos/mui";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore } from "@/store/v2";
|
import { memoStore } from "@/store/v2";
|
||||||
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
|
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import Renderer from "./Renderer";
|
import Renderer from "./Renderer";
|
||||||
import { RendererContext } from "./types";
|
import { RendererContext } from "./types";
|
||||||
|
|
||||||
|
|
@ -39,7 +39,12 @@ const TaskListItem = observer(({ node, complete, children }: Props) => {
|
||||||
return (
|
return (
|
||||||
<li className={cn("w-full grid grid-cols-[24px_1fr]")}>
|
<li className={cn("w-full grid grid-cols-[24px_1fr]")}>
|
||||||
<span className="w-6 h-6 flex justify-start items-center">
|
<span className="w-6 h-6 flex justify-start items-center">
|
||||||
<Checkbox size="sm" checked={complete} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
|
<Checkbox
|
||||||
|
className="h-4 w-4"
|
||||||
|
checked={complete}
|
||||||
|
disabled={context.readonly}
|
||||||
|
onCheckedChange={(checked) => handleCheckboxChange(checked === true)}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<p className={cn(complete && "line-through opacity-80")}>
|
<p className={cn(complete && "line-through opacity-80")}>
|
||||||
{children.map((child, index) => (
|
{children.map((child, index) => (
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { memo, useEffect, useRef, useState } from "react";
|
import { memo, useEffect, useRef, useState } from "react";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore } from "@/store/v2";
|
import { memoStore } from "@/store/v2";
|
||||||
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
|
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { isSuperUser } from "@/utils/user";
|
import { isSuperUser } from "@/utils/user";
|
||||||
import Renderer from "./Renderer";
|
import Renderer from "./Renderer";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react";
|
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Memo, MemoRelation_Type, Memo_Property } from "@/types/proto/api/v1/memo_service";
|
import { Memo, MemoRelation_Type, Memo_Property } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import MemoRelationForceGraph from "../MemoRelationForceGraph";
|
import MemoRelationForceGraph from "../MemoRelationForceGraph";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Drawer } from "@mui/joy";
|
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { GanttChartIcon } from "lucide-react";
|
import { GanttChartIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import MemoDetailSidebar from "./MemoDetailSidebar";
|
import MemoDetailSidebar from "./MemoDetailSidebar";
|
||||||
|
|
||||||
|
|
@ -19,24 +19,17 @@ const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
|
||||||
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setOpen(inOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
|
<SheetTrigger asChild>
|
||||||
<GanttChartIcon className="w-5 h-auto dark:text-gray-400" />
|
<Button variant="ghost" className="bg-transparent! px-2">
|
||||||
</Button>
|
<GanttChartIcon className="w-5 h-auto dark:text-gray-400" />
|
||||||
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
|
</Button>
|
||||||
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
|
</SheetTrigger>
|
||||||
<MemoDetailSidebar className="py-4" memo={memo} parentPage={parentPage} />
|
<SheetContent side="right" className="w-full sm:w-80 px-4 bg-zinc-100 dark:bg-zinc-900">
|
||||||
</div>
|
<MemoDetailSidebar className="py-4" memo={memo} parentPage={parentPage} />
|
||||||
</Drawer>
|
</SheetContent>
|
||||||
</>
|
</Sheet>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Option, Select } from "@mui/joy";
|
|
||||||
import { Settings2Icon } from "lucide-react";
|
import { Settings2Icon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { viewStore } from "@/store/v2";
|
import { viewStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -26,29 +26,39 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("memo.direction")}</span>
|
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("memo.direction")}</span>
|
||||||
<Select
|
<Select
|
||||||
value={viewStore.state.orderByTimeAsc}
|
value={viewStore.state.orderByTimeAsc.toString()}
|
||||||
onChange={(_, value) =>
|
onValueChange={(value) =>
|
||||||
viewStore.state.setPartial({
|
viewStore.state.setPartial({
|
||||||
orderByTimeAsc: Boolean(value),
|
orderByTimeAsc: value === "true",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Option value={false}>{t("memo.direction-desc")}</Option>
|
<SelectTrigger className="w-32">
|
||||||
<Option value={true}>{t("memo.direction-asc")}</Option>
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="false">{t("memo.direction-desc")}</SelectItem>
|
||||||
|
<SelectItem value="true">{t("memo.direction-asc")}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("common.layout")}</span>
|
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("common.layout")}</span>
|
||||||
<Select
|
<Select
|
||||||
value={viewStore.state.layout}
|
value={viewStore.state.layout}
|
||||||
onChange={(_, value) =>
|
onValueChange={(value) =>
|
||||||
viewStore.state.setPartial({
|
viewStore.state.setPartial({
|
||||||
layout: value as "LIST" | "MASONRY",
|
layout: value as "LIST" | "MASONRY",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Option value={"LIST"}>{t("memo.list")}</Option>
|
<SelectTrigger className="w-32">
|
||||||
<Option value={"MASONRY"}>{t("memo.masonry")}</Option>
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="LIST">{t("memo.list")}</SelectItem>
|
||||||
|
<SelectItem value="MASONRY">{t("memo.masonry")}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { Autocomplete, AutocompleteOption, Chip } from "@mui/joy";
|
|
||||||
import { Button, Checkbox } from "@usememos/mui";
|
|
||||||
import { uniqBy } from "lodash-es";
|
import { uniqBy } from "lodash-es";
|
||||||
import { LinkIcon } from "lucide-react";
|
import { LinkIcon, X } from "lucide-react";
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Command, CommandInput, CommandItem, CommandList, CommandEmpty } from "@/components/ui/command";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
|
@ -129,52 +131,69 @@ const AddMemoRelationPopover = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
||||||
<PopoverTrigger className="relative">
|
<PopoverTrigger asChild>
|
||||||
<Button className="flex items-center justify-center p-0" variant="plain" asChild>
|
<Button variant="ghost">
|
||||||
<LinkIcon className="w-5 h-5 mx-auto p-0" />
|
<LinkIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="center">
|
<PopoverContent align="center">
|
||||||
<div className="w-[16rem] p-1 flex flex-col justify-start items-start">
|
<div className="w-[16rem] p-1 flex flex-col justify-start items-start">
|
||||||
<Autocomplete
|
{/* Selected memos display */}
|
||||||
className="w-full"
|
{selectedMemos.length > 0 && (
|
||||||
size="md"
|
<div className="w-full mb-2 flex flex-wrap gap-1">
|
||||||
clearOnBlur
|
{selectedMemos.map((memo) => (
|
||||||
disableClearable
|
<Badge key={memo.name} variant="outline" className="max-w-full flex items-center gap-1 p-2">
|
||||||
placeholder={t("reference.search-placeholder")}
|
<div className="flex-1 min-w-0">
|
||||||
noOptionsText={t("reference.no-memos-found")}
|
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
|
||||||
options={filteredMemos}
|
<span className="text-sm leading-5 truncate block">{memo.content}</span>
|
||||||
loading={isFetching}
|
</div>
|
||||||
inputValue={searchText}
|
<X
|
||||||
value={selectedMemos}
|
className="w-3 h-3 cursor-pointer hover:text-red-500 flex-shrink-0"
|
||||||
multiple
|
onClick={() => setSelectedMemos((memos) => memos.filter((m) => m.name !== memo.name))}
|
||||||
onInputChange={(_, value) => setSearchText(value.trimStart())}
|
/>
|
||||||
getOptionKey={(memo) => memo.name}
|
</Badge>
|
||||||
getOptionLabel={(memo) => memo.content}
|
))}
|
||||||
isOptionEqualToValue={(memo, value) => memo.name === value.name}
|
</div>
|
||||||
renderOption={(props, memo) => (
|
)}
|
||||||
<AutocompleteOption {...props} key={memo.name}>
|
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
{/* Search and selection interface */}
|
||||||
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
|
<Command className="w-full">
|
||||||
<p className="mt-0.5 text-sm leading-5 line-clamp-2">{searchText ? getHighlightedContent(memo.content) : memo.snippet}</p>
|
<CommandInput
|
||||||
</div>
|
placeholder={t("reference.search-placeholder")}
|
||||||
</AutocompleteOption>
|
value={searchText}
|
||||||
)}
|
onValueChange={setSearchText}
|
||||||
renderTags={(memos) =>
|
className="h-9"
|
||||||
memos.map((memo) => (
|
/>
|
||||||
<Chip key={memo.name} className="max-w-full! rounded!" variant="outlined" color="neutral">
|
<CommandList className="max-h-[200px]">
|
||||||
|
<CommandEmpty>{isFetching ? "Loading..." : t("reference.no-memos-found")}</CommandEmpty>
|
||||||
|
{filteredMemos.map((memo) => (
|
||||||
|
<CommandItem
|
||||||
|
key={memo.name}
|
||||||
|
value={memo.name}
|
||||||
|
onSelect={() => {
|
||||||
|
setSelectedMemos((prev) => [...prev, memo]);
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
<div className="w-full flex flex-col justify-start items-start">
|
||||||
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
|
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
|
||||||
<span className="w-full text-sm leading-5 truncate">{memo.content}</span>
|
<p className="mt-0.5 text-sm leading-5 line-clamp-2">
|
||||||
|
{searchText ? getHighlightedContent(memo.content) : memo.snippet}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Chip>
|
</CommandItem>
|
||||||
))
|
))}
|
||||||
}
|
</CommandList>
|
||||||
onChange={(_, value) => setSelectedMemos(value)}
|
</Command>
|
||||||
/>
|
|
||||||
<div className="mt-2 w-full flex flex-row justify-end items-center gap-2">
|
<div className="mt-2 w-full flex flex-row justify-end items-center gap-2">
|
||||||
<Checkbox size="sm" label={"Embed"} checked={embedded} onChange={(e) => setEmbedded(e.target.checked)} />
|
<div className="flex items-center space-x-2">
|
||||||
<Button color="primary" onClick={addMemoRelations} disabled={selectedMemos.length === 0}>
|
<Checkbox id="embed-checkbox" checked={embedded} onCheckedChange={(checked) => setEmbedded(checked === true)} />
|
||||||
|
<label htmlFor="embed-checkbox" className="text-sm">
|
||||||
|
Embed
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Button onClick={addMemoRelations} disabled={selectedMemos.length === 0}>
|
||||||
{t("common.add")}
|
{t("common.add")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
import { MapPinIcon, XIcon } from "lucide-react";
|
import { MapPinIcon, XIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import LeafletMap from "@/components/LeafletMap";
|
import LeafletMap from "@/components/LeafletMap";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Location } from "@/types/proto/api/v1/memo_service";
|
import { Location } from "@/types/proto/api/v1/memo_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
|
|
@ -86,6 +87,7 @@ const LocationSelector = (props: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeLocation = (e: React.MouseEvent) => {
|
const removeLocation = (e: React.MouseEvent) => {
|
||||||
|
console.log("here");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
|
|
@ -94,35 +96,39 @@ const LocationSelector = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button className="flex items-center justify-center p-0" size="sm" variant="plain">
|
<Button variant="ghost" asChild>
|
||||||
<MapPinIcon className="w-5 h-5 mx-auto shrink-0" />
|
<div>
|
||||||
{props.location && (
|
<MapPinIcon className="w-5 h-5 mx-auto shrink-0" />
|
||||||
<>
|
{props.location && (
|
||||||
<span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">{props.location.placeholder}</span>
|
<>
|
||||||
<XIcon className="w-5 h-5 mx-auto shrink-0 hidden group-hover:block opacity-60 hover:opacity-80" onClick={removeLocation} />
|
<span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">
|
||||||
</>
|
{props.location.placeholder}
|
||||||
)}
|
</span>
|
||||||
|
<XIcon className="w-5 h-5 mx-auto shrink-0 opacity-60 hover:opacity-80" onClick={removeLocation} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="center">
|
<PopoverContent align="center">
|
||||||
<div className="min-w-80 sm:w-lg p-1 flex flex-col justify-start items-start">
|
<div className="min-w-80 sm:w-lg p-1 flex flex-col justify-start items-start">
|
||||||
<LeafletMap key={JSON.stringify(state.initilized)} latlng={state.position} onChange={onPositionChanged} />
|
<LeafletMap key={JSON.stringify(state.initilized)} latlng={state.position} onChange={onPositionChanged} />
|
||||||
<div className="mt-2 w-full flex flex-row justify-between items-center gap-2">
|
<div className="mt-2 w-full flex flex-row justify-between items-center gap-2">
|
||||||
<div className="flex flex-row items-center justify-start gap-2">
|
<div className="flex flex-row items-center justify-start gap-2 w-full">
|
||||||
<Input
|
<div className="relative flex-1">
|
||||||
placeholder="Choose a position first."
|
{state.position && (
|
||||||
value={state.placeholder}
|
<div className="absolute left-2 top-1/2 -translate-y-1/2 text-xs leading-6 opacity-60 z-10">
|
||||||
size="sm"
|
[{state.position.lat.toFixed(2)}, {state.position.lng.toFixed(2)}]
|
||||||
startDecorator={
|
</div>
|
||||||
state.position && (
|
)}
|
||||||
<div className="text-xs leading-6 opacity-60">
|
<Input
|
||||||
[{state.position.lat.toFixed(2)}, {state.position.lng.toFixed(2)}]
|
placeholder="Choose a position first."
|
||||||
</div>
|
value={state.placeholder}
|
||||||
)
|
disabled={!state.position}
|
||||||
}
|
className={state.position ? "pl-24" : ""}
|
||||||
disabled={!state.position}
|
onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))}
|
||||||
onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Link } from "@mui/joy";
|
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
|
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
|
||||||
import { EditorRefActions } from "../Editor";
|
import { EditorRefActions } from "../Editor";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -10,9 +9,8 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkdownMenu = (props: Props) => {
|
const MarkdownMenu = (props: Props) => {
|
||||||
const t = useTranslate();
|
|
||||||
|
|
||||||
const { editorRef } = props;
|
const { editorRef } = props;
|
||||||
|
const t = useTranslate();
|
||||||
|
|
||||||
const handleCodeBlockClick = () => {
|
const handleCodeBlockClick = () => {
|
||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
|
|
@ -64,8 +62,8 @@ const MarkdownMenu = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="plain" className="p-0">
|
<Button variant="ghost">
|
||||||
<SquareSlashIcon className="w-5 h-5" />
|
<SquareSlashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="start" className="text-sm p-1">
|
<PopoverContent align="start" className="text-sm p-1">
|
||||||
|
|
@ -85,9 +83,14 @@ const MarkdownMenu = (props: Props) => {
|
||||||
<span>{t("markdown.checkbox")}</span>
|
<span>{t("markdown.checkbox")}</span>
|
||||||
</button>
|
</button>
|
||||||
<div className="pl-2">
|
<div className="pl-2">
|
||||||
<Link fontSize={12} href="https://www.usememos.com/docs/getting-started/content-syntax" target="_blank">
|
<a
|
||||||
|
className="text-xs text-blue-600 hover:underline"
|
||||||
|
href="https://www.usememos.com/docs/getting-started/content-syntax"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{t("markdown.content-syntax")}
|
{t("markdown.content-syntax")}
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { HashIcon } from "lucide-react";
|
import { HashIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import OverflowTip from "@/components/kit/OverflowTip";
|
import OverflowTip from "@/components/kit/OverflowTip";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
|
||||||
import { EditorRefActions } from "../Editor";
|
import { EditorRefActions } from "../Editor";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -35,8 +35,8 @@ const TagSelector = observer((props: Props) => {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="plain" className="p-0">
|
<Button variant="ghost">
|
||||||
<HashIcon className="w-5 h-5" />
|
<HashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="start" sideOffset={2}>
|
<PopoverContent align="start" sideOffset={2}>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { LoaderIcon, PaperclipIcon } from "lucide-react";
|
import { LoaderIcon, PaperclipIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useContext, useRef, useState } from "react";
|
import { useContext, useRef, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { attachmentStore } from "@/store/v2";
|
import { attachmentStore } from "@/store/v2";
|
||||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||||
import { MemoEditorContext } from "../types";
|
import { MemoEditorContext } from "../types";
|
||||||
|
|
@ -73,7 +73,7 @@ const UploadAttachmentButton = observer((props: Props) => {
|
||||||
const isUploading = state.uploadingFlag || props.isUploading;
|
const isUploading = state.uploadingFlag || props.isUploading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className="relative p-0" variant="plain" disabled={isUploading}>
|
<Button className="relative" variant="ghost" disabled={isUploading}>
|
||||||
{isUploading ? <LoaderIcon className="w-5 h-5 animate-spin" /> : <PaperclipIcon className="w-5 h-5" />}
|
{isUploading ? <LoaderIcon className="w-5 h-5 animate-spin" /> : <PaperclipIcon className="w-5 h-5" />}
|
||||||
<input
|
<input
|
||||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { ChevronDownIcon } from "lucide-react";
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import VisibilityIcon from "@/components/VisibilityIcon";
|
import VisibilityIcon from "@/components/VisibilityIcon";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import getCaretCoordinates from "textarea-caret";
|
import getCaretCoordinates from "textarea-caret";
|
||||||
import OverflowTip from "@/components/kit/OverflowTip";
|
import OverflowTip from "@/components/kit/OverflowTip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { EditorRefActions } from ".";
|
import { EditorRefActions } from ".";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { last } from "lodash-es";
|
import { last } from "lodash-es";
|
||||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Node, NodeType, OrderedListItemNode, TaskListItemNode, UnorderedListItemNode } from "@/types/proto/api/v1/markdown_service";
|
import { Node, NodeType, OrderedListItemNode, TaskListItemNode, UnorderedListItemNode } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import TagSuggestions from "./TagSuggestions";
|
import TagSuggestions from "./TagSuggestions";
|
||||||
|
|
||||||
export interface EditorRefActions {
|
export interface EditorRefActions {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { LoaderIcon, SendIcon } from "lucide-react";
|
import { LoaderIcon, SendIcon } from "lucide-react";
|
||||||
|
|
@ -7,17 +6,18 @@ import React, { useEffect, useMemo, 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 useLocalStorage from "react-use/lib/useLocalStorage";
|
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import { TAB_SPACE_WIDTH } from "@/helpers/consts";
|
import { TAB_SPACE_WIDTH } from "@/helpers/consts";
|
||||||
import { isValidUrl } from "@/helpers/utils";
|
import { isValidUrl } from "@/helpers/utils";
|
||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { extractMemoIdFromName } from "@/store/common";
|
import { extractMemoIdFromName } from "@/store/common";
|
||||||
import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store/v2";
|
import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store/v2";
|
||||||
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
import { Attachment } from "@/types/proto/api/v1/attachment_service";
|
||||||
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { convertVisibilityFromString } from "@/utils/memo";
|
import { convertVisibilityFromString } from "@/utils/memo";
|
||||||
import DateTimeInput from "../DateTimeInput";
|
import DateTimeInput from "../DateTimeInput";
|
||||||
|
|
@ -503,7 +503,7 @@ const MemoEditor = observer((props: Props) => {
|
||||||
<AttachmentListView attachmentList={state.attachmentList} setAttachmentList={handleSetAttachmentList} />
|
<AttachmentListView attachmentList={state.attachmentList} setAttachmentList={handleSetAttachmentList} />
|
||||||
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
|
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
|
||||||
<div className="relative w-full flex flex-row justify-between items-center py-1" onFocus={(e) => e.stopPropagation()}>
|
<div className="relative w-full flex flex-row justify-between items-center py-1" onFocus={(e) => e.stopPropagation()}>
|
||||||
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 space-x-2">
|
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 -space-x-2">
|
||||||
<TagSelector editorRef={editorRef} />
|
<TagSelector editorRef={editorRef} />
|
||||||
<MarkdownMenu editorRef={editorRef} />
|
<MarkdownMenu editorRef={editorRef} />
|
||||||
<UploadAttachmentButton isUploading={state.isUploadingAttachment} />
|
<UploadAttachmentButton isUploading={state.isUploadingAttachment} />
|
||||||
|
|
@ -520,7 +520,7 @@ const MemoEditor = observer((props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 -mr-1 flex flex-row justify-end items-center">
|
<div className="shrink-0 -mr-1 flex flex-row justify-end items-center">
|
||||||
{props.onCancel && (
|
{props.onCancel && (
|
||||||
<Button variant="plain" className="opacity-60" disabled={state.isRequesting} onClick={handleCancelBtnClick}>
|
<Button variant="ghost" className="opacity-60" disabled={state.isRequesting} onClick={handleCancelBtnClick}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { MapPinIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Location } from "@/types/proto/api/v1/memo_service";
|
import { Location } from "@/types/proto/api/v1/memo_service";
|
||||||
import LeafletMap from "./LeafletMap";
|
import LeafletMap from "./LeafletMap";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
location: Location;
|
location: Location;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useColorScheme } from "@mui/joy";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
|
import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { extractMemoIdFromName } from "@/store/common";
|
import { extractMemoIdFromName } from "@/store/common";
|
||||||
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
|
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { LinkType, NodeType } from "./types";
|
import { LinkType, NodeType } from "./types";
|
||||||
import { convertMemoRelationsToGraphData } from "./utils";
|
import { convertMemoRelationsToGraphData } from "./utils";
|
||||||
|
|
||||||
|
|
@ -19,11 +18,30 @@ const DEFAULT_NODE_COLOR = "#a1a1aa";
|
||||||
|
|
||||||
const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
|
const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const { mode } = useColorScheme();
|
const [mode, setMode] = useState<"light" | "dark">("light");
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined);
|
const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined);
|
||||||
const [graphSize, setGraphSize] = useState({ width: 0, height: 0 });
|
const [graphSize, setGraphSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
// Simple dark mode detection
|
||||||
|
useEffect(() => {
|
||||||
|
const updateMode = () => {
|
||||||
|
const isDark = document.documentElement.classList.contains("dark");
|
||||||
|
setMode(isDark ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMode();
|
||||||
|
|
||||||
|
// Watch for changes to the dark class
|
||||||
|
const observer = new MutationObserver(updateMode);
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class"],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
setGraphSize(containerRef.current.getBoundingClientRect());
|
setGraphSize(containerRef.current.getBoundingClientRect());
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { LinkIcon, MilestoneIcon } from "lucide-react";
|
import { LinkIcon, MilestoneIcon } from "lucide-react";
|
||||||
import { memo, useState } from "react";
|
import { memo, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { extractMemoIdFromName } from "@/store/common";
|
import { extractMemoIdFromName } from "@/store/common";
|
||||||
import { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service";
|
import { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
|
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { memo, useCallback, useState } from "react";
|
import { memo, useCallback, useState } from "react";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore, userStore, workspaceStore } from "@/store/v2";
|
import { memoStore, userStore, workspaceStore } from "@/store/v2";
|
||||||
import { State } from "@/types/proto/api/v1/common";
|
import { State } from "@/types/proto/api/v1/common";
|
||||||
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { convertVisibilityToString } from "@/utils/memo";
|
import { convertVisibilityToString } from "@/utils/memo";
|
||||||
import { isSuperUser } from "@/utils/user";
|
import { isSuperUser } from "@/utils/user";
|
||||||
|
|
@ -170,10 +170,13 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
|
||||||
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
|
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
|
||||||
<div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2">
|
<div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2">
|
||||||
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
|
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
|
||||||
<Tooltip title={t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)} placement="top">
|
<Tooltip>
|
||||||
<span className="flex justify-center items-center hover:opacity-70">
|
<TooltipTrigger>
|
||||||
<VisibilityIcon visibility={memo.visibility} />
|
<span className="flex justify-center items-center hover:opacity-70">
|
||||||
</span>
|
<VisibilityIcon visibility={memo.visibility} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
|
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
|
||||||
|
|
@ -195,11 +198,18 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.showPinned && memo.pinned && (
|
{props.showPinned && memo.pinned && (
|
||||||
<Tooltip title={t("common.unpin")} placement="top">
|
<TooltipProvider>
|
||||||
<span className="cursor-pointer">
|
<Tooltip>
|
||||||
<BookmarkIcon className="w-4 h-auto text-amber-500" onClick={onPinIconClick} />
|
<TooltipTrigger asChild>
|
||||||
</span>
|
<span className="cursor-pointer">
|
||||||
</Tooltip>
|
<BookmarkIcon className="w-4 h-auto text-amber-500" onClick={onPinIconClick} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("common.unpin")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
{nsfw && showNSFWContent && (
|
{nsfw && showNSFWContent && (
|
||||||
<span className="cursor-pointer">
|
<span className="cursor-pointer">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import useWindowScroll from "react-use/lib/useWindowScroll";
|
import useWindowScroll from "react-use/lib/useWindowScroll";
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import NavigationDrawer from "./NavigationDrawer";
|
import NavigationDrawer from "./NavigationDrawer";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
|
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import BrandBanner from "./BrandBanner";
|
import BrandBanner from "./BrandBanner";
|
||||||
import UserBanner from "./UserBanner";
|
import UserBanner from "./UserBanner";
|
||||||
|
|
@ -89,9 +89,16 @@ const Navigation = observer((props: Props) => {
|
||||||
viewTransition
|
viewTransition
|
||||||
>
|
>
|
||||||
{props.collapsed ? (
|
{props.collapsed ? (
|
||||||
<Tooltip title={navLink.title} placement="right" arrow>
|
<TooltipProvider>
|
||||||
<div>{navLink.icon}</div>
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger asChild>
|
||||||
|
<div>{navLink.icon}</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
<p>{navLink.title}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
) : (
|
) : (
|
||||||
navLink.icon
|
navLink.icon
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Drawer } from "@mui/joy";
|
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import Navigation from "./Navigation";
|
import Navigation from "./Navigation";
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
|
|
@ -18,28 +18,20 @@ const NavigationDrawer = observer(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
|
||||||
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(inOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
<Button variant="plain" className="px-2" onClick={toggleDrawer(true)}>
|
<SheetTrigger asChild>
|
||||||
<UserAvatar className="shrink-0 w-6 h-6 rounded-md" avatarUrl={avatarUrl} />
|
<Button variant="ghost" className="px-2">
|
||||||
<span className="font-bold text-lg leading-10 ml-2 text-ellipsis shrink-0 cursor-pointer overflow-hidden text-gray-700 dark:text-gray-300">
|
<UserAvatar className="shrink-0 w-6 h-6 rounded-md" avatarUrl={avatarUrl} />
|
||||||
{title}
|
<span className="font-bold text-lg leading-10 ml-2 text-ellipsis shrink-0 cursor-pointer overflow-hidden text-gray-700 dark:text-gray-300">
|
||||||
</span>
|
{title}
|
||||||
</Button>
|
</span>
|
||||||
<Drawer anchor="left" size="sm" open={open} onClose={toggleDrawer(false)}>
|
</Button>
|
||||||
<div className="w-full h-full overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
|
</SheetTrigger>
|
||||||
<Navigation />
|
<SheetContent side="left" className="w-full sm:w-80 overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
|
||||||
</div>
|
<Navigation />
|
||||||
</Drawer>
|
</SheetContent>
|
||||||
</>
|
</Sheet>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { ArrowUpIcon, LoaderIcon } from "lucide-react";
|
import { ArrowUpIcon, LoaderIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { matchPath } from "react-router-dom";
|
import { matchPath } from "react-router-dom";
|
||||||
import PullToRefresh from "react-simple-pull-to-refresh";
|
import PullToRefresh from "react-simple-pull-to-refresh";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
|
|
@ -220,7 +220,7 @@ const BackToTop = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="plain" onClick={scrollToTop}>
|
<Button variant="ghost" onClick={scrollToTop}>
|
||||||
{t("router.back-to-top")}
|
{t("router.back-to-top")}
|
||||||
<ArrowUpIcon className="ml-1 w-4 h-auto" />
|
<ArrowUpIcon className="ml-1 w-4 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { LoaderIcon } from "lucide-react";
|
import { LoaderIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ClientError } from "nice-grpc-web";
|
import { ClientError } from "nice-grpc-web";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { authServiceClient } from "@/grpcweb";
|
import { authServiceClient } from "@/grpcweb";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
|
@ -62,8 +63,7 @@ const PasswordSignInForm = observer(() => {
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
<div className="w-full flex flex-col justify-start items-start">
|
||||||
<span className="leading-8 text-gray-600">{t("common.username")}</span>
|
<span className="leading-8 text-gray-600">{t("common.username")}</span>
|
||||||
<Input
|
<Input
|
||||||
className="w-full bg-white dark:bg-black"
|
className="w-full bg-white dark:bg-black h-10"
|
||||||
size="lg"
|
|
||||||
type="text"
|
type="text"
|
||||||
readOnly={actionBtnLoadingState.isLoading}
|
readOnly={actionBtnLoadingState.isLoading}
|
||||||
placeholder={t("common.username")}
|
placeholder={t("common.username")}
|
||||||
|
|
@ -78,8 +78,7 @@ const PasswordSignInForm = observer(() => {
|
||||||
<div className="w-full flex flex-col justify-start items-start">
|
<div className="w-full flex flex-col justify-start items-start">
|
||||||
<span className="leading-8 text-gray-600">{t("common.password")}</span>
|
<span className="leading-8 text-gray-600">{t("common.password")}</span>
|
||||||
<Input
|
<Input
|
||||||
className="w-full bg-white dark:bg-black"
|
className="w-full bg-white dark:bg-black h-10"
|
||||||
size="lg"
|
|
||||||
type="password"
|
type="password"
|
||||||
readOnly={actionBtnLoadingState.isLoading}
|
readOnly={actionBtnLoadingState.isLoading}
|
||||||
placeholder={t("common.password")}
|
placeholder={t("common.password")}
|
||||||
|
|
@ -93,14 +92,7 @@ const PasswordSignInForm = observer(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-end items-center w-full mt-6">
|
<div className="flex flex-row justify-end items-center w-full mt-6">
|
||||||
<Button
|
<Button type="submit" className="w-full h-10" disabled={actionBtnLoadingState.isLoading} onClick={handleSignInButtonClick}>
|
||||||
type="submit"
|
|
||||||
color="primary"
|
|
||||||
size="lg"
|
|
||||||
fullWidth
|
|
||||||
disabled={actionBtnLoadingState.isLoading}
|
|
||||||
onClick={handleSignInButtonClick}
|
|
||||||
>
|
|
||||||
{t("common.sign-in")}
|
{t("common.sign-in")}
|
||||||
{actionBtnLoadingState.isLoading && <LoaderIcon className="w-5 h-auto ml-2 animate-spin opacity-60" />}
|
{actionBtnLoadingState.isLoading && <LoaderIcon className="w-5 h-auto ml-2 animate-spin opacity-60" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
|
|
||||||
const MIN_SCALE = 0.5;
|
const MIN_SCALE = 0.5;
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import { useRef, useState } from "react";
|
||||||
import useClickAway from "react-use/lib/useClickAway";
|
import useClickAway from "react-use/lib/useClickAway";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore, workspaceStore } from "@/store/v2";
|
import { memoStore, workspaceStore } from "@/store/v2";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
memo: Memo;
|
memo: Memo;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoStore } from "@/store/v2";
|
import { memoStore } from "@/store/v2";
|
||||||
import { State } from "@/types/proto/api/v1/common";
|
import { State } from "@/types/proto/api/v1/common";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import { User } from "@/types/proto/api/v1/user_service";
|
import { User } from "@/types/proto/api/v1/user_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
memo: Memo;
|
memo: Memo;
|
||||||
|
|
@ -65,20 +65,27 @@ const ReactionView = observer((props: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={stringifyUsers(users, reactionType)} placement="top">
|
<TooltipProvider>
|
||||||
<div
|
<Tooltip>
|
||||||
className={cn(
|
<TooltipTrigger asChild>
|
||||||
"h-7 border border-zinc-200 px-2 py-0.5 rounded-full flex flex-row justify-center items-center gap-1 dark:border-zinc-700",
|
<div
|
||||||
"text-sm text-gray-600 dark:text-gray-400",
|
className={cn(
|
||||||
currentUser && !readonly && "cursor-pointer",
|
"h-7 border border-zinc-200 px-2 py-0.5 rounded-full flex flex-row justify-center items-center gap-1 dark:border-zinc-700",
|
||||||
hasReaction && "bg-blue-100 border-blue-200 dark:bg-zinc-900",
|
"text-sm text-gray-600 dark:text-gray-400",
|
||||||
)}
|
currentUser && !readonly && "cursor-pointer",
|
||||||
onClick={handleReactionClick}
|
hasReaction && "bg-blue-100 border-blue-200 dark:bg-zinc-900",
|
||||||
>
|
)}
|
||||||
<span>{reactionType}</span>
|
onClick={handleReactionClick}
|
||||||
<span className="opacity-60">{users.length}</span>
|
>
|
||||||
</div>
|
<span>{reactionType}</span>
|
||||||
</Tooltip>
|
<span className="opacity-60">{users.length}</span>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{stringifyUsers(users, reactionType)}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { List, ListItem } from "@mui/joy";
|
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
|
|
@ -50,7 +50,7 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p className="title-text">{t("tag.rename-tag")}</p>
|
<p className="title-text">{t("tag.rename-tag")}</p>
|
||||||
<Button variant="plain" onClick={() => destroy()}>
|
<Button variant="ghost" onClick={() => destroy()}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -64,17 +64,17 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
||||||
<span className="w-20 text-sm whitespace-nowrap shrink-0 text-right">{t("tag.new-name")}</span>
|
<span className="w-20 text-sm whitespace-nowrap shrink-0 text-right">{t("tag.new-name")}</span>
|
||||||
<Input className="w-full" type="text" placeholder="A new tag name" value={newName} onChange={handleTagNameInputChange} />
|
<Input className="w-full" type="text" placeholder="A new tag name" value={newName} onChange={handleTagNameInputChange} />
|
||||||
</div>
|
</div>
|
||||||
<List size="sm" marker="disc">
|
<ul className="list-disc list-inside text-sm ml-4">
|
||||||
<ListItem>
|
<li>
|
||||||
<p className="leading-5">{t("tag.rename-tip")}</p>
|
<p className="leading-5">{t("tag.rename-tip")}</p>
|
||||||
</ListItem>
|
</li>
|
||||||
</List>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-end items-center space-x-2">
|
<div className="w-full flex flex-row justify-end items-center space-x-2">
|
||||||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
|
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
|
<Button disabled={requestState.isLoading} onClick={handleConfirm}>
|
||||||
{t("common.confirm")}
|
{t("common.confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { SearchIcon } from "lucide-react";
|
import { SearchIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { memoFilterStore } from "@/store/v2";
|
import { memoFilterStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
|
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { ClipboardIcon, TrashIcon } from "lucide-react";
|
import { ClipboardIcon, TrashIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { userServiceClient } from "@/grpcweb";
|
import { userServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { UserAccessToken } from "@/types/proto/api/v1/user_service";
|
import { UserAccessToken } from "@/types/proto/api/v1/user_service";
|
||||||
|
|
@ -99,7 +99,7 @@ const AccessTokenSection = () => {
|
||||||
<tr key={userAccessToken.accessToken}>
|
<tr key={userAccessToken.accessToken}>
|
||||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
|
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
|
||||||
<span className="font-mono">{getFormatedAccessToken(userAccessToken.accessToken)}</span>
|
<span className="font-mono">{getFormatedAccessToken(userAccessToken.accessToken)}</span>
|
||||||
<Button variant="plain" onClick={() => copyAccessToken(userAccessToken.accessToken)}>
|
<Button variant="ghost" onClick={() => copyAccessToken(userAccessToken.accessToken)}>
|
||||||
<ClipboardIcon className="w-4 h-auto text-gray-400" />
|
<ClipboardIcon className="w-4 h-auto text-gray-400" />
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -114,7 +114,7 @@ const AccessTokenSection = () => {
|
||||||
</td>
|
</td>
|
||||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
|
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteAccessToken(userAccessToken);
|
handleDeleteAccessToken(userAccessToken);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { Radio, RadioGroup } from "@mui/joy";
|
|
||||||
import { Button, Input } from "@usememos/mui";
|
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { MoreVerticalIcon } from "lucide-react";
|
import { MoreVerticalIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { userServiceClient } from "@/grpcweb";
|
import { userServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
|
|
@ -12,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common";
|
||||||
import { User, User_Role } from "@/types/proto/api/v1/user_service";
|
import { User, User_Role } from "@/types/proto/api/v1/user_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showCreateUserDialog from "../CreateUserDialog";
|
import showCreateUserDialog from "../CreateUserDialog";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
interface LocalState {
|
interface LocalState {
|
||||||
creatingUser: User;
|
creatingUser: User;
|
||||||
|
|
@ -167,15 +169,23 @@ const MemberSection = observer(() => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-start items-start gap-1">
|
<div className="flex flex-col justify-start items-start gap-1">
|
||||||
<span>{t("common.role")}</span>
|
<span>{t("common.role")}</span>
|
||||||
<RadioGroup orientation="horizontal" defaultValue={User_Role.USER} onChange={handleUserRoleInputChange}>
|
<RadioGroup
|
||||||
<Radio value={User_Role.USER} label={t("setting.member-section.user")} />
|
defaultValue={User_Role.USER}
|
||||||
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} />
|
onValueChange={(value) => handleUserRoleInputChange({ target: { value } } as React.ChangeEvent<HTMLInputElement>)}
|
||||||
|
className="flex flex-row gap-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={User_Role.USER} id="user-role" />
|
||||||
|
<Label htmlFor="user-role">{t("setting.member-section.user")}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={User_Role.ADMIN} id="admin-role" />
|
||||||
|
<Label htmlFor="admin-role">{t("setting.member-section.admin")}</Label>
|
||||||
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Button color="primary" onClick={handleCreateUserBtnClick}>
|
<Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
|
||||||
{t("common.create")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center mt-6">
|
<div className="w-full flex flex-row justify-between items-center mt-6">
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { Chip, ChipDelete } from "@mui/joy";
|
|
||||||
import { Button, Input, Switch } from "@usememos/mui";
|
|
||||||
import { isEqual, uniq } from "lodash-es";
|
import { isEqual, uniq } from "lodash-es";
|
||||||
import { CheckIcon } from "lucide-react";
|
import { CheckIcon, X } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { workspaceSettingNamePrefix } from "@/store/common";
|
import { workspaceSettingNamePrefix } from "@/store/common";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
||||||
|
|
@ -70,42 +72,42 @@ const MemoRelatedSettings = observer(() => {
|
||||||
<span>{t("setting.system-section.disable-public-memos")}</span>
|
<span>{t("setting.system-section.disable-public-memos")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.disallowPublicVisibility}
|
checked={memoRelatedSetting.disallowPublicVisibility}
|
||||||
onChange={(event) => updatePartialSetting({ disallowPublicVisibility: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disallowPublicVisibility: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.system-section.display-with-updated-time")}</span>
|
<span>{t("setting.system-section.display-with-updated-time")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.displayWithUpdateTime}
|
checked={memoRelatedSetting.displayWithUpdateTime}
|
||||||
onChange={(event) => updatePartialSetting({ displayWithUpdateTime: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ displayWithUpdateTime: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.memo-related-settings.enable-link-preview")}</span>
|
<span>{t("setting.memo-related-settings.enable-link-preview")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.enableLinkPreview}
|
checked={memoRelatedSetting.enableLinkPreview}
|
||||||
onChange={(event) => updatePartialSetting({ enableLinkPreview: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ enableLinkPreview: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.memo-related-settings.enable-memo-comments")}</span>
|
<span>{t("setting.memo-related-settings.enable-memo-comments")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.enableComment}
|
checked={memoRelatedSetting.enableComment}
|
||||||
onChange={(event) => updatePartialSetting({ enableComment: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ enableComment: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.system-section.enable-double-click-to-edit")}</span>
|
<span>{t("setting.system-section.enable-double-click-to-edit")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.enableDoubleClickEdit}
|
checked={memoRelatedSetting.enableDoubleClickEdit}
|
||||||
onChange={(event) => updatePartialSetting({ enableDoubleClickEdit: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ enableDoubleClickEdit: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.system-section.disable-markdown-shortcuts-in-editor")}</span>
|
<span>{t("setting.system-section.disable-markdown-shortcuts-in-editor")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.disableMarkdownShortcuts}
|
checked={memoRelatedSetting.disableMarkdownShortcuts}
|
||||||
onChange={(event) => updatePartialSetting({ disableMarkdownShortcuts: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disableMarkdownShortcuts: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
|
|
@ -122,33 +124,27 @@ const MemoRelatedSettings = observer(() => {
|
||||||
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
|
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
|
||||||
{memoRelatedSetting.reactions.map((reactionType) => {
|
{memoRelatedSetting.reactions.map((reactionType) => {
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Badge key={reactionType} variant="outline" className="h-8 flex items-center gap-1">
|
||||||
className="h-8!"
|
|
||||||
key={reactionType}
|
|
||||||
variant="outlined"
|
|
||||||
size="lg"
|
|
||||||
endDecorator={
|
|
||||||
<ChipDelete
|
|
||||||
onDelete={() => updatePartialSetting({ reactions: memoRelatedSetting.reactions.filter((r) => r !== reactionType) })}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{reactionType}
|
{reactionType}
|
||||||
</Chip>
|
<X
|
||||||
|
className="w-3 h-3 cursor-pointer hover:text-red-500"
|
||||||
|
onClick={() => updatePartialSetting({ reactions: memoRelatedSetting.reactions.filter((r) => r !== reactionType) })}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<Input
|
<div className="flex items-center gap-1">
|
||||||
className="w-32 rounded-full! pl-1!"
|
<Input
|
||||||
placeholder={t("common.input")}
|
className="w-32"
|
||||||
value={editingReaction}
|
placeholder={t("common.input")}
|
||||||
onChange={(event) => setEditingReaction(event.target.value.trim())}
|
value={editingReaction}
|
||||||
endDecorator={
|
onChange={(event) => setEditingReaction(event.target.value.trim())}
|
||||||
<CheckIcon
|
/>
|
||||||
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
|
<CheckIcon
|
||||||
onClick={() => upsertReaction()}
|
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
|
||||||
/>
|
onClick={() => upsertReaction()}
|
||||||
}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -156,43 +152,37 @@ const MemoRelatedSettings = observer(() => {
|
||||||
<span>{t("setting.memo-related-settings.enable-blur-nsfw-content")}</span>
|
<span>{t("setting.memo-related-settings.enable-blur-nsfw-content")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={memoRelatedSetting.enableBlurNsfwContent}
|
checked={memoRelatedSetting.enableBlurNsfwContent}
|
||||||
onChange={(event) => updatePartialSetting({ enableBlurNsfwContent: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ enableBlurNsfwContent: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
|
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
|
||||||
{memoRelatedSetting.nsfwTags.map((nsfwTag) => {
|
{memoRelatedSetting.nsfwTags.map((nsfwTag) => {
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Badge key={nsfwTag} variant="outline" className="h-8 flex items-center gap-1">
|
||||||
className="h-8!"
|
|
||||||
key={nsfwTag}
|
|
||||||
variant="outlined"
|
|
||||||
size="lg"
|
|
||||||
endDecorator={
|
|
||||||
<ChipDelete
|
|
||||||
onDelete={() => updatePartialSetting({ nsfwTags: memoRelatedSetting.nsfwTags.filter((r) => r !== nsfwTag) })}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{nsfwTag}
|
{nsfwTag}
|
||||||
</Chip>
|
<X
|
||||||
|
className="w-3 h-3 cursor-pointer hover:text-red-500"
|
||||||
|
onClick={() => updatePartialSetting({ nsfwTags: memoRelatedSetting.nsfwTags.filter((r) => r !== nsfwTag) })}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<Input
|
<div className="flex items-center gap-1">
|
||||||
className="w-32 rounded-full! pl-1!"
|
<Input
|
||||||
placeholder={t("common.input")}
|
className="w-32"
|
||||||
value={editingNsfwTag}
|
placeholder={t("common.input")}
|
||||||
onChange={(event) => setEditingNsfwTag(event.target.value.trim())}
|
value={editingNsfwTag}
|
||||||
endDecorator={
|
onChange={(event) => setEditingNsfwTag(event.target.value.trim())}
|
||||||
<CheckIcon
|
/>
|
||||||
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
|
<CheckIcon
|
||||||
onClick={() => upsertNsfwTags()}
|
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
|
||||||
/>
|
onClick={() => upsertNsfwTags()}
|
||||||
}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-full flex justify-end">
|
<div className="mt-2 w-full flex justify-end">
|
||||||
<Button color="primary" disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
|
<Button disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
|
||||||
{t("common.save")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { MoreVerticalIcon, PenLineIcon } from "lucide-react";
|
import { MoreVerticalIcon, PenLineIcon } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
|
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
|
||||||
import showUpdateAccountDialog from "../UpdateAccountDialog";
|
import showUpdateAccountDialog from "../UpdateAccountDialog";
|
||||||
import UserAvatar from "../UserAvatar";
|
import UserAvatar from "../UserAvatar";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
import AccessTokenSection from "./AccessTokenSection";
|
import AccessTokenSection from "./AccessTokenSection";
|
||||||
import UserSessionsSection from "./UserSessionsSection";
|
import UserSessionsSection from "./UserSessionsSection";
|
||||||
|
|
||||||
|
|
@ -27,13 +27,13 @@ const MyAccountSection = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-start items-center mt-2 space-x-2">
|
<div className="w-full flex flex-row justify-start items-center mt-2 space-x-2">
|
||||||
<Button variant="outlined" onClick={showUpdateAccountDialog}>
|
<Button variant="outline" onClick={showUpdateAccountDialog}>
|
||||||
<PenLineIcon className="w-4 h-4 mx-auto mr-1" />
|
<PenLineIcon className="w-4 h-4 mx-auto mr-1" />
|
||||||
{t("common.edit")}
|
{t("common.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outlined">
|
<Button variant="outline">
|
||||||
<MoreVerticalIcon className="w-4 h-4 mx-auto" />
|
<MoreVerticalIcon className="w-4 h-4 mx-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Divider, Option, Select } from "@mui/joy";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
||||||
|
|
@ -44,27 +45,26 @@ const PreferencesSection = observer(() => {
|
||||||
|
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
|
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
|
||||||
<Select
|
<Select value={setting.memoVisibility} onValueChange={handleDefaultMemoVisibilityChanged}>
|
||||||
className="min-w-fit!"
|
<SelectTrigger className="min-w-fit">
|
||||||
value={setting.memoVisibility}
|
<div className="flex items-center gap-2">
|
||||||
startDecorator={<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />}
|
<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />
|
||||||
onChange={(_, visibility) => {
|
<SelectValue />
|
||||||
if (visibility) {
|
</div>
|
||||||
handleDefaultMemoVisibilityChanged(visibility);
|
</SelectTrigger>
|
||||||
}
|
<SelectContent>
|
||||||
}}
|
{[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC]
|
||||||
>
|
.map((v) => convertVisibilityToString(v))
|
||||||
{[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC]
|
.map((item) => (
|
||||||
.map((v) => convertVisibilityToString(v))
|
<SelectItem key={item} value={item} className="whitespace-nowrap">
|
||||||
.map((item) => (
|
{t(`memo.visibility.${item.toLowerCase() as Lowercase<typeof item>}`)}
|
||||||
<Option key={item} value={item} className="whitespace-nowrap">
|
</SelectItem>
|
||||||
{t(`memo.visibility.${item.toLowerCase() as Lowercase<typeof item>}`)}
|
))}
|
||||||
</Option>
|
</SelectContent>
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider className="my-3!" />
|
<Separator className="my-3" />
|
||||||
|
|
||||||
<WebhookSection />
|
<WebhookSection />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Divider, List, ListItem } from "@mui/joy";
|
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { MoreVerticalIcon } from "lucide-react";
|
import { MoreVerticalIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { identityProviderServiceClient } from "@/grpcweb";
|
import { identityProviderServiceClient } from "@/grpcweb";
|
||||||
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
|
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
|
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
|
||||||
import LearnMore from "../LearnMore";
|
import LearnMore from "../LearnMore";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
|
||||||
const SSOSection = () => {
|
const SSOSection = () => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
|
|
@ -48,7 +48,7 @@ const SSOSection = () => {
|
||||||
{t("common.create")}
|
{t("common.create")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Separator />
|
||||||
{identityProviderList.map((identityProvider) => (
|
{identityProviderList.map((identityProvider) => (
|
||||||
<div
|
<div
|
||||||
key={identityProvider.name}
|
key={identityProvider.name}
|
||||||
|
|
@ -95,8 +95,8 @@ const SSOSection = () => {
|
||||||
|
|
||||||
<div className="w-full mt-4">
|
<div className="w-full mt-4">
|
||||||
<p className="text-sm">{t("common.learn-more")}:</p>
|
<p className="text-sm">{t("common.learn-more")}:</p>
|
||||||
<List component="ul" marker="disc" size="sm">
|
<ul className="list-disc list-inside text-sm ml-4">
|
||||||
<ListItem>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
className="text-sm text-blue-600 hover:underline"
|
className="text-sm text-blue-600 hover:underline"
|
||||||
to="https://www.usememos.com/docs/advanced-settings/sso"
|
to="https://www.usememos.com/docs/advanced-settings/sso"
|
||||||
|
|
@ -104,8 +104,8 @@ const SSOSection = () => {
|
||||||
>
|
>
|
||||||
{t("setting.sso-section.single-sign-on")}
|
{t("setting.sso-section.single-sign-on")}
|
||||||
</Link>
|
</Link>
|
||||||
</ListItem>
|
</li>
|
||||||
</List>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
import { Divider, List, ListItem, Radio, RadioGroup, Tooltip } from "@mui/joy";
|
|
||||||
import { Button, Input, Switch } from "@usememos/mui";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { HelpCircleIcon } from "lucide-react";
|
import { HelpCircleIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { workspaceSettingNamePrefix } from "@/store/common";
|
import { workspaceSettingNamePrefix } from "@/store/common";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
||||||
|
|
@ -131,23 +136,38 @@ const StorageSection = observer(() => {
|
||||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||||
<div className="font-medium text-gray-700 dark:text-gray-500">{t("setting.storage-section.current-storage")}</div>
|
<div className="font-medium text-gray-700 dark:text-gray-500">{t("setting.storage-section.current-storage")}</div>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
orientation="horizontal"
|
value={workspaceStorageSetting.storageType.toString()}
|
||||||
className="w-full"
|
onValueChange={(value) => {
|
||||||
value={workspaceStorageSetting.storageType}
|
handleStorageTypeChanged(parseInt(value) as unknown as WorkspaceStorageSetting_StorageType);
|
||||||
onChange={(event) => {
|
|
||||||
handleStorageTypeChanged(event.target.value as WorkspaceStorageSetting_StorageType);
|
|
||||||
}}
|
}}
|
||||||
|
className="flex flex-row gap-4"
|
||||||
>
|
>
|
||||||
<Radio value={WorkspaceStorageSetting_StorageType.DATABASE} label={t("setting.storage-section.type-database")} />
|
<div className="flex items-center space-x-2">
|
||||||
<Radio value={WorkspaceStorageSetting_StorageType.LOCAL} label={t("setting.storage-section.type-local")} />
|
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.DATABASE.toString()} id="database" />
|
||||||
<Radio value={WorkspaceStorageSetting_StorageType.S3} label={"S3"} />
|
<Label htmlFor="database">{t("setting.storage-section.type-database")}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.LOCAL.toString()} id="local" />
|
||||||
|
<Label htmlFor="local">{t("setting.storage-section.type-local")}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.S3.toString()} id="s3" />
|
||||||
|
<Label htmlFor="s3">S3</Label>
|
||||||
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<span className="text-gray-700 dark:text-gray-500 mr-1">{t("setting.system-section.max-upload-size")}</span>
|
<span className="text-gray-700 dark:text-gray-500 mr-1">{t("setting.system-section.max-upload-size")}</span>
|
||||||
<Tooltip title={t("setting.system-section.max-upload-size-hint")} placement="top">
|
<TooltipProvider>
|
||||||
<HelpCircleIcon className="w-4 h-auto" />
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger>
|
||||||
|
<HelpCircleIcon className="w-4 h-auto" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("setting.system-section.max-upload-size-hint")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
<Input className="w-16 font-mono" value={workspaceStorageSetting.uploadSizeLimitMb} onChange={handleMaxUploadSizeChanged} />
|
<Input className="w-16 font-mono" value={workspaceStorageSetting.uploadSizeLimitMb} onChange={handleMaxUploadSizeChanged} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -189,20 +209,23 @@ const StorageSection = observer(() => {
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span className="text-gray-700 dark:text-gray-500 mr-1">Use Path Style</span>
|
<span className="text-gray-700 dark:text-gray-500 mr-1">Use Path Style</span>
|
||||||
<Switch checked={workspaceStorageSetting.s3Config?.usePathStyle} onChange={handleS3ConfigUsePathStyleChanged} />
|
<Switch
|
||||||
|
checked={workspaceStorageSetting.s3Config?.usePathStyle}
|
||||||
|
onCheckedChange={(checked) => handleS3ConfigUsePathStyleChanged({ target: { checked } } as any)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Button color="primary" disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
|
<Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
|
||||||
{t("common.save")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider className="my-2!" />
|
<Separator className="my-2" />
|
||||||
<div className="w-full mt-4">
|
<div className="w-full mt-4">
|
||||||
<p className="text-sm">{t("common.learn-more")}:</p>
|
<p className="text-sm">{t("common.learn-more")}:</p>
|
||||||
<List component="ul" marker="disc" size="sm">
|
<ul className="text-sm list-disc ml-4 space-y-1">
|
||||||
<ListItem>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
className="text-sm text-blue-600 hover:underline"
|
className="text-sm text-blue-600 hover:underline"
|
||||||
to="https://www.usememos.com/docs/advanced-settings/local-storage"
|
to="https://www.usememos.com/docs/advanced-settings/local-storage"
|
||||||
|
|
@ -210,8 +233,8 @@ const StorageSection = observer(() => {
|
||||||
>
|
>
|
||||||
Docs - Local storage
|
Docs - Local storage
|
||||||
</Link>
|
</Link>
|
||||||
</ListItem>
|
</li>
|
||||||
<ListItem>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
className="text-sm text-blue-600 hover:underline"
|
className="text-sm text-blue-600 hover:underline"
|
||||||
to="https://www.usememos.com/blog/choosing-a-storage-for-your-resource"
|
to="https://www.usememos.com/blog/choosing-a-storage-for-your-resource"
|
||||||
|
|
@ -219,8 +242,8 @@ const StorageSection = observer(() => {
|
||||||
>
|
>
|
||||||
Choosing a Storage for Your Resource: Database, S3 or Local Storage?
|
Choosing a Storage for Your Resource: Database, S3 or Local Storage?
|
||||||
</Link>
|
</Link>
|
||||||
</ListItem>
|
</li>
|
||||||
</List>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react";
|
import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { userServiceClient } from "@/grpcweb";
|
import { userServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { UserSession } from "@/types/proto/api/v1/user_service";
|
import { UserSession } from "@/types/proto/api/v1/user_service";
|
||||||
|
|
@ -124,7 +124,7 @@ const UserSessionsSection = () => {
|
||||||
</td>
|
</td>
|
||||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
|
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="ghost"
|
||||||
disabled={isCurrentSession(userSession)}
|
disabled={isCurrentSession(userSession)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleRevokeSession(userSession);
|
handleRevokeSession(userSession);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button } from "@usememos/mui";
|
|
||||||
import { ExternalLinkIcon, TrashIcon } from "lucide-react";
|
import { ExternalLinkIcon, TrashIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { webhookServiceClient } from "@/grpcweb";
|
import { webhookServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { Webhook } from "@/types/proto/api/v1/webhook_service";
|
import { Webhook } from "@/types/proto/api/v1/webhook_service";
|
||||||
|
|
@ -85,7 +85,7 @@ const WebhookSection = () => {
|
||||||
</td>
|
</td>
|
||||||
<td className="relative whitespace-nowrap px-3 py-2 text-right text-sm">
|
<td className="relative whitespace-nowrap px-3 py-2 text-right text-sm">
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteWebhook(webhook);
|
handleDeleteWebhook(webhook);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { Select, Option, Divider } from "@mui/joy";
|
|
||||||
import { Button, Textarea, Switch } from "@usememos/mui";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { ExternalLinkIcon } from "lucide-react";
|
import { ExternalLinkIcon } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { identityProviderServiceClient } from "@/grpcweb";
|
import { identityProviderServiceClient } from "@/grpcweb";
|
||||||
import { workspaceSettingNamePrefix } from "@/store/common";
|
import { workspaceSettingNamePrefix } from "@/store/common";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
|
|
@ -71,19 +74,18 @@ const WorkspaceSection = observer(() => {
|
||||||
{t("setting.system-section.server-name")}:{" "}
|
{t("setting.system-section.server-name")}:{" "}
|
||||||
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
|
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outlined" onClick={handleUpdateCustomizedProfileButtonClick}>
|
<Button variant="outline" onClick={handleUpdateCustomizedProfileButtonClick}>
|
||||||
{t("common.edit")}
|
{t("common.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Separator />
|
||||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.system-section.title")}</p>
|
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.system-section.title")}</p>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.system-section.additional-style")}</span>
|
<span>{t("setting.system-section.additional-style")}</span>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="font-mono"
|
className="font-mono w-full"
|
||||||
rows={3}
|
rows={3}
|
||||||
fullWidth
|
|
||||||
placeholder={t("setting.system-section.additional-style-placeholder")}
|
placeholder={t("setting.system-section.additional-style-placeholder")}
|
||||||
value={workspaceGeneralSetting.additionalStyle}
|
value={workspaceGeneralSetting.additionalStyle}
|
||||||
onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })}
|
onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })}
|
||||||
|
|
@ -92,9 +94,8 @@ const WorkspaceSection = observer(() => {
|
||||||
<span>{t("setting.system-section.additional-script")}</span>
|
<span>{t("setting.system-section.additional-script")}</span>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="font-mono"
|
className="font-mono w-full"
|
||||||
rows={3}
|
rows={3}
|
||||||
fullWidth
|
|
||||||
placeholder={t("setting.system-section.additional-script-placeholder")}
|
placeholder={t("setting.system-section.additional-script-placeholder")}
|
||||||
value={workspaceGeneralSetting.additionalScript}
|
value={workspaceGeneralSetting.additionalScript}
|
||||||
onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })}
|
onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })}
|
||||||
|
|
@ -114,7 +115,7 @@ const WorkspaceSection = observer(() => {
|
||||||
<Switch
|
<Switch
|
||||||
disabled={workspaceStore.state.profile.mode === "demo"}
|
disabled={workspaceStore.state.profile.mode === "demo"}
|
||||||
checked={workspaceGeneralSetting.disallowUserRegistration}
|
checked={workspaceGeneralSetting.disallowUserRegistration}
|
||||||
onChange={(event) => updatePartialSetting({ disallowUserRegistration: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disallowUserRegistration: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
|
|
@ -125,39 +126,43 @@ const WorkspaceSection = observer(() => {
|
||||||
(identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth)
|
(identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth)
|
||||||
}
|
}
|
||||||
checked={workspaceGeneralSetting.disallowPasswordAuth}
|
checked={workspaceGeneralSetting.disallowPasswordAuth}
|
||||||
onChange={(event) => updatePartialSetting({ disallowPasswordAuth: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disallowPasswordAuth: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.workspace-section.disallow-change-username")}</span>
|
<span>{t("setting.workspace-section.disallow-change-username")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={workspaceGeneralSetting.disallowChangeUsername}
|
checked={workspaceGeneralSetting.disallowChangeUsername}
|
||||||
onChange={(event) => updatePartialSetting({ disallowChangeUsername: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeUsername: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span>{t("setting.workspace-section.disallow-change-nickname")}</span>
|
<span>{t("setting.workspace-section.disallow-change-nickname")}</span>
|
||||||
<Switch
|
<Switch
|
||||||
checked={workspaceGeneralSetting.disallowChangeNickname}
|
checked={workspaceGeneralSetting.disallowChangeNickname}
|
||||||
onChange={(event) => updatePartialSetting({ disallowChangeNickname: event.target.checked })}
|
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeNickname: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row justify-between items-center">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
|
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
|
||||||
<Select
|
<Select
|
||||||
className="min-w-fit!"
|
value={workspaceGeneralSetting.weekStartDayOffset.toString()}
|
||||||
value={workspaceGeneralSetting.weekStartDayOffset}
|
onValueChange={(value) => {
|
||||||
onChange={(_, weekStartDayOffset) => {
|
updatePartialSetting({ weekStartDayOffset: parseInt(value) || 0 });
|
||||||
updatePartialSetting({ weekStartDayOffset: weekStartDayOffset || 0 });
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Option value={-1}>{t("setting.workspace-section.saturday")}</Option>
|
<SelectTrigger className="min-w-fit">
|
||||||
<Option value={0}>{t("setting.workspace-section.sunday")}</Option>
|
<SelectValue />
|
||||||
<Option value={1}>{t("setting.workspace-section.monday")}</Option>
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="-1">{t("setting.workspace-section.saturday")}</SelectItem>
|
||||||
|
<SelectItem value="0">{t("setting.workspace-section.sunday")}</SelectItem>
|
||||||
|
<SelectItem value="1">{t("setting.workspace-section.monday")}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-full flex justify-end">
|
<div className="mt-2 w-full flex justify-end">
|
||||||
<Button color="primary" disabled={isEqual(workspaceGeneralSetting, originalSetting)} onClick={handleSaveGeneralSetting}>
|
<Button disabled={isEqual(workspaceGeneralSetting, originalSetting)} onClick={handleSaveGeneralSetting}>
|
||||||
{t("common.save")}
|
{t("common.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import type { StatCardProps } from "@/types/statistics";
|
import type { StatCardProps } from "@/types/statistics";
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
export const StatCard = ({ icon, label, count, onClick, tooltip, className }: StatCardProps) => {
|
export const StatCard = ({ icon, label, count, onClick, tooltip, className }: StatCardProps) => {
|
||||||
const content = (
|
const content = (
|
||||||
|
|
@ -22,9 +22,14 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip} placement="top" arrow>
|
<TooltipProvider>
|
||||||
{content}
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{tooltip}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Button, Input, Textarea } from "@usememos/mui";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { convertFileToBase64 } from "@/helpers/utils";
|
import { convertFileToBase64 } from "@/helpers/utils";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { userStore, workspaceStore } from "@/store/v2";
|
import { userStore, workspaceStore } from "@/store/v2";
|
||||||
|
|
@ -142,7 +144,7 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p className="title-text">{t("setting.account-section.update-information")}</p>
|
<p className="title-text">{t("setting.account-section.update-information")}</p>
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,16 +190,14 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
|
||||||
{t("common.email")}
|
{t("common.email")}
|
||||||
<span className="text-sm text-gray-400 ml-1">({t("setting.account-section.email-note")})</span>
|
<span className="text-sm text-gray-400 ml-1">({t("setting.account-section.email-note")})</span>
|
||||||
</p>
|
</p>
|
||||||
<Input fullWidth type="email" value={state.email} onChange={handleEmailChanged} />
|
<Input className="w-full" type="email" value={state.email} onChange={handleEmailChanged} />
|
||||||
<p className="text-sm">{t("common.description")}</p>
|
<p className="text-sm">{t("common.description")}</p>
|
||||||
<Textarea rows={2} fullWidth value={state.description} onChange={handleDescriptionChanged} />
|
<Textarea className="w-full" rows={2} value={state.description} onChange={handleDescriptionChanged} />
|
||||||
<div className="w-full flex flex-row justify-end items-center pt-4 space-x-2">
|
<div className="w-full flex flex-row justify-end items-center pt-4 space-x-2">
|
||||||
<Button variant="plain" onClick={handleCloseBtnClick}>
|
<Button variant="ghost" onClick={handleCloseBtnClick}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" onClick={handleSaveBtnClick}>
|
<Button onClick={handleSaveBtnClick}>{t("common.save")}</Button>
|
||||||
{t("common.save")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { Button, Input, Textarea } from "@usememos/mui";
|
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { workspaceSettingNamePrefix } from "@/store/common";
|
import { workspaceSettingNamePrefix } from "@/store/common";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
import { WorkspaceSettingKey } from "@/store/v2/workspace";
|
||||||
|
|
@ -99,7 +101,7 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => {
|
||||||
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
|
||||||
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
|
||||||
<p className="title-text">{t("setting.system-section.customize-server.title")}</p>
|
<p className="title-text">{t("setting.system-section.customize-server.title")}</p>
|
||||||
<Button variant="plain" onClick={handleCloseButtonClick}>
|
<Button variant="ghost" onClick={handleCloseButtonClick}>
|
||||||
<XIcon className="w-5 h-auto" />
|
<XIcon className="w-5 h-auto" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,19 +111,19 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => {
|
||||||
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
|
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
|
||||||
<Input className="w-full" type="text" value={customProfile.logoUrl} onChange={handleLogoUrlChanged} />
|
<Input className="w-full" type="text" value={customProfile.logoUrl} onChange={handleLogoUrlChanged} />
|
||||||
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.description")}</p>
|
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.description")}</p>
|
||||||
<Textarea rows={3} fullWidth value={customProfile.description} onChange={handleDescriptionChanged} />
|
<Textarea rows={3} value={customProfile.description} onChange={handleDescriptionChanged} />
|
||||||
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.locale")}</p>
|
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.locale")}</p>
|
||||||
<LocaleSelect className="w-full!" value={customProfile.locale} onChange={handleLocaleSelectChange} />
|
<LocaleSelect className="w-full!" value={customProfile.locale} onChange={handleLocaleSelectChange} />
|
||||||
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.appearance")}</p>
|
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.appearance")}</p>
|
||||||
<AppearanceSelect className="w-full!" value={customProfile.appearance as Appearance} onChange={handleAppearanceSelectChange} />
|
<AppearanceSelect className="w-full!" value={customProfile.appearance as Appearance} onChange={handleAppearanceSelectChange} />
|
||||||
<div className="mt-4 w-full flex flex-row justify-between items-center space-x-2">
|
<div className="mt-4 w-full flex flex-row justify-between items-center space-x-2">
|
||||||
<div className="flex flex-row justify-start items-center">
|
<div className="flex flex-row justify-start items-center">
|
||||||
<Button variant="outlined" onClick={handleRestoreButtonClick}>
|
<Button variant="outline" onClick={handleRestoreButtonClick}>
|
||||||
{t("common.restore")}
|
{t("common.restore")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-end items-center gap-2">
|
<div className="flex flex-row justify-end items-center gap-2">
|
||||||
<Button variant="plain" onClick={handleCloseButtonClick}>
|
<Button variant="ghost" onClick={handleCloseButtonClick}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" onClick={handleSaveButtonClick}>
|
<Button color="primary" onClick={handleSaveButtonClick}>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, BellI
|
||||||
import { authServiceClient } from "@/grpcweb";
|
import { authServiceClient } from "@/grpcweb";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { cn } from "@/utils";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Globe2Icon, LockIcon, UsersIcon } from "lucide-react";
|
import { Globe2Icon, LockIcon, UsersIcon } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visibility: Visibility;
|
visibility: Visibility;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tooltip } from "@mui/joy";
|
|
||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import { cn } from "@/utils";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
@ -20,11 +20,20 @@ const OverflowTip = ({ children, className }: Props) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={children} placement="top" arrow disableHoverListener={!isOverflowed}>
|
<TooltipProvider>
|
||||||
<div ref={textElementRef} className={cn("truncate", className)}>
|
<Tooltip>
|
||||||
{children}
|
<TooltipTrigger asChild>
|
||||||
</div>
|
<div ref={textElementRef} className={cn("truncate", className)}>
|
||||||
</Tooltip>
|
{children}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
{isOverflowed && (
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{children}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,34 @@
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root;
|
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
function PopoverContent({ className, align = "center", sideOffset = 4, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
<PopoverPrimitive.Portal>
|
||||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
<PopoverPrimitive.Content
|
||||||
<PopoverPrimitive.Portal>
|
data-slot="popover-content"
|
||||||
<PopoverPrimitive.Content
|
align={align}
|
||||||
ref={ref}
|
sideOffset={sideOffset}
|
||||||
align={align}
|
className={cn(
|
||||||
sideOffset={sideOffset}
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-auto origin-(--radix-popover-content-transform-origin) rounded-md border p-1 shadow-md outline-hidden",
|
||||||
className={cn(
|
className,
|
||||||
"z-2000 w-auto rounded-md bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 p-1 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
)}
|
||||||
className,
|
{...props}
|
||||||
)}
|
/>
|
||||||
{...props}
|
</PopoverPrimitive.Portal>
|
||||||
/>
|
);
|
||||||
</PopoverPrimitive.Portal>
|
}
|
||||||
));
|
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent };
|
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||||
|
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function Badge({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
|
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants };
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-8 px-3 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-7 rounded-md gap-1 px-2 has-[>svg]:px-2",
|
||||||
|
lg: "h-9 rounded-md px-4 has-[>svg]:px-3",
|
||||||
|
icon: "size-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const Button = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
>(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
|
return <Comp ref={ref} data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||||
|
});
|
||||||
|
Button.displayName = "Button";
|
||||||
|
|
||||||
|
export { Button, buttonVariants };
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
data-slot="checkbox"
|
||||||
|
className={cn(
|
||||||
|
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="flex items-center justify-center text-current transition-none">
|
||||||
|
<CheckIcon className="size-3.5" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Checkbox };
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
import { SearchIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive
|
||||||
|
data-slot="command"
|
||||||
|
className={cn("bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandDialog({
|
||||||
|
title = "Command Palette",
|
||||||
|
description = "Search for a command to run...",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
className?: string;
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogHeader className="sr-only">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent className={cn("overflow-hidden p-0", className)} showCloseButton={showCloseButton}>
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
|
return (
|
||||||
|
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
|
||||||
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
data-slot="command-input"
|
||||||
|
className={cn(
|
||||||
|
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot="command-list"
|
||||||
|
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
|
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot="command-group"
|
||||||
|
className={cn(
|
||||||
|
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
|
return <CommandPrimitive.Separator data-slot="command-separator" className={cn("bg-border -mx-1 h-px", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
data-slot="command-item"
|
||||||
|
className={cn(
|
||||||
|
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span data-slot="command-shortcut" className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator };
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal data-slot="dialog-portal">
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot="dialog-close"
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="dialog-header" className={cn("flex flex-col gap-2 text-center sm:text-left", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="dialog-footer" className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return <DialogPrimitive.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description data-slot="dialog-description" className={cn("text-muted-foreground text-sm", className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
data-slot="input"
|
||||||
|
className={cn(
|
||||||
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input };
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
data-slot="label"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Label };
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||||
|
import { CircleIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||||
|
return <RadioGroupPrimitive.Root data-slot="radio-group" className={cn("grid gap-3", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
data-slot="radio-group-item"
|
||||||
|
className={cn(
|
||||||
|
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator data-slot="radio-group-indicator" className="relative flex items-center justify-center">
|
||||||
|
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem };
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectTrigger({
|
||||||
|
className,
|
||||||
|
size = "default",
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
|
size?: "sm" | "default";
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
data-slot="select-trigger"
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectContent({ className, children, position = "popper", ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
data-slot="select-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Label data-slot="select-label" className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
data-slot="select-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
data-slot="select-separator"
|
||||||
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
data-slot="select-scroll-up-button"
|
||||||
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUpIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollDownButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
data-slot="select-scroll-down-button"
|
||||||
|
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Separator({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
decorative = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
data-slot="separator"
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Separator };
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||||
|
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||||
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
data-slot="sheet-overlay"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
side = "right",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
data-slot="sheet-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
side === "right" &&
|
||||||
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||||
|
side === "left" &&
|
||||||
|
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||||
|
side === "top" && "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
|
side === "bottom" &&
|
||||||
|
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||||
|
return <SheetPrimitive.Title data-slot="sheet-title" className={cn("text-foreground font-semibold", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||||
|
return <SheetPrimitive.Description data-slot="sheet-description" className={cn("text-muted-foreground text-sm", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SwitchPrimitive.Root
|
||||||
|
data-slot="switch"
|
||||||
|
className={cn(
|
||||||
|
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SwitchPrimitive.Thumb
|
||||||
|
data-slot="switch-thumb"
|
||||||
|
className={cn(
|
||||||
|
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Switch };
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
data-slot="textarea"
|
||||||
|
className={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Textarea };
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
import * as React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||||
|
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||||
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TooltipContent({ className, sideOffset = 0, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
data-slot="tooltip-content"
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||||
|
</TooltipPrimitive.Content>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
|
|
@ -3,7 +3,7 @@ import { Outlet } from "react-router-dom";
|
||||||
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const HomeLayout = observer(() => {
|
const HomeLayout = observer(() => {
|
||||||
const { md, lg } = useResponsiveWidth();
|
const { md, lg } = useResponsiveWidth();
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ import usePrevious from "react-use/lib/usePrevious";
|
||||||
import Navigation from "@/components/Navigation";
|
import Navigation from "@/components/Navigation";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import Loading from "@/pages/Loading";
|
import Loading from "@/pages/Loading";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { workspaceStore } from "@/store/v2";
|
import { workspaceStore } from "@/store/v2";
|
||||||
import memoFilterStore from "@/store/v2/memoFilter";
|
import memoFilterStore from "@/store/v2/memoFilter";
|
||||||
import { cn } from "@/utils";
|
|
||||||
|
|
||||||
const RootLayout = observer(() => {
|
const RootLayout = observer(() => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue