@caweb/cli 1.11.2 → 1.12.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.
@@ -22,9 +22,13 @@ const { DIVI_VER } = JSON.parse( fs.readFileSync( path.join(projectPath, 'packag
22
22
  /**
23
23
  * Modules allow list
24
24
  */
25
+ const commonElements = ['div', 'a', 'b', 'p', 'br', 'span', 'strong', 'ul', 'ol', 'li'];
26
+
27
+
25
28
  const allowedModules = {
26
- 'et_pb_text': ['p', 'span', 'a', 'b'],
27
- 'et_pb_heading': ['span', 'a', 'b']
29
+ 'et_pb_text': [...commonElements, 'code'],
30
+ 'et_pb_heading': ['span', 'a', 'b'],
31
+ 'et_pb_code': commonElements,
28
32
  }
29
33
 
30
34
 
@@ -100,7 +104,7 @@ function convertRawAttrs( rawAttrs ) {
100
104
 
101
105
  let attrs = {};
102
106
 
103
- // attributes follow the format of key="value" key="value"
107
+ // attributes follow the format of key="value"
104
108
  // we want to split the string by "space
105
109
  let attrArray = rawAttrs.split( '" ' );
106
110
 
@@ -144,7 +148,8 @@ function getAttrString(attributes, limiter) {
144
148
  case 'style':
145
149
  key = [];
146
150
 
147
- value.split(';').forEach( (v) => {
151
+ value.split(';').filter(Boolean).forEach( (v) => {
152
+
148
153
  let k = v.split(':')[0].trim();
149
154
  let s = v.split(':')[1].trim();
150
155
 
@@ -266,7 +271,9 @@ function addElement( tag, opts = {} ) {
266
271
  return content;
267
272
  }
268
273
 
269
- let output = `${lmtrObj.open}${tag}${attrString}${tag && isSelfClosing ? ' /' : ''}${lmtrObj.close}${content.trim()}`
274
+ content = content.trim();
275
+
276
+ let output = `${lmtrObj.open}${tag}${attrString}${tag && isSelfClosing ? ' /' : ''}${lmtrObj.close}${content}`
270
277
 
271
278
  if( ! isUnclosed && ! isSelfClosing && tag) {
272
279
  output += closeElement(tag, lmtrObj );
@@ -286,7 +293,6 @@ function closeElement( tag, limiter ) {
286
293
  return `${lmtrObj.open}/${tag}${lmtrObj.close}`
287
294
  }
288
295
 
289
-
290
296
  /**
291
297
  * Generates shortcodes from the json object.
292
298
  * Converts a json dom object into shortcodes.
@@ -333,13 +339,13 @@ function generateShortcodes( mainContent, opts = {
333
339
 
334
340
  // we convert the raw attributes to a json object
335
341
  let attrs = convertRawAttrs( rawAttrs );
336
-
342
+
337
343
  /**
338
344
  * We dont want to convert empty elements
339
345
  */
340
346
  if( ! content.length ){
341
- // image tags are self closing and don't have content this is ok.
342
- if( 'img' === type ) {
347
+ // image tags and br are self closing and don't have content this is ok.
348
+ if( ['img', 'br'].includes( type ) ) {
343
349
  // do nothing this is ok.
344
350
  // no length no type blank spaces
345
351
  } else if( '' !== type ){
@@ -369,6 +375,10 @@ function generateShortcodes( mainContent, opts = {
369
375
  if( classes.includes('container-fluid') && ! opts.inFullwidth) {
370
376
  opts.inFullwidth = true;
371
377
 
378
+ // fullwidth sections don't need the container-fluid class
379
+ // we remove the container-fluid class from the attributes
380
+ attrs.class = attrs.class.replace('container-fluid', '');
381
+
372
382
  let hasContainers = content.filter((r,i) => {
373
383
  if(r.attributes.class.match(/container/g)){
374
384
  // we add the raw attributes to the containers
@@ -381,10 +391,17 @@ function generateShortcodes( mainContent, opts = {
381
391
 
382
392
  if( ! hasContainers ) {
383
393
  output += addElement(
384
- 'et_pb_fullwidth_section',
394
+ 'et_pb_section',
385
395
  {
386
396
  limiter:opts.limiter,
387
- content: generateShortcodes(content, opts)
397
+ content: generateShortcodes(content, opts),
398
+ attrs: {
399
+ _builder_version: DIVI_VER,
400
+ fullwidth: 'on',
401
+ inner_width: 'auto',
402
+ inner_max_width: '1080px',
403
+ ...attrs,
404
+ }
388
405
  }
389
406
  );
390
407
  }else{
@@ -473,17 +490,29 @@ function generateShortcodes( mainContent, opts = {
473
490
  let colType = '';
474
491
  // calculate the column type
475
492
  switch( colSize ) {
476
- // 1 column
477
- case 12:
478
- colType = '4_4';
493
+ // 1/4 columns
494
+ case 3:
495
+ colType = '1_4';
496
+ break;
497
+ // 1/3 columns
498
+ case 4:
499
+ colType = '1_3';
479
500
  break;
480
- // 2 columns
501
+ // 1/2 columns
481
502
  case 6:
482
503
  colType = '1_2';
483
504
  break;
484
- // 3 columns
485
- case 4:
486
- colType = '1_3';
505
+ // 2/3 columns
506
+ case 8:
507
+ colType = '2_3';
508
+ break;
509
+ // 3/4 column
510
+ case 9:
511
+ colType = '3_4';
512
+ break;
513
+ // 1 column
514
+ case 12:
515
+ colType = '4_4';
487
516
  break;
488
517
  }
489
518
 
@@ -539,8 +568,9 @@ function generateShortcodes( mainContent, opts = {
539
568
  output += addElement(
540
569
  type,
541
570
  {
542
- limiter: opts.limiter,
543
- content: generateShortcodes(content, opts)
571
+ limiter: 'angle',
572
+ content: generateShortcodes(content, opts),
573
+ attrs
544
574
  }
545
575
  );
546
576
 
@@ -552,7 +582,17 @@ function generateShortcodes( mainContent, opts = {
552
582
 
553
583
  // code module
554
584
  case 'code':
555
- output += addElement(setFullwidthTag('et_pb_code', opts.inFullwidth), {attrs, limiter: 'bracket', content: generateShortcodes(content)} );
585
+ // if already opened
586
+ let codeElement = 'et_pb_column' === opts.openElement ?
587
+ setFullwidthTag('et_pb_code', opts.inFullwidth) :
588
+ 'code'
589
+
590
+ output += addElement(
591
+ codeElement,
592
+ {
593
+ attrs, limiter: 'code' === codeElement ? 'angle' : 'bracket',
594
+ content: generateShortcodes(content, opts)
595
+ } );
556
596
  break;
557
597
  // header modules
558
598
  case 'h1':
@@ -565,7 +605,6 @@ function generateShortcodes( mainContent, opts = {
565
605
  // this allows other elements to be added to the header modules
566
606
  // opts.openElement = 'et_pb_heading';
567
607
 
568
-
569
608
  output += generateModuleShortcode('heading', htmlElement);
570
609
  // opts.openElement = false;
571
610
  break;
@@ -581,11 +620,16 @@ function generateShortcodes( mainContent, opts = {
581
620
  );
582
621
 
583
622
  break;
584
- // all theses elements can go in a text module
623
+ // all theses elements can go in other modules
624
+ // they also need to be added to the allowedModules list
585
625
  case 'a':
586
626
  case 'b':
587
627
  case 'p':
628
+ case 'ol':
629
+ case 'ul':
630
+ case 'li':
588
631
  case 'span':
632
+ case 'strong':
589
633
 
590
634
  if( opts.inFullwidth ) {
591
635
  // figure out what to do with these elements if in a fullwidth section
@@ -614,7 +658,6 @@ function generateShortcodes( mainContent, opts = {
614
658
  }
615
659
  );
616
660
 
617
-
618
661
  // a text module has already been opened so we just add to it
619
662
  }else {
620
663
  output += addElement(
@@ -629,7 +672,6 @@ function generateShortcodes( mainContent, opts = {
629
672
  break;
630
673
  // default is a string element
631
674
  default:
632
- // console.log( htmlElement)
633
675
  output += htmlElement;
634
676
  break;
635
677
 
@@ -641,6 +683,127 @@ function generateShortcodes( mainContent, opts = {
641
683
  return output;
642
684
  }
643
685
 
686
+ /**
687
+ * Bootstraps to Object conversion process.
688
+ *
689
+ * @param {*} classes
690
+ * @param {*} styles
691
+ * @returns {object}
692
+ */
693
+ function bsToProp(classes, styles = {}) {
694
+ let props = {};
695
+
696
+ let fontWeight = {
697
+ lighter: '200',
698
+ light: '300',
699
+ normal: '400',
700
+ medium: '500',
701
+ semibold: '600',
702
+ bold: '700',
703
+ extrabold: '800',
704
+ }
705
+ let fontColors = {
706
+ 'light': 'ededef',
707
+ 'dark': '3b3a48',
708
+ 'white': 'fff',
709
+ 'black': '000',
710
+ }
711
+ let alignments = {
712
+ 'left': 'left',
713
+ 'center': 'center',
714
+ 'right': 'right',
715
+ }
716
+
717
+ for( const c of classes.values() ) {
718
+ if( c.startsWith('fw-') ) {
719
+ let fontWeightValue = c.replace('fw-', '');
720
+ props.font_weight = fontWeight[fontWeightValue] || fontWeight.normal;
721
+ classes.remove(c);
722
+ }
723
+
724
+ if( c.startsWith('text-') ) {
725
+ let textUtil = c.replace('text-', '');
726
+ // text orientation
727
+ if( alignments[textUtil] ){
728
+ props.text_orientation = alignments[textUtil];
729
+ classes.remove(c);
730
+ }
731
+
732
+ // text color
733
+ if( fontColors[textUtil] ) {
734
+ props.font_color = fontColors[textUtil];
735
+ classes.remove(c);
736
+ }
737
+
738
+ }
739
+ }
740
+
741
+ if( styles && styles.length ) {
742
+ styles.split(';').filter(Boolean).forEach( (v) => {
743
+ let k = v.split(':')[0].trim();
744
+ let s = v.split(':')[1].trim();
745
+ // style mappings
746
+ switch( k ) {
747
+ case 'background-color':
748
+ props.background_color = s;
749
+ case 'background-image':
750
+ let gradient = s.replace(/.*[,\s]*linear-gradient\((.*?)\).*/g, '$1').replace(/["']/g, '');
751
+
752
+ if( gradient ) {
753
+ // if the gradient is present we want to add the appropriate values
754
+ let gradientValues = gradient.split(',');
755
+
756
+ props.background_color_gradient_direction = gradientValues[0].trim();
757
+ props.background_color_gradient_stops = gradientValues.slice(1).join('|');
758
+ props.use_background_color_gradient = 'on';
759
+ }
760
+
761
+ props.background_image = s.replace(/url\((.*?)\).*/g, '$1').replace(/["']/g, '');
762
+ break;
763
+ case 'background-repeat':
764
+ props.background_repeat = s.replace(/(.*?)(repeat|no-repeat)/g, '$2');
765
+ break;
766
+ case 'background-position':
767
+ /**
768
+ * position can be 4 different syntaxes so lets split the values
769
+ * @link https://developer.mozilla.org/en-US/docs/Web/CSS/background-position
770
+ */
771
+ let values = s.split(' ');
772
+ // for whatever reason Divi has these values inverted
773
+
774
+ switch( values.length ) {
775
+ case 1:
776
+ s = values[0];
777
+ break;
778
+ case 2:
779
+ s = `${values[1]}_${values[0]}`;
780
+ break;
781
+ case 3:
782
+ case 4:
783
+ s = `${values[2]}_${values[0]}`;
784
+ break;
785
+ }
786
+ props.background_position = s;
787
+ break;
788
+ case 'background-size':
789
+ props.background_size = s;
790
+ case 'color':
791
+ props.font_color = s;
792
+ break;
793
+ }
794
+ });
795
+ }
796
+
797
+ return props;
798
+ }
799
+
800
+ /**
801
+ * Generates a Divi module shortcode from the module name and element.
802
+ *
803
+ * @param {*} module
804
+ * @param {*} element
805
+ * @returns
806
+ */
644
807
  function generateModuleShortcode(module, element ){
645
808
  let content = '';
646
809
  let attrs = {};
@@ -767,7 +930,7 @@ function generateModuleShortcode(module, element ){
767
930
  }
768
931
 
769
932
  // if the element has an image, we want to add it to the attributes
770
- if( img ){
933
+ if( img && img.length ) {
771
934
  attrs.show_image = 'on';
772
935
  attrs.featured_image = img[0].getAttribute('src');
773
936
  }
@@ -801,61 +964,23 @@ function generateModuleShortcode(module, element ){
801
964
  break;
802
965
  }
803
966
  case 'heading': {
804
- let title_font = [];
805
- let title_text_color = '';
806
967
 
807
968
  attrs = {
808
969
  title: element.innerHTML.trim(),
809
- title_level: element.tagName.toLowerCase()
970
+ title_level: element.tagName.toLowerCase(),
971
+ ...bsToProp(element.classList, element?._rawAttrs?.style),
810
972
  };
811
973
 
812
- for( const c of element.classList.values() ) {
813
- // font weight classes
814
- if( c.startsWith('fw-') ) {
815
- let fontWeight = {
816
- lighter: '200',
817
- light: '300',
818
- normal: '400',
819
- medium: '500',
820
- semibold: '600',
821
- bold: '700',
822
- extrabold: '800',
823
- }
824
-
825
-
826
- let fontWeightValue = c.replace('fw-', '');
827
-
828
- title_font[1] = fontWeight[fontWeightValue] || fontWeight.normal;
829
-
830
- // remove the class from the class list
831
- element.classList.remove(c);
832
- // text classes
833
- }else if( c.startsWith('text-') ) {
834
- let fontColors = {
835
- 'light': 'ededef',
836
- 'dark': '3b3a48',
837
- 'white': 'fff',
838
- 'black': '000',
839
- }
840
-
841
- let fontColorValue = c.replace('text-', '');
842
-
843
- if( fontColors[fontColorValue] ) {
844
- title_text_color = `#${fontColors[fontColorValue]}`;
845
- }
846
-
847
-
848
- // remove the class from the class list
849
- element.classList.remove(c);
850
- }
974
+ // if there was a text orientation, it needs to be title_text_align.
975
+ if( attrs.text_orientation ) {
976
+ attrs.title_text_align = attrs.text_orientation;
977
+ delete attrs.text_orientation;
851
978
  }
852
979
 
853
- if( title_font.length ) {
854
- attrs.title_font = title_font.join('|');
855
- }
856
-
857
- if( title_text_color ) {
858
- attrs.title_text_color = title_text_color;
980
+ // if there was a font color, it needs to be title_text_color.
981
+ if( attrs.font_color ) {
982
+ attrs.title_text_color = attrs.font_color;
983
+ delete attrs.font_color;
859
984
  }
860
985
 
861
986
  content = element.innerHTML.trim()
@@ -896,8 +1021,26 @@ export default async function convertSite({
896
1021
  spinner.stop();
897
1022
 
898
1023
  let buildPath = path.join( appPath, 'build' );
899
- let siteData = fs.existsSync( appPath, 'caweb.json' ) ? JSON.parse( fs.readFileSync( path.join(appPath, 'caweb.json') ) ) : {};
1024
+ let favicon = path.join( appPath, 'build', 'favicon.ico' );
1025
+ let logo = fs.existsSync(path.join(appPath, 'build', 'media', 'logo.png') ) ?
1026
+ path.join(appPath, 'build', 'media', 'logo.png') :
1027
+ path.join(appPath, 'build', 'caweb', 'template', 'media', 'logo.png');
1028
+
1029
+ let siteData = fs.existsSync( appPath, 'caweb.json' ) ?
1030
+ JSON.parse( fs.readFileSync( path.join(appPath, 'caweb.json') ) ) :
1031
+ {
1032
+ site:{
1033
+ favicon,
1034
+ logo
1035
+ }
1036
+ };
1037
+
900
1038
  let pages = [];
1039
+
1040
+ // if site data has no site object, we want to create it
1041
+ if( ! siteData.site ) {
1042
+ siteData.site = {favicon, logo};
1043
+ }
901
1044
 
902
1045
  /**
903
1046
  * Return all .html files in the build directory
@@ -965,6 +1108,16 @@ export default async function convertSite({
965
1108
  }
966
1109
 
967
1110
  }
1111
+
1112
+ // if no favicon is set, we want to set the default favicon
1113
+ if( ! siteData?.favicon ){
1114
+ siteData.favicon = favicon;
1115
+ }
1116
+
1117
+ // if no logo is set, we want to set the default logo
1118
+ if( ! siteData?.logo ){
1119
+ siteData.logo = logo;
1120
+ }
968
1121
 
969
1122
  // add sync entry and pages to siteData
970
1123
  siteData = {
@@ -9,7 +9,7 @@ import chalk from 'chalk';
9
9
  /**
10
10
  * Internal dependencies
11
11
  */
12
- import { appPath, writeLine } from '../../lib/index.js';
12
+ import { appPath, writeLine, clearLine } from '../../lib/index.js';
13
13
 
14
14
  import {
15
15
  promptForGeneralInfo,
@@ -78,6 +78,7 @@ export default async function createSite({
78
78
  writeLine('You can also edit the configuration file later.', {color: 'cyan', prefix: 'i'});
79
79
 
80
80
  // populate the site data
81
+ // prompt for each section
81
82
  siteData = {
82
83
  ...await promptForGeneralInfo(siteTitle),
83
84
  header: await promptForHeader(),
@@ -93,10 +94,36 @@ export default async function createSite({
93
94
  JSON.stringify( {site:siteData}, null, 4 )
94
95
  );
95
96
 
97
+ // if there is no src directory, create it
98
+ if( ! fs.existsSync( path.join( appPath, 'src' ) ) ) {
99
+ fs.mkdirSync( path.join( appPath, 'src' ) );
100
+
101
+ // create the index.js file
102
+ fs.writeFileSync(
103
+ path.join( appPath, 'src', 'index.js' ),
104
+ `// This is the main entry point for your CAWebPublishing site.\n// You can start coding your site here.\n\nconsole.log('Welcome to your CAWebPublishing site!');`
105
+ );
106
+ }
107
+
108
+ // create content/pages directory if it doesn't exist
109
+ if( ! fs.existsSync( path.join( appPath, 'content', 'pages' ) ) ) {
110
+ fs.mkdirSync( path.join( appPath, 'content', 'pages' ), { recursive: true });
111
+ }
112
+
113
+ // create media directory if it doesn't exist
114
+ if( ! fs.existsSync( path.join( appPath, 'media' ) ) ) {
115
+ fs.mkdirSync( path.join( appPath, 'media' ) );
116
+ }
117
+
118
+ // clearLines for stored messages
119
+ // clearlines = (6 * 3) + 7
120
+ // (sectionCount * storedMessageLines) + Intro Lines
121
+ clearLine( (6 * 3) + 7 );
122
+
96
123
  writeLine('CAWebPublishing Site Creation Process Complete', {char: '#', borderColor: 'green'});
97
124
  writeLine('You can now start the site by running the following command:', {color: 'cyan', prefix: 'i'});
98
125
  writeLine(`npm run caweb serve`, {color: 'cyan', prefix: 'i'});
99
126
 
100
- spinner.start('CAWebPublishing Site Configuration file saved.');
101
-
127
+ spinner.text = `CAWebPublishing Site Configuration file saved at ${path.join( appPath, 'caweb.json' )}.`;
128
+
102
129
  };
@@ -28,8 +28,7 @@ const headerNav = [
28
28
  "sub": [
29
29
  {
30
30
  "label": "Accessibility",
31
- "url": "https://caweb.cdt.ca.gov/accessibility-2/",
32
- "description": "Accessibility"
31
+ "url": "https://caweb.cdt.ca.gov/accessibility-2/"
33
32
  },
34
33
  ]
35
34
  }
@@ -157,7 +156,7 @@ async function promptForHeader(){
157
156
  }
158
157
 
159
158
  // if navigation is selected, prompt for navigation configurations.
160
- if( features.includes('navigation') ){
159
+ if( features.includes('navigation') ){
161
160
 
162
161
  writeLine('Navigation', {bold: true});
163
162
  writeLine('The Navigation is a set of links that are displayed to navigate your site.', {color: 'cyan', prefix: 'i'});
@@ -184,8 +183,6 @@ async function promptForHeader(){
184
183
  clearLine((header.nav.length * 3) + 2);
185
184
  }
186
185
 
187
- // clear lines
188
- clearLine(6);
189
186
  writeLine('Header Configurations Stored.', {color: 'yellow', char: '#', borderColor: 'yellow'});
190
187
 
191
188
  // return the header object
@@ -230,7 +227,7 @@ async function promptForFooter(){
230
227
 
231
228
  // clear lines for each link plus the link stored message
232
229
  // + 5 for the header and info message
233
- clearLine((footer.nav.length * 3) + 5);
230
+ clearLine((footer.nav.length * 3) + 2);
234
231
 
235
232
  writeLine('Footer Configurations Stored.', {color: 'yellow', char: '#', borderColor: 'yellow'});
236
233
 
@@ -518,15 +515,37 @@ async function promptForGeneralInfo(title){
518
515
  writeLine('General Site Information', {color: 'magenta', char: '#', borderColor: 'magenta'});
519
516
 
520
517
  let info = {
521
- title: await input({
522
- message: 'What is the title of the site?',
523
- default: title,
524
- required: true,
525
- },
526
- {
527
- clearPromptOnDone: true,
528
- }
529
- ).catch(() => {process.exit(1);})
518
+ title: await input({
519
+ message: 'What is the title of the site?',
520
+ default: title,
521
+ required: true,
522
+ },
523
+ {
524
+ clearPromptOnDone: true,
525
+ }
526
+ ).catch(() => {
527
+ // clear lines.
528
+ clearLine(10);
529
+ process.exit(1);
530
+ }),
531
+ domain: await input({
532
+ message: 'What is the domain of the site?',
533
+ default: 'http://localhost',
534
+ validate: (input) => {
535
+ if( ! input.startsWith('http') && ! input.startsWith('https') ){
536
+ return 'Domain must start with http or https.';
537
+ }
538
+ return true;
539
+ }
540
+ },
541
+ {
542
+ clearPromptOnDone: true,
543
+ }
544
+ ).catch(() => {
545
+ // clear lines.
546
+ clearLine(10);
547
+ process.exit(1);
548
+ }),
530
549
  };
531
550
 
532
551
  // clear lines.