@windward/core 0.14.0 → 0.16.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/bitbucket-pipelines.yml +8 -0
  3. package/components/Content/Blocks/BlockQuote.vue +51 -47
  4. package/components/Content/Blocks/GenerateAIQuestionButton.vue +151 -37
  5. package/components/Content/Blocks/OpenResponse.vue +11 -3
  6. package/components/Content/Blocks/UserUpload/ManageDataTableUserFiles.vue +2 -11
  7. package/components/Content/Blocks/Video.vue +1 -1
  8. package/components/Glossary/GlossaryVerification.vue +240 -0
  9. package/components/Navigation/Items/GlossaryNav.vue +8 -2
  10. package/components/Navigation/Items/UserUploadNav.vue +32 -13
  11. package/components/Settings/AccordionSettings.vue +37 -27
  12. package/components/Settings/ClickableIconsSettings.vue +1 -0
  13. package/components/Settings/ImageSettings.vue +5 -12
  14. package/components/Settings/TabSettings.vue +33 -23
  15. package/components/Settings/TextEditorSettings.vue +16 -245
  16. package/components/Settings/VideoSettings.vue +1 -1
  17. package/components/utils/ContentViewer.vue +3 -3
  18. package/components/utils/TinyMCEWrapper.vue +24 -14
  19. package/components/utils/glossary/CourseGlossary.vue +75 -95
  20. package/components/utils/glossary/CourseGlossaryForm.vue +42 -13
  21. package/components/utils/glossary/GlossaryToolTip.vue +7 -11
  22. package/helpers/GlossaryHelper.ts +18 -16
  23. package/i18n/en-US/components/content/blocks/generate_questions.ts +26 -11
  24. package/i18n/en-US/pages/glossary.ts +3 -1
  25. package/i18n/en-US/shared/content_blocks.ts +1 -1
  26. package/i18n/en-US/shared/notification.ts +5 -0
  27. package/i18n/en-US/shared/permission.ts +4 -0
  28. package/i18n/en-US/shared/settings.ts +1 -1
  29. package/i18n/es-ES/components/content/blocks/generate_questions.ts +11 -1
  30. package/i18n/es-ES/pages/glossary.ts +3 -1
  31. package/i18n/es-ES/shared/content_blocks.ts +1 -1
  32. package/i18n/es-ES/shared/notification.ts +5 -0
  33. package/i18n/es-ES/shared/permission.ts +8 -4
  34. package/i18n/es-ES/shared/settings.ts +1 -2
  35. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +11 -1
  36. package/i18n/sv-SE/pages/glossary.ts +3 -1
  37. package/i18n/sv-SE/shared/content_blocks.ts +1 -1
  38. package/i18n/sv-SE/shared/notification.ts +4 -0
  39. package/i18n/sv-SE/shared/permission.ts +8 -4
  40. package/i18n/sv-SE/shared/settings.ts +1 -1
  41. package/jest.config.js +3 -0
  42. package/models/BaseModel.ts +16 -0
  43. package/models/CourseGlossaryTerm.ts +22 -0
  44. package/models/UserFileAsset.ts +1 -0
  45. package/package.json +4 -3
  46. package/pages/glossary.vue +41 -4
  47. package/pages/userUpload.vue +70 -47
  48. package/plugin.js +34 -12
  49. package/store/glossary.js +57 -0
  50. package/test/Components/Content/Blocks/Feedback.spec.js +3 -1
  51. package/test/Components/Content/Blocks/Video.spec.js +2 -2
  52. package/test/Components/Glossary/GlossaryVerification.spec.js +19 -0
  53. package/test/Components/utils/glossary/CourseGlossary.spec.js +19 -0
  54. package/test/Components/utils/glossary/CourseGlossaryForm.spec.js +28 -0
  55. package/test/__mocks__/helpersMock.js +0 -24
  56. package/test/__mocks__/modelMock.js +5 -0
  57. package/test/helpers/GlossaryHelper.spec.js +32 -14
  58. package/test/mocks.js +11 -0
  59. package/test/setup/before.js +15 -0
  60. package/helpers/GlossaryTerm.ts +0 -31
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ### Release [0.16.0] created - 2025-04-29
4
+
5
+
6
+ ### Release [0.15.0] created - 2025-04-09
7
+
8
+
3
9
  ### Release [0.14.0] created - 2025-03-25
4
10
 
5
11
 
