Commit b770042a authored by Johnny's avatar Johnny

refactor: migrate eslint

parent d649d326
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint", "prettier"],
"ignorePatterns": ["node_modules", "dist", "public", "src/assets"],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": ["off"],
"react/react-in-jsx-scope": "off",
"react/jsx-no-target-blank": "off",
"no-restricted-syntax": [
"error",
{
"selector": "VariableDeclarator[init.callee.name='useTranslation'] > ObjectPattern > Property[key.name='t']:not([parent.declarations.0.init.callee.object.name='i18n'])",
"message": "Destructuring 't' from useTranslation is not allowed. Please use the 'useTranslate' hook from '@/utils/i18n'."
}
]
},
"settings": {
"react": {
"version": "detect"
}
},
"overrides": [
{
"files": ["src/utils/i18n.ts"],
"rules": {
"no-restricted-syntax": "off"
}
}
]
}
import eslint from "@eslint/js";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import tseslint from "typescript-eslint";
export default [
...tseslint.config(eslint.configs.recommended, tseslint.configs.recommended),
eslintPluginPrettierRecommended,
{
ignores: ["**/dist/**", "**/node_modules/**", "**/proto/**"],
},
{
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": ["off"],
"react/react-in-jsx-scope": "off",
"react/jsx-no-target-blank": "off",
"no-restricted-syntax": [
"error",
{
selector:
"VariableDeclarator[init.callee.name='useTranslation'] > ObjectPattern > Property[key.name='t']:not([parent.declarations.0.init.callee.object.name='i18n'])",
message: "Destructuring 't' from useTranslation is not allowed. Please use the 'useTranslate' hook from '@/utils/i18n'.",
},
],
},
},
{
files: ["src/utils/i18n.ts"],
rules: {
"no-restricted-syntax": "off",
},
},
];
......@@ -52,6 +52,7 @@
"devDependencies": {
"@bufbuild/buf": "^1.50.1",
"@bufbuild/protobuf": "^2.2.3",
"@eslint/js": "^9.23.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/d3": "^7.4.3",
"@types/katex": "^0.16.7",
......@@ -63,15 +64,13 @@
"@types/react-dom": "^18.3.5",
"@types/textarea-caret": "^3.0.3",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-legacy": "^6.0.2",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21",
"code-inspector-plugin": "^0.18.3",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint": "^9.23.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-prettier": "^5.2.5",
"eslint-plugin-react": "^7.37.4",
"long": "^5.3.1",
"nice-grpc-web": "^3.3.6",
......@@ -80,6 +79,7 @@
"protobufjs": "^7.4.0",
"terser": "^5.39.0",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0",
"vite": "^6.2.1"
},
"pnpm": {
......
This diff is collapsed.
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useState } from "react";
import { toast } from "react-hot-toast";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
interface Props extends DialogProps {
onConfirm: () => void;
}
interface State {
description: string;
expiration: number;
}
const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
const { destroy, onConfirm } = props;
const t = useTranslate();
const currentUser = useCurrentUser();
const [state, setState] = useState({
description: "",
expiration: 3600 * 8,
});
const requestState = useLoading(false);
const expirationOptions = [
{
label: t("setting.access-token-section.create-dialog.duration-8h"),
value: 3600 * 8,
},
{
label: t("setting.access-token-section.create-dialog.duration-1m"),
value: 3600 * 24 * 30,
},
{
label: t("setting.access-token-section.create-dialog.duration-never"),
value: 0,
},
];
const setPartialState = (partialState: Partial<State>) => {
setState({
...state,
...partialState,
});
};
const handleDescriptionInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
description: e.target.value,
});
};
const handleRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
expiration: Number(e.target.value),
});
};
const handleSaveBtnClick = async () => {
if (!state.description) {
toast.error(t("message.description-is-required"));
return;
}
try {
await userServiceClient.createUserAccessToken({
name: currentUser.name,
description: state.description,
expiresAt: state.expiration ? new Date(Date.now() + state.expiration * 1000) : undefined,
});
onConfirm();
destroy();
} catch (error: any) {
toast.error(error.details);
console.error(error);
}
};
return (
<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">
<p>{t("setting.access-token-section.create-dialog.create-access-token")}</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.access-token-section.create-dialog.description")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.access-token-section.create-dialog.some-description")}
value={state.description}
onChange={handleDescriptionInputChange}
/>
</div>
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
</span>
<div className="w-full flex flex-row justify-start items-center text-base">
<RadioGroup orientation="horizontal" value={state.expiration} onChange={handleRoleInputChange}>
{expirationOptions.map((option) => (
<Radio key={option.value} value={option.value} checked={state.expiration === option.value} label={option.label} />
))}
</RadioGroup>
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")}
</Button>
</div>
</div>
</div>
);
};
function showCreateAccessTokenDialog(onConfirm: () => void) {
generateDialog(
{
className: "create-access-token-dialog",
dialogName: "create-access-token-dialog",
},
CreateAccessTokenDialog,
{
onConfirm,
},
);
}
export default showCreateAccessTokenDialog;
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useState } from "react";
import { toast } from "react-hot-toast";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
interface Props extends DialogProps {
onConfirm: () => void;
}
interface State {
description: string;
expiration: number;
}
const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
const { destroy, onConfirm } = props;
const t = useTranslate();
const currentUser = useCurrentUser();
const [state, setState] = useState({
description: "",
expiration: 3600 * 8,
});
const requestState = useLoading(false);
const expirationOptions = [
{
label: t("setting.access-token-section.create-dialog.duration-8h"),
value: 3600 * 8,
},
{
label: t("setting.access-token-section.create-dialog.duration-1m"),
value: 3600 * 24 * 30,
},
{
label: t("setting.access-token-section.create-dialog.duration-never"),
value: 0,
},
];
const setPartialState = (partialState: Partial<State>) => {
setState({
...state,
...partialState,
});
};
const handleDescriptionInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
description: e.target.value,
});
};
const handleRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
expiration: Number(e.target.value),
});
};
const handleSaveBtnClick = async () => {
if (!state.description) {
toast.error(t("message.description-is-required"));
return;
}
try {
await userServiceClient.createUserAccessToken({
name: currentUser.name,
description: state.description,
expiresAt: state.expiration ? new Date(Date.now() + state.expiration * 1000) : undefined,
});
onConfirm();
destroy();
} catch (error: any) {
toast.error(error.details);
console.error(error);
}
};
return (
<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">
<p>{t("setting.access-token-section.create-dialog.create-access-token")}</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.access-token-section.create-dialog.description")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.access-token-section.create-dialog.some-description")}
value={state.description}
onChange={handleDescriptionInputChange}
/>
</div>
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
</span>
<div className="w-full flex flex-row justify-start items-center text-base">
<RadioGroup orientation="horizontal" value={state.expiration} onChange={handleRoleInputChange}>
{expirationOptions.map((option) => (
<Radio key={option.value} value={option.value} checked={state.expiration === option.value} label={option.label} />
))}
</RadioGroup>
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")}
</Button>
</div>
</div>
</div>
);
};
function showCreateAccessTokenDialog(onConfirm: () => void) {
generateDialog(
{
className: "create-access-token-dialog",
dialogName: "create-access-token-dialog",
},
CreateAccessTokenDialog,
{
onConfirm,
},
);
}
export default showCreateAccessTokenDialog;
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { webhookServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
interface Props extends DialogProps {
webhookId?: number;
onConfirm: () => void;
}
interface State {
name: string;
url: string;
}
const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
const { webhookId, destroy, onConfirm } = props;
const t = useTranslate();
const [state, setState] = useState({
name: "",
url: "",
});
const requestState = useLoading(false);
const isCreating = webhookId === undefined;
useEffect(() => {
if (webhookId) {
webhookServiceClient
.getWebhook({
id: webhookId,
})
.then((webhook) => {
setState({
name: webhook.name,
url: webhook.url,
});
});
}
}, []);
const setPartialState = (partialState: Partial<State>) => {
setState({
...state,
...partialState,
});
};
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
name: e.target.value,
});
};
const handleUrlInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
url: e.target.value,
});
};
const handleSaveBtnClick = async () => {
if (!state.name || !state.url) {
toast.error(t("message.fill-all-required-fields"));
return;
}
try {
if (isCreating) {
await webhookServiceClient.createWebhook({
name: state.name,
url: state.url,
});
} else {
await webhookServiceClient.updateWebhook({
webhook: {
id: webhookId,
name: state.name,
url: state.url,
},
updateMask: ["name", "url"],
});
}
onConfirm();
destroy();
} catch (error: any) {
console.error(error);
toast.error(error.details);
}
};
return (
<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">
<p className="title-text">
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.webhook-section.create-dialog.title")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.webhook-section.create-dialog.an-easy-to-remember-name")}
value={state.name}
onChange={handleTitleInputChange}
/>
</div>
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.webhook-section.create-dialog.payload-url")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.webhook-section.create-dialog.url-example-post-receive")}
value={state.url}
onChange={handleUrlInputChange}
/>
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")}
</Button>
</div>
</div>
</div>
);
};
function showCreateWebhookDialog(onConfirm: () => void) {
generateDialog(
{
className: "create-webhook-dialog",
dialogName: "create-webhook-dialog",
},
CreateWebhookDialog,
{
onConfirm,
},
);
}
export default showCreateWebhookDialog;
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { webhookServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
import { generateDialog } from "./Dialog";
interface Props extends DialogProps {
webhookId?: number;
onConfirm: () => void;
}
interface State {
name: string;
url: string;
}
const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
const { webhookId, destroy, onConfirm } = props;
const t = useTranslate();
const [state, setState] = useState({
name: "",
url: "",
});
const requestState = useLoading(false);
const isCreating = webhookId === undefined;
useEffect(() => {
if (webhookId) {
webhookServiceClient
.getWebhook({
id: webhookId,
})
.then((webhook) => {
setState({
name: webhook.name,
url: webhook.url,
});
});
}
}, []);
const setPartialState = (partialState: Partial<State>) => {
setState({
...state,
...partialState,
});
};
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
name: e.target.value,
});
};
const handleUrlInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPartialState({
url: e.target.value,
});
};
const handleSaveBtnClick = async () => {
if (!state.name || !state.url) {
toast.error(t("message.fill-all-required-fields"));
return;
}
try {
if (isCreating) {
await webhookServiceClient.createWebhook({
name: state.name,
url: state.url,
});
} else {
await webhookServiceClient.updateWebhook({
webhook: {
id: webhookId,
name: state.name,
url: state.url,
},
updateMask: ["name", "url"],
});
}
onConfirm();
destroy();
} catch (error: any) {
console.error(error);
toast.error(error.details);
}
};
return (
<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">
<p className="title-text">
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.webhook-section.create-dialog.title")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.webhook-section.create-dialog.an-easy-to-remember-name")}
value={state.name}
onChange={handleTitleInputChange}
/>
</div>
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.webhook-section.create-dialog.payload-url")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder={t("setting.webhook-section.create-dialog.url-example-post-receive")}
value={state.url}
onChange={handleUrlInputChange}
/>
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")}
</Button>
</div>
</div>
</div>
);
};
function showCreateWebhookDialog(onConfirm: () => void) {
generateDialog(
{
className: "create-webhook-dialog",
dialogName: "create-webhook-dialog",
},
CreateWebhookDialog,
{
onConfirm,
},
);
}
export default showCreateWebhookDialog;
......@@ -33,7 +33,7 @@ const LocaleSelect: FC<Props> = (props: Props) => {
</Option>
);
}
} catch (error) {
} catch {
// do nth
}
......
......@@ -77,7 +77,7 @@ const MemoActionMenu = (props: Props) => {
["pinned"],
);
}
} catch (error) {
} catch {
// do nth
}
};
......@@ -108,7 +108,7 @@ const MemoActionMenu = (props: Props) => {
}
if (isInMemoDetailPage) {
memo.state === State.ARCHIVED ? navigateTo("/") : navigateTo("/archived");
navigateTo(memo.state === State.ARCHIVED ? "/" : "/archived");
}
memoUpdatedCallback();
};
......
......@@ -45,7 +45,7 @@ const CodeBlock: React.FC<Props> = ({ language, content }: Props) => {
language: formatedLanguage,
}).value;
}
} catch (error) {
} catch {
// Skip error and use default highlighted code.
}
......
import { BaseProps } from "./types";
interface Props extends BaseProps {}
const LineBreak: React.FC<Props> = () => {
const LineBreak = () => {
return <br />;
};
......
......@@ -14,7 +14,7 @@ const getFaviconWithGoogleS2 = (url: string) => {
try {
const urlObject = new URL(url);
return `https://www.google.com/s2/favicons?sz=128&domain=${urlObject.hostname}`;
} catch (error) {
} catch {
return undefined;
}
};
......
......@@ -86,7 +86,11 @@ const TagSuggestions = observer(({ editorRef, editorActions }: Props) => {
const caretCordinates = getCaretCoordinates(editor, index);
caretCordinates.top -= editor.scrollTop;
isActive ? setPosition(caretCordinates) : hide();
if (isActive) {
setPosition(caretCordinates);
} else {
hide();
}
};
const listenersAreRegisteredRef = useRef(false);
......
......@@ -4,6 +4,7 @@ export interface NodeType {
memo: MemoRelation_Memo;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface LinkType {
// ...add more additional properties relevant to the link here.
}
......@@ -49,7 +49,7 @@ const ReactionSelector = (props: Props) => {
});
}
await memoStore.getOrFetchMemoByName(memo.name, { skipCache: true });
} catch (error) {
} catch {
// skip error.
}
setOpen(false);
......
......@@ -58,7 +58,7 @@ const ReactionView = (props: Props) => {
await memoServiceClient.deleteMemoReaction({ id: reaction.id });
}
}
} catch (error) {
} catch {
// Skip error.
}
await memoStore.getOrFetchMemoByName(memo.name, { skipCache: true });
......
......@@ -9,7 +9,6 @@ const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
// eslint-disable-next-line react/prop-types
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
......
......@@ -25,7 +25,7 @@ export const isValidUrl = (url: string): boolean => {
try {
new URL(url);
return true;
} catch (err) {
} catch {
return false;
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment