@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 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 can fail
6
+ ### Wrapper-only section closure Fixed in 0.2.0
7
7
 
8
- In some cases, a section cannot be closed using only a wrapper, and must instead be closed explicitly with an element (_).
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
@@ -1,3 +1,3 @@
1
1
  **
2
2
 
3
- cd "$_PROJECT_PATH"
3
+ cd "$_PROJECT_PATH"
package/lib/%deploy.yr CHANGED
@@ -4,4 +4,6 @@
4
4
 
5
5
  ___deploy
6
6
 
7
+ source "$_PROJECT_PATH/actions/common"
8
+ cd "$_PROJECT_PATH"
7
9
  gh-pages dist
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,9 @@
1
+ %%
2
+
3
+ _@try(#showError) {
4
+ try {
5
+ ___
6
+ } catch(error) {
7
+ if (#showError) console.log(error);
8
+ }
9
+ @}
@@ -0,0 +1,68 @@
1
+ @@
2
+
3
+ const htmlEntities = {
4
+ '&amp;': '&',
5
+ '&lt;': '<',
6
+ '&gt;': '>',
7
+ '&quot;': '"',
8
+ '&#039;': "'",
9
+ '&ndash;': '-',
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
@@ -189,6 +189,8 @@ function dragElement(
189
189
  };
190
190
 
191
191
  const start = (x, y, e) => {
192
+ document.querySelectorAll('.drag-clone').forEach(item => item.remove());
193
+
192
194
  X = x; Y = y;
193
195
  activeEl = (!options.clone) ? element : createClone();
194
196
 
@@ -0,0 +1,9 @@
1
+ @@
2
+
3
+ function windowListener(callback) {
4
+ if (typeof window.addEventListener != 'undefined') {
5
+ window.addEventListener('message', callback);
6
+ } else if (typeof window.attachEvent != 'undefined') {
7
+ window.attachEvent('onmessage', callback);
8
+ }
9
+ }
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
- const child = spawn(path, args, { shell: true });
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
- //child.stdin.on('data', function(data){
261
- // console.log(data.toString());
262
- //});
254
+ if (log) console.log('spawn pid:', child.pid);
263
255
 
264
- child.stdout.on('data', (data) => {
265
- data = data.toString();
266
- if (log) console.log(data);
267
- if (callbackData) callbackData(data);
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.on('data', (data) => {
271
- data = data.toString();
272
- if (log) console.log('\x1b[31m' + data + '\x1b[0m');
273
- if (callbackError) callbackError(data);
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('finish spawn\n');
278
- //if (state.building === 1) {
279
- // state.building = true;
280
- // return this.devops(appName, deploy, log, exit);
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.1.1",
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
- try {
22
- return new Array((indentation * 2) - 1).join(' ');
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
- parse[parseKey.trim()] = parseValue.trim();
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
- yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
251
+ if (!['wrapperheader', 'header', 'footer', 'scripts'].includes(section))
252
+ yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
236
253
  let yrIndentation = 0, ignoreYr, wildCard = '', replaceYr;
237
- if (state.wrapper.length > 0) {
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, { redoWrapper: true });
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 + '&#47;&#47;';
853
+ });
854
+ }
855
+
856
+ if (text.trimStart().startsWith('_')) {
857
+ text = text.replace(/^(\s*)_/, (_, spaces) => {
858
+ return spaces + '&#95;';
859
+ });
860
+ }
861
+
862
+ if (text.trimStart().startsWith('.')) {
863
+ text = text.replace(/^(\s*)\./, (_, spaces) => {
864
+ return spaces + '&#46;';
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
- if (state.wrapper.length > 0) {
719
- const wrapper = state.wrapper[0];
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.shift();
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
- for (let j = wrapper.layers.length - 1; j >= 0; j--) {
865
- const layer = wrapper.layers[j];
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
- common.getWhiteSpace(layer.indentation) + `</${layer.tag}>\n`;
1258
+ if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
1259
+ layer.whiteSpace + `</${layer.tag}>\n`;
870
1260
 
871
- wrapper.layers.pop();
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 = `<script>
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
- </script>`;
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
- <script id="psj"${(config.name) ? ` src="./${config.jsname}.js">`
1671
+ <s` + `cript id="p` + `sj"${(config.name) ? ` src="./${config.jsname}.js">`
1281
1672
  : `>\n${sections.parsedjs}\n`
1282
- }</script>
1283
- <script>
1673
+ }</s` + `cript>
1674
+ </body>
1675
+ <s` + `cript>
1284
1676
  document.addEventListener('DOMContentLoaded', () => {
1285
1677
  document.body.style.display = 'block';
1286
1678
  });
1287
- </script>
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
  }