@windward/core 0.18.0 → 0.20.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 CHANGED
@@ -1,59 +1,58 @@
1
1
  # Changelog
2
2
 
3
- ### Release [0.18.0] created - 2025-05-21
3
+ ## Release [0.20.0] - 2025-07-01
4
4
 
5
+ * Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
6
+ * Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
7
+ * Merged in feature/LE-1948/empty-bucket-localization-fix (pull request #397)
8
+ * Merged in LE-1911-email-block-to-cc-and-subject-te (pull request #395)
9
+ * Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #391)
10
+ * Merged in bugfix/LE-1941-divider-dashed-line-renders-soli (pull request #396)
11
+ * Merged in feature/LE-1948/empty-bucket (pull request #393)
12
+ * Merged in feature/LE-1906-ai-assistant-button-text (pull request #394)
13
+ * Merged in bugfix/LE-1739-email-block-to-cc-and-subject-te (pull request #392)
14
+ * Merged in bugfix/LE-1917-student-experience-cleanup (pull request #386)
15
+ * Merged release/0.19.0 into bugfix/LE-1917-student-experience-cleanup
5
16
 
6
- ### Release [0.17.0] created - 2025-05-13
7
17
 
18
+ ### Release [0.19.0] created - 2025-05-30
8
19
 
9
- ### Release [0.16.0] created - 2025-04-29
20
+ ### Release [0.18.0] created - 2025-05-21
10
21
 
22
+ ### Release [0.17.0] created - 2025-05-13
11
23
 
12
- ### Release [0.15.0] created - 2025-04-09
24
+ ### Release [0.16.0] created - 2025-04-29
13
25
 
26
+ ### Release [0.15.0] created - 2025-04-09
14
27
 
15
28
  ### Release [0.14.0] created - 2025-03-25
16
29
 
17
-
18
30
  ### Release [0.16.0] created - 2025-03-11
19
31
 
20
-
21
32
  ### Hotfix [0.12.6] created - 2025-02-26
22
33
 
23
-
24
34
  ### Hotfix [0.12.5] created - 2025-02-26
25
35
 
26
-
27
36
  ### Hotfix [0.12.4] created - 2025-02-25
28
37
 
29
-
30
38
  ### Hotfix [0.12.3] created - 2025-02-21
31
39
 
32
-
33
40
  ### Hotfix [0.12.2] created - 2025-02-21
34
41
 
35
-
36
42
  ### Hotfix [0.12.1] created - 2025-02-21
37
43
 
38
-
39
44
  ### Release [0.12.0] created - 2025-02-18
40
45
 
41
-
42
46
  ### Release [0.11.0] created - 2025-02-05
43
47
 
44
-
45
48
  ### Release [0.10.0] created - 2025-01-03
46
49
 
47
-
48
50
  ### Hotfix [0.9.1] created - 2024-12-10
49
51
 
50
-
51
52
  ## Release [0.7.0] - 2024-08-29
52
53
 
53
- * Version bump to 0.7.0
54
-
54
+ - Version bump to 0.7.0
55
55
 
56
56
  ### Release [0.7.0] created - 2024-08-29
57
57
 
58
-
59
58
  ### Release [0.6.0] created - 2024-07-30
@@ -0,0 +1,35 @@
1
+ #!/bin/bash
2
+
3
+ # Checkout the release branch
4
+ git checkout master || exit 1
5
+ git pull origin master || exit 1
6
+
7
+ # Configure Git user for the current repository
8
+ git config user.email "commits-noreply@bitbucket.org"
9
+ git config user.name "bitbucket-pipelines"
10
+
11
+ # Extract version from the last merge commit message. Expecting `Merged in xx.xx.xx`
12
+ VERSION=$(basename "$(git log -1 --pretty=%B | grep -o -E "Merged in (release|hotfix)\/([0-9]+\.[0-9]+\.[0-9]+)")")
13
+
14
+ if [ -z "$VERSION" ]; then
15
+ echo "Error: Could not extract version from release branch name."
16
+ exit 1
17
+ fi
18
+
19
+ echo "Running auto-rc-tag delete for version $VERSION"
20
+
21
+ # Delete all rc tags for the version
22
+ rc_tags=$(git tag --list "$VERSION-rc*")
23
+
24
+ if [ -n "$rc_tags" ]; then
25
+ echo "Deleting rc tags for version $VERSION..."
26
+ for tag in $rc_tags; do
27
+ git tag -d "$tag"
28
+ git push origin ":refs/tags/$tag"
29
+ done
30
+ echo "All rc tags for version $VERSION have been deleted."
31
+ else
32
+ echo "No rc tags found for version $VERSION."
33
+ fi
34
+
35
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+
3
+ # Checkout the release branch
4
+ git checkout master || exit 1
5
+ git pull origin master || exit 1
6
+
7
+ # Configure Git user for the current repository
8
+ git config user.email "commits-noreply@bitbucket.org"
9
+ git config user.name "bitbucket-pipelines"
10
+
11
+ # Extract version from the last merge commit message. Expecting `Merged in xx.xx.xx`
12
+ VERSION=$(basename "$(git log -1 --pretty=%B | grep -o -E "Merged in (release|hotfix)\/([0-9]+\.[0-9]+\.[0-9]+)")")
13
+
14
+ if [ -z "$VERSION" ]; then
15
+ echo "Error: Could not extract version from release branch name."
16
+ exit 1
17
+ fi
18
+
19
+ echo "Running auto-tag for version $VERSION"
20
+
21
+
22
+ # Fetch the latest state of the repository
23
+ git fetch origin
24
+
25
+ # Get the latest commit on master branch
26
+ LATEST_MASTER_COMMIT=$(git rev-parse origin/master)
27
+ if [ $? -ne 0 ]; then
28
+ echo "Error: Failed to get the latest commit on master"
29
+ exit 1
30
+ fi
31
+
32
+ # Apply the tag to the latest master commit
33
+ git tag -a "$VERSION" -m "Tagging $VERSION" "$LATEST_MASTER_COMMIT"
34
+ if [ $? -ne 0 ]; then
35
+ echo "Error: Failed to tag the commit $LATEST_MASTER_COMMIT with tag $VERSION"
36
+ exit 1
37
+ fi
38
+
39
+ # Push the tag to the repository
40
+ git push origin "$VERSION"
41
+ if [ $? -ne 0 ]; then
42
+ echo "Error: Failed to push the tag $VERSION"
43
+ exit 1
44
+ fi
45
+
46
+ echo "Tag $VERSION successfully applied to commit $LATEST_MASTER_COMMIT and pushed to repository."
47
+
48
+ exit 0
@@ -19,4 +19,6 @@ pipelines:
19
19
  "release/*":
20
20
  import: infrastructure-automation:master:auto-update
21
21
  "hotfix/*":
22
- import: infrastructure-automation:master:auto-update
22
+ import: infrastructure-automation:master:auto-update
23
+ master:
24
+ import: infrastructure-automation:master:auto-publish-release
@@ -7,13 +7,7 @@
7
7
  "
8
8
  >
9
9
  <v-col cols="12" class="pa-0">
10
- <h2
11
- v-if="
12
- block.metadata.config.title &&
13
- block.metadata.config.display_title
14
- "
15
- tabindex="0"
16
- >
10
+ <h2 v-if="block.metadata.config.title" tabindex="0">
17
11
  {{ block.metadata.config.title }}
18
12
  </h2>
19
13
  <p
@@ -27,8 +21,8 @@
27
21
  </v-row>
28
22
  <v-row>
29
23
  <v-expansion-panels
30
- :key="expansionPanelKey"
31
24
  v-model="selectedPanels"
25
+ :key="expansionPanelKey"
32
26
  accordion
33
27
  >
34
28
  <v-container
@@ -130,23 +124,24 @@
130
124
  ></v-col>
131
125
  <v-col
132
126
  class="div-details"
133
- v-if="item.to || item.cc"
127
+ :style="{ color: detailsTextColor }"
128
+ v-if="item.to || item.cc || item.subject"
134
129
  >
135
- <div class="div-details-to">
130
+ <div v-if="item.to" class="div-details-to">
136
131
  {{
137
132
  $t(
138
133
  'windward.core.components.content.blocks.email.to'
139
134
  )
140
135
  }}: {{ item.to }}
141
136
  </div>
142
- <div class="div-details-cc">
137
+ <div v-if="item.cc" class="div-details-cc">
143
138
  {{
144
139
  $t(
145
140
  'windward.core.components.content.blocks.email.cc'
146
141
  )
147
142
  }}: {{ item.cc }}
148
143
  </div>
149
- <div class="div-details-cc">
144
+ <div v-if="item.subject" class="div-details-subject">
150
145
  {{
151
146
  $t(
152
147
  'windward.core.components.content.blocks.email.subject'
@@ -279,6 +274,16 @@ export default {
279
274
  selectedPanels: 0,
280
275
  }
281
276
  },
277
+ computed: {
278
+ detailsTextColor() {
279
+ // Use a different AAA compliant color based on the theme
280
+ if (this.$vuetify.theme.dark) {
281
+ return '#A8A8A8'
282
+ } else {
283
+ return '#595959'
284
+ }
285
+ },
286
+ },
282
287
  watch: {
283
288
  render(newValue) {
284
289
  if (newValue) {
@@ -311,7 +316,12 @@ export default {
311
316
  },
312
317
  methods: {
313
318
  onRemoveTags(body) {
314
- return body.replace(/(<([^>]+)>)/gi, '')
319
+ if (typeof body !== 'string') {
320
+ return ''
321
+ }
322
+ let text = body.replace(/&nbsp;/g, ' ')
323
+ text = text.replace(/\u00A0/g, ' ')
324
+ return text.replace(/(<([^>]+)>)/gi, '').trim()
315
325
  },
316
326
  async onBeforeSave() {
317
327
  this.block.metadata.config.emails.forEach((element) => {
@@ -333,12 +343,12 @@ export default {
333
343
  font-size: 12px;
334
344
  }
335
345
  .div-details {
336
- color: grey;
337
- font-size: 10px;
346
+ font-size: 14px;
338
347
  margin-top: -25px;
339
348
  }
340
- .div-details-cc {
341
- margin-top: -9px;
349
+ .div-details-cc,
350
+ .div-details-subject {
351
+ margin-top: -5px;
342
352
  }
343
353
  .span-details {
344
354
  font-weight: bold;
@@ -43,9 +43,11 @@ export default {
43
43
  </script>
44
44
  <style>
45
45
  .solid {
46
+ border: none;
46
47
  border-bottom: solid 1px var(--v-primary-base);
47
48
  }
48
49
  .dashed {
50
+ border: none;
49
51
  border-bottom: dashed 1px var(--v-primary-base);
50
52
  }
51
53
  </style>
@@ -35,7 +35,7 @@
35
35
  :enrollment="enrollment"
36
36
  ></DisplayUserFilesTable>
37
37
  </v-col>
38
- <v-col cols="12" v-if="showUpload">
38
+ <v-col v-if="showUpload" cols="12">
39
39
  <div v-if="blockExists">
40
40
  <v-form
41
41
  ref="form"
@@ -76,59 +76,23 @@
76
76
  <script>
77
77
  import _ from 'lodash'
78
78
  import { mapGetters } from 'vuex'
79
- import TextViewer from '~/components/Text/TextViewer'
80
- import TextEditor from '~/components/Text/TextEditor'
81
79
  import Uuid from '~/helpers/Uuid'
82
80
  import Download from '~/helpers/Download'
83
-
84
- import UserFileAsset from '../../../models/UserFileAsset'
85
81
  import Enrollment from '~/models/Enrollment'
86
82
  import ContentBlock from '~/models/ContentBlock'
87
83
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
88
84
  import FileDropZone from '~/components/Core/FileDropZone.vue'
85
+ import UserFileAsset from '../../../models/UserFileAsset'
89
86
 
90
87
  import DisplayUserFilesTable from './UserUpload/DisplayUserFilesTable.vue'
91
88
 
92
89
  export default {
93
90
  name: 'UserUpload',
94
91
  components: {
95
- TextEditor,
96
- TextViewer,
97
92
  FileDropZone,
98
93
  DisplayUserFilesTable,
99
94
  },
100
95
  extends: BaseContentBlock,
101
- beforeMount() {
102
- if (_.isEmpty(this.block)) {
103
- this.block = {}
104
- }
105
- // Default the instructions to collapsed
106
- this.block.__expandInstructions = false
107
-
108
- if (_.isEmpty(this.block.body)) {
109
- this.block.body = this.$t(
110
- 'windward.core.shared.content_blocks.title.user_upload'
111
- )
112
- }
113
- if (_.isEmpty(this.block.metadata)) {
114
- this.block.metadata = {}
115
- }
116
- if (_.isEmpty(this.block.metadata.config)) {
117
- this.block.metadata.config = {}
118
- }
119
- if (!_.isBoolean(this.block.metadata.config.display_title)) {
120
- this.$set(this.block.metadata.config, 'display_title', true)
121
- }
122
- if (_.isEmpty(this.block.metadata.config.instructions)) {
123
- this.block.metadata.config.instructions = ''
124
- }
125
- if (_.isEmpty(this.block.metadata.config.uploadSettings)) {
126
- this.block.metadata.config.uploadSettings = {
127
- multiple: false,
128
- accept: '',
129
- }
130
- }
131
- },
132
96
  data() {
133
97
  return {
134
98
  Download,
@@ -201,6 +165,37 @@ export default {
201
165
  }
202
166
  },
203
167
  },
168
+ beforeMount() {
169
+ if (_.isEmpty(this.block)) {
170
+ this.block = {}
171
+ }
172
+ // Default the instructions to collapsed
173
+ this.block.__expandInstructions = false
174
+
175
+ if (_.isEmpty(this.block.body)) {
176
+ this.block.body = this.$t(
177
+ 'windward.core.shared.content_blocks.title.user_upload'
178
+ )
179
+ }
180
+ if (_.isEmpty(this.block.metadata)) {
181
+ this.block.metadata = {}
182
+ }
183
+ if (_.isEmpty(this.block.metadata.config)) {
184
+ this.block.metadata.config = {}
185
+ }
186
+ if (!_.isBoolean(this.block.metadata.config.display_title)) {
187
+ this.$set(this.block.metadata.config, 'display_title', true)
188
+ }
189
+ if (_.isEmpty(this.block.metadata.config.instructions)) {
190
+ this.block.metadata.config.instructions = ''
191
+ }
192
+ if (_.isEmpty(this.block.metadata.config.uploadSettings)) {
193
+ this.block.metadata.config.uploadSettings = {
194
+ multiple: false,
195
+ accept: '',
196
+ }
197
+ }
198
+ },
204
199
  mounted() {
205
200
  if (this.blockExists) {
206
201
  this.loadUserUploads()
@@ -10,7 +10,13 @@
10
10
  <template #form="{ on, attrs }">
11
11
  <div v-bind="attrs" v-on="on">
12
12
  <v-alert
13
- v-if="!$ContextService.courseInSourceOrganization()"
13
+ v-if="
14
+ !$ContextService.courseInSourceOrganization() &&
15
+ $PermissionService.userHasAccessTo(
16
+ 'plugin.windward.core.organization.course.glossary',
17
+ 'writable'
18
+ )
19
+ "
14
20
  type="warning"
15
21
  >
16
22
  {{
@@ -23,11 +23,11 @@
23
23
  'windward.core.shared.settings.title.click_to_enter'
24
24
  )
25
25
  }}</template>
26
- <template #body="{ item, index }">
26
+ <template #body="{ index }">
27
27
  <v-container :key="expansionPanelKey">
28
28
  <v-text-field
29
29
  v-model="block.metadata.config.emails[index].from"
30
- :rules="$Validation.getRule('shortInput')"
30
+ :rules="getFromRules()"
31
31
  :counter="$Validation.getLimit('shortInput')"
32
32
  outlined
33
33
  :label="
@@ -36,20 +36,22 @@
36
36
  )
37
37
  "
38
38
  :disabled="render"
39
+ required
39
40
  ></v-text-field>
40
41
  <v-text-field
41
42
  v-model="block.metadata.config.emails[index].to"
42
- :rules="$Validation.getRule('shortInput')"
43
+ :rules="getToRules()"
43
44
  :counter="$Validation.getLimit('shortInput')"
44
45
  outlined
45
46
  :label="
46
47
  $t('windward.core.components.settings.email.to')
47
48
  "
48
49
  :disabled="render"
50
+ required
49
51
  ></v-text-field>
50
52
  <v-text-field
51
53
  v-model="block.metadata.config.emails[index].cc"
52
- :rules="$Validation.getRule('shortInput')"
54
+ :rules="getCcRules()"
53
55
  :counter="$Validation.getLimit('shortInput')"
54
56
  outlined
55
57
  :label="
@@ -61,7 +63,7 @@
61
63
  v-model="
62
64
  block.metadata.config.emails[index].subject
63
65
  "
64
- :rules="$Validation.getRule('shortInput')"
66
+ :rules="getSubjectRules()"
65
67
  :counter="$Validation.getLimit('shortInput')"
66
68
  outlined
67
69
  :label="
@@ -70,6 +72,7 @@
70
72
  )
71
73
  "
72
74
  :disabled="render"
75
+ required
73
76
  ></v-text-field>
74
77
  <v-btn
75
78
  class="mb-4"
@@ -249,9 +252,31 @@ export default {
249
252
  onDragged() {
250
253
  // need to remount body after dragged to update the content body on the element
251
254
  this.expansionPanelKey = Crypto.id()
252
- //close all panels on drag, leaving them open confuses the tracking as indexes changes around
255
+ // close all panels on drag, leaving them open confuses the tracking as indexes changes around
253
256
  this.block.metadata.config.selectedPanels = null
254
257
  },
258
+ getFromRules() {
259
+ return [
260
+ ...this.$Validation.getRule('shortInput'),
261
+ ...this.$Validation.getRule('exists'),
262
+ ]
263
+ },
264
+ getToRules() {
265
+ return [
266
+ ...this.$Validation.getRule('shortInput'),
267
+ ...this.$Validation.getRule('exists'),
268
+ ]
269
+ },
270
+ getCcRules() {
271
+ // CC field is not required, so only apply length validation
272
+ return this.$Validation.getRule('shortInput')
273
+ },
274
+ getSubjectRules() {
275
+ return [
276
+ ...this.$Validation.getRule('shortInput'),
277
+ ...this.$Validation.getRule('exists'),
278
+ ]
279
+ },
255
280
  },
256
281
  }
257
282
  </script>
@@ -35,50 +35,11 @@ import BaseContentSettings from '~/components/Content/Settings/BaseContentSettin
35
35
  import _ from 'lodash'
36
36
  import Uuid from '~/helpers/Uuid'
37
37
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
38
- import TextEditor from '~/components/Text/TextEditor'
39
38
 
40
39
  export default {
41
40
  name: 'UserUploadSettings',
42
- components: { TextEditor, BaseContentBlockSettings },
41
+ components: { BaseContentBlockSettings },
43
42
  extends: BaseContentSettings,
44
- beforeMount() {
45
- if (_.isEmpty(this.block)) {
46
- this.block = {}
47
- }
48
-
49
- if (_.isEmpty(this.block.body)) {
50
- this.block.body = this.$t(
51
- 'windward.core.shared.content_blocks.title.user_upload'
52
- )
53
- }
54
- if (_.isEmpty(this.block.metadata)) {
55
- this.block.metadata = {}
56
- }
57
- if (_.isEmpty(this.block.metadata.config)) {
58
- this.block.metadata.config = {}
59
- }
60
- if (_.isEmpty(this.block.metadata.config.title)) {
61
- this.block.metadata.config.title = ''
62
- }
63
- if (!_.isBoolean(this.block.metadata.config.display_title)) {
64
- this.$set(this.block.metadata.config, 'display_title', true)
65
- }
66
- if (
67
- _.isEmpty(this.block.metadata.config.instructions) &&
68
- this.block.id &&
69
- !Uuid.test(this.block.id)
70
- ) {
71
- this.block.metadata.config.instructions = this.$t(
72
- 'windward.core.components.settings.user_upload.instructions'
73
- )
74
- }
75
- if (_.isEmpty(this.block.metadata.config.uploadSettings)) {
76
- this.block.metadata.config.uploadSettings = {
77
- multiple: false,
78
- accept: '',
79
- }
80
- }
81
- },
82
43
  data() {
83
44
  return {
84
45
  valid: true,
@@ -89,7 +50,7 @@ export default {
89
50
  name: this.$t(
90
51
  'windward.core.components.settings.user_upload.types.all'
91
52
  ),
92
- value: '*',
53
+ value: 'image/jpeg,image/gif,image/png,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/zip',
93
54
  },
94
55
  {
95
56
  name: this.$t(
@@ -123,6 +84,44 @@ export default {
123
84
  },
124
85
  }
125
86
  },
87
+ beforeMount() {
88
+ if (_.isEmpty(this.block)) {
89
+ this.block = {}
90
+ }
91
+
92
+ if (_.isEmpty(this.block.body)) {
93
+ this.block.body = this.$t(
94
+ 'windward.core.shared.content_blocks.title.user_upload'
95
+ )
96
+ }
97
+ if (_.isEmpty(this.block.metadata)) {
98
+ this.block.metadata = {}
99
+ }
100
+ if (_.isEmpty(this.block.metadata.config)) {
101
+ this.block.metadata.config = {}
102
+ }
103
+ if (_.isEmpty(this.block.metadata.config.title)) {
104
+ this.block.metadata.config.title = ''
105
+ }
106
+ if (!_.isBoolean(this.block.metadata.config.display_title)) {
107
+ this.$set(this.block.metadata.config, 'display_title', true)
108
+ }
109
+ if (
110
+ _.isEmpty(this.block.metadata.config.instructions) &&
111
+ this.block.id &&
112
+ !Uuid.test(this.block.id)
113
+ ) {
114
+ this.block.metadata.config.instructions = this.$t(
115
+ 'windward.core.components.settings.user_upload.instructions'
116
+ )
117
+ }
118
+ if (_.isEmpty(this.block.metadata.config.uploadSettings)) {
119
+ this.block.metadata.config.uploadSettings = {
120
+ multiple: false,
121
+ accept: '',
122
+ }
123
+ }
124
+ },
126
125
  mounted() {},
127
126
  beforeDestroy() {
128
127
  if (this.debouncer) {
@@ -30,7 +30,7 @@
30
30
  item-text="text"
31
31
  class="btn-selector"
32
32
  outlined
33
- hide-details
33
+ :hide-details="!isBucketGameType"
34
34
  dense
35
35
  :label="
36
36
  $t(
@@ -39,11 +39,14 @@
39
39
  "
40
40
  return-object
41
41
  ></v-select>
42
+ <div v-if="isBucketGameType" class="text-caption mt-1">
43
+ {{ $t('windward.core.components.content.blocks.generate_questions.replaces_content') }}
44
+ </div>
42
45
  </v-col>
43
46
  <v-col
47
+ v-if="isFlashcardType"
44
48
  cols="auto"
45
49
  class="d-flex align-center"
46
- v-if="isFlashcardType"
47
50
  >
48
51
  <v-switch
49
52
  v-model="replaceExisting"
@@ -51,11 +54,7 @@
51
54
  dense
52
55
  color="secondary"
53
56
  class="mt-0 pt-0"
54
- :label="
55
- $t(
56
- 'windward.games.components.settings.flashcard.form.replace_existing'
57
- )
58
- "
57
+ :label="replaceExistingLabel"
59
58
  :disabled="isLoading"
60
59
  ></v-switch>
61
60
  </v-col>
@@ -71,11 +70,7 @@
71
70
  <v-icon v-if="!isLoading" class="pr-1"
72
71
  >mdi-magic-staff</v-icon
73
72
  >
74
- {{
75
- $t(
76
- 'windward.core.components.content.blocks.generate_questions.button_label'
77
- )
78
- }}
73
+ {{ buttonLabel }}
79
74
  <template v-slot:loader>
80
75
  <v-progress-circular
81
76
  indeterminate
@@ -116,7 +111,7 @@ export default {
116
111
  'windward.core.components.content.blocks.generate_questions.blooms.none'
117
112
  ),
118
113
  },
119
- replaceExisting: this.replaceExistingMode,
114
+ replaceExisting: this.questionType === 'bucket_game' ? true : this.replaceExistingMode,
120
115
  }
121
116
  },
122
117
  computed: {
@@ -126,6 +121,19 @@ export default {
126
121
  isFlashcardType() {
127
122
  return this.questionType === 'flashcard'
128
123
  },
124
+ isBucketGameType() {
125
+ return this.questionType === 'bucket_game'
126
+ },
127
+ replaceExistingLabel() {
128
+ if (this.isBucketGameType) {
129
+ return this.$t(
130
+ 'windward.games.components.settings.bucket_game.form.replace_existing'
131
+ )
132
+ }
133
+ return this.$t(
134
+ 'windward.games.components.settings.flashcard.form.replace_existing'
135
+ )
136
+ },
129
137
  flattenedContent() {
130
138
  let cloneContentTree = _.cloneDeep(this.contentTree)
131
139
  const homepage = this.$ContentService.getHomepage()
@@ -184,9 +192,10 @@ export default {
184
192
  ]
185
193
 
186
194
  // Only add higher-level Bloom's taxonomy for supported question types
187
- // Flashcards use only basic levels
195
+ // Flashcards and bucket games use only basic levels
188
196
  if (
189
197
  !this.isFlashcardType &&
198
+ !this.isBucketGameType &&
190
199
  (this.questionType === 'multi_choice_single_answer' ||
191
200
  this.questionType === 'ordering' ||
192
201
  this.questionType === 'multi_choice_multi_answer')
@@ -209,6 +218,21 @@ export default {
209
218
  }
210
219
  return basicBloomTaxonomy
211
220
  },
221
+ buttonLabel() {
222
+ if (this.questionType === 'flashcard') {
223
+ return this.$t(
224
+ 'windward.core.components.content.blocks.generate_questions.button_label_flashcard'
225
+ )
226
+ } else if (this.questionType === 'bucket_game') {
227
+ return this.$t(
228
+ 'windward.core.components.content.blocks.generate_questions.button_label_bucket_game'
229
+ )
230
+ } else {
231
+ return this.$t(
232
+ 'windward.core.components.content.blocks.generate_questions.button_label'
233
+ )
234
+ }
235
+ },
212
236
  },
213
237
  methods: {
214
238
  async generateAIQuestion() {
@@ -277,6 +301,52 @@ export default {
277
301
  'Invalid response from flashcard generation'
278
302
  )
279
303
  }
304
+ } else if (this.questionType === 'bucket_game') {
305
+ // BUCKET GAME GENERATION
306
+ const activity = new Activity()
307
+
308
+ const endpoint = `suggest/bucket_game${bloomsRequest}`
309
+
310
+ response = await Activity.custom(
311
+ course,
312
+ content,
313
+ activity,
314
+ endpoint
315
+ ).get()
316
+
317
+ let activityData = null
318
+
319
+ if (response && response.activity) {
320
+ activityData = response.activity
321
+ } else if (
322
+ response &&
323
+ response.length > 0 &&
324
+ response[0] &&
325
+ response[0].activity
326
+ ) {
327
+ activityData = response[0].activity
328
+ } else if (Array.isArray(response) && response.length > 0) {
329
+ activityData = response[0]
330
+ }
331
+
332
+ if (
333
+ activityData &&
334
+ activityData.metadata &&
335
+ activityData.metadata.config &&
336
+ activityData.metadata.config.bucket_titles &&
337
+ activityData.metadata.config.bucket_answers
338
+ ) {
339
+ // For bucket games, always use replace mode
340
+ this.$emit(
341
+ 'click:generate',
342
+ activityData,
343
+ true
344
+ )
345
+ } else {
346
+ throw new Error(
347
+ 'Invalid response from bucket game generation'
348
+ )
349
+ }
280
350
  } else {
281
351
  // ASSESSMENT QUESTION GENERATION
282
352
  const assessment = new Assessment({ id: this.block.id })
@@ -308,16 +378,32 @@ export default {
308
378
  const basePath =
309
379
  'windward.core.components.content.blocks.generate_questions.error'
310
380
 
311
- let errorText =
312
- this.$t(`${basePath}.${errorType}`) +
313
- '\n\n' +
314
- this.$t(`${basePath}.${errorType}_support`)
381
+ let errorText = ''
315
382
 
316
- if (errorType === 'technical') {
317
- const errorCode =
318
- error.response?.data?.error?.details?.error_type ||
319
- 'UNKNOWN'
320
- errorText = errorText.replace('[ERROR_CODE]', errorCode)
383
+ // Check for content mismatch error specifically for bucket games
384
+ if (
385
+ (errorMessage === 'activity.error.content_mismatch' ||
386
+ errorType === 'content_mismatch') &&
387
+ this.questionType === 'bucket_game'
388
+ ) {
389
+ errorText =
390
+ this.$t(`${basePath}.content_mismatch_bucket_game`) +
391
+ '\n\n' +
392
+ this.$t(
393
+ `${basePath}.content_mismatch_bucket_game_support`
394
+ )
395
+ } else {
396
+ errorText =
397
+ this.$t(`${basePath}.${errorType}`) +
398
+ '\n\n' +
399
+ this.$t(`${basePath}.${errorType}_support`)
400
+
401
+ if (errorType === 'technical') {
402
+ const errorCode =
403
+ error.response?.data?.error?.details?.error_type ||
404
+ 'UNKNOWN'
405
+ errorText = errorText.replace('[ERROR_CODE]', errorCode)
406
+ }
321
407
  }
322
408
 
323
409
  this.$dialog.error(errorText, {
@@ -343,3 +429,5 @@ export default {
343
429
  border-radius: 15px;
344
430
  }
345
431
  </style>
432
+ }
433
+ </style>
@@ -12,6 +12,10 @@ export default {
12
12
  content_mismatch_support:
13
13
  "The current content isn't suitable for this type of question. Consider adding more specific examples, numerical data, or comparable items depending on your desired question type.",
14
14
 
15
+ content_mismatch_bucket_game: "Content not suitable for bucket games.",
16
+ content_mismatch_bucket_game_support:
17
+ "Consider adding content with clear categories and multiple sortable items that can be grouped into 2-4 distinct buckets.",
18
+
15
19
  llm_unavailable: 'Question generation temporarily unavailable.',
16
20
  llm_unavailable_support:
17
21
  "We're unable to connect to our AI service at the moment. Please try again in a few minutes or contact support if the issue persists.",
@@ -21,6 +25,8 @@ export default {
21
25
  'Something went wrong on our end. Please try again or contact support if this continues. Reference code: [ERROR_CODE]',
22
26
  },
23
27
  button_label: 'Generate Question',
28
+ button_label_flashcard: 'Generate Flashcards',
29
+ button_label_bucket_game: 'Generate Buckets',
24
30
  selected_pages: 'Selected Page',
25
31
  ai_assistance: 'AI Assistance',
26
32
  blooms: {
@@ -32,4 +38,5 @@ export default {
32
38
  analyze: 'Analyze',
33
39
  evaluate: 'Evaluate',
34
40
  },
41
+ replaces_content: 'Replaces current block content.',
35
42
  }
@@ -7,4 +7,7 @@ export default {
7
7
  cc: 'CC',
8
8
  body: 'Email Body',
9
9
  placeholder: 'Email body text',
10
+ from_required: 'From field is required',
11
+ to_required: 'To field is required',
12
+ subject_required: 'Subject field is required',
10
13
  }
@@ -14,6 +14,10 @@ export default {
14
14
  content_mismatch_support:
15
15
  'Por favor, añada más texto, ejemplos o explicaciones a esta sección. Recomendamos al menos 50 palabras de contenido relevante para generar preguntas apropiadas.',
16
16
 
17
+ content_mismatch_bucket_game: 'El contenido no es adecuado para juegos de categorías.',
18
+ content_mismatch_bucket_game_support:
19
+ 'Considera agregar contenido con categorías claras y múltiples elementos clasificables que se puedan agrupar en 2-4 categorías distintas.',
20
+
17
21
  llm_unavailable:
18
22
  'La generación de preguntas no está disponible temporalmente.',
19
23
  llm_unavailable_support:
@@ -24,6 +28,8 @@ export default {
24
28
  'Algo salió mal. Inténtalo de nuevo o ponte en contacto con el servicio de asistencia si el problema persiste.',
25
29
  },
26
30
  button_label: 'Generar pregunta',
31
+ button_label_flashcard: 'Generar tarjetas',
32
+ button_label_bucket_game: 'Generar categorías',
27
33
  selected_pages: 'Página seleccionada',
28
34
  ai_assistance: 'Asistencia de IA',
29
35
  blooms: {
@@ -35,4 +41,5 @@ export default {
35
41
  analyze: 'Analizar',
36
42
  evaluate: 'Evaluar',
37
43
  },
44
+ replaces_content: 'Reemplaza el contenido del bloque actual.',
38
45
  }
@@ -7,4 +7,7 @@ export default {
7
7
  cc: 'CC',
8
8
  body: 'Cuerpo del correo electrónico',
9
9
  placeholder: 'Texto del cuerpo del correo electrónico',
10
+ from_required: 'El campo De es obligatorio',
11
+ to_required: 'El campo Para es obligatorio',
12
+ subject_required: 'El campo Asunto es obligatorio',
10
13
  }
@@ -12,6 +12,10 @@ export default {
12
12
  content_mismatch_support:
13
13
  'Vänligen lägg till mer text, exempel eller förklaringar till detta avsnitt. Vi rekommenderar minst 50 ord av relevant innehåll för att generera lämpliga frågor.',
14
14
 
15
+ content_mismatch_bucket_game: 'Innehållet är inte lämpligt för kategorispel.',
16
+ content_mismatch_bucket_game_support:
17
+ 'Överväg att lägga till innehåll med tydliga kategorier och flera sorterbara objekt som kan grupperas i 2-4 distinkta kategorier.',
18
+
15
19
  llm_unavailable: 'Frågegenerering tillfälligt otillgänglig.',
16
20
  llm_unavailable_support:
17
21
  'Vi kan inte ansluta till vår AI-tjänst för tillfället. Försök igen om några minuter.',
@@ -21,6 +25,8 @@ export default {
21
25
  'Något gick fel. Försök igen eller kontakta supporten om detta fortsätter.',
22
26
  },
23
27
  button_label: 'Generera fråga',
28
+ button_label_flashcard: 'Generera flashkort',
29
+ button_label_bucket_game: 'Generera kategorier',
24
30
  selected_pages: 'Vald sida',
25
31
  ai_assistance: 'AI-hjälp',
26
32
  blooms: {
@@ -32,4 +38,5 @@ export default {
32
38
  analyze: 'Analysera',
33
39
  evaluate: 'Utvärdera',
34
40
  },
41
+ replaces_content: 'Ersätter aktuellt blockinnehåll.',
35
42
  }
@@ -4,7 +4,10 @@ export default {
4
4
  subject: 'Ämne',
5
5
  from: 'Från',
6
6
  to: 'Till',
7
- cc: 'CC',
7
+ cc: 'Kopia',
8
8
  body: 'E-posttext',
9
- placeholder: 'E-postbrödtext',
9
+ placeholder: 'E-posttext',
10
+ from_required: 'Från-fältet är obligatoriskt',
11
+ to_required: 'Till-fältet är obligatoriskt',
12
+ subject_required: 'Ämne-fältet är obligatoriskt',
10
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/core",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Windward UI Core Plugins",
5
5
  "main": "plugin.js",
6
6
  "scripts": {