@windward/core 0.26.0 → 0.28.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 (109) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/components/Content/Blocks/Accordion.vue +9 -15
  3. package/components/Content/Blocks/BlockQuote.vue +29 -4
  4. package/components/Content/Blocks/ClickableIcons.vue +22 -9
  5. package/components/Content/Blocks/Email.vue +11 -4
  6. package/components/Content/Blocks/Feedback/FeedbackAnalytics.vue +179 -0
  7. package/components/Content/Blocks/Feedback.vue +115 -111
  8. package/components/Content/Blocks/FileDownload.vue +2 -2
  9. package/components/Content/Blocks/Image.vue +144 -0
  10. package/components/Content/Blocks/OpenResponse.vue +419 -5
  11. package/components/Content/Blocks/ScenarioChoice.vue +11 -2
  12. package/components/Content/Blocks/Tab.vue +16 -29
  13. package/components/Content/Blocks/UserUpload.vue +66 -38
  14. package/components/Content/Blocks/Video.vue +377 -28
  15. package/components/Settings/AccordionSettings.vue +3 -15
  16. package/components/Settings/BlockQuoteSettings.vue +6 -4
  17. package/components/Settings/ClickableIconsSettings.vue +24 -10
  18. package/components/Settings/EmailSettings.vue +3 -11
  19. package/components/Settings/FileDownloadSettings.vue +8 -2
  20. package/components/Settings/ImageSettings.vue +26 -0
  21. package/components/Settings/OpenResponseCollateSettings.vue +10 -0
  22. package/components/Settings/OpenResponseSettings.vue +67 -7
  23. package/components/Settings/ScenarioChoiceSettings.vue +11 -5
  24. package/components/Settings/TabSettings.vue +3 -18
  25. package/components/Settings/UserUploadSettings.vue +16 -8
  26. package/components/Settings/VideoSettings/SourcePicker.vue +55 -21
  27. package/components/Settings/VideoSettings.vue +18 -2
  28. package/components/utils/ContentViewer.vue +180 -1
  29. package/components/utils/glossary/GlossaryToolTip.vue +4 -23
  30. package/helpers/GlossaryHelper.ts +4 -7
  31. package/i18n/en-US/components/content/blocks/accordion.ts +3 -0
  32. package/i18n/en-US/components/content/blocks/block_quote.ts +3 -1
  33. package/i18n/en-US/components/content/blocks/feedback.ts +2 -0
  34. package/i18n/en-US/components/content/blocks/file_download.ts +2 -1
  35. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  36. package/i18n/en-US/components/content/blocks/open_response.ts +19 -1
  37. package/i18n/en-US/components/content/blocks/open_response_collate.ts +1 -1
  38. package/i18n/en-US/components/content/blocks/scenario_choice.ts +2 -0
  39. package/i18n/en-US/components/content/blocks/user_upload.ts +2 -1
  40. package/i18n/en-US/components/settings/accordion.ts +2 -1
  41. package/i18n/en-US/components/settings/block_quote.ts +1 -1
  42. package/i18n/en-US/components/settings/clickable_icon.ts +5 -0
  43. package/i18n/en-US/components/settings/email.ts +2 -1
  44. package/i18n/en-US/components/settings/file_download.ts +2 -2
  45. package/i18n/en-US/components/settings/image.ts +1 -0
  46. package/i18n/en-US/components/settings/open_response.ts +8 -0
  47. package/i18n/en-US/components/settings/open_response_collate.ts +3 -0
  48. package/i18n/en-US/components/settings/scenario_choice.ts +3 -1
  49. package/i18n/en-US/components/settings/tab.ts +4 -3
  50. package/i18n/en-US/components/settings/user_upload.ts +1 -0
  51. package/i18n/en-US/components/settings/video.ts +3 -1
  52. package/i18n/en-US/shared/content_blocks.ts +1 -1
  53. package/i18n/es-ES/components/content/blocks/accordion.ts +3 -0
  54. package/i18n/es-ES/components/content/blocks/block_quote.ts +3 -1
  55. package/i18n/es-ES/components/content/blocks/feedback.ts +2 -0
  56. package/i18n/es-ES/components/content/blocks/file_download.ts +2 -1
  57. package/i18n/es-ES/components/content/blocks/index.ts +2 -0
  58. package/i18n/es-ES/components/content/blocks/open_response.ts +19 -2
  59. package/i18n/es-ES/components/content/blocks/open_response_collate.ts +1 -1
  60. package/i18n/es-ES/components/content/blocks/scenario_choice.ts +2 -0
  61. package/i18n/es-ES/components/content/blocks/user_upload.ts +2 -1
  62. package/i18n/es-ES/components/settings/accordion.ts +4 -2
  63. package/i18n/es-ES/components/settings/block_quote.ts +1 -1
  64. package/i18n/es-ES/components/settings/clickable_icon.ts +7 -0
  65. package/i18n/es-ES/components/settings/email.ts +2 -1
  66. package/i18n/es-ES/components/settings/image.ts +1 -0
  67. package/i18n/es-ES/components/settings/open_response.ts +8 -0
  68. package/i18n/es-ES/components/settings/open_response_collate.ts +3 -0
  69. package/i18n/es-ES/components/settings/scenario_choice.ts +3 -1
  70. package/i18n/es-ES/components/settings/tab.ts +3 -2
  71. package/i18n/es-ES/components/settings/user_upload.ts +1 -0
  72. package/i18n/es-ES/components/settings/video.ts +3 -1
  73. package/i18n/es-ES/shared/content_blocks.ts +1 -1
  74. package/i18n/sv-SE/components/content/blocks/accordion.ts +3 -0
  75. package/i18n/sv-SE/components/content/blocks/block_quote.ts +3 -1
  76. package/i18n/sv-SE/components/content/blocks/feedback.ts +2 -0
  77. package/i18n/sv-SE/components/content/blocks/file_download.ts +2 -1
  78. package/i18n/sv-SE/components/content/blocks/index.ts +2 -0
  79. package/i18n/sv-SE/components/content/blocks/open_response.ts +19 -2
  80. package/i18n/sv-SE/components/content/blocks/open_response_collate.ts +1 -1
  81. package/i18n/sv-SE/components/content/blocks/scenario_choice.ts +2 -0
  82. package/i18n/sv-SE/components/content/blocks/user_upload.ts +2 -1
  83. package/i18n/sv-SE/components/settings/accordion.ts +2 -1
  84. package/i18n/sv-SE/components/settings/block_quote.ts +1 -1
  85. package/i18n/sv-SE/components/settings/clickable_icon.ts +6 -0
  86. package/i18n/sv-SE/components/settings/email.ts +2 -1
  87. package/i18n/sv-SE/components/settings/image.ts +1 -0
  88. package/i18n/sv-SE/components/settings/open_response.ts +8 -0
  89. package/i18n/sv-SE/components/settings/open_response_collate.ts +3 -0
  90. package/i18n/sv-SE/components/settings/scenario_choice.ts +3 -1
  91. package/i18n/sv-SE/components/settings/tab.ts +5 -3
  92. package/i18n/sv-SE/components/settings/user_upload.ts +1 -0
  93. package/i18n/sv-SE/components/settings/video.ts +3 -1
  94. package/i18n/sv-SE/shared/content_blocks.ts +1 -1
  95. package/models/SurveyResultMetric.ts +8 -0
  96. package/package.json +2 -2
  97. package/plugin.js +8 -0
  98. package/test/Components/Content/Blocks/Feedback/FeedbackTemplates/FeedbackAnalytics.spec.js +23 -0
  99. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.spec.js +1 -1
  100. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.spec.js +1 -1
  101. package/test/Components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.spec.js +1 -1
  102. package/test/Components/Settings/AccordionSettings.spec.js +0 -13
  103. package/test/Components/Settings/ClickableIconsSettings.spec.js +1 -12
  104. package/test/Components/Settings/EmailSettings.spec.js +0 -9
  105. package/test/Components/Settings/TabSettings.spec.js +0 -13
  106. package/test/helpers/GlossaryHelper.spec.js +8 -8
  107. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionLikert.vue +1 -1
  108. package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionOpenResponse.vue +1 -1
  109. /package/components/Content/Blocks/{FeedbackTemplates → Feedback/FeedbackTemplates}/FeedbackQuestionTrueFalse.vue +0 -0
