BoldGrid Easy SEO – Simple and Effective SEO - Version 1.6.0-rc.1

Version Description

Download this release

Release Info

Developer boldgrid
Plugin Icon 128x128 BoldGrid Easy SEO – Simple and Effective SEO
Version 1.6.0-rc.1
Comparing to
See all releases

Code changes from version 1.5.1 to 1.6.0-rc.1

assets/js/bgseo.js CHANGED
@@ -481,48 +481,69 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
481
 
482
  ( function( $, counter ) {
483
 
484
- $( function() {
485
-
486
- var $content = $( '#content' ),
487
- $count = $( '#wp-word-count' ).find( '.word-count' ),
488
- prevCount = 0,
489
- contentEditor,
490
- words;
491
 
492
- function update() {
493
- var text, count;
494
 
495
- if ( ! contentEditor || contentEditor.isHidden() ) {
496
- text = $content.val();
497
- } else {
498
- text = contentEditor.getContent( { format: 'raw' } );
499
- }
500
 
501
- count = counter.count( text );
502
- words = BOLDGRID.SEO.Words.words( text );
 
 
 
 
503
 
504
- if ( count !== prevCount ) {
505
- $content.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
506
- }
 
 
 
 
 
507
 
508
- prevCount = count;
509
- }
 
 
 
 
 
 
510
 
511
- $( document ).on( 'tinymce-editor-init', function( event, editor ) {
512
- if ( editor.id !== 'content' ) {
513
- return;
514
- }
 
 
 
 
515
 
516
- contentEditor = editor;
 
 
 
 
 
 
 
 
517
 
518
- editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
519
- } );
520
 
521
- $content.on( 'input keyup', _.debounce( update, 1000 ) );
 
 
522
 
523
- update();
 
 
 
524
 
525
- } );
526
 
527
  } )( jQuery, new wp.utils.WordCounter() );
528
 
@@ -643,10 +664,9 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
643
 
644
  'use strict';
645
 
646
- var self, report, api;
647
 
648
  api = BOLDGRID.SEO;
649
- report = api.report;
650
 
651
  /**
652
  * BoldGrid TinyMCE Analysis.
@@ -660,42 +680,32 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
660
  api.TinyMCE = {
661
 
662
  /**
663
- * Initialize TinyMCE Content.
664
  *
665
- * @since 1.3.1
 
 
666
  */
667
- init : function () {
668
- self.onloadContent();
669
- $( document ).ready( function() {
670
- self.editorChange();
671
- });
672
- },
673
 
674
  /**
675
- * Runs actions on window load to prepare for analysis.
676
  *
677
- * This method gets the current editor in use by the user on the
678
- * initial page load ( text editor or visual editor ), and also
679
- * is responsible for creating the iframe preview of the page/post
680
- * so we can get the raw html in use by the template/theme the user
681
- * has activated.
682
  *
683
- * @since 1.3.1
684
  */
685
- onloadContent: function() {
686
- var text,
687
- editor = $( '#content.wp-editor-area[aria-hidden=false]' );
688
 
689
- $( window ).on( 'load bgseo-media-inserted', function() {
690
- var content = self.getContent();
691
-
692
- // Get rendered page content from frontend site.
693
- self.getRenderedContent();
694
-
695
- // Trigger the content analysis for the tinyMCE content.
696
- _.defer( function() {
697
- $( '#content' ).trigger( 'bgseo-analysis', [content] );
698
- });
699
  });
700
  },
701
 
@@ -708,11 +718,11 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
708
  */
709
  getContent : function() {
710
  var content;
711
- // Get the content of the visual editor or text editor that's present.
712
  if ( tinymce.ActiveEditor ) {
713
  content = tinyMCE.get( wpActiveEditor ).getContent();
714
  } else {
715
- content = $( '#content' ).val();
716
  // Remove newlines and carriage returns.
717
  content = content.replace( /\r?\n|\r/g, '' );
718
  }
@@ -722,64 +732,33 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
722
  // Stores raw and stripped down versions of the content for analysis.
723
  content = {
724
  'raw': rawContent,
725
- 'text': self.stripper( content.toLowerCase() ),
726
  };
727
 
728
  return content;
729
  },
730
 
 
731
  /**
732
- * Only ajax for preview if permalink is available. This only
733
- * impacts "New" page and posts. To counter
734
- * this we will disable the checks made until the content has had
735
- * a chance to be updated. We will store the found headings minus
736
- * the initial found headings in the content, so we know what the
737
- * template has in use on the actual rendered page.
738
  *
739
- * @since 1.3.1
740
  *
741
- * @returns null No return.
742
  */
743
- getRenderedContent : function() {
744
- var renderedContent, preview;
745
-
746
- // Get the preview url from WordPress.
747
- preview = $( '#preview-action > .preview.button' ).attr( 'href' );
748
-
749
- if ( $( '#sample-permalink' ).length ) {
750
- // Only run this once after the initial iframe has loaded to get current template stats.
751
- $.get( preview, function( renderedTemplate ) {
752
- var headings, h1, h2, $rendered;
753
-
754
- // The rendered page content.
755
- $rendered = $( renderedTemplate );
756
-
757
- // H1's that appear in rendered content.
758
- h1 = $rendered.find( 'h1' );
759
- // HS's that appear in rendered content.
760
- h2 = $rendered.find( 'h2' );
761
-
762
- // The rendered content stats.
763
- renderedContent = {
764
- h1Count : h1.length - report.rawstatistics.h1Count,
765
- h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
766
- return ! _.findWhere( report.rawstatistics.h1text, obj );
767
- }),
768
- h2Count : h2.length - report.rawstatistics.h2Count,
769
- h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
770
- return ! _.findWhere( report.rawstatistics.h2text, obj );
771
- }),
772
- };
773
-
774
- // Add the rendered stats to our report for use later.
775
- _.extend( report, { rendered : renderedContent } );
776
-
777
- // Trigger the SEO report to rebuild in the template after initial stats are created.
778
- $( '#content' ).trigger( 'bgseo-analysis', [ self.getContent() ] );
779
 
780
- }, 'html' );
 
 
 
781
  }
 
 
782
  },
 
783
  /**
784
  * Listens for changes made in the text editor mode.
785
  *
@@ -813,6 +792,17 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
813
  return text;
814
  },
815
 
 
 
 
 
 
 
 
 
 
 
 
816
  /**
817
  * Checks which editor is the active editor.
818
  *
@@ -833,7 +823,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
833
  text = tinyMCE.get( wpActiveEditor ).getContent();
834
  break;
835
  case 'content' :
836
- text = $( '#content' ).val();
837
  text = text.replace( /\r?\n|\r/g, '' );
838
  break;
839
  }
@@ -843,32 +833,32 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
843
 
844
  text = {
845
  'raw': rawText,
846
- 'text': self.stripper( text.toLowerCase() ),
847
  };
848
 
849
  // Trigger the text analysis for report.
850
- $( '#content' ).trigger( 'bgseo-analysis', [text] );
851
  },
852
 
853
  /**
854
- * Strips out unwanted html.
855
- *
856
- * This is helpful in removing the remaining traces of HTML
857
- * that is sometimes leftover to form our clean text output and
858
- * run our text analysis on.
859
- *
860
- * @since 1.3.1
861
  *
862
- * @returns {string} The content with any remaining html removed.
863
  */
864
- stripper: function( html ) {
865
- var tmp;
866
 
867
- tmp = document.implementation.createHTMLDocument( 'New' ).body;
868
- tmp.innerHTML = html;
 
 
 
 
 
 
 
 
869
 
870
- return tmp.textContent || tmp.innerText || " ";
871
- },
872
  };
873
 
874
  self = api.TinyMCE;
@@ -1289,6 +1279,287 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1289
 
1290
  })( jQuery );
1291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
  ( function ( $ ) {
1293
 
1294
  'use strict';
@@ -1347,7 +1618,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1347
  _checkbox : function() {
1348
  // Listen for changes to input value.
1349
  self.settings.displayTitle.on( 'change', _.debounce( function() {
1350
- $( this ).trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
1351
  }, 1000 ) );
1352
  },
1353
 
@@ -1519,10 +1790,11 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1519
  },
1520
  };
1521
 
1522
- content = api.TinyMCE.getContent();
 
1523
 
1524
- h1s = $( content.raw ).find( 'h1' );
1525
- h2s = $( content.raw ).find( 'h2' );
1526
 
1527
  // If no h1s or h2s are found return the defaults.
1528
  if ( ! h1s.length && ! h2s.length ) return headings;
@@ -1593,7 +1865,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1593
  getSettings : function() {
1594
  self.settings = {
1595
  keyword : $( '#bgseo-custom-keyword' ),
1596
- content : $( '#content' ),
1597
  };
1598
  },
1599
 
@@ -1700,7 +1972,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1700
  keyword = keyword.toLowerCase();
1701
 
1702
  keywordCount = self.keywordCount( content, keyword );
1703
- wordCount = api.Report.getWordCount();
1704
  // Get the density.
1705
  result = ( ( keywordCount / wordCount ) * 100 );
1706
  // Round it off.
@@ -1818,7 +2090,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1818
  */
1819
  getKeyword : function() {
1820
  var customKeyword,
1821
- content = api.TinyMCE.getContent();
1822
 
1823
  if ( self.getCustomKeyword().length ) {
1824
  customKeyword = self.getCustomKeyword();
@@ -1850,9 +2122,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
1850
  var count;
1851
 
1852
  if ( _.isUndefined( markup ) ) {
1853
- markup = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ?
1854
- api.Words.words( self.settings.content.val() ) :
1855
- api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
1856
  }
1857
 
1858
  count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
@@ -2263,7 +2533,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2263
  report = api.report;
2264
 
2265
  /**
2266
- * BoldGrid TinyMCE Analysis.
2267
  *
2268
  * This is responsible for generating the actual reports
2269
  * displayed within the BoldGrid SEO Dashboard when the user
@@ -2274,7 +2544,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2274
  api.Report = {
2275
 
2276
  /**
2277
- * Initialize TinyMCE Content.
2278
  *
2279
  * @since 1.3.1
2280
  */
@@ -2300,16 +2570,10 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2300
  getSettings : function() {
2301
  self.settings = {
2302
  title : $( '#boldgrid-seo-field-meta_title' ),
2303
- description : $( '#boldgrid-seo-field-meta_description' ),
2304
- wordCounter : $( '#wp-word-count .word-count' ),
2305
- content : $( '#content' ),
2306
  };
2307
  },
2308
 
2309
- getWordCount : function() {
2310
- return Number( self.settings.wordCounter.text() );
2311
- },
2312
-
2313
  /**
2314
  * Generate the Report based on analysis done.
2315
  *
@@ -2341,10 +2605,10 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2341
  if ( eventInfo ) {
2342
  // Listen for changes to raw HTML in editor.
2343
  if ( eventInfo.raw ) {
2344
- var raws = eventInfo.raw;
2345
 
2346
- var h1 = $( raws ).find( 'h1' ),
2347
- h2 = $( raws ).find( 'h2' ),
2348
  headings = {};
2349
 
2350
  headings = {
@@ -2352,7 +2616,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2352
  h1text : api.Headings.getHeadingText( h1 ),
2353
  h2Count : h2.length,
2354
  h2text : api.Headings.getHeadingText( h2 ),
2355
- imageCount: $( raws ).find( 'img' ).length,
2356
  };
2357
  // Set the heading counts and image count found in new content update.
2358
  _( report.rawstatistics ).extend( headings );
@@ -2371,7 +2635,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2371
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2372
  },
2373
  keywordContent : {
2374
- lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
2375
  },
2376
  keywordHeadings : {
2377
  length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
@@ -2385,7 +2649,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2385
  if ( eventInfo.text ) {
2386
  var kw, headingCount = api.Headings.getRealHeadingCount(),
2387
  content = eventInfo.text,
2388
- raw = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ? api.Words.words( self.settings.content.val() ) : api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
2389
 
2390
  // Get length of title field.
2391
  titleLength = self.settings.title.val().length;
@@ -2443,7 +2707,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2443
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2444
  },
2445
  keywordContent : {
2446
- lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
2447
  },
2448
  keywordHeadings : {
2449
  length : api.Headings.keywords( headingCount ),
@@ -2455,8 +2719,8 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2455
  },
2456
  headings : headingCount,
2457
  wordCount : {
2458
- length : self.getWordCount(),
2459
- lengthScore : api.ContentAnalysis.seoContentLengthScore( self.getWordCount() ),
2460
  },
2461
  sectionScore: {},
2462
  sectionStatus: {},
@@ -2485,7 +2749,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2485
  _( report.bgseo_keywords.keywordTitle ).extend({
2486
  lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
2487
  });
2488
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
2489
  }
2490
 
2491
  // Listen to changes to the SEO Description and update report.
@@ -2503,7 +2767,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2503
  _( report.bgseo_keywords.keywordDescription ).extend({
2504
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2505
  });
2506
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
2507
  }
2508
 
2509
  // Listen for changes to noindex/index and update report.
@@ -2511,7 +2775,7 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2511
  _( report.bgseo_visibility.robotIndex ).extend({
2512
  lengthScore : eventInfo.robotIndex,
2513
  });
2514
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
2515
  }
2516
 
2517
  // Listen for changes to nofollow/follow and update report.
@@ -2519,12 +2783,12 @@ BOLDGRID.SEO = BOLDGRID.SEO || {};
2519
  _( report.bgseo_visibility.robotFollow ).extend({
2520
  lengthScore : eventInfo.robotFollow,
2521
  });
2522
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
2523
  }
2524
  }
2525
 
2526
  // Send the final analysis to display the report.
2527
- self.settings.content.trigger( 'bgseo-report', [ report ] );
2528
  });
2529
  },
2530
 
481
 
482
  ( function( $, counter ) {
483
 
484
+ 'use strict';
 
 
 
 
 
 
485
 
486
+ var self, api;
 
487
 
488
+ api = BOLDGRID.SEO;
 
 
 
 
489
 
490
+ /**
491
+ * Handle tracking of wordcount.
492
+ *
493
+ * @since 1.6.0
494
+ */
495
+ api.Wordcount = {
496
 
497
+ /**
498
+ * Number of words in the content.
499
+ *
500
+ * @since 1.6.0
501
+ *
502
+ * @type {Number}
503
+ */
504
+ count: 0,
505
 
506
+ /**
507
+ * List of words on the page.
508
+ *
509
+ * @since 1.6.0
510
+ *
511
+ * @type {array}
512
+ */
513
+ words: [],
514
 
515
+ /**
516
+ * When the page loads, run the update methods.
517
+ *
518
+ * @since 1.6.0
519
+ */
520
+ init : function () {
521
+ $( self.update );
522
+ },
523
 
524
+ /**
525
+ * Update this classes word count metrics.
526
+ *
527
+ * @since 1.6.0
528
+ */
529
+ update : function () {
530
+ var count,
531
+ words,
532
+ text = api.Editor.ui.getRawText();
533
 
534
+ count = counter.count( text );
535
+ words = BOLDGRID.SEO.Words.words( text );
536
 
537
+ if ( count !== self.count ) {
538
+ api.Editor.element.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
539
+ }
540
 
541
+ self.words = words;
542
+ self.count = count;
543
+ }
544
+ };
545
 
546
+ self = api.Wordcount;
547
 
548
  } )( jQuery, new wp.utils.WordCounter() );
549
 
664
 
665
  'use strict';
666
 
667
+ var self, api;
668
 
669
  api = BOLDGRID.SEO;
 
670
 
671
  /**
672
  * BoldGrid TinyMCE Analysis.
680
  api.TinyMCE = {
681
 
682
  /**
683
+ * Selector to find editor id.
684
  *
685
+ * @since 1.6.0
686
+ *
687
+ * @type {String}
688
  */
689
+ selector : '#content',
 
 
 
 
 
690
 
691
  /**
692
+ * Selector to find preview button.
693
  *
694
+ * @since 1.6.0
 
 
 
 
695
  *
696
+ * @type {String}
697
  */
698
+ previewSelector : '#preview-action > .preview.button',
 
 
699
 
700
+ /**
701
+ * Initialize TinyMCE Content.
702
+ *
703
+ * @since 1.3.1
704
+ */
705
+ setup : function () {
706
+ $( document ).ready( function() {
707
+ self._setupWordCount();
708
+ self.editorChange();
 
709
  });
710
  },
711
 
718
  */
719
  getContent : function() {
720
  var content;
721
+
722
  if ( tinymce.ActiveEditor ) {
723
  content = tinyMCE.get( wpActiveEditor ).getContent();
724
  } else {
725
+ content = api.Editor.element.val();
726
  // Remove newlines and carriage returns.
727
  content = content.replace( /\r?\n|\r/g, '' );
728
  }
732
  // Stores raw and stripped down versions of the content for analysis.
733
  content = {
734
  'raw': rawContent,
735
+ 'text': api.Editor.stripper( content.toLowerCase() ),
736
  };
737
 
738
  return content;
739
  },
