js-kompo-utils 1.0.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/README.md +1 -0
- package/app.js +22 -0
- package/components/VlApexChart.vue +89 -0
- package/components/VlCKEditorTemplate.vue +52 -0
- package/components/VlCKEditorWithVars.vue +80 -0
- package/components/VlCollapsible.vue +91 -0
- package/components/VlCopyBox.vue +61 -0
- package/components/VlCreditCardInput.vue +104 -0
- package/components/VlDateMonthDay.vue +327 -0
- package/components/VlDateTextInput.vue +151 -0
- package/components/VlInternationalPhoneInput.vue +902 -0
- package/components/VlPartialDate.vue +283 -0
- package/components/VlPasswordInput.vue +358 -0
- package/components/VlQrCodeScanner.vue +91 -0
- package/components/VlSwipeableTab.vue +35 -0
- package/components/VlSwipeableTabs.vue +325 -0
- package/components/VlText.vue +72 -0
- package/components/VlTutorialStepBuilder.vue +671 -0
- package/components/VlValidatedInput.vue +262 -0
- package/components/data/countries.js +253 -0
- package/components/getAllDefaultComponents.js +12 -0
- package/docs/architecture.md +71 -0
- package/docs/tutorials.md +247 -0
- package/docs/usage.md +128 -0
- package/functions/index.js +7 -0
- package/functions/inputs.js +64 -0
- package/functions/loading.js +20 -0
- package/kompo-ckeditor-1.0.17/index.js +5 -0
- package/kompo-ckeditor-1.0.17/komponents/CKEditor.vue +36 -0
- package/kompo-ckeditor-1.0.17/komponents/CKEditorExtended.vue +66 -0
- package/kompo-ckeditor-1.0.17/komponents/EditableCKEditor.vue +42 -0
- package/kompo-ckeditor-1.0.17/komponents/TranslatableEditor.vue +28 -0
- package/kompo-ckeditor-1.0.17/mixins/CKEditor.js +291 -0
- package/kompo-ckeditor-1.0.17/mixins/CKEditorUtilities.js +52 -0
- package/kompo-ckeditor-1.0.17/mixins/CkUploadAdapter.js +95 -0
- package/kompo-ckeditor-1.0.17/mixins/translations/en.js +1 -0
- package/kompo-ckeditor-1.0.17/mixins/translations/fr.js +1 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockCommand.js +100 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockEditing.js +225 -0
- package/kompo-ckeditor-1.0.17/plugins/JsonBlockUi.js +21 -0
- package/kompo-ckeditor-1.0.17/plugins/MentionPlugin.js +118 -0
- package/orchestrator/KompoJsBuilder.js +333 -0
- package/package.json +25 -0
- package/raw_animations/count-up-animation.js +220 -0
- package/raw_animations/magic-poof.js +210 -0
- package/tutorials/TutorialConfig.js +34 -0
- package/tutorials/TutorialEngine.js +448 -0
- package/tutorials/index.js +2 -0
- package/tutorials/modules/AutoNext.js +86 -0
- package/tutorials/modules/Avatar.js +94 -0
- package/tutorials/modules/Bubble.js +617 -0
- package/tutorials/modules/Cursor.js +315 -0
- package/tutorials/modules/Highlight.js +282 -0
- package/tutorials/modules/HoverForcer.js +250 -0
- package/tutorials/modules/Mobile.js +148 -0
- package/tutorials/modules/Options.js +98 -0
- package/tutorials/modules/Overlay.js +142 -0
- package/tutorials/modules/Scroll.js +37 -0
- package/tutorials/modules/Typewriter.js +132 -0
- package/tutorials/step-builder/step-builder-bridge.js +436 -0
- package/tutorials/step-builder/utils/bezier-editor.js +379 -0
- package/tutorials/step-builder/utils/code-generator.js +127 -0
- package/tutorials/step-builder/utils/element-picker.js +125 -0
- package/tutorials/tutorial.scss +816 -0
- package/tutorials/utils/selectors.js +225 -0
- package/tutorials/utils/stability.js +76 -0
- package/tutorials/utils/svg.js +46 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {Plugin} from 'ckeditor5';
|
|
2
|
+
// Ensure highlight.js is imported in the environment (e.g., via global script)
|
|
3
|
+
import hljs from 'highlight.js'; // (optional, if using bundling)
|
|
4
|
+
import { JsonBlockCommand } from './JsonBlockCommand';
|
|
5
|
+
|
|
6
|
+
import json from 'highlight.js/lib/languages/json';
|
|
7
|
+
import 'highlight.js/styles/atom-one-dark.css'; // or the theme of your choice
|
|
8
|
+
import { padStart } from 'lodash';
|
|
9
|
+
|
|
10
|
+
hljs.registerLanguage('json', json);
|
|
11
|
+
|
|
12
|
+
const TAB_SIZE = window.TAB_SIZE || 2;
|
|
13
|
+
|
|
14
|
+
function getJsonText(jsonBlockElement) {
|
|
15
|
+
let text = '';
|
|
16
|
+
for (const child of jsonBlockElement.getChildren()) {
|
|
17
|
+
if (child.is('$text')) {
|
|
18
|
+
text += child.data;
|
|
19
|
+
} else if (child.name === 'softBreak') {
|
|
20
|
+
text += '\n';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class JsonBlockEditing extends Plugin {
|
|
27
|
+
static get pluginName() { return 'JsonBlockEditing'; }
|
|
28
|
+
|
|
29
|
+
init() {
|
|
30
|
+
const editor = this.editor;
|
|
31
|
+
const schema = editor.model.schema;
|
|
32
|
+
const conversion = editor.conversion;
|
|
33
|
+
const model = editor.model;
|
|
34
|
+
|
|
35
|
+
// **Schema**: Register the 'jsonBlock' model element
|
|
36
|
+
schema.register('jsonBlock', {
|
|
37
|
+
allowWhere: '$block',
|
|
38
|
+
allowContentOf: '$block',
|
|
39
|
+
isBlock: true,
|
|
40
|
+
isLimit: true // This is key!
|
|
41
|
+
});
|
|
42
|
+
// Restrict attributes within the jsonBlock (only plain text, no styles)
|
|
43
|
+
schema.addAttributeCheck((context, attributeName) => {
|
|
44
|
+
if (context.endsWith('jsonBlock')) {
|
|
45
|
+
// Do not allow any formatting attributes in text within the JSON block
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// **Upcast Conversion (view -> model)**: from <pre class="json-block"><code class="language-json"> to model
|
|
51
|
+
conversion.for('upcast').elementToElement({
|
|
52
|
+
view: {
|
|
53
|
+
name: 'pre',
|
|
54
|
+
classes: 'json-block'
|
|
55
|
+
},
|
|
56
|
+
model: (viewElement, { writer }) => {
|
|
57
|
+
const jsonBlock = writer.createElement('jsonBlock');
|
|
58
|
+
|
|
59
|
+
return jsonBlock;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Add dynamic highlighting using highlight.js without interfering with editing
|
|
64
|
+
conversion.for('downcast').elementToElement({
|
|
65
|
+
model: 'jsonBlock',
|
|
66
|
+
view: (modelItem, conversionApi) => {
|
|
67
|
+
const { writer } = conversionApi;
|
|
68
|
+
|
|
69
|
+
const pre = writer.createContainerElement('pre', { class: 'json-block' });
|
|
70
|
+
|
|
71
|
+
return pre;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Intercept the Enter key within jsonBlock to insert a new line instead of exiting the block
|
|
76
|
+
editor.editing.view.document.on('enter', (evt, data) => {
|
|
77
|
+
const pos = editor.model.document.selection.getFirstPosition();
|
|
78
|
+
if (pos.parent.is('element', 'jsonBlock')) {
|
|
79
|
+
editor.model.change(writer => {
|
|
80
|
+
// Insert a newline character at the current position
|
|
81
|
+
writer.insertText('\n', pos);
|
|
82
|
+
});
|
|
83
|
+
evt.stop(); // prevent default behavior (exiting or splitting the block)
|
|
84
|
+
}
|
|
85
|
+
}, { priority: 'high' });
|
|
86
|
+
|
|
87
|
+
editor.editing.view.document.on('keydown', (evt, data) => {
|
|
88
|
+
const selection = model.document.selection;
|
|
89
|
+
const position = selection.getFirstPosition();
|
|
90
|
+
|
|
91
|
+
// Detect if Tab is pressed
|
|
92
|
+
if (data.keyCode === 9) {
|
|
93
|
+
model.change(writer => {
|
|
94
|
+
writer.insertText(''.padStart(TAB_SIZE, ' '), position);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
data.preventDefault();
|
|
98
|
+
evt.stop();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
editor.editing.view.document.on('blur', (evt, data) => {
|
|
103
|
+
const pos = editor.model.document.selection.getFirstPosition();
|
|
104
|
+
const jsonBlock = pos.findAncestor('jsonBlock');
|
|
105
|
+
if (jsonBlock) {
|
|
106
|
+
editor.model.change(writer => {
|
|
107
|
+
let jsonTextWithMentions = '';
|
|
108
|
+
let jsonMappingMentions = [];
|
|
109
|
+
for (const child of jsonBlock.getChildren()) {
|
|
110
|
+
if (child.is('$text')) {
|
|
111
|
+
jsonTextWithMentions += child.data;
|
|
112
|
+
} else if (child.hasAttribute('data-mention')) {
|
|
113
|
+
const mention = child.getAttribute('data-mention');
|
|
114
|
+
jsonTextWithMentions += '{{ ' + mention + ' }}'; // Use the mention name for JSON
|
|
115
|
+
jsonMappingMentions[mention] = '<span class="mention" contenteditable="false" data-mention="' + mention + '" data-original="' + child.getAttribute('data-original') + '">' + child.getAttribute('data-original') + '</span>';
|
|
116
|
+
} else if (child.name === 'softBreak') {
|
|
117
|
+
jsonTextWithMentions += '\n';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
let formatted = JSON.stringify(JSON.parse(jsonTextWithMentions), null, 2);
|
|
123
|
+
const mentionRegex = /\{\{\s*[a-zA-Z_]*?\s\}\}/;
|
|
124
|
+
const splitLines = [];
|
|
125
|
+
|
|
126
|
+
let remainingLine = formatted;
|
|
127
|
+
let match;
|
|
128
|
+
|
|
129
|
+
while ((match = mentionRegex.exec(remainingLine)) !== null) {
|
|
130
|
+
const parts = remainingLine.split(match[0]);
|
|
131
|
+
|
|
132
|
+
if (parts[0]) {
|
|
133
|
+
splitLines.push(parts[0]); // Agregar texto antes del mention
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
splitLines.push(jsonMappingMentions[match[0].replaceAll(/[\{\}\s]/g, '')]); // Agregar el mention como un nodo intermedio
|
|
137
|
+
|
|
138
|
+
remainingLine = parts[1];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (remainingLine) {
|
|
142
|
+
splitLines.push(remainingLine); // Agregar texto después del mention
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const previousChildren = Array.from(jsonBlock.getChildren());
|
|
146
|
+
|
|
147
|
+
previousChildren.forEach((child) => {
|
|
148
|
+
if (child.is('$text')) {
|
|
149
|
+
writer.remove(child)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
let insertPosition = writer.createPositionAt(jsonBlock, 'end');
|
|
154
|
+
|
|
155
|
+
for (const line of splitLines) {
|
|
156
|
+
// If it has <>, it's a mention span
|
|
157
|
+
if (line.startsWith('<') && line.endsWith('>')) {
|
|
158
|
+
const mentionElement = writer.createElement('mention', {
|
|
159
|
+
'data-mention': line.match(/data-mention="([^"]+)"/)[1],
|
|
160
|
+
'data-original': line.match(/data-original="([^"]+)"/)[1]
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
writer.insert(mentionElement, insertPosition);
|
|
164
|
+
// Actualizar la posición después de insertar
|
|
165
|
+
insertPosition = writer.createPositionAfter(mentionElement);
|
|
166
|
+
} else {
|
|
167
|
+
// Si es texto, insertarlo en la posición actual
|
|
168
|
+
writer.insertText(line, insertPosition);
|
|
169
|
+
// Actualizar la posición después de insertar
|
|
170
|
+
insertPosition = writer.createPositionAt(jsonBlock, 'end');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
previousChildren.forEach((child) => {
|
|
175
|
+
if (!child.is('$text')) {
|
|
176
|
+
writer.remove(child)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// If JSON is invalid, do nothing
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Register the 'jsonBlock' command
|
|
189
|
+
editor.commands.add('jsonBlock', new JsonBlockCommand(editor));
|
|
190
|
+
|
|
191
|
+
// Selection management for highlighting/validation (see below)
|
|
192
|
+
this._setupHighlightAndValidation();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_setupHighlightAndValidation() {
|
|
196
|
+
const editor = this.editor;
|
|
197
|
+
const model = editor.model;
|
|
198
|
+
const view = editor.editing.view;
|
|
199
|
+
|
|
200
|
+
// Detect changes in the JSON block content for validation
|
|
201
|
+
model.document.on('change:data', () => {
|
|
202
|
+
const jsonBlock = model.document.selection.getFirstPosition().findAncestor('jsonBlock');
|
|
203
|
+
if (!jsonBlock) return;
|
|
204
|
+
// Validate JSON syntax of the current content
|
|
205
|
+
const jsonText = getJsonText(jsonBlock);
|
|
206
|
+
let isValid = true;
|
|
207
|
+
try {
|
|
208
|
+
if (jsonText.trim() !== '') {
|
|
209
|
+
JSON.parse(jsonText);
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
isValid = false;
|
|
213
|
+
}
|
|
214
|
+
// Update CSS classes in the view based on validity (e.g., red/green border)
|
|
215
|
+
const viewElement = editor.editing.mapper.toViewElement(jsonBlock);
|
|
216
|
+
if (!viewElement) return; // ✅ Protect against errors if the block was removed
|
|
217
|
+
|
|
218
|
+
view.change(writer => {
|
|
219
|
+
writer.removeClass([ 'json-valid', 'json-invalid' ], viewElement);
|
|
220
|
+
writer.addClass(isValid ? 'json-valid' : 'json-invalid', viewElement);
|
|
221
|
+
writer.setAttribute('data-validity', isValid ? 'Valid JSON' : 'Invalid JSON', viewElement);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {ButtonView, Plugin} from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
export class JsonBlockUI extends Plugin {
|
|
4
|
+
init() {
|
|
5
|
+
const editor = this.editor;
|
|
6
|
+
editor.ui.componentFactory.add('jsonBlock', locale => {
|
|
7
|
+
const command = editor.commands.get('jsonBlock');
|
|
8
|
+
const button = new ButtonView(locale);
|
|
9
|
+
button.set({
|
|
10
|
+
label: 'JSON Block',
|
|
11
|
+
withText: true, // Displays the label as text (you could use a custom icon)
|
|
12
|
+
tooltip: 'Insert a JSON block'
|
|
13
|
+
});
|
|
14
|
+
// Bind the button state to the command
|
|
15
|
+
button.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
|
|
16
|
+
// Execute the command when clicked
|
|
17
|
+
this.listenTo(button, 'execute', () => editor.execute('jsonBlock'));
|
|
18
|
+
return button;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Plugin } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
export default class MentionPlugin extends Plugin {
|
|
4
|
+
init() {
|
|
5
|
+
const editor = this.editor;
|
|
6
|
+
|
|
7
|
+
editor.model.schema.register('mention', {
|
|
8
|
+
isInline: true,
|
|
9
|
+
isObject: true,
|
|
10
|
+
allowWhere: '$text',
|
|
11
|
+
allowAttributes: ['data-mention', 'data-original']
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Allow data-mention as an attribute in $text:
|
|
15
|
+
editor.model.schema.extend('$text', {
|
|
16
|
+
allowAttributes: ['data-mention', 'data-original']
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Upcast the <span class="mention" data-mention="..."> view to the model:
|
|
20
|
+
editor.conversion.for('upcast').elementToElement({
|
|
21
|
+
view: {
|
|
22
|
+
name: 'span',
|
|
23
|
+
classes: 'mention'
|
|
24
|
+
},
|
|
25
|
+
model: (viewElement, { writer }) => {
|
|
26
|
+
return writer.createElement('mention', {
|
|
27
|
+
'data-mention': viewElement.getAttribute('data-mention'),
|
|
28
|
+
'data-original': viewElement.getAttribute('data-original') || viewElement.getChild(0).data
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
editor.conversion.for('downcast').elementToElement({
|
|
34
|
+
model: 'mention',
|
|
35
|
+
view: (modelItem, { writer }) => {
|
|
36
|
+
const dataMention = modelItem.getAttribute('data-mention');
|
|
37
|
+
const dataOriginal = modelItem.getAttribute('data-original');
|
|
38
|
+
|
|
39
|
+
// Create the <span> element with attributes and inner content
|
|
40
|
+
const span = writer.createContainerElement('span', {
|
|
41
|
+
class: 'mention',
|
|
42
|
+
'data-mention': dataMention,
|
|
43
|
+
'data-original': dataOriginal,
|
|
44
|
+
'contenteditable': 'false'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Set the inner content of the <span> to the value of data-original
|
|
48
|
+
const textNode = writer.createText(dataOriginal);
|
|
49
|
+
writer.insert(writer.createPositionAt(span, 0), textNode);
|
|
50
|
+
|
|
51
|
+
return span;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Implement a robust postfixer to maintain mention integrity
|
|
56
|
+
editor.model.document.registerPostFixer(writer => {
|
|
57
|
+
let wasFixed = false;
|
|
58
|
+
const changes = editor.model.document.differ.getChanges();
|
|
59
|
+
|
|
60
|
+
function isSpanNode(node) {
|
|
61
|
+
return node && node.is('$text') && node.hasAttribute('data-mention');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const change of changes) {
|
|
65
|
+
if (change.type === 'remove') {
|
|
66
|
+
const position = change.position;
|
|
67
|
+
|
|
68
|
+
if (isSpanNode(position.nodeAfter) && position.nodeBefore.getAttribute('data-original') != position.nodeBefore._data) {
|
|
69
|
+
writer.remove(position.nodeAfter);
|
|
70
|
+
wasFixed = true;
|
|
71
|
+
} else if (isSpanNode(position.nodeBefore) && position.nodeAfter.getAttribute('data-original') != position.nodeAfter._data) {
|
|
72
|
+
writer.remove(position.nodeBefore);
|
|
73
|
+
wasFixed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return wasFixed;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Add a patch to handle the `document-selection-wrong-position` error when clicking on a mention span
|
|
82
|
+
editor.editing.view.document.on('mousedown', (evt, data) => {
|
|
83
|
+
try {
|
|
84
|
+
const domTarget = data.domTarget;
|
|
85
|
+
|
|
86
|
+
if (domTarget.nodeName === 'SPAN' && domTarget.classList.contains('mention')) {
|
|
87
|
+
const domMention = domTarget.closest('.mention');
|
|
88
|
+
const viewMention = editor.editing.view.domConverter.mapDomToView(domMention);
|
|
89
|
+
|
|
90
|
+
if (viewMention) {
|
|
91
|
+
const modelMention = editor.editing.mapper.toModelElement(viewMention);
|
|
92
|
+
if (modelMention) {
|
|
93
|
+
editor.editing.view.focus();
|
|
94
|
+
|
|
95
|
+
editor.model.change(writer => {
|
|
96
|
+
// Select the span element completely
|
|
97
|
+
const range = writer.createRangeIn(modelMention);
|
|
98
|
+
writer.setSelection(range);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Prevent default behavior to avoid invalid selection
|
|
102
|
+
data.preventDefault();
|
|
103
|
+
evt.stop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.name === 'CKEditorError' && error.message.includes('document-selection-wrong-position')) {
|
|
109
|
+
editor.model.change(writer => {
|
|
110
|
+
writer.setSelection(editor.model.document.getRoot(), 'end');
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
export class KompoJsConfigBuilder {
|
|
2
|
+
components = {};
|
|
3
|
+
bootstrapCallback = null;
|
|
4
|
+
filesToImport = new Set();
|
|
5
|
+
includeTutorialEngine = true;
|
|
6
|
+
tutorialEngineConfig = {};
|
|
7
|
+
includeJquery = true;
|
|
8
|
+
includeCkEditor = true;
|
|
9
|
+
includeKompoMaps = true;
|
|
10
|
+
vuePlugins = [];
|
|
11
|
+
windowDecorators = [];
|
|
12
|
+
windowUtilsObject = {};
|
|
13
|
+
appSelector = '#app';
|
|
14
|
+
|
|
15
|
+
beforeStepCallbacks = {};
|
|
16
|
+
afterStepCallbacks = {};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} componentName
|
|
20
|
+
* @returns {KompoJsConfigBuilder}
|
|
21
|
+
*/
|
|
22
|
+
excludeComponent(componentName) {
|
|
23
|
+
delete this.components[componentName];
|
|
24
|
+
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} componentName
|
|
30
|
+
* @param {object} componentDefinition - A resolved Vue component object
|
|
31
|
+
* @returns {KompoJsConfigBuilder}
|
|
32
|
+
*/
|
|
33
|
+
addComponent(componentName, componentDefinition) {
|
|
34
|
+
this.components[componentName] = componentDefinition;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {{[key: string]: object}} components - Map of name to resolved Vue component objects
|
|
40
|
+
* @returns {KompoJsConfigBuilder}
|
|
41
|
+
*/
|
|
42
|
+
addComponents(components) {
|
|
43
|
+
for (const [componentName, componentDefinition] of Object.entries(components)) {
|
|
44
|
+
this.addComponent(componentName, componentDefinition);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string[]} componentNames
|
|
52
|
+
* @returns {KompoJsConfigBuilder}
|
|
53
|
+
*/
|
|
54
|
+
excludeComponents(componentNames) {
|
|
55
|
+
for (const componentName of componentNames) {
|
|
56
|
+
this.excludeComponent(componentName);
|
|
57
|
+
}
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
bootstrap(callback) {
|
|
62
|
+
this.bootstrapCallback = callback;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Function} callback - A callback that requires/imports the file, e.g. () => require('./file.js')
|
|
68
|
+
* @returns {KompoJsConfigBuilder}
|
|
69
|
+
*/
|
|
70
|
+
addFileToImport(callback) {
|
|
71
|
+
this.filesToImport.add(callback);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Function[]} callbacks
|
|
77
|
+
* @returns {KompoJsConfigBuilder}
|
|
78
|
+
*/
|
|
79
|
+
addFilesToImport(callbacks) {
|
|
80
|
+
for (const callback of callbacks) {
|
|
81
|
+
this.addFileToImport(callback);
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
excludeFileToImport(callback) {
|
|
87
|
+
this.filesToImport.delete(callback);
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
excludeFilesToImport(callbacks) {
|
|
92
|
+
for (const callback of callbacks) {
|
|
93
|
+
this.excludeFileToImport(callback);
|
|
94
|
+
}
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
withTutorialEngine(config = {}) {
|
|
99
|
+
this.includeTutorialEngine = true;
|
|
100
|
+
this.tutorialEngineConfig = config;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
withoutTutorialEngine() {
|
|
105
|
+
this.includeTutorialEngine = false;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
withJquery() {
|
|
110
|
+
this.includeJquery = true;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
withoutJquery() {
|
|
115
|
+
this.includeJquery = false;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
withCkEditor() {
|
|
120
|
+
this.includeCkEditor = true;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
withoutCkEditor() {
|
|
125
|
+
this.includeCkEditor = false;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
withKompoMaps() {
|
|
130
|
+
this.includeKompoMaps = true;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
withoutKompoMaps() {
|
|
135
|
+
this.includeKompoMaps = false;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {object} plugin - A Vue plugin to register via Vue.use()
|
|
141
|
+
* @returns {KompoJsConfigBuilder}
|
|
142
|
+
*/
|
|
143
|
+
addVuePlugin(plugin) {
|
|
144
|
+
this.vuePlugins.push(plugin);
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
addWindowDecorator(decorator) {
|
|
149
|
+
this.windowDecorators.push(decorator);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addWindowUtilsItem(key, value) {
|
|
154
|
+
this.windowUtilsObject[key] = value;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
mergeWindowUtils(utilsObject) {
|
|
159
|
+
this.windowUtilsObject = {
|
|
160
|
+
...this.windowUtilsObject,
|
|
161
|
+
...utilsObject
|
|
162
|
+
};
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setAppSelector(selector) {
|
|
167
|
+
this.appSelector = selector;
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
addBeforeStepCallback(stepName, callback) {
|
|
172
|
+
if (!this.beforeStepCallbacks[stepName]) {
|
|
173
|
+
this.beforeStepCallbacks[stepName] = [];
|
|
174
|
+
}
|
|
175
|
+
this.beforeStepCallbacks[stepName].push(callback);
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addAfterStepCallback(stepName, callback) {
|
|
180
|
+
if (!this.afterStepCallbacks[stepName]) {
|
|
181
|
+
this.afterStepCallbacks[stepName] = [];
|
|
182
|
+
}
|
|
183
|
+
this.afterStepCallbacks[stepName].push(callback);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export class KompoJsInitializer {
|
|
189
|
+
|
|
190
|
+
static ESSENTIAL_STEPS = new Set([
|
|
191
|
+
'bootstrapExecution',
|
|
192
|
+
'bootVue',
|
|
193
|
+
'instanceApp',
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {KompoJsConfigBuilder} config
|
|
198
|
+
*/
|
|
199
|
+
constructor(config, window) {
|
|
200
|
+
this.config = config;
|
|
201
|
+
this.window = window;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
initialize() {
|
|
205
|
+
this._callStep('bootstrapExecution');
|
|
206
|
+
|
|
207
|
+
this._callStep('requireFilesToImport');
|
|
208
|
+
|
|
209
|
+
this._callStep('bootJqueryIfRequired');
|
|
210
|
+
|
|
211
|
+
this._callStep('bootVue');
|
|
212
|
+
|
|
213
|
+
this._callStep('decorateWindow');
|
|
214
|
+
|
|
215
|
+
this._callStep('bootCkEditorIfRequired');
|
|
216
|
+
|
|
217
|
+
this._callStep('bootMapsIfRequired');
|
|
218
|
+
|
|
219
|
+
this._callStep('bootTutorialEngineIfRequired');
|
|
220
|
+
|
|
221
|
+
this._callStep('instanceApp');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_callStep(stepName) {
|
|
225
|
+
const isEssential = KompoJsInitializer.ESSENTIAL_STEPS.has(stepName);
|
|
226
|
+
|
|
227
|
+
if (isEssential) {
|
|
228
|
+
this._executeStep(stepName);
|
|
229
|
+
} else {
|
|
230
|
+
try {
|
|
231
|
+
this._executeStep(stepName);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`[KompoJs] Non-essential step "${stepName}" failed:`, error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
_executeStep(stepName) {
|
|
239
|
+
if (this.config.beforeStepCallbacks[stepName]) {
|
|
240
|
+
for (const callback of this.config.beforeStepCallbacks[stepName]) {
|
|
241
|
+
callback();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this[stepName]();
|
|
246
|
+
|
|
247
|
+
if (this.config.afterStepCallbacks[stepName]) {
|
|
248
|
+
for (const callback of this.config.afterStepCallbacks[stepName]) {
|
|
249
|
+
callback();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
bootstrapExecution() {
|
|
255
|
+
if (this.config.bootstrapCallback) {
|
|
256
|
+
this.config.bootstrapCallback();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
requireFilesToImport() {
|
|
261
|
+
for (const callback of this.config.filesToImport) {
|
|
262
|
+
try {
|
|
263
|
+
callback();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('[KompoJs] File import failed:', error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
bootJqueryIfRequired() {
|
|
271
|
+
if (this.config.includeJquery) {
|
|
272
|
+
this.window.$ = this.window.jQuery = require('jquery');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
bootVue() {
|
|
277
|
+
const Vue = require('vue/dist/vue');
|
|
278
|
+
this.window.Vue = Vue;
|
|
279
|
+
|
|
280
|
+
require('vue-kompo');
|
|
281
|
+
|
|
282
|
+
for (const plugin of this.config.vuePlugins) {
|
|
283
|
+
Vue.use(plugin);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const [componentName, componentDefinition] of Object.entries(this.config.components)) {
|
|
287
|
+
Vue.component(componentName, componentDefinition);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
decorateWindow() {
|
|
292
|
+
for (const [key, value] of Object.entries(this.config.windowUtilsObject)) {
|
|
293
|
+
if (!this.window.utils) {
|
|
294
|
+
this.window.utils = {};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.window.utils[key] = value;
|
|
298
|
+
this.window[key] = value;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const decorator of this.config.windowDecorators) {
|
|
302
|
+
decorator(this.window);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
bootCkEditorIfRequired() {
|
|
307
|
+
if (this.config.includeCkEditor) {
|
|
308
|
+
require('../kompo-ckeditor-1.0.17/index.js');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
bootMapsIfRequired() {
|
|
313
|
+
if (this.config.includeKompoMaps) {
|
|
314
|
+
this.window.GoogleMapsStyle = require('kompo-googlemaps/styles/silver').default;
|
|
315
|
+
require('kompo-googlemaps');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
bootTutorialEngineIfRequired() {
|
|
320
|
+
if (this.config.includeTutorialEngine) {
|
|
321
|
+
const { TutorialEngine } = require('../tutorials/TutorialEngine.js');
|
|
322
|
+
const engine = new TutorialEngine(this.config.tutorialEngineConfig);
|
|
323
|
+
engine.preload = TutorialEngine.preload;
|
|
324
|
+
this.window.TutorialEngine = engine;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
instanceApp() {
|
|
329
|
+
new this.window.Vue({
|
|
330
|
+
el: this.config.appSelector,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|