@windward/core 0.0.7 → 0.0.9
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/components/Content/Blocks/ClickableIcons.vue +128 -40
- package/components/Content/Blocks/Image.vue +37 -10
- package/components/Content/Blocks/OpenResponse.vue +32 -10
- package/components/Content/Blocks/OpenResponseCollate.vue +69 -42
- package/components/Content/Blocks/ScenarioChoice.vue +262 -0
- package/components/Content/Blocks/Tab.vue +2 -7
- package/components/Settings/AccordionSettings.vue +97 -74
- package/components/Settings/ClickableIconsSettings.vue +101 -86
- package/components/Settings/ImageSettings.vue +10 -0
- package/components/Settings/OpenResponseCollateSettings.vue +9 -8
- package/components/Settings/ScenarioChoiceSettings.vue +329 -0
- package/components/Settings/TabSettings.vue +75 -61
- package/components/Settings/TextEditorSettings.vue +2 -3
- package/components/utils/FillInBlank/FillInBlankInput.vue +4 -1
- package/components/utils/TinyMCEWrapper.vue +11 -4
- package/components/utils/assets/tinymce/css/content.scss +1 -1
- package/helpers/FillInBlankHelper.ts +0 -2
- package/helpers/tinymce/plugin.ts +1 -1
- package/i18n/en-US/components/content/blocks/index.ts +2 -2
- package/i18n/en-US/components/content/blocks/open_response_collate.ts +1 -0
- package/i18n/en-US/components/content/blocks/scenario_choice.ts +5 -0
- package/i18n/en-US/components/settings/accordion.ts +5 -0
- package/i18n/en-US/components/settings/clickable_icon.ts +8 -0
- package/i18n/en-US/components/settings/image.ts +3 -1
- package/i18n/en-US/components/settings/index.ts +6 -0
- package/i18n/en-US/components/settings/scenario_choice.ts +19 -0
- package/i18n/en-US/components/settings/tab.ts +7 -0
- package/i18n/en-US/components/utils/FillInBlank/FillInBlankInput.ts +1 -1
- package/i18n/en-US/shared/content_blocks.ts +1 -0
- package/i18n/en-US/shared/settings.ts +2 -0
- package/i18n/es-ES/components/content/blocks/feedback.ts +2 -0
- package/i18n/es-ES/components/content/blocks/index.ts +2 -2
- package/i18n/es-ES/components/content/blocks/open_response_collate.ts +1 -0
- package/i18n/es-ES/components/content/blocks/scenario_choice.ts +6 -0
- package/i18n/es-ES/components/navigation/index.ts +2 -0
- package/i18n/es-ES/components/settings/accordion.ts +5 -0
- package/i18n/es-ES/components/settings/clickable_icon.ts +8 -0
- package/i18n/es-ES/components/settings/image.ts +3 -1
- package/i18n/es-ES/components/settings/index.ts +6 -0
- package/i18n/es-ES/components/settings/scenario_choice.ts +19 -0
- package/i18n/es-ES/components/{content/blocks → settings}/tab.ts +3 -0
- package/i18n/es-ES/components/utils/FillInBlank/FillInBlankInput.ts +13 -0
- package/i18n/es-ES/components/utils/FillInBlank/FillInTheBlanksManager.ts +11 -0
- package/i18n/es-ES/components/utils/FillInBlank/index.ts +6 -0
- package/i18n/es-ES/components/utils/index.ts +2 -0
- package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +1 -0
- package/i18n/es-ES/shared/content_blocks.ts +1 -0
- package/i18n/es-ES/shared/menu.ts +1 -0
- package/i18n/es-ES/shared/settings.ts +2 -0
- package/i18n/index.ts +11 -0
- package/i18n/sv-SE/components/content/blocks/feedback.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/index.ts +2 -2
- package/i18n/sv-SE/components/content/blocks/open_response_collate.ts +1 -0
- package/i18n/sv-SE/components/content/blocks/scenario_choice.ts +5 -0
- package/i18n/sv-SE/components/navigation/index.ts +2 -0
- package/i18n/sv-SE/components/settings/accordion.ts +5 -0
- package/i18n/sv-SE/components/settings/clickable_icon.ts +8 -0
- package/i18n/sv-SE/components/settings/image.ts +3 -1
- package/i18n/sv-SE/components/settings/index.ts +6 -0
- package/i18n/sv-SE/components/settings/scenario_choice.ts +19 -0
- package/i18n/sv-SE/components/{content/blocks → settings}/tab.ts +3 -0
- package/i18n/sv-SE/components/utils/FillInBlank/FillInBlankInput.ts +13 -0
- package/i18n/sv-SE/components/utils/FillInBlank/FillInTheBlanksManager.ts +11 -0
- package/i18n/sv-SE/components/utils/FillInBlank/index.ts +6 -0
- package/i18n/sv-SE/components/utils/index.ts +2 -0
- package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +1 -0
- package/i18n/sv-SE/shared/content_blocks.ts +1 -0
- package/i18n/sv-SE/shared/menu.ts +1 -0
- package/i18n/sv-SE/shared/settings.ts +2 -0
- package/package.json +2 -1
- package/plugin.js +24 -5
- package/test/Components/Content/Blocks/ScenarioChoice.spec.js +21 -0
- package/test/Components/Settings/ClickableIconsSettings.spec.js +1 -1
- package/test/Components/Settings/ScenarioChoiceSettings.spec.js +20 -0
- package/test/Feature/LocaleKeys.spec.js +9 -0
- package/test/__mocks__/componentsMock.js +24 -0
- package/test/__mocks__/helpersMock.js +3 -0
- package/test/__mocks__/modelMock.js +4 -0
- package/test/locales.js +95 -0
- package/i18n/en-US/components/content/blocks/tab.ts +0 -4
|
@@ -1,42 +1,60 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
3
|
<v-container>
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
</
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
}}
|
|
17
|
-
</p>
|
|
18
|
-
</v-row>
|
|
4
|
+
<h3>{{ block.metadata.config.title }}</h3>
|
|
5
|
+
|
|
6
|
+
<h4>{{ block.metadata.config.description }}</h4>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
{{
|
|
10
|
+
$t(
|
|
11
|
+
'windward.core.components.settings.clickable_icon.information'
|
|
12
|
+
)
|
|
13
|
+
}}
|
|
14
|
+
</p>
|
|
19
15
|
</v-container>
|
|
20
16
|
<v-container>
|
|
21
17
|
<v-row
|
|
22
18
|
v-for="(item, itemIndex) in block.metadata.config.items"
|
|
23
19
|
:key="itemIndex"
|
|
24
20
|
no-gutters
|
|
21
|
+
:class="rowClass(itemIndex)"
|
|
25
22
|
>
|
|
26
23
|
<v-col cols="2">
|
|
27
24
|
<v-btn
|
|
28
25
|
class="pt-8 pb-8 outlined-button mb-4"
|
|
29
|
-
:color="itemColor(
|
|
26
|
+
:color="itemColor(itemIndex)"
|
|
27
|
+
:fab="block.metadata.config.display.round_icon"
|
|
30
28
|
x-large
|
|
31
29
|
outlined
|
|
32
30
|
@click="item.active = !item.active"
|
|
33
31
|
>
|
|
34
|
-
<v-icon
|
|
32
|
+
<v-icon v-if="isIcon(item.icon)" class="black--text">{{
|
|
33
|
+
item.icon
|
|
34
|
+
}}</v-icon>
|
|
35
|
+
<span v-else :class="iconClass + ' black--text'">{{
|
|
36
|
+
decode(item.icon)
|
|
37
|
+
}}</span>
|
|
35
38
|
</v-btn>
|
|
36
39
|
</v-col>
|
|
37
|
-
<v-col cols="10"
|
|
38
|
-
<h4
|
|
39
|
-
|
|
40
|
+
<v-col cols="10">
|
|
41
|
+
<h4
|
|
42
|
+
v-if="
|
|
43
|
+
block.metadata.config.display.show_title ||
|
|
44
|
+
item.active
|
|
45
|
+
"
|
|
46
|
+
class="mt-4"
|
|
47
|
+
role="button"
|
|
48
|
+
@click="item.active = !item.active"
|
|
49
|
+
>
|
|
50
|
+
{{ item.title }}
|
|
51
|
+
</h4>
|
|
52
|
+
<v-expand-transition>
|
|
53
|
+
<div v-if="item.active">
|
|
54
|
+
<v-divider light class="my-4" />
|
|
55
|
+
<TextViewer v-model="item.body"></TextViewer>
|
|
56
|
+
</div>
|
|
57
|
+
</v-expand-transition>
|
|
40
58
|
</v-col>
|
|
41
59
|
</v-row>
|
|
42
60
|
</v-container>
|
|
@@ -44,6 +62,7 @@
|
|
|
44
62
|
</template>
|
|
45
63
|
<script>
|
|
46
64
|
import _ from 'lodash'
|
|
65
|
+
import he from 'he'
|
|
47
66
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
48
67
|
import TextViewer from '~/components/Text/TextViewer'
|
|
49
68
|
|
|
@@ -53,13 +72,77 @@ export default {
|
|
|
53
72
|
TextViewer,
|
|
54
73
|
},
|
|
55
74
|
extends: BaseContentBlock,
|
|
75
|
+
data() {
|
|
76
|
+
return {}
|
|
77
|
+
},
|
|
56
78
|
computed: {
|
|
79
|
+
decode() {
|
|
80
|
+
return (str) => {
|
|
81
|
+
return he.decode(str || '')
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
rowClass() {
|
|
85
|
+
return (itemIndex) => {
|
|
86
|
+
let classes = 'option-container mb-4 pa-1'
|
|
87
|
+
// If show background is enabled and the item has a color
|
|
88
|
+
// Otherwise we do NOT want to apply the `black--text` class since there's no color
|
|
89
|
+
// And things will look bad in dark mode
|
|
90
|
+
if (
|
|
91
|
+
_.get(
|
|
92
|
+
this.block,
|
|
93
|
+
'metadata.config.display.show_background',
|
|
94
|
+
false
|
|
95
|
+
) &&
|
|
96
|
+
this.itemColor(itemIndex)
|
|
97
|
+
) {
|
|
98
|
+
classes += ' ' + this.itemColor(itemIndex) + ' black--text'
|
|
99
|
+
}
|
|
100
|
+
return classes
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
iconClass() {
|
|
104
|
+
let classes = 'text-icon'
|
|
105
|
+
if (
|
|
106
|
+
_.get(this.block, 'metadata.config.display.italic_icon', false)
|
|
107
|
+
) {
|
|
108
|
+
classes += ' font-italic'
|
|
109
|
+
}
|
|
110
|
+
return classes
|
|
111
|
+
},
|
|
57
112
|
itemColor() {
|
|
58
|
-
return (
|
|
59
|
-
|
|
60
|
-
|
|
113
|
+
return (itemIndex) => {
|
|
114
|
+
// If autocolor is enabled then calculate the color on the index
|
|
115
|
+
if (this.block.metadata.config.display.autocolor) {
|
|
116
|
+
const colors = [
|
|
117
|
+
'light-blue lighten-4',
|
|
118
|
+
'light-green lighten-4',
|
|
119
|
+
'yellow lighten-4',
|
|
120
|
+
'orange lighten-3',
|
|
121
|
+
'red lighten-3',
|
|
122
|
+
'deep-purple lighten-3',
|
|
123
|
+
]
|
|
124
|
+
let colorIndex = itemIndex
|
|
125
|
+
// If we exceed the above list of colors loop back around to the beginning
|
|
126
|
+
if (colorIndex >= colors.length) {
|
|
127
|
+
colorIndex =
|
|
128
|
+
colorIndex -
|
|
129
|
+
Math.floor(colorIndex / colors.length) *
|
|
130
|
+
colors.length
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return colors[colorIndex]
|
|
61
134
|
} else {
|
|
62
|
-
|
|
135
|
+
// Otherwise get the predefined color
|
|
136
|
+
const color = _.get(
|
|
137
|
+
this.block,
|
|
138
|
+
'metadata.config.items[' + itemIndex + '].color',
|
|
139
|
+
''
|
|
140
|
+
)
|
|
141
|
+
if (_.isObject(color)) {
|
|
142
|
+
return _.get(color, 'class', '')
|
|
143
|
+
} else {
|
|
144
|
+
return color
|
|
145
|
+
}
|
|
63
146
|
}
|
|
64
147
|
}
|
|
65
148
|
},
|
|
@@ -76,32 +159,37 @@ export default {
|
|
|
76
159
|
if (_.isEmpty(this.block.metadata.config.description)) {
|
|
77
160
|
this.block.metadata.config.description = ''
|
|
78
161
|
}
|
|
79
|
-
if (this.block.metadata.config.
|
|
80
|
-
this.block.metadata.config.
|
|
81
|
-
|
|
82
|
-
|
|
162
|
+
if (_.isEmpty(this.block.metadata.config.display)) {
|
|
163
|
+
this.block.metadata.config.display = {
|
|
164
|
+
show_title: false,
|
|
165
|
+
show_background: false,
|
|
166
|
+
round_icon: false,
|
|
167
|
+
italic_icon: false,
|
|
168
|
+
autocolor: true,
|
|
169
|
+
}
|
|
83
170
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
title: 'Title',
|
|
89
|
-
displayText: false,
|
|
171
|
+
if (this.block.metadata.config.items.length) {
|
|
172
|
+
for (const index in this.block.metadata.config.items) {
|
|
173
|
+
this.block.metadata.config.items[index].active = false
|
|
174
|
+
}
|
|
90
175
|
}
|
|
91
176
|
},
|
|
92
177
|
methods: {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
txt.innerHTML = str
|
|
97
|
-
|
|
98
|
-
return txt.value
|
|
178
|
+
isIcon(str) {
|
|
179
|
+
return str && _.isString(str) && str.indexOf('mdi-') === 0
|
|
99
180
|
},
|
|
100
181
|
},
|
|
101
182
|
}
|
|
102
183
|
</script>
|
|
103
184
|
<style scoped>
|
|
185
|
+
.option-container {
|
|
186
|
+
border-radius: 1rem;
|
|
187
|
+
}
|
|
104
188
|
.outlined-button {
|
|
105
189
|
border-width: 4px;
|
|
190
|
+
background: #fff;
|
|
191
|
+
}
|
|
192
|
+
.text-icon {
|
|
193
|
+
font-size: 1.5rem;
|
|
106
194
|
}
|
|
107
195
|
</style>
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<
|
|
4
|
-
<v-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
></v-
|
|
9
|
-
|
|
3
|
+
<div v-if="!block.body" class="img-holder">
|
|
4
|
+
<v-skeleton-loader
|
|
5
|
+
height="300px"
|
|
6
|
+
:elevation="2"
|
|
7
|
+
type="image"
|
|
8
|
+
></v-skeleton-loader>
|
|
9
|
+
|
|
10
|
+
<div class="no-source-overlay">
|
|
11
|
+
<v-icon x-large>mdi-file-question</v-icon><br />
|
|
12
|
+
{{
|
|
13
|
+
$t(
|
|
14
|
+
'windward.core.components.content.blocks.image.no_image_url'
|
|
15
|
+
)
|
|
16
|
+
}}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
10
19
|
<v-responsive :aspect-ratio="aspectRatio">
|
|
11
20
|
<v-img
|
|
12
21
|
v-if="block.body"
|
|
13
22
|
:alt="block.metadata.config.alt"
|
|
14
23
|
:aria-describedby="block.metadata.config.aria_described_by"
|
|
15
|
-
class="
|
|
24
|
+
:class="imageClass"
|
|
16
25
|
:src="block.body"
|
|
17
26
|
contain
|
|
18
27
|
>
|
|
19
|
-
<template
|
|
28
|
+
<template #placeholder>
|
|
20
29
|
<v-skeleton-loader
|
|
21
30
|
type="image, image, list-item-avatar"
|
|
22
31
|
height="100%"
|
|
@@ -59,6 +68,9 @@ export default {
|
|
|
59
68
|
if (_.isEmpty(this.block.metadata.config.alt)) {
|
|
60
69
|
this.block.metadata.config.alt = ''
|
|
61
70
|
}
|
|
71
|
+
if (!_.isBoolean(this.block.metadata.config.hide_background)) {
|
|
72
|
+
this.block.metadata.config.hide_background = false
|
|
73
|
+
}
|
|
62
74
|
if (_.isEmpty(this.block.metadata.config.aria_describedby)) {
|
|
63
75
|
this.block.metadata.config.aria_describedby = ''
|
|
64
76
|
}
|
|
@@ -75,6 +87,14 @@ export default {
|
|
|
75
87
|
describedByText() {
|
|
76
88
|
return _.get(this.block.metadata, 'config.aria_describedby', null)
|
|
77
89
|
},
|
|
90
|
+
imageClass() {
|
|
91
|
+
let imageClass = ''
|
|
92
|
+
// If NOT hide background, inclide the extra class
|
|
93
|
+
if (!_.get(this.block.metadata, 'config.hide_background', false)) {
|
|
94
|
+
imageClass += ' img-white'
|
|
95
|
+
}
|
|
96
|
+
return 'img-display' + imageClass
|
|
97
|
+
},
|
|
78
98
|
},
|
|
79
99
|
watch: {
|
|
80
100
|
value(newValue) {
|
|
@@ -101,7 +121,14 @@ export default {
|
|
|
101
121
|
border-radius: 3px;
|
|
102
122
|
}
|
|
103
123
|
.img-holder {
|
|
104
|
-
|
|
124
|
+
height: 300px;
|
|
125
|
+
}
|
|
126
|
+
.img-white {
|
|
127
|
+
background: #fff;
|
|
128
|
+
}
|
|
129
|
+
.no-source-overlay {
|
|
130
|
+
text-align: center;
|
|
131
|
+
margin-top: -175px;
|
|
105
132
|
}
|
|
106
133
|
::v-deep .v-skeleton-loader.v-skeleton-loader--is-loading {
|
|
107
134
|
.v-skeleton-loader__image {
|
|
@@ -75,9 +75,11 @@
|
|
|
75
75
|
|
|
76
76
|
<script>
|
|
77
77
|
import _ from 'lodash'
|
|
78
|
+
import { mapGetters } from 'vuex'
|
|
78
79
|
import TextViewer from '~/components/Text/TextViewer.vue'
|
|
79
80
|
import TextEditor from '~/components/Text/TextEditor.vue'
|
|
80
81
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
82
|
+
import UserContentBlockState from '~/models/UserContentBlockState'
|
|
81
83
|
|
|
82
84
|
export default {
|
|
83
85
|
name: 'ContentBlockOpenResponse',
|
|
@@ -93,6 +95,18 @@ export default {
|
|
|
93
95
|
submitted: false,
|
|
94
96
|
}
|
|
95
97
|
},
|
|
98
|
+
computed: {
|
|
99
|
+
...mapGetters({
|
|
100
|
+
enrollment: 'enrollment/get',
|
|
101
|
+
}),
|
|
102
|
+
canSubmit() {
|
|
103
|
+
// Make sure the response is not empty and not equal to the starting text
|
|
104
|
+
return (
|
|
105
|
+
this.response &&
|
|
106
|
+
this.response !== this.block.metadata.config.starting_text
|
|
107
|
+
)
|
|
108
|
+
},
|
|
109
|
+
},
|
|
96
110
|
beforeMount() {
|
|
97
111
|
if (_.isEmpty(this.block.body)) {
|
|
98
112
|
this.block.body = ''
|
|
@@ -107,23 +121,31 @@ export default {
|
|
|
107
121
|
this.block.metadata.config.starting_text = ''
|
|
108
122
|
}
|
|
109
123
|
},
|
|
110
|
-
computed: {
|
|
111
|
-
canSubmit() {
|
|
112
|
-
// Make sure the response is not empty and not equal to the starting text
|
|
113
|
-
return (
|
|
114
|
-
this.response &&
|
|
115
|
-
this.response !== this.block.metadata.config.starting_text
|
|
116
|
-
)
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
124
|
watch: {},
|
|
120
125
|
mounted() {},
|
|
121
126
|
methods: {
|
|
122
|
-
onAfterSetContentBlockState() {
|
|
127
|
+
async onAfterSetContentBlockState() {
|
|
128
|
+
// Check to see if we have a state already for this block with the same block_id
|
|
129
|
+
// States are loaded via the ContentBlock.id but in this particular case we want to
|
|
130
|
+
// maintain the state ACROSS different ContentBlock.ids but with the same linked Block.id
|
|
131
|
+
const userState = await UserContentBlockState.where({
|
|
132
|
+
'metadata->block->tag': 'plugin-core-open-response',
|
|
133
|
+
course_user_id: this.enrollment.id,
|
|
134
|
+
})
|
|
135
|
+
.where('metadata->block->block_id', this.block.block_id)
|
|
136
|
+
.first()
|
|
137
|
+
|
|
138
|
+
// Apply the "True" state
|
|
139
|
+
if (!_.isEmpty(userState)) {
|
|
140
|
+
this.response = _.get(userState, 'metadata.response', '')
|
|
141
|
+
this.submitted = _.get(userState, 'metadata.submitted', false)
|
|
142
|
+
}
|
|
143
|
+
|
|
123
144
|
// If after the state is applied the response is still empty then apply the default response
|
|
124
145
|
if (this.response === '') {
|
|
125
146
|
this.response = this.block.metadata.config.starting_text
|
|
126
147
|
}
|
|
148
|
+
|
|
127
149
|
this.stateLoaded = true
|
|
128
150
|
},
|
|
129
151
|
onSubmit() {
|
|
@@ -31,7 +31,9 @@ export default {
|
|
|
31
31
|
extends: BaseContentBlock,
|
|
32
32
|
components: {},
|
|
33
33
|
data() {
|
|
34
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
saveState: false, // Override the base block to disable state saving
|
|
36
|
+
}
|
|
35
37
|
},
|
|
36
38
|
beforeMount() {
|
|
37
39
|
if (_.isEmpty(this.block.body)) {
|
|
@@ -46,7 +48,8 @@ export default {
|
|
|
46
48
|
if (_.isEmpty(this.block.metadata.config.filename)) {
|
|
47
49
|
this.block.metadata.config.filename = ''
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
// _.isEmpty(true) returns false. use isBoolean to check if this prop exists
|
|
52
|
+
if (!_.isBoolean(this.block.metadata.config.include_prompts)) {
|
|
50
53
|
this.block.metadata.config.include_prompts = false
|
|
51
54
|
}
|
|
52
55
|
},
|
|
@@ -62,50 +65,74 @@ export default {
|
|
|
62
65
|
mounted() {},
|
|
63
66
|
methods: {
|
|
64
67
|
async onCollate() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
try {
|
|
69
|
+
let userState = await UserContentBlockState.where({
|
|
70
|
+
'metadata->block->tag': 'plugin-core-open-response',
|
|
71
|
+
course_user_id: this.enrollment.id,
|
|
72
|
+
})
|
|
73
|
+
.whereIn(
|
|
74
|
+
'metadata->block->block_id',
|
|
75
|
+
this.block.metadata.config.linked
|
|
76
|
+
)
|
|
77
|
+
.get()
|
|
78
|
+
let collated = ''
|
|
79
|
+
const sortedStates = []
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
// Sorted the states based on the linked order
|
|
82
|
+
this.block.metadata.config.linked.forEach((linkedId) => {
|
|
83
|
+
const found = userState.find((state) => {
|
|
84
|
+
return state.metadata.block.block_id === linkedId
|
|
85
|
+
})
|
|
86
|
+
if (found) {
|
|
87
|
+
sortedStates.push(found)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
sortedStates.forEach((state) => {
|
|
92
|
+
// Prepend the prompt from the state if include prompts is enabled
|
|
93
|
+
if (this.block.metadata.config.include_prompts) {
|
|
94
|
+
collated +=
|
|
95
|
+
'<strong>' +
|
|
96
|
+
state.metadata.block.body +
|
|
97
|
+
'</strong><hr />'
|
|
98
|
+
}
|
|
99
|
+
if (!_.isEmpty(state.metadata.response)) {
|
|
100
|
+
collated += '\n' + state.metadata.response
|
|
101
|
+
} else {
|
|
102
|
+
collated +=
|
|
103
|
+
'\n<p>' +
|
|
104
|
+
this.$t(
|
|
105
|
+
'windward.core.components.content.blocks.open_response_collate.no_response'
|
|
106
|
+
) +
|
|
107
|
+
'</p>'
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
let filename = this.block.metadata.config.filename
|
|
111
|
+
if (_.isEmpty(this.block.metadata.config.filename)) {
|
|
112
|
+
// Default filename is the users name + the current page
|
|
113
|
+
filename =
|
|
114
|
+
this.$auth.user.last_name +
|
|
115
|
+
'_' +
|
|
116
|
+
this.$auth.user.first_name +
|
|
117
|
+
'_' +
|
|
118
|
+
_.get(this.content, 'content.name_prefix') +
|
|
119
|
+
_.get(this.content, 'content.name')
|
|
120
|
+
|
|
121
|
+
// Change spaces to underscores and remove special characters
|
|
122
|
+
filename = filename.replaceAll(/\s+/gi, '_')
|
|
123
|
+
filename = filename.replaceAll(/[^a-z0-9\-\_]/gi, '')
|
|
90
124
|
}
|
|
91
|
-
})
|
|
92
|
-
let filename = this.block.metadata.config.filename
|
|
93
|
-
if (_.isEmpty(this.block.metadata.config.filename)) {
|
|
94
|
-
// Default filename is the users name + the current page
|
|
95
|
-
filename =
|
|
96
|
-
this.$auth.user.last_name +
|
|
97
|
-
'_' +
|
|
98
|
-
this.$auth.user.first_name +
|
|
99
|
-
'_' +
|
|
100
|
-
_.get(this.content, 'content.name_prefix') +
|
|
101
|
-
_.get(this.content, 'content.name')
|
|
102
125
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
this.generateDocument(collated, filename)
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.error(e)
|
|
130
|
+
this.$dialog.error(
|
|
131
|
+
this.$t(
|
|
132
|
+
'windward.core.components.content.blocks.open_response_collate.generate_error'
|
|
133
|
+
)
|
|
134
|
+
)
|
|
106
135
|
}
|
|
107
|
-
|
|
108
|
-
this.generateDocument(collated, filename)
|
|
109
136
|
},
|
|
110
137
|
generateDocument(htmlBody, filename = '') {
|
|
111
138
|
// Specify file name. If one isn't supplied then a default name of `exported_document_YYYY-MM-DD.doc` is used
|