740
 
741
+
742
  /**
743
+ * Get the raw text from the editor.
 
 
 
 
 
744
  *
745
+ * @since 1.6.0
746
  *
747
+ * @return {string} Editor content.
748
  */
749
+ getRawText: function() {
750
+ var text,
751
+ contentEditor = tinyMCE.get( wpActiveEditor );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
 
753
+ if ( ! contentEditor || contentEditor.isHidden() ) {
754
+ text = api.Editor.element.val();
755
+ } else {
756
+ text = contentEditor.getContent( { format: 'raw' } );
757
  }
758
+
759
+ return text;
760
  },
761
+
762
  /**
763
  * Listens for changes made in the text editor mode.
764
  *
792
  return text;
793
  },
794
 
795
+ /**
796
+ * Is this a new Post?
797
+ *
798
+ * @since 1.6.0
799
+ *
800
+ * @return {boolean} Is this post-new.php?
801
+ */
802
+ isNewPost : function () {
803
+ return ! $( '#sample-permalink' ).length;
804
+ },
805
+
806
  /**
807
  * Checks which editor is the active editor.
808
  *
823
  text = tinyMCE.get( wpActiveEditor ).getContent();
824
  break;
825
  case 'content' :
826
+ text = api.Editor.element.val();
827
  text = text.replace( /\r?\n|\r/g, '' );
828
  break;
829
  }
833
 
834
  text = {
835
  'raw': rawText,
836
+ 'text': api.Editor.stripper( text.toLowerCase() ),
837
  };
838
 
839
  // Trigger the text analysis for report.
840
+ api.Editor.element.trigger( 'bgseo-analysis', [text] );
841
  },
842
 
843
  /**
844
+ * Bind events to the editor input and update the wordcount class.
 
 
 
 
 
 
845
  *
846
+ * @since 1.6.0
847
  */
848
+ _setupWordCount : function() {
849
+ var debouncedCb = _.debounce( api.Wordcount.update, 1000 );
850
 
851
+ $( document ).on( 'tinymce-editor-init', function( event, editor ) {
852
+ if ( editor.id !== 'content' ) {
853
+ return;
854
+ }
855
+
856
+ editor.on( 'AddUndo keyup', debouncedCb );
857
+ } );
858
+
859
+ api.Editor.element.on( 'input keyup', debouncedCb );
860
+ }
861
 
 
 
862
  };
863
 
864
  self = api.TinyMCE;
1279
 
1280
  })( jQuery );
1281
 
1282
+ ( function ( $ ) {
1283
+
1284
+ 'use strict';
1285
+
1286
+ var self, api, report;
1287
+
1288
+ api = BOLDGRID.SEO;
1289
+ report = api.report;
1290
+
1291
+ /**
1292
+ * BoldGrid Editor Interface.
1293
+ *
1294
+ * This class allows us to control which editor interface functions will run.
1295
+ * On first load the it runs the setup method of whichever ui is currently loaded.
1296
+ * It then assigns that ui to this classes ui variable for cross api use.
1297
+ *
1298
+ * @since 1.6.0
1299
+ */
1300
+ api.Editor = {
1301
+
1302
+ /**
1303
+ * Interface loaded.
1304
+ *
1305
+ * @since 1.6.0
1306
+ *
1307
+ * @type {object} seo.tinymce or seo-Gutenberg
1308
+ */
1309
+ ui: null,
1310
+
1311
+ /**
1312
+ * WP Element to use to trigger events.
1313
+ *
1314
+ * @since 1.6.0
1315
+ *
1316
+ * @type {$} Editor jQuery Element.
1317
+ */
1318
+ element: null,
1319
+
1320
+ /**
1321
+ * Setup the correct editor interface.
1322
+ *
1323
+ * @since 1.3.1
1324
+ */
1325
+ init : function () {
1326
+ self.ui = wp.data ? api.Gutenberg : api.TinyMCE;
1327
+ self.element = $( self.ui.selector );
1328
+ self.ui.setup();
1329
+ self.onloadContent();
1330
+ },
1331
+
1332
+ /**
1333
+ * Runs actions on window load to prepare for analysis.
1334
+ *
1335
+ * This method gets the current editor in use by the user on the
1336
+ * initial page load ( text editor or visual editor ), and also
1337
+ * is responsible for creating the iframe preview of the page/post
1338
+ * so we can get the raw html in use by the template/theme the user
1339
+ * has activated.
1340
+ *
1341
+ * @since 1.3.1
1342
+ */
1343
+ onloadContent : function() {
1344
+ var text,
1345
+ editor = $( '#content.wp-editor-area[aria-hidden=false]' );
1346
+
1347
+ $( window ).on( 'load bgseo-media-inserted', function() {
1348
+ var content = self.ui.getContent();
1349
+
1350
+ // Get rendered page content from frontend site.
1351
+ self.getRenderedContent();
1352
+
1353
+ // Trigger the content analysis for the content.
1354
+ _.defer( function() {
1355
+ self.element.trigger( 'bgseo-analysis', [content] );
1356
+ } );
1357
+ } );
1358
+ },
1359
+
1360
+ /**
1361
+ * Only ajax for preview if permalink is available. This only
1362
+ * impacts "New" page and posts. To counter
1363
+ * this we will disable the checks made until the content has had
1364
+ * a chance to be updated. We will store the found headings minus
1365
+ * the initial found headings in the content, so we know what the
1366
+ * template has in use on the actual rendered page.
1367
+ *
1368
+ * @since 1.3.1
1369
+ *
1370
+ * @returns null No return.
1371
+ */
1372
+ getRenderedContent : function() {
1373
+ var renderedContent, preview;
1374
+
1375
+ // Get the preview url from WordPress.
1376
+ preview = $( api.Editor.ui.previewSelector ).attr( 'href' );
1377
+
1378
+ if ( ! api.Editor.ui.isNewPost() ) {
1379
+ // Only run this once after the initial iframe has loaded to get current template stats.
1380
+ $.get( preview, function( renderedTemplate ) {
1381
+ var headings, h1, h2, $rendered;
1382
+
1383
+ // The rendered page content.
1384
+ $rendered = $( renderedTemplate );
1385
+
1386
+ // H1's that appear in rendered content.
1387
+ h1 = $rendered.find( 'h1' );
1388
+ // HS's that appear in rendered content.
1389
+ h2 = $rendered.find( 'h2' );
1390
+
1391
+ // The rendered content stats.
1392
+ renderedContent = {
1393
+ h1Count : h1.length - report.rawstatistics.h1Count,
1394
+ h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
1395
+ return ! _.findWhere( report.rawstatistics.h1text, obj );
1396
+ }),
1397
+ h2Count : h2.length - report.rawstatistics.h2Count,
1398
+ h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
1399
+ return ! _.findWhere( report.rawstatistics.h2text, obj );
1400
+ }),
1401
+ };
1402
+
1403
+ // Add the rendered stats to our report for use later.
1404
+ _.extend( report, { rendered : renderedContent } );
1405
+
1406
+ // Trigger the SEO report to rebuild in the template after initial stats are created.
1407
+ self.triggerAnalysis();
1408
+
1409
+ }, 'html' );
1410
+ }
1411
+ },
1412
+
1413
+ /**
1414
+ * Strips out unwanted html.
1415
+ *
1416
+ * This is helpful in removing the remaining traces of HTML
1417
+ * that is sometimes leftover to form our clean text output and
1418
+ * run our text analysis on.
1419
+ *
1420
+ * @since 1.3.1
1421
+ *
1422
+ * @returns {string} The content with any remaining html removed.
1423
+ */
1424
+ stripper : function( html ) {
1425
+ var tmp;
1426
+
1427
+ tmp = document.implementation.createHTMLDocument( 'New' ).body;
1428
+ tmp.innerHTML = html;
1429
+
1430
+ return tmp.textContent || tmp.innerText || " ";
1431
+ },
1432
+
1433
+ /**
1434
+ * Fire an event that will force, analysis to run.
1435
+ *
1436
+ * @since 1.6.0
1437
+ */
1438
+ triggerAnalysis: function() {
1439
+ self.element.trigger( 'bgseo-analysis', [ self.ui.getContent() ] );
1440
+ }
1441
+ };
1442
+
1443
+ self = api.Editor;
1444
+
1445
+ })( jQuery );
1446
+
1447
+ ( function ( $ ) {
1448
+
1449
+ 'use strict';
1450
+
1451
+ var self, api;
1452
+
1453
+ api = BOLDGRID.SEO;
1454
+
1455
+ /**
1456
+ * BoldGrid Gutenberg Analysis.
1457
+ *
1458
+ * This is responsible for generating the actual reports
1459
+ * displayed within the BoldGrid SEO Dashboard when the user
1460
+ * is on a page or a post.
1461
+ *
1462
+ * @since 1.3.1
1463
+ */
1464
+ api.Gutenberg = {
1465
+
1466
+ /**
1467
+ * Selector to find editor id.
1468
+ *
1469
+ * This is only used to trigger events. No dom content queries in Gutenberg context.
1470
+ *
1471
+ * @since 1.6.0
1472
+ *
1473
+ * @type {String}
1474
+ */
1475
+ selector : '#editor',
1476
+
1477
+ /**
1478
+ * Selector to find preview button.
1479
+ *
1480
+ * @since 1.6.0
1481
+ *
1482
+ * @type {String}
1483
+ */
1484
+ previewSelector : '.editor-post-preview',
1485
+
1486
+ /**
1487
+ * Initialize Content.
1488
+ *
1489
+ * @since 1.6.0
1490
+ */
1491
+ setup : function () {
1492
+ $( api.Editor.triggerAnalysis );
1493
+ self._setupEditorChange();
1494
+ },
1495
+
1496
+ /**
1497
+ * Are we currently on a new post?
1498
+ *
1499
+ * @since 1.6.0
1500
+ *
1501
+ * @return {boolean} Is this a post-new.php?
1502
+ */
1503
+ isNewPost : function() {
1504
+ return wp.data.select( 'core/editor' ).isCleanNewPost();
1505
+ },
1506
+
1507
+ /**
1508
+ * Gets the content from the editor for analysis.
1509
+ *
1510
+ * @since 1.6.0
1511
+ *
1512
+ * @returns {Object} content Contains content in raw and text formats.
1513
+ */
1514
+ getContent : function() {
1515
+ var content = self.getRawText();
1516
+
1517
+ // Stores raw and stripped down versions of the content for analysis.
1518
+ content = {
1519
+ 'raw': content,
1520
+ 'text': api.Editor.stripper( content.toLowerCase() ),
1521
+ };
1522
+
1523
+ return content;
1524
+ },
1525
+
1526
+ /**
1527
+ * Get the raw text from the editor.
1528
+ *
1529
+ * @since 1.6.0
1530
+ *
1531
+ * @return {string} Editor content.
1532
+ */
1533
+ getRawText : function () {
1534
+ return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'content' );
1535
+ },
1536
+
1537
+ /**
1538
+ * Listens for changes made in the text editor mode.
1539
+ *
1540
+ * @since 1.6.0
1541
+ *
1542
+ * @returns {string} text The new content to perform analysis on.
1543
+ */
1544
+ _setupEditorChange: function() {
1545
+ var latestContent = '';
1546
+
1547
+ wp.data.subscribe( _.debounce( function () {
1548
+
1549
+ // Make sure content is different before running analysis.
1550
+ if ( self.getRawText() !== latestContent ) {
1551
+ api.Wordcount.update();
1552
+ api.Editor.triggerAnalysis();
1553
+ latestContent = self.getRawText();
1554
+ }
1555
+ }, 1000 ) );
1556
+ }
1557
+ };
1558
+
1559
+ self = api.Gutenberg;
1560
+
1561
+ })( jQuery );
1562
+
1563
  ( function ( $ ) {
1564
 
1565
  'use strict';
1618
  _checkbox : function() {
1619
  // Listen for changes to input value.
1620
  self.settings.displayTitle.on( 'change', _.debounce( function() {
1621
+ $( this ).trigger( 'bgseo-analysis', [ api.Editor.ui.getContent() ] );
1622
  }, 1000 ) );
1623
  },
1624
 
1790
  },
1791
  };
1792
 
1793
+ content = api.Editor.ui.getContent();
1794
+ content = $( '<div>' + content.raw + '</div>' );
1795
 
1796
+ h1s = content.find( 'h1' );
1797
+ h2s = content.find( 'h2' );
1798
 
1799
  // If no h1s or h2s are found return the defaults.
1800
  if ( ! h1s.length && ! h2s.length ) return headings;
1865
  getSettings : function() {
1866
  self.settings = {
1867
  keyword : $( '#bgseo-custom-keyword' ),
1868
+ content : api.Editor.element,
1869
  };
1870
  },
1871
 
1972
  keyword = keyword.toLowerCase();
1973
 
1974
  keywordCount = self.keywordCount( content, keyword );
1975
+ wordCount = api.Wordcount.count;
1976
  // Get the density.
1977
  result = ( ( keywordCount / wordCount ) * 100 );
1978
  // Round it off.
2090
  */
