@windward/core 0.8.0 → 0.9.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 (34) hide show
  1. package/.eslintrc.js +5 -1
  2. package/.prettierrc +3 -2
  3. package/CHANGELOG.md +3 -0
  4. package/components/Content/Blocks/ClickableIcons.vue +3 -9
  5. package/components/Content/Blocks/GenerateAIQuestionButton.vue +85 -18
  6. package/components/Content/Blocks/Image.vue +7 -182
  7. package/components/Content/Blocks/Tab.vue +10 -0
  8. package/components/Navigation/Items/GlossaryNav.vue +25 -10
  9. package/components/Settings/ImageSettings.vue +12 -240
  10. package/components/Settings/TabSettings.vue +17 -1
  11. package/components/Settings/TextEditorSettings.vue +17 -15
  12. package/components/utils/ContentViewer.vue +0 -3
  13. package/components/utils/FillInBlank/FillInBlankInput.vue +29 -47
  14. package/components/utils/TinyMCEWrapper.vue +37 -79
  15. package/components/utils/glossary/CourseGlossary.vue +5 -3
  16. package/components/utils/glossary/GlossaryToolTip.vue +8 -1
  17. package/helpers/GlossaryHelper.ts +38 -18
  18. package/helpers/tinymce/WindwardPlugins.ts +166 -118
  19. package/i18n/en-US/components/content/blocks/generate_questions.ts +3 -2
  20. package/i18n/en-US/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  21. package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +1 -0
  22. package/i18n/es-ES/components/content/blocks/generate_questions.ts +2 -1
  23. package/i18n/es-ES/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  24. package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +3 -2
  25. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +2 -1
  26. package/i18n/sv-SE/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  27. package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +2 -0
  28. package/package.json +2 -1
  29. package/pages/glossary.vue +1 -1
  30. package/stylelint.config.js +14 -0
  31. package/test/Components/Content/Blocks/OpenResponseCollate.spec.js +3 -3
  32. package/test/Components/Settings/TabSettings.spec.js +2 -2
  33. package/test/__mocks__/contentBlockMock.js +20 -0
  34. package/test/helpers/GlossaryHelper.spec.js +17 -0
package/.eslintrc.js CHANGED
@@ -4,7 +4,11 @@ module.exports = {
4
4
  browser: true,
5
5
  node: true,
6
6
  },
7
- extends: ['plugin:nuxt/recommended', 'plugin:prettier/recommended'],
7
+ extends: [
8
+ '@nuxtjs/eslint-config-typescript',
9
+ 'plugin:prettier/recommended',
10
+ 'plugin:nuxt/recommended',
11
+ ],
8
12
  plugins: [],
9
13
  // add your custom rules here
10
14
  rules: {},
package/.prettierrc CHANGED
@@ -1,4 +1,5 @@
1
1
  {
2
- "semi": false,
3
- "singleQuote": true
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "trailingComma": "es5"
4
5
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ### Hotfix [0.9.1] created - 2024-12-10
4
+
5
+
3
6
  ## Release [0.7.0] - 2024-08-29
4
7
 
5
8
  * Version bump to 0.7.0
@@ -4,7 +4,6 @@
4
4
  <h2 v-if="block.metadata.config.title" tabindex="0">
5
5
  {{ block.metadata.config.title }}
6
6
  </h2>
7
-
8
7
  <p
9
8
  v-if="block.metadata.config.description"
10
9
  tabindex="0"
@@ -12,14 +11,6 @@
12
11
  >
13
12
  {{ block.metadata.config.description }}
14
13
  </p>
15
-
16
- <p>
17
- {{
18
- $t(
19
- 'windward.core.components.settings.clickable_icon.information'
20
- )
21
- }}
22
- </p>
23
14
  </v-container>
24
15
  <v-container class="pa-0">
25
16
  <v-row
@@ -100,6 +91,9 @@ export default {
100
91
  )
101
92
  if (_.isEmpty(this.block.metadata.config.items)) {
102
93
  this.block.metadata.config.items = []
94
+ this.block.metadata.config.description = this.$t(
95
+ 'windward.core.components.settings.clickable_icon.information'
96
+ )
103
97
  }
104
98
  if (_.isEmpty(this.block.metadata.config.title)) {
105
99
  this.block.metadata.config.title = ''
@@ -1,21 +1,50 @@
1
1
  <template>
2
- <v-btn
3
- elevation="0"
4
- color="secondary"
5
- @click="generateAIQuestion"
6
- :loading="isLoading"
7
- :disabled="isLoading"
8
- >
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
- }}
15
- <template v-slot:loader>
16
- <v-progress-circular indeterminate size="23"></v-progress-circular>
17
- </template>
18
- </v-btn>
2
+ <div>
3
+ <v-row>
4
+ <v-col cols="12" class="d-flex justify-end pb-1">
5
+ <v-btn
6
+ elevation="0"
7
+ color="secondary"
8
+ class="mb-1 btn-selector"
9
+ :loading="isLoading"
10
+ :disabled="isLoading"
11
+ @click="generateAIQuestion"
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
+ }}
21
+ <template v-slot:loader>
22
+ <v-progress-circular
23
+ indeterminate
24
+ size="23"
25
+ ></v-progress-circular>
26
+ </template>
27
+ </v-btn>
28
+ </v-col>
29
+ <v-col cols="12" class="d-flex justify-end pt-0">
30
+ <v-select
31
+ v-model="selectedContent"
32
+ :items="flattenedContent"
33
+ class="btn-selector"
34
+ outlined
35
+ hide-details
36
+ dense
37
+ :label="
38
+ $t(
39
+ 'windward.core.components.content.blocks.generate_questions.selected_pages'
40
+ )
41
+ "
42
+ item-text="content.name"
43
+ return-object
44
+ ></v-select>
45
+ </v-col>
46
+ </v-row>
47
+ </div>
19
48
  </template>
