@windward/games 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/components/content/blocks/multipleChoice/MultipleChoice.vue +8 -1
- package/components/content/blocks/multipleChoice/QuestionDialog.vue +0 -9
- package/components/content/blocks/quizshowGame/feedbackIcons.vue +2 -2
- package/components/settings/BucketGameSettingsManager.vue +136 -96
- package/components/settings/CrosswordPuzzleSettingsManager.vue +248 -1
- package/components/settings/FlashCardSlidesManager.vue +122 -51
- package/components/settings/MatchingGameManager.vue +179 -84
- package/components/settings/MultipleChoiceSettingsManager.vue +132 -0
- package/components/settings/QuizShowSettingsManager.vue +2 -1
- package/components/settings/SevenStrikesSettingsManager.vue +91 -0
- package/components/settings/SortingGameSettingsManager.vue +103 -58
- package/components/settings/WordJumbleSettingsManager.vue +114 -49
- package/i18n/en-US/components/settings/crossword.ts +4 -0
- package/i18n/en-US/components/settings/multiple_choice.ts +4 -0
- package/i18n/es-ES/components/settings/crossword.ts +4 -0
- package/i18n/es-ES/components/settings/multiple_choice.ts +4 -0
- package/i18n/sv-SE/components/settings/crossword.ts +4 -0
- package/i18n/sv-SE/components/settings/multiple_choice.ts +4 -0
- package/package.json +1 -1
- package/plugin.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.24.0] - 2025-10-23
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2034/generate-crossword (pull request #262)
|
|
6
|
+
* Merged in bugfix/LE-2020-the-quiz-show-manager-panel-does (pull request #263)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Release [0.23.0] - 2025-09-16
|
|
10
|
+
|
|
11
|
+
* Merged in feature/LE-2035/seven-strikes (pull request #259)
|
|
12
|
+
* Merged in feature/LE-1998/MC-games (pull request #258)
|
|
13
|
+
* Merged in feature/LE-1853-multiple-choice-block-optional-h (pull request #257)
|
|
14
|
+
* Merged in bugfix/MIND-6075-decouple-generateaiquestionbut (pull request #254)
|
|
15
|
+
* Merged in feature/LE-2091-multiple-choice-block-add-space- (pull request #253)
|
|
16
|
+
* Merged release/0.22.0 into feature/LE-2091-multiple-choice-block-add-space-
|
|
17
|
+
|
|
18
|
+
|
|
3
19
|
## Release [0.22.0] - 2025-08-28
|
|
4
20
|
|
|
5
21
|
* Merged in feature/LE-2036/update-core-package-v (pull request #252)
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
<div class="d-flex justify-space-between">
|
|
82
82
|
<div class="d-flex">
|
|
83
83
|
<p
|
|
84
|
-
class="mb-0"
|
|
84
|
+
class="mb-0 mr-1"
|
|
85
85
|
:aria-labelledby="answer.value"
|
|
86
86
|
>
|
|
87
87
|
{{
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
text
|
|
148
148
|
elevation="0"
|
|
149
149
|
class="mr-4 btn-hint"
|
|
150
|
+
:disabled="hasHint(question)"
|
|
150
151
|
@click="onHint(question)"
|
|
151
152
|
>{{
|
|
152
153
|
$t(
|
|
@@ -318,6 +319,12 @@ export default {
|
|
|
318
319
|
},
|
|
319
320
|
},
|
|
320
321
|
methods: {
|
|
322
|
+
hasHint(question) {
|
|
323
|
+
if (_.isEmpty(question.hint)) {
|
|
324
|
+
return true
|
|
325
|
+
}
|
|
326
|
+
return false
|
|
327
|
+
},
|
|
321
328
|
onAnswerDescription(question, answer) {
|
|
322
329
|
this.activeQuestion = question
|
|
323
330
|
this.activeAnswer = answer
|
|
@@ -18,15 +18,6 @@
|
|
|
18
18
|
@input="onCheckValidation"
|
|
19
19
|
></TextEditor>
|
|
20
20
|
</v-col>
|
|
21
|
-
<v-col xl="3" lg="3" md="4" sm="12" cols="12" v-if="false">
|
|
22
|
-
<GenerateAIQuestionButton
|
|
23
|
-
:course="course"
|
|
24
|
-
:content="content"
|
|
25
|
-
:block="block"
|
|
26
|
-
:question-type="questionType"
|
|
27
|
-
@click:generate="onQuestionGenerated"
|
|
28
|
-
></GenerateAIQuestionButton>
|
|
29
|
-
</v-col>
|
|
30
21
|
<v-col cols="12">
|
|
31
22
|
<ImageAssetSettings
|
|
32
23
|
v-model="question.question_metadata.body_asset"
|
|
@@ -196,14 +196,18 @@
|
|
|
196
196
|
<v-container class="pa-4 mb-6">
|
|
197
197
|
<v-row>
|
|
198
198
|
<v-col cols="12">
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
199
|
+
<PluginRef
|
|
200
|
+
target="contentBlockSettingTool"
|
|
201
|
+
:attrs="{
|
|
202
|
+
value: block,
|
|
203
|
+
course: course,
|
|
204
|
+
content: currentContent,
|
|
205
|
+
}"
|
|
206
|
+
:on="{
|
|
207
|
+
input: onPluginSetBlock,
|
|
208
|
+
append: onPluginAppendBlock,
|
|
209
|
+
}"
|
|
210
|
+
></PluginRef>
|
|
207
211
|
</v-col>
|
|
208
212
|
</v-row>
|
|
209
213
|
</v-container>
|
|
@@ -217,7 +221,6 @@ import {
|
|
|
217
221
|
MathExpressionEditor,
|
|
218
222
|
MathLiveWrapper,
|
|
219
223
|
ContentViewer,
|
|
220
|
-
GenerateAIQuestionButton
|
|
221
224
|
} from '@windward/core/utils'
|
|
222
225
|
import colors from 'vuetify/lib/util/colors'
|
|
223
226
|
import TextEditor from '~/components/Text/TextEditor'
|
|
@@ -225,6 +228,7 @@ import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentB
|
|
|
225
228
|
import Crypto from '~/helpers/Crypto'
|
|
226
229
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
227
230
|
import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
|
|
231
|
+
import PluginRef from '~/components/Core/PluginRef.vue'
|
|
228
232
|
import Uuid from '~/helpers/Uuid'
|
|
229
233
|
|
|
230
234
|
export default {
|
|
@@ -237,9 +241,9 @@ export default {
|
|
|
237
241
|
SortableExpansionPanel,
|
|
238
242
|
BaseContentBlockSettings,
|
|
239
243
|
TextEditor,
|
|
240
|
-
|
|
244
|
+
PluginRef,
|
|
241
245
|
},
|
|
242
|
-
beforeMount() {
|
|
246
|
+
beforeMount() {
|
|
243
247
|
if (_.isEmpty(this.block)) {
|
|
244
248
|
this.block = {}
|
|
245
249
|
}
|
|
@@ -424,125 +428,161 @@ export default {
|
|
|
424
428
|
return htmlString.replace(/(<([^>]+)>)/gi, '')
|
|
425
429
|
}
|
|
426
430
|
},
|
|
427
|
-
// Handler for receiving bucket game data from
|
|
428
|
-
|
|
431
|
+
// Handler for receiving bucket game data from Plugins
|
|
432
|
+
onPluginSetBlock(activityData) {
|
|
429
433
|
this.loading = true
|
|
430
434
|
try {
|
|
431
435
|
// Process the activity data
|
|
432
|
-
if (
|
|
436
|
+
if (
|
|
437
|
+
activityData &&
|
|
438
|
+
activityData.metadata &&
|
|
433
439
|
activityData.metadata.config &&
|
|
434
440
|
activityData.metadata.config.bucket_titles &&
|
|
435
441
|
activityData.metadata.config.bucket_answers &&
|
|
436
442
|
Array.isArray(activityData.metadata.config.bucket_titles) &&
|
|
437
|
-
Array.isArray(activityData.metadata.config.bucket_answers)
|
|
438
|
-
|
|
443
|
+
Array.isArray(activityData.metadata.config.bucket_answers)
|
|
444
|
+
) {
|
|
439
445
|
// Save new buckets and answers
|
|
440
|
-
const newBuckets =
|
|
441
|
-
|
|
446
|
+
const newBuckets =
|
|
447
|
+
activityData.metadata.config.bucket_titles
|
|
448
|
+
const newAnswers =
|
|
449
|
+
activityData.metadata.config.bucket_answers
|
|
442
450
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
this.block.metadata.config.
|
|
451
|
+
// Replace mode: Clear existing buckets and answers
|
|
452
|
+
this.block.metadata.config.bucket_titles.splice(
|
|
453
|
+
0,
|
|
454
|
+
this.block.metadata.config.bucket_titles.length
|
|
455
|
+
)
|
|
456
|
+
this.block.metadata.config.bucket_answers.splice(
|
|
457
|
+
0,
|
|
458
|
+
this.block.metadata.config.bucket_answers.length
|
|
459
|
+
)
|
|
447
460
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
})
|
|
461
|
+
// Add all new buckets
|
|
462
|
+
newBuckets.forEach((bucket) => {
|
|
463
|
+
this.block.metadata.config.bucket_titles.push({
|
|
464
|
+
title: bucket.title || '',
|
|
465
|
+
color: bucket.color || colors.blueGrey.lighten5,
|
|
466
|
+
expand: false,
|
|
455
467
|
})
|
|
468
|
+
})
|
|
456
469
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
470
|
+
// Add all new answers
|
|
471
|
+
newAnswers.forEach((bucketAnswers, index) => {
|
|
472
|
+
this.block.metadata.config.bucket_answers[index] = []
|
|
473
|
+
if (Array.isArray(bucketAnswers)) {
|
|
474
|
+
bucketAnswers.forEach((answer) => {
|
|
475
|
+
this.block.metadata.config.bucket_answers[
|
|
476
|
+
index
|
|
477
|
+
].push({
|
|
478
|
+
bucket_index: index,
|
|
479
|
+
display: answer.display || '',
|
|
480
|
+
feedback: answer.feedback || '',
|
|
481
|
+
expand: false,
|
|
482
|
+
id: Crypto.id(),
|
|
469
483
|
})
|
|
470
|
-
}
|
|
471
|
-
})
|
|
472
|
-
} else {
|
|
473
|
-
// Merge mode: Add new buckets and answers to existing ones
|
|
474
|
-
const existingBucketCount = this.block.metadata.config.bucket_titles.length
|
|
475
|
-
|
|
476
|
-
// Add new buckets
|
|
477
|
-
newBuckets.forEach(bucket => {
|
|
478
|
-
this.block.metadata.config.bucket_titles.push({
|
|
479
|
-
title: bucket.title || '',
|
|
480
|
-
color: bucket.color || colors.blueGrey.lighten5,
|
|
481
|
-
expand: false
|
|
482
484
|
})
|
|
483
|
-
}
|
|
485
|
+
}
|
|
486
|
+
})
|
|
484
487
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
this.block.metadata.config.bucket_answers[adjustedIndex] = []
|
|
490
|
-
}
|
|
491
|
-
if (Array.isArray(bucketAnswers)) {
|
|
492
|
-
bucketAnswers.forEach(answer => {
|
|
493
|
-
this.block.metadata.config.bucket_answers[adjustedIndex].push({
|
|
494
|
-
bucket_index: adjustedIndex,
|
|
495
|
-
display: answer.display || '',
|
|
496
|
-
feedback: answer.feedback || '',
|
|
497
|
-
expand: false,
|
|
498
|
-
id: Crypto.id()
|
|
499
|
-
})
|
|
500
|
-
})
|
|
501
|
-
}
|
|
502
|
-
})
|
|
488
|
+
// Update title and instructions if provided and we're in replace mode
|
|
489
|
+
if (activityData.metadata.config.title) {
|
|
490
|
+
this.block.metadata.config.title =
|
|
491
|
+
activityData.metadata.config.title
|
|
503
492
|
}
|
|
504
493
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
494
|
+
if (activityData.metadata.config.instructions) {
|
|
495
|
+
this.block.metadata.config.instructions =
|
|
496
|
+
activityData.metadata.config.instructions
|
|
497
|
+
}
|
|
510
498
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
499
|
+
// Update feedback messages if provided
|
|
500
|
+
if (activityData.metadata.config.feedback_correct) {
|
|
501
|
+
this.block.metadata.config.feedback_correct =
|
|
502
|
+
activityData.metadata.config.feedback_correct
|
|
503
|
+
}
|
|
514
504
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
505
|
+
if (activityData.metadata.config.feedback_incorrect) {
|
|
506
|
+
this.block.metadata.config.feedback_incorrect =
|
|
507
|
+
activityData.metadata.config.feedback_incorrect
|
|
508
|
+
}
|
|
519
509
|
|
|
520
|
-
|
|
521
|
-
|
|
510
|
+
this.$toast.success(
|
|
511
|
+
this.$t(
|
|
512
|
+
'windward.games.components.settings.bucket_game.form.replaced_successfully'
|
|
513
|
+
),
|
|
514
|
+
{ duration: 3000 }
|
|
515
|
+
)
|
|
516
|
+
} else {
|
|
517
|
+
this.$toast.error(
|
|
518
|
+
this.$t(
|
|
519
|
+
'windward.games.components.settings.bucket_game.form.invalid_response'
|
|
520
|
+
),
|
|
521
|
+
{
|
|
522
|
+
duration: 5000,
|
|
522
523
|
}
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
// Extract error message from the response
|
|
528
|
+
const errorMessage = error.message || 'Unknown error occurred'
|
|
529
|
+
this.$toast.error(
|
|
530
|
+
`${this.$t(
|
|
531
|
+
'windward.games.components.settings.bucket_game.form.failed_to_process'
|
|
532
|
+
)}: ${errorMessage}`,
|
|
533
|
+
{
|
|
534
|
+
duration: 5000,
|
|
523
535
|
}
|
|
536
|
+
)
|
|
537
|
+
} finally {
|
|
538
|
+
this.loading = false
|
|
539
|
+
}
|
|
540
|
+
},
|
|
524
541
|
|
|
542
|
+
onPluginAppendBlock(activityData) {
|
|
543
|
+
this.loading = true
|
|
544
|
+
try {
|
|
545
|
+
// Process the activity data
|
|
546
|
+
if (
|
|
547
|
+
activityData &&
|
|
548
|
+
activityData.metadata &&
|
|
549
|
+
activityData.metadata.config &&
|
|
550
|
+
activityData.metadata.config.bucket_titles &&
|
|
551
|
+
activityData.metadata.config.bucket_answers &&
|
|
552
|
+
Array.isArray(activityData.metadata.config.bucket_titles) &&
|
|
553
|
+
Array.isArray(activityData.metadata.config.bucket_answers)
|
|
554
|
+
) {
|
|
525
555
|
this.$toast.success(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
556
|
+
this.$t(
|
|
557
|
+
'windward.games.components.settings.bucket_game.form.added_successfully'
|
|
558
|
+
),
|
|
529
559
|
{ duration: 3000 }
|
|
530
560
|
)
|
|
531
561
|
} else {
|
|
532
|
-
this.$toast.error(
|
|
533
|
-
|
|
534
|
-
|
|
562
|
+
this.$toast.error(
|
|
563
|
+
this.$t(
|
|
564
|
+
'windward.games.components.settings.bucket_game.form.invalid_response'
|
|
565
|
+
),
|
|
566
|
+
{
|
|
567
|
+
duration: 5000,
|
|
568
|
+
}
|
|
569
|
+
)
|
|
535
570
|
}
|
|
536
571
|
} catch (error) {
|
|
537
572
|
// Extract error message from the response
|
|
538
573
|
const errorMessage = error.message || 'Unknown error occurred'
|
|
539
|
-
this.$toast.error(
|
|
540
|
-
|
|
541
|
-
|
|
574
|
+
this.$toast.error(
|
|
575
|
+
`${this.$t(
|
|
576
|
+
'windward.games.components.settings.bucket_game.form.failed_to_process'
|
|
577
|
+
)}: ${errorMessage}`,
|
|
578
|
+
{
|
|
579
|
+
duration: 5000,
|
|
580
|
+
}
|
|
581
|
+
)
|
|
542
582
|
} finally {
|
|
543
583
|
this.loading = false
|
|
544
584
|
}
|
|
545
|
-
}
|
|
585
|
+
},
|
|
546
586
|
},
|
|
547
587
|
}
|
|
548
588
|
</script>
|
|
@@ -90,6 +90,24 @@
|
|
|
90
90
|
</v-row>
|
|
91
91
|
</v-container>
|
|
92
92
|
</v-container>
|
|
93
|
+
<v-container class="pa-4 mb-6">
|
|
94
|
+
<v-row>
|
|
95
|
+
<v-col cols="12">
|
|
96
|
+
<PluginRef
|
|
97
|
+
target="contentBlockSettingTool"
|
|
98
|
+
:attrs="{
|
|
99
|
+
value: block,
|
|
100
|
+
course: course,
|
|
101
|
+
content: currentContent,
|
|
102
|
+
}"
|
|
103
|
+
:on="{
|
|
104
|
+
input: onPluginSetBlock,
|
|
105
|
+
append: onPluginAppendBlock,
|
|
106
|
+
}"
|
|
107
|
+
></PluginRef>
|
|
108
|
+
</v-col>
|
|
109
|
+
</v-row>
|
|
110
|
+
</v-container>
|
|
93
111
|
<div v-if="loading" class="text-center">
|
|
94
112
|
<v-progress-circular
|
|
95
113
|
:size="70"
|
|
@@ -103,15 +121,17 @@
|
|
|
103
121
|
|
|
104
122
|
<script>
|
|
105
123
|
import _ from 'lodash'
|
|
124
|
+
import { mapGetters } from 'vuex'
|
|
106
125
|
import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
|
|
107
126
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
108
127
|
import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
|
|
128
|
+
import PluginRef from '~/components/Core/PluginRef.vue'
|
|
109
129
|
import Uuid from '~/helpers/Uuid'
|
|
110
130
|
|
|
111
131
|
export default {
|
|
112
132
|
name: 'CrosswordPuzzleSettingsManager',
|
|
113
133
|
extends: BaseContentSettings,
|
|
114
|
-
components: { SortableExpansionPanel, BaseContentBlockSettings },
|
|
134
|
+
components: { SortableExpansionPanel, BaseContentBlockSettings, PluginRef },
|
|
115
135
|
beforeMount() {
|
|
116
136
|
if (_.isEmpty(this.block)) {
|
|
117
137
|
this.block = {}
|
|
@@ -186,6 +206,12 @@ export default {
|
|
|
186
206
|
},
|
|
187
207
|
}
|
|
188
208
|
},
|
|
209
|
+
computed: {
|
|
210
|
+
...mapGetters({
|
|
211
|
+
course: 'course/get',
|
|
212
|
+
currentContent: 'content/get',
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
189
215
|
methods: {
|
|
190
216
|
onAddElement() {
|
|
191
217
|
// pushes in new crossword object
|
|
@@ -207,6 +233,227 @@ export default {
|
|
|
207
233
|
this.onAddElement()
|
|
208
234
|
}
|
|
209
235
|
},
|
|
236
|
+
onPluginSetBlock(activityData) {
|
|
237
|
+
this.loading = true
|
|
238
|
+
try {
|
|
239
|
+
const result = this.processGeneratedCrossword(activityData)
|
|
240
|
+
if (!result) {
|
|
241
|
+
this.showInvalidResponseToast()
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.$set(
|
|
246
|
+
this.block.metadata,
|
|
247
|
+
'config',
|
|
248
|
+
this.block.metadata.config || {}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
this.$set(
|
|
252
|
+
this.block.metadata.config,
|
|
253
|
+
'words',
|
|
254
|
+
result.words.map((item, index) => ({
|
|
255
|
+
id: index,
|
|
256
|
+
word: item.word,
|
|
257
|
+
clue: item.clue,
|
|
258
|
+
expand: false,
|
|
259
|
+
}))
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (result.title) {
|
|
263
|
+
this.block.metadata.config.title = result.title
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.$toast.success(
|
|
267
|
+
this.$t(
|
|
268
|
+
'windward.games.components.settings.crossword.replaced_successfully'
|
|
269
|
+
),
|
|
270
|
+
{ duration: 3000 }
|
|
271
|
+
)
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Failed to process generated crossword items', error)
|
|
274
|
+
this.$toast.error(
|
|
275
|
+
this.$t(
|
|
276
|
+
'windward.games.components.settings.crossword.failed_to_process'
|
|
277
|
+
),
|
|
278
|
+
{ duration: 5000 }
|
|
279
|
+
)
|
|
280
|
+
} finally {
|
|
281
|
+
this.loading = false
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
onPluginAppendBlock(activityData) {
|
|
285
|
+
this.loading = true
|
|
286
|
+
try {
|
|
287
|
+
const result = this.processGeneratedCrossword(activityData)
|
|
288
|
+
if (!result) {
|
|
289
|
+
this.showInvalidResponseToast()
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!Array.isArray(this.block.metadata.config.words)) {
|
|
294
|
+
this.$set(this.block.metadata.config, 'words', [])
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const startingIndex = this.block.metadata.config.words.length
|
|
298
|
+
|
|
299
|
+
result.words.forEach((item, index) => {
|
|
300
|
+
this.block.metadata.config.words.push({
|
|
301
|
+
id: startingIndex + index,
|
|
302
|
+
word: item.word,
|
|
303
|
+
clue: item.clue,
|
|
304
|
+
expand: false,
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
this.renumberWordIds()
|
|
309
|
+
|
|
310
|
+
if (result.title) {
|
|
311
|
+
const defaultTitle = this.$t(
|
|
312
|
+
'windward.games.components.content.blocks.crossword.crossword'
|
|
313
|
+
)
|
|
314
|
+
if (
|
|
315
|
+
!this.block.metadata.config.title ||
|
|
316
|
+
this.block.metadata.config.title === defaultTitle
|
|
317
|
+
) {
|
|
318
|
+
this.block.metadata.config.title = result.title
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.$toast.success(
|
|
323
|
+
this.$t(
|
|
324
|
+
'windward.games.components.settings.crossword.added_successfully'
|
|
325
|
+
),
|
|
326
|
+
{ duration: 3000 }
|
|
327
|
+
)
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error('Failed to append generated crossword items', error)
|
|
330
|
+
this.$toast.error(
|
|
331
|
+
this.$t(
|
|
332
|
+
'windward.games.components.settings.crossword.failed_to_process'
|
|
333
|
+
),
|
|
334
|
+
{ duration: 5000 }
|
|
335
|
+
)
|
|
336
|
+
} finally {
|
|
337
|
+
this.loading = false
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
processGeneratedCrossword(activityData) {
|
|
341
|
+
const config = _.get(activityData, 'metadata.config', null)
|
|
342
|
+
const words = _.get(config, 'words', [])
|
|
343
|
+
|
|
344
|
+
if (!Array.isArray(words) || words.length === 0) {
|
|
345
|
+
return null
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const sanitizedWords = words
|
|
349
|
+
.map((item) => {
|
|
350
|
+
const sanitizedWord = this.normalizeWord(item?.word)
|
|
351
|
+
const sanitizedClue = this.normalizeClue(item?.clue)
|
|
352
|
+
|
|
353
|
+
if (!sanitizedWord || !sanitizedClue) {
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
word: sanitizedWord,
|
|
359
|
+
clue: sanitizedClue,
|
|
360
|
+
expand: false,
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
.filter((item) => item !== null)
|
|
364
|
+
|
|
365
|
+
if (sanitizedWords.length < 2) {
|
|
366
|
+
return null
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const limitedWords = sanitizedWords.slice(0, 10)
|
|
370
|
+
|
|
371
|
+
const normalizedTitle = this.normalizeTitle(_.get(config, 'title', ''))
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
title: normalizedTitle,
|
|
375
|
+
words: limitedWords,
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
normalizeWord(word) {
|
|
379
|
+
if (!word) {
|
|
380
|
+
return ''
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const formatted = word
|
|
384
|
+
.toString()
|
|
385
|
+
.normalize('NFKD')
|
|
386
|
+
.replace(/[^A-Za-z]/g, '')
|
|
387
|
+
.toUpperCase()
|
|
388
|
+
.slice(0, 20)
|
|
389
|
+
|
|
390
|
+
return formatted
|
|
391
|
+
},
|
|
392
|
+
normalizeClue(clue) {
|
|
393
|
+
if (!clue) {
|
|
394
|
+
return ''
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let normalized = clue
|
|
398
|
+
.toString()
|
|
399
|
+
.replace(/\s+/g, ' ')
|
|
400
|
+
.trim()
|
|
401
|
+
|
|
402
|
+
if (!normalized) {
|
|
403
|
+
return ''
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (normalized.endsWith('?')) {
|
|
407
|
+
normalized = normalized.slice(0, -1).trim()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const maxLength = 155
|
|
411
|
+
if (normalized.length > maxLength) {
|
|
412
|
+
normalized = normalized.slice(0, maxLength).trim()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (normalized && !/[.!]$/.test(normalized)) {
|
|
416
|
+
if (normalized.length >= maxLength) {
|
|
417
|
+
normalized = normalized.slice(0, maxLength - 1).trim()
|
|
418
|
+
}
|
|
419
|
+
normalized += '.'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return normalized
|
|
423
|
+
},
|
|
424
|
+
normalizeTitle(title) {
|
|
425
|
+
if (!title) {
|
|
426
|
+
return ''
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let normalized = title
|
|
430
|
+
.toString()
|
|
431
|
+
.replace(/\s+/g, ' ')
|
|
432
|
+
.trim()
|
|
433
|
+
|
|
434
|
+
if (!normalized) {
|
|
435
|
+
return ''
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (normalized.length > 100) {
|
|
439
|
+
normalized = normalized.slice(0, 100).trim()
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return normalized
|
|
443
|
+
},
|
|
444
|
+
renumberWordIds() {
|
|
445
|
+
this.block.metadata.config.words.forEach((item, index) => {
|
|
446
|
+
item.id = index
|
|
447
|
+
})
|
|
448
|
+
},
|
|
449
|
+
showInvalidResponseToast() {
|
|
450
|
+
this.$toast.error(
|
|
451
|
+
this.$t(
|
|
452
|
+
'windward.games.components.settings.crossword.invalid_response'
|
|
453
|
+
),
|
|
454
|
+
{ duration: 5000 }
|
|
455
|
+
)
|
|
456
|
+
},
|
|
210
457
|
},
|
|
211
458
|
}
|
|
212
459
|
</script>
|