diff --git a/.changeset/mighty-mirrors-eat.md b/.changeset/mighty-mirrors-eat.md
new file mode 100644
index 0000000..5dddcbf
--- /dev/null
+++ b/.changeset/mighty-mirrors-eat.md
@@ -0,0 +1,5 @@
+---
+'anki-templates': minor
+---
+
+feat(all): tool support for ChatGPT, search, translation and customization (工具支持ChatGPT、搜索、翻译和自定义)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6a238ee..5056c3e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -28,6 +28,7 @@ jobs:
uses: changesets/action@v1
with:
publish: pnpm changeset tag
+ title: 'chore: release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/build/rollup.js b/build/rollup.js
index 5fec1a4..2471e2f 100644
--- a/build/rollup.js
+++ b/build/rollup.js
@@ -15,7 +15,6 @@ import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import fs from 'node:fs/promises';
import path from 'node:path';
-import * as R from 'remeda';
import postcss from 'rollup-plugin-postcss';
import { swc } from 'rollup-plugin-swc3';
import { visualizer } from 'rollup-plugin-visualizer';
@@ -31,7 +30,7 @@ export async function rollupOptions(config) {
.readFile(
path.resolve(
import.meta.dirname,
- '../locales/',
+ '../translations/',
`${config.locale}.json`,
),
{ encoding: 'utf8' },
@@ -42,13 +41,11 @@ export async function rollupOptions(config) {
input: 'entry',
plugins: [
virtual({
- 'at/options': dataToEsm(templates[config.id]),
- 'at/locale': dataToEsm({
+ 'at/options': dataToEsm({
+ ...templates[config.id],
locale: config.locale,
}),
- 'at/i18n': dataToEsm(
- R.mapKeys(i18nMap, (key) => `t${R.capitalize(key)}`),
- ),
+ 'at/i18n': dataToEsm(i18nMap),
entry: buildEntry(),
}),
replace({
@@ -122,7 +119,9 @@ export async function rollupOptions(config) {
].filter(Boolean),
}),
url(),
- visualizer(),
+ visualizer({
+ emitFile: true,
+ }),
html({
fileName: `front.html`,
template({ files }) {
@@ -164,6 +163,9 @@ ${buildFields()}
compress: {
drop_console: true,
},
+ format: {
+ comments: false,
+ },
}),
false,
),
diff --git a/package.json b/package.json
index 8d6f40b..b5fe9ac 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"description": "",
"scripts": {
"build": "node build/cli.js",
- "dev": "node build/cli.js --dev",
+ "dev": "ROLLUP_WATCH=true node build/cli.js --dev",
"lint": "pnpm eslint .",
"format": "pnpm eslint --fix .",
"test": "vitest"
@@ -58,7 +58,6 @@
},
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
- "@headlessui/react": "^2.1.4",
"ahooks": "^3.8.1",
"clsx": "^2.1.1",
"jotai": "^2.9.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 666cecd..7fd0d95 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,9 +11,6 @@ importers:
'@formkit/auto-animate':
specifier: ^0.8.2
version: 0.8.2
- '@headlessui/react':
- specifier: ^2.1.4
- version: 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ahooks:
specifier: ^3.8.1
version: 3.8.1(react@18.3.1)
@@ -452,37 +449,9 @@ packages:
'@fastify/deepmerge@1.3.0':
resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
- '@floating-ui/core@1.6.7':
- resolution: {integrity: sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==}
-
- '@floating-ui/dom@1.6.10':
- resolution: {integrity: sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==}
-
- '@floating-ui/react-dom@2.1.1':
- resolution: {integrity: sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==}
- peerDependencies:
- react: '>=16.8.0'
- react-dom: '>=16.8.0'
-
- '@floating-ui/react@0.26.23':
- resolution: {integrity: sha512-9u3i62fV0CFF3nIegiWiRDwOs7OW/KhSUJDNx2MkQM3LbE5zQOY01sL3nelcVBXvX7Ovvo3A49I8ql+20Wg/Hw==}
- peerDependencies:
- react: '>=16.8.0'
- react-dom: '>=16.8.0'
-
- '@floating-ui/utils@0.2.7':
- resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==}
-
'@formkit/auto-animate@0.8.2':
resolution: {integrity: sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==}
- '@headlessui/react@2.1.4':
- resolution: {integrity: sha512-tqxvLFJ2Cx0FWfHKSR0ENM3oa+ATvpGZR5OUswtMNRA7KbkNr4sE+oYM7HQS7zqaIoTu7OVBeQV/JO3pnez9JQ==}
- engines: {node: '>=10'}
- peerDependencies:
- react: ^18
- react-dom: ^18
-
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
@@ -546,37 +515,6 @@ packages:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- '@react-aria/focus@3.18.2':
- resolution: {integrity: sha512-Jc/IY+StjA3uqN73o6txKQ527RFU7gnG5crEl5Xy3V+gbYp2O5L3ezAo/E0Ipi2cyMbG6T5Iit1IDs7hcGu8aw==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
- '@react-aria/interactions@3.22.2':
- resolution: {integrity: sha512-xE/77fRVSlqHp2sfkrMeNLrqf2amF/RyuAS6T5oDJemRSgYM3UoxTbWjucPhfnoW7r32pFPHHgz4lbdX8xqD/g==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
- '@react-aria/ssr@3.9.5':
- resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==}
- engines: {node: '>= 12'}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
- '@react-aria/utils@3.25.2':
- resolution: {integrity: sha512-GdIvG8GBJJZygB4L2QJP1Gabyn2mjFsha73I2wSe+o4DYeGWoJiMZRM06PyTIxLH4S7Sn7eVDtsSBfkc2VY/NA==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
- '@react-stately/utils@3.10.3':
- resolution: {integrity: sha512-moClv7MlVSHpbYtQIkm0Cx+on8Pgt1XqtPx6fy9rQFb2DNc9u1G3AUVnqA17buOkH1vLxAtX4MedlxMWyRCYYA==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
- '@react-types/shared@3.24.1':
- resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
-
'@rollup/plugin-alias@5.1.0':
resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==}
engines: {node: '>=14.0.0'}
@@ -835,15 +773,6 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
- '@tanstack/react-virtual@3.10.6':
- resolution: {integrity: sha512-xaSy6uUxB92O8mngHZ6CvbhGuqxQ5lIZWCBy+FjhrbHmOwc6BnOnKkYm2FsB1/BpKw/+FVctlMbEtI+F6I1aJg==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-
- '@tanstack/virtual-core@3.10.6':
- resolution: {integrity: sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw==}
-
'@trivago/prettier-plugin-sort-imports@4.3.0':
resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==}
peerDependencies:
@@ -3354,9 +3283,6 @@ packages:
resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==}
engines: {node: ^14.18.0 || >=16.0.0}
- tabbable@6.2.0:
- resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
-
tailwindcss-animate@1.0.7:
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
@@ -4029,42 +3955,8 @@ snapshots:
'@fastify/deepmerge@1.3.0': {}
- '@floating-ui/core@1.6.7':
- dependencies:
- '@floating-ui/utils': 0.2.7
-
- '@floating-ui/dom@1.6.10':
- dependencies:
- '@floating-ui/core': 1.6.7
- '@floating-ui/utils': 0.2.7
-
- '@floating-ui/react-dom@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@floating-ui/dom': 1.6.10
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
- '@floating-ui/react@0.26.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@floating-ui/react-dom': 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@floating-ui/utils': 0.2.7
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- tabbable: 6.2.0
-
- '@floating-ui/utils@0.2.7': {}
-
'@formkit/auto-animate@0.8.2': {}
- '@headlessui/react@2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@floating-ui/react': 0.26.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@react-aria/focus': 3.18.2(react@18.3.1)
- '@react-aria/interactions': 3.22.2(react@18.3.1)
- '@tanstack/react-virtual': 3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/retry@0.3.0': {}
@@ -4139,46 +4031,6 @@ snapshots:
'@pkgr/core@0.1.1': {}
- '@react-aria/focus@3.18.2(react@18.3.1)':
- dependencies:
- '@react-aria/interactions': 3.22.2(react@18.3.1)
- '@react-aria/utils': 3.25.2(react@18.3.1)
- '@react-types/shared': 3.24.1(react@18.3.1)
- '@swc/helpers': 0.5.13
- clsx: 2.1.1
- react: 18.3.1
-
- '@react-aria/interactions@3.22.2(react@18.3.1)':
- dependencies:
- '@react-aria/ssr': 3.9.5(react@18.3.1)
- '@react-aria/utils': 3.25.2(react@18.3.1)
- '@react-types/shared': 3.24.1(react@18.3.1)
- '@swc/helpers': 0.5.13
- react: 18.3.1
-
- '@react-aria/ssr@3.9.5(react@18.3.1)':
- dependencies:
- '@swc/helpers': 0.5.13
- react: 18.3.1
-
- '@react-aria/utils@3.25.2(react@18.3.1)':
- dependencies:
- '@react-aria/ssr': 3.9.5(react@18.3.1)
- '@react-stately/utils': 3.10.3(react@18.3.1)
- '@react-types/shared': 3.24.1(react@18.3.1)
- '@swc/helpers': 0.5.13
- clsx: 2.1.1
- react: 18.3.1
-
- '@react-stately/utils@3.10.3(react@18.3.1)':
- dependencies:
- '@swc/helpers': 0.5.13
- react: 18.3.1
-
- '@react-types/shared@3.24.1(react@18.3.1)':
- dependencies:
- react: 18.3.1
-
'@rollup/plugin-alias@5.1.0(rollup@4.21.2)':
dependencies:
slash: 4.0.0
@@ -4352,6 +4204,7 @@ snapshots:
'@swc/helpers@0.5.13':
dependencies:
tslib: 2.7.0
+ optional: true
'@swc/types@0.1.12':
dependencies:
@@ -4370,14 +4223,6 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.10
- '@tanstack/react-virtual@3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@tanstack/virtual-core': 3.10.6
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
-
- '@tanstack/virtual-core@3.10.6': {}
-
'@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3)':
dependencies:
'@babel/generator': 7.17.7
@@ -7035,8 +6880,6 @@ snapshots:
'@pkgr/core': 0.1.1
tslib: 2.7.0
- tabbable@6.2.0: {}
-
tailwindcss-animate@1.0.7(tailwindcss@3.4.10):
dependencies:
tailwindcss: 3.4.10
diff --git a/src/components/about.tsx b/src/components/about.tsx
index be513c2..18858b1 100644
--- a/src/components/about.tsx
+++ b/src/components/about.tsx
@@ -1,5 +1,5 @@
import { version } from '../../package.json';
-import { locale } from 'at/locale';
+import { locale } from 'at/options';
import clsx from 'clsx';
export const EnAbout = () => (
diff --git a/src/components/block.tsx b/src/components/block.tsx
index e52bf44..24a99e0 100644
--- a/src/components/block.tsx
+++ b/src/components/block.tsx
@@ -1,19 +1,40 @@
+import { Button } from './button';
+import { ToolsContext } from './tools/context';
+import { selectionMenuAtom } from '@/store/settings';
import clsx from 'clsx';
-import { PropsWithChildren, ReactNode, forwardRef } from 'react';
+import { useAtomValue } from 'jotai/react';
+import { FC, PropsWithChildren, ReactNode } from 'react';
-export const Block = forwardRef<
- HTMLDivElement,
- PropsWithChildren & { name: ReactNode; className?: string; id?: string }
->(({ children, className, name, id }, ref) => (
-
-
{name}
-
- {children}
+export const Block: FC<
+ PropsWithChildren & {
+ name: ReactNode;
+ action?: string;
+ onAction?: () => void;
+ className?: string;
+ id?: string;
+ enableTools?: boolean;
+ }
+> = ({ children, className, name, id, enableTools, action, onAction }, ref) => {
+ const prefSelectionMenu = useAtomValue(selectionMenuAtom);
+
+ return (
+
+
+ {name}
+ {action ? {action} : null}
+
+
+ {enableTools && prefSelectionMenu ? (
+ {children}
+ ) : (
+ children
+ )}
+
-
-));
+ );
+};
diff --git a/src/components/button.tsx b/src/components/button.tsx
index 8fa625e..0507d45 100644
--- a/src/components/button.tsx
+++ b/src/components/button.tsx
@@ -1,16 +1,24 @@
import clsx from 'clsx';
import { FC, PropsWithChildren } from 'react';
-export const Button: FC<
- PropsWithChildren & { onClick?: () => void; className?: string }
-> = ({ onClick, children, className }) => (
+interface ButtonProps {
+ status?: 'normal' | 'danger';
+ onClick?: () => void;
+ className?: string;
+}
+
+export const Button: FC
= ({
+ status = 'normal',
+ onClick,
+ children,
+ className,
+}) => (
{children}
diff --git a/src/components/card-shell.tsx b/src/components/card-shell.tsx
index 0e3feb2..6d8765f 100644
--- a/src/components/card-shell.tsx
+++ b/src/components/card-shell.tsx
@@ -2,22 +2,17 @@ import { About } from './about';
import { Block } from './block';
import { Dot } from './dot';
import { AnkiField } from './field';
-import { SelectionMenu } from './selection-menu';
-import {
- Settings,
- biggerTextAtom,
- hideAboutAtom,
- noScorllAtom,
- selectionMenuAtom,
-} from './settings';
import { TimerBlock } from './timer';
import { useBack } from '@/hooks/use-back';
import { useField } from '@/hooks/use-field';
-import { tAbout, tAnswer, tBack, tTemplateSetting } from 'at/i18n';
-import { locale } from 'at/locale';
+import { Page, PageContext } from '@/hooks/use-page';
+import { DEFAULT_PAGES } from '@/pages';
+import { biggerTextAtom, hideAboutAtom, noScorllAtom } from '@/store/settings';
+import * as t from 'at/i18n';
+import { locale } from 'at/options';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
-import { FC, ReactNode, useRef, useState } from 'react';
+import { FC, ReactNode, useState } from 'react';
interface Props {
header?: ReactNode;
@@ -32,18 +27,15 @@ export const CardShell: FC = ({
questionExtra,
answer,
}) => {
- const prefSelectionMenu = useAtomValue(selectionMenuAtom);
const prefHideAbout = useAtomValue(hideAboutAtom);
const prefBiggerText = useAtomValue(biggerTextAtom);
const prefNoScroll = useAtomValue(noScorllAtom);
-
const [back] = useBack();
- const questionRef = useRef(null);
-
const tags = useField('Tags')?.split(' ');
- const [showSettings, setShowSettings] = useState(false);
+ const [page, setPage] = useState(Page.Index);
+ const PageComponent = DEFAULT_PAGES[page];
return (
= ({
)}
>
{header}
- {showSettings ? (
-
- {tTemplateSetting}
- setShowSettings(false)}
- >
- {tBack}
-
-
- }
- >
-
-
- ) : null}
-
-
+
+ {PageComponent ? : null}
+
+
{title}
{tags?.length ? (
@@ -85,35 +60,35 @@ export const CardShell: FC = ({
>
) : null}
- setShowSettings(true)}
- >
- {tTemplateSetting}
-
-
- }
- className="relative"
- >
- {prefSelectionMenu ? : null}
-
- {questionExtra}
-
- {back && answer ? (
-
- {answer}
-
- ) : null}
-
- {prefHideAbout ? null : (
-
-
+ }
+ action={t.templateSetting}
+ onAction={() => setPage(Page.Settings)}
+ className="relative"
+ enableTools
+ >
+
+ {questionExtra}
- )}
-
+ {back && answer ? (
+
+ {answer}
+
+ ) : null}
+
+ {prefHideAbout ? null : (
+
+
+
+ )}
+
+
);
};
diff --git a/src/components/checkbox.tsx b/src/components/checkbox.tsx
index c1edfd0..171dad4 100644
--- a/src/components/checkbox.tsx
+++ b/src/components/checkbox.tsx
@@ -1,5 +1,5 @@
import clsx from 'clsx';
-import { FC, useId } from 'react';
+import { FC, ReactNode, useId } from 'react';
import { doNothing } from 'remeda';
export const Checkbox: FC<{
@@ -7,7 +7,7 @@ export const Checkbox: FC<{
checked?: boolean;
className?: string;
title: string;
- subtitle?: string;
+ subtitle?: ReactNode;
disabled?: boolean;
}> = ({ onChange, className, checked, title, subtitle, disabled }) => {
const id = useId();
diff --git a/src/components/field.tsx b/src/components/field.tsx
index d1b40a5..16b6164 100644
--- a/src/components/field.tsx
+++ b/src/components/field.tsx
@@ -1,7 +1,7 @@
-import { FIELD_ID } from '@/utils/const';
+import { FIELD_ID, FIELDS_CONTAINER_ID } from '@/utils/const';
import useCreation from 'ahooks/es/useCreation';
import clsx from 'clsx';
-import { FC, memo, useCallback, useId } from 'react';
+import { FC, memo, useCallback, useEffect, useId } from 'react';
export const AnkiField: FC<{
name: string;
@@ -22,14 +22,14 @@ export const AnkiField: FC<{
[fieldNode],
);
- // useEffect(() => {
- // return () => {
- // if (fieldNode) {
- // fieldNode.remove();
- // document.getElementById(FIELDS_CONTAINER_ID)?.appendChild(fieldNode);
- // }
- // };
- // }, [fieldNode]);
+ useEffect(() => {
+ return () => {
+ if (fieldNode) {
+ fieldNode.remove();
+ document.getElementById(FIELDS_CONTAINER_ID)?.appendChild(fieldNode);
+ }
+ };
+ }, [fieldNode]);
const styleId = useId();
@@ -39,6 +39,7 @@ export const AnkiField: FC<{
id={`anki-field-${name}`}
className={clsx(
'anki-field',
+ 'first:!mt-0 last:!mb-0',
'overflow-x-auto',
'prose prose-neutral dark:prose-invert',
styleId,
diff --git a/src/components/link-button.tsx b/src/components/link-button.tsx
deleted file mode 100644
index 151eac2..0000000
--- a/src/components/link-button.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FC } from 'react';
-
-export const LinkButton: FC<{ image: string; href: string }> = ({
- image,
- href,
-}) => {
- return (
-
- );
-};
diff --git a/src/components/menu-buttons.tsx b/src/components/menu-buttons.tsx
deleted file mode 100644
index e8d5d21..0000000
--- a/src/components/menu-buttons.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { LinkButton } from './link-button';
-import imgBaiduFanyi from '@/assets/baidu-fanyi.png';
-import imgBaidu from '@/assets/baidu.png';
-import imgGoogleTranslate from '@/assets/google-translate.svg';
-import imgGoogle from '@/assets/google.svg';
-import { locale } from 'at/locale';
-import { FC } from 'react';
-
-const BaiduTranslate: FC<{ sel: string }> = ({ sel }) => (
-
-);
-
-const BaiduSearch: FC<{ sel: string }> = ({ sel }) => (
-
-);
-
-const GoogleTranslate: FC<{ sel: string }> = ({ sel }) => (
-
-);
-
-const GoogleSearch: FC<{ sel: string }> = ({ sel }) => (
-
-);
-
-export const TranslateButton =
- locale === 'zh' ? BaiduTranslate : GoogleTranslate;
-export const SearchButton = locale === 'zh' ? BaiduSearch : GoogleSearch;
diff --git a/src/components/modal.tsx b/src/components/modal.tsx
deleted file mode 100644
index d8e01f3..0000000
--- a/src/components/modal.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Dialog, Transition } from '@headlessui/react';
-import clsx from 'clsx';
-import { FC, Fragment, PropsWithChildren } from 'react';
-import { doNothing } from 'remeda';
-
-export const Modal: FC<
- PropsWithChildren & {
- isOpen?: boolean;
- onClose?: () => void;
- title?: string;
- }
-> = ({ isOpen, onClose = doNothing, title, children }) => {
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- {title}
-
- {children}
-
-
-
-
-
-
- >
- );
-};
diff --git a/src/components/selection-menu.tsx b/src/components/selection-menu.tsx
deleted file mode 100644
index 297d8ee..0000000
--- a/src/components/selection-menu.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { SearchButton, TranslateButton } from './menu-buttons';
-import { useTextSelection } from '@/hooks/use-text-selection';
-import { FC, PropsWithChildren, RefObject, useEffect, useState } from 'react';
-import { useThrottle } from 'react-use';
-
-export const SelectionMenu: FC<
- PropsWithChildren & { target: RefObject }
-> = ({ target }) => {
- const { clientRect, textContent, isCollapsed } = useTextSelection(target);
-
- const [open, setOpen] = useState(false);
-
- useEffect(() => {
- if (textContent?.trim() === '') {
- setOpen(false);
- return;
- }
- setOpen(true);
- }, [textContent]);
-
- const throttledRect = useThrottle(clientRect, 60);
-
- if (!open || !throttledRect || isCollapsed || !textContent) {
- return null;
- }
-
- const { top, left, width, height } = throttledRect;
-
- return (
-
- );
-};
diff --git a/src/components/timer.tsx b/src/components/timer.tsx
index d3c18d4..874b7ed 100644
--- a/src/components/timer.tsx
+++ b/src/components/timer.tsx
@@ -1,22 +1,10 @@
import { Block } from './block';
import { Input } from './input';
-import { hideTimerAtom } from './settings';
-import { atomWithLocalStorage } from '@/utils/storage';
+import { hideTimerAtom } from '@/store/settings';
+import { atomWithScopedStorage } from '@/utils/storage';
import useCountDown from 'ahooks/es/useCountDown';
import useCreation from 'ahooks/es/useCreation';
-import {
- tClose,
- tDay,
- tDefaultTimerTitle,
- tHour,
- tMinute,
- tSecond,
- tSetting,
- tTargetDate,
- tTimer,
- tTimerSetting,
- tTimerTitle,
-} from 'at/i18n';
+import * as t from 'at/i18n';
import { useAtom, useAtomValue } from 'jotai';
import { FC, useState } from 'react';
@@ -30,10 +18,10 @@ export const Timer: FC = () => {
const { days, hours, minutes, seconds } = formattedRes;
return [
- [tDay, days],
- [tHour, hours],
- [tMinute, minutes],
- [tSecond, seconds],
+ [t.day, days],
+ [t.hour, hours],
+ [t.minute, minutes],
+ [t.second, seconds],
];
}, [formattedRes]);
@@ -64,10 +52,10 @@ interface TimerProps {
const defaultTimerProps = {
targetDate: '2023-12-31',
- title: tDefaultTimerTitle,
+ title: t.defaultTimerTitle,
};
-export const timerAtom = atomWithLocalStorage(
+export const timerAtom = atomWithScopedStorage(
'timer',
defaultTimerProps,
);
@@ -84,22 +72,14 @@ export const TimerBlock = () => {
return (
- {showSetting ? tTimerSetting : tTimer}
- setShowSetting((p) => !p)}
- >
- {showSetting ? tClose : tSetting}
-
-
- }
+ name={{showSetting ? t.timerSetting : t.timer} }
+ action={showSetting ? t.close : t.setting}
+ onAction={() => setShowSetting((prev) => !prev)}
>
{showSetting ? (
<>
setTimer((prevTimer) => ({
@@ -111,7 +91,7 @@ export const TimerBlock = () => {
/>
setTimer((prevTimer) => ({
diff --git a/src/components/tools/context.tsx b/src/components/tools/context.tsx
new file mode 100644
index 0000000..30c114c
--- /dev/null
+++ b/src/components/tools/context.tsx
@@ -0,0 +1,52 @@
+import { useTextSelection } from '@/hooks/use-text-selection';
+import { toolsAtom } from '@/store/tools';
+import { getUrl } from '@/utils/tool';
+import { useAtomValue } from 'jotai/react';
+import { FC, PropsWithChildren, useMemo, useRef } from 'react';
+import { useThrottle } from 'react-use';
+
+export const ToolsContext: FC = ({ children }) => {
+ const ref = useRef(null);
+ const { clientRect, textContent } = useTextSelection(ref);
+ const throttledRect = useThrottle(clientRect, 60);
+ const text = textContent?.trim();
+ const tools = useAtomValue(toolsAtom);
+
+ const toolsPopover = useMemo(() => {
+ if (!throttledRect || !text) {
+ return null;
+ }
+ const { top, left, height } = throttledRect;
+ return (
+
+ );
+ }, [throttledRect, text, tools]);
+
+ return (
+
+ {children}
+ {toolsPopover}
+
+ );
+};
diff --git a/src/components/tools/edit.tsx b/src/components/tools/edit.tsx
new file mode 100644
index 0000000..d6e7928
--- /dev/null
+++ b/src/components/tools/edit.tsx
@@ -0,0 +1,66 @@
+import { Button } from '../button';
+import { Input } from '../input';
+import { Tool } from '@/store/tools';
+import * as t from 'at/i18n';
+import { FC, useState } from 'react';
+
+export const ToolEdit: FC<{
+ initialTool: Tool;
+ onSave: (tool: Tool) => void;
+ onCancel: () => void;
+}> = ({ initialTool, onSave, onCancel }) => {
+ const [tool, setTool] = useState(initialTool);
+
+ return (
+ <>
+
+ setTool((prevTool) => ({
+ ...prevTool,
+ name: value,
+ }))
+ }
+ className="mt-2"
+ />
+
+ setTool((prevTool) => ({
+ ...prevTool,
+ url: value,
+ }))
+ }
+ className="mt-2"
+ />
+
+ setTool((prevTool) => ({
+ ...prevTool,
+ prefixText: value,
+ }))
+ }
+ className="mt-2"
+ />
+
+ setTool((prevTool) => ({
+ ...prevTool,
+ suffixText: value,
+ }))
+ }
+ className="mt-2"
+ />
+
+ onCancel()}>{t.cancel}
+ onSave(tool)}>{t.save}
+
+ >
+ );
+};
diff --git a/src/entries/basic.tsx b/src/entries/basic.tsx
index 414e0f7..85bf797 100644
--- a/src/entries/basic.tsx
+++ b/src/entries/basic.tsx
@@ -2,7 +2,7 @@ import { CardShell } from '../components/card-shell';
import { AnkiField } from '@/components/field';
import { FIELD_ID } from '@/utils/const';
import { isFieldEmpty } from '@/utils/field';
-import { tQuestion } from 'at/i18n';
+import * as t from 'at/i18n';
import clsx from 'clsx';
export default () => {
@@ -11,7 +11,7 @@ export default () => {
return (
diff --git a/src/entries/mcq.tsx b/src/entries/mcq.tsx
index 3e5e2f8..2d9fc7b 100644
--- a/src/entries/mcq.tsx
+++ b/src/entries/mcq.tsx
@@ -1,14 +1,14 @@
import { CardShell } from '../components/card-shell';
import { AnkiField } from '../components/field';
+import { useBack } from '../hooks/use-back';
+import { useCrossState } from '../hooks/use-cross-state';
+import { useField } from '../hooks/use-field';
import {
biggerTextAtom,
blurOptionsAtom,
hideQuestionTypeAtom,
-} from '../components/settings';
-import { useBack } from '../hooks/use-back';
-import { useCrossState } from '../hooks/use-cross-state';
-import { useField } from '../hooks/use-field';
-import { randomOptionsAtom } from '@/components/settings';
+ randomOptionsAtom,
+} from '@/store/settings';
import '@/styles/mcq.css';
import { flipToBack } from '@/utils/bridge';
import { FIELD_ID } from '@/utils/const';
@@ -17,15 +17,8 @@ import { useAutoAnimate } from '@formkit/auto-animate/preact';
import useCreation from 'ahooks/es/useCreation';
import useMemoizedFn from 'ahooks/es/useMemoizedFn';
import useSelections from 'ahooks/es/useSelections';
-import {
- tCorrectAnswer,
- tMissedAnswer,
- tMultipleAnswer,
- tQuestion,
- tSingleAnswer,
- tWrongAnswer,
-} from 'at/i18n';
-import { locale } from 'at/locale';
+import * as t from 'at/i18n';
+import { locale } from 'at/options';
import { fields } from 'at/options';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
@@ -33,9 +26,9 @@ import { useEffect } from 'react';
import { doNothing, shuffle } from 'remeda';
const ANSWER_TYPE_MAP = {
- missedAnswer: tMissedAnswer,
- correctAnswer: tCorrectAnswer,
- wrongAnswer: tWrongAnswer,
+ missedAnswer: t.missedAnswer,
+ correctAnswer: t.correctAnswer,
+ wrongAnswer: t.wrongAnswer,
};
const fieldToAlpha = (field: string) => field.slice(field.length - 1);
@@ -124,9 +117,9 @@ export default () => {
{isMultipleChoice ? tMultipleAnswer : tSingleAnswer}>
+ <>{isMultipleChoice ? t.multipleAnswer : t.singleAnswer}>
)
}
questionExtra={
diff --git a/src/entries/tf.tsx b/src/entries/tf.tsx
index ab16ec4..d4ee981 100644
--- a/src/entries/tf.tsx
+++ b/src/entries/tf.tsx
@@ -7,7 +7,7 @@ import { extractTfItems } from '@/utils/extract-tf-items';
import { isFieldEmpty } from '@/utils/field';
import useCreation from 'ahooks/es/useCreation';
import useMemoizedFn from 'ahooks/es/useMemoizedFn';
-import { tQuestion, tYourWrongAnswer } from 'at/i18n';
+import * as t from 'at/i18n';
import clsx from 'clsx';
import { CheckCircle, XCircle, Triangle } from 'lucide-react';
import { useCallback } from 'react';
@@ -134,13 +134,13 @@ export default () => {
return (
{items}
{back ? (
- {tYourWrongAnswer}
+ {t.yourWrongAnswer}
>;
+
+export const PageContext = createContext<{
+ page: Page;
+ setPage: (page: Page) => void;
+}>({
+ page: Page.Index,
+ setPage: doNothing,
+});
+
+export const usePage = () => {
+ return useContext(PageContext).page;
+};
+
+export const useNavigate = () => {
+ return useContext(PageContext).setPage;
+};
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..df4ba18
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,8 @@
+import SettingsPage from './settings';
+import ToolsPage from './tools';
+import { Page, PageMap } from '@/hooks/use-page';
+
+export const DEFAULT_PAGES: PageMap = {
+ [Page.Settings]: SettingsPage,
+ [Page.Tools]: ToolsPage,
+};
diff --git a/src/components/settings.tsx b/src/pages/settings.tsx
similarity index 54%
rename from src/components/settings.tsx
rename to src/pages/settings.tsx
index 6f71555..61c2679 100644
--- a/src/components/settings.tsx
+++ b/src/pages/settings.tsx
@@ -1,76 +1,61 @@
-import { About } from './about';
-import { Checkbox } from './checkbox';
-import { atomWithLocalStorage } from '@/utils/storage';
+import { About } from '@/components/about';
+import { Block } from '@/components/block';
+import { Checkbox } from '@/components/checkbox';
+import { Page, useNavigate } from '@/hooks/use-page';
import {
- tBiggerText,
- tBlurOptions,
- tBlurOptionsDetail,
- tHideAbout,
- tHideQuestionType,
- tHideQuestionTypeDetail,
- tHideTimer,
- tNoScroll,
- tRandomOption,
- tRandomOptionDetail,
- tSelMenu,
- tSelMenuDetail,
-} from 'at/i18n';
+ biggerTextAtom,
+ blurOptionsAtom,
+ hideAboutAtom,
+ hideQuestionTypeAtom,
+ hideTimerAtom,
+ noScorllAtom,
+ randomOptionsAtom,
+ selectionMenuAtom,
+} from '@/store/settings';
+import * as t from 'at/i18n';
import { id } from 'at/options';
import { useAtom } from 'jotai';
import { FC } from 'react';
-export const randomOptionsAtom = atomWithLocalStorage(
- 'randomOptions',
- true,
-);
-export const selectionMenuAtom = atomWithLocalStorage(
- 'selectionMenu',
- true,
-);
-export const hideAboutAtom = atomWithLocalStorage('hideAbout', false);
-export const biggerTextAtom = atomWithLocalStorage(
- 'biggerText',
- false,
-);
-export const hideTimerAtom = atomWithLocalStorage('hideTimer', false);
-export const hideQuestionTypeAtom = atomWithLocalStorage(
- 'hideQuestionType',
- false,
-);
-export const noScorllAtom = atomWithLocalStorage('noScorll', true);
-export const blurOptionsAtom = atomWithLocalStorage(
- 'blurOptions',
- false,
-);
-
const CommonOptions: FC = () => {
const [selectionMenu, setSelectionMenu] = useAtom(selectionMenuAtom);
const [hideAbout, setHideAbout] = useAtom(hideAboutAtom);
const [biggerText, setBiggerText] = useAtom(biggerTextAtom);
const [hideTimer, setHideTimer] = useAtom(hideTimerAtom);
const [noScorll, setNoScorll] = useAtom(noScorllAtom);
+ const navigate = useNavigate();
return (
<>
-
-
+ {t.selMenuDetail}
+ navigate(Page.Tools)}
+ >
+ {t.setting}
+
+
+ }
checked={selectionMenu}
onChange={setSelectionMenu}
/>
+
+
@@ -91,20 +76,20 @@ if (id === 'mcq') {
return (
<>
@@ -120,14 +105,19 @@ if (id === 'mcq') {
OptionList = () => null;
}
-export const Settings: FC = () => {
+export default () => {
+ const navigate = useNavigate();
return (
- <>
+ navigate(Page.Index)}
+ >
- >
+
);
};
diff --git a/src/pages/tools.tsx b/src/pages/tools.tsx
new file mode 100644
index 0000000..a078dc9
--- /dev/null
+++ b/src/pages/tools.tsx
@@ -0,0 +1,85 @@
+import { Block } from '@/components/block';
+import { Button } from '@/components/button';
+import { ToolEdit } from '@/components/tools/edit';
+import { useNavigate, Page } from '@/hooks/use-page';
+import { Tool, toolsAtom } from '@/store/tools';
+import * as t from 'at/i18n';
+import { useAtom } from 'jotai/react';
+import { Pencil, Trash2 } from 'lucide-react';
+import { useState } from 'react';
+
+export default () => {
+ const navigate = useNavigate();
+ const [tools, setTools] = useAtom(toolsAtom);
+ const [edit, setEdit] = useState(null);
+
+ const onSave = (tool: Tool) => {
+ const { id } = tool;
+ setTools((prevTools) => {
+ const idx = prevTools.findIndex((item) => item.id === id);
+ if (idx < 0) {
+ prevTools.push(tool);
+ } else {
+ prevTools[idx] = { ...tool };
+ }
+ return prevTools.slice();
+ });
+ setEdit(null);
+ };
+
+ const onDelete = (tool: Tool) => {
+ if (!confirm(t.confirmDelete)) {
+ return;
+ }
+ setTools((prevTools) => {
+ return prevTools.filter((item) => item.id !== tool.id);
+ });
+ };
+
+ const onAdd = () => {
+ setEdit({
+ id: Math.random().toString(36).slice(2),
+ });
+ };
+
+ return (
+ <>
+ navigate(Page.Settings)}
+ >
+ {edit ? (
+ setEdit(null)}
+ />
+ ) : (
+
+
{t.add}
+ {tools.map((tool) => (
+
+
{tool.name}
+
+
setEdit(tool)}>
+
+
+
onDelete(tool)}>
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ >
+ );
+};
diff --git a/src/store.ts b/src/store/index.ts
similarity index 100%
rename from src/store.ts
rename to src/store/index.ts
diff --git a/src/store/settings.ts b/src/store/settings.ts
new file mode 100644
index 0000000..2377eb1
--- /dev/null
+++ b/src/store/settings.ts
@@ -0,0 +1,25 @@
+import { atomWithScopedStorage } from '@/utils/storage';
+
+export const randomOptionsAtom = atomWithScopedStorage(
+ 'randomOptions',
+ true,
+);
+export const selectionMenuAtom = atomWithScopedStorage(
+ 'selectionMenu',
+ true,
+);
+export const hideAboutAtom = atomWithScopedStorage('hideAbout', false);
+export const biggerTextAtom = atomWithScopedStorage(
+ 'biggerText',
+ false,
+);
+export const hideTimerAtom = atomWithScopedStorage('hideTimer', false);
+export const hideQuestionTypeAtom = atomWithScopedStorage(
+ 'hideQuestionType',
+ false,
+);
+export const noScorllAtom = atomWithScopedStorage('noScorll', true);
+export const blurOptionsAtom = atomWithScopedStorage(
+ 'blurOptions',
+ false,
+);
diff --git a/src/store/tools.ts b/src/store/tools.ts
new file mode 100644
index 0000000..c0305fa
--- /dev/null
+++ b/src/store/tools.ts
@@ -0,0 +1,38 @@
+import { atomWithGlobalStorage } from '@/utils/storage';
+import * as t from 'at/i18n';
+import { locale } from 'at/options';
+
+export interface Tool {
+ id: string;
+ name?: string;
+ prefixText?: string;
+ suffixText?: string;
+ url?: string;
+}
+
+export const DEFAULT_TOOLS: Tool[] = [
+ {
+ id: 'search',
+ name: t.search,
+ url:
+ locale === 'zh'
+ ? 'https://www.baidu.com/#wd={q}'
+ : 'https://www.google.com/search?q={q}',
+ },
+ {
+ id: 'translate',
+ name: t.translate,
+ url:
+ locale === 'zh'
+ ? 'https://fanyi.baidu.com/#en/zh/{q}'
+ : 'https://translate.google.com/?sl=auto&text={q}&op=translate',
+ },
+ {
+ id: 'chatgpt',
+ name: 'ChatGPT',
+ prefixText: t.explainFollowing,
+ url: 'https://chatgpt.com/?q={q}',
+ },
+];
+
+export const toolsAtom = atomWithGlobalStorage('tools', DEFAULT_TOOLS);
diff --git a/src/utils/storage.ts b/src/utils/storage.ts
index 7458940..b78590b 100644
--- a/src/utils/storage.ts
+++ b/src/utils/storage.ts
@@ -3,7 +3,16 @@ import { atomWithStorage, createJSONStorage } from 'jotai/utils';
const storage = createJSONStorage(() => localStorage);
-export const atomWithLocalStorage = (key: string, initialValue: T) =>
- atomWithStorage(`at:${id}:${key}`, initialValue, storage, {
- getOnInit: true,
- });
+export const atomWithScopedStorage =
+ /*@__NO_SIDE_EFFECTS__*/
+ (key: string, initialValue: T) =>
+ atomWithStorage(`at:${id}:${key}`, initialValue, storage, {
+ getOnInit: true,
+ });
+
+export const atomWithGlobalStorage =
+ /*@__NO_SIDE_EFFECTS__*/
+ (key: string, initialValue: T) =>
+ atomWithStorage(`at:_global:${key}`, initialValue, storage, {
+ getOnInit: true,
+ });
diff --git a/src/utils/tool.ts b/src/utils/tool.ts
new file mode 100644
index 0000000..c4802e5
--- /dev/null
+++ b/src/utils/tool.ts
@@ -0,0 +1,15 @@
+import { Tool } from '@/store/tools';
+
+export function getUrl(tool: Tool, text: string) {
+ const { url, prefixText, suffixText } = tool;
+ let q = '';
+ if (prefixText) {
+ q = prefixText;
+ }
+ q += text;
+ if (suffixText) {
+ q += suffixText;
+ }
+ q = encodeURIComponent(q);
+ return url?.replace('{q}', q);
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 00452f1..0a9b43d 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-require-imports */
-import en from './locales/en.json';
-import zh from './locales/zh.json';
+import en from './translations/en.json';
+import zh from './translations/zh.json';
/** @type {import('tailwindcss').Config} */
module.exports = {
diff --git a/locales/en.json b/translations/en.json
similarity index 58%
rename from locales/en.json
rename to translations/en.json
index 6dc0a8d..4cc6ab2 100644
--- a/locales/en.json
+++ b/translations/en.json
@@ -12,7 +12,7 @@
"randomOption": "Random option",
"randomOptionDetail": "The options after random shuffling will be displayed, and the original order will be restored after displaying the answer",
"selMenu": "Text selection menu",
- "selMenuDetail": "After selecting the text, a menu will pop up, and you can click to quickly jump to Google Search or Google Translate to view information related to the selected text",
+ "selMenuDetail": "After selecting the text, a menu will pop up, and you can click to quickly jump to tools like Google Search or Google Translate to view information related to the selected text",
"hideAbout": "Hide “About”",
"biggerText": "Larger question text",
"day": "Days",
@@ -34,5 +34,19 @@
"noScroll": "Don't auto scroll when flipping",
"blurOptions": "Blur options",
"blurOptionsDetail": "When enabled, options will be blurred and will become clear after clicking on the option area.",
- "yourWrongAnswer": "Your wrong answer"
+ "yourWrongAnswer": "Your wrong answer",
+ "help": "Help",
+ "tool": "Tool",
+ "add": "Add",
+ "name": "Name",
+ "url": "URL",
+ "prefixText": "Prefix Text",
+ "suffixText": "Suffix Text",
+ "cancel": "Cancel",
+ "save": "Save",
+ "confirmDelete": "Are you sure to delete?",
+ "translate": "Translate",
+ "search": "Search",
+ "explainFollowing": "Explain the following content: ",
+ "toolHelp": "Each tool requires a URL to be set. For example, Google Search:
https://www.google.com/search?q={q}
When you select text and click the corresponding tool, the {q}
in the link will be replaced with the selected text, and the browser will automatically navigate to the updated link.
Prefix text and suffix text refer to adding text before and after the selected text, which is then used as {q}
for replacement.
"
}
diff --git a/locales/zh.json b/translations/zh.json
similarity index 58%
rename from locales/zh.json
rename to translations/zh.json
index 2148a05..0e7177a 100644
--- a/locales/zh.json
+++ b/translations/zh.json
@@ -12,7 +12,7 @@
"randomOption": "随机选项",
"randomOptionDetail": "开启后展示随机打乱的选项,显示答案后恢复至原顺序",
"selMenu": "选中菜单",
- "selMenuDetail": "选中题目文字后弹出菜单,点击可快速跳转至百度翻译、百度搜索查看选中文字相关的信息",
+ "selMenuDetail": "选中题目文字后弹出菜单,点击可快速跳转至百度翻译、百度搜索等工具查看选中文字相关的信息",
"hideAbout": "隐藏首页“关于”",
"biggerText": "大号题目文字",
"day": "天",
@@ -34,5 +34,19 @@
"noScroll": "翻转时不要自动滚动",
"blurOptions": "选项模糊",
"blurOptionsDetail": "开启后选项会被模糊,点击选项区域后恢复",
- "yourWrongAnswer": "你的错误答案"
+ "yourWrongAnswer": "你的错误答案",
+ "help": "帮助",
+ "tool": "工具",
+ "add": "添加",
+ "name": "名称",
+ "url": "链接",
+ "prefixText": "前置文本",
+ "suffixText": "后置文本",
+ "cancel": "取消",
+ "save": "保存",
+ "confirmDelete": "确认删除吗?",
+ "translate": "翻译",
+ "search": "搜索",
+ "explainFollowing": "解释以下内容: ",
+ "toolHelp": "每一个工具都需要设置 url。以谷歌搜索为例
https://www.google.com/search?q={q}
在选中文本后点击对应的工具时,链接中的 {q}
将会被替换为您选择的文本,然后将自动跳转到替换后的链接。
前置文本和后置文本是指在选中的文本前后添加对应的文本,然后作为 {q}
来执行替换。
"
}
diff --git a/tsconfig.json b/tsconfig.json
index 2fd6ac1..41778b5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
- }
+ },
+ "allowSyntheticDefaultImports": true
}
}