mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
Merge pull request #529 from erzabytyci/user-tiers
improve Satzmix exercise
This commit is contained in:
@@ -9,9 +9,21 @@ import { withBasePath } from "@/shared/utils/basePath";
|
|||||||
import useQuery from "@/shared/hooks/useQuery";
|
import useQuery from "@/shared/hooks/useQuery";
|
||||||
import styles from "./jumbled-sentence.module.css";
|
import styles from "./jumbled-sentence.module.css";
|
||||||
|
|
||||||
function toWordPerLine(value) {
|
function toElementPerLine(value) {
|
||||||
if (typeof value !== "string") return "";
|
if (typeof value !== "string") return "";
|
||||||
return value.trim().split(/\s+/).filter(Boolean).join("\n");
|
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
if (!trimmedValue) return "";
|
||||||
|
|
||||||
|
if (trimmedValue.includes("\n")) {
|
||||||
|
return trimmedValue
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedValue.split(/\s+/).filter(Boolean).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateJumbledSentenceExercise() {
|
export default function CreateJumbledSentenceExercise() {
|
||||||
@@ -50,10 +62,12 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
setSentences(
|
setSentences(
|
||||||
(loadedExercise.sentences || []).length
|
(loadedExercise.sentences || []).length
|
||||||
? loadedExercise.sentences.map((item) => ({
|
? loadedExercise.sentences.map((item) => ({
|
||||||
sentence: toWordPerLine(item.sentence || ""),
|
sentence: toElementPerLine(item.sentence || ""),
|
||||||
alternates:
|
alternates:
|
||||||
item.alternates && item.alternates.length > 0
|
item.alternates && item.alternates.length > 0
|
||||||
? item.alternates
|
? item.alternates.map((alternate) =>
|
||||||
|
toElementPerLine(alternate)
|
||||||
|
)
|
||||||
: [""],
|
: [""],
|
||||||
alternateFeedbacks:
|
alternateFeedbacks:
|
||||||
item.alternateFeedbacks &&
|
item.alternateFeedbacks &&
|
||||||
@@ -67,7 +81,9 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
correctSentenceFeedback: item.correctSentenceFeedback || "",
|
correctSentenceFeedback: item.correctSentenceFeedback || "",
|
||||||
incorrectAlternates:
|
incorrectAlternates:
|
||||||
item.incorrectAlternates && item.incorrectAlternates.length > 0
|
item.incorrectAlternates && item.incorrectAlternates.length > 0
|
||||||
? item.incorrectAlternates
|
? item.incorrectAlternates.map((alternate) =>
|
||||||
|
toElementPerLine(alternate)
|
||||||
|
)
|
||||||
: [""],
|
: [""],
|
||||||
incorrectFeedbacks:
|
incorrectFeedbacks:
|
||||||
item.incorrectFeedbacks &&
|
item.incorrectFeedbacks &&
|
||||||
@@ -205,10 +221,10 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
|
|
||||||
const normalizedSentences = sentences.map((item) => ({
|
const normalizedSentences = sentences.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
sentence: toWordPerLine(item.sentence),
|
sentence: toElementPerLine(item.sentence),
|
||||||
alternates: item.alternates
|
alternates: item.alternates
|
||||||
.map((alt, index) => ({
|
.map((alt, index) => ({
|
||||||
alt: typeof alt === "string" ? alt.trim() : "",
|
alt: toElementPerLine(alt),
|
||||||
feedback:
|
feedback:
|
||||||
typeof item.alternateFeedbacks?.[index] === "string"
|
typeof item.alternateFeedbacks?.[index] === "string"
|
||||||
? item.alternateFeedbacks[index].trim()
|
? item.alternateFeedbacks[index].trim()
|
||||||
@@ -232,7 +248,7 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
: "",
|
: "",
|
||||||
incorrectAlternates: (item.incorrectAlternates || [])
|
incorrectAlternates: (item.incorrectAlternates || [])
|
||||||
.map((alt, index) => ({
|
.map((alt, index) => ({
|
||||||
alt: typeof alt === "string" ? alt.trim() : "",
|
alt: toElementPerLine(alt),
|
||||||
feedback:
|
feedback:
|
||||||
typeof item.incorrectFeedbacks?.[index] === "string"
|
typeof item.incorrectFeedbacks?.[index] === "string"
|
||||||
? item.incorrectFeedbacks[index].trim()
|
? item.incorrectFeedbacks[index].trim()
|
||||||
@@ -346,15 +362,20 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.sectionTitle}>Alternativ-Sätze:</div>
|
<div className={styles.sectionTitle}>Alternativ-Sätze:</div>
|
||||||
|
<div className={styles.sentenceHint}>
|
||||||
|
Schreiben Sie auch hier jedes Element in eine neue Zeile.
|
||||||
|
</div>
|
||||||
{s.alternates.map((alt, altIdx) => (
|
{s.alternates.map((alt, altIdx) => (
|
||||||
<div key={altIdx} className={styles.alternateGroup}>
|
<div key={altIdx} className={styles.alternateGroup}>
|
||||||
<div className={styles.alternateRow}>
|
<div className={styles.alternateRow}>
|
||||||
<input
|
<textarea
|
||||||
value={alt}
|
value={alt}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleAlternateChange(idx, altIdx, e.target.value)
|
handleAlternateChange(idx, altIdx, e.target.value)
|
||||||
}
|
}
|
||||||
className={`${styles.fieldInput} ${styles.alternateInput}`}
|
className={`${styles.fieldInput} ${styles.sentenceTextarea}`}
|
||||||
|
rows={4}
|
||||||
|
placeholder="Jeden Tag\nlerne ich\nDeutsch"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -410,10 +431,13 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
<div className={styles.sectionTitle}>
|
<div className={styles.sectionTitle}>
|
||||||
Alternativ-Sätze (Falsche):
|
Alternativ-Sätze (Falsche):
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.sentenceHint}>
|
||||||
|
Schreiben Sie auch hier jedes Element in eine neue Zeile.
|
||||||
|
</div>
|
||||||
{s.incorrectAlternates.map((alt, altIdx) => (
|
{s.incorrectAlternates.map((alt, altIdx) => (
|
||||||
<div key={altIdx} className={styles.alternateGroup}>
|
<div key={altIdx} className={styles.alternateGroup}>
|
||||||
<div className={styles.alternateRowNoGapTop}>
|
<div className={styles.alternateRowNoGapTop}>
|
||||||
<input
|
<textarea
|
||||||
value={alt}
|
value={alt}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleIncorrectAlternateChange(
|
handleIncorrectAlternateChange(
|
||||||
@@ -422,8 +446,9 @@ export default function CreateJumbledSentenceExercise() {
|
|||||||
e.target.value
|
e.target.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className={`${styles.fieldInput} ${styles.alternateInput}`}
|
className={`${styles.fieldInput} ${styles.sentenceTextarea}`}
|
||||||
placeholder="Falsche Alternative (optional)"
|
rows={4}
|
||||||
|
placeholder="Deutsch\nlerne ich\njeden Tag"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ describe("admin jumbled sentence exercises API", () => {
|
|||||||
title: "Satzreihenfolge A1",
|
title: "Satzreihenfolge A1",
|
||||||
sentences: [
|
sentences: [
|
||||||
{
|
{
|
||||||
sentence: "Ich lerne jeden Tag Deutsch",
|
sentence: "Ich\nlerne jeden Tag\nDeutsch",
|
||||||
alternates: ["Jeden Tag lerne ich Deutsch"],
|
alternates: ["lerne jeden Tag\nIch\nDeutsch"],
|
||||||
alternateFeedbacks: ["Auch richtig."],
|
alternateFeedbacks: ["Auch richtig."],
|
||||||
correctSentenceFeedback: "Perfekt!",
|
correctSentenceFeedback: "Perfekt!",
|
||||||
incorrectAlternates: ["Deutsch ich lerne jeden Tag"],
|
incorrectAlternates: ["Deutsch\nIch\nlerne jeden Tag"],
|
||||||
incorrectFeedbacks: ["Subjekt und Verb bitte prüfen."],
|
incorrectFeedbacks: ["Subjekt und Verb bitte prüfen."],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -33,8 +33,23 @@ export default function JumbledSentenceExercisePage() {
|
|||||||
const cleanTokenForComparison = (token) =>
|
const cleanTokenForComparison = (token) =>
|
||||||
stripIgnoredPunctuation(token).toLowerCase();
|
stripIgnoredPunctuation(token).toLowerCase();
|
||||||
|
|
||||||
|
const splitElements = (value) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
if (!trimmedValue) return [];
|
||||||
|
|
||||||
|
if (trimmedValue.includes("\n")) {
|
||||||
|
return trimmedValue
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((element) => element.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedValue.split(/\s+/).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
const tokenizeSentenceForDisplay = (value) =>
|
const tokenizeSentenceForDisplay = (value) =>
|
||||||
value.trim().split(/\s+/).map(stripIgnoredPunctuation).filter(Boolean);
|
splitElements(value).map(stripIgnoredPunctuation).filter(Boolean);
|
||||||
|
|
||||||
const normalizeSentence = (value) =>
|
const normalizeSentence = (value) =>
|
||||||
tokenizeSentenceForDisplay(value)
|
tokenizeSentenceForDisplay(value)
|
||||||
|
|||||||
@@ -11,8 +11,23 @@ function cleanToken(token) {
|
|||||||
return token.replace(/[\p{P}]/gu, "").toLowerCase();
|
return token.replace(/[\p{P}]/gu, "").toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTokens(sentence) {
|
function splitElements(sentence) {
|
||||||
return sentence.trim().split(/\s+/).map(cleanToken).filter(Boolean).sort();
|
const trimmedSentence = sentence.trim();
|
||||||
|
|
||||||
|
if (!trimmedSentence) return [];
|
||||||
|
|
||||||
|
if (trimmedSentence.includes("\n")) {
|
||||||
|
return trimmedSentence
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((element) => element.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedSentence.split(/\s+/).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeElements(sentence) {
|
||||||
|
return splitElements(sentence).map(cleanToken).filter(Boolean).sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sameWordSet(a, b) {
|
function sameWordSet(a, b) {
|
||||||
@@ -52,7 +67,7 @@ export const jumbledSentenceSchema = z
|
|||||||
}, z.array(z.string())),
|
}, z.array(z.string())),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
const baseTokens = normalizeTokens(data.sentence);
|
const baseTokens = normalizeElements(data.sentence);
|
||||||
|
|
||||||
if (data.alternateFeedbacks.length !== data.alternates.length) {
|
if (data.alternateFeedbacks.length !== data.alternates.length) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
@@ -64,7 +79,7 @@ export const jumbledSentenceSchema = z
|
|||||||
|
|
||||||
for (let index = 0; index < data.alternates.length; index++) {
|
for (let index = 0; index < data.alternates.length; index++) {
|
||||||
const alternate = data.alternates[index];
|
const alternate = data.alternates[index];
|
||||||
const alternateTokens = normalizeTokens(alternate);
|
const alternateTokens = normalizeElements(alternate);
|
||||||
|
|
||||||
if (!sameWordSet(baseTokens, alternateTokens)) {
|
if (!sameWordSet(baseTokens, alternateTokens)) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
@@ -78,7 +93,7 @@ export const jumbledSentenceSchema = z
|
|||||||
|
|
||||||
for (let index = 0; index < data.incorrectAlternates.length; index++) {
|
for (let index = 0; index < data.incorrectAlternates.length; index++) {
|
||||||
const incorrectAlternate = data.incorrectAlternates[index];
|
const incorrectAlternate = data.incorrectAlternates[index];
|
||||||
const incorrectTokens = normalizeTokens(incorrectAlternate);
|
const incorrectTokens = normalizeElements(incorrectAlternate);
|
||||||
|
|
||||||
if (!sameWordSet(baseTokens, incorrectTokens)) {
|
if (!sameWordSet(baseTokens, incorrectTokens)) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
|
|||||||
Reference in New Issue
Block a user