@windward/core 0.28.0 → 0.30.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.
- package/CHANGELOG.md +22 -0
- package/components/Content/Blocks/ClickableIcons.vue +0 -6
- package/components/Content/Blocks/Tab.vue +12 -6
- package/components/Content/Blocks/UserUpload/DisplayUserFilesTable.vue +17 -6
- package/components/Content/Blocks/UserUpload/SubmittedDisplayUserFilesTable.vue +161 -0
- package/components/Content/Blocks/UserUpload.vue +150 -117
- package/components/Content/Blocks/Video.vue +21 -5
- package/components/Settings/ClickableIconsSettings.vue +6 -1
- package/components/Settings/TextEditorSettings.vue +244 -1
- package/components/Settings/UserUploadSettings.vue +1 -1
- package/components/utils/TinyMCEWrapper.vue +39 -15
- package/components/utils/glossary/CourseGlossaryForm.vue +3 -3
- package/i18n/en-US/components/content/blocks/user_upload.ts +7 -1
- package/i18n/en-US/components/settings/text_editor.ts +14 -0
- package/i18n/en-US/components/settings/user_upload.ts +1 -1
- package/i18n/es-ES/components/content/blocks/user_upload.ts +7 -1
- package/i18n/es-ES/components/settings/text_editor.ts +15 -0
- package/i18n/es-ES/components/settings/user_upload.ts +1 -1
- package/i18n/sv-SE/components/content/blocks/user_upload.ts +7 -1
- package/i18n/sv-SE/components/settings/text_editor.ts +15 -0
- package/i18n/sv-SE/components/settings/user_upload.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.30.0] - 2026-03-26
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2286-file-submission-block-submit-but (pull request #498)
|
|
6
|
+
* Merged in feature/LE-2277/generate-case-studies (pull request #484)
|
|
7
|
+
* Merged in bugfix/LE-2332-tab-text-color-in-dark-mode-with (pull request #497)
|
|
8
|
+
* Merged in feature/LE-2286-file-submission-block-submit-but (pull request #492)
|
|
9
|
+
* Merged in feature/LE-2321-increase-glossary-term-character (pull request #494)
|
|
10
|
+
* Merged in bugfix/LE-2332-tab-text-color-in-dark-mode-with (pull request #493)
|
|
11
|
+
* Merge remote-tracking branch 'origin/release/0.30.0' into feature/LE-2277/generate-case-studies
|
|
12
|
+
* Merged release/0.29.0 into feature/LE-2286-file-submission-block-submit-but
|
|
13
|
+
* Merged release/0.29.0 into feature/LE-2286-file-submission-block-submit-but
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Release [0.29.0] - 2026-03-11
|
|
17
|
+
|
|
18
|
+
* Merged in feature/LE-2319-preselect-cc-transcript-language (pull request #491)
|
|
19
|
+
* Merged in bugfix/LE-2292-clickable-icon-instructions-fiel (pull request #490)
|
|
20
|
+
* Merged release/0.29.0 into bugfix/LE-2292-clickable-icon-instructions-fiel
|
|
21
|
+
* Merged in feature/LE-2076-matching-game-allow-rich-text-ed (pull request #489)
|
|
22
|
+
* Merged in bugfix/LE-2292-clickable-icon-instructions-fiel (pull request #486)
|
|
23
|
+
|
|
24
|
+
|
|
3
25
|
## Release [0.28.0] - 2026-02-18
|
|
4
26
|
|
|
5
27
|
* Merged in feature/LE-2250/open-response-feedback-2 (pull request #485)
|
|
@@ -237,9 +237,6 @@ export default {
|
|
|
237
237
|
)
|
|
238
238
|
if (_.isEmpty(this.block.metadata.config.items)) {
|
|
239
239
|
this.block.metadata.config.items = []
|
|
240
|
-
this.block.metadata.config.description = this.$t(
|
|
241
|
-
'windward.core.components.settings.clickable_icon.information'
|
|
242
|
-
)
|
|
243
240
|
}
|
|
244
241
|
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
245
242
|
this.block.metadata.config.title = this.$t(
|
|
@@ -249,9 +246,6 @@ export default {
|
|
|
249
246
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
250
247
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
251
248
|
}
|
|
252
|
-
if (_.isEmpty(this.block.metadata.config.description)) {
|
|
253
|
-
this.block.metadata.config.description = ''
|
|
254
|
-
}
|
|
255
249
|
if (_.isEmpty(this.block.metadata.config.display)) {
|
|
256
250
|
this.block.metadata.config.display = {
|
|
257
251
|
show_title: true,
|
|
@@ -37,11 +37,13 @@
|
|
|
37
37
|
v-for="(tab, tabIndex) in block.metadata.config.items"
|
|
38
38
|
:key="tabIndex"
|
|
39
39
|
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
<div class="tab-headers">
|
|
41
|
+
{{
|
|
42
|
+
tab.tabHeader === '' || tab.tabHeader === null
|
|
43
|
+
? 'Item ' + (tabIndex + 1)
|
|
44
|
+
: tab.tabHeader
|
|
45
|
+
}}
|
|
46
|
+
</div>
|
|
45
47
|
</v-tab>
|
|
46
48
|
<v-tab-item
|
|
47
49
|
v-for="(tabContent, tabContentIndex) in block.metadata
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
<TextViewer
|
|
56
58
|
v-if="!tabContent.expand"
|
|
57
59
|
v-model="tabContent.content"
|
|
60
|
+
class="text-viewer"
|
|
58
61
|
text-viewer
|
|
59
62
|
></TextViewer>
|
|
60
63
|
<TextEditor
|
|
@@ -183,6 +186,9 @@ export default {
|
|
|
183
186
|
|
|
184
187
|
<style scoped>
|
|
185
188
|
.text-viewer {
|
|
186
|
-
color: var(--v-primary-base);
|
|
189
|
+
color: var(--v-primary-base) !important;
|
|
190
|
+
}
|
|
191
|
+
.tab-headers {
|
|
192
|
+
color: white !important;
|
|
187
193
|
}
|
|
188
194
|
</style>
|
|
@@ -3,18 +3,16 @@
|
|
|
3
3
|
<v-card-title
|
|
4
4
|
>{{
|
|
5
5
|
$t(
|
|
6
|
-
'windward.core.components.content.blocks.user_upload.
|
|
6
|
+
'windward.core.components.content.blocks.user_upload.review_current_uploads'
|
|
7
7
|
)
|
|
8
8
|
}}
|
|
9
|
-
-
|
|
10
|
-
{{ $d(new Date(userFileAsset.created_at), 'long') }}
|
|
11
9
|
</v-card-title>
|
|
12
10
|
<v-card-text>
|
|
13
|
-
<v-simple-table>
|
|
11
|
+
<v-simple-table class="simple-table" elevation="1">
|
|
14
12
|
<template #default>
|
|
15
13
|
<thead>
|
|
16
14
|
<tr>
|
|
17
|
-
<th>
|
|
15
|
+
<th style="width: 60%">
|
|
18
16
|
{{ $t('shared.file.name') }}
|
|
19
17
|
</th>
|
|
20
18
|
<th>
|
|
@@ -73,14 +71,24 @@
|
|
|
73
71
|
</tbody>
|
|
74
72
|
</template>
|
|
75
73
|
</v-simple-table>
|
|
74
|
+
<v-col class="d-flex justify-center">
|
|
75
|
+
<v-btn
|
|
76
|
+
color="success"
|
|
77
|
+
elevation="0"
|
|
78
|
+
class="text-center"
|
|
79
|
+
@click="onSubmit"
|
|
80
|
+
>
|
|
81
|
+
{{ $t('shared.forms.submit') }}
|
|
82
|
+
</v-btn>
|
|
83
|
+
</v-col>
|
|
76
84
|
</v-card-text>
|
|
77
85
|
</v-card>
|
|
78
86
|
</template>
|
|
79
87
|
|
|
80
88
|
<script>
|
|
89
|
+
import UserFileAsset from '../../../../models/UserFileAsset'
|
|
81
90
|
import Download from '~/helpers/Download'
|
|
82
91
|
|
|
83
|
-
import UserFileAsset from '../../../../models/UserFileAsset'
|
|
84
92
|
import FileAsset from '~/models/FileAsset'
|
|
85
93
|
|
|
86
94
|
import ContentBlock from '~/models/ContentBlock'
|
|
@@ -114,6 +122,9 @@ export default {
|
|
|
114
122
|
this.userFileAsset = this.value
|
|
115
123
|
},
|
|
116
124
|
methods: {
|
|
125
|
+
onSubmit() {
|
|
126
|
+
this.$emit('submit')
|
|
127
|
+
},
|
|
117
128
|
onConfirmDelete(file) {
|
|
118
129
|
const self = this
|
|
119
130
|
this.$toast.info(this.$t('shared.forms.confirm_delete_text'), {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="userFileAsset.length">
|
|
3
|
+
<h5 class="mb-2">
|
|
4
|
+
{{
|
|
5
|
+
$t(
|
|
6
|
+
'windward.core.components.content.blocks.user_upload.submission_history'
|
|
7
|
+
)
|
|
8
|
+
}}
|
|
9
|
+
</h5>
|
|
10
|
+
<v-card v-for="(fileSubmission, index) in userFileAsset" :key="index">
|
|
11
|
+
<v-card-title class="pb-0">
|
|
12
|
+
<p class="mb-0">
|
|
13
|
+
{{
|
|
14
|
+
$t(
|
|
15
|
+
'windward.core.components.content.blocks.user_upload.submission'
|
|
16
|
+
)
|
|
17
|
+
}}
|
|
18
|
+
-
|
|
19
|
+
{{ $d(new Date(fileSubmission.created_at), 'long') }}
|
|
20
|
+
</p>
|
|
21
|
+
</v-card-title>
|
|
22
|
+
<v-card-text>
|
|
23
|
+
<v-simple-table>
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th style="width: 60%">
|
|
27
|
+
{{
|
|
28
|
+
$t(
|
|
29
|
+
'windward.core.components.content.blocks.user_upload.filename'
|
|
30
|
+
)
|
|
31
|
+
}}
|
|
32
|
+
</th>
|
|
33
|
+
<th style="width: 20%">
|
|
34
|
+
{{
|
|
35
|
+
$t(
|
|
36
|
+
'windward.core.components.content.blocks.user_upload.file_size'
|
|
37
|
+
)
|
|
38
|
+
}}
|
|
39
|
+
</th>
|
|
40
|
+
<th style="width: 20%">
|
|
41
|
+
{{
|
|
42
|
+
$t(
|
|
43
|
+
'windward.core.components.content.blocks.user_upload.download'
|
|
44
|
+
)
|
|
45
|
+
}}
|
|
46
|
+
</th>
|
|
47
|
+
</tr>
|
|
48
|
+
</thead>
|
|
49
|
+
<tbody>
|
|
50
|
+
<tr
|
|
51
|
+
v-for="file in fileSubmission.file_assets"
|
|
52
|
+
:key="file.id"
|
|
53
|
+
>
|
|
54
|
+
<td>{{ file.name }}</td>
|
|
55
|
+
<td>
|
|
56
|
+
{{ file.asset.metadata.size | humanFilesize }}
|
|
57
|
+
</td>
|
|
58
|
+
<td>
|
|
59
|
+
<v-btn
|
|
60
|
+
link
|
|
61
|
+
elevation="0"
|
|
62
|
+
@click="
|
|
63
|
+
Download.url(
|
|
64
|
+
file.asset.public_url,
|
|
65
|
+
file.name
|
|
66
|
+
)
|
|
67
|
+
"
|
|
68
|
+
>
|
|
69
|
+
<v-icon>mdi-download</v-icon>
|
|
70
|
+
<span class="sr-only">
|
|
71
|
+
{{ $t('shared.file.download') }}
|
|
72
|
+
</span>
|
|
73
|
+
</v-btn>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</v-simple-table>
|
|
78
|
+
</v-card-text>
|
|
79
|
+
</v-card>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
import UserFileAsset from '../../../../models/UserFileAsset'
|
|
85
|
+
import Download from '~/helpers/Download'
|
|
86
|
+
|
|
87
|
+
import FileAsset from '~/models/FileAsset'
|
|
88
|
+
|
|
89
|
+
import ContentBlock from '~/models/ContentBlock'
|
|
90
|
+
import Enrollment from '~/models/Enrollment'
|
|
91
|
+
|
|
92
|
+
export default {
|
|
93
|
+
name: 'DisplayUserFilesTable',
|
|
94
|
+
components: {},
|
|
95
|
+
props: {
|
|
96
|
+
value: {
|
|
97
|
+
type: Array,
|
|
98
|
+
required: false,
|
|
99
|
+
default: () => {
|
|
100
|
+
return []
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
data() {
|
|
105
|
+
return {
|
|
106
|
+
Download,
|
|
107
|
+
userFileAsset: {},
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
watch: {
|
|
111
|
+
value(newVal) {
|
|
112
|
+
this.userFileAsset = newVal
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
mounted() {
|
|
116
|
+
this.userFileAsset = this.value
|
|
117
|
+
},
|
|
118
|
+
methods: {
|
|
119
|
+
onConfirmDelete(file) {
|
|
120
|
+
const self = this
|
|
121
|
+
this.$toast.info(this.$t('shared.forms.confirm_delete_text'), {
|
|
122
|
+
duration: null,
|
|
123
|
+
action: [
|
|
124
|
+
{
|
|
125
|
+
text: this.$t('shared.forms.cancel'),
|
|
126
|
+
onClick: (e, toastObject) => {
|
|
127
|
+
toastObject.goAway(0)
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
text: this.$t('shared.forms.confirm'),
|
|
132
|
+
onClick: (e, toastObject) => {
|
|
133
|
+
toastObject.goAway(0)
|
|
134
|
+
self.deleteFile(file)
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
async deleteFile(file) {
|
|
141
|
+
await new FileAsset({ id: file.file_asset_id })
|
|
142
|
+
.for(
|
|
143
|
+
new Enrollment(this.enrollment),
|
|
144
|
+
new ContentBlock({
|
|
145
|
+
id: this.userFileAsset.content_block_id,
|
|
146
|
+
}),
|
|
147
|
+
new UserFileAsset({ id: this.userFileAsset.id })
|
|
148
|
+
)
|
|
149
|
+
.delete()
|
|
150
|
+
|
|
151
|
+
// Remove the deleted file from the array for display reasons
|
|
152
|
+
this.userFileAsset.file_assets =
|
|
153
|
+
this.userFileAsset.file_assets.filter(function (f) {
|
|
154
|
+
return f.file_asset_id !== file.file_asset_id
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
this.$emit('input', this.userFileAsset)
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
@@ -1,109 +1,104 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-container
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
<v-container
|
|
3
|
+
:class="['pa-0', blockDirectionClasses]"
|
|
4
|
+
:dir="blockTextDirection"
|
|
5
|
+
>
|
|
6
|
+
<h2
|
|
7
|
+
v-if="
|
|
8
|
+
block.metadata.config.title &&
|
|
9
|
+
block.metadata.config.display_title
|
|
10
|
+
"
|
|
11
|
+
tabindex="0"
|
|
12
|
+
>
|
|
13
|
+
{{ block.metadata.config.title }}
|
|
14
|
+
</h2>
|
|
15
|
+
<v-row>
|
|
16
|
+
<v-col v-if="block.metadata.config.instructions" cols="12">
|
|
17
|
+
<p tabindex="0" class="pt-3">
|
|
18
|
+
{{ block.metadata.config.instructions }}
|
|
19
|
+
</p>
|
|
20
|
+
</v-col>
|
|
21
|
+
<v-col v-if="!blockExists" cols="12">
|
|
22
|
+
<v-alert type="warning">
|
|
23
|
+
<p>
|
|
24
|
+
{{
|
|
25
|
+
$t(
|
|
26
|
+
'windward.core.components.content.blocks.user_upload.must_save'
|
|
27
|
+
)
|
|
28
|
+
}}
|
|
17
29
|
</p>
|
|
18
|
-
</v-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
v-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<v-container class="text-center">
|
|
59
|
-
<v-btn
|
|
60
|
-
:disabled="!canUpload || loading"
|
|
61
|
-
color="primary"
|
|
62
|
-
elevation="0"
|
|
63
|
-
class="text-center"
|
|
64
|
-
@click="handleUpload"
|
|
65
|
-
>
|
|
66
|
-
{{ $t('shared.forms.upload') }}
|
|
67
|
-
</v-btn>
|
|
68
|
-
</v-container>
|
|
69
|
-
</v-form>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</v-col>
|
|
74
|
-
</v-row>
|
|
75
|
-
</div>
|
|
30
|
+
</v-alert>
|
|
31
|
+
</v-col>
|
|
32
|
+
</v-row>
|
|
33
|
+
<v-row v-if="render">
|
|
34
|
+
<v-col v-if="blockExists" cols="12">
|
|
35
|
+
<div class="upload-container" :class="uploadContainerClass">
|
|
36
|
+
<v-col>
|
|
37
|
+
<FileDropZone
|
|
38
|
+
v-model="uploadFiles"
|
|
39
|
+
:accept="
|
|
40
|
+
block.metadata.config.uploadSettings.accept
|
|
41
|
+
"
|
|
42
|
+
:multiple="
|
|
43
|
+
block.metadata.config.uploadSettings.multiple
|
|
44
|
+
"
|
|
45
|
+
></FileDropZone>
|
|
46
|
+
<v-col class="d-flex justify-center">
|
|
47
|
+
<v-btn
|
|
48
|
+
:disabled="!canUpload || loading"
|
|
49
|
+
color="primary"
|
|
50
|
+
elevation="0"
|
|
51
|
+
class="text-center"
|
|
52
|
+
@click="handleUpload"
|
|
53
|
+
>
|
|
54
|
+
{{ $t('shared.forms.upload') }}
|
|
55
|
+
</v-btn>
|
|
56
|
+
</v-col>
|
|
57
|
+
</v-col>
|
|
58
|
+
<DisplayUserFilesTable
|
|
59
|
+
v-model="uploadedUserFileAsset"
|
|
60
|
+
:enrollment="enrollment"
|
|
61
|
+
@submit="handleSubmit"
|
|
62
|
+
></DisplayUserFilesTable>
|
|
63
|
+
<v-divider class="my-4"></v-divider>
|
|
64
|
+
<SubmittedDisplayUserFilesTable
|
|
65
|
+
v-model="submittedUserFileAssets"
|
|
66
|
+
></SubmittedDisplayUserFilesTable>
|
|
67
|
+
</div>
|
|
68
|
+
</v-col>
|
|
69
|
+
</v-row>
|
|
76
70
|
</v-container>
|
|
77
71
|
</template>
|
|
78
72
|
|
|
79
73
|
<script>
|
|
80
74
|
import _ from 'lodash'
|
|
81
75
|
import { mapGetters } from 'vuex'
|
|
76
|
+
import UserFileAsset from '../../../models/UserFileAsset'
|
|
77
|
+
import DisplayUserFilesTable from './UserUpload/DisplayUserFilesTable.vue'
|
|
78
|
+
import SubmittedDisplayUserFilesTable from './UserUpload/SubmittedDisplayUserFilesTable.vue'
|
|
82
79
|
import Uuid from '~/helpers/Uuid'
|
|
83
80
|
import Download from '~/helpers/Download'
|
|
84
81
|
import Enrollment from '~/models/Enrollment'
|
|
85
82
|
import ContentBlock from '~/models/ContentBlock'
|
|
86
83
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
87
84
|
import FileDropZone from '~/components/Core/FileDropZone.vue'
|
|
88
|
-
import UserFileAsset from '../../../models/UserFileAsset'
|
|
89
|
-
|
|
90
|
-
import DisplayUserFilesTable from './UserUpload/DisplayUserFilesTable.vue'
|
|
91
85
|
|
|
92
86
|
export default {
|
|
93
87
|
name: 'UserUpload',
|
|
94
88
|
components: {
|
|
95
89
|
FileDropZone,
|
|
96
90
|
DisplayUserFilesTable,
|
|
91
|
+
SubmittedDisplayUserFilesTable,
|
|
97
92
|
},
|
|
98
93
|
extends: BaseContentBlock,
|
|
99
94
|
data() {
|
|
100
95
|
return {
|
|
101
96
|
Download,
|
|
102
97
|
saveState: false, // Override the base block to disable state saving
|
|
103
|
-
valid: true,
|
|
104
98
|
loading: false,
|
|
105
99
|
uploadFiles: [],
|
|
106
|
-
|
|
100
|
+
uploadedUserFileAsset: {},
|
|
101
|
+
submittedUserFileAssets: [],
|
|
107
102
|
studentUpload: null,
|
|
108
103
|
maxFileLimit: 10,
|
|
109
104
|
}
|
|
@@ -121,44 +116,48 @@ export default {
|
|
|
121
116
|
return Uuid.test(this.block.id)
|
|
122
117
|
},
|
|
123
118
|
canUpload() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
119
|
+
const currentFiles = _.get(
|
|
120
|
+
this.uploadedUserFileAsset,
|
|
121
|
+
'file_assets',
|
|
122
|
+
[]
|
|
123
|
+
)
|
|
130
124
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
125
|
+
// Multiple disabled, single file selected
|
|
126
|
+
if (
|
|
127
|
+
!Array.isArray(this.uploadFiles) &&
|
|
128
|
+
!this.block.metadata.config.uploadSettings.multiple &&
|
|
129
|
+
this.uploadFiles &&
|
|
130
|
+
currentFiles.length === 0
|
|
131
|
+
) {
|
|
132
|
+
return true
|
|
133
|
+
} else if (
|
|
134
|
+
// Multi enabled, one or more files selected
|
|
135
|
+
this.block.metadata.config.uploadSettings.multiple &&
|
|
136
|
+
Array.isArray(this.uploadFiles) &&
|
|
137
|
+
this.uploadFiles.length > 0 &&
|
|
138
|
+
this.uploadFiles.length + currentFiles.length <=
|
|
139
|
+
this.maxFileLimit
|
|
140
|
+
) {
|
|
141
|
+
return true
|
|
142
|
+
} else {
|
|
143
|
+
// No files
|
|
144
|
+
return false
|
|
152
145
|
}
|
|
153
|
-
|
|
146
|
+
},
|
|
147
|
+
canSubmit() {
|
|
148
|
+
return !this.canUpload && !_.isEmpty(this.uploadedUserFileAsset)
|
|
154
149
|
},
|
|
155
150
|
showUpload() {
|
|
156
|
-
const currentFiles = _.get(
|
|
157
|
-
|
|
151
|
+
const currentFiles = _.get(
|
|
152
|
+
this.uploadedUserFileAsset,
|
|
153
|
+
'file_assets',
|
|
154
|
+
[]
|
|
155
|
+
)
|
|
158
156
|
// Multiple disabled, confirm there's no current files
|
|
159
157
|
if (
|
|
160
158
|
!this.block.metadata.config.uploadSettings.multiple &&
|
|
161
|
-
currentFiles.length === 0
|
|
159
|
+
currentFiles.length === 0 &&
|
|
160
|
+
this.submittedUserFileAssets.length === 0
|
|
162
161
|
) {
|
|
163
162
|
return true
|
|
164
163
|
} else if (
|
|
@@ -223,7 +222,6 @@ export default {
|
|
|
223
222
|
) {
|
|
224
223
|
uploadFiles = [uploadFiles]
|
|
225
224
|
}
|
|
226
|
-
|
|
227
225
|
// Create a new UserFileAsset if we don't have an instance yet
|
|
228
226
|
let userFileAssetRequest = new UserFileAsset({
|
|
229
227
|
file: uploadFiles,
|
|
@@ -233,31 +231,66 @@ export default {
|
|
|
233
231
|
)
|
|
234
232
|
|
|
235
233
|
// Apply the existing id if we already have an instance
|
|
236
|
-
if (Uuid.test(this.
|
|
237
|
-
userFileAssetRequest.id = this.
|
|
234
|
+
if (Uuid.test(this.uploadedUserFileAsset.id)) {
|
|
235
|
+
userFileAssetRequest.id = this.uploadedUserFileAsset.id
|
|
238
236
|
}
|
|
239
237
|
|
|
240
238
|
// Add our files to upload
|
|
241
239
|
userFileAssetRequest.file = uploadFiles
|
|
242
240
|
|
|
243
241
|
try {
|
|
244
|
-
// Apply the response back to the reactive this.
|
|
245
|
-
this.
|
|
242
|
+
// Apply the response back to the reactive this.uploadedUserFileAsset
|
|
243
|
+
this.uploadedUserFileAsset = await userFileAssetRequest.save()
|
|
246
244
|
} catch (e) {
|
|
247
245
|
this.$toast.error(this.$t('shared.forms.errors.unknown'))
|
|
248
|
-
console.
|
|
246
|
+
console.error(e)
|
|
249
247
|
}
|
|
250
248
|
this.loading = false
|
|
251
249
|
this.uploadFiles = []
|
|
252
250
|
},
|
|
251
|
+
async handleSubmit() {
|
|
252
|
+
if (this.canSubmit) {
|
|
253
|
+
this.loading = true
|
|
254
|
+
|
|
255
|
+
// Create an update request for the existing UserFileAsset
|
|
256
|
+
const userFileAssetRequest = new UserFileAsset({
|
|
257
|
+
status: 'submitted',
|
|
258
|
+
}).for(
|
|
259
|
+
new Enrollment(this.enrollment),
|
|
260
|
+
new ContentBlock({ id: this.block.id })
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// Apply the existing id to update the specific record
|
|
264
|
+
if (Uuid.test(this.uploadedUserFileAsset.id)) {
|
|
265
|
+
userFileAssetRequest.id = this.uploadedUserFileAsset.id
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
// Save the update
|
|
269
|
+
const response = await userFileAssetRequest.save()
|
|
270
|
+
this.submittedUserFileAssets.unshift(response)
|
|
271
|
+
this.uploadedUserFileAsset = {}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
this.$toast.error(this.$t('shared.forms.errors.unknown'))
|
|
274
|
+
console.error(e)
|
|
275
|
+
}
|
|
276
|
+
this.loading = false
|
|
277
|
+
}
|
|
278
|
+
},
|
|
253
279
|
async loadUserUploads() {
|
|
254
280
|
if (this.enrollment && this.block.id) {
|
|
255
|
-
|
|
281
|
+
const result = await new UserFileAsset()
|
|
256
282
|
.for(
|
|
257
283
|
new Enrollment(this.enrollment),
|
|
258
284
|
new ContentBlock({ id: this.block.id })
|
|
259
285
|
)
|
|
260
|
-
.
|
|
286
|
+
.get()
|
|
287
|
+
result.forEach((file) => {
|
|
288
|
+
if (file.status === 'submitted') {
|
|
289
|
+
this.submittedUserFileAssets.push(file)
|
|
290
|
+
} else {
|
|
291
|
+
this.uploadedUserFileAsset = file
|
|
292
|
+
}
|
|
293
|
+
})
|
|
261
294
|
}
|
|
262
295
|
},
|
|
263
296
|
},
|
|
@@ -439,14 +439,15 @@ export default {
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
// 4. Sort tracks: course
|
|
442
|
+
// 4. Sort tracks: current course locale first (target language on translated
|
|
443
|
+
// courses, source language on non-translated courses).
|
|
443
444
|
allTracks.sort((a, b) => {
|
|
444
|
-
if (a.srclang
|
|
445
|
-
if (b.srclang
|
|
445
|
+
if (this.srclangMatchesLocale(a.srclang, this.courseCurrentLocale)) return -1
|
|
446
|
+
if (this.srclangMatchesLocale(b.srclang, this.courseCurrentLocale)) return 1
|
|
446
447
|
return 0
|
|
447
448
|
})
|
|
448
449
|
|
|
449
|
-
// 5. Set default on first track (
|
|
450
|
+
// 5. Set default on first track (target language for translated courses)
|
|
450
451
|
if (allTracks.length > 0) {
|
|
451
452
|
allTracks[0].default = true
|
|
452
453
|
}
|
|
@@ -766,10 +767,25 @@ export default {
|
|
|
766
767
|
const langBase = langLower.split('-')[0]
|
|
767
768
|
|
|
768
769
|
// Check if the full language code or its base is in allowedCaptionLocales
|
|
769
|
-
return this.allowedCaptionLocales.has(langLower) ||
|
|
770
|
+
return this.allowedCaptionLocales.has(langLower) ||
|
|
770
771
|
this.allowedCaptionLocales.has(langBase)
|
|
771
772
|
},
|
|
772
773
|
|
|
774
|
+
/**
|
|
775
|
+
* Check whether a srclang code refers to the same language as a locale code.
|
|
776
|
+
* Handles case differences ("PT-BR" vs "pt-br") and DeepL short codes
|
|
777
|
+
* ("ES" matching "es-ES").
|
|
778
|
+
* @param {string} srclang - Track srclang (e.g. "PT-BR", "ES")
|
|
779
|
+
* @param {string} locale - Locale code to match against (e.g. "pt-br", "es-es")
|
|
780
|
+
* @returns {boolean}
|
|
781
|
+
*/
|
|
782
|
+
srclangMatchesLocale(srclang, locale) {
|
|
783
|
+
if (!srclang || !locale) return false
|
|
784
|
+
const a = srclang.toLowerCase()
|
|
785
|
+
const b = locale.toLowerCase()
|
|
786
|
+
return a === b || a.split('-')[0] === b.split('-')[0]
|
|
787
|
+
},
|
|
788
|
+
|
|
773
789
|
/**
|
|
774
790
|
* Check if the given text has words, omitting HTML tags and HTML entities
|
|
775
791
|
* @param {string} text - The text to check
|
|
@@ -300,10 +300,15 @@ export default {
|
|
|
300
300
|
'windward.core.components.settings.clickable_icon.clickable_icon_title'
|
|
301
301
|
)
|
|
302
302
|
}
|
|
303
|
-
if (
|
|
303
|
+
if (
|
|
304
|
+
_.isEmpty(this.block.metadata.config.instructions) &&
|
|
305
|
+
!this.block.metadata.config.__isInitialized
|
|
306
|
+
) {
|
|
304
307
|
this.block.metadata.config.instructions = this.$t(
|
|
305
308
|
'windward.core.components.settings.clickable_icon.instructions'
|
|
306
309
|
)
|
|
310
|
+
// save state of initialization so we can allow user to set inputs to empty
|
|
311
|
+
this.$set(this.block.metadata.config, '__isInitialized', true)
|
|
307
312
|
}
|
|
308
313
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
309
314
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
@@ -49,13 +49,63 @@
|
|
|
49
49
|
v-bind="attrs"
|
|
50
50
|
type=" table-row-divider, list-item, divider, list-item, divider, image,image"
|
|
51
51
|
></v-skeleton-loader>
|
|
52
|
+
|
|
53
|
+
<div v-if="isTextBlock" class="case-study-controls mt-2">
|
|
54
|
+
<div class="text-caption mb-2">
|
|
55
|
+
{{
|
|
56
|
+
$t(
|
|
57
|
+
'windward.core.components.settings.text_editor.case_study_help'
|
|
58
|
+
)
|
|
59
|
+
}}
|
|
60
|
+
</div>
|
|
61
|
+
<v-autocomplete
|
|
62
|
+
v-model="selectedCaseStudyPages"
|
|
63
|
+
class="mb-2 case-study-page-selector"
|
|
64
|
+
:items="flattenedContent"
|
|
65
|
+
outlined
|
|
66
|
+
hide-details
|
|
67
|
+
multiple
|
|
68
|
+
chips
|
|
69
|
+
small-chips
|
|
70
|
+
deletable-chips
|
|
71
|
+
:disabled="render || isGeneratingCaseStudy"
|
|
72
|
+
:label="
|
|
73
|
+
$t(
|
|
74
|
+
'windward.core.components.settings.text_editor.case_study_selected_pages'
|
|
75
|
+
)
|
|
76
|
+
"
|
|
77
|
+
item-text="content.name"
|
|
78
|
+
return-object
|
|
79
|
+
></v-autocomplete>
|
|
80
|
+
<v-btn
|
|
81
|
+
elevation="0"
|
|
82
|
+
color="secondary"
|
|
83
|
+
block
|
|
84
|
+
:loading="isGeneratingCaseStudy"
|
|
85
|
+
:disabled="render || isGeneratingCaseStudy"
|
|
86
|
+
@click="onGenerateCaseStudy"
|
|
87
|
+
>
|
|
88
|
+
<v-icon v-if="!isGeneratingCaseStudy" class="pr-1">
|
|
89
|
+
mdi-magic-staff
|
|
90
|
+
</v-icon>
|
|
91
|
+
<span v-if="!isGeneratingCaseStudy">{{
|
|
92
|
+
$t(
|
|
93
|
+
'windward.core.components.settings.text_editor.generate_case_study'
|
|
94
|
+
)
|
|
95
|
+
}}</span>
|
|
96
|
+
</v-btn>
|
|
97
|
+
</div>
|
|
52
98
|
</div>
|
|
53
99
|
</template>
|
|
54
100
|
|
|
55
101
|
<script>
|
|
102
|
+
import _ from 'lodash'
|
|
103
|
+
import { mapGetters } from 'vuex'
|
|
56
104
|
import Crypto from '~/helpers/Crypto'
|
|
57
105
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
58
106
|
import TextEditor from '~/components/Text/TextEditor'
|
|
107
|
+
import Course from '~/models/Course'
|
|
108
|
+
import Organization from '~/models/Organization'
|
|
59
109
|
export default {
|
|
60
110
|
name: 'TextEditorSettings',
|
|
61
111
|
components: {
|
|
@@ -76,10 +126,42 @@ export default {
|
|
|
76
126
|
},
|
|
77
127
|
hideTextEditor: false,
|
|
78
128
|
updateKey: Crypto.id(),
|
|
129
|
+
isGeneratingCaseStudy: false,
|
|
130
|
+
selectedCaseStudyPages: [],
|
|
79
131
|
}
|
|
80
132
|
},
|
|
133
|
+
computed: {
|
|
134
|
+
...mapGetters({
|
|
135
|
+
organization: 'organization/get',
|
|
136
|
+
course: 'course/get',
|
|
137
|
+
currentContent: 'content/get',
|
|
138
|
+
}),
|
|
139
|
+
isTextBlock() {
|
|
140
|
+
return _.get(this.block, 'tag', '') === 'content-blocks-text'
|
|
141
|
+
},
|
|
142
|
+
flattenedContent() {
|
|
143
|
+
const flatTree = this.$ContentService.getFlatTree()
|
|
144
|
+
|
|
145
|
+
const homepage = this.$ContentService.getHomepage()
|
|
146
|
+
if (!_.isEmpty(homepage)) {
|
|
147
|
+
flatTree.unshift(homepage)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return flatTree
|
|
151
|
+
},
|
|
152
|
+
},
|
|
81
153
|
mounted() {
|
|
82
154
|
this.setConfig({ expand: false })
|
|
155
|
+
|
|
156
|
+
// Default selected pages to the current page
|
|
157
|
+
if (
|
|
158
|
+
this.isTextBlock &&
|
|
159
|
+
Array.isArray(this.selectedCaseStudyPages) &&
|
|
160
|
+
this.selectedCaseStudyPages.length === 0 &&
|
|
161
|
+
!_.isEmpty(this.currentContent)
|
|
162
|
+
) {
|
|
163
|
+
this.selectedCaseStudyPages = [_.cloneDeep(this.currentContent)]
|
|
164
|
+
}
|
|
83
165
|
},
|
|
84
166
|
methods: {
|
|
85
167
|
onExpand() {
|
|
@@ -87,8 +169,169 @@ export default {
|
|
|
87
169
|
this.block.metadata.config.expand = this.hideTextEditor
|
|
88
170
|
this.updateKey = Crypto.id()
|
|
89
171
|
},
|
|
172
|
+
blockHasMeaningfulText() {
|
|
173
|
+
const html = _.get(this.block, 'body', '') || ''
|
|
174
|
+
if (!html) {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
// Strip tags and check for meaningful length
|
|
178
|
+
const text = String(html).replace(/<[^>]*>/g, '').trim()
|
|
179
|
+
return text.length > 0
|
|
180
|
+
},
|
|
181
|
+
async requestCaseStudyGeneration(contentIds) {
|
|
182
|
+
const organizationId = _.get(this.organization, 'id', null)
|
|
183
|
+
const courseId = _.get(this.course, 'id', null)
|
|
184
|
+
|
|
185
|
+
if (!organizationId || !courseId) {
|
|
186
|
+
throw new Error('missing_context')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const request = new Course()
|
|
190
|
+
request.custom(
|
|
191
|
+
new Organization({ id: organizationId }),
|
|
192
|
+
new Course({ id: courseId }),
|
|
193
|
+
'llm-case-study'
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
const resourcePath = request._customResource
|
|
197
|
+
if (!resourcePath) {
|
|
198
|
+
throw new Error('missing_resource')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const payload = {
|
|
202
|
+
content_ids: contentIds,
|
|
203
|
+
language: this.$i18n?.locale,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const requestConfig = request._reqConfig(
|
|
207
|
+
{
|
|
208
|
+
method: 'POST',
|
|
209
|
+
url: `${request.baseURL()}/${resourcePath}`,
|
|
210
|
+
data: payload,
|
|
211
|
+
},
|
|
212
|
+
{ forceMethod: true }
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
const response = await request.request(requestConfig)
|
|
216
|
+
return response?.data ?? response
|
|
217
|
+
},
|
|
218
|
+
onGenerateCaseStudy(force = false) {
|
|
219
|
+
if (this.isGeneratingCaseStudy || this.render) {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const contentIds = (this.selectedCaseStudyPages || [])
|
|
224
|
+
.map((c) => _.get(c, 'id', null))
|
|
225
|
+
.filter((id) => typeof id === 'string' && id.length > 0)
|
|
226
|
+
|
|
227
|
+
if (contentIds.length === 0) {
|
|
228
|
+
this.$toast?.error(
|
|
229
|
+
this.$t(
|
|
230
|
+
'windward.core.components.settings.text_editor.case_study_select_pages_error'
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!force && this.blockHasMeaningfulText()) {
|
|
237
|
+
const confirmText = this.$t(
|
|
238
|
+
'windward.core.components.settings.text_editor.case_study_replace_confirm'
|
|
239
|
+
)
|
|
240
|
+
this.$dialog.show(confirmText, {
|
|
241
|
+
icon: 'mdi-alert-outline',
|
|
242
|
+
duration: null,
|
|
243
|
+
action: [
|
|
244
|
+
{
|
|
245
|
+
text: this.$t('shared.forms.cancel'),
|
|
246
|
+
onClick: (_e, toastObject) => {
|
|
247
|
+
toastObject.goAway(0)
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
text: this.$t('shared.forms.confirm'),
|
|
252
|
+
onClick: (_e, toastObject) => {
|
|
253
|
+
toastObject.goAway(0)
|
|
254
|
+
this.onGenerateCaseStudy(true)
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
})
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.isGeneratingCaseStudy = true
|
|
263
|
+
|
|
264
|
+
this.requestCaseStudyGeneration(contentIds)
|
|
265
|
+
.then((data) => {
|
|
266
|
+
if (!data || typeof data.html !== 'string') {
|
|
267
|
+
throw new Error('invalid_response')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.block.body = data.html
|
|
271
|
+
this.updateKey = Crypto.id()
|
|
272
|
+
|
|
273
|
+
this.$toast?.success(
|
|
274
|
+
this.$t(
|
|
275
|
+
'windward.core.components.settings.text_editor.case_study_generated'
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
.catch((error) => {
|
|
280
|
+
// eslint-disable-next-line no-console
|
|
281
|
+
console.error('Case study generation failed', error)
|
|
282
|
+
|
|
283
|
+
const messageKey = _.get(
|
|
284
|
+
error,
|
|
285
|
+
'response.data.error.message',
|
|
286
|
+
''
|
|
287
|
+
)
|
|
288
|
+
const details = _.get(
|
|
289
|
+
error,
|
|
290
|
+
'response.data.error.details',
|
|
291
|
+
{}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const errorSubtype = _.get(details, 'error_subtype', '')
|
|
295
|
+
const errorType = String(messageKey).split('.').pop()
|
|
296
|
+
|
|
297
|
+
if (errorSubtype === 'CHARACTER_LIMIT_EXCEEDED') {
|
|
298
|
+
this.$toast?.error(
|
|
299
|
+
this.$t(
|
|
300
|
+
'windward.core.components.settings.text_editor.case_study_character_limit'
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
} else if (
|
|
304
|
+
_.get(details, 'error_type', '') ===
|
|
305
|
+
'INSUFFICIENT_CONTENT' ||
|
|
306
|
+
errorType === 'insufficient_content'
|
|
307
|
+
) {
|
|
308
|
+
this.$toast?.error(
|
|
309
|
+
this.$t(
|
|
310
|
+
'windward.core.components.settings.text_editor.case_study_insufficient_content'
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
} else {
|
|
314
|
+
this.$toast?.error(
|
|
315
|
+
this.$t(
|
|
316
|
+
'windward.core.components.settings.text_editor.case_study_error'
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
.finally(() => {
|
|
322
|
+
this.isGeneratingCaseStudy = false
|
|
323
|
+
})
|
|
324
|
+
},
|
|
90
325
|
},
|
|
91
326
|
}
|
|
92
327
|
</script>
|
|
93
328
|
|
|
94
|
-
<style scoped
|
|
329
|
+
<style scoped>
|
|
330
|
+
.case-study-controls {
|
|
331
|
+
max-width: 100%;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.case-study-page-selector >>> .v-select__selections {
|
|
335
|
+
padding-top: 8px;
|
|
336
|
+
}
|
|
337
|
+
</style>
|
|
@@ -180,7 +180,7 @@ export default {
|
|
|
180
180
|
!Uuid.test(this.block.id)
|
|
181
181
|
) {
|
|
182
182
|
this.block.metadata.config.instructions = this.$t(
|
|
183
|
-
'windward.core.components.
|
|
183
|
+
'windward.core.components.content.blocks.user_upload.instructions'
|
|
184
184
|
)
|
|
185
185
|
}
|
|
186
186
|
if (_.isEmpty(this.block.metadata.config.uploadSettings)) {
|
|
@@ -156,6 +156,7 @@ export default {
|
|
|
156
156
|
showGlossary: { type: Boolean, required: false, default: false },
|
|
157
157
|
render: { type: Boolean, required: false, default: false },
|
|
158
158
|
hideTextEditor: { type: Boolean, required: false, default: false },
|
|
159
|
+
defaultAlignment: { type: String, required: false, default: null },
|
|
159
160
|
},
|
|
160
161
|
data() {
|
|
161
162
|
return {
|
|
@@ -165,7 +166,13 @@ export default {
|
|
|
165
166
|
paused: false,
|
|
166
167
|
isRevising: false,
|
|
167
168
|
rephraseToneIndex: 0,
|
|
168
|
-
toneSequence: [
|
|
169
|
+
toneSequence: [
|
|
170
|
+
'neutral',
|
|
171
|
+
'conversational',
|
|
172
|
+
'formal',
|
|
173
|
+
'succinct',
|
|
174
|
+
'encouraging',
|
|
175
|
+
],
|
|
169
176
|
}
|
|
170
177
|
},
|
|
171
178
|
|
|
@@ -356,6 +363,15 @@ export default {
|
|
|
356
363
|
value: 'windward-table-subject-report',
|
|
357
364
|
},
|
|
358
365
|
],
|
|
366
|
+
init_instance_callback: (editor) => {
|
|
367
|
+
if (this.defaultAlignment) {
|
|
368
|
+
editor.execCommand(
|
|
369
|
+
'mceToggleFormat',
|
|
370
|
+
false,
|
|
371
|
+
this.defaultAlignment
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
},
|
|
359
375
|
setup: () => {
|
|
360
376
|
// Here we can add plugin
|
|
361
377
|
getTinymce().PluginManager.add(
|
|
@@ -579,9 +595,10 @@ export default {
|
|
|
579
595
|
return null
|
|
580
596
|
}
|
|
581
597
|
|
|
582
|
-
const tone =
|
|
583
|
-
this.
|
|
584
|
-
|
|
598
|
+
const tone =
|
|
599
|
+
this.toneSequence[
|
|
600
|
+
this.rephraseToneIndex % this.toneSequence.length
|
|
601
|
+
]
|
|
585
602
|
this.rephraseToneIndex =
|
|
586
603
|
(this.rephraseToneIndex + 1) % this.toneSequence.length
|
|
587
604
|
|
|
@@ -748,12 +765,12 @@ export default {
|
|
|
748
765
|
}
|
|
749
766
|
|
|
750
767
|
// Wrap response with temporary markers so we can reselect inserted content
|
|
751
|
-
const startId = `ww-revise-start-${
|
|
752
|
-
.
|
|
753
|
-
|
|
754
|
-
const endId = `ww-revise-end-${
|
|
755
|
-
.
|
|
756
|
-
|
|
768
|
+
const startId = `ww-revise-start-${
|
|
769
|
+
this.seed
|
|
770
|
+
}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
771
|
+
const endId = `ww-revise-end-${
|
|
772
|
+
this.seed
|
|
773
|
+
}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
757
774
|
const wrappedHtml =
|
|
758
775
|
`<span id="${startId}" data-ww-revise="s"></span>` +
|
|
759
776
|
responseData.html +
|
|
@@ -769,7 +786,10 @@ export default {
|
|
|
769
786
|
if (startEl && endEl) {
|
|
770
787
|
const selectRange = editor.dom.createRng()
|
|
771
788
|
// Select everything between markers
|
|
772
|
-
if (
|
|
789
|
+
if (
|
|
790
|
+
selectRange.setStartAfter &&
|
|
791
|
+
selectRange.setEndBefore
|
|
792
|
+
) {
|
|
773
793
|
selectRange.setStartAfter(startEl)
|
|
774
794
|
selectRange.setEndBefore(endEl)
|
|
775
795
|
} else {
|
|
@@ -787,15 +807,19 @@ export default {
|
|
|
787
807
|
}
|
|
788
808
|
const startIndex = childIndex(startEl) + 1
|
|
789
809
|
const endIndex = childIndex(endEl)
|
|
790
|
-
selectRange.setStart(
|
|
810
|
+
selectRange.setStart(
|
|
811
|
+
startParent,
|
|
812
|
+
startIndex
|
|
813
|
+
)
|
|
791
814
|
selectRange.setEnd(endParent, endIndex)
|
|
792
815
|
}
|
|
793
816
|
editor.selection.setRng(selectRange)
|
|
794
817
|
|
|
795
818
|
// Remove markers after selection is set
|
|
796
|
-
if (startEl.parentNode)
|
|
797
|
-
|
|
798
|
-
|
|
819
|
+
if (startEl.parentNode)
|
|
820
|
+
startEl.parentNode.removeChild(startEl)
|
|
821
|
+
if (endEl.parentNode)
|
|
822
|
+
endEl.parentNode.removeChild(endEl)
|
|
799
823
|
}
|
|
800
824
|
} catch (_e) {
|
|
801
825
|
// Ignore selection restoration errors
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<v-text-field
|
|
4
4
|
id="glossary-form-term"
|
|
5
5
|
v-model="selectedTerm.term"
|
|
6
|
-
:counter="
|
|
6
|
+
:counter="75"
|
|
7
7
|
:label="$t('windward.core.pages.glossary.term')"
|
|
8
8
|
required
|
|
9
9
|
:rules="validation.termRules"
|
|
@@ -80,8 +80,8 @@ export default {
|
|
|
80
80
|
termRules: [
|
|
81
81
|
(v) => !!v || this.$t('shared.forms.errors.required'),
|
|
82
82
|
(v) =>
|
|
83
|
-
(v && v.length <=
|
|
84
|
-
this.$t('shared.forms.errors.text_lt', [
|
|
83
|
+
(v && v.length <= 75) ||
|
|
84
|
+
this.$t('shared.forms.errors.text_lt', [75]),
|
|
85
85
|
(v) =>
|
|
86
86
|
(v && this.termIsUnique(v)) ||
|
|
87
87
|
this.$t('shared.forms.errors.field_unique'),
|
|
@@ -2,10 +2,16 @@ export default {
|
|
|
2
2
|
title: 'File Submission',
|
|
3
3
|
user_uploads: 'User Uploads',
|
|
4
4
|
dialog_view: 'View Uploads',
|
|
5
|
-
|
|
5
|
+
instructions:
|
|
6
|
+
'Upload your files below. Then, review your current uploads, and select Submit when you are ready.',
|
|
6
7
|
must_save: 'You must save this block before users can make uploads.',
|
|
7
8
|
uploaded: 'Uploaded',
|
|
8
9
|
name: 'Name',
|
|
9
10
|
size: 'Size',
|
|
10
11
|
download: 'Download',
|
|
12
|
+
review_current_uploads: 'Review Current Uploads',
|
|
13
|
+
submission_history: 'Submission History',
|
|
14
|
+
submission: 'Submission',
|
|
15
|
+
filename: 'Filename',
|
|
16
|
+
file_size: 'File Size',
|
|
11
17
|
}
|
|
@@ -7,4 +7,18 @@ export default {
|
|
|
7
7
|
click_to_expand: 'Click to expand editor',
|
|
8
8
|
click_to_hide_editor: 'Click to hide editor',
|
|
9
9
|
click_to_hide_glossary: 'Click to hide glossary',
|
|
10
|
+
case_study_help:
|
|
11
|
+
'Select one or more pages to generate a fictional case study scenario based on their content.',
|
|
12
|
+
case_study_selected_pages: 'Source pages',
|
|
13
|
+
generate_case_study: 'Generate Case Study',
|
|
14
|
+
case_study_select_pages_error: 'Select at least one page to generate a case study.',
|
|
15
|
+
case_study_replace_confirm:
|
|
16
|
+
'This will replace the current text in this block. Continue?',
|
|
17
|
+
case_study_generated: 'Case study generated.',
|
|
18
|
+
case_study_insufficient_content:
|
|
19
|
+
'Not enough content on the selected pages. Add more content or select additional pages.',
|
|
20
|
+
case_study_character_limit:
|
|
21
|
+
'The selected pages contain too much content. Select fewer pages and try again.',
|
|
22
|
+
case_study_error:
|
|
23
|
+
'Unable to generate case study. Try selecting fewer pages or edit manually.',
|
|
10
24
|
}
|
|
@@ -2,11 +2,17 @@ export default {
|
|
|
2
2
|
title: 'Envío de archivos',
|
|
3
3
|
user_uploads: 'Cargas de usuarios',
|
|
4
4
|
dialog_view: 'Ver cargas',
|
|
5
|
-
|
|
5
|
+
instructions:
|
|
6
|
+
'Sube tus archivos a continuación. Luego, revisa tus archivos actuales y selecciona "Enviar" cuando estés listo.',
|
|
6
7
|
must_save:
|
|
7
8
|
'Debes guardar este bloque antes de que los usuarios puedan realizar cargas.',
|
|
8
9
|
uploaded: 'Subido',
|
|
9
10
|
name: 'Nombre',
|
|
10
11
|
size: 'Tamaño',
|
|
11
12
|
download: 'Descargar',
|
|
13
|
+
review_current_uploads: 'Revisar las cargas actuales',
|
|
14
|
+
submission_history: 'Historial de envíos',
|
|
15
|
+
submission: 'Envío',
|
|
16
|
+
filename: 'Nombre del archivo',
|
|
17
|
+
file_size: 'Tamaño de archivo',
|
|
12
18
|
}
|
|
@@ -7,4 +7,19 @@ export default {
|
|
|
7
7
|
click_to_expand: 'Haga Click ampliar el editor',
|
|
8
8
|
click_to_hide_editor: 'Haga click para esconder el editor',
|
|
9
9
|
click_to_hide_glossary: 'Haga click para esconder el glosario',
|
|
10
|
+
case_study_help:
|
|
11
|
+
'Seleccione una o más páginas para generar un caso práctico ficticio basado en su contenido.',
|
|
12
|
+
case_study_selected_pages: 'Páginas de origen',
|
|
13
|
+
generate_case_study: 'Generar caso práctico',
|
|
14
|
+
case_study_select_pages_error:
|
|
15
|
+
'Seleccione al menos una página para generar un caso práctico.',
|
|
16
|
+
case_study_replace_confirm:
|
|
17
|
+
'Esto reemplazará el texto actual de este bloque. ¿Continuar?',
|
|
18
|
+
case_study_generated: 'Caso práctico generado.',
|
|
19
|
+
case_study_insufficient_content:
|
|
20
|
+
'No hay suficiente contenido en las páginas seleccionadas. Agregue más contenido o seleccione páginas adicionales.',
|
|
21
|
+
case_study_character_limit:
|
|
22
|
+
'Las páginas seleccionadas contienen demasiado contenido. Seleccione menos páginas e inténtelo de nuevo.',
|
|
23
|
+
case_study_error:
|
|
24
|
+
'No se pudo generar el caso práctico. Intente seleccionar menos páginas o edite manualmente.',
|
|
10
25
|
}
|
|
@@ -2,11 +2,17 @@ export default {
|
|
|
2
2
|
title: 'Filinlämning',
|
|
3
3
|
user_uploads: 'Användaruppladdningar',
|
|
4
4
|
dialog_view: 'Visa uppladdningar',
|
|
5
|
-
|
|
5
|
+
instructions:
|
|
6
|
+
'Ladda upp dina filer nedan. Granska sedan dina aktuella uppladdningar och välj Skicka när du är redo.',
|
|
6
7
|
must_save:
|
|
7
8
|
'Du måste spara detta block innan användare kan göra uppladdningar.',
|
|
8
9
|
uploaded: 'Uppladdat',
|
|
9
10
|
name: 'Namn',
|
|
10
11
|
size: 'Storlek',
|
|
11
12
|
download: 'Ladda ner',
|
|
13
|
+
review_current_uploads: 'Granska aktuella uppladdningar',
|
|
14
|
+
submission_history: 'Inlämningshistorik',
|
|
15
|
+
submission: 'Underkastelse',
|
|
16
|
+
filename: 'Filnamn',
|
|
17
|
+
file_size: 'Fil-storlek',
|
|
12
18
|
}
|
|
@@ -7,4 +7,19 @@ export default {
|
|
|
7
7
|
click_to_expand: 'klicka för att förbruka',
|
|
8
8
|
click_to_hide_editor: 'klicka för att dölja editorn',
|
|
9
9
|
click_to_hide_glossary: 'klicka för att dölja ordlistan',
|
|
10
|
+
case_study_help:
|
|
11
|
+
'Välj en eller flera sidor för att generera ett fiktivt fallstudiescenario baserat på deras innehåll.',
|
|
12
|
+
case_study_selected_pages: 'Källsidor',
|
|
13
|
+
generate_case_study: 'Generera fallstudie',
|
|
14
|
+
case_study_select_pages_error:
|
|
15
|
+
'Välj minst en sida för att generera en fallstudie.',
|
|
16
|
+
case_study_replace_confirm:
|
|
17
|
+
'Detta kommer att ersätta den nuvarande texten i detta block. Fortsätt?',
|
|
18
|
+
case_study_generated: 'Fallstudie genererad.',
|
|
19
|
+
case_study_insufficient_content:
|
|
20
|
+
'Inte tillräckligt med innehåll på de valda sidorna. Lägg till mer innehåll eller välj fler sidor.',
|
|
21
|
+
case_study_character_limit:
|
|
22
|
+
'De valda sidorna innehåller för mycket innehåll. Välj färre sidor och försök igen.',
|
|
23
|
+
case_study_error:
|
|
24
|
+
'Kunde inte generera fallstudie. Försök välja färre sidor eller redigera manuellt.',
|
|
10
25
|
}
|