@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
|
@@ -18,12 +18,6 @@
|
|
|
18
18
|
:init="config"
|
|
19
19
|
tag-name="div"
|
|
20
20
|
model-events="change keydown blur focus mouseDown paste input submit SetContent"
|
|
21
|
-
@onfocus="onEditorFocus"
|
|
22
|
-
v-click-outside="{
|
|
23
|
-
handler: onClickOutside,
|
|
24
|
-
closeConditional,
|
|
25
|
-
include,
|
|
26
|
-
}"
|
|
27
21
|
>
|
|
28
22
|
</Editor>
|
|
29
23
|
|
|
@@ -35,7 +29,7 @@
|
|
|
35
29
|
<v-btn-toggle dense multiple class="pt-1 d-flex justify-end">
|
|
36
30
|
<slot name="actions-prepend" :render="render"></slot>
|
|
37
31
|
<slot name="actions" :render="render">
|
|
38
|
-
<v-btn-toggle
|
|
32
|
+
<v-btn-toggle v-if="allowRead" dense>
|
|
39
33
|
<v-btn v-if="render" color="primary" outlined @click="read">
|
|
40
34
|
<v-icon> mdi-play</v-icon>
|
|
41
35
|
</v-btn>
|
|
@@ -51,19 +45,6 @@
|
|
|
51
45
|
<v-icon> mdi-stop</v-icon>
|
|
52
46
|
</v-btn>
|
|
53
47
|
</v-btn-toggle>
|
|
54
|
-
|
|
55
|
-
<v-btn
|
|
56
|
-
v-if="render && !inline"
|
|
57
|
-
color="primary"
|
|
58
|
-
elevation="0"
|
|
59
|
-
outlined
|
|
60
|
-
@click="onClickOutside"
|
|
61
|
-
>{{
|
|
62
|
-
$t(
|
|
63
|
-
'windward.core.components.utils.tiny_mce_wrapper.minimize'
|
|
64
|
-
)
|
|
65
|
-
}}
|
|
66
|
-
</v-btn>
|
|
67
48
|
</slot>
|
|
68
49
|
<slot name="actions-append" :render="render"></slot>
|
|
69
50
|
</v-btn-toggle>
|
|
@@ -124,12 +105,14 @@ import 'tinymce/skins/ui/oxide-dark/content.js'
|
|
|
124
105
|
import 'tinymce/skins/content/default/content.js'
|
|
125
106
|
import 'tinymce/skins/content/dark/content.js'
|
|
126
107
|
|
|
108
|
+
import { Synthesizer } from 'speechreader'
|
|
109
|
+
import Crypto from '~/helpers/Crypto'
|
|
110
|
+
import MathHelper from '../../helpers/MathHelper'
|
|
127
111
|
import { WindwardPlugins } from '../../helpers/tinymce/WindwardPlugins'
|
|
112
|
+
|
|
128
113
|
import ContentCss from '!raw-loader!sass-loader!./assets/tinymce/content/global.scss'
|
|
129
114
|
import EditorCss from '!raw-loader!sass-loader!./assets/tinymce/ui/global.scss'
|
|
130
|
-
|
|
131
|
-
import MathHelper from '../../helpers/MathHelper'
|
|
132
|
-
import { Synthesizer } from 'speechreader'
|
|
115
|
+
|
|
133
116
|
export default {
|
|
134
117
|
name: 'ContentEditorRichText',
|
|
135
118
|
components: {
|
|
@@ -150,33 +133,22 @@ export default {
|
|
|
150
133
|
default:
|
|
151
134
|
'undo redo | styles | bold italic underline strikethrough removeformat | alignleft aligncenter alignright | table tablerowprops tablecellprops |bullist numlist outdent indent |glossaryButton fibFormatButton mathButton a11yButton',
|
|
152
135
|
},
|
|
153
|
-
|
|
136
|
+
rootBlock: { type: String, required: false, default: 'div' },
|
|
154
137
|
label: { type: String, required: false, default: '' },
|
|
155
138
|
hint: { type: String, required: false, default: '' },
|
|
156
139
|
inline: { type: Boolean, required: false, default: false },
|
|
157
140
|
allowRead: { type: Boolean, required: false, default: false },
|
|
158
141
|
},
|
|
159
|
-
beforeMount() {
|
|
160
|
-
if (MathHelper.containsLatex(this.value)) {
|
|
161
|
-
this.text = MathHelper.wrapMathContentForTinyMCE(this.value)
|
|
162
|
-
} else {
|
|
163
|
-
this.text = this.value
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
142
|
data() {
|
|
167
143
|
return {
|
|
168
|
-
render:
|
|
144
|
+
render: true,
|
|
169
145
|
text: '',
|
|
170
146
|
seed: Crypto.id(),
|
|
171
147
|
synthesizer: null,
|
|
172
148
|
paused: false,
|
|
173
149
|
}
|
|
174
150
|
},
|
|
175
|
-
|
|
176
|
-
text: function (newVal) {
|
|
177
|
-
this.$emit('input', this.text)
|
|
178
|
-
},
|
|
179
|
-
},
|
|
151
|
+
|
|
180
152
|
computed: {
|
|
181
153
|
dataCy() {
|
|
182
154
|
return (
|
|
@@ -189,36 +161,12 @@ export default {
|
|
|
189
161
|
isDarkTheme() {
|
|
190
162
|
return this.$vuetify.theme.isDark
|
|
191
163
|
},
|
|
192
|
-
filteredMenubar() {
|
|
193
|
-
if (this.render) {
|
|
194
|
-
return this.menubar
|
|
195
|
-
} else {
|
|
196
|
-
return ''
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
filteredToolbar() {
|
|
200
|
-
if (this.render) {
|
|
201
|
-
return this.toolbar
|
|
202
|
-
} else {
|
|
203
|
-
return ''
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
filteredHeight() {
|
|
207
|
-
if (!this.render) {
|
|
208
|
-
return 100
|
|
209
|
-
} else if (this.height !== null) {
|
|
210
|
-
return this.height
|
|
211
|
-
} else {
|
|
212
|
-
return 500
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
164
|
config() {
|
|
216
165
|
return {
|
|
217
166
|
draggable_modal: true,
|
|
218
|
-
height: this.filteredHeight,
|
|
219
167
|
visual: false,
|
|
220
|
-
forced_root_block: this.
|
|
221
|
-
menubar: this.
|
|
168
|
+
forced_root_block: this.rootBlock,
|
|
169
|
+
menubar: this.menubar,
|
|
222
170
|
toolbar_mode: 'sliding',
|
|
223
171
|
browser_spellcheck: true,
|
|
224
172
|
contextmenu: false,
|
|
@@ -244,7 +192,11 @@ export default {
|
|
|
244
192
|
items: ' bold italic underline strikethrough superscript subscript codeformat | formats align | language | removeformat',
|
|
245
193
|
},
|
|
246
194
|
},
|
|
195
|
+
autoresize_min_height: 100,
|
|
196
|
+
autoresize_max_height: 1000,
|
|
197
|
+
autoresize_bottom_margin: 0,
|
|
247
198
|
plugins: [
|
|
199
|
+
'autoresize',
|
|
248
200
|
'advlist',
|
|
249
201
|
'autolink',
|
|
250
202
|
'lists',
|
|
@@ -260,8 +212,13 @@ export default {
|
|
|
260
212
|
'wordcount',
|
|
261
213
|
'table',
|
|
262
214
|
'WindwardToolKit',
|
|
215
|
+
'help',
|
|
263
216
|
],
|
|
264
|
-
|
|
217
|
+
help_accessibility: true,
|
|
218
|
+
iframe_aria_text: this.$t(
|
|
219
|
+
'windward.core.components.utils.tiny_mce_wrapper.aria_text'
|
|
220
|
+
),
|
|
221
|
+
toolbar: this.toolbar,
|
|
265
222
|
inline: this.inline,
|
|
266
223
|
table_advtab: false,
|
|
267
224
|
table_cell_advtab: false,
|
|
@@ -352,7 +309,6 @@ export default {
|
|
|
352
309
|
}
|
|
353
310
|
)
|
|
354
311
|
},
|
|
355
|
-
|
|
356
312
|
formats: {
|
|
357
313
|
glossary: {
|
|
358
314
|
inline: 'span',
|
|
@@ -480,6 +436,21 @@ export default {
|
|
|
480
436
|
return []
|
|
481
437
|
},
|
|
482
438
|
},
|
|
439
|
+
|
|
440
|
+
watch: {
|
|
441
|
+
text: function (_newVal) {
|
|
442
|
+
this.$emit('input', this.text)
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
beforeMount() {
|
|
447
|
+
if (MathHelper.containsLatex(this.value)) {
|
|
448
|
+
this.text = MathHelper.wrapMathContentForTinyMCE(this.value)
|
|
449
|
+
} else {
|
|
450
|
+
this.text = this.value
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
|
|
483
454
|
methods: {
|
|
484
455
|
read() {
|
|
485
456
|
if (this.paused) {
|
|
@@ -510,18 +481,6 @@ export default {
|
|
|
510
481
|
this.paused = false
|
|
511
482
|
}
|
|
512
483
|
},
|
|
513
|
-
onEditorFocus() {
|
|
514
|
-
if (!this.render) {
|
|
515
|
-
this.seed = Crypto.id()
|
|
516
|
-
this.render = true
|
|
517
|
-
}
|
|
518
|
-
},
|
|
519
|
-
onClickOutside() {
|
|
520
|
-
if (this.render) {
|
|
521
|
-
this.seed = Crypto.id()
|
|
522
|
-
this.render = false
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
484
|
include() {
|
|
526
485
|
// vuetify function to include an element with this class into clickable area without close
|
|
527
486
|
return this.elementBody
|
|
@@ -530,7 +489,7 @@ export default {
|
|
|
530
489
|
closeConditional(e) {
|
|
531
490
|
const targetClass = e.target.className
|
|
532
491
|
let parentNode
|
|
533
|
-
//check if target is svg element
|
|
492
|
+
// check if target is svg element
|
|
534
493
|
if (e.target.matches('svg') || e.target instanceof SVGElement) {
|
|
535
494
|
parentNode = e.target.parentNode.parentNode
|
|
536
495
|
} else {
|
|
@@ -546,7 +505,6 @@ export default {
|
|
|
546
505
|
) {
|
|
547
506
|
return false
|
|
548
507
|
} else {
|
|
549
|
-
this.onClickOutside()
|
|
550
508
|
return true
|
|
551
509
|
}
|
|
552
510
|
},
|
|
@@ -158,15 +158,17 @@
|
|
|
158
158
|
<script>
|
|
159
159
|
import { mapGetters, mapMutations } from 'vuex'
|
|
160
160
|
import _ from 'lodash'
|
|
161
|
+
import { encode } from 'he'
|
|
162
|
+
import CourseGlossaryForm from './CourseGlossaryForm'
|
|
161
163
|
import DialogBox from '~/components/Core/DialogBox.vue'
|
|
162
164
|
import Crypto from '~/helpers/Crypto'
|
|
163
165
|
import Course from '~/models/Course'
|
|
164
166
|
import SpeedDial from '~/components/Core/SpeedDial.vue'
|
|
167
|
+
import TextViewer from '~/components/Text/TextViewer.vue'
|
|
165
168
|
|
|
166
|
-
import CourseGlossaryForm from './CourseGlossaryForm'
|
|
167
169
|
export default {
|
|
168
170
|
name: 'CourseGlossary',
|
|
169
|
-
components: { DialogBox, CourseGlossaryForm, SpeedDial },
|
|
171
|
+
components: { DialogBox, CourseGlossaryForm, SpeedDial, TextViewer },
|
|
170
172
|
layout: 'course',
|
|
171
173
|
middleware: ['auth'],
|
|
172
174
|
props: {},
|
|
@@ -278,7 +280,7 @@ export default {
|
|
|
278
280
|
const updatedCourse = await course.save()
|
|
279
281
|
this.$store.commit('course/set', updatedCourse)
|
|
280
282
|
this.$toast.success(this.$t('shared.forms.saved'))
|
|
281
|
-
//force update on datatable
|
|
283
|
+
// force update on datatable
|
|
282
284
|
this.tableKey = Crypto.id()
|
|
283
285
|
},
|
|
284
286
|
saveNew() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="glossary-word"
|
|
4
|
+
:class="{ active: show }"
|
|
4
5
|
v-click-outside="onClickOutside"
|
|
5
6
|
@click="show = !show"
|
|
6
7
|
>
|
|
@@ -9,7 +10,8 @@
|
|
|
9
10
|
v-model="show"
|
|
10
11
|
:open-on-hover="false"
|
|
11
12
|
color="primary"
|
|
12
|
-
max-width="
|
|
13
|
+
max-width="768px"
|
|
14
|
+
z-index="10"
|
|
13
15
|
>
|
|
14
16
|
<template #activator="{ on }">
|
|
15
17
|
<span v-on="on">
|
|
@@ -19,6 +21,7 @@
|
|
|
19
21
|
<div>
|
|
20
22
|
<div v-if="this.$slots['definition']">
|
|
21
23
|
<h6 class="text-capitalize">
|
|
24
|
+
<slot name="term"></slot>
|
|
22
25
|
{{
|
|
23
26
|
$t(
|
|
24
27
|
'windward.core.components.utils.tiny_mce_wrapper.definition'
|
|
@@ -32,6 +35,7 @@
|
|
|
32
35
|
</div>
|
|
33
36
|
<div v-if="this.$slots['alternate_forms']">
|
|
34
37
|
<h4 class="text-capitalize">
|
|
38
|
+
<slot name="term"></slot>
|
|
35
39
|
{{
|
|
36
40
|
$t(
|
|
37
41
|
'windward.core.components.utils.tiny_mce_wrapper.alternate_forms'
|
|
@@ -84,4 +88,7 @@ export default {
|
|
|
84
88
|
font-weight: bold;
|
|
85
89
|
color: var(--v-dark-text-base);
|
|
86
90
|
}
|
|
91
|
+
.active {
|
|
92
|
+
background: var(--v-secondary-base) !important;
|
|
93
|
+
}
|
|
87
94
|
</style>
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import GlossaryTerm from '../helpers/GlossaryTerm'
|
|
2
1
|
import * as _ from 'lodash'
|
|
2
|
+
import { decode } from 'he'
|
|
3
|
+
import GlossaryTerm from '../helpers/GlossaryTerm'
|
|
3
4
|
|
|
4
5
|
export default class GlossaryHelper {
|
|
5
6
|
private static glossaryRegex =
|
|
6
7
|
/<span.*?.class="glossary-word".*?>(.*?)<\/span>/g
|
|
8
|
+
|
|
7
9
|
public static makeToolTip(term: GlossaryTerm, text: String): string {
|
|
8
|
-
|
|
10
|
+
const word = _.isString(term.term)
|
|
9
11
|
? '<template v-slot:term>' + text + '</template>'
|
|
10
12
|
: ''
|
|
11
|
-
|
|
13
|
+
const definition = _.isString(term.definition)
|
|
12
14
|
? '<template v-slot:definition>' + term.definition + '</template>'
|
|
13
15
|
: ''
|
|
14
|
-
|
|
16
|
+
const alternate_forms = _.isString(term.alternate_forms)
|
|
15
17
|
? '<template v-slot:alternate_forms>' +
|
|
16
18
|
term.alternate_forms +
|
|
17
19
|
'</template>'
|
|
18
20
|
: ''
|
|
19
|
-
|
|
21
|
+
const related_terms = _.isString(term.related_term)
|
|
20
22
|
? '<template v-slot:related_terms>' +
|
|
21
23
|
term.related_term +
|
|
22
24
|
'</template>'
|
|
@@ -30,6 +32,7 @@ export default class GlossaryHelper {
|
|
|
30
32
|
'</plugin-core-glossary-tool-tip>'
|
|
31
33
|
)
|
|
32
34
|
}
|
|
35
|
+
|
|
33
36
|
public static containsGlossaryTerms(content: string): boolean {
|
|
34
37
|
const regex = new RegExp(this.glossaryRegex)
|
|
35
38
|
|
|
@@ -50,7 +53,7 @@ export default class GlossaryHelper {
|
|
|
50
53
|
if (match.index === regex.lastIndex) {
|
|
51
54
|
regex.lastIndex++
|
|
52
55
|
}
|
|
53
|
-
|
|
56
|
+
match[1] = decode(match[1])
|
|
54
57
|
// The result can be accessed through the `m`-variable.
|
|
55
58
|
currentGlossary.forEach((term: any) => {
|
|
56
59
|
let addTerm = true
|
|
@@ -64,12 +67,15 @@ export default class GlossaryHelper {
|
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
if (
|
|
67
|
-
(
|
|
70
|
+
(_.trim(_.toLower(match[1])) ===
|
|
68
71
|
_.trim(_.toLower(term.term)) ||
|
|
69
|
-
term.alternate_forms &&
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
(term.alternate_forms &&
|
|
73
|
+
_.some(
|
|
74
|
+
term.alternate_forms,
|
|
75
|
+
(alt) =>
|
|
76
|
+
_.trim(_.toLower(alt)) ===
|
|
77
|
+
_.trim(_.toLower(match[1]))
|
|
78
|
+
))) &&
|
|
73
79
|
addTerm
|
|
74
80
|
) {
|
|
75
81
|
verifiedTerms.push(new GlossaryTerm(term))
|
|
@@ -98,13 +104,20 @@ export default class GlossaryHelper {
|
|
|
98
104
|
regex.lastIndex++
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
match[1] = decode(match[1])
|
|
101
108
|
let inGlossary = []
|
|
102
109
|
|
|
103
110
|
inGlossary = glossary.filter((item: any) => {
|
|
104
111
|
if (
|
|
105
|
-
_.trim(_.toLower(item.term)) ===
|
|
106
|
-
|
|
107
|
-
item.alternate_forms &&
|
|
112
|
+
_.trim(_.toLower(item.term)) ===
|
|
113
|
+
_.trim(_.toLower(match[1])) ||
|
|
114
|
+
(item.alternate_forms &&
|
|
115
|
+
_.some(
|
|
116
|
+
item.alternate_forms,
|
|
117
|
+
(alt) =>
|
|
118
|
+
_.trim(_.toLower(alt)) ===
|
|
119
|
+
_.trim(_.toLower(match[1]))
|
|
120
|
+
))
|
|
108
121
|
) {
|
|
109
122
|
return item
|
|
110
123
|
}
|
|
@@ -131,6 +144,7 @@ export default class GlossaryHelper {
|
|
|
131
144
|
|
|
132
145
|
return unVerifiedTerms
|
|
133
146
|
}
|
|
147
|
+
|
|
134
148
|
public static renderGlossaryWordsHtml(content: string, glossary: any) {
|
|
135
149
|
const regex: any = new RegExp(this.glossaryRegex)
|
|
136
150
|
const currentGlossary: any = glossary
|
|
@@ -143,13 +157,19 @@ export default class GlossaryHelper {
|
|
|
143
157
|
regex.lastIndex++
|
|
144
158
|
}
|
|
145
159
|
|
|
160
|
+
match[1] = decode(match[1])
|
|
146
161
|
// The result can be accessed through the `m`-variable.
|
|
147
162
|
currentGlossary.forEach((term: any) => {
|
|
148
163
|
if (
|
|
149
|
-
_.trim(_.toLower(match[1])) ===
|
|
150
|
-
|
|
151
|
-
(
|
|
152
|
-
_.some(
|
|
164
|
+
_.trim(_.toLower(match[1])) ===
|
|
165
|
+
_.trim(_.toLower(term.term)) ||
|
|
166
|
+
(term.alternate_forms &&
|
|
167
|
+
_.some(
|
|
168
|
+
term.alternate_forms,
|
|
169
|
+
(alt) =>
|
|
170
|
+
_.trim(_.toLower(alt)) ===
|
|
171
|
+
_.trim(_.toLower(match[1]))
|
|
172
|
+
))
|
|
153
173
|
) {
|
|
154
174
|
content = content.replace(
|
|
155
175
|
match[0],
|