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.
@@ -0,0 +1,206 @@
1
+ import type { PageBody, Component, ComponentQuery } from '../types';
2
+
3
+ /**
4
+ * Manager for handling component operations
5
+ */
6
+ export class ComponentManager {
7
+ private body: PageBody;
8
+ private parsedComponents: Component[];
9
+
10
+ constructor(body: PageBody) {
11
+ this.body = body;
12
+ this.parsedComponents = this.parse();
13
+ }
14
+
15
+ /**
16
+ * Parse components from JSON string
17
+ */
18
+ private parse(): Component[] {
19
+ try {
20
+ return JSON.parse(this.body.components) as Component[];
21
+ } catch (error) {
22
+ console.error('Failed to parse components:', error);
23
+ return [];
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Find components by query
29
+ */
30
+ find(query: ComponentQuery): Component[] {
31
+ return this.findInTree(this.parsedComponents, query);
32
+ }
33
+
34
+ /**
35
+ * Recursively search component tree
36
+ */
37
+ private findInTree(components: Component[], query: ComponentQuery): Component[] {
38
+ const results: Component[] = [];
39
+
40
+ for (const component of components) {
41
+ let matches = true;
42
+
43
+ // Check ID
44
+ if (query.id && component.attributes?.id !== query.id) {
45
+ matches = false;
46
+ }
47
+
48
+ // Check type
49
+ if (query.type && component.type !== query.type) {
50
+ matches = false;
51
+ }
52
+
53
+ // Check tagName
54
+ if (query.tagName && component.tagName !== query.tagName) {
55
+ matches = false;
56
+ }
57
+
58
+ // Check attributes
59
+ if (query.attributes && matches) {
60
+ for (const [key, value] of Object.entries(query.attributes)) {
61
+ if (component.attributes?.[key] !== value) {
62
+ matches = false;
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ if (matches) {
69
+ results.push(component);
70
+ }
71
+
72
+ // Search in nested components
73
+ if (component.components && component.components.length > 0) {
74
+ results.push(...this.findInTree(component.components, query));
75
+ }
76
+ }
77
+
78
+ return results;
79
+ }
80
+
81
+ /**
82
+ * Find a single component by ID
83
+ */
84
+ findById(id: string): Component | null {
85
+ const results = this.find({ id });
86
+ return results.length > 0 ? results[0]! : null;
87
+ }
88
+
89
+ /**
90
+ * Find components by type
91
+ */
92
+ findByType(type: string): Component[] {
93
+ return this.find({ type });
94
+ }
95
+
96
+ /**
97
+ * Find components by tag name
98
+ */
99
+ findByTagName(tagName: string): Component[] {
100
+ return this.find({ tagName });
101
+ }
102
+
103
+ /**
104
+ * Add a component to the root level
105
+ */
106
+ addComponent(component: Component): void {
107
+ this.parsedComponents.push(component);
108
+ }
109
+
110
+ /**
111
+ * Add a component as a child of another component
112
+ */
113
+ addChildComponent(parentId: string, component: Component): boolean {
114
+ const parent = this.findById(parentId);
115
+ if (parent) {
116
+ if (!parent.components) {
117
+ parent.components = [];
118
+ }
119
+ parent.components.push(component);
120
+ return true;
121
+ }
122
+ return false;
123
+ }
124
+
125
+ /**
126
+ * Remove a component by ID
127
+ */
128
+ removeComponent(id: string): boolean {
129
+ return this.removeFromTree(this.parsedComponents, id);
130
+ }
131
+
132
+ /**
133
+ * Recursively remove component from tree
134
+ */
135
+ private removeFromTree(components: Component[], id: string): boolean {
136
+ for (let i = 0; i < components.length; i++) {
137
+ const component = components[i];
138
+
139
+ if (component?.attributes?.id === id) {
140
+ components.splice(i, 1);
141
+ return true;
142
+ }
143
+
144
+ if (component?.components && component.components.length > 0) {
145
+ if (this.removeFromTree(component.components, id)) {
146
+ return true;
147
+ }
148
+ }
149
+ }
150
+
151
+ return false;
152
+ }
153
+
154
+ /**
155
+ * Update a component's attributes
156
+ */
157
+ updateComponent(id: string, updates: Partial<Component>): boolean {
158
+ const component = this.findById(id);
159
+ if (component) {
160
+ Object.assign(component, updates);
161
+ return true;
162
+ }
163
+ return false;
164
+ }
165
+
166
+ /**
167
+ * Get all components
168
+ */
169
+ getAll(): Component[] {
170
+ return this.parsedComponents;
171
+ }
172
+
173
+ /**
174
+ * Get component count
175
+ */
176
+ count(): number {
177
+ return this.countInTree(this.parsedComponents);
178
+ }
179
+
180
+ /**
181
+ * Recursively count components
182
+ */
183
+ private countInTree(components: Component[]): number {
184
+ let count = components.length;
185
+ for (const component of components) {
186
+ if (component.components && component.components.length > 0) {
187
+ count += this.countInTree(component.components);
188
+ }
189
+ }
190
+ return count;
191
+ }
192
+
193
+ /**
194
+ * Sync changes back to page body
195
+ */
196
+ sync(): void {
197
+ this.body.components = JSON.stringify(this.parsedComponents);
198
+ }
199
+
200
+ /**
201
+ * Replace all components
202
+ */
203
+ replaceAll(components: Component[]): void {
204
+ this.parsedComponents = components;
205
+ }
206
+ }
@@ -0,0 +1,138 @@
1
+ import type { PageData, PageBody } from '../types';
2
+ import { ComponentManager } from './ComponentManager';
3
+ import { StyleManager } from './StyleManager';
4
+ import { AssetManager } from './AssetManager';
5
+ import { ToolbarManager } from './ToolbarManager';
6
+
7
+ /**
8
+ * Main class for managing page content
9
+ */
10
+ export class Page {
11
+ private data: PageData;
12
+ public components: ComponentManager;
13
+ public styles: StyleManager;
14
+ public assets: AssetManager;
15
+ public toolbars: ToolbarManager;
16
+
17
+ constructor(pageData: PageData | string) {
18
+ if (typeof pageData === 'string') {
19
+ this.data = JSON.parse(pageData) as PageData;
20
+ } else {
21
+ this.data = pageData;
22
+ }
23
+
24
+ // Initialize managers
25
+ this.components = new ComponentManager(this.data.body);
26
+ this.styles = new StyleManager(this.data.body);
27
+ this.assets = new AssetManager(this.data.body);
28
+ this.toolbars = new ToolbarManager();
29
+ }
30
+
31
+ /**
32
+ * Get the page title
33
+ */
34
+ getTitle(): string {
35
+ return this.data.title;
36
+ }
37
+
38
+ /**
39
+ * Set the page title
40
+ */
41
+ setTitle(title: string): void {
42
+ this.data.title = title;
43
+ }
44
+
45
+ /**
46
+ * Get the page item ID
47
+ */
48
+ getItemId(): number {
49
+ return this.data.item_id;
50
+ }
51
+
52
+ /**
53
+ * Set the page item ID
54
+ */
55
+ setItemId(itemId: number): void {
56
+ this.data.item_id = itemId;
57
+ }
58
+
59
+ /**
60
+ * Get the raw HTML
61
+ */
62
+ getHTML(): string {
63
+ return this.data.body.html;
64
+ }
65
+
66
+ /**
67
+ * Set the raw HTML
68
+ */
69
+ setHTML(html: string): void {
70
+ this.data.body.html = html;
71
+ }
72
+
73
+ /**
74
+ * Get the compiled CSS
75
+ */
76
+ getCSS(): string {
77
+ return this.data.body.css;
78
+ }
79
+
80
+ /**
81
+ * Set the compiled CSS
82
+ */
83
+ setCSS(css: string): void {
84
+ this.data.body.css = css;
85
+ }
86
+
87
+ /**
88
+ * Get the page body
89
+ */
90
+ getBody(): PageBody {
91
+ return this.data.body;
92
+ }
93
+
94
+ /**
95
+ * Export the page as JSON string
96
+ */
97
+ toJSON(): string {
98
+ // Sync all managers back to data
99
+ this.components.sync();
100
+ this.styles.sync();
101
+ this.assets.sync();
102
+
103
+ return JSON.stringify(this.data, null, 2);
104
+ }
105
+
106
+ /**
107
+ * Export the page as object
108
+ */
109
+ toObject(): PageData {
110
+ // Sync all managers back to data
111
+ this.components.sync();
112
+ this.styles.sync();
113
+ this.assets.sync();
114
+
115
+ return this.data;
116
+ }
117
+
118
+ /**
119
+ * Load page from JSON file
120
+ */
121
+ static fromJSON(json: string): Page {
122
+ return new Page(json);
123
+ }
124
+
125
+ /**
126
+ * Clone the page
127
+ */
128
+ clone(): Page {
129
+ return new Page(JSON.parse(JSON.stringify(this.data)));
130
+ }
131
+
132
+ /**
133
+ * Get raw page data
134
+ */
135
+ getRawData(): PageData {
136
+ return this.data;
137
+ }
138
+ }
@@ -0,0 +1,221 @@
1
+ import type { PageBody, Style, StyleQuery, CSSProperties } from '../types';
2
+
3
+ /**
4
+ * Manager for handling CSS styles
5
+ */
6
+ export class StyleManager {
7
+ private body: PageBody;
8
+ private styles: Style[];
9
+
10
+ constructor(body: PageBody) {
11
+ this.body = body;
12
+ this.styles = body.styles || [];
13
+ }
14
+
15
+ /**
16
+ * Find styles by query
17
+ */
18
+ find(query: StyleQuery): Style[] {
19
+ return this.styles.filter((style) => {
20
+ let matches = true;
21
+
22
+ // Check selector
23
+ if (query.selector) {
24
+ const hasSelector = style.selectors.some((sel) => {
25
+ if (typeof sel === 'string') {
26
+ return sel === query.selector || sel.includes(query.selector!);
27
+ }
28
+ return sel.name === query.selector;
29
+ });
30
+
31
+ if (!hasSelector && style.selectorsAdd !== query.selector) {
32
+ matches = false;
33
+ }
34
+ }
35
+
36
+ // Check media query
37
+ if (query.mediaText && style.mediaText !== query.mediaText) {
38
+ matches = false;
39
+ }
40
+
41
+ // Check state
42
+ if (query.state && style.state !== query.state) {
43
+ matches = false;
44
+ }
45
+
46
+ return matches;
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Find styles for a specific selector
52
+ */
53
+ findBySelector(selector: string): Style[] {
54
+ return this.find({ selector });
55
+ }
56
+
57
+ /**
58
+ * Find styles for a media query
59
+ */
60
+ findByMedia(mediaText: string): Style[] {
61
+ return this.find({ mediaText });
62
+ }
63
+
64
+ /**
65
+ * Add a new style rule
66
+ */
67
+ addStyle(style: Style): void {
68
+ this.styles.push(style);
69
+ }
70
+
71
+ /**
72
+ * Remove styles by selector
73
+ */
74
+ removeBySelector(selector: string): number {
75
+ const initialLength = this.styles.length;
76
+ this.styles = this.styles.filter((style) => {
77
+ const hasSelector = style.selectors.some((sel) => {
78
+ if (typeof sel === 'string') {
79
+ return sel === selector;
80
+ }
81
+ return sel.name === selector;
82
+ });
83
+ return !hasSelector && style.selectorsAdd !== selector;
84
+ });
85
+ return initialLength - this.styles.length;
86
+ }
87
+
88
+ /**
89
+ * Update styles for a selector
90
+ */
91
+ updateStyle(selector: string, properties: CSSProperties, options?: { mediaText?: string; state?: string }): boolean {
92
+ const matchingStyles = this.styles.filter((style) => {
93
+ const hasSelector = style.selectors.some((sel) => {
94
+ if (typeof sel === 'string') {
95
+ return sel === selector;
96
+ }
97
+ return sel.name === selector;
98
+ }) || style.selectorsAdd === selector;
99
+
100
+ if (!hasSelector) return false;
101
+
102
+ // Check media query if specified
103
+ if (options?.mediaText && style.mediaText !== options.mediaText) {
104
+ return false;
105
+ }
106
+
107
+ // Check state if specified
108
+ if (options?.state && style.state !== options.state) {
109
+ return false;
110
+ }
111
+
112
+ return true;
113
+ });
114
+
115
+ if (matchingStyles.length > 0) {
116
+ matchingStyles.forEach((style) => {
117
+ Object.assign(style.style, properties);
118
+ });
119
+ return true;
120
+ }
121
+
122
+ return false;
123
+ }
124
+
125
+ /**
126
+ * Get style properties for a selector
127
+ */
128
+ getStyleProperties(selector: string, options?: { mediaText?: string; state?: string }): CSSProperties | null {
129
+ const styles = this.find({ selector, ...options });
130
+ if (styles.length > 0) {
131
+ return styles[0]!.style;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ /**
137
+ * Get all styles
138
+ */
139
+ getAll(): Style[] {
140
+ return this.styles;
141
+ }
142
+
143
+ /**
144
+ * Get style count
145
+ */
146
+ count(): number {
147
+ return this.styles.length;
148
+ }
149
+
150
+ /**
151
+ * Compile styles to CSS string
152
+ */
153
+ compileToCSS(): string {
154
+ const cssRules: string[] = [];
155
+
156
+ for (const style of this.styles) {
157
+ const selector = this.buildSelector(style);
158
+ const properties = this.buildProperties(style.style);
159
+ const rule = `${selector}{${properties}}`;
160
+
161
+ if (style.atRuleType === 'media' && style.mediaText) {
162
+ cssRules.push(`@media ${style.mediaText}{${rule}}`);
163
+ } else {
164
+ cssRules.push(rule);
165
+ }
166
+ }
167
+
168
+ return cssRules.join('');
169
+ }
170
+
171
+ /**
172
+ * Build selector string from style
173
+ */
174
+ private buildSelector(style: Style): string {
175
+ if (style.selectorsAdd) {
176
+ let selector = style.selectorsAdd;
177
+ if (style.state) {
178
+ selector += `:${style.state}`;
179
+ }
180
+ return selector;
181
+ }
182
+
183
+ const selectors = style.selectors.map((sel) => {
184
+ if (typeof sel === 'string') {
185
+ return sel;
186
+ }
187
+ return sel.name;
188
+ });
189
+
190
+ let selector = selectors.join(', ');
191
+ if (style.state) {
192
+ selector = selectors.map((s) => `${s}:${style.state}`).join(', ');
193
+ }
194
+
195
+ return selector;
196
+ }
197
+
198
+ /**
199
+ * Build CSS properties string
200
+ */
201
+ private buildProperties(properties: CSSProperties): string {
202
+ return Object.entries(properties)
203
+ .map(([key, value]) => `${key}:${value};`)
204
+ .join('');
205
+ }
206
+
207
+ /**
208
+ * Sync changes back to page body
209
+ */
210
+ sync(): void {
211
+ this.body.styles = this.styles;
212
+ this.body.css = this.compileToCSS();
213
+ }
214
+
215
+ /**
216
+ * Replace all styles
217
+ */
218
+ replaceAll(styles: Style[]): void {
219
+ this.styles = styles;
220
+ }
221
+ }