aberdeen 1.0.12 → 1.0.13
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/dist/aberdeen.js +47 -6
- package/dist/aberdeen.js.map +3 -3
- package/dist-min/aberdeen.js +3 -3
- package/dist-min/aberdeen.js.map +3 -3
- package/package.json +1 -1
- package/src/aberdeen.ts +48 -6
- package/html-to-aberdeen +0 -354
package/package.json
CHANGED
package/src/aberdeen.ts
CHANGED
|
@@ -1148,28 +1148,67 @@ const arrayHandler: ProxyHandler<any[]> = {
|
|
|
1148
1148
|
},
|
|
1149
1149
|
};
|
|
1150
1150
|
|
|
1151
|
+
/**
|
|
1152
|
+
* Helper functions that wrap iterators to proxy values
|
|
1153
|
+
*/
|
|
1154
|
+
function wrapIteratorSingle(iterator: IterableIterator<any>): IterableIterator<any> {
|
|
1155
|
+
return {
|
|
1156
|
+
[Symbol.iterator]() { return this; },
|
|
1157
|
+
next() {
|
|
1158
|
+
const result = iterator.next();
|
|
1159
|
+
if (result.done) return result;
|
|
1160
|
+
return {
|
|
1161
|
+
done: false,
|
|
1162
|
+
value: optProxy(result.value)
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function wrapIteratorPair(iterator: IterableIterator<[any, any]>): IterableIterator<[any, any]> {
|
|
1168
|
+
return {
|
|
1169
|
+
[Symbol.iterator]() { return this; },
|
|
1170
|
+
next() {
|
|
1171
|
+
const result = iterator.next();
|
|
1172
|
+
if (result.done) return result;
|
|
1173
|
+
return {
|
|
1174
|
+
done: false,
|
|
1175
|
+
value: [optProxy(result.value[0]), optProxy(result.value[1])]
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1151
1181
|
const mapMethodHandlers = {
|
|
1152
1182
|
get(this: any, key: any): any {
|
|
1153
1183
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1184
|
+
// Make sure key is unproxied
|
|
1185
|
+
if (typeof key === "object" && key)
|
|
1186
|
+
key = (key as any)[TARGET_SYMBOL] || key;
|
|
1154
1187
|
subscribe(target, key);
|
|
1155
1188
|
return optProxy(target.get(key));
|
|
1156
1189
|
},
|
|
1157
1190
|
set(this: any, key: any, newData: any): any {
|
|
1158
1191
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1159
|
-
// Make sure newData
|
|
1192
|
+
// Make sure key and newData are unproxied
|
|
1193
|
+
if (typeof key === "object" && key)
|
|
1194
|
+
key = (key as any)[TARGET_SYMBOL] || key;
|
|
1160
1195
|
if (typeof newData === "object" && newData)
|
|
1161
1196
|
newData = (newData as any)[TARGET_SYMBOL] || newData;
|
|
1162
1197
|
const oldData = target.get(key);
|
|
1163
1198
|
if (newData !== oldData) {
|
|
1199
|
+
const oldSize = target.size;
|
|
1164
1200
|
target.set(key, newData);
|
|
1165
1201
|
emit(target, key, newData, oldData);
|
|
1166
|
-
emit(target, MAP_SIZE_SYMBOL, target.size,
|
|
1202
|
+
emit(target, MAP_SIZE_SYMBOL, target.size, oldSize);
|
|
1167
1203
|
runImmediateQueue();
|
|
1168
1204
|
}
|
|
1169
1205
|
return this;
|
|
1170
1206
|
},
|
|
1171
1207
|
delete(this: any, key: any): boolean {
|
|
1172
1208
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1209
|
+
// Make sure key is unproxied
|
|
1210
|
+
if (typeof key === "object" && key)
|
|
1211
|
+
key = (key as any)[TARGET_SYMBOL] || key;
|
|
1173
1212
|
const oldData = target.get(key);
|
|
1174
1213
|
const result: boolean = target.delete(key);
|
|
1175
1214
|
if (result) {
|
|
@@ -1192,28 +1231,31 @@ const mapMethodHandlers = {
|
|
|
1192
1231
|
},
|
|
1193
1232
|
has(this: any, key: any): boolean {
|
|
1194
1233
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1234
|
+
// Make sure key is unproxied
|
|
1235
|
+
if (typeof key === "object" && key)
|
|
1236
|
+
key = (key as any)[TARGET_SYMBOL] || key;
|
|
1195
1237
|
subscribe(target, key);
|
|
1196
1238
|
return target.has(key);
|
|
1197
1239
|
},
|
|
1198
1240
|
keys(this: any): IterableIterator<any> {
|
|
1199
1241
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1200
1242
|
subscribe(target, ANY_SYMBOL);
|
|
1201
|
-
return target.keys();
|
|
1243
|
+
return wrapIteratorSingle(target.keys());
|
|
1202
1244
|
},
|
|
1203
1245
|
values(this: any): IterableIterator<any> {
|
|
1204
1246
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1205
1247
|
subscribe(target, ANY_SYMBOL);
|
|
1206
|
-
return target.values();
|
|
1248
|
+
return wrapIteratorSingle(target.values());
|
|
1207
1249
|
},
|
|
1208
1250
|
entries(this: any): IterableIterator<[any, any]> {
|
|
1209
1251
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1210
1252
|
subscribe(target, ANY_SYMBOL);
|
|
1211
|
-
return target.entries();
|
|
1253
|
+
return wrapIteratorPair(target.entries());
|
|
1212
1254
|
},
|
|
1213
1255
|
[Symbol.iterator](this: any): IterableIterator<[any, any]> {
|
|
1214
1256
|
const target: Map<any, any> = this[TARGET_SYMBOL];
|
|
1215
1257
|
subscribe(target, ANY_SYMBOL);
|
|
1216
|
-
return target[Symbol.iterator]();
|
|
1258
|
+
return wrapIteratorPair(target[Symbol.iterator]());
|
|
1217
1259
|
}
|
|
1218
1260
|
};
|
|
1219
1261
|
|
package/html-to-aberdeen
DELETED
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// WARNING: This script was created by Claude Sonnet 3.7, and hasn't
|
|
4
|
-
// received any human code review. It seems to do the job though!
|
|
5
|
-
|
|
6
|
-
export function parseHTML(html) {
|
|
7
|
-
const result = {
|
|
8
|
-
body: []
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
let currentPosition = 0;
|
|
12
|
-
let currentParent = result;
|
|
13
|
-
const stack = [];
|
|
14
|
-
|
|
15
|
-
while (currentPosition < html.length) {
|
|
16
|
-
// Skip whitespace
|
|
17
|
-
while (currentPosition < html.length && /\s/.test(html[currentPosition])) {
|
|
18
|
-
currentPosition++;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (currentPosition >= html.length) break;
|
|
22
|
-
|
|
23
|
-
// Check for comment
|
|
24
|
-
if (html.substring(currentPosition, currentPosition + 4) === '<!--') {
|
|
25
|
-
const endComment = html.indexOf('-->', currentPosition);
|
|
26
|
-
if (endComment === -1) break;
|
|
27
|
-
|
|
28
|
-
const commentContent = html.substring(currentPosition + 4, endComment);
|
|
29
|
-
currentParent.children = currentParent.children || [];
|
|
30
|
-
currentParent.children.push({
|
|
31
|
-
type: 'comment',
|
|
32
|
-
content: commentContent
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
currentPosition = endComment + 3;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Check for tag
|
|
40
|
-
if (html[currentPosition] === '<') {
|
|
41
|
-
// Check if it's a closing tag
|
|
42
|
-
if (html[currentPosition + 1] === '/') {
|
|
43
|
-
const endTag = html.indexOf('>', currentPosition);
|
|
44
|
-
if (endTag === -1) break;
|
|
45
|
-
|
|
46
|
-
const tagName = html.substring(currentPosition + 2, endTag).trim().toLowerCase();
|
|
47
|
-
|
|
48
|
-
// Pop from stack
|
|
49
|
-
if (stack.length > 0) {
|
|
50
|
-
currentParent = stack.pop();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
currentPosition = endTag + 1;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// It's an opening tag
|
|
58
|
-
const endTag = html.indexOf('>', currentPosition);
|
|
59
|
-
if (endTag === -1) break;
|
|
60
|
-
|
|
61
|
-
const selfClosing = html[endTag - 1] === '/';
|
|
62
|
-
const tagContent = html.substring(currentPosition + 1, selfClosing ? endTag - 1 : endTag).trim();
|
|
63
|
-
const spaceIndex = tagContent.search(/\s/);
|
|
64
|
-
|
|
65
|
-
let tagName, attributesStr;
|
|
66
|
-
if (spaceIndex === -1) {
|
|
67
|
-
tagName = tagContent;
|
|
68
|
-
attributesStr = '';
|
|
69
|
-
} else {
|
|
70
|
-
tagName = tagContent.substring(0, spaceIndex);
|
|
71
|
-
attributesStr = tagContent.substring(spaceIndex + 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
tagName = tagName.toLowerCase();
|
|
75
|
-
|
|
76
|
-
// Parse attributes
|
|
77
|
-
const attributes = [];
|
|
78
|
-
let attrMatch;
|
|
79
|
-
const attrRegex = /([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
80
|
-
|
|
81
|
-
while ((attrMatch = attrRegex.exec(attributesStr)) !== null) {
|
|
82
|
-
const name = attrMatch[1];
|
|
83
|
-
const value = attrMatch[2] || attrMatch[3] || attrMatch[4] || '';
|
|
84
|
-
attributes.push({ name, value });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const newElement = {
|
|
88
|
-
type: 'element',
|
|
89
|
-
tagName,
|
|
90
|
-
attributes,
|
|
91
|
-
children: []
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Add to current parent
|
|
95
|
-
if (currentParent === result) {
|
|
96
|
-
currentParent.body = currentParent.body || [];
|
|
97
|
-
currentParent.body.push(newElement);
|
|
98
|
-
} else {
|
|
99
|
-
currentParent.children = currentParent.children || [];
|
|
100
|
-
currentParent.children.push(newElement);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!selfClosing && !['br', 'hr', 'img', 'input', 'link', 'meta'].includes(tagName)) {
|
|
104
|
-
stack.push(currentParent);
|
|
105
|
-
currentParent = newElement;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
currentPosition = endTag + 1;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// It's text content
|
|
113
|
-
let endText = html.indexOf('<', currentPosition);
|
|
114
|
-
if (endText === -1) endText = html.length;
|
|
115
|
-
|
|
116
|
-
const textContent = html.substring(currentPosition, endText);
|
|
117
|
-
if (textContent.trim()) {
|
|
118
|
-
if (currentParent === result) {
|
|
119
|
-
currentParent.body = currentParent.body || [];
|
|
120
|
-
currentParent.body.push({
|
|
121
|
-
type: 'text',
|
|
122
|
-
content: textContent
|
|
123
|
-
});
|
|
124
|
-
} else {
|
|
125
|
-
currentParent.children = currentParent.children || [];
|
|
126
|
-
currentParent.children.push({
|
|
127
|
-
type: 'text',
|
|
128
|
-
content: textContent
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
currentPosition = endText;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Read from stdin
|
|
140
|
-
let html = '';
|
|
141
|
-
process.stdin.setEncoding('utf8');
|
|
142
|
-
process.stdin.on('data', (chunk) => {
|
|
143
|
-
html += chunk;
|
|
144
|
-
});
|
|
145
|
-
process.stdin.on('end', () => {
|
|
146
|
-
// Convert HTML to Aberdeen code
|
|
147
|
-
const aberdeenCode = convertHTMLToAberdeen(html);
|
|
148
|
-
|
|
149
|
-
// Output to stdout
|
|
150
|
-
process.stdout.write(aberdeenCode);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Main conversion function
|
|
154
|
-
function convertHTMLToAberdeen(html) {
|
|
155
|
-
// Parse HTML into a simple AST
|
|
156
|
-
const ast = parseHTML(html);
|
|
157
|
-
|
|
158
|
-
// Generate the Aberdeen code
|
|
159
|
-
let aberdeenCode = ``;
|
|
160
|
-
|
|
161
|
-
// Process the body's children
|
|
162
|
-
for (const node of ast.body) {
|
|
163
|
-
aberdeenCode += processNode(node);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return aberdeenCode;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Process a node and return Aberdeen code
|
|
170
|
-
function processNode(node, indentLevel = 0) {
|
|
171
|
-
const indent = ' '.repeat(indentLevel);
|
|
172
|
-
|
|
173
|
-
if (node.type === 'text') {
|
|
174
|
-
const text = node.content.trim();
|
|
175
|
-
if (text) {
|
|
176
|
-
return `${indent}$(':${escapeString(text)}');\n`;
|
|
177
|
-
}
|
|
178
|
-
return '';
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (node.type === 'comment') {
|
|
182
|
-
return `${indent}// ${node.content.trim()}\n`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (node.type === 'element') {
|
|
186
|
-
// Get tag name
|
|
187
|
-
const tagName = node.tagName.toLowerCase();
|
|
188
|
-
|
|
189
|
-
// Get classes
|
|
190
|
-
const classAttr = node.attributes.find(attr => attr.name === 'class');
|
|
191
|
-
const classes = classAttr
|
|
192
|
-
? classAttr.value.split(/\s+/).filter(Boolean).join('.')
|
|
193
|
-
: '';
|
|
194
|
-
|
|
195
|
-
// Get other attributes
|
|
196
|
-
const attributes = {};
|
|
197
|
-
for (const attr of node.attributes) {
|
|
198
|
-
if (attr.name !== 'class') {
|
|
199
|
-
attributes[attr.name] = attr.value;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check if node has only text content
|
|
204
|
-
const hasOnlyTextContent =
|
|
205
|
-
node.children.length === 1 &&
|
|
206
|
-
node.children[0].type === 'text' &&
|
|
207
|
-
node.children[0].content.trim();
|
|
208
|
-
|
|
209
|
-
// Build the tag string
|
|
210
|
-
let tagString = tagName;
|
|
211
|
-
if (classes) {
|
|
212
|
-
tagString += `.${classes}`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (hasOnlyTextContent) {
|
|
216
|
-
tagString += `:${escapeString(node.children[0].content.trim())}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let result = `${indent}$('${tagString}'`;
|
|
220
|
-
|
|
221
|
-
// Add attributes if any
|
|
222
|
-
if (Object.keys(attributes).length > 0) {
|
|
223
|
-
result += `, ${formatAttributes(attributes, indent)}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Process child nodes
|
|
227
|
-
const childNodes = node.children.filter(child =>
|
|
228
|
-
child.type === 'element' ||
|
|
229
|
-
(child.type === 'text' && child.content.trim())
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
if (childNodes.length > 0 && !hasOnlyTextContent) {
|
|
233
|
-
// Get all descendants in a single-child chain
|
|
234
|
-
const singleChildChain = getSingleChildChain(node);
|
|
235
|
-
|
|
236
|
-
if (singleChildChain.length > 1) {
|
|
237
|
-
// We have a chain of single children, add them all on the same line
|
|
238
|
-
for (let i = 1; i < singleChildChain.length; i++) {
|
|
239
|
-
const chainNode = singleChildChain[i];
|
|
240
|
-
|
|
241
|
-
// Get tag name
|
|
242
|
-
const chainTagName = chainNode.tagName.toLowerCase();
|
|
243
|
-
|
|
244
|
-
// Get classes
|
|
245
|
-
const chainClassAttr = chainNode.attributes.find(attr => attr.name === 'class');
|
|
246
|
-
const chainClasses = chainClassAttr
|
|
247
|
-
? chainClassAttr.value.split(/\s+/).filter(Boolean).join('.')
|
|
248
|
-
: '';
|
|
249
|
-
|
|
250
|
-
// Build the tag string
|
|
251
|
-
let chainTagString = chainTagName;
|
|
252
|
-
if (chainClasses) {
|
|
253
|
-
chainTagString += `.${chainClasses}`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Check if node has only text content
|
|
257
|
-
const chainHasOnlyTextContent =
|
|
258
|
-
chainNode.children.length === 1 &&
|
|
259
|
-
chainNode.children[0].type === 'text' &&
|
|
260
|
-
chainNode.children[0].content.trim();
|
|
261
|
-
|
|
262
|
-
if (chainHasOnlyTextContent) {
|
|
263
|
-
chainTagString += `:${escapeString(chainNode.children[0].content.trim())}`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
result += `, '${chainTagString}'`;
|
|
267
|
-
|
|
268
|
-
// Add attributes if any
|
|
269
|
-
const chainAttributes = {};
|
|
270
|
-
for (const attr of chainNode.attributes) {
|
|
271
|
-
if (attr.name !== 'class') {
|
|
272
|
-
chainAttributes[attr.name] = attr.value;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (Object.keys(chainAttributes).length > 0) {
|
|
277
|
-
result += `, ${formatAttributes(chainAttributes, indent)}`;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Check if the last node in the chain has any non-text children
|
|
282
|
-
const lastNode = singleChildChain[singleChildChain.length - 1];
|
|
283
|
-
const lastNodeChildren = lastNode.children.filter(child =>
|
|
284
|
-
child.type === 'element' ||
|
|
285
|
-
(child.type === 'text' && child.content.trim() &&
|
|
286
|
-
!(lastNode.children.length === 1 && lastNode.children[0].type === 'text'))
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
if (lastNodeChildren.length > 0) {
|
|
290
|
-
result += `, () => {\n`;
|
|
291
|
-
for (const child of lastNodeChildren) {
|
|
292
|
-
result += processNode(child, indentLevel + 1);
|
|
293
|
-
}
|
|
294
|
-
result += `${indent}}`;
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
// Multiple children, use a content function
|
|
298
|
-
result += `, () => {\n`;
|
|
299
|
-
for (const child of childNodes) {
|
|
300
|
-
result += processNode(child, indentLevel + 1);
|
|
301
|
-
}
|
|
302
|
-
result += `${indent}}`;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
result += `);\n`;
|
|
307
|
-
return result;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return '';
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Get a chain of nodes where each node has exactly one element child
|
|
314
|
-
function getSingleChildChain(node) {
|
|
315
|
-
const chain = [node];
|
|
316
|
-
let current = node;
|
|
317
|
-
|
|
318
|
-
while (true) {
|
|
319
|
-
// Get element children
|
|
320
|
-
const elementChildren = current.children.filter(child => child.type === 'element');
|
|
321
|
-
|
|
322
|
-
// If there's exactly one element child, add it to the chain
|
|
323
|
-
if (elementChildren.length === 1) {
|
|
324
|
-
current = elementChildren[0];
|
|
325
|
-
chain.push(current);
|
|
326
|
-
} else {
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return chain;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Format attributes object with proper indentation
|
|
335
|
-
function formatAttributes(attributes, indent) {
|
|
336
|
-
const attrLines = JSON.stringify(attributes, null, 4).split('\n');
|
|
337
|
-
|
|
338
|
-
if (attrLines.length <= 1) {
|
|
339
|
-
return JSON.stringify(attributes);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return attrLines.map((line, i) => {
|
|
343
|
-
if (i === 0) return line;
|
|
344
|
-
return indent + line;
|
|
345
|
-
}).join('\n');
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Escape special characters in strings
|
|
349
|
-
function escapeString(str) {
|
|
350
|
-
return str
|
|
351
|
-
.replace(/\\/g, '\\\\')
|
|
352
|
-
.replace(/'/g, "\\'")
|
|
353
|
-
.replace(/\n/g, '\\n');
|
|
354
|
-
}
|