@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 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 actions"
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
- if (!this.value || this.value.tag !== 'content-blocks-text') {
105
- return false
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
- const body = _.get(this.value, 'body', '')
109
- if (!body || typeof body !== 'string') {
110
- return false
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(body, 'text/html')
260
+ const doc = new DOMParser().parseFromString(html, 'text/html')
116
261
  const paragraphs = Array.from(doc.querySelectorAll('p, li'))
117
- paragraphCount = paragraphs.filter((el) =>
118
- (el.textContent || '').trim()
119
- ).length
262
+ return paragraphs.filter((el) => (el.textContent || '').trim())
263
+ .length
120
264
  } catch (_e) {
121
- // Fallback: simple split on double line breaks
122
- const normalized = body
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
- paragraphCount = normalized.length
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, '&amp;')
281
+ .replace(/</g, '&lt;')
282
+ .replace(/>/g, '&gt;')
283
+ .replace(/"/g, '&quot;')
284
+ .replace(/'/g, '&#39;')
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 paragraphCount >= 2
299
+ return null
131
300
  },
132
- },
133
- methods: {
134
- onClickShowTransforms(e) {
135
- e.stopPropagation()
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
- buildTransformedBlock(sourceBlock, data, targetType) {
138
- const baseOrder = _.get(sourceBlock, 'order', 0)
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: baseOrder + 0.5,
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 performBlockTransform(e, targetType) {
246
- e.stopPropagation()
247
-
644
+ async generateMultiItemBlockFromText(targetType) {
248
645
  const html = _.get(this.value, 'body', '')
249
646
  if (!html || typeof html !== 'string') {
250
- return
647
+ throw new Error('invalid_html')
251
648
  }
252
649
 
253
- if (this.isTransforming) {
254
- return
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
- if (this.$toast) {
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
- if (this.$toast) {
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
- const requestConfig = request._reqConfig(
300
- {
301
- method: 'POST',
302
- url: `${request.baseURL()}/${resourcePath}`,
303
- data: payload,
304
- },
305
- { forceMethod: true }
306
- )
711
+ let newBlock = null
307
712
 
308
- const response = await request.request(requestConfig)
309
- const data = response?.data || response
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
- if (
312
- !data ||
313
- !Array.isArray(data.items) ||
314
- data.items.length === 0
315
- ) {
316
- throw new Error('invalid_response')
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
- const newBlock = this.buildTransformedBlock(
320
- this.value,
321
- data,
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
- this.$toast.error(
338
- this.$t(
339
- 'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
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: {},
@@ -80,7 +80,7 @@ export default class ScormHelper {
80
80
  return
81
81
  }
82
82
 
83
- const payload = {
83
+ const payload: any = {
84
84
  type: 'update:tracking',
85
85
  launch_config: this.getLaunchConfig(),
86
86
  url: null,
@@ -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 Consumer Block Title',
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
- 'This block needs to be configured. Please select a LTI tool in the settings panel.',
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 Consumer Block Title',
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
- 'Detta block måste konfigureras. Välj ett LTI-verktyg i inställningspanelen.',
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 Consumer Block Title',
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
- 'This block needs to be configured. Please select a LTI tool in the settings panel.',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/integrations",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Windward UI Plugin Integrations for 3rd Party Systems",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
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: ['block.content-blocks-text'],
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: [