@windward/core 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.eslintrc.js +5 -1
  2. package/.prettierrc +3 -2
  3. package/CHANGELOG.md +3 -0
  4. package/components/Content/Blocks/ClickableIcons.vue +3 -9
  5. package/components/Content/Blocks/GenerateAIQuestionButton.vue +85 -18
  6. package/components/Content/Blocks/Image.vue +7 -182
  7. package/components/Content/Blocks/Tab.vue +10 -0
  8. package/components/Navigation/Items/GlossaryNav.vue +25 -10
  9. package/components/Settings/ImageSettings.vue +12 -240
  10. package/components/Settings/TabSettings.vue +17 -1
  11. package/components/Settings/TextEditorSettings.vue +17 -15
  12. package/components/utils/ContentViewer.vue +0 -3
  13. package/components/utils/FillInBlank/FillInBlankInput.vue +29 -47
  14. package/components/utils/TinyMCEWrapper.vue +37 -79
  15. package/components/utils/glossary/CourseGlossary.vue +5 -3
  16. package/components/utils/glossary/GlossaryToolTip.vue +8 -1
  17. package/helpers/GlossaryHelper.ts +38 -18
  18. package/helpers/tinymce/WindwardPlugins.ts +166 -118
  19. package/i18n/en-US/components/content/blocks/generate_questions.ts +3 -2
  20. package/i18n/en-US/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  21. package/i18n/en-US/components/utils/tiny_mce_wrapper.ts +1 -0
  22. package/i18n/es-ES/components/content/blocks/generate_questions.ts +2 -1
  23. package/i18n/es-ES/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  24. package/i18n/es-ES/components/utils/tiny_mce_wrapper.ts +3 -2
  25. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +2 -1
  26. package/i18n/sv-SE/components/utils/FillInBlank/FillInBlankInput.ts +2 -0
  27. package/i18n/sv-SE/components/utils/tiny_mce_wrapper.ts +2 -0
  28. package/package.json +2 -1
  29. package/pages/glossary.vue +1 -1
  30. package/stylelint.config.js +14 -0
  31. package/test/Components/Content/Blocks/OpenResponseCollate.spec.js +3 -3
  32. package/test/Components/Settings/TabSettings.spec.js +2 -2
  33. package/test/__mocks__/contentBlockMock.js +20 -0
  34. package/test/helpers/GlossaryHelper.spec.js +17 -0
@@ -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 dense v-if="allowRead">
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
- import Crypto from '~/helpers/Crypto'
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
- root_block: { type: String, required: false, default: 'div' },
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: false,
144
+ render: true,
169
145
  text: '',
170
146
  seed: Crypto.id(),
171
147
  synthesizer: null,
172
148
  paused: false,
173
149
  }
174
150
  },
175
- watch: {
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.root_block,
221
- menubar: this.filteredMenubar,
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
- toolbar: this.filteredToolbar,
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="200px"
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
- let word = _.isString(term.term)
10
+ const word = _.isString(term.term)
9
11
  ? '<template v-slot:term>' + text + '</template>'
10
12
  : ''
11
- let definition = _.isString(term.definition)
13
+ const definition = _.isString(term.definition)
12
14
  ? '<template v-slot:definition>' + term.definition + '</template>'
13
15
  : ''
14
- let alternate_forms = _.isString(term.alternate_forms)
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
- let related_terms = _.isString(term.related_term)
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
- ( _.trim(_.toLower(match[1])) ===
70
+ (_.trim(_.toLower(match[1])) ===
68
71
  _.trim(_.toLower(term.term)) ||
69
- term.alternate_forms && _.some(term.alternate_forms, alt => _.trim(_.toLower(alt)) === _.trim(_.toLower(match[1])))
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)) === _.trim(_.toLower(match[1]))
106
- ||
107
- item.alternate_forms && _.some(item.alternate_forms, alt => _.trim(_.toLower(alt)) === _.trim(_.toLower(match[1])))
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])) === _.trim(_.toLower(term.term))
150
- ||
151
- ( term.alternate_forms &&
152
- _.some(term.alternate_forms, alt => _.trim(_.toLower(alt)) === _.trim(_.toLower(match[1]))))
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],