20
49
 
21
50
  <script>
@@ -23,6 +52,9 @@ import AssessmentQuestion from '~/models/AssessmentQuestion'
23
52
  import Course from '~/models/Course'
24
53
  import Assessment from '~/models/Assessment'
25
54
  import Content from '~/models/Content'
55
+ import { mapGetters } from 'vuex'
56
+ import _ from 'lodash'
57
+ import Crypto from '~/helpers/Crypto'
26
58
 
27
59
  export default {
28
60
  name: 'GenerateAIQuestionButton',
@@ -35,15 +67,45 @@ export default {
35
67
  data() {
36
68
  return {
37
69
  isLoading: false,
70
+ selectedContent: '',
38
71
  }
39
72
  },
73
+ computed: {
74
+ ...mapGetters({
75
+ contentTree: 'content/getTree',
76
+ }),
77
+ flattenedContent() {
78
+ let cloneContentTree = _.cloneDeep(this.contentTree)
79
+ const homepage = this.$ContentService.getHomepage()
80
+ if (!_.isEmpty(homepage)) {
81
+ cloneContentTree.unshift(homepage)
82
+ }
83
+ let fullTree = []
84
+ // flatten content tree to get nested children pages
85
+ cloneContentTree.forEach((content) => {
86
+ fullTree.push(content)
87
+ if (content.children.length > 0) {
88
+ fullTree = fullTree.concat(_.flatten(content.children))
89
+ }
90
+ })
91
+ //
92
+ if (_.isEmpty(this.selectedContent)) {
93
+ // returns array so hold here to pluck out below
94
+ const currentPage = fullTree.filter(
95
+ (contentPage) => contentPage.id === this.content.id
96
+ )
97
+ this.selectedContent = currentPage[0] ? currentPage[0] : ''
98
+ }
99
+ return fullTree
100
+ },
101
+ },
40
102
  methods: {
41
103
  async generateAIQuestion() {
42
104
  this.isLoading = true
43
105
  try {
44
106
  const response = await AssessmentQuestion.custom(
45
107
  new Course(this.course),
46
- new Content(this.content),
108
+ new Content(this.selectedContent),
47
109
  new Assessment({ id: this.block.id }),
48
110
  new AssessmentQuestion(),
49
111
  `suggest/${this.questionType}`
@@ -67,3 +129,8 @@ export default {
67
129
  },
68
130
  }
69
131
  </script>
132
+ <style scoped>
133
+ .btn-selector {
134
+ width: 100%;
135
+ }
136
+ </style>
@@ -1,99 +1,25 @@
1
1
  <template>
2
2
  <div>
3
- <v-container class="pa-0 ma-0">
4
- <div v-if="!imageUrl" class="img-holder">
5
- <v-skeleton-loader
6
- height="300px"
7
- :elevation="2"
8
- type="image"
9
- ></v-skeleton-loader>
10
-
11
- <div class="no-source-overlay">
12
- <v-icon x-large>mdi-file-question</v-icon><br />
13
- {{
14
- $t(
15
- 'windward.core.components.content.blocks.image.no_image_url'
16
- )
17
- }}
18
- </div>
19
- </div>
20
- <v-responsive :aspect-ratio="aspectRatio">
21
- <v-img
22
- v-if="imageUrl"
23
- :alt="altText"
24
- :aria-describedby="describedByText"
25
- :class="imageClass"
26
- :src="imageUrl"
27
- contain
28
- @click="onHandleModal"
29
- >
30
- <template #placeholder>
31
- <v-skeleton-loader
32
- type="image, image, list-item-avatar"
33
- height="100%"
34
- width="100%"
35
- ></v-skeleton-loader>
36
- </template>
37
- </v-img>
38
- </v-responsive>
39
- <TextViewer
40
- v-if="describedById"
41
- v-model="describedByText"
42
- :id="describedById"
43
- class="sr-only"
44
- ></TextViewer>
45
- </v-container>
46
- <DialogBox v-model="dialog" persistent :trigger="false">
47
- <template #title></template>
48
- <template #form="{ on, attrs }">
49
- <v-responsive
50
- :aspect-ratio="aspectRatio"
51
- v-bind="attrs"
52
- v-on="on"
53
- >
54
- <v-img
55
- v-if="imageUrl"
56
- :alt="altText"
57
- :aria-describedby="describedByText"
58
- :class="imageClass"
59
- :src="imageUrl"
60
- contain
61
- @click="onHandleModal"
62
- >
63
- <template #placeholder>
64
- <v-skeleton-loader
65
- type="image, image, list-item-avatar"
66
- height="100%"
67
- width="100%"
68
- ></v-skeleton-loader>
69
- </template>
70
- </v-img>
71
- </v-responsive>
72
- </template>
73
- </DialogBox>
3
+ <ImageAssetViewer
4
+ v-model="block.metadata.config"
5
+ :assets="block.assets"
6
+ ></ImageAssetViewer>
74
7
  </div>
75
8
  </template>
76
9
 
77
10
  <script>
78
11
  import _ from 'lodash'
79
- import DialogBox from '~/components/Core/DialogBox.vue'
80
- import TextViewer from '~/components/Text/TextViewer'
81
- import Crypto from '~/helpers/Crypto'
12
+ import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
82
13
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
83
14
 
84
15
  export default {
85
16
  name: 'ContentBlockImage',
86
17
  extends: BaseContentBlock,
87
18
  components: {
88
- DialogBox,
89
- TextViewer,
19
+ ImageAssetViewer,
90
20
  },
91
21
  data() {
92
- return {
93
- id: 'image_' + Crypto.id(),
94
- aspectRatio: undefined,
95
- dialog: false,
96
- }
22
+ return {}
97
23
  },
98
24
  beforeMount() {
99
25
  if (_.isEmpty(this.block.metadata.config)) {
@@ -112,106 +38,5 @@ export default {
112
38
  this.block.metadata.config.ariaDescribedBy = ''
113
39
  }
114
40
  },
115
- computed: {
116
- describedById() {
117
- // If there's a described by
118
- if (this.describedByText) {
119
- return this.id
120
- } else {
121
- return null
122
- }
123
- },
124
- altText() {
125
- // Get the asset info first and fallback to the block metadata if inherit is set to false
126
- if (_.get(this.block.metadata, 'config.inherit', true)) {
127
- return _.get(this.fileAsset, 'asset.metadata.props.alt', null)
128
- } else {
129
- return _.get(this.block.metadata, 'config.alt', null)
130
- }
131
- },
132
- describedByText() {
133
- // Get the asset info first and fallback to the block metadata if inherit is set to false
134
- if (_.get(this.block.metadata, 'config.inherit', true)) {
135
- return _.get(
136
- this.fileAsset,
137
- 'asset.metadata.props.aria_describedby',
138
- null
139
- )
140
- } else {
141
- return _.get(
142
- this.block.metadata,
143
- 'config.ariaDescribedBy',
144
- null
145
- )
146
- }
147
- },
148
- fileAsset() {
149
- return this.resolveAsset(
150
- _.get(this.block, 'metadata.config.asset', null)
151
- )
152
- },
153
- imageUrl() {
154
- // Get the image public url and fallback to the block body
155
- return _.get(this.fileAsset, 'asset.public_url', this.block.body)
156
- },
157
- imageClass() {
158
- let imageClass = ''
159
- // change cursor to pointer for images that open in larger modal
160
- if (this.block.metadata.config.modal) {
161
- imageClass += ' container-pointer'
162
- }
163
- // If NOT hide background, inclide the extra class
164
- if (!_.get(this.block.metadata, 'config.hideBackground', false)) {
165
- imageClass += ' img-white'
166
- }
167
- return 'img-display' + imageClass
168
- },
169
- },
170
- watch: {
171
- value(newValue) {
172
- if (
173
- !_.isEmpty(newValue.metadata.config.height) &&
174
- !_.isEmpty(newValue.metadata.config.width)
175
- ) {
176
- this.aspectRatio =
177
- newValue.metadata.config.width +
178
- '/' +
179
- newValue.metadata.config.height
180
- } else {
181
- this.aspectRatio = undefined
182
- }
183
- },
184
- },
185
- methods: {
186
- onHandleModal() {
187
- if (this.block.metadata.config.modal) {
188
- this.dialog = true
189
- }
190
- },
191
- },
192
41
  }
193
42
  </script>
194
-
195
- <style lang="scss" scoped>
196
- .img-display {
197
- border-radius: 3px;
198
- }
199
- .img-holder {
200
- height: 300px;
201
- }
202
- .img-white {
203
- background: #fff;
204
- }
205
- .no-source-overlay {
206
- text-align: center;
207
- margin-top: -175px;
208
- }
209
- .container-pointer {
210
- cursor: pointer;
211
- }
212
- ::v-deep .v-skeleton-loader.v-skeleton-loader--is-loading {
213
- .v-skeleton-loader__image {
214
- height: 100%;
215
- }
216
- }
217
- </style>
@@ -47,6 +47,14 @@
47
47
  v-if="!render && tabContent.expand"
48
48
  v-model="tabContent.content"
49
49
  ></TextEditor>
50
+ <ImageAssetViewer
51
+ v-if="
52
+ tabContent.imageAsset &&
53
+ tabContent.imageAsset.asset
54
+ "
55
+ v-model="tabContent.imageAsset"
56
+ :assets="block.assets"
57
+ ></ImageAssetViewer>
50
58
  </v-container>
51
59
  </v-tab-item>
52
60
  </v-tabs>
@@ -58,6 +66,7 @@
58
66
  import _ from 'lodash'
59
67
  import TextEditor from '~/components/Text/TextEditor'
60
68
  import TextViewer from '~/components/Text/TextViewer'
69
+ import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
61
70
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
62
71
 
63
72
  export default {
@@ -65,6 +74,7 @@ export default {
65
74
  components: {
66
75
  TextEditor,
67
76
  TextViewer,
77
+ ImageAssetViewer,
68
78
  },
69
79
  extends: BaseContentBlock,
70
80
  beforeMount() {
@@ -1,14 +1,26 @@
1
1
  <template>
2
- <v-list-item :to="'/course/' + course.id + '/glossary'">
3
- <v-list-item-action>
4
- <v-icon>mdi-comment-text-multiple</v-icon>
5
- </v-list-item-action>
6
- <v-list-item-content>
7
- <v-list-item-title
8
- >{{ $t('windward.core.shared.menu.course_glossary') }}
9
- </v-list-item-title>
10
- </v-list-item-content>
11
- </v-list-item>
2
+ <v-tooltip right>
3
+ <template #activator="{ on, attrs }">
4
+ <v-list-item
5
+ :class="color"
6
+ v-on="on"
7
+ v-bind="attrs"
8
+ :to="'/course/' + course.id + '/glossary'"
9
+ >
10
+ <v-list-item-action>
11
+ <v-icon v-on="on" v-bind="attrs"
12
+ >mdi-comment-text-multiple</v-icon
13
+ >
14
+ </v-list-item-action>
15
+ <v-list-item-content>
16
+ <v-list-item-title
17
+ >{{ $t('windward.core.shared.menu.course_glossary') }}
18
+ </v-list-item-title>
19
+ </v-list-item-content>
20
+ </v-list-item>
21
+ </template>
22
+ <span>{{ $t('windward.core.shared.menu.course_glossary') }}</span>
23
+ </v-tooltip>
12
24
  </template>
13
25
 
14
26
  <script>
@@ -18,6 +30,9 @@ export default {
18
30
  name: 'GlossaryNav',
19
31
  layout: 'course',
20
32
  middleware: ['auth'],
33
+ props: {
34
+ color: { type: String, required: false, default: '' },
35
+ },
21
36
  data() {
22
37
  return {}
23
38
  },