@@ -1,15 +1,12 @@
1
1
  <template>
2
- <v-container>
3
- <v-container v-if="!doesFeedbackExist" class="pa-0">
2
+ <div>
3
+ <v-alert v-if="!chosenPreset" type="warning">{{
4
+ $t('windward.core.components.content.blocks.feedback.no_feedback')
5
+ }}</v-alert>
6
+ <v-container v-if="!doesFeedbackExist && chosenPreset" class="pa-0">
4
7
  <v-row>
5
8
  <h2>
6
- {{
7
- block.metadata.config.definition.name
8
- ? block.metadata.config.definition.name
9
- : $t(
10
- 'windward.core.components.content.blocks.feedback.feedback'
11
- )
12
- }}
9
+ {{ block.metadata.config.definition.name }}
13
10
  </h2>
14
11
  </v-row>
15
12
  <v-row class="pt-2">
@@ -45,126 +42,133 @@
45
42
  </p>
46
43
  </v-row>
47
44
  </v-container>
48
- <v-divider class="mt-4 mb-6"></v-divider>
49
- <v-form
50
- ref="form"
51
- v-model="valid"
52
- lazy-validation
53
- v-if="!doesFeedbackExist"
54
- >
55
- <div v-if="block.metadata.config.definition.metadata">
45
+ <v-divider v-if="chosenPreset" class="mt-4 mb-6"></v-divider>
46
+ <!-- Survey Container with theme-based background -->
47
+ <v-card v-if="chosenPreset" class="pa-6 surface" flat>
48
+ <v-form
49
+ ref="form"
50
+ v-model="valid"
51
+ lazy-validation
52
+ v-if="!doesFeedbackExist"
53
+ >
54
+ <div v-if="block.metadata.config.definition.metadata">
55
+ <v-container
56
+ v-for="(question, questionIndex) in chosenPreset"
57
+ :key="questionIndex"
58
+ fluid
59
+ >
60
+ <v-container
61
+ v-if="question.type === 'likert'"
62
+ class="pa-0 ma-0"
63
+ >
64
+ <FeedbackQuestionLikert
65
+ v-model="chosenPreset[questionIndex]"
66
+ ></FeedbackQuestionLikert>
67
+ </v-container>
68
+ <v-container
69
+ v-if="question.type === 'true_false'"
70
+ class="pa-0 ma-0"
71
+ >
72
+ <FeedbackQuestionTrueFalse
73
+ v-model="chosenPreset[questionIndex]"
74
+ ></FeedbackQuestionTrueFalse>
75
+ </v-container>
76
+ <v-container
77
+ v-if="question.type === 'open_response'"
78
+ class="pa-0 ma-0"
79
+ >
80
+ <FeedbackQuestionOpenResponse
81
+ v-model="chosenPreset[questionIndex]"
82
+ ></FeedbackQuestionOpenResponse>
83
+ </v-container>
84
+ </v-container>
85
+ </div>
86
+ </v-form>
87
+ <v-row
88
+ class="d-flex justify-end align-center"
89
+ v-if="render && !doesFeedbackExist"
90
+ >
91
+ <v-btn
92
+ color="primary"
93
+ outlined
94
+ elevation="0"
95
+ class="text-center mt-4 mr-2"
96
+ @click="onReset()"
97
+ >{{
98
+ $t(
99
+ 'windward.core.components.content.blocks.feedback.reset'
100
+ )
101
+ }}</v-btn
102
+ >
103
+ <v-btn
104
+ elevation="0"
105
+ color="success"
106
+ class="text-center mt-4"
107
+ @click="validate"
108
+ >{{
109
+ $t(
110
+ 'windward.core.components.content.blocks.feedback.save'
111
+ )
112
+ }}</v-btn
113
+ >
114
+ </v-row>
115
+ <v-form
116
+ ref="form"
117
+ v-model="valid"
118
+ disabled
119
+ lazy-validation
120
+ v-if="doesFeedbackExist"
121
+ >
56
122
  <v-container
