@windward/core 0.24.0 → 0.25.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 CHANGED
@@ -1,87 +1,92 @@
1
1
  # Changelog
2
2
 
3
- ## Release [0.24.0] - 2025-10-06
3
+ ## Release [0.25.0] - 2025-10-29
4
4
 
5
- * Merged in bugfix/LE-2134-email-block-show-hide-title (pull request #444)
6
- * Merged in feature/LE-2097-tabs-and-accordions-hide-backgro (pull request #436)
7
- * Merged release/0.24.0 into feature/LE-2097-tabs-and-accordions-hide-backgro
8
- * Merged in bugfix/LE-1841-all-blocks-save-or-cancel-change (pull request #440)
9
- * Merged release/0.24.0 into bugfix/LE-1841-all-blocks-save-or-cancel-change
10
- * Merged in feature/LE-2100-edit-in-text-area-update-text-fi (pull request #435)
5
+ * Merged in bugfix/LE-2067-scorm-provider (pull request #451)
6
+ * Merged in feature/LE-2108/revise-text (pull request #445)
7
+ * Merge remote-tracking branch 'origin/release/0.24.0' into release/0.25.0
8
+ * Merged in feature/LE-2123/open-res-gen (pull request #446)
9
+ * Merged in feature/LE-2127-increase-clickable-icon-item-tit (pull request #450)
10
+ * Merged in feature/LE-2157-remove-user-uploads-and-glossary (pull request #449)
11
11
 
12
12
 
13
- ## Release [0.23.0] - 2025-09-18
13
+ ## Release [0.24.0] - 2025-10-06
14
14
 
15
- * Merged in feature/LE-2099-track-engagement-on-videos-from- (pull request #439)
16
- * Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #438)
17
- * Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #437)
18
- * Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #434)
19
- * Merged in bugfix/MIND-6075-decouple-generateaiquestionbut (pull request #432)
20
- * Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #433)
21
- * Merged in bugfix/LE-2052-clickable-icon-text-color-should (pull request #426)
22
- * Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #430)
23
- * Merged release/0.23.0 into bugfix/LE-2071-vimeo-videos-not-working
24
- * Merged release/0.22.0 into bugfix/LE-2052-clickable-icon-text-color-should
15
+ - Merged in feature/LE-2123/open-res-gen (pull request #446)
16
+ - Merged in bugfix/LE-2134-email-block-show-hide-title (pull request #444)
17
+ - Merged in feature/LE-2097-tabs-and-accordions-hide-backgro (pull request #436)
18
+ - Merged release/0.24.0 into feature/LE-2097-tabs-and-accordions-hide-backgro
19
+ - Merged in bugfix/LE-1841-all-blocks-save-or-cancel-change (pull request #440)
20
+ - Merged release/0.24.0 into bugfix/LE-1841-all-blocks-save-or-cancel-change
21
+ - Merged in feature/LE-2100-edit-in-text-area-update-text-fi (pull request #435)
25
22
 
23
+ ## Release [0.23.0] - 2025-09-18
26
24
 
27
- ## Release [0.22.0] - 2025-08-28
25
+ - Merged in feature/LE-2099-track-engagement-on-videos-from- (pull request #439)
26
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #438)
27
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #437)
28
+ - Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #434)
29
+ - Merged in bugfix/MIND-6075-decouple-generateaiquestionbut (pull request #432)
30
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #433)
31
+ - Merged in bugfix/LE-2052-clickable-icon-text-color-should (pull request #426)
32
+ - Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #430)
33
+ - Merged release/0.23.0 into bugfix/LE-2071-vimeo-videos-not-working
34
+ - Merged release/0.22.0 into bugfix/LE-2052-clickable-icon-text-color-should
28
35
 
29
- * Merge branch 'master' into release/0.22.0
30
- * Merged in bugfix/LE-2075-the-panel-of-the-text-block-isnt (pull request #427)
31
- * Merged in bug-fix/LE-2060/llm-character-limit-validation (pull request #421)
32
- * Merged in bugfix/LE-2031-open-response-download-collate-q (pull request #422)
33
- * Merged in bugfix/LE-1960-video-using-urls-as-a-source (pull request #423)
34
- * Merged in hotfix/0.21.1 (pull request #425)
35
- * Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
36
- * Merged in bugfix/LE-2057-save-button-disappeared-again-on (pull request #419)
37
- * Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
38
- * Merged in feature/LE-2036/word-jumble-gen (pull request #420)
39
- * Merged in feature/LE-1997/scenario-gen (pull request #416)
40
- * Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #415)
41
- * Merged in release/0.21.0 (pull request #401)
42
- * Merged release/0.21.0 into bugfix/LE-1960-video-using-urls-as-a-source
43
- * Merged release/0.21.0 into bugfix/LE-1928-user-upload-allowed-file-types
36
+ ## Release [0.22.0] - 2025-08-28
44
37
 
38
+ - Merge branch 'master' into release/0.22.0
39
+ - Merged in bugfix/LE-2075-the-panel-of-the-text-block-isnt (pull request #427)
40
+ - Merged in bug-fix/LE-2060/llm-character-limit-validation (pull request #421)
41
+ - Merged in bugfix/LE-2031-open-response-download-collate-q (pull request #422)
42
+ - Merged in bugfix/LE-1960-video-using-urls-as-a-source (pull request #423)
43
+ - Merged in hotfix/0.21.1 (pull request #425)
44
+ - Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
45
+ - Merged in bugfix/LE-2057-save-button-disappeared-again-on (pull request #419)
46
+ - Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
47
+ - Merged in feature/LE-2036/word-jumble-gen (pull request #420)
48
+ - Merged in feature/LE-1997/scenario-gen (pull request #416)
49
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #415)
50
+ - Merged in release/0.21.0 (pull request #401)
51
+ - Merged release/0.21.0 into bugfix/LE-1960-video-using-urls-as-a-source
52
+ - Merged release/0.21.0 into bugfix/LE-1928-user-upload-allowed-file-types
45
53
 
46
54
  ## Hotfix [0.21.1] created - 2025-08-20
47
55
 
48
-
49
56
  ## Release [0.21.0] - 2025-08-01
50
57
 
51
- * Merged in feature/LE-2045/lableing-fixes (pull request #414)
52
- * Merged in feature/LE-2009-organizations-custom-color-palle (pull request #410)
53
- * Merged in feature/LE-2011-open-response-download-field-to- (pull request #411)
54
- * Merged in feature/LE-1912/sorting-game-gen-2 (pull request #413)
55
- * Merged in feature/LE-2029-open-response-block-design (pull request #412)
56
- * Merged in feature/LE-1912/sorting-game-gen (pull request #409)
57
- * Merged in feature/LE-2011-open-response-download-field-to- (pull request #408)
58
- * Merged in feature/LE-1924-clickable-icons-ux (pull request #407)
59
- * Merged in feature/LE-1913/matching-block (pull request #405)
60
- * Merged in feature/LE-1924-clickable-icons-ux (pull request #404)
61
- * Merged in feature/LE-2011-open-response-download-field-to- (pull request #406)
62
- * Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #398)
63
- * Merged release/0.21.0 into feature/LE-2011-open-response-download-field-to-
64
- * Merge branch 'release/0.21.0' into feature/LE-1913/matching-block
65
- * Merged in bugfix/LE-1990-video-black-line-on-left-and-rig (pull request #403)
66
- * Merged in feature/LE-1900-fill-in-the-blank-formatting-sho (pull request #402)
67
- * Merged release/0.21.0 into feature/LE-1924-clickable-icons-ux
68
- * Merge branch 'release/0.20.0' into feature/LE-1913/matching-block
69
-
58
+ - Merged in feature/LE-2045/lableing-fixes (pull request #414)
59
+ - Merged in feature/LE-2009-organizations-custom-color-palle (pull request #410)
60
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #411)
61
+ - Merged in feature/LE-1912/sorting-game-gen-2 (pull request #413)
62
+ - Merged in feature/LE-2029-open-response-block-design (pull request #412)
63
+ - Merged in feature/LE-1912/sorting-game-gen (pull request #409)
64
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #408)
65
+ - Merged in feature/LE-1924-clickable-icons-ux (pull request #407)
66
+ - Merged in feature/LE-1913/matching-block (pull request #405)
67
+ - Merged in feature/LE-1924-clickable-icons-ux (pull request #404)
68
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #406)
69
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #398)
70
+ - Merged release/0.21.0 into feature/LE-2011-open-response-download-field-to-
71
+ - Merge branch 'release/0.21.0' into feature/LE-1913/matching-block
72
+ - Merged in bugfix/LE-1990-video-black-line-on-left-and-rig (pull request #403)
73
+ - Merged in feature/LE-1900-fill-in-the-blank-formatting-sho (pull request #402)
74
+ - Merged release/0.21.0 into feature/LE-1924-clickable-icons-ux
75
+ - Merge branch 'release/0.20.0' into feature/LE-1913/matching-block
70
76
 
71
77
  ## Release [0.20.0] - 2025-07-01
72
78
 
73
- * Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
74
- * Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
75
- * Merged in feature/LE-1948/empty-bucket-localization-fix (pull request #397)
76
- * Merged in LE-1911-email-block-to-cc-and-subject-te (pull request #395)
77
- * Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #391)
78
- * Merged in bugfix/LE-1941-divider-dashed-line-renders-soli (pull request #396)
79
- * Merged in feature/LE-1948/empty-bucket (pull request #393)
80
- * Merged in feature/LE-1906-ai-assistant-button-text (pull request #394)
81
- * Merged in bugfix/LE-1739-email-block-to-cc-and-subject-te (pull request #392)
82
- * Merged in bugfix/LE-1917-student-experience-cleanup (pull request #386)
83
- * Merged release/0.19.0 into bugfix/LE-1917-student-experience-cleanup
84
-
79
+ - Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
80
+ - Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
81
+ - Merged in feature/LE-1948/empty-bucket-localization-fix (pull request #397)
82
+ - Merged in LE-1911-email-block-to-cc-and-subject-te (pull request #395)
83
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #391)
84
+ - Merged in bugfix/LE-1941-divider-dashed-line-renders-soli (pull request #396)
85
+ - Merged in feature/LE-1948/empty-bucket (pull request #393)
86
+ - Merged in feature/LE-1906-ai-assistant-button-text (pull request #394)
87
+ - Merged in bugfix/LE-1739-email-block-to-cc-and-subject-te (pull request #392)
88
+ - Merged in bugfix/LE-1917-student-experience-cleanup (pull request #386)
89
+ - Merged release/0.19.0 into bugfix/LE-1917-student-experience-cleanup
85
90
 
86
91
  ### Release [0.19.0] created - 2025-05-30
87
92
 
@@ -163,13 +163,17 @@
163
163
  hide-modal
164
164
  ></ImageAssetSettings>
165
165
  <v-text-field
166
+ :id="'item-' + index + '-title'"
166
167
  v-model="
167
168
  block.metadata.config.items[index].title
168
169
  "
169
- :id="'item-' + index + '-title'"
170
170
  outlined
171
- :rules="$Validation.getRule('shortInput')"
172
- :counter="$Validation.getLimit('shortInput')"
171
+ :rules="
172
+ $Validation.getRule('clickableIconInput')
173
+ "
174
+ :counter="
175
+ $Validation.getLimit('clickableIconInput')
176
+ "
173
177
  :label="
174
178
  $t(
175
179
  'windward.core.components.settings.clickable_icon.item_title'
@@ -1,13 +1,15 @@
1
1
  <template>
2
- <v-container>
2
+ <v-container class="pb-6">
3
3
  <v-row>
4
4
  <BaseContentBlockSettings
5
5
  v-model="block.metadata.config"
6
6
  :disabled="render"
7
7
  ></BaseContentBlockSettings>
8
8
  </v-row>
9
- <p>
10
- {{ $t('windward.core.components.settings.open_response.question') }}
9
+ <p class="pt-4 mb-2">
10
+ {{
11
+ $t('windward.core.components.settings.open_response.question')
12
+ }}
11
13
  </p>
12
14
  <TextEditor
13
15
  id="block-settings-body"
@@ -41,18 +43,36 @@
41
43
  :height="200"
42
44
  :disabled="render"
43
45
  ></TextEditor>
46
+ <v-row class="pt-6">
47
+ <v-col cols="12">
48
+ <PluginRef
49
+ target="contentBlockSettingTool"
50
+ :attrs="{
51
+ value: block,
52
+ course,
53
+ content: currentContent,
54
+ }"
55
+ :on="{
56
+ input: onApplyGeneratedBlock,
57
+ append: onApplyGeneratedBlock,
58
+ }"
59
+ ></PluginRef>
60
+ </v-col>
61
+ </v-row>
44
62
  </v-container>
45
63
  </template>
46
64
 
47
65
  <script>
48
66
  import _ from 'lodash'
67
+ import { mapGetters } from 'vuex'
49
68
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
50
69
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
70
+ import PluginRef from '~/components/Core/PluginRef.vue'
51
71
  import TextEditor from '~/components/Text/TextEditor'
52
72
 
53
73
  export default {
54
- name: 'ImageSettings',
55
- components: { TextEditor, BaseContentBlockSettings },
74
+ name: 'OpenResponseSettings',
75
+ components: { TextEditor, BaseContentBlockSettings, PluginRef },
56
76
  extends: BaseContentSettings,
57
77
  props: {
58
78
  settings: { type: Object, required: false, default: null },
@@ -61,6 +81,12 @@ export default {
61
81
  data() {
62
82
  return {}
63
83
  },
84
+ computed: {
85
+ ...mapGetters({
86
+ course: 'course/get',
87
+ currentContent: 'content/get',
88
+ }),
89
+ },
64
90
  beforeMount() {
65
91
  if (_.isEmpty(this.block)) {
66
92
  this.block = {}
@@ -91,6 +117,29 @@ export default {
91
117
  }
92
118
  },
93
119
  mounted() {},
94
- methods: {},
120
+ methods: {
121
+ onApplyGeneratedBlock(payload = {}) {
122
+ if (_.isEmpty(payload)) {
123
+ return
124
+ }
125
+
126
+ const generatedBlock = _.cloneDeep(payload)
127
+ const config = _.get(generatedBlock, 'metadata.config', {})
128
+
129
+ this.block.body = _.get(generatedBlock, 'body', '') ?? ''
130
+ this.block.metadata.config.title =
131
+ _.get(config, 'title', '') ?? ''
132
+ this.block.metadata.config.instructions =
133
+ _.get(config, 'instructions', '') ?? ''
134
+ this.block.metadata.config.sample_response =
135
+ _.get(config, 'sample_response', '') ?? ''
136
+ this.block.metadata.config.starting_text =
137
+ _.get(config, 'starting_text', '') ?? ''
138
+
139
+ if (_.has(config, 'display_title')) {
140
+ this.block.metadata.config.display_title = config.display_title
141
+ }
142
+ },
143
+ },
95
144
  }
96
145
  </script>
@@ -66,6 +66,7 @@ import _ from 'lodash'
66
66
  import 'tinymce'
67
67
  import Editor from '@tinymce/tinymce-vue'
68
68
  import { getTinymce } from '@tinymce/tinymce-vue/lib/cjs/main/ts/TinyMCE'
69
+ import { mapGetters } from 'vuex'
69
70
  /* Required TinyMCE components */
70
71
  import 'tinymce/icons/default/icons.min.js'
71
72
  import 'tinymce/themes/silver/theme.min.js'
@@ -119,6 +120,8 @@ import Crypto from '~/helpers/Crypto'
119
120
  import GlossaryVerification from '../Glossary/GlossaryVerification.vue'
120
121
  import MathHelper from '../../helpers/MathHelper'
121
122
  import { WindwardPlugins } from '../../helpers/tinymce/WindwardPlugins'
123
+ import Course from '~/models/Course'
124
+ import Organization from '~/models/Organization'
122
125
 
123
126
  import ContentCss from '!raw-loader!sass-loader!./assets/tinymce/content/global.scss'
124
127
  import EditorCss from '!raw-loader!sass-loader!./assets/tinymce/ui/global.scss'
@@ -143,7 +146,7 @@ export default {
143
146
  type: String,
144
147
  required: false,
145
148
  default:
146
- 'styles | bold italic underline strikethrough removeformat | alignleft aligncenter alignright | table tablerowprops tablecellprops |bullist numlist outdent indent |glossaryButton fibFormatButton mathButton a11yButton | undo redo',
149
+ 'styles | bold italic underline strikethrough removeformat | alignleft aligncenter alignright | table tablerowprops tablecellprops |bullist numlist outdent indent |glossaryButton fibFormatButton mathButton a11yButton reviseText | undo redo',
147
150
  },
148
151
  rootBlock: { type: String, required: false, default: 'div' },
149
152
  label: { type: String, required: false, default: '' },
@@ -160,10 +163,17 @@ export default {
160
163
  seed: Crypto.id(),
161
164
  synthesizer: null,
162
165
  paused: false,
166
+ isRevising: false,
167
+ rephraseToneIndex: 0,
168
+ toneSequence: ['neutral', 'conversational', 'formal', 'succinct', 'encouraging'],
163
169
  }
164
170
  },
165
171
 
166
172
  computed: {
173
+ ...mapGetters({
174
+ organization: 'organization/get',
175
+ course: 'course/get',
176
+ }),
167
177
  hasActions() {
168
178
  if (this.allowRead && !this.render) {
169
179
  return true
@@ -346,12 +356,14 @@ export default {
346
356
  value: 'windward-table-subject-report',
347
357
  },
348
358
  ],
349
- setup(_editor) {
359
+ setup: () => {
350
360
  // Here we can add plugin
351
361
  getTinymce().PluginManager.add(
352
362
  'WindwardToolKit',
353
363
  (editor) => {
354
- new WindwardPlugins(editor)
364
+ new WindwardPlugins(editor, {
365
+ onRevise: this.handleRevision,
366
+ })
355
367
  }
356
368
  )
357
369
  },
@@ -519,6 +531,244 @@ export default {
519
531
  return true
520
532
  }
521
533
  },
534
+ getRevisionContext() {
535
+ return {
536
+ organizationId: this.organization?.id ?? null,
537
+ courseId: this.course?.id ?? null,
538
+ }
539
+ },
540
+ selectionContainsProtected(node) {
541
+ if (!node) {
542
+ return false
543
+ }
544
+
545
+ if (typeof Node === 'undefined') {
546
+ return false
547
+ }
548
+
549
+ const editor = this.getEditor()
550
+ const body = editor ? editor.getBody() : null
551
+ const protectedClasses = [
552
+ 'windward-math-content',
553
+ 'windward-fill-blank',
554
+ 'glossary-word',
555
+ ]
556
+
557
+ let current = node
558
+
559
+ while (current && current !== body) {
560
+ if (current.nodeType === Node.ELEMENT_NODE) {
561
+ const classList = current.classList || []
562
+ if (
563
+ protectedClasses.some((className) =>
564
+ classList.contains(className)
565
+ )
566
+ ) {
567
+ return true
568
+ }
569
+ }
570
+ current = current.parentNode
571
+ }
572
+
573
+ return false
574
+ },
575
+ nextTone(operation) {
576
+ if (operation !== 'rephrase') {
577
+ this.rephraseToneIndex = 0
578
+
579
+ return null
580
+ }
581
+
582
+ const tone = this.toneSequence[
583
+ this.rephraseToneIndex % this.toneSequence.length
584
+ ]
585
+ this.rephraseToneIndex =
586
+ (this.rephraseToneIndex + 1) % this.toneSequence.length
587
+
588
+ return tone
589
+ },
590
+ async handleRevision(operation) {
591
+ const editor = this.getEditor()
592
+ if (!editor || this.isRevising) {
593
+ return
594
+ }
595
+
596
+ const selection = editor.selection
597
+ const selectionHtml = selection
598
+ ? selection.getContent({ format: 'html' })
599
+ : ''
600
+ const useSelection =
601
+ selection &&
602
+ !selection.isCollapsed() &&
603
+ selectionHtml.trim().length > 0
604
+
605
+ const targetHtml = useSelection
606
+ ? selectionHtml
607
+ : editor.getContent({ format: 'html' })
608
+
609
+ if (!targetHtml || targetHtml.trim().length === 0) {
610
+ return
611
+ }
612
+
613
+ if (useSelection) {
614
+ const range = selection.getRng()
615
+ if (
616
+ this.selectionContainsProtected(range.startContainer) ||
617
+ this.selectionContainsProtected(range.endContainer)
618
+ ) {
619
+ if (this.$toast) {
620
+ this.$toast.error(
621
+ this.$t(
622
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_protected_error'
623
+ )
624
+ )
625
+ }
626
+
627
+ return
628
+ }
629
+ }
630
+
631
+ const { organizationId, courseId } = this.getRevisionContext()
632
+
633
+ if (!organizationId || !courseId) {
634
+ if (this.$toast) {
635
+ this.$toast.error(
636
+ this.$t(
637
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
638
+ )
639
+ )
640
+ }
641
+
642
+ return
643
+ }
644
+
645
+ const tone = this.nextTone(operation)
646
+ const payload = {
647
+ operation,
648
+ html: targetHtml,
649
+ language: this.$i18n.locale,
650
+ }
651
+
652
+ if (tone) {
653
+ payload.tone = tone
654
+ }
655
+
656
+ const revisionRequest = new Course()
657
+ revisionRequest.custom(
658
+ new Organization({ id: organizationId }),
659
+ new Course({ id: courseId }),
660
+ 'llm-revise'
661
+ )
662
+
663
+ const resourcePath = revisionRequest._customResource
664
+
665
+ if (!resourcePath) {
666
+ if (this.$toast) {
667
+ this.$toast.error(
668
+ this.$t(
669
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
670
+ )
671
+ )
672
+ }
673
+
674
+ this.isRevising = false
675
+ editor.setProgressState(false)
676
+
677
+ return
678
+ }
679
+
680
+ this.isRevising = true
681
+ editor.setProgressState(true)
682
+ let bookmark = null
683
+ if (!useSelection && selection) {
684
+ bookmark = selection.getBookmark(2, true)
685
+ }
686
+
687
+ try {
688
+ const requestConfig = revisionRequest._reqConfig(
689
+ {
690
+ method: 'POST',
691
+ url: `${revisionRequest.baseURL()}/${resourcePath}`,
692
+ data: payload,
693
+ },
694
+ { forceMethod: true }
695
+ )
696
+
697
+ const response = await revisionRequest.request(requestConfig)
698
+ const responseData = response?.data || response
699
+
700
+ if (!responseData || !responseData.html) {
701
+ throw new Error('missing_html')
702
+ }
703
+
704
+ editor.undoManager.transact(() => {
705
+ if (useSelection) {
706
+ const range = selection.getRng()
707
+ const fragment = editor.dom.createFragment(
708
+ responseData.html
709
+ )
710
+
711
+ if (!fragment.hasChildNodes()) {
712
+ return
713
+ }
714
+
715
+ const firstNode = fragment.firstChild
716
+ const lastNode = fragment.lastChild
717
+
718
+ if (!firstNode || !lastNode) {
719
+ return
720
+ }
721
+
722
+ range.deleteContents()
723
+ range.insertNode(fragment)
724
+
725
+ const newRange = editor.dom.createRng()
726
+
727
+ if (firstNode.nodeType === Node.TEXT_NODE) {
728
+ newRange.setStart(firstNode, 0)
729
+ } else {
730
+ newRange.setStartBefore(firstNode)
731
+ }
732
+
733
+ if (lastNode.nodeType === Node.TEXT_NODE) {
734
+ newRange.setEnd(
735
+ lastNode,
736
+ lastNode.textContent ? lastNode.textContent.length : 0
737
+ )
738
+ } else {
739
+ newRange.setEndAfter(lastNode)
740
+ }
741
+
742
+ editor.selection.setRng(newRange)
743
+ } else {
744
+ editor.setContent(responseData.html)
745
+ }
746
+ })
747
+
748
+ editor.fire('change')
749
+
750
+ if (!useSelection && bookmark) {
751
+ try {
752
+ editor.selection.moveToBookmark(bookmark)
753
+ } catch (_error) {
754
+ editor.selection.select(editor.getBody(), true)
755
+ editor.selection.collapse(false)
756
+ }
757
+ }
758
+ } catch (error) {
759
+ console.error('TinyMCE revision failed', error)
760
+ if (this.$toast) {
761
+ this.$toast.error(
762
+ this.$t(
763
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
764
+ )
765
+ )
766
+ }
767
+ } finally {
768
+ this.isRevising = false
769
+ editor.setProgressState(false)
770
+ }
771
+ },
522
772
  },
523
773
  }
524
774
  </script>
@@ -5,17 +5,29 @@ import local from '../../i18n/index'
5
5
  /**
6
6
  * Class representing the WindwardPlugins.
7
7
  */
8
+ type RevisionOptions = {
9
+ onRevise?: (operation: string) => void
10
+ }
11
+
12
+ type RevisionMenuItem = {
13
+ type: 'menuitem'
14
+ text: string
15
+ onAction: () => void
16
+ }
17
+
8
18
  export class WindwardPlugins {
9
19
  editor: any
10
20
  formula: any
11
21
  fillInBlank: any
12
22
  private window: any
23
+ private options: RevisionOptions
13
24
 
14
- constructor(editor: any) {
25
+ constructor(editor: any, options: RevisionOptions = {}) {
15
26
  this.editor = editor
16
27
  this.formula = undefined
17
28
  this.fillInBlank = undefined
18
29
  this.window = window
30
+ this.options = options
19
31
  this.register()
20
32
  }
21
33
 
@@ -70,6 +82,10 @@ export class WindwardPlugins {
70
82
  'glossaryIcon',
71
83
  '<svg viewBox="0 0 24 24" width="20px" height="20px" ><path d="M3,15H1V3A2,2 0 0,1 3,1H19V3H3V15M12,23A1,1 0 0,1 11,22V19H7A2,2 0 0,1 5,17V7A2,2 0 0,1 7,5H21A2,2 0 0,1 23,7V17A2,2 0 0,1 21,19H16.9L13.2,22.71C13,22.89 12.76,23 12.5,23H12M9,9V11H19V9H9M9,13V15H17V13H9Z"></path></svg>'
72
84
  )
85
+ this.registerIcon(
86
+ 'reviseIcon',
87
+ '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L11,10.25L10.75,12.75L13.25,12.5L20.21,5.54C20.61,5.15 20.61,4.5 20.21,4.11M17,8L7,18L2,20L4,15L14,5L17,8Z"></path></svg>'
88
+ )
73
89
  }
74
90
 
75
91
  /**
@@ -188,6 +204,53 @@ export class WindwardPlugins {
188
204
  )
189
205
  }
190
206
 
207
+ private registerRevisionControls() {
208
+ const reviseLabel = this.$t(
209
+ 'windward.core.components.utils.tiny_mce_wrapper.revise_text'
210
+ )
211
+
212
+ const items = [
213
+ {
214
+ text: this.$t(
215
+ 'windward.core.components.utils.tiny_mce_wrapper.shorten'
216
+ ),
217
+ value: 'shorten',
218
+ },
219
+ {
220
+ text: this.$t(
221
+ 'windward.core.components.utils.tiny_mce_wrapper.rephrase'
222
+ ),
223
+ value: 'rephrase',
224
+ },
225
+ {
226
+ text: this.$t(
227
+ 'windward.core.components.utils.tiny_mce_wrapper.proofread'
228
+ ),
229
+ value: 'proofread',
230
+ },
231
+ ]
232
+
233
+ this.editor.ui.registry.addMenuButton('reviseText', {
234
+ icon: 'reviseIcon',
235
+ tooltip: reviseLabel,
236
+ fetch: (callback: (menuItems: RevisionMenuItem[]) => void) => {
237
+ callback(
238
+ items.map((item) => ({
239
+ type: 'menuitem',
240
+ text: item.text,
241
+ onAction: () => this.handleRevision(item.value),
242
+ }))
243
+ )
244
+ },
245
+ })
246
+ }
247
+
248
+ private handleRevision(operation: string) {
249
+ if (this.options.onRevise) {
250
+ this.options.onRevise(operation)
251
+ }
252
+ }
253
+
191
254
  /**
192
255
  * Add a button to the editor's UI registry
193
256
  * @param {string} name Name of the button
@@ -470,6 +533,7 @@ export class WindwardPlugins {
470
533
  private register() {
471
534
  this.addIcons()
472
535
  this.addButtons()
536
+ this.registerRevisionControls()
473
537
  this.addMenuItems()
474
538
  this.setContent()
475
539
  this.setInputEvents()
@@ -21,6 +21,13 @@ export default {
21
21
  math: 'Math',
22
22
  minimize: 'Minimize',
23
23
  read_text_aloud: 'Read Text aloud',
24
+ revise_text: 'Revise text',
25
+ shorten: 'Shorten',
26
+ rephrase: 'Rephrase',
27
+ proofread: 'Proofread',
28
+ revision_error: 'Unable to revise text. Try selecting a smaller portion or edit manually.',
29
+ revision_protected_error:
30
+ 'Select text outside of math, fill-in-the-blank, or glossary items before revising.',
24
31
  heading_order_incorrect:
25
32
  'Headings must be applied in sequential order: H2 should be followed by H3, H4, and so on',
26
33
  wcag_guidelines: '(WCAG {0} guidelines)',
@@ -22,6 +22,14 @@ export default {
22
22
  math: 'Matemáticas',
23
23
  minimize: 'Minimizar',
24
24
  read_text_aloud: 'Leer texto',
25
+ revise_text: 'Revisar texto',
26
+ shorten: 'Acortar',
27
+ rephrase: 'Reformular',
28
+ proofread: 'Corregir',
29
+ revision_error:
30
+ 'No se pudo revisar el texto. Intenta seleccionar un fragmento más pequeño o edita manualmente.',
31
+ revision_protected_error:
32
+ 'Selecciona texto fuera de elementos de matemáticas, rellenado o glosario antes de revisar.',
25
33
  heading_order_incorrect:
26
34
  'Los títulos deben aplicarse en orden secuencial: H2 debe ir seguido de H3, H4, etc.',
27
35
  wcag_guidelines: '(Directrices WCAG {0})',
@@ -22,6 +22,14 @@ export default {
22
22
  math: 'Matematik',
23
23
  minimize: 'Minimera',
24
24
  read_text_aloud: 'Läs texten högt',
25
+ revise_text: 'Revidera text',
26
+ shorten: 'Förkorta',
27
+ rephrase: 'Omformulera',
28
+ proofread: 'Korrekturläs',
29
+ revision_error:
30
+ 'Det gick inte att revidera texten. Försök markera ett mindre avsnitt eller redigera manuellt.',
31
+ revision_protected_error:
32
+ 'Markera text utanför matematik, lucktexter eller ordlistor innan du reviderar.',
25
33
  heading_order_incorrect:
26
34
  'Rubriker måste tillämpas i sekventiell ordning: H2 ska följas av H3, H4, och så vidare',
27
35
  wcag_guidelines: '(WCAG {0} riktlinjer)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/core",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Windward UI Core Plugins",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
package/plugin.js CHANGED
@@ -18,7 +18,6 @@ import Image from './components/Content/Blocks/Image'
18
18
  import UserUpload from './components/Content/Blocks/UserUpload'
19
19
  import FileDownload from './components/Content/Blocks/FileDownload'
20
20
 
21
- import GlossaryPage from './pages/glossary.vue'
22
21
  import TinymcePlugin from './pages/plugins/tinymce/_plugin.vue'
23
22
 
24
23
  import CourseGlossaryToolNav from './components/Navigation/Items/CourseGlossaryToolNav.vue'
@@ -40,7 +39,6 @@ import EmailSettings from './components/Settings/EmailSettings.vue'
40
39
  import BlockQuoteSettigns from './components/Settings/BlockQuoteSettings.vue'
41
40
  import HorizontalRuleSettings from './components/Settings/HorizontalRuleSettings.vue'
42
41
 
43
- import UserUploadPage from './pages/userUpload.vue'
44
42
  import GlossaryToolTip from './components/utils/glossary/GlossaryToolTip.vue'
45
43
  import TextEditorSettings from './components/Settings/TextEditorSettings.vue'
46
44
  import FillInTheBlanks from './components/utils/FillInBlank/FillInTheBlanksManager.vue'
@@ -71,22 +69,6 @@ export default {
71
69
  },
72
70
  i18n: locales.messages,
73
71
  pages: [
74
- {
75
- page: 'user-uploads',
76
- path: '/course/:course/section/:section/user-uploads',
77
- i18n: 'menu.user-upload',
78
- icon: 'mdi-cloud-upload',
79
- name: 'PluginUserUploadPage',
80
- template: UserUploadPage,
81
- },
82
- {
83
- page: 'glossary',
84
- path: '/course/:course/section/:section/glossary',
85
- i18n: 'windward.core.shared.menu.course_glossary',
86
- icon: 'mdi-comment-text-multiple',
87
- name: 'CourseGlossaryPage',
88
- template: GlossaryPage,
89
- },
90
72
  {
91
73
  page: 'tinymce-plugin',
92
74
  path: '/plugins/tinymce/:plugin',
@@ -116,32 +98,6 @@ export default {
116
98
  },
117
99
  ],
118
100
  menu: [
119
- {
120
- tag: 'core-user-upload-nav',
121
- path: '/course/{course.id}/section/{section.id}/user-uploads',
122
- icon: 'mdi-cloud-upload',
123
- i18n: 'windward.core.components.navigation.user_upload.title',
124
- context: ['course'],
125
- display: ['menu'],
126
- permissions: {
127
- 'windward.organization.course.file': {
128
- writable: true,
129
- },
130
- },
131
- },
132
- {
133
- tag: 'core-user-glossary-nav',
134
- path: '/course/{course.id}/section/{section.id}/glossary',
135
- icon: 'mdi-comment-text-multiple',
136
- i18n: 'windward.core.shared.menu.course_glossary',
137
- context: ['course'],
138
- display: ['menu'],
139
- permissions: {
140
- 'windward.organization.course.content': {
141
- readable: true,
142
- },
143
- },
144
- },
145
101
  {
146
102
  tag: 'core-ask-the-expert',
147
103
  i18n: 'windward.core.shared.menu.ask_the_expert',
package/test/mocks.js CHANGED
@@ -67,8 +67,12 @@ const mocks = {
67
67
  app: {},
68
68
  $router: [],
69
69
  $Validation: {
70
- getRule() {},
71
- getLimit() {},
70
+ getRule() {
71
+ return []
72
+ },
73
+ getLimit() {
74
+ return 0
75
+ },
72
76
  addLimit() {},
73
77
  addRule() {},
74
78
  addLimits() {},