@craftile/core 0.6.3 → 0.7.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/package.json +3 -3
- package/src/commands/insert-block-from-preset.ts +79 -34
- package/src/engine.ts +82 -1
- package/src/vue/useBlocksEngine.ts +27 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftile/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Framework-agnostic block editor engine for Craftile",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"craftile",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@craftile/event-bus": "0.
|
|
30
|
-
"@craftile/types": "0.
|
|
29
|
+
"@craftile/event-bus": "0.7.0",
|
|
30
|
+
"@craftile/types": "0.7.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"vitest": "^3.2.4",
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { Block, BlockSchema,
|
|
1
|
+
import type { Block, BlockProperties, BlockSchema, BlockStructure, Page } from '@craftile/types';
|
|
2
2
|
import type { Command, EngineEmitFn } from '../types';
|
|
3
3
|
import { generateId } from '../utils';
|
|
4
4
|
import type { BlocksManager } from '../blocks-manager';
|
|
5
5
|
|
|
6
6
|
export interface InsertBlockFromPresetOptions {
|
|
7
7
|
blockType: string;
|
|
8
|
-
presetIndex
|
|
8
|
+
presetIndex?: number;
|
|
9
|
+
presetData?: BlockStructure;
|
|
9
10
|
parentId?: string;
|
|
10
11
|
regionName?: string;
|
|
11
12
|
index?: number;
|
|
@@ -16,7 +17,8 @@ export interface InsertBlockFromPresetOptions {
|
|
|
16
17
|
export class InsertBlockFromPresetCommand implements Command {
|
|
17
18
|
private page: Page;
|
|
18
19
|
private blockType: string;
|
|
19
|
-
private presetIndex
|
|
20
|
+
private presetIndex?: number;
|
|
21
|
+
private presetData?: BlockStructure;
|
|
20
22
|
private parentId?: string;
|
|
21
23
|
private regionName?: string;
|
|
22
24
|
private index?: number;
|
|
@@ -32,6 +34,7 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
32
34
|
this.page = page;
|
|
33
35
|
this.blockType = options.blockType;
|
|
34
36
|
this.presetIndex = options.presetIndex;
|
|
37
|
+
this.presetData = options.presetData;
|
|
35
38
|
this.parentId = options.parentId;
|
|
36
39
|
this.regionName = options.regionName;
|
|
37
40
|
this.index = options.index;
|
|
@@ -39,41 +42,81 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
39
42
|
this.blocksManager = options.blocksManager;
|
|
40
43
|
this.emit = options.emit;
|
|
41
44
|
|
|
45
|
+
if (options.presetIndex === undefined && !options.presetData) {
|
|
46
|
+
throw new Error('Either presetIndex or presetData must be provided');
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
const blockSchema = this.blocksManager.get(this.blockType);
|
|
43
50
|
if (!blockSchema) {
|
|
44
51
|
throw new Error(`Block type '${this.blockType}' not found`);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
let properties: BlockProperties | undefined;
|
|
55
|
+
|
|
56
|
+
if (options.presetData) {
|
|
57
|
+
properties = options.presetData.properties;
|
|
58
|
+
} else if (options.presetIndex !== undefined) {
|
|
59
|
+
const preset = blockSchema.presets?.[options.presetIndex];
|
|
60
|
+
if (!preset) {
|
|
61
|
+
throw new Error(`Preset at index ${options.presetIndex} not found for block type '${this.blockType}'`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
properties = preset.properties;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
this.properties = this.buildProperties(blockSchema,
|
|
67
|
+
this.properties = this.buildProperties(blockSchema, properties);
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
apply(): void {
|
|
56
71
|
const blockSchema = this.blocksManager.get(this.blockType);
|
|
57
|
-
const preset = blockSchema?.presets?.[this.presetIndex];
|
|
58
72
|
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
if (this.presetData) {
|
|
74
|
+
// Handle BlockStructure (paste operation)
|
|
75
|
+
this.insertedBlock = {
|
|
76
|
+
type: this.blockType,
|
|
77
|
+
id: this.blockId,
|
|
78
|
+
name: this.presetData.name || blockSchema?.meta?.name || this.blockType,
|
|
79
|
+
semanticId: this.presetData.semanticId || this.presetData.id,
|
|
80
|
+
properties: this.properties,
|
|
81
|
+
children: [],
|
|
82
|
+
parentId: undefined,
|
|
83
|
+
static: this.presetData.static,
|
|
84
|
+
disabled: this.presetData.disabled,
|
|
85
|
+
repeated: this.presetData.repeated,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.page.blocks[this.blockId] = this.insertedBlock;
|
|
89
|
+
this.createdBlockIds.push(this.blockId);
|
|
62
90
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
if (this.presetData.children && this.presetData.children.length > 0) {
|
|
92
|
+
this.insertedBlock.children = this.createChildrenFromPreset(this.presetData.children, this.blockId);
|
|
93
|
+
}
|
|
94
|
+
} else if (this.presetIndex !== undefined) {
|
|
95
|
+
// Handle BlockPreset from schema (preset insertion)
|
|
96
|
+
const preset = blockSchema?.presets?.[this.presetIndex];
|
|
97
|
+
if (!preset) {
|
|
98
|
+
throw new Error(`Preset at index ${this.presetIndex} not found`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.insertedBlock = {
|
|
102
|
+
type: this.blockType,
|
|
103
|
+
id: this.blockId,
|
|
104
|
+
name: preset.name,
|
|
105
|
+
properties: this.properties,
|
|
106
|
+
children: [],
|
|
107
|
+
parentId: undefined,
|
|
108
|
+
};
|
|
71
109
|
|
|
72
|
-
|
|
73
|
-
|
|
110
|
+
this.page.blocks[this.blockId] = this.insertedBlock;
|
|
111
|
+
this.createdBlockIds.push(this.blockId);
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
|
|
113
|
+
if (preset.children && preset.children.length > 0) {
|
|
114
|
+
this.insertedBlock.children = this.createChildrenFromPreset(preset.children, this.blockId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!this.insertedBlock) {
|
|
119
|
+
throw new Error('Failed to create block');
|
|
77
120
|
}
|
|
78
121
|
|
|
79
122
|
// Insert the root block in the appropriate location
|
|
@@ -160,37 +203,39 @@ export class InsertBlockFromPresetCommand implements Command {
|
|
|
160
203
|
return this.insertedBlock;
|
|
161
204
|
}
|
|
162
205
|
|
|
163
|
-
private createChildrenFromPreset(
|
|
206
|
+
private createChildrenFromPreset(structures: BlockStructure[], parentId: string): string[] {
|
|
164
207
|
const childIds: string[] = [];
|
|
165
208
|
|
|
166
|
-
for (const
|
|
209
|
+
for (const structure of structures) {
|
|
167
210
|
const childId = generateId();
|
|
168
|
-
const childSchema = this.blocksManager.get(
|
|
211
|
+
const childSchema = this.blocksManager.get(structure.type);
|
|
169
212
|
|
|
170
213
|
if (!childSchema) {
|
|
171
|
-
console.warn(`Block type '${
|
|
214
|
+
console.warn(`Block type '${structure.type}' not found, skipping child`);
|
|
172
215
|
continue;
|
|
173
216
|
}
|
|
174
217
|
|
|
175
|
-
const childProperties = this.buildProperties(childSchema,
|
|
218
|
+
const childProperties = this.buildProperties(childSchema, structure.properties);
|
|
176
219
|
|
|
177
220
|
const childBlock: Block = {
|
|
178
|
-
type:
|
|
221
|
+
type: structure.type,
|
|
179
222
|
id: childId,
|
|
180
|
-
name:
|
|
181
|
-
semanticId:
|
|
223
|
+
name: structure.name || childSchema.meta?.name || structure.type,
|
|
224
|
+
semanticId: structure.semanticId || structure.id,
|
|
182
225
|
properties: childProperties,
|
|
183
226
|
children: [],
|
|
184
227
|
parentId,
|
|
185
|
-
static:
|
|
228
|
+
static: structure.static,
|
|
229
|
+
disabled: structure.disabled,
|
|
230
|
+
repeated: structure.repeated,
|
|
186
231
|
};
|
|
187
232
|
|
|
188
233
|
this.page.blocks[childId] = childBlock;
|
|
189
234
|
this.createdBlockIds.push(childId);
|
|
190
235
|
|
|
191
236
|
// Recursively create nested children
|
|
192
|
-
if (
|
|
193
|
-
childBlock.children = this.createChildrenFromPreset(
|
|
237
|
+
if (structure.children && structure.children.length > 0) {
|
|
238
|
+
childBlock.children = this.createChildrenFromPreset(structure.children, childId);
|
|
194
239
|
}
|
|
195
240
|
|
|
196
241
|
childIds.push(childId);
|
package/src/engine.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventBus } from '@craftile/event-bus';
|
|
2
2
|
import type { EngineConfig, EngineEvents } from './types';
|
|
3
|
-
import type { Block, BlockSchema, Page } from '@craftile/types';
|
|
3
|
+
import type { Block, BlockSchema, BlockStructure, Page } from '@craftile/types';
|
|
4
4
|
import { BlocksManager } from './blocks-manager';
|
|
5
5
|
import { HistoryManager } from './history-manager';
|
|
6
6
|
import { InsertBlockCommand } from './commands/insert-block';
|
|
@@ -272,6 +272,87 @@ export class Engine extends EventBus<EngineEvents> {
|
|
|
272
272
|
return this.page.blocks[blockId];
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Export a block and its children as a nested structure
|
|
277
|
+
* Useful for copying blocks or creating presets from existing blocks
|
|
278
|
+
*/
|
|
279
|
+
exportBlockAsNestedStructure(blockId: string): BlockStructure {
|
|
280
|
+
const block = this.page.blocks[blockId];
|
|
281
|
+
if (!block) {
|
|
282
|
+
throw new Error(`Block not found: ${blockId}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const structure: BlockStructure = {
|
|
286
|
+
type: block.type,
|
|
287
|
+
id: block.id,
|
|
288
|
+
semanticId: block.semanticId,
|
|
289
|
+
properties: structuredClone(block.properties),
|
|
290
|
+
name: block.name,
|
|
291
|
+
static: block.static,
|
|
292
|
+
disabled: block.disabled,
|
|
293
|
+
repeated: block.repeated,
|
|
294
|
+
children: [],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (block.children && block.children.length > 0) {
|
|
298
|
+
structure.children = block.children.map((childId) => this.exportBlockAsNestedStructure(childId));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return structure;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Paste a block from a nested structure
|
|
306
|
+
*
|
|
307
|
+
* @param structure - The block structure to paste
|
|
308
|
+
* @param options - Optional configuration for block insertion
|
|
309
|
+
* @param options.parentId - ID of parent block (for nested blocks)
|
|
310
|
+
* @param options.regionName - Target region name (defaults to 'main')
|
|
311
|
+
* @param options.index - Position to insert at (defaults to end)
|
|
312
|
+
* @returns The ID of the newly inserted block
|
|
313
|
+
* @throws {Error} When block type is not registered or parent-child relationship is invalid
|
|
314
|
+
* @emits block:insert - When the block is successfully inserted
|
|
315
|
+
*/
|
|
316
|
+
pasteBlock(
|
|
317
|
+
structure: BlockStructure,
|
|
318
|
+
options?: {
|
|
319
|
+
parentId?: string;
|
|
320
|
+
regionName?: string;
|
|
321
|
+
index?: number;
|
|
322
|
+
}
|
|
323
|
+
): string {
|
|
324
|
+
const blockSchema = this.blocksManager.get(structure.type);
|
|
325
|
+
if (!blockSchema) {
|
|
326
|
+
throw new Error(`Block type '${structure.type}' is not registered`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (options?.parentId) {
|
|
330
|
+
const parentBlock = this.page.blocks[options.parentId];
|
|
331
|
+
if (!parentBlock) {
|
|
332
|
+
throw new Error(`Parent block not found: ${options.parentId}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!this.blocksManager.canBeChild(structure.type, parentBlock.type)) {
|
|
336
|
+
throw new Error(`Block type '${structure.type}' cannot be a child of '${parentBlock.type}'`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const command = new InsertBlockFromPresetCommand(this.page, {
|
|
341
|
+
blockType: structure.type,
|
|
342
|
+
presetData: structure,
|
|
343
|
+
parentId: options?.parentId,
|
|
344
|
+
regionName: options?.regionName,
|
|
345
|
+
index: options?.index,
|
|
346
|
+
blocksManager: this.blocksManager,
|
|
347
|
+
emit: this.emit.bind(this),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
command.apply();
|
|
351
|
+
this.historyManager.addCommand(command);
|
|
352
|
+
|
|
353
|
+
return command.getBlockId();
|
|
354
|
+
}
|
|
355
|
+
|
|
275
356
|
/**
|
|
276
357
|
* Get current page state
|
|
277
358
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, onUnmounted, computed, toRaw, nextTick, type Ref } from 'vue';
|
|
2
|
-
import type { Block, Page, Region } from '@craftile/types';
|
|
2
|
+
import type { Block, BlockStructure, Page, Region } from '@craftile/types';
|
|
3
3
|
import type { EngineConfig } from '../types';
|
|
4
4
|
import { Engine } from '../engine';
|
|
5
5
|
|
|
@@ -54,6 +54,14 @@ export interface UseBlocksEngineReturn {
|
|
|
54
54
|
setBlockName: (blockId: string, name: string) => void;
|
|
55
55
|
toggleBlock: (blockId: string, disabled?: boolean) => void;
|
|
56
56
|
duplicateBlock: (blockId: string) => string;
|
|
57
|
+
pasteBlock: (
|
|
58
|
+
structure: BlockStructure,
|
|
59
|
+
options?: {
|
|
60
|
+
parentId?: string;
|
|
61
|
+
regionName?: string;
|
|
62
|
+
index?: number;
|
|
63
|
+
}
|
|
64
|
+
) => string;
|
|
57
65
|
|
|
58
66
|
undo: () => boolean;
|
|
59
67
|
redo: () => boolean;
|
|
@@ -275,6 +283,23 @@ export function useBlocksEngine(
|
|
|
275
283
|
return newBlockId;
|
|
276
284
|
};
|
|
277
285
|
|
|
286
|
+
const pasteBlock = (
|
|
287
|
+
structure: BlockStructure,
|
|
288
|
+
options?: {
|
|
289
|
+
parentId?: string;
|
|
290
|
+
regionName?: string;
|
|
291
|
+
index?: number;
|
|
292
|
+
}
|
|
293
|
+
): string => {
|
|
294
|
+
const blockId = engine.pasteBlock(structure, options);
|
|
295
|
+
|
|
296
|
+
if (!autoSync) {
|
|
297
|
+
syncStateFromEngine();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return blockId;
|
|
301
|
+
};
|
|
302
|
+
|
|
278
303
|
const undo = (): boolean => {
|
|
279
304
|
const result = engine.undo();
|
|
280
305
|
|
|
@@ -332,6 +357,7 @@ export function useBlocksEngine(
|
|
|
332
357
|
setBlockName,
|
|
333
358
|
toggleBlock,
|
|
334
359
|
duplicateBlock,
|
|
360
|
+
pasteBlock,
|
|
335
361
|
|
|
336
362
|
// History methods
|
|
337
363
|
undo,
|