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/src/types.ts ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Core type definitions for the HTML content editing library
3
+ */
4
+
5
+ export interface PageData {
6
+ title: string;
7
+ item_id: number;
8
+ body: PageBody;
9
+ }
10
+
11
+ export interface PageBody {
12
+ html: string;
13
+ components: string; // JSON string of Component[]
14
+ assets: Asset[];
15
+ css: string;
16
+ styles: Style[];
17
+ }
18
+
19
+ export interface Component {
20
+ type: string;
21
+ attributes?: Record<string, any>;
22
+ components?: Component[];
23
+ tagName?: string;
24
+ void?: boolean;
25
+ style?: string;
26
+ script?: string;
27
+ [key: string]: any;
28
+ }
29
+
30
+ export interface ToolbarConfig {
31
+ enabled: boolean;
32
+ actions: ToolbarAction[];
33
+ }
34
+
35
+ export interface ToolbarAction {
36
+ id: string;
37
+ label: string;
38
+ icon: string;
39
+ enabled: boolean;
40
+ danger?: boolean;
41
+ description?: string;
42
+ handler?: string;
43
+ }
44
+
45
+ export interface Asset {
46
+ type: 'image' | 'video' | 'audio' | 'document';
47
+ src: string;
48
+ unitDim: 'px' | '%' | 'em' | 'rem';
49
+ height: number;
50
+ width: number;
51
+ blinkCDN?: boolean;
52
+ }
53
+
54
+ export interface Style {
55
+ selectors: (string | SelectorObject)[];
56
+ selectorsAdd?: string;
57
+ style: CSSProperties;
58
+ mediaText?: string;
59
+ atRuleType?: 'media' | 'keyframes' | 'supports';
60
+ state?: 'hover' | 'active' | 'focus' | 'visited';
61
+ }
62
+
63
+ export interface SelectorObject {
64
+ name: string;
65
+ active?: boolean;
66
+ }
67
+
68
+ export type CSSProperties = Record<string, string>;
69
+
70
+ export interface ParsedComponents {
71
+ components: Component[];
72
+ }
73
+
74
+ export interface ComponentQuery {
75
+ id?: string;
76
+ type?: string;
77
+ attributes?: Record<string, any>;
78
+ tagName?: string;
79
+ }
80
+
81
+ export interface StyleQuery {
82
+ selector?: string;
83
+ mediaText?: string;
84
+ state?: string;
85
+ }
86
+
87
+ export interface UpdateOptions {
88
+ merge?: boolean;
89
+ overwrite?: boolean;
90
+ }
91
+
92
+ export interface ToolbarRule {
93
+ selector: ComponentSelector;
94
+ config: ToolbarConfig;
95
+ }
96
+
97
+ export type ComponentSelector =
98
+ | { id: string }
99
+ | { type: string }
100
+ | { tagName: string }
101
+ | { attributes: Record<string, any> }
102
+ | { custom: (component: Component) => boolean };
103
+
104
+ export interface InitConfig {
105
+ // Required: iframe element ID (user creates this in their HTML)
106
+ iframeId: string;
107
+
108
+ // Required: page data
109
+ data: PageData | string;
110
+
111
+ // Optional: toolbar configuration (runtime only)
112
+ toolbars?: ToolbarInitConfig;
113
+
114
+ // Optional: UI container IDs (user controls placement)
115
+ ui?: {
116
+ sidebar?: {
117
+ containerId?: string; // Where to render sidebar (optional)
118
+ enabled?: boolean;
119
+ };
120
+ stats?: {
121
+ containerId?: string; // Where to render stats (optional)
122
+ enabled?: boolean;
123
+ };
124
+ selectedInfo?: {
125
+ containerId?: string; // Where to render selected component info (optional)
126
+ enabled?: boolean;
127
+ };
128
+ };
129
+
130
+ // Optional: event callbacks
131
+ onComponentSelect?: (component: Component) => void;
132
+ onComponentEdit?: (component: Component) => void;
133
+ onComponentDelete?: (component: Component) => void;
134
+ onComponentDuplicate?: (component: Component, duplicate: Component) => void;
135
+ }
136
+
137
+ export interface ToolbarInitConfig {
138
+ byId?: Record<string, ToolbarConfig>;
139
+ byType?: Record<string, ToolbarConfig>;
140
+ byTag?: Record<string, ToolbarConfig>;
141
+ default?: ToolbarConfig;
142
+ }
143
+
144
+ export interface EditorTsEditor {
145
+ page: any; // Page class (avoid circular dependency)
146
+ on(event: string, callback: Function): void;
147
+ off(event: string, callback: Function): void;
148
+ refresh(): void;
149
+ save(): string;
150
+ destroy(): void;
151
+ elements: {
152
+ iframe: HTMLIFrameElement;
153
+ sidebar?: HTMLElement;
154
+ stats?: HTMLElement;
155
+ selectedInfo?: HTMLElement;
156
+ };
157
+ }
@@ -0,0 +1,199 @@
1
+ import type { Component, CSSProperties } from '../types';
2
+
3
+ /**
4
+ * Utility functions for working with page data
5
+ */
6
+
7
+ /**
8
+ * Deep clone an object
9
+ */
10
+ export function deepClone<T>(obj: T): T {
11
+ return JSON.parse(JSON.stringify(obj));
12
+ }
13
+
14
+ /**
15
+ * Generate a unique ID
16
+ */
17
+ export function generateId(prefix = 'id'): string {
18
+ const random = Math.random().toString(36).substring(2, 9);
19
+ const timestamp = Date.now().toString(36);
20
+ return `${prefix}-${timestamp}-${random}`;
21
+ }
22
+
23
+ /**
24
+ * Convert CSS object to CSS string
25
+ */
26
+ export function cssObjectToString(css: CSSProperties): string {
27
+ return Object.entries(css)
28
+ .map(([key, value]) => `${key}: ${value};`)
29
+ .join(' ');
30
+ }
31
+
32
+ /**
33
+ * Convert CSS string to CSS object
34
+ */
35
+ export function cssStringToObject(cssString: string): CSSProperties {
36
+ const css: CSSProperties = {};
37
+ const declarations = cssString.split(';').filter((d) => d.trim());
38
+
39
+ for (const declaration of declarations) {
40
+ const [key, ...valueParts] = declaration.split(':');
41
+ if (key && valueParts.length > 0) {
42
+ css[key.trim()] = valueParts.join(':').trim();
43
+ }
44
+ }
45
+
46
+ return css;
47
+ }
48
+
49
+ /**
50
+ * Flatten component tree to array
51
+ */
52
+ export function flattenComponents(components: Component[]): Component[] {
53
+ const result: Component[] = [];
54
+
55
+ function traverse(comps: Component[]) {
56
+ for (const comp of comps) {
57
+ result.push(comp);
58
+ if (comp.components && comp.components.length > 0) {
59
+ traverse(comp.components);
60
+ }
61
+ }
62
+ }
63
+
64
+ traverse(components);
65
+ return result;
66
+ }
67
+
68
+ /**
69
+ * Find component by path (e.g., "0.1.2" for first component, second child, third grandchild)
70
+ */
71
+ export function getComponentByPath(components: Component[], path: string): Component | null {
72
+ const indices = path.split('.').map(Number);
73
+ let current: Component[] = components;
74
+
75
+ for (let i = 0; i < indices.length; i++) {
76
+ const index = indices[i];
77
+ if (index === undefined || !current[index]) {
78
+ return null;
79
+ }
80
+
81
+ if (i === indices.length - 1) {
82
+ return current[index]!;
83
+ }
84
+
85
+ current = current[index]!.components || [];
86
+ }
87
+
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Sanitize HTML string
93
+ */
94
+ export function sanitizeHTML(html: string): string {
95
+ return html
96
+ .replace(/&/g, '&amp;')
97
+ .replace(/</g, '&lt;')
98
+ .replace(/>/g, '&gt;')
99
+ .replace(/"/g, '&quot;')
100
+ .replace(/'/g, '&#039;');
101
+ }
102
+
103
+ /**
104
+ * Extract all IDs from component tree
105
+ */
106
+ export function extractComponentIds(components: Component[]): string[] {
107
+ const ids: string[] = [];
108
+
109
+ function traverse(comps: Component[]) {
110
+ for (const comp of comps) {
111
+ if (comp.attributes?.id) {
112
+ ids.push(comp.attributes.id as string);
113
+ }
114
+ if (comp.components && comp.components.length > 0) {
115
+ traverse(comp.components);
116
+ }
117
+ }
118
+ }
119
+
120
+ traverse(components);
121
+ return ids;
122
+ }
123
+
124
+ /**
125
+ * Merge CSS properties (later properties override earlier ones)
126
+ */
127
+ export function mergeCSSProperties(...properties: CSSProperties[]): CSSProperties {
128
+ return Object.assign({}, ...properties);
129
+ }
130
+
131
+ /**
132
+ * Check if a selector is an ID selector
133
+ */
134
+ export function isIdSelector(selector: string): boolean {
135
+ return selector.startsWith('#');
136
+ }
137
+
138
+ /**
139
+ * Check if a selector is a class selector
140
+ */
141
+ export function isClassSelector(selector: string): boolean {
142
+ return selector.startsWith('.');
143
+ }
144
+
145
+ /**
146
+ * Parse selector to extract name
147
+ */
148
+ export function parseSelector(selector: string): { type: 'id' | 'class' | 'tag' | 'complex'; name: string } {
149
+ if (isIdSelector(selector)) {
150
+ return { type: 'id', name: selector.substring(1) };
151
+ }
152
+ if (isClassSelector(selector)) {
153
+ return { type: 'class', name: selector.substring(1) };
154
+ }
155
+ if (selector.includes(' ') || selector.includes('>') || selector.includes('+')) {
156
+ return { type: 'complex', name: selector };
157
+ }
158
+ return { type: 'tag', name: selector };
159
+ }
160
+
161
+ /**
162
+ * Format file size in bytes to human-readable format
163
+ */
164
+ export function formatFileSize(bytes: number): string {
165
+ const units = ['B', 'KB', 'MB', 'GB'];
166
+ let size = bytes;
167
+ let unitIndex = 0;
168
+
169
+ while (size >= 1024 && unitIndex < units.length - 1) {
170
+ size /= 1024;
171
+ unitIndex++;
172
+ }
173
+
174
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
175
+ }
176
+
177
+ /**
178
+ * Validate URL
179
+ */
180
+ export function isValidURL(url: string): boolean {
181
+ try {
182
+ new URL(url);
183
+ return true;
184
+ } catch {
185
+ return false;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Extract domain from URL
191
+ */
192
+ export function extractDomain(url: string): string | null {
193
+ try {
194
+ const urlObj = new URL(url);
195
+ return urlObj.hostname;
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
@@ -0,0 +1,140 @@
1
+ import type { ToolbarConfig, ToolbarAction } from '../types';
2
+
3
+ /**
4
+ * Default toolbar configurations
5
+ */
6
+
7
+ /**
8
+ * Default toolbar actions available for all components
9
+ */
10
+ export const defaultToolbarActions: ToolbarAction[] = [
11
+ {
12
+ id: 'edit',
13
+ label: 'Edit',
14
+ icon: '✏️',
15
+ enabled: true,
16
+ description: 'Edit component properties in sidebar',
17
+ },
18
+ {
19
+ id: 'editJS',
20
+ label: 'Edit JS',
21
+ icon: '📜',
22
+ enabled: true,
23
+ description: 'Edit component JavaScript with Monaco editor',
24
+ },
25
+ {
26
+ id: 'duplicate',
27
+ label: 'Duplicate',
28
+ icon: '📋',
29
+ enabled: true,
30
+ description: 'Create a copy of this component',
31
+ },
32
+ {
33
+ id: 'delete',
34
+ label: 'Delete',
35
+ icon: '🗑️',
36
+ enabled: true,
37
+ danger: true,
38
+ description: 'Remove this component from the page',
39
+ },
40
+ ];
41
+
42
+ /**
43
+ * Default toolbar configuration
44
+ */
45
+ export const defaultToolbarConfig: ToolbarConfig = {
46
+ enabled: true,
47
+ actions: defaultToolbarActions,
48
+ };
49
+
50
+ /**
51
+ * Create a custom toolbar config
52
+ */
53
+ export function createToolbarConfig(
54
+ actions: ToolbarAction[],
55
+ enabled = true
56
+ ): ToolbarConfig {
57
+ return {
58
+ enabled,
59
+ actions,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Merge toolbar configs (later config overrides earlier)
65
+ */
66
+ export function mergeToolbarConfigs(
67
+ base: ToolbarConfig,
68
+ override: Partial<ToolbarConfig>
69
+ ): ToolbarConfig {
70
+ return {
71
+ enabled: override.enabled ?? base.enabled,
72
+ actions: override.actions ?? base.actions,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Get enabled actions from a toolbar config
78
+ */
79
+ export function getEnabledActions(config: ToolbarConfig): ToolbarAction[] {
80
+ return config.actions.filter((action) => action.enabled);
81
+ }
82
+
83
+ /**
84
+ * Find a specific action in toolbar config
85
+ */
86
+ export function findToolbarAction(
87
+ config: ToolbarConfig,
88
+ actionId: string
89
+ ): ToolbarAction | null {
90
+ const action = config.actions.find((a) => a.id === actionId);
91
+ return action || null;
92
+ }
93
+
94
+ /**
95
+ * Preset toolbar configs for common scenarios
96
+ */
97
+ export const toolbarPresets = {
98
+ /**
99
+ * Full toolbar with all actions
100
+ */
101
+ full: defaultToolbarConfig,
102
+
103
+ /**
104
+ * Read-only toolbar (only view actions)
105
+ */
106
+ readOnly: createToolbarConfig([
107
+ {
108
+ id: 'view',
109
+ label: 'View',
110
+ icon: '👁️',
111
+ enabled: true,
112
+ description: 'View component details',
113
+ },
114
+ ]),
115
+
116
+ /**
117
+ * Edit-only toolbar (no delete)
118
+ */
119
+ editOnly: createToolbarConfig([
120
+ { ...defaultToolbarActions[0]! }, // edit
121
+ { ...defaultToolbarActions[1]! }, // editJS
122
+ { ...defaultToolbarActions[2]! }, // duplicate
123
+ ]),
124
+
125
+ /**
126
+ * Minimal toolbar (edit and delete only)
127
+ */
128
+ minimal: createToolbarConfig([
129
+ { ...defaultToolbarActions[0]! }, // edit
130
+ { ...defaultToolbarActions[3]! }, // delete
131
+ ]),
132
+
133
+ /**
134
+ * Disabled toolbar
135
+ */
136
+ disabled: {
137
+ enabled: false,
138
+ actions: [],
139
+ },
140
+ };