2091
  getKeyword : function() {
2092
  var customKeyword,
2093
+ content = api.Editor.ui.getRawText();
2094
 
2095
  if ( self.getCustomKeyword().length ) {
2096
  customKeyword = self.getCustomKeyword();
2122
  var count;
2123
 
2124
  if ( _.isUndefined( markup ) ) {
2125
+ markup = api.Words.words( api.Editor.ui.getRawText() );
 
 
2126
  }
2127
 
2128
  count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
2533
  report = api.report;
2534
 
2535
  /**
2536
+ * BoldGrid Editor Content Analysis.
2537
  *
2538
  * This is responsible for generating the actual reports
2539
  * displayed within the BoldGrid SEO Dashboard when the user
2544
  api.Report = {
2545
 
2546
  /**
2547
+ * Initialize Content.
2548
  *
2549
  * @since 1.3.1
2550
  */
2570
  getSettings : function() {
2571
  self.settings = {
2572
  title : $( '#boldgrid-seo-field-meta_title' ),
2573
+ description : $( '#boldgrid-seo-field-meta_description' )
 
 
2574
  };
2575
  },
2576
 
 
 
 
 
2577
  /**
2578
  * Generate the Report based on analysis done.
2579
  *
2605
  if ( eventInfo ) {
2606
  // Listen for changes to raw HTML in editor.
2607
  if ( eventInfo.raw ) {
2608
+ var $raws = $( '<div>' + eventInfo.raw + '</div>' );
2609
 
2610
+ var h1 = $raws.find( 'h1' ),
2611
+ h2 = $raws.find( 'h2' ),
2612
  headings = {};
2613
 
2614
  headings = {
2616
  h1text : api.Headings.getHeadingText( h1 ),
2617
  h2Count : h2.length,
2618
  h2text : api.Headings.getHeadingText( h2 ),
2619
+ imageCount: $raws.find( 'img' ).length,
2620
  };
2621
  // Set the heading counts and image count found in new content update.
2622
  _( report.rawstatistics ).extend( headings );
2635
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2636
  },
2637
  keywordContent : {
2638
+ lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
2639
  },
2640
  keywordHeadings : {
2641
  length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
2649
  if ( eventInfo.text ) {
2650
  var kw, headingCount = api.Headings.getRealHeadingCount(),
2651
  content = eventInfo.text,
2652
+ raw = api.Editor.ui.getRawText();
2653
 
2654
  // Get length of title field.
2655
  titleLength = self.settings.title.val().length;
2707
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2708
  },
2709
  keywordContent : {
2710
+ lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
2711
  },
2712
  keywordHeadings : {
2713
  length : api.Headings.keywords( headingCount ),
2719
  },
2720
  headings : headingCount,
2721
  wordCount : {
2722
+ length : api.Wordcount.count,
2723
+ lengthScore : api.ContentAnalysis.seoContentLengthScore( api.Wordcount.count ),
2724
  },
2725
  sectionScore: {},
2726
  sectionStatus: {},
2749
  _( report.bgseo_keywords.keywordTitle ).extend({
2750
  lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
2751
  });
2752
+ api.Editor.triggerAnalysis();
2753
  }
2754
 
2755
  // Listen to changes to the SEO Description and update report.
2767
  _( report.bgseo_keywords.keywordDescription ).extend({
2768
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
2769
  });
2770
+ api.Editor.triggerAnalysis();
2771
  }
2772
 
2773
  // Listen for changes to noindex/index and update report.
2775
  _( report.bgseo_visibility.robotIndex ).extend({
2776
  lengthScore : eventInfo.robotIndex,
2777
  });
2778
+ api.Editor.triggerAnalysis();
2779
  }
2780
 
2781
  // Listen for changes to nofollow/follow and update report.
2783
  _( report.bgseo_visibility.robotFollow ).extend({
2784
  lengthScore : eventInfo.robotFollow,
2785
  });
2786
+ api.Editor.triggerAnalysis();
2787
  }
2788
  }
2789
 
2790
  // Send the final analysis to display the report.
2791
+ api.Editor.element.trigger( 'bgseo-report', [ report ] );
2792
  });
2793
  },
2794
 
assets/js/bgseo.min.js CHANGED
@@ -1 +1 @@
1
- var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO={report:{bgseo_visibility:{},bgseo_keywords:{},bgseo_meta:{},rawstatistics:{},textstatistics:{}}},function(e){"use strict";butterbean.views.register_control("dashboard",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-dashboard"),_.bindAll(this,"render"),this.model.bind("change",this.render)},results:function(e){var t={};return _.each(e,function(e){_.extend(t,e)}),t},setAnalysis:function(e,t){var n,o=this.model.get("section"),s=_.pick(t,o);this.sectionReport=this.results(s),this.model.set("analysis",this.sectionReport),_(t).each(function(e){_.isUndefined(e.sectionScore)||(n=BOLDGRID.SEO.Sections.score(e),_(e).extend(n))}),_(t.bgseo_keywords).extend({overview:{score:BOLDGRID.SEO.Dashboard.overviewScore(t)}}),_(t.bgseo_keywords.overview).extend({status:BOLDGRID.SEO.Dashboard.overviewStatus(t.bgseo_keywords.overview.score)}),BOLDGRID.SEO.Sections.navHighlight(t),BOLDGRID.SEO.Sections.overviewStatus(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery),function(e){"use strict";butterbean.views.register_control("keywords",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-keywords"),_.bindAll(this,"render"),this.model.bind("change",this.render)},setAnalysis:function(e,t){this.model.set(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Util={init:function(){_.mixin({modifyObject:function(e,t){return _.object(_.map(e,function(e,n){return[n,t(e)]}))},pickDeep:function(e){var t={},n=Array.prototype.concat.apply(Array.prototype,Array.prototype.slice.call(arguments,1));return this.each(n,function(n){var o=n.split(".");n=o.shift(),n in e&&(o.length>0?t[n]?_.extend(t[n],_.pickDeep(e[n],o.join("."))):t[n]=_.pickDeep(e[n],o.join(".")):t[n]=e[n])}),t}}),Number.prototype.isBetween||(Number.prototype.isBetween=function(e,t){_.isUndefined(e)&&(e=0),_.isUndefined(t)&&(t=0);var n=Math.max(e,t),o=Math.min(e,t);return this>o&&this<n}),Number.prototype.rounded||(Number.prototype.rounded=function(e){_.isUndefined(e)&&(e=0);var t=Math.pow(10,e),n=Math.round(this*t)/t;return n}),String.prototype.printf||(String.prototype.printf=function(){for(var e=this,t=0;/%s/.test(e);)e=e.replace("%s",arguments[t++]);return e}),String.prototype.occurences||(String.prototype.occurences=function(e,t){if(e+="",e.length<=0)return this.length+1;for(var n=0,o=0,s=t?1:e.length;;){if(o=this.indexOf(e,o),!(o>=0))break;++n,o+=s}return n})}},t=o.Util}(jQuery),function(){"use strict";var e,t;t=BOLDGRID.SEO,t.Words={init:function(t){var n,o;if(t)for(n in t)t.hasOwnProperty(n)&&(e.settings[n]=t[n]);o=e.settings.l10n.shortcodes,o&&o.length&&(e.settings.shortcodesRegExp=new RegExp("\\[\\/?(?:"+o.join("|")+")[^\\]]*?\\]","g"))},settings:{HTMLRegExp:/<\/?[a-z][^>]*?>/gi,HTMLcommentRegExp:/<!--[\s\S]*?-->/g,spaceRegExp:/&nbsp;|&#160;/gi,HTMLEntityRegExp:/&\S+?;/g,connectorRegExp:/--|\u2014/g,removeRegExp:new RegExp(["[","!-@[-`{-~","€-¿×÷"," -⯿","⸀-⹿","]"].join(""),"g"),astralRegExp:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,wordsRegExp:/.+?\s+/g,characters_excluding_spacesRegExp:/\S/g,characters_including_spacesRegExp:/[^\f\n\r\t\v\u00AD\u2028\u2029]/g,l10n:window.wordCountL10n||{}},words:function(t,n){var o=0;return n=n||e.settings.l10n.type,"characters_excluding_spaces"!==n&&"characters_including_spaces"!==n&&(n="words"),t&&(t+="\n",t=t.replace(e.settings.HTMLRegExp,"\n"),t=t.replace(e.settings.HTMLcommentRegExp,""),e.settings.shortcodesRegExp&&(t=t.replace(e.settings.shortcodesRegExp,"\n")),t=t.replace(e.settings.spaceRegExp," "),"words"===n?(t=t.replace(e.settings.HTMLEntityRegExp,""),t=t.replace(e.settings.connectorRegExp," "),t=t.replace(e.settings.removeRegExp,"")):(t=t.replace(e.settings.HTMLEntityRegExp,"a"),t=t.replace(e.settings.astralRegExp,"a")),t=t.match(e.settings[n+"RegExp"]),t&&(o=t)),o}},e=t.Words}(),function(e,t){e(function(){function n(){var e,n;e=!o||o.isHidden()?r.val():o.getContent({format:"raw"}),n=t.count(e),s=BOLDGRID.SEO.Words.words(e),n!==i&&r.trigger("bgseo-analysis",[{words:s,count:n}]),i=n}var o,s,r=e("#content"),i=(e("#wp-word-count").find(".word-count"),0);e(document).on("tinymce-editor-init",function(e,t){"content"===t.id&&(o=t,t.on("nodechange keyup",_.debounce(n,1e3)))}),r.on("input keyup",_.debounce(n,1e3)),n()})}(jQuery,new wp.utils.WordCounter),function(e){"use strict";var t;BOLDGRID.SEO.Admin={init:function(){e(document).ready(function(){t._setWordCounts()})},wordCount:function(n){var o=n.attr("maxlength"),s=e("<span />",{"class":"boldgrid-seo-meta-counter",style:"font-weight: bold"}),r=e("<div />",{"class":"boldgrid-seo-meta-countdown boldgrid-seo-meta-extra",html:" characters left"});o&&n.removeAttr("maxlength").after(r.prepend(s)).on("keyup focus",function(){t.setCounter(s,n,o)}),t.setCounter(s,n,o)},setCounter:function(e,t,n){var o=t.val(),s=o.length;e.html(n-s),"boldgrid-seo-field-meta_description"===t.context.id?s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoDescription.length.okScore)?e.css({color:"#FBBC05"}):s.isBetween(_bgseoContentAnalysis.seoDescription.length.okScore-1,_bgseoContentAnalysis.seoDescription.length.goodScore+1)?e.css({color:"#34A853"}):e.css({color:"black"}):s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoTitle.length.okScore)?e.css({color:"#FBBC05"}):s>_bgseoContentAnalysis.seoTitle.length.okScore-1?e.css({color:"#34A853"}):e.css({color:"black"})},_setWordCounts:function(){e("#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description").each(function(){t.wordCount(e(this))})}},t=BOLDGRID.SEO.Admin}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.TinyMCE={init:function(){t.onloadContent(),e(document).ready(function(){t.editorChange()})},onloadContent:function(){e("#content.wp-editor-area[aria-hidden=false]");e(window).on("load bgseo-media-inserted",function(){var n=t.getContent();t.getRenderedContent(),_.defer(function(){e("#content").trigger("bgseo-analysis",[n])})})},getContent:function(){var n;tinymce.ActiveEditor?n=tinyMCE.get(wpActiveEditor).getContent():(n=e("#content").val(),n=n.replace(/\r?\n|\r/g,""));var o=e.parseHTML(n);return n={raw:o,text:t.stripper(n.toLowerCase())}},getRenderedContent:function(){var s,r;r=e("#preview-action > .preview.button").attr("href"),e("#sample-permalink").length&&e.get(r,function(r){var i,a,d;d=e(r),i=d.find("h1"),a=d.find("h2"),s={h1Count:i.length-n.rawstatistics.h1Count,h1text:_.filter(o.Headings.getHeadingText(i),function(e){return!_.findWhere(n.rawstatistics.h1text,e)}),h2Count:a.length-n.rawstatistics.h2Count,h2text:_.filter(o.Headings.getHeadingText(a),function(e){return!_.findWhere(n.rawstatistics.h2text,e)})},_.extend(n,{rendered:s}),e("#content").trigger("bgseo-analysis",[t.getContent()])},"html")},editorChange:function(){var n,o;return e("#content.wp-editor-area").on("input propertychange paste nodechange",function(){o=e(this).attr("id"),n=t.wpContent(o)}),n},tmceChange:function(e){var n,o;return o=e.target.id,n=t.wpContent(o)},wpContent:function(n){var o={};switch(n){case"tinymce":tinymce.activeEditor&&(o=tinyMCE.get(wpActiveEditor).getContent());break;case"content":o=e("#content").val(),o=o.replace(/\r?\n|\r/g,"")}var s=e.parseHTML(o);o={raw:s,text:t.stripper(o.toLowerCase())},e("#content").trigger("bgseo-analysis",[o])},stripper:function(e){var t;return t=document.implementation.createHTMLDocument("New").body,t.innerHTML=e,t.textContent||t.innerText||" "}},t=o.TinyMCE}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.ContentAnalysis={seoContentLengthScore:function(e){var t,n,o={};return e=Number(e),t=_bgseoContentAnalysis.content.length,n=t.contentLength.printf(e)+" ",0===e&&(o={status:"red",msg:t.badEmpty}),e.isBetween(0,t.badShortScore)&&(o={status:"red",msg:n+t.badShort}),e.isBetween(t.badShortScore-1,t.okScore)&&(o={status:"yellow",msg:n+t.ok}),e>t.okScore-1&&(o={status:"green",msg:n+t.good}),o},seoImageLengthScore:function(e){var t={status:"green",msg:_bgseoContentAnalysis.image.length.good};return e||(t={status:"red",msg:_bgseoContentAnalysis.image.length.bad}),t},keywords:function(e){var t=o.Keywords.getKeyword();return e.occurences(t)}},t=o.ContentAnalysis}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Dashboard={overviewScore:function(e){var n,o=t.totalScore(e),s=_.size(butterbean.models.sections);return n=2*s,(o/n*100).rounded(2)},overviewStatus:function(e){var t;return t="green",e<40&&(t="red"),e.isBetween(39,76)&&(t="yellow"),t},getStatuses:function(e){var t={};return _.each(butterbean.models.sections,function(n){var o,s=n.get("name");o=e[s].sectionStatus,t[s]=o,_(t[s]).extend(o)}),t},assignNumbers:function(e){var n,o;return o=t.getStatuses(e),n=_.mapObject(o,function(e){var t;return"red"===e&&(t=0),"yellow"===e&&(t=1),"green"===e&&(t=2),t})},totalScore:function(e){var n,o=t.assignNumbers(e);return n=_(o).reduce(function(e,t){return e+t},0)}},t=o.Dashboard}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Description={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._description()},getSettings:function(){t.settings={description:e("#boldgrid-seo-field-meta_description")}},_description:function(){t.settings.description.on("input propertychange paste",_.debounce(function(){e(this).trigger("bgseo-analysis",[{descLength:t.settings.description.val().length}])},1e3))},getDescription:function(){return t.settings.description},descriptionScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoDescription.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(){var e,n;return e=o.Keywords.getKeyword(),n=t.getDescription().val(),n=n.toLowerCase(),n.occurences(e)}},t=o.Description}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Headings={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._checkbox()},getSettings:function(){t.settings={displayTitle:e('[name="boldgrid-display-post-title"]').last()}},_checkbox:function(){t.settings.displayTitle.on("change",_.debounce(function(){e(this).trigger("bgseo-analysis",[o.TinyMCE.getContent()])},1e3))},score:function(e){var n;return n={status:"green",msg:_bgseoContentAnalysis.headings.h1.good},e>1&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badMultiple}),e>1&&t.settings.displayTitle.is(":checked")&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badBoldgridTheme}),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badEmpty}),n},keywords:function(e){var n={length:0},s=o.Keywords.getKeyword();if(_.isUndefined(e)&&(e={count:t.getRealHeadingCount()}),!_.isEmpty(e))return _(e.count).each(function(e,t){var o=e.text;_(o).each(function(e){n.length=Number(n.length)+Number(e.heading.occurences(s)*e.count)})}),n.length},getHeadingText:function(t){var n={};return n=_.countBy(t,function(t,n){return e.trim(e(t).text().toLowerCase())}),n=_.map(n,function(e,t){return!_(n).has({heading:t,count:e})&&{heading:t,count:e}})},getRealHeadingCount:function(){var e={};return _.isUndefined(n.rendered)?e=t.getContentHeadings():(e={count:{h1:{length:n.rendered.h1Count+n.rawstatistics.h1Count,text:_(n.rendered.h1text).union(n.rawstatistics.h1text)},h2:{length:n.rendered.h2Count+n.rawstatistics.h2Count,text:_(n.rendered.h2text).union(n.rawstatistics.h2text)}}},_(e).extend({lengthScore:t.score(e.count.h1.length)})),e},getContentHeadings:function(){var n,s,r,i;return n={count:{h1:{length:0,text:{}},h2:{length:0,text:{}}}},i=o.TinyMCE.getContent(),s=e(i.raw).find("h1"),r=e(i.raw).find("h2"),s.length||r.length?n={count:{h1:{length:s.length,text:t.getHeadingText(s)},h2:{length:r.length,text:t.getHeadingText(r)}}}:n}},t=o.Headings}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Keywords={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._keywords(),t.setPlaceholder()},getSettings:function(){t.settings={keyword:e("#bgseo-custom-keyword"),content:e("#content")}},_keywords:function(){t.settings.keyword.on("input propertychange paste",_.debounce(function(){var e={};t.settings.keyword.val().length;e={keywords:{title:{length:o.Title.keywords(),lengthScore:0},description:{length:o.Description.keywords(),lengthScore:0},keyword:t.getCustomKeyword()}},t.settings.keyword.trigger("bgseo-analysis",[e])},1e3))},setPlaceholder:function(e){t.settings.keyword.attr("placeholder",e)},keywordCount:function(e,t){var n;return n=e.split(t).length-1},phraseLength:function(e){return 0===e.length?0:(e=e.replace(/(^\s*)|(\s*$)/gi,""),e=e.replace(/[ ]{2,}/gi," "),e=e.replace(/\n /,"\n"),e.split(" ").length)},keywordDensity:function(e){var n,s,r,i;return i=t.getKeyword(),_.isUndefined(i)?0:(i=i.toLowerCase(),s=t.keywordCount(e,i),r=o.Report.getWordCount(),n=s/r*100,n=Math.round(10*n)/10)},normalizeWords:function(e){return e.replace("'","")},trim:function(e){return e.trim()},recommendedKeywords:function(n,o){var s,r=_bgseoContentAnalysis.stopWords,i={},a=[];if(!_.isEmpty(n)){r=r.split(",").map(t.trim),r=r.map(t.normalizeWords);for(var d=0;d<n.length;d++){var g=e.trim(n[d]).toLowerCase();!g||g.length<3||r.indexOf(g)>-1||(_.isUndefined(i[g])?(i[g]=a.length,a.push([g,1])):a[i[g]][1]++)}return a.sort(function(e,t){return t[1]-e[1]}),s=a.slice(0,o)}},getCustomKeyword:function(){return e.trim(t.settings.keyword.val()).toLowerCase()},getKeyword:function(){var s,r=o.TinyMCE.getContent();return t.getCustomKeyword().length?s=t.getCustomKeyword():_.isUndefined(n.textstatistics.recommendedKeywords)||_.isUndefined(n.textstatistics.recommendedKeywords[0])?_.isEmpty(e.trim(r.text))?s=void 0:t.recommendedKeywords(o.Words.words(r.raw),1):s=n.textstatistics.recommendedKeywords[0][0],s},getRecommendedCount:function(e){var n;return _.isUndefined(e)&&(e=!tinyMCE.activeEditor||tinyMCE.activeEditor.hidden?o.Words.words(t.settings.content.val()):o.Words.words(tinyMCE.activeEditor.getContent({format:"raw"}))),n=_.modifyObject(_bgseoContentAnalysis.keywords.recommendedCount,function(t){var n=Number(t/100*o.Words.words(e).length).rounded(0);return n>0?n:1})},score:function(){var e={};return e={title:t.titleScore(),description:t.descriptionScore()}},titleScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.ok}),t},descriptionScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.ok}),t},contentScore:function(e){var n,o,s;return o=t.getRecommendedCount(),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.content.keywordUsage.bad}),e.isBetween(o.min-1,o.max+1)&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.goodSingular:_bgseoContentAnalysis.content.keywordUsage.good.printf(o.min),n={status:"green",msg:s}),e<o.min&&0!==e&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okShortSingular:_bgseoContentAnalysis.content.keywordUsage.okShort.printf(o.min),n={status:"yellow",msg:s}),e>o.max&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okLongSingular:_bgseoContentAnalysis.content.keywordUsage.okLong.printf(o.min),n={status:"red",msg:s}),n},headingScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.headings.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.headings.keywordUsage.bad}),e>3&&(t={status:"yellow",msg:_bgseoContentAnalysis.headings.keywordUsage.ok}),t},keywordPhraseScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.keywords.keywordPhrase.good},1===e&&(t={status:"yellow",msg:_bgseoContentAnalysis.keywords.keywordPhrase.ok}),0===e&&(t={status:"red",msg:_bgseoContentAnalysis.keywords.keywordPhrase.bad}),t}},t=o.Keywords}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Readability={gradeLevel:function(e){var n,o={};return n=textstatistics(e).fleschKincaidReadingEase(),o=t.gradeAnalysis(n)},gradeAnalysis:function(e){var t,n={};return e>90&&(n={score:e,gradeLevel:"5th grade",explanation:"Very easy to read. Easily understood by an average 11-year-old student.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodHigh}}),e.isBetween(79,91)&&(n={score:e,gradeLevel:"6th grade",explanation:"Easy to read. Conversational English for consumers.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedHigh}}),e.isBetween(69,81)&&(n={score:e,gradeLevel:"7th grade",explanation:"Fairly easy to read.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedLow}}),e.isBetween(59,71)&&(n={score:e,gradeLevel:"8th & 9th",explanation:"Plain English. Easily understood by 13- to 15-year-old students.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodLow}}),e.isBetween(49,61)&&(n={score:e,gradeLevel:"10th to 12th",explanation:"Fairly difficult to read.",lengthScore:{status:"yellow",msg:_bgseoContentAnalysis.readingEase.ok}}),e.isBetween(29,51)&&(n={score:e,gradeLevel:"College Student",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badHigh}}),e<30&&(n={score:e,gradeLevel:"College Graduate",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badLow}}),t=_bgseoContentAnalysis.readingEase.score.printf(e)+" ",n.lengthScore.msg=n.lengthScore.msg.replace(/^/,t),n}},t=o.Readability}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Report={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.generateReport()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title"),description:e("#boldgrid-seo-field-meta_description"),wordCounter:e("#wp-word-count .word-count"),content:e("#content")}},getWordCount:function(){return Number(t.settings.wordCounter.text())},generateReport:function(){_.isUndefined(t.settings)||e(document).on("bgseo-analysis",function(s,r){var i,a;if(i=t.settings.title.val().length,a=t.settings.description.val().length,r.words&&_(n.textstatistics).extend({recommendedKeywords:o.Keywords.recommendedKeywords(r.words,1),customKeyword:o.Keywords.getKeyword()}),r){if(r.raw){var d=r.raw,g=e(d).find("h1"),c=e(d).find("h2"),l={};l={h1Count:g.length,h1text:o.Headings.getHeadingText(g),h2Count:c.length,h2text:o.Headings.getHeadingText(c),imageCount:e(d).find("img").length},_(n.rawstatistics).extend(l)}if(r.keywords&&_(n.bgseo_keywords).extend({keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.TinyMCE.getContent().text))},keywordHeadings:{length:o.Headings.keywords(o.Headings.getRealHeadingCount()),lengthScore:o.Keywords.headingScore(o.Headings.keywords(o.Headings.getRealHeadingCount()))},customKeyword:r.keywords.keyword}),r.text){var u,y=o.Headings.getRealHeadingCount(),h=r.text,w=!tinyMCE.activeEditor||tinyMCE.activeEditor.hidden?o.Words.words(t.settings.content.val()):o.Words.words(tinyMCE.activeEditor.getContent({format:"raw"}));i=t.settings.title.val().length,a=t.settings.description.val().length,u=o.Keywords.recommendedKeywords(w,1),_.isUndefined(u)||_.isUndefined(u[0])||o.Keywords.setPlaceholder(u[0][0]),_(n).extend({bgseo_meta:{title:{length:i,lengthScore:o.Title.titleScore(i)},description:{length:a,lengthScore:o.Description.descriptionScore(a),keywordUsage:o.Description.keywords()},titleKeywordUsage:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},descKeywordUsage:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},sectionScore:{},sectionStatus:{}},bgseo_visibility:{robotIndex:{lengthScore:o.Robots.indexScore()},robotFollow:{lengthScore:o.Robots.followScore()},sectionScore:{},sectionStatus:{}},bgseo_keywords:{keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.TinyMCE.getContent().text))},keywordHeadings:{length:o.Headings.keywords(y),lengthScore:o.Keywords.headingScore(o.Headings.keywords(y))},image:{length:n.rawstatistics.imageCount,lengthScore:o.ContentAnalysis.seoImageLengthScore(n.rawstatistics.imageCount)},headings:y,wordCount:{length:t.getWordCount(),lengthScore:o.ContentAnalysis.seoContentLengthScore(t.getWordCount())},sectionScore:{},sectionStatus:{}},textstatistics:{recommendedKeywords:u,recommendedCount:o.Keywords.getRecommendedCount(w),keywordDensity:o.Keywords.keywordDensity(h,o.Keywords.getKeyword())}})}r.titleLength&&(_(n.bgseo_meta.title).extend({length:r.titleLength,lengthScore:o.Title.titleScore(r.titleLength)}),_(n.bgseo_meta.titleKeywordUsage).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),_(n.bgseo_keywords.keywordTitle).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.descLength&&(_(n.bgseo_meta.description).extend({length:r.descLength,lengthScore:o.Description.descriptionScore(r.descLength)}),_(n.bgseo_meta.descKeywordUsage).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),_(n.bgseo_keywords.keywordDescription).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.robotIndex&&(_(n.bgseo_visibility.robotIndex).extend({lengthScore:r.robotIndex}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.robotFollow&&(_(n.bgseo_visibility.robotFollow).extend({lengthScore:r.robotFollow}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()]))}t.settings.content.trigger("bgseo-report",[n])})},get:function(e){var t={};return t=_.isUndefined(e)?n:_.pickDeep(n,e)}},t=o.Report}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Robots={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._index(),t._follow()},getSettings:function(){t.settings={indexInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]"),noIndex:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]'),followInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]"),noFollow:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]')}},_index:function(){t.settings.indexInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotIndex:t.indexScore()}])})},indexScore:function(){var e;return e={status:"green",msg:_bgseoContentAnalysis.noIndex.good},t.settings.noIndex.is(":checked")&&(e={status:"red",msg:_bgseoContentAnalysis.noIndex.bad}),e},_follow:function(){t.settings.followInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotFollow:t.followScore()}])})},followScore:function(){var e={status:"green",msg:_bgseoContentAnalysis.noFollow.good};return t.settings.noFollow.is(":checked")&&(e={status:"yellow",msg:_bgseoContentAnalysis.noFollow.bad}),e}},t=o.Robots}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Sections={status:function(e){var t="green";return e.red>0?t="red":e.yellow>0&&(t="yellow"),t},score:function(e){var n,o,s;return n={red:0,green:0,yellow:0},o=_(e).countBy(function(t){return _.isUndefined(t.lengthScore)||"sectionScore"===_.property("sectionScore")(e)?"":t.lengthScore.status}),_(o).each(function(e,t){_.has(n,t)&&(n[t]=e)}),s={sectionScore:n,sectionStatus:t.status(n)}},removeStatus:function(e){e.removeClass("red yellow green")},navHighlight:function(n){_.each(butterbean.models.sections,function(o){var s,r=o.get("manager"),i=o.get("name");s=e('[href="#butterbean-'+r+"-section-"+i+'"]').closest("li"),t.removeStatus(s),s.addClass(n[i].sectionStatus)})},overviewStatus:function(n){var o=e("#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')");t.removeStatus(o),o.addClass("overview-status "+n.bgseo_keywords.overview.status)}},t=o.Sections}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Title={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._title()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title")}},getTitle:function(){return t.settings.title},_title:function(){t.settings.title.on("input propertychange paste",_.debounce(function(){t.settings.title.trigger("bgseo-analysis",[{titleLength:t.settings.title.val().length}])},1e3))},titleScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoTitle.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore+1)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(e,n){return 0===arguments.length?(n=o.Keywords.getKeyword(),e=t.getTitle().val()):1===arguments.length&&(n=o.Keywords.getKeyword()),e=e.toLowerCase(),e.occurences(n)}},t=o.Title}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Tooltips={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.hideTooltips(),t._enableTooltips(),t._toggleTooltip()},getSettings:function(){t.settings={description:e(".butterbean-control .butterbean-description"),tooltip:e("<span />",{"class":"bgseo-tooltip dashicons dashicons-editor-help","aria-expanded":"false"}),onClick:e(".butterbean-label, .bgseo-tooltip")}},_toggleTooltip:function(){t.settings.onClick.on("click",function(e){t.toggleTooltip(e)})},_enableTooltips:function(){t.settings.description.prev().append(t.settings.tooltip)},toggleTooltip:function(t){e(t.currentTarget).next(".butterbean-description").slideToggle()},hideTooltips:function(){t.settings.description.hide()}},t=o.Tooltips}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t;t=BOLDGRID.SEO,t.Init={load:function(){_.each(t,function(e){return e.init&&e.init()})}}}(jQuery),BOLDGRID.SEO.Init.load();
1
+ var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO={report:{bgseo_visibility:{},bgseo_keywords:{},bgseo_meta:{},rawstatistics:{},textstatistics:{}}},function(e){"use strict";butterbean.views.register_control("dashboard",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-dashboard"),_.bindAll(this,"render"),this.model.bind("change",this.render)},results:function(e){var t={};return _.each(e,function(e){_.extend(t,e)}),t},setAnalysis:function(e,t){var n,o=this.model.get("section"),s=_.pick(t,o);this.sectionReport=this.results(s),this.model.set("analysis",this.sectionReport),_(t).each(function(e){_.isUndefined(e.sectionScore)||(n=BOLDGRID.SEO.Sections.score(e),_(e).extend(n))}),_(t.bgseo_keywords).extend({overview:{score:BOLDGRID.SEO.Dashboard.overviewScore(t)}}),_(t.bgseo_keywords.overview).extend({status:BOLDGRID.SEO.Dashboard.overviewStatus(t.bgseo_keywords.overview.score)}),BOLDGRID.SEO.Sections.navHighlight(t),BOLDGRID.SEO.Sections.overviewStatus(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery),function(e){"use strict";butterbean.views.register_control("keywords",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-keywords"),_.bindAll(this,"render"),this.model.bind("change",this.render)},setAnalysis:function(e,t){this.model.set(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Util={init:function(){_.mixin({modifyObject:function(e,t){return _.object(_.map(e,function(e,n){return[n,t(e)]}))},pickDeep:function(e){var t={},n=Array.prototype.concat.apply(Array.prototype,Array.prototype.slice.call(arguments,1));return this.each(n,function(n){var o=n.split(".");n=o.shift(),n in e&&(o.length>0?t[n]?_.extend(t[n],_.pickDeep(e[n],o.join("."))):t[n]=_.pickDeep(e[n],o.join(".")):t[n]=e[n])}),t}}),Number.prototype.isBetween||(Number.prototype.isBetween=function(e,t){_.isUndefined(e)&&(e=0),_.isUndefined(t)&&(t=0);var n=Math.max(e,t),o=Math.min(e,t);return this>o&&this<n}),Number.prototype.rounded||(Number.prototype.rounded=function(e){_.isUndefined(e)&&(e=0);var t=Math.pow(10,e),n=Math.round(this*t)/t;return n}),String.prototype.printf||(String.prototype.printf=function(){for(var e=this,t=0;/%s/.test(e);)e=e.replace("%s",arguments[t++]);return e}),String.prototype.occurences||(String.prototype.occurences=function(e,t){if(e+="",e.length<=0)return this.length+1;for(var n=0,o=0,s=t?1:e.length;;){if(o=this.indexOf(e,o),!(o>=0))break;++n,o+=s}return n})}},t=o.Util}(jQuery),function(){"use strict";var e,t;t=BOLDGRID.SEO,t.Words={init:function(t){var n,o;if(t)for(n in t)t.hasOwnProperty(n)&&(e.settings[n]=t[n]);o=e.settings.l10n.shortcodes,o&&o.length&&(e.settings.shortcodesRegExp=new RegExp("\\[\\/?(?:"+o.join("|")+")[^\\]]*?\\]","g"))},settings:{HTMLRegExp:/<\/?[a-z][^>]*?>/gi,HTMLcommentRegExp:/<!--[\s\S]*?-->/g,spaceRegExp:/&nbsp;|&#160;/gi,HTMLEntityRegExp:/&\S+?;/g,connectorRegExp:/--|\u2014/g,removeRegExp:new RegExp(["[","!-@[-`{-~","€-¿×÷"," -⯿","⸀-⹿","]"].join(""),"g"),astralRegExp:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,wordsRegExp:/.+?\s+/g,characters_excluding_spacesRegExp:/\S/g,characters_including_spacesRegExp:/[^\f\n\r\t\v\u00AD\u2028\u2029]/g,l10n:window.wordCountL10n||{}},words:function(t,n){var o=0;return n=n||e.settings.l10n.type,"characters_excluding_spaces"!==n&&"characters_including_spaces"!==n&&(n="words"),t&&(t+="\n",t=t.replace(e.settings.HTMLRegExp,"\n"),t=t.replace(e.settings.HTMLcommentRegExp,""),e.settings.shortcodesRegExp&&(t=t.replace(e.settings.shortcodesRegExp,"\n")),t=t.replace(e.settings.spaceRegExp," "),"words"===n?(t=t.replace(e.settings.HTMLEntityRegExp,""),t=t.replace(e.settings.connectorRegExp," "),t=t.replace(e.settings.removeRegExp,"")):(t=t.replace(e.settings.HTMLEntityRegExp,"a"),t=t.replace(e.settings.astralRegExp,"a")),t=t.match(e.settings[n+"RegExp"]),t&&(o=t)),o}},e=t.Words}(),function(e,t){"use strict";var n,o;o=BOLDGRID.SEO,o.Wordcount={count:0,words:[],init:function(){e(n.update)},update:function(){var e,s,r=o.Editor.ui.getRawText();e=t.count(r),s=BOLDGRID.SEO.Words.words(r),e!==n.count&&o.Editor.element.trigger("bgseo-analysis",[{words:s,count:e}]),n.words=s,n.count=e}},n=o.Wordcount}(jQuery,new wp.utils.WordCounter),function(e){"use strict";var t;BOLDGRID.SEO.Admin={init:function(){e(document).ready(function(){t._setWordCounts()})},wordCount:function(n){var o=n.attr("maxlength"),s=e("<span />",{"class":"boldgrid-seo-meta-counter",style:"font-weight: bold"}),r=e("<div />",{"class":"boldgrid-seo-meta-countdown boldgrid-seo-meta-extra",html:" characters left"});o&&n.removeAttr("maxlength").after(r.prepend(s)).on("keyup focus",function(){t.setCounter(s,n,o)}),t.setCounter(s,n,o)},setCounter:function(e,t,n){var o=t.val(),s=o.length;e.html(n-s),"boldgrid-seo-field-meta_description"===t.context.id?s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoDescription.length.okScore)?e.css({color:"#FBBC05"}):s.isBetween(_bgseoContentAnalysis.seoDescription.length.okScore-1,_bgseoContentAnalysis.seoDescription.length.goodScore+1)?e.css({color:"#34A853"}):e.css({color:"black"}):s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoTitle.length.okScore)?e.css({color:"#FBBC05"}):s>_bgseoContentAnalysis.seoTitle.length.okScore-1?e.css({color:"#34A853"}):e.css({color:"black"})},_setWordCounts:function(){e("#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description").each(function(){t.wordCount(e(this))})}},t=BOLDGRID.SEO.Admin}(jQuery),function(e){"use strict";var t,n;n=BOLDGRID.SEO,n.TinyMCE={selector:"#content",previewSelector:"#preview-action > .preview.button",setup:function(){e(document).ready(function(){t._setupWordCount(),t.editorChange()})},getContent:function(){var t;tinymce.ActiveEditor?t=tinyMCE.get(wpActiveEditor).getContent():(t=n.Editor.element.val(),t=t.replace(/\r?\n|\r/g,""));var o=e.parseHTML(t);return t={raw:o,text:n.Editor.stripper(t.toLowerCase())}},getRawText:function(){var e,t=tinyMCE.get(wpActiveEditor);return e=!t||t.isHidden()?n.Editor.element.val():t.getContent({format:"raw"})},editorChange:function(){var n,o;return e("#content.wp-editor-area").on("input propertychange paste nodechange",function(){o=e(this).attr("id"),n=t.wpContent(o)}),n},tmceChange:function(e){var n,o;return o=e.target.id,n=t.wpContent(o)},isNewPost:function(){return!e("#sample-permalink").length},wpContent:function(t){var o={};switch(t){case"tinymce":tinymce.activeEditor&&(o=tinyMCE.get(wpActiveEditor).getContent());break;case"content":o=n.Editor.element.val(),o=o.replace(/\r?\n|\r/g,"")}var s=e.parseHTML(o);o={raw:s,text:n.Editor.stripper(o.toLowerCase())},n.Editor.element.trigger("bgseo-analysis",[o])},_setupWordCount:function(){var t=_.debounce(n.Wordcount.update,1e3);e(document).on("tinymce-editor-init",function(e,n){"content"===n.id&&n.on("AddUndo keyup",t)}),n.Editor.element.on("input keyup",t)}},t=n.TinyMCE}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.ContentAnalysis={seoContentLengthScore:function(e){var t,n,o={};return e=Number(e),t=_bgseoContentAnalysis.content.length,n=t.contentLength.printf(e)+" ",0===e&&(o={status:"red",msg:t.badEmpty}),e.isBetween(0,t.badShortScore)&&(o={status:"red",msg:n+t.badShort}),e.isBetween(t.badShortScore-1,t.okScore)&&(o={status:"yellow",msg:n+t.ok}),e>t.okScore-1&&(o={status:"green",msg:n+t.good}),o},seoImageLengthScore:function(e){var t={status:"green",msg:_bgseoContentAnalysis.image.length.good};return e||(t={status:"red",msg:_bgseoContentAnalysis.image.length.bad}),t},keywords:function(e){var t=o.Keywords.getKeyword();return e.occurences(t)}},t=o.ContentAnalysis}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Dashboard={overviewScore:function(e){var n,o=t.totalScore(e),s=_.size(butterbean.models.sections);return n=2*s,(o/n*100).rounded(2)},overviewStatus:function(e){var t;return t="green",e<40&&(t="red"),e.isBetween(39,76)&&(t="yellow"),t},getStatuses:function(e){var t={};return _.each(butterbean.models.sections,function(n){var o,s=n.get("name");o=e[s].sectionStatus,t[s]=o,_(t[s]).extend(o)}),t},assignNumbers:function(e){var n,o;return o=t.getStatuses(e),n=_.mapObject(o,function(e){var t;return"red"===e&&(t=0),"yellow"===e&&(t=1),"green"===e&&(t=2),t})},totalScore:function(e){var n,o=t.assignNumbers(e);return n=_(o).reduce(function(e,t){return e+t},0)}},t=o.Dashboard}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Description={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._description()},getSettings:function(){t.settings={description:e("#boldgrid-seo-field-meta_description")}},_description:function(){t.settings.description.on("input propertychange paste",_.debounce(function(){e(this).trigger("bgseo-analysis",[{descLength:t.settings.description.val().length}])},1e3))},getDescription:function(){return t.settings.description},descriptionScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoDescription.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(){var e,n;return e=o.Keywords.getKeyword(),n=t.getDescription().val(),n=n.toLowerCase(),n.occurences(e)}},t=o.Description}(jQuery),function(e){"use strict";var t,n,o;n=BOLDGRID.SEO,o=n.report,n.Editor={ui:null,element:null,init:function(){t.ui=wp.data?n.Gutenberg:n.TinyMCE,t.element=e(t.ui.selector),t.ui.setup(),t.onloadContent()},onloadContent:function(){e("#content.wp-editor-area[aria-hidden=false]");e(window).on("load bgseo-media-inserted",function(){var e=t.ui.getContent();t.getRenderedContent(),_.defer(function(){t.element.trigger("bgseo-analysis",[e])})})},getRenderedContent:function(){var s,r;r=e(n.Editor.ui.previewSelector).attr("href"),n.Editor.ui.isNewPost()||e.get(r,function(r){var i,a,d;d=e(r),i=d.find("h1"),a=d.find("h2"),s={h1Count:i.length-o.rawstatistics.h1Count,h1text:_.filter(n.Headings.getHeadingText(i),function(e){return!_.findWhere(o.rawstatistics.h1text,e)}),h2Count:a.length-o.rawstatistics.h2Count,h2text:_.filter(n.Headings.getHeadingText(a),function(e){return!_.findWhere(o.rawstatistics.h2text,e)})},_.extend(o,{rendered:s}),t.triggerAnalysis()},"html")},stripper:function(e){var t;return t=document.implementation.createHTMLDocument("New").body,t.innerHTML=e,t.textContent||t.innerText||" "},triggerAnalysis:function(){t.element.trigger("bgseo-analysis",[t.ui.getContent()])}},t=n.Editor}(jQuery),function(e){"use strict";var t,n;n=BOLDGRID.SEO,n.Gutenberg={selector:"#editor",previewSelector:".editor-post-preview",setup:function(){e(n.Editor.triggerAnalysis),t._setupEditorChange()},isNewPost:function(){return wp.data.select("core/editor").isCleanNewPost()},getContent:function(){var e=t.getRawText();return e={raw:e,text:n.Editor.stripper(e.toLowerCase())}},getRawText:function(){return wp.data.select("core/editor").getEditedPostAttribute("content")},_setupEditorChange:function(){var e="";wp.data.subscribe(_.debounce(function(){t.getRawText()!==e&&(n.Wordcount.update(),n.Editor.triggerAnalysis(),e=t.getRawText())},1e3))}},t=n.Gutenberg}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Headings={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._checkbox()},getSettings:function(){t.settings={displayTitle:e('[name="boldgrid-display-post-title"]').last()}},_checkbox:function(){t.settings.displayTitle.on("change",_.debounce(function(){e(this).trigger("bgseo-analysis",[o.Editor.ui.getContent()])},1e3))},score:function(e){var n;return n={status:"green",msg:_bgseoContentAnalysis.headings.h1.good},e>1&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badMultiple}),e>1&&t.settings.displayTitle.is(":checked")&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badBoldgridTheme}),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badEmpty}),n},keywords:function(e){var n={length:0},s=o.Keywords.getKeyword();if(_.isUndefined(e)&&(e={count:t.getRealHeadingCount()}),!_.isEmpty(e))return _(e.count).each(function(e,t){var o=e.text;_(o).each(function(e){n.length=Number(n.length)+Number(e.heading.occurences(s)*e.count)})}),n.length},getHeadingText:function(t){var n={};return n=_.countBy(t,function(t,n){return e.trim(e(t).text().toLowerCase())}),n=_.map(n,function(e,t){return!_(n).has({heading:t,count:e})&&{heading:t,count:e}})},getRealHeadingCount:function(){var e={};return _.isUndefined(n.rendered)?e=t.getContentHeadings():(e={count:{h1:{length:n.rendered.h1Count+n.rawstatistics.h1Count,text:_(n.rendered.h1text).union(n.rawstatistics.h1text)},h2:{length:n.rendered.h2Count+n.rawstatistics.h2Count,text:_(n.rendered.h2text).union(n.rawstatistics.h2text)}}},_(e).extend({lengthScore:t.score(e.count.h1.length)})),e},getContentHeadings:function(){var n,s,r,i;return n={count:{h1:{length:0,text:{}},h2:{length:0,text:{}}}},i=o.Editor.ui.getContent(),i=e("<div>"+i.raw+"</div>"),s=i.find("h1"),r=i.find("h2"),s.length||r.length?n={count:{h1:{length:s.length,text:t.getHeadingText(s)},h2:{length:r.length,text:t.getHeadingText(r)}}}:n}},t=o.Headings}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Keywords={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._keywords(),t.setPlaceholder()},getSettings:function(){t.settings={keyword:e("#bgseo-custom-keyword"),content:o.Editor.element}},_keywords:function(){t.settings.keyword.on("input propertychange paste",_.debounce(function(){var e={};t.settings.keyword.val().length;e={keywords:{title:{length:o.Title.keywords(),lengthScore:0},description:{length:o.Description.keywords(),lengthScore:0},keyword:t.getCustomKeyword()}},t.settings.keyword.trigger("bgseo-analysis",[e])},1e3))},setPlaceholder:function(e){t.settings.keyword.attr("placeholder",e)},keywordCount:function(e,t){var n;return n=e.split(t).length-1},phraseLength:function(e){return 0===e.length?0:(e=e.replace(/(^\s*)|(\s*$)/gi,""),e=e.replace(/[ ]{2,}/gi," "),e=e.replace(/\n /,"\n"),e.split(" ").length)},keywordDensity:function(e){var n,s,r,i;return i=t.getKeyword(),_.isUndefined(i)?0:(i=i.toLowerCase(),s=t.keywordCount(e,i),r=o.Wordcount.count,n=s/r*100,n=Math.round(10*n)/10)},normalizeWords:function(e){return e.replace("'","")},trim:function(e){return e.trim()},recommendedKeywords:function(n,o){var s,r=_bgseoContentAnalysis.stopWords,i={},a=[];if(!_.isEmpty(n)){r=r.split(",").map(t.trim),r=r.map(t.normalizeWords);for(var d=0;d<n.length;d++){var g=e.trim(n[d]).toLowerCase();!g||g.length<3||r.indexOf(g)>-1||(_.isUndefined(i[g])?(i[g]=a.length,a.push([g,1])):a[i[g]][1]++)}return a.sort(function(e,t){return t[1]-e[1]}),s=a.slice(0,o)}},getCustomKeyword:function(){return e.trim(t.settings.keyword.val()).toLowerCase()},getKeyword:function(){var s,r=o.Editor.ui.getRawText();return t.getCustomKeyword().length?s=t.getCustomKeyword():_.isUndefined(n.textstatistics.recommendedKeywords)||_.isUndefined(n.textstatistics.recommendedKeywords[0])?_.isEmpty(e.trim(r.text))?s=void 0:t.recommendedKeywords(o.Words.words(r.raw),1):s=n.textstatistics.recommendedKeywords[0][0],s},getRecommendedCount:function(e){var t;return _.isUndefined(e)&&(e=o.Words.words(o.Editor.ui.getRawText())),t=_.modifyObject(_bgseoContentAnalysis.keywords.recommendedCount,function(t){var n=Number(t/100*o.Words.words(e).length).rounded(0);return n>0?n:1})},score:function(){var e={};return e={title:t.titleScore(),description:t.descriptionScore()}},titleScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.ok}),t},descriptionScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.ok}),t},contentScore:function(e){var n,o,s;return o=t.getRecommendedCount(),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.content.keywordUsage.bad}),e.isBetween(o.min-1,o.max+1)&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.goodSingular:_bgseoContentAnalysis.content.keywordUsage.good.printf(o.min),n={status:"green",msg:s}),e<o.min&&0!==e&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okShortSingular:_bgseoContentAnalysis.content.keywordUsage.okShort.printf(o.min),n={status:"yellow",msg:s}),e>o.max&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okLongSingular:_bgseoContentAnalysis.content.keywordUsage.okLong.printf(o.min),n={status:"red",msg:s}),n},headingScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.headings.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.headings.keywordUsage.bad}),e>3&&(t={status:"yellow",msg:_bgseoContentAnalysis.headings.keywordUsage.ok}),t},keywordPhraseScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.keywords.keywordPhrase.good},1===e&&(t={status:"yellow",msg:_bgseoContentAnalysis.keywords.keywordPhrase.ok}),0===e&&(t={status:"red",msg:_bgseoContentAnalysis.keywords.keywordPhrase.bad}),t}},t=o.Keywords}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Readability={gradeLevel:function(e){var n,o={};return n=textstatistics(e).fleschKincaidReadingEase(),o=t.gradeAnalysis(n)},gradeAnalysis:function(e){var t,n={};return e>90&&(n={score:e,gradeLevel:"5th grade",explanation:"Very easy to read. Easily understood by an average 11-year-old student.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodHigh}}),e.isBetween(79,91)&&(n={score:e,gradeLevel:"6th grade",explanation:"Easy to read. Conversational English for consumers.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedHigh}}),e.isBetween(69,81)&&(n={score:e,gradeLevel:"7th grade",explanation:"Fairly easy to read.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedLow}}),e.isBetween(59,71)&&(n={score:e,gradeLevel:"8th & 9th",explanation:"Plain English. Easily understood by 13- to 15-year-old students.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodLow}}),e.isBetween(49,61)&&(n={score:e,gradeLevel:"10th to 12th",explanation:"Fairly difficult to read.",lengthScore:{status:"yellow",msg:_bgseoContentAnalysis.readingEase.ok}}),e.isBetween(29,51)&&(n={score:e,gradeLevel:"College Student",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badHigh}}),e<30&&(n={score:e,gradeLevel:"College Graduate",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badLow}}),t=_bgseoContentAnalysis.readingEase.score.printf(e)+" ",n.lengthScore.msg=n.lengthScore.msg.replace(/^/,t),n}},t=o.Readability}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Report={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.generateReport()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title"),description:e("#boldgrid-seo-field-meta_description")}},generateReport:function(){_.isUndefined(t.settings)||e(document).on("bgseo-analysis",function(s,r){var i,a;if(i=t.settings.title.val().length,a=t.settings.description.val().length,r.words&&_(n.textstatistics).extend({recommendedKeywords:o.Keywords.recommendedKeywords(r.words,1),customKeyword:o.Keywords.getKeyword()}),r){if(r.raw){var d=e("<div>"+r.raw+"</div>"),g=d.find("h1"),c=d.find("h2"),l={};l={h1Count:g.length,h1text:o.Headings.getHeadingText(g),h2Count:c.length,h2text:o.Headings.getHeadingText(c),imageCount:d.find("img").length},_(n.rawstatistics).extend(l)}if(r.keywords&&_(n.bgseo_keywords).extend({keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.Editor.ui.getContent().text))},keywordHeadings:{length:o.Headings.keywords(o.Headings.getRealHeadingCount()),lengthScore:o.Keywords.headingScore(o.Headings.keywords(o.Headings.getRealHeadingCount()))},customKeyword:r.keywords.keyword}),r.text){var u,y=o.Headings.getRealHeadingCount(),h=r.text,w=o.Editor.ui.getRawText();i=t.settings.title.val().length,a=t.settings.description.val().length,u=o.Keywords.recommendedKeywords(w,1),_.isUndefined(u)||_.isUndefined(u[0])||o.Keywords.setPlaceholder(u[0][0]),_(n).extend({bgseo_meta:{title:{length:i,lengthScore:o.Title.titleScore(i)},description:{length:a,lengthScore:o.Description.descriptionScore(a),keywordUsage:o.Description.keywords()},titleKeywordUsage:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},descKeywordUsage:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},sectionScore:{},sectionStatus:{}},bgseo_visibility:{robotIndex:{lengthScore:o.Robots.indexScore()},robotFollow:{lengthScore:o.Robots.followScore()},sectionScore:{},sectionStatus:{}},bgseo_keywords:{keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.Editor.ui.getContent().text))},keywordHeadings:{length:o.Headings.keywords(y),lengthScore:o.Keywords.headingScore(o.Headings.keywords(y))},image:{length:n.rawstatistics.imageCount,lengthScore:o.ContentAnalysis.seoImageLengthScore(n.rawstatistics.imageCount)},headings:y,wordCount:{length:o.Wordcount.count,lengthScore:o.ContentAnalysis.seoContentLengthScore(o.Wordcount.count)},sectionScore:{},sectionStatus:{}},textstatistics:{recommendedKeywords:u,recommendedCount:o.Keywords.getRecommendedCount(w),keywordDensity:o.Keywords.keywordDensity(h,o.Keywords.getKeyword())}})}r.titleLength&&(_(n.bgseo_meta.title).extend({length:r.titleLength,lengthScore:o.Title.titleScore(r.titleLength)}),_(n.bgseo_meta.titleKeywordUsage).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),_(n.bgseo_keywords.keywordTitle).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),o.Editor.triggerAnalysis()),r.descLength&&(_(n.bgseo_meta.description).extend({length:r.descLength,lengthScore:o.Description.descriptionScore(r.descLength)}),_(n.bgseo_meta.descKeywordUsage).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),_(n.bgseo_keywords.keywordDescription).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),o.Editor.triggerAnalysis()),r.robotIndex&&(_(n.bgseo_visibility.robotIndex).extend({lengthScore:r.robotIndex}),o.Editor.triggerAnalysis()),r.robotFollow&&(_(n.bgseo_visibility.robotFollow).extend({lengthScore:r.robotFollow}),o.Editor.triggerAnalysis())}o.Editor.element.trigger("bgseo-report",[n])})},get:function(e){var t={};return t=_.isUndefined(e)?n:_.pickDeep(n,e)}},t=o.Report}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Robots={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._index(),t._follow()},getSettings:function(){t.settings={indexInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]"),noIndex:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]'),followInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]"),noFollow:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]')}},_index:function(){t.settings.indexInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotIndex:t.indexScore()}])})},indexScore:function(){var e;return e={status:"green",msg:_bgseoContentAnalysis.noIndex.good},t.settings.noIndex.is(":checked")&&(e={status:"red",msg:_bgseoContentAnalysis.noIndex.bad}),e},_follow:function(){t.settings.followInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotFollow:t.followScore()}])})},followScore:function(){var e={status:"green",msg:_bgseoContentAnalysis.noFollow.good};return t.settings.noFollow.is(":checked")&&(e={status:"yellow",msg:_bgseoContentAnalysis.noFollow.bad}),e}},t=o.Robots}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Sections={status:function(e){var t="green";return e.red>0?t="red":e.yellow>0&&(t="yellow"),t},score:function(e){var n,o,s;return n={red:0,green:0,yellow:0},o=_(e).countBy(function(t){return _.isUndefined(t.lengthScore)||"sectionScore"===_.property("sectionScore")(e)?"":t.lengthScore.status}),_(o).each(function(e,t){_.has(n,t)&&(n[t]=e)}),s={sectionScore:n,sectionStatus:t.status(n)}},removeStatus:function(e){e.removeClass("red yellow green")},navHighlight:function(n){_.each(butterbean.models.sections,function(o){var s,r=o.get("manager"),i=o.get("name");s=e('[href="#butterbean-'+r+"-section-"+i+'"]').closest("li"),t.removeStatus(s),s.addClass(n[i].sectionStatus)})},overviewStatus:function(n){var o=e("#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')");t.removeStatus(o),o.addClass("overview-status "+n.bgseo_keywords.overview.status)}},t=o.Sections}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Title={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._title()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title")}},getTitle:function(){return t.settings.title},_title:function(){t.settings.title.on("input propertychange paste",_.debounce(function(){t.settings.title.trigger("bgseo-analysis",[{titleLength:t.settings.title.val().length}])},1e3))},titleScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoTitle.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore+1)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(e,n){return 0===arguments.length?(n=o.Keywords.getKeyword(),e=t.getTitle().val()):1===arguments.length&&(n=o.Keywords.getKeyword()),e=e.toLowerCase(),e.occurences(n)}},t=o.Title}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Tooltips={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.hideTooltips(),t._enableTooltips(),t._toggleTooltip()},getSettings:function(){t.settings={description:e(".butterbean-control .butterbean-description"),tooltip:e("<span />",{"class":"bgseo-tooltip dashicons dashicons-editor-help","aria-expanded":"false"}),onClick:e(".butterbean-label, .bgseo-tooltip")}},_toggleTooltip:function(){t.settings.onClick.on("click",function(e){t.toggleTooltip(e)})},_enableTooltips:function(){t.settings.description.prev().append(t.settings.tooltip)},toggleTooltip:function(t){e(t.currentTarget).next(".butterbean-description").slideToggle()},hideTooltips:function(){t.settings.description.hide()}},t=o.Tooltips}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t;t=BOLDGRID.SEO,t.Init={load:function(){_.each(t,function(e){return e.init&&e.init()})}}}(jQuery),BOLDGRID.SEO.Init.load();
assets/js/bgseo/boldgrid-seo-editor.js ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ( function ( $ ) {
2
+
3
+ 'use strict';
4
+
5
+ var self, api, report;
6
+
7
+ api = BOLDGRID.SEO;
8
+ report = api.report;
9
+
10
+ /**
11
+ * BoldGrid Editor Interface.
12
+ *
13
+ * This class allows us to control which editor interface functions will run.
14
+ * On first load the it runs the setup method of whichever ui is currently loaded.
15
+ * It then assigns that ui to this classes ui variable for cross api use.
16
+ *
17
+ * @since 1.6.0
18
+ */
19
+ api.Editor = {
20
+
21
+ /**
22
+ * Interface loaded.
23
+ *
24
+ * @since 1.6.0
25
+ *
26
+ * @type {object} seo.tinymce or seo-Gutenberg
27
+ */
28
+ ui: null,
29
+
30
+ /**
31
+ * WP Element to use to trigger events.
32
+ *
33
+ * @since 1.6.0
34
+ *
35
+ * @type {$} Editor jQuery Element.
36
+ */
37
+ element: null,
38
+
39
+ /**
40
+ * Setup the correct editor interface.
41
+ *
42
+ * @since 1.3.1
43
+ */
44
+ init : function () {
45
+ self.ui = wp.data ? api.Gutenberg : api.TinyMCE;
46
+ self.element = $( self.ui.selector );
47
+ self.ui.setup();
48
+ self.onloadContent();
49
+ },
50
+
51
+ /**
52
+ * Runs actions on window load to prepare for analysis.
53
+ *
54
+ * This method gets the current editor in use by the user on the
55
+ * initial page load ( text editor or visual editor ), and also
56
+ * is responsible for creating the iframe preview of the page/post
57
+ * so we can get the raw html in use by the template/theme the user
58
+ * has activated.
59
+ *
60
+ * @since 1.3.1
61
+ */
62
+ onloadContent : function() {
63
+ var text,
64
+ editor = $( '#content.wp-editor-area[aria-hidden=false]' );
65
+
66
+ $( window ).on( 'load bgseo-media-inserted', function() {
67
+ var content = self.ui.getContent();
68
+
69
+ // Get rendered page content from frontend site.
70
+ self.getRenderedContent();
71
+
72
+ // Trigger the content analysis for the content.
73
+ _.defer( function() {
74
+ self.element.trigger( 'bgseo-analysis', [content] );
75
+ } );
76
+ } );
77
+ },
78
+
79
+ /**
80
+ * Only ajax for preview if permalink is available. This only
81
+ * impacts "New" page and posts. To counter
82
+ * this we will disable the checks made until the content has had
83
+ * a chance to be updated. We will store the found headings minus
84
+ * the initial found headings in the content, so we know what the
85
+ * template has in use on the actual rendered page.
86
+ *
87
+ * @since 1.3.1
88
+ *
89
+ * @returns null No return.
90
+ */
91
+ getRenderedContent : function() {
92
+ var renderedContent, preview;
93
+
94
+ // Get the preview url from WordPress.
95
+ preview = $( api.Editor.ui.previewSelector ).attr( 'href' );
96
+
97
+ if ( ! api.Editor.ui.isNewPost() ) {
98
+ // Only run this once after the initial iframe has loaded to get current template stats.
99
+ $.get( preview, function( renderedTemplate ) {
100
+ var headings, h1, h2, $rendered;
101
+
102
+ // The rendered page content.
103
+ $rendered = $( renderedTemplate );
104
+
105
+ // H1's that appear in rendered content.
106
+ h1 = $rendered.find( 'h1' );
107
+ // HS's that appear in rendered content.
108
+ h2 = $rendered.find( 'h2' );
109
+
110
+ // The rendered content stats.
111
+ renderedContent = {
112
+ h1Count : h1.length - report.rawstatistics.h1Count,
113
+ h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
114
+ return ! _.findWhere( report.rawstatistics.h1text, obj );
115
+ }),
116
+ h2Count : h2.length - report.rawstatistics.h2Count,
117
+ h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
118
+ return ! _.findWhere( report.rawstatistics.h2text, obj );
119
+ }),
120
+ };
121
+
122
+ // Add the rendered stats to our report for use later.
123
+ _.extend( report, { rendered : renderedContent } );
124
+
125
+ // Trigger the SEO report to rebuild in the template after initial stats are created.
126
+ self.triggerAnalysis();
127
+
128
+ }, 'html' );
129
+ }
130
+ },
131
+
132
+ /**
133
+ * Strips out unwanted html.
134
+ *
135
+ * This is helpful in removing the remaining traces of HTML
136
+ * that is sometimes leftover to form our clean text output and
137
+ * run our text analysis on.
138
+ *
139
+ * @since 1.3.1
140
+ *
141
+ * @returns {string} The content with any remaining html removed.
142
+ */
143
+ stripper : function( html ) {
144
+ var tmp;
145
+
146
+ tmp = document.implementation.createHTMLDocument( 'New' ).body;
147
+ tmp.innerHTML = html;
148
+
149
+ return tmp.textContent || tmp.innerText || " ";
150
+ },
151
+
152
+ /**
153
+ * Fire an event that will force, analysis to run.
154
+ *
155
+ * @since 1.6.0
156
+ */
157
+ triggerAnalysis: function() {
158
+ self.element.trigger( 'bgseo-analysis', [ self.ui.getContent() ] );
159
+ }
160
+ };
161
+
162
+ self = api.Editor;
163
+
164
+ })( jQuery );
assets/js/bgseo/boldgrid-seo-gutenberg.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ( function ( $ ) {
2
+
3
+ 'use strict';
4
+
5
+ var self, api;
6
+
7
+ api = BOLDGRID.SEO;
8
+
9
+ /**
10
+ * BoldGrid Gutenberg Analysis.
11
+ *
12
+ * This is responsible for generating the actual reports
13
+ * displayed within the BoldGrid SEO Dashboard when the user
14
+ * is on a page or a post.
15
+ *
16
+ * @since 1.3.1
17
+ */
18
+ api.Gutenberg = {
19
+
20
+ /**
21
+ * Selector to find editor id.
22
+ *
23
+ * This is only used to trigger events. No dom content queries in Gutenberg context.
24
+ *
25
+ * @since 1.6.0
26
+ *
27
+ * @type {String}
28
+ */
29
+ selector : '#editor',
30
+
31
+ /**
32
+ * Selector to find preview button.
33
+ *
34
+ * @since 1.6.0
35
+ *
36
+ * @type {String}
37
+ */
38
+ previewSelector : '.editor-post-preview',
39
+
40
+ /**
41
+ * Initialize Content.
42
+ *
43
+ * @since 1.6.0
44
+ */
45
+ setup : function () {
46
+ $( api.Editor.triggerAnalysis );
47
+ self._setupEditorChange();
48
+ },
49
+
50
+ /**
51
+ * Are we currently on a new post?
52
+ *
53
+ * @since 1.6.0
54
+ *
55
+ * @return {boolean} Is this a post-new.php?
56
+ */
57
+ isNewPost : function() {
58
+ return wp.data.select( 'core/editor' ).isCleanNewPost();
59
+ },
60
+
61
+ /**
62
+ * Gets the content from the editor for analysis.
63
+ *
64
+ * @since 1.6.0
65
+ *
66
+ * @returns {Object} content Contains content in raw and text formats.
67
+ */
68
+ getContent : function() {
69
+ var content = self.getRawText();
70
+
71
+ // Stores raw and stripped down versions of the content for analysis.
72
+ content = {
73
+ 'raw': content,
74
+ 'text': api.Editor.stripper( content.toLowerCase() ),
75
+ };
76
+
77
+ return content;
78
+ },
79
+
80
+ /**
81
+ * Get the raw text from the editor.
82
+ *
83
+ * @since 1.6.0
84
+ *
85
+ * @return {string} Editor content.
86
+ */
87
+ getRawText : function () {
88
+ return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'content' );
89
+ },
90
+
91
+ /**
92
+ * Listens for changes made in the text editor mode.
93
+ *
94
+ * @since 1.6.0
95
+ *
96
+ * @returns {string} text The new content to perform analysis on.
97
+ */
98
+ _setupEditorChange: function() {
99
+ var latestContent = '';
100
+
101
+ wp.data.subscribe( _.debounce( function () {
102
+
103
+ // Make sure content is different before running analysis.
104
+ if ( self.getRawText() !== latestContent ) {
105
+ api.Wordcount.update();
106
+ api.Editor.triggerAnalysis();
107
+ latestContent = self.getRawText();
108
+ }
109
+ }, 1000 ) );
110
+ }
111
+ };
112
+
113
+ self = api.Gutenberg;
114
+
115
+ })( jQuery );
assets/js/bgseo/boldgrid-seo-headings.js CHANGED
@@ -56,7 +56,7 @@
56
  _checkbox : function() {
57
  // Listen for changes to input value.
58
  self.settings.displayTitle.on( 'change', _.debounce( function() {
59
- $( this ).trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
60
  }, 1000 ) );
61
  },
