@windward/core 0.5.2 → 0.7.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 (53) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/components/Content/Blocks/Accordion.vue +28 -0
  3. package/components/Content/Blocks/ClickableIcons.vue +31 -21
  4. package/components/Content/Blocks/Email.vue +11 -2
  5. package/components/Content/Blocks/FileDownload.vue +6 -8
  6. package/components/Content/Blocks/GenerateAIQuestionButton.vue +14 -6
  7. package/components/Content/Blocks/HorizontalRule.vue +1 -4
  8. package/components/Content/Blocks/ScenarioChoice.vue +58 -33
  9. package/components/Content/Blocks/Tab.vue +5 -0
  10. package/components/Content/Blocks/UserUpload.vue +1 -8
  11. package/components/Content/Blocks/Video.vue +96 -8
  12. package/components/Settings/AccordionSettings.vue +176 -136
  13. package/components/Settings/BlockQuoteSettings.vue +41 -2
  14. package/components/Settings/ClickableIconsSettings.vue +66 -10
  15. package/components/Settings/EmailSettings.vue +39 -4
  16. package/components/Settings/FileDownloadSettings.vue +38 -7
  17. package/components/Settings/HorizontalRuleSettings.vue +0 -3
  18. package/components/Settings/ScenarioChoiceSettings.vue +47 -9
  19. package/components/Settings/TabSettings.vue +124 -71
  20. package/components/Settings/TextEditorSettings.vue +14 -2
  21. package/components/Settings/UserUploadSettings.vue +42 -23
  22. package/components/Settings/VideoSettings/SourcePicker.vue +222 -0
  23. package/components/Settings/VideoSettings.vue +151 -165
  24. package/components/utils/TinyMCEWrapper.vue +25 -3
  25. package/components/utils/assets/tinymce/content/global.scss +14 -0
  26. package/components/utils/glossary/CourseGlossary.vue +52 -27
  27. package/helpers/tinymce/WindwardPlugins.ts +59 -10
  28. package/i18n/en-US/components/content/blocks/generate_questions.ts +2 -1
  29. package/i18n/en-US/components/content/blocks/video.ts +2 -51
  30. package/i18n/en-US/components/settings/accordion.ts +1 -0
  31. package/i18n/en-US/components/settings/tab.ts +1 -0
  32. package/i18n/en-US/components/settings/user_upload.ts +1 -0
  33. package/i18n/en-US/components/settings/video.ts +51 -0
  34. package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +9 -1
  35. package/i18n/en-US/shared/settings.ts +3 -0
  36. package/i18n/es-ES/components/content/blocks/generate_questions.ts +2 -1
  37. package/i18n/es-ES/components/content/blocks/video.ts +3 -53
  38. package/i18n/es-ES/components/settings/accordion.ts +1 -0
  39. package/i18n/es-ES/components/settings/tab.ts +2 -0
  40. package/i18n/es-ES/components/settings/user_upload.ts +1 -0
  41. package/i18n/es-ES/components/settings/video.ts +53 -0
  42. package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +9 -0
  43. package/i18n/es-ES/shared/settings.ts +3 -0
  44. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +2 -1
  45. package/i18n/sv-SE/components/content/blocks/video.ts +2 -51
  46. package/i18n/sv-SE/components/settings/accordion.ts +2 -0
  47. package/i18n/sv-SE/components/settings/tab.ts +1 -0
  48. package/i18n/sv-SE/components/settings/user_upload.ts +1 -0
  49. package/i18n/sv-SE/components/settings/video.ts +51 -0
  50. package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +8 -0
  51. package/i18n/sv-SE/shared/settings.ts +3 -0
  52. package/package.json +2 -1
  53. package/plugin.js +1 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## Release [0.7.0] - 2024-08-29
4
+
5
+ * Version bump to 0.7.0
6
+
7
+
8
+ ### Release [0.7.0] created - 2024-08-29
9
+
10
+
11
+ ### Release [0.6.0] created - 2024-07-30
@@ -1,5 +1,16 @@
1
1
  <template>
2
2
  <div>
3
+ <v-container
4
+ class="pa-0"
5
+ v-if="
6
+ block.metadata.config.title ||
7
+ block.metadata.config.instructions
8
+ "
9
+ >
10
+ <h2>{{ block.metadata.config.title }}</h2>
11
+
12
+ <p>{{ block.metadata.config.instructions }}</p>
13
+ </v-container>
3
14
  <v-expansion-panels