57
- v-for="(question, questionIndex) in chosenPreset"
123
+ v-for="(
124
+ question, questionIndex
125
+ ) in existingFeedback.survey_question_answers"
58
126
  :key="questionIndex"
59
127
  fluid
60
128
  >
61
- <v-container
62
- v-if="question.type === 'likert'"
63
- class="pa-0 ma-0"
64
- >
129
+ <div v-if="question.type === 'likert'">
65
130
  <FeedbackQuestionLikert
66
- v-model="chosenPreset[questionIndex]"
131
+ v-model="
132
+ existingFeedback.survey_question_answers[
133
+ questionIndex
134
+ ]
135
+ "
67
136
  ></FeedbackQuestionLikert>
68
- </v-container>
69
- <v-container
70
- v-if="question.type === 'true_false'"
71
- class="pa-0 ma-0"
72
- >
137
+ </div>
138
+ <div v-if="question.type === 'true_false'">
73
139
  <FeedbackQuestionTrueFalse
74
- v-model="chosenPreset[questionIndex]"
140
+ v-model="
141
+ existingFeedback.survey_question_answers[
142
+ questionIndex
143
+ ]
144
+ "
75
145
  ></FeedbackQuestionTrueFalse>
76
- </v-container>
77
- <v-container
78
- v-if="question.type === 'open_response'"
79
- class="pa-0 ma-0"
80
- >
146
+ </div>
147
+ <div v-if="question.type === 'open_response'">
81
148
  <FeedbackQuestionOpenResponse
