j1-template 2023.6.0 → 2023.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/_includes/themes/j1/layouts/content_generator_news_panel_posts.html +11 -9
  3. data/_includes/themes/j1/layouts/content_generator_post.html +8 -6
  4. data/_includes/themes/j1/procedures/global/create_word_cloud.proc +1 -1
  5. data/_includes/themes/j1/procedures/layouts/module_writer.proc +1 -1
  6. data/_includes/themes/j1/procedures/posts/collate_timeline.proc +4 -4
  7. data/_includes/themes/j1/procedures/posts/pager.proc +1 -1
  8. data/_layouts/_home.html +88 -0
  9. data/_layouts/home.html +1 -1
  10. data/assets/data/banner.html +7 -7
  11. data/assets/data/panel.html +37 -25
  12. data/assets/data/quicklinks.html +40 -30
  13. data/assets/data/speak2me.html +219 -0
  14. data/assets/data/translator.html +32 -4
  15. data/assets/themes/j1/adapter/js/speak2me.js +425 -0
  16. data/assets/themes/j1/adapter/js/translator.js +10 -2
  17. data/assets/themes/j1/core/css/icon-fonts/mdib.css +21 -0
  18. data/assets/themes/j1/core/css/icon-fonts/mdib.min.css +1 -1
  19. data/assets/themes/j1/core/css/themes/unodark/bootstrap.css +43 -0
  20. data/assets/themes/j1/core/css/themes/unodark/bootstrap.min.css +1 -1
  21. data/assets/themes/j1/core/css/themes/unolight/bootstrap.css +141 -1
  22. data/assets/themes/j1/core/css/themes/unolight/bootstrap.min.css +1 -1
  23. data/assets/themes/j1/core/js/template.js +1658 -0
  24. data/assets/themes/j1/core/js/template.min.js +11 -5
  25. data/assets/themes/j1/core/js/template.min.js.map +1 -1
  26. data/assets/themes/j1/modules/speak2me/LICENSE +21 -0
  27. data/assets/themes/j1/modules/speak2me/js/speak2me.js +947 -0
  28. data/assets/themes/j1/modules/speak2me/js/speak2me.min.js +26 -0
  29. data/assets/themes/j1/modules/translator/js/translator.js +30 -13
  30. data/assets/themes/j1/modules/translator/js/translator.min.js +1 -1
  31. data/lib/j1/version.rb +1 -1
  32. data/lib/starter_web/Gemfile +4 -4
  33. data/lib/starter_web/README.md +5 -5
  34. data/lib/starter_web/_config.yml +7 -1
  35. data/lib/starter_web/_data/blocks/banner.yml +7 -6
  36. data/lib/starter_web/_data/blocks/panel.yml +37 -39
  37. data/lib/starter_web/_data/layouts/home.yml +1 -1
  38. data/lib/starter_web/_data/modules/advertising.yml +10 -88
  39. data/lib/starter_web/_data/modules/buymeacoffee.yml +30 -0
  40. data/lib/starter_web/_data/modules/defaults/advertising.yml +3 -1
  41. data/lib/starter_web/_data/modules/defaults/blog_navigator.yml +97 -164
  42. data/lib/starter_web/_data/modules/defaults/buymeacoffee.yml +30 -0
  43. data/lib/starter_web/_data/modules/defaults/navigator.yml +8 -4
  44. data/lib/starter_web/_data/modules/defaults/speak2me.yml +72 -0
  45. data/lib/starter_web/_data/modules/scroller.yml +1 -1
  46. data/lib/starter_web/_data/modules/speak2me.yml +33 -0
  47. data/lib/starter_web/_data/resources.yml +24 -1
  48. data/lib/starter_web/_data/templates/feed.xml +1 -1
  49. data/lib/starter_web/_includes/google/static/{google_ad_5128488466.html → google_ad__your-slot-id.html} +1 -1
  50. data/lib/starter_web/_plugins/asciidoctor/callout.rb +1 -1
  51. data/lib/starter_web/_plugins/asciidoctor/carousel-block.rb +2 -2
  52. data/lib/starter_web/_plugins/asciidoctor/gallery-block.rb +2 -2
  53. data/lib/starter_web/_plugins/asciidoctor/gist-block.rb +2 -2
  54. data/lib/starter_web/_plugins/asciidoctor/google-ad-block.rb +1 -1
  55. data/lib/starter_web/_plugins/asciidoctor/lightbox-block.rb +2 -7
  56. data/lib/starter_web/_plugins/asciidoctor/masonry-block.rb +2 -3
  57. data/lib/starter_web/_plugins/asciidoctor/masterslider-block.rb +2 -2
  58. data/lib/starter_web/_plugins/asciidoctor/mdib-icon-inline.rb +43 -0
  59. data/lib/starter_web/_plugins/asciidoctor/mdil-icon-inline.rb +1 -1
  60. data/lib/starter_web/_plugins/asciidoctor/range-slider-block.rb +1 -1
  61. data/lib/starter_web/_plugins/asciidoctor/slick-block.rb +2 -2
  62. data/lib/starter_web/_plugins/asciidoctor/textbook-block.rb +1 -1
  63. data/lib/starter_web/_plugins/filter/liquify.rb +22 -22
  64. data/lib/starter_web/_plugins/index/lunr.rb +1 -1
  65. data/lib/starter_web/assets/images/collections/books/biography/a_life_in_questions.jpg +0 -0
  66. data/lib/starter_web/assets/images/collections/books/biography/becoming.jpg +0 -0
  67. data/lib/starter_web/assets/images/collections/books/biography/born_to_run.jpg +0 -0
  68. data/lib/starter_web/assets/images/collections/books/biography/forty_autumns.jpg +0 -0
  69. data/lib/starter_web/assets/images/collections/books/biography/not_dead_yet.jpg +0 -0
  70. data/lib/starter_web/assets/images/collections/books/biography/the_princess_diarist.jpg +0 -0
  71. data/lib/starter_web/assets/images/collections/books/biography/when_breath_becomes_air.jpg +0 -0
  72. data/lib/starter_web/assets/images/collections/books/fantasy/harry-potter-deathly-hallows.jpg +0 -0
  73. data/lib/starter_web/assets/images/collections/books/fantasy/harry-potter-philosophers-stone.jpg +0 -0
  74. data/lib/starter_web/assets/images/collections/books/fantasy/mistborn-trilogy.jpg +0 -0
  75. data/lib/starter_web/assets/images/collections/books/fantasy/ready-player-one.jpg +0 -0
  76. data/lib/starter_web/assets/images/collections/books/fantasy/sword-of-destiny.jpg +0 -0
  77. data/lib/starter_web/assets/images/collections/books/fantasy/terry-pratchet-diary.jpg +0 -0
  78. data/lib/starter_web/assets/images/collections/books/romance/breath-of-snow-and-ashes.jpg +0 -0
  79. data/lib/starter_web/assets/images/collections/books/romance/it-ends-with-us.jpg +0 -0
  80. data/lib/starter_web/assets/images/collections/books/romance/outlander-novel.jpg +0 -0
  81. data/lib/starter_web/assets/images/collections/books/romance/outlander-short-story.jpg +0 -0
  82. data/lib/starter_web/assets/images/collections/books/romance/the-fiery-cross.jpg +0 -0
  83. data/lib/starter_web/assets/images/collections/books/romance/the_dressmaker.jpg +0 -0
  84. data/lib/starter_web/collections/_biography/a-life-in-questions.adoc +13 -8
  85. data/lib/starter_web/collections/_biography/becoming.adoc +18 -12
  86. data/lib/starter_web/collections/_biography/born-to-run.adoc +17 -13
  87. data/lib/starter_web/collections/_biography/forty-autumns.adoc +14 -9
  88. data/lib/starter_web/collections/_biography/not-dead-yet.adoc +12 -7
  89. data/lib/starter_web/collections/_biography/princess-diarist-the.adoc +13 -8
  90. data/lib/starter_web/collections/_biography/when-breath-becomes-air.adoc +14 -9
  91. data/lib/starter_web/collections/_fantasy/harry-potter-deathly-hallows.adoc +13 -7
  92. data/lib/starter_web/collections/_fantasy/harry-potter-philosophers-stone.adoc +12 -8
  93. data/lib/starter_web/collections/_fantasy/mistborn-trilogy.adoc +14 -10
  94. data/lib/starter_web/collections/_fantasy/ready-player-one.adoc +12 -11
  95. data/lib/starter_web/collections/_fantasy/sword-of-destiny.adoc +15 -7
  96. data/lib/starter_web/collections/_fantasy/terry-pratchet-diary.adoc +11 -6
  97. data/lib/starter_web/collections/_romance/breath-of-snow-and-ashes.adoc +15 -10
  98. data/lib/starter_web/collections/_romance/it-ends-with-us.adoc +11 -6
  99. data/lib/starter_web/collections/_romance/outlander-novel.adoc +11 -6
  100. data/lib/starter_web/collections/_romance/{virgins-outlander-short-story.adoc → outlander-virgins-short-story.adoc} +11 -6
  101. data/lib/starter_web/collections/_romance/{dressmaker-the.adoc → the-dressmaker.adoc} +11 -6
  102. data/lib/starter_web/collections/_romance/{fiery-cross-the.adoc → the-fiery-cross.adoc} +11 -6
  103. data/lib/starter_web/collections/posts/public/featured/_posts/0000-00-00-welcome-to-j1.adoc.erb +6 -6
  104. data/lib/starter_web/collections/posts/public/featured/_posts/2021-01-01-about-cookies.adoc +12 -11
  105. data/lib/starter_web/collections/posts/public/featured/_posts/2021-02-01-static-site-generators.adoc +9 -7
  106. data/lib/starter_web/collections/posts/public/featured/_posts/2022-02-01-about-j1.adoc +8 -8
  107. data/lib/starter_web/dot.ruby-version +1 -1
  108. data/lib/starter_web/index.html +10 -8
  109. data/lib/starter_web/package.json +1 -1
  110. data/lib/starter_web/pages/public/about/features.adoc +7 -1
  111. data/lib/starter_web/pages/public/about/reporting_issues.adoc +2 -0
  112. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/000_intro.adoc +2 -0
  113. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/100_converter.adoc +2 -0
  114. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/200_themes.adoc +2 -0
  115. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/_includes/documents/000_intro.asciidoc +1 -1
  116. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/_includes/documents/100_converter/112_getting_started.asciidoc +1 -0
  117. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/_includes/documents/100_converter/113_themes.asciidoc +1 -0
  118. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/documentation.adoc +2 -0
  119. data/lib/starter_web/pages/public/asciidoc_skeletons/multi-document/_includes/documents/100_chapter.asciidoc +3 -1
  120. data/lib/starter_web/pages/public/asciidoc_skeletons/multi-document/_includes/documents/200_chapter.asciidoc +1 -0
  121. data/lib/starter_web/pages/public/asciidoc_skeletons/multi-document/multi.adoc +2 -0
  122. data/lib/starter_web/pages/public/asciidoc_skeletons/simple-document/simple.adoc +6 -0
  123. data/lib/starter_web/pages/public/blog/navigator/archive/allview.html +6 -13
  124. data/lib/starter_web/pages/public/blog/navigator/archive/categoryview.html +17 -11
  125. data/lib/starter_web/pages/public/blog/navigator/archive/dateview.html +7 -8
  126. data/lib/starter_web/pages/public/blog/navigator/archive/tagview.html +5 -7
  127. data/lib/starter_web/pages/public/blog/navigator/index.html +15 -14
  128. data/lib/starter_web/pages/public/features/general.adoc +7 -1
  129. data/lib/starter_web/pages/public/features/template.adoc +87 -128
  130. data/lib/starter_web/pages/public/learn/bookshelf/article_previewer/viewer_all_books.adoc +3 -0
  131. data/lib/starter_web/pages/public/learn/bookshelf/article_previewer/viewer_biography.adoc +1 -1
  132. data/lib/starter_web/pages/public/learn/bookshelf/article_previewer/viewer_fantasy.adoc +1 -0
  133. data/lib/starter_web/pages/public/learn/bookshelf/article_previewer/viewer_romance.adoc +1 -1
  134. data/lib/starter_web/pages/public/learn/bookshelf/jekyll_collections.adoc +11 -8
  135. data/lib/starter_web/pages/public/learn/bs_sass_variables/bs_sass_variables.adoc +4 -0
  136. data/lib/starter_web/pages/public/learn/core_web_vitals/core_web_vitals.adoc +8 -4
  137. data/lib/starter_web/pages/public/learn/roundtrip/_includes/documents/100_gistblock.asciidoc +2 -1
  138. data/lib/starter_web/pages/public/learn/roundtrip/_includes/documents/themes_bootstrap.asciidoc +27 -28
  139. data/lib/starter_web/pages/public/learn/roundtrip/asciidoc_extensions.adoc +164 -153
  140. data/lib/starter_web/pages/public/learn/roundtrip/bootstrap_themes.adoc +22 -16
  141. data/lib/starter_web/pages/public/learn/roundtrip/highlghter_rouge.adoc +3 -2
  142. data/lib/starter_web/pages/public/learn/roundtrip/icon_fonts.adoc +62 -74
  143. data/lib/starter_web/pages/public/learn/roundtrip/lunr_search.1.asciidoc +460 -0
  144. data/lib/starter_web/pages/public/learn/roundtrip/lunr_search.adoc +4 -0
  145. data/lib/starter_web/pages/public/learn/roundtrip/modal_extentions.adoc +21 -15
  146. data/lib/starter_web/pages/public/learn/roundtrip/present_images.adoc +470 -449
  147. data/lib/starter_web/pages/public/learn/roundtrip/present_videos.adoc +53 -54
  148. data/lib/starter_web/pages/public/learn/roundtrip/responsive_tables.adoc +46 -34
  149. data/lib/starter_web/pages/public/learn/roundtrip/typography.adoc +25 -22
  150. data/lib/starter_web/pages/public/learn/where_to_go.adoc +6 -2
  151. data/lib/starter_web/pages/public/legal/en/100_copyright.adoc +4 -0
  152. data/lib/starter_web/pages/public/legal/en/200_impress.adoc +2 -0
  153. data/lib/starter_web/pages/public/legal/en/300_privacy.adoc +22 -3
  154. data/lib/starter_web/pages/public/legal/en/400_comment_policy.adoc +5 -0
  155. data/lib/starter_web/pages/public/manuals/speak2me.adoc +412 -0
  156. data/lib/starter_web/pages/public/panels/intro_panel/panel.adoc +27 -19
  157. data/lib/starter_web/pages/public/plans/plans.adoc +3 -0
  158. data/lib/starter_web/pages/public/tools/cheatsheet/gem.adoc +2 -0
  159. data/lib/starter_web/pages/public/tools/cheatsheet/git.adoc +2 -0
  160. data/lib/starter_web/pages/public/tools/cheatsheet/j1.adoc +2 -0
  161. data/lib/starter_web/pages/public/tools/cheatsheet/yaml.adoc +6 -0
  162. data/lib/starter_web/pages/public/tools/previewer/preview_bootstrap_theme.adoc +17 -3
  163. data/lib/starter_web/utilsrv/_defaults/package.json +1 -1
  164. data/lib/starter_web/utilsrv/package.json +1 -1
  165. metadata +38 -7
  166. data/lib/starter_web/_includes/google/static/google_ad_7284712660.html +0 -19
