@windward/integrations 0.21.0 → 0.23.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 +12 -0
- package/components/Content/Blocks/ActionPanel/TransformActivity.vue +386 -0
- package/components/Content/Blocks/ActionPanel/TransformBlock.vue +402 -59
- package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +22 -2
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +8 -2
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +4 -0
- package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumer.vue +2 -2
- package/components/Integration/JobTable.vue +35 -3
- package/components/Integration/TranslateCourse.vue +574 -0
- package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +27 -0
- package/i18n/ar-SA/components/ai_agent/chat.ts +20 -0
- package/i18n/ar-SA/components/ai_agent/index.ts +5 -0
- package/i18n/ar-SA/components/content/blocks/action_panel/index.ts +5 -0
- package/i18n/ar-SA/components/content/blocks/action_panel/transform_block.ts +9 -0
- package/i18n/ar-SA/components/content/blocks/external_integration/index.ts +5 -0
- package/i18n/ar-SA/components/content/blocks/external_integration/lti_consumer.ts +17 -0
- package/i18n/ar-SA/components/content/blocks/index.ts +7 -0
- package/i18n/ar-SA/components/content/index.ts +5 -0
- package/i18n/ar-SA/components/external_integration/driver/lti1p1.ts +17 -0
- package/i18n/ar-SA/components/external_integration/driver/lti1p3.ts +25 -0
- package/i18n/ar-SA/components/external_integration/driver/scorm.ts +14 -0
- package/i18n/ar-SA/components/external_integration/index.ts +36 -0
- package/i18n/ar-SA/components/external_integration/provider_target.ts +9 -0
- package/i18n/ar-SA/components/file_import/index.ts +5 -0
- package/i18n/ar-SA/components/file_import/resourcespace.ts +4 -0
- package/i18n/ar-SA/components/index.ts +19 -0
- package/i18n/ar-SA/components/integration/driver.ts +67 -0
- package/i18n/ar-SA/components/integration/index.ts +9 -0
- package/i18n/ar-SA/components/integration/job.ts +25 -0
- package/i18n/ar-SA/components/integration/job_log.ts +24 -0
- package/i18n/ar-SA/components/llm/blooms.ts +15 -0
- package/i18n/ar-SA/components/llm/content_selector.ts +3 -0
- package/i18n/ar-SA/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/ar-SA/components/llm/generate_content/generate_questions.ts +92 -0
- package/i18n/ar-SA/components/llm/generate_content/index.ts +8 -0
- package/i18n/ar-SA/components/llm/index.ts +10 -0
- package/i18n/ar-SA/components/navigation/index.ts +5 -0
- package/i18n/ar-SA/components/navigation/integrations.ts +9 -0
- package/i18n/ar-SA/components/settings/external_integration/index.ts +5 -0
- package/i18n/ar-SA/components/settings/external_integration/lti_consumer.ts +10 -0
- package/i18n/ar-SA/components/settings/index.ts +5 -0
- package/i18n/ar-SA/index.ts +16 -0
- package/i18n/ar-SA/modules/index.ts +5 -0
- package/i18n/ar-SA/pages/admin/index.ts +5 -0
- package/i18n/ar-SA/pages/admin/translateCourse.ts +68 -0
- package/i18n/ar-SA/pages/course/external_integration/index.ts +6 -0
- package/i18n/ar-SA/pages/course/index.ts +5 -0
- package/i18n/ar-SA/pages/importContent.ts +3 -0
- package/i18n/ar-SA/pages/importCourse.ts +13 -0
- package/i18n/ar-SA/pages/index.ts +15 -0
- package/i18n/ar-SA/pages/login/index.ts +9 -0
- package/i18n/ar-SA/pages/login/lti.ts +23 -0
- package/i18n/ar-SA/pages/login/saml.ts +7 -0
- package/i18n/ar-SA/pages/login/scorm.ts +28 -0
- package/i18n/ar-SA/pages/vendor.ts +11 -0
- package/i18n/ar-SA/shared/content_blocks.ts +9 -0
- package/i18n/ar-SA/shared/error.ts +9 -0
- package/i18n/ar-SA/shared/file.ts +5 -0
- package/i18n/ar-SA/shared/index.ts +17 -0
- package/i18n/ar-SA/shared/menu.ts +3 -0
- package/i18n/ar-SA/shared/notification.ts +14 -0
- package/i18n/ar-SA/shared/permission.ts +52 -0
- package/i18n/ar-SA/shared/settings.ts +9 -0
- package/i18n/en-US/components/content/blocks/action_panel/index.ts +2 -0
- package/i18n/en-US/components/content/blocks/action_panel/transform_activity.ts +8 -0
- package/i18n/en-US/components/content/blocks/action_panel/transform_block.ts +1 -1
- package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +1 -0
- package/i18n/en-US/components/integration/job.ts +2 -0
- package/i18n/en-US/components/navigation/integrations.ts +1 -0
- package/i18n/en-US/components/settings/external_integration/lti_consumer.ts +2 -0
- package/i18n/en-US/pages/admin/index.ts +5 -0
- package/i18n/en-US/pages/admin/translateCourse.ts +68 -0
- package/i18n/en-US/pages/index.ts +2 -0
- package/i18n/es-ES/components/content/blocks/action_panel/index.ts +2 -0
- package/i18n/es-ES/components/content/blocks/action_panel/transform_activity.ts +8 -0
- package/i18n/es-ES/components/content/blocks/action_panel/transform_block.ts +1 -1
- package/i18n/es-ES/components/content/blocks/external_integration/lti_consumer.ts +8 -7
- package/i18n/es-ES/components/integration/job.ts +2 -0
- package/i18n/es-ES/components/navigation/integrations.ts +1 -0
- package/i18n/es-ES/components/settings/external_integration/lti_consumer.ts +3 -0
- package/i18n/es-ES/pages/admin/index.ts +5 -0
- package/i18n/es-ES/pages/admin/translateCourse.ts +68 -0
- package/i18n/es-ES/pages/index.ts +2 -0
- package/i18n/fr-FR/components/ai_agent/chat.ts +20 -0
- package/i18n/fr-FR/components/ai_agent/index.ts +5 -0
- package/i18n/fr-FR/components/content/blocks/action_panel/index.ts +5 -0
- package/i18n/fr-FR/components/content/blocks/action_panel/transform_block.ts +9 -0
- package/i18n/fr-FR/components/content/blocks/external_integration/index.ts +5 -0
- package/i18n/fr-FR/components/content/blocks/external_integration/lti_consumer.ts +17 -0
- package/i18n/fr-FR/components/content/blocks/index.ts +7 -0
- package/i18n/fr-FR/components/content/index.ts +5 -0
- package/i18n/fr-FR/components/external_integration/driver/lti1p1.ts +17 -0
- package/i18n/fr-FR/components/external_integration/driver/lti1p3.ts +25 -0
- package/i18n/fr-FR/components/external_integration/driver/scorm.ts +14 -0
- package/i18n/fr-FR/components/external_integration/index.ts +36 -0
- package/i18n/fr-FR/components/external_integration/provider_target.ts +9 -0
- package/i18n/fr-FR/components/file_import/index.ts +5 -0
- package/i18n/fr-FR/components/file_import/resourcespace.ts +4 -0
- package/i18n/fr-FR/components/index.ts +19 -0
- package/i18n/fr-FR/components/integration/driver.ts +67 -0
- package/i18n/fr-FR/components/integration/index.ts +9 -0
- package/i18n/fr-FR/components/integration/job.ts +25 -0
- package/i18n/fr-FR/components/integration/job_log.ts +24 -0
- package/i18n/fr-FR/components/llm/blooms.ts +15 -0
- package/i18n/fr-FR/components/llm/content_selector.ts +3 -0
- package/i18n/fr-FR/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/fr-FR/components/llm/generate_content/generate_questions.ts +92 -0
- package/i18n/fr-FR/components/llm/generate_content/index.ts +8 -0
- package/i18n/fr-FR/components/llm/index.ts +10 -0
- package/i18n/fr-FR/components/navigation/index.ts +5 -0
- package/i18n/fr-FR/components/navigation/integrations.ts +9 -0
- package/i18n/fr-FR/components/settings/external_integration/index.ts +5 -0
- package/i18n/fr-FR/components/settings/external_integration/lti_consumer.ts +10 -0
- package/i18n/fr-FR/components/settings/index.ts +5 -0
- package/i18n/fr-FR/index.ts +16 -0
- package/i18n/fr-FR/modules/index.ts +5 -0
- package/i18n/fr-FR/pages/admin/index.ts +5 -0
- package/i18n/fr-FR/pages/admin/translateCourse.ts +68 -0
- package/i18n/fr-FR/pages/course/external_integration/index.ts +6 -0
- package/i18n/fr-FR/pages/course/index.ts +5 -0
- package/i18n/fr-FR/pages/importContent.ts +3 -0
- package/i18n/fr-FR/pages/importCourse.ts +13 -0
- package/i18n/fr-FR/pages/index.ts +15 -0
- package/i18n/fr-FR/pages/login/index.ts +9 -0
- package/i18n/fr-FR/pages/login/lti.ts +23 -0
- package/i18n/fr-FR/pages/login/saml.ts +7 -0
- package/i18n/fr-FR/pages/login/scorm.ts +28 -0
- package/i18n/fr-FR/pages/vendor.ts +11 -0
- package/i18n/fr-FR/shared/content_blocks.ts +9 -0
- package/i18n/fr-FR/shared/error.ts +9 -0
- package/i18n/fr-FR/shared/file.ts +5 -0
- package/i18n/fr-FR/shared/index.ts +17 -0
- package/i18n/fr-FR/shared/menu.ts +3 -0
- package/i18n/fr-FR/shared/notification.ts +14 -0
- package/i18n/fr-FR/shared/permission.ts +52 -0
- package/i18n/fr-FR/shared/settings.ts +9 -0
- package/i18n/index.ts +4 -0
- package/i18n/sv-SE/components/content/blocks/action_panel/index.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/action_panel/transform_activity.ts +8 -0
- package/i18n/sv-SE/components/content/blocks/action_panel/transform_block.ts +1 -1
- package/i18n/sv-SE/components/content/blocks/external_integration/lti_consumer.ts +9 -8
- package/i18n/sv-SE/components/integration/job.ts +6 -4
- package/i18n/sv-SE/components/navigation/integrations.ts +1 -0
- package/i18n/sv-SE/components/settings/external_integration/lti_consumer.ts +3 -0
- package/i18n/sv-SE/pages/admin/index.ts +5 -0
- package/i18n/sv-SE/pages/admin/translateCourse.ts +68 -0
- package/i18n/sv-SE/pages/index.ts +2 -0
- package/package.json +1 -1
- package/pages/admin/translateCourse.vue +81 -0
- package/plugin.js +32 -0
|
@@ -38,6 +38,9 @@ import { mapGetters } from 'vuex'
|
|
|
38
38
|
import Organization from '~/models/Organization'
|
|
39
39
|
import Course from '~/models/Course'
|
|
40
40
|
|
|
41
|
+
const MIN_SENTENCE_COUNT_FOR_TEXT_TRANSFORM = 4
|
|
42
|
+
const SENTENCES_PER_PARAGRAPH = 2
|
|
43
|
+
|
|
41
44
|
export default {
|
|
42
45
|
props: {
|
|
43
46
|
// The block associated with this action panel
|
|
@@ -71,6 +74,7 @@ export default {
|
|
|
71
74
|
...mapGetters({
|
|
72
75
|
organization: 'organization/get',
|
|
73
76
|
course: 'course/get',
|
|
77
|
+
activeContentBlock: 'contentBlock/get',
|
|
74
78
|
}),
|
|
75
79
|
vModel: {
|
|
76
80
|
get() {
|
|
@@ -80,28 +84,55 @@ export default {
|
|
|
80
84
|
this.$emit('input', value)
|
|
81
85
|
},
|
|
82
86
|
},
|
|
87
|
+
sourceBlock() {
|
|
88
|
+
if (!this.value) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const active = this.activeContentBlock
|
|
93
|
+
if (
|
|
94
|
+
active &&
|
|
95
|
+
typeof active === 'object' &&
|
|
96
|
+
_.get(active, 'id', null) &&
|
|
97
|
+
_.get(active, 'id', null) === _.get(this.value, 'id', null)
|
|
98
|
+
) {
|
|
99
|
+
return active
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.value
|
|
103
|
+
},
|
|
83
104
|
sourceKind() {
|
|
84
|
-
return this.getBlockKindFromTag(_.get(this.
|
|
105
|
+
return this.getBlockKindFromTag(_.get(this.sourceBlock, 'tag', ''))
|
|
85
106
|
},
|
|
86
|
-
|
|
107
|
+
textSentenceCount() {
|
|
87
108
|
if (this.sourceKind !== 'text') {
|
|
88
109
|
return 0
|
|
89
110
|
}
|
|
90
|
-
return this.
|
|
111
|
+
return this.countTextSentences(
|
|
112
|
+
_.get(this.sourceBlock, 'body', ''),
|
|
113
|
+
this.$i18n?.locale
|
|
114
|
+
)
|
|
91
115
|
},
|
|
92
116
|
structuredItemCount() {
|
|
93
|
-
if (
|
|
117
|
+
if (
|
|
118
|
+
!['accordion', 'tab', 'clickable_icons'].includes(
|
|
119
|
+
this.sourceKind
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
94
122
|
return 0
|
|
95
123
|
}
|
|
96
|
-
return this.countStructuredItems(this.
|
|
124
|
+
return this.countStructuredItems(this.sourceBlock)
|
|
97
125
|
},
|
|
98
126
|
availableActions() {
|
|
99
|
-
if (!this.
|
|
127
|
+
if (!this.sourceBlock) {
|
|
100
128
|
return []
|
|
101
129
|
}
|
|
102
130
|
|
|
103
131
|
if (this.sourceKind === 'text') {
|
|
104
|
-
if (
|
|
132
|
+
if (
|
|
133
|
+
this.textSentenceCount <
|
|
134
|
+
MIN_SENTENCE_COUNT_FOR_TEXT_TRANSFORM
|
|
135
|
+
) {
|
|
105
136
|
return []
|
|
106
137
|
}
|
|
107
138
|
return [
|
|
@@ -127,9 +158,6 @@ export default {
|
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
if (this.sourceKind === 'accordion') {
|
|
130
|
-
if (this.structuredItemCount < 2) {
|
|
131
|
-
return []
|
|
132
|
-
}
|
|
133
161
|
const actions = [
|
|
134
162
|
{
|
|
135
163
|
tag: 'content-blocks-text',
|
|
@@ -158,9 +186,6 @@ export default {
|
|
|
158
186
|
}
|
|
159
187
|
|
|
160
188
|
if (this.sourceKind === 'tab') {
|
|
161
|
-
if (this.structuredItemCount < 2) {
|
|
162
|
-
return []
|
|
163
|
-
}
|
|
164
189
|
const actions = [
|
|
165
190
|
{
|
|
166
191
|
tag: 'content-blocks-text',
|
|
@@ -189,9 +214,6 @@ export default {
|
|
|
189
214
|
}
|
|
190
215
|
|
|
191
216
|
if (this.sourceKind === 'clickable_icons') {
|
|
192
|
-
if (this.structuredItemCount < 2) {
|
|
193
|
-
return []
|
|
194
|
-
}
|
|
195
217
|
const actions = [
|
|
196
218
|
{
|
|
197
219
|
tag: 'content-blocks-text',
|
|
@@ -251,26 +273,328 @@ export default {
|
|
|
251
273
|
|
|
252
274
|
return 'unknown'
|
|
253
275
|
},
|
|
254
|
-
|
|
276
|
+
extractTextFromHtml(html) {
|
|
277
|
+
if (!html || typeof html !== 'string') {
|
|
278
|
+
return ''
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (typeof DOMParser === 'undefined') {
|
|
282
|
+
return html.replace(/<[^>]+>/g, ' ')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
287
|
+
return (doc?.body?.textContent || '').trim()
|
|
288
|
+
} catch (_e) {
|
|
289
|
+
return html.replace(/<[^>]+>/g, ' ')
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
segmentSentences(text, locale) {
|
|
293
|
+
const input = typeof text === 'string' ? text : ''
|
|
294
|
+
if (!input.trim()) {
|
|
295
|
+
return []
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
if (typeof Intl !== 'undefined' && Intl.Segmenter) {
|
|
300
|
+
const segmenter = new Intl.Segmenter(locale || undefined, {
|
|
301
|
+
granularity: 'sentence',
|
|
302
|
+
})
|
|
303
|
+
const rawSegments = Array.from(segmenter.segment(input))
|
|
304
|
+
|
|
305
|
+
return rawSegments
|
|
306
|
+
.map((seg, idx) => {
|
|
307
|
+
const start = Number(seg.index || 0)
|
|
308
|
+
const end =
|
|
309
|
+
idx + 1 < rawSegments.length
|
|
310
|
+
? Number(rawSegments[idx + 1].index || 0)
|
|
311
|
+
: input.length
|
|
312
|
+
return {
|
|
313
|
+
start,
|
|
314
|
+
end,
|
|
315
|
+
text: input.slice(start, end),
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
.filter((seg) => seg.text.trim().length > 0)
|
|
319
|
+
}
|
|
320
|
+
} catch (_e) {
|
|
321
|
+
// Ignore and fall back to regex
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Fallback sentence splitter (best-effort)
|
|
325
|
+
const re = /[^.!?]+[.!?]+(?:\s+|$)|[^.!?]+$/g
|
|
326
|
+
const segments = []
|
|
327
|
+
let match = null
|
|
328
|
+
while ((match = re.exec(input)) !== null) {
|
|
329
|
+
const value = match[0] || ''
|
|
330
|
+
if (!value.trim()) {
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
segments.push({
|
|
334
|
+
start: match.index,
|
|
335
|
+
end: match.index + value.length,
|
|
336
|
+
text: value,
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
return segments
|
|
340
|
+
},
|
|
341
|
+
countTextSentences(html, locale) {
|
|
342
|
+
const text = this.extractTextFromHtml(html)
|
|
343
|
+
return this.segmentSentences(text, locale).length
|
|
344
|
+
},
|
|
345
|
+
countTopLevelParagraphs(html) {
|
|
255
346
|
if (!html || typeof html !== 'string') {
|
|
256
347
|
return 0
|
|
257
348
|
}
|
|
258
349
|
|
|
350
|
+
if (typeof DOMParser === 'undefined') {
|
|
351
|
+
return 0
|
|
352
|
+
}
|
|
353
|
+
|
|
259
354
|
try {
|
|
260
355
|
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
356
|
+
const body = doc?.body
|
|
357
|
+
if (!body) {
|
|
358
|
+
return 0
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const paragraphs = Array.from(body.children).filter((el) => {
|
|
362
|
+
const tag = (el.tagName || '').toLowerCase()
|
|
363
|
+
if (tag !== 'p' && tag !== 'li') {
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
return (el.textContent || '').trim().length > 0
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
return paragraphs.length
|
|
264
370
|
} catch (_e) {
|
|
265
|
-
|
|
266
|
-
const normalized = html
|
|
267
|
-
.replace(/<[^>]+>/g, '\n')
|
|
268
|
-
.split(/\n+/)
|
|
269
|
-
.map((p) => p.trim())
|
|
270
|
-
.filter((p) => p.length > 0)
|
|
271
|
-
return normalized.length
|
|
371
|
+
return 0
|
|
272
372
|
}
|
|
273
373
|
},
|
|
374
|
+
resolveTextOffset(textNodes, targetOffset) {
|
|
375
|
+
let offset = Math.max(0, Math.trunc(Number(targetOffset) || 0))
|
|
376
|
+
|
|
377
|
+
for (const node of textNodes) {
|
|
378
|
+
const len = (node.nodeValue || '').length
|
|
379
|
+
if (offset <= len) {
|
|
380
|
+
return { node, offset }
|
|
381
|
+
}
|
|
382
|
+
offset -= len
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const last = textNodes[textNodes.length - 1]
|
|
386
|
+
return { node: last, offset: (last?.nodeValue || '').length }
|
|
387
|
+
},
|
|
388
|
+
splitHtmlIntoParagraphs(html, locale) {
|
|
389
|
+
if (!html || typeof html !== 'string') {
|
|
390
|
+
return null
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (typeof DOMParser === 'undefined') {
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
398
|
+
const body = doc?.body
|
|
399
|
+
if (!body) {
|
|
400
|
+
return null
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const hasMeaningfulTextOutsideElements = Array.from(
|
|
404
|
+
body.childNodes
|
|
405
|
+
).some(
|
|
406
|
+
(node) =>
|
|
407
|
+
node.nodeType === Node.TEXT_NODE &&
|
|
408
|
+
(node.textContent || '').trim()
|
|
409
|
+
)
|
|
410
|
+
const elementChildren = Array.from(body.children)
|
|
411
|
+
|
|
412
|
+
let container = null
|
|
413
|
+
if (
|
|
414
|
+
elementChildren.length === 1 &&
|
|
415
|
+
!hasMeaningfulTextOutsideElements
|
|
416
|
+
) {
|
|
417
|
+
container = elementChildren[0]
|
|
418
|
+
} else if (
|
|
419
|
+
elementChildren.length === 0 &&
|
|
420
|
+
(body.textContent || '').trim()
|
|
421
|
+
) {
|
|
422
|
+
container = body
|
|
423
|
+
} else {
|
|
424
|
+
return null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const containerTag =
|
|
428
|
+
container === body
|
|
429
|
+
? 'body'
|
|
430
|
+
: (container.tagName || '').toLowerCase()
|
|
431
|
+
const disallowedContainers = new Set([
|
|
432
|
+
'ul',
|
|
433
|
+
'ol',
|
|
434
|
+
'table',
|
|
435
|
+
'tbody',
|
|
436
|
+
'thead',
|
|
437
|
+
'tr',
|
|
438
|
+
'td',
|
|
439
|
+
'th',
|
|
440
|
+
])
|
|
441
|
+
if (disallowedContainers.has(containerTag)) {
|
|
442
|
+
return null
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Avoid creating nested paragraphs by splitting inside containers that already have paragraphs
|
|
446
|
+
if (container !== body && containerTag !== 'p') {
|
|
447
|
+
if (
|
|
448
|
+
typeof container.querySelector === 'function' &&
|
|
449
|
+
container.querySelector('p')
|
|
450
|
+
) {
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const text = container.textContent || ''
|
|
456
|
+
const sentences = this.segmentSentences(text, locale)
|
|
457
|
+
if (sentences.length < MIN_SENTENCE_COUNT_FOR_TEXT_TRANSFORM) {
|
|
458
|
+
return null
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const ranges = []
|
|
462
|
+
for (
|
|
463
|
+
let i = 0;
|
|
464
|
+
i < sentences.length;
|
|
465
|
+
i += SENTENCES_PER_PARAGRAPH
|
|
466
|
+
) {
|
|
467
|
+
const start = sentences[i].start
|
|
468
|
+
const end =
|
|
469
|
+
sentences[
|
|
470
|
+
Math.min(
|
|
471
|
+
i + SENTENCES_PER_PARAGRAPH - 1,
|
|
472
|
+
sentences.length - 1
|
|
473
|
+
)
|
|
474
|
+
].end
|
|
475
|
+
ranges.push({ start, end })
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const walker = doc.createTreeWalker(container, NodeFilter.SHOW_TEXT)
|
|
479
|
+
const textNodes = []
|
|
480
|
+
let current = walker.nextNode()
|
|
481
|
+
while (current) {
|
|
482
|
+
if ((current.nodeValue || '').length > 0) {
|
|
483
|
+
textNodes.push(current)
|
|
484
|
+
}
|
|
485
|
+
current = walker.nextNode()
|
|
486
|
+
}
|
|
487
|
+
if (textNodes.length === 0) {
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const paragraphs = []
|
|
492
|
+
for (const { start, end } of ranges) {
|
|
493
|
+
const startPos = this.resolveTextOffset(textNodes, start)
|
|
494
|
+
const endPos = this.resolveTextOffset(textNodes, end)
|
|
495
|
+
if (!startPos?.node || !endPos?.node) {
|
|
496
|
+
continue
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const range = doc.createRange()
|
|
500
|
+
range.setStart(startPos.node, startPos.offset)
|
|
501
|
+
range.setEnd(endPos.node, endPos.offset)
|
|
502
|
+
|
|
503
|
+
const fragment = range.cloneContents()
|
|
504
|
+
const p = doc.createElement('p')
|
|
505
|
+
p.appendChild(fragment)
|
|
506
|
+
|
|
507
|
+
if ((p.textContent || '').trim().length > 0) {
|
|
508
|
+
paragraphs.push(p)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (paragraphs.length < 2) {
|
|
513
|
+
return null
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
body.innerHTML = ''
|
|
517
|
+
paragraphs.forEach((p) => body.appendChild(p))
|
|
518
|
+
return body.innerHTML
|
|
519
|
+
},
|
|
520
|
+
prepareHtmlForBlockTransform(html, locale) {
|
|
521
|
+
if (!html || typeof html !== 'string') {
|
|
522
|
+
throw new Error('invalid_html')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const sentenceCount = this.countTextSentences(html, locale)
|
|
526
|
+
if (sentenceCount < MIN_SENTENCE_COUNT_FOR_TEXT_TRANSFORM) {
|
|
527
|
+
throw new Error('insufficient_content')
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// If the microservice will see enough top-level paragraphs, don't touch it.
|
|
531
|
+
if (this.countTopLevelParagraphs(html) >= 2) {
|
|
532
|
+
return html
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Unwrap a single container (common when editors wrap content)
|
|
536
|
+
if (typeof DOMParser !== 'undefined') {
|
|
537
|
+
try {
|
|
538
|
+
let currentHtml = html
|
|
539
|
+
for (let i = 0; i < 2; i++) {
|
|
540
|
+
if (this.countTopLevelParagraphs(currentHtml) >= 2) {
|
|
541
|
+
return currentHtml
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const doc = new DOMParser().parseFromString(
|
|
545
|
+
currentHtml,
|
|
546
|
+
'text/html'
|
|
547
|
+
)
|
|
548
|
+
const body = doc?.body
|
|
549
|
+
if (!body) {
|
|
550
|
+
break
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const hasMeaningfulTextOutsideElements = Array.from(
|
|
554
|
+
body.childNodes
|
|
555
|
+
).some(
|
|
556
|
+
(node) =>
|
|
557
|
+
node.nodeType === Node.TEXT_NODE &&
|
|
558
|
+
(node.textContent || '').trim()
|
|
559
|
+
)
|
|
560
|
+
const elementChildren = Array.from(body.children)
|
|
561
|
+
|
|
562
|
+
if (
|
|
563
|
+
elementChildren.length === 1 &&
|
|
564
|
+
!hasMeaningfulTextOutsideElements
|
|
565
|
+
) {
|
|
566
|
+
const container = elementChildren[0]
|
|
567
|
+
const tag = (container.tagName || '').toLowerCase()
|
|
568
|
+
if (
|
|
569
|
+
tag === 'div' ||
|
|
570
|
+
tag === 'section' ||
|
|
571
|
+
tag === 'article'
|
|
572
|
+
) {
|
|
573
|
+
currentHtml = container.innerHTML || ''
|
|
574
|
+
continue
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
break
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const splitHtml = this.splitHtmlIntoParagraphs(
|
|
582
|
+
currentHtml,
|
|
583
|
+
locale
|
|
584
|
+
)
|
|
585
|
+
if (
|
|
586
|
+
splitHtml &&
|
|
587
|
+
this.countTopLevelParagraphs(splitHtml) >= 2
|
|
588
|
+
) {
|
|
589
|
+
return splitHtml
|
|
590
|
+
}
|
|
591
|
+
} catch (_e) {
|
|
592
|
+
// Ignore and fall through to error
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
throw new Error('insufficient_content')
|
|
597
|
+
},
|
|
274
598
|
countStructuredItems(block) {
|
|
275
599
|
const items = _.get(block, 'metadata.config.items', [])
|
|
276
600
|
return Array.isArray(items) ? items.length : 0
|
|
@@ -321,6 +645,7 @@ export default {
|
|
|
321
645
|
const normalizedOrder = Number.isFinite(baseOrder)
|
|
322
646
|
? Math.trunc(baseOrder)
|
|
323
647
|
: 0
|
|
648
|
+
|
|
324
649
|
const newBlock = {
|
|
325
650
|
id: _.uniqueId('create_content_block_'),
|
|
326
651
|
content_id: null,
|
|
@@ -354,7 +679,9 @@ export default {
|
|
|
354
679
|
|
|
355
680
|
const title = typeof config.title === 'string' ? config.title : ''
|
|
356
681
|
const instructions =
|
|
357
|
-
typeof config.instructions === 'string'
|
|
682
|
+
typeof config.instructions === 'string'
|
|
683
|
+
? config.instructions
|
|
684
|
+
: ''
|
|
358
685
|
const displayTitle = _.isBoolean(config.display_title)
|
|
359
686
|
? config.display_title
|
|
360
687
|
: true
|
|
@@ -375,7 +702,9 @@ export default {
|
|
|
375
702
|
typeof rawItem.content === 'string'
|
|
376
703
|
? rawItem.content
|
|
377
704
|
: '',
|
|
378
|
-
imageConfig: this.normalizeImageConfig(
|
|
705
|
+
imageConfig: this.normalizeImageConfig(
|
|
706
|
+
rawItem.fileConfig
|
|
707
|
+
),
|
|
379
708
|
}
|
|
380
709
|
}
|
|
381
710
|
|
|
@@ -389,19 +718,27 @@ export default {
|
|
|
389
718
|
typeof rawItem.content === 'string'
|
|
390
719
|
? rawItem.content
|
|
391
720
|
: '',
|
|
392
|
-
imageConfig: this.normalizeImageConfig(
|
|
721
|
+
imageConfig: this.normalizeImageConfig(
|
|
722
|
+
rawItem.imageAsset
|
|
723
|
+
),
|
|
393
724
|
}
|
|
394
725
|
}
|
|
395
726
|
|
|
396
727
|
if (kind === 'clickable_icons') {
|
|
397
|
-
const fileConfig = this.normalizeImageConfig(
|
|
728
|
+
const fileConfig = this.normalizeImageConfig(
|
|
729
|
+
rawItem.fileConfig
|
|
730
|
+
)
|
|
398
731
|
const iconImage = rawItem.iconImage === true
|
|
399
732
|
|
|
400
733
|
return {
|
|
401
734
|
title:
|
|
402
|
-
typeof rawItem.title === 'string'
|
|
735
|
+
typeof rawItem.title === 'string'
|
|
736
|
+
? rawItem.title
|
|
737
|
+
: '',
|
|
403
738
|
bodyHtml:
|
|
404
|
-
typeof rawItem.body === 'string'
|
|
739
|
+
typeof rawItem.body === 'string'
|
|
740
|
+
? rawItem.body
|
|
741
|
+
: '',
|
|
405
742
|
imageConfig:
|
|
406
743
|
iconImage || this.hasAssetInImageConfig(fileConfig)
|
|
407
744
|
? fileConfig
|
|
@@ -432,7 +769,8 @@ export default {
|
|
|
432
769
|
}
|
|
433
770
|
|
|
434
771
|
structured.items.forEach((item) => {
|
|
435
|
-
const itemTitle =
|
|
772
|
+
const itemTitle =
|
|
773
|
+
typeof item.title === 'string' ? item.title : ''
|
|
436
774
|
const bodyHtml =
|
|
437
775
|
typeof item.bodyHtml === 'string' ? item.bodyHtml : ''
|
|
438
776
|
|
|
@@ -618,15 +956,15 @@ export default {
|
|
|
618
956
|
'windward.core.components.settings.clickable_icon.information'
|
|
619
957
|
),
|
|
620
958
|
display: {
|
|
621
|
-
show_title:
|
|
622
|
-
show_background:
|
|
959
|
+
show_title: true,
|
|
960
|
+
show_background: true,
|
|
623
961
|
round_icon: false,
|
|
624
962
|
italic_icon: false,
|
|
625
963
|
large_icon: false,
|
|
626
964
|
autocolor: true,
|
|
627
965
|
},
|
|
628
966
|
items: items.map((item) => ({
|
|
629
|
-
icon: '',
|
|
967
|
+
icon: 'mdi-star',
|
|
630
968
|
fileConfig: {},
|
|
631
969
|
iconImage: false,
|
|
632
970
|
title: item.title || '',
|
|
@@ -642,14 +980,11 @@ export default {
|
|
|
642
980
|
return newBlock
|
|
643
981
|
},
|
|
644
982
|
async generateMultiItemBlockFromText(targetType) {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
983
|
+
const sourceBlock = this.sourceBlock
|
|
984
|
+
const html = _.get(sourceBlock, 'body', '')
|
|
985
|
+
const locale = this.$i18n?.locale
|
|
649
986
|
|
|
650
|
-
|
|
651
|
-
throw new Error('insufficient_paragraphs')
|
|
652
|
-
}
|
|
987
|
+
const preparedHtml = this.prepareHtmlForBlockTransform(html, locale)
|
|
653
988
|
|
|
654
989
|
const organizationId = _.get(this.organization, 'id', null)
|
|
655
990
|
const courseId = _.get(this.course, 'id', null)
|
|
@@ -671,9 +1006,9 @@ export default {
|
|
|
671
1006
|
}
|
|
672
1007
|
|
|
673
1008
|
const payload = {
|
|
674
|
-
html,
|
|
1009
|
+
html: preparedHtml,
|
|
675
1010
|
target_tag: targetType,
|
|
676
|
-
language:
|
|
1011
|
+
language: locale,
|
|
677
1012
|
}
|
|
678
1013
|
|
|
679
1014
|
const requestConfig = request._reqConfig(
|
|
@@ -692,7 +1027,7 @@ export default {
|
|
|
692
1027
|
throw new Error('invalid_response')
|
|
693
1028
|
}
|
|
694
1029
|
|
|
695
|
-
return this.buildTransformedBlock(
|
|
1030
|
+
return this.buildTransformedBlock(sourceBlock, data, targetType)
|
|
696
1031
|
},
|
|
697
1032
|
async performBlockTransform(e, targetType) {
|
|
698
1033
|
e.stopPropagation()
|
|
@@ -701,7 +1036,9 @@ export default {
|
|
|
701
1036
|
return
|
|
702
1037
|
}
|
|
703
1038
|
|
|
704
|
-
|
|
1039
|
+
const sourceBlock = this.sourceBlock
|
|
1040
|
+
|
|
1041
|
+
if (!sourceBlock || !sourceBlock.tag) {
|
|
705
1042
|
return
|
|
706
1043
|
}
|
|
707
1044
|
|
|
@@ -717,19 +1054,16 @@ export default {
|
|
|
717
1054
|
newBlock = await this.generateMultiItemBlockFromText(
|
|
718
1055
|
targetType
|
|
719
1056
|
)
|
|
1057
|
+
} else if (targetType === 'content-blocks-text') {
|
|
1058
|
+
newBlock = this.buildTextBlockFromStructured(sourceBlock)
|
|
720
1059
|
} else {
|
|
721
|
-
if (this.countStructuredItems(
|
|
1060
|
+
if (this.countStructuredItems(sourceBlock) < 2) {
|
|
722
1061
|
throw new Error('insufficient_items')
|
|
723
1062
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
newBlock = this.buildStructuredBlockFromStructured(
|
|
729
|
-
this.value,
|
|
730
|
-
targetType
|
|
731
|
-
)
|
|
732
|
-
}
|
|
1063
|
+
newBlock = this.buildStructuredBlockFromStructured(
|
|
1064
|
+
sourceBlock,
|
|
1065
|
+
targetType
|
|
1066
|
+
)
|
|
733
1067
|
}
|
|
734
1068
|
|
|
735
1069
|
if (!newBlock) {
|
|
@@ -746,8 +1080,17 @@ export default {
|
|
|
746
1080
|
// eslint-disable-next-line no-console
|
|
747
1081
|
console.error('Block transform failed', e)
|
|
748
1082
|
|
|
1083
|
+
const reason = _.get(
|
|
1084
|
+
e,
|
|
1085
|
+
'response.data.error.details.reason',
|
|
1086
|
+
''
|
|
1087
|
+
)
|
|
1088
|
+
|
|
749
1089
|
if (this.$toast) {
|
|
750
|
-
if (
|
|
1090
|
+
if (
|
|
1091
|
+
String(e?.message) === 'insufficient_content' ||
|
|
1092
|
+
reason === 'NOT_ENOUGH_PARAGRAPHS'
|
|
1093
|
+
) {
|
|
751
1094
|
this.$toast.error(
|
|
752
1095
|
this.$t(
|
|
753
1096
|
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_insufficient_paragraphs'
|
|
@@ -85,6 +85,15 @@
|
|
|
85
85
|
:value="value"
|
|
86
86
|
/>
|
|
87
87
|
</v-form>
|
|
88
|
+
<v-alert v-if="launchError" type="error">
|
|
89
|
+
<p>
|
|
90
|
+
{{
|
|
91
|
+
$t(
|
|
92
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.unknown_error'
|
|
93
|
+
)
|
|
94
|
+
}}
|
|
95
|
+
</p>
|
|
96
|
+
</v-alert>
|
|
88
97
|
<iframe
|
|
89
98
|
v-if="
|
|
90
99
|
block.metadata.config.launch_type === 'inline' &&
|
|
@@ -112,14 +121,22 @@
|
|
|
112
121
|
</template>
|
|
113
122
|
<template #form="{ on, attrs }">
|
|
114
123
|
<iframe
|
|
115
|
-
v-if="launched"
|
|
124
|
+
v-if="!launchError && launched"
|
|
116
125
|
v-bind="attrs"
|
|
117
126
|
:name="frameId"
|
|
118
127
|
class="launch-frame"
|
|
119
128
|
v-on="on"
|
|
120
129
|
></iframe>
|
|
121
|
-
|
|
122
130
|
<div v-else></div>
|
|
131
|
+
<v-alert v-if="launchError" type="error">
|
|
132
|
+
<p>
|
|
133
|
+
{{
|
|
134
|
+
$t(
|
|
135
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.unknown_error'
|
|
136
|
+
)
|
|
137
|
+
}}
|
|
138
|
+
</p>
|
|
139
|
+
</v-alert>
|
|
123
140
|
</template>
|
|
124
141
|
</DialogBox>
|
|
125
142
|
</div>
|
|
@@ -184,6 +201,7 @@ export default {
|
|
|
184
201
|
launched: false,
|
|
185
202
|
launchTimeout: {},
|
|
186
203
|
openModal: false,
|
|
204
|
+
launchError: false,
|
|
187
205
|
}
|
|
188
206
|
},
|
|
189
207
|
async fetch() {
|
|
@@ -288,6 +306,7 @@ export default {
|
|
|
288
306
|
}
|
|
289
307
|
},
|
|
290
308
|
async onLaunch() {
|
|
309
|
+
this.launchError = false
|
|
291
310
|
try {
|
|
292
311
|
// Clear the launch data in-case we're in inline mode
|
|
293
312
|
// If the iframe isn't destroyed subsequent launches will be new windows instead of the target iframe
|
|
@@ -330,6 +349,7 @@ export default {
|
|
|
330
349
|
)
|
|
331
350
|
}
|
|
332
351
|
} catch (e) {
|
|
352
|
+
this.launchError = true
|
|
333
353
|
// eslint-disable-next-line no-console
|
|
334
354
|
console.error('LTI Link Launch Fail', e)
|
|
335
355
|
}
|