82
- v-model="chosenPreset[questionIndex]"
149
+ v-model="
150
+ existingFeedback.survey_question_answers[
151
+ questionIndex
152
+ ]
153
+ "
154
+ :feedbackExist="true"
83
155
  ></FeedbackQuestionOpenResponse>
84
- </v-container>
156
+ </div>
85
157
  </v-container>
86
- </div>
87
- </v-form>
88
- <v-row
89
- class="d-flex justify-end align-center"
90
- v-if="render && !doesFeedbackExist"
91
- >
92
- <v-btn
93
- color="primary"
94
- outlined
95
- elevation="0"
96
- class="text-center mt-4 mr-2"
97
- @click="onReset()"
98
- >{{
99
- $t('windward.core.components.content.blocks.feedback.reset')
100
- }}</v-btn
101
- >
102
- <v-btn
103
- elevation="0"
104
- color="success"
105
- class="text-center mt-4"
106
- @click="validate"
107
- >{{
108
- $t('windward.core.components.content.blocks.feedback.save')
109
- }}</v-btn
110
- >
111
- </v-row>
112
- <v-form
113
- ref="form"
114
- v-model="valid"
115
- disabled
116
- lazy-validation
117
- v-if="doesFeedbackExist"
118
- >
119
- <v-container
120
- v-for="(
121
- question, questionIndex
122
- ) in existingFeedback.survey_question_answers"
123
- :key="questionIndex"
124
- fluid
125
- >
126
- <div v-if="question.type === 'likert'">
127
- <FeedbackQuestionLikert
128
- v-model="
129
- existingFeedback.survey_question_answers[
130
- questionIndex
131
- ]
132
- "
133
- ></FeedbackQuestionLikert>
134
- </div>
135
- <div v-if="question.type === 'true_false'">
136
- <FeedbackQuestionTrueFalse
137
- v-model="
138
- existingFeedback.survey_question_answers[
139
- questionIndex
140
- ]
141
- "
142
- ></FeedbackQuestionTrueFalse>
143
- </div>
144
- <div v-if="question.type === 'open_response'">
145
- <FeedbackQuestionOpenResponse
146
- v-model="
147
- existingFeedback.survey_question_answers[
148
- questionIndex
149
- ]
150
- "
151
- :feedbackExist="true"
152
- ></FeedbackQuestionOpenResponse>
153
- </div>
154
- </v-container>
155
- </v-form>
156
- </v-container>
158
+ </v-form>
159
+ </v-card>
160
+ </div>
157
161
  </template>
