diff --git a/CLAUDE.md b/CLAUDE.md index 266d9572..177595c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,60 +1,60 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +このファイルは、このリポジトリのコードを扱う際にClaude Code (claude.ai/code)に対するガイダンスを提供します。 -## Development Commands +## 開発コマンド -- `yarn dev` - Start development mode with Vite -- `yarn build` - Build the extension (runs TypeScript compilation + Vite build) -- `yarn lint` - Run ESLint to check code quality -- `yarn zip` - Create a distributable extension zip file from the built dist folder +- `yarn dev` - Viteを使用した開発モードの開始 +- `yarn build` - 拡張機能のビルド(TypeScriptコンパイル + Viteビルドを実行) +- `yarn lint` - ESLintを実行してコード品質をチェック +- `yarn zip` - ビルドされたdistフォルダから配布可能な拡張機能のzipファイルを作成 -## Architecture Overview +## アーキテクチャ概要 -This is a Chrome Extension (Manifest V3) called **Selection Command** that allows users to perform various actions on selected text on web pages. +これは**Selection Command**と呼ばれるChrome拡張機能(Manifest V3)で、ユーザーがWebページ上で選択したテキストに対してさまざまなアクションを実行できます。 -### Key Components +### 主要コンポーネント -**Chrome Extension Structure:** +**Chrome拡張機能の構造:** -- `manifest.json` - Extension manifest defining permissions, content scripts, and background workers -- `src/background_script.ts` - Service worker handling extension lifecycle and background operations -- `src/content_script.tsx` - Main content script injected into web pages -- `src/options_page.tsx` - Extension options/settings page +- `manifest.json` - 権限、コンテンツスクリプト、バックグラウンドワーカーを定義する拡張機能マニフェスト +- `src/background_script.ts` - 拡張機能のライフサイクルとバックグラウンド操作を処理するサービスワーカー +- `src/content_script.tsx` - Webページに注入されるメインのコンテンツスクリプト +- `src/options_page.tsx` - 拡張機能のオプション/設定ページ -**Core Architecture:** +**コアアーキテクチャ:** -- **Actions** (`src/action/`) - Core functionality modules including background operations, popup handling, page actions, and command execution -- **Components** (`src/components/`) - React components organized by feature: - - `menu/` - Context menu and menu item components - - `option/` - Settings and configuration UI - - `pageAction/` - Page automation and recording components - - `result/` - Result display and popup components - - `ui/` - Reusable UI components (uses Radix UI) -- **Services** (`src/services/`) - Business logic and utilities including settings management, storage, analytics, and page action handling -- **Hooks** (`src/hooks/`) - Custom React hooks for state management and Chrome extension APIs +- **Actions** (`src/action/`) - バックグラウンド操作、ポップアップ処理、ページアクション、コマンド実行を含むコア機能モジュール +- **Components** (`src/components/`) - 機能別に整理されたReactコンポーネント: + - `menu/` - コンテキストメニューとメニューアイテムコンポーネント + - `option/` - 設定と構成UI + - `pageAction/` - ページ自動化と記録コンポーネント + - `result/` - 結果表示とポップアップコンポーネント + - `ui/` - 再利用可能なUIコンポーネント(Radix UIを使用) +- **Services** (`src/services/`) - 設定管理、ストレージ、分析、ページアクション処理を含むビジネスロジックとユーティリティ +- **Hooks** (`src/hooks/`) - 状態管理とChrome拡張機能APIのためのカスタムReactフック -**Key Features:** +**主要機能:** -- **Page Actions** - Record and replay browser automation sequences -- **Command Hub** - Web interface for sharing and discovering commands (separate Next.js app in `pages/`) -- **Context Menus** - Right-click actions on selected text -- **Settings Management** - Import/export configurations and user preferences +- **ページアクション** - ブラウザ自動化シーケンスの記録と再生 +- **コマンドハブ** - コマンドの共有と発見のためのWebインターフェース(`pages/`内の独立したNext.jsアプリ) +- **コンテキストメニュー** - 選択したテキストに対する右クリックアクション +- **設定管理** - 構成とユーザー設定のインポート/エクスポート -### Technical Stack +### 技術スタック -- **Frontend**: React 18 with TypeScript -- **Build System**: Vite with `@crxjs/vite-plugin` for Chrome extension development -- **UI Components**: Shadcn -- **Form and Validation**: react-hook-form and zod -- **Styling**: CSS Modules + Tailwind CSS(ver.3) -- **State Management**: React hooks with Chrome extension storage APIs -- **Testing**: ESLint for code quality +- **フロントエンド**: React 18 with TypeScript +- **ビルドシステム**: Vite with `@crxjs/vite-plugin` for Chrome extension development +- **UIコンポーネント**: Shadcn +- **フォームとバリデーション**: react-hook-form and zod +- **スタイリング**: CSS Modules + Tailwind CSS(ver.3) +- **状態管理**: React hooks with Chrome extension storage APIs +- **テスト**: ESLint for code quality -### Project Structure Notes +### プロジェクト構造の注意事項 -- The main extension code is in `src/` -- The command hub website is a separate Next.js application in `pages/` -- Extension supports internationalization with locale files in `public/_locales/` -- Uses Shadow DOM for content script styling isolation -- Implements Robula+ algorithm for robust XPath selector generation (`src/lib/robula-plus/`) +- メインの拡張機能コードは`src/`内にある +- コマンドハブのウェブサイトは`pages/`内の独立したNext.jsアプリケーション +- 拡張機能は`public/_locales/`のロケールファイルによる国際化をサポート +- コンテンツスクリプトのスタイリング分離にShadow DOMを使用 +- 堅牢なXPathセレクター生成のためのRobula+アルゴリズムを実装(`src/lib/robula-plus/`) diff --git a/public/_locales/de/messages.json b/public/_locales/de/messages.json index 3fcf4d6e..b06b8442 100644 --- a/public/_locales/de/messages.json +++ b/public/_locales/de/messages.json @@ -222,7 +222,7 @@ "message": "Befehle definieren, die basierend auf dem ausgewählten Text ausgeführt werden sollen." }, "Option_commands_desc_count": { - "message": " / 100 Einträge" + "message": " Einträge" }, "Option_title": { "message": "Titel" diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 7ece6283..1fa6d703 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -222,7 +222,7 @@ "message": "Define commands to execute based on selected text." }, "Option_commands_desc_count": { - "message": " / 100 Records" + "message": " items" }, "Option_title": { "message": "Title" diff --git a/public/_locales/es/messages.json b/public/_locales/es/messages.json index 8b546eda..f6e51ae6 100644 --- a/public/_locales/es/messages.json +++ b/public/_locales/es/messages.json @@ -222,7 +222,7 @@ "message": "Definir comandos para ejecutar basados en el texto seleccionado." }, "Option_commands_desc_count": { - "message": " / 100 Registros" + "message": " registros" }, "Option_title": { "message": "Título" diff --git a/public/_locales/fr/messages.json b/public/_locales/fr/messages.json index 3698edcf..e956dbbc 100644 --- a/public/_locales/fr/messages.json +++ b/public/_locales/fr/messages.json @@ -222,7 +222,7 @@ "message": "Définir des commandes à exécuter en fonction du texte sélectionné." }, "Option_commands_desc_count": { - "message": " / 100 Enregistrements" + "message": " éléments" }, "Option_title": { "message": "Titre" diff --git a/public/_locales/hi/messages.json b/public/_locales/hi/messages.json index fbdd412b..e30f4ac8 100644 --- a/public/_locales/hi/messages.json +++ b/public/_locales/hi/messages.json @@ -222,7 +222,7 @@ "message": "चयनित टेक्स्ट के आधार पर निष्पादित करने के लिए कमांड परिभाषित करता है।" }, "Option_commands_desc_count": { - "message": " / 100 आइटम" + "message": " आइटम" }, "Option_title": { "message": "शीर्षक" diff --git a/public/_locales/id/messages.json b/public/_locales/id/messages.json index 0f74e261..3c270188 100644 --- a/public/_locales/id/messages.json +++ b/public/_locales/id/messages.json @@ -222,7 +222,7 @@ "message": "Tentukan perintah untuk dieksekusi berdasarkan teks yang dipilih." }, "Option_commands_desc_count": { - "message": " / 100 Catatan" + "message": " item" }, "Option_title": { "message": "Judul" diff --git a/public/_locales/it/messages.json b/public/_locales/it/messages.json index 94363fc4..5d869372 100644 --- a/public/_locales/it/messages.json +++ b/public/_locales/it/messages.json @@ -222,7 +222,7 @@ "message": "Definisce i comandi da eseguire in base al testo selezionato." }, "Option_commands_desc_count": { - "message": " / 100 elementi" + "message": " elementi" }, "Option_title": { "message": "Titolo" diff --git a/public/_locales/ja/messages.json b/public/_locales/ja/messages.json index f31492ac..ac4e67e6 100644 --- a/public/_locales/ja/messages.json +++ b/public/_locales/ja/messages.json @@ -222,7 +222,7 @@ "message": "選択したテキストを元に実行するコマンドを定義します。" }, "Option_commands_desc_count": { - "message": " / 100 件" + "message": " 件" }, "Option_title": { "message": "タイトル" diff --git a/public/_locales/ko/messages.json b/public/_locales/ko/messages.json index 9997794f..a29e72d7 100644 --- a/public/_locales/ko/messages.json +++ b/public/_locales/ko/messages.json @@ -222,7 +222,7 @@ "message": "선택한 텍스트를 기반으로 실행할 명령을 정의합니다." }, "Option_commands_desc_count": { - "message": " / 100개 항목" + "message": " 개" }, "Option_title": { "message": "제목" diff --git a/public/_locales/ms/messages.json b/public/_locales/ms/messages.json index c8b1640b..6dde455f 100644 --- a/public/_locales/ms/messages.json +++ b/public/_locales/ms/messages.json @@ -222,7 +222,7 @@ "message": "Tentukan arahan untuk dilaksanakan berdasarkan teks yang dipilih." }, "Option_commands_desc_count": { - "message": " / 100 Rekod" + "message": " item" }, "Option_title": { "message": "Tajuk" diff --git a/public/_locales/pt_BR/messages.json b/public/_locales/pt_BR/messages.json index c9b1aeb7..d19dd0e3 100644 --- a/public/_locales/pt_BR/messages.json +++ b/public/_locales/pt_BR/messages.json @@ -222,7 +222,7 @@ "message": "Definir comandos para executar com base no texto selecionado." }, "Option_commands_desc_count": { - "message": " / 100 Registros" + "message": " itens" }, "Option_title": { "message": "Título" diff --git a/public/_locales/pt_PT/messages.json b/public/_locales/pt_PT/messages.json index 918ab745..206ab9ad 100644 --- a/public/_locales/pt_PT/messages.json +++ b/public/_locales/pt_PT/messages.json @@ -222,7 +222,7 @@ "message": "Definir comandos para executar com base no texto selecionado." }, "Option_commands_desc_count": { - "message": " / 100 Registos" + "message": " itens" }, "Option_title": { "message": "Título" diff --git a/public/_locales/ru/messages.json b/public/_locales/ru/messages.json index 2f5c167a..6bb4ca45 100644 --- a/public/_locales/ru/messages.json +++ b/public/_locales/ru/messages.json @@ -222,7 +222,7 @@ "message": "Определяет команды для выполнения на основе выбранного текста." }, "Option_commands_desc_count": { - "message": " / 100 элементов" + "message": " элементов" }, "Option_title": { "message": "Название" diff --git a/public/_locales/zh_CN/messages.json b/public/_locales/zh_CN/messages.json index 13db41c1..84dfeaa9 100644 --- a/public/_locales/zh_CN/messages.json +++ b/public/_locales/zh_CN/messages.json @@ -222,7 +222,7 @@ "message": "定义基于选中文本执行的命令。" }, "Option_commands_desc_count": { - "message": " / 100个项目" + "message": " 个" }, "Option_title": { "message": "标题" diff --git a/src/components/Popup.module.css b/src/components/Popup.module.css index 4aed3138..3fa37371 100644 --- a/src/components/Popup.module.css +++ b/src/components/Popup.module.css @@ -19,31 +19,29 @@ --background: var(--sc-bg-color-h) var(--sc-bg-color-s) var(--sc-bg-color-l); --foreground: 222.2 47.4% 11.2%; --border: var(--sc-bd-color-h) var(--sc-bd-color-s) var(--sc-bd-color-l); - --accent: var(--sc-bg-color-h) var(--sc-bg-color-s) - calc(var(--sc-bg-color-l) - 6%); + --accent: var(--sc-bg-color-h) var(--sc-bg-color-s) calc(var(--sc-bg-color-l) - 6%); --accent-foreground: 224 71% 4%; } -.popup[data-state='open'] { +.popup[data-state="open"] { animation-duration: var(--sc-duration) !important; } -.popup[data-state='closed'] { +.popup[data-state="closed"] { animation-duration: 100ms !important; } .previewContainer { position: relative; - & > div { + &>div { transform: translate(0, 0) !important; } } .previewLabel { - font-family: var(--font-monospace); + @apply font-mono text-gray-800; font-size: 1.2rem; - color: #444; } .previewDescription { diff --git a/src/components/Popup.tsx b/src/components/Popup.tsx index 80b2ef19..81bbd4c6 100644 --- a/src/components/Popup.tsx +++ b/src/components/Popup.tsx @@ -1,14 +1,14 @@ -import { useState, useEffect, createContext, forwardRef } from 'react' -import { Popover, PopoverContent, PopoverAnchor } from '@/components/ui/popover' -import { Menu } from '@/components/menu/Menu' -import { useUserSettings } from '@/hooks/useSetting' -import { useDetectStartup } from '@/hooks/useDetectStartup' -import { useTabCommandReceiver } from '@/hooks/useTabCommandReceiver' -import { hexToHsl, isMac, onHover, cn } from '@/lib/utils' -import { t } from '@/services/i18n' -import { STYLE_VARIABLE, EXIT_DURATION, SIDE, ALIGN } from '@/const' +import { useState, useEffect, createContext, forwardRef } from "react" +import { Popover, PopoverContent, PopoverAnchor } from "@/components/ui/popover" +import { Menu } from "@/components/menu/Menu" +import { useUserSettings } from "@/hooks/useSetting" +import { useDetectStartup } from "@/hooks/useDetectStartup" +import { useTabCommandReceiver } from "@/hooks/useTabCommandReceiver" +import { hexToHsl, isMac, onHover, cn } from "@/lib/utils" +import { t } from "@/services/i18n" +import { STYLE_VARIABLE, EXIT_DURATION, SIDE, ALIGN } from "@/const" -import css from './Popup.module.css' +import css from "./Popup.module.css" export type PopupProps = { positionElm: Element | null @@ -46,7 +46,7 @@ export const Popup = forwardRef( userSettings?.userStyles && userSettings.userStyles.reduce((acc: any, cur: any) => { if (cur.value == null) return acc - if (cur.name === 'background-color' || cur.name === 'border-color') { + if (cur.name === "background-color" || cur.name === "border-color") { const hsl = hexToHsl(cur.value) return { ...acc, @@ -116,9 +116,10 @@ export const Popup = forwardRef( align={align} sideOffset={sideOffset} alignOffset={alignOffset} - className={cn(css.popup, isPreview && 'z-10')} + className={cn(css.popup, isPreview && "z-10 mt-2")} style={userStyles} onOpenAutoFocus={noFocus} + onCloseAutoFocus={noFocus} {...onHover(handleOnHover, true)} > {!isContextMenu ? : null} @@ -136,7 +137,7 @@ export function PreviewDesc(props: PopupProps) { const { userSettings } = useUserSettings() const key = userSettings?.startupMethod?.keyboardParam - const os = isMac() ? 'mac' : 'windows' + const os = isMac() ? "mac" : "windows" const keyLabel = t(`Option_keyboardParam_${key}_${os}`) return ( @@ -145,16 +146,16 @@ export function PreviewDesc(props: PopupProps) { Preview...