62
 
@@ -228,10 +228,11 @@
228
  },
229
  };
230
 
231
- content = api.TinyMCE.getContent();
 
232
 
233
- h1s = $( content.raw ).find( 'h1' );
234
- h2s = $( content.raw ).find( 'h2' );
235
 
236
  // If no h1s or h2s are found return the defaults.
237
  if ( ! h1s.length && ! h2s.length ) return headings;
56
  _checkbox : function() {
57
  // Listen for changes to input value.
58
  self.settings.displayTitle.on( 'change', _.debounce( function() {
59
+ $( this ).trigger( 'bgseo-analysis', [ api.Editor.ui.getContent() ] );
60
  }, 1000 ) );
61
  },
62
 
228
  },
229
  };
230
 
231
+ content = api.Editor.ui.getContent();
232
+ content = $( '<div>' + content.raw + '</div>' );
233
 
234
+ h1s = content.find( 'h1' );
235
+ h2s = content.find( 'h2' );
236
 
237
  // If no h1s or h2s are found return the defaults.
238
  if ( ! h1s.length && ! h2s.length ) return headings;
assets/js/bgseo/boldgrid-seo-keywords.js CHANGED
@@ -43,7 +43,7 @@
43
  getSettings : function() {
44
  self.settings = {
45
  keyword : $( '#bgseo-custom-keyword' ),
46
- content : $( '#content' ),
47
  };
48
  },
