@windward/core 0.20.0 → 0.21.1

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 (29) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/components/Content/Blocks/ClickableIcons.vue +113 -116
  3. package/components/Content/Blocks/OpenResponseCollate.vue +30 -15
  4. package/components/Content/Blocks/Video.vue +67 -40
  5. package/components/Settings/OpenResponseCollateSettings.vue +48 -16
  6. package/components/Settings/UserUploadSettings.vue +77 -17
  7. package/components/utils/FillInBlank/FillInBlankInput.vue +24 -1
  8. package/components/utils/FillInBlank/FillInTheBlanksManager.vue +5 -0
  9. package/components/utils/GenerateAIQuestionButton.vue +152 -12
  10. package/components/utils/TinyMCEWrapper.vue +22 -2
  11. package/i18n/en-US/components/content/blocks/generate_questions.ts +10 -0
  12. package/i18n/en-US/components/settings/open_response_collate.ts +2 -2
  13. package/i18n/en-US/components/settings/user_upload.ts +6 -4
  14. package/i18n/en-US/components/utils/FillInBlank/FillInBlankInput.ts +1 -0
  15. package/i18n/en-US/components/utils/FillInBlank/FillInTheBlanksManager.ts +1 -0
  16. package/i18n/en-US/shared/settings.ts +2 -0
  17. package/i18n/es-ES/components/content/blocks/generate_questions.ts +10 -0
  18. package/i18n/es-ES/components/settings/open_response_collate.ts +1 -0
  19. package/i18n/es-ES/components/settings/user_upload.ts +6 -4
  20. package/i18n/es-ES/components/utils/FillInBlank/FillInBlankInput.ts +4 -3
  21. package/i18n/es-ES/components/utils/FillInBlank/FillInTheBlanksManager.ts +2 -1
  22. package/i18n/es-ES/shared/settings.ts +2 -0
  23. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +10 -0
  24. package/i18n/sv-SE/components/settings/open_response_collate.ts +2 -0
  25. package/i18n/sv-SE/components/settings/user_upload.ts +7 -5
  26. package/i18n/sv-SE/components/utils/FillInBlank/FillInBlankInput.ts +6 -5
  27. package/i18n/sv-SE/components/utils/FillInBlank/FillInTheBlanksManager.ts +5 -4
  28. package/i18n/sv-SE/shared/settings.ts +2 -0
  29. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## Hotfix [0.21.1] created - 2025-08-20
4
+
5
+
6
+ ## Release [0.21.0] - 2025-08-01
7
+
8
+ * Merged in feature/LE-2045/lableing-fixes (pull request #414)
9
+ * Merged in feature/LE-2009-organizations-custom-color-palle (pull request #410)
10
+ * Merged in feature/LE-2011-open-response-download-field-to- (pull request #411)
11
+ * Merged in feature/LE-1912/sorting-game-gen-2 (pull request #413)
12
+ * Merged in feature/LE-2029-open-response-block-design (pull request #412)
13
+ * Merged in feature/LE-1912/sorting-game-gen (pull request #409)
14
+ * Merged in feature/LE-2011-open-response-download-field-to- (pull request #408)
15
+ * Merged in feature/LE-1924-clickable-icons-ux (pull request #407)
16
+ * Merged in feature/LE-1913/matching-block (pull request #405)
17
+ * Merged in feature/LE-1924-clickable-icons-ux (pull request #404)
18
+ * Merged in feature/LE-2011-open-response-download-field-to- (pull request #406)
19
+ * Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #398)
20
+ * Merged release/0.21.0 into feature/LE-2011-open-response-download-field-to-
21
+ * Merge branch 'release/0.21.0' into feature/LE-1913/matching-block
22
+ * Merged in bugfix/LE-1990-video-black-line-on-left-and-rig (pull request #403)
23
+ * Merged in feature/LE-1900-fill-in-the-blank-formatting-sho (pull request #402)
24
+ * Merged release/0.21.0 into feature/LE-1924-clickable-icons-ux
25
+ * Merge branch 'release/0.20.0' into feature/LE-1913/matching-block
26
+
27
+
3
28
  ## Release [0.20.0] - 2025-07-01
4
29
 
5
30
  * Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
@@ -19,71 +19,87 @@
19
19
  </p>
20
20
  </v-container>
21
21
  <v-container class="pa-0">
22
- <v-row
22
+ <v-container
23
23
  v-for="(item, itemIndex) in block.metadata.config.items"
24
24
  :key="itemIndex"
25
25
  no-gutters
26
+ role="button"
27
+ class="flex-nowrap mb-3"
26
28
  :class="rowClass(itemIndex)"
27
- class="pa-0"
29
+ @click="item.active = !item.active"
28
30
  >
29
- <v-col cols="8" v-bind="iconColumnAttrs">
30
- <button
31
- class="mr-1"
32
- :class="activatorButtonClass(itemIndex)"
33
- @click="item.active = !item.active"
34
- >
35
- <v-avatar
36
- v-if="item.iconImage && item.fileConfig"
37
- class="clickable--image"
38
- :rounded="
39
- block.metadata.config.display.round_icon
40
- ? '100%'
41
- : '0'
42
- "
43
- size="100%"
44
- >
45
- <ImageAssetViewer
46
- v-if="
47
- item.fileConfig.asset &&
48
- item.fileConfig.asset.file_asset_id
31
+ <v-row class="mb-0 pb-0" no-gutters>
32
+ <v-col cols="auto" class="flex-shrink-0 ma-2 mr-0">
33
+ <button :class="activatorButtonClass(itemIndex)">
34
+ <v-avatar
35
+ v-if="item.iconImage && item.fileConfig"
36
+ class="clickable--image"
37
+ :rounded="
38
+ block.metadata.config.display.round_icon
39
+ ? '100%'
40
+ : '0'
49
41
  "
50
- v-model="item.fileConfig"
51
- :assets="block.assets"
52
- class="image-asset-viewer"
53
- :inherit-sizing="true"
54
- ></ImageAssetViewer>
55
- </v-avatar>
56
- <v-icon
57
- v-else-if="isIcon(item.icon)"
58
- class="clickable--icon black--text"
59
- >{{ item.icon }}</v-icon
60
- >
61
- <span v-else :class="iconClass + ' black--text'">{{
62
- decode(item.icon)
63
- }}</span>
64
- </button>
65
- </v-col>
66
- <v-col cols="12" v-bind="bodyColumnAttrs">
67
- <v-container :class="bodyClass">
68
- <h4
69
- v-if="
70
- block.metadata.config.display.show_title ||
71
- item.active
72
- "
73
- role="button"
74
- @click="item.active = !item.active"
75
- >
76
- {{ item.title }}
77
- </h4>
78
- <v-expand-transition>
79
- <div v-if="item.active">
80
- <v-divider light class="my-4" />
81
- <TextViewer v-model="item.body"></TextViewer>
82
- </div>
83
- </v-expand-transition>
84
- </v-container>
85
- </v-col>
86
- </v-row>
42
+ size="100%"
43
+ >
44
+ <ImageAssetViewer
45
+ v-if="
46
+ item.fileConfig.asset &&
47
+ item.fileConfig.asset.file_asset_id
48
+ "
49
+ v-model="item.fileConfig"
50
+ :assets="block.assets"
51
+ class="image-asset-viewer"
52
+ :inherit-sizing="true"
53
+ ></ImageAssetViewer>
54
+ </v-avatar>
55
+ <v-icon
56
+ v-else-if="isIcon(item.icon)"
57
+ class="clickable--icon black--text"
58
+ >{{ item.icon }}</v-icon
59
+ >
60
+ <span v-else :class="iconClass + ' black--text'">{{
61
+ decode(item.icon)
62
+ }}</span>
63
+ </button>
64
+ </v-col>
65
+ <v-col style="min-width: 0" class="ma-3 mt-5">
66
+ <v-row :class="clickableContainerClass">
67
+ <v-col style="min-width: 0">
68
+ <h3
69
+ v-if="
70
+ block.metadata.config.display
71
+ .show_title || item.active
72
+ "
73
+ class="wrap-text"
74
+ >
75
+ {{ item.title }}
76
+ </h3>
77
+ </v-col>
78
+ <v-col cols="auto" class="d-flex justify-end">
79
+ <v-icon v-if="!item.active" class="icon-chevron"
80
+ >mdi-chevron-down</v-icon
81
+ >
82
+ <v-icon v-else class="icon-chevron"
83
+ >mdi-chevron-up</v-icon
84
+ >
85
+ </v-col>
86
+ </v-row>
87
+ <div v-if="item.active && !isMobile" class="pr-8">
88
+ <TextViewer
89
+ v-model="item.body"
90
+ class="content-body"
91
+ />
92
+ </div>
93
+ </v-col>
94
+ </v-row>
95
+ <v-row
96
+ v-if="isMobile && item.active"
97
+ cols="12"
98
+ class="px-6 pb-4"
99
+ >
100
+ <TextViewer v-model="item.body" class="content-body" />
101
+ </v-row>
102
+ </v-container>
87
103
  </v-container>
88
104
  </div>
89
105
  </template>
@@ -128,46 +144,9 @@ export default {
128
144
  ) {
129
145
  classes += ' ' + this.itemColor(itemIndex) + ' black--text'
130
146
  }
131
- if (window.innerWidth <= 600) {
132
- classes += 'd-flex justify-center '
133
- }
134
147
  return classes
135
148
  }
136
149
  },
137
- iconColumnAttrs() {
138
- if (this.block.metadata.config.display.large_icon) {
139
- return {
140
- xl: '3',
141
- lg: '3',
142
- md: '4',
143
- sm: '4',
144
- }
145
- } else {
146
- return {
147
- xl: '2',
148
- lg: '3',
149
- md: '3',
150
- sm: '3',
151
- }
152
- }
153
- },
154
- bodyColumnAttrs() {
155
- if (this.block.metadata.config.display.large_icon) {
156
- return {
157
- xl: '9',
158
- lg: '8',
159
- md: '8',
160
- sm: '8',
161
- }
162
- } else {
163
- return {
164
- xl: '10',
165
- lg: '9',
166
- md: '9',
167
- sm: '9',
168
- }
169
- }
170
- },
171
150
  iconClass() {
172
151
  let classes = 'clickable--text'
173
152
  if (
@@ -198,12 +177,12 @@ export default {
198
177
  // If autocolor is enabled then calculate the color on the index
199
178
  if (this.block.metadata.config.display.autocolor) {
200
179
  const colors = [
201
- 'light-blue lighten-4',
202
- 'light-green lighten-4',
203
- 'yellow lighten-4',
204
- 'orange lighten-3',
205
- 'red lighten-3',
206
- 'deep-purple lighten-3',
180
+ 'extended-system-9',
181
+ 'extended-system-7',
182
+ 'extended-system-6',
183
+ 'extended-system-5',
184
+ 'extended-system-4',
185
+ 'extended-system-10',
207
186
  ]
208
187
  let colorIndex = itemIndex
209
188
  // If we exceed the above list of colors loop back around to the beginning
@@ -230,12 +209,15 @@ export default {
230
209
  }
231
210
  }
232
211
  },
233
- bodyClass() {
234
- if (window.innerWidth <= 665) {
235
- return ''
236
- } else {
237
- return 'ml-4'
212
+ isMobile() {
213
+ return window.innerWidth <= 400
214
+ },
215
+ clickableContainerClass() {
216
+ let classList = 'align-start'
217
+ if (window.innerWidth > 400) {
218
+ classList += ' mb-3'
238
219
  }
220
+ return classList
239
221
  },
240
222
  },
241
223
  beforeMount() {
@@ -277,11 +259,6 @@ export default {
277
259
  isIcon(str) {
278
260
  return str && _.isString(str) && str.indexOf('mdi-') === 0
279
261
  },
280
- getImagePublicUrl(asset) {
281
- const foundAsset = this.resolveAsset(asset)
282
-
283
- return _.get(foundAsset, 'asset.public_url', null)
284
- },
285
262
  },
286
263
  }
287
264
  </script>
@@ -291,9 +268,8 @@ export default {
291
268
  }
292
269
  button.button-icon {
293
270
  border-width: 4px;
294
- width: 100% !important;
295
- height: auto !important;
296
271
  aspect-ratio: 1 / 1;
272
+ border-radius: 16px;
297
273
  }
298
274
 
299
275
  button.button-icon.button-icon--outline {
@@ -307,16 +283,20 @@ button.button-icon.button-icon--rounded {
307
283
  }
308
284
 
309
285
  .button-icon--normal {
286
+ width: 5rem;
287
+ height: 5rem;
310
288
  .clickable--icon {
311
- font-size: 5rem;
289
+ font-size: 3rem;
312
290
  }
313
291
  .clickable--text {
314
292
  font-size: 1.5rem;
315
293
  }
316
294
  }
317
295
  .button-icon--large {
296
+ width: 7rem;
297
+ height: 7rem;
318
298
  .clickable--icon {
319
- font-size: 9rem;
299
+ font-size: 5rem;
320
300
  }
321
301
  .clickable--text {
322
302
  font-size: 2.5rem;
@@ -326,4 +306,21 @@ button.button-icon.button-icon--rounded {
326
306
  height: inherit;
327
307
  width: inherit;
328
308
  }
309
+
310
+ .content-body {
311
+ overflow-wrap: break-word;
312
+ word-break: break-word;
313
+ }
314
+ .icon-chevron {
315
+ color: inherit;
316
+ }
317
+ h3 {
318
+ font-size: 1.2rem;
319
+ font-weight: 500;
320
+ }
321
+ .wrap-text {
322
+ word-wrap: break-word;
323
+ overflow-wrap: break-word;
324
+ white-space: normal;
325
+ }
329
326
  </style>
@@ -34,13 +34,21 @@ import UserContentBlockState from '~/models/UserContentBlockState'
34
34
 
35
35
  export default {
36
36
  name: 'ContentBlockOpenResponseCollate',
37
- extends: BaseContentBlock,
38
37
  components: {},
38
+ extends: BaseContentBlock,
39
39
  data() {
40
40
  return {
41
41
  saveState: false, // Override the base block to disable state saving
42
42
  }
43
43
  },
44
+ computed: {
45
+ ...mapGetters({
46
+ organization: 'organization/get',
47
+ course: 'course/get',
48
+ enrollment: 'enrollment/get',
49
+ content: 'content/get',
50
+ }),
51
+ },
44
52
  beforeMount() {
45
53
  if (_.isEmpty(this.block.body)) {
46
54
  this.block.body = ''
@@ -59,20 +67,10 @@ export default {
59
67
  this.block.metadata.config.include_prompts = false
60
68
  }
61
69
  },
62
- computed: {
63
- ...mapGetters({
64
- organization: 'organization/get',
65
- course: 'course/get',
66
- enrollment: 'enrollment/get',
67
- content: 'content/get',
68
- }),
69
- },
70
- watch: {},
71
- mounted() {},
72
70
  methods: {
73
71
  async onCollate() {
74
72
  try {
75
- let userState = await UserContentBlockState.where({
73
+ const userState = await UserContentBlockState.where({
76
74
  'metadata->block->tag': 'plugin-core-open-response',
77
75
  course_user_id: this.enrollment.id,
78
76
  })
@@ -98,14 +96,14 @@ export default {
98
96
  '</strong><hr />'
99
97
  }
100
98
  if (!_.isEmpty(state.metadata.response)) {
101
- collated += '\n' + state.metadata.response
99
+ collated += '\n' + state.metadata.response + '<br>'
102
100
  } else {
103
101
  collated +=
104
102
  '\n<p>' +
105
103
  this.$t(
106
104
  'windward.core.components.content.blocks.open_response_collate.no_response'
107
105
  ) +
108
- '</p>'
106
+ '</p><br>'
109
107
  }
110
108
  })
111
109
  let filename = this.block.metadata.config.filename
@@ -123,7 +121,24 @@ export default {
123
121
  filename = filename.replaceAll(/\s+/gi, '_')
124
122
  filename = filename.replaceAll(/[^a-z0-9\-\_]/gi, '')
125
123
  }
126
-
124
+ if (!_.isEmpty(this.block.metadata.config.additional_text)) {
125
+ if (
126
+ this.block.metadata.config.additional_text_alignment ===
127
+ 'top'
128
+ ) {
129
+ collated =
130
+ this.block.metadata.config.additional_text +
131
+ '<br><br><br>' +
132
+ collated
133
+ } else if (
134
+ this.block.metadata.config.additional_text_alignment ===
135
+ 'bottom'
136
+ ) {
137
+ collated +=
138
+ '<br><br><br>' +
139
+ this.block.metadata.config.additional_text
140
+ }
141
+ }
127
142
  this.generateDocument(collated, filename)
128
143
  } catch (e) {
129
144
  // eslint-disable-next-line no-console
@@ -13,46 +13,48 @@
13
13
  {{ block.metadata.config.instructions }}
14
14
  </p>
15
15
 
16
- <VuetifyPlayer
17
- :language="$i18n && $i18n.locale ? $i18n.locale : 'en-US'"
18
- type="auto"
19
- :playlist="linkedPlaylist"
20
- :autoplay="block.metadata.config.attributes.autoplay"
21
- :autopictureinpicture="
22
- block.metadata.config.attributes.autopictureinpicture
23
- "
24
- :controls="block.metadata.config.attributes.controls"
25
- :controlslist="block.metadata.config.attributes.controlslist"
26
- :crossorigin="block.metadata.config.attributes.crossorigin"
27
- :disablepictureinpicture="
28
- block.metadata.config.attributes.disablepictureinpicture
29
- "
30
- :disableremoteplayback="
31
- block.metadata.config.attributes.disableremoteplayback
32
- "
33
- :height="block.metadata.config.attributes.height"
34
- :width="block.metadata.config.attributes.width"
35
- :rewind="block.metadata.config.attributes.rewind"
36
- :loop="block.metadata.config.attributes.loop"
37
- :muted="block.metadata.config.attributes.muted"
38
- :volume.sync="volume"
39
- :cc.sync="ccVisible"
40
- :playsinline="block.metadata.config.attributes.playsinline"
41
- :poster="linkedPosterPublicUrl"
42
- :preload="block.metadata.config.attributes.preload"
43
- :captionsmenu="block.metadata.config.attributes.captionsmenu"
44
- :playlistmenu="block.metadata.config.attributes.playlistmenu"
45
- :playlistautoadvance="
46
- block.metadata.config.attributes.playlistautoadvance
47
- "
48
- :playbackrates="block.metadata.config.attributes.playbackrates"
49
- :captions-autoscroll.sync="captionsAutoscroll"
50
- :captions-paragraph-view.sync="captionsParagraphView"
51
- :captions-visible.sync="captionsVisible"
52
- flat
53
- @seeking="onSeeking"
54
- @timeupdate="onTimeupdate"
55
- >
16
+ <div class="video-wrapper">
17
+ <VuetifyPlayer
18
+ class="no-border-player"
19
+ :language="$i18n && $i18n.locale ? $i18n.locale : 'en-US'"
20
+ type="auto"
21
+ :playlist="linkedPlaylist"
22
+ :autoplay="block.metadata.config.attributes.autoplay"
23
+ :autopictureinpicture="
24
+ block.metadata.config.attributes.autopictureinpicture
25
+ "
26
+ :controls="block.metadata.config.attributes.controls"
27
+ :controlslist="block.metadata.config.attributes.controlslist"
28
+ :crossorigin="block.metadata.config.attributes.crossorigin"
29
+ :disablepictureinpicture="
30
+ block.metadata.config.attributes.disablepictureinpicture
31
+ "
32
+ :disableremoteplayback="
33
+ block.metadata.config.attributes.disableremoteplayback
34
+ "
35
+ :height="block.metadata.config.attributes.height"
36
+ :width="block.metadata.config.attributes.width"
37
+ :rewind="block.metadata.config.attributes.rewind"
38
+ :loop="block.metadata.config.attributes.loop"
39
+ :muted="block.metadata.config.attributes.muted"
40
+ :volume.sync="volume"
41
+ :cc.sync="ccVisible"
42
+ :playsinline="block.metadata.config.attributes.playsinline"
43
+ :poster="linkedPosterPublicUrl"
44
+ :preload="block.metadata.config.attributes.preload"
45
+ :captionsmenu="block.metadata.config.attributes.captionsmenu"
46
+ :playlistmenu="block.metadata.config.attributes.playlistmenu"
47
+ :playlistautoadvance="
48
+ block.metadata.config.attributes.playlistautoadvance
49
+ "
50
+ :playbackrates="block.metadata.config.attributes.playbackrates"
51
+ :captions-autoscroll.sync="captionsAutoscroll"
52
+ :captions-paragraph-view.sync="captionsParagraphView"
53
+ :captions-visible.sync="captionsVisible"
54
+ flat
55
+ @seeking="onSeeking"
56
+ @timeupdate="onTimeupdate"
57
+ >
56
58
  <template #no-source>
57
59
  <v-card>
58
60
  <v-card-title class="justify-center">
@@ -71,6 +73,7 @@
71
73
  </v-card>
72
74
  </template>
73
75
  </VuetifyPlayer>
76
+ </div>
74
77
  <!-- display first note in the playlist for now -->
75
78
  <v-alert
76
79
  v-if="notes.length > 0"
@@ -475,3 +478,27 @@ export default {
475
478
  overflow: hidden;
476
479
  }
477
480
  </style>
481
+
482
+ <style>
483
+ /* Remove black backgrounds from video player containers */
484
+ .no-border-player,
485
+ .no-border-player > div:not(.vjs-control-bar),
486
+ .no-border-player .video-js:not(.vjs-control-bar) {
487
+ background: transparent !important;
488
+ background-color: transparent !important;
489
+ }
490
+
491
+ /* Ensure video element has transparent background */
492
+ .no-border-player video {
493
+ background: transparent !important;
494
+ background-color: transparent !important;
495
+ }
496
+
497
+ /* Make video player larger */
498
+ .video-wrapper {
499
+ margin-left: -15px;
500
+ margin-right: -15px;
501
+ padding-left: 0;
502
+ padding-right: 0;
503
+ }
504
+ </style>
@@ -60,6 +60,30 @@
60
60
  </v-list-item>
61
61
  </v-list-item-group>
62
62
  </v-list>
63
+ <v-container class="pa-0">
64
+ <p>
65
+ {{
66
+ $t(
67
+ 'windward.core.components.settings.open_response_collate.additional_text'
68
+ )
69
+ }}
70
+ </p>
71
+ <TextEditor
72
+ v-model="block.metadata.config.additional_text"
73
+ :disabled="render"
74
+ ></TextEditor>
75
+ <v-btn-toggle
76
+ v-model="block.metadata.config.additional_text_alignment"
77
+ class="pt-2"
78
+ >
79
+ <v-btn value="top" :disabled="render">{{
80
+ $t('windward.core.shared.settings.top')
81
+ }}</v-btn>
82
+ <v-btn value="bottom" :disabled="render">{{
83
+ $t('windward.core.shared.settings.bottom')
84
+ }}</v-btn>
85
+ </v-btn-toggle>
86
+ </v-container>
63
87
  </v-form>
64
88
  </v-container>
65
89
  </template>
@@ -67,16 +91,15 @@
67
91
  <script>
68
92
  import _ from 'lodash'
69
93
  import { mapGetters } from 'vuex'
70
- import draggable from 'vuedraggable'
71
- import TextEditor from '~/components/Text/TextEditor'
72
94
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
73
95
  import ContentBlock from '~/models/ContentBlock'
74
96
  import Course from '~/models/Course'
97
+ import TextEditor from '~/components/Text/TextEditor'
75
98
 
76
99
  export default {
77
100
  name: 'OpenResponseCollateSettings',
101
+ components: { TextEditor },
78
102
  extends: BaseContentSettings,
79
- components: { draggable, TextEditor },
80
103
  props: {
81
104
  settings: { type: Object, required: false, default: null },
82
105
  context: { type: String, required: false, default: 'block' },
@@ -87,6 +110,16 @@ export default {
87
110
  linked: [],
88
111
  }
89
112
  },
113
+ async fetch() {
114
+ this.contentBlocks = await ContentBlock.where(
115
+ 'tag',
116
+ 'plugin-core-open-response'
117
+ )
118
+ .for(new Course({ id: this.course.id }))
119
+ .get()
120
+
121
+ this.sortContentBlocks(this.contentBlocks)
122
+ },
90
123
  computed: {
91
124
  ...mapGetters({
92
125
  organization: 'organization/get',
@@ -100,16 +133,6 @@ export default {
100
133
  }
101
134
  },
102
135
  },
103
- async fetch() {
104
- this.contentBlocks = await ContentBlock.where(
105
- 'tag',
106
- 'plugin-core-open-response'
107
- )
108
- .for(new Course({ id: this.course.id }))
109
- .get()
110
-
111
- this.sortContentBlocks(this.contentBlocks)
112
- },
113
136
  beforeMount() {
114
137
  if (_.isEmpty(this.block)) {
115
138
  this.block = {}
@@ -129,21 +152,30 @@ export default {
129
152
  if (_.isEmpty(this.block.metadata.config.filename)) {
130
153
  this.block.metadata.config.filename = ''
131
154
  }
155
+ if (_.isEmpty(this.block.metadata.config.additional_text)) {
156
+ this.block.metadata.config.additional_text = ''
157
+ }
158
+ if (_.isEmpty(this.block.metadata.config.additional_text_alignment)) {
159
+ this.block.metadata.config.additional_text_alignment = 'top'
160
+ }
132
161
  // _.isEmpty(true) returns false. use isBoolean to check if this prop exists
133
162
  if (!_.isBoolean(this.block.metadata.config.include_prompts)) {
134
163
  this.block.metadata.config.include_prompts = false
135
164
  }
136
165
  this.linked = this.block.metadata.config.linked
137
166
  },
138
- watch: {},
139
167
  mounted() {},
140
168
  methods: {
141
169
  sortContentBlocks(contentBlocks) {
142
- let contentBlocksByPage = []
170
+ const contentBlocksByPage = []
143
171
  const blockMap = []
144
172
  // getting page order
145
173
  const contentBlockTree = this.$ContentService.getFlatTree()
146
- contentBlockTree.forEach(function (page, index) {
174
+ const homePage = this.$ContentService.getHomepage()
175
+ if (homePage !== null) {
176
+ contentBlockTree.unshift(homePage)
177
+ }
178
+ contentBlockTree.forEach(function (page) {
147
179
  contentBlocks.forEach((block) => {
148
180
  // If the block is on this page and it wasn't already used on a different page
149
181
  if (