@yr-lang/yr 0.2.0 → 0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
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
+
3
9
  ## [0.2.0] - 2026-03-01
4
10
 
5
11
  ### Fixed
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
+ '&': '&',
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.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Yr is a modern structural language and parser",
5
5
  "main": "./node.js",
6
6
  "exports": {
package/yr.js CHANGED
@@ -28,6 +28,7 @@ const common = {
28
28
  const namespaces = {
29
29
  '@&': { name: 'jsapp', parser: 'array', default: [], merge: true },
30
30
  '++': { name: 'wrappers', parser: 'auxstring', default: {}, merge: true },
31
+ '+>': { name: 'wrapperheader', parser: 'html', default: '', merge: true },
31
32
  '>+': { name: 'wrapperbody', parser: 'html', default: '', merge: true },
32
33
  '><': { name: 'body', parser: 'html', default: '', merge: true },
33
34
  '@>': { name: 'jsheader', parser: 'string', default: '', merge: true },
@@ -149,6 +150,13 @@ function getElementAttributes(line) {
149
150
  }
150
151
 
151
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
+
152
160
  if (config.preview && !line.includes('.__') && !line.includes('.{{__')) {
153
161
  if (!elementId) elementId = '__' + crypto.generateToken(8);
154
162
  if (!line.trim().startsWith('_')) elementId = '{{' + elementId;
@@ -183,6 +191,11 @@ function addIdToElement(line, config, elementId=false) {
183
191
  return line;
184
192
  }
185
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
+
186
199
  const parserFns = {
187
200
  array(line, sections, section, state, lineNumber, config={}) {
188
201
  if (state.sectionChanged) sections[section].push('')
@@ -200,7 +213,7 @@ const parserFns = {
200
213
  sections[section] += line + '\n';
201
214
  },
202
215
  html(line, sections, section, state, lineNumber, config={}) {
203
- if (config.ignoreHtml) return;
216
+ if (config.ignoreHtml && !['wrapperheader', 'header', 'footer', 'scripts'].includes(section)) return;
204
217
  if (!sections.yr[section]) sections.yr[section] = '';
205
218
  const parsedLine = parseLine(line);
206
219
  if (parsedLine.line.startsWith('//')) {
@@ -235,7 +248,8 @@ const parserFns = {
235
248
  elementId = '__' + split[1];
236
249
  parsedLine.line = split[0].trimEnd();
237
250
  }
238
- yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
251
+ if (!['wrapperheader', 'header', 'footer', 'scripts'].includes(section))
252
+ yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
239
253
  let yrIndentation = 0, ignoreYr, wildCard = '', replaceYr;
240
254
  // Bug fix: use while loop to close ALL wrappers that need closing (not just one)
241
255
  // A single element may need to close multiple nested wrappers at once
@@ -253,7 +267,7 @@ const parserFns = {
253
267
  if (!layer) continue;
254
268
  // Only close layers that belong inside this wrapper (indentation >= wrapper.reference)
255
269
  if (layer.indentation >= wrapper.reference) {
256
- sections[layer.section] +=
270
+ if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
257
271
  layer.whiteSpace + `</${layer.tag}>\n`;
258
272
  state.layers.pop();
259
273
  } else {
@@ -284,13 +298,13 @@ const parserFns = {
284
298
  if (parsedLine.level < state.level) {
285
299
  for (let j = state.layers.length; j > parsedLine.level - 1; j--) {
286
300
  if (!state.layers[j - 1]) continue;
287
- sections[state.layers[j - 1].section] +=
301
+ if (!VOID_TAGS.has(state.layers[j - 1].tag)) sections[state.layers[j - 1].section] +=
288
302
  state.layers[j - 1].whiteSpace + `</${state.layers[j - 1].tag}>\n`;
289
303
  state.layers.pop();
290
304
  }
291
305
  } else if (parsedLine.level === state.level
292
306
  && state.element && state.layers.length > 0) {
293
- 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] +=
294
308
  `</${state.layers[state.layers.length - 1].tag}>\n`;
295
309
  state.layers.pop();
296
310
  }
@@ -299,7 +313,7 @@ const parserFns = {
299
313
  || state.layers[state.layers.length - 1].indentation === parsedLine.indentation)) {
300
314
  for (let j = state.layers.length - 1; j >= 0; j--) {
301
315
  if (j === -1) continue;
302
- sections[state.layers[j].section] +=
316
+ if (!VOID_TAGS.has(state.layers[j].tag)) sections[state.layers[j].section] +=
303
317
  state.layers[j].whiteSpace + `</${state.layers[j].tag}>\n`;
304
318
  state.layers.pop();
305
319
  }
@@ -315,7 +329,7 @@ const parserFns = {
315
329
  if (tag[0] === '_') {
316
330
  tag = tag.replace(/__/, '');
317
331
  if (!tag) tag = 'div';
318
- wildCard += '<!--#@#-->';
332
+ if (!config.simple) wildCard += '<!--#@#-->';
319
333
  }
320
334
  let attributes = '', id = '', classes = '';
321
335
  for (let item of lineSplit) {
@@ -336,12 +350,22 @@ const parserFns = {
336
350
  attributes += ` ${item}`;
337
351
  }
338
352
  }
339
- 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) {
340
362
  const wrapper = tag.replace(/!/, '').split('/');
341
363
  wrapper[0] = utils.capitalize(wrapper[0]);
342
364
  wrapper[1] = utils.capitalize(wrapper[1]);
343
365
  const wrapperName = wrapper.join('/');
344
- core.extend(wrapper, wrapperName, sections, state, { redoWrapper: true });
366
+ core.extend(wrapper, wrapperName, sections, state, {
367
+ redoWrapper: true, simple: config.simple
368
+ });
345
369
  let newWrapper, wrapperIndentation;
346
370
  if (!tag.includes('!')) {
347
371
  newWrapper = {
@@ -408,7 +432,7 @@ const parserFns = {
408
432
  classes = classes.split('class="').join(`class="${elementId} `);
409
433
  }
410
434
  }
411
- if (config.preview) {
435
+ if (config.preview && !['wrapperheader', 'header', 'footer', 'scripts'].includes(section)) {
412
436
  if (!classes) {
413
437
  classes = ` class="${elementId}"`;
414
438
  } else if (!classes.includes(elementId)) {
@@ -422,7 +446,7 @@ const parserFns = {
422
446
  state.element = true;
423
447
  } else {
424
448
  if (section) {
425
- if (section === 'header') {
449
+ if (section === 'header' || section === 'wrapperheader') {
426
450
  sections[section] += parsedLine.whiteSpace + parsedLine.line + '\n';
427
451
  } else {
428
452
  let attributes = '';
@@ -437,6 +461,16 @@ const parserFns = {
437
461
  }
438
462
  }
439
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
+
440
474
  const parsers = {
441
475
  namespaces, tokens: Object.keys(namespaces),
442
476
  parsers: _parsers, mergers, names,
@@ -614,6 +648,8 @@ const core = {
614
648
  }
615
649
  },
616
650
  extend(wrapper, wrapperName, sections, state, config={}) {
651
+ if (config.simple && !wrapperName.includes('@')) return;
652
+
617
653
  if (!sections.parsedyr)
618
654
  sections.parsedyr = { header: '', body: '', footer: '', scripts: '' };
619
655
 
@@ -649,7 +685,7 @@ const core = {
649
685
  sections.yr = {};
650
686
 
651
687
  const aux = {};
652
- for (let item of ['header', 'body', 'footer', 'scripts']) {
688
+ for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
653
689
  aux[item] = sections[item];
654
690
  sections[item] = '';
655
691
  }
@@ -667,7 +703,7 @@ const core = {
667
703
  if (!sections.wrappers[wrapperName].vars)
668
704
  sections.wrappers[wrapperName].vars = {};
669
705
 
670
- for (let item of ['header', 'body', 'footer', 'scripts']) {
706
+ for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
671
707
  sections.wrappers[wrapperName].parsed[item] = result[item];
672
708
  sections[item] = aux[item];
673
709
  }
@@ -683,6 +719,329 @@ const core = {
683
719
  }
684
720
  },
685
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
+
686
1045
  const sections = (config.sections) ? config.sections : parsers.defaults();
687
1046
 
688
1047
  const state = {
@@ -742,7 +1101,8 @@ const core = {
742
1101
  for (let j = state.layers.length - 1; j >= 0; j--) {
743
1102
  const layer = state.layers[j];
744
1103
  if (!layer || layer.indentation < wrapper.reference) break;
745
- sections[layer.section] += layer.whiteSpace + `</${layer.tag}>\n`;
1104
+ if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
1105
+ layer.whiteSpace + `</${layer.tag}>\n`;
746
1106
  state.layers.pop();
747
1107
  }
748
1108
 
@@ -775,7 +1135,9 @@ const core = {
775
1135
  const wrapper = wrapperName.split('/');
776
1136
  if (wrapper.length === 1) wrapper.unshift('__');
777
1137
 
778
- this.extend(wrapper, wrapperName, sections, state);
1138
+ this.extend(wrapper, wrapperName, sections, state, {
1139
+ simple: config.simple
1140
+ });
779
1141
  //} else if (line.startsWith('\\\\')) {
780
1142
  // const wrapperName = line.replace(/\\\\/g, '').trim() + '\n';
781
1143
 
@@ -839,7 +1201,7 @@ const core = {
839
1201
  };
840
1202
 
841
1203
  const aux = {};
842
- for (let item of ['header', 'body', 'footer', 'scripts']) {
1204
+ for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
843
1205
  aux[item] = sections[item];
844
1206
  sections[item] = '';
845
1207
  }
@@ -857,7 +1219,7 @@ const core = {
857
1219
  if (!sections.wrappers[config.wrapper].vars)
858
1220
  sections.wrappers[config.wrapper].vars = {};
859
1221
 
860
- for (let item of ['header', 'body', 'footer', 'scripts']) {
1222
+ for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
861
1223
  sections.wrappers[config.wrapper].parsed[item] = extension[item];
862
1224
  sections[item] = aux[item]
863
1225
  }
@@ -893,7 +1255,7 @@ const core = {
893
1255
  if (!layer) continue;
894
1256
  if (layer.indentation < wrapper.reference) break;
895
1257
 
896
- sections[layer.section] +=
1258
+ if (!VOID_TAGS.has(layer.tag)) sections[layer.section] +=
897
1259
  layer.whiteSpace + `</${layer.tag}>\n`;
898
1260
 
899
1261
  state.layers.pop();
@@ -905,7 +1267,7 @@ const core = {
905
1267
  for (let j = state.layers.length - 1; j >= 0; j--) {
906
1268
  if (j === -1) continue;
907
1269
 
908
- sections[state.layers[j].section] +=
1270
+ if (!VOID_TAGS.has(state.layers[j].tag)) sections[state.layers[j].section] +=
909
1271
  common.getWhiteSpace(state.layers[j].indentation) + `</${state.layers[j].tag}>\n`;
910
1272
 
911
1273
  state.layers.pop();
@@ -966,7 +1328,7 @@ const core = {
966
1328
  sections.parsedyr.extensions += item + '\n';
967
1329
  }
968
1330
 
969
- for (let value of ['header', 'body', 'footer', 'scripts']) {
1331
+ for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
970
1332
  if (!sections[value]) sections[value] = '';
971
1333
  if (!sections.yr[value]) sections.yr[value] = '';
972
1334
 
@@ -1037,7 +1399,7 @@ const core = {
1037
1399
 
1038
1400
  parsedItem = JSON.parse(`{${parsedItem}}`);
1039
1401
 
1040
- for (let value of ['header', 'body', 'footer', 'scripts']) {
1402
+ for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
1041
1403
  if (sections[`parsed${value}`].includes(`${parsedItem.id}`)) {
1042
1404
  parsedItem.attributes = sections
1043
1405
  .wrappers[`${parsedItem.category}/${parsedItem.option}`].vars.attributes;
@@ -1172,7 +1534,7 @@ const core = {
1172
1534
  }
1173
1535
  }
1174
1536
 
1175
- for (let value of ['header', 'body', 'footer', 'scripts']) {
1537
+ for (let value of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
1176
1538
  if (sections.parsedyr[value].includes(item.replace(/!/g, '')
1177
1539
  .trim().toLowerCase())) {
1178
1540
  newExtensions += item + '\n';
@@ -1193,7 +1555,7 @@ const core = {
1193
1555
  continue;
1194
1556
  }
1195
1557
 
1196
- for (let key of ['header', 'body', 'footer', 'scripts']) {
1558
+ for (let key of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
1197
1559
  //if (sections.parsedyr[key].includes(`.${value}`)) {
1198
1560
  if (!newCss[item]) newCss[item] = {};
1199
1561
  newCss[item][value] = parsedCss[item][value];
@@ -1208,7 +1570,7 @@ const core = {
1208
1570
  //const templateRegex = id =>
1209
1571
  // new RegExp(`\\{\\{[^}]*?\\.?${id}\\b[^}]*?\\}\\}`);
1210
1572
 
1211
- //for (let item of ['header', 'body', 'footer', 'scripts']) {
1573
+ //for (let item of ['wrapperheader', 'header', 'body', 'footer', 'scripts']) {
1212
1574
  // for (let value of sections.parsedyr[item].split('\n')) {
1213
1575
  // const match = value.match(/(?:\.|\.\{\{)\s*(__[A-Za-z0-9_-]+)/);
1214
1576
  // if (!match) continue;
@@ -1266,6 +1628,9 @@ const core = {
1266
1628
  if (sections.wrapperjscustom)
1267
1629
  sections.parsedjs += '\n' + sections.wrapperjscustom;
1268
1630
 
1631
+ if (sections.wrapperheader)
1632
+ sections.parsedheader += '\n' + sections.wrapperheader;
1633
+
1269
1634
  if (!config.lang) config.lang = 'en-US';
1270
1635
 
1271
1636
  if (config.name) {
@@ -1277,7 +1642,7 @@ const core = {
1277
1642
  if (sections.apptests.length > 0 && !sections.parsedbody.includes('class="__')) {
1278
1643
  sections.parsedbody += '\n<div class="__cclass"></div>';
1279
1644
 
1280
- sections.parsedscripts = `<script>
1645
+ sections.parsedscripts = `<s` + `cript>
1281
1646
  try {
1282
1647
  function require(name) {
1283
1648
  //return window[name];
@@ -1289,7 +1654,7 @@ ${sections.parsedjs}
1289
1654
  ${sections.appheader.join('')}
1290
1655
  ${sections.apptests.join('')}
1291
1656
  } catch(error) { console.log(error); }
1292
- </script>`;
1657
+ </s` + `cript>`;
1293
1658
  }
1294
1659
 
1295
1660
  sections.parsedhtml = `<!DOCTYPE html>
@@ -1302,16 +1667,16 @@ ${(config.name) ? ` <link rel="stylesheet" href="./${config.cssname}.css">` : `
1302
1667
  <body style="display: none">
1303
1668
  ${sections.parsedbody}
1304
1669
  ${sections.parsedfooter}
1305
- </body>
1306
1670
  ${sections.parsedscripts}
1307
- <script id="psj"${(config.name) ? ` src="./${config.jsname}.js">`
1671
+ <s` + `cript id="p` + `sj"${(config.name) ? ` src="./${config.jsname}.js">`
1308
1672
  : `>\n${sections.parsedjs}\n`
1309
- }</script>
1310
- <script>
1673
+ }</s` + `cript>
1674
+ </body>
1675
+ <s` + `cript>
1311
1676
  document.addEventListener('DOMContentLoaded', () => {
1312
1677
  document.body.style.display = 'block';
1313
1678
  });
1314
- </script>
1679
+ </s` + `cript>
1315
1680
  </html>`;
1316
1681
 
1317
1682
  if (config.name) sections.ui = [
@@ -1332,4 +1697,13 @@ if (typeof module !== 'undefined' && module.exports)
1332
1697
  if (typeof window !== 'undefined' && !window.yr) {
1333
1698
  const parse = core.parse.bind(core);
1334
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');
1335
1709
  }