diff --git a/apps/client/components/main/QuestionInput/QuestionInputSentence.vue b/apps/client/components/main/QuestionInput/QuestionInputSentence.vue new file mode 100644 index 000000000..da8ef9313 --- /dev/null +++ b/apps/client/components/main/QuestionInput/QuestionInputSentence.vue @@ -0,0 +1,199 @@ + + + diff --git a/apps/client/components/main/QuestionInput/QuestionInput.vue b/apps/client/components/main/QuestionInput/QuestionInputWords.vue similarity index 93% rename from apps/client/components/main/QuestionInput/QuestionInput.vue rename to apps/client/components/main/QuestionInput/QuestionInputWords.vue index 0461f9efd..3de722db0 100644 --- a/apps/client/components/main/QuestionInput/QuestionInput.vue +++ b/apps/client/components/main/QuestionInput/QuestionInputWords.vue @@ -1,18 +1,15 @@ @@ -12,11 +13,13 @@ import { onMounted, watch } from "vue"; import { useCurrentStatementEnglishSound } from "~/composables/main/englishSound"; import { useAutoPlayEnglish } from "~/composables/user/sound"; +import { useShowWordNumber } from "~/composables/user/wordNumber"; import { useCourseStore } from "~/store/course"; const courseStore = useCourseStore(); const { playSound } = useCurrentStatementEnglishSound(); const { isAutoPlayEnglish } = useAutoPlayEnglish(); +const { isShowWordNumber } = useShowWordNumber(); onMounted(() => { handleAutoPlayEnglish(); diff --git a/apps/client/components/user/Setting.vue b/apps/client/components/user/Setting.vue index d12dfc086..b0b23b9f1 100644 --- a/apps/client/components/user/Setting.vue +++ b/apps/client/components/user/Setting.vue @@ -170,6 +170,21 @@ /> + + + + 显示单词数量 +

关闭此功能将禁用空格提交

+ + + + + @@ -234,6 +249,7 @@ import { useKeyboardSound, } from "~/composables/user/sound"; import { useSpaceSubmitAnswer } from "~/composables/user/submitKey"; +import { useShowWordNumber } from "~/composables/user/wordNumber"; import { useShowWordsWidth } from "~/composables/user/words"; import { parseShortcutKeys } from "~/utils/keyboardShortcuts"; @@ -263,6 +279,7 @@ const { togglePronunciation, } = usePronunciation(); const { showWordsWidth, toggleAutoWordsWidth } = useShowWordsWidth(); +const { showWordNumber, toggleShowWordNumber } = useShowWordNumber(); const { useSpace, toggleUseSpaceSubmitAnswer } = useSpaceSubmitAnswer(); const { showErrorTip, toggleShowErrorTip } = useErrorTip(); const { @@ -315,4 +332,9 @@ onUnmounted(() => { .btn-outline.btn-secondary { @apply text-fuchsia-500 outline-fuchsia-500; } + +.label-warn { + color: rgb(192, 179, 37); + font-size: 13px; +} diff --git a/apps/client/composables/main/question.ts b/apps/client/composables/main/question.ts index 42d3add21..039aeaecc 100644 --- a/apps/client/composables/main/question.ts +++ b/apps/client/composables/main/question.ts @@ -1,3 +1,5 @@ +import type { Ref } from "vue"; + import { nextTick, reactive, ref, watchEffect } from "vue"; interface Word { @@ -11,7 +13,7 @@ interface Word { id: number; } -interface InputOptions { +interface WordsInputOptions { source: () => string; setInputCursorPosition: (position: number) => void; getInputCursorPosition: () => number; @@ -32,12 +34,12 @@ export function clearQuestionInput() { inputValue.value = ""; } -export function useInput({ +export function useWordsInput({ source, setInputCursorPosition, getInputCursorPosition, inputChangedCallback, -}: InputOptions) { +}: WordsInputOptions) { let mode: Mode = Mode.Input; let currentEditWord: Word; @@ -278,6 +280,7 @@ export function useInput({ } function handleKeyboardInput(e: KeyboardEvent, options?: KeyboardInputOptions) { + console.log(e, inputValue); // 禁止方向键移动 if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.code)) { e.preventDefault(); @@ -356,3 +359,41 @@ export function useInput({ isFixMode, }; } + +export function useSentenceInput(source: () => string) { + let inputStatus: Ref<"correct" | "wrong" | "pending"> = ref("pending"); + + function setInputValue(val: string) { + inputValue.value = val; + inputStatus.value = "pending"; + } + + function formatInputText(word: string) { + return word + .toLocaleLowerCase() + .replace(/‘|’|“|"|”/g, "'") + .replace(/[\s]+/g, " ") + .trim(); + } + + function submitAnswer(correctCallback?: () => void, wrongCallback?: () => void) { + const english = source(); + const formattedInput = formatInputText(inputValue.value); + + if (formattedInput === english.toLocaleLowerCase()) { + inputStatus.value = "correct"; + clearQuestionInput(); + correctCallback?.(); + } else { + inputStatus.value = "wrong"; + wrongCallback?.(); + } + } + + return { + inputValue, + setInputValue, + submitAnswer, + inputStatus, + }; +} diff --git a/apps/client/composables/main/tests/question/sentence-mode.spec.ts b/apps/client/composables/main/tests/question/sentence-mode.spec.ts new file mode 100644 index 000000000..35eac90b2 --- /dev/null +++ b/apps/client/composables/main/tests/question/sentence-mode.spec.ts @@ -0,0 +1,47 @@ +import { describe, expect, it, vi } from "vitest"; + +import { useSentenceInput } from "../../question"; + +describe("question", () => { + it("should be correct when checked the answer", async () => { + const { setInputValue, submitAnswer } = useSentenceInput(() => "i eat"); + + setInputValue("i eat"); + + const correctCallback = vi.fn(); + const wrongCallback = vi.fn(); + submitAnswer(correctCallback, wrongCallback); + + expect(correctCallback).toBeCalled(); + expect(wrongCallback).not.toBeCalled(); + }); + + it("should be incorrect when checked the answer", async () => { + const { setInputValue, submitAnswer } = useSentenceInput(() => "i eat"); + + setInputValue("i like"); + + const correctCallback = vi.fn(); + const wrongCallback = vi.fn(); + submitAnswer(correctCallback, wrongCallback); + + expect(correctCallback).not.toBeCalled(); + expect(wrongCallback).toBeCalled(); + }); + + it.each(["i don‘t", "i don’t", "i don“t", `i don"t`, `i don”t`])( + "should be correct when input '%s'", + async (input) => { + const { setInputValue, submitAnswer } = useSentenceInput(() => "i don't"); + + setInputValue(input); + + const correctCallback = vi.fn(); + const wrongCallback = vi.fn(); + submitAnswer(correctCallback, wrongCallback); + + expect(correctCallback).toBeCalled(); + expect(wrongCallback).not.toBeCalled(); + }, + ); +}); diff --git a/apps/client/composables/main/tests/question.spec.ts b/apps/client/composables/main/tests/question/words-mode.spec.ts similarity index 93% rename from apps/client/composables/main/tests/question.spec.ts rename to apps/client/composables/main/tests/question/words-mode.spec.ts index c15e0349d..479cc4522 100644 --- a/apps/client/composables/main/tests/question.spec.ts +++ b/apps/client/composables/main/tests/question/words-mode.spec.ts @@ -1,13 +1,13 @@ import { describe, expect, it, vi } from "vitest"; -import { useInput } from "../question"; +import { useWordsInput } from "../../question"; describe("question", () => { it("should parse user input correctly", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { userInputWords, setInputValue } = useInput({ + const { userInputWords, setInputValue } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -45,7 +45,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, submitAnswer } = useInput({ + const { setInputValue, submitAnswer } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -65,7 +65,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { userInputWords, setInputValue, submitAnswer } = useInput({ + const { userInputWords, setInputValue, submitAnswer } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -88,7 +88,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, submitAnswer } = useInput({ + const { setInputValue, submitAnswer } = useWordsInput({ source: () => "i don't", setInputCursorPosition, getInputCursorPosition, @@ -109,7 +109,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { userInputWords } = useInput({ + const { userInputWords } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -122,7 +122,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { userInputWords } = useInput({ + const { userInputWords } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -135,7 +135,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = vi.fn(); - const { userInputWords, setInputValue } = useInput({ + const { userInputWords, setInputValue } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -162,7 +162,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useInput({ + const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -180,7 +180,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useInput({ + const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useWordsInput({ source: () => "i eat", setInputCursorPosition, getInputCursorPosition, @@ -203,7 +203,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useInput({ + const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -232,7 +232,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, handleKeyboardInput } = useInput({ + const { setInputValue, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -261,7 +261,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, submitAnswer, fixIncorrectWord, handleKeyboardInput } = useInput({ + const { setInputValue, submitAnswer, fixIncorrectWord, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -284,7 +284,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = () => 0; - const { setInputValue, submitAnswer, fixIncorrectWord, handleKeyboardInput } = useInput({ + const { setInputValue, submitAnswer, fixIncorrectWord, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -307,7 +307,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = vi.fn(); - const { setInputValue, handleKeyboardInput } = useInput({ + const { setInputValue, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -339,7 +339,7 @@ describe("question", () => { submitAnswer, activePreviousIncorrectWord, fixIncorrectWord, - } = useInput({ + } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -362,7 +362,7 @@ describe("question", () => { const setInputCursorPosition = () => {}; const getInputCursorPosition = vi.fn(); - const { setInputValue, handleKeyboardInput } = useInput({ + const { setInputValue, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -396,7 +396,7 @@ describe("question", () => { const getInputCursorPosition = vi.fn(); const { setInputValue, userInputWords, submitAnswer, fixIncorrectWord, handleKeyboardInput } = - useInput({ + useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -439,7 +439,7 @@ describe("question", () => { const getInputCursorPosition = vi.fn(); const inputChangedCallback = vi.fn(); - const { setInputValue, handleKeyboardInput } = useInput({ + const { setInputValue, handleKeyboardInput } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -462,7 +462,7 @@ describe("question", () => { const getInputCursorPosition = vi.fn(); const inputChangedCallback = vi.fn(); - const { setInputValue, handleKeyboardInput, submitAnswer } = useInput({ + const { setInputValue, handleKeyboardInput, submitAnswer } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, @@ -506,7 +506,7 @@ describe("question", () => { const getInputCursorPosition = vi.fn(); const inputChangedCallback = vi.fn(); - const { setInputValue, handleKeyboardInput, submitAnswer, userInputWords } = useInput({ + const { setInputValue, handleKeyboardInput, submitAnswer, userInputWords } = useWordsInput({ source: () => "like code", setInputCursorPosition, getInputCursorPosition, @@ -539,7 +539,7 @@ describe("question", () => { const getInputCursorPosition = vi.fn(); const inputChangedCallback = vi.fn(); - const { setInputValue, handleKeyboardInput, submitAnswer } = useInput({ + const { setInputValue, handleKeyboardInput, submitAnswer } = useWordsInput({ source: () => "i eat apple", setInputCursorPosition, getInputCursorPosition, diff --git a/apps/client/composables/user/wordNumber.ts b/apps/client/composables/user/wordNumber.ts new file mode 100644 index 000000000..6629d1b57 --- /dev/null +++ b/apps/client/composables/user/wordNumber.ts @@ -0,0 +1,19 @@ +import { useLocalStorageBoolean } from "~/utils/localStorage"; + +export const SHOW_WORD_NUMBER = "showWordNumber"; + +export function useShowWordNumber() { + const { + value: showWordNumber, + toggle: toggleShowWordNumber, + isTrue: isShowWordNumber, + remove: removeShowWordNumber, + } = useLocalStorageBoolean(SHOW_WORD_NUMBER, true); + + return { + showWordNumber, + toggleShowWordNumber, + isShowWordNumber, + removeShowWordNumber, + }; +}