@@ -1,6 +1,14 @@
1
1
  image: atlassian/default-image:3
2
2
 
3
3
  pipelines:
4
+ pull-requests:
5
+ '**':
6
+ - step:
7
+ name: Build and test
8
+ image: node:16.20.2
9
+ script:
10
+ - npm install
11
+ - npm run test
4
12
  custom:
5
13
  create-release:
6
14
  import: infrastructure-automation:master:create-release
@@ -34,7 +34,9 @@
34
34
  <v-row>
35
35
  <v-col cols="12">
36
36
  <p>
37
- {{ citation }}
37
+ <span>{{
38
+ citation && source ? citation + ',' : citation
39
+ }}</span>
38
40
  <span :class="sourceClass">{{ source }}</span>
39
41
  </p>
40
42
  </v-col>
@@ -51,38 +53,10 @@ import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
51
53
 
52
54
  export default {
53
55
  name: 'ContentBlockTab',
54
- extends: BaseContentBlock,
55
- beforeMount() {
56
- if (_.isEmpty(this.block)) {
57
- this.block = {}
58
- }
59
- if (_.isEmpty(this.block.metadata)) {
60
- this.block.metadata = {}
61
- }
62
- if (_.isEmpty(this.block.metadata.config)) {
63
- this.block.metadata.config = {}
64
- }
65
- if (_.isEmpty(this.block.metadata.config.title)) {
66
- this.block.metadata.config.title = ''
67
- }
68
- if (_.isEmpty(this.block.metadata.config.instructions)) {
69
- this.block.metadata.config.instructions = ''
70
- }
71
- if (_.isEmpty(this.block.metadata.config.block_quote)) {
72
- this.block.metadata.config.block_quote = {
73
- quote: '',
74
- author: '',
75
- author_title: '',
76
- organization: '',
77
- source_title: '',
78
- source_type: '',
79
- source_url: '',
80
- }
81
- }
82
- this.block.body = this.$t(
83
- 'windward.core.components.content.blocks.block_quote.body'
84
- )
56
+ components: {
57
+ TextViewer,
85
58
  },
59
+ extends: BaseContentBlock,
86
60
  data() {
87
61
  return {}
88
62
  },
@@ -101,7 +75,6 @@ export default {
101
75
  parts.push(value)
102
76
  }
103
77
  }
104
-
105
78
  // If there were parts to return then prepend with a mdash
106
79
  if (parts.length > 0) {
107
80
  return he.decode('&mdash;') + parts.join(', ')
@@ -113,24 +86,23 @@ export default {
113
86
  const parts = []
114
87
 
115
88
  const sourceTitle =
116
- this.block.metadata.config.block_quote['source_title'] || ''
89
+ this.block.metadata.config.block_quote.source_title || ''
117
90
  const sourceType =
118
- this.block.metadata.config.block_quote['source_type'] || ''
91
+ this.block.metadata.config.block_quote.source_type || ''
119
92
 
120
- if (sourceTitle && sourceType) {
93
+ if (sourceType) {
121
94
  switch (sourceType) {
122
95
  case 'online_journal':
123
96
  // Wrap the sourceTitle for online journals in quotes
124
- parts.push('"' + sourceTitle + '"')
97
+ if (!_.isEmpty(sourceTitle)) {
98
+ parts.push('"' + sourceTitle + '"')
99
+ }
125
100
 
126
101
  // For online journals also add the url if it exists
127
- if (
128
- this.block.metadata.config.block_quote['source_url']
129
- ) {
102
+ if (this.block.metadata.config.block_quote.source_url) {
130
103
  parts.push(
131
- this.block.metadata.config.block_quote[
132
- 'source_url'
133
- ]
104
+ this.block.metadata.config.block_quote
105
+ .source_url
134
106
  )
135
107
  }
136
108
  break
@@ -140,7 +112,6 @@ export default {
140
112
  break
141
113
  }
142
114
  }
143
-
144
115
  // If the citation is empty (aka we only have sources)
145
116
  // and there's sources then prepend with a mdash
146
117
  if (parts.length > 0 && this.citation === '') {
@@ -154,14 +125,47 @@ export default {
154
125
  },
155
126
  sourceClass() {
156
127
  if (
157
- this.block.metadata.config.block_quote['source_title'] &&
158
- this.block.metadata.config.block_quote['source_type'] &&
159
- this.block.metadata.config.block_quote['source_type'] === 'book'
128
+ this.block.metadata.config.block_quote.source_title &&
129
+ this.block.metadata.config.block_quote.source_type &&
130
+ this.block.metadata.config.block_quote.source_type === 'book'
160
131
  ) {
161
132
  return 'span-title'
133
+ } else {
134
+ return ''
162
135
  }
163
136
  },
164
137
  },
138
+ beforeMount() {
139
+ if (_.isEmpty(this.block)) {
140
+ this.block = {}
141
+ }
142
+ if (_.isEmpty(this.block.metadata)) {
143
+ this.block.metadata = {}
144
+ }
145
+ if (_.isEmpty(this.block.metadata.config)) {
146
+ this.block.metadata.config = {}
147
+ }
148
+ if (_.isEmpty(this.block.metadata.config.title)) {
149
+ this.block.metadata.config.title = ''
150
+ }
151
+ if (_.isEmpty(this.block.metadata.config.instructions)) {
152
+ this.block.metadata.config.instructions = ''
153
+ }
154
+ if (_.isEmpty(this.block.metadata.config.block_quote)) {
155
+ this.block.metadata.config.block_quote = {
156
+ quote: '',
157
+ author: '',
158
+ author_title: '',
159
+ organization: '',
160
+ source_title: '',
161
+ source_type: '',
162
+ source_url: '',
163
+ }
164
+ }
165
+ this.block.body = this.$t(
166
+ 'windward.core.components.content.blocks.block_quote.body'
167
+ )
168
+ },
165
169
  mounted() {},
166
170
  methods: {},
167
171
  }
@@ -1,7 +1,46 @@
1
1
  <template>
2
2
  <div>
3
- <v-row>
4
- <v-col cols="12" class="d-flex justify-end pb-1">
3
+ <v-row class="container-generate-ai mt-2">
4
+ <v-col>{{
5
+ $t(
6
+ 'windward.core.components.content.blocks.generate_questions.ai_assistance'
7
+ )
8
+ }}</v-col>
9
+ <v-col>
10
+ <v-select
11
+ v-model="selectedContent"
12
+ :items="flattenedContent"
13
+ class="btn-selector"
14
+ outlined
15
+ hide-details
16
+ dense
17
+ :label="
18
+ $t(
19
+ 'windward.core.components.content.blocks.generate_questions.selected_pages'
20
+ )
21
+ "
22
+ item-text="content.name"
23
+ return-object
24
+ ></v-select>
25
+ </v-col>
26
+ <v-col>
27
+ <v-select
28
+ v-model="selectedDifficulty"
29
+ :items="taxonomyLevels"
30
+ item-text="text"
31
+ class="btn-selector"
32
+ outlined
33
+ hide-details
34
+ dense
35
+ :label="
36
+ $t(
37
+ 'windward.core.components.content.blocks.generate_questions.blooms.blooms_taxonomy'
38
+ )
39
+ "
40
+ return-object
41
+ ></v-select>
42
+ </v-col>
43
+ <v-col>
5
44
  <v-btn
6
45
  elevation="0"
7
46
  color="secondary"
@@ -10,37 +49,33 @@
10
49
  :disabled="isLoading"
11
50
  @click="generateAIQuestion"
12
51
  >
13
- <v-icon class="pr-1" v-if="!isLoading">mdi-magic-staff</v-icon>
14
- {{ $t('windward.core.components.content.blocks.generate_questions.button_label') }}
52
+ <v-icon v-if="!isLoading" class="pr-1"
53
+ >mdi-magic-staff</v-icon
54
+ >
55
+ {{
56
+ $t(
57
+ 'windward.core.components.content.blocks.generate_questions.button_label'
58
+ )
59
+ }}
15
60
  <template v-slot:loader>
16
- <v-progress-circular indeterminate size="23"></v-progress-circular>
61
+ <v-progress-circular
62
+ indeterminate
63
+ size="23"
64
+ ></v-progress-circular>
17
65
  </template>
18
66
  </v-btn>
19
67
  </v-col>
20
- <v-col cols="12" class="d-flex justify-end pt-0">
21
- <v-select
22
- v-model="selectedContent"
23
- :items="flattenedContent"
24
- class="btn-selector"
25
- outlined
26
- hide-details
27
- dense
28
- :label="$t('windward.core.components.content.blocks.generate_questions.selected_pages')"
29
- item-text="content.name"
30
- return-object
31
- ></v-select>
32
- </v-col>
33
68
  </v-row>
34
69
  </div>
35
70
  </template>
36
71
 
37
72
  <script>
73
+ import _ from 'lodash'
74
+ import { mapGetters } from 'vuex'
38
75
  import AssessmentQuestion from '~/models/AssessmentQuestion'
39
76
  import Course from '~/models/Course'
40
77
  import Assessment from '~/models/Assessment'
41
78
  import Content from '~/models/Content'
42
- import { mapGetters } from 'vuex'
43
- import _ from 'lodash'
44
79
  import Crypto from '~/helpers/Crypto'
45
80
 
46
81
  export default {
@@ -55,6 +90,12 @@ export default {
55
90
  return {
56
91
  isLoading: false,
57
92
  selectedContent: '',
93
+ selectedDifficulty: {
94
+ value: 'None',
95
+ text: this.$t(
96
+ 'windward.core.components.content.blocks.generate_questions.blooms.none'
97
+ ),
98
+ },
58
99
  }
59
100
  },
60
101
  computed: {
@@ -73,6 +114,10 @@ export default {
73
114
  fullTree.push(content)
74
115
  if (content.children.length > 0) {
75
116
  fullTree = fullTree.concat(_.flatten(content.children))
117
+ // check if children have children
118
+ content.children.forEach((child) => {
119
+ fullTree = fullTree.concat(_.flatten(child.children))
120
+ })
76
121
  }
77
122
  })
78
123
  //
@@ -85,17 +130,78 @@ export default {
85
130
  }
86
131
  return fullTree
87
132
  },
133
+ taxonomyLevels() {
134
+ let basicBloomTaxonomy = [
135
+ {
136
+ value: 'None',
137
+ text: this.$t(
138
+ 'windward.core.components.content.blocks.generate_questions.blooms.none'
139
+ ),
140
+ },
141
+ {
142
+ value: 'Remember',
143
+ text: this.$t(
144
+ 'windward.core.components.content.blocks.generate_questions.blooms.remember'
145
+ ),
146
+ },
147
+ {
148
+ value: 'Understand',
149
+ text: this.$t(
150
+ 'windward.core.components.content.blocks.generate_questions.blooms.understand'
151
+ ),
152
+ },
153
+ {
154
+ value: 'Apply',
155
+ text: this.$t(
156
+ 'windward.core.components.content.blocks.generate_questions.blooms.apply'
157
+ ),
158
+ },
159
+ ]
160
+
161
+ if (
162
+ this.questionType === 'multi_choice_single_answer' ||
163
+ this.questionType === 'ordering' ||
164
+ this.questionType === 'multi_choice_multi_answer'
165
+ ) {
166
+ const multiBlooms = [
167
+ {
168
+ value: 'Analyze',
169
+ text: this.$t(
170
+ 'windward.core.components.content.blocks.generate_questions.blooms.analyze'
171
+ ),
172
+ },
173
+ {
174
+ value: 'Evaluate',
175
+ text: this.$t(
176
+ 'windward.core.components.content.blocks.generate_questions.blooms.evaluate'
177
+ ),
178
+ },
179
+ ]
180
+ basicBloomTaxonomy = basicBloomTaxonomy.concat(multiBlooms)
181
+ }
182
+ return basicBloomTaxonomy
183
+ },
88
184
  },
89
185
  methods: {
90
186
  async generateAIQuestion() {
91
187
  this.isLoading = true
188
+ let bloomsRequest = ''
189
+ if (
190
+ this.selectedDifficulty.text !==
191
+ this.$t(
192
+ 'windward.core.components.content.blocks.generate_questions.blooms.none'
193
+ )
194
+ ) {
195
+ // send value to api call so its not changing with language
196
+ bloomsRequest = `?blooms_level=${this.selectedDifficulty.value}`
197
+ }
92
198
  try {
93
199
  const response = await AssessmentQuestion.custom(
94
200
  new Course(this.course),
95
201
  new Content(this.selectedContent),
96
202
  new Assessment({ id: this.block.id }),
97
203
  new AssessmentQuestion(),
98
- `suggest/${this.questionType}`
204
+ `suggest/${this.questionType}${bloomsRequest}`
99
205
  ).get()
100
206
 
101
207
  if (response && response.length > 0) {
@@ -103,27 +209,31 @@ export default {
103
209
  this.$emit('click:generate', generatedQuestion)
104
210
  }
105
211
  } catch (error) {
106
- const errorMessage = error.response?.data?.error?.message || 'assessment.error.technical'
212
+ const errorMessage =
213
+ error.response?.data?.error?.message ||
214
+ 'assessment.error.technical'
107
215
  const errorType = errorMessage.split('.').pop()
108
- const basePath = 'windward.core.components.content.blocks.generate_questions.error'
216
+ const basePath =
217
+ 'windward.core.components.content.blocks.generate_questions.error'
218
+
219
+ let errorText =
220
+ this.$t(`${basePath}.${errorType}`) +
221
+ '\n\n' +
222
+ this.$t(`${basePath}.${errorType}_support`)
109
223
 
110
- let errorText = this.$t(`${basePath}.${errorType}`) + '\n\n' +
111
- this.$t(`${basePath}.${errorType}_support`)
112
-
113
224
  if (errorType === 'technical') {
114
- const errorCode = error.response?.data?.error?.details?.error_type || 'UNKNOWN'
225
+ const errorCode =
226
+ error.response?.data?.error?.details?.error_type ||
227
+ 'UNKNOWN'
115
228
  errorText = errorText.replace('[ERROR_CODE]', errorCode)
116
229
  }
117
-
118
- this.$dialog.error(
119
- errorText,
120
- {
121
- duration: 5000,
122
- keepOnHover: true,
123
- singleton: true,
124
- type: 'error'
125
- }
126
- )
230
+
231
+ this.$dialog.error(errorText, {
232
+ duration: 5000,
233
+ keepOnHover: true,
234
+ singleton: true,
235
+ type: 'error',
236
+ })
127
237
  } finally {
128
238
  this.isLoading = false
129
239
  }
@@ -136,4 +246,8 @@ export default {
136
246
  .btn-selector {
137
247
  width: 100%;
138
248
  }
249
+ .container-generate-ai {
250
+ outline: 1px solid var(--v-secondary-base);
251
+ border-radius: 15px;
252
+ }
139
253
  </style>
@@ -155,7 +155,13 @@ export default {
155
155
  this.block.metadata.config.starting_text = ''
156
156
  }
157
157
  },
158
- watch: {},
158
+ watch: {
159
+ render(newVal) {
160
+ if (newVal) {
161
+ this.onAfterSetContentBlockState()
162
+ }
163
+ },
164
+ },
159
165
  mounted() {},
160
166
  methods: {
161
167
  async onAfterSetContentBlockState() {
@@ -181,11 +187,13 @@ export default {
181
187
  }
182
188
 
183
189
  // If after the state is applied the response is still empty then apply the default response
184
- if (this.response === '') {
190
+ if (_.isEmpty(this.response)) {
185
191
  this.response = this.block.metadata.config.starting_text
186
192
  }
193
+ } else {
194
+ // If we don't have a block_id then we are in the initial setup state
195
+ this.response = this.block.metadata.config.starting_text
187
196
  }
188
-
189
197
  this.stateLoaded = true
190
198
  },
191
199
  onSubmit() {
@@ -1,9 +1,9 @@
1
1
  <template>
2
- <div class="text-center">
2
+ <div>
3
3
  <DialogBox
4
4
  v-bind="$attrs"
5
5
  transition="dialog-bottom-transition"
6
- :color="color"
6
+ color="primary"
7
7
  v-if="value.file_assets && value.file_assets.length > 0"
8
8
  >
9
9
  <template #title>{{
@@ -45,15 +45,6 @@ export default {
45
45
  userUploads: {},
46
46
  }
47
47
  },
48
- computed: {
49
- color() {
50
- if (this.value.file_assets && this.value.file_assets.length > 0) {
51
- return 'success'
52
- } else {
53
- return ''
54
- }
55
- },
56
- },
57
48
  watch: {
58
49
  value(newVal) {
59
50
  this.userUploads = newVal
@@ -332,7 +332,7 @@ export default {
332
332
  captionsmenu: true, // Show the captions below the video
333
333
  playlistmenu: true, // Show the playlist menu if there's multiple videos
334
334
  playlistautoadvance: true, // Play the next source group
335
- playbackrates: [0.5, 1, 1.5, 2], // Default playback speeds
335
+ playbackrates: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2], // Default playback speeds
336
336
  },
337
337
  playlist: [
338
338
  /*{