diff --git a/src/hooks/FavWords.js b/src/hooks/FavWords.js index 6c833d5..c781279 100644 --- a/src/hooks/FavWords.js +++ b/src/hooks/FavWords.js @@ -5,6 +5,7 @@ import { getWordsWithDefault, setWords } from "../libs/storage"; import { useSyncMeta } from "./Sync"; export function useFavWords() { + const [loading, setLoading] = useState(false); const [favWords, setFavWords] = useState({}); const { updateSyncMeta } = useSyncMeta(); @@ -24,17 +25,43 @@ export function useFavWords() { [updateSyncMeta, favWords] ); + const mergeWords = useCallback( + async (newWords) => { + const favs = { ...favWords }; + newWords.forEach((word) => { + if (!favs[word]) { + favs[word] = { createdAt: Date.now() }; + } + }); + await setWords(favs); + await updateSyncMeta(KV_WORDS_KEY); + await trySyncWords(); + setFavWords(favs); + }, + [updateSyncMeta, favWords] + ); + + const clearWords = useCallback(async () => { + await setWords({}); + await updateSyncMeta(KV_WORDS_KEY); + await trySyncWords(); + setFavWords({}); + }, [updateSyncMeta]); + useEffect(() => { (async () => { try { + setLoading(true); await trySyncWords(); const favWords = await getWordsWithDefault(); setFavWords(favWords); } catch (err) { console.log("[query fav]", err); + } finally { + setLoading(false); } })(); }, []); - return { favWords, toggleFav }; + return { loading, favWords, toggleFav, mergeWords, clearWords }; } diff --git a/src/views/Options/DownloadButton.js b/src/views/Options/DownloadButton.js new file mode 100644 index 0000000..9efbd24 --- /dev/null +++ b/src/views/Options/DownloadButton.js @@ -0,0 +1,27 @@ +import FileDownloadIcon from "@mui/icons-material/FileDownload"; +import Button from "@mui/material/Button"; + +export default function DownloadButton({ data, text, fileName }) { + const handleClick = (e) => { + e.preventDefault(); + if (data) { + const url = window.URL.createObjectURL(new Blob([data])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", fileName || `${Date.now()}.json`); + document.body.appendChild(link); + link.click(); + link.remove(); + } + }; + return ( + + ); +} diff --git a/src/views/Options/FavWords.js b/src/views/Options/FavWords.js index 9bfb288..3d1ff6d 100644 --- a/src/views/Options/FavWords.js +++ b/src/views/Options/FavWords.js @@ -1,21 +1,23 @@ import Stack from "@mui/material/Stack"; import { OPT_TRANS_BAIDU } from "../../config"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState } from "react"; import Typography from "@mui/material/Typography"; import Accordion from "@mui/material/Accordion"; import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import CircularProgress from "@mui/material/CircularProgress"; -import FileDownloadIcon from "@mui/icons-material/FileDownload"; -import FileUploadIcon from "@mui/icons-material/FileUpload"; import { useI18n } from "../../hooks/I18n"; import Alert from "@mui/material/Alert"; import { apiTranslate } from "../../apis"; import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; import { useFavWords } from "../../hooks/FavWords"; -import { DictCont } from "../Selection/TranCont"; +import DictCont from "../Selection/DictCont"; +import DownloadButton from "./DownloadButton"; +import UploadButton from "./UploadButton"; +import Button from "@mui/material/Button"; +import ClearAllIcon from "@mui/icons-material/ClearAll"; +import { isValidWord } from "../../libs/utils"; function DictField({ word }) { const [dictResult, setDictResult] = useState(null); @@ -53,7 +55,7 @@ function DictField({ word }) { return ; } -function FavAccordion({ word }) { +function FavAccordion({ word, index }) { const [expanded, setExpanded] = useState(false); const handleChange = (e) => { @@ -66,7 +68,7 @@ function FavAccordion({ word }) { {/* {`[${new Date( createdAt ).toLocaleString()}] ${word}`} */} - {word} + {`${index + 1}. ${word}`} {expanded && } @@ -75,80 +77,9 @@ function FavAccordion({ word }) { ); } -function DownloadButton({ data, text, fileName }) { - const handleClick = (e) => { - e.preventDefault(); - if (data) { - const url = window.URL.createObjectURL(new Blob([data])); - const link = document.createElement("a"); - link.href = url; - link.setAttribute( - "download", - fileName || `kiss-words_${Date.now()}.json` - ); - document.body.appendChild(link); - link.click(); - link.remove(); - } - }; - return ( - - ); -} - -function UploadButton({ handleImport, text }) { - const i18n = useI18n(); - const inputRef = useRef(null); - const handleClick = () => { - inputRef.current && inputRef.current.click(); - }; - const onChange = (e) => { - const file = e.target.files[0]; - if (!file) { - return; - } - - if (!file.type.includes("json")) { - alert(i18n("error_wrong_file_type")); - return; - } - - const reader = new FileReader(); - reader.onload = async (e) => { - handleImport(e.target.result); - }; - reader.readAsText(file); - }; - - return ( - - ); -} - export default function FavWords() { const i18n = useI18n(); - const { favWords } = useFavWords(); + const { loading, favWords, mergeWords, clearWords } = useFavWords(); const favList = Object.entries(favWords).sort((a, b) => a[0].localeCompare(b[0]) ); @@ -156,8 +87,11 @@ export default function FavWords() { const handleImport = async (data) => { try { - console.log("data", data); - // await rules.merge(JSON.parse(data)); + const newWords = data + .split("\n") + .map((line) => line.split(",")[0].trim()) + .filter(isValidWord); + await mergeWords(newWords); } catch (err) { console.log("[import rules]", err); } @@ -173,17 +107,42 @@ export default function FavWords() { useFlexGap flexWrap="wrap" > - - + + - {favList.map(([word, { createdAt }]) => ( - - ))} + {loading ? ( + + ) : ( + favList.map(([word, { createdAt }], index) => ( + + )) + )} diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js index b9995ef..ff03a21 100644 --- a/src/views/Options/Rules.js +++ b/src/views/Options/Rules.js @@ -16,7 +16,7 @@ import { URL_KISS_RULES_NEW_ISSUE, OPT_SYNCTYPE_WORKER, } from "../../config"; -import { useState, useRef, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo } from "react"; import { useI18n } from "../../hooks/I18n"; import Typography from "@mui/material/Typography"; import Accordion from "@mui/material/Accordion"; @@ -26,8 +26,6 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import { useRules } from "../../hooks/Rules"; import MenuItem from "@mui/material/MenuItem"; import Grid from "@mui/material/Grid"; -import FileDownloadIcon from "@mui/icons-material/FileDownload"; -import FileUploadIcon from "@mui/icons-material/FileUpload"; import { useSetting } from "../../hooks/Setting"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; @@ -50,6 +48,8 @@ import OwSubRule from "./OwSubRule"; import ClearAllIcon from "@mui/icons-material/ClearAll"; import HelpButton from "./HelpButton"; import { useSyncCaches } from "../../hooks/Sync"; +import DownloadButton from "./DownloadButton"; +import UploadButton from "./UploadButton"; function RuleFields({ rule, rules, setShow, setKeyword }) { const initFormValues = rule || { @@ -392,77 +392,6 @@ function RuleAccordion({ rule, rules }) { ); } -function DownloadButton({ data, text, fileName }) { - const handleClick = (e) => { - e.preventDefault(); - if (data) { - const url = window.URL.createObjectURL(new Blob([data])); - const link = document.createElement("a"); - link.href = url; - link.setAttribute( - "download", - fileName || `kiss-rules_${Date.now()}.json` - ); - document.body.appendChild(link); - link.click(); - link.remove(); - } - }; - return ( - - ); -} - -function UploadButton({ handleImport, text }) { - const i18n = useI18n(); - const inputRef = useRef(null); - const handleClick = () => { - inputRef.current && inputRef.current.click(); - }; - const onChange = (e) => { - const file = e.target.files[0]; - if (!file) { - return; - } - - if (!file.type.includes("json")) { - alert(i18n("error_wrong_file_type")); - return; - } - - const reader = new FileReader(); - reader.onload = async (e) => { - handleImport(e.target.result); - }; - reader.readAsText(file); - }; - - return ( - - ); -} - function ShareButton({ rules, injectRules, selectedUrl }) { const alert = useAlert(); const i18n = useI18n(); @@ -564,6 +493,7 @@ function UserRules({ subRules }) { { + if (inputRef.current) { + inputRef.current.click(); + inputRef.current.value = null; + } + }; + const onChange = (e) => { + const file = e.target.files[0]; + if (!file) { + return; + } + + if (!file.type.includes(fileType)) { + alert(i18n("error_wrong_file_type")); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + handleImport(e.target.result); + }; + reader.readAsText(file); + }; + + return ( + + ); +} diff --git a/src/views/Selection/DictCont.js b/src/views/Selection/DictCont.js new file mode 100644 index 0000000..e62686c --- /dev/null +++ b/src/views/Selection/DictCont.js @@ -0,0 +1,62 @@ +import Box from "@mui/material/Box"; +import Chip from "@mui/material/Chip"; +import Stack from "@mui/material/Stack"; +import FavBtn from "./FavBtn"; + +const exchangeMap = { + word_third: "第三人称单数", + word_ing: "现在分词", + word_done: "过去式", + word_past: "过去分词", + word_pl: "复数", + word_proto: "原词", +}; + +export default function DictCont({ dictResult }) { + if (!dictResult) { + return; + } + + return ( + + +
+ {dictResult.simple_means?.word_name} +
+ +
+ + {dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => ( +
+
{`英[${ph_en}] 美[${ph_am}]`}
+
    + {parts.map(({ part, means }, idx) => ( +
  • + {part ? `[${part}] ${means.join("; ")}` : means.join("; ")} +
  • + ))} +
+
+ ))} + +
+ {Object.entries(dictResult.simple_means?.exchange || {}) + .map(([key, val]) => `${exchangeMap[key] || key}: ${val.join(", ")}`) + .join("; ")} +
+ + + {Object.values(dictResult.simple_means?.tags || {}) + .flat() + .filter((item) => item) + .map((item) => ( + + ))} + +
+ ); +} diff --git a/src/views/Selection/TranCont.js b/src/views/Selection/TranCont.js index 95c9068..b806284 100644 --- a/src/views/Selection/TranCont.js +++ b/src/views/Selection/TranCont.js @@ -2,7 +2,6 @@ import TextField from "@mui/material/TextField"; import Box from "@mui/material/Box"; import Alert from "@mui/material/Alert"; import CircularProgress from "@mui/material/CircularProgress"; -import Chip from "@mui/material/Chip"; import Stack from "@mui/material/Stack"; import { useI18n } from "../../hooks/I18n"; import { DEFAULT_TRANS_APIS, OPT_TRANS_BAIDU } from "../../config"; @@ -10,65 +9,7 @@ import { useEffect, useState } from "react"; import { apiTranslate } from "../../apis"; import { isValidWord } from "../../libs/utils"; import CopyBtn from "./CopyBtn"; -import FavBtn from "./FavBtn"; - -const exchangeMap = { - word_third: "第三人称单数", - word_ing: "现在分词", - word_done: "过去式", - word_past: "过去分词", - word_pl: "复数", - word_proto: "原词", -}; - -export function DictCont({ dictResult }) { - if (!dictResult) { - return; - } - - return ( - - -
- {dictResult.simple_means?.word_name} -
- -
- - {dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => ( -
-
{`英[${ph_en}] 美[${ph_am}]`}
-
    - {parts.map(({ part, means }, idx) => ( -
  • - {part ? `[${part}] ${means.join("; ")}` : means.join("; ")} -
  • - ))} -
-
- ))} - -
- {Object.entries(dictResult.simple_means?.exchange || {}) - .map(([key, val]) => `${exchangeMap[key] || key}: ${val.join(", ")}`) - .join("; ")} -
- - - {Object.values(dictResult.simple_means?.tags || {}) - .flat() - .filter((item) => item) - .map((item) => ( - - ))} - -
- ); -} +import DictCont from "./DictCont"; export default function TranCont({ text,