@yr-lang/yr 0.1.1 → 0.2.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/BUGS.md +3 -21
- package/CHANGELOG.md +51 -0
- package/lib/%build.yr +1 -1
- package/lib/%deploy.yr +2 -0
- package/lib/%gh-pages.yr +3 -1
- package/lib/%serve.yr +6 -0
- package/lib/@try.yr +9 -0
- package/lib/contenteditable.yr +68 -0
- package/lib/element.yr +2 -0
- package/lib/windowListener.yr +9 -0
- package/node.js +39 -33
- package/package.json +2 -2
- package/yr.js +473 -72
package/BUGS.md
CHANGED
|
@@ -3,21 +3,10 @@
|
|
|
3
3
|
This file documents known bugs and limitations in yr.
|
|
4
4
|
The project is under active development.
|
|
5
5
|
|
|
6
|
-
### Wrapper-only section closure
|
|
6
|
+
### Wrapper-only section closure ✓ Fixed in 0.2.0
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
For example, the following may result in an error:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
++
|
|
14
|
-
_ .teste
|
|
15
|
-
_wrapper/test
|
|
16
|
-
_ .teste2
|
|
17
|
-
_wrapper/test2
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
However, explicitly closing the section with elements works as expected:
|
|
8
|
+
Previously, a section could not always be closed using only a wrapper and required an explicit
|
|
9
|
+
`_` element. This has been resolved — the following now works as expected:
|
|
21
10
|
|
|
22
11
|
```
|
|
23
12
|
++
|
|
@@ -25,11 +14,4 @@ _ .teste
|
|
|
25
14
|
_wrapper/test
|
|
26
15
|
_ .teste2
|
|
27
16
|
_wrapper/test2
|
|
28
|
-
_
|
|
29
|
-
_
|
|
30
17
|
```
|
|
31
|
-
|
|
32
|
-
#### Notes
|
|
33
|
-
|
|
34
|
-
* This appears to be related to how section termination is detected when the last node is a wrapper.
|
|
35
|
-
* The parser may not recognize wrappers as valid closing tokens in some nesting scenarios.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0] - 2026-04-27
|
|
4
|
+
|
|
5
|
+
Remove `wrapperheader`, `header`, `scripts` and `footer` from parsedYr
|
|
6
|
+
Created `wrapperheader` sections
|
|
7
|
+
Added `VOID_TAGS` in order to not add elementId to all elements `if (preview === true)`
|
|
8
|
+
|
|
9
|
+
## [0.2.0] - 2026-03-01
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Wrapper-only section closure** — Sections containing nested wrappers no longer require an
|
|
14
|
+
explicit `_` to close. The parser now correctly closes open layers when a wrapper is terminated
|
|
15
|
+
by indentation, producing balanced HTML in all nesting scenarios. Resolves the issue documented
|
|
16
|
+
in `BUGS.md`.
|
|
17
|
+
|
|
18
|
+
- **Multiple wrapper closure on a single line** — When an element's indentation requires closing
|
|
19
|
+
more than one wrapper at once, the parser now closes all of them (changed `if` to `while` in
|
|
20
|
+
the html parser wrapper check). Previously only the innermost wrapper was closed, causing
|
|
21
|
+
sibling elements to be rendered inside the wrong wrapper.
|
|
22
|
+
|
|
23
|
+
- **Section change leaves wrappers open** — When a namespace token (e.g. `><` → `##`) was
|
|
24
|
+
encountered while one or more wrappers were active, only the outermost wrapper was cleaned up
|
|
25
|
+
via `shift()`. Now all open wrappers are properly closed innermost-first.
|
|
26
|
+
|
|
27
|
+
- **Closing tag indentation** — `getWhiteSpace()` was designed to receive a hierarchy level but
|
|
28
|
+
was called with a raw character-count indentation, producing increasingly wrong whitespace for
|
|
29
|
+
deeper elements. Closing tags now mirror the indentation of their opening tags.
|
|
30
|
+
|
|
31
|
+
- **`throw -1` loses file/project context** — A stray semicolon caused the error message for
|
|
32
|
+
odd-indentation errors to drop the filename and project name. The `-1` error message now
|
|
33
|
+
matches the `-2` error message in completeness.
|
|
34
|
+
|
|
35
|
+
- **`getElementAttributes` spurious empty key** — When an `att={{}}` block ended with a trailing
|
|
36
|
+
comma, an empty string key was unconditionally added to the parsed attributes object. Now only
|
|
37
|
+
added if `parseKey` is non-empty.
|
|
38
|
+
|
|
39
|
+
- **Self-importing wrapper causes infinite recursion** — A wrapper that listed itself via `!!`
|
|
40
|
+
in its own `++` section would loop indefinitely. The extension is now registered in
|
|
41
|
+
`sections.extensions` before its content is parsed, so the guard fires correctly on
|
|
42
|
+
re-entry.
|
|
43
|
+
|
|
44
|
+
- **Wrapper without `++` section does not invoke JS function** — A wrapper that had a
|
|
45
|
+
`_@wrapper(Category, Option)` function in `@>` but no `++` body would have its div created
|
|
46
|
+
but the JS function never called. The `wrapperjs` entry is now added independently of whether
|
|
47
|
+
a `++` section exists.
|
|
48
|
+
|
|
49
|
+
## [0.1.1] - prior
|
|
50
|
+
|
|
51
|
+
Initial public releases.
|
package/lib/%build.yr
CHANGED
package/lib/%deploy.yr
CHANGED
package/lib/%gh-pages.yr
CHANGED
|
@@ -14,6 +14,8 @@ gh-pages() {
|
|
|
14
14
|
rm -rf $git_name
|
|
15
15
|
fi
|
|
16
16
|
|
|
17
|
+
if [[ ! -d .git ]]; then exit 1; fi
|
|
18
|
+
|
|
17
19
|
git add .
|
|
18
20
|
git commit -m "# DEPLOY"
|
|
19
21
|
git push -f origin main
|
|
@@ -57,4 +59,4 @@ gh-pages_fix() {
|
|
|
57
59
|
-X PUT \
|
|
58
60
|
-F source.branch=gh-pages \
|
|
59
61
|
2>/dev/null || true
|
|
60
|
-
}
|
|
62
|
+
}
|
package/lib/%serve.yr
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
___serve
|
|
4
4
|
|
|
5
|
+
APP_NAME=$(echo $_CONFIG | jq -r .name)
|
|
5
6
|
cd $_PROJECT_PATH
|
|
6
7
|
if [[ ! -d node_modules ]]; then npm i $(cat node_modules.txt); fi
|
|
8
|
+
|
|
9
|
+
pkill -9 -f "^node app/app.js$"
|
|
7
10
|
node app/app.js
|
|
11
|
+
|
|
12
|
+
#pkill -9 -f "$APP_NAME" 2>/dev/null
|
|
13
|
+
#exec -a "$APP_NAME" node app/app.js
|
package/lib/@try.yr
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@@
|
|
2
|
+
|
|
3
|
+
const htmlEntities = {
|
|
4
|
+
'&': '&',
|
|
5
|
+
'<': '<',
|
|
6
|
+
'>': '>',
|
|
7
|
+
'"': '"',
|
|
8
|
+
''': "'",
|
|
9
|
+
'–': '-',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function unblurcontenteditable(element) {
|
|
13
|
+
element.setAttribute('contenteditable', false);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function blurcontenteditable(element, callback=false, selectEnd=false, blurOnEnter=true, removeEmpty=false) {
|
|
17
|
+
element.setAttribute('contenteditable', true);
|
|
18
|
+
if (element.getAttribute('_blurcontenteditable') === 'true') return;
|
|
19
|
+
element.setAttribute('_blurcontenteditable', true);
|
|
20
|
+
let content = element.textContent.trim();
|
|
21
|
+
|
|
22
|
+
if (blurOnEnter) element.addEventListener('keydown', (e) => {
|
|
23
|
+
if (e.key === 'Enter' || e.keyCode === 13) {
|
|
24
|
+
e.target.innerHTML = e.target.innerHTML.replace('<br>', '');
|
|
25
|
+
element.blur();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (selectEnd) {
|
|
30
|
+
for (let item of ['click', 'focus']) element.addEventListener(item, (e) => {
|
|
31
|
+
const range = document.createRange();
|
|
32
|
+
range.selectNodeContents(element);
|
|
33
|
+
range.collapse(false);
|
|
34
|
+
window.getSelection().removeAllRanges();
|
|
35
|
+
window.getSelection().addRange(range);
|
|
36
|
+
element.focus();
|
|
37
|
+
range.detach();
|
|
38
|
+
element.scrollTop = e.target.scrollHeight;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
element.addEventListener('blur', (e) => {
|
|
43
|
+
if (element._ignoreBlurCallback) return;
|
|
44
|
+
|
|
45
|
+
if (removeEmpty && e.target.innerHTML === '') {
|
|
46
|
+
((removeEmpty === true) ? element : removeEmpty).remove();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (blurOnEnter)
|
|
51
|
+
e.target.innerHTML = e.target.innerHTML.replace('<br>', '');
|
|
52
|
+
|
|
53
|
+
if (e.target.innerHTML === content) return;
|
|
54
|
+
|
|
55
|
+
element.innerHTML = e.target.innerHTML.replace(/<\/div><div>/gi, '\n')
|
|
56
|
+
.replace(/(<.*?div>)/gi, '\n').trim();
|
|
57
|
+
|
|
58
|
+
let newContent = element.textContent;
|
|
59
|
+
for (let item in htmlEntities)
|
|
60
|
+
newContent = newContent.split(item).join(htmlEntities[item]);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
callback(content, newContent, e);
|
|
64
|
+
} catch(error) {/* pass */}
|
|
65
|
+
|
|
66
|
+
content = element.textContent;
|
|
67
|
+
});
|
|
68
|
+
}
|
package/lib/element.yr
CHANGED
package/node.js
CHANGED
|
@@ -147,6 +147,8 @@ module.exports = {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
delete sections.wrapperjs;
|
|
150
|
+
delete sections.header;
|
|
151
|
+
delete sections.footer;
|
|
150
152
|
|
|
151
153
|
for (let item in sections)
|
|
152
154
|
result += `${core.parsers.names[item]}\n\n${sections[item]}\n`;
|
|
@@ -241,51 +243,55 @@ module.exports = {
|
|
|
241
243
|
}
|
|
242
244
|
},
|
|
243
245
|
spawn(path, args=[], callbackClose=false, log=true, exit=false, callbackData=false, callbackError=false) {
|
|
244
|
-
if (args === '-k') {
|
|
245
|
-
try {
|
|
246
|
-
console.log('killing spawn');
|
|
247
|
-
console.log(path, args);
|
|
248
|
-
//path.stdin.pause();
|
|
249
|
-
path.kill();
|
|
250
|
-
console.log('spawn killed');
|
|
251
|
-
} catch(error) { console.log(error); }
|
|
252
|
-
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
246
|
if (log) console.log('starting spawn...');
|
|
257
247
|
|
|
258
|
-
|
|
248
|
+
// garante execução via bash (sem shell intermediário problemático)
|
|
249
|
+
const child = spawn(path, args, {
|
|
250
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
251
|
+
detached: true
|
|
252
|
+
});
|
|
259
253
|
|
|
260
|
-
|
|
261
|
-
// console.log(data.toString());
|
|
262
|
-
//});
|
|
254
|
+
if (log) console.log('spawn pid:', child.pid);
|
|
263
255
|
|
|
264
|
-
child.stdout
|
|
265
|
-
data
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
if (child.stdout) {
|
|
257
|
+
child.stdout.on('data', (data) => {
|
|
258
|
+
data = data.toString();
|
|
259
|
+
if (log) process.stdout.write(data);
|
|
260
|
+
if (callbackData) callbackData(data);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
269
263
|
|
|
270
|
-
child.stderr
|
|
271
|
-
data
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
264
|
+
if (child.stderr) {
|
|
265
|
+
child.stderr.on('data', (data) => {
|
|
266
|
+
data = data.toString();
|
|
267
|
+
if (log) process.stderr.write(data);
|
|
268
|
+
if (callbackError) callbackError(data);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
275
271
|
|
|
276
272
|
child.on('exit', (code, signal) => {
|
|
277
|
-
if (log) console.log('
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
//state.building = false;
|
|
273
|
+
if (log) console.log('exit:', { code, signal });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
child.on('close', (code, signal) => {
|
|
277
|
+
if (log) console.log('close:', { code, signal });
|
|
283
278
|
if (callbackClose) callbackClose(code, signal);
|
|
284
279
|
if (exit) process.exit();
|
|
285
280
|
});
|
|
286
281
|
|
|
287
282
|
return child;
|
|
288
283
|
},
|
|
284
|
+
kill(child) {
|
|
285
|
+
if (!child || !child.pid) return;
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
// mata o grupo inteiro (bash + node + qualquer filho)
|
|
289
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
290
|
+
console.log('process group killed:', child.pid);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
console.log('kill error:', e);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
289
295
|
build(projectName=false, config={}, viewsPaths=env.VIEWS, assetsPath=false) {
|
|
290
296
|
if (!config) config = {};
|
|
291
297
|
|
|
@@ -532,7 +538,7 @@ dist/`);
|
|
|
532
538
|
}
|
|
533
539
|
|
|
534
540
|
if (result.devops.build) {
|
|
535
|
-
this.spawn(`${projectPath}/actions/build`, [], () => {
|
|
541
|
+
if (!config.ignoreBuild) this.spawn(`${projectPath}/actions/build`, [], () => {
|
|
536
542
|
if (config.exec) this.spawn(`${projectPath}/app/app.js`);
|
|
537
543
|
});
|
|
538
544
|
} else if (config.exec) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yr-lang/yr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Yr is a modern structural language and parser",
|
|
5
5
|
"main": "./node.js",
|
|
6
6
|
"exports": {
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"license": "SEE LICENSE IN LICENSE",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/yr-lang/yr"
|
|
12
|
+
"url": "git+https://github.com/yr-lang/yr.git"
|
|
13
13
|
}
|
|
14
14
|
}
|
package/yr.js
CHANGED
|
@@ -17,16 +17,18 @@ const utils = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const common = {
|
|
20
|
+
// Bug fix: was using new Array((i*2)-1).join which was designed for 'level' units,
|
|
21
|
+
// but is called with raw indentation (char count). Use simple repeat instead.
|
|
20
22
|
getWhiteSpace(indentation) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch(error) { return ''; }
|
|
23
|
+
if (!indentation || indentation < 0) return '';
|
|
24
|
+
return ' '.repeat(indentation);
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const namespaces = {
|
|
28
29
|
'@&': { name: 'jsapp', parser: 'array', default: [], merge: true },
|
|
29
30
|
'++': { name: 'wrappers', parser: 'auxstring', default: {}, merge: true },
|
|
31
|
+
'+>': { name: 'wrapperheader', parser: 'html', default: '', merge: true },
|
|
30
32
|
'>+': { name: 'wrapperbody', parser: 'html', default: '', merge: true },
|
|
31
33
|
'><': { name: 'body', parser: 'html', default: '', merge: true },
|
|
32
34
|
'@>': { name: 'jsheader', parser: 'string', default: '', merge: true },
|
|
@@ -140,12 +142,21 @@ function getElementAttributes(line) {
|
|
|
140
142
|
lastChar = value;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
// Bug fix: only add the last key-value if parseKey is non-empty
|
|
146
|
+
// (avoids adding a spurious empty key when attrs end with a trailing comma)
|
|
147
|
+
if (parseKey.trim()) parse[parseKey.trim()] = parseValue.trim();
|
|
144
148
|
parseKey = '', parseValue = '';
|
|
145
149
|
return { ...attributes, ...parse };
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
function addIdToElement(line, config, elementId=false) {
|
|
153
|
+
let tag = line.trim().split(' ')[0];
|
|
154
|
+
|
|
155
|
+
if (tag.startsWith('_')) {
|
|
156
|
+
tag = tag.replace(/^_/, '');
|
|
157
|
+
if (VOID_TAGS.has(tag) || ['title', 'script', 'style'].includes(tag)) return line;
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
if (config.preview && !line.includes('.__') && !line.includes('.{{__')) {
|
|
150
161
|
if (!elementId) elementId = '__' + crypto.generateToken(8);
|
|
151
162
|
if (!line.trim().startsWith('_')) elementId = '{{' + elementId;
|
|
@@ -180,6 +191,11 @@ function addIdToElement(line, config, elementId=false) {
|
|
|
180
191
|
return line;
|
|
181
192
|
}
|
|
182
193
|
|
|
194
|
+
const VOID_TAGS = new Set([
|
|
195
|
+
'area','base','br','col','embed','hr','img','input',
|
|
196
|
+
'link','meta','param','source','track','wbr'
|
|
197
|
+
]);
|
|
198
|
+
|
|
183
199
|
const parserFns = {
|
|
184
200
|
array(line, sections, section, state, lineNumber, config={}) {
|
|
185
201
|
if (state.sectionChanged) sections[section].push('')
|
|
@@ -197,7 +213,7 @@ const parserFns = {
|
|
|
197
213
|
sections[section] += line + '\n';
|
|
198
214
|
},
|
|
199
215
|
html(line, sections, section, state, lineNumber, config={}) {
|
|
200
|
-
if (config.ignoreHtml) return;
|
|
216
|
+
if (config.ignoreHtml && !['wrapperheader', 'header', 'footer', 'scripts'].includes(section)) return;
|
|
201
217
|
if (!sections.yr[section]) sections.yr[section] = '';
|
|
202
218
|
const parsedLine = parseLine(line);
|
|
203
219
|
if (parsedLine.line.startsWith('//')) {
|
|
@@ -232,18 +248,35 @@ const parserFns = {
|
|
|
232
248
|
elementId = '__' + split[1];
|
|
233
249
|
parsedLine.line = split[0].trimEnd();
|
|
234
250
|
}
|
|
235
|
-
|
|
251
|
+
if (!['wrapperheader', 'header', 'footer', 'scripts'].includes(section))
|
|
252
|
+
yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
|
|
236
253
|
let yrIndentation = 0, ignoreYr, wildCard = '', replaceYr;
|
|
237
|
-
|
|
254
|
+
// Bug fix: use while loop to close ALL wrappers that need closing (not just one)
|
|
255
|
+
// A single element may need to close multiple nested wrappers at once
|
|
256
|
+
while (state.wrapper.length > 0) {
|
|
238
257
|
let wrapper = state.wrapper[state.wrapper.length - 1];
|
|
239
258
|
if (parsedLine.indentation >= wrapper.reference) {
|
|
240
259
|
yrIndentation = wrapper.new - wrapper.reference + 2;
|
|
241
260
|
replaceYr = sections.yr[section].includes('#@#\n');
|
|
261
|
+
break; // Still inside this wrapper, stop closing
|
|
242
262
|
} else {
|
|
243
|
-
wrapper = state.wrapper[state.wrapper.length - 1];
|
|
244
263
|
sections.yr[section] = sections.yr[section].replace(/#@#\n/, '');
|
|
264
|
+
// Close any layers that were opened inside this wrapper before popping it
|
|
265
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
266
|
+
const layer = state.layers[j];
|
|
267
|
+
if (!layer) continue;
|
|
268
|
+
// Only close layers that belong inside this wrapper (indentation >= wrapper.reference)
|
|
269
|
+
if (layer.indentation >= wrapper.reference) {
|
|
270
|
+
if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
|
|
271
|
+
layer.whiteSpace + `</${layer.tag}>\n`;
|
|
272
|
+
state.layers.pop();
|
|
273
|
+
} else {
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
245
277
|
if (wrapper.wildCard) sections[section] += wrapper.wildCard + '\n';
|
|
246
278
|
state.wrapper.pop();
|
|
279
|
+
// After popping, loop again to check if we need to close more wrappers
|
|
247
280
|
}
|
|
248
281
|
}
|
|
249
282
|
if (yrIndentation < 0) yrIndentation = 0;
|
|
@@ -257,7 +290,7 @@ const parserFns = {
|
|
|
257
290
|
}
|
|
258
291
|
if (parsedLine.indentation !== -1) {
|
|
259
292
|
if (parsedLine.indentation % 2 !== 0)
|
|
260
|
-
throw `-1: indentation error at line ${lineNumber}` + ''
|
|
293
|
+
throw `-1: indentation error at line ${lineNumber}` + '' +
|
|
261
294
|
` (${config.name}.yr, ${config.project}):\n\n"""\n${line}\n"""`;
|
|
262
295
|
if (parsedLine.level > state.level + 1)
|
|
263
296
|
throw `-2: indentation error at line ${lineNumber}` + '' +
|
|
@@ -265,13 +298,13 @@ const parserFns = {
|
|
|
265
298
|
if (parsedLine.level < state.level) {
|
|
266
299
|
for (let j = state.layers.length; j > parsedLine.level - 1; j--) {
|
|
267
300
|
if (!state.layers[j - 1]) continue;
|
|
268
|
-
sections[state.layers[j - 1].section] +=
|
|
301
|
+
if (!VOID_TAGS.has(state.layers[j - 1].tag)) sections[state.layers[j - 1].section] +=
|
|
269
302
|
state.layers[j - 1].whiteSpace + `</${state.layers[j - 1].tag}>\n`;
|
|
270
303
|
state.layers.pop();
|
|
271
304
|
}
|
|
272
305
|
} else if (parsedLine.level === state.level
|
|
273
306
|
&& state.element && state.layers.length > 0) {
|
|
274
|
-
sections[state.layers[state.layers.length - 1].section] +=
|
|
307
|
+
if (!VOID_TAGS.has(state.layers[state.layers.length - 1].tag)) sections[state.layers[state.layers.length - 1].section] +=
|
|
275
308
|
`</${state.layers[state.layers.length - 1].tag}>\n`;
|
|
276
309
|
state.layers.pop();
|
|
277
310
|
}
|
|
@@ -280,7 +313,7 @@ const parserFns = {
|
|
|
280
313
|
|| state.layers[state.layers.length - 1].indentation === parsedLine.indentation)) {
|
|
281
314
|
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
282
315
|
if (j === -1) continue;
|
|
283
|
-
sections[state.layers[j].section] +=
|
|
316
|
+
if (!VOID_TAGS.has(state.layers[j].tag)) sections[state.layers[j].section] +=
|
|
284
317
|
state.layers[j].whiteSpace + `</${state.layers[j].tag}>\n`;
|
|
285
318
|
state.layers.pop();
|
|
286
319
|
}
|
|
@@ -296,7 +329,7 @@ const parserFns = {
|
|
|
296
329
|
if (tag[0] === '_') {
|
|
297
330
|
tag = tag.replace(/__/, '');
|
|
298
331
|
if (!tag) tag = 'div';
|
|
299
|
-
wildCard += '<!--#@#-->';
|
|
332
|
+
if (!config.simple) wildCard += '<!--#@#-->';
|
|
300
333
|
}
|
|
301
334
|
let attributes = '', id = '', classes = '';
|
|
302
335
|
for (let item of lineSplit) {
|
|
@@ -317,12 +350,22 @@ const parserFns = {
|
|
|
317
350
|
attributes += ` ${item}`;
|
|
318
351
|
}
|
|
319
352
|
}
|
|
320
|
-
if (tag.includes('/')) {
|
|
353
|
+
if (tag.includes('/') && config.simple) {
|
|
354
|
+
const wrapper = '___' + tag.replace(/!/, '').replace(/\//, '__');
|
|
355
|
+
tag = 'div';
|
|
356
|
+
if (!classes) {
|
|
357
|
+
classes = ` class="${elementId} ${wrapper}"`;
|
|
358
|
+
} else if (!classes.includes(elementId)) {
|
|
359
|
+
classes = classes.split('class="').join(`class="${elementId} ${wrapper} `);
|
|
360
|
+
}
|
|
361
|
+
} else if (tag.includes('/') && !config.simple) {
|
|
321
362
|
const wrapper = tag.replace(/!/, '').split('/');
|
|
322
363
|
wrapper[0] = utils.capitalize(wrapper[0]);
|
|
323
364
|
wrapper[1] = utils.capitalize(wrapper[1]);
|
|
324
365
|
const wrapperName = wrapper.join('/');
|
|
325
|
-
core.extend(wrapper, wrapperName, sections, state, {
|
|
366
|
+
core.extend(wrapper, wrapperName, sections, state, {
|
|
367
|
+
redoWrapper: true, simple: config.simple
|
|
368
|
+
});
|
|
326
369
|
let newWrapper, wrapperIndentation;
|
|
327
370
|
if (!tag.includes('!')) {
|
|
328
371
|
newWrapper = {
|
|
@@ -333,33 +376,28 @@ const parserFns = {
|
|
|
333
376
|
state.wrapper.push(newWrapper);
|
|
334
377
|
wrapperIndentation = parsedLine.indentation;
|
|
335
378
|
}
|
|
379
|
+
// Always set up element attributes for wrapperjs call,
|
|
380
|
+
// even if wrapper has no ++ section (no wrapperbody)
|
|
381
|
+
elementAttributes.id = elementId;
|
|
382
|
+
elementAttributes.category = wrapper[0];
|
|
383
|
+
elementAttributes.option = wrapper[1];
|
|
384
|
+
let attributes = [];
|
|
385
|
+
try {
|
|
386
|
+
attributes = sections.wrappers[wrapperName]
|
|
387
|
+
? sections.wrappers[wrapperName].vars.attributes
|
|
388
|
+
: sections.vars.attributes;
|
|
389
|
+
} catch(error) {/* pass */}
|
|
390
|
+
if (!attributes) attributes = sections.vars.attributes;
|
|
391
|
+
elementAttributes.attributes = attributes;
|
|
392
|
+
// Call the JS wrapper function if it exists, regardless of whether ++ is present
|
|
393
|
+
if (sections.jsheader
|
|
394
|
+
.includes(`function __${wrapperName.replace(/\//, '_')}(`)
|
|
395
|
+
&& !sections.wrapperjs.includes(`"id":"${elementId}"`)) {
|
|
396
|
+
const wrapperjs =
|
|
397
|
+
`__${wrapperName.replace(/\//, '_')}(${JSON.stringify(elementAttributes)});\n`;
|
|
398
|
+
sections.wrapperjs += wrapperjs;
|
|
399
|
+
}
|
|
336
400
|
if (sections.wrappers[wrapperName]) {
|
|
337
|
-
elementAttributes.id = elementId;
|
|
338
|
-
elementAttributes.category = wrapper[0];
|
|
339
|
-
elementAttributes.option = wrapper[1];
|
|
340
|
-
let attributes = [];
|
|
341
|
-
try {
|
|
342
|
-
attributes = sections.wrappers[wrapperName].vars.attributes;
|
|
343
|
-
} catch(error) {
|
|
344
|
-
console.log(wrapperName);
|
|
345
|
-
console.log(sections.wrappers[wrapperName]);
|
|
346
|
-
console.log(parsedLine);
|
|
347
|
-
console.log(error);
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
if (!attributes) attributes = sections.vars.attributes;
|
|
351
|
-
elementAttributes.attributes = attributes;
|
|
352
|
-
let redone;
|
|
353
|
-
try {
|
|
354
|
-
redone = !sections.wrappers[wrapperName].redone;
|
|
355
|
-
} catch(error) {/* pass */}
|
|
356
|
-
if (sections.jsheader
|
|
357
|
-
.includes(`function __${wrapperName.replace(/\//, '_')}(`)
|
|
358
|
-
&& !sections.wrapperjs.includes(`"id":"${elementId}"`)) {
|
|
359
|
-
const wrapperjs =
|
|
360
|
-
`__${wrapperName.replace(/\//, '_')}(${JSON.stringify(elementAttributes)});\n`;
|
|
361
|
-
sections.wrapperjs += wrapperjs;
|
|
362
|
-
}
|
|
363
401
|
if (!tag.includes('!')) {
|
|
364
402
|
if (sections.wrappers[wrapperName].yrwrapperbody) {
|
|
365
403
|
for (let value of sections.wrappers[wrapperName]
|
|
@@ -394,7 +432,7 @@ const parserFns = {
|
|
|
394
432
|
classes = classes.split('class="').join(`class="${elementId} `);
|
|
395
433
|
}
|
|
396
434
|
}
|
|
397
|
-
if (config.preview) {
|
|
435
|
+
if (config.preview && !['wrapperheader', 'header', 'footer', 'scripts'].includes(section)) {
|
|
398
436
|
if (!classes) {
|
|
399
437
|
classes = ` class="${elementId}"`;
|
|
400
438
|
} else if (!classes.includes(elementId)) {
|
|
@@ -408,7 +446,7 @@ const parserFns = {
|
|
|
408
446
|
state.element = true;
|
|
409
447
|
} else {
|
|
410
448
|
if (section) {
|
|
411
|
-
if (section === 'header') {
|
|
449
|
+
if (section === 'header' || section === 'wrapperheader') {
|
|
412
450
|
sections[section] += parsedLine.whiteSpace + parsedLine.line + '\n';
|
|
413
451
|
} else {
|
|
414
452
|
let attributes = '';
|
|
@@ -423,6 +461,16 @@ const parserFns = {
|
|
|
423
461
|
}
|
|
424
462
|
}
|
|
425
463
|
|
|
464
|
+
function isYrSyntax(code) {
|
|
465
|
+
code = code.split('!! preview\n!! bodyblock\n!! iframeConsole\n!! element\n').join('');
|
|
466
|
+
code = code.trim();
|
|
467
|
+
|
|
468
|
+
for (let item of [...Object.keys(namespaces), '!!', '[[', '--'])
|
|
469
|
+
if (code.startsWith(item)) return true;
|
|
470
|
+
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
426
474
|
const parsers = {
|
|
427
475
|
namespaces, tokens: Object.keys(namespaces),
|
|
428
476
|
parsers: _parsers, mergers, names,
|
|
@@ -600,6 +648,8 @@ const core = {
|
|
|
600
648
|
}
|
|
601
649
|
},
|
|
602
650
|
extend(wrapper, wrapperName, sections, state, config={}) {
|
|
651
|
+
if (config.simple && !wrapperName.includes('@')) return;
|
|
652
|
+
|
|
603
653
|
if (!sections.parsedyr)
|
|
604
654
|
sections.parsedyr = { header: '', body: '', footer: '', scripts: '' };
|
|
605
655
|
|
|
@@ -627,11 +677,15 @@ const core = {
|
|
|
627
677
|
return;
|
|
628
678
|
}
|
|
629
679
|
|
|
680
|
+
// Bug fix: register extension BEFORE parsing its content
|
|
681
|
+
// to prevent infinite recursion when a wrapper imports itself via !!
|
|
682
|
+
sections.extensions += '!! ' + wrapperName + '\n';
|
|
683
|
+
|
|
630
684
|
const _yr = sections.yr;
|
|
631
685
|
sections.yr = {};
|
|
632
686
|
|
|
633
687
|
const aux = {};
|
|
634
|
-
for (let item of ['header', 'body', 'footer', 'scripts']) {
|
|
688
|
+
for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
635
689
|
aux[item] = sections[item];
|
|
636
690
|
sections[item] = '';
|
|
637
691
|
}
|
|
@@ -640,8 +694,6 @@ const core = {
|
|
|
640
694
|
sections, wrapper: `${wrapper.join('/')}`, ...config
|
|
641
695
|
});
|
|
642
696
|
|
|
643
|
-
sections.extensions += '!! ' + wrapperName + '\n';
|
|
644
|
-
|
|
645
697
|
if (!sections.wrappers[wrapperName])
|
|
646
698
|
sections.wrappers[wrapperName] = {};
|
|
647
699
|
|
|
@@ -651,7 +703,7 @@ const core = {
|
|
|
651
703
|
if (!sections.wrappers[wrapperName].vars)
|
|
652
704
|
sections.wrappers[wrapperName].vars = {};
|
|
653
705
|
|
|
654
|
-
for (let item of ['header', 'body', 'footer', 'scripts']) {
|
|
706
|
+
for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
655
707
|
sections.wrappers[wrapperName].parsed[item] = result[item];
|
|
656
708
|
sections[item] = aux[item];
|
|
657
709
|
}
|
|
@@ -667,6 +719,329 @@ const core = {
|
|
|
667
719
|
}
|
|
668
720
|
},
|
|
669
721
|
parse(code, config={}) {
|
|
722
|
+
if (!isYrSyntax(code)) {
|
|
723
|
+
const sections = { parsedhtml: code };
|
|
724
|
+
|
|
725
|
+
if (config.name) sections.ui = [{
|
|
726
|
+
name: (config.name.includes('.html'))
|
|
727
|
+
? config.name : `${config.name}.html`,
|
|
728
|
+
content: sections.parsedhtml
|
|
729
|
+
}];
|
|
730
|
+
|
|
731
|
+
return sections;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (config.opposite) {
|
|
735
|
+
const file = code;
|
|
736
|
+
const isProject = config.isProject;
|
|
737
|
+
let yrResult = isProject ? '' : '!! @wrapper\n\n';
|
|
738
|
+
|
|
739
|
+
const VOID_TAGS = new Set([
|
|
740
|
+
'area','base','br','col','embed','hr','img','input',
|
|
741
|
+
'link','meta','param','source','track','wbr'
|
|
742
|
+
]);
|
|
743
|
+
|
|
744
|
+
const P_BREAKERS = new Set([
|
|
745
|
+
'address','article','aside','blockquote','div','dl',
|
|
746
|
+
'fieldset','footer','form','h1','h2','h3','h4','h5','h6',
|
|
747
|
+
'header','hr','main','nav','ol','p','pre','section','table','ul'
|
|
748
|
+
]);
|
|
749
|
+
|
|
750
|
+
function tokenize(html) {
|
|
751
|
+
const tokens = [];
|
|
752
|
+
let i = 0;
|
|
753
|
+
|
|
754
|
+
while (i < html.length) {
|
|
755
|
+
if (html[i] === '<') {
|
|
756
|
+
const close = html.indexOf('>', i);
|
|
757
|
+
if (close === -1) break;
|
|
758
|
+
|
|
759
|
+
tokens.push({
|
|
760
|
+
type: 'tag',
|
|
761
|
+
value: html.slice(i, close + 1)
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
i = close + 1;
|
|
765
|
+
} else {
|
|
766
|
+
const next = html.indexOf('<', i);
|
|
767
|
+
tokens.push({
|
|
768
|
+
type: 'text',
|
|
769
|
+
value: html.slice(i, next === -1 ? html.length : next)
|
|
770
|
+
});
|
|
771
|
+
i = next === -1 ? html.length : next;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return tokens;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function parseTag(str) {
|
|
779
|
+
const isClosing = /^<\//.test(str);
|
|
780
|
+
const isSelfClosing = /\/>$/.test(str);
|
|
781
|
+
|
|
782
|
+
const nameMatch = str.match(/^<\/?([a-zA-Z0-9-]+)/);
|
|
783
|
+
if (!nameMatch) return null;
|
|
784
|
+
|
|
785
|
+
const tag = nameMatch[1].toLowerCase();
|
|
786
|
+
|
|
787
|
+
const attrs = {};
|
|
788
|
+
[...str.matchAll(/([\w-:]+)(?:="([^"]*)")?/g)].forEach(m => {
|
|
789
|
+
const key = m[1];
|
|
790
|
+
if (key === tag) return;
|
|
791
|
+
attrs[key] = m[2] ?? true;
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
return { tag, attrs, isClosing, isSelfClosing };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function parseHTML(html) {
|
|
798
|
+
const tokens = tokenize(html);
|
|
799
|
+
|
|
800
|
+
const root = { tag: '_root', children: [] };
|
|
801
|
+
const stack = [root];
|
|
802
|
+
|
|
803
|
+
for (let t of tokens) {
|
|
804
|
+
let current = stack[stack.length - 1];
|
|
805
|
+
|
|
806
|
+
if (t.type === 'text') {
|
|
807
|
+
const text = t.value.trim();
|
|
808
|
+
if (text) current.children.push({ text });
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const parsed = parseTag(t.value);
|
|
813
|
+
if (!parsed) continue;
|
|
814
|
+
|
|
815
|
+
const { tag, attrs, isClosing, isSelfClosing } = parsed;
|
|
816
|
+
|
|
817
|
+
if (isClosing) {
|
|
818
|
+
while (stack.length > 1) {
|
|
819
|
+
const top = stack.pop();
|
|
820
|
+
if (top.tag === tag) break;
|
|
821
|
+
}
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (current.tag === 'p' && P_BREAKERS.has(tag)) {
|
|
826
|
+
stack.pop();
|
|
827
|
+
current = stack[stack.length - 1];
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const node = { tag, attrs, children: [] };
|
|
831
|
+
current.children.push(node);
|
|
832
|
+
|
|
833
|
+
if (!isSelfClosing && !VOID_TAGS.has(tag)) {
|
|
834
|
+
stack.push(node);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return root.children;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function toYr(nodes, indent = 0) {
|
|
842
|
+
let out = '';
|
|
843
|
+
|
|
844
|
+
for (let node of nodes) {
|
|
845
|
+
const space = ' '.repeat(indent);
|
|
846
|
+
|
|
847
|
+
if (node.text) {
|
|
848
|
+
let text = node.text;
|
|
849
|
+
|
|
850
|
+
if (text.trimStart().startsWith('//')) {
|
|
851
|
+
text = text.replace(/^(\s*)\/\//, (_, spaces) => {
|
|
852
|
+
return spaces + '//';
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (text.trimStart().startsWith('_')) {
|
|
857
|
+
text = text.replace(/^(\s*)_/, (_, spaces) => {
|
|
858
|
+
return spaces + '_';
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (text.trimStart().startsWith('.')) {
|
|
863
|
+
text = text.replace(/^(\s*)\./, (_, spaces) => {
|
|
864
|
+
return spaces + '.';
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
out += `${space}${text}\n`;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (node.tag === 'script') continue;
|
|
873
|
+
|
|
874
|
+
let line = '_';
|
|
875
|
+
|
|
876
|
+
if (node.tag !== 'div') line += node.tag;
|
|
877
|
+
|
|
878
|
+
if (node.attrs?.id) line += ` #${node.attrs.id}`;
|
|
879
|
+
|
|
880
|
+
if (node.attrs?.class) {
|
|
881
|
+
const cls = node.attrs.class.split(' ').join('.');
|
|
882
|
+
line += ` .${cls}`;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
for (let key in node.attrs) {
|
|
886
|
+
if (key === 'class' || key === 'id') continue;
|
|
887
|
+
|
|
888
|
+
if (node.attrs[key] === true) {
|
|
889
|
+
line += ` ${key}`;
|
|
890
|
+
} else {
|
|
891
|
+
line += ` ${key}="${node.attrs[key]}"`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
out += `${space}${line}\n`;
|
|
896
|
+
|
|
897
|
+
if (node.children?.length) {
|
|
898
|
+
out += toYr(node.children, indent + 2);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return out;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function extract(tag, str) {
|
|
906
|
+
const match = str.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`));
|
|
907
|
+
return match ? match[1].trim() : '';
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function formatCSS(css) {
|
|
911
|
+
if (!css) return '';
|
|
912
|
+
const comments = [];
|
|
913
|
+
let normalized = css.replace(/\/\*[\s\S]*?\*\//g, m => {
|
|
914
|
+
const idx = comments.length;
|
|
915
|
+
comments.push(m);
|
|
916
|
+
return `__COMMENT_${idx}__`;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
normalized = normalized
|
|
920
|
+
.replace(/\s*\{\s*/g, ' {')
|
|
921
|
+
.replace(/\s*\}\s*/g, ' }')
|
|
922
|
+
.replace(/\s*;\s*/g, '; ')
|
|
923
|
+
.replace(/,\s*/g, ', ')
|
|
924
|
+
//.replace(/([\w-])\s*:\s*(?!:)/g, '$1: ')
|
|
925
|
+
.replace(/\s+/g, ' ')
|
|
926
|
+
.replace(/__COMMENT_(\d+)__/g, (_, i) => '\n' + comments[+i] + '\n')
|
|
927
|
+
.trim();
|
|
928
|
+
|
|
929
|
+
let indent = 0;
|
|
930
|
+
let out = '';
|
|
931
|
+
let i = 0;
|
|
932
|
+
|
|
933
|
+
while (i < normalized.length) {
|
|
934
|
+
const char = normalized[i];
|
|
935
|
+
|
|
936
|
+
if (char === '{') {
|
|
937
|
+
out = out.trimEnd();
|
|
938
|
+
out += ' {\n';
|
|
939
|
+
indent++;
|
|
940
|
+
out += ' '.repeat(indent);
|
|
941
|
+
} else if (char === '}') {
|
|
942
|
+
indent--;
|
|
943
|
+
out = out.trimEnd();
|
|
944
|
+
out += '\n' + ' '.repeat(indent) + '}\n';
|
|
945
|
+
if (indent >= 0) out += '\n' + ' '.repeat(indent);
|
|
946
|
+
} else if (char === ';') {
|
|
947
|
+
out = out.trimEnd();
|
|
948
|
+
out += ';\n' + ' '.repeat(indent);
|
|
949
|
+
} else if (char === ' ' && out.endsWith('\n' + ' '.repeat(indent))) {
|
|
950
|
+
} else {
|
|
951
|
+
out += char;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
i++;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return out
|
|
958
|
+
.split('\n')
|
|
959
|
+
.map(l => l.trimEnd())
|
|
960
|
+
.join('\n')
|
|
961
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
962
|
+
.trim();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function wrapScript(js) {
|
|
966
|
+
if (!js) return '';
|
|
967
|
+
let category = '';
|
|
968
|
+
let option = '';
|
|
969
|
+
|
|
970
|
+
const match = js.match(/__([A-Za-z0-9]+)_([A-Za-z0-9]+)/);
|
|
971
|
+
if (match) {
|
|
972
|
+
category = match[1];
|
|
973
|
+
option = match[2];
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const startBlock =
|
|
977
|
+
`async function __${category}_${option}(_config) {
|
|
978
|
+
let element = (_config.id === 'body') ? document.body
|
|
979
|
+
: document.querySelector(\`.\${_config.id}\`);
|
|
980
|
+
if (!element) return;
|
|
981
|
+
try {
|
|
982
|
+
element._config = _config;
|
|
983
|
+
if (window.__preview) {
|
|
984
|
+
element._html = element.outerHTML;
|
|
985
|
+
element._refresh = () => {
|
|
986
|
+
element.outerHTML = element._html;
|
|
987
|
+
__Triggers_Disclaimermodal(_config)
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
`;
|
|
991
|
+
|
|
992
|
+
js = js.replace(startBlock, `_@wrapper(${category}, ${option}) {\n`);
|
|
993
|
+
|
|
994
|
+
const catchBlock = `
|
|
995
|
+
} catch(error) {
|
|
996
|
+
if (window.__preview || false) {
|
|
997
|
+
//element.innerHTML = '<div style="background: red; color: white">'
|
|
998
|
+
//+ \`${category} ${option} error: \${error.message}</div>\`;
|
|
999
|
+
console.error(error);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}`;
|
|
1003
|
+
|
|
1004
|
+
js = js.replace(catchBlock, '\n@}');
|
|
1005
|
+
return js;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const tree = parseHTML(file);
|
|
1009
|
+
|
|
1010
|
+
const headContent = extract('head', file);
|
|
1011
|
+
const bodyContent = extract('body', file);
|
|
1012
|
+
const styleContent = extract('style', headContent);
|
|
1013
|
+
const scriptContent = extract('script', bodyContent);
|
|
1014
|
+
|
|
1015
|
+
function removeTags(nodes, blacklist = ['style', 'script']) {
|
|
1016
|
+
return nodes
|
|
1017
|
+
.filter(n => !n.tag || !blacklist.includes(n.tag))
|
|
1018
|
+
.map(n => ({
|
|
1019
|
+
...n,
|
|
1020
|
+
children: n.children ? removeTags(n.children, blacklist) : []
|
|
1021
|
+
}));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const cleanTree = removeTags(tree);
|
|
1025
|
+
|
|
1026
|
+
let headNodes = [];
|
|
1027
|
+
let bodyNodes = [];
|
|
1028
|
+
|
|
1029
|
+
for (let n of cleanTree) {
|
|
1030
|
+
if (n.tag === 'html') {
|
|
1031
|
+
for (let c of n.children) {
|
|
1032
|
+
if (c.tag === 'head') headNodes = c.children;
|
|
1033
|
+
if (c.tag === 'body') bodyNodes = c.children;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
yrResult += '>>\n\n' + toYr(headNodes) + '\n\n';
|
|
1039
|
+
yrResult += (isProject ? '<#' : '##') + '\n\n' + formatCSS(styleContent) + '\n\n';
|
|
1040
|
+
yrResult += (isProject ? '><' : '++') + '\n\n' + toYr(bodyNodes) + '\n\n';
|
|
1041
|
+
yrResult += (isProject ? '<@' : '@>') + '\n\n' + wrapScript(scriptContent) + '\n';
|
|
1042
|
+
return yrResult;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
670
1045
|
const sections = (config.sections) ? config.sections : parsers.defaults();
|
|
671
1046
|
|
|
672
1047
|
const state = {
|
|
@@ -715,16 +1090,26 @@ const core = {
|
|
|
715
1090
|
section = parsers.namespaces[line].name;
|
|
716
1091
|
state.sectionChanged = true;
|
|
717
1092
|
|
|
718
|
-
|
|
719
|
-
|
|
1093
|
+
// Bug fix: close ALL open wrappers when section changes, innermost first (pop, not shift)
|
|
1094
|
+
while (state.wrapper.length > 0) {
|
|
1095
|
+
const wrapper = state.wrapper[state.wrapper.length - 1];
|
|
720
1096
|
|
|
721
1097
|
sections.yr[wrapper.section] =
|
|
722
1098
|
sections.yr[wrapper.section].replace(/#@#\n/, '');
|
|
723
1099
|
|
|
1100
|
+
// Close layers created inside this wrapper
|
|
1101
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
1102
|
+
const layer = state.layers[j];
|
|
1103
|
+
if (!layer || layer.indentation < wrapper.reference) break;
|
|
1104
|
+
if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
|
|
1105
|
+
layer.whiteSpace + `</${layer.tag}>\n`;
|
|
1106
|
+
state.layers.pop();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
724
1109
|
if (wrapper.wildCard)
|
|
725
1110
|
sections[wrapper.section] += wrapper.wildCard + '\n';
|
|
726
1111
|
|
|
727
|
-
state.wrapper.
|
|
1112
|
+
state.wrapper.pop();
|
|
728
1113
|
}
|
|
729
1114
|
|
|
730
1115
|
continue;
|
|
@@ -750,7 +1135,9 @@ const core = {
|
|
|
750
1135
|
const wrapper = wrapperName.split('/');
|
|
751
1136
|
if (wrapper.length === 1) wrapper.unshift('__');
|
|
752
1137
|
|
|
753
|
-
this.extend(wrapper, wrapperName, sections, state
|
|
1138
|
+
this.extend(wrapper, wrapperName, sections, state, {
|
|
1139
|
+
simple: config.simple
|
|
1140
|
+
});
|
|
754
1141
|
//} else if (line.startsWith('\\\\')) {
|
|
755
1142
|
// const wrapperName = line.replace(/\\\\/g, '').trim() + '\n';
|
|
756
1143
|
|
|
@@ -814,7 +1201,7 @@ const core = {
|
|
|
814
1201
|
};
|
|
815
1202
|
|
|
816
1203
|
const aux = {};
|
|
817
|
-
for (let item of ['header', 'body', 'footer', 'scripts']) {
|
|
1204
|
+
for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
818
1205
|
aux[item] = sections[item];
|
|
819
1206
|
sections[item] = '';
|
|
820
1207
|
}
|
|
@@ -832,7 +1219,7 @@ const core = {
|
|
|
832
1219
|
if (!sections.wrappers[config.wrapper].vars)
|
|
833
1220
|
sections.wrappers[config.wrapper].vars = {};
|
|
834
1221
|
|
|
835
|
-
for (let item of ['header', 'body', 'footer', 'scripts']) {
|
|
1222
|
+
for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
836
1223
|
sections.wrappers[config.wrapper].parsed[item] = extension[item];
|
|
837
1224
|
sections[item] = aux[item]
|
|
838
1225
|
}
|
|
@@ -861,15 +1248,17 @@ const core = {
|
|
|
861
1248
|
sections[wrapper.section] += wrapper.wildCard + '\n';
|
|
862
1249
|
|
|
863
1250
|
|
|
864
|
-
|
|
865
|
-
|
|
1251
|
+
// Only close layers that were created INSIDE this wrapper (indentation >= wrapper.reference)
|
|
1252
|
+
// External layers (pre-existing before the wrapper) should NOT be closed here
|
|
1253
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
1254
|
+
const layer = state.layers[j];
|
|
866
1255
|
if (!layer) continue;
|
|
1256
|
+
if (layer.indentation < wrapper.reference) break;
|
|
867
1257
|
|
|
868
|
-
sections[layer.section] +=
|
|
869
|
-
|
|
1258
|
+
if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
|
|
1259
|
+
layer.whiteSpace + `</${layer.tag}>\n`;
|
|
870
1260
|
|
|
871
|
-
|
|
872
|
-
state.layers = state.layers.filter(e => e !== layer);
|
|
1261
|
+
state.layers.pop();
|
|
873
1262
|
}
|
|
874
1263
|
|
|
875
1264
|
state.wrapper.pop();
|
|
@@ -878,7 +1267,7 @@ const core = {
|
|
|
878
1267
|
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
879
1268
|
if (j === -1) continue;
|
|
880
1269
|
|
|
881
|
-
sections[state.layers[j].section] +=
|
|
1270
|
+
if (!VOID_TAGS.has(state.layers[j].tag)) sections[state.layers[j].section] +=
|
|
882
1271
|
common.getWhiteSpace(state.layers[j].indentation) + `</${state.layers[j].tag}>\n`;
|
|
883
1272
|
|
|
884
1273
|
state.layers.pop();
|
|
@@ -939,7 +1328,7 @@ const core = {
|
|
|
939
1328
|
sections.parsedyr.extensions += item + '\n';
|
|
940
1329
|
}
|
|
941
1330
|
|
|
942
|
-
for (let value of ['header', 'body', 'footer', 'scripts']) {
|
|
1331
|
+
for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
943
1332
|
if (!sections[value]) sections[value] = '';
|
|
944
1333
|
if (!sections.yr[value]) sections.yr[value] = '';
|
|
945
1334
|
|
|
@@ -1010,7 +1399,7 @@ const core = {
|
|
|
1010
1399
|
|
|
1011
1400
|
parsedItem = JSON.parse(`{${parsedItem}}`);
|
|
1012
1401
|
|
|
1013
|
-
for (let value of ['header', 'body', 'footer', 'scripts']) {
|
|
1402
|
+
for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
1014
1403
|
if (sections[`parsed${value}`].includes(`${parsedItem.id}`)) {
|
|
1015
1404
|
parsedItem.attributes = sections
|
|
1016
1405
|
.wrappers[`${parsedItem.category}/${parsedItem.option}`].vars.attributes;
|
|
@@ -1145,7 +1534,7 @@ const core = {
|
|
|
1145
1534
|
}
|
|
1146
1535
|
}
|
|
1147
1536
|
|
|
1148
|
-
for (let value of ['header', 'body', 'footer', 'scripts']) {
|
|
1537
|
+
for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
1149
1538
|
if (sections.parsedyr[value].includes(item.replace(/!/g, '')
|
|
1150
1539
|
.trim().toLowerCase())) {
|
|
1151
1540
|
newExtensions += item + '\n';
|
|
@@ -1166,7 +1555,7 @@ const core = {
|
|
|
1166
1555
|
continue;
|
|
1167
1556
|
}
|
|
1168
1557
|
|
|
1169
|
-
for (let key of ['header', 'body', 'footer', 'scripts']) {
|
|
1558
|
+
for (let key of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
1170
1559
|
//if (sections.parsedyr[key].includes(`.${value}`)) {
|
|
1171
1560
|
if (!newCss[item]) newCss[item] = {};
|
|
1172
1561
|
newCss[item][value] = parsedCss[item][value];
|
|
@@ -1181,7 +1570,7 @@ const core = {
|
|
|
1181
1570
|
//const templateRegex = id =>
|
|
1182
1571
|
// new RegExp(`\\{\\{[^}]*?\\.?${id}\\b[^}]*?\\}\\}`);
|
|
1183
1572
|
|
|
1184
|
-
//for (let item of ['header', 'body', 'footer', 'scripts']) {
|
|
1573
|
+
//for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
|
|
1185
1574
|
// for (let value of sections.parsedyr[item].split('\n')) {
|
|
1186
1575
|
// const match = value.match(/(?:\.|\.\{\{)\s*(__[A-Za-z0-9_-]+)/);
|
|
1187
1576
|
// if (!match) continue;
|
|
@@ -1239,6 +1628,9 @@ const core = {
|
|
|
1239
1628
|
if (sections.wrapperjscustom)
|
|
1240
1629
|
sections.parsedjs += '\n' + sections.wrapperjscustom;
|
|
1241
1630
|
|
|
1631
|
+
if (sections.wrapperheader)
|
|
1632
|
+
sections.parsedheader += '\n' + sections.wrapperheader;
|
|
1633
|
+
|
|
1242
1634
|
if (!config.lang) config.lang = 'en-US';
|
|
1243
1635
|
|
|
1244
1636
|
if (config.name) {
|
|
@@ -1250,7 +1642,7 @@ const core = {
|
|
|
1250
1642
|
if (sections.apptests.length > 0 && !sections.parsedbody.includes('class="__')) {
|
|
1251
1643
|
sections.parsedbody += '\n<div class="__cclass"></div>';
|
|
1252
1644
|
|
|
1253
|
-
sections.parsedscripts = `<
|
|
1645
|
+
sections.parsedscripts = `<s` + `cript>
|
|
1254
1646
|
try {
|
|
1255
1647
|
function require(name) {
|
|
1256
1648
|
//return window[name];
|
|
@@ -1262,7 +1654,7 @@ ${sections.parsedjs}
|
|
|
1262
1654
|
${sections.appheader.join('')}
|
|
1263
1655
|
${sections.apptests.join('')}
|
|
1264
1656
|
} catch(error) { console.log(error); }
|
|
1265
|
-
</
|
|
1657
|
+
</s` + `cript>`;
|
|
1266
1658
|
}
|
|
1267
1659
|
|
|
1268
1660
|
sections.parsedhtml = `<!DOCTYPE html>
|
|
@@ -1275,16 +1667,16 @@ ${(config.name) ? ` <link rel="stylesheet" href="./${config.cssname}.css">` : `
|
|
|
1275
1667
|
<body style="display: none">
|
|
1276
1668
|
${sections.parsedbody}
|
|
1277
1669
|
${sections.parsedfooter}
|
|
1278
|
-
</body>
|
|
1279
1670
|
${sections.parsedscripts}
|
|
1280
|
-
<
|
|
1671
|
+
<s` + `cript id="p` + `sj"${(config.name) ? ` src="./${config.jsname}.js">`
|
|
1281
1672
|
: `>\n${sections.parsedjs}\n`
|
|
1282
|
-
}</
|
|
1283
|
-
|
|
1673
|
+
}</s` + `cript>
|
|
1674
|
+
</body>
|
|
1675
|
+
<s` + `cript>
|
|
1284
1676
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1285
1677
|
document.body.style.display = 'block';
|
|
1286
1678
|
});
|
|
1287
|
-
</
|
|
1679
|
+
</s` + `cript>
|
|
1288
1680
|
</html>`;
|
|
1289
1681
|
|
|
1290
1682
|
if (config.name) sections.ui = [
|
|
@@ -1305,4 +1697,13 @@ if (typeof module !== 'undefined' && module.exports)
|
|
|
1305
1697
|
if (typeof window !== 'undefined' && !window.yr) {
|
|
1306
1698
|
const parse = core.parse.bind(core);
|
|
1307
1699
|
window.yr = parse;
|
|
1700
|
+
|
|
1701
|
+
const createLog = (text) => {
|
|
1702
|
+
const parsed = parse(text);
|
|
1703
|
+
console.log(parsed);
|
|
1704
|
+
document.body.innerHTML += `<div>${text}</div>`;
|
|
1705
|
+
document.body.innerHTML += `<div>${parsed.parsedyr.body}</div>`;
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
createLog('><\n_ .teste');
|
|
1308
1709
|
}
|