@junnyontop-pixel/neo-app 2.2.0 → 2.3.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/compiler/NeoParser.js +82 -5
- package/core/NeoCore.js +26 -0
- package/package.json +1 -1
- package/src/App.neo +7 -1
package/compiler/NeoParser.js
CHANGED
|
@@ -6,7 +6,8 @@ export class NeoParser {
|
|
|
6
6
|
styles: [],
|
|
7
7
|
innerHTML: "",
|
|
8
8
|
events: [],
|
|
9
|
-
children: []
|
|
9
|
+
children: [],
|
|
10
|
+
attrs: {}
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
// @ID:Tag
|
|
@@ -28,9 +29,22 @@ export class NeoParser {
|
|
|
28
29
|
// { content }
|
|
29
30
|
const contentMatch = rawCode.match(/\{([\s\S]*?)\}$/);
|
|
30
31
|
if (contentMatch) {
|
|
31
|
-
let
|
|
32
|
-
let rest = content; // ⭐ 여기서 rest 생성
|
|
32
|
+
let rest = contentMatch[1].trim();
|
|
33
33
|
|
|
34
|
+
// // ✅ 0) ::attrs 먼저 처리 (메타 블록은 먼저 제거해야 함)
|
|
35
|
+
// while (rest.includes("::attrs")) {
|
|
36
|
+
// const start = rest.indexOf("::attrs");
|
|
37
|
+
// const { block, nextIndex } = extractBlock(rest, start);
|
|
38
|
+
|
|
39
|
+
// // attrs 누적 (여러 ::attrs 지원 가능)
|
|
40
|
+
// const parsed = parseAttrs(block);
|
|
41
|
+
// result.attrs = { ...result.attrs, ...parsed };
|
|
42
|
+
|
|
43
|
+
// // ::attrs 블록 제거
|
|
44
|
+
// rest = rest.slice(0, start) + rest.slice(nextIndex);
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
// ✅ 1) children 태그들 파싱
|
|
34
48
|
while (rest.includes('@')) {
|
|
35
49
|
const start = rest.indexOf('@');
|
|
36
50
|
const { block, nextIndex } = extractTag(rest, start);
|
|
@@ -40,27 +54,42 @@ export class NeoParser {
|
|
|
40
54
|
rest = rest.slice(0, start) + rest.slice(nextIndex);
|
|
41
55
|
}
|
|
42
56
|
|
|
57
|
+
// ✅ 2) ::attrs 메타 블록 파싱
|
|
58
|
+
if (rest.includes('::attrs')) {
|
|
59
|
+
const start = rest.indexOf('::attrs');
|
|
60
|
+
const { block, nextIndex } = extractBlock(rest, start);
|
|
61
|
+
|
|
62
|
+
result.attrs = parseAttrs(block);
|
|
63
|
+
|
|
64
|
+
rest = rest.slice(0, start) + rest.slice(nextIndex);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ✅ 2) 남은 것에서 innerHTML / on: 만 파싱
|
|
43
68
|
const lines = rest.split('\n');
|
|
44
69
|
|
|
45
70
|
lines.forEach(line => {
|
|
46
71
|
const text = line.trim();
|
|
72
|
+
if (!text) return;
|
|
47
73
|
|
|
48
74
|
if (text.startsWith('innerHTML:')) {
|
|
49
75
|
let value = text.slice('innerHTML:'.length).trim();
|
|
76
|
+
|
|
77
|
+
// 문자열 리터럴이면 바깥 따옴표만 제거
|
|
50
78
|
if (
|
|
51
79
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
52
80
|
(value.startsWith("'") && value.endsWith("'"))
|
|
53
81
|
) {
|
|
54
82
|
value = value.slice(1, -1);
|
|
55
83
|
}
|
|
84
|
+
|
|
56
85
|
result.innerHTML = value;
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
if (text.startsWith('on:')) {
|
|
60
|
-
const [, type, ...
|
|
89
|
+
const [, type, ...restParts] = text.split(':');
|
|
61
90
|
result.events.push({
|
|
62
91
|
type: type.trim(),
|
|
63
|
-
action:
|
|
92
|
+
action: restParts.join(':').trim()
|
|
64
93
|
});
|
|
65
94
|
}
|
|
66
95
|
});
|
|
@@ -93,4 +122,52 @@ function extractTag(code, startIndex) {
|
|
|
93
122
|
block: code.slice(startIndex, i).trim(),
|
|
94
123
|
nextIndex: i
|
|
95
124
|
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extractBlock(code, startIndex) {
|
|
128
|
+
const open = code.indexOf('{', startIndex);
|
|
129
|
+
if (open === -1) {
|
|
130
|
+
return { block: "", nextIndex: startIndex };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let depth = 1;
|
|
134
|
+
let i = open + 1;
|
|
135
|
+
|
|
136
|
+
while (i < code.length && depth > 0) {
|
|
137
|
+
if (code[i] === '{') depth++;
|
|
138
|
+
if (code[i] === '}') depth--;
|
|
139
|
+
i++;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
block: code.slice(open + 1, i - 1),
|
|
144
|
+
nextIndex: i
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseAttrs(block) {
|
|
149
|
+
const attrs = {};
|
|
150
|
+
|
|
151
|
+
block.split('\n').forEach(line => {
|
|
152
|
+
const text = line.trim();
|
|
153
|
+
if (!text) return;
|
|
154
|
+
|
|
155
|
+
// "type: "text"," 같은 마지막 콤마 제거
|
|
156
|
+
const cleaned = text.replace(/,\s*$/, '');
|
|
157
|
+
if (!cleaned.includes(':')) return;
|
|
158
|
+
|
|
159
|
+
const [key, ...rest] = cleaned.split(':');
|
|
160
|
+
let value = rest.join(':').trim();
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
164
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
165
|
+
) {
|
|
166
|
+
value = value.slice(1, -1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
attrs[key.trim()] = value;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return attrs;
|
|
96
173
|
}
|
package/core/NeoCore.js
CHANGED
|
@@ -7,6 +7,32 @@ export class NeoCore {
|
|
|
7
7
|
el.className = data.styles.join(' ');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
if (data.attrs && typeof data.attrs === 'object') {
|
|
11
|
+
for (const [key, value] of Object.entries(data.attrs)) {
|
|
12
|
+
|
|
13
|
+
// boolean attr
|
|
14
|
+
if (typeof value === 'boolean') {
|
|
15
|
+
el[key] = value;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// input 관련 핵심 property
|
|
20
|
+
if (
|
|
21
|
+
el instanceof HTMLInputElement ||
|
|
22
|
+
el instanceof HTMLTextAreaElement ||
|
|
23
|
+
el instanceof HTMLSelectElement
|
|
24
|
+
) {
|
|
25
|
+
if (key in el) {
|
|
26
|
+
el[key] = value;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// fallback: 일반 attribute
|
|
32
|
+
el.setAttribute(key, String(value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
// 1️⃣ 텍스트 먼저
|
|
11
37
|
if (data.innerHTML) {
|
|
12
38
|
el.innerHTML = NeoCore.renderTemplate(data.innerHTML);
|
package/package.json
CHANGED