@windward/core 0.24.0 → 0.25.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/CHANGELOG.md CHANGED
@@ -1,87 +1,98 @@
1
1
  # Changelog
2
2
 
3
- ## Release [0.24.0] - 2025-10-06
3
+ ## Hotfix [0.25.1] - 2025-11-07
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 feature/LE-2108/revise-text-fix (pull request #453)
6
+ * Merged in feature/LE-2123/open-res-gen-fix (pull request #452)
11
7
 
12
8
 
13
- ## Release [0.23.0] - 2025-09-18
9
+ ## Release [0.25.0] - 2025-10-29
14
10
 
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
11
+ * Merged in bugfix/LE-2067-scorm-provider (pull request #451)
12
+ * Merged in feature/LE-2108/revise-text (pull request #445)
13
+ * Merge remote-tracking branch 'origin/release/0.24.0' into release/0.25.0
14
+ * Merged in feature/LE-2123/open-res-gen (pull request #446)
15
+ * Merged in feature/LE-2127-increase-clickable-icon-item-tit (pull request #450)
16
+ * Merged in feature/LE-2157-remove-user-uploads-and-glossary (pull request #449)
25
17
 
26
18
 
27
- ## Release [0.22.0] - 2025-08-28
19
+ ## Release [0.24.0] - 2025-10-06
28
20
 
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
21
+ - Merged in feature/LE-2123/open-res-gen (pull request #446)
22
+ - Merged in bugfix/LE-2134-email-block-show-hide-title (pull request #444)
23
+ - Merged in feature/LE-2097-tabs-and-accordions-hide-backgro (pull request #436)
24
+ - Merged release/0.24.0 into feature/LE-2097-tabs-and-accordions-hide-backgro
25
+ - Merged in bugfix/LE-1841-all-blocks-save-or-cancel-change (pull request #440)
26
+ - Merged release/0.24.0 into bugfix/LE-1841-all-blocks-save-or-cancel-change
27
+ - Merged in feature/LE-2100-edit-in-text-area-update-text-fi (pull request #435)
44
28
 
29
+ ## Release [0.23.0] - 2025-09-18
45
30
 
46
- ## Hotfix [0.21.1] created - 2025-08-20
31
+ - Merged in feature/LE-2099-track-engagement-on-videos-from- (pull request #439)
32
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #438)
33
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #437)
34
+ - Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #434)
35
+ - Merged in bugfix/MIND-6075-decouple-generateaiquestionbut (pull request #432)
36
+ - Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #433)
37
+ - Merged in bugfix/LE-2052-clickable-icon-text-color-should (pull request #426)
38
+ - Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #430)
39
+ - Merged release/0.23.0 into bugfix/LE-2071-vimeo-videos-not-working
40
+ - Merged release/0.22.0 into bugfix/LE-2052-clickable-icon-text-color-should
47
41
 
42
+ ## Release [0.22.0] - 2025-08-28
48
43
 
49
- ## Release [0.21.0] - 2025-08-01
44
+ - Merge branch 'master' into release/0.22.0
45
+ - Merged in bugfix/LE-2075-the-panel-of-the-text-block-isnt (pull request #427)
46
+ - Merged in bug-fix/LE-2060/llm-character-limit-validation (pull request #421)
47
+ - Merged in bugfix/LE-2031-open-response-download-collate-q (pull request #422)
48
+ - Merged in bugfix/LE-1960-video-using-urls-as-a-source (pull request #423)
49
+ - Merged in hotfix/0.21.1 (pull request #425)
50
+ - Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
51
+ - Merged in bugfix/LE-2057-save-button-disappeared-again-on (pull request #419)
52
+ - Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
53
+ - Merged in feature/LE-2036/word-jumble-gen (pull request #420)
54
+ - Merged in feature/LE-1997/scenario-gen (pull request #416)
55
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #415)
56
+ - Merged in release/0.21.0 (pull request #401)
57
+ - Merged release/0.21.0 into bugfix/LE-1960-video-using-urls-as-a-source
58
+ - Merged release/0.21.0 into bugfix/LE-1928-user-upload-allowed-file-types
59
+
60
+ ## Hotfix [0.21.1] created - 2025-08-20
50
61
 
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
62
+ ## Release [0.21.0] - 2025-08-01
69
63
 
64
+ - Merged in feature/LE-2045/lableing-fixes (pull request #414)
65
+ - Merged in feature/LE-2009-organizations-custom-color-palle (pull request #410)
66
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #411)
67
+ - Merged in feature/LE-1912/sorting-game-gen-2 (pull request #413)
68
+ - Merged in feature/LE-2029-open-response-block-design (pull request #412)
69
+ - Merged in feature/LE-1912/sorting-game-gen (pull request #409)
70
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #408)
71
+ - Merged in feature/LE-1924-clickable-icons-ux (pull request #407)
72
+ - Merged in feature/LE-1913/matching-block (pull request #405)
73
+ - Merged in feature/LE-1924-clickable-icons-ux (pull request #404)
74
+ - Merged in feature/LE-2011-open-response-download-field-to- (pull request #406)
75
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #398)
76
+ - Merged release/0.21.0 into feature/LE-2011-open-response-download-field-to-
77
+ - Merge branch 'release/0.21.0' into feature/LE-1913/matching-block
78
+ - Merged in bugfix/LE-1990-video-black-line-on-left-and-rig (pull request #403)
79
+ - Merged in feature/LE-1900-fill-in-the-blank-formatting-sho (pull request #402)
80
+ - Merged release/0.21.0 into feature/LE-1924-clickable-icons-ux
81
+ - Merge branch 'release/0.20.0' into feature/LE-1913/matching-block
70
82
 
71
83
  ## Release [0.20.0] - 2025-07-01
72
84
 
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
-
85
+ - Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
86
+ - Merge remote-tracking branch 'origin/release/0.20.0' into release/0.20.0
87
+ - Merged in feature/LE-1948/empty-bucket-localization-fix (pull request #397)
88
+ - Merged in LE-1911-email-block-to-cc-and-subject-te (pull request #395)
89
+ - Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #391)
90
+ - Merged in bugfix/LE-1941-divider-dashed-line-renders-soli (pull request #396)
91
+ - Merged in feature/LE-1948/empty-bucket (pull request #393)
92
+ - Merged in feature/LE-1906-ai-assistant-button-text (pull request #394)
93
+ - Merged in bugfix/LE-1739-email-block-to-cc-and-subject-te (pull request #392)
94
+ - Merged in bugfix/LE-1917-student-experience-cleanup (pull request #386)
95
+ - Merged release/0.19.0 into bugfix/LE-1917-student-experience-cleanup
85
96
 
86
97
  ### Release [0.19.0] created - 2025-05-30
87
98
 
@@ -3,7 +3,8 @@
3
3
  <v-container
4
4
  v-if="
5
5
  block.metadata.config.title ||
6
- block.metadata.config.instructions
6
+ block.metadata.config.instructions ||
7
+ block.metadata.config.starting_text
7
8
  "
8
9
  class="pa-0"
9
10
  >
@@ -24,6 +25,14 @@
24
25
  >
25
26
  {{ block.metadata.config.instructions }}
26
27
  </p>
28
+
29
+ <p
30
+ v-if="block.metadata.config.starting_text"
31
+ tabindex="0"
32
+ class="pt-3"
33
+ >
34
+ {{ block.metadata.config.starting_text }}
35
+ </p>
27
36
  </v-container>
28
37
  <div v-if="stateLoaded">
29
38
  <div v-if="block.body && !submitted">
@@ -173,6 +182,7 @@ export default {
173
182
  }
174
183
  if (_.isEmpty(this.block.metadata.config.starting_text)) {
175
184
  this.block.metadata.config.starting_text = ''
185
+
176
186
  }
177
187
  },
178
188
  mounted() {},
@@ -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,304 @@ 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
+ const findBlockAncestor = (node) => {
614
+ try {
615
+ if (!node) return null
616
+ const body = editor ? editor.getBody() : null
617
+ let current = node
618
+ const isBlock = (n) => {
619
+ if (!n || n.nodeType !== Node.ELEMENT_NODE) return false
620
+ const name = n.nodeName || ''
621
+ return /^(P|DIV|LI|UL|OL|H1|H2|H3|H4|H5|H6)$/.test(name)
622
+ }
623
+ while (current && current !== body) {
624
+ if (isBlock(current)) return current
625
+ current = current.parentNode
626
+ }
627
+ return null
628
+ } catch (_e) {
629
+ return null
630
+ }
631
+ }
632
+
633
+ if (useSelection) {
634
+ const range = selection.getRng()
635
+ if (
636
+ this.selectionContainsProtected(range.startContainer) ||
637
+ this.selectionContainsProtected(range.endContainer)
638
+ ) {
639
+ if (this.$toast) {
640
+ this.$toast.error(
641
+ this.$t(
642
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_protected_error'
643
+ )
644
+ )
645
+ }
646
+
647
+ return
648
+ }
649
+ }
650
+
651
+ const { organizationId, courseId } = this.getRevisionContext()
652
+
653
+ if (!organizationId || !courseId) {
654
+ if (this.$toast) {
655
+ this.$toast.error(
656
+ this.$t(
657
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
658
+ )
659
+ )
660
+ }
661
+
662
+ return
663
+ }
664
+
665
+ const tone = this.nextTone(operation)
666
+ const payload = {
667
+ operation,
668
+ html: targetHtml,
669
+ language: this.$i18n.locale,
670
+ }
671
+
672
+ if (tone) {
673
+ payload.tone = tone
674
+ }
675
+
676
+ const revisionRequest = new Course()
677
+ revisionRequest.custom(
678
+ new Organization({ id: organizationId }),
679
+ new Course({ id: courseId }),
680
+ 'llm-revise'
681
+ )
682
+
683
+ const resourcePath = revisionRequest._customResource
684
+
685
+ if (!resourcePath) {
686
+ if (this.$toast) {
687
+ this.$toast.error(
688
+ this.$t(
689
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
690
+ )
691
+ )
692
+ }
693
+
694
+ this.isRevising = false
695
+ editor.setProgressState(false)
696
+
697
+ return
698
+ }
699
+
700
+ this.isRevising = true
701
+ editor.setProgressState(true)
702
+ let bookmark = null
703
+ if (!useSelection && selection) {
704
+ bookmark = selection.getBookmark(2, true)
705
+ }
706
+
707
+ try {
708
+ const requestConfig = revisionRequest._reqConfig(
709
+ {
710
+ method: 'POST',
711
+ url: `${revisionRequest.baseURL()}/${resourcePath}`,
712
+ data: payload,
713
+ },
714
+ { forceMethod: true }
715
+ )
716
+
717
+ const response = await revisionRequest.request(requestConfig)
718
+ const responseData = response?.data || response
719
+
720
+ if (!responseData || !responseData.html) {
721
+ throw new Error('missing_html')
722
+ }
723
+
724
+ editor.undoManager.transact(() => {
725
+ if (useSelection) {
726
+ try {
727
+ const rng = selection.getRng()
728
+ const startBlock = findBlockAncestor(
729
+ rng ? rng.startContainer : null
730
+ )
731
+ const endBlock = findBlockAncestor(
732
+ rng ? rng.endContainer : null
733
+ )
734
+
735
+ // If selection spans multiple block elements, expand to full blocks
736
+ if (
737
+ startBlock &&
738
+ endBlock &&
739
+ startBlock !== endBlock
740
+ ) {
741
+ const expanded = editor.dom.createRng()
742
+ expanded.setStartBefore(startBlock)
743
+ expanded.setEndAfter(endBlock)
744
+ editor.selection.setRng(expanded)
745
+ }
746
+ } catch (_e) {
747
+ // If expansion fails, fall back to current selection
748
+ }
749
+
750
+ // Wrap response with temporary markers so we can reselect inserted content
751
+ const startId = `ww-revise-start-${this.seed}-${Date.now()}-${Math.random()
752
+ .toString(36)
753
+ .slice(2)}`
754
+ const endId = `ww-revise-end-${this.seed}-${Date.now()}-${Math.random()
755
+ .toString(36)
756
+ .slice(2)}`
757
+ const wrappedHtml =
758
+ `<span id="${startId}" data-ww-revise="s"></span>` +
759
+ responseData.html +
760
+ `<span id="${endId}" data-ww-revise="e"></span>`
761
+
762
+ editor.selection.setContent(wrappedHtml)
763
+
764
+ // Try to reselect the inserted content and remove markers
765
+ try {
766
+ const doc = editor.getDoc()
767
+ const startEl = doc.getElementById(startId)
768
+ const endEl = doc.getElementById(endId)
769
+ if (startEl && endEl) {
770
+ const selectRange = editor.dom.createRng()
771
+ // Select everything between markers
772
+ if (selectRange.setStartAfter && selectRange.setEndBefore) {
773
+ selectRange.setStartAfter(startEl)
774
+ selectRange.setEndBefore(endEl)
775
+ } else {
776
+ // Fallback to parent/offset if needed
777
+ const startParent = startEl.parentNode
778
+ const endParent = endEl.parentNode
779
+ const childIndex = (node) => {
780
+ let i = 0
781
+ let cur = node
782
+ while (cur && cur.previousSibling) {
783
+ i++
784
+ cur = cur.previousSibling
785
+ }
786
+ return i
787
+ }
788
+ const startIndex = childIndex(startEl) + 1
789
+ const endIndex = childIndex(endEl)
790
+ selectRange.setStart(startParent, startIndex)
791
+ selectRange.setEnd(endParent, endIndex)
792
+ }
793
+ editor.selection.setRng(selectRange)
794
+
795
+ // Remove markers after selection is set
796
+ if (startEl.parentNode) startEl.parentNode.removeChild(startEl)
797
+ if (endEl.parentNode) endEl.parentNode.removeChild(endEl)
798
+
799
+ }
800
+ } catch (_e) {
801
+ // Ignore selection restoration errors
802
+ }
803
+ } else {
804
+ editor.setContent(responseData.html)
805
+ }
806
+ })
807
+
808
+ editor.fire('change')
809
+
810
+ if (!useSelection && bookmark) {
811
+ try {
812
+ editor.selection.moveToBookmark(bookmark)
813
+ } catch (_error) {
814
+ editor.selection.select(editor.getBody(), true)
815
+ editor.selection.collapse(false)
816
+ }
817
+ }
818
+ } catch (error) {
819
+ console.error('TinyMCE revision failed', error)
820
+ if (this.$toast) {
821
+ this.$toast.error(
822
+ this.$t(
823
+ 'windward.core.components.utils.tiny_mce_wrapper.revision_error'
824
+ )
825
+ )
826
+ }
827
+ } finally {
828
+ this.isRevising = false
829
+ editor.setProgressState(false)
830
+ }
831
+ },
522
832
  },
523
833
  }
524
834
  </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.1",
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() {},