{isContextMenu && ( -

{t('previewOnContextMenu')}

+

{t("previewOnContextMenu")}

)} {!visible && isKeyboard && (

- {t('previewOnKeyboard', [keyLabel])} + {t("previewOnKeyboard", [keyLabel])}

)} {!visible && isLeftClickHold && (

- {t('previewOnLeftClickHold', [keyLabel])} + {t("previewOnLeftClickHold", [keyLabel])}

)} diff --git a/src/components/menu/Menu.tsx b/src/components/menu/Menu.tsx index 93a94052..fbdd370d 100644 --- a/src/components/menu/Menu.tsx +++ b/src/components/menu/Menu.tsx @@ -1,12 +1,11 @@ -import React, { useState, useRef } from "react" -import clsx from "clsx" +import React, { useState, useRef, useEffect } from "react" import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, } from "@/components/ui/menubar" -import { ScrollArea } from "@/components/ui/scroll-area" +import { ScrollAreaConditional } from "@/components/ui/scroll-area" import { STYLE, SIDE } from "@/const" import { MenuItem } from "./MenuItem" @@ -16,7 +15,7 @@ import { MenuImage } from "@/components/menu/MenuImage" import css from "./Menu.module.css" import type { Command, CommandFolder } from "@/types" import { useSettingsWithImageCache, useUserSettings } from "@/hooks/useSetting" -import { onHover, isMenuCommand } from "@/lib/utils" +import { cn, onHover, isMenuCommand } from "@/lib/utils" import { toCommandTree, type CommandTreeNode, @@ -48,7 +47,7 @@ export function Menu(): JSX.Element { return ( { const { folder, children, isHorizontal, depth = 0 } = props - const [hoverTrigger, setHoverTrigger] = useState("") - const [hoverContent, setHoverContent] = useState("") - const activeFolder = hoverTrigger || hoverContent + const [triggeredFolder, setTriggeredFolder] = useState("") + const [hoveredFolder, setHoveredFolder] = useState("") + const activeFolder = triggeredFolder || hoveredFolder const menuSide = isHorizontal ? props.side === SIDE.bottom @@ -129,32 +128,77 @@ const MenuFolder = (props: { const contentRef = useRef(null) const [anchorRect, setAnchorRect] = useState(null) const [contentRect, setContentRect] = useState(null) + const resizeObserverRef = useRef(null) + + const updateRects = () => { + if (anchorRef.current && contentRef.current) { + setAnchorRect(anchorRef.current.getBoundingClientRect()) + setContentRect(contentRef.current.getBoundingClientRect()) + } + } const onHoverTrigger = (enterVal: any) => { props.onHoverTrigger(enterVal) - // Delay to wait finishing animation. - setTimeout(() => { - if (anchorRef.current && contentRef.current) { - setAnchorRect(anchorRef.current?.getBoundingClientRect()) - setContentRect(contentRef.current?.getBoundingClientRect()) + + // Initial rect update + updateRects() + + // Setup ResizeObserver if not already setup + if (!resizeObserverRef.current) { + resizeObserverRef.current = new ResizeObserver(() => { + updateRects() + }) + + // Observe anchor element + if (anchorRef.current) { + resizeObserverRef.current.observe(anchorRef.current) } - }, 200) + + // Observe content element if available, otherwise retry once + if (contentRef.current) { + resizeObserverRef.current.observe(contentRef.current) + } else { + setTimeout(() => { + if (contentRef.current && resizeObserverRef.current) { + resizeObserverRef.current.observe(contentRef.current) + updateRects() + } + }, 50) + } + } } - const baseSize = anchorRef.current?.clientHeight ?? 0 + // Cleanup ResizeObserver on unmount + useEffect(() => { + return () => { + if (resizeObserverRef.current) { + resizeObserverRef.current.disconnect() + resizeObserverRef.current = null + } + } + }, []) + + // Also cleanup when folder changes + useEffect(() => { + if (resizeObserverRef.current) { + resizeObserverRef.current.disconnect() + resizeObserverRef.current = null + } + }, [folder.id]) + + const baseSize = anchorRef.current?.getBoundingClientRect().height ?? 0 const menubarStyle = isHorizontal ? { maxWidth: baseSize * 10 /* buttons */ + 1 * 9 /* gap */ + 2 * 2 /* padding */ + - 1 * 2 /* border */ + - 5, + 1 * 2 /* border */, } : { maxHeight: - baseSize * 11.4 /* buttons */ + - 2 * 11 /* gap */ + + baseSize * 11.5 /* buttons */ + + 1 * 10 /* gap */ + 2 * 2 /* padding */ + 1 * 2 /* border */, } @@ -162,7 +206,7 @@ const MenuFolder = (props: { return ( {!(folder.onlyIcon && isHorizontal) && ( - {folder.title} + {folder.title} )} {!isHorizontal && } e.preventDefault()} {...onHover(props.onHoverContent, folder.id)} > - {!isHorizontal ? ( - - - {children?.map((child) => ( - - ))} - - - ) : ( + @@ -225,14 +248,14 @@ const MenuFolder = (props: { isHorizontal={isHorizontal} side={props.side} menuRef={props.menuRef} - onHoverTrigger={setHoverTrigger} - onHoverContent={setHoverContent} + onHoverTrigger={setTriggeredFolder} + onHoverContent={setHoveredFolder} depth={depth + 1} key={child.content.id} /> ))} - )} + React.ReactNode okText: string + okDisabled?: boolean children?: React.ReactNode } @@ -40,14 +42,18 @@ export function Dialog(props: Props) { {props.children} - diff --git a/src/components/option/HubBanner.module.css b/src/components/option/HubBanner.module.css index 7dd2be32..7f1c8da0 100644 --- a/src/components/option/HubBanner.module.css +++ b/src/components/option/HubBanner.module.css @@ -1,7 +1,6 @@ .menuLabel { - font-family: var(--font-monospace); + @apply font-mono text-gray-800; font-size: 1.2rem; - color: #444; } .banner { @@ -16,7 +15,7 @@ } .description { + @apply font-mono text-gray-800; font-size: 0.8rem; - font-family: var(--font-family); padding-left: 4px; } diff --git a/src/components/option/ImportExport.tsx b/src/components/option/ImportExport.tsx index 8baac1a7..1899327d 100644 --- a/src/components/option/ImportExport.tsx +++ b/src/components/option/ImportExport.tsx @@ -276,15 +276,6 @@ export function ImportExport() { } const handleRestore = async () => { - const hasAnyBackup = Object.values(backupData).some( - (backup) => backup.status === BACKUP_STATUS.AVAILABLE, - ) - - if (!hasAnyBackup) { - alert(t("Option_RestoreFromBackup_no_data")) - return - } - setRestoreDialog(true) } @@ -379,11 +370,6 @@ export function ImportExport() { onClick={handleRestore} className={css.menuButton} type="button" - disabled={ - !Object.values(backupData).some( - (backup) => backup.status === BACKUP_STATUS.AVAILABLE, - ) - } title={ Object.values(backupData).every( (backup) => backup.status === BACKUP_STATUS.CHECKING, @@ -451,6 +437,11 @@ export function ImportExport() { return {t("Option_RestoreFromBackup_dialog_select")} }} okText={t("Option_RestoreFromBackup_dialog_restore")} + okDisabled={ + !Object.values(backupData).some( + (backup) => backup.status === BACKUP_STATUS.AVAILABLE, + ) + } > {(() => { const availableBackups = Object.entries(backupData).filter( diff --git a/src/components/option/Option.module.css b/src/components/option/Option.module.css index 2db517c5..1bd60ac8 100644 --- a/src/components/option/Option.module.css +++ b/src/components/option/Option.module.css @@ -1,6 +1,6 @@ :root { --font-family: - -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-color: #333; --menu-top: 150px; @@ -22,7 +22,7 @@ margin: 0; } -.titleSpan + .titleSpan { +.titleSpan+.titleSpan { margin-left: 4px; } @@ -45,9 +45,8 @@ } .menuLabel { - @apply font-mono; + @apply font-mono text-gray-800; font-size: 1.2rem; - color: #444; margin-bottom: 16px; } @@ -57,13 +56,13 @@ } .menuButton { - @apply font-mono flex items-center; + @apply flex items-center; + @apply font-mono text-gray-800; border: none; background: none; font-size: 0.9rem; padding: 6px 8px; border-radius: 4px; - color: #444; transition: background-color, ease-out 0.2s, @@ -77,7 +76,7 @@ } .buttonImport { - composes: button from '../Common.module.css'; + composes: button from "../Common.module.css"; width: 100%; padding: 6px 8px; @@ -93,11 +92,9 @@ .bgHatching { background-color: #fff; - background-image: repeating-linear-gradient( - 45deg, - #6b7280, - #6b7280 1px, - transparent 1px, - transparent 4px - ); + background-image: repeating-linear-gradient(45deg, + #6b7280, + #6b7280 1px, + transparent 1px, + transparent 4px); } diff --git a/src/components/option/Option.tsx b/src/components/option/Option.tsx index 72cab0fd..27707edb 100644 --- a/src/components/option/Option.tsx +++ b/src/components/option/Option.tsx @@ -1,17 +1,17 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect } from "react" -import { Settings } from '@/services/settings' -import { capitalize } from '@/lib/utils' -import { APP_ID, VERSION } from '@/const' +import { Settings } from "@/services/settings" +import { capitalize } from "@/lib/utils" +import { APP_ID, VERSION } from "@/const" -import { Popup } from '@/components/Popup' -import { TableOfContents } from '@/components/option/TableOfContents' -import { ImportExport } from '@/components/option/ImportExport' -import { HubBanner } from '@/components/option/HubBanner' -import { SettingForm } from '@/components/option/SettingForm' -import StorageUsage from '@/components/option/StorageUsage' +import { Popup } from "@/components/Popup" +import { TableOfContents } from "@/components/option/TableOfContents" +import { ImportExport } from "@/components/option/ImportExport" +import { HubBanner } from "@/components/option/HubBanner" +import { SettingForm } from "@/components/option/SettingForm" +import StorageUsage from "@/components/option/StorageUsage" -import css from './Option.module.css' +import css from "./Option.module.css" const SCROLL_OFFSET = 80 @@ -49,7 +49,7 @@ export function Option() { elm.getBoundingClientRect().top + window.scrollY - SCROLL_OFFSET window.scrollTo({ top: targetPosition, - behavior: 'smooth', + behavior: "smooth", }) } } @@ -58,7 +58,7 @@ export function Option() {

- {APP_ID.split('-').map((n) => { + {APP_ID.split("-").map((n) => { return ( {capitalize(n)} @@ -90,10 +90,10 @@ export function Option() { ref={setPopupElm} />

-
+
-
+
diff --git a/src/components/option/StorageUsage.tsx b/src/components/option/StorageUsage.tsx index 43b384ea..b6bc99ec 100644 --- a/src/components/option/StorageUsage.tsx +++ b/src/components/option/StorageUsage.tsx @@ -101,8 +101,8 @@ const StorageUsage: React.FC = () => { return (

Storage Usage

-
-

+
+

Sync Area

@@ -112,8 +112,8 @@ const StorageUsage: React.FC = () => { backgroundColor="#f3f4f6" className="w-full" /> -
-
+
+
System: {storageData.sync.systemPercent}%
@@ -134,8 +134,8 @@ const StorageUsage: React.FC = () => {
-
-

+
+

Local Area

@@ -145,7 +145,7 @@ const StorageUsage: React.FC = () => { backgroundColor="#f3f4f6" className="w-full" /> -
+
System: {storageData.local.systemPercent}% diff --git a/src/components/option/TableOfContents.module.css b/src/components/option/TableOfContents.module.css index 56b14923..e1faf6d3 100644 --- a/src/components/option/TableOfContents.module.css +++ b/src/components/option/TableOfContents.module.css @@ -1,7 +1,7 @@ .container { + @apply text-gray-800; margin: 0; padding: 0; - color: #444; } .label { diff --git a/src/components/option/editor/CommandListMenu.tsx b/src/components/option/editor/CommandListMenu.tsx index 12604e3c..23e59ed1 100644 --- a/src/components/option/editor/CommandListMenu.tsx +++ b/src/components/option/editor/CommandListMenu.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import { Button } from '@/components/ui/button' -import { Terminal, FolderPlus, Search } from 'lucide-react' -import { Tooltip } from '@/components/Tooltip' -import { HUB_URL } from '@/const' -import { t as _t } from '@/services/i18n' +import React from "react" +import { Button } from "@/components/ui/button" +import { Terminal, FolderPlus, Search } from "lucide-react" +import { Tooltip } from "@/components/Tooltip" +import { HUB_URL } from "@/const" +import { t as _t } from "@/services/i18n" const t = (key: string, p?: string[]) => _t(`Option_${key}`, p) interface Props { @@ -25,7 +25,7 @@ export const CommandListMenu: React.FC = ({
{commandCount ?? 0} - {t('commands_desc_count')} + {t("commands_desc_count")}
) -} \ No newline at end of file +} diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 10602807..ea4e7532 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -1,8 +1,8 @@ -'use client' +"use client" -import * as React from 'react' -import * as LabelPrimitive from '@radix-ui/react-label' -import { Slot } from '@radix-ui/react-slot' +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" import { Controller, ControllerProps, @@ -10,10 +10,10 @@ import { FieldValues, FormProvider, useFormContext, -} from 'react-hook-form' +} from "react-hook-form" -import { cn } from '@/lib/utils' -import { Label } from '@/components/ui/label' +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" const Form = FormProvider @@ -49,7 +49,7 @@ const useFormField = () => { const fieldState = getFieldState(fieldContext.name, formState) if (!fieldContext) { - throw new Error('useFormField should be used within ') + throw new Error("useFormField should be used within ") } const { id } = itemContext @@ -84,7 +84,7 @@ const FormItem = React.forwardRef< ) }) -FormItem.displayName = 'FormItem' +FormItem.displayName = "FormItem" const FormLabel = React.forwardRef< React.ElementRef, @@ -96,8 +96,8 @@ const FormLabel = React.forwardRef<