@windward/core 0.10.0 → 0.11.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 (27) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/components/Content/Blocks/Accordion.vue +96 -32
  3. package/components/Content/Blocks/FileDownload.vue +1 -1
  4. package/components/Content/Blocks/GenerateAIQuestionButton.vue +17 -22
  5. package/components/Content/Blocks/OpenResponse.vue +2 -2
  6. package/components/Content/Blocks/Tab.vue +65 -1
  7. package/components/Content/Blocks/Video.vue +34 -0
  8. package/components/Navigation/Items/CourseGlossaryToolNav.vue +15 -1
  9. package/components/Navigation/Items/GlossaryNav.vue +3 -3
  10. package/components/Settings/AccordionSettings.vue +40 -2
  11. package/components/Settings/TabSettings.vue +44 -3
  12. package/components/Settings/VideoSettings.vue +16 -10
  13. package/components/utils/ContentViewer.vue +19 -0
  14. package/components/utils/TinyMCEWrapper.vue +1 -0
  15. package/components/utils/assets/tinymce/content/global.scss +10 -0
  16. package/components/utils/assets/tinymce/ui/global.scss +6 -2
  17. package/components/utils/glossary/CourseGlossary.vue +6 -3
  18. package/i18n/en-US/components/content/blocks/generate_questions.ts +21 -1
  19. package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +1 -0
  20. package/i18n/es-ES/components/content/blocks/generate_questions.ts +24 -1
  21. package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +1 -0
  22. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +21 -1
  23. package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +1 -0
  24. package/package.json +2 -2
  25. package/pages/glossary.vue +10 -2
  26. package/test/Components/Settings/AccordionSettings.spec.js +16 -2
  27. package/test/Components/Settings/TabSettings.spec.js +26 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ### Release [0.11.0] created - 2025-02-05
4
+
5
+
3
6
  ### Release [0.10.0] created - 2025-01-03
4
7
 
5
8
 
@@ -46,7 +46,10 @@
46
46
  class="expansion-panel-body"
47
47
  :key="expansionPanelKey"
48
48
  >
49
- <v-container>
49
+ <v-container
50
+ class="px-0 d-flex"
51
+ :class="getTextImageContainerClass(item)"
52
+ >
50
53
  <TextViewer
51
54
  v-if="!item.expand"
52
55
  v-model="item.content"
@@ -58,19 +61,18 @@
58
61
  block.metadata.config.items[itemIndex].content
59
62
  "
60
63
  ></TextEditor>
61
- </v-container>
62
- <v-container
63
- v-if="
64
- block.metadata.config.items[itemIndex].fileConfig
65
- .asset
66
- "
67
- >
68
64
  <ImageAssetViewer
65
+ v-if="
66
+ block.metadata.config.items[itemIndex]
67
+ .fileConfig.asset
68
+ "
69
69
  v-model="
70
70
  block.metadata.config.items[itemIndex]
71
71
  .fileConfig
72
72
  "
73
+ :class="getImageOrder(item)"
73
74
  :assets="block.assets"
75
+ :max-width="getImageWidth(item)"
74
76
  ></ImageAssetViewer>
75
77
  </v-container>
76
78
  </v-expansion-panel-content>
@@ -113,7 +115,14 @@ export default {
113
115
  header: '',
114
116
  expand: false,
115
117
  content: '',
116
- fileConfig: {},
118
+ fileConfig: {
119
+ display: {
120
+ width: 100,
121
+ margin: '',
122
+ padding: '',
123
+ },
124
+ hideBackground: true,
125
+ },
117
126
  }
118
127
  this.block.metadata.config.items = []
119
128
  this.block.metadata.config.items.push(defaultObject)
@@ -127,36 +136,42 @@ export default {
127
136
  expansionPanelKey: '0',
128
137
  editingInContentItem: false,
129
138
  selectedPanels: null,
139
+ fullWidth: true,
130
140
  }
131
141
  },