158
162
 
159
163
  <script>
160
164
  import _ from 'lodash'
161
165
  import { mapGetters } from 'vuex'
162
- import FeedbackQuestionLikert from './FeedbackTemplates/FeedbackQuestionLikert.vue'
163
- import FeedbackQuestionOpenResponse from './FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
164
- import FeedbackQuestionTrueFalse from './FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
166
+ import SurveyResult from '../../../models/SurveyResult'
167
+ import FeedbackQuestionLikert from './Feedback/FeedbackTemplates/FeedbackQuestionLikert.vue'
168
+ import FeedbackQuestionOpenResponse from './Feedback/FeedbackTemplates/FeedbackQuestionOpenResponse.vue'
169
+ import FeedbackQuestionTrueFalse from './Feedback/FeedbackTemplates/FeedbackQuestionTrueFalse.vue'
165
170
  import Uuid from '~/helpers/Uuid'
166
171
 
167
- import SurveyResult from '../../../models/SurveyResult'
168
172
  import Enrollment from '~/models/Enrollment'
169
173
  import ContentBlock from '~/models/ContentBlock'
170
174
  import Crypto from '~/helpers/Crypto'
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-container class="pa-0">
2
+ <div :class="blockDirectionClasses" :dir="blockTextDirection">
3
3
  <h2
4
4
  v-if="
5
5
  block.metadata.config.title &&
@@ -51,7 +51,7 @@
51
51
  "
52
52
  :value="resolvedItemAssets"
53
53
  ></FileLinks>
54
- </v-container>
54
+ </div>
55
55
  </template>
56
56
 
57
57
  <script>
@@ -1,9 +1,56 @@
1
1
  <template>
2
2
  <div>
3
+ <v-container
4
+ v-if="
5
+ block.metadata.config.title ||
6
+ block.metadata.config.instructions
7
+ "
8
+ class="pa-0"
9
+ >
10
+ <h2
11
+ v-if="
12
+ block.metadata.config.title &&
13
+ block.metadata.config.display_title
14
+ "
15
+ tabindex="0"
16
+ >
17
+ {{ block.metadata.config.title }}
18
+ </h2>
19
+
20
+ <p
21
+ v-if="block.metadata.config.instructions"
22
+ tabindex="0"
23
+ class="pt-3"
24
+ >
25
+ {{ block.metadata.config.instructions }}
26
+ </p>
27
+ </v-container>
3
28
  <ImageAssetViewer
29
+ v-if="block.metadata.config.asset"
4
30
  v-model="block.metadata.config"
5
31
  :assets="block.assets"
6
32
  ></ImageAssetViewer>
33
+ <v-alert v-else type="warning">{{
34
+ $t('windward.core.components.settings.image.no_image')
35
+ }}</v-alert>
36
+
37
+ <!-- Visible image description for translated courses only -->
38
+ <!-- aria-hidden prevents screen readers from reading this twice (already read via aria-describedby on the image) -->
39
+ <div
40
+ v-if="isTranslatedBlock && imageDescription"
41
+ class="image-description mt-3 pa-3"
42
+ aria-hidden="true"
43
+ >
44
+ <h4
45
+ class="image-description__heading text-subtitle-2 font-weight-bold mb-1"
46
+ >
47
+ {{ $t('shared.file.image.description') }}
48
+ </h4>
49
+ <div
50
+ class="image-description__text text-body-2"
51
+ v-html="imageDescription"
52
+ ></div>
53
+ </div>
7
54
  </div>
8
55
  </template>
9
56
 
@@ -11,6 +58,7 @@
11
58
  import _ from 'lodash'
12
59
  import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
13
60
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
61
+ import Uuid from '~/helpers/Uuid'
14
62
 
