@windward/games 0.27.0 → 0.28.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 +27 -0
- package/components/content/blocks/flashcards/Flashcard.vue +15 -0
- package/components/content/blocks/flashcards/FlashcardSlides.vue +1 -30
- package/components/content/blocks/matchingGame/MatchingGame.vue +17 -24
- package/components/content/blocks/wordJumble/WordJumble.vue +23 -3
- package/components/settings/MatchingGameManager.vue +16 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.28.0] - 2026-03-10
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2298-word-jumble (pull request #288)
|
|
6
|
+
* Merged release/0.28.0 into feature/LE-2298-word-jumble
|
|
7
|
+
* Merged in feature/LE-2076-matching-game-allow-rich-text-ed (pull request #287)
|
|
8
|
+
* Merged release/0.28.0 into feature/LE-2076-matching-game-allow-rich-text-ed
|
|
9
|
+
* Merged in feature/LE-2076-matching-game-allow-rich-text-ed (pull request #286)
|
|
10
|
+
* Merged in bugfix/LE-2232-flashcards-when-the-prev-or-next (pull request #285)
|
|
11
|
+
* Merged release/0.28.0 into bugfix/LE-2232-flashcards-when-the-prev-or-next
|
|
12
|
+
* Merged release/0.28.0 into feature/LE-2076-matching-game-allow-rich-text-ed
|
|
13
|
+
* Merged in feature/LE-2076-matching-game-allow-rich-text-ed (pull request #281)
|
|
14
|
+
* Merged in feature/LE-2298-word-jumble (pull request #282)
|
|
15
|
+
* Merged release/0.28.0 into bugfix/LE-2232-flashcards-when-the-prev-or-next
|
|
16
|
+
* Merged release/0.28.0 into feature/LE-2298-word-jumble
|
|
17
|
+
* Merged master into release/0.28.0
|
|
18
|
+
* Merged in release/0.27.0 (pull request #280)
|
|
19
|
+
* Merged in release/0.26.0 (pull request #269)
|
|
20
|
+
* Merged in release/0.25.0 (pull request #266)
|
|
21
|
+
* Merged in release/0.24.0 (pull request #261)
|
|
22
|
+
* Merged in release/0.23.0 (pull request #256)
|
|
23
|
+
* Merged in release/0.22.0 (pull request #248)
|
|
24
|
+
* Merged in release/0.21.0 (pull request #244)
|
|
25
|
+
* Merged in release/0.20.0 (pull request #238)
|
|
26
|
+
* Merged in hotfix/0.19.1 (pull request #236)
|
|
27
|
+
* Merged in hotfix/0.19.1 (pull request #232)
|
|
28
|
+
|
|
29
|
+
|
|
3
30
|
## Release [0.27.0] - 2026-01-27
|
|
4
31
|
|
|
5
32
|
* Merged in bugfix/LE-2232-flashcards-when-the-prev-or-next (pull request #274)
|
|
@@ -66,6 +66,7 @@ export default {
|
|
|
66
66
|
options: { type: Object, default: {} },
|
|
67
67
|
slide: { type: Number, required: true, default: 0 },
|
|
68
68
|
assets: { type: Array, required: true },
|
|
69
|
+
index: { type: Number, required: true, default: 0 },
|
|
69
70
|
},
|
|
70
71
|
computed: {
|
|
71
72
|
cardClass() {
|
|
@@ -111,8 +112,22 @@ export default {
|
|
|
111
112
|
}
|
|
112
113
|
},
|
|
113
114
|
},
|
|
115
|
+
watch: {
|
|
116
|
+
slide(newValue) {
|
|
117
|
+
if (newValue === this.index) {
|
|
118
|
+
this.focusCard()
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
},
|
|
114
122
|
mounted() {
|
|
115
123
|
this.side = true
|
|
124
|
+
// next tick doesn't work here. Need to wait for v-carousel animations
|
|
125
|
+
// to finish to focus on the card even if it is mounted
|
|
126
|
+
if (this.slide === this.index) {
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
this.focusCard()
|
|
129
|
+
}, 250)
|
|
130
|
+
}
|
|
116
131
|
},
|
|
117
132
|
methods: {
|
|
118
133
|
toggleCard() {
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
ref="flashcard"
|
|
63
63
|
:options="card"
|
|
64
64
|
:slide="currentSlide"
|
|
65
|
+
:index="index"
|
|
65
66
|
:key="seed + '-' + currentSlide"
|
|
66
67
|
:assets="block.assets"
|
|
67
68
|
/>
|
|
@@ -128,12 +129,6 @@ export default {
|
|
|
128
129
|
}
|
|
129
130
|
},
|
|
130
131
|
computed: {
|
|
131
|
-
slide() {
|
|
132
|
-
if (this.block.metadata.config.cards.length > 0) {
|
|
133
|
-
return this.currentSlide + 1
|
|
134
|
-
}
|
|
135
|
-
return 0
|
|
136
|
-
},
|
|
137
132
|
styleForText() {
|
|
138
133
|
// if highlight is set, use black instead of primary
|
|
139
134
|
if (
|
|
@@ -146,31 +141,7 @@ export default {
|
|
|
146
141
|
}
|
|
147
142
|
},
|
|
148
143
|
},
|
|
149
|
-
watch: {
|
|
150
|
-
currentSlide() {
|
|
151
|
-
this.focusCurrentFlashcard()
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
144
|
methods: {
|
|
155
|
-
focusCurrentFlashcard() {
|
|
156
|
-
// nexttick wasn't working here for refs on v-carousel
|
|
157
|
-
// setting hard wait time for slide to load
|
|
158
|
-
setTimeout(() => {
|
|
159
|
-
const refs = this.$refs.flashcard
|
|
160
|
-
// Refs not ready yet
|
|
161
|
-
if (!refs) {
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
const slideNumber = this.currentSlide || 0
|
|
165
|
-
|
|
166
|
-
// Access refs directly - don't try to convert to array
|
|
167
|
-
const current = refs[slideNumber]
|
|
168
|
-
|
|
169
|
-
if (current && typeof current.focusCard === 'function') {
|
|
170
|
-
current.focusCard()
|
|
171
|
-
}
|
|
172
|
-
}, 500)
|
|
173
|
-
},
|
|
174
145
|
onActionPanelEdit() {
|
|
175
146
|
this.render = !this.render
|
|
176
147
|
},
|
|
@@ -76,12 +76,10 @@
|
|
|
76
76
|
group="people"
|
|
77
77
|
tabindex="0"
|
|
78
78
|
>
|
|
79
|
-
<
|
|
79
|
+
<TextViewer
|
|
80
80
|
class="card_text secondary"
|
|
81
|
-
|
|
82
|
-
>
|
|
83
|
-
{{ mainPrompt['prompt'] }}
|
|
84
|
-
</div>
|
|
81
|
+
v-model="mainPrompt['prompt']"
|
|
82
|
+
></TextViewer>
|
|
85
83
|
</draggable>
|
|
86
84
|
</v-card-text>
|
|
87
85
|
</v-card>
|
|
@@ -155,16 +153,14 @@
|
|
|
155
153
|
group="people"
|
|
156
154
|
tabindex="0"
|
|
157
155
|
>
|
|
158
|
-
<
|
|
156
|
+
<TextViewer
|
|
159
157
|
class="card_text secondary"
|
|
160
|
-
|
|
161
|
-
>
|
|
162
|
-
{{
|
|
158
|
+
v-model="
|
|
163
159
|
block.metadata.config.prompts[
|
|
164
160
|
startingIndex
|
|
165
161
|
][startingIndex].prompt
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
"
|
|
163
|
+
></TextViewer>
|
|
168
164
|
</draggable>
|
|
169
165
|
</v-card-text>
|
|
170
166
|
</v-card>
|
|
@@ -321,10 +317,11 @@ import _ from 'lodash'
|
|
|
321
317
|
import draggable from 'vuedraggable'
|
|
322
318
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
323
319
|
import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
|
|
320
|
+
import TextViewer from '~/components/Text/TextViewer'
|
|
324
321
|
|
|
325
322
|
export default {
|
|
326
323
|
name: 'MatchingGame',
|
|
327
|
-
components: { draggable, ImageAssetViewer },
|
|
324
|
+
components: { draggable, ImageAssetViewer, TextViewer },
|
|
328
325
|
extends: BaseContentBlock,
|
|
329
326
|
beforeMount() {
|
|
330
327
|
if (_.isEmpty(this.block)) {
|
|
@@ -483,21 +480,10 @@ export default {
|
|
|
483
480
|
if (evt.to === evt.from) {
|
|
484
481
|
return
|
|
485
482
|
}
|
|
486
|
-
|
|
487
483
|
const { answerObjects, prompts } = this.block.metadata.config
|
|
488
484
|
const draggedElement = answerObjects[evt.oldIndex]
|
|
489
|
-
// flatten nested prompts array and then find target by either text or image
|
|
490
|
-
let target = prompts
|
|
491
|
-
.flatMap((outerElement) => outerElement)
|
|
492
|
-
.find((element) =>
|
|
493
|
-
this.mainPrompt.textOrImage === 'text'
|
|
494
|
-
? element.prompt ===
|
|
495
|
-
evt.to.firstChild?.textContent.trim()
|
|
496
|
-
: element.fileConfig.asset?.file_asset_id ===
|
|
497
|
-
evt.to.firstElementChild?.id
|
|
498
|
-
)
|
|
499
485
|
|
|
500
|
-
this.setFeedback(
|
|
486
|
+
this.setFeedback(this.mainPrompt, draggedElement)
|
|
501
487
|
},
|
|
502
488
|
setFeedback(target, draggedElement) {
|
|
503
489
|
//set feedback information here
|
|
@@ -565,6 +551,12 @@ export default {
|
|
|
565
551
|
.dragArea {
|
|
566
552
|
min-height: 100%;
|
|
567
553
|
min-width: 100%;
|
|
554
|
+
height: auto;
|
|
555
|
+
}
|
|
556
|
+
.dragArea /deep/ .text-viewer {
|
|
557
|
+
overflow: visible !important;
|
|
558
|
+
height: auto !important;
|
|
559
|
+
max-height: none !important;
|
|
568
560
|
}
|
|
569
561
|
.card-bucket {
|
|
570
562
|
line-height: 1.1em;
|
|
@@ -580,6 +572,7 @@ export default {
|
|
|
580
572
|
}
|
|
581
573
|
.card_text {
|
|
582
574
|
font-size: large;
|
|
575
|
+
height: auto;
|
|
583
576
|
}
|
|
584
577
|
.card-answer-option {
|
|
585
578
|
min-width: 23%;
|
|
@@ -91,7 +91,11 @@
|
|
|
91
91
|
{{ word.value }}
|
|
92
92
|
</p>
|
|
93
93
|
</v-row>
|
|
94
|
-
<v-alert
|
|
94
|
+
<v-alert
|
|
95
|
+
v-if="showFeedback"
|
|
96
|
+
fluid
|
|
97
|
+
:class="feedbackStatus"
|
|
98
|
+
>
|
|
95
99
|
<v-row class="pa-4 d-flex justify-center">
|
|
96
100
|
<p class="mb-0 p-text-override">
|
|
97
101
|
{{
|
|
@@ -111,6 +115,7 @@
|
|
|
111
115
|
"
|
|
112
116
|
color="success"
|
|
113
117
|
elevation="0"
|
|
118
|
+
:ref="'continueButton' + index"
|
|
114
119
|
@click="onSlideChanged(true)"
|
|
115
120
|
>{{ $t('shared.forms.continue') }}
|
|
116
121
|
</v-btn>
|
|
@@ -120,7 +125,8 @@
|
|
|
120
125
|
'container-feedback-error'
|
|
121
126
|
"
|
|
122
127
|
color="error"
|
|
123
|
-
|
|
128
|
+
:ref="'tryAgainButton' + index"
|
|
129
|
+
@click="onHideFeedback(index)"
|
|
124
130
|
>{{
|
|
125
131
|
$t(
|
|
126
132
|
'windward.games.components.content.blocks.bucket_game.try_again'
|
|
@@ -142,6 +148,7 @@
|
|
|
142
148
|
<v-row class="d-flex mt-8">
|
|
143
149
|
<v-col class="d-flex justify-end">
|
|
144
150
|
<v-btn
|
|
151
|
+
:ref="'checkAnswer' + index"
|
|
145
152
|
:disabled="disableButtons"
|
|
146
153
|
color="primary"
|
|
147
154
|
elevation="0"
|
|
@@ -297,15 +304,20 @@ export default {
|
|
|
297
304
|
this.showAnswer = true
|
|
298
305
|
this.disableButtons = true
|
|
299
306
|
},
|
|
300
|
-
onHideFeedback() {
|
|
307
|
+
onHideFeedback(index) {
|
|
301
308
|
this.showFeedback = false
|
|
302
309
|
this.disableButtons = false
|
|
310
|
+
this.$nextTick(() => {
|
|
311
|
+
const buttonLabel = 'checkAnswer' + index
|
|
312
|
+
this.$refs[buttonLabel][0].$el.focus()
|
|
313
|
+
})
|
|
303
314
|
},
|
|
304
315
|
onCheckAnswer(word) {
|
|
305
316
|
this.studentResponse = this.studentResponse.toLowerCase()
|
|
306
317
|
this.showFeedback = true
|
|
307
318
|
this.disableButtons = true
|
|
308
319
|
const answer = word.value.toLowerCase()
|
|
320
|
+
const wordIndex = this.block.metadata.config.words.indexOf(word)
|
|
309
321
|
if (
|
|
310
322
|
!_.isEmpty(this.studentResponse) &&
|
|
311
323
|
this.studentResponse === answer
|
|
@@ -323,6 +335,10 @@ export default {
|
|
|
323
335
|
'windward.games.components.content.blocks.word_jumble.correct'
|
|
324
336
|
)
|
|
325
337
|
}
|
|
338
|
+
this.$nextTick(() => {
|
|
339
|
+
const buttonLabel = 'continueButton' + wordIndex
|
|
340
|
+
this.$refs[buttonLabel][0].$el.focus()
|
|
341
|
+
})
|
|
326
342
|
} else {
|
|
327
343
|
// updates class
|
|
328
344
|
this.feedbackStatus = 'container-feedback-error'
|
|
@@ -338,6 +354,10 @@ export default {
|
|
|
338
354
|
'windward.games.components.content.blocks.word_jumble.incorrect'
|
|
339
355
|
)
|
|
340
356
|
}
|
|
357
|
+
this.$nextTick(() => {
|
|
358
|
+
const buttonLabel = 'tryAgainButton' + wordIndex
|
|
359
|
+
this.$refs[buttonLabel][0].$el.focus()
|
|
360
|
+
})
|
|
341
361
|
}
|
|
342
362
|
},
|
|
343
363
|
onSlideChanged(changeSlide) {
|
|
@@ -72,9 +72,11 @@
|
|
|
72
72
|
<span v-if="prompt.textOrImage === 'text'">
|
|
73
73
|
{{
|
|
74
74
|
prompt.prompt
|
|
75
|
-
?
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
? removeHtmlTags(
|
|
76
|
+
block.metadata.config.prompts[
|
|
77
|
+
index
|
|
78
|
+
][indexPrompt].prompt
|
|
79
|
+
)
|
|
78
80
|
: $t(
|
|
79
81
|
'windward.games.components.content.blocks.matching_game.click_here'
|
|
80
82
|
)
|
|
@@ -143,30 +145,24 @@
|
|
|
143
145
|
</v-row>
|
|
144
146
|
</v-container>
|
|
145
147
|
<v-container class="pa-0">
|
|
146
|
-
<
|
|
148
|
+
<TextEditor
|
|
147
149
|
v-if="prompt.textOrImage === 'text'"
|
|
148
150
|
v-model="
|
|
149
151
|
block.metadata.config.prompts[
|
|
150
152
|
index
|
|
151
153
|
][indexPrompt].prompt
|
|
152
154
|
"
|
|
153
|
-
:rules="
|
|
154
|
-
$Validation.getRule('longInput')
|
|
155
|
-
"
|
|
156
|
-
:counter="
|
|
157
|
-
$Validation.getLimit(
|
|
158
|
-
'longInput'
|
|
159
|
-
)
|
|
160
|
-
"
|
|
161
155
|
:label="
|
|
162
156
|
$t(
|
|
163
157
|
'windward.games.components.settings.matching_game.form.prompt_body'
|
|
164
158
|
)
|
|
165
159
|
"
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
toolbar="styles | bold italic underline strikethrough removeformat | alignleft aligncenter alignright | mathButton a11yButton reviseText | undo redo"
|
|
161
|
+
menubar="view"
|
|
162
|
+
:default-alignment="'aligncenter'"
|
|
168
163
|
:disabled="render"
|
|
169
|
-
|
|
164
|
+
class="mb-3"
|
|
165
|
+
></TextEditor>
|
|
170
166
|
</v-container>
|
|
171
167
|
<v-container
|
|
172
168
|
v-if="prompt.textOrImage === 'image'"
|
|
@@ -278,6 +274,7 @@ import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentB
|
|
|
278
274
|
import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
|
|
279
275
|
import Uuid from '~/helpers/Uuid'
|
|
280
276
|
import ImageAssetSettings from '~/components/Content/Settings/ImageAssetSettings.vue'
|
|
277
|
+
import TextEditor from '~/components/Text/TextEditor'
|
|
281
278
|
|
|
282
279
|
export default {
|
|
283
280
|
name: 'MatchingGameManager',
|
|
@@ -287,6 +284,7 @@ export default {
|
|
|
287
284
|
ImageAssetSettings,
|
|
288
285
|
BaseContentBlockSettings,
|
|
289
286
|
PluginRef,
|
|
287
|
+
TextEditor,
|
|
290
288
|
},
|
|
291
289
|
beforeMount() {
|
|
292
290
|
if (_.isEmpty(this.block)) {
|
|
@@ -357,6 +355,9 @@ export default {
|
|
|
357
355
|
},
|
|
358
356
|
mounted() {},
|
|
359
357
|
methods: {
|
|
358
|
+
removeHtmlTags(input) {
|
|
359
|
+
return input.replace(/<\/?[a-z][^>]*(>|$)/gi, '')
|
|
360
|
+
},
|
|
360
361
|
onAddPrompt(index = 0) {
|
|
361
362
|
if (this.block.metadata.config.prompts[index] === undefined) {
|
|
362
363
|
// forces the component to track the state by making this an observable
|