@windward/core 0.3.0 → 0.4.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.
Files changed (43) hide show
  1. package/components/Content/Blocks/Accordion.vue +37 -0
  2. package/components/Content/Blocks/ClickableIcons.vue +107 -20
  3. package/components/Content/Blocks/Email.vue +9 -0
  4. package/components/Content/Blocks/Image.vue +47 -19
  5. package/components/Content/Blocks/Video.vue +80 -9
  6. package/components/Settings/AccordionSettings.vue +53 -0
  7. package/components/Settings/ClickableIconsSettings.vue +84 -7
  8. package/components/Settings/EmailSettings.vue +0 -9
  9. package/components/Settings/ImageSettings.vue +142 -39
  10. package/components/Settings/OpenResponseSettings.vue +1 -2
  11. package/components/Settings/VideoSettings.vue +100 -63
  12. package/components/utils/ContentViewer.vue +6 -1
  13. package/components/utils/MathExpressionEditor.vue +10 -5
  14. package/components/utils/TinyMCEWrapper.vue +114 -18
  15. package/components/utils/assets/tinymce/content/dark/content.scss +4 -0
  16. package/components/utils/assets/tinymce/{css/content.scss → content/global.scss} +38 -37
  17. package/components/utils/assets/tinymce/content/light/content.scss +4 -0
  18. package/components/utils/assets/tinymce/ui/dark/content.scss +803 -0
  19. package/components/utils/assets/tinymce/ui/dark/skin.scss +4727 -0
  20. package/components/utils/assets/tinymce/ui/global.scss +19 -0
  21. package/components/utils/assets/tinymce/ui/light/content.scss +822 -0
  22. package/components/utils/assets/tinymce/ui/light/skin.scss +4731 -0
  23. package/components/utils/glossary/CourseGlossary.vue +1 -1
  24. package/config/tinymce.config.ts +22 -14
  25. package/helpers/FillInBlankHelper.ts +34 -28
  26. package/helpers/GlossaryHelper.ts +90 -73
  27. package/helpers/MathHelper.ts +49 -28
  28. package/helpers/tinymce/plugin.ts +9 -7
  29. package/i18n/en-US/components/settings/clickable_icon.ts +2 -0
  30. package/i18n/en-US/components/settings/image.ts +6 -1
  31. package/i18n/en-US/shared/settings.ts +3 -0
  32. package/i18n/es-ES/components/settings/clickable_icon.ts +2 -0
  33. package/i18n/es-ES/components/settings/image.ts +8 -1
  34. package/i18n/es-ES/shared/settings.ts +3 -0
  35. package/i18n/sv-SE/components/settings/clickable_icon.ts +2 -0
  36. package/i18n/sv-SE/components/settings/image.ts +6 -1
  37. package/i18n/sv-SE/shared/settings.ts +3 -0
  38. package/package.json +4 -3
  39. package/test/Components/Settings/AccordionSettings.spec.js +16 -2
  40. package/test/__mocks__/contentBlockMock.js +6 -0
  41. package/test/__mocks__/contentSettingsMock.js +6 -0
  42. package/test/helpers/MathHelper.spec.js +22 -3
  43. package/tsconfig.json +1 -0
@@ -45,11 +45,12 @@
45
45
  <v-tab-item class="mb-1">
46
46
  <ContentBlockAsset
47
47
  v-model="media.source"
48
- mimes="video/mp4,audio/mpeg,video/webm"
48
+ :assets.sync="block.assets"
49
+ mimes="video/mp4,audio/mpeg,video/webm,video/youtube"
49
50
  allow-url
50
51
  class="mb-4"
51
52
  :disabled="render"
52
- @click:file="onSourceSelect"
53
+ @change="onSourceSelect"
53
54
  >
54
55
  <template #title>
55
56
  {{
@@ -66,14 +67,14 @@
66
67
  }}
67
68
  </template>
68
69
  </ContentBlockAsset>
69
-
70
70
  <ContentBlockAsset
71
71
  v-model="media.track"
72
+ :assets.sync="block.assets"
72
73
  mimes="text/vtt,text/xml"