15
63
  export default {
16
64
  name: 'ContentBlockImage',
@@ -21,6 +69,47 @@ export default {
21
69
  data() {
22
70
  return {}
23
71
  },
72
+ computed: {
73
+ /**
74
+ * Check if this block is from a translated course
75
+ * Translated blocks have metadata.translation object
76
+ */
77
+ isTranslatedBlock() {
78
+ return !!_.get(this.block, 'metadata.translation', null)
79
+ },
80
+ /**
81
+ * Get the file asset for this image
82
+ * Uses resolveAsset from BaseContentBlock which wraps FileAssetHelper
83
+ */
84
+ fileAsset() {
85
+ const config = _.get(this.block, 'metadata.config', {})
86
+ return this.resolveAsset(config.asset)
87
+ },
88
+ /**
89
+ * Get the image description (ariaDescribedBy) from config or asset
90
+ * Checks multiple sources in order of priority
91
+ */
92
+ imageDescription() {
93
+ const config = _.get(this.block, 'metadata.config', {})
94
+
95
+ // If not inheriting from global, use the config value
96
+ if (!config.inherit) {
97
+ return config.ariaDescribedBy || ''
98
+ }
99
+
100
+ // If inheriting, try to get from file asset (locale-aware props first)
101
+ return (
102
+ _.get(this.fileAsset, 'aria_describedby', null) ||
103
+ _.get(this.fileAsset, 'props.aria_describedby', null) ||
104
+ _.get(
105
+ this.fileAsset,
106
+ 'asset.metadata.props.aria_describedby',
107
+ null
108
+ ) ||
109
+ ''
110
+ )
111
+ },
112
+ },
24
113
  beforeMount() {
25
114
  if (_.isEmpty(this.block.metadata.config)) {
26
115
  this.block.metadata.config = {}
@@ -37,6 +126,61 @@ export default {
37
126
  if (_.isEmpty(this.block.metadata.config.ariaDescribedBy)) {
38
127
  this.block.metadata.config.ariaDescribedBy = ''
39
128
  }
129
+ // If the block is brand new and the title is missing then pre-set the word 'Image' to it
130
+ if (
131
+ !Uuid.test(this.block.id) &&
132
+ _.isEmpty(this.block.metadata.config.title)
133
+ ) {
134
+ this.block.metadata.config.title = this.$t(
135
+ 'windward.core.shared.content_blocks.title.image'
136
+ )
137
+ } else if (_.isEmpty(this.block.metadata.config.title)) {
138
+ // Otherwise make sure the title key at least exists
139
+ this.block.metadata.config.title = ''
140
+ }
141
+ if (!_.isBoolean(this.block.metadata.config.display_title)) {
142
+ this.$set(this.block.metadata.config, 'display_title', true)
143
+ }
144
+ if (_.isEmpty(this.block.metadata.config.instructions)) {
145
+ this.block.metadata.config.instructions = ''
146
+ }
40
147
  },
41
148
  }
42
149
  </script>
150
+
151
+ <style lang="scss" scoped>
152
+ .image-description {
153
+ background-color: rgba(var(--v-theme-surface-variant), 0.5);
154
+ border-radius: 4px;
155
+ border-left: 3px solid rgba(var(--v-theme-primary), 0.7);
156
+
157
+ &__heading {
158
+ color: rgba(var(--v-theme-on-surface), 0.87);
159
+ }
160
+
161
+ &__text {
162
+ color: rgba(var(--v-theme-on-surface), 0.7);
163
+
164
+ // Deep selector to style HTML content rendered via v-html
165
+ ::v-deep {
166
+ p {
167
+ margin-bottom: 0.5rem;
168
+
169
+ &:last-child {
170
+ margin-bottom: 0;
171
+ }
172
+ }
173
+
174
+ ul,
175
+ ol {
176
+ margin: 0.5rem 0;
177
+ padding-left: 1.5rem;
178
+ }
179
+
180
+ li {
181
+ margin-bottom: 0.25rem;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ </style>