132
142
  watch: {
133
- value(newValue) {
134
- if (
135
- !this.render &&
136
- this.selectedPanels !== newValue.metadata.config.selectedPanels
137
- ) {
138
- // update editing content block
139
- this.selectedPanels = newValue.metadata.config.selectedPanels
140
- this.expansionPanelKey = Crypto.id()
141
- }
142
-
143
- const length = newValue.metadata.config.items.length
144
- let counter = 0
145
- newValue.metadata.config.items.forEach((element) => {
143
+ value: {
144
+ deep: true,
145
+ handler(newValue) {
146
146
  if (
147
- element.expand === true &&
148
- this.editingInContentItem !== true
147
+ !this.render &&
148
+ this.selectedPanels !==
149
+ newValue.metadata.config.selectedPanels
149
150
  ) {
150
- this.editingInContentItem = true
151
+ // update editing content block
152
+ this.selectedPanels =
153
+ newValue.metadata.config.selectedPanels
151
154
  this.expansionPanelKey = Crypto.id()
152
155
  }
153
- if (element.expand === false) {
154
- counter = counter + 1
155
- }
156
- if (counter === length) {
157
- this.editingInContentItem = false
158
- }
159
- })
156
+
157
+ const length = newValue.metadata.config.items.length
158
+ let counter = 0
159
+ newValue.metadata.config.items.forEach((element) => {
160
+ if (
161
+ element.expand === true &&
162
+ this.editingInContentItem !== true
163
+ ) {
164
+ this.editingInContentItem = true
165
+ this.expansionPanelKey = Crypto.id()
166
+ }
167
+ if (element.expand === false) {
168
+ counter = counter + 1
169
+ }
170
+ if (counter === length) {
171
+ this.editingInContentItem = false
172
+ }
173
+ })
174
+ },
160
175
  },
161
176
  render() {
162
177
  if (this.render === true) {
@@ -199,6 +214,55 @@ export default {
199
214
  const foundAsset = this.resolveAsset(asset)
200
215
  return _.get(foundAsset, 'asset.metadata.props.alt', null)
201
216
  },
217
+ getImageWidth(item) {
218
+ if (item.fileConfig.display) {
219
+ return String(item.fileConfig.display.width) + '%'
220
+ }
221
+ },
222
+ getTextImageContainerClass(item) {
223
+ const { margin, width } = item.fileConfig.display
224
+
225
+ if (width === 100) {
226
+ return margin === 'top'
227
+ ? 'flex-column-reverse '
228
+ : 'flex-column '
229
+ }
230
+
231
+ // If no display items have been set, keep the image in its own row
232
+ if (!margin) {
233
+ return 'flex-column '
234
+ }
235
+
236
+ switch (margin) {
237
+ case 'top':
238
+ return 'flex-column-reverse '
239
+ case 'right':
240
+ return _.isEmpty(item.content)
241
+ ? 'justify-end'
242
+ : 'justify-space-between'
243
+ case 'left':
244
+ return ''
245
+ default:
246
+ return 'flex-column '
247
+ }
248
+
249
+ },
250
+ getImageOrder(item) {
251
+ const margin = item.fileConfig.display?.margin
252
+ if (margin) {
253
+ switch (margin) {
254
+ case 'left':
255
+ return 'order-first pr-1 '
256
+ case 'right':
257
+ return ''
258
+ default:
259
+ return 'align-self-center '
260
+ }
261
+ } else {
262
+ // if adjusting size and margin hasn't been set yet keep centered here
263
+ return 'align-self-center '
264
+ }
265
+ },
202
266
  async onBeforeSave() {
203
267
  this.block.metadata.config.items.forEach((element) => {
204
268
  element.expand = false
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-container>
2
+ <v-container class="pa-0">
3
3
  <h2>
4
4
  {{
5
5
  block.metadata.config.title ||
@@ -10,19 +10,10 @@
10
10
  :disabled="isLoading"
11
11
  @click="generateAIQuestion"
12
12
  >
13
- <v-icon class="pr-1" v-if="!isLoading"
14
- >mdi-magic-staff</v-icon
15
- >
16
- {{
17
- this.$t(
18
- 'windward.core.components.content.blocks.generate_questions.button_label'
19
- )
20
- }}
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') }}
21
15
  <template v-slot:loader>
22
- <v-progress-circular
23
- indeterminate
24
- size="23"
25
- ></v-progress-circular>
16
+ <v-progress-circular indeterminate size="23"></v-progress-circular>
26
17
  </template>
27
18
  </v-btn>
28
19
  </v-col>
@@ -34,11 +25,7 @@
34
25
  outlined
35
26
  hide-details
36
27
  dense
37
- :label="
38
- $t(
39
- 'windward.core.components.content.blocks.generate_questions.selected_pages'
40
- )
41
- "
28
+ :label="$t('windward.core.components.content.blocks.generate_questions.selected_pages')"
42
29
  item-text="content.name"
43
30
  return-object
44
31
  ></v-select>
@@ -116,11 +103,18 @@ export default {
116
103
  this.$emit('click:generate', generatedQuestion)
117
104
  }
118
105
  } catch (error) {
119
- console.error(error)
106
+ const errorType = error.response?.data?.errors?.detail?.split('.').pop() || 'default'
107
+ const basePath = 'windward.core.components.content.blocks.generate_questions.error'
108
+
120
109
  this.$dialog.error(
121
- this.$t(
122
- 'windward.core.components.content.blocks.generate_questions.error'
123
- )
110
+ this.$t(`${basePath}.${errorType}`) + '\n\n' +
111
+ this.$t(`${basePath}.${errorType}_support`),
112
+ {
113
+ duration: 5000,
114
+ keepOnHover: true,
115
+ singleton: true,
116
+ type: 'error'
117
+ }
124
118
  )
125
119
  } finally {
126
120
  this.isLoading = false
@@ -129,8 +123,9 @@ export default {
129
123
  },
130
124
  }
131
125
  </script>
126
+
132
127
  <style scoped>
133
128
  .btn-selector {
134
129
  width: 100%;
135
130
  }
136
- </style>
131
+ </style>
@@ -27,7 +27,7 @@
27
27
  :height="200"
28
28
  menubar=""
29
29
  ></TextEditor>
30
- <p class="pa-3 text-center blue-grey lighten-5">
30
+ <p class="pa-3 text-center">
31
31
  <v-btn
32
32
  color="primary"
33
33
  elevation="0"
@@ -66,7 +66,7 @@
66
66
  ></TextViewer>
67
67
  </v-alert>
68
68
  </div>
69
- <p class="pa-3 text-center blue-grey lighten-5">
69
+ <p class="pa-3 text-center">
70
70
  <v-btn
71
71
  elevation="0"
72
72
  color="primary"
@@ -37,7 +37,10 @@
37
37
  .config.items"
38
38
  :key="'tabContent ' + tabContentIndex"
39
39
  >
40
- <v-container>
40
+ <v-container
41
+ class="d-flex"
42
+ :class="getTextImageContainerClass(tabContent)"
43
+ >
41
44
  <TextViewer
42
45
  v-if="!tabContent.expand"
43
46
  v-model="tabContent.content"
@@ -54,6 +57,8 @@
54
57
  "
55
58
  v-model="tabContent.imageAsset"
56
59
  :assets="block.assets"
60
+ :class="getImageOrder(tabContent)"
61
+ :max-width="getImageWidth(tabContent)"
57
62
  ></ImageAssetViewer>
58
63
  </v-container>
59
64
  </v-tab-item>
@@ -86,6 +91,14 @@ export default {
86
91
  tabHeader: '',
87
92
  expand: false,
88
93
  content: '',
94
+ imageAsset: {
95
+ display: {
96
+ width: 100,
97
+ margin: '',
98
+ padding: '',
99
+ },
100
+ hideBackground: true,
101
+ },
89
102
  }
90
103
  this.block.metadata.config.items = []
91
104
  this.block.metadata.config.items.push(defaultObject)
@@ -113,6 +126,57 @@ export default {
113
126
  })
114
127
  this.block.metadata.config.currentTab = null
115
128
  },
129
+ getImageWidth(item) {
130
+ if (item.imageAsset?.display) {
131
+ return String(item.imageAsset.display.width) + '%'
132
+ }
133
+ },
134
+ getTextImageContainerClass(item) {
135
+ const { margin, width } = item.imageAsset.display
136
+ // if full width set container to column
137
+ if (width === 100) {
138
+ return margin === 'top'
139
+ ? 'flex-column-reverse '
140
+ : 'flex-column '
141
+ }
142
+
143
+ // If no display items have been set, keep the image in its own row
144
+ if (!margin) {
145
+ return 'flex-column '
146
+ }
147
+
148
+ switch (margin) {
149
+ case 'top':
150
+ return 'flex-column-reverse '
151
+ case 'right':
152
+ return _.isEmpty(item.content)
153
+ ? 'justify-end'
154
+ : 'justify-space-between'
155
+ case 'left':
156
+ return ''
157
+ default:
158
+ return 'flex-column '
159
+ }
160
+ },
161
+ getImageOrder(item) {
162
+ const margin = item.imageAsset.display?.margin
163
+ // filter out tab blocks that were created before custom
164
+ // spacing was added to image asset settings
165
+ if (margin) {
166
+ // if top or bottom image will always be centered
167
+ switch (margin) {
168
+ case 'left':
169
+ return 'order-first pr-1 '
170
+ case 'right':
171
+ return ''
172
+ default:
173
+ return 'align-self-center '
174
+ }
175
+ } else {
176
+ // if adjusting size and margin hasn't been set yet keep centered here
177
+ return 'align-self-center '
178
+ }
179
+ },
116
180
  },
117
181
  }
118
182
  </script>
@@ -29,6 +29,8 @@
29
29
  :rewind="block.metadata.config.attributes.rewind"
30
30
  :loop="block.metadata.config.attributes.loop"
31
31
  :muted="block.metadata.config.attributes.muted"
32
+ :volume.sync="volume"
33
+ :cc.sync="ccVisible"
32
34
  :playsinline="block.metadata.config.attributes.playsinline"
33
35
  :poster="linkedPosterPublicUrl"
34
36
  :preload="block.metadata.config.attributes.preload"
@@ -268,6 +270,38 @@ export default {
268
270
  }
269
271
  },
270
272
  },
273
+ ccVisible: {
274
+ get() {
275
+ const user = new AuthUserRepository(this.$nuxt)
276
+ return user.getPreference('video.cc-visible', false)
277
+ },
278
+ set(v) {
279
+ try {
280
+ const user = new AuthUserRepository(this.$nuxt)
281
+ user.setPreference('video.cc-visible', v)
282
+ user.save()
283
+ } catch (e) {
284
+ console.error('Could not save user video preference!')
285
+ console.error(e)
286
+ }
287
+ },
288
+ },
289
+ volume: {
290
+ get() {
291
+ const user = new AuthUserRepository(this.$nuxt)
292
+ return user.getPreference('video.volume', 0.5)
293
+ },
294
+ set(v) {
295
+ try {
296
+ const user = new AuthUserRepository(this.$nuxt)
297
+ user.setPreference('video.volume', v)
298
+ user.save()
299
+ } catch (e) {
300
+ console.error('Could not save user video preference!')
301
+ console.error(e)
302
+ }
303
+ },
304
+ },
271
305
  },
272
306
  data() {
273
307
  return {
@@ -9,7 +9,21 @@
9
9
  <template #title>{{ $t(config.i18n) }}</template>
10
10
  <template #form="{ on, attrs }">
11
11
  <div v-bind="attrs" v-on="on">
12
- <CourseGlossary />
12
+ <v-alert
13
+ v-if="!$ContextService.courseInSourceOrganization()"
14
+ type="warning"
15
+ >
16
+ {{
17
+ $t(
18
+ 'shared.forms.source_organization_course_lock'
19
+ )
20
+ }}
21
+ </v-alert>
22
+ <CourseGlossary
23
+ :disabled="
24
+ !$ContextService.courseInSourceOrganization()
25
+ "
26
+ />
13
27
  </div>
14
28
  </template>
15
29
  </DialogBox>
@@ -3,12 +3,12 @@
3
3
  <template #activator="{ on, attrs }">
4
4
  <v-list-item
5
5
  :class="color"
6
- v-on="on"
7
- v-bind="attrs"
8
6
  :to="'/course/' + course.id + '/glossary'"
7
+ v-bind="attrs"
8
+ v-on="on"
9
9
  >
10
10
  <v-list-item-action>
11
- <v-icon v-on="on" v-bind="attrs"
11
+ <v-icon v-bind="attrs" v-on="on"
12
12
  >mdi-comment-text-multiple</v-icon
13
13
  >
14
14
  </v-list-item-action>
@@ -104,9 +104,11 @@
104
104
  .fileConfig
105
105
  "
106
106
  :assets.sync="block.assets"
107
+ :disabled="render"
107
108
  hide-background
108
109
  hide-decorative
109
110
  hide-modal
111
+ show-spacing
110
112
  ></ImageAssetSettings>
111
113
  </v-container>
112
114
  </v-container>
@@ -184,7 +186,14 @@ export default {
184
186
  header: '',
185
187
  expand: false,
186
188
  content: '',
187
- fileConfig: {},
189
+ fileConfig: {
190
+ display: {
191
+ width: 100,
192
+ margin: '',
193
+ padding: '',
194
+ },
195
+ hideBackground: true,
196
+ },
188
197
  }
189
198
  this.block.metadata.config.items = []
190
199
  this.block.metadata.config.items.push(defaultObject)
@@ -206,8 +215,30 @@ export default {
206
215
  },
207
216
  mounted() {
208
217
  this.block.metadata.config.selectedPanels = 0
218
+ this.checkForCustomSpacing()
209
219
  },
210
220
  methods: {
221
+ checkForCustomSpacing() {
222
+ // check if block has items set up already and if they were created before the display key was added to image asset object
223
+ if (
224
+ this.block.metadata.config.items.length >= 1 &&
225
+ _.isEmpty(
226
+ this.block.metadata.config.items[0].fileConfig.display
227
+ )
228
+ ) {
229
+ // need to add display.margin ect. for custom spacing component so that changes are tracked reactively
230
+ this.block.metadata.config.items.forEach((item) => {
231
+ if (!item.fileConfig.display) {
232
+ item.fileConfig.display = {
233
+ width: 100,
234
+ margin: '',
235
+ padding: '',
236
+ }
237
+ item.fileConfig.hideBackground = true
238
+ }
239
+ })
240
+ }
241
+ },
211
242
  onUpdatePanel($event) {
212
243
  if ($event !== this.block.metadata.config.selectedPanels) {
213
244
  //catch click event to open selected panel to edit
@@ -223,7 +254,14 @@ export default {
223
254
  header: '',
224
255
  expand: false,
225
256
  content: '',
226
- fileConfig: {},
257
+ fileConfig: {
258
+ display: {
259
+ width: 100,
260
+ margin: '',
261
+ padding: '',
262
+ },
263
+ hideBackground: true,
264
+ },
227
265
  }
228
266
  this.block.metadata.config.items.push(default_item)
229
267
  this.block.metadata.config.selectedPanels =
@@ -76,13 +76,18 @@
76
76
  ></TextEditor>
77
77
 
78
78
  <v-card class="mt-2">
79
- <v-card-text>
79
+ <v-card-text class="pa-0">
80
80
  <ImageAssetSettings
81
81
  v-model="
82
82
  block.metadata.config.items[index]
83
83
  .imageAsset
84
84
  "
85
85
  :assets.sync="block.assets"
86
+ :disabled="render"
87
+ hide-background
88
+ hide-decorative
89
+ hide-modal
90
+ show-spacing
86
91
  >
87
92
  </ImageAssetSettings>
88
93
  </v-card-text>
@@ -160,7 +165,14 @@ export default {
160
165
  tabHeader: '',
161
166
  expand: false,
162
167
  content: '',
163
- imageAsset: null,
168
+ imageAsset: {
169
+ display: {
170
+ width: 100,
171
+ margin: '',
172
+ padding: '',
173
+ },
174
+ hideBackground: true,
175
+ },
164
176
  }
165
177
  this.block.metadata.config.items = []
166
178
  this.block.metadata.config.items.push(defaultObject)
@@ -178,6 +190,7 @@ export default {
178
190
  if (this.block.metadata.config.items.length <= 0) {
179
191
  this.onAddElement()
180
192
  }
193
+ this.checkForCustomSpacing()
181
194
  },
182
195
  beforeDestroy() {
183
196
  if (this.debouncer) {
@@ -185,12 +198,40 @@ export default {
185
198
  }
186
199
  },
187
200
  methods: {
201
+ checkForCustomSpacing() {
202
+ // check if block has items set up already and if they were created before the dispaly key was added to image asset object
203
+ if (
204
+ this.block.metadata.config.items.length >= 1 &&
205
+ _.isEmpty(
206
+ this.block.metadata.config.items[0].imageAsset.display
207
+ )
208
+ ) {
209
+ // need to add display.margin ect. for custom spacing component so that changes are tracked reactively
210
+ this.block.metadata.config.items.forEach((item) => {
211
+ if (!item.imageAsset.display) {
212
+ item.imageAsset.display = {
213
+ width: 100,
214
+ margin: '',
215
+ padding: '',
216
+ }
217
+ item.imageAsset.hideBackground = true
218
+ }
219
+ })
220
+ }
221
+ },
188
222
  onAddElement() {
189
223
  const defaultObject = {
190
224
  tabHeader: '',
191
225
  expand: false,
192
226
  content: '',
193
- imageAsset: null,
227
+ imageAsset: {
228
+ display: {
229
+ width: 100,
230
+ margin: '',
231
+ padding: '',
232
+ },
233
+ hideBackground: true,
234
+ },
194
235
  }
195
236
  this.block.metadata.config.items.push(defaultObject)
196
237
  this.block.metadata.config.currentTab =
@@ -123,7 +123,10 @@
123
123
  (file, rawFile) =>
124
124
  onAdSourceSelect(file, rawFile, 0)
125
125
  "
126
- @change:track="onAdTrackSelect($event, 0)"
126
+ @change:track="
127
+ (file, rawFile) =>
128
+ onAdTrackSelect(file, rawFile, 0)
129
+ "
127
130
  ></SourcePicker>
128
131
  </v-tab-item>
129
132
 
@@ -148,7 +151,10 @@
148
151
  (file, rawFile) =>
149
152
  onAdSourceSelect(file, rawFile, 100)
150
153
  "
151
- @change:track="onAdTrackSelect($event, 100)"
154
+ @change:track="
155
+ (file, rawFile) =>
156
+ onAdTrackSelect(file, rawFile, 100)
157
+ "
152
158
  ></SourcePicker>
153
159
  </v-tab-item>
154
160
  </v-tabs-items>
@@ -626,7 +632,7 @@ export default {
626
632
 
627
633
  this.reloadVideo()
628
634
  },
629
- onSourceSelect(file, rawFile) {
635
+ onSourceSelect(file, _rawFile) {
630
636
  // No file, clear the selection
631
637
  if (_.isEmpty(file)) {
632
638
  delete this.block.metadata.config.playlist[this.playlistIndex]
@@ -637,7 +643,7 @@ export default {
637
643
 
638
644
  // Clear out any associated captions
639
645
  this.media.track = null
640
- this.onTrackSelect(null)
646
+ this.onTrackSelect()
641
647
 
642
648
  this.showVideo = false
643
649
  } else {
@@ -657,11 +663,11 @@ export default {
657
663
 
658
664
  // Clear the tracks. it will either be inherited via linked captions or be hard-set by the user
659
665
  this.media.track = null
660
- this.onTrackSelect(null)
666
+ this.onTrackSelect()
661
667
  }
662
668
  this.reloadVideo()
663
669
  },
664
- onTrackSelect(file) {
670
+ onTrackSelect(file = null, _rawFile = null) {
665
671
  if (_.isEmpty(file)) {
666
672
  this.block.metadata.config.playlist[this.playlistIndex].tracks =
667
673
  []
@@ -672,7 +678,7 @@ export default {
672
678
  }
673
679
  this.reloadVideo()
674
680
  },
675
- onAdSourceSelect(file, rawFile, playAtPercent) {
681
+ onAdSourceSelect(file, _rawFile, playAtPercent) {
676
682
  const adIndex = this.getAdSlot(playAtPercent)
677
683
 
678
684
  // Prep in case the ad slot isn't setup yet
@@ -693,11 +699,11 @@ export default {
693
699
 
694
700
  // Clear the tracks. it will either be inherited via linked captions or be hard-set by the user
695
701
  this.media.ads[playAtPercent].track = null
696
- this.onAdTrackSelect(null, playAtPercent)
702
+ this.onAdTrackSelect(null, null, playAtPercent)
697
703
  }
698
704
  this.reloadVideo()
699
705
  },
700
- onAdTrackSelect(file, playAtPercent) {
706
+ onAdTrackSelect(file, _rawFile, playAtPercent) {
701
707
  const adIndex = this.getAdSlot(playAtPercent)
702
708
 
703
709
  if (_.isEmpty(file)) {
@@ -715,7 +721,7 @@ export default {
715
721
  }
716
722
  this.reloadVideo()
717
723
  },
718
- onPlaybackChange(e) {
724
+ onPlaybackChange(_e) {
719
725
  // Sort the list so we don't get instances of 2, 1, 0.25, 3
720
726
  this.block.metadata.config.attributes.playbackrates.sort()
721
727
  },
@@ -54,6 +54,21 @@ export default {
54
54
  content =
55
55
  FillInBlankHelper.renderFillInTHeBlankHtml(content)
56
56
  }
57
+ // finds any underscores grouped together of any length over 2
58
+ let regex = /(_{2,})/g
59
+ if (content.match(regex)) {
60
+ content = content.replace(regex, (match) => {
61
+ return (
62
+ '<span role="img" aria-label="' +
63
+ this.$t(
64
+ 'windward.core.components.utils.tiny_mce_wrapper.blank'
65
+ ) +
66
+ '">' +
67
+ match +
68
+ '</span>'
69
+ )
70
+ })
71
+ }
57
72
  }
58
73
  return Vue.compile('<div>' + content + '</div>')
59
74
  },
@@ -84,4 +99,8 @@ img {
84
99
  font-size: 1rem;
85
100
  line-height: 1.75em;
86
101
  }
102
+ caption {
103
+ color: var(--v-primary-base);
104
+ text-align: left;
105
+ }
87
106
  </style>
@@ -440,6 +440,7 @@ export default {
440
440
  ' color: #1a1d1e;' +
441
441
  '}',
442
442
  importcss_append: true,
443
+ link_default_target: '_blank',
443
444
  }
444
445
  },
445
446
  elementBody() {
@@ -9,7 +9,17 @@ table {
9
9
  border: 1px solid var(--v-primary-base);
10
10
  padding: 3px;
11
11
  }
12
+
13
+ // Define styles for floating buttons and behavior in small screens
14
+ @media (max-width: 600px) {
15
+ // Ensure table is scrollable horizontally
16
+ display: block;
17
+ white-space: nowrap;
18
+ overflow-x: auto;
19
+ position: relative;
20
+ }
12
21
  }
22
+
13
23
  table {
14
24
  &.windward-table-default {
15
25
  color: black;
@@ -1,8 +1,12 @@
1
1
  table {
2
- border: 1px solid black !important;
2
+ border: var(--v-primary-base);
3
3
  th,
4
4
  td {
5
- border: 1px solid black !important;
5
+ border: var(--v-primary-base);
6
+ }
7
+ caption {
8
+ text-align: left;
9
+ color: var(--v-primary-base);
6
10
  }
7
11
  }
8
12
 
@@ -33,6 +33,7 @@
33
33
  'writable'
34
34
  )
35
35
  "
36
+ :disabled="disabled"
36
37
  @click:save="save"
37
38
  @click:save-new="saveNew"
38
39
  @click:trigger="addTerm"
@@ -108,6 +109,7 @@
108
109
  <SpeedDial
109
110
  direction="left"
110
111
  transition="slide-x-reverse-transition"
112
+ :disabled="disabled"
111
113
  >
112
114
  <v-btn
113
115
  color="error"
@@ -142,7 +144,7 @@
142
144
  >
143
145
  {{ $t('shared.forms.edit') }}
144
146
  <span class="sr-only">{{
145
- $t('forms.edit')
147
+ $t('shared.forms.edit')
146
148
  }}</span>
147
149
  </v-btn>
148
150
  </SpeedDial>
@@ -171,8 +173,9 @@ export default {
171
173
  components: { DialogBox, CourseGlossaryForm, SpeedDial, TextViewer },
172
174
  layout: 'course',
173
175
  middleware: ['auth'],
174
- props: {},
175
-
176
+ props: {
177
+ disabled: { type: Boolean, required: false, default: false },
178
+ },
176
179
  data() {
177
180
  return {
178
181
  dialog: false,
@@ -1,5 +1,25 @@
1
1
  export default {
2
- error: 'Could not generate question from provided content.',
2
+ error: {
3
+ default: 'Could not generate question from provided content.',
4
+ default_support:
5
+ 'Please try again or contact support if the issue persists.',
6
+
7
+ insufficient_content: 'More content needed to generate questions.',
8
+ insufficient_content_support:
9
+ 'Please add more text, examples, or explanations to this section. We recommend at least 2-3 paragraphs of content.',
10
+
11
+ content_mismatch: "Content doesn't match question type.",
12
+ content_mismatch_support:
13
+ "The current content isn't suitable for this type of question. Consider adding more specific examples or comparable items.",
14
+
15
+ llm_unavailable: 'Question generation temporarily unavailable.',
16
+ llm_unavailable_support:
17
+ "We're unable to connect to our AI service at the moment. Please try again in a few minutes.",
18
+
19
+ technical: 'Unable to process request.',
20
+ technical_support:
21
+ 'Something went wrong. Please try again or contact support if this continues.',
22
+ },
3
23
  button_label: 'Generate Question',
4
24
  selected_pages: 'Selected Page',
5
25
  }
@@ -27,4 +27,5 @@ export default {
27
27
  link_href_missing: 'a (anchor) element must contain an href attribute.',
28
28
  link_text_missing:
29
29
  'a (anchor) element must contain text. The text may occur in the anchor text or in the title attribute of the anchor or in the Alt text of an image used within the anchor',
30
+ blank: 'blank',
30
31
  }
@@ -1,5 +1,28 @@
1
1
  export default {
2
- error: "No se pudo generar una pregunta a partir del contenido proporcionado.",
2
+ error: {
3
+ default:
4
+ 'No se pudo generar la pregunta a partir del contenido proporcionado.',
5
+ default_support:
6
+ 'Inténtalo de nuevo o ponte en contacto con el servicio de asistencia si el problema persiste.',
7
+
8
+ insufficient_content:
9
+ 'Se necesita más contenido para generar preguntas.',
10
+ insufficient_content_support:
11
+ 'Agregue más texto, ejemplos o explicaciones a esta sección. Recomendamos al menos 2 o 3 párrafos de contenido.',
12
+
13
+ content_mismatch: 'El contenido no coincide con el tipo de pregunta.',
14
+ content_mismatch_support:
15
+ 'El contenido actual no es adecuado para este tipo de pregunta. Considere agregar ejemplos más específicos o elementos comparables.',
16
+
17
+ llm_unavailable:
18
+ 'La generación de preguntas no está disponible temporalmente.',
19
+ llm_unavailable_support:
20
+ 'No podemos conectarnos a nuestro servicio de IA en este momento. Inténtalo nuevamente en unos minutos.',
21
+
22
+ technical: 'No se puede procesar la solicitud.',
23
+ technical_support:
24
+ 'Algo salió mal. Inténtalo de nuevo o ponte en contacto con el servicio de asistencia si el problema persiste.',
25
+ },
3
26
  button_label: 'Generar pregunta',
4
27
  selected_pages: 'Página seleccionada',
5
28
  }
@@ -28,4 +28,5 @@ export default {
28
28
  link_href_missing:
29
29
  'El Elemento HTML Anchor <a> debe contener un atributo href',
30
30
  link_text_missing: 'El Elemento HTML Anchor <a> debe contener texto. ',
31
+ blank: 'vacio',
31
32
  }
@@ -1,5 +1,25 @@
1
1
  export default {
2
- error: "Kunde inte generera en fråga från det tillhandahållna innehållet.",
2
+ error: {
3
+ default: 'Kunde inte generera fråga från tillhandahållet innehåll.',
4
+ default_support:
5
+ 'Försök igen eller kontakta supporten om problemet kvarstår.',
6
+
7
+ insufficient_content: 'Mer innehåll behövs för att generera frågor.',
8
+ insufficient_content_support:
9
+ 'Vänligen lägg till mer text, exempel eller förklaringar till det här avsnittet. Vi rekommenderar minst 2-3 stycken innehåll.',
10
+
11
+ content_mismatch: 'Innehållet matchar inte frågetyp.',
12
+ content_mismatch_support:
13
+ 'Det aktuella innehållet är inte lämpligt för den här typen av frågor. Överväg att lägga till mer specifika exempel eller jämförbara objekt.',
14
+
15
+ llm_unavailable: 'Frågegenerering tillfälligt otillgänglig.',
16
+ llm_unavailable_support:
17
+ 'Vi kan inte ansluta till vår AI-tjänst för tillfället. Försök igen om några minuter.',
18
+
19
+ technical: 'Kan inte behandla begäran.',
20
+ technical_support:
21
+ 'Något gick fel. Försök igen eller kontakta supporten om detta fortsätter.',
22
+ },
3
23
  button_label: 'Generera fråga',
4
24
  selected_pages: 'Vald sida',
5
25
  }
@@ -28,4 +28,5 @@ export default {
28
28
  link_href_missing:
29
29
  'HTML Anchor <a>-elementet måste innehålla ett href-attribut',
30
30
  link_text_missing: 'HTML-ankarelementet <a> måste innehålla text.',
31
+ blank: 'tom',
31
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/core",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Windward UI Core Plugins",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "homepage": "https://bitbucket.org/mindedge/windward-ui-plugin-core#readme",
23
23
  "dependencies": {
24
- "@mindedge/vuetify-player": "^0.4.1",
24
+ "@mindedge/vuetify-player": "^0.4.2",
25
25
  "@tinymce/tinymce-vue": "^3.2.8",
26
26
  "accessibility-scanner": "^0.0.1",
27
27
  "eslint": "^8.11.0",
@@ -1,6 +1,12 @@
1
1
  <template>
2
2
  <v-row justify="center" align="center">
3
3
  <v-col cols="12">
4
+ <v-alert
5
+ v-if="!$ContextService.courseInSourceOrganization()"
6
+ type="warning"
7
+ >
8
+ {{ $t('shared.forms.source_organization_course_lock') }}
9
+ </v-alert>
4
10
  <div class="d-flex mb-5">
5
11
  <h2 class="mr-auto flex-grow-1" tabindex="0">
6
12
  {{ $t('windward.core.pages.glossary.title') }}
@@ -8,7 +14,9 @@
8
14
  </div>
9
15
 
10
16
  <v-spacer class="mt-5 mb-5" />
11
- <CourseGlossary />
17
+ <CourseGlossary
18
+ :disabled="!$ContextService.courseInSourceOrganization()"
19
+ />
12
20
  </v-col>
13
21
  </v-row>
14
22
  </template>
@@ -17,9 +25,9 @@
17
25
  import { CourseGlossary } from '../utils/index'
18
26
  export default {
19
27
  name: 'Glossary',
20
- middleware: ['auth', 'privilege'],
21
28
  components: { CourseGlossary },
22
29
  layout: 'authenticated',
30
+ middleware: ['auth', 'privilege'],
23
31
  meta: {
24
32
  privilege: {
25
33
  'windward.organization.course.contentBlock': {
@@ -31,13 +31,27 @@ describe('AccordionSettings', () => {
31
31
  header: '',
32
32
  expand: false,
33
33
  content: '',
34
- fileConfig: {},
34
+ fileConfig: {
35
+ display: {
36
+ width: 100,
37
+ margin: '',
38
+ padding: '',
39
+ },
40
+ hideBackground: true,
41
+ },
35
42
  },
36
43
  {
37
44
  header: '',
38
45
  expand: false,
39
46
  content: '',
40
- fileConfig: {},
47
+ fileConfig: {
48
+ display: {
49
+ width: 100,
50
+ margin: '',
51
+ padding: '',
52
+ },
53
+ hideBackground: true,
54
+ },
41
55
  },
42
56
  ])
43
57
  })
@@ -27,8 +27,32 @@ describe('TabSettings', () => {
27
27
  })
28
28
  wrapper.vm.onAddElement()
29
29
  expect(wrapper.vm.$data.block.metadata.config.items).toEqual([
30
- { tabHeader: '', expand: false, content: '', imageAsset: null },
31
- { tabHeader: '', expand: false, content: '', imageAsset: null },
30
+ {
31
+ tabHeader: '',
32
+ expand: false,
33
+ content: '',
34
+ imageAsset: {
35
+ display: {
36
+ width: 100,
37
+ margin: '',
38
+ padding: '',
39
+ },
40
+ hideBackground: true,
41
+ },
42
+ },
43
+ {
44
+ tabHeader: '',
45
+ expand: false,
46
+ content: '',
47
+ imageAsset: {
48
+ display: {
49
+ width: 100,
50
+ margin: '',
51
+ padding: '',
52
+ },
53
+ hideBackground: true,
54
+ },
55
+ },
32
56
  ])
33
57
  })
34
58