49
 
@@ -150,7 +150,7 @@
150
  keyword = keyword.toLowerCase();
151
 
152
  keywordCount = self.keywordCount( content, keyword );
153
- wordCount = api.Report.getWordCount();
154
  // Get the density.
155
  result = ( ( keywordCount / wordCount ) * 100 );
156
  // Round it off.
@@ -268,7 +268,7 @@
268
  */
269
  getKeyword : function() {
270
  var customKeyword,
271
- content = api.TinyMCE.getContent();
272
 
273
  if ( self.getCustomKeyword().length ) {
274
  customKeyword = self.getCustomKeyword();
@@ -300,9 +300,7 @@
300
  var count;
301
 
302
  if ( _.isUndefined( markup ) ) {
303
- markup = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ?
304
- api.Words.words( self.settings.content.val() ) :
305
- api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
306
  }
307
 
308
  count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
43
  getSettings : function() {
44
  self.settings = {
45
  keyword : $( '#bgseo-custom-keyword' ),
46
+ content : api.Editor.element,
47
  };
48
  },
49
 
150
  keyword = keyword.toLowerCase();
151
 
152
  keywordCount = self.keywordCount( content, keyword );
153
+ wordCount = api.Wordcount.count;
154
  // Get the density.
155
  result = ( ( keywordCount / wordCount ) * 100 );
156
  // Round it off.
268
  */
269
  getKeyword : function() {
270
  var customKeyword,
271
+ content = api.Editor.ui.getRawText();
272
 
273
  if ( self.getCustomKeyword().length ) {
274
  customKeyword = self.getCustomKeyword();
300
  var count;
301
 
302
  if ( _.isUndefined( markup ) ) {
303
+ markup = api.Words.words( api.Editor.ui.getRawText() );
 
 
304
  }
305
 
306
  count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
assets/js/bgseo/boldgrid-seo-report.js CHANGED
@@ -8,7 +8,7 @@
8
  report = api.report;
9
 
10
  /**
11
- * BoldGrid TinyMCE Analysis.
12
  *
13
  * This is responsible for generating the actual reports
14
  * displayed within the BoldGrid SEO Dashboard when the user
@@ -19,7 +19,7 @@
19
  api.Report = {
20
 
21
  /**
22
- * Initialize TinyMCE Content.
23
  *
24
  * @since 1.3.1
25
  */
@@ -45,16 +45,10 @@
45
  getSettings : function() {
46
  self.settings = {
47
  title : $( '#boldgrid-seo-field-meta_title' ),
48
- description : $( '#boldgrid-seo-field-meta_description' ),
49
- wordCounter : $( '#wp-word-count .word-count' ),
50
- content : $( '#content' ),
51
  };
52
  },
53
 
54
- getWordCount : function() {
55
- return Number( self.settings.wordCounter.text() );
56
- },
57
-
58
  /**
59
  * Generate the Report based on analysis done.
60
  *
@@ -86,10 +80,10 @@
86
  if ( eventInfo ) {
87
  // Listen for changes to raw HTML in editor.
88
  if ( eventInfo.raw ) {
89
- var raws = eventInfo.raw;
90
 
91
- var h1 = $( raws ).find( 'h1' ),
92
- h2 = $( raws ).find( 'h2' ),
93
  headings = {};
94
 
95
  headings = {
@@ -97,7 +91,7 @@
97
  h1text : api.Headings.getHeadingText( h1 ),
98
  h2Count : h2.length,
99
  h2text : api.Headings.getHeadingText( h2 ),
100
- imageCount: $( raws ).find( 'img' ).length,
101
  };
102
  // Set the heading counts and image count found in new content update.
103
  _( report.rawstatistics ).extend( headings );
@@ -116,7 +110,7 @@
116
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
117
  },
118
  keywordContent : {
119
- lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
120
  },
121
  keywordHeadings : {
122
  length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
@@ -130,7 +124,7 @@
130
  if ( eventInfo.text ) {
131
  var kw, headingCount = api.Headings.getRealHeadingCount(),
132
  content = eventInfo.text,
133
- raw = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ? api.Words.words( self.settings.content.val() ) : api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
134
 
135
  // Get length of title field.
136
  titleLength = self.settings.title.val().length;
@@ -188,7 +182,7 @@
188
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
189
  },
190
  keywordContent : {
191
- lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
192
  },
193
  keywordHeadings : {
194
  length : api.Headings.keywords( headingCount ),
@@ -200,8 +194,8 @@
200
  },
201
  headings : headingCount,
202
  wordCount : {
203
- length : self.getWordCount(),
204
- lengthScore : api.ContentAnalysis.seoContentLengthScore( self.getWordCount() ),
205
  },
206
  sectionScore: {},
207
  sectionStatus: {},
@@ -230,7 +224,7 @@
230
  _( report.bgseo_keywords.keywordTitle ).extend({
231
  lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
232
  });
233
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
234
  }
235
 
236
  // Listen to changes to the SEO Description and update report.
@@ -248,7 +242,7 @@
248
  _( report.bgseo_keywords.keywordDescription ).extend({
249
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
250
  });
251
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
252
  }
253
 
254
  // Listen for changes to noindex/index and update report.
@@ -256,7 +250,7 @@
256
  _( report.bgseo_visibility.robotIndex ).extend({
257
  lengthScore : eventInfo.robotIndex,
258
  });
259
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
260
  }
261
 
262
  // Listen for changes to nofollow/follow and update report.
@@ -264,12 +258,12 @@
264
  _( report.bgseo_visibility.robotFollow ).extend({
265
  lengthScore : eventInfo.robotFollow,
266
  });
267
- self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
268
  }
269
  }
270
 
271
  // Send the final analysis to display the report.
272
- self.settings.content.trigger( 'bgseo-report', [ report ] );
273
  });
274
  },
275
 
8
  report = api.report;
9
 
10
  /**
11
+ * BoldGrid Editor Content Analysis.
12
  *
13
  * This is responsible for generating the actual reports
14
  * displayed within the BoldGrid SEO Dashboard when the user
19
  api.Report = {
20
 
21
  /**
22
+ * Initialize Content.
23
  *
24
  * @since 1.3.1
25
  */
45
  getSettings : function() {
46
  self.settings = {
47
  title : $( '#boldgrid-seo-field-meta_title' ),
48
+ description : $( '#boldgrid-seo-field-meta_description' )
 
 
49
  };
50
  },
51
 
 
 
 
 
52
  /**
53
  * Generate the Report based on analysis done.
54
  *
80
  if ( eventInfo ) {
81
  // Listen for changes to raw HTML in editor.
82
  if ( eventInfo.raw ) {
83
+ var $raws = $( '<div>' + eventInfo.raw + '</div>' );
84
 
85
+ var h1 = $raws.find( 'h1' ),
86
+ h2 = $raws.find( 'h2' ),
87
  headings = {};
88
 
89
  headings = {
91
  h1text : api.Headings.getHeadingText( h1 ),
92
  h2Count : h2.length,
93
  h2text : api.Headings.getHeadingText( h2 ),
94
+ imageCount: $raws.find( 'img' ).length,
95
  };
96
  // Set the heading counts and image count found in new content update.
97
  _( report.rawstatistics ).extend( headings );
110
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
111
  },
112
  keywordContent : {
113
+ lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
114
  },
115
  keywordHeadings : {
116
  length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
124
  if ( eventInfo.text ) {
125
  var kw, headingCount = api.Headings.getRealHeadingCount(),
126
  content = eventInfo.text,
127
+ raw = api.Editor.ui.getRawText();
128
 
129
  // Get length of title field.
130
  titleLength = self.settings.title.val().length;
182
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
183
  },
184
  keywordContent : {
185
+ lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
186
  },
187
  keywordHeadings : {
188
  length : api.Headings.keywords( headingCount ),
194
  },
195
  headings : headingCount,
196
  wordCount : {
197
+ length : api.Wordcount.count,
198
+ lengthScore : api.ContentAnalysis.seoContentLengthScore( api.Wordcount.count ),
199
  },
200
  sectionScore: {},
201
  sectionStatus: {},
224
  _( report.bgseo_keywords.keywordTitle ).extend({
225
  lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
226
  });
227
+ api.Editor.triggerAnalysis();
228
  }
229
 
230
  // Listen to changes to the SEO Description and update report.
242
  _( report.bgseo_keywords.keywordDescription ).extend({
243
  lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
244
  });
245
+ api.Editor.triggerAnalysis();
246
  }
247
 
248
  // Listen for changes to noindex/index and update report.
250
  _( report.bgseo_visibility.robotIndex ).extend({
251
  lengthScore : eventInfo.robotIndex,
252
  });
253
+ api.Editor.triggerAnalysis();
254
  }
255
 
256
  // Listen for changes to nofollow/follow and update report.
258
  _( report.bgseo_visibility.robotFollow ).extend({
259
  lengthScore : eventInfo.robotFollow,
260
  });
261
+ api.Editor.triggerAnalysis();
262
  }
263
  }
264
 
265
  // Send the final analysis to display the report.
266
+ api.Editor.element.trigger( 'bgseo-report', [ report ] );
267
  });
268
  },
269
 
assets/js/bgseo/boldgrid-seo-tinymce.js CHANGED
@@ -2,10 +2,9 @@
2
 
3
  'use strict';
4
 
5
- var self, report, api;
6
 
7
  api = BOLDGRID.SEO;
8
- report = api.report;
9
 
10
  /**
11
  * BoldGrid TinyMCE Analysis.
@@ -19,42 +18,32 @@
19
  api.TinyMCE = {
20
 
21
  /**
22
- * Initialize TinyMCE Content.
23
  *
24
- * @since 1.3.1
 
 
25
  */
26
- init : function () {
27
- self.onloadContent();
28
- $( document ).ready( function() {
29
- self.editorChange();
30
- });
31
- },
32
 
33
  /**
34
- * Runs actions on window load to prepare for analysis.
35
  *
36
- * This method gets the current editor in use by the user on the
37
- * initial page load ( text editor or visual editor ), and also
38
- * is responsible for creating the iframe preview of the page/post
39
- * so we can get the raw html in use by the template/theme the user
40
- * has activated.
41
  *
42
- * @since 1.3.1
43
  */
44
- onloadContent: function() {
45
- var text,
46
- editor = $( '#content.wp-editor-area[aria-hidden=false]' );
47
-
48
- $( window ).on( 'load bgseo-media-inserted', function() {
49
- var content = self.getContent();
50
 
51
- // Get rendered page content from frontend site.
52
- self.getRenderedContent();
53
-
54
- // Trigger the content analysis for the tinyMCE content.
55
- _.defer( function() {
56
- $( '#content' ).trigger( 'bgseo-analysis', [content] );
57
- });
 
 
58
  });
59
  },
60
 
@@ -67,11 +56,11 @@
67
  */
68
  getContent : function() {
69
  var content;
70
- // Get the content of the visual editor or text editor that's present.
71
  if ( tinymce.ActiveEditor ) {
72
  content = tinyMCE.get( wpActiveEditor ).getContent();
73
  } else {
74
- content = $( '#content' ).val();
75
  // Remove newlines and carriage returns.
76
  content = content.replace( /\r?\n|\r/g, '' );
77
  }
@@ -81,64 +70,33 @@
81
  // Stores raw and stripped down versions of the content for analysis.
82
  content = {
83
  'raw': rawContent,
84
- 'text': self.stripper( content.toLowerCase() ),
85
  };
86
 
87
  return content;
88
  },
89
 
 
90
  /**
91
- * Only ajax for preview if permalink is available. This only
92
- * impacts "New" page and posts. To counter
93
- * this we will disable the checks made until the content has had
94
- * a chance to be updated. We will store the found headings minus
95
- * the initial found headings in the content, so we know what the
96
- * template has in use on the actual rendered page.
97
  *
98
- * @since 1.3.1
99
  *
100
- * @returns null No return.
101
  */
102
- getRenderedContent : function() {
103
- var renderedContent, preview;
104
-
105
- // Get the preview url from WordPress.
106
- preview = $( '#preview-action > .preview.button' ).attr( 'href' );
107
-
108
- if ( $( '#sample-permalink' ).length ) {
109
- // Only run this once after the initial iframe has loaded to get current template stats.
110
- $.get( preview, function( renderedTemplate ) {
111
- var headings, h1, h2, $rendered;
112
-
113
- // The rendered page content.
114
- $rendered = $( renderedTemplate );
115
-
116
- // H1's that appear in rendered content.
117
- h1 = $rendered.find( 'h1' );
118
- // HS's that appear in rendered content.
119
- h2 = $rendered.find( 'h2' );
120
-
121
- // The rendered content stats.
122
- renderedContent = {
123
- h1Count : h1.length - report.rawstatistics.h1Count,
124
- h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
125
- return ! _.findWhere( report.rawstatistics.h1text, obj );
126
- }),
127
- h2Count : h2.length - report.rawstatistics.h2Count,
128
- h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
129
- return ! _.findWhere( report.rawstatistics.h2text, obj );
130
- }),
131
- };
132
-
133
- // Add the rendered stats to our report for use later.
134
- _.extend( report, { rendered : renderedContent } );
135
-
136
- // Trigger the SEO report to rebuild in the template after initial stats are created.
137
- $( '#content' ).trigger( 'bgseo-analysis', [ self.getContent() ] );
138
-
139
- }, 'html' );
140
  }
 
 
141
  },
 
