@windward/integrations 0.20.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 +20 -0
- package/components/Content/Blocks/ActionPanel/TransformActivity.vue +386 -0
- package/components/Content/Blocks/ActionPanel/TransformBlock.vue +875 -105
- 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 +45 -12
- package/helpers/ExternalIntegration/ScormHelper.ts +1 -1
- 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 +3 -1
- package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +3 -2
- 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 +3 -1
- package/i18n/es-ES/components/content/blocks/external_integration/lti_consumer.ts +10 -9
- 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 +3 -1
- package/i18n/sv-SE/components/content/blocks/external_integration/lti_consumer.ts +11 -10
- 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 +38 -1
|
@@ -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)"
|
|
@@ -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
|
|
@@ -65,32 +68,13 @@ export default {
|
|
|
65
68
|
return {
|
|
66
69
|
showMenu: false,
|
|
67
70
|
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
71
|
}
|
|
89
72
|
},
|
|
90
73
|
computed: {
|
|
91
74
|
...mapGetters({
|
|
92
75
|
organization: 'organization/get',
|
|
93
76
|
course: 'course/get',
|
|
77
|
+
activeContentBlock: 'contentBlock/get',
|
|
94
78
|
}),
|
|
95
79
|
vModel: {
|
|
96
80
|
get() {
|
|
@@ -100,59 +84,812 @@ export default {
|
|
|
100
84
|
this.$emit('input', value)
|
|
101
85
|
},
|
|
102
86
|
},
|
|
103
|
-
|
|
104
|
-
if (!this.value
|
|
105
|
-
return
|
|
87
|
+
sourceBlock() {
|
|
88
|
+
if (!this.value) {
|
|
89
|
+
return null
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
|
|
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
|
|
111
100
|
}
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
102
|
+
return this.value
|
|
103
|
+
},
|
|
104
|
+
sourceKind() {
|
|
105
|
+
return this.getBlockKindFromTag(_.get(this.sourceBlock, 'tag', ''))
|
|
106
|
+
},
|
|
107
|
+
textSentenceCount() {
|
|
108
|
+
if (this.sourceKind !== 'text') {
|
|
109
|
+
return 0
|
|
110
|
+
}
|
|
111
|
+
return this.countTextSentences(
|
|
112
|
+
_.get(this.sourceBlock, 'body', ''),
|
|
113
|
+
this.$i18n?.locale
|
|
114
|
+
)
|
|
115
|
+
},
|
|
116
|
+
structuredItemCount() {
|
|
117
|
+
if (
|
|
118
|
+
!['accordion', 'tab', 'clickable_icons'].includes(
|
|
119
|
+
this.sourceKind
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
return this.countStructuredItems(this.sourceBlock)
|
|
125
|
+
},
|
|
126
|
+
availableActions() {
|
|
127
|
+
if (!this.sourceBlock) {
|
|
128
|
+
return []
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
if (this.sourceKind === 'text') {
|
|
132
|
+
if (
|
|
133
|
+
this.textSentenceCount <
|
|
134
|
+
MIN_SENTENCE_COUNT_FOR_TEXT_TRANSFORM
|
|
135
|
+
) {
|
|
136
|
+
return []
|
|
137
|
+
}
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
tag: 'plugin-core-accordion',
|
|
141
|
+
label: this.$t(
|
|
142
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
tag: 'plugin-core-tab',
|
|
147
|
+
label: this.$t(
|
|
148
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
tag: 'plugin-core-clickable-icons',
|
|
153
|
+
label: this.$t(
|
|
154
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.sourceKind === 'accordion') {
|
|
161
|
+
const actions = [
|
|
162
|
+
{
|
|
163
|
+
tag: 'content-blocks-text',
|
|
164
|
+
label: this.$t(
|
|
165
|
+
'components.content.blocks.text.block_title'
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
if (this.structuredItemCount >= 2) {
|
|
170
|
+
actions.push(
|
|
171
|
+
{
|
|
172
|
+
tag: 'plugin-core-tab',
|
|
173
|
+
label: this.$t(
|
|
174
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
tag: 'plugin-core-clickable-icons',
|
|
179
|
+
label: this.$t(
|
|
180
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
return actions
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (this.sourceKind === 'tab') {
|
|
189
|
+
const actions = [
|
|
190
|
+
{
|
|
191
|
+
tag: 'content-blocks-text',
|
|
192
|
+
label: this.$t(
|
|
193
|
+
'components.content.blocks.text.block_title'
|
|
194
|
+
),
|
|
195
|
+
},
|
|
196
|
+
]
|
|
197
|
+
if (this.structuredItemCount >= 2) {
|
|
198
|
+
actions.push(
|
|
199
|
+
{
|
|
200
|
+
tag: 'plugin-core-accordion',
|
|
201
|
+
label: this.$t(
|
|
202
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
203
|
+
),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
tag: 'plugin-core-clickable-icons',
|
|
207
|
+
label: this.$t(
|
|
208
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
209
|
+
),
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
return actions
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (this.sourceKind === 'clickable_icons') {
|
|
217
|
+
const actions = [
|
|
218
|
+
{
|
|
219
|
+
tag: 'content-blocks-text',
|
|
220
|
+
label: this.$t(
|
|
221
|
+
'components.content.blocks.text.block_title'
|
|
222
|
+
),
|
|
223
|
+
},
|
|
224
|
+
]
|
|
225
|
+
if (this.structuredItemCount >= 2) {
|
|
226
|
+
actions.push(
|
|
227
|
+
{
|
|
228
|
+
tag: 'plugin-core-accordion',
|
|
229
|
+
label: this.$t(
|
|
230
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
231
|
+
),
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
tag: 'plugin-core-tab',
|
|
235
|
+
label: this.$t(
|
|
236
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
237
|
+
),
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
return actions
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return []
|
|
245
|
+
},
|
|
246
|
+
canTransformBlock() {
|
|
247
|
+
return this.availableActions.length > 0
|
|
131
248
|
},
|
|
132
249
|
},
|
|
133
250
|
methods: {
|
|
134
251
|
onClickShowTransforms(e) {
|
|
135
252
|
e.stopPropagation()
|
|
136
253
|
},
|
|
137
|
-
|
|
138
|
-
|
|
254
|
+
getBlockKindFromTag(tag) {
|
|
255
|
+
if (!tag || typeof tag !== 'string') {
|
|
256
|
+
return 'unknown'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (tag === 'content-blocks-text') {
|
|
260
|
+
return 'text'
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const normalized = tag.replace(/^plugin-/, '')
|
|
264
|
+
if (normalized === 'core-accordion') {
|
|
265
|
+
return 'accordion'
|
|
266
|
+
}
|
|
267
|
+
if (normalized === 'core-tab') {
|
|
268
|
+
return 'tab'
|
|
269
|
+
}
|
|
270
|
+
if (normalized === 'core-clickable-icons') {
|
|
271
|
+
return 'clickable_icons'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return 'unknown'
|
|
275
|
+
},
|
|
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) {
|
|
346
|
+
if (!html || typeof html !== 'string') {
|
|
347
|
+
return 0
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (typeof DOMParser === 'undefined') {
|
|
351
|
+
return 0
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
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
|
|
370
|
+
} catch (_e) {
|
|
371
|
+
return 0
|
|
372
|
+
}
|
|
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
|
+
},
|
|
598
|
+
countStructuredItems(block) {
|
|
599
|
+
const items = _.get(block, 'metadata.config.items', [])
|
|
600
|
+
return Array.isArray(items) ? items.length : 0
|
|
601
|
+
},
|
|
602
|
+
escapeHtml(text) {
|
|
603
|
+
return String(text == null ? '' : text)
|
|
604
|
+
.replace(/&/g, '&')
|
|
605
|
+
.replace(/</g, '<')
|
|
606
|
+
.replace(/>/g, '>')
|
|
607
|
+
.replace(/"/g, '"')
|
|
608
|
+
.replace(/'/g, ''')
|
|
609
|
+
},
|
|
610
|
+
normalizeAssetReference(asset) {
|
|
611
|
+
if (!asset) {
|
|
612
|
+
return null
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (typeof asset === 'string') {
|
|
616
|
+
return { file_asset_id: asset }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (typeof asset === 'object') {
|
|
620
|
+
return asset
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return null
|
|
624
|
+
},
|
|
625
|
+
normalizeImageConfig(rawConfig) {
|
|
626
|
+
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
627
|
+
return null
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const config = _.cloneDeep(rawConfig)
|
|
631
|
+
if (typeof config.asset === 'string') {
|
|
632
|
+
config.asset = this.normalizeAssetReference(config.asset)
|
|
633
|
+
} else if (config.asset && typeof config.asset === 'object') {
|
|
634
|
+
config.asset = this.normalizeAssetReference(config.asset)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return config
|
|
638
|
+
},
|
|
639
|
+
hasAssetInImageConfig(imageConfig) {
|
|
640
|
+
return !!_.get(imageConfig, 'asset.file_asset_id', null)
|
|
641
|
+
},
|
|
642
|
+
buildNewBlockBase(sourceBlock) {
|
|
643
|
+
const baseOrderRaw = _.get(sourceBlock, 'order', 0)
|
|
644
|
+
const baseOrder = Number(baseOrderRaw)
|
|
645
|
+
const normalizedOrder = Number.isFinite(baseOrder)
|
|
646
|
+
? Math.trunc(baseOrder)
|
|
647
|
+
: 0
|
|
648
|
+
|
|
139
649
|
const newBlock = {
|
|
140
650
|
id: _.uniqueId('create_content_block_'),
|
|
141
651
|
content_id: null,
|
|
142
652
|
tag: '',
|
|
143
653
|
body: '',
|
|
144
654
|
status: 'draft',
|
|
145
|
-
order:
|
|
655
|
+
order: normalizedOrder,
|
|
146
656
|
type: {
|
|
147
657
|
save_state: false,
|
|
148
658
|
trackable: false,
|
|
149
659
|
completable: false,
|
|
150
660
|
},
|
|
661
|
+
assets: _.cloneDeep(_.get(sourceBlock, 'assets', [])),
|
|
151
662
|
metadata: {
|
|
152
663
|
config: {},
|
|
153
664
|
},
|
|
154
665
|
}
|
|
155
666
|
|
|
667
|
+
const display = _.cloneDeep(
|
|
668
|
+
_.get(sourceBlock, 'metadata.display', null)
|
|
669
|
+
)
|
|
670
|
+
if (display) {
|
|
671
|
+
newBlock.metadata.display = display
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return newBlock
|
|
675
|
+
},
|
|
676
|
+
extractStructuredBlockData(sourceBlock) {
|
|
677
|
+
const kind = this.getBlockKindFromTag(_.get(sourceBlock, 'tag', ''))
|
|
678
|
+
const config = _.get(sourceBlock, 'metadata.config', {}) || {}
|
|
679
|
+
|
|
680
|
+
const title = typeof config.title === 'string' ? config.title : ''
|
|
681
|
+
const instructions =
|
|
682
|
+
typeof config.instructions === 'string'
|
|
683
|
+
? config.instructions
|
|
684
|
+
: ''
|
|
685
|
+
const displayTitle = _.isBoolean(config.display_title)
|
|
686
|
+
? config.display_title
|
|
687
|
+
: true
|
|
688
|
+
|
|
689
|
+
const rawItems = Array.isArray(config.items) ? config.items : []
|
|
690
|
+
const items = rawItems.map((rawItem) => {
|
|
691
|
+
if (!rawItem || typeof rawItem !== 'object') {
|
|
692
|
+
return { title: '', bodyHtml: '', imageConfig: null }
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (kind === 'accordion') {
|
|
696
|
+
return {
|
|
697
|
+
title:
|
|
698
|
+
typeof rawItem.header === 'string'
|
|
699
|
+
? rawItem.header
|
|
700
|
+
: '',
|
|
701
|
+
bodyHtml:
|
|
702
|
+
typeof rawItem.content === 'string'
|
|
703
|
+
? rawItem.content
|
|
704
|
+
: '',
|
|
705
|
+
imageConfig: this.normalizeImageConfig(
|
|
706
|
+
rawItem.fileConfig
|
|
707
|
+
),
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (kind === 'tab') {
|
|
712
|
+
return {
|
|
713
|
+
title:
|
|
714
|
+
typeof rawItem.tabHeader === 'string'
|
|
715
|
+
? rawItem.tabHeader
|
|
716
|
+
: '',
|
|
717
|
+
bodyHtml:
|
|
718
|
+
typeof rawItem.content === 'string'
|
|
719
|
+
? rawItem.content
|
|
720
|
+
: '',
|
|
721
|
+
imageConfig: this.normalizeImageConfig(
|
|
722
|
+
rawItem.imageAsset
|
|
723
|
+
),
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (kind === 'clickable_icons') {
|
|
728
|
+
const fileConfig = this.normalizeImageConfig(
|
|
729
|
+
rawItem.fileConfig
|
|
730
|
+
)
|
|
731
|
+
const iconImage = rawItem.iconImage === true
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
title:
|
|
735
|
+
typeof rawItem.title === 'string'
|
|
736
|
+
? rawItem.title
|
|
737
|
+
: '',
|
|
738
|
+
bodyHtml:
|
|
739
|
+
typeof rawItem.body === 'string'
|
|
740
|
+
? rawItem.body
|
|
741
|
+
: '',
|
|
742
|
+
imageConfig:
|
|
743
|
+
iconImage || this.hasAssetInImageConfig(fileConfig)
|
|
744
|
+
? fileConfig
|
|
745
|
+
: null,
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return { title: '', bodyHtml: '', imageConfig: null }
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
return {
|
|
753
|
+
kind,
|
|
754
|
+
title,
|
|
755
|
+
instructions,
|
|
756
|
+
displayTitle,
|
|
757
|
+
items,
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
buildTextBlockFromStructured(sourceBlock) {
|
|
761
|
+
const structured = this.extractStructuredBlockData(sourceBlock)
|
|
762
|
+
const parts = []
|
|
763
|
+
|
|
764
|
+
if (structured.displayTitle && structured.title) {
|
|
765
|
+
parts.push(`<h2>${this.escapeHtml(structured.title)}</h2>`)
|
|
766
|
+
}
|
|
767
|
+
if (structured.instructions) {
|
|
768
|
+
parts.push(`<p>${this.escapeHtml(structured.instructions)}</p>`)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
structured.items.forEach((item) => {
|
|
772
|
+
const itemTitle =
|
|
773
|
+
typeof item.title === 'string' ? item.title : ''
|
|
774
|
+
const bodyHtml =
|
|
775
|
+
typeof item.bodyHtml === 'string' ? item.bodyHtml : ''
|
|
776
|
+
|
|
777
|
+
if (itemTitle) {
|
|
778
|
+
parts.push(`<h3>${this.escapeHtml(itemTitle)}</h3>`)
|
|
779
|
+
}
|
|
780
|
+
if (bodyHtml) {
|
|
781
|
+
parts.push(bodyHtml)
|
|
782
|
+
}
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
786
|
+
newBlock.tag = 'content-blocks-text'
|
|
787
|
+
newBlock.body = parts.join('')
|
|
788
|
+
newBlock.metadata.config = { expand: false }
|
|
789
|
+
return newBlock
|
|
790
|
+
},
|
|
791
|
+
buildStructuredBlockFromStructured(sourceBlock, targetType) {
|
|
792
|
+
const structured = this.extractStructuredBlockData(sourceBlock)
|
|
793
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
794
|
+
|
|
795
|
+
if (targetType === 'plugin-core-accordion') {
|
|
796
|
+
newBlock.tag = 'plugin-core-accordion'
|
|
797
|
+
newBlock.body = this.$t(
|
|
798
|
+
'windward.core.shared.content_blocks.title.accordion'
|
|
799
|
+
)
|
|
800
|
+
newBlock.metadata.config = {
|
|
801
|
+
title: structured.title,
|
|
802
|
+
display_title: structured.displayTitle,
|
|
803
|
+
instructions: structured.instructions,
|
|
804
|
+
items: structured.items.map((item) => ({
|
|
805
|
+
header: item.title || '',
|
|
806
|
+
expand: false,
|
|
807
|
+
content: item.bodyHtml || '',
|
|
808
|
+
fileConfig: item.imageConfig || {
|
|
809
|
+
display: {
|
|
810
|
+
width: 100,
|
|
811
|
+
margin: '',
|
|
812
|
+
padding: '',
|
|
813
|
+
},
|
|
814
|
+
hideBackground: true,
|
|
815
|
+
},
|
|
816
|
+
})),
|
|
817
|
+
}
|
|
818
|
+
return newBlock
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (targetType === 'plugin-core-tab') {
|
|
822
|
+
newBlock.tag = 'plugin-core-tab'
|
|
823
|
+
newBlock.body = this.$t(
|
|
824
|
+
'windward.core.shared.content_blocks.title.tab'
|
|
825
|
+
)
|
|
826
|
+
newBlock.metadata.config = {
|
|
827
|
+
title: structured.title,
|
|
828
|
+
display_title: structured.displayTitle,
|
|
829
|
+
instructions: structured.instructions,
|
|
830
|
+
currentTab: 0,
|
|
831
|
+
items: structured.items.map((item) => ({
|
|
832
|
+
tabHeader: item.title || '',
|
|
833
|
+
expand: false,
|
|
834
|
+
content: item.bodyHtml || '',
|
|
835
|
+
imageAsset: item.imageConfig || {
|
|
836
|
+
display: {
|
|
837
|
+
width: 100,
|
|
838
|
+
margin: '',
|
|
839
|
+
padding: '',
|
|
840
|
+
},
|
|
841
|
+
hideBackground: true,
|
|
842
|
+
},
|
|
843
|
+
})),
|
|
844
|
+
}
|
|
845
|
+
return newBlock
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (targetType === 'plugin-core-clickable-icons') {
|
|
849
|
+
newBlock.tag = 'plugin-core-clickable-icons'
|
|
850
|
+
newBlock.body = this.$t(
|
|
851
|
+
'windward.core.shared.content_blocks.title.clickable_icons'
|
|
852
|
+
)
|
|
853
|
+
newBlock.metadata.config = {
|
|
854
|
+
title: structured.title,
|
|
855
|
+
display_title: structured.displayTitle,
|
|
856
|
+
instructions: structured.instructions,
|
|
857
|
+
description: this.$t(
|
|
858
|
+
'windward.core.components.settings.clickable_icon.information'
|
|
859
|
+
),
|
|
860
|
+
display: {
|
|
861
|
+
show_title: false,
|
|
862
|
+
show_background: false,
|
|
863
|
+
round_icon: false,
|
|
864
|
+
italic_icon: false,
|
|
865
|
+
large_icon: false,
|
|
866
|
+
autocolor: true,
|
|
867
|
+
},
|
|
868
|
+
items: structured.items.map((item) => {
|
|
869
|
+
const imageConfig = item.imageConfig
|
|
870
|
+
const hasAsset = this.hasAssetInImageConfig(imageConfig)
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
icon: '',
|
|
874
|
+
fileConfig: hasAsset ? imageConfig : {},
|
|
875
|
+
iconImage: hasAsset,
|
|
876
|
+
title: item.title || '',
|
|
877
|
+
body: item.bodyHtml || '',
|
|
878
|
+
color: {
|
|
879
|
+
class: '',
|
|
880
|
+
},
|
|
881
|
+
active: false,
|
|
882
|
+
}
|
|
883
|
+
}),
|
|
884
|
+
}
|
|
885
|
+
return newBlock
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
throw new Error(`Unsupported targetType: ${targetType}`)
|
|
889
|
+
},
|
|
890
|
+
buildTransformedBlock(sourceBlock, data, targetType) {
|
|
891
|
+
const newBlock = this.buildNewBlockBase(sourceBlock)
|
|
892
|
+
|
|
156
893
|
const blockTitle = data.block_title || ''
|
|
157
894
|
const items = Array.isArray(data.items) ? data.items : []
|
|
158
895
|
|
|
@@ -219,15 +956,15 @@ export default {
|
|
|
219
956
|
'windward.core.components.settings.clickable_icon.information'
|
|
220
957
|
),
|
|
221
958
|
display: {
|
|
222
|
-
show_title:
|
|
223
|
-
show_background:
|
|
959
|
+
show_title: true,
|
|
960
|
+
show_background: true,
|
|
224
961
|
round_icon: false,
|
|
225
962
|
italic_icon: false,
|
|
226
963
|
large_icon: false,
|
|
227
964
|
autocolor: true,
|
|
228
965
|
},
|
|
229
966
|
items: items.map((item) => ({
|
|
230
|
-
icon: '',
|
|
967
|
+
icon: 'mdi-star',
|
|
231
968
|
fileConfig: {},
|
|
232
969
|
iconImage: false,
|
|
233
970
|
title: item.title || '',
|
|
@@ -242,30 +979,18 @@ export default {
|
|
|
242
979
|
|
|
243
980
|
return newBlock
|
|
244
981
|
},
|
|
245
|
-
async
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
if (!html || typeof html !== 'string') {
|
|
250
|
-
return
|
|
251
|
-
}
|
|
982
|
+
async generateMultiItemBlockFromText(targetType) {
|
|
983
|
+
const sourceBlock = this.sourceBlock
|
|
984
|
+
const html = _.get(sourceBlock, 'body', '')
|
|
985
|
+
const locale = this.$i18n?.locale
|
|
252
986
|
|
|
253
|
-
|
|
254
|
-
return
|
|
255
|
-
}
|
|
987
|
+
const preparedHtml = this.prepareHtmlForBlockTransform(html, locale)
|
|
256
988
|
|
|
257
989
|
const organizationId = _.get(this.organization, 'id', null)
|
|
258
990
|
const courseId = _.get(this.course, 'id', null)
|
|
259
991
|
|
|
260
992
|
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
|
|
993
|
+
throw new Error('missing_context')
|
|
269
994
|
}
|
|
270
995
|
|
|
271
996
|
const request = new Course()
|
|
@@ -277,55 +1002,76 @@ export default {
|
|
|
277
1002
|
|
|
278
1003
|
const resourcePath = request._customResource
|
|
279
1004
|
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
|
|
1005
|
+
throw new Error('missing_resource')
|
|
288
1006
|
}
|
|
289
1007
|
|
|
290
1008
|
const payload = {
|
|
291
|
-
html,
|
|
1009
|
+
html: preparedHtml,
|
|
292
1010
|
target_tag: targetType,
|
|
293
|
-
language:
|
|
1011
|
+
language: locale,
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const requestConfig = request._reqConfig(
|
|
1015
|
+
{
|
|
1016
|
+
method: 'POST',
|
|
1017
|
+
url: `${request.baseURL()}/${resourcePath}`,
|
|
1018
|
+
data: payload,
|
|
1019
|
+
},
|
|
1020
|
+
{ forceMethod: true }
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
const response = await request.request(requestConfig)
|
|
1024
|
+
const data = response?.data || response
|
|
1025
|
+
|
|
1026
|
+
if (!data || !Array.isArray(data.items) || data.items.length < 2) {
|
|
1027
|
+
throw new Error('invalid_response')
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return this.buildTransformedBlock(sourceBlock, data, targetType)
|
|
1031
|
+
},
|
|
1032
|
+
async performBlockTransform(e, targetType) {
|
|
1033
|
+
e.stopPropagation()
|
|
1034
|
+
|
|
1035
|
+
if (this.isTransforming) {
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const sourceBlock = this.sourceBlock
|
|
1040
|
+
|
|
1041
|
+
if (!sourceBlock || !sourceBlock.tag) {
|
|
1042
|
+
return
|
|
294
1043
|
}
|
|
295
1044
|
|
|
296
1045
|
this.isTransforming = true
|
|
297
1046
|
|
|
298
1047
|
try {
|
|
299
|
-
|
|
300
|
-
{
|
|
301
|
-
method: 'POST',
|
|
302
|
-
url: `${request.baseURL()}/${resourcePath}`,
|
|
303
|
-
data: payload,
|
|
304
|
-
},
|
|
305
|
-
{ forceMethod: true }
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
const response = await request.request(requestConfig)
|
|
309
|
-
const data = response?.data || response
|
|
1048
|
+
let newBlock = null
|
|
310
1049
|
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1050
|
+
if (this.sourceKind === 'text') {
|
|
1051
|
+
if (targetType === 'content-blocks-text') {
|
|
1052
|
+
return
|
|
1053
|
+
}
|
|
1054
|
+
newBlock = await this.generateMultiItemBlockFromText(
|
|
1055
|
+
targetType
|
|
1056
|
+
)
|
|
1057
|
+
} else if (targetType === 'content-blocks-text') {
|
|
1058
|
+
newBlock = this.buildTextBlockFromStructured(sourceBlock)
|
|
1059
|
+
} else {
|
|
1060
|
+
if (this.countStructuredItems(sourceBlock) < 2) {
|
|
1061
|
+
throw new Error('insufficient_items')
|
|
1062
|
+
}
|
|
1063
|
+
newBlock = this.buildStructuredBlockFromStructured(
|
|
1064
|
+
sourceBlock,
|
|
1065
|
+
targetType
|
|
1066
|
+
)
|
|
317
1067
|
}
|
|
318
1068
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
targetType
|
|
323
|
-
)
|
|
1069
|
+
if (!newBlock) {
|
|
1070
|
+
throw new Error('invalid_new_block')
|
|
1071
|
+
}
|
|
324
1072
|
|
|
325
|
-
// Let the host content page own block insertion via the global event
|
|
326
1073
|
this.$eb.$emit('create:content-block', newBlock)
|
|
327
1074
|
|
|
328
|
-
// Immediately focus the new block for the user
|
|
329
1075
|
const activeBlock = _.cloneDeep(newBlock)
|
|
330
1076
|
this.$ContentService.setContentBlock(activeBlock)
|
|
331
1077
|
this.$eb.$emit('block:focus', activeBlock)
|
|
@@ -333,12 +1079,36 @@ export default {
|
|
|
333
1079
|
// Surface errors instead of failing silently
|
|
334
1080
|
// eslint-disable-next-line no-console
|
|
335
1081
|
console.error('Block transform failed', e)
|
|
1082
|
+
|
|
1083
|
+
const reason = _.get(
|
|
1084
|
+
e,
|
|
1085
|
+
'response.data.error.details.reason',
|
|
1086
|
+
''
|
|
1087
|
+
)
|
|
1088
|
+
|
|
336
1089
|
if (this.$toast) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
1090
|
+
if (
|
|
1091
|
+
String(e?.message) === 'insufficient_content' ||
|
|
1092
|
+
reason === 'NOT_ENOUGH_PARAGRAPHS'
|
|
1093
|
+
) {
|
|
1094
|
+
this.$toast.error(
|
|
1095
|
+
this.$t(
|
|
1096
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_insufficient_paragraphs'
|
|
1097
|
+
)
|
|
340
1098
|
)
|
|
341
|
-
)
|
|
1099
|
+
} else if (String(e?.message) === 'insufficient_items') {
|
|
1100
|
+
this.$toast.error(
|
|
1101
|
+
this.$t(
|
|
1102
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_insufficient_items'
|
|
1103
|
+
)
|
|
1104
|
+
)
|
|
1105
|
+
} else {
|
|
1106
|
+
this.$toast.error(
|
|
1107
|
+
this.$t(
|
|
1108
|
+
'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
|
|
1109
|
+
)
|
|
1110
|
+
)
|
|
1111
|
+
}
|
|
342
1112
|
}
|
|
343
1113
|
} finally {
|
|
344
1114
|
this.isTransforming = false
|