73
74
  allow-url
74
75
  class="mb-4"
75
76
  :disabled="render"
76
- @click:file="onTrackSelect"
77
+ @change="onTrackSelect"
77
78
  >
78
79
  <template #title>
79
80
  {{
@@ -95,10 +96,11 @@
95
96
  <v-tab-item class="mb-1">
96
97
  <ContentBlockAsset
97
98
  v-model="media.poster"
99
+ :assets.sync="block.assets"
98
100
  mimes="image/png,image/jpeg"
99
101
  allow-url
100
102
  :disabled="render"
101
- @click:file="onPosterSelect"
103
+ @change="onPosterSelect"
102
104
  >
103
105
  <template #title>
104
106
  {{
@@ -120,11 +122,12 @@
120
122
  <v-tab-item class="mb-1">
121
123
  <ContentBlockAsset
122
124
  v-model="media.ads.preroll.source"
125
+ :assets.sync="block.assets"
123
126
  mimes="video/mp4,video/webm"
124
127
  allow-url
125
128
  class="mb-4"
126
129
  :disabled="render"
127
- @click:file="onAdSourceSelect($event, 0)"
130
+ @change="onAdSourceSelect($event, 0)"
128
131
  >
129
132
  <template #title>
130
133
  {{
@@ -144,10 +147,11 @@
144
147
 
145
148
  <ContentBlockAsset
146
149
  v-model="media.ads.preroll.track"
150
+ :assets.sync="block.assets"
147
151
  mimes="text/vtt,text/xml"
148
152
  allow-url
149
153
  :disabled="render"
150
- @click:file="onAdTrackSelect($event, 0)"
154
+ @change="onAdTrackSelect($event, 0)"
151
155
  >
152
156
  <template #title>
153
157
  {{
@@ -169,11 +173,12 @@
169
173
  <v-tab-item class="mb-1">
170
174
  <ContentBlockAsset
171
175
  v-model="media.ads.postroll.source"
176
+ :assets.sync="block.assets"
172
177
  mimes="video/mp4,video/webm"
173
178
  allow-url
174
179
  class="mb-4"
175
180
  :disabled="render"
176
- @click:file="onAdSourceSelect($event, 100)"
181
+ @change="onAdSourceSelect($event, 100)"
177
182
  >
178
183
  <template #title>
179
184
  {{
@@ -193,10 +198,11 @@
193
198
 
194
199
  <ContentBlockAsset
195
200
  v-model="media.ads.postroll.track"
201
+ :assets.sync="block.assets"
196
202
  mimes="text/vtt,text/xml"
197
203
  allow-url
198
204
  :disabled="render"
199
- @click:file="onAdTrackSelect($event, 100)"
205
+ @change="onAdTrackSelect($event, 100)"
200
206
  >
201
207
  <template #title>
202
208
  {{
@@ -330,6 +336,7 @@
330
336
  <v-row>
331
337
  <v-col cols="12">
332
338
  <v-select
339
+ id="video-playback-rates"
333
340
  v-model="
334
341
  block.metadata.config.attributes
335
342
  .playbackrates
@@ -454,8 +461,9 @@
454
461
  </div>
455
462
  </template>
456
463
  <script>
464
+ import _ from 'lodash'
457
465
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
458
-
466
+ import Uuid from '~/helpers/Uuid'
459
467
  export default {
460
468
  name: 'VideoSettings',
461
469
  extends: BaseContentSettings,
@@ -495,20 +503,83 @@ export default {
495
503
  this.playlistPaginator = 1
496
504
  }
497
505
  },
498
- mounted() {},
506
+ mounted() {
507
+ this.reloadMedia()
508
+ },
499
509
  methods: {
500
510
  reloadMedia() {
501
- this.media.source = null
502
- this.media.track = null
503
- this.media.poster = null
511
+ // Prefill the media sources with the related file asset
512
+ // If the source is empty then it will just return null
513
+ this.media.source = this.resolveAsset(
514
+ _.get(
515
+ this.block,
516
+ 'metadata.config.playlist[' +
517
+ this.playlistIndex +
518
+ '].sources[0]',
519
+ null
520
+ )
521
+ )
522
+ this.media.track = this.resolveAsset(
523
+ _.get(
524
+ this.block,
525
+ 'metadata.config.playlist[' +
526
+ this.playlistIndex +
527
+ '].tracks[0]',
528
+ null
529
+ )
530
+ )
531
+ this.media.poster = this.resolveAsset(
532
+ _.get(this.block, 'metadata.config.attributes.poster', null)
533
+ )
534
+
504
535
  this.media.ads = {
505
536
  preroll: {
506
- source: null,
507
- track: null,
537
+ source: this.resolveAsset(
538
+ _.get(
539
+ this.block,
540
+ 'metadata.config.playlist[' +
541
+ this.playlistIndex +
542
+ '].ads[' +
543
+ this.getAdSlot(0) +
544
+ '].sources[0]',
545
+ null
546
+ )
547
+ ),
548
+ track: this.resolveAsset(
549
+ _.get(
550
+ this.block,
551
+ 'metadata.config.playlist[' +
552
+ this.playlistIndex +
553
+ '].ads[' +
554
+ this.getAdSlot(0) +
555
+ '].tracks[0]',
556
+ null
557
+ )
558
+ ),
508
559
  },
509
560
  postroll: {
510
- source: null,
511
- track: null,
561
+ source: this.resolveAsset(
562
+ _.get(
563
+ this.block,
564
+ 'metadata.config.playlist[' +
565
+ this.playlistIndex +
566
+ '].ads[' +
567
+ this.getAdSlot(100) +
568
+ '].sources[0]',
569
+ null
570
+ )
571
+ ),
572
+ track: this.resolveAsset(
573
+ _.get(
574
+ this.block,
575
+ 'metadata.config.playlist[' +
576
+ this.playlistIndex +
577
+ '].ads[' +
578
+ this.getAdSlot(100) +
579
+ '].tracks[0]',
580
+ null
581
+ )
582
+ ),
512
583
  },
513
584
  }
514
585
  },
@@ -593,19 +664,11 @@ export default {
593
664
  return this.block.metadata.config.playlist.length - 1
594
665
  },
595
666
  onPosterSelect(file) {
596
- if (file === null) {
597
- delete this.block.metadata.config.attributes
598
- .poster_file_asset_id
599
- this.block.metadata.config.attributes.poster = ''
600
- } else {
601
- this.block.metadata.config.attributes.poster_file_asset_id =
602
- file.file_asset_id
603
- this.block.metadata.config.attributes.poster =
604
- file.asset.public_url
605
- }
667
+ this.block.metadata.config.attributes.poster = file
668
+
669
+ this.reloadVideo()
606
670
  },
607
671
  onSourceSelect(file) {
608
- this.reloadVideo()
609
672
  // No file, clear the selection
610
673
  if (_.isEmpty(file)) {
611
674
  delete this.block.metadata.config.playlist[this.playlistIndex]
@@ -626,37 +689,23 @@ export default {
626
689
  // Apply the playlist source video(s)
627
690
  this.block.metadata.config.playlist[
628
691
  this.playlistIndex
629
- ].sources = [
630
- {
631
- file_asset_id: file.file_asset_id,
632
- src: file.asset.public_url,
633
- type: file.asset.metadata.mime,
634
- },
635
- ]
692
+ ].sources = [file]
636
693
 
637
694
  this.showVideo = true
638
695
  }
696
+ this.reloadVideo()
639
697
  },
640
698
  onTrackSelect(file) {
641
- this.reloadVideo()
642
699
  if (_.isEmpty(file)) {
643
700
  this.block.metadata.config.playlist[this.playlistIndex].tracks =
644
701
  []
645
702
  this.showVideo = false
646
703
  } else {
647
704
  this.block.metadata.config.playlist[this.playlistIndex].tracks =
648
- [
649
- {
650
- file_asset_id: file.file_asset_id,
651
- src: file.asset.public_url,
652
- kind: 'captions',
653
- srclang: 'en-US',
654
- default: true,
655
- },
656
- ]
705
+ [file]
657
706
  }
707
+ this.reloadVideo()
658
708
  },
659
-
660
709
  onAdSourceSelect(file, playAtPercent) {
661
710
  const adIndex = this.getAdSlot(playAtPercent)
662
711
 
@@ -674,14 +723,9 @@ export default {
674
723
 
675
724
  this.block.metadata.config.playlist[this.playlistIndex].ads[
676
725
  adIndex
677
- ].sources = [
678
- {
679
- file_asset_id: file.file_asset_id,
680
- src: file.asset.public_url,
681
- type: file.asset.metadata.mime,
682
- },
683
- ]
726
+ ].sources = [file]
684
727
  }
728
+ this.reloadVideo()
685
729
  },
686
730
  onAdTrackSelect(file, playAtPercent) {
687
731
  const adIndex = this.getAdSlot(playAtPercent)
@@ -697,16 +741,9 @@ export default {
697
741
 
698
742
  this.block.metadata.config.playlist[this.playlistIndex].ads[
699
743
  adIndex
700
- ].tracks = [
701
- {
702
- file_asset_id: file.file_asset_id,
703
- src: file.asset.public_url,
704
- kind: 'captions',
705
- srclang: 'en-US',
706
- default: true,
707
- },
708
- ]
744
+ ].tracks = [file]
709
745
  }
746
+ this.reloadVideo()
710
747
  },
711
748
  onPlaybackChange(e) {
712
749
  // Sort the list so we don't get instances of 2, 1, 0.25, 3
@@ -79,6 +79,11 @@ img {
79
79
  width: 100% !important;
80
80
  }
81
81
  .text-viewer {
82
- @import './assets/tinymce/css/content';
82
+ @import './assets/tinymce/content/global';
83
+ }
84
+ .ML__text {
85
+ font-family: 'Roboto', sans-serif !important;
86
+ font-size: 1rem;
87
+ line-height: 1.75em;
83
88
  }
84
89
  </style>
@@ -41,9 +41,12 @@
41
41
  :key="buttonIndex"
42
42
  class="btn"
43
43
  @click="insert(button)"
44
- v-html="'<strong>'+convertLatexToMarkup(button.text)+'</strong>'"
45
- >
46
- </button>
44
+ v-html="
45
+ '<strong>' +
46
+ convertLatexToMarkup(button.text) +
47
+ '</strong>'
48
+ "
49
+ ></button>
47
50
  </div>
48
51
  </v-container>
49
52
  </v-tab-item>
@@ -131,7 +134,7 @@ export default {
131
134
  spokenText: '',
132
135
  spokenTextChanged: false,
133
136
  options: {
134
- smartFence: true,
137
+ smartFence: false,
135
138
  smartMode: false,
136
139
  virtualKeyboardMode: 'none',
137
140
  keypressSound: 'none',
@@ -250,7 +253,9 @@ export default {
250
253
  },
251
254
  insert(latex) {
252
255
  this.$refs.mathfield.insert(this.getBtnCode(latex), {
253
- selectionMode: 'placeholder',
256
+ selectionMode: 'item',
257
+ insertionMode: 'replaceSelection',
258
+ focus: true,
254
259
  })
255
260
  },
256
261
 
@@ -1,11 +1,15 @@
1
1
  <template>
2
2
  <div :class="'tinymce-included-' + seed">
3
- <div>
4
- <editor
5
- :key="seed + config.skin"
3
+ <div
4
+ :class="
5
+ 'windward-tinymce-wrapper windward-tinymce-wrapper' +
6
+ (isDarkTheme ? '--theme-dark' : '--theme-light')
7
+ "
8
+ >
9
+ <Editor
10
+ :key="seed + (isDarkTheme ? '-theme-dark' : '-theme-light')"
6
11
  v-model="text"
7
12
  initial-value=""
8
- :api-key="api_key"
9
13
  :init="config"
10
14
  tag-name="div"
11
15
  model-events="change keydown blur focus mouseDown paste input submit SetContent"
@@ -16,7 +20,7 @@
16
20
  include,
17
21
  }"
18
22
  >
19
- </editor>
23
+ </Editor>
20
24
  </div>
21
25
 
22
26
  <v-btn-toggle dense multiple class="pt-1 d-flex justify-end">
@@ -41,14 +45,66 @@
41
45
 
42
46
  <script>
43
47
  import _ from 'lodash'
48
+ import 'tinymce'
44
49
  import Editor from '@tinymce/tinymce-vue'
50
+
51
+ /* Required TinyMCE components */
52
+ import 'tinymce/icons/default/icons.min.js'
53
+ import 'tinymce/themes/silver/theme.min.js'
54
+ import 'tinymce/models/dom/model.min.js'
55
+
56
+ /* Import a skin (can be a custom skin instead of the default) */
57
+ import 'tinymce/skins/ui/oxide/skin.js'
58
+ import 'tinymce/skins/ui/oxide-dark/skin.js'
59
+
60
+ /* Import plugins */
61
+ import 'tinymce/plugins/accordion'
62
+ import 'tinymce/plugins/advlist'
63
+ import 'tinymce/plugins/anchor'
64
+ import 'tinymce/plugins/autolink'
65
+ import 'tinymce/plugins/autoresize'
66
+ import 'tinymce/plugins/autosave'
67
+ import 'tinymce/plugins/charmap'
68
+ import 'tinymce/plugins/code'
69
+ import 'tinymce/plugins/codesample'
70
+ import 'tinymce/plugins/directionality'
71
+ import 'tinymce/plugins/emoticons'
72
+ import 'tinymce/plugins/fullscreen'
73
+ import 'tinymce/plugins/help'
74
+ import 'tinymce/plugins/image'
75
+ import 'tinymce/plugins/importcss'
76
+ import 'tinymce/plugins/insertdatetime'
77
+ import 'tinymce/plugins/link'
78
+ import 'tinymce/plugins/lists'
79
+ import 'tinymce/plugins/media'
80
+ import 'tinymce/plugins/nonbreaking'
81
+ import 'tinymce/plugins/pagebreak'
82
+ import 'tinymce/plugins/preview'
83
+ import 'tinymce/plugins/quickbars'
84
+ import 'tinymce/plugins/save'
85
+ import 'tinymce/plugins/searchreplace'
86
+ import 'tinymce/plugins/table'
87
+ import 'tinymce/plugins/visualblocks'
88
+ import 'tinymce/plugins/visualchars'
89
+ import 'tinymce/plugins/wordcount'
90
+
91
+ /* content UI CSS is required */
92
+ import 'tinymce/skins/ui/oxide/content.js'
93
+ import 'tinymce/skins/ui/oxide-dark/content.js'
94
+
95
+ /* The default content CSS can be changed or replaced with appropriate CSS for the editor content. */
96
+ import 'tinymce/skins/content/default/content.js'
97
+ import 'tinymce/skins/content/dark/content.js'
98
+
45
99
  import { WindwardPlugins } from '../../helpers/tinymce/plugin'
46
- import contentCss from '!raw-loader!sass-loader!./assets/tinymce/css/content.scss'
100
+ import ContentCss from '!raw-loader!sass-loader!./assets/tinymce/content/global.scss'
101
+ import EditorCss from '!raw-loader!sass-loader!./assets/tinymce/ui/global.scss'
47
102
  import Crypto from '~/helpers/Crypto'
103
+ import MathHelper from '../../helpers/MathHelper'
48
104
  export default {
49
105
  name: 'ContentEditorRichText',
50
106
  components: {
51
- editor: Editor,
107
+ Editor,
52
108
  },
53
109
  props: {
54
110
  value: { type: String, required: true, default: '' },
@@ -69,7 +125,11 @@ export default {
69
125
  label: { type: String, required: true, default: '' },
70
126
  },
71
127
  beforeMount() {
72
- this.text = this.value
128
+ if (MathHelper.containsLatex(this.value)) {
129
+ this.text = MathHelper.wrapMathContentForTinyMCE(this.value)
130
+ } else {
131
+ this.text = this.value
132
+ }
73
133
  },
74
134
  data() {
75
135
  return {
@@ -84,6 +144,9 @@ export default {
84
144
  },
85
145
  },
86
146
  computed: {
147
+ isDarkTheme() {
148
+ return this.$vuetify.theme.isDark
149
+ },
87
150
  filteredMenubar() {
88
151
  if (this.render) {
89
152
  return this.menubar
@@ -117,6 +180,15 @@ export default {
117
180
  browser_spellcheck: true,
118
181
  contextmenu: false,
119
182
  statusbar: false,
183
+ promotion: false,
184
+ license_key: 'gpl',
185
+ ui_mode: 'split',
186
+ dialog_type: 'modal',
187
+ body_class: this.isDarkTheme
188
+ ? 'editor--theme-dark'
189
+ : 'editor--theme-light',
190
+ block_formats:
191
+ 'Paragraph=p; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6;',
120
192
  menu: {
121
193
  insert: {
122
194
  title: 'Insert',
@@ -132,10 +204,21 @@ export default {
132
204
  },
133
205
  },
134
206
  plugins: [
135
- 'advlist autolink lists link charmap',
136
- 'searchreplace visualblocks code fullscreen',
137
- 'anchor insertdatetime ',
138
- 'paste code wordcount table WindwardToolKit',
207
+ 'advlist',
208
+ 'autolink',
209
+ 'lists',
210
+ 'link',
211
+ 'charmap',
212
+ 'searchreplace',
213
+ 'visualblocks',
214
+ 'code',
215
+ 'fullscreen',
216
+ 'anchor',
217
+ 'insertdatetime',
218
+ 'code',
219
+ 'wordcount',
220
+ 'table',
221
+ 'WindwardToolKit',
139
222
  ],
140
223
  toolbar: this.filteredToolbar,
141
224
  table_advtab: false,
@@ -289,12 +372,13 @@ export default {
289
372
  'windward.core.shared.settings.title.placeholder'
290
373
  ),
291
374
  //required as it will be displayed as inline style in tinymce renderer
292
-
293
- skin: this.$vuetify.theme.isDark ? 'oxide-dark' : 'oxide',
375
+ skin: false,
294
376
  content_css: this.$vuetify.theme.isDark ? 'dark' : 'default',
377
+
295
378
  //we need to inject the glossary style directly
296
379
  content_style:
297
- contentCss +
380
+ ContentCss +
381
+ EditorCss +
298
382
  ' .glossary-word {\n' +
299
383
  ' display: inline-block;\n' +
300
384
  ' border-radius: 12px;\n' +
@@ -345,10 +429,11 @@ export default {
345
429
  }
346
430
 
347
431
  if (
348
- parentNode.closest('.tinymce-included-' + this.seed) ||
432
+ (parentNode &&
433
+ (parentNode.closest('.tinymce-included-' + this.seed) ||
434
+ _.includes(parentNode.className, 'tox'))) ||
349
435
  _.includes(targetClass, 'tinymce-included-' + this.seed) ||
350
- _.includes(targetClass, 'tox') ||
351
- _.includes(parentNode.className, 'tox')
436
+ _.includes(targetClass, 'tox')
352
437
  ) {
353
438
  return false
354
439
  } else {
@@ -359,3 +444,14 @@ export default {
359
444
  },
360
445
  }
361
446
  </script>
447
+
448
+ <style lang="scss">
449
+ html:has(body.theme--light) {
450
+ @import './assets/tinymce/ui/light/content.scss';
451
+ @import './assets/tinymce/ui/light/skin.scss';
452
+ }
453
+ html:has(body.theme--dark) {
454
+ @import './assets/tinymce/ui/dark/content.scss';
455
+ @import './assets/tinymce/ui/dark/skin.scss';
456
+ }
457
+ </style>
@@ -0,0 +1,4 @@
1
+ body {
2
+ background-color: #24292a;
3
+ color: #fff;
4
+ }