142
  /**
143
  * Listens for changes made in the text editor mode.
144
  *
@@ -172,6 +130,17 @@
172
  return text;
173
  },
174
 
 
 
 
 
 
 
 
 
 
 
 
175
  /**
176
  * Checks which editor is the active editor.
177
  *
@@ -192,7 +161,7 @@
192
  text = tinyMCE.get( wpActiveEditor ).getContent();
193
  break;
194
  case 'content' :
195
- text = $( '#content' ).val();
196
  text = text.replace( /\r?\n|\r/g, '' );
197
  break;
198
  }
@@ -202,32 +171,32 @@
202
 
203
  text = {
204
  'raw': rawText,
205
- 'text': self.stripper( text.toLowerCase() ),
206
  };
207
 
208
  // Trigger the text analysis for report.
209
- $( '#content' ).trigger( 'bgseo-analysis', [text] );
210
  },
211
 
212
  /**
213
- * Strips out unwanted html.
214
  *
215
- * This is helpful in removing the remaining traces of HTML
216
- * that is sometimes leftover to form our clean text output and
217
- * run our text analysis on.
218
- *
219
- * @since 1.3.1
220
- *
221
- * @returns {string} The content with any remaining html removed.
222
  */
223
- stripper: function( html ) {
224
- var tmp;
225
 
226
- tmp = document.implementation.createHTMLDocument( 'New' ).body;
227
- tmp.innerHTML = html;
 
 
 
 
 
 
 
 
228
 
229
- return tmp.textContent || tmp.innerText || " ";
230
- },
231
  };
