@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.
- package/.eslintrc.js +5 -1
- package/.prettierrc +3 -2
- package/CHANGELOG.md +3 -0
- package/components/Content/Blocks/ClickableIcons.vue +3 -9
- package/components/Content/Blocks/GenerateAIQuestionButton.vue +85 -18
- package/components/Content/Blocks/Image.vue +7 -182
- package/components/Content/Blocks/Tab.vue +10 -0
- package/components/Navigation/Items/GlossaryNav.vue +25 -10
- package/components/Settings/ImageSettings.vue +12 -240
- package/components/Settings/TabSettings.vue +17 -1
- package/components/Settings/TextEditorSettings.vue +17 -15
- package/components/utils/ContentViewer.vue +0 -3
- package/components/utils/FillInBlank/FillInBlankInput.vue +29 -47
- package/components/utils/TinyMCEWrapper.vue +37 -79
- package/components/utils/glossary/CourseGlossary.vue +5 -3
- package/components/utils/glossary/GlossaryToolTip.vue +8 -1
- package/helpers/GlossaryHelper.ts +38 -18
- package/helpers/tinymce/WindwardPlugins.ts +166 -118
- package/i18n/en-US/components/content/blocks/generate_questions.ts +3 -2
- package/i18n/en-US/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
- package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +1 -0
- package/i18n/es-ES/components/content/blocks/generate_questions.ts +2 -1
- package/i18n/es-ES/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
- package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +3 -2
- package/i18n/sv-SE/components/content/blocks/generate_questions.ts +2 -1
- package/i18n/sv-SE/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
- package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +2 -0
- package/package.json +2 -1
- package/pages/glossary.vue +1 -1
- package/stylelint.config.js +14 -0
- package/test/Components/Content/Blocks/OpenResponseCollate.spec.js +3 -3
- package/test/Components/Settings/TabSettings.spec.js +2 -2
- package/test/__mocks__/contentBlockMock.js +20 -0
- 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: [
|
|
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
package/CHANGELOG.md
CHANGED
|
@@ -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
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
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
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
3
|
-
<
|
|
4
|
-
<v-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
},
|