@@ -2542,6 +2542,16 @@ module.exports = function navigator(options) {
2542
2542
  });
2543
2543
  } // END Translator
2544
2544
 
2545
+ // ---------------------------------------------------------------------
2546
+ // Speak2Me dialog
2547
+ //
2548
+ if ($('li.speak')) {
2549
+ logger.debug('register SHOW event for J1 Speak2Me');
2550
+ $('li.speak > a', this).on('click', function (e) {
2551
+ j1.adapter.speak2me.showDialog();
2552
+ });
2553
+ } // END NBI Notebooks
2554
+
2545
2555
  // ---------------------------------------------------------------------
2546
2556
  // NBI Notebooks dialog
2547
2557
  //
@@ -2957,6 +2967,1653 @@ module.exports = function scrollSmooth(options) {
2957
2967
 
2958
2968
  /***/ }),
2959
2969
 
2970
+ /***/ 633:
2971
+ /***/ ((module) => {
2972
+
2973
+ // -----------------------------------------------------------------------------
2974
+ // ESLint shimming
2975
+ // -----------------------------------------------------------------------------
2976
+ /* eslint indent: "off" */
2977
+ /* eslint no-undef: "off" */
2978
+ /* eslint no-unused-vars: "off" */
2979
+ // -----------------------------------------------------------------------------
2980
+
2981
+ module.exports = {
2982
+ // Where to render the table of contents.
2983
+ tocSelector: '.js-toc',
2984
+ // Where to grab the headings to build the table of contents.
2985
+ contentSelector: '.js-toc-content',
2986
+ // Which headings to grab inside of the contentSelector element.
2987
+ headingSelector: 'h2, h3, h4, h5, h6',
2988
+ // Headings that match the ignoreSelector will be skipped.
2989
+ ignoreSelector: '.notoc',
2990
+ // For headings inside relative or absolute positioned containers within content
2991
+ hasInnerContainers: false,
2992
+ // Main class to add to links.
2993
+ linkClass: 'toc-link',
2994
+ // Extra classes to add to links.
2995
+ extraLinkClasses: '',
2996
+ // Class to add to active links,
2997
+ // the link corresponding to the top most heading on the page.
2998
+ activeLinkClass: 'is-active-link',
2999
+ // Main class to add to lists.
3000
+ listClass: 'toc-list',
3001
+ // Extra classes to add to lists.
3002
+ extraListClasses: '',
3003
+ // Class that gets added when a list should be collapsed.
3004
+ isCollapsedClass: 'is-collapsed',
3005
+ // Class that gets added when a list should be able
3006
+ // to be collapsed but isn't necessarily collapsed.
3007
+ collapsibleClass: 'is-collapsible',
3008
+ // Class to add to list items.
3009
+ listItemClass: 'toc-list-item',
3010
+ // Class to add to active list items.
3011
+ activeListItemClass: 'is-active-li',
3012
+ // How many heading levels should not be collapsed.
3013
+ // For example, number 6 will show everything since
3014
+ // there are only 6 heading levels and number 0 will collapse them all.
3015
+ // The sections that are hidden will open
3016
+ // and close as you scroll to headings within them.
3017
+ collapseDepth: 3,
3018
+ // Smooth scrolling enabled.
3019
+ scrollSmooth: true,
3020
+ // Smooth scroll duration.
3021
+ scrollSmoothDuration: 300,
3022
+ // Smooth scroll offset.
3023
+ scrollSmoothOffset: 0,
3024
+ // Callback for scroll end.
3025
+ scrollEndCallback: function (e) {},
3026
+ // Headings offset between the headings and the top of the document (this is meant for minor adjustments).
3027
+ headingsOffset: 1,
3028
+ // Timeout between events firing to make sure it's
3029
+ // not too rapid (for performance reasons).
3030
+ throttleTimeout: 150,
3031
+ // Element to add the positionFixedClass to.
3032
+ positionFixedSelector: null,
3033
+ // Fixed position class to add to make sidebar fixed after scrolling
3034
+ // down past the fixedSidebarOffset.
3035
+ positionFixedClass: 'is-position-fixed',
3036
+ // fixedSidebarOffset can be any number but by default is set
3037
+ // to auto which sets the fixedSidebarOffset to the sidebar
3038
+ // element's offsetTop from the top of the document on init.
3039
+ fixedSidebarOffset: 'auto',
3040
+ // includeHtml can be set to true to include the HTML markup from the
3041
+ // heading node instead of just including the textContent.
3042
+ includeHtml: false,
3043
+ // onclick function to apply to all links in toc. will be called with
3044
+ // the event as the first parameter, and this can be used to stop,
3045
+ // propagation, prevent default or perform action
3046
+ onClick: function (e) {},
3047
+ // orderedList can be set to false to generate unordered lists (ul)
3048
+ // instead of ordered lists (ol)
3049
+ orderedList: true,
3050
+ // If there is a fixed article scroll container, set to calculate titles' offset
3051
+ scrollContainer: null,
3052
+ // prevent ToC DOM rendering if it's already rendered by an external system
3053
+ skipRendering: false,
3054
+ // Optional callback to change heading labels.
3055
+ // For example it can be used to cut down and put ellipses on multiline headings you deem too long.
3056
+ // Called each time a heading is parsed. Expects a string in return, the modified label to display.
3057
+ // function (string) => string
3058
+ headingLabelCallback: false,
3059
+ // ignore headings that are hidden in DOM
3060
+ ignoreHiddenElements: false,
3061
+ // Optional callback to modify properties of parsed headings.
3062
+ // The heading element is passed in node parameter and information parsed by default parser is provided in obj parameter.
3063
+ // Function has to return the same or modified obj.
3064
+ // The heading will be excluded from TOC if nothing is returned.
3065
+ // function (object, HTMLElement) => object | void
3066
+ headingObjectCallback: null,
3067
+ // Set the base path, useful if you use a `base` tag in `head`.
3068
+ basePath: '',
3069
+ // Only takes affect when `tocSelector` is scrolling,
3070
+ // keep the toc scroll position in sync with the content.
3071
+ disableTocScrollSync: false
3072
+ };
3073
+
3074
+ /***/ }),
3075
+
3076
+ /***/ 291:
3077
+ /***/ ((module) => {
3078
+
3079
+ /**
3080
+ * This file is responsible for parsing the content from the DOM and making
3081
+ * sure data is nested properly.
3082
+ *
3083
+ * @author Tim Scanlin
3084
+ */
3085
+
3086
+ // -----------------------------------------------------------------------------
3087
+ // ESLint shimming
3088
+ // -----------------------------------------------------------------------------
3089
+ /* eslint indent: "off" */
3090
+ /* eslint no-undef: "off" */
3091
+ /* eslint semi: "off" */
3092
+ // -----------------------------------------------------------------------------
3093
+
3094
+ module.exports = function parseContent(options) {
3095
+ var reduce = [].reduce;
3096
+
3097
+ /**
3098
+ * Get the last item in an array and return a reference to it.
3099
+ * @param {Array} array
3100
+ * @return {Object}
3101
+ */
3102
+ function getLastItem(array) {
3103
+ return array[array.length - 1];
3104
+ }
3105
+
3106
+ /**
3107
+ * Get heading level for a heading dom node.
3108
+ * @param {HTMLElement} heading
3109
+ * @return {Number}
3110
+ */
3111
+ function getHeadingLevel(heading) {
3112
+ return +heading.nodeName.split('H').join('');
3113
+ }
3114
+
3115
+ /**
3116
+ * Get important properties from a heading element and store in a plain object.
3117
+ * @param {HTMLElement} heading
3118
+ * @return {Object}
3119
+ */
3120
+ function getHeadingObject(heading) {
3121
+ // each node is processed twice by this method because nestHeadingsArray() and addNode() calls it
3122
+ // first time heading is real DOM node element, second time it is obj
3123
+ // that is causing problem so I am processing only original DOM node
3124
+ if (!(heading instanceof window.HTMLElement)) return heading;
3125
+ if (options.ignoreHiddenElements && (!heading.offsetHeight || !heading.offsetParent)) {
3126
+ return null;
3127
+ }
3128
+ var obj = {
3129
+ id: heading.id,
3130
+ children: [],
3131
+ nodeName: heading.nodeName,
3132
+ headingLevel: getHeadingLevel(heading),
3133
+ textContent: options.headingLabelCallback ? String(options.headingLabelCallback(heading.textContent)) : heading.textContent.trim()
3134
+ };
3135
+ if (options.includeHtml) {
3136
+ obj.childNodes = heading.childNodes;
3137
+ }
3138
+ if (options.headingObjectCallback) {
3139
+ return options.headingObjectCallback(obj, heading);
3140
+ }
3141
+ return obj;
3142
+ }
3143
+
3144
+ /**
3145
+ * Add a node to the nested array.
3146
+ * @param {Object} node
3147
+ * @param {Array} nest
3148
+ * @return {Array}
3149
+ */
3150
+ function addNode(node, nest) {
3151
+ var obj = getHeadingObject(node);
3152
+ var level = obj.headingLevel;
3153
+ var array = nest;
3154
+ var lastItem = getLastItem(array);
3155
+ var lastItemLevel = lastItem ? lastItem.headingLevel : 0;
3156
+ var counter = level - lastItemLevel;
3157
+ while (counter > 0) {
3158
+ lastItem = getLastItem(array);
3159
+ if (lastItem && lastItem.children !== undefined) {
3160
+ array = lastItem.children;
3161
+ }
3162
+ counter--;
3163
+ }
3164
+ if (level >= options.collapseDepth) {
3165
+ obj.isCollapsed = true;
3166
+ }
3167
+ array.push(obj);
3168
+ return array;
3169
+ }
3170
+
3171
+ /**
3172
+ * Select headings in content area, exclude any selector in options.ignoreSelector
3173
+ * @param {String} contentSelector
3174
+ * @param {Array} headingSelector
3175
+ * @return {Array}
3176
+ */
3177
+ function selectHeadings(contentSelector, headingSelector) {
3178
+ var selectors = headingSelector;
3179
+ if (options.ignoreSelector) {
3180
+ selectors = headingSelector.split(',').map(function mapSelectors(selector) {
3181
+ return selector.trim() + ':not(' + options.ignoreSelector + ')';
3182
+ });
3183
+ }
3184
+ try {
3185
+ return document.querySelector(contentSelector).querySelectorAll(selectors);
3186
+ } catch (e) {
3187
+ console.warn('Element not found: ' + contentSelector); // eslint-disable-line
3188
+ return null;
3189
+ }
3190
+ }
3191
+
3192
+ /**
3193
+ * Nest headings array into nested arrays with 'children' property.
3194
+ * @param {Array} headingsArray
3195
+ * @return {Object}
3196
+ */
3197
+ function nestHeadingsArray(headingsArray) {
3198
+ return reduce.call(headingsArray, function reducer(prev, curr) {
3199
+ var currentHeading = getHeadingObject(curr);
3200
+ if (currentHeading) {
3201
+ addNode(currentHeading, prev.nest);
3202
+ }
3203
+ return prev;
3204
+ }, {
3205
+ nest: []
3206
+ });
3207
+ }
3208
+ return {
3209
+ nestHeadingsArray: nestHeadingsArray,
3210
+ selectHeadings: selectHeadings
3211
+ };
3212
+ };
3213
+
3214
+ /***/ }),
3215
+
3216
+ /***/ 229:
3217
+ /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
3218
+
3219
+ /*
3220
+ # -----------------------------------------------------------------------------
3221
+ # ~/assets/themes/j1/modules/speak2me/js/speak2me.js
3222
+ # speak2me v.1.0 implementation (based on Articulate.js) for J1 Theme
3223
+ #
3224
+ # Product/Info:
3225
+ # https://jekyll.one
3226
+ # https://github.com/acoti/articulate.js/tree/master
3227
+ #
3228
+ # Copyright (C) 2023 Juergen Adams
3229
+ # Copyright (C) 2017 Adam Coti
3230
+ #
3231
+ # J1 Theme is licensed under the MIT License.
3232
+ # See: https://github.com/jekyll-one-org/j1-template/blob/main/LICENSE.md
3233
+ # Articulate is licensed under the MIT License.
3234
+ # See: https://github.com/acoti/articulate.js/blob/master/LICENSE
3235
+ # -----------------------------------------------------------------------------
3236
+ */
3237
+
3238
+ /* Articulate.js (1.1.0). (C) 2017 Adam Coti.
3239
+ MIT @license: en.wikipedia.org/wiki/MIT_License
3240
+ See Github page at: https://github.com/acoti/articulate.js
3241
+ See Web site at: http://articulate.purefreedom.com
3242
+ */
3243
+
3244
+ (function ($) {
3245
+ 'use strict';
3246
+
3247
+ const defaultOptions = __webpack_require__(633);
3248
+ const ParseContent = __webpack_require__(291);
3249
+ const parseContent = ParseContent(defaultOptions);
3250
+ const scrollBehavior = 'smooth';
3251
+ const speechCycle = 10;
3252
+ const speechMonitorCycle = 10;
3253
+ const textSliceLength = 30;
3254
+ const minWords = 3;
3255
+ const pageScanCycle = 1000;
3256
+ const pageScanLines = 10000;
3257
+ const isFirefox = /Firefox/i.test(navigator.userAgent);
3258
+ const isEdge = /Edg/i.test(navigator.userAgent);
3259
+ const chrome = /chrome/i.test(navigator.userAgent);
3260
+ const isChrome = chrome && !isEdge;
3261
+ const voiceUserDefault = 'Google UK English Female';
3262
+ const voiceChromeDefault = 'Google US English';
3263
+ const ignoreProvider = 'Microsoft';
3264
+ const defaultLanguage = 'en-GB';
3265
+ var currentTranslation = getCookie('googtrans');
3266
+ var scrollBlockOffset = 100;
3267
+ var customOptions = {};
3268
+ var myOptions = {};
3269
+ var ignoreTagsUser = new Array();
3270
+ var recognizeTagsUser = new Array();
3271
+ var replacements = new Array();
3272
+ var customTags = new Array();
3273
+ var voices = new Array();
3274
+ var headingsArray = [];
3275
+ var rateDefault = 0.9;
3276
+ var pitchDefault = 1;
3277
+ var volumeDefault = 0.9;
3278
+ var rate = rateDefault;
3279
+ var pitch = pitchDefault;
3280
+ var volume = volumeDefault;
3281
+ var pause_spoken = '. ';
3282
+ var chunkCounter = 0;
3283
+ var userStoppedSpeaking = false;
3284
+ var chunkSpoken = false;
3285
+ var scrollOnce = true;
3286
+ var rateUserDefault;
3287
+ var pitchUserDefault;
3288
+ var volumeUserDefault;
3289
+ var currentLanguage;
3290
+ var voiceLanguageDefault;
3291
+ var chunkCounterMax;
3292
+ var user_session;
3293
+ var scanFinished;
3294
+ var voiceLanguageGoogleDefault = {
3295
+ 'de-DE': 'Google Deutsch',
3296
+ 'en-US': 'Google US English',
3297
+ 'en-GB': 'Google UK English Female',
3298
+ 'es-ES': 'Google español',
3299
+ 'fr-FR': 'Google français',
3300
+ // 'hi-IN': 'Google हिन्दी',
3301
+ // 'id-ID': 'Google Bahasa Indonesia',
3302
+ 'it-IT': 'Google italiano',
3303
+ // 'jp-JP': 'Google 日本語',
3304
+ // 'ko-KR': 'Google 한국의',
3305
+ 'nl-NL': 'Google Nederlands',
3306
+ 'pl-PL': 'Google polski',
3307
+ // 'pt-BR': 'Google português do Brasil',
3308
+ 'pt-PT': 'Google português do Brasil'
3309
+ // 'ru-RU': 'Google русский',
3310
+ // 'zh-CN': 'Google 普通话(中国大陆)',
3311
+ };
3312
+
3313
+ var voiceLanguageMicrosoftDefault = {
3314
+ 'sq-AL': 'Microsoft Anila Online (Natural) - Albanian (Albania)',
3315
+ 'ar-EG': 'Microsoft Salma Online (Natural) - Arabic (Egypt)',
3316
+ 'bg-BG': 'Microsoft Kalina Online (Natural) - Bulgarian (Bulgaria)',
3317
+ 'zh-CN': 'Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland)',
3318
+ 'hr-HR': 'Microsoft Gabrijela Online (Natural) - Croatian (Croatia)',
3319
+ 'cs-CZ': 'Microsoft Antonin Online (Natural) - Czech (Czech)',
3320
+ 'da-DK': 'Microsoft Christel Online (Natural) - Danish (Denmark)',
3321
+ 'nl-NL': 'Microsoft Colette Online (Natural) - Dutch (Netherlands)',
3322
+ 'en-GB': 'Microsoft Libby Online (Natural) - English (United Kingdom)',
3323
+ 'en-US': 'Microsoft Aria Online (Natural) - English (United States)',
3324
+ 'et-EE': 'Microsoft Anu Online (Natural) - Estonian (Estonia)',
3325
+ 'fi-FI': 'Microsoft Noora Online (Natural) - Finnish (Finland)',
3326
+ 'fr-FR': 'Microsoft Denise Online (Natural) - French (France)',
3327
+ 'ka-GE': 'Microsoft Giorgi Online (Natural) - Georgian (Georgia)',
3328
+ 'de-DE': 'Microsoft Katja Online (Natural) - German (Germany)',
3329
+ 'el-GR': 'Microsoft Athina Online (Natural) - Greek (Greece)',
3330
+ 'he-IL': 'Microsoft Avri Online (Natural) - Hebrew (Israel)',
3331
+ 'hi-IN': 'Microsoft Madhur Online (Natural) - Hindi (India)',
3332
+ 'hu-HU': 'Microsoft Noemi Online (Natural) - Hungarian (Hungary)',
3333
+ 'it-IT': 'Microsoft Elsa Online (Natural) - Italian (Italy)',
3334
+ 'ja-JP': 'Microsoft Nanami Online (Natural) - Japanese (Japan)'
3335
+ };
3336
+ var voiceLanguageFirefoxDefault = {
3337
+ 'en-GB': 'Microsoft Hazel - English (United Kingdom) (en-GB)',
3338
+ 'en-US': 'Microsoft Zira Desktop - English (United States) (en-US)',
3339
+ 'de-DE': 'Microsoft Katja Online (Natural) - German (Germany)'
3340
+ };
3341
+
3342
+ // -------------------------------------------------------------------------
3343
+ // Internal functions
3344
+ // -------------------------------------------------------------------------
3345
+
3346
+ // scan a page to get correct positions for scrolling and highlightning
3347
+ //
3348
+ function scanPage(options) {
3349
+ // see: https://stackoverflow.com/questions/3163615/how-to-scroll-an-html-page-to-a-given-anchor
3350
+ // see: https://stackoverflow.com/questions/22154129/how-to-make-setinterval-behave-more-in-sync-or-how-to-use-settimeout-instea
3351
+ var line = 0;
3352
+ var lines;
3353
+ function scanSection(counter) {
3354
+ // because of the current translation in progress, the length
3355
+ // of a page may change to higher or lower values (asian)
3356
+ //
3357
+ lines = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
3358
+ $('#content').attr("style", "opacity: .3");
3359
+ if (line < lines) {
3360
+ setTimeout(function () {
3361
+ counter++;
3362
+ line = line + pageScanLines;
3363
+ window.scrollTo({
3364
+ top: line,
3365
+ behavior: 'smooth'
3366
+ });
3367
+ scanSection(counter);
3368
+ }, pageScanCycle);
3369
+ } else {
3370
+ setTimeout(function () {
3371
+ scanFinished = true;
3372
+ $('#content').attr("style", "opacity: 1");
3373
+ $(window).scrollTop(0);
3374
+ }, pageScanCycle);
3375
+ }
3376
+ }
3377
+ scanSection(0);
3378
+ } // END scanPage
3379
+
3380
+ // merge (configuration) objects
3381
+ //
3382
+ function extend() {
3383
+ var target = {};
3384
+ for (var i = 0; i < arguments.length; i++) {
3385
+ var source = arguments[i];
3386
+ for (var key in source) {
3387
+ if (hasOwnProperty.call(source, key)) {
3388
+ target[key] = source[key];
3389
+ }
3390
+ }
3391
+ }
3392
+ return target;
3393
+ } // END extend
3394
+
3395
+ // get the conten of a Cookie (by its name)
3396
+ //
3397
+ function getCookie(name) {
3398
+ var nameEQ = name + '=';
3399
+ var ca = document.cookie.split(';');
3400
+ for (var i = 0; i < ca.length; i++) {
3401
+ var c = ca[i];
3402
+ while (c.charAt(0) === ' ') {
3403
+ c = c.substring(1, c.length);
3404
+ }
3405
+ if (c.indexOf(nameEQ) === 0) {
3406
+ var value = c.substring(nameEQ.length, c.length);
3407
+ return value;
3408
+ }
3409
+ }
3410
+ return undefined;
3411
+ } // END getCookie
3412
+
3413
+ function voiceTag(prepend, append) {
3414
+ this.prepend = prepend;
3415
+ this.append = append;
3416
+ } // END voiceTag
3417
+
3418
+ function voiceObj(name, language) {
3419
+ this.name = name;
3420
+ this.language = language;
3421
+ } // END voiceObj
3422
+
3423
+ // count the number of words in a string
3424
+ //
3425
+ function wordCount(str) {
3426
+ var count = 0;
3427
+ var words = str.split(" ");
3428
+ for (var i = 0; i < words.length; i++) {
3429
+ // inner loop -- do the count
3430
+ if (words[i] != "") {
3431
+ count += 1;
3432
+ }
3433
+ }
3434
+ return count;
3435
+ } // END wordCount
3436
+
3437
+ // This populates the "voices" array with objects that represent the
3438
+ // available voices in the current browser. Each object has two
3439
+ // properties: name and language. It is loaded asynchronously in
3440
+ // deference to Chrome.
3441
+ //
3442
+ function populateVoiceList() {
3443
+ var systemVoices = speechSynthesis.getVoices();
3444
+ for (var i = 0; i < systemVoices.length; i++) {
3445
+ voices.push(new voiceObj(systemVoices[i].name, systemVoices[i].lang));
3446
+ }
3447
+ } // END populateVoiceList
3448
+
3449
+ populateVoiceList();
3450
+ if (typeof speechSynthesis !== 'undefined' && speechSynthesis.onvoiceschanged !== undefined) {
3451
+ speechSynthesis.onvoiceschanged = populateVoiceList;
3452
+ }
3453
+
3454
+ // After checking for compatability, define the utterance object and
3455
+ // then cancel the speech immediately even though nothing is yet spoken.
3456
+ // This is to fix a quirk in Windows Chrome.
3457
+ //
3458
+ if ('speechSynthesis' in window) {
3459
+ var speech = new SpeechSynthesisUtterance();
3460
+ window.speechSynthesis.cancel();
3461
+ }
3462
+ if (currentTranslation === undefined) {
3463
+ // currentLanguage = 'en-US'
3464
+ currentLanguage = defaultLanguage;
3465
+ } else {
3466
+ var translation = currentTranslation.split('/');
3467
+ if (translation[2] == 'en') {
3468
+ currentLanguage = 'en-GB';
3469
+ } else if (translation[2].includes('ar')) {
3470
+ currentLanguage = 'ar-EG';
3471
+ } else if (translation[2].includes('cs')) {
3472
+ currentLanguage = 'cs-CZ';
3473
+ } else if (translation[2].includes('da')) {
3474
+ currentLanguage = 'da-DK';
3475
+ } else if (translation[2].includes('en')) {
3476
+ currentLanguage = 'en-UK';
3477
+ } else if (translation[2].includes('et')) {
3478
+ currentLanguage = 'et-EE';
3479
+ } else if (translation[2].includes('ka')) {
3480
+ currentLanguage = 'ka-GE';
3481
+ } else if (translation[2].includes('el')) {
3482
+ currentLanguage = 'el-GR';
3483
+ } else if (translation[2].includes('iw')) {
3484
+ currentLanguage = 'he-IL';
3485
+ } else if (translation[2].includes('hi')) {
3486
+ currentLanguage = 'hi-IN';
3487
+ } else if (translation[2].includes('ja')) {
3488
+ currentLanguage = 'ja-JP';
3489
+ } else if (translation[2].includes('zh')) {
3490
+ currentLanguage = 'zh-CN';
3491
+ } else {
3492
+ currentLanguage = translation[2] + '-' + translation[2].toUpperCase();
3493
+ }
3494
+ }
3495
+ if (isChrome) {
3496
+ var voiceLanguageDefault = voiceLanguageGoogleDefault[currentLanguage];
3497
+ }
3498
+ if (isEdge) {
3499
+ var voiceLanguageDefault = voiceLanguageMicrosoftDefault[currentLanguage];
3500
+ }
3501
+ if (isFirefox) {
3502
+ var voiceLanguageDefault = voiceLanguageFirefoxDefault[currentLanguage];
3503
+ }
3504
+
3505
+ // -------------------------------------------------------------------------
3506
+ // Public functions (methods)
3507
+ // -------------------------------------------------------------------------
3508
+ //
3509
+ var methods = {
3510
+ // main speak2me method.
3511
+ //
3512
+ speak: function (options) {
3513
+ var opts = $.extend({}, $.fn.speak2me.defaults, options);
3514
+ var toSpeak = '';
3515
+ var voiceTags = new Array();
3516
+ var _this = this;
3517
+ var obj, processed, finished;
3518
+ var ignoreTags;
3519
+ scanFinished = false;
3520
+ myOptions = extend(defaultOptions, customOptions || {});
3521
+
3522
+ // scan page to find correct positions for scrolling and highlightning
3523
+ //
3524
+ scanPage();
3525
+
3526
+ // Default values
3527
+ //
3528
+ voiceTags['a'] = new voiceTag('Link, ', pause_spoken);
3529
+ voiceTags['q'] = new voiceTag('', pause_spoken);
3530
+ voiceTags['ol'] = new voiceTag('Start of list.', 'End of list. ');
3531
+ voiceTags['ul'] = new voiceTag('Start of list.', 'End of list. ');
3532
+ voiceTags['dl'] = new voiceTag('Start of list.', 'End of list. ');
3533
+ voiceTags['dt'] = new voiceTag('', ', ');
3534
+ voiceTags['img'] = new voiceTag('Start of an embedded image with the description,', ', ');
3535
+ voiceTags['table'] = new voiceTag('Start of an embedded table,', 'This element ist not spoken.');
3536
+ voiceTags['card-header'] = new voiceTag('', '');
3537
+ voiceTags['doc-example'] = new voiceTag('Start of an embedded example element,', 'This element ist not spoken.');
3538
+ voiceTags['admonitionblock'] = new voiceTag('Start of an attention element of type, ', '');
3539
+ voiceTags['listingblock'] = new voiceTag('Start of an embedded structured text block,', 'This element ist not spoken.');
3540
+ voiceTags['carousel'] = new voiceTag('Start of an embedded carousel element,', 'This element ist not spoken.');
3541
+ voiceTags['slider'] = new voiceTag('Start of an embedded slider element,', 'This element ist not spoken.');
3542
+ voiceTags['masonry'] = new voiceTag('Start of an embedded masonry element,', 'This element ist not spoken.');
3543
+ voiceTags['lightbox'] = new voiceTag('Start of an embedded lightbox element,', 'This element ist not spoken.');
3544
+ voiceTags['gallery'] = new voiceTag('Start of an embedded gallery element,', 'This element ist not spoken.');
3545
+ voiceTags['figure'] = new voiceTag('Start of an embedded figure with the caption,', '');
3546
+ voiceTags['blockquote'] = new voiceTag('Blockquote start.', 'Blockquote end.');
3547
+ voiceTags['quoteblock'] = new voiceTag('Start of an embedded quote block element,', 'Quote block element end.');
3548
+
3549
+ // ignoreTags = ['masonry', 'carousel', 'slider', 'pre','audio','button','canvas','code','del','dialog','embed','form','head','iframe','meter','nav','noscript','object','s','script','select','style','textarea','video'];
3550
+ ignoreTags = ['audio', 'button', 'canvas', 'code', 'del', 'pre', 'dialog', 'embed', 'form', 'head', 'iframe', 'meter', 'nav', 'noscript', 'object', 's', 'script', 'select', 'style', 'textarea', 'video'];
3551
+
3552
+ // TODO: NOT working for multiple 'tab' windows
3553
+ // dispayed in the same browser
3554
+ //
3555
+ // If something is currently being spoken, ignore new voice
3556
+ // request. Otherwise it would be queued, which is doable if
3557
+ // someone wanted that, but not what I wanted.
3558
+ //
3559
+ if (window.speechSynthesis.speaking) {
3560
+ return;
3561
+ }
3562
+ ;
3563
+
3564
+ // TODO: coincident active speech synthesis in multiple
3565
+ // browser windows or tabs does NOT work
3566
+ //
3567
+ // user_session = j1.readCookie('j1.user.session');
3568
+ // if (user_session.speech_synthesis_active === true) {
3569
+ // return
3570
+ // };
3571
+
3572
+ // user_session.speech_synthesis_active = true;
3573
+ // j1.writeCookie({
3574
+ // name: 'user_session',
3575
+ // data: user_session,
3576
+ // secure: false,
3577
+ // expires: 0
3578
+ // });
3579
+
3580
+ // top-level function to prepare the HTML content of a page
3581
+ // and transform the resulting (speakable) text into chunks
3582
+ //
3583
+ var processSpeech = setInterval(function () {
3584
+ if (scanFinished) {
3585
+ // Cycle through all the elements in the original jQuery
3586
+ // selector, process and clean them one at a time, and
3587
+ // continually append it to the variable "toSpeak".
3588
+ //
3589
+ _this.each(function () {
3590
+ obj = $(this).clone(); // clone the DOM node and its descendants of what the user wants spoken
3591
+ processed = processDOMelements(obj); // process and manipulate DOM tree of this clone
3592
+ processed = jQuery(processed).html(); // convert the result of all that to a string
3593
+ finished = cleanDOMelements(processed); // do some text manipulation
3594
+ toSpeak = finished;
3595
+ });
3596
+
3597
+ // Check if users have set their own rate/pitch/volume
3598
+ // defaults, otherwise use defaults
3599
+ //
3600
+ if (rateUserDefault !== undefined) {
3601
+ rate = rateUserDefault;
3602
+ } else {
3603
+ rate = rateDefault;
3604
+ }
3605
+ if (pitchUserDefault !== undefined) {
3606
+ pitch = pitchUserDefault;
3607
+ } else {
3608
+ pitch = pitchDefault;
3609
+ }
3610
+ if (volumeUserDefault !== undefined) {
3611
+ volume = volumeUserDefault;
3612
+ } else {
3613
+ volume = volumeDefault;
3614
+ }
3615
+
3616
+ // create and configure the utterance object
3617
+ //
3618
+ speech = new SpeechSynthesisUtterance();
3619
+ speech.rate = rate;
3620
+ speech.pitch = pitch;
3621
+ speech.volume = volume;
3622
+ speech.voice = speechSynthesis.getVoices().filter(function (voice) {
3623
+ return voice.name == voiceLanguageDefault;
3624
+ })[0];
3625
+ speech.previousScrollPosition = 0;
3626
+ processTextChunks(speech, toSpeak);
3627
+ clearInterval(processSpeech);
3628
+ }
3629
+ }, speechCycle); // END processSpeech
3630
+
3631
+ // create the chunks array from (speakable) text generated
3632
+ //
3633
+ function splitTextIntoChunks(text) {
3634
+ var chunks = [];
3635
+
3636
+ // strip strange elements from text
3637
+ // unclear why a elements of ' >' is generated in text
3638
+ // may caused by a HTML tag
3639
+ text = text.replace(/^\s+>/gm, '');
3640
+
3641
+ // cleanup text
3642
+ text = text.replace(/(\r\n|\n|\r)/gm, '');
3643
+ text = text.replace(/\s+/gm, ' ');
3644
+ chunks = text.split('.');
3645
+
3646
+ // 1st cleanup of chunks
3647
+ chunks.forEach((chunk, index) => {
3648
+ chunks[index] = chunks[index].replace(/^\s+/g, '');
3649
+ chunks[index] = chunks[index].replaceAll('""', '');
3650
+ });
3651
+
3652
+ // 2nd cleanup of chunks (delete chunks NOT speakable)
3653
+ chunks.forEach((chunk, index) => {
3654
+ if (chunks[index].length > 0) {
3655
+ chunks[index] = chunks[index] + '. ';
3656
+ } else {
3657
+ // remove empty text element from chunks array
3658
+ chunks.splice(index, 1);
3659
+ }
3660
+ });
3661
+
3662
+ // 3rd cleanup of chunks (delete empty chunks)
3663
+ chunks.forEach((chunk, index) => {
3664
+ if (chunks[index].length == 0) {
3665
+ // remove empty text element from chunks array
3666
+ chunks.splice(index, 1);
3667
+ }
3668
+ });
3669
+
3670
+ // build the chunk OBJECT array
3671
+ //
3672
+ var chunkSet = [];
3673
+ chunks.forEach((chunk, index) => {
3674
+ var text = chunks[index];
3675
+ var sectionText = textSlice(text, textSliceLength, minWords);
3676
+ var $paragraph = $('#content').find("p:contains('" + sectionText + "')");
3677
+ var offset;
3678
+ if ($paragraph.length > 0) {
3679
+ offset = Math.round($paragraph[0].offsetTop);
3680
+ } else {
3681
+ offset = undefined;
3682
+ $paragraph = undefined;
3683
+ }
3684
+ chunkSet.push({
3685
+ text: text,
3686
+ offsetTop: offset,
3687
+ $paragraph: $paragraph
3688
+ });
3689
+ });
3690
+
3691
+ // Get headings array
3692
+ headingsArray = parseContent.selectHeadings(defaultOptions.contentSelector, defaultOptions.headingSelector);
3693
+
3694
+ // parse the headingsArray to add missing offset values
3695
+ // for headlines
3696
+ //
3697
+ chunkSet.forEach((chunk, index) => {
3698
+ var text;
3699
+ var innerText;
3700
+ if (chunk.offset === undefined) {
3701
+ // cleanup the spoken text for compare
3702
+ text = chunk.text.replaceAll('. ', '');
3703
+
3704
+ // jadams:
3705
+ // for this type of loop, NOT all headings are found in the array
3706
+ if (headingsArray !== null) {
3707
+ // see: https://stackoverflow.com/questions/29285897/difference-between-for-in-and-for-of-statements
3708
+ // for in loops over enumerable property names of an object
3709
+ // for of (new in ES6) does use an object-specific iterator
3710
+ // and loops over the values generated by that.
3711
+ for (var node of headingsArray) {
3712
+ // for (var node in headingsArray) {
3713
+ // cleanup the innerText for compare
3714
+ innerText = node.innerText.replaceAll('?', '');
3715
+ innerText = node.innerText;
3716
+ if (innerText == text) {
3717
+ var headline = $('#' + node.id);
3718
+ if (headline.length > 0) {
3719
+ var offsetTop = headline.offset().top;
3720
+ chunk.offsetTop = Math.round(offsetTop);
3721
+ // console.debug('speak2me, text: ' + node.innerText + ', offsetTop: ' + chunk.offsetTop);
3722
+ } else {
3723
+ // console.warn('speak2me, text: ' + node.innerText + ', offsetTop not caclulated.');
3724
+ }
3725
+ }
3726
+ }
3727
+ }
3728
+ }
3729
+ });
3730
+ return chunkSet;
3731
+ }
3732
+
3733
+ // create a slice of text used later to identify the
3734
+ // containing paragraph
3735
+ //
3736
+ function textSlice(text, slicelenght, wordsMin) {
3737
+ var startSubString = 0;
3738
+ var endSubString = startSubString + slicelenght;
3739
+ var subText = text.substr(startSubString, endSubString);
3740
+ var stringArray = subText.split(/(\s+)/);
3741
+ var words;
3742
+
3743
+ // Remove last two elements are a fraction of subText
3744
+ stringArray.pop();
3745
+ stringArray.pop();
3746
+
3747
+ // built the new string
3748
+ subText = stringArray.join('');
3749
+ subText = subText.replaceAll('.', '');
3750
+
3751
+ // at least two words required
3752
+ words = wordCount(subText);
3753
+ if (words < wordsMin) {
3754
+ return undefined;
3755
+ console.warn('no search possible on this fraction of subText');
3756
+ } else {
3757
+ return subText;
3758
+ }
3759
+ }
3760
+
3761
+ // process chunks (to speak) sequentially
3762
+ //
3763
+ function processTextChunks(speaker, chunks) {
3764
+ const synth = window.speechSynthesis;
3765
+
3766
+ // indicate active converter in the quicklinks bar
3767
+ //
3768
+ $('.mdib-speaker').addClass('mdib-spin');
3769
+
3770
+ // manage scrolling and highlightning for the active spoken text
3771
+ //
3772
+ speaker.addEventListener('start', event => {
3773
+ // store current scroll position
3774
+ //
3775
+ if (speaker.offsetTop !== undefined) {
3776
+ speaker.currentScrollPosition = speaker.offsetTop;
3777
+ }
3778
+
3779
+ // adjust scrolling position offsetTop for 'post series'
3780
+ //
3781
+ if ($('.bmd-layout-header').length) {
3782
+ speaker.offsetTop = $('.bmd-layout-header')[0].offsetTop + (scrollBlockOffset / 2 + 3) + speaker.offsetTop;
3783
+ speaker.previousScrollPosition = speaker.offsetTop;
3784
+ }
3785
+
3786
+ // add highlightning
3787
+ //
3788
+ if (speaker.$paragraph !== undefined) {
3789
+ speaker.$paragraph.addClass('speak-highlighted');
3790
+ }
3791
+
3792
+ // scroll to paragraph currently spoken if a valid
3793
+ // offsetTop is available
3794
+ //
3795
+ if (speaker.offsetTop !== undefined) {
3796
+ if (speaker.offsetTop >= speaker.previousScrollPosition) {
3797
+ window.scrollTo({
3798
+ top: speaker.offsetTop - scrollBlockOffset,
3799
+ behavior: scrollBehavior
3800
+ });
3801
+ }
3802
+ }
3803
+ });
3804
+
3805
+ // listener to manage highlightning for spoken text elements
3806
+ // and set next chunk to speak
3807
+ //
3808
+ speaker.addEventListener('end', function (event) {
3809
+ // workaround WRONG offsetTop positions (LOWER as expected)
3810
+ //
3811
+ if (speaker.offsetTop !== undefined) {
3812
+ if (speaker.offsetTop >= speaker.previousScrollPosition) {
3813
+ speaker.previousScrollPosition = speaker.offsetTop;
3814
+ }
3815
+ }
3816
+
3817
+ // remove highlightning for the paragraph already spoken
3818
+ //
3819
+ if (speaker.$paragraph !== undefined) {
3820
+ speaker.$paragraph.removeClass('speak-highlighted');
3821
+ }
3822
+ chunkSpoken = false;
3823
+ chunkCounter++;
3824
+ });
3825
+
3826
+ // loop to prepare chunks to speak or sto the voice output
3827
+ //
3828
+ var speechMonitor = setInterval(function () {
3829
+ // check if all chunks (text) are spoken
3830
+ //
3831
+ if (chunkCounter == chunkCounterMax || userStoppedSpeaking) {
3832
+ chunkCounter = 0;
3833
+ userStoppedSpeaking = false;
3834
+ chunkSpoken = false;
3835
+ speaker.$paragraph !== undefined && speaker.$paragraph.removeClass('speak-highlighted');
3836
+ window.scrollTo({
3837
+ top: 0,
3838
+ behavior: 'smooth'
3839
+ });
3840
+ $('.mdib-speaker').removeClass('mdib-spin');
3841
+ clearInterval(speechMonitor);
3842
+ } else {
3843
+ // prepare speaker data and start the voice
3844
+ //
3845
+ speaker.text = chunks[chunkCounter].text;
3846
+ speaker.offsetTop = chunks[chunkCounter].offsetTop;
3847
+ speaker.$paragraph = chunks[chunkCounter].$paragraph;
3848
+
3849
+ // speak current text line
3850
+ if (!chunkSpoken) {
3851
+ synth.speak(speaker);
3852
+ chunkSpoken = true;
3853
+ }
3854
+ }
3855
+ }, speechMonitorCycle); // END speechMonitor
3856
+ } // END processTextChunks
3857
+
3858
+ // transform all configured DOM elements of a page that
3859
+ // can be spoken into plain text
3860
+ //
3861
+ function processDOMelements(clone) {
3862
+ var copy, title, title_element, content_type, content_element, content, appended, prepend;
3863
+
3864
+ // Remove tags from the "ignoreTags" array because the
3865
+ // user called "speak2me('recognize')" and said he/she
3866
+ // doesn't want some tags un-spoken. Double negative there,
3867
+ // but it does make sense.
3868
+ //
3869
+ if (recognizeTagsUser.length > 0) {
3870
+ for (var prop in recognizeTagsUser) {
3871
+ var index = ignoreTags.indexOf(recognizeTagsUser[prop]);
3872
+ if (index > -1) {
3873
+ ignoreTags.splice(index, 1);
3874
+ }
3875
+ }
3876
+ }
3877
+ ;
3878
+
3879
+ // Remove DOM objects listed in the "ignoreTags" array
3880
+ //
3881
+ for (var prop in ignoreTags) {
3882
+ jQuery(clone).find(ignoreTags[prop]).addBack(ignoreTags[prop]).not('[data-speak2me-recognize]').each(function () {
3883
+ jQuery(this).html('');
3884
+ });
3885
+ }
3886
+ ;
3887
+
3888
+ // Remove DOM objects specified in the "ignoreTagsUser" array
3889
+ // Specified when calling "speak2me('ignore')".
3890
+ //
3891
+ if (ignoreTagsUser.length > 0) {
3892
+ for (var prop in ignoreTagsUser) {
3893
+ jQuery(clone).find(ignoreTagsUser[prop]).addBack(ignoreTagsUser[prop]).not('[data-speak2me-recognize]').each(function () {
3894
+ jQuery(this).html('');
3895
+ });
3896
+ }
3897
+ ;
3898
+ }
3899
+ ;
3900
+
3901
+ // Remove DOM objects marked in the HTML code by "data-speak2me-ignore".
3902
+ //
3903
+ jQuery(clone).find('[data-speak2me-ignore]').addBack('[data-speak2me-ignore]').each(function () {
3904
+ jQuery(this).html('');
3905
+ });
3906
+
3907
+ // Remove DOM objects in the HTML code mareked by class "speak2me-ignore".
3908
+ //
3909
+ jQuery(clone).find('.speak2me-ignore').addBack('[data-speak2me-ignore]').each(function () {
3910
+ jQuery(this).html('');
3911
+ });
3912
+
3913
+ // Search for prepend data specified in the HTML code by "data-speak2me-prepend".
3914
+ //
3915
+ jQuery(clone).find('[data-speak2me-prepend]').addBack('[data-speak2me-prepend]').each(function () {
3916
+ copy = jQuery(this).data('speak2me-prepend');
3917
+ jQuery(this).prepend(copy + ' ');
3918
+ });
3919
+
3920
+ // Search for append data specified in the HTML code by "data-speak2me-append".
3921
+ //
3922
+ jQuery(clone).find('[data-speak2me-append]').addBack('[data-speak2me-append]').each(function () {
3923
+ copy = jQuery(this).data('speak2me-append');
3924
+ jQuery(this).append(' ' + copy);
3925
+ });
3926
+
3927
+ // Search for tags to prepend and append specified by the "voiceTags" array.
3928
+ //
3929
+ var count = 0;
3930
+ for (var tag in voiceTags) {
3931
+ jQuery(clone).find(tag).each(function () {
3932
+ if (customTags[tag]) {
3933
+ jQuery(this).prepend(customTags[tag].prepend + ' ');
3934
+ jQuery(this).append(' ' + customTags[tag].append);
3935
+ } else {
3936
+ jQuery(this).prepend(voiceTags[tag].prepend + ' ');
3937
+ jQuery(this).append(' ' + voiceTags[tag].append);
3938
+ }
3939
+ ;
3940
+ });
3941
+ }
3942
+ ;
3943
+
3944
+ // Search for <h1> through <h6> and <li> and <br> to add
3945
+ // a pause at the end of those tags. This is done
3946
+ // because these tags require a pause, but often don't
3947
+ // have a comma or period at the end of their text.
3948
+ //
3949
+ jQuery(clone).find('h1,h2,h3,h4,h5,h6,li,p').addBack('h1,h2,h3,h4,h5,h6,li,p').each(function () {
3950
+ jQuery(this).append(pause_spoken);
3951
+ });
3952
+
3953
+ // Search for <br> tags to add a pause at the end.
3954
+ jQuery(clone).find('br').each(function () {
3955
+ jQuery(this).after(pause_spoken);
3956
+ });
3957
+
3958
+ // Search for <figure>, check for <figcaption>, insert
3959
+ // that text if exists and then remove the whole DOM object.
3960
+ //
3961
+ jQuery(clone).find('figure').addBack('figure').each(function () {
3962
+ copy = jQuery(this).find('figcaption').html();
3963
+ if (customTags['figure']) {
3964
+ prepend = customTags['figure'].prepend;
3965
+ } else {
3966
+ prepend = voiceTags['figure'].prepend;
3967
+ }
3968
+ if (copy != undefined && copy !== '') {
3969
+ jQuery('<div>' + prepend + ' ' + copy + '</div>').insertBefore(this);
3970
+ }
3971
+ jQuery(this).remove();
3972
+ });
3973
+
3974
+ // Search for <image> tags, check for ALT attribute, and insert
3975
+ // text if exists and then dinally remove the DOM object.
3976
+ // NOTE: had to make adjustments for nesting in <picture> tags.
3977
+ //
3978
+ jQuery(clone).find('img').addBack('img').each(function () {
3979
+ copy = jQuery(this).attr('alt');
3980
+ var parent = jQuery(this).parent();
3981
+ var parentName = parent.get(0).tagName;
3982
+ if (customTags['img']) {
3983
+ prepend = customTags['img'].prepend;
3984
+ } else {
3985
+ prepend = voiceTags['img'].prepend;
3986
+ }
3987
+ if (copy !== undefined && copy != '') {
3988
+ if (parentName == 'PICTURE') {
3989
+ var par;
3990
+ jQuery('<div>' + prepend + ' ' + copy + pause_spoken + '</div>').insertBefore(parent);
3991
+ } else {
3992
+ jQuery('<div>' + prepend + ' ' + copy + pause_spoken + '</div>').insertBefore(this);
3993
+ }
3994
+ }
3995
+ jQuery(this).remove();
3996
+ });
3997
+
3998
+ // TODO: identify why the text 'Follow the link' is
3999
+ // already placed on the anchor
4000
+ //
4001
+ jQuery(clone).find('a').addBack('a').each(function () {
4002
+ var anchor = jQuery(this);
4003
+ copy = anchor[0].innerText;
4004
+ prepend = voiceTags['a'].prepend;
4005
+ appended = voiceTags['a'].append;
4006
+
4007
+ // jQuery('<div>' + prepend + copy + '</div>').insertBefore(this);
4008
+ jQuery('<div>' + copy + '</div>').insertBefore(this);
4009
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4010
+ jQuery(this).remove();
4011
+ });
4012
+
4013
+ // Search for admonitionblock elements and extract the type and
4014
+ // content. Insert type and content and then remove the DOM object.
4015
+ //
4016
+ jQuery(clone).find('.admonitionblock').addBack('admonitionblock').each(function () {
4017
+ content_type = this.classList[1];
4018
+ content_element = jQuery(this).find('.content');
4019
+ content = content_element[0].innerText;
4020
+ prepend = voiceTags['admonitionblock'].prepend + content_type + '. ';
4021
+ appended = voiceTags['admonitionblock'].append;
4022
+ if (content !== undefined && content != '') {
4023
+ jQuery('<div>' + prepend + ' ' + content + '</div>').insertBefore(this);
4024
+ jQuery('<div>' + appended + pause_spoken + '</div>').insertBefore(this);
4025
+ }
4026
+ jQuery(this).remove();
4027
+ });
4028
+
4029
+ // Search for quote block elements.
4030
+ //
4031
+ jQuery(clone).find('.quoteblock').addBack('quoteblock').each(function () {
4032
+ var attribution = jQuery(this).find('.attribution');
4033
+ content_element = jQuery(this).find('blockquote');
4034
+ content = content_element[0].innerText + 'quoted by, ' + attribution[0].innerText + ', ';
4035
+ prepend = voiceTags['quoteblock'].prepend;
4036
+ appended = voiceTags['quoteblock'].append;
4037
+ if (content !== undefined && content != '') {
4038
+ jQuery('<div>' + prepend + ' ' + content + '</div>').insertBefore(this);
4039
+ jQuery('<div>' + appended + pause_spoken + '</div>').insertBefore(this);
4040
+ }
4041
+ jQuery(this).remove();
4042
+ });
4043
+
4044
+ // Search for <table>, check for <caption>, insert text
4045
+ // if exists and then remove the DOM object.
4046
+ //
4047
+ jQuery(clone).find('table').addBack('table').each(function () {
4048
+ copy = jQuery(this).find('caption').text();
4049
+ prepend = voiceTags['table'].prepend;
4050
+ appended = voiceTags['table'].append;
4051
+ if (copy !== undefined && copy != '') {
4052
+ jQuery('<div>' + prepend + ' ' + copy + '</div>').insertBefore(this);
4053
+ jQuery('<div>' + appended + pause_spoken + '</div>').insertBefore(this);
4054
+ }
4055
+ jQuery(this).remove();
4056
+ });
4057
+
4058
+ // Search for cards|header elements and then remove the DOM object.
4059
+ //
4060
+ jQuery(clone).find('.card-header').addBack('card-header').each(function () {
4061
+ title_element = jQuery(this).find('.card-title');
4062
+ prepend = voiceTags['card-header'].prepend;
4063
+ appended = voiceTags['card-header'].append;
4064
+ if (title_element.length) {
4065
+ title = title_element[0].innerText + pause_spoken;
4066
+ } else {
4067
+ title = '';
4068
+ }
4069
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4070
+ jQuery('<div>' + appended + title + '</div>').insertBefore(this);
4071
+ jQuery(title_element).remove();
4072
+ });
4073
+
4074
+ // Search for doc-example elements and then remove the DOM object.
4075
+ //
4076
+ jQuery(clone).find('.doc-example').addBack('doc-example').each(function () {
4077
+ prepend = voiceTags['doc-example'].prepend;
4078
+ appended = voiceTags['doc-example'].append;
4079
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4080
+ jQuery('<div>' + appended + pause_spoken + '</div>').insertBefore(this);
4081
+ jQuery(this).remove();
4082
+ });
4083
+
4084
+ // Search for listingblock elements, check for previous declared <div>
4085
+ // container that contains the title element and insert the
4086
+ // text if exists and then finally remove the DOM object.
4087
+ //
4088
+ jQuery(clone).find('.listingblock').addBack('listingblock').each(function () {
4089
+ title_element = jQuery(this).find('.title');
4090
+ if (title_element.length) {
4091
+ copy = title_element[0].innerText;
4092
+ } else {
4093
+ copy = '';
4094
+ }
4095
+ prepend = voiceTags['listingblock'].prepend;
4096
+ appended = voiceTags['listingblock'].append;
4097
+ if (copy !== undefined && copy != '') {
4098
+ jQuery('<div>' + prepend + ' with the caption,' + copy + pause_spoken + '</div>').insertBefore(this);
4099
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4100
+ } else {
4101
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4102
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4103
+ }
4104
+ jQuery(this).remove();
4105
+ });
4106
+
4107
+ // Search for <carousel> tags, check for previous declared <div>
4108
+ // container that contains the title element and insert the
4109
+ // text if exists and finally remove the DOM object.
4110
+ //
4111
+ jQuery(clone).find('carousel').addBack('carousel').each(function () {
4112
+ if ($(this).prev()[0].innerText !== undefined) {
4113
+ title = $(this).prev()[0].innerText;
4114
+ title_element = jQuery(this).prev();
4115
+ // remove the title 'before' the DOM object deleted
4116
+ //
4117
+ jQuery(title_element).remove();
4118
+ } else {
4119
+ title = '';
4120
+ }
4121
+ prepend = voiceTags['carousel'].prepend;
4122
+ appended = voiceTags['carousel'].append;
4123
+ if (title !== undefined && title != '') {
4124
+ jQuery('<div>' + prepend + ' with the caption,' + title + pause_spoken + '</div>').insertBefore(this);
4125
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4126
+ } else {
4127
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4128
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4129
+ }
4130
+ jQuery(this).remove();
4131
+ });
4132
+
4133
+ // Search for <slider> tags, check for previous declared <div>
4134
+ // container that contains the title element and insert the
4135
+ // text if exists and finally remove the DOM object.
4136
+ //
4137
+ jQuery(clone).find('slider').addBack('slider').each(function () {
4138
+ if ($(this).prev()[0].innerText !== undefined) {
4139
+ title = $(this).prev()[0].innerText;
4140
+ title_element = jQuery(this).prev();
4141
+ // remove the title 'before' the DOM object deleted
4142
+ //
4143
+ jQuery(title_element).remove();
4144
+ } else {
4145
+ title = '';
4146
+ }
4147
+ prepend = voiceTags['slider'].prepend;
4148
+ appended = voiceTags['slider'].append;
4149
+ if (title !== undefined && title != '') {
4150
+ jQuery('<div>' + prepend + ' with the caption, ' + title + pause_spoken + '</div>').insertBefore(this);
4151
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4152
+ } else {
4153
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4154
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4155
+ }
4156
+ jQuery(this).remove();
4157
+ });
4158
+
4159
+ // Search for <gallery> tags, check for previous declared <div>
4160
+ // container that contains the title element and insert the
4161
+ // text if exists and finally remove the DOM object.
4162
+ //
4163
+ jQuery(clone).find('gallery').addBack('gallery').each(function () {
4164
+ if ($(this).prev()[0].innerText !== undefined) {
4165
+ title = $(this).prev()[0].innerText;
4166
+ title_element = jQuery(this).prev();
4167
+ // remove the title BEFORE the DOM object gets deleted
4168
+ //
4169
+ jQuery(title_element).remove();
4170
+ } else {
4171
+ title = '';
4172
+ }
4173
+ prepend = voiceTags['gallery'].prepend;
4174
+ appended = voiceTags['gallery'].append;
4175
+ if (title !== undefined && title != '') {
4176
+ prepend !== '' && jQuery('<div>' + prepend + ' with the caption ' + title + pause_spoken + '</div>').insertBefore(this);
4177
+ appended !== '' && jQuery('<div>' + appended + '</div>').insertBefore(this);
4178
+ } else {
4179
+ prepend !== '' && jQuery('<div>' + prepend + '</div>').insertBefore(this);
4180
+ appended !== '' && jQuery('<div>' + appended + '</div>').insertBefore(this);
4181
+ }
4182
+ jQuery(this).remove();
4183
+ });
4184
+
4185
+ // Search for <slider> tags, and extract the <caption> tag data,
4186
+ // insert the text if exists and finally remove the DOM object.
4187
+ //
4188
+ jQuery(clone).find('lightbox').addBack('gallery').each(function () {
4189
+ if ($(this).prev()[0].innerText !== undefined) {
4190
+ title = $(this).prev()[0].innerText;
4191
+ title_element = jQuery(this).prev();
4192
+ // remove the title 'before' the DOM object deleted
4193
+ //
4194
+ jQuery(title_element).remove();
4195
+ } else {
4196
+ title = '';
4197
+ }
4198
+ prepend = voiceTags['lightbox'].prepend;
4199
+ appended = voiceTags['lightbox'].append;
4200
+ if (title !== undefined && title != '') {
4201
+ jQuery('<div>' + prepend + ' with the caption,' + title + pause_spoken + '</div>').insertBefore(this);
4202
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4203
+ } else {
4204
+ jQuery('<div>' + prepend + '</div>').insertBefore(this);
4205
+ jQuery('<div>' + appended + '</div>').insertBefore(this);
4206
+ }
4207
+ jQuery(this).remove();
4208
+ });
4209
+
4210
+ // Search for DOM object to be replaced specified in
4211
+ // the HTML code by "data-speak2me-swap".
4212
+ //
4213
+ jQuery(clone).find('[data-speak2me-swap]').addBack('[data-speak2me-swap]').each(function () {
4214
+ copy = jQuery(this).data('speak2me-swap');
4215
+ jQuery(this).text(copy);
4216
+ });
4217
+
4218
+ // Search for DOM object to spelled out specified in
4219
+ // the HTML code by "data-speak2me-spell".
4220
+ //
4221
+ jQuery(clone).find('[data-speak2me-spell]').addBack('[data-speak2me-spell]').each(function () {
4222
+ copy = jQuery(this).text();
4223
+ copy = copy.split('').join(' ');
4224
+ jQuery(this).text(copy);
4225
+ });
4226
+ return clone;
4227
+ }
4228
+
4229
+ // run final cleanups on all DOM elements processed.
4230
+ //
4231
+ function cleanDOMelements(final) {
4232
+ var start, ended, speak, part1, part2, final;
4233
+
4234
+ // Search for <speak2me> in comments, copy the text,
4235
+ // place it outside the comment, and then splice together
4236
+ // "final" string again, which omits the comment.
4237
+ //
4238
+ while (final.indexOf('<!-- <speak2me>') != -1) {
4239
+ start = final.indexOf('<!-- <speak2me>');
4240
+ ended = final.indexOf('</speak2me> -->', start);
4241
+ if (ended == -1) {
4242
+ break;
4243
+ }
4244
+ speak = final.substring(start + 17, ended);
4245
+ part1 = final.substring(0, start);
4246
+ part2 = final.substring(ended + 17);
4247
+ final = part1 + ' ' + speak + ' ' + part2;
4248
+ }
4249
+ ;
4250
+
4251
+ // Strip out remaining comments.
4252
+ //
4253
+ final = final.replace(/<!--[\s\S]*?-->/g, '');
4254
+
4255
+ // Strip out remaining HTML tags.
4256
+ //
4257
+ final = final.replace(/(<([^>]+)>)/ig, '');
4258
+
4259
+ // Replace a string of characters with another as specified
4260
+ // by "speak2me('replace')".
4261
+ //
4262
+ var len = replacements.length;
4263
+ var i = 0;
4264
+ var old, rep;
4265
+ while (i < len) {
4266
+ old = replacements[i];
4267
+ old = old.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
4268
+ rep = replacements[i + 1] + ' ';
4269
+ var regexp = new RegExp(old, 'gi');
4270
+ var final = final.replace(regexp, rep);
4271
+ i = i + 2;
4272
+ }
4273
+
4274
+ // Remove double quotes.
4275
+ //
4276
+ final = final.replaceAll('"', '');
4277
+ final = final.replaceAll('“', '');
4278
+ final = final.replaceAll('”', '');
4279
+
4280
+ // Remove all colon ':' and replace by a dot.
4281
+ //
4282
+ final = final.replaceAll(':', '.');
4283
+
4284
+ // Replace all strange '., ' by a pause.
4285
+ //
4286
+ final = final.replaceAll('., ', '. ');
4287
+ final = final.replaceAll(' , ', ', ');
4288
+
4289
+ // Remove strange double pause elements.
4290
+ //
4291
+ final = final.replaceAll('. .', '');
4292
+ final = final.replaceAll(', .', '');
4293
+ final = final.replaceAll(' , ', '');
4294
+
4295
+ // Replace empty lines.
4296
+ //
4297
+ final = final.replace(/^$/g, '\n');
4298
+ final = final.replace(/^\s+$/g, '\n');
4299
+
4300
+ // Replace single full stops in line ' . ' or '. '.
4301
+ //
4302
+ final = final.replace(/\s+\.\s+/g, '\n');
4303
+ final = final.replace(/\s+\.\s+$/g, '\n');
4304
+
4305
+ // Replace strange double full stops '..'.
4306
+ //
4307
+ final = final.replace(/\.\./g, '.');
4308
+
4309
+ // Replace the abbreviations '.e.g.', 'E.g.' and 'etc.'.
4310
+ //
4311
+ final = final.replaceAll('e.g.', 'for example');
4312
+ final = final.replaceAll('E.g.', 'For example, ');
4313
+ final = final.replaceAll('etc.', 'and so on, ');
4314
+
4315
+ // Replace language specific abbreviations.
4316
+ // NOTE: may required for some voices|languages (like Gewrman)
4317
+ //
4318
+ final = final.replaceAll('z. B.', 'zum Beispiel, ');
4319
+
4320
+ // Remove question and exclamation (?|!) marks.
4321
+ //
4322
+ final = final.replace(/[\!\?]/g, '. ');
4323
+
4324
+ // Replace em-dashes and double-dashes with a pause
4325
+ // since most voices doesn't do so when speaking.
4326
+ //
4327
+ final = final.replaceAll('—', pause_spoken);
4328
+ final = final.replaceAll('–', pause_spoken);
4329
+ final = final.replaceAll('--', pause_spoken);
4330
+
4331
+ // When read from the DOM, a few special characters
4332
+ // (&amp; for example) display as their hex codes
4333
+ // rather than resolving into their actual character.
4334
+ //
4335
+ var txt = document.createElement('textarea');
4336
+ txt.innerHTML = final;
4337
+ final = txt.value;
4338
+
4339
+ // split the final text in to chunks (sentences).
4340
+ //
4341
+ const textChunks = splitTextIntoChunks(final);
4342
+ chunkCounterMax = textChunks.length;
4343
+ return textChunks;
4344
+ }
4345
+
4346
+ // return the Utterance object of the SpeechSynthesis API.
4347
+ //
4348
+ return speech;
4349
+ },
4350
+ // END speak
4351
+
4352
+ pause: function () {
4353
+ window.speechSynthesis.pause();
4354
+ return this;
4355
+ },
4356
+ // END pause
4357
+
4358
+ resume: function () {
4359
+ window.speechSynthesis.resume();
4360
+ return this;
4361
+ },
4362
+ // END resume
4363
+
4364
+ // jadams
4365
+ stop: function () {
4366
+ window.speechSynthesis.cancel();
4367
+ userStoppedSpeaking = true;
4368
+
4369
+ // jadams
4370
+ // NOTE: stopping coincident active speech synthesis
4371
+ // in multiple browser windows (tabs) does NOT work
4372
+ //
4373
+ // user_session.speech_synthesis_active = false;
4374
+ // j1.writeCookie({
4375
+ // name: 'user_session',
4376
+ // data: user_session,
4377
+ // secure: false,
4378
+ // expires: 0
4379
+ // });
4380
+
4381
+ return this;
4382
+ },
4383
+ // END stop
4384
+
4385
+ enabled: function () {
4386
+ return 'speechSynthesis' in window;
4387
+ },
4388
+ // END enabled
4389
+
4390
+ isSpeaking: function () {
4391
+ return window.speechSynthesis.speaking;
4392
+ },
4393
+ // END is Speaking
4394
+
4395
+ isPaused: function () {
4396
+ return window.speechSynthesis.paused;
4397
+ },
4398
+ // END isPaused
4399
+
4400
+ rate: function () {
4401
+ var num = arguments[0];
4402
+ if (num >= 0.1 && num <= 10) {
4403
+ rateUserDefault = num;
4404
+ } else if (num === undefined) {
4405
+ rateUserDefault = void 0;
4406
+ rate = rateDefault;
4407
+ }
4408
+ return this;
4409
+ },
4410
+ // END rate
4411
+
4412
+ pitch: function () {
4413
+ var num = arguments[0];
4414
+ if (num >= 0.1 && num <= 2) {
4415
+ pitchUserDefault = num;
4416
+ } else if (num === undefined) {
4417
+ pitchUserDefault = void 0;
4418
+ pitch = pitchDefault;
4419
+ }
4420
+ return this;
4421
+ },
4422
+ // END pitch
4423
+
4424
+ volume: function () {
4425
+ var num = arguments[0];
4426
+ if (num >= 0 && num <= 1) {
4427
+ volumeUserDefault = num;
4428
+ } else if (num === undefined) {
4429
+ volumeUserDefault = void 0;
4430
+ volume = volumeDefault;
4431
+ }
4432
+ ;
4433
+ return this;
4434
+ },
4435
+ // END volume
4436
+
4437
+ ignore: function () {
4438
+ var len = arguments.length;
4439
+ ignoreTagsUser.length = 0;
4440
+ while (len > 0) {
4441
+ len--;
4442
+ ignoreTagsUser.push(arguments[len]);
4443
+ }
4444
+ ;
4445
+ return this;
4446
+ },
4447
+ // END ignore
4448
+
4449
+ recognize: function () {
4450
+ var len = arguments.length;
4451
+ recognizeTagsUser.length = 0;
4452
+ while (len > 0) {
4453
+ len--;
4454
+ recognizeTagsUser.push(arguments[len]);
4455
+ }
4456
+ return this;
4457
+ },
4458
+ // END recognize
4459
+
4460
+ replace: function () {
4461
+ var len = arguments.length;
4462
+ replacements.length = 0;
4463
+ var i = 0;
4464
+ while (i < len) {
4465
+ replacements.push(arguments[i], arguments[i + 1]);
4466
+ i = i + 2;
4467
+ if (len - i == 1) {
4468
+ break;
4469
+ }
4470
+ ;
4471
+ }
4472
+ ;
4473
+ return this;
4474
+ },
4475
+ // END replace
4476
+
4477
+ customize: function () {
4478
+ var len = arguments.length;
4479
+ if (len == 0) {
4480
+ customTags = [];
4481
+ }
4482
+ if (len == 2) {
4483
+ if (['img', 'table', 'figure'].indexOf(arguments[0]) == -1) {
4484
+ console.warn("When customizing, tag indicated must be either 'img', 'table', or 'figure'.");
4485
+ return;
4486
+ }
4487
+ customTags[arguments[0].toString()] = new voiceTag(arguments[1].toString());
4488
+ }
4489
+ if (len == 3) {
4490
+ if (['q', "ol", "ul", "blockquote"].indexOf(arguments[0]) == -1) {
4491
+ console.warn("When customizing, tag indicated must be either 'q', 'ol', 'ul' or 'blockquote'.");
4492
+ return;
4493
+ }
4494
+ customTags[arguments[0].toString()] = new voiceTag(arguments[1].toString(), arguments[2].toString());
4495
+ }
4496
+ return this;
4497
+ },
4498
+ // END customize
4499
+
4500
+ getVoices: function () {
4501
+ // If no arguments, then the user has requested the array of
4502
+ // voices populated earlier.
4503
+ //
4504
+ if (arguments.length == 0) {
4505
+ return voices;
4506
+ }
4507
+
4508
+ // If there's another argument, we'll assume it's a jQuery
4509
+ // selector designating where to put the dropdown menu.
4510
+ // And if there's a third argument, that will be custom text
4511
+ // for the dropdown menu.
4512
+ // Then we'll create a dropdown menu with the voice names and,
4513
+ //in parenthesis, the language code.
4514
+ //
4515
+ var obj = jQuery(arguments[0]);
4516
+ var selectionTxt = 'Choose a voice';
4517
+ if (arguments[1] !== undefined) {
4518
+ selectionTxt = arguments[1];
4519
+ }
4520
+ obj.append(jQuery("<select id='voiceSelect' name='voiceSelect'><option value='none'>" + selectionTxt + "</option></select>"));
4521
+
4522
+ // jadams
4523
+ var skippedVoices = 0;
4524
+ for (var i = 0; i < voices.length; i++) {
4525
+ if (isChrome && voices[i].name.includes(ignoreProvider)) {
4526
+ skippedVoices++;
4527
+ continue;
4528
+ }
4529
+ if (isEdge && !voices[i].name.includes('Natural')) {
4530
+ skippedVoices++;
4531
+ continue;
4532
+ }
4533
+ var option = document.createElement('option');
4534
+ option.textContent = voices[i].name + ' (' + voices[i].language + ')';
4535
+ option.setAttribute('value', voices[i].name);
4536
+
4537
+ // set used voice as 'selected'
4538
+ if (voiceLanguageDefault !== undefined) {
4539
+ if (voices[i].name === voiceLanguageDefault) {
4540
+ option.setAttribute('selected', 'selected');
4541
+ }
4542
+ } else {
4543
+ if (voices[i].name.includes(voiceUserDefault)) {
4544
+ // option.setAttribute('selected', 'selected');
4545
+ }
4546
+ }
4547
+ option.setAttribute('data-speak2me-language', voices[i].language);
4548
+ obj.find('select').append(option);
4549
+ }
4550
+ return i - skippedVoices;
4551
+ },
4552
+ // END get Voiuces
4553
+
4554
+ setVoice: function () {
4555
+ // The setVoice function has to have two attributes
4556
+ // if not, exit the function.
4557
+ //
4558
+ if (arguments.length < 2) {
4559
+ return this;
4560
+ }
4561
+ var requestedVoice, requestedLanguage;
4562
+
4563
+ // User wants to change the voice directly. If that name indeed
4564
+ // exists, update the "voiceUserDefault" variable.
4565
+ //
4566
+ if (arguments[0] == 'name') {
4567
+ requestedVoice = arguments[1];
4568
+ for (var i = 0; i < voices.length; i++) {
4569
+ if (voices[i].name == requestedVoice) {
4570
+ voiceUserDefault = requestedVoice;
4571
+ }
4572
+ }
4573
+ }
4574
+
4575
+ // User wants to change the voice by only specifying the
4576
+ // first two characters of the language code. Case insensitive.
4577
+ //
4578
+ if (arguments[0] == 'language') {
4579
+ requestedLanguage = arguments[1].toUpperCase();
4580
+ if (requestedLanguage.length == 2) {
4581
+ for (var i = 0; i < voices.length; i++) {
4582
+ if (voices[i].language.substring(0, 2).toUpperCase() == requestedLanguage) {
4583
+ voiceUserDefault = voices[i].name;
4584
+ break;
4585
+ }
4586
+ }
4587
+ } else {
4588
+ // User wants to change the voice by specifying the
4589
+ // complete language code.
4590
+ for (var i = 0; i < voices.length; i++) {
4591
+ if (voices[i].language == requestedLanguage) {
4592
+ voiceUserDefault = voices[i].name;
4593
+ break;
4594
+ }
4595
+ }
4596
+ }
4597
+ }
4598
+ return this;
4599
+ } // END set Voice
4600
+ }; // END methods
4601
+
4602
+ // main speak2me method
4603
+ //
4604
+ $.fn.speak2me = function (method) {
4605
+ if (methods[method]) {
4606
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
4607
+ } else if (typeof method === 'object' || !method) {
4608
+ return methods.speak.apply(this, arguments);
4609
+ } else {
4610
+ jQuery.error('Method ' + method + ' does not exist on jQuery.speak2me');
4611
+ }
4612
+ };
4613
+ })(jQuery);
4614
+
4615
+ /***/ }),
4616
+
2960
4617
  /***/ 525:
2961
4618
  /***/ ((module) => {
2962
4619
 
@@ -9871,6 +11528,7 @@ window.j1.core.scrollSmooth = __webpack_require__(814);
9871
11528
 
9872
11529
  const J1Tocbot = __webpack_require__(799);
9873
11530
  const J1AttrChangeListener = __webpack_require__(610);
11531
+ const J1Speak2Me = __webpack_require__(229);
9874
11532
 
9875
11533
  // Passing log data over Internet|SeeMe (currently NOT used)
9876
11534
  // -----------------------------------------------------------------------------