232
 
233
  self = api.TinyMCE;
2
 
3
  'use strict';
4
 
5
+ var self, api;
6
 
7
  api = BOLDGRID.SEO;
 
8
 
9
  /**
10
  * BoldGrid TinyMCE Analysis.
18
  api.TinyMCE = {
19
 
20
  /**
21
+ * Selector to find editor id.
22
  *
23
+ * @since 1.6.0
24
+ *
25
+ * @type {String}
26
  */
27
+ selector : '#content',
 
 
 
 
 
28
 
29
  /**
30
+ * Selector to find preview button.
31
  *
32
+ * @since 1.6.0
 
 
 
 
33
  *
34
+ * @type {String}
35
  */
36
+ previewSelector : '#preview-action > .preview.button',
 
 
 
 
 
37
 
38
+ /**
39
+ * Initialize TinyMCE Content.
40
+ *
41
+ * @since 1.3.1
42
+ */
43
+ setup : function () {
44
+ $( document ).ready( function() {
45
+ self._setupWordCount();
46
+ self.editorChange();
47
  });
48
  },
49
 
56
  */
57
  getContent : function() {
58
  var content;
59
+
60
  if ( tinymce.ActiveEditor ) {
61
  content = tinyMCE.get( wpActiveEditor ).getContent();
62
  } else {
63
+ content = api.Editor.element.val();
64
  // Remove newlines and carriage returns.
65
  content = content.replace( /\r?\n|\r/g, '' );
66
  }
70
  // Stores raw and stripped down versions of the content for analysis.
71
  content = {
72
  'raw': rawContent,
73
+ 'text': api.Editor.stripper( content.toLowerCase() ),
74
  };
75
 
76
  return content;
77
  },
78
 
79
+
80
  /**
81
+ * Get the raw text from the editor.
 
 
 
 
 
82
  *
83
+ * @since 1.6.0
84
  *
85
+ * @return {string} Editor content.
86
  */
