Shuttle SEO Master

Adds automatic h1, h2 and h3 tag conversion from custom styles in Shuttle Data WYSIWYG-editors

  1. //
  2. // Written by Glenn Wiking
  3. // Script Version: 0.2.45
  4. //
  5. //
  6. // ==UserScript==
  7. // @name Shuttle SEO Master
  8. // @namespace SSM
  9. // @description Adds automatic h1, h2 and h3 tag conversion from custom styles in Shuttle Data WYSIWYG-editors
  10. // @version 0.2.45
  11. // @icon https://dlw0tascjxd4x.cloudfront.net/assets/img/symbol.svg
  12. // @license MIT
  13.  
  14. // @include *.shuttle.be/admin/*
  15. // @include *app.shuttle.be/*
  16. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
  17. // @require https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js
  18.  
  19.  
  20. // ==/UserScript==
  21.  
  22. $(window).on("load", function() {
  23. // Documentation
  24. let documentation =
  25. "<p>SEO Master is a tool that lets you more easily control <strong>SEO essentials</strong> within Shuttle. Its features are documented in more detail below.</p>"
  26. +
  27. "<p>The primary feature of SEO Master is the <strong>SEO button</strong>. When enabled, this button will appear in a WYSIWYG editors throughout Shuttle. Its behavior can be mapped in settings.</p>"
  28. +
  29. "<p>You can also map this behavior to selecting an item from the Styles dropdown menu.</p>"
  30. +
  31. "<br><p><img src='https://shuttle-storage.s3.amazonaws.com/addons/Flavor/SEO-Button-Doc1.png'></p>"
  32. +
  33. "<p>It's also possible to disable the SEO button, in which case the mapped behavior will be automatically applied to all WYSIWYG fields when a data entry or text editor in pages is opened. If you want per-context control, the SEO button is what you're after.</p>"
  34. +
  35. "<p>Currently supported features:</p>"
  36. +
  37. "<ul>"
  38. +
  39. "<li>Replacing <code>span.custom-style-h<em>x</em></code> with <code>h<em>x</em>.custom-style-h<em>x</em></code> in the current editor.</li>"
  40. +
  41. "</ul>"
  42. +
  43. "<br>"
  44. +
  45. "<p>More features coming soon!</p>"
  46. // Apply CKE
  47. function apply(cke) {
  48. console.log( cke );
  49. cke.addClass("affected");
  50. // Appearance
  51. cke.find(".cke_top").append("<span id='cke_seo' class='cke_toolbar' role='toolbar'></span>");
  52. cke.find("#cke_seo").append("<span class='cke_toolbar_start'></span><span class='cke_toolgroup hidden'></span><span class='cke_toolbar_end'></span>");
  53. cke.find("#cke_seo .cke_toolgroup").append("<a id='cke_seo_button' class='cke_button cke_button_seo cke_button_off' title='SEO' tabindex='-1' hideonfocus='true' role='button' onkeypress='return false;'></a>");
  54. cke.find("#cke_seo_button").append("<span class='cke_button_icon cke_button__seo_icon'></span>");
  55. cke.find("#cke_seo_button").append("<span class='cke_button_label cke_button__seo_label'></span>");
  56. cke.find(".cke_button__seo_label").text("SEO");
  57. if ( localStorage.getItem("SEO-button-active") == null ) {
  58. localStorage.setItem("SEO-button-active", true);
  59. } else {
  60. if ( localStorage.getItem("SEO-button-active") == "true" ) {
  61. $(".cke_button_seo").parent().removeClass("hidden");
  62. }
  63. }
  64. console.log( "GG" );
  65. // Headings Clean (SEO Button)
  66. function clean() {
  67. cke.find(".cke_wysiwyg_frame").contents().find("span[class^='custom-style-h']").each( function() {
  68. let classes = $(this).attr("class").split(" ");
  69. let h;
  70. let content;
  71. for ( i = 0; i < classes.length; i++ ) {
  72. console.log( classes[i] );
  73. if ( classes[i].includes("custom-style-h") ) {
  74. h = classes[i].replace("custom-style-h","");
  75. content = $(this).html();
  76. }
  77. }
  78. //$(this).parent().prepend("<h"+ h +" class='" + classes.toString().replace(/,/g," ") + "'>"+ content +"</h"+ h +">");
  79. $(this).parent("p").before("<h"+ h +" class='" + classes.toString().replace(/,/g," ") + "'>"+ content +"</h"+ h +">");
  80. //$(this).addClass("gg");
  81. console.log( classes.toString().replace(/,/g," ") );
  82. console.log( $(this).parent() );
  83. $(this).parent("p").remove();
  84. });
  85. }
  86. if ( localStorage.getItem("SEO-button-active") != null ) {
  87. if ( localStorage.getItem("SEO-button-active") == "false" ) { clean(); };
  88. };
  89. console.log("G");
  90. cke.find("#cke_seo_button").on("click", clean);
  91. cke.find(".cke_toolbar a.cke_combo_button[aria-haspopup]").on("click", function() {
  92. cke.addClass("style-open");
  93. console.log("Styles");
  94. });
  95. };
  96. /*let applyOnStyles = setInterval( function() {
  97. $(".cke_combopanel[style*='z-index: 10001']").on("click", function() {
  98. if ( localStorage.getItem("SEO-style-apply-active") != null ) {
  99. if ( localStorage.getItem("SEO-style-apply-active") == "false" ) {
  100. setTimeout( function() {
  101. clean();
  102. }, 200);
  103. };
  104. };
  105. });
  106. $(".cke_combopanel[style*='z-index: 10001']").attr("g","gg");
  107. $("[g]").on("click", function() {
  108. console.log("GGG");
  109. });
  110. if ( $(".cke_combopanel[style*='z-index: 10001'].affected").length == 1 ) { clearInterval(applyOnStyles) }
  111. console.log( $(".cke_combopanel[style*='z-index: 10001']:not(.affected)") );
  112. //$(".cke_combopanel[style*='z-index: 10001']").addClass("affected");
  113. }, 500);*/
  114. // Apply Settings
  115. let iconCog = "<svg viewBox='410.9 287.6 20 20'><path d='M429,297.6c0-1.2,0.8-2.2,1.9-2.9c-0.2-0.7-0.5-1.4-0.8-2c-1.3,0.3-2.3-0.2-3.2-1.1c-0.9-0.9-1.2-1.9-0.8-3.2c-0.6-0.3-1.3-0.6-2-0.8c-0.7,1.2-1.9,1.9-3.1,1.9 c-1.2,0-2.5-0.8-3.1-1.9c-0.7,0.2-1.4,0.5-2,0.8c0.3,1.3,0.1,2.3-0.8,3.2 c-0.9,0.9-1.9,1.4-3.2,1.1c-0.3,0.6-0.6,1.3-0.8,2c1.2,0.7,1.9,1.7,1.9,2.9c0,1.2-0.8,2.5-1.9,3.1c0.2,0.7,0.5,1.4,0.8,2 c1.3-0.3,2.3-0.1,3.2,0.8c0.9,0.9,1.2,1.9,0.8,3.2c0.6,0.3,1.3,0.6,2,0.8c0.7-1.2,1.9-1.9,3.1-1.9c1.2,0,2.5,0.8,3.1,1.9 c0.7-0.2,1.4-0.5,2-0.8c-0.3-1.3-0.1-2.3,0.8-3.2c0.9-0.9,1.9-1.4,3.2-1.1c0.3-0.6,0.6-1.3,0.8-2C429.8,299.9,429,298.9,429,297.6z M420.9,302c-2.4,0-4.3-1.9-4.3-4.3c0-2.4,1.9-4.3,4.3-4.3c2.4,0,4.3,1.9,4.3,4.3C425.3,300,423.3,302,420.9,302z'></path></svg>"
  116. let iconLog = "<svg viewBox='0 0 20 20'><g><path d='M16.3,8.6H8c-0.8,0-0.9,0.6-0.9,1.4s0.1,1.4,0.9,1.4h8.3c0.8,0,0.9-0.6,0.9-1.4S17.1,8.6,16.3,8.6z M19.1,15.7H8 c-0.8,0-0.9,0.6-0.9,1.4s0.1,1.4,0.9,1.4h11.1c0.8,0,0.9-0.6,0.9-1.4S19.9,15.7,19.1,15.7z M8,4.3h11.1c0.8,0,0.9-0.6,0.9-1.4 s-0.1-1.4-0.9-1.4H8c-0.8,0-0.9,0.6-0.9,1.4S7.2,4.3,8,4.3z M3.4,8.6H0.9C0.1,8.6,0,9.2,0,10s0.1,1.4,0.9,1.4h2.6 c0.8,0,0.9-0.6,0.9-1.4S4.2,8.6,3.4,8.6z M3.4,15.7H0.9c-0.8,0-0.9,0.6-0.9,1.4s0.1,1.4,0.9,1.4h2.6c0.8,0,0.9-0.6,0.9-1.4 S4.2,15.7,3.4,15.7z M3.4,1.4H0.9C0.1,1.4,0,2.1,0,2.9s0.1,1.4,0.9,1.4h2.6c0.8,0,0.9-0.6,0.9-1.4S4.2,1.4,3.4,1.4z'></path></g></svg>"
  117. let iconPage = "<svg viewBox='0 0 384 512'><path d='M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm64 236c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-64c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-72v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm96-114.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z'/></svg>"
  118. let iconImport = "<svg viewBox='0 0 20 20'><path d='M15,7h-3V1H8v6H5l5,5L15,7z M19.338,13.532c-0.21-0.224-1.611-1.723-2.011-2.114C17.062,11.159,16.683,11,16.285,11h-1.757 l3.064,2.994h-3.544c-0.102,0-0.194,0.052-0.24,0.133L12.992,16H7.008l-0.816-1.873c-0.046-0.081-0.139-0.133-0.24-0.133H2.408 L5.471,11H3.715c-0.397,0-0.776,0.159-1.042,0.418c-0.4,0.392-1.801,1.891-2.011,2.114c-0.489,0.521-0.758,0.936-0.63,1.449 l0.561,3.074c0.128,0.514,0.691,0.936,1.252,0.936h16.312c0.561,0,1.124-0.422,1.252-0.936l0.561-3.074 C20.096,14.468,19.828,14.053,19.338,13.532z'></path></svg>"
  119. let iconHelp = "<svg x='0px' y='0px' viewBox='0 0 20 20' xml:space='preserve'><g><path d='M19,10c0,5-4,9-9,9c-5,0-9-4-9-9c0-5,4-9,9-9C15,1,19,5,19,10z M14.1,7.4c0-2.2-1.9-3.8-4.1-3.8c-2.2,0-3.8,1.9-3.8,1.9 L7.8,7c0,0,1.1-1.1,2.2-1.1s1.9,0.8,1.9,1.5c0,1.5-3,1.5-3,3.8v0.7h2.3v-0.4C11.1,10.4,14.1,10.4,14.1,7.4z M11.5,15.3 c0-0.8-0.7-1.5-1.5-1.5s-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5S11.5,16.1,11.5,15.3z'></path></g></svg>";
  120. function settings() {
  121. let panel = $("a[data-active-id='cookies']").closest(".shuttle-Panel-inner");
  122. panel.append("<div class='shuttle-SubNav shuttle-SubNav--alt heading-addons'></div>");
  123. panel.find(".heading-addons").append("<div class='shuttle-SubNav-title'>Add-ons</div>");
  124. panel.find(".heading-addons").append("<ul class='shuttle-SubNav-wrapItems Nav Nav--stacked'></ul>");
  125. }
  126. function settingsSEO() {
  127. let panel = $("a[data-active-id='cookies']").closest(".shuttle-Panel-inner");
  128. let menuItems = [];
  129. menuItems.push({"label":"Settings", "icon":iconCog}, {"label":"Documentation", "icon":iconPage}, {"label":"Changelog", "icon":iconLog}, {"label":"Import/export Settings", "icon":iconImport});
  130. $(".heading-addons ul").append("<li class='shuttle-SubNav-item'><a class='shuttle-SubNav-itemTarget settings-seo' href=''><div class='Icon'></div>SEO Master</a></li>");
  131. $(".heading-addons .settings-seo .Icon").append( iconCog );
  132. $(".settings-seo").on("click", function() {
  133. if ( $(".panel-seo").length == 0 ) {
  134. $(".shuttle-Panel--mainNav").nextAll(".shuttle-Panels:visible").append("<div class='shuttle-Panels shuttle-Panels--nested panel-seo' style='margin-left: 250px; top: 0px;'></div>")
  135. $(".panel-seo").append("<div class='shuttle-Panel shuttle-Panel--withTabs'></div>");
  136. $(".panel-seo > .shuttle-Panel").append("<div class='shuttle-Panel-inner' style='top: 115px; bottom: 0px;'></div>");
  137. $(".panel-seo > .shuttle-Panel").append("<div class='shuttle-Panel-header'></div>");
  138. } else {
  139. $(".panel-seo").parent().find("> .shuttle-Panels--nested:not(.panel-seo)").hide();
  140. }
  141. // Open Settings
  142. panel.find(".is-selected:visible").removeClass("is-selected");
  143. $(this).addClass("is-selected");
  144. // Fill with Appliccable Settings
  145. if ( $(".panel-seo.affected").length == 0 ) {
  146. $(".panel-seo .shuttle-Panel-header").append("<div class='shuttle-Panel-title u-textTruncate'>SEO Master</div>");
  147. $(".panel-seo .shuttle-Panel-header").append("<div class='shuttle-Panel-toolbar'></div>");
  148. $(".panel-seo .shuttle-Panel-header").append("<ul class='PanelNav Nav'></ul>");
  149. for ( i = 0; i < menuItems.length; i++ ) {
  150. let menuLabel = menuItems[i].label;
  151. let menuIcon = menuItems[i].icon;
  152. $(".panel-seo .shuttle-Panel-header ul").append("<li class='PanelNav-item'><a class='PanelNav-itemTarget' href='' data-tab='"+ menuLabel +"'><div class='Icon'>" + menuIcon + "</div>"+ menuLabel +"</a></li>");
  153. }
  154. $(".panel-seo .shuttle-Panel-header ul a").on("click", function(e) {
  155. e.preventDefault();
  156. $(".panel-seo .shuttle-Panel-header ul a").removeClass("is-selected");
  157. $(this).addClass("is-selected");
  158. });
  159. $(".panel-seo").addClass("affected");
  160. }
  161. // Settings Menus
  162. $(".PanelNav-itemTarget").on("click", function() {
  163. let which = $(this).attr("data-tab");
  164. function addPanel() { // Clean up and Add Panel
  165. $(".panel-seo .shuttle-SEO, .panel-seo .Form-item--actions").remove();
  166. $(".panel-seo .shuttle-Panel-inner").append("<div class='shuttle-SEO'></div>");
  167. };
  168. console.log(which);
  169. // Settings
  170. if ( which == "Settings" ) {
  171. addPanel();
  172. $(".shuttle-SEO").parent().parent().append("<div class='Form-item Form-item--actions'><div class='Form-controls'><input class='Button Button--primary' type='submit' value='Save'></div></div>");
  173. // Show SEO Button
  174. $(".shuttle-SEO").append("<div class='Form-item'><label for='seo-button-toggler' class='Form-label'>Show SEO Button</label> <div class='Form-controls'> <span class='switchery switchery-default seo-button-toggler'><small></small></span> </div> </div>");
  175. $(".shuttle-SEO label[for='seo-button-toggler']").append( "<span class='Form-help-icon' data-toggle='tooltip' data-original-title='Disabling the SEO Button will cause the selected behavior to apply automatically to all open WYSIWYG-editors'></span>" );
  176. $(".shuttle-SEO label[for='seo-button-toggler'] .Form-help-icon").append( iconHelp );
  177. $(".shuttle-SEO .seo-button-toggler").on("click", function() {
  178. $(this).toggleClass("on");
  179. if ( $(this).hasClass("on") ) {
  180. localStorage.setItem("SEO-button-active",true);
  181. } else {
  182. localStorage.setItem("SEO-button-active",false);
  183. }
  184. });
  185. if ( localStorage.getItem("SEO-button-active") == null ) {
  186. localStorage.setItem("SEO-button-active", true);
  187. } else {
  188. if ( localStorage.getItem("SEO-button-active") == "true" ) {
  189. $(".seo-button-toggler").addClass("on");
  190. }
  191. };
  192. // Apply on Style Change Button
  193. $(".shuttle-SEO").append("<div class='Form-item'><label for='seo-style-apply-toggler' class='Form-label'>Apply on style choice</label> <div class='Form-controls'> <span class='switchery switchery-default seo-style-apply-toggler'><small></small></span> </div> </div>");
  194. $(".shuttle-SEO label[for='seo-style-apply-toggler']").append( "<span class='Form-help-icon' data-toggle='tooltip' data-original-title='Immediately applies the selected feature to all appropriate elements in the open editor when selecting a Header style from the Styles dropdown'></span>" );
  195. $(".shuttle-SEO label[for='seo-style-apply-toggler'] .Form-help-icon").append( iconHelp );
  196. $(".shuttle-SEO .seo-style-apply-toggler").on("click", function() {
  197. $(this).toggleClass("on");
  198. if ( $(this).hasClass("on") ) {
  199. localStorage.setItem("SEO-style-apply-active",true);
  200. } else {
  201. localStorage.setItem("SEO-style-apply-active",false);
  202. }
  203. });
  204. if ( localStorage.getItem("SEO-style-apply-active") == null ) {
  205. localStorage.setItem("SEO-apply-active-active", true);
  206. } else {
  207. if ( localStorage.getItem("SEO-style-apply-active") == "true" ) {
  208. $(".seo-style-apply-toggler").addClass("on");
  209. }
  210. };
  211. // Enable character limit visualizer
  212. $(".shuttle-SEO").append("<div class='Form-item'><label for='seo-limit-toggler' class='Form-label'>Enable character limit visualizer</label> <div class='Form-controls'> <span class='switchery switchery-default seo-limit-toggler'><small></small></span> </div> </div>");
  213. $(".shuttle-SEO label[for='seo-limit-toggler']").append( "<span class='Form-help-icon' data-toggle='tooltip' data-original-title='Shows character limits in a number of relevant input and textarea elements'></span>" );
  214. $(".shuttle-SEO label[for='seo-limit-toggler'] .Form-help-icon").append( iconHelp );
  215. $(".shuttle-SEO .seo-limit-toggler").on("click", function() {
  216. $(this).toggleClass("on");
  217. if ( $(this).hasClass("on") ) {
  218. localStorage.setItem("SEO-limit-active",true);
  219. } else {
  220. localStorage.setItem("SEO-limit-active",false);
  221. }
  222. });
  223. if ( localStorage.getItem("SEO-limit-active") == null ) {
  224. localStorage.setItem("SEO-limit-active", true);
  225. } else {
  226. if ( localStorage.getItem("SEO-limit-active") == "true" ) {
  227. $(".seo-limit-toggler").addClass("on");
  228. }
  229. };
  230. $(".shuttle-SEO .Form-help-icon").on("mouseenter", function() {
  231. let tooltipContent = $(this).attr("data-original-title");
  232. let oTop = $(this).offset().top;
  233. let oLeft = $(this).offset().left;
  234. $("body").append("<div class='Tooltip fade in Tooltip--top Tooltip-seo'> <div class='Tooltip-item'>"+ tooltipContent +"</div> </div>");
  235. $(".Tooltip-seo").css({"top": oTop, "left": oLeft});
  236. });
  237. $(".shuttle-SEO .Form-help-icon").on("mouseleave", function() {
  238. $(".Tooltip").remove();
  239. });
  240. $(".shuttle-SEO .switchery, .shuttle-SEO .switchery small").addClass("animatable");
  241. }
  242. // Documentation
  243. if ( which == "Documentation" ) {
  244. addPanel();
  245. $(".shuttle-SEO").append("<div class='shuttle-Panel-title full-width no-float'>Documentation</div><div class='doc-text'>"+ documentation +"</div>");
  246. }
  247. // Changelog
  248. if ( which == "Changelog" ) {
  249. addPanel();
  250. $(".shuttle-SEO").append("<div class='shuttle-Panel-title full-width no-float'>Changelog</div><div class='doc-text'> Coming soon! </div>");
  251. }
  252. // Import/Export
  253. if ( which == "Import/export Settings" ) {
  254. addPanel();
  255. $(".shuttle-SEO").append("<div class='shuttle-Panel-title full-width no-float'>Import / Export settings</div><div class='doc-text'> Coming soon! </div>");
  256. }
  257. });
  258. $(".panel-seo .PanelNav-itemTarget[data-tab='Settings']").trigger("click");
  259. });
  260. };
  261. function settingsClose() {
  262. let panel = $("a[data-active-id='cookies']").closest(".shuttle-Panel-inner");
  263. let otherLink = panel.find(".shuttle-SubNav:not(.heading-addons) .shuttle-SubNav-wrapItems .shuttle-SubNav-itemTarget");
  264. otherLink.off("click");
  265. otherLink.on("click", function() {
  266. $(".heading-addons a").removeClass("is-selected");
  267. $(".panel-seo").remove();
  268. });
  269. $(".shuttle-Panel--mainNav li a").off("click");
  270. $(".shuttle-Panel--mainNav li a").on("click", function() {
  271. $(".heading-addons a").removeClass("is-selected");
  272. if ( $(this).attr("data-active-id") == "settings" && $(this).hasClass("is-selected") == false ) {
  273. console.log("Settings clicked while inactive");
  274. if ( $(".panel-seo").length ) {
  275. console.log("SEO Panel existed");
  276. setTimeout( function() {
  277. $(".shuttle-SubNav-itemTarget").removeClass("is-selected");
  278. $(".settings-seo").addClass("is-selected");
  279. }, 150);
  280. }
  281. }
  282. });
  283. };
  284. setInterval( function() {
  285. if ( $(".cke:not(.affected):not([style*='z-index: 10001']):visible").length ) {
  286. apply( $(".cke:not(.affected):not([style*='z-index: 10001']):visible") );
  287. }
  288. if ( $("a[data-active-id='settings'].is-selected").length && $("a[data-active-id='cookies']").length && $(".heading-addons").length == 0 ) {
  289. settings();
  290. }
  291. if ( $(".heading-addons").length && $(".settings-seo").length == 0 ) {
  292. settingsSEO();
  293. settingsClose();
  294. }
  295. }, 1000);
  296. // SEO Form Element Characters
  297. setInterval( function() {
  298. let descs = $("textarea[name*='meta_description']:not(.affected), textarea[id^='languages']:not(.affected), input#og_description:not(.affected), input[name*='meta_title']:not(.affected), input[name*='og_title']:not(.affected), input[name*='seo_titel']:not(.affected), textarea[name*='seo_omschrijving']:not(.affected)");
  299. descs.each( function() {
  300. $(this).parent().attr("limit", 150);
  301. if ( $(this).closest("form").find("input#key").length ) {
  302. if ( $(this).closest("form").find("input#key").val().includes("title") ) {
  303. $(this).parent().attr("limit", 60);
  304. }
  305. }
  306. if ( $(this).attr("name").includes("meta_title") || $(this).attr("name").includes("og_title") || $(this).attr("name").includes("seo_titel") ) {
  307. $(this).parent().attr("limit", 60);
  308. }
  309. });
  310. $(".seo-parent[limit='150']").attr("lowerlimit",50);
  311. function check() {
  312. if ( localStorage.getItem("SEO-limit-active") == null ) {
  313. localStorage.setItem("SEO-limit-active", true);
  314. } else {
  315. if ( localStorage.getItem("SEO-limit-active") == "true" ) {
  316. $(".seo-check-toggler").addClass("on");
  317. const limit = parseInt( $(this).parent().attr("limit") );
  318. const lowerLimit = parseInt( $(this).parent().attr("lowerlimit") );
  319. $(this).parent().attr("length", $(this).val().length );
  320. $(this).parent().addClass("seo-parent");
  321. if ( $(this).val().includes("@") ) {
  322. console.log("Dynamic");
  323. $(this).closest(".seo-parent").addClass("dyn");
  324. if ( ! $(this).val().includes("truncate(") ) {
  325. console.log("Not Truncated");
  326. $(this).parent().addClass("unknown").removeClass("known");
  327. } else {
  328. //console.log("Truncated");
  329. //let trimmed = parseInt( $(this).val().split("truncate(")[1].substr(0,5) ); // 2 Characters Over, Should Be: ") "
  330. let trimmed = 0;
  331. let totalTrunc = 0;
  332. let truncations = 0;
  333. $(this).val().split(" ").forEach( function(val, i) {
  334. if ( val.substr(0,1) == "@" ) {
  335. //console.log( "%c" + val, "color:#B66" );
  336. let trunc = parseInt( val.split("truncate(")[1].substr(0,5) );
  337. trimmed = trimmed + trunc;
  338. totalTrunc = totalTrunc + trunc;
  339. if ( val.includes("truncate(") ) {
  340. truncations = truncations + 1;
  341. }
  342. } else {
  343. //console.log( "%c" + val, "color:#66B" );
  344. trimmed = trimmed + val.length;
  345. }
  346. //console.log(truncations);
  347. });
  348. trimmed = trimmed + ( $(this).val().split(" ").length - 1 )
  349. $(this).parent().attr("remainder", trimmed - totalTrunc).attr("trunc", totalTrunc);
  350. console.log( trimmed );
  351. $(this).parent().attr("length", trimmed);
  352. $(this).parent().removeClass("unknown").addClass("known");
  353. if ( truncations > 1 ) { $(this).parent().addClass("multi") } else { $(this).parent().removeClass("multi") }
  354. //if ( remainder == 0 ) { $(this).parent().removeAttr("remainder") }
  355. }
  356. } else {
  357. $(this).closest(".seo-parent").removeClass("dyn known unknown");
  358. }
  359. if ( $(this).val().length == 0 ) { $(this).closest(".seo-parent").addClass("empty") } else { $(this).closest(".seo-parent").removeClass("empty") }
  360.  
  361. console.log( $(this).val() );
  362. let length = parseInt( $(this).parent().attr("length") );
  363.  
  364. if ( length > limit ) {
  365. $(this).parent().removeClass("tooshort").addClass("toolong");
  366. } else if ( length < lowerLimit ) {
  367. $(this).parent().removeClass("toolong").addClass("tooshort");
  368. } else {
  369. $(this).parent().removeClass("toolong tooshort");
  370. }
  371. console.log( $(this) );
  372. console.log( parseInt( $(this).parent().attr("length") ) + " | " + $(this).val().length + " - " + limit + " | " + lowerLimit );
  373. $(this).addClass("affected");
  374. //$(this).closest(".seo-parent.dyn[length='NaN']").removeclass("known");
  375. }
  376. };
  377. }
  378. descs.off("click change input");
  379. descs.on("click change input", check);
  380. descs.each(check);
  381. }, 1000);
  382. let css =
  383. ".seo-avail {border: 1px solid #7d03ab !important}"
  384. +
  385. ".cke_top .cke_seo .cke_button__source_icon {background-position: 0 -624px !important;}"
  386. +
  387. ".cke_top .cke_seo .cke_button {width: 46px !important;}"
  388. +
  389. ".cke_combo__styles {margin-right: 6px !important}"
  390. +
  391. ".cke_button_seo {width: 46px !important}"
  392. +
  393. ".cke_button__seo_label {display: block !important}"
  394. +
  395. "#cke_seo_button .cke_button__seo_icon {background: url(https://shuttle-assets-new.s3.amazonaws.com/assets/js/vendor/ckeditor/skins/moono/icons.png) no-repeat 0 -624px !important;}"
  396. +
  397. '.full-width {width: 100%}'
  398. +
  399. '.cke_toolgroup.hidden {height: 0; width: 0; opacity: 0; pointer-events: none;}'
  400. +
  401. '.no-float {float: unset !important}'
  402. +
  403. '.doc-text {margin-top: 10px; max-width: 720px; width: 60vw !important;}'
  404. +
  405. ".doc-text code {background: #444445; padding: 4px 6px; border: 1px solid #537488; border-radius: 4px; margin: 0 3px;}"
  406. +
  407. ".doc-text code em {color: #77c3f4; font-weight: 600;}"
  408. +
  409. ".switchery {background-color: #E2E2E2;}"
  410. +
  411. ".switchery.on {background-color: #359FE3 !important; border-color: #5ca1e8 !important;}"
  412. +
  413. ".animatable {transition: all 200ms ease-in-out 0s;}"
  414. +
  415. ".switchery.on small {margin-left: 20px;}"
  416. +
  417. ".Form-help-icon svg {height: 18px; width: 20px;}"
  418. +
  419. ".Tooltip-seo {transform: translateX( calc(-50% + 10px)) translateY(-100%);}"
  420. +
  421. '.seo-parent::after {content: attr(length) " / " attr(limit); position: absolute; bottom: 0; left: 0; transform: translateY(100%); font-size: 15px;}'
  422. +
  423. '.seo-parent.toolong::after, .seo-parent.tooshort:not(.unknown)::after, .seo-parent.empty[lowerlimit]::after, .seo-parent.empty[length="0"] {color: #F14949;}'
  424. +
  425. '.seo-parent.unknown::after {content: "? / " attr(limit)}'
  426. +
  427. '.seo-parent.known::after, .seo-parent.known.multi[trunc="NaN"]::after {content: "Truncated to: " attr(length) " / " attr(limit)}'
  428. +
  429. '.seo-parent.known.multi::after {content: "Truncated to " attr(trunc) " (+" attr(remainder) ") : " attr(length) " / " attr(limit)}'
  430. +
  431. '.seo-parent.known:not(.multi):not([remainder="0"])::after {content: "Truncated to " attr(trunc) " (+" attr(remainder) ") : " attr(length) " / " attr(limit)}'
  432. +
  433. '.seo-parent.unknown[length="NaN"]::after, .seo-parent.known[length="NaN"]::after, .seo-parent.dyn:not(.known)::after, .seo-parent.dyn.known[length="NaN"] {content: "? / " attr(limit) !important;}'
  434. +
  435. '.seo-parent.unknown:not([length="NaN"]):not(.dyn)::after {content: attr(length) " / " attr(limit) !important;}';
  436. $("head").append("<style class='seo-master' type='text/css'></style>");
  437. $(".seo-master").html( css );
  438. });