chore(web): migrate from ESLint+Prettier to Biome

- Install @biomejs/biome@2.3.5 as unified linter and formatter
- Remove ESLint, Prettier and all related plugins (221 packages removed)
- Migrate linting rules from ESLint to Biome configuration
- Migrate formatting rules from Prettier to Biome configuration
- Exclude auto-generated proto files from linting (src/types/proto/**)
- Exclude CSS files from Biome (Tailwind syntax not yet supported)
- Update package.json scripts:
  - lint: tsc + biome check
  - lint:fix: biome check --write
  - format: biome format --write
- Auto-fix import organization across 60+ files
- Fix duplicate key in Russian locale (ru.json)
- Update CLAUDE.md documentation to reflect Biome usage

Benefits:
- 10-100x faster linting performance
- Simplified toolchain with single configuration file
- 221 fewer npm dependencies
- Unified linting, formatting, and import organization
This commit is contained in:
Steven 2025-11-14 23:58:07 +08:00
parent 64111369d3
commit 156908c77f
70 changed files with 439 additions and 2347 deletions

View file

@ -60,10 +60,12 @@ pnpm build # Build to web/dist/
pnpm release # Build and copy to server/router/frontend/dist/
```
**Lint:**
**Lint and Format:**
```bash
cd web
pnpm lint # TypeScript check + ESLint
pnpm lint # TypeScript check + Biome lint
pnpm lint:fix # Auto-fix linting issues
pnpm format # Format code with Biome
```
### CLI Flags and Environment Variables
@ -209,9 +211,10 @@ The frontend uses `nice-grpc-web` to call backend services. Client setup is in `
- **Components:** PascalCase filenames (e.g., `MemoEditor.tsx`)
- **Hooks:** camelCase filenames (e.g., `useMemoList.ts`)
- **Formatting:** Prettier enforced (see `web/.prettierrc.js`)
- **Import Ordering:** Managed by `@trivago/prettier-plugin-sort-imports`
- **Formatting:** Biome enforced (see `web/biome.json`)
- **Import Ordering:** Automatic via Biome's organize imports
- **Styling:** Tailwind utility classes preferred over custom CSS
- **Linting:** Biome replaces ESLint and Prettier for faster, unified tooling
### Commit Messages
@ -338,6 +341,7 @@ For SQLite (default), all data is stored in the directory specified by `--data`
- `react-router-dom` - Routing
- `mobx` / `mobx-react-lite` - State management
- `tailwindcss` - Styling
- `@biomejs/biome` - Fast linter and formatter (replaces ESLint + Prettier)
- `nice-grpc-web` - gRPC-Web client
- `@radix-ui/*` - Headless UI components
- `react-i18next` - Internationalization

View file

@ -1,8 +0,0 @@
module.exports = {
printWidth: 140,
useTabs: false,
semi: true,
singleQuote: false,
plugins: [require.resolve("@trivago/prettier-plugin-sort-imports")],
importOrder: ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^@/((?!css).+)", "^[./]", "^(.+).css"],
};

202
web/biome.json Normal file
View file

@ -0,0 +1,202 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": [
"**",
"!!**/dist",
"!src/types/proto"
],
"ignoreUnknown": true
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 140,
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"expand": "auto",
"useEditorconfig": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": false,
"complexity": {
"noAdjacentSpacesInRegex": "error",
"noExtraBooleanCast": "error",
"noUselessCatch": "error",
"noUselessEscapeInRegex": "error",
"noUselessTypeConstraint": "error"
},
"correctness": {
"noConstAssign": "error",
"noConstantCondition": "error",
"noEmptyCharacterClassInRegex": "error",
"noEmptyPattern": "error",
"noGlobalObjectCalls": "error",
"noInvalidBuiltinInstantiation": "error",
"noInvalidConstructorSuper": "error",
"noNonoctalDecimalEscape": "error",
"noPrecisionLoss": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedPrivateClassMembers": "error",
"noUnusedVariables": "error",
"useIsNan": "error",
"useValidForDirection": "error",
"useValidTypeof": "error",
"useYield": "error"
},
"style": {
"noCommonJs": "error",
"noNamespace": "error",
"useArrayLiterals": "error",
"useAsConstAssertion": "error",
"useBlockStatements": "off"
},
"suspicious": {
"noAsyncPromiseExecutor": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCompareNegZero": "error",
"noConstantBinaryExpressions": "error",
"noControlCharactersInRegex": "error",
"noDebugger": "error",
"noDuplicateCase": "error",
"noDuplicateClassMembers": "error",
"noDuplicateElseIf": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noEmptyBlockStatements": "off",
"noExplicitAny": "off",
"noExtraNonNullAssertion": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noImportAssign": "error",
"noIrregularWhitespace": "error",
"noMisleadingCharacterClass": "error",
"noMisleadingInstantiator": "error",
"noNonNullAssertedOptionalChain": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeDeclarationMerging": "error",
"noUnsafeNegation": "error",
"noUselessRegexBackrefs": "error",
"noWith": "error",
"useGetterReturn": "error",
"useNamespaceKeyword": "error"
}
},
"includes": [
"**",
"!**/dist/**",
"!**/node_modules/**",
"!src/types/proto/**"
]
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "double",
"attributePosition": "auto",
"bracketSpacing": true
},
"globals": []
},
"css": {
"parser": {
"cssModules": false,
"allowWrongLineComments": true,
"tailwindDirectives": true
}
},
"html": {
"formatter": {
"indentScriptAndStyle": false,
"selfCloseVoidElements": "always"
}
},
"overrides": [
{
"includes": [
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.cts"
],
"linter": {
"rules": {
"complexity": {
"noArguments": "error"
},
"correctness": {
"noConstAssign": "off",
"noGlobalObjectCalls": "off",
"noInvalidBuiltinInstantiation": "off",
"noInvalidConstructorSuper": "off",
"noSetterReturn": "off",
"noUndeclaredVariables": "off",
"noUnreachable": "off",
"noUnreachableSuper": "off"
},
"style": {
"useConst": "error"
},
"suspicious": {
"noClassAssign": "off",
"noDuplicateClassMembers": "off",
"noDuplicateObjectKeys": "off",
"noDuplicateParameters": "off",
"noFunctionAssign": "off",
"noImportAssign": "off",
"noRedeclare": "off",
"noUnsafeNegation": "off",
"noVar": "error",
"noWith": "off",
"useGetterReturn": "off"
}
}
}
},
{
"includes": [
"src/utils/i18n.ts"
],
"linter": {
"rules": {}
}
}
],
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

View file

@ -1,34 +0,0 @@
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",
},
},
];

View file

@ -5,7 +5,9 @@
"dev": "vite",
"build": "vite build",
"release": "vite build --mode release --outDir=../server/router/frontend/dist --emptyOutDir",
"lint": "tsc --noEmit --skipLibCheck && eslint --ext .js,.ts,.tsx, src"
"lint": "tsc --noEmit --skipLibCheck && biome check src",
"lint:fix": "biome check --write src",
"format": "biome format --write src"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -63,9 +65,8 @@
"uuid": "^11.1.0"
},
"devDependencies": {
"@biomejs/biome": "2.3.5",
"@bufbuild/protobuf": "^2.8.0",
"@eslint/js": "^9.35.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/d3": "^7.4.3",
"@types/katex": "^0.16.7",
"@types/leaflet": "^1.9.20",
@ -79,17 +80,11 @@
"@types/unist": "^3.0.3",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.7.0",
"eslint": "^9.35.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"long": "^5.3.2",
"nice-grpc-web": "^3.3.8",
"prettier": "^3.6.2",
"terser": "^5.44.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.45.0",
"vite": "^7.1.5"
},
"pnpm": {

2241
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom";
import useNavigateTo from "./hooks/useNavigateTo";
import { userStore, instanceStore } from "./store";
import { instanceStore, userStore } from "./store";
import { cleanupExpiredOAuthState } from "./utils/oauth";
import { loadTheme, setupSystemThemeListener } from "./utils/theme";

View file

@ -13,8 +13,8 @@ import React, { useState } from "react";
import { cn } from "@/lib/utils";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { getAttachmentThumbnailUrl, getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
import PreviewImageDialog from "./PreviewImageDialog";
import SquareDiv from "./kit/SquareDiv";
import PreviewImageDialog from "./PreviewImageDialog";
interface Props {
attachment: Attachment;

View file

@ -1,25 +1,22 @@
// Main component
export { default } from "./MasonryView";
// Sub-components (exported for testing or advanced usage)
export { MasonryColumn } from "./MasonryColumn";
export { MasonryItem } from "./MasonryItem";
// Hooks
export { useMasonryLayout } from "./useMasonryLayout";
// Utilities
export { distributeItemsToColumns } from "./distributeItems";
// Types
export type {
MasonryViewProps,
MasonryItemProps,
MasonryColumnProps,
DistributionResult,
MemoWithHeight,
MemoRenderContext,
} from "./types";
// Constants
export { MINIMUM_MEMO_VIEWPORT_WIDTH, REDISTRIBUTION_DEBOUNCE_MS } from "./constants";
// Utilities
export { distributeItemsToColumns } from "./distributeItems";
// Sub-components (exported for testing or advanced usage)
export { MasonryColumn } from "./MasonryColumn";
export { MasonryItem } from "./MasonryItem";
export { default } from "./MasonryView";
// Types
export type {
DistributionResult,
MasonryColumnProps,
MasonryItemProps,
MasonryViewProps,
MemoRenderContext,
MemoWithHeight,
} from "./types";
// Hooks
export { useMasonryLayout } from "./useMasonryLayout";

View file

@ -9,8 +9,8 @@ import {
FileTextIcon,
LinkIcon,
MoreVerticalIcon,
TrashIcon,
SquareCheckIcon,
TrashIcon,
} from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
@ -18,8 +18,7 @@ import toast from "react-hot-toast";
import { useLocation } from "react-router-dom";
import ConfirmDialog from "@/components/ConfirmDialog";
import useNavigateTo from "@/hooks/useNavigateTo";
import { memoStore, userStore } from "@/store";
import { instanceStore } from "@/store";
import { instanceStore, memoStore, userStore } from "@/store";
import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";

View file

@ -4,7 +4,7 @@ import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { memoFilterStore } from "@/store";
import { stringifyFilters, MemoFilter } from "@/store/memoFilter";
import { MemoFilter, stringifyFilters } from "@/store/memoFilter";
import { MemoContentContext } from "./MemoContentContext";
/**

View file

@ -1,7 +1,7 @@
import { isEqual } from "lodash-es";
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, Memo_Property, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
import MemoRelationForceGraph from "../MemoRelationForceGraph";

View file

@ -1,6 +1,6 @@
export { LinkMemoDialog } from "./LinkMemoDialog";
export { LocationDialog } from "./LocationDialog";
export type { LinkMemoState, LocationState } from "./types";
export { useFileUpload } from "./useFileUpload";
export { useLinkMemo } from "./useLinkMemo";
export { useLocation } from "./useLocation";
export type { LocationState, LinkMemoState } from "./types";

View file

@ -1,6 +1,6 @@
import { CheckIcon, ChevronDownIcon } from "lucide-react";
import VisibilityIcon from "@/components/VisibilityIcon";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import VisibilityIcon from "@/components/VisibilityIcon";
import { Visibility } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";

View file

@ -1,7 +1,7 @@
import { observer } from "mobx-react-lite";
import OverflowTip from "@/components/kit/OverflowTip";
import { EditorRefActions } from ".";
import { Command } from "../types/command";
import { EditorRefActions } from ".";
import { SuggestionsPopup } from "./SuggestionsPopup";
import { useSuggestions } from "./useSuggestions";

View file

@ -2,8 +2,8 @@ import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, use
import { cn } from "@/lib/utils";
import { Command } from "../types/command";
import CommandSuggestions from "./CommandSuggestions";
import TagSuggestions from "./TagSuggestions";
import { editorCommands } from "./commands";
import TagSuggestions from "./TagSuggestions";
import { useListAutoCompletion } from "./useListAutoCompletion";
export interface EditorRefActions {

View file

@ -13,14 +13,14 @@ import { isValidUrl } from "@/helpers/utils";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore, attachmentStore, userStore, instanceStore } from "@/store";
import { attachmentStore, instanceStore, memoStore, userStore } from "@/store";
import { extractMemoIdFromName } from "@/store/common";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput";
import { LocationDisplay, AttachmentList, RelationList } from "../memo-metadata";
import { AttachmentList, LocationDisplay, RelationList } from "../memo-metadata";
import InsertMenu from "./ActionButton/InsertMenu";
import VisibilitySelector from "./ActionButton/VisibilitySelector";
import Editor, { EditorRefActions } from "./Editor";

View file

@ -1,4 +1,4 @@
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
import { Edit3Icon, MoreVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import toast from "react-hot-toast";

View file

@ -4,8 +4,7 @@ import { memo, useEffect, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore } from "@/store";
import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { Reaction } from "@/types/proto/api/v1/memo_service";
import { Memo, Reaction } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
import ReactionSelector from "./ReactionSelector";
import ReactionView from "./ReactionView";

View file

@ -7,7 +7,7 @@ import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore, instanceStore } from "@/store";
import { instanceStore, memoStore, userStore } from "@/store";
import { State } from "@/types/proto/api/v1/common";
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
@ -17,11 +17,11 @@ import MemoActionMenu from "./MemoActionMenu";
import MemoContent from "./MemoContent";
import MemoEditor from "./MemoEditor";
import MemoReactionistView from "./MemoReactionListView";
import { AttachmentList, LocationDisplay, RelationList } from "./memo-metadata";
import PreviewImageDialog from "./PreviewImageDialog";
import ReactionSelector from "./ReactionSelector";
import UserAvatar from "./UserAvatar";
import VisibilityIcon from "./VisibilityIcon";
import { LocationDisplay, AttachmentList, RelationList } from "./memo-metadata";
interface Props {
memo: Memo;

View file

@ -5,7 +5,7 @@ import useClickAway from "react-use/lib/useClickAway";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore, instanceStore } from "@/store";
import { instanceStore, memoStore } from "@/store";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";

View file

@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { instanceStore } from "@/store";
import { instanceSettingNamePrefix } from "@/store/common";
import { InstanceSetting_MemoRelatedSetting, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
import { InstanceSetting_Key, InstanceSetting_MemoRelatedSetting } from "@/types/proto/api/v1/instance_service";
import { useTranslate } from "@/utils/i18n";
import SettingGroup from "./SettingGroup";
import SettingRow from "./SettingRow";

View file

@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { userStore, instanceStore } from "@/store";
import { instanceStore, userStore } from "@/store";
import { Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";

View file

@ -1,6 +1,6 @@
import dayjs from "dayjs";
import { observer } from "mobx-react-lite";
import { useState, useCallback } from "react";
import { useCallback, useState } from "react";
import memoFilterStore from "@/store/memoFilter";
import type { StatisticsData } from "@/types/statistics";
import ActivityCalendar from "../ActivityCalendar";

View file

@ -1,4 +1,4 @@
import { Moon, Monitor, Palette, Sun, Wallpaper } from "lucide-react";
import { Monitor, Moon, Palette, Sun, Wallpaper } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { instanceStore } from "@/store";
import { THEME_OPTIONS } from "@/utils/theme";

View file

@ -9,7 +9,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { convertFileToBase64 } from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore, instanceStore } from "@/store";
import { instanceStore, userStore } from "@/store";
import { User as UserPb } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
import UserAvatar from "./UserAvatar";

View file

@ -1,4 +1,4 @@
import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, GlobeIcon, PaletteIcon, CheckIcon } from "lucide-react";
import { ArchiveIcon, CheckIcon, GlobeIcon, LogOutIcon, PaletteIcon, SettingsIcon, SquareUserIcon, User2Icon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { authServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
@ -6,7 +6,7 @@ import useNavigateTo from "@/hooks/useNavigateTo";
import { locales } from "@/i18n";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { userStore, instanceStore } from "@/store";
import { instanceStore, userStore } from "@/store";
import { getLocaleDisplayName, useTranslate } from "@/utils/i18n";
import { THEME_OPTIONS } from "@/utils/theme";
import UserAvatar from "./UserAvatar";

View file

@ -1,4 +1,4 @@
import { useRef, useState, useEffect } from "react";
import { useEffect, useRef, useState } from "react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

View file

@ -1,4 +1,4 @@
import { DndContext, closestCenter, MouseSensor, TouchSensor, useSensor, useSensors, DragEndEvent } from "@dnd-kit/core";
import { closestCenter, DndContext, DragEndEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useState } from "react";
import { Attachment } from "@/types/proto/api/v1/attachment_service";

View file

@ -3,14 +3,14 @@
* Provides consistent styling and behavior across editor and view modes
*/
export { default as LocationDisplay } from "./LocationDisplay";
export { default as AttachmentCard } from "./AttachmentCard";
export { default as AttachmentList } from "./AttachmentList";
export { default as RelationList } from "./RelationList";
export { default as LocationDisplay } from "./LocationDisplay";
// Base components (can be used for other metadata types)
export { default as MetadataCard } from "./MetadataCard";
export { default as AttachmentCard } from "./AttachmentCard";
export { default as RelationCard } from "./RelationCard";
export { default as RelationList } from "./RelationList";
// Types
export type { DisplayMode, BaseMetadataProps } from "./types";
export type { BaseMetadataProps, DisplayMode } from "./types";

View file

@ -1,8 +1,8 @@
export * from "./useLoading";
export * from "./useCurrentUser";
export * from "./useNavigateTo";
export * from "./useAsyncEffect";
export * from "./useResponsiveWidth";
export * from "./useCurrentUser";
export * from "./useFilteredMemoStats";
export * from "./useLoading";
export * from "./useMemoFilters";
export * from "./useMemoSorting";
export * from "./useFilteredMemoStats";
export * from "./useNavigateTo";
export * from "./useResponsiveWidth";

View file

@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useCallback, useState } from "react";
/**
* Hook for managing dialog state with a clean API

View file

@ -1,5 +1,5 @@
import { useMemo } from "react";
import { userStore, instanceStore } from "@/store";
import { instanceStore, userStore } from "@/store";
import { extractUserIdFromName } from "@/store/common";
import memoFilterStore from "@/store/memoFilter";
import { InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";

View file

@ -2,7 +2,7 @@ import { last } from "lodash-es";
import { observer } from "mobx-react-lite";
import { useMemo } from "react";
import { matchPath, Outlet, useLocation } from "react-router-dom";
import { MemoExplorer, MemoExplorerDrawer, MemoExplorerContext } from "@/components/MemoExplorer";
import { MemoExplorer, MemoExplorerContext, MemoExplorerDrawer } from "@/components/MemoExplorer";
import MobileHeader from "@/components/MobileHeader";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useFilteredMemoStats } from "@/hooks/useFilteredMemoStats";

View file

@ -1,4 +1,4 @@
import { clsx, type ClassValue } from "clsx";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {

View file

@ -436,4 +436,4 @@
"rename-tag": "إعادة تسمية العلامة",
"rename-tip": "سيتم تحديث جميع مذكراتك بهذه العلامة."
}
}
}

View file

@ -419,4 +419,4 @@
"rename-tag": "Přejmenovat štítek",
"rename-tip": "Všechny vaše poznámky s tímto štítkem budou aktualizovány."
}
}
}

View file

@ -476,4 +476,4 @@
"tags": "Tags",
"upload-attachment": "Upload Attachment(s)"
}
}
}

View file

@ -432,4 +432,4 @@
"rename-tag": "Renombrar etiqueta",
"rename-tip": "Todos tus memos con esta etiqueta serán actualizados."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Renommer le tag",
"rename-tip": "Toutes vos notes avec ce tag seront mises à jour."
}
}
}

View file

@ -437,4 +437,4 @@
"rename-tag": "टैग का नाम बदलें",
"rename-tip": "आपके सभी मेमो इस टैग के साथ अपडेट किए जाएंगे।"
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Preimenuj tag",
"rename-tip": "Svi vaši memoi s ovim tagom će biti ažurirani."
}
}
}

View file

@ -435,4 +435,4 @@
"rename-tag": "Címke átnevezése",
"rename-tip": "Minden jegyzeted ezzel a címkével frissülni fog."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Ganti nama tag",
"rename-tip": "Semua memo Anda dengan tag ini akan diperbarui."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Rinomina tag",
"rename-tip": "Tutti i tuoi memo con questo tag saranno aggiornati."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "タグ名を変更",
"rename-tip": "このタグが付いたすべてのメモが更新されます。"
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "თეგის გადარქმევა",
"rename-tip": "თქვენი ყველა მემო ამ თეგით განახლდება."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "태그 이름 변경",
"rename-tip": "이 태그가 포함된 모든 메모가 업데이트됩니다."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "टॅगचे नाव बदला",
"rename-tip": "या टॅगसह तुमचे सर्व मेमो अपडेट केले जातील."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Gi nytt navn til tagg",
"rename-tip": "Alle dine memoer med denne taggen vil bli oppdatert."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Zmień nazwę tagu",
"rename-tip": "Wszystkie Twoje notatki z tym tagiem zostaną zaktualizowane."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Renomear etiqueta",
"rename-tip": "Todos os seus memos com esta etiqueta serão atualizados."
}
}
}

View file

@ -118,9 +118,6 @@
"memo": {
"archived-at": "В архиве",
"click-to-show-nsfw-content": "Нажмите, чтобы посмотреть",
"comment": {
"self": "Комментарии"
},
"code": "Код",
"comment": {
"self": "Комментарии",
@ -377,14 +374,14 @@
"url": "Ссылка"
},
"instance-section": {
"disallow-change-nickname": "Запретить смену псевдонима",
"disallow-change-username": "Запретить смену имени пользователя",
"disallow-password-auth": "Запретить вход по паролю",
"disallow-user-registration": "Запретить регистрацию пользователей",
"monday": "Понедельник",
"saturday": "Суббота",
"sunday": "Воскресенье",
"week-start-day": "Начало недели"
"disallow-change-nickname": "Запретить смену псевдонима",
"disallow-change-username": "Запретить смену имени пользователя",
"disallow-password-auth": "Запретить вход по паролю",
"disallow-user-registration": "Запретить регистрацию пользователей",
"monday": "Понедельник",
"saturday": "Суббота",
"sunday": "Воскресенье",
"week-start-day": "Начало недели"
}
},
"tag": {

View file

@ -436,4 +436,4 @@
"rename-tag": "Preimenuj značko",
"rename-tip": "Vse vaše beležke s to značko bodo posodobljene."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Byt namn på tagg",
"rename-tip": "Alla dina anteckningar med denna tagg kommer att uppdateras"
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "เปลี่ยนชื่อแท็ก",
"rename-tip": "บันทึกทั้งหมดที่มีแท็กนี้จะถูกอัปเดต"
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Etiketi yeniden adlandır",
"rename-tip": "Bu etiketle ilişkili tüm notların etiketini değiştirecektir."
}
}
}

View file

@ -436,4 +436,4 @@
"rename-tag": "Перейменувати тег",
"rename-tip": "Всі ваші нотатки з цим тегом також будуть оновлені"
}
}
}

View file

@ -442,4 +442,4 @@
"tags": "标签",
"upload-attachment": "上传附件"
}
}
}

View file

@ -470,4 +470,4 @@
"tags": "標籤",
"upload-attachment": "上傳附件"
}
}
}

View file

@ -8,8 +8,8 @@ import MemberSection from "@/components/Settings/MemberSection";
import MemoRelatedSettings from "@/components/Settings/MemoRelatedSettings";
import MyAccountSection from "@/components/Settings/MyAccountSection";
import PreferencesSection from "@/components/Settings/PreferencesSection";
import SSOSection from "@/components/Settings/SSOSection";
import SectionMenuItem from "@/components/Settings/SectionMenuItem";
import SSOSection from "@/components/Settings/SSOSection";
import StorageSection from "@/components/Settings/StorageSection";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import useCurrentUser from "@/hooks/useCurrentUser";

View file

@ -1,4 +1,4 @@
import { Suspense, lazy } from "react";
import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router-dom";
import App from "@/App";
import MainLayout from "@/layouts/MainLayout";

View file

@ -4,10 +4,10 @@
* Manages file attachment state including uploads and metadata.
* This is a server state store that fetches and caches attachment data.
*/
import { makeObservable, observable, computed } from "mobx";
import { computed, makeObservable, observable } from "mobx";
import { attachmentServiceClient } from "@/grpcweb";
import { CreateAttachmentRequest, Attachment, UpdateAttachmentRequest } from "@/types/proto/api/v1/attachment_service";
import { StandardState, createServerStore } from "./base-store";
import { Attachment, CreateAttachmentRequest, UpdateAttachmentRequest } from "@/types/proto/api/v1/attachment_service";
import { createServerStore, StandardState } from "./base-store";
import { createRequestKey } from "./store-utils";
/**

View file

@ -6,7 +6,7 @@
* - BaseClientStore: For stores that manage UI/client state
* - Common patterns for all stores
*/
import { makeObservable, action } from "mobx";
import { action, makeObservable } from "mobx";
import { RequestDeduplicator, StoreError } from "./store-utils";
/**

View file

@ -53,33 +53,29 @@ import memoFilterStore from "./memoFilter";
import userStore from "./user";
import viewStore from "./view";
// Utilities and Types
export { StoreError, RequestDeduplicator, createRequestKey } from "./store-utils";
export { StandardState, createServerStore, createClientStore } from "./base-store";
export type { BaseState, ServerStoreConfig, ClientStoreConfig } from "./base-store";
// Re-export filter types
export type { FilterFactor, MemoFilter } from "./memoFilter";
export { getMemoFilterKey, parseFilterQuery, stringifyFilters } from "./memoFilter";
// Re-export view types
export type { LayoutMode } from "./view";
export type { BaseState, ClientStoreConfig, ServerStoreConfig } from "./base-store";
export { createClientStore, createServerStore, StandardState } from "./base-store";
// Re-export common utilities
export {
activityNamePrefix,
extractIdentityProviderIdFromName,
extractMemoIdFromName,
extractUserIdFromName,
identityProviderNamePrefix,
instanceSettingNamePrefix,
memoNamePrefix,
userNamePrefix,
} from "./common";
// Re-export instance types
export type { Theme } from "./instance";
export { isValidTheme } from "./instance";
// Re-export common utilities
export {
instanceSettingNamePrefix,
userNamePrefix,
memoNamePrefix,
identityProviderNamePrefix,
activityNamePrefix,
extractUserIdFromName,
extractMemoIdFromName,
extractIdentityProviderIdFromName,
} from "./common";
// Re-export filter types
export type { FilterFactor, MemoFilter } from "./memoFilter";
export { getMemoFilterKey, parseFilterQuery, stringifyFilters } from "./memoFilter";
// Utilities and Types
export { createRequestKey, RequestDeduplicator, StoreError } from "./store-utils";
// Re-export view types
export type { LayoutMode } from "./view";
// Export store instances
export {
@ -88,7 +84,6 @@ export {
userStore,
instanceStore,
attachmentStore,
// Client state stores
memoFilterStore,
viewStore,

View file

@ -7,10 +7,15 @@
import { uniqBy } from "lodash-es";
import { computed } from "mobx";
import { instanceServiceClient } from "@/grpcweb";
import { InstanceProfile, InstanceSetting_Key } from "@/types/proto/api/v1/instance_service";
import { InstanceSetting_GeneralSetting, InstanceSetting_MemoRelatedSetting, InstanceSetting } from "@/types/proto/api/v1/instance_service";
import {
InstanceProfile,
InstanceSetting,
InstanceSetting_GeneralSetting,
InstanceSetting_Key,
InstanceSetting_MemoRelatedSetting,
} from "@/types/proto/api/v1/instance_service";
import { isValidateLocale } from "@/utils/i18n";
import { StandardState, createServerStore } from "./base-store";
import { createServerStore, StandardState } from "./base-store";
import { instanceSettingNamePrefix } from "./common";
import { createRequestKey } from "./store-utils";

View file

@ -2,7 +2,7 @@ import { uniqueId } from "lodash-es";
import { makeAutoObservable } from "mobx";
import { memoServiceClient } from "@/grpcweb";
import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v1/memo_service";
import { RequestDeduplicator, createRequestKey, StoreError } from "./store-utils";
import { createRequestKey, RequestDeduplicator, StoreError } from "./store-utils";
import userStore from "./user";
class LocalState {

View file

@ -7,7 +7,7 @@
* Filters are URL-driven and shareable - copying the URL preserves the filter state.
*/
import { uniqBy } from "lodash-es";
import { makeObservable, observable, action, computed } from "mobx";
import { action, computed, makeObservable, observable } from "mobx";
import { StandardState } from "./base-store";
/**

View file

@ -1,21 +1,21 @@
import { uniqueId } from "lodash-es";
import { makeAutoObservable, computed } from "mobx";
import { authServiceClient, userServiceClient, shortcutServiceClient } from "@/grpcweb";
import { computed, makeAutoObservable } from "mobx";
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/grpcweb";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import {
User,
UserNotification,
UserSetting,
UserSetting_Key,
UserSetting_GeneralSetting,
UserSetting_SessionsSetting,
UserSetting_AccessTokensSetting,
UserSetting_GeneralSetting,
UserSetting_Key,
UserSetting_SessionsSetting,
UserSetting_WebhooksSetting,
UserStats,
} from "@/types/proto/api/v1/user_service";
import { findNearestMatchedLanguage } from "@/utils/i18n";
import instanceStore from "./instance";
import { RequestDeduplicator, createRequestKey, StoreError } from "./store-utils";
import { createRequestKey, RequestDeduplicator, StoreError } from "./store-utils";
class LocalState {
currentUser?: string;

View file

@ -9,14 +9,14 @@
--primary-foreground: oklch(0.98 0.008 80);
--secondary: oklch(0.92 0.025 70);
--secondary-foreground: oklch(0.35 0.03 60);
--muted: oklch(0.90 0.025 75);
--muted-foreground: oklch(0.50 0.02 68);
--muted: oklch(0.9 0.025 75);
--muted-foreground: oklch(0.5 0.02 68);
--accent: oklch(0.88 0.035 55);
--accent-foreground: oklch(0.25 0.02 65);
--destructive: oklch(0.48 0.15 25);
--destructive-foreground: oklch(0.98 0.008 80);
--border: oklch(0.85 0.025 72);
--input: oklch(0.80 0.03 75);
--input: oklch(0.8 0.03 75);
--ring: oklch(0.45 0.08 45);
--chart-1: oklch(0.5686 0.0549 42.7834);
--chart-2: oklch(0.6275 0.0392 85.6723);