87
+ getRawText: function() {
88
+ var text,
89
+ contentEditor = tinyMCE.get( wpActiveEditor );
90
+
91
+ if ( ! contentEditor || contentEditor.isHidden() ) {
92
+ text = api.Editor.element.val();
93
+ } else {
94
+ text = contentEditor.getContent( { format: 'raw' } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
+
97
+ return text;
98
  },
99
+
100
  /**
101
  * Listens for changes made in the text editor mode.
102
  *
130
  return text;
131
  },
132
 
133
+ /**
134
+ * Is this a new Post?
135
+ *
136
+ * @since 1.6.0
137
+ *
138
+ * @return {boolean} Is this post-new.php?
139
+ */
140
+ isNewPost : function () {
141
+ return ! $( '#sample-permalink' ).length;
142
+ },
143
+
144
  /**
145
  * Checks which editor is the active editor.
146
  *
161
  text = tinyMCE.get( wpActiveEditor ).getContent();
162
  break;
163
  case 'content' :
164
+ text = api.Editor.element.val();
165
  text = text.replace( /\r?\n|\r/g, '' );
166
  break;
167
  }
171
 
172
  text = {
173
  'raw': rawText,
174
+ 'text': api.Editor.stripper( text.toLowerCase() ),
175
  };
176
 
177
  // Trigger the text analysis for report.
178
+ api.Editor.element.trigger( 'bgseo-analysis', [text] );
179
  },
180
 
181
  /**
182
+ * Bind events to the editor input and update the wordcount class.
183
  *
184
+ * @since 1.6.0
 
 
 
 
 
 
185
  */
186
+ _setupWordCount : function() {
187
+ var debouncedCb = _.debounce( api.Wordcount.update, 1000 );
188
 
189
+ $( document ).on( 'tinymce-editor-init', function( event, editor ) {
190
+ if ( editor.id !== 'content' ) {
191
+ return;
192
+ }
193
+
194
+ editor.on( 'AddUndo keyup', debouncedCb );
195
+ } );
196
+
197
+ api.Editor.element.on( 'input keyup', debouncedCb );
198
+ }
199
 
 
 
200
  };
201
 
202
  self = api.TinyMCE;
assets/js/bgseo/boldgrid-seo-wordcount.js CHANGED
@@ -1,46 +1,67 @@
1
  ( function( $, counter ) {
2
 
3
- $( function() {
4
 
5
- var $content = $( '#content' ),
6
- $count = $( '#wp-word-count' ).find( '.word-count' ),
7
- prevCount = 0,
8
- contentEditor,
9
- words;
10
 
11
- function update() {
12
- var text, count;
13
 
14
- if ( ! contentEditor || contentEditor.isHidden() ) {
15
- text = $content.val();
16
- } else {
17
- text = contentEditor.getContent( { format: 'raw' } );
18
- }
 
19
 
20
- count = counter.count( text );
21
- words = BOLDGRID.SEO.Words.words( text );
 
 
 
 
 
 
22
 
23
- if ( count !== prevCount ) {
24
- $content.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
25
- }
 
 
 
 
 
26
 
27
- prevCount = count;
28
- }
 
 
 
 
 
 
29
 
30
- $( document ).on( 'tinymce-editor-init', function( event, editor ) {
31
- if ( editor.id !== 'content' ) {
32
- return;
33
- }
 
 
 
 
 
34
 
35
- contentEditor = editor;
36
-
37
- editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
38
- } );
39
 
40
- $content.on( 'input keyup', _.debounce( update, 1000 ) );
 
 
41
 
42
- update();
 
 
 
43
 
44
- } );
45
 
46
  } )( jQuery, new wp.utils.WordCounter() );
1
  ( function( $, counter ) {
2
 
3
+ 'use strict';
4
 
5
+ var self, api;
 
 
 
 
6
 
7
+ api = BOLDGRID.SEO;
 
8
 
9
+ /**
10
+ * Handle tracking of wordcount.
11
+ *
12
+ * @since 1.6.0
13
+ */
14
+ api.Wordcount = {
15
 
16
+ /**
17
+ * Number of words in the content.
18
+ *
19
+ * @since 1.6.0
20
+ *
21
+ * @type {Number}
22
+ */
23
+ count: 0,
24
 
25
+ /**
26
+ * List of words on the page.
27
+ *
28
+ * @since 1.6.0
29
+ *
30
+ * @type {array}
31
+ */
32
+ words: [],
33
 
34
+ /**
35
+ * When the page loads, run the update methods.
36
+ *
37
+ * @since 1.6.0
38
+ */
39
+ init : function () {
40
+ $( self.update );
41
+ },
42
 
43
+ /**
44
+ * Update this classes word count metrics.
45
+ *
46
+ * @since 1.6.0
47
+ */
48
+ update : function () {
49
+ var count,
50
+ words,
51
+ text = api.Editor.ui.getRawText();
52
 
53
+ count = counter.count( text );
54
+ words = BOLDGRID.SEO.Words.words( text );
 
 
55
 
56
+ if ( count !== self.count ) {
57
+ api.Editor.element.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
58
+ }
59
 
60
+ self.words = words;
61
+ self.count = count;
62
+ }
63
+ };
64
 
65
+ self = api.Wordcount;
66
 
67
  } )( jQuery, new wp.utils.WordCounter() );
assets/js/text-statistics/README.md CHANGED
@@ -6,10 +6,8 @@ JavaScript port of [TextStatistics.php](https://github.com/DaveChild/Text-Statis
6
  I've done what I think is a reasonably faithful port. Documentation incoming!
7
  I removed a lot of the original comments during the port, but seeing as the API remained largely the same, I'll add them in shortly.
8
 
9
- The beginning of a test suite in [Mocha](https://mochajs.org/) is here, covering cleaning the text and some cases of word and sentence counting.
10
 
11
  ## Installation
12
 
13
- Run this in the browser using a simple `<script>` include - or you can install for node with `npm install text-statistics`.
14
-
15
- **[Famous! As seen in Time!](http://time.com/2958650/twitter-reading-level/)** (heh.)
6
  I've done what I think is a reasonably faithful port. Documentation incoming!
7
  I removed a lot of the original comments during the port, but seeing as the API remained largely the same, I'll add them in shortly.
8
 
9
+ Same goes for a test suite - I'll get something working in node in a bit. :)
10
 
11
  ## Installation
12
 
13
+ Run this in the browser using a simple `<script>` include - or you can install for node with `npm install text-statistics`.
 
 
assets/js/text-statistics/index.js CHANGED
@@ -12,23 +12,21 @@
12
 
13
  fullStopTags.forEach(function(tag) {
14
  text = text.replace("</" + tag + ">",".");
15
- });
16
 
17
  text = text
18
  .replace(/<[^>]+>/g, "") // Strip tags
19
- .replace(/[,:;()\/&+]|\-\-/g, " ") // Replace commas, hyphens etc (count them as spaces)
20
- .replace(/[\.!?]/g, ".") // Unify terminators
21
- .replace(/^\s+/, "") // Strip leading whitespace
22
- .replace(/[\.]?(\w+)[\.]?(\w+)@(\w+)[\.](\w+)[\.]?/g, "$1$2@$3$4") // strip periods in email addresses (so they remain counted as one word)
23
- .replace(/[ ]*(\n|\r\n|\r)[ ]*/g, ".") // Replace new lines with periods
24
- .replace(/([\.])[\.]+/g, ".") // Check for duplicated terminators
25
- .replace(/[ ]*([\.])/g, ". ") // Pad sentence terminators
26
- .replace(/\s+/g, " ") // Remove multiple spaces
27
- .replace(/\s+$/, ""); // Strip trailing whitespace
28
 
29
- if(text.slice(-1) != '.') {
30
- text += "."; // Add final terminator, just in case it's missing.
31
- }
32
  return text;
33
  }
34
 
@@ -86,7 +84,7 @@
86
 
87
  TextStatistics.prototype.wordCount = function(text) {
88
  text = text ? cleanText(text) : this.text;
89
- return text.split(/[^a-z0-9\'@\.\-]+/i).length || 1;
90
  };
91
 
92
  TextStatistics.prototype.averageWordsPerSentence = function(text) {
@@ -147,7 +145,7 @@
147
  };
148
 
149
  // Return if we've hit one of those...
150
- if (problemWords.hasOwnProperty(word)) return problemWords[word];
151
 
152
  // These syllables would be counted as two but should be one
153
  var subSyllables = [
@@ -212,7 +210,7 @@
212
  wordPartCount = word
213
  .split(/[^aeiouy]+/ig)
214
  .filter(function(wordPart) {
215
- return !!wordPart.replace(/\s+/ig,"").length;
216
  })
217
  .length;
218
 
@@ -236,4 +234,4 @@
236
  }
237
 
238
  (typeof module != "undefined" && module.exports) ? (module.exports = textStatistics) : (typeof define != "undefined" ? (define("textstatistics", [], function() { return textStatistics; })) : (glob.textstatistics = textStatistics));
239
- })(this);
12
 
13
  fullStopTags.forEach(function(tag) {
14
  text = text.replace("</" + tag + ">",".");
15
+ })
16
 
17
  text = text
18
  .replace(/<[^>]+>/g, "") // Strip tags
19
+ .replace(/[,:;()\-]/, " ") // Replace commans, hyphens etc (count them as spaces)
20
+ .replace(/[\.!?]/, ".") // Unify terminators
21
+ .replace(/^\s+/,"") // Strip leading whitespace
22
+ .replace(/[ ]*(\n|\r\n|\r)[ ]*/," ") // Replace new lines with spaces
23
+ .replace(/([\.])[\. ]+/,".") // Check for duplicated terminators
24
+ .replace(/[ ]*([\.])/,". ") // Pad sentence terminators
25
+ .replace(/\s+/," ") // Remove multiple spaces
26
+ .replace(/\s+$/,""); // Strip trailing whitespace
 
27
 
28
+ text += "."; // Add final terminator, just in case it's missing.
29
+
 
30
  return text;
31
  }
32
 
84
 
85
  TextStatistics.prototype.wordCount = function(text) {
86
  text = text ? cleanText(text) : this.text;
87
+ return text.split(/[^a-z0-9]+/i).length || 1;
88
  };
89
 
90
  TextStatistics.prototype.averageWordsPerSentence = function(text) {
145
  };
146
 
147
  // Return if we've hit one of those...
148
+ if (problemWords[word]) return problemWords[word];
149
 
150
  // These syllables would be counted as two but should be one
151
  var subSyllables = [
210
  wordPartCount = word
211
  .split(/[^aeiouy]+/ig)
212
  .filter(function(wordPart) {
213
+ return !!wordPart.replace(/\s+/ig,"").length
214
  })
215
  .length;
216
 
234
  }
235
 
236
  (typeof module != "undefined" && module.exports) ? (module.exports = textStatistics) : (typeof define != "undefined" ? (define("textstatistics", [], function() { return textStatistics; })) : (glob.textstatistics = textStatistics));
237
+ })(this);
boldgrid-easy-seo.php CHANGED
@@ -14,8 +14,8 @@
14
  * Plugin Name: BoldGrid Easy SEO
15
  * Plugin URI: https://www.boldgrid.com/boldgrid-seo/
16
  * Description: Easily manage your website's search engine optimization with Easy SEO by BoldGrid!
17
- * Version: 1.5.1
18
- * Author: BoldGrid.com <wpb@boldgrid.com>
19
  * Author URI: https://www.boldgrid.com/
20
  * License: GPL-2.0+
21
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
14
  * Plugin Name: BoldGrid Easy SEO
15
  * Plugin URI: https://www.boldgrid.com/boldgrid-seo/
16
  * Description: Easily manage your website's search engine optimization with Easy SEO by BoldGrid!
17
+ * Version: 1.6.0-rc.1
18
+ * Author: BoldGrid <support@boldgrid.com>
19
  * Author URI: https://www.boldgrid.com/
20
  * License: GPL-2.0+
21
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
includes/class-boldgrid-seo-butterbean.php CHANGED
@@ -67,11 +67,15 @@ class Boldgrid_Seo_Butterbean {
67
  /* === Register Settings === */
68
  $manager->register_setting(
69
  'bgseo_title',
70
- array( 'sanitize_callback' => 'wp_filter_nohtml_kses' )
 
 
71
  );
72
  $manager->register_setting(
73
  'bgseo_description',
74
- array( 'sanitize_callback' => 'wp_kses_post' )
 
 
75
  );
76
  $manager->register_setting(
77
  'bgseo_canonical',
@@ -93,7 +97,9 @@ class Boldgrid_Seo_Butterbean {
93
  );
94
  $manager->register_setting(
95
  'bgseo_custom_keyword',
96
- array( 'sanitize_callback' => 'wp_filter_nohtml_kses' )
 
 
97
  );
98
 
99
  }
67
  /* === Register Settings === */
68
  $manager->register_setting(
69
  'bgseo_title',
70
+ array( 'sanitize_callback' => function( $setting ) {
71
+ return wp_specialchars_decode( wp_filter_nohtml_kses ( $setting ) );
72
+ } )
73
  );
74
  $manager->register_setting(
75
  'bgseo_description',
76
+ array( 'sanitize_callback' => function( $setting ) {
77
+ return wp_specialchars_decode( wp_kses_post( $setting ) );
78
+ } )
79
  );
80
  $manager->register_setting(
81
  'bgseo_canonical',
97
  );
98
  $manager->register_setting(
99
  'bgseo_custom_keyword',
100
+ array( 'sanitize_callback' => function( $setting ) {
101
+ return wp_specialchars_decode( wp_filter_nohtml_kses ( $setting ) );
102
+ } )
103
  );
104
 
105
  }
includes/configs/i18n/seoDescription.config.php CHANGED
@@ -7,12 +7,12 @@ return array(
7
  '</a>'
8
  ),
9
  /* translators: 1: opening <a> tag 2: closing </a> tag */
10
- 'badLong' => sprintf( __( 'Your custom %1$sSEO Description%2$s is over the 156 character recommended length, you should consider making it shorter.', 'bgseo' ),
11
  '<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
12
  '</a>'
13
  ),
14
  /* translators: 1: opening <a> tag 2: closing </a> tag */
15
- 'ok' => sprintf( __( 'You should make your %1$sSEO Description%2$s longer! We recommend 125-156 characters for the best results.', 'bgseo' ),
16
  '<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
17
  '</a>'
18
  ),
@@ -24,7 +24,7 @@ return array(
24
  // Max value.
25
  'okScore' => 125,
26
  // Max value.
27
- 'goodScore' => 156,
28
  ),
29
  'keywordUsage' => array(
30
  /* translators: 1: opening <a> tag 2: closing </a> tag */
7
  '</a>'
8
  ),
9
  /* translators: 1: opening <a> tag 2: closing </a> tag */
10
+ 'badLong' => sprintf( __( 'Your custom %1$sSEO Description%2$s is over the 300 character recommended length, you should consider making it shorter.', 'bgseo' ),
11
  '<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
12
  '</a>'
13
  ),
14
  /* translators: 1: opening <a> tag 2: closing </a> tag */
15
+ 'ok' => sprintf( __( 'You should make your %1$sSEO Description%2$s longer! We recommend 125-300 characters for the best results.', 'bgseo' ),
16
  '<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
17
  '</a>'
18
  ),
24
  // Max value.
25
  'okScore' => 125,
26
  // Max value.
27
+ 'goodScore' => 300,
28
  ),
29
  'keywordUsage' => array(
30
  /* translators: 1: opening <a> tag 2: closing </a> tag */
includes/configs/meta-box.config.php CHANGED
@@ -51,7 +51,7 @@ return array(
51
  'attr' => array(
52
  'id' => 'boldgrid-seo-field-meta_description',
53
  'placeholder' => $this->util->meta_description(),
54
- 'maxlength' => '156',
55
  'class' => 'widefat',
56
  ),
57
  'label' => __( 'SEO Description', 'bgseo' ),
51
  'attr' => array(
52
  'id' => 'boldgrid-seo-field-meta_description',
53
  'placeholder' => $this->util->meta_description(),
54
+ 'maxlength' => '300',
55
  'class' => 'widefat',
56
  ),
57
  'label' => __( 'SEO Description', 'bgseo' ),
languages/bgseo.pot CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2017 bgseo
2
  # This file is distributed under the same license as the bgseo package.
3
  msgid ""
4
  msgstr ""
@@ -7,7 +7,7 @@ msgstr ""
7
  "MIME-Version: 1.0\n"
8
  "Content-Type: text/plain; charset=UTF-8\n"
9
  "Content-Transfer-Encoding: 8bit\n"
10
- "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
11
  "Language-Team: The BoldGrid Team <support@boldgrid.com>\n"
12
  "X-Poedit-Basepath: ..\n"
13
  "X-Poedit-SourceCharset: UTF-8\n"
@@ -181,11 +181,11 @@ msgid "Your custom %1$sSEO Description%2$s is empty! Try adding a description w
181
  msgstr ""
182
 
183
  #: includes/configs/i18n/seoDescription.config.php:10
184
- msgid "Your custom %1$sSEO Description%2$s is over the 156 character recommended length, you should consider making it shorter."
185
  msgstr ""
186
 
187
  #: includes/configs/i18n/seoDescription.config.php:15
188
- msgid "You should make your %1$sSEO Description%2$s longer! We recommend 125-156 characters for the best results."
189
  msgstr ""
190
 
191
  #: includes/configs/i18n/seoDescription.config.php:20
1
+ # Copyright (C) 2018 bgseo
2
  # This file is distributed under the same license as the bgseo package.
3
  msgid ""
4
  msgstr ""
7
  "MIME-Version: 1.0\n"
8
  "Content-Type: text/plain; charset=UTF-8\n"
9
  "Content-Transfer-Encoding: 8bit\n"
10
+ "PO-Revision-Date: 2018-MO-DA HO:MI+ZONE\n"
11
  "Language-Team: The BoldGrid Team <support@boldgrid.com>\n"
12
  "X-Poedit-Basepath: ..\n"
13
  "X-Poedit-SourceCharset: UTF-8\n"
181
  msgstr ""
182
 
183
  #: includes/configs/i18n/seoDescription.config.php:10
184
+ msgid "Your custom %1$sSEO Description%2$s is over the 300 character recommended length, you should consider making it shorter."
185
  msgstr ""
186
 
187
  #: includes/configs/i18n/seoDescription.config.php:15
188
+ msgid "You should make your %1$sSEO Description%2$s longer! We recommend 125-300 characters for the best results."
189
  msgstr ""
190
 
191
  #: includes/configs/i18n/seoDescription.config.php:20
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: boldgrid, timph, rramo012, imh_brad, joemoto
3
  Tags: seo, search engine optimization, content analysis, readability, boldgrid
4
  Requires at least: 4.4
5
- Tested up to: 4.9.1
6
  Requires PHP: 5.3
7
- Stable tag: 1.5.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -77,6 +77,14 @@ The BoldGrid Easy SEO plugin is open source software. Join in on our [GitHub rep
77
 
78
  == Changelog ==
79
 
 
 
 
 
 
 
 
 
80
  = 1.5.1 =
81
 
82
  Release Date: November 14th, 2017
2
  Contributors: boldgrid, timph, rramo012, imh_brad, joemoto
3
  Tags: seo, search engine optimization, content analysis, readability, boldgrid
4
  Requires at least: 4.4
5
+ Tested up to: 5.0
6
  Requires PHP: 5.3
7
+ Stable tag: 1.6.0-rc.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
77
 
78
  == Changelog ==
79
 
80
+ = 1.6.0 =
81
+
82
+ Release Date: November 26th, 2018
83
+
84
+ * New Feature: Added Gutenberg and WordPress 5 compatibility.
85
+ * Update: Max length for meta descriptions was updated to 300.
86
+ * Bug Fix: Save special chars for meta fields.
87
+
88
  = 1.5.1 =
89
 
90
  Release Date: November 14th, 2017