4
15
  :value="selectedPanels"
5
16
  flat
@@ -14,6 +25,8 @@
14
25
  <v-expansion-panel-header
15
26
  class="expansion-panel-header"
16
27
  color="primary"
28
+ :ref="'expansion-panel-' + itemIndex"
29
+ @click="onOpenAccordion(itemIndex)"
17
30
  >
18
31
  {{
19
32
  item.header === '' || item.header === null
@@ -89,6 +102,9 @@ export default {
89
102
  if (_.isEmpty(this.block.metadata.config)) {
90
103
  this.block.metadata.config = {}
91
104
  }
105
+ if (_.isEmpty(this.block.metadata.config.title)) {
106
+ this.block.metadata.config.title = ''
107
+ }
92
108
  if (_.isEmpty(this.block.metadata.config.items)) {
93
109
  const defaultObject = {
94
110
  header: '',
@@ -157,6 +173,18 @@ export default {
157
173
  }
158
174
  },
159
175
  methods: {
176
+ onOpenAccordion(value) {
177
+ const indexString = value.toString()
178
+ const referenceString = 'expansion-panel-' + indexString
179
+ if (
180
+ this.$refs[referenceString] &&
181
+ this.$refs[referenceString][0].$el
182
+ ) {
183
+ setTimeout(() => {
184
+ this.$vuetify.goTo(this.$refs[referenceString][0].$el)
185
+ }, 300)
186
+ }
187
+ },
160
188
  getImagePublicUrl(asset) {
161
189
  const foundAsset = this.resolveAsset(asset)
162
190
 
@@ -3,7 +3,7 @@
3
3
  <v-container class="pa-0">
4
4
  <h2>{{ block.metadata.config.title }}</h2>
5
5
 
6
- <h4>{{ block.metadata.config.description }}</h4>
6
+ <p>{{ block.metadata.config.description }}</p>
7
7
 
8
8
  <p>
9
9
  {{
@@ -23,6 +23,7 @@
23
23
  >
24
24
  <v-col cols="12" v-bind="iconColumnAttrs">
25
25
  <button
26
+ class="mr-1"
26
27
  :class="activatorButtonClass(itemIndex)"
27
28
  @click="item.active = !item.active"
28
29
  >
@@ -49,23 +50,24 @@
49
50
  </button>
50
51
  </v-col>
51
52
  <v-col cols="12" v-bind="bodyColumnAttrs">
52
- <h4
53
- v-if="
54
- block.metadata.config.display.show_title ||
55
- item.active
56
- "
57
- class="mt-4"
58
- role="button"
59
- @click="item.active = !item.active"
60
- >
61
- {{ item.title }}
62
- </h4>
63
- <v-expand-transition>
64
- <div v-if="item.active">
65
- <v-divider light class="my-4" />
66
- <TextViewer v-model="item.body"></TextViewer>
67
- </div>
68
- </v-expand-transition>
53
+ <v-container :class="bodyClass">
54
+ <h4
55
+ v-if="
56
+ block.metadata.config.display.show_title ||
57
+ item.active
58
+ "
59
+ role="button"
60
+ @click="item.active = !item.active"
61
+ >
62
+ {{ item.title }}
63
+ </h4>
64
+ <v-expand-transition>
65
+ <div v-if="item.active">
66
+ <v-divider light class="my-4" />
67
+ <TextViewer v-model="item.body"></TextViewer>
68
+ </div>
69
+ </v-expand-transition>
70
+ </v-container>
69
71
  </v-col>
70
72
  </v-row>
71
73
  </v-container>
@@ -74,7 +76,6 @@
74
76
  <script>
75
77
  import _ from 'lodash'
76
78
  import he from 'he'
77
- import Uuid from '~/helpers/Uuid'
78
79
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
79
80
  import TextViewer from '~/components/Text/TextViewer'
80
81
 
@@ -114,7 +115,9 @@ export default {
114
115
  }
115
116
  },
116
117
  data() {
117
- return {}
118
+ return {
119
+ saveState: false,
120
+ }
118
121
  },
119
122
  computed: {
120
123
  decode() {
@@ -152,7 +155,7 @@ export default {
152
155
  } else {
153
156
  return {
154
157
  xl: '2',
155
- lg: '2',
158
+ lg: '3',
156
159
  md: '3',
157
160
  sm: '3',
158
161
  }
@@ -237,6 +240,13 @@ export default {
237
240
  }
238
241
  }
239
242
  },
243
+ bodyClass() {
244
+ if (window.innerWidth <= 665) {
245
+ return ''
246
+ } else {
247
+ return 'ml-4'
248
+ }
249
+ },
240
250
  },
241
251
  methods: {
242
252
  isIcon(str) {
@@ -25,7 +25,7 @@
25
25
  class="pa-0 primary container-subject d-flex justify-center align-center"
26
26
  >
27
27
  <v-row class="d-flex ma-5">
28
- <v-col cols="6">
28
+ <v-col cols="12">
29
29
  {{
30
30
  $t(
31
31
  'windward.core.components.content.blocks.email.email'
@@ -33,6 +33,7 @@
33
33
  }}
34
34
  </v-col>
35
35
  <v-col
36
+ v-if="false"
36
37
  cols="6"
37
38
  class="d-flex justify-end column-simulation"
38
39
  >
@@ -170,7 +171,11 @@
170
171
  >
171
172
  <v-row class="pt-4">
172
173
  <v-col cols="10" class="pa-0">
173
- <v-btn elevation="0" color="primary" text>
174
+ <v-btn
175
+ elevation="0"
176
+ color="primary"
177
+ text
178
+ >
174
179
  <v-icon>mdi-reply</v-icon>
175
180
  {{
176
181
  $t(
@@ -197,6 +202,7 @@
197
202
  </v-col>
198
203
  <v-col cols="2" class="pa-0 d-flex justify-end">
199
204
  <v-btn
205
+ v-show="false"
200
206
  elevation="0"
201
207
  color="primary"
202
208
  outlined
@@ -350,4 +356,7 @@ export default {
350
356
  .v-expansion-panels {
351
357
  z-index: 0 !important;
352
358
  }
359
+ .v-btn:before {
360
+ background-color: transparent !important;
361
+ }
353
362
  </style>
@@ -8,17 +8,15 @@
8
8
  )
9
9
  }}
10
10
  </h2>
11
- <p v-if="!block.metadata.config.instructions">
11
+ <p>
12
12
  {{
13
- $t(
14
- 'windward.core.components.content.blocks.file_download.default_instructions'
15
- )
13
+ block.metadata.config.instructions
14
+ ? block.metadata.config.instructions
15
+ : $t(
16
+ 'windward.core.components.content.blocks.file_download.default_instructions'
17
+ )
16
18
  }}
17
19
  </p>
18
- <TextViewer
19
- v-if="block.metadata.config.instructions"
20
- :value="block.metadata.config.instructions"
21
- />
22
20
  <v-alert
23
21
  v-if="
24
22
  !block.metadata.config.items ||
@@ -1,13 +1,17 @@
1
1
  <template>
2
2
  <v-btn
3
3
  elevation="0"
4
- color="primary"
5
- icon
4
+ color="secondary"
6
5
  @click="generateAIQuestion"
7
6
  :loading="isLoading"
8
7
  :disabled="isLoading"
9
8
  >
10
- <v-icon v-if="!isLoading">mdi-magic-staff</v-icon>
9
+ <v-icon class="pr-1" v-if="!isLoading">mdi-magic-staff</v-icon>
10
+ {{
11
+ this.$t(
12
+ 'windward.core.components.content.blocks.generate_questions.button_label'
13
+ )
14
+ }}
11
15
  <template v-slot:loader>
12
16
  <v-progress-circular indeterminate size="23"></v-progress-circular>
13
17
  </template>
@@ -42,7 +46,7 @@ export default {
42
46
  new Content(this.content),
43
47
  new Assessment({ id: this.block.id }),
44
48
  new AssessmentQuestion(),
45
- `suggest/${this.questionType}`
49
+ `suggest/${this.questionType}`
46
50
  ).get()
47
51
 
48
52
  if (response && response.length > 0) {
@@ -51,11 +55,15 @@ export default {
51
55
  }
52
56
  } catch (error) {
53
57
  console.error(error)
54
- this.$dialog.error(this.$t('windward.core.components.content.blocks.generate_questions.error'))
58
+ this.$dialog.error(
59
+ this.$t(
60
+ 'windward.core.components.content.blocks.generate_questions.error'
61
+ )
62
+ )
55
63
  } finally {
56
64
  this.isLoading = false
57
65
  }
58
66
  },
59
67
  },
60
68
  }
61
- </script>
69
+ </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <hr :class="isDashed" />
3
+ <hr :class="isDashed" class="mt-2 mb-2" />
4
4
  </div>
5
5
  </template>
6
6
  <script>
@@ -19,9 +19,6 @@ export default {
19
19
  if (_.isEmpty(this.block.metadata.config)) {
20
20
  this.block.metadata.config = {}
21
21
  }
22
- if (_.isEmpty(this.block.metadata.config)) {
23
- this.block.metadata.config = {}
24
- }
25
22
  if (_.isEmpty(this.block.metadata.config.dashed)) {
26
23
  this.block.metadata.config.dashed = false
27
24
  }
@@ -2,19 +2,17 @@
2
2
  <div>
3
3
  <v-container class="pa-0">
4
4
  <h2>{{ block.metadata.config.title }}</h2>
5
- <h4>{{ block.metadata.config.description }}</h4>
6
- <p>
7
- {{
8
- $t(
9
- 'windward.core.components.content.blocks.scenario_choice.information'
10
- )
11
- }}
12
- </p>
5
+ <p>{{ block.metadata.config.description }}</p>
13
6
  <div
14
7
  v-if="choiceIndex !== null && block.metadata.config.show_reset"
15
8
  class="text-right"
16
9
  >
17
- <v-btn elevation="0" color="primary" outlined @click="onClickReset">
10
+ <v-btn
11
+ elevation="0"
12
+ color="primary"
13
+ outlined
14
+ @click="onClickReset"
15
+ >
18
16
  {{
19
17
  $t(
20
18
  'windward.core.components.content.blocks.scenario_choice.try_again'
@@ -30,7 +28,7 @@
30
28
  no-gutters
31
29
  :class="rowClass(item, itemIndex)"
32
30
  >
33
- <v-col cols="2" class="text-center">
31
+ <v-col cols="12" v-bind="iconColumnAttrs" class="text-center">
34
32
  <div
35
33
  class="pt-8 pb-8 text-icon-container mb-4 mx-auto"
36
34
  role="button"
@@ -48,30 +46,34 @@
48
46
  }}
49
47
  </strong>
50
48
  </v-col>
51
- <v-col cols="10">
52
- <h4
53
- role="button"
54
- class="mt-4"
55
- @click="onClickItem(itemIndex)"
56
- >
57
- {{ item.title }}
58
- </h4>
49
+ <v-col cols="12" v-bind="bodyColumnAttrs">
50
+ <v-container :class="bodyClass">
51
+ <h4
52
+ role="button"
53
+ class="mt-4"
54
+ @click="onClickItem(itemIndex)"
55
+ >
56
+ {{ item.title }}
57
+ </h4>
59
58
 
60
- <v-expand-transition>
61
- <div v-if="choiceIndex !== null">
62
- <v-divider light class="my-4" />
63
- <TextViewer v-model="item.body"></TextViewer>
64
- <a
65
- v-if="linkedPage !== null"
66
- class="white--text text-decoration-underline"
67
- @click="
68
- onClickLink(linkedPage.course_content_id)
69
- "
70
- >
71
- {{ linkedPage.text }}
72
- </a>
73
- </div>
74
- </v-expand-transition>
59
+ <v-expand-transition>
60
+ <div v-if="choiceIndex !== null">
61
+ <v-divider light class="my-4" />
62
+ <TextViewer v-model="item.body"></TextViewer>
63
+ <a
64
+ v-if="linkedPage !== null"
65
+ class="white--text text-decoration-underline"
66
+ @click="
67
+ onClickLink(
68
+ linkedPage.course_content_id
69
+ )
70
+ "
71
+ >
72
+ {{ linkedPage.text }}
73
+ </a>
74
+ </div>
75
+ </v-expand-transition>
76
+ </v-container>
75
77
  </v-col>
76
78
  </v-row>
77
79
  </v-container>
@@ -199,6 +201,29 @@ export default {
199
201
  return null
200
202
  }
201
203
  },
204
+ iconColumnAttrs() {
205
+ return {
206
+ xl: '2',
207
+ lg: '3',
208
+ md: '3',
209
+ sm: '3',
210
+ }
211
+ },
212
+ bodyColumnAttrs() {
213
+ return {
214
+ xl: '10',
215
+ lg: '9',
216
+ md: '9',
217
+ sm: '9',
218
+ }
219
+ },
220
+ bodyClass() {
221
+ if (window.innerWidth <= 665) {
222
+ return ''
223
+ } else {
224
+ return 'ml-4'
225
+ }
226
+ },
202
227
  },
203
228
  beforeMount() {
204
229
  // Apply the default config
@@ -1,5 +1,10 @@
1
1
  <template>
2
2
  <div>
3
+ <v-container class="pa-0">
4
+ <h2>{{ block.metadata.config.title }}</h2>
5
+
6
+ <p>{{ block.metadata.config.instructions }}</p>
7
+ </v-container>
3
8
  <v-container class="pa-0">
4
9
  <v-tabs dark v-model="block.metadata.config.currentTab" show-arrows>
5
10
  <v-tabs-slider></v-tabs-slider>
@@ -6,14 +6,7 @@
6
6
  </h2>
7
7
  <v-row>
8
8
  <v-col cols="12">
9
- <TextViewer
10
- v-if="render || !block.__expandInstructions"
11
- v-model="block.metadata.config.instructions"
12
- ></TextViewer>
13
- <TextEditor
14
- v-if="!render && block.__expandInstructions"
15
- v-model="block.metadata.config.instructions"
16
- />
9
+ <p>{{ block.metadata.config.instructions }}</p>
17
10
  </v-col>
18
11
  <v-col v-if="!blockExists" cols="12">
19
12
  <v-alert color="warning">
@@ -5,14 +5,12 @@
5
5
  <v-icon class="mr-2">mdi-cloud-question</v-icon>
6
6
  {{
7
7
  $t(
8
- 'windward.core.components.content.blocks.video.video.not_configured_title'
8
+ 'windward.core.components.content.blocks.video.not_configured_title'
9
9
  )
10
10
  }}
11
11
  </v-card-title>
12
12
  <v-card-text>{{
13
- $t(
14
- 'windward.core.components.content.blocks.video.video.edit_prompt'
15
- )
13
+ $t('windward.core.components.content.blocks.video.edit_prompt')
16
14
  }}</v-card-text>
17
15
  </v-card>
18
16
 
@@ -60,6 +58,17 @@
60
58
  "
61
59
  :playbackrates="block.metadata.config.attributes.playbackrates"
62
60
  />
61
+ <!-- display first note in the playlist for now -->
62
+ <v-alert
63
+ v-if="notes.length > 0"
64
+ :color="
65
+ $vuetify.theme.isDark
66
+ ? 'v-navigation-drawer'
67
+ : 'blue-grey lighten-5'
68
+ "
69
+ >
70
+ <TextViewer v-model="notes[0]"></TextViewer>
71
+ </v-alert>
63
72
  </div>
64
73
  </template>
65
74
 
@@ -67,10 +76,13 @@
67
76
  import _ from 'lodash'
68
77
  import VuetifyPlayer from '@mindedge/vuetify-player'
69
78
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
79
+ import TextViewer from '~/components/Text/TextViewer'
80
+ import he from 'he'
70
81
 
71
82
  export default {
72
83
  name: 'ContentBlockVideo',
73
84
  components: {
85
+ TextViewer,
74
86
  VuetifyPlayer,
75
87
  },
76
88
  extends: BaseContentBlock,
@@ -96,12 +108,30 @@ export default {
96
108
  )
97
109
  return _.get(file, 'asset.public_url')
98
110
  },
111
+ notes() {
112
+ const playlist = this.block.metadata.config.playlist
113
+ let result = []
114
+ for (const index in playlist) {
115
+ for (const sourceIndex in playlist[index].sources) {
116
+ let file = this.resolveAsset(
117
+ playlist[index].sources[sourceIndex]
118
+ )
119
+ if (
120
+ !_.isEmpty(_.get(file, 'asset.metadata.notes')) &&
121
+ //check the file notes are actually text and not an empty div
122
+ this.hasWords(_.get(file, 'asset.metadata.notes'))
123
+ ) {
124
+ result.push(_.get(file, 'asset.metadata.notes'))
125
+ }
126
+ }
127
+ }
128
+ return result
129
+ },
99
130
  /**
100
131
  * Resolve assets to their playlist items
101
132
  */
102
133
  linkedPlaylist() {
103
134
  const playlist = _.cloneDeep(this.block.metadata.config.playlist)
104
-
105
135
  for (const index in playlist) {
106
136
  for (const sourceIndex in playlist[index].sources) {
107
137
  let file = this.resolveAsset(
@@ -112,6 +142,13 @@ export default {
112
142
  src: _.get(file, 'asset.public_url', ''),
113
143
  type: _.get(file, 'asset.metadata.mime', ''),
114
144
  }
145
+
146
+ // If there's linked captions and there's no hard-set captions
147
+ // Fallback to the linked captions
148
+ const linkedCaptions = this.getLinkedCaptions(file)
149
+ if (playlist[index].tracks.length === 0 && linkedCaptions) {
150
+ playlist[index].tracks.push(linkedCaptions)
151
+ }
115
152
  }
116
153
 
117
154
  for (const trackIndex in playlist[index].tracks) {
@@ -126,7 +163,6 @@ export default {
126
163
  default: true,
127
164
  }
128
165
  }
129
-
130
166
  for (const adIndex in playlist[index].ads) {
131
167
  for (const adSourceIndex in playlist[index].ads[adIndex]
132
168
  .sources) {
@@ -138,7 +174,20 @@ export default {
138
174
  src: _.get(file, 'asset.public_url', ''),
139
175
  type: _.get(file, 'asset.metadata.mime', ''),
140
176
  }
177
+
178
+ // If there's linked captions and there's no hard-set captions
179
+ // Fallback to the linked captions
180
+ const linkedCaptions = this.getLinkedCaptions(file)
181
+ if (
182
+ playlist[index].ads[adIndex].tracks.length === 0 &&
183
+ linkedCaptions
184
+ ) {
185
+ playlist[index].ads[adIndex].tracks.push(
186
+ linkedCaptions
187
+ )
188
+ }
141
189
  }
190
+
142
191
  for (const adTrackIndex in playlist[index].ads[adIndex]
143
192
  .tracks) {
144
193
  let file = this.resolveAsset(
@@ -154,7 +203,11 @@ export default {
154
203
  }
155
204
  }
156
205
  }
157
-
206
+ // reset adds to empty array as we aren't using these right now
207
+ // left code above in case we want to implement later
208
+ playlist.forEach((element) => {
209
+ element.ads = []
210
+ })
158
211
  return playlist
159
212
  },
160
213
  },
@@ -249,11 +302,46 @@ export default {
249
302
  this.$set(this.block, 'assets', [])
250
303
  }
251
304
  },
252
- mounted() {},
253
305
  methods: {
254
306
  async onBeforeSave() {
255
307
  this.block.body = 'video'
256
308
  },
309
+ getLinkedCaptions(file) {
310
+ // Check to see if the video source has a linked asset and it's a vtt file
311
+ const linkedCaption = _.find(
312
+ _.get(file, 'asset.linked_assets', []),
313
+ function (f) {
314
+ return _.get(f, 'asset.metadata.extension', '') === 'vtt'
315
+ }
316
+ )
317
+
318
+ if (linkedCaption) {
319
+ const foundAsset = this.block.assets.find((a) => {
320
+ return a.file_asset_id === linkedCaption.file_asset_id
321
+ })
322
+
323
+ // The linked captions aren't part of the block.assets
324
+ // Add it so the block knows how to properly load it
325
+ if (!foundAsset) {
326
+ this.block.assets.push(_.cloneDeep(linkedCaption))
327
+ }
328
+ }
329
+
330
+ return linkedCaption || null
331
+ },
332
+
333
+ /**
334
+ * Check if the given text has words, omitting HTML tags and HTML entities
335
+ * @param {string} text - The text to check
336
+ * @returns {boolean} - True if the text has words, false otherwise
337
+ */
338
+ hasWords(text) {
339
+ const strippedText = he
340
+ .decode(text)
341
+ .replace(/<\/?[^>]+(>|$)/g, '') // Remove HTML tags
342
+ .trim()
343
+ return /\b\w+\b/.test(strippedText) // Check if there are any words
344
+ },
257
345
  },
258
346
  }
259
347
  </script>