editor-ts 0.0.1
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 +401 -0
- package/index.ts +81 -0
- package/package.json +42 -0
- package/src/core/AssetManager.ts +127 -0
- package/src/core/ComponentManager.ts +206 -0
- package/src/core/Page.ts +138 -0
- package/src/core/StyleManager.ts +221 -0
- package/src/core/ToolbarManager.ts +170 -0
- package/src/core/init.ts +374 -0
- package/src/styles/branding.css +109 -0
- package/src/types.ts +157 -0
- package/src/utils/helpers.ts +199 -0
- package/src/utils/toolbar.ts +140 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { Component, ToolbarConfig, ToolbarRule, ComponentSelector } from '../types';
|
|
2
|
+
import { defaultToolbarConfig } from '../utils/toolbar';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manager for runtime toolbar configurations
|
|
6
|
+
* Toolbars are NOT stored in JSON - they are configured at runtime
|
|
7
|
+
*/
|
|
8
|
+
export class ToolbarManager {
|
|
9
|
+
private rules: ToolbarRule[] = [];
|
|
10
|
+
private globalDefault: ToolbarConfig = defaultToolbarConfig;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Set global default toolbar for all components
|
|
14
|
+
*/
|
|
15
|
+
setGlobalDefault(config: ToolbarConfig): void {
|
|
16
|
+
this.globalDefault = config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configure toolbar for components matching a selector
|
|
21
|
+
*/
|
|
22
|
+
configure(selector: ComponentSelector, config: ToolbarConfig): void {
|
|
23
|
+
// Remove existing rule for same selector
|
|
24
|
+
this.rules = this.rules.filter(rule =>
|
|
25
|
+
JSON.stringify(rule.selector) !== JSON.stringify(selector)
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Add new rule
|
|
29
|
+
this.rules.push({ selector, config });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Configure toolbar by component ID
|
|
34
|
+
*/
|
|
35
|
+
configureById(id: string, config: ToolbarConfig): void {
|
|
36
|
+
this.configure({ id }, config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configure toolbar by component type
|
|
41
|
+
*/
|
|
42
|
+
configureByType(type: string, config: ToolbarConfig): void {
|
|
43
|
+
this.configure({ type }, config);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Configure toolbar by tag name
|
|
48
|
+
*/
|
|
49
|
+
configureByTag(tagName: string, config: ToolbarConfig): void {
|
|
50
|
+
this.configure({ tagName }, config);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Configure toolbar with custom matcher function
|
|
55
|
+
*/
|
|
56
|
+
configureCustom(matcher: (component: Component) => boolean, config: ToolbarConfig): void {
|
|
57
|
+
this.configure({ custom: matcher }, config);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get toolbar configuration for a specific component
|
|
62
|
+
*/
|
|
63
|
+
getToolbarForComponent(component: Component): ToolbarConfig {
|
|
64
|
+
// Check rules in reverse order (last added has priority)
|
|
65
|
+
for (let i = this.rules.length - 1; i >= 0; i--) {
|
|
66
|
+
const rule = this.rules[i];
|
|
67
|
+
if (rule && this.matchesSelector(component, rule.selector)) {
|
|
68
|
+
return rule.config;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Return global default
|
|
73
|
+
return this.globalDefault;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get toolbar by component ID (convenience method)
|
|
78
|
+
*/
|
|
79
|
+
getToolbarById(components: Component[], id: string): ToolbarConfig | null {
|
|
80
|
+
const component = this.findComponentById(components, id);
|
|
81
|
+
if (component) {
|
|
82
|
+
return this.getToolbarForComponent(component);
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if component matches selector
|
|
89
|
+
*/
|
|
90
|
+
private matchesSelector(component: Component, selector: ComponentSelector): boolean {
|
|
91
|
+
if ('id' in selector) {
|
|
92
|
+
return component.attributes?.id === selector.id;
|
|
93
|
+
}
|
|
94
|
+
if ('type' in selector) {
|
|
95
|
+
return component.type === selector.type;
|
|
96
|
+
}
|
|
97
|
+
if ('tagName' in selector) {
|
|
98
|
+
return component.tagName === selector.tagName;
|
|
99
|
+
}
|
|
100
|
+
if ('attributes' in selector) {
|
|
101
|
+
return Object.entries(selector.attributes).every(
|
|
102
|
+
([key, value]) => component.attributes?.[key] === value
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if ('custom' in selector) {
|
|
106
|
+
return selector.custom(component);
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find component by ID in tree
|
|
113
|
+
*/
|
|
114
|
+
private findComponentById(components: Component[], id: string): Component | null {
|
|
115
|
+
for (const comp of components) {
|
|
116
|
+
if (comp.attributes?.id === id) {
|
|
117
|
+
return comp;
|
|
118
|
+
}
|
|
119
|
+
if (comp.components && comp.components.length > 0) {
|
|
120
|
+
const found = this.findComponentById(comp.components, id);
|
|
121
|
+
if (found) return found;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Remove toolbar configuration for a selector
|
|
129
|
+
*/
|
|
130
|
+
removeConfiguration(selector: ComponentSelector): boolean {
|
|
131
|
+
const initialLength = this.rules.length;
|
|
132
|
+
this.rules = this.rules.filter(rule =>
|
|
133
|
+
JSON.stringify(rule.selector) !== JSON.stringify(selector)
|
|
134
|
+
);
|
|
135
|
+
return this.rules.length < initialLength;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Clear all toolbar configurations
|
|
140
|
+
*/
|
|
141
|
+
clearAll(): void {
|
|
142
|
+
this.rules = [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all toolbar rules
|
|
147
|
+
*/
|
|
148
|
+
getAllRules(): ToolbarRule[] {
|
|
149
|
+
return [...this.rules];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Export toolbar configuration as JSON (for sharing config, not data)
|
|
154
|
+
*/
|
|
155
|
+
exportConfig(): string {
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
globalDefault: this.globalDefault,
|
|
158
|
+
rules: this.rules,
|
|
159
|
+
}, null, 2);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Import toolbar configuration from JSON
|
|
164
|
+
*/
|
|
165
|
+
importConfig(json: string): void {
|
|
166
|
+
const config = JSON.parse(json);
|
|
167
|
+
this.globalDefault = config.globalDefault || defaultToolbarConfig;
|
|
168
|
+
this.rules = config.rules || [];
|
|
169
|
+
}
|
|
170
|
+
}
|
package/src/core/init.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorTs Editor Initialization
|
|
3
|
+
* Users control the layout - init() just populates their containers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Page } from './Page';
|
|
7
|
+
import type { InitConfig, EditorTsEditor, Component } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize EditorTs Editor
|
|
11
|
+
* User creates the HTML structure, init() populates it
|
|
12
|
+
*/
|
|
13
|
+
export function init(config: InitConfig): EditorTsEditor {
|
|
14
|
+
// Get the iframe element (required)
|
|
15
|
+
const iframe = document.getElementById(config.iframeId) as HTMLIFrameElement;
|
|
16
|
+
if (!iframe || iframe.tagName !== 'IFRAME') {
|
|
17
|
+
throw new Error(`Iframe element #${config.iframeId} not found or is not an iframe`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create Page instance
|
|
21
|
+
const page = new Page(config.data);
|
|
22
|
+
|
|
23
|
+
// Configure toolbars from config
|
|
24
|
+
if (config.toolbars) {
|
|
25
|
+
if (config.toolbars.byId) {
|
|
26
|
+
Object.entries(config.toolbars.byId).forEach(([id, toolbarConfig]) => {
|
|
27
|
+
page.toolbars.configureById(id, toolbarConfig);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.toolbars.byType) {
|
|
32
|
+
Object.entries(config.toolbars.byType).forEach(([type, toolbarConfig]) => {
|
|
33
|
+
page.toolbars.configureByType(type, toolbarConfig);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (config.toolbars.byTag) {
|
|
38
|
+
Object.entries(config.toolbars.byTag).forEach(([tag, toolbarConfig]) => {
|
|
39
|
+
page.toolbars.configureByTag(tag, toolbarConfig);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (config.toolbars.default) {
|
|
44
|
+
page.toolbars.setGlobalDefault(config.toolbars.default);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Event system
|
|
49
|
+
const eventListeners: Record<string, Function[]> = {};
|
|
50
|
+
|
|
51
|
+
const on = (event: string, callback: Function) => {
|
|
52
|
+
if (!eventListeners[event]) {
|
|
53
|
+
eventListeners[event] = [];
|
|
54
|
+
}
|
|
55
|
+
eventListeners[event]!.push(callback);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const off = (event: string, callback: Function) => {
|
|
59
|
+
if (eventListeners[event]) {
|
|
60
|
+
eventListeners[event] = eventListeners[event]!.filter(cb => cb !== callback);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const emit = (event: string, ...args: any[]) => {
|
|
65
|
+
if (eventListeners[event]) {
|
|
66
|
+
eventListeners[event]!.forEach(callback => callback(...args));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Get optional UI containers
|
|
71
|
+
const sidebarContainer = config.ui?.sidebar?.containerId
|
|
72
|
+
? document.getElementById(config.ui.sidebar.containerId)
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
const statsContainer = config.ui?.stats?.containerId
|
|
76
|
+
? document.getElementById(config.ui.stats.containerId)
|
|
77
|
+
: null;
|
|
78
|
+
|
|
79
|
+
const selectedInfoContainer = config.ui?.selectedInfo?.containerId
|
|
80
|
+
? document.getElementById(config.ui.selectedInfo.containerId)
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
// Populate stats if container provided
|
|
84
|
+
if (statsContainer && config.ui?.stats?.enabled !== false) {
|
|
85
|
+
statsContainer.innerHTML = `
|
|
86
|
+
<div style="font-size: 0.85rem;">
|
|
87
|
+
<div>Components: ${page.components.count()}</div>
|
|
88
|
+
<div>Styles: ${page.styles.count()}</div>
|
|
89
|
+
<div>Assets: ${page.assets.count()}</div>
|
|
90
|
+
</div>
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Build iframe content with WYSIWYG
|
|
95
|
+
const iframeContent = `<!DOCTYPE html>
|
|
96
|
+
<html>
|
|
97
|
+
<head>
|
|
98
|
+
<meta charset="UTF-8">
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
100
|
+
<title>${page.getTitle()}</title>
|
|
101
|
+
<style>${page.getCSS()}</style>
|
|
102
|
+
<style>
|
|
103
|
+
/* WYSIWYG editing styles */
|
|
104
|
+
.editorts-highlight {
|
|
105
|
+
outline: 2px dashed var(--color-editor-light-text, #212C3E) !important;
|
|
106
|
+
outline-offset: 2px;
|
|
107
|
+
cursor: pointer !important;
|
|
108
|
+
position: relative !important;
|
|
109
|
+
}
|
|
110
|
+
.editorts-highlight:hover {
|
|
111
|
+
outline: 2px solid var(--color-editor-light-text, #212C3E) !important;
|
|
112
|
+
background-color: rgba(33, 44, 62, 0.05) !important;
|
|
113
|
+
}
|
|
114
|
+
.editorts-selected {
|
|
115
|
+
outline: 3px solid #10b981 !important;
|
|
116
|
+
background-color: rgba(16, 185, 129, 0.1) !important;
|
|
117
|
+
}
|
|
118
|
+
.editorts-context-toolbar {
|
|
119
|
+
position: absolute;
|
|
120
|
+
top: -42px;
|
|
121
|
+
left: 0;
|
|
122
|
+
background: white;
|
|
123
|
+
border: 2px solid var(--color-editor-light-text, #212C3E);
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
padding: 0.4rem;
|
|
126
|
+
display: flex;
|
|
127
|
+
gap: 0.3rem;
|
|
128
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.25);
|
|
129
|
+
z-index: 9999;
|
|
130
|
+
}
|
|
131
|
+
.toolbar-action {
|
|
132
|
+
background: white;
|
|
133
|
+
border: 1px solid var(--color-primary-border, #e5e7eb);
|
|
134
|
+
padding: 0.5rem 0.75rem;
|
|
135
|
+
border-radius: 4px;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
font-size: 0.85rem;
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
font-family: var(--font-main);
|
|
140
|
+
transition: all 0.2s;
|
|
141
|
+
}
|
|
142
|
+
.toolbar-action:hover {
|
|
143
|
+
background: var(--color-editor-light-bg, #EDF0F5);
|
|
144
|
+
border-color: var(--color-editor-light-text, #212C3E);
|
|
145
|
+
}
|
|
146
|
+
.toolbar-action.danger:hover {
|
|
147
|
+
background: #fee;
|
|
148
|
+
border-color: #ef4444;
|
|
149
|
+
color: #ef4444;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
152
|
+
</head>
|
|
153
|
+
${page.getHTML()}
|
|
154
|
+
<script>
|
|
155
|
+
let selectedElement = null;
|
|
156
|
+
|
|
157
|
+
// Initialize WYSIWYG
|
|
158
|
+
function initWYSIWYG() {
|
|
159
|
+
document.querySelectorAll('[id]').forEach(el => {
|
|
160
|
+
if (!el.id || el.id.startsWith('editorts-')) return;
|
|
161
|
+
|
|
162
|
+
el.classList.add('editorts-highlight');
|
|
163
|
+
|
|
164
|
+
el.addEventListener('click', (e) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
selectElement(el);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function selectElement(el) {
|
|
172
|
+
// Clear previous selection
|
|
173
|
+
if (selectedElement) {
|
|
174
|
+
selectedElement.classList.remove('editorts-selected');
|
|
175
|
+
const oldToolbar = selectedElement.querySelector('.editorts-context-toolbar');
|
|
176
|
+
if (oldToolbar) oldToolbar.remove();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Highlight new selection
|
|
180
|
+
selectedElement = el;
|
|
181
|
+
el.classList.add('editorts-selected');
|
|
182
|
+
|
|
183
|
+
// Notify parent
|
|
184
|
+
window.parent.postMessage({
|
|
185
|
+
type: 'editorts:componentSelected',
|
|
186
|
+
id: el.id,
|
|
187
|
+
tagName: el.tagName.toLowerCase(),
|
|
188
|
+
className: el.className
|
|
189
|
+
}, '*');
|
|
190
|
+
|
|
191
|
+
// Request toolbar config
|
|
192
|
+
window.parent.postMessage({
|
|
193
|
+
type: 'editorts:getToolbar',
|
|
194
|
+
id: el.id
|
|
195
|
+
}, '*');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Listen for toolbar config from parent
|
|
199
|
+
window.addEventListener('message', (event) => {
|
|
200
|
+
if (event.data.type === 'editorts:toolbarConfig') {
|
|
201
|
+
renderToolbar(event.data.config, event.data.elementId);
|
|
202
|
+
} else if (event.data.type === 'editorts:toolbarAction') {
|
|
203
|
+
handleToolbarAction(event.data.action, event.data.elementId);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
function renderToolbar(toolbarConfig, elementId) {
|
|
208
|
+
const el = document.getElementById(elementId);
|
|
209
|
+
if (!el || !toolbarConfig.enabled) return;
|
|
210
|
+
|
|
211
|
+
const toolbar = document.createElement('div');
|
|
212
|
+
toolbar.className = 'editorts-context-toolbar';
|
|
213
|
+
|
|
214
|
+
const enabledActions = toolbarConfig.actions.filter(a => a.enabled);
|
|
215
|
+
enabledActions.forEach(action => {
|
|
216
|
+
const btn = document.createElement('button');
|
|
217
|
+
btn.className = 'toolbar-action' + (action.danger ? ' danger' : '');
|
|
218
|
+
btn.textContent = action.icon + ' ' + action.label;
|
|
219
|
+
btn.onclick = () => {
|
|
220
|
+
window.parent.postMessage({
|
|
221
|
+
type: 'editorts:toolbarAction',
|
|
222
|
+
action: action.id,
|
|
223
|
+
elementId: elementId
|
|
224
|
+
}, '*');
|
|
225
|
+
};
|
|
226
|
+
toolbar.appendChild(btn);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
el.appendChild(toolbar);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function handleToolbarAction(action, elementId) {
|
|
233
|
+
const el = document.getElementById(elementId);
|
|
234
|
+
if (!el) return;
|
|
235
|
+
|
|
236
|
+
if (action === 'delete') {
|
|
237
|
+
el.remove();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Initialize
|
|
242
|
+
if (document.readyState === 'loading') {
|
|
243
|
+
document.addEventListener('DOMContentLoaded', initWYSIWYG);
|
|
244
|
+
} else {
|
|
245
|
+
initWYSIWYG();
|
|
246
|
+
}
|
|
247
|
+
</script>
|
|
248
|
+
</html>`;
|
|
249
|
+
|
|
250
|
+
// Load content into iframe
|
|
251
|
+
iframe.srcdoc = iframeContent;
|
|
252
|
+
|
|
253
|
+
// Handle messages from iframe
|
|
254
|
+
window.addEventListener('message', (event) => {
|
|
255
|
+
if (event.data.type === 'editorts:componentSelected') {
|
|
256
|
+
const component = page.components.findById(event.data.id);
|
|
257
|
+
if (component) {
|
|
258
|
+
// Update selected info container if provided
|
|
259
|
+
if (selectedInfoContainer && config.ui?.selectedInfo?.enabled !== false) {
|
|
260
|
+
selectedInfoContainer.innerHTML = `
|
|
261
|
+
<div><strong>ID:</strong> ${event.data.id}</div>
|
|
262
|
+
<div><strong>Tag:</strong> ${event.data.tagName}</div>
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Emit event
|
|
267
|
+
emit('componentSelect', component);
|
|
268
|
+
if (config.onComponentSelect) {
|
|
269
|
+
config.onComponentSelect(component);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else if (event.data.type === 'editorts:getToolbar') {
|
|
273
|
+
// Send toolbar config to iframe
|
|
274
|
+
const component = page.components.findById(event.data.id);
|
|
275
|
+
if (component) {
|
|
276
|
+
const toolbarConfig = page.toolbars.getToolbarForComponent(component);
|
|
277
|
+
iframe.contentWindow?.postMessage({
|
|
278
|
+
type: 'editorts:toolbarConfig',
|
|
279
|
+
config: toolbarConfig,
|
|
280
|
+
elementId: event.data.id
|
|
281
|
+
}, '*');
|
|
282
|
+
}
|
|
283
|
+
} else if (event.data.type === 'editorts:toolbarAction') {
|
|
284
|
+
handleToolbarAction(event.data.action, event.data.elementId);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Handle toolbar actions
|
|
289
|
+
function handleToolbarAction(actionId: string, elementId: string) {
|
|
290
|
+
const component = page.components.findById(elementId);
|
|
291
|
+
if (!component) return;
|
|
292
|
+
|
|
293
|
+
switch (actionId) {
|
|
294
|
+
case 'edit':
|
|
295
|
+
emit('componentEdit', component);
|
|
296
|
+
if (config.onComponentEdit) {
|
|
297
|
+
config.onComponentEdit(component);
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case 'editJS':
|
|
302
|
+
emit('componentEditJS', component);
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'duplicate':
|
|
306
|
+
const clone = JSON.parse(JSON.stringify(component));
|
|
307
|
+
clone.attributes = clone.attributes || {};
|
|
308
|
+
clone.attributes.id = elementId + '-copy-' + Date.now();
|
|
309
|
+
page.components.addComponent(clone);
|
|
310
|
+
|
|
311
|
+
emit('componentDuplicate', component, clone);
|
|
312
|
+
if (config.onComponentDuplicate) {
|
|
313
|
+
config.onComponentDuplicate(component, clone);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
refresh();
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
case 'delete':
|
|
320
|
+
page.components.removeComponent(elementId);
|
|
321
|
+
|
|
322
|
+
emit('componentDelete', component);
|
|
323
|
+
if (config.onComponentDelete) {
|
|
324
|
+
config.onComponentDelete(component);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Notify iframe to remove element
|
|
328
|
+
iframe.contentWindow?.postMessage({
|
|
329
|
+
type: 'editorts:toolbarAction',
|
|
330
|
+
action: 'delete',
|
|
331
|
+
elementId: elementId
|
|
332
|
+
}, '*');
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Refresh iframe
|
|
338
|
+
function refresh() {
|
|
339
|
+
iframe.srcdoc = iframeContent;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Save page data
|
|
343
|
+
function save(): string {
|
|
344
|
+
return page.toJSON();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Destroy editor
|
|
348
|
+
function destroy() {
|
|
349
|
+
iframe.srcdoc = '';
|
|
350
|
+
if (sidebarContainer) sidebarContainer.innerHTML = '';
|
|
351
|
+
if (statsContainer) statsContainer.innerHTML = '';
|
|
352
|
+
if (selectedInfoContainer) selectedInfoContainer.innerHTML = '';
|
|
353
|
+
|
|
354
|
+
Object.keys(eventListeners).forEach(key => {
|
|
355
|
+
eventListeners[key] = [];
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Return EditorTsEditor instance
|
|
360
|
+
return {
|
|
361
|
+
page,
|
|
362
|
+
on,
|
|
363
|
+
off,
|
|
364
|
+
refresh,
|
|
365
|
+
save,
|
|
366
|
+
destroy,
|
|
367
|
+
elements: {
|
|
368
|
+
iframe,
|
|
369
|
+
sidebar: sidebarContainer || undefined,
|
|
370
|
+
stats: statsContainer || undefined,
|
|
371
|
+
selectedInfo: selectedInfoContainer || undefined,
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
|
|
3
|
+
|
|
4
|
+
/* Set Noto Sans as the default font family */
|
|
5
|
+
:root {
|
|
6
|
+
font-family: "Noto Sans", sans-serif;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Theme variables */
|
|
10
|
+
:root {
|
|
11
|
+
/* Font families */
|
|
12
|
+
--font-main: 'Noto Sans', sans-serif;
|
|
13
|
+
--font-secondary: 'Poppins', sans-serif;
|
|
14
|
+
|
|
15
|
+
/* Dark mode colors */
|
|
16
|
+
--color-catppuccin-mocha-dark: #1e1e2e;
|
|
17
|
+
--color-primary-dark-bg: #171717;
|
|
18
|
+
--color-secondary-dark-bg: #212121;
|
|
19
|
+
--color-primary-dark-border: #323232;
|
|
20
|
+
--color-dark-chat-bg: #2c2c2c;
|
|
21
|
+
--color-primary-dark-text: #FFFFFF;
|
|
22
|
+
--color-primary-dark-input-bg: #404040;
|
|
23
|
+
--color-dark-focus-ring: rgba(255, 255, 255, 0.3);
|
|
24
|
+
--color-dark-button-focus-ring: rgba(255, 255, 255, 0.4);
|
|
25
|
+
|
|
26
|
+
/* Light mode colors */
|
|
27
|
+
--color-editor-light-text: #212C3E;
|
|
28
|
+
--color-editor-light-bg: #EDF0F5;
|
|
29
|
+
--color-primary-bg: #F9F9F9;
|
|
30
|
+
--color-secondary-bg: #FFFFFF;
|
|
31
|
+
--color-sidemenu-bg: #F0F2F8;
|
|
32
|
+
--color-primary-border: #e5e7eb;
|
|
33
|
+
--color-primary-text: #000000;
|
|
34
|
+
--color-primary-input-bg: #f9f9f9;
|
|
35
|
+
--color-light-focus-ring: rgba(0, 0, 0, 0.2);
|
|
36
|
+
--color-light-button-focus-ring: rgba(0, 0, 0, 0.3);
|
|
37
|
+
|
|
38
|
+
/* Shadow styles */
|
|
39
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
40
|
+
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
|
41
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
42
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Apply font to all elements */
|
|
46
|
+
body {
|
|
47
|
+
font-family: var(--font-main);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Optional: Use Poppins for headings */
|
|
51
|
+
h1, h2, h3, h4, h5, h6 {
|
|
52
|
+
font-family: var(--font-secondary);
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Apply theme colors */
|
|
57
|
+
.bg-primary {
|
|
58
|
+
background-color: var(--color-primary-bg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.bg-secondary {
|
|
62
|
+
background-color: var(--color-secondary-bg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.bg-sidemenu {
|
|
66
|
+
background-color: var(--color-sidemenu-bg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.text-primary {
|
|
70
|
+
color: var(--color-primary-text);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.border-primary {
|
|
74
|
+
border-color: var(--color-primary-border);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.input-bg {
|
|
78
|
+
background-color: var(--color-primary-input-bg);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Focus styles */
|
|
82
|
+
input:focus, textarea:focus, select:focus {
|
|
83
|
+
outline: none;
|
|
84
|
+
box-shadow: 0 0 0 3px var(--color-light-focus-ring);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button:focus {
|
|
88
|
+
outline: none;
|
|
89
|
+
box-shadow: 0 0 0 3px var(--color-light-button-focus-ring);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Dark mode support */
|
|
93
|
+
@media (prefers-color-scheme: dark) {
|
|
94
|
+
:root {
|
|
95
|
+
--color-primary-bg: var(--color-primary-dark-bg);
|
|
96
|
+
--color-secondary-bg: var(--color-secondary-dark-bg);
|
|
97
|
+
--color-primary-text: var(--color-primary-dark-text);
|
|
98
|
+
--color-primary-input-bg: var(--color-primary-dark-input-bg);
|
|
99
|
+
--color-primary-border: var(--color-primary-dark-border);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
input:focus, textarea:focus, select:focus {
|
|
103
|
+
box-shadow: 0 0 0 3px var(--color-dark-focus-ring);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
button:focus {
|
|
107
|
+
box-shadow: 0 0 0 3px var(--color-dark-button-focus-ring);
|
|
108
|
+
}
|
|
109
|
+
}
|