@windward/integrations 0.20.0 → 0.21.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 +8 -0
- package/components/Content/Blocks/ActionPanel/TransformBlock.vue +518 -91
- package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +18 -12
- package/helpers/ExternalIntegration/ScormHelper.ts +1 -1
- package/i18n/en-US/components/content/blocks/action_panel/transform_block.ts +2 -0
- package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +2 -2
- package/i18n/es-ES/components/content/blocks/action_panel/transform_block.ts +2 -0
- package/i18n/es-ES/components/content/blocks/external_integration/lti_consumer.ts +2 -2
- package/i18n/sv-SE/components/content/blocks/action_panel/transform_block.ts +2 -0
- package/i18n/sv-SE/components/content/blocks/external_integration/lti_consumer.ts +2 -2
- package/package.json +1 -1
- package/plugin.js +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.21.0] - 2026-01-12
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-221/block-transform-additions (pull request #127)
|
|
6
|
+
* Merged in feature/LE-2218/endless-spinner (pull request #126)
|
|
7
|
+
* Merge branch 'feature/LE-2218/endless-spinner' into feature/LE-221/block-transform-additions
|
|
8
|
+
* Merged in feature/LE-2155-consistent-ui-for-adding-new-blo (pull request #124)
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
## Release [0.20.0] - 2025-12-03
|
|
4
12
|
|
|
5
13
|
* Merged in feature/LE-2177-switch-block-type-for-text-heavy (pull request #118)
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
</template>
|
|
20
20
|
|
|
21
21
|
<v-list-item
|
|
22
|
-
v-for="action in
|
|
22
|
+
v-for="action in availableActions"
|
|
23
23
|
:key="action.tag"
|
|
24
24
|
:disabled="isTransforming"
|
|
25
25
|
@click="performBlockTransform($event, action.tag)"
|
|
@@ -65,26 +65,6 @@ export default {
|
|
|
65
65
|
return {
|
|
66
66
|
showMenu: false,
|
|
67
67
|
isTransforming: false,
|
|
68
|
-
actions: [
|
|
69
|
-
{
|
|
70
|
-
tag: 'plugin-core-accordion',
|
|
71
|
-
label: this.$t(
|
|
72
|
-
'windward.core.shared.content_blocks.title.accordion'
|
|
73
|
-
),
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
tag: 'plugin-core-tab',
|
|
77
|
-
label: this.$t(
|
|
78
|
-
'windward.core.shared.content_blocks.title.tab'
|
|
79
|
-
),
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
tag: 'plugin-core-clickable-icons',
|
|
83
|
-
label: this.$t(
|
|
84
|
-
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
85
|
-
),
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
68
|
}
|
|
89
69
|
},
|
|
90
70
|
computed: {
|
|
@@ -100,59 +80,478 @@ export default {
|
|
|
100
80
|
this.$emit('input', value)
|
|
101
81
|
},
|
|
102
82
|
},
|
|
83
|
+
sourceKind() {
|
|
84
|
+
return this.getBlockKindFromTag(_.get(this.value, 'tag', ''))
|
|
85
|
+
},
|
|
86
|
+
textParagraphCount() {
|
|
87
|
+
if (this.sourceKind !== 'text') {
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
return this.countTextParagraphs(_.get(this.value, 'body', ''))
|
|
91
|
+
},
|
|
92
|
+
structuredItemCount() {
|
|
93
|
+
if (!['accordion', 'tab', 'clickable_icons'].includes(this.sourceKind)) {
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
return this.countStructuredItems(this.value)
|
|
97
|
+
},
|
|
98
|
+
availableActions() {
|
|
99
|
+
if (!this.value) {
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (this.sourceKind === 'text') {
|
|
104
|
+
if (this.textParagraphCount < 2) {
|
|
105
|
+
return []
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
tag: 'plugin-core-accordion',
|
|
110
|
+
label: this.$t(
|
|
111
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
tag: 'plugin-core-tab',
|
|
116
|
+
label: this.$t(
|
|
117
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
tag: 'plugin-core-clickable-icons',
|
|
122
|
+
label: this.$t(
|
|
123
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.sourceKind === 'accordion') {
|
|
130
|
+
if (this.structuredItemCount < 2) {
|
|
131
|
+
return []
|
|
132
|
+
}
|
|
133
|
+
const actions = [
|
|
134
|
+
{
|
|
135
|
+
tag: 'content-blocks-text',
|
|
136
|
+
label: this.$t(
|
|
137
|
+
'components.content.blocks.text.block_title'
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
if (this.structuredItemCount >= 2) {
|
|
142
|
+
actions.push(
|
|
143
|
+
{
|
|
144
|
+
tag: 'plugin-core-tab',
|
|
145
|
+
label: this.$t(
|
|
146
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
tag: 'plugin-core-clickable-icons',
|
|
151
|
+
label: this.$t(
|
|
152
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
153
|
+
),
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
return actions
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.sourceKind === 'tab') {
|
|
161
|
+
if (this.structuredItemCount < 2) {
|
|
162
|
+
return []
|
|
163
|
+
}
|
|
164
|
+
const actions = [
|
|
165
|
+
{
|
|
166
|
+
tag: 'content-blocks-text',
|
|
167
|
+
label: this.$t(
|
|
168
|
+
'components.content.blocks.text.block_title'
|
|
169
|
+
),
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
if (this.structuredItemCount >= 2) {
|
|
173
|
+
actions.push(
|
|
174
|
+
{
|
|
175
|
+
tag: 'plugin-core-accordion',
|
|
176
|
+
label: this.$t(
|
|
177
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
178
|
+
),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
tag: 'plugin-core-clickable-icons',
|
|
182
|
+
label: this.$t(
|
|
183
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
184
|
+
),
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
return actions
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (this.sourceKind === 'clickable_icons') {
|
|
192
|
+
if (this.structuredItemCount < 2) {
|
|
193
|
+
return []
|
|
194
|
+
}
|
|
195
|
+
const actions = [
|
|
196
|
+
{
|
|
197
|
+
tag: 'content-blocks-text',
|
|
198
|
+
label: this.$t(
|
|
199
|
+
'components.content.blocks.text.block_title'
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
if (this.structuredItemCount >= 2) {
|
|
204
|
+
actions.push(
|
|
205
|
+
{
|
|
206
|
+
tag: 'plugin-core-accordion',
|
|
207
|
+
label: this.$t(
|
|
208
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
209
|
+
),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
tag: 'plugin-core-tab',
|
|
213
|
+
label: this.$t(
|
|
214
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
215
|
+
),
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
return actions
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return []
|
|
223
|
+
},
|
|
103
224
|
canTransformBlock() {
|
|
104
|
-
|
|
105
|
-
|
|
225
|
+
return this.availableActions.length > 0
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
methods: {
|
|
229
|
+
onClickShowTransforms(e) {
|
|
230
|
+
e.stopPropagation()
|
|
231
|
+
},
|
|
232
|
+
getBlockKindFromTag(tag) {
|
|
233
|
+
if (!tag || typeof tag !== 'string') {
|
|
234
|
+
return 'unknown'
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (tag === 'content-blocks-text') {
|
|
238
|
+
return 'text'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const normalized = tag.replace(/^plugin-/, '')
|
|
242
|
+
if (normalized === 'core-accordion') {
|
|
243
|
+
return 'accordion'
|
|
244
|
+
}
|
|
245
|
+
if (normalized === 'core-tab') {
|
|
246
|
+
return 'tab'
|
|
247
|
+
}
|
|
248
|
+
if (normalized === 'core-clickable-icons') {
|
|
249
|
+
return 'clickable_icons'
|
|
106
250
|
}
|
|
107
251
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
252
|
+
return 'unknown'
|
|
253
|
+
},
|
|
254
|
+
countTextParagraphs(html) {
|
|
255
|
+
if (!html || typeof html !== 'string') {
|
|
256
|
+
return 0
|
|
111
257
|
}
|
|
112
258
|
|
|
113
|
-
let paragraphCount = 0
|
|
114
259
|
try {
|
|
115
|
-
const doc = new DOMParser().parseFromString(
|
|
260
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
116
261
|
const paragraphs = Array.from(doc.querySelectorAll('p, li'))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
).length
|
|
262
|
+
return paragraphs.filter((el) => (el.textContent || '').trim())
|
|
263
|
+
.length
|
|
120
264
|
} catch (_e) {
|
|
121
|
-
// Fallback: simple split on
|
|
122
|
-
const normalized =
|
|
265
|
+
// Fallback: simple split on line breaks after stripping tags
|
|
266
|
+
const normalized = html
|
|
123
267
|
.replace(/<[^>]+>/g, '\n')
|
|
124
268
|
.split(/\n+/)
|
|
125
269
|
.map((p) => p.trim())
|
|
126
270
|
.filter((p) => p.length > 0)
|
|
127
|
-
|
|
271
|
+
return normalized.length
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
countStructuredItems(block) {
|
|
275
|
+
const items = _.get(block, 'metadata.config.items', [])
|
|
276
|
+
return Array.isArray(items) ? items.length : 0
|
|
277
|
+
},
|
|
278
|
+
escapeHtml(text) {
|
|
279
|
+
return String(text == null ? '' : text)
|
|
280
|
+
.replace(/&/g, '&')
|
|
281
|
+
.replace(/</g, '<')
|
|
282
|
+
.replace(/>/g, '>')
|
|
283
|
+
.replace(/"/g, '"')
|
|
284
|
+
.replace(/'/g, ''')
|
|
285
|
+
},
|
|
286
|
+
normalizeAssetReference(asset) {
|
|
287
|
+
if (!asset) {
|
|
288
|
+
return null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof asset === 'string') {
|
|
292
|
+
return { file_asset_id: asset }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (typeof asset === 'object') {
|
|
296
|
+
return asset
|
|
128
297
|
}
|
|
129
298
|
|
|
130
|
-
return
|
|
299
|
+
return null
|
|
131
300
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
301
|
+
normalizeImageConfig(rawConfig) {
|
|
302
|
+
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const config = _.cloneDeep(rawConfig)
|
|
307
|
+
if (typeof config.asset === 'string') {
|
|
308
|
+
config.asset = this.normalizeAssetReference(config.asset)
|
|
309
|
+
} else if (config.asset && typeof config.asset === 'object') {
|
|
310
|
+
config.asset = this.normalizeAssetReference(config.asset)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return config
|
|
136
314
|
},
|
|
137
|
-
|
|
138
|
-
|
|
315
|
+
hasAssetInImageConfig(imageConfig) {
|
|
316
|
+
return !!_.get(imageConfig, 'asset.file_asset_id', null)
|
|
317
|
+
},
|
|
318
|
+
buildNewBlockBase(sourceBlock) {
|
|
319
|
+
const baseOrderRaw = _.get(sourceBlock, 'order', 0)
|
|
320
|
+
const baseOrder = Number(baseOrderRaw)
|
|
321
|
+
const normalizedOrder = Number.isFinite(baseOrder)
|
|
322
|
+
? Math.trunc(baseOrder)
|
|
323
|
+
: 0
|
|
139
324
|
const newBlock = {
|
|
140
325
|
id: _.uniqueId('create_content_block_'),
|
|
141
326
|
content_id: null,
|
|
142
327
|
tag: '',
|
|
143
328
|
body: '',
|
|
144
329
|
status: 'draft',
|
|
145
|
-
order:
|
|
330
|
+
order: normalizedOrder,
|
|
146
331
|
type: {
|
|
147
332
|
save_state: false,
|
|
148
333
|
trackable: false,
|
|
149
334
|
completable: false,
|
|
150
335
|
},
|
|
336
|
+
assets: _.cloneDeep(_.get(sourceBlock, 'assets', [])),
|
|
151
337
|
metadata: {
|
|
152
338
|
config: {},
|
|
153
339
|
},
|
|
154
340
|
}
|
|
155
341
|
|
|
342
|
+
const display = _.cloneDeep(
|
|
343
|
+
_.get(sourceBlock, 'metadata.display', null)
|
|
344
|
+
)
|
|
345
|
+
if (display) {
|
|
346
|
+
newBlock.metadata.display = display
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return newBlock
|
|
350
|
+
},
|
|
351
|
+
extractStructuredBlockData(sourceBlock) {
|
|
352
|
+
const kind = this.getBlockKindFromTag(_.get(sourceBlock, 'tag', ''))
|
|
353
|
+
const config = _.get(sourceBlock, 'metadata.config', {}) || {}
|
|
354
|
+
|
|
355
|
+
const title = typeof config.title === 'string' ? config.title : ''
|
|
356
|
+
const instructions =
|
|
357
|
+
typeof config.instructions === 'string' ? config.instructions : ''
|
|
358
|
+
const displayTitle = _.isBoolean(config.display_title)
|
|
359
|
+
? config.display_title
|
|
360
|
+
: true
|
|
361
|
+
|
|
362
|
+
const rawItems = Array.isArray(config.items) ? config.items : []
|
|
363
|
+
const items = rawItems.map((rawItem) => {
|
|
364
|
+
if (!rawItem || typeof rawItem !== 'object') {
|
|
365
|
+
return { title: '', bodyHtml: '', imageConfig: null }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (kind === 'accordion') {
|
|
369
|
+
return {
|
|
370
|
+
title:
|
|
371
|
+
typeof rawItem.header === 'string'
|
|
372
|
+
? rawItem.header
|
|
373
|
+
: '',
|
|
374
|
+
bodyHtml:
|
|
375
|
+
typeof rawItem.content === 'string'
|
|
376
|
+
? rawItem.content
|
|
377
|
+
: '',
|
|
378
|
+
imageConfig: this.normalizeImageConfig(rawItem.fileConfig),
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (kind === 'tab') {
|
|
383
|
+
return {
|
|
384
|
+
title:
|
|
385
|
+
typeof rawItem.tabHeader === 'string'
|
|
386
|
+
? rawItem.tabHeader
|
|
387
|
+
: '',
|
|
388
|
+
bodyHtml:
|
|
389
|
+
typeof rawItem.content === 'string'
|
|
390
|
+
? rawItem.content
|
|
391
|
+
: '',
|
|
392
|
+
imageConfig: this.normalizeImageConfig(rawItem.imageAsset),
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (kind === 'clickable_icons') {
|
|
397
|
+
const fileConfig = this.normalizeImageConfig(rawItem.fileConfig)
|
|
398
|
+
const iconImage = rawItem.iconImage === true
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
title:
|
|
402
|
+
typeof rawItem.title === 'string' ? rawItem.title : '',
|
|
403
|
+
bodyHtml:
|
|
404
|
+
typeof rawItem.body === 'string' ? rawItem.body : '',
|
|
405
|
+
imageConfig:
|
|
406
|
+
iconImage || this.hasAssetInImageConfig(fileConfig)
|
|
407
|
+
? fileConfig
|
|
408
|
+
: null,
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return { title: '', bodyHtml: '', imageConfig: null }
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
kind,
|
|
417
|
+
title,
|
|
418
|
+
instructions,
|
|
419
|
+
displayTitle,
|
|
420
|
+
items,
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
buildTextBlockFromStructured(sourceBlock) {
|
|
424
|
+
const structured = this.extractStructuredBlockData(sourceBlock)
|
|
425
|
+
const parts = []
|
|
426
|
+
|
|
427
|
+
if (structured.displayTitle && structured.title) {
|
|
428
|
+
parts.push(`<h2>${this.escapeHtml(structured.title)}</h2>`)
|
|
429
|
+
}
|
|
430
|
+
if (structured.instructions) {
|
|
431
|
+
parts.push(`<p>${this.escapeHtml(structured.instructions)}</p>`)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
structured.items.forEach((item) => {
|
|
435
|
+
const itemTitle = typeof item.title === 'string' ? item.title : ''
|
|
436
|
+
const bodyHtml =
|
|
437
|
+
typeof item.bodyHtml === 'string' ? item.bodyHtml : ''
|
|
438
|
+
|
|
439
|
+
if (itemTitle) {
|
|
440
|
+
parts.push(`<h3>${this.escapeHtml(itemTitle)}</h3>`)
|
|
441
|
+
}
|
|
442
|
+
if (bodyHtml) {
|
|
443
|
+
parts.push(bodyHtml)
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
448
|
+
newBlock.tag = 'content-blocks-text'
|
|
449
|
+
newBlock.body = parts.join('')
|
|
450
|
+
newBlock.metadata.config = { expand: false }
|
|
451
|
+
return newBlock
|
|
452
|
+
},
|
|
453
|
+
buildStructuredBlockFromStructured(sourceBlock, targetType) {
|
|
454
|
+
const structured = this.extractStructuredBlockData(sourceBlock)
|
|
455
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
456
|
+
|
|
457
|
+
if (targetType === 'plugin-core-accordion') {
|
|
458
|
+
newBlock.tag = 'plugin-core-accordion'
|
|
459
|
+
newBlock.body = this.$t(
|
|
460
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
461
|
+
)
|
|
462
|
+
newBlock.metadata.config = {
|
|
463
|
+
title: structured.title,
|
|
464
|
+
display_title: structured.displayTitle,
|
|
465
|
+
instructions: structured.instructions,
|
|
466
|
+
items: structured.items.map((item) => ({
|
|
467
|
+
header: item.title || '',
|
|
468
|
+
expand: false,
|
|
469
|
+
content: item.bodyHtml || '',
|
|
470
|
+
fileConfig: item.imageConfig || {
|
|
471
|
+
display: {
|
|
472
|
+
width: 100,
|
|
473
|
+
margin: '',
|
|
474
|
+
padding: '',
|
|
475
|
+
},
|
|
476
|
+
hideBackground: true,
|
|
477
|
+
},
|
|
478
|
+
})),
|
|
479
|
+
}
|
|
480
|
+
return newBlock
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (targetType === 'plugin-core-tab') {
|
|
484
|
+
newBlock.tag = 'plugin-core-tab'
|
|
485
|
+
newBlock.body = this.$t(
|
|
486
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
487
|
+
)
|
|
488
|
+
newBlock.metadata.config = {
|
|
489
|
+
title: structured.title,
|
|
490
|
+
display_title: structured.displayTitle,
|
|
491
|
+
instructions: structured.instructions,
|
|
492
|
+
currentTab: 0,
|
|
493
|
+
items: structured.items.map((item) => ({
|
|
494
|
+
tabHeader: item.title || '',
|
|
495
|
+
expand: false,
|
|
496
|
+
content: item.bodyHtml || '',
|
|
497
|
+
imageAsset: item.imageConfig || {
|
|
498
|
+
display: {
|
|
499
|
+
width: 100,
|
|
500
|
+
margin: '',
|
|
501
|
+
padding: '',
|
|
502
|
+
},
|
|
503
|
+
hideBackground: true,
|
|
504
|
+
},
|
|
505
|
+
})),
|
|
506
|
+
}
|
|
507
|
+
return newBlock
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (targetType === 'plugin-core-clickable-icons') {
|
|
511
|
+
newBlock.tag = 'plugin-core-clickable-icons'
|
|
512
|
+
newBlock.body = this.$t(
|
|
513
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
514
|
+
)
|
|
515
|
+
newBlock.metadata.config = {
|
|
516
|
+
title: structured.title,
|
|
517
|
+
display_title: structured.displayTitle,
|
|
518
|
+
instructions: structured.instructions,
|
|
519
|
+
description: this.$t(
|
|
520
|
+
'windward.core.components.settings.clickable_icon.information'
|
|
521
|
+
),
|
|
522
|
+
display: {
|
|
523
|
+
show_title: false,
|
|
524
|
+
show_background: false,
|
|
525
|
+
round_icon: false,
|
|
526
|
+
italic_icon: false,
|
|
527
|
+
large_icon: false,
|
|
528
|
+
autocolor: true,
|
|
529
|
+
},
|
|
530
|
+
items: structured.items.map((item) => {
|
|
531
|
+
const imageConfig = item.imageConfig
|
|
532
|
+
const hasAsset = this.hasAssetInImageConfig(imageConfig)
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
icon: '',
|
|
536
|
+
fileConfig: hasAsset ? imageConfig : {},
|
|
537
|
+
iconImage: hasAsset,
|
|
538
|
+
title: item.title || '',
|
|
539
|
+
body: item.bodyHtml || '',
|
|
540
|
+
color: {
|
|
541
|
+
class: '',
|
|
542
|
+
},
|
|
543
|
+
active: false,
|
|
544
|
+
}
|
|
545
|
+
}),
|
|
546
|
+
}
|
|
547
|
+
return newBlock
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
throw new Error(`Unsupported targetType: ${targetType}`)
|
|
551
|
+
},
|
|
552
|
+
buildTransformedBlock(sourceBlock, data, targetType) {
|
|
553
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
554
|
+
|
|
156
555
|
const blockTitle = data.block_title || ''
|
|
157
556
|
const items = Array.isArray(data.items) ? data.items : []
|
|
158
557
|
|
|
@@ -242,30 +641,21 @@ export default {
|
|
|
242
641
|
|
|
243
642
|
return newBlock
|
|
244
643
|
},
|
|
245
|
-
async
|
|
246
|
-
e.stopPropagation()
|
|
247
|
-
|
|
644
|
+
async generateMultiItemBlockFromText(targetType) {
|
|
248
645
|
const html = _.get(this.value, 'body', '')
|
|
249
646
|
if (!html || typeof html !== 'string') {
|
|
250
|
-
|
|
647
|
+
throw new Error('invalid_html')
|
|
251
648
|
}
|
|
252
649
|
|
|
253
|
-
if (this.
|
|
254
|
-
|
|
650
|
+
if (this.countTextParagraphs(html) < 2) {
|
|
651
|
+
throw new Error('insufficient_paragraphs')
|
|
255
652
|
}
|
|
256
653
|
|
|
257
654
|
const organizationId = _.get(this.organization, 'id', null)
|
|
258
655
|
const courseId = _.get(this.course, 'id', null)
|
|
259
656
|
|
|
260
657
|
if (!organizationId || !courseId) {
|
|
261
|
-
|
|
262
|
-
this.$toast.error(
|
|
263
|
-
this.$t(
|
|
264
|
-
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
|
|
265
|
-
)
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
return
|
|
658
|
+
throw new Error('missing_context')
|
|
269
659
|
}
|
|
270
660
|
|
|
271
661
|
const request = new Course()
|
|
@@ -277,14 +667,7 @@ export default {
|
|
|
277
667
|
|
|
278
668
|
const resourcePath = request._customResource
|
|
279
669
|
if (!resourcePath) {
|
|
280
|
-
|
|
281
|
-
this.$toast.error(
|
|
282
|
-
this.$t(
|
|
283
|
-
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
|
|
284
|
-
)
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
return
|
|
670
|
+
throw new Error('missing_resource')
|
|
288
671
|
}
|
|
289
672
|
|
|
290
673
|
const payload = {
|
|
@@ -293,39 +676,68 @@ export default {
|
|
|
293
676
|
language: this.$i18n.locale,
|
|
294
677
|
}
|
|
295
678
|
|
|
679
|
+
const requestConfig = request._reqConfig(
|
|
680
|
+
{
|
|
681
|
+
method: 'POST',
|
|
682
|
+
url: `${request.baseURL()}/${resourcePath}`,
|
|
683
|
+
data: payload,
|
|
684
|
+
},
|
|
685
|
+
{ forceMethod: true }
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
const response = await request.request(requestConfig)
|
|
689
|
+
const data = response?.data || response
|
|
690
|
+
|
|
691
|
+
if (!data || !Array.isArray(data.items) || data.items.length < 2) {
|
|
692
|
+
throw new Error('invalid_response')
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return this.buildTransformedBlock(this.value, data, targetType)
|
|
696
|
+
},
|
|
697
|
+
async performBlockTransform(e, targetType) {
|
|
698
|
+
e.stopPropagation()
|
|
699
|
+
|
|
700
|
+
if (this.isTransforming) {
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (!this.value || !this.value.tag) {
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
296
708
|
this.isTransforming = true
|
|
297
709
|
|
|
298
710
|
try {
|
|
299
|
-
|
|
300
|
-
{
|
|
301
|
-
method: 'POST',
|
|
302
|
-
url: `${request.baseURL()}/${resourcePath}`,
|
|
303
|
-
data: payload,
|
|
304
|
-
},
|
|
305
|
-
{ forceMethod: true }
|
|
306
|
-
)
|
|
711
|
+
let newBlock = null
|
|
307
712
|
|
|
308
|
-
|
|
309
|
-
|
|
713
|
+
if (this.sourceKind === 'text') {
|
|
714
|
+
if (targetType === 'content-blocks-text') {
|
|
715
|
+
return
|
|
716
|
+
}
|
|
717
|
+
newBlock = await this.generateMultiItemBlockFromText(
|
|
718
|
+
targetType
|
|
719
|
+
)
|
|
720
|
+
} else {
|
|
721
|
+
if (this.countStructuredItems(this.value) < 2) {
|
|
722
|
+
throw new Error('insufficient_items')
|
|
723
|
+
}
|
|
310
724
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
725
|
+
if (targetType === 'content-blocks-text') {
|
|
726
|
+
newBlock = this.buildTextBlockFromStructured(this.value)
|
|
727
|
+
} else {
|
|
728
|
+
newBlock = this.buildStructuredBlockFromStructured(
|
|
729
|
+
this.value,
|
|
730
|
+
targetType
|
|
731
|
+
)
|
|
732
|
+
}
|
|
317
733
|
}
|
|
318
734
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
targetType
|
|
323
|
-
)
|
|
735
|
+
if (!newBlock) {
|
|
736
|
+
throw new Error('invalid_new_block')
|
|
737
|
+
}
|
|
324
738
|
|
|
325
|
-
// Let the host content page own block insertion via the global event
|
|
326
739
|
this.$eb.$emit('create:content-block', newBlock)
|
|
327
740
|
|
|
328
|
-
// Immediately focus the new block for the user
|
|
329
741
|
const activeBlock = _.cloneDeep(newBlock)
|
|
330
742
|
this.$ContentService.setContentBlock(activeBlock)
|
|
331
743
|
this.$eb.$emit('block:focus', activeBlock)
|
|
@@ -333,12 +745,27 @@ export default {
|
|
|
333
745
|
// Surface errors instead of failing silently
|
|
334
746
|
// eslint-disable-next-line no-console
|
|
335
747
|
console.error('Block transform failed', e)
|
|
748
|
+
|
|
336
749
|
if (this.$toast) {
|
|
337
|
-
|
|
338
|
-
this.$
|
|
339
|
-
|
|
750
|
+
if (String(e?.message) === 'insufficient_paragraphs') {
|
|
751
|
+
this.$toast.error(
|
|
752
|
+
this.$t(
|
|
753
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_insufficient_paragraphs'
|
|
754
|
+
)
|
|
340
755
|
)
|
|
341
|
-
)
|
|
756
|
+
} else if (String(e?.message) === 'insufficient_items') {
|
|
757
|
+
this.$toast.error(
|
|
758
|
+
this.$t(
|
|
759
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_insufficient_items'
|
|
760
|
+
)
|
|
761
|
+
)
|
|
762
|
+
} else {
|
|
763
|
+
this.$toast.error(
|
|
764
|
+
this.$t(
|
|
765
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
|
|
766
|
+
)
|
|
767
|
+
)
|
|
768
|
+
}
|
|
342
769
|
}
|
|
343
770
|
} finally {
|
|
344
771
|
this.isTransforming = false
|
|
@@ -7,18 +7,6 @@
|
|
|
7
7
|
<br />
|
|
8
8
|
<v-divider class="primary"></v-divider>
|
|
9
9
|
<br />
|
|
10
|
-
<v-select
|
|
11
|
-
v-model="block.metadata.config.launch_type"
|
|
12
|
-
:label="
|
|
13
|
-
$t(
|
|
14
|
-
'windward.integrations.components.settings.external_integration.lti_consumer.launch_type'
|
|
15
|
-
)
|
|
16
|
-
"
|
|
17
|
-
:items="launchTypes"
|
|
18
|
-
item-text="name"
|
|
19
|
-
item-value="value"
|
|
20
|
-
:disabled="render"
|
|
21
|
-
></v-select>
|
|
22
10
|
{{
|
|
23
11
|
$t(
|
|
24
12
|
'windward.integrations.components.settings.external_integration.lti_consumer.link_select'
|
|
@@ -65,6 +53,18 @@
|
|
|
65
53
|
</v-list-item>
|
|
66
54
|
</v-list-item-group>
|
|
67
55
|
</v-list>
|
|
56
|
+
<v-select
|
|
57
|
+
v-model="block.metadata.config.launch_type"
|
|
58
|
+
:label="
|
|
59
|
+
$t(
|
|
60
|
+
'windward.integrations.components.settings.external_integration.lti_consumer.launch_type'
|
|
61
|
+
)
|
|
62
|
+
"
|
|
63
|
+
:items="launchTypes"
|
|
64
|
+
item-text="name"
|
|
65
|
+
item-value="value"
|
|
66
|
+
:disabled="render"
|
|
67
|
+
></v-select>
|
|
68
68
|
</v-container>
|
|
69
69
|
</template>
|
|
70
70
|
|
|
@@ -75,6 +75,7 @@ import Course from '~/models/Course'
|
|
|
75
75
|
import Organization from '~/models/Organization'
|
|
76
76
|
import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
|
|
77
77
|
import Consumer from '../../../models/ExternalIntegration/Consumer'
|
|
78
|
+
import _ from 'lodash'
|
|
78
79
|
|
|
79
80
|
export default {
|
|
80
81
|
name: 'ContentBlockExternalIntegrationLti1p1ConsumerSettings',
|
|
@@ -161,6 +162,11 @@ export default {
|
|
|
161
162
|
if (!_.isBoolean(this.block.metadata.config.display_title)) {
|
|
162
163
|
this.$set(this.block.metadata.config, 'display_title', true)
|
|
163
164
|
}
|
|
165
|
+
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
166
|
+
this.block.metadata.config.title = this.$t(
|
|
167
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.title'
|
|
168
|
+
)
|
|
169
|
+
}
|
|
164
170
|
},
|
|
165
171
|
mounted() {},
|
|
166
172
|
methods: {},
|
|
@@ -2,6 +2,8 @@ export default {
|
|
|
2
2
|
change_block_type: 'Change block type',
|
|
3
3
|
change_block_type_insufficient_paragraphs:
|
|
4
4
|
'Change block type is available when the text has at least two paragraphs.',
|
|
5
|
+
change_block_type_insufficient_items:
|
|
6
|
+
'Change block type is available when the block has at least two items.',
|
|
5
7
|
change_block_type_error:
|
|
6
8
|
'Unable to change block type. Try again or edit manually.',
|
|
7
9
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
title: 'LTI
|
|
2
|
+
title: 'LTI Link',
|
|
3
3
|
instructions:
|
|
4
4
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fringilla ipsum eget ante tempus blandit. Maecenas vel massa\n' +
|
|
5
5
|
'nec tellus vestibulum porttitor non a enim.',
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
launch_in_new_window: 'launch {0} in new tab',
|
|
9
9
|
launch_in_modal: 'launch {0} in modal',
|
|
10
10
|
configure_warning:
|
|
11
|
-
'
|
|
11
|
+
'Select an available LTI link to get started. Then, configure the desired launch type.',
|
|
12
12
|
no_access:
|
|
13
13
|
'You do not have permissions to launch this link. Please contact your system administrator.',
|
|
14
14
|
missing_tool: 'The tool id {0} is missing',
|
|
@@ -2,6 +2,8 @@ export default {
|
|
|
2
2
|
change_block_type: 'Cambiar tipo de bloque',
|
|
3
3
|
change_block_type_insufficient_paragraphs:
|
|
4
4
|
'Cambiar el tipo de bloque está disponible cuando el texto tiene al menos dos párrafos.',
|
|
5
|
+
change_block_type_insufficient_items:
|
|
6
|
+
'Cambiar el tipo de bloque está disponible cuando el bloque tiene al menos dos elementos.',
|
|
5
7
|
change_block_type_error:
|
|
6
8
|
'No se puede cambiar el tipo de bloque. Inténtalo de nuevo o edítalo manualmente.',
|
|
7
9
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
title: 'LTI
|
|
2
|
+
title: 'Enlace LTI',
|
|
3
3
|
instructions:
|
|
4
4
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fringilla ipsum eget ante tempus blandit. Maecenas vel massa\n' +
|
|
5
5
|
'nec tellus vestibulum porttitor non a enim.',
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
launch_in_new_window: 'lansera {0} i ny flik',
|
|
9
9
|
launch_in_modal: 'lansera {0} i modal',
|
|
10
10
|
configure_warning:
|
|
11
|
-
'
|
|
11
|
+
'Seleccione un enlace LTI disponible para comenzar. Luego, configure el tipo de lanzamiento deseado.',
|
|
12
12
|
no_access:
|
|
13
13
|
'Du har inte behörighet att starta den här länken. Kontakta din systemadministratör.',
|
|
14
14
|
missing_tool: 'Verktygs-ID {0} saknas',
|
|
@@ -2,6 +2,8 @@ export default {
|
|
|
2
2
|
change_block_type: 'Ändra blocktyp',
|
|
3
3
|
change_block_type_insufficient_paragraphs:
|
|
4
4
|
'Ändra blocktyp är tillgängligt när texten har minst två stycken.',
|
|
5
|
+
change_block_type_insufficient_items:
|
|
6
|
+
'Ändra blocktyp är tillgängligt när blocket har minst två objekt.',
|
|
5
7
|
change_block_type_error:
|
|
6
8
|
'Kunde inte byta blocktyp. Försök igen eller ändra manuellt.',
|
|
7
9
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
title: 'LTI
|
|
2
|
+
title: 'LTI-länk',
|
|
3
3
|
instructions:
|
|
4
4
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fringilla ipsum eget ante tempus blandit. Maecenas vel massa\n' +
|
|
5
5
|
'nec tellus vestibulum porttitor non a enim.',
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
launch_in_new_window: 'launch {0} in new tab',
|
|
9
9
|
launch_in_modal: 'launch {0} in modal',
|
|
10
10
|
configure_warning:
|
|
11
|
-
'
|
|
11
|
+
'Välj en tillgänglig LTI-länk för att komma igång. Konfigurera sedan önskad starttyp.',
|
|
12
12
|
no_access:
|
|
13
13
|
'You do not have permissions to launch this link. Please contact your system administrator.',
|
|
14
14
|
missing_tool: 'The tool id {0} is missing',
|
package/package.json
CHANGED
package/plugin.js
CHANGED
|
@@ -318,7 +318,12 @@ export default {
|
|
|
318
318
|
{
|
|
319
319
|
tag: 'windward-integrations-action-panel-transform-block',
|
|
320
320
|
template: ActionPanelTransformBlock,
|
|
321
|
-
context: [
|
|
321
|
+
context: [
|
|
322
|
+
'block.content-blocks-text',
|
|
323
|
+
'block.core-accordion',
|
|
324
|
+
'block.core-tab',
|
|
325
|
+
'block.core-clickable-icons',
|
|
326
|
+
],
|
|
322
327
|
},
|
|
323
328
|
],
|
|
324
329
|
courseSetting: [
|