Atomic Blocks – Gutenberg Blocks Collection - Version 1.2.8

Version Description

  • Fix focus bug on Testimonial block title.
Download this release

Release Info

Developer atomicblocks
Plugin Icon 128x128 Atomic Blocks – Gutenberg Blocks Collection
Version 1.2.8
Comparing to
See all releases

Code changes from version 1.2.7 to 1.2.8

Files changed (78) hide show
  1. README.md +3 -0
  2. README.txt +4 -2
  3. atomicblocks.php +1 -1
  4. dist/blocks.build.js +1 -1
  5. src/blocks.js +16 -0
  6. src/blocks/block-accordion/components/accordion.js +41 -0
  7. src/blocks/block-accordion/components/icons.js +18 -0
  8. src/blocks/block-accordion/components/inspector.js +62 -0
  9. src/blocks/block-accordion/index.js +137 -0
  10. src/blocks/block-accordion/styles/editor.scss +11 -0
  11. src/blocks/block-accordion/styles/style.scss +52 -0
  12. src/blocks/block-author-profile/components/avatar.js +33 -0
  13. src/blocks/block-author-profile/components/icons.js +14 -0
  14. src/blocks/block-author-profile/components/inspector.js +181 -0
  15. src/blocks/block-author-profile/components/profile.js +42 -0
  16. src/blocks/block-author-profile/components/social.js +75 -0
  17. src/blocks/block-author-profile/index.js +317 -0
  18. src/blocks/block-author-profile/styles/editor.scss +33 -0
  19. src/blocks/block-author-profile/styles/style.scss +210 -0
  20. src/blocks/block-button/components/button.js +36 -0
  21. src/blocks/block-button/components/icons.js +18 -0
  22. src/blocks/block-button/components/inspector.js +128 -0
  23. src/blocks/block-button/index.js +187 -0
  24. src/blocks/block-button/styles/editor.scss +19 -0
  25. src/blocks/block-button/styles/style.scss +81 -0
  26. src/blocks/block-container/components/container.js +44 -0
  27. src/blocks/block-container/components/inspector.js +187 -0
  28. src/blocks/block-container/index.js +270 -0
  29. src/blocks/block-container/styles/editor.scss +31 -0
  30. src/blocks/block-container/styles/style.scss +103 -0
  31. src/blocks/block-cta/components/cta.js +39 -0
  32. src/blocks/block-cta/components/inspector.js +235 -0
  33. src/blocks/block-cta/index.js +406 -0
  34. src/blocks/block-cta/styles/editor.scss +31 -0
  35. src/blocks/block-cta/styles/style.scss +310 -0
  36. src/blocks/block-drop-cap/components/dropcap.js +42 -0
  37. src/blocks/block-drop-cap/components/icons.js +14 -0
  38. src/blocks/block-drop-cap/components/inspector.js +69 -0
  39. src/blocks/block-drop-cap/index.js +140 -0
  40. src/blocks/block-drop-cap/styles/editor.scss +55 -0
  41. src/blocks/block-drop-cap/styles/style.scss +79 -0
  42. src/blocks/block-layout-split/components/icons.js +14 -0
  43. src/blocks/block-layout-split/components/inspector.js +88 -0
  44. src/blocks/block-layout-split/components/profile.js +43 -0
  45. src/blocks/block-layout-split/components/social.js +75 -0
  46. src/blocks/block-layout-split/index.js +286 -0
  47. src/blocks/block-layout-split/styles/editor.scss +33 -0
  48. src/blocks/block-layout-split/styles/style.scss +59 -0
  49. src/blocks/block-notice/components/button.js +41 -0
  50. src/blocks/block-notice/components/icons.js +18 -0
  51. src/blocks/block-notice/components/inspector.js +125 -0
  52. src/blocks/block-notice/components/notice.js +49 -0
  53. src/blocks/block-notice/index.js +228 -0
  54. src/blocks/block-notice/styles/editor.scss +11 -0
  55. src/blocks/block-notice/styles/style.scss +78 -0
  56. src/blocks/block-post-grid/edit.js +298 -0
  57. src/blocks/block-post-grid/index.js +54 -0
  58. src/blocks/block-post-grid/styles/editor.scss +14 -0
  59. src/blocks/block-post-grid/styles/style.scss +171 -0
  60. src/blocks/block-sharing/components/inspector.js +169 -0
  61. src/blocks/block-sharing/components/sharing.js +38 -0
  62. src/blocks/block-sharing/index.js +179 -0
  63. src/blocks/block-sharing/styles/editor.scss +7 -0
  64. src/blocks/block-sharing/styles/style.scss +167 -0
  65. src/blocks/block-spacer/components/icons.js +18 -0
  66. src/blocks/block-spacer/components/inspector.js +112 -0
  67. src/blocks/block-spacer/components/spacer.js +42 -0
  68. src/blocks/block-spacer/index.js +138 -0
  69. src/blocks/block-spacer/styles/editor.scss +66 -0
  70. src/blocks/block-spacer/styles/style.scss +55 -0
  71. src/blocks/block-testimonial/components/icons.js +14 -0
  72. src/blocks/block-testimonial/components/inspector.js +109 -0
  73. src/blocks/block-testimonial/components/testimonial.js +44 -0
  74. src/blocks/block-testimonial/index.js +282 -0
  75. src/blocks/block-testimonial/styles/editor.scss +30 -0
  76. src/blocks/block-testimonial/styles/style.scss +155 -0
  77. src/common.scss +158 -0
  78. src/utils/helper.js +18 -0
README.md CHANGED
@@ -74,6 +74,9 @@ Yes, you will need to install the [Gutenberg plugin](https://wordpress.org/plugi
74
 
75
  ## Changelog
76
 
 
 
 
77
  **1.2.7**
78
  * Fix the URLInput on the Button block.
79
 
74
 
75
  ## Changelog
76
 
77
+ **1.2.8**
78
+ * Fix focus bug on Testimonial block title.
79
+
80
  **1.2.7**
81
  * Fix the URLInput on the Button block.
82
 
README.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://atomicblocks.com
4
  Tags: gutenberg, blocks, page builder, gutenberg blocks, editor, atomicblocks, builder, wordpress 5.0, options
5
  Requires at least: 4.7
6
  Tested up to: 4.9.5
7
- Stable tag: 1.2.7
8
  Requires PHP: 5.2.4
9
  License: GPLv2 or later
10
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -96,10 +96,12 @@ Yes, you will need to install the [Gutenberg plugin](https://wordpress.org/plugi
96
 
97
  == Changelog ==
98
 
 
 
 
99
  = 1.2.7 =
100
  * Fix URLInput on the Button block.
101
 
102
-
103
  = 1.2.6 =
104
  * Remove unnecessary state from blocks.
105
  * Improve call to decodeEntities.
4
  Tags: gutenberg, blocks, page builder, gutenberg blocks, editor, atomicblocks, builder, wordpress 5.0, options
5
  Requires at least: 4.7
6
  Tested up to: 4.9.5
7
+ Stable tag: 1.2.8
8
  Requires PHP: 5.2.4
9
  License: GPLv2 or later
10
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
96
 
97
  == Changelog ==
98
 
99
+ = 1.2.8 =
100
+ * Fix focus bug on Testimonial block title.
101
+
102
  = 1.2.7 =
103
  * Fix URLInput on the Button block.
104
 
 
105
  = 1.2.6 =
106
  * Remove unnecessary state from blocks.
107
  * Improve call to decodeEntities.
atomicblocks.php CHANGED
@@ -5,7 +5,7 @@
5
  * Description: A beautiful collection of handy Gutenberg blocks to help you get started with the new WordPress editor.
6
  * Author: atomicblocks
7
  * Author URI: http://arraythemes.com
8
- * Version: 1.2.7
9
  * License: GPL2+
10
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
11
  *
5
  * Description: A beautiful collection of handy Gutenberg blocks to help you get started with the new WordPress editor.
6
  * Author: atomicblocks
7
  * Author URI: http://arraythemes.com
8
+ * Version: 1.2.8
9
  * License: GPL2+
10
  * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
11
  *
dist/blocks.build.js CHANGED
@@ -2060,7 +2060,7 @@ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true }
2060
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
2061
 
2062
  "use strict";
2063
- eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_classnames__ = __webpack_require__(/*! classnames */ 1);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_classnames___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_classnames__);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__components_inspector__ = __webpack_require__(/*! ./components/inspector */ 181);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__components_testimonial__ = __webpack_require__(/*! ./components/testimonial */ 182);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__components_icons__ = __webpack_require__(/*! ./components/icons */ 185);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_style_scss__ = __webpack_require__(/*! ./styles/style.scss */ 186);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_style_scss___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__styles_style_scss__);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_editor_scss__ = __webpack_require__(/*! ./styles/editor.scss */ 187);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_editor_scss___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5__styles_editor_scss__);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\n/**\n * BLOCK: Atomic Blocks Testimonial\n */\n\n// Import block dependencies and components\n\n\n\n\n\n// Import CSS\n\n\n\n// Internationalization\nvar __ = wp.i18n.__;\n\n// Extend component\n\nvar Component = wp.element.Component;\n\n// Register block\n\nvar registerBlockType = wp.blocks.registerBlockType;\n\n// Register editor components\n\nvar _wp$editor = wp.editor,\n RichText = _wp$editor.RichText,\n AlignmentToolbar = _wp$editor.AlignmentToolbar,\n BlockControls = _wp$editor.BlockControls,\n BlockAlignmentToolbar = _wp$editor.BlockAlignmentToolbar,\n MediaUpload = _wp$editor.MediaUpload;\n\n// Register components\n\nvar _wp$components = wp.components,\n Button = _wp$components.Button,\n SelectControl = _wp$components.SelectControl;\n\nvar ABTestimonialBlock = function (_Component) {\n\t_inherits(ABTestimonialBlock, _Component);\n\n\tfunction ABTestimonialBlock() {\n\t\t_classCallCheck(this, ABTestimonialBlock);\n\n\t\treturn _possibleConstructorReturn(this, (ABTestimonialBlock.__proto__ || Object.getPrototypeOf(ABTestimonialBlock)).apply(this, arguments));\n\t}\n\n\t_createClass(ABTestimonialBlock, [{\n\t\tkey: 'render',\n\t\tvalue: function render() {\n\t\t\tvar _this2 = this;\n\n\t\t\t// Setup the attributes\n\t\t\tvar _props = this.props,\n\t\t\t _props$attributes = _props.attributes,\n\t\t\t testimonialName = _props$attributes.testimonialName,\n\t\t\t testimonialTitle = _props$attributes.testimonialTitle,\n\t\t\t testimonialContent = _props$attributes.testimonialContent,\n\t\t\t testimonialAlignment = _props$attributes.testimonialAlignment,\n\t\t\t testimonialImgURL = _props$attributes.testimonialImgURL,\n\t\t\t testimonialImgID = _props$attributes.testimonialImgID,\n\t\t\t testimonialBackgroundColor = _props$attributes.testimonialBackgroundColor,\n\t\t\t testimonialTextColor = _props$attributes.testimonialTextColor,\n\t\t\t testimonialFontSize = _props$attributes.testimonialFontSize,\n\t\t\t testimonialCiteAlign = _props$attributes.testimonialCiteAlign,\n\t\t\t attributes = _props.attributes,\n\t\t\t isSelected = _props.isSelected,\n\t\t\t editable = _props.editable,\n\t\t\t className = _props.className,\n\t\t\t setAttributes = _props.setAttributes;\n\n\n\t\t\tvar onSelectImage = function onSelectImage(img) {\n\t\t\t\tsetAttributes({\n\t\t\t\t\ttestimonialImgID: img.id,\n\t\t\t\t\ttestimonialImgURL: img.url\n\t\t\t\t});\n\t\t\t};\n\n\t\t\treturn [\n\t\t\t// Show the alignment toolbar on focus\n\t\t\twp.element.createElement(\n\t\t\t\tBlockControls,\n\t\t\t\t{ key: 'controls' },\n\t\t\t\twp.element.createElement(AlignmentToolbar, {\n\t\t\t\t\tvalue: testimonialAlignment,\n\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\treturn setAttributes({ testimonialAlignment: value });\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t),\n\t\t\t// Show the block controls on focus\n\t\t\twp.element.createElement(__WEBPACK_IMPORTED_MODULE_1__components_inspector__[\"a\" /* default */], Object.assign({ setAttributes: setAttributes }, this.props)),\n\t\t\t// Show the block markup in the editor\n\t\t\twp.element.createElement(\n\t\t\t\t__WEBPACK_IMPORTED_MODULE_2__components_testimonial__[\"a\" /* default */],\n\t\t\t\tthis.props,\n\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\ttagName: 'div',\n\t\t\t\t\tmultiline: 'p',\n\t\t\t\t\tplaceholder: __('Add testimonial text...'),\n\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\tvalue: testimonialContent,\n\t\t\t\t\tformattingControls: ['bold', 'italic', 'strikethrough', 'link'],\n\t\t\t\t\tclassName: __WEBPACK_IMPORTED_MODULE_0_classnames___default()('ab-testimonial-text'),\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign: testimonialAlignment\n\t\t\t\t\t},\n\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\treturn setAttributes({ testimonialContent: value });\n\t\t\t\t\t},\n\t\t\t\t\tinlineToolbar: true\n\t\t\t\t}),\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'div',\n\t\t\t\t\t{ 'class': 'ab-testimonial-info' },\n\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{ 'class': 'ab-testimonial-avatar-wrap' },\n\t\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t{ 'class': 'ab-testimonial-image-wrap' },\n\t\t\t\t\t\t\twp.element.createElement(MediaUpload, {\n\t\t\t\t\t\t\t\tbuttonProps: {\n\t\t\t\t\t\t\t\t\tclassName: 'change-image'\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tonSelect: function onSelect(img) {\n\t\t\t\t\t\t\t\t\treturn setAttributes({\n\t\t\t\t\t\t\t\t\t\ttestimonialImgID: img.id,\n\t\t\t\t\t\t\t\t\t\ttestimonialImgURL: img.url\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttype: 'image',\n\t\t\t\t\t\t\t\tvalue: testimonialImgID,\n\t\t\t\t\t\t\t\trender: function render(_ref) {\n\t\t\t\t\t\t\t\t\tvar open = _ref.open;\n\t\t\t\t\t\t\t\t\treturn wp.element.createElement(\n\t\t\t\t\t\t\t\t\t\tButton,\n\t\t\t\t\t\t\t\t\t\t{ onClick: open },\n\t\t\t\t\t\t\t\t\t\t!testimonialImgID ? __WEBPACK_IMPORTED_MODULE_3__components_icons__[\"a\" /* default */].upload : wp.element.createElement('img', {\n\t\t\t\t\t\t\t\t\t\t\t'class': 'ab-testimonial-avatar',\n\t\t\t\t\t\t\t\t\t\t\tsrc: testimonialImgURL,\n\t\t\t\t\t\t\t\t\t\t\talt: 'avatar'\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\t\ttagName: 'h2',\n\t\t\t\t\t\tplaceholder: __('Add name'),\n\t\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\t\tvalue: testimonialName,\n\t\t\t\t\t\tclassName: 'ab-testimonial-name',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn _this2.props.setAttributes({ testimonialName: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\t\ttagName: 'h5',\n\t\t\t\t\t\tplaceholder: __('Add title'),\n\t\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\t\tvalue: testimonialTitle,\n\t\t\t\t\t\tclassName: 'ab-testimonial-title',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn _this2.props.setAttributes({ testimonialTitle: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t)];\n\t\t}\n\t}]);\n\n\treturn ABTestimonialBlock;\n}(Component);\n\n// Register the block\n\n\nregisterBlockType('atomic-blocks/ab-testimonial', {\n\ttitle: __('AB Testimonial'),\n\tdescription: __('Add a user testimonial with a name and title.'),\n\ticon: 'format-quote',\n\tcategory: 'atomic-blocks',\n\tkeywords: [__('testimonial'), __('quote'), __('atomic')],\n\tattributes: {\n\t\ttestimonialName: {\n\t\t\ttype: 'string',\n\t\t\tselector: '.ab-testimonial-name'\n\t\t},\n\t\ttestimonialTitle: {\n\t\t\ttype: 'string',\n\t\t\tselector: '.ab-testimonial-title'\n\t\t},\n\t\ttestimonialContent: {\n\t\t\ttype: 'array',\n\t\t\tselector: '.ab-testimonial-text',\n\t\t\tsource: 'children'\n\t\t},\n\t\ttestimonialAlignment: {\n\t\t\ttype: 'string'\n\t\t},\n\t\ttestimonialImgURL: {\n\t\t\ttype: 'string',\n\t\t\tsource: 'attribute',\n\t\t\tattribute: 'src',\n\t\t\tselector: 'img'\n\t\t},\n\t\ttestimonialImgID: {\n\t\t\ttype: 'number'\n\t\t},\n\t\ttestimonialBackgroundColor: {\n\t\t\ttype: 'string',\n\t\t\tdefault: '#f2f2f2'\n\t\t},\n\t\ttestimonialTextColor: {\n\t\t\ttype: 'string',\n\t\t\tdefault: '#32373c'\n\t\t},\n\t\ttestimonialFontSize: {\n\t\t\ttype: 'number',\n\t\t\tdefault: 18\n\t\t},\n\t\ttestimonialCiteAlign: {\n\t\t\ttype: 'string',\n\t\t\tdefault: 'left-aligned'\n\t\t}\n\t},\n\n\t// Render the block components\n\tedit: ABTestimonialBlock,\n\n\t// Save the attributes and markup\n\tsave: function save(props) {\n\n\t\t// Setup the attributes\n\t\tvar _props$attributes2 = props.attributes,\n\t\t testimonialName = _props$attributes2.testimonialName,\n\t\t testimonialTitle = _props$attributes2.testimonialTitle,\n\t\t testimonialContent = _props$attributes2.testimonialContent,\n\t\t testimonialAlignment = _props$attributes2.testimonialAlignment,\n\t\t testimonialImgURL = _props$attributes2.testimonialImgURL,\n\t\t testimonialImgID = _props$attributes2.testimonialImgID,\n\t\t testimonialBackgroundColor = _props$attributes2.testimonialBackgroundColor,\n\t\t testimonialTextColor = _props$attributes2.testimonialTextColor,\n\t\t testimonialFontSize = _props$attributes2.testimonialFontSize,\n\t\t testimonialCiteAlign = _props$attributes2.testimonialCiteAlign;\n\n\t\t// Save the block markup for the front end\n\n\t\treturn wp.element.createElement(\n\t\t\t__WEBPACK_IMPORTED_MODULE_2__components_testimonial__[\"a\" /* default */],\n\t\t\tprops,\n\t\t\twp.element.createElement(\n\t\t\t\t'div',\n\t\t\t\t{\n\t\t\t\t\tclassName: __WEBPACK_IMPORTED_MODULE_0_classnames___default()('ab-testimonial-text'),\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign: testimonialAlignment\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttestimonialContent\n\t\t\t),\n\t\t\twp.element.createElement(\n\t\t\t\t'div',\n\t\t\t\t{ 'class': 'ab-testimonial-info' },\n\t\t\t\ttestimonialImgURL && !!testimonialImgURL.length && wp.element.createElement(\n\t\t\t\t\t'div',\n\t\t\t\t\t{ 'class': 'ab-testimonial-avatar-wrap' },\n\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{ 'class': 'ab-testimonial-image-wrap' },\n\t\t\t\t\t\twp.element.createElement('img', {\n\t\t\t\t\t\t\t'class': 'ab-testimonial-avatar',\n\t\t\t\t\t\t\tsrc: testimonialImgURL,\n\t\t\t\t\t\t\talt: 'avatar'\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t),\n\t\t\t\ttestimonialName && !!testimonialName.length && wp.element.createElement(\n\t\t\t\t\t'h2',\n\t\t\t\t\t{ 'class': 'ab-testimonial-name',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\ttestimonialName\n\t\t\t\t),\n\t\t\t\ttestimonialTitle && !!testimonialTitle.length && wp.element.createElement(\n\t\t\t\t\t'small',\n\t\t\t\t\t{ 'class': 'ab-testimonial-title',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\ttestimonialTitle\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t}\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTgwLmpzIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vLy4vc3JjL2Jsb2Nrcy9ibG9jay10ZXN0aW1vbmlhbC9pbmRleC5qcz9jMzQyIl0sInNvdXJjZXNDb250ZW50IjpbInZhciBfY3JlYXRlQ2xhc3MgPSBmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoXCJ2YWx1ZVwiIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlOyBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBkZXNjcmlwdG9yLmtleSwgZGVzY3JpcHRvcik7IH0gfSByZXR1cm4gZnVuY3Rpb24gKENvbnN0cnVjdG9yLCBwcm90b1Byb3BzLCBzdGF0aWNQcm9wcykgeyBpZiAocHJvdG9Qcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpOyBpZiAoc3RhdGljUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTsgcmV0dXJuIENvbnN0cnVjdG9yOyB9OyB9KCk7XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbm5vdCBjYWxsIGEgY2xhc3MgYXMgYSBmdW5jdGlvblwiKTsgfSB9XG5cbmZ1bmN0aW9uIF9wb3NzaWJsZUNvbnN0cnVjdG9yUmV0dXJuKHNlbGYsIGNhbGwpIHsgaWYgKCFzZWxmKSB7IHRocm93IG5ldyBSZWZlcmVuY2VFcnJvcihcInRoaXMgaGFzbid0IGJlZW4gaW5pdGlhbGlzZWQgLSBzdXBlcigpIGhhc24ndCBiZWVuIGNhbGxlZFwiKTsgfSByZXR1cm4gY2FsbCAmJiAodHlwZW9mIGNhbGwgPT09IFwib2JqZWN0XCIgfHwgdHlwZW9mIGNhbGwgPT09IFwiZnVuY3Rpb25cIikgPyBjYWxsIDogc2VsZjsgfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSBcImZ1bmN0aW9uXCIgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKFwiU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCBcIiArIHR5cGVvZiBzdXBlckNsYXNzKTsgfSBzdWJDbGFzcy5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKHN1cGVyQ2xhc3MgJiYgc3VwZXJDbGFzcy5wcm90b3R5cGUsIHsgY29uc3RydWN0b3I6IHsgdmFsdWU6IHN1YkNsYXNzLCBlbnVtZXJhYmxlOiBmYWxzZSwgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSB9IH0pOyBpZiAoc3VwZXJDbGFzcykgT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mKHN1YkNsYXNzLCBzdXBlckNsYXNzKSA6IHN1YkNsYXNzLl9fcHJvdG9fXyA9IHN1cGVyQ2xhc3M7IH1cblxuLyoqXG4gKiBCTE9DSzogQXRvbWljIEJsb2NrcyBUZXN0aW1vbmlhbFxuICovXG5cbi8vIEltcG9ydCBibG9jayBkZXBlbmRlbmNpZXMgYW5kIGNvbXBvbmVudHNcbmltcG9ydCBjbGFzc25hbWVzIGZyb20gJ2NsYXNzbmFtZXMnO1xuaW1wb3J0IEluc3BlY3RvciBmcm9tICcuL2NvbXBvbmVudHMvaW5zcGVjdG9yJztcbmltcG9ydCBUZXN0aW1vbmlhbCBmcm9tICcuL2NvbXBvbmVudHMvdGVzdGltb25pYWwnO1xuaW1wb3J0IGljb25zIGZyb20gJy4vY29tcG9uZW50cy9pY29ucyc7XG5cbi8vIEltcG9ydCBDU1NcbmltcG9ydCAnLi9zdHlsZXMvc3R5bGUuc2Nzcyc7XG5pbXBvcnQgJy4vc3R5bGVzL2VkaXRvci5zY3NzJztcblxuLy8gSW50ZXJuYXRpb25hbGl6YXRpb25cbnZhciBfXyA9IHdwLmkxOG4uX187XG5cbi8vIEV4dGVuZCBjb21wb25lbnRcblxudmFyIENvbXBvbmVudCA9IHdwLmVsZW1lbnQuQ29tcG9uZW50O1xuXG4vLyBSZWdpc3RlciBibG9ja1xuXG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG5cbi8vIFJlZ2lzdGVyIGVkaXRvciBjb21wb25lbnRzXG5cbnZhciBfd3AkZWRpdG9yID0gd3AuZWRpdG9yLFxuICAgIFJpY2hUZXh0ID0gX3dwJGVkaXRvci5SaWNoVGV4dCxcbiAgICBBbGlnbm1lbnRUb29sYmFyID0gX3dwJGVkaXRvci5BbGlnbm1lbnRUb29sYmFyLFxuICAgIEJsb2NrQ29udHJvbHMgPSBfd3AkZWRpdG9yLkJsb2NrQ29udHJvbHMsXG4gICAgQmxvY2tBbGlnbm1lbnRUb29sYmFyID0gX3dwJGVkaXRvci5CbG9ja0FsaWdubWVudFRvb2xiYXIsXG4gICAgTWVkaWFVcGxvYWQgPSBfd3AkZWRpdG9yLk1lZGlhVXBsb2FkO1xuXG4vLyBSZWdpc3RlciBjb21wb25lbnRzXG5cbnZhciBfd3AkY29tcG9uZW50cyA9IHdwLmNvbXBvbmVudHMsXG4gICAgQnV0dG9uID0gX3dwJGNvbXBvbmVudHMuQnV0dG9uLFxuICAgIFNlbGVjdENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5TZWxlY3RDb250cm9sO1xuXG52YXIgQUJUZXN0aW1vbmlhbEJsb2NrID0gZnVuY3Rpb24gKF9Db21wb25lbnQpIHtcblx0X2luaGVyaXRzKEFCVGVzdGltb25pYWxCbG9jaywgX0NvbXBvbmVudCk7XG5cblx0ZnVuY3Rpb24gQUJUZXN0aW1vbmlhbEJsb2NrKCkge1xuXHRcdF9jbGFzc0NhbGxDaGVjayh0aGlzLCBBQlRlc3RpbW9uaWFsQmxvY2spO1xuXG5cdFx0cmV0dXJuIF9wb3NzaWJsZUNvbnN0cnVjdG9yUmV0dXJuKHRoaXMsIChBQlRlc3RpbW9uaWFsQmxvY2suX19wcm90b19fIHx8IE9iamVjdC5nZXRQcm90b3R5cGVPZihBQlRlc3RpbW9uaWFsQmxvY2spKS5hcHBseSh0aGlzLCBhcmd1bWVudHMpKTtcblx0fVxuXG5cdF9jcmVhdGVDbGFzcyhBQlRlc3RpbW9uaWFsQmxvY2ssIFt7XG5cdFx0a2V5OiAncmVuZGVyJyxcblx0XHR2YWx1ZTogZnVuY3Rpb24gcmVuZGVyKCkge1xuXHRcdFx0dmFyIF90aGlzMiA9IHRoaXM7XG5cblx0XHRcdC8vIFNldHVwIHRoZSBhdHRyaWJ1dGVzXG5cdFx0XHR2YXIgX3Byb3BzID0gdGhpcy5wcm9wcyxcblx0XHRcdCAgICBfcHJvcHMkYXR0cmlidXRlcyA9IF9wcm9wcy5hdHRyaWJ1dGVzLFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsTmFtZSA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsTmFtZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbFRpdGxlID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxUaXRsZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbENvbnRlbnQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbENvbnRlbnQsXG5cdFx0XHQgICAgdGVzdGltb25pYWxBbGlnbm1lbnQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbEFsaWdubWVudCxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbEltZ1VSTCA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsSW1nVVJMLFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsSW1nSUQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbEltZ0lELFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsQmFja2dyb3VuZENvbG9yID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxCYWNrZ3JvdW5kQ29sb3IsXG5cdFx0XHQgICAgdGVzdGltb25pYWxUZXh0Q29sb3IgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbFRleHRDb2xvcixcblx0XHRcdCAgICB0ZXN0aW1vbmlhbEZvbnRTaXplID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxGb250U2l6ZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbENpdGVBbGlnbiA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsQ2l0ZUFsaWduLFxuXHRcdFx0ICAgIGF0dHJpYnV0ZXMgPSBfcHJvcHMuYXR0cmlidXRlcyxcblx0XHRcdCAgICBpc1NlbGVjdGVkID0gX3Byb3BzLmlzU2VsZWN0ZWQsXG5cdFx0XHQgICAgZWRpdGFibGUgPSBfcHJvcHMuZWRpdGFibGUsXG5cdFx0XHQgICAgY2xhc3NOYW1lID0gX3Byb3BzLmNsYXNzTmFtZSxcblx0XHRcdCAgICBzZXRBdHRyaWJ1dGVzID0gX3Byb3BzLnNldEF0dHJpYnV0ZXM7XG5cblxuXHRcdFx0dmFyIG9uU2VsZWN0SW1hZ2UgPSBmdW5jdGlvbiBvblNlbGVjdEltYWdlKGltZykge1xuXHRcdFx0XHRzZXRBdHRyaWJ1dGVzKHtcblx0XHRcdFx0XHR0ZXN0aW1vbmlhbEltZ0lEOiBpbWcuaWQsXG5cdFx0XHRcdFx0dGVzdGltb25pYWxJbWdVUkw6IGltZy51cmxcblx0XHRcdFx0fSk7XG5cdFx0XHR9O1xuXG5cdFx0XHRyZXR1cm4gW1xuXHRcdFx0Ly8gU2hvdyB0aGUgYWxpZ25tZW50IHRvb2xiYXIgb24gZm9jdXNcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0QmxvY2tDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdjb250cm9scycgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KEFsaWdubWVudFRvb2xiYXIsIHtcblx0XHRcdFx0XHR2YWx1ZTogdGVzdGltb25pYWxBbGlnbm1lbnQsXG5cdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IHRlc3RpbW9uaWFsQWxpZ25tZW50OiB2YWx1ZSB9KTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0pXG5cdFx0XHQpLFxuXHRcdFx0Ly8gU2hvdyB0aGUgYmxvY2sgY29udHJvbHMgb24gZm9jdXNcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChJbnNwZWN0b3IsIE9iamVjdC5hc3NpZ24oeyBzZXRBdHRyaWJ1dGVzOiBzZXRBdHRyaWJ1dGVzIH0sIHRoaXMucHJvcHMpKSxcblx0XHRcdC8vIFNob3cgdGhlIGJsb2NrIG1hcmt1cCBpbiB0aGUgZWRpdG9yXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFRlc3RpbW9uaWFsLFxuXHRcdFx0XHR0aGlzLnByb3BzLFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoUmljaFRleHQsIHtcblx0XHRcdFx0XHR0YWdOYW1lOiAnZGl2Jyxcblx0XHRcdFx0XHRtdWx0aWxpbmU6ICdwJyxcblx0XHRcdFx0XHRwbGFjZWhvbGRlcjogX18oJ0FkZCB0ZXN0aW1vbmlhbCB0ZXh0Li4uJyksXG5cdFx0XHRcdFx0a2VlcFBsYWNlaG9sZGVyT25Gb2N1czogdHJ1ZSxcblx0XHRcdFx0XHR2YWx1ZTogdGVzdGltb25pYWxDb250ZW50LFxuXHRcdFx0XHRcdGZvcm1hdHRpbmdDb250cm9sczogWydib2xkJywgJ2l0YWxpYycsICdzdHJpa2V0aHJvdWdoJywgJ2xpbmsnXSxcblx0XHRcdFx0XHRjbGFzc05hbWU6IGNsYXNzbmFtZXMoJ2FiLXRlc3RpbW9uaWFsLXRleHQnKSxcblx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0dGV4dEFsaWduOiB0ZXN0aW1vbmlhbEFsaWdubWVudFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IHRlc3RpbW9uaWFsQ29udGVudDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRpbmxpbmVUb29sYmFyOiB0cnVlXG5cdFx0XHRcdH0pLFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J2RpdicsXG5cdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW5mbycgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0XHRcdHsgJ2NsYXNzJzogJ2FiLXRlc3RpbW9uaWFsLWF2YXRhci13cmFwJyB9LFxuXHRcdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW1hZ2Utd3JhcCcgfSxcblx0XHRcdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KE1lZGlhVXBsb2FkLCB7XG5cdFx0XHRcdFx0XHRcdFx0YnV0dG9uUHJvcHM6IHtcblx0XHRcdFx0XHRcdFx0XHRcdGNsYXNzTmFtZTogJ2NoYW5nZS1pbWFnZSdcblx0XHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRcdG9uU2VsZWN0OiBmdW5jdGlvbiBvblNlbGVjdChpbWcpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0dGVzdGltb25pYWxJbWdJRDogaW1nLmlkLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHR0ZXN0aW1vbmlhbEltZ1VSTDogaW1nLnVybFxuXHRcdFx0XHRcdFx0XHRcdFx0fSk7XG5cdFx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0XHR0eXBlOiAnaW1hZ2UnLFxuXHRcdFx0XHRcdFx0XHRcdHZhbHVlOiB0ZXN0aW1vbmlhbEltZ0lELFxuXHRcdFx0XHRcdFx0XHRcdHJlbmRlcjogZnVuY3Rpb24gcmVuZGVyKF9yZWYpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHZhciBvcGVuID0gX3JlZi5vcGVuO1xuXHRcdFx0XHRcdFx0XHRcdFx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRcdFx0XHRcdFx0QnV0dG9uLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHR7IG9uQ2xpY2s6IG9wZW4gfSxcblx0XHRcdFx0XHRcdFx0XHRcdFx0IXRlc3RpbW9uaWFsSW1nSUQgPyBpY29ucy51cGxvYWQgOiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoJ2ltZycsIHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHQnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtYXZhdGFyJyxcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRzcmM6IHRlc3RpbW9uaWFsSW1nVVJMLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdGFsdDogJ2F2YXRhcidcblx0XHRcdFx0XHRcdFx0XHRcdFx0fSlcblx0XHRcdFx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHR9KVxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdCksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFJpY2hUZXh0LCB7XG5cdFx0XHRcdFx0XHR0YWdOYW1lOiAnaDInLFxuXHRcdFx0XHRcdFx0cGxhY2Vob2xkZXI6IF9fKCdBZGQgbmFtZScpLFxuXHRcdFx0XHRcdFx0a2VlcFBsYWNlaG9sZGVyT25Gb2N1czogdHJ1ZSxcblx0XHRcdFx0XHRcdHZhbHVlOiB0ZXN0aW1vbmlhbE5hbWUsXG5cdFx0XHRcdFx0XHRjbGFzc05hbWU6ICdhYi10ZXN0aW1vbmlhbC1uYW1lJyxcblx0XHRcdFx0XHRcdHN0eWxlOiB7XG5cdFx0XHRcdFx0XHRcdGNvbG9yOiB0ZXN0aW1vbmlhbFRleHRDb2xvclxuXHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gX3RoaXMyLnByb3BzLnNldEF0dHJpYnV0ZXMoeyB0ZXN0aW1vbmlhbE5hbWU6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChSaWNoVGV4dCwge1xuXHRcdFx0XHRcdFx0dGFnTmFtZTogJ2g1Jyxcblx0XHRcdFx0XHRcdHBsYWNlaG9sZGVyOiBfXygnQWRkIHRpdGxlJyksXG5cdFx0XHRcdFx0XHRrZWVwUGxhY2Vob2xkZXJPbkZvY3VzOiB0cnVlLFxuXHRcdFx0XHRcdFx0dmFsdWU6IHRlc3RpbW9uaWFsVGl0bGUsXG5cdFx0XHRcdFx0XHRjbGFzc05hbWU6ICdhYi10ZXN0aW1vbmlhbC10aXRsZScsXG5cdFx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0XHRjb2xvcjogdGVzdGltb25pYWxUZXh0Q29sb3Jcblx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIF90aGlzMi5wcm9wcy5zZXRBdHRyaWJ1dGVzKHsgdGVzdGltb25pYWxUaXRsZTogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSlcblx0XHRcdFx0KVxuXHRcdFx0KV07XG5cdFx0fVxuXHR9XSk7XG5cblx0cmV0dXJuIEFCVGVzdGltb25pYWxCbG9jaztcbn0oQ29tcG9uZW50KTtcblxuLy8gUmVnaXN0ZXIgdGhlIGJsb2NrXG5cblxucmVnaXN0ZXJCbG9ja1R5cGUoJ2F0b21pYy1ibG9ja3MvYWItdGVzdGltb25pYWwnLCB7XG5cdHRpdGxlOiBfXygnQUIgVGVzdGltb25pYWwnKSxcblx0ZGVzY3JpcHRpb246IF9fKCdBZGQgYSB1c2VyIHRlc3RpbW9uaWFsIHdpdGggYSBuYW1lIGFuZCB0aXRsZS4nKSxcblx0aWNvbjogJ2Zvcm1hdC1xdW90ZScsXG5cdGNhdGVnb3J5OiAnYXRvbWljLWJsb2NrcycsXG5cdGtleXdvcmRzOiBbX18oJ3Rlc3RpbW9uaWFsJyksIF9fKCdxdW90ZScpLCBfXygnYXRvbWljJyldLFxuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0dGVzdGltb25pYWxOYW1lOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJyxcblx0XHRcdHNlbGVjdG9yOiAnLmFiLXRlc3RpbW9uaWFsLW5hbWUnXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbFRpdGxlOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJyxcblx0XHRcdHNlbGVjdG9yOiAnLmFiLXRlc3RpbW9uaWFsLXRpdGxlJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxDb250ZW50OiB7XG5cdFx0XHR0eXBlOiAnYXJyYXknLFxuXHRcdFx0c2VsZWN0b3I6ICcuYWItdGVzdGltb25pYWwtdGV4dCcsXG5cdFx0XHRzb3VyY2U6ICdjaGlsZHJlbidcblx0XHR9LFxuXHRcdHRlc3RpbW9uaWFsQWxpZ25tZW50OiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxJbWdVUkw6IHtcblx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3NyYycsXG5cdFx0XHRzZWxlY3RvcjogJ2ltZydcblx0XHR9LFxuXHRcdHRlc3RpbW9uaWFsSW1nSUQ6IHtcblx0XHRcdHR5cGU6ICdudW1iZXInXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbEJhY2tncm91bmRDb2xvcjoge1xuXHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRkZWZhdWx0OiAnI2YyZjJmMidcblx0XHR9LFxuXHRcdHRlc3RpbW9uaWFsVGV4dENvbG9yOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJyxcblx0XHRcdGRlZmF1bHQ6ICcjMzIzNzNjJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxGb250U2l6ZToge1xuXHRcdFx0dHlwZTogJ251bWJlcicsXG5cdFx0XHRkZWZhdWx0OiAxOFxuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxDaXRlQWxpZ246IHtcblx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0ZGVmYXVsdDogJ2xlZnQtYWxpZ25lZCdcblx0XHR9XG5cdH0sXG5cblx0Ly8gUmVuZGVyIHRoZSBibG9jayBjb21wb25lbnRzXG5cdGVkaXQ6IEFCVGVzdGltb25pYWxCbG9jayxcblxuXHQvLyBTYXZlIHRoZSBhdHRyaWJ1dGVzIGFuZCBtYXJrdXBcblx0c2F2ZTogZnVuY3Rpb24gc2F2ZShwcm9wcykge1xuXG5cdFx0Ly8gU2V0dXAgdGhlIGF0dHJpYnV0ZXNcblx0XHR2YXIgX3Byb3BzJGF0dHJpYnV0ZXMyID0gcHJvcHMuYXR0cmlidXRlcyxcblx0XHQgICAgdGVzdGltb25pYWxOYW1lID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsTmFtZSxcblx0XHQgICAgdGVzdGltb25pYWxUaXRsZSA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbFRpdGxlLFxuXHRcdCAgICB0ZXN0aW1vbmlhbENvbnRlbnQgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxDb250ZW50LFxuXHRcdCAgICB0ZXN0aW1vbmlhbEFsaWdubWVudCA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbEFsaWdubWVudCxcblx0XHQgICAgdGVzdGltb25pYWxJbWdVUkwgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxJbWdVUkwsXG5cdFx0ICAgIHRlc3RpbW9uaWFsSW1nSUQgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxJbWdJRCxcblx0XHQgICAgdGVzdGltb25pYWxCYWNrZ3JvdW5kQ29sb3IgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxCYWNrZ3JvdW5kQ29sb3IsXG5cdFx0ICAgIHRlc3RpbW9uaWFsVGV4dENvbG9yID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsVGV4dENvbG9yLFxuXHRcdCAgICB0ZXN0aW1vbmlhbEZvbnRTaXplID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsRm9udFNpemUsXG5cdFx0ICAgIHRlc3RpbW9uaWFsQ2l0ZUFsaWduID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsQ2l0ZUFsaWduO1xuXG5cdFx0Ly8gU2F2ZSB0aGUgYmxvY2sgbWFya3VwIGZvciB0aGUgZnJvbnQgZW5kXG5cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0VGVzdGltb25pYWwsXG5cdFx0XHRwcm9wcyxcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0J2RpdicsXG5cdFx0XHRcdHtcblx0XHRcdFx0XHRjbGFzc05hbWU6IGNsYXNzbmFtZXMoJ2FiLXRlc3RpbW9uaWFsLXRleHQnKSxcblx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0dGV4dEFsaWduOiB0ZXN0aW1vbmlhbEFsaWdubWVudFxuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSxcblx0XHRcdFx0dGVzdGltb25pYWxDb250ZW50XG5cdFx0XHQpLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW5mbycgfSxcblx0XHRcdFx0dGVzdGltb25pYWxJbWdVUkwgJiYgISF0ZXN0aW1vbmlhbEltZ1VSTC5sZW5ndGggJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdCdkaXYnLFxuXHRcdFx0XHRcdHsgJ2NsYXNzJzogJ2FiLXRlc3RpbW9uaWFsLWF2YXRhci13cmFwJyB9LFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRcdCdkaXYnLFxuXHRcdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW1hZ2Utd3JhcCcgfSxcblx0XHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudCgnaW1nJywge1xuXHRcdFx0XHRcdFx0XHQnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtYXZhdGFyJyxcblx0XHRcdFx0XHRcdFx0c3JjOiB0ZXN0aW1vbmlhbEltZ1VSTCxcblx0XHRcdFx0XHRcdFx0YWx0OiAnYXZhdGFyJ1xuXHRcdFx0XHRcdFx0fSlcblx0XHRcdFx0XHQpXG5cdFx0XHRcdCksXG5cdFx0XHRcdHRlc3RpbW9uaWFsTmFtZSAmJiAhIXRlc3RpbW9uaWFsTmFtZS5sZW5ndGggJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdCdoMicsXG5cdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtbmFtZScsXG5cdFx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0XHRjb2xvcjogdGVzdGltb25pYWxUZXh0Q29sb3Jcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHRlc3RpbW9uaWFsTmFtZVxuXHRcdFx0XHQpLFxuXHRcdFx0XHR0ZXN0aW1vbmlhbFRpdGxlICYmICEhdGVzdGltb25pYWxUaXRsZS5sZW5ndGggJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdCdzbWFsbCcsXG5cdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtdGl0bGUnLFxuXHRcdFx0XHRcdFx0c3R5bGU6IHtcblx0XHRcdFx0XHRcdFx0Y29sb3I6IHRlc3RpbW9uaWFsVGV4dENvbG9yXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHR0ZXN0aW1vbmlhbFRpdGxlXG5cdFx0XHRcdClcblx0XHRcdClcblx0XHQpO1xuXHR9XG59KTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9ibG9ja3MvYmxvY2stdGVzdGltb25pYWwvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDE4MFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///180\n");
2064
 
2065
  /***/ }),
2066
  /* 181 */
2060
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
2061
 
2062
  "use strict";
2063
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_classnames__ = __webpack_require__(/*! classnames */ 1);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_classnames___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_classnames__);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__components_inspector__ = __webpack_require__(/*! ./components/inspector */ 181);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__components_testimonial__ = __webpack_require__(/*! ./components/testimonial */ 182);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__components_icons__ = __webpack_require__(/*! ./components/icons */ 185);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_style_scss__ = __webpack_require__(/*! ./styles/style.scss */ 186);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_style_scss___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__styles_style_scss__);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_editor_scss__ = __webpack_require__(/*! ./styles/editor.scss */ 187);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_editor_scss___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5__styles_editor_scss__);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\n/**\n * BLOCK: Atomic Blocks Testimonial\n */\n\n// Import block dependencies and components\n\n\n\n\n\n// Import CSS\n\n\n\n// Internationalization\nvar __ = wp.i18n.__;\n\n// Extend component\n\nvar Component = wp.element.Component;\n\n// Register block\n\nvar registerBlockType = wp.blocks.registerBlockType;\n\n// Register editor components\n\nvar _wp$editor = wp.editor,\n RichText = _wp$editor.RichText,\n AlignmentToolbar = _wp$editor.AlignmentToolbar,\n BlockControls = _wp$editor.BlockControls,\n BlockAlignmentToolbar = _wp$editor.BlockAlignmentToolbar,\n MediaUpload = _wp$editor.MediaUpload;\n\n// Register components\n\nvar _wp$components = wp.components,\n Button = _wp$components.Button,\n SelectControl = _wp$components.SelectControl;\n\nvar ABTestimonialBlock = function (_Component) {\n\t_inherits(ABTestimonialBlock, _Component);\n\n\tfunction ABTestimonialBlock() {\n\t\t_classCallCheck(this, ABTestimonialBlock);\n\n\t\treturn _possibleConstructorReturn(this, (ABTestimonialBlock.__proto__ || Object.getPrototypeOf(ABTestimonialBlock)).apply(this, arguments));\n\t}\n\n\t_createClass(ABTestimonialBlock, [{\n\t\tkey: 'render',\n\t\tvalue: function render() {\n\t\t\tvar _this2 = this;\n\n\t\t\t// Setup the attributes\n\t\t\tvar _props = this.props,\n\t\t\t _props$attributes = _props.attributes,\n\t\t\t testimonialName = _props$attributes.testimonialName,\n\t\t\t testimonialTitle = _props$attributes.testimonialTitle,\n\t\t\t testimonialContent = _props$attributes.testimonialContent,\n\t\t\t testimonialAlignment = _props$attributes.testimonialAlignment,\n\t\t\t testimonialImgURL = _props$attributes.testimonialImgURL,\n\t\t\t testimonialImgID = _props$attributes.testimonialImgID,\n\t\t\t testimonialBackgroundColor = _props$attributes.testimonialBackgroundColor,\n\t\t\t testimonialTextColor = _props$attributes.testimonialTextColor,\n\t\t\t testimonialFontSize = _props$attributes.testimonialFontSize,\n\t\t\t testimonialCiteAlign = _props$attributes.testimonialCiteAlign,\n\t\t\t attributes = _props.attributes,\n\t\t\t isSelected = _props.isSelected,\n\t\t\t editable = _props.editable,\n\t\t\t className = _props.className,\n\t\t\t setAttributes = _props.setAttributes;\n\n\n\t\t\tvar onSelectImage = function onSelectImage(img) {\n\t\t\t\tsetAttributes({\n\t\t\t\t\ttestimonialImgID: img.id,\n\t\t\t\t\ttestimonialImgURL: img.url\n\t\t\t\t});\n\t\t\t};\n\n\t\t\treturn [\n\t\t\t// Show the alignment toolbar on focus\n\t\t\twp.element.createElement(\n\t\t\t\tBlockControls,\n\t\t\t\t{ key: 'controls' },\n\t\t\t\twp.element.createElement(AlignmentToolbar, {\n\t\t\t\t\tvalue: testimonialAlignment,\n\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\treturn setAttributes({ testimonialAlignment: value });\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t),\n\t\t\t// Show the block controls on focus\n\t\t\twp.element.createElement(__WEBPACK_IMPORTED_MODULE_1__components_inspector__[\"a\" /* default */], Object.assign({ setAttributes: setAttributes }, this.props)),\n\t\t\t// Show the block markup in the editor\n\t\t\twp.element.createElement(\n\t\t\t\t__WEBPACK_IMPORTED_MODULE_2__components_testimonial__[\"a\" /* default */],\n\t\t\t\tthis.props,\n\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\ttagName: 'div',\n\t\t\t\t\tmultiline: 'p',\n\t\t\t\t\tplaceholder: __('Add testimonial text...'),\n\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\tvalue: testimonialContent,\n\t\t\t\t\tformattingControls: ['bold', 'italic', 'strikethrough', 'link'],\n\t\t\t\t\tclassName: __WEBPACK_IMPORTED_MODULE_0_classnames___default()('ab-testimonial-text'),\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign: testimonialAlignment\n\t\t\t\t\t},\n\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\treturn setAttributes({ testimonialContent: value });\n\t\t\t\t\t},\n\t\t\t\t\tinlineToolbar: true\n\t\t\t\t}),\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'div',\n\t\t\t\t\t{ 'class': 'ab-testimonial-info' },\n\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{ 'class': 'ab-testimonial-avatar-wrap' },\n\t\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t{ 'class': 'ab-testimonial-image-wrap' },\n\t\t\t\t\t\t\twp.element.createElement(MediaUpload, {\n\t\t\t\t\t\t\t\tbuttonProps: {\n\t\t\t\t\t\t\t\t\tclassName: 'change-image'\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tonSelect: function onSelect(img) {\n\t\t\t\t\t\t\t\t\treturn setAttributes({\n\t\t\t\t\t\t\t\t\t\ttestimonialImgID: img.id,\n\t\t\t\t\t\t\t\t\t\ttestimonialImgURL: img.url\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttype: 'image',\n\t\t\t\t\t\t\t\tvalue: testimonialImgID,\n\t\t\t\t\t\t\t\trender: function render(_ref) {\n\t\t\t\t\t\t\t\t\tvar open = _ref.open;\n\t\t\t\t\t\t\t\t\treturn wp.element.createElement(\n\t\t\t\t\t\t\t\t\t\tButton,\n\t\t\t\t\t\t\t\t\t\t{ onClick: open },\n\t\t\t\t\t\t\t\t\t\t!testimonialImgID ? __WEBPACK_IMPORTED_MODULE_3__components_icons__[\"a\" /* default */].upload : wp.element.createElement('img', {\n\t\t\t\t\t\t\t\t\t\t\t'class': 'ab-testimonial-avatar',\n\t\t\t\t\t\t\t\t\t\t\tsrc: testimonialImgURL,\n\t\t\t\t\t\t\t\t\t\t\talt: 'avatar'\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\t\ttagName: 'h2',\n\t\t\t\t\t\tplaceholder: __('Add name'),\n\t\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\t\tvalue: testimonialName,\n\t\t\t\t\t\tclassName: 'ab-testimonial-name',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn _this2.props.setAttributes({ testimonialName: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(RichText, {\n\t\t\t\t\t\ttagName: 'small',\n\t\t\t\t\t\tplaceholder: __('Add title'),\n\t\t\t\t\t\tkeepPlaceholderOnFocus: true,\n\t\t\t\t\t\tvalue: testimonialTitle,\n\t\t\t\t\t\tclassName: 'ab-testimonial-title',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn _this2.props.setAttributes({ testimonialTitle: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t)];\n\t\t}\n\t}]);\n\n\treturn ABTestimonialBlock;\n}(Component);\n\n// Register the block\n\n\nregisterBlockType('atomic-blocks/ab-testimonial', {\n\ttitle: __('AB Testimonial'),\n\tdescription: __('Add a user testimonial with a name and title.'),\n\ticon: 'format-quote',\n\tcategory: 'atomic-blocks',\n\tkeywords: [__('testimonial'), __('quote'), __('atomic')],\n\tattributes: {\n\t\ttestimonialName: {\n\t\t\ttype: 'string',\n\t\t\tselector: '.ab-testimonial-name'\n\t\t},\n\t\ttestimonialTitle: {\n\t\t\ttype: 'array',\n\t\t\tselector: '.ab-testimonial-title',\n\t\t\tsource: 'children'\n\t\t},\n\t\ttestimonialContent: {\n\t\t\ttype: 'array',\n\t\t\tselector: '.ab-testimonial-text',\n\t\t\tsource: 'children'\n\t\t},\n\t\ttestimonialAlignment: {\n\t\t\ttype: 'string'\n\t\t},\n\t\ttestimonialImgURL: {\n\t\t\ttype: 'string',\n\t\t\tsource: 'attribute',\n\t\t\tattribute: 'src',\n\t\t\tselector: 'img'\n\t\t},\n\t\ttestimonialImgID: {\n\t\t\ttype: 'number'\n\t\t},\n\t\ttestimonialBackgroundColor: {\n\t\t\ttype: 'string',\n\t\t\tdefault: '#f2f2f2'\n\t\t},\n\t\ttestimonialTextColor: {\n\t\t\ttype: 'string',\n\t\t\tdefault: '#32373c'\n\t\t},\n\t\ttestimonialFontSize: {\n\t\t\ttype: 'number',\n\t\t\tdefault: 18\n\t\t},\n\t\ttestimonialCiteAlign: {\n\t\t\ttype: 'string',\n\t\t\tdefault: 'left-aligned'\n\t\t}\n\t},\n\n\t// Render the block components\n\tedit: ABTestimonialBlock,\n\n\t// Save the attributes and markup\n\tsave: function save(props) {\n\n\t\t// Setup the attributes\n\t\tvar _props$attributes2 = props.attributes,\n\t\t testimonialName = _props$attributes2.testimonialName,\n\t\t testimonialTitle = _props$attributes2.testimonialTitle,\n\t\t testimonialContent = _props$attributes2.testimonialContent,\n\t\t testimonialAlignment = _props$attributes2.testimonialAlignment,\n\t\t testimonialImgURL = _props$attributes2.testimonialImgURL,\n\t\t testimonialImgID = _props$attributes2.testimonialImgID,\n\t\t testimonialBackgroundColor = _props$attributes2.testimonialBackgroundColor,\n\t\t testimonialTextColor = _props$attributes2.testimonialTextColor,\n\t\t testimonialFontSize = _props$attributes2.testimonialFontSize,\n\t\t testimonialCiteAlign = _props$attributes2.testimonialCiteAlign;\n\n\t\t// Save the block markup for the front end\n\n\t\treturn wp.element.createElement(\n\t\t\t__WEBPACK_IMPORTED_MODULE_2__components_testimonial__[\"a\" /* default */],\n\t\t\tprops,\n\t\t\twp.element.createElement(\n\t\t\t\t'div',\n\t\t\t\t{\n\t\t\t\t\tclassName: __WEBPACK_IMPORTED_MODULE_0_classnames___default()('ab-testimonial-text'),\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign: testimonialAlignment\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttestimonialContent\n\t\t\t),\n\t\t\twp.element.createElement(\n\t\t\t\t'div',\n\t\t\t\t{ 'class': 'ab-testimonial-info' },\n\t\t\t\ttestimonialImgURL && !!testimonialImgURL.length && wp.element.createElement(\n\t\t\t\t\t'div',\n\t\t\t\t\t{ 'class': 'ab-testimonial-avatar-wrap' },\n\t\t\t\t\twp.element.createElement(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{ 'class': 'ab-testimonial-image-wrap' },\n\t\t\t\t\t\twp.element.createElement('img', {\n\t\t\t\t\t\t\t'class': 'ab-testimonial-avatar',\n\t\t\t\t\t\t\tsrc: testimonialImgURL,\n\t\t\t\t\t\t\talt: 'avatar'\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t),\n\t\t\t\ttestimonialName && !!testimonialName.length && wp.element.createElement(\n\t\t\t\t\t'h2',\n\t\t\t\t\t{ 'class': 'ab-testimonial-name',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\ttestimonialName\n\t\t\t\t),\n\t\t\t\ttestimonialTitle && !!testimonialTitle.length && wp.element.createElement(\n\t\t\t\t\t'small',\n\t\t\t\t\t{ 'class': 'ab-testimonial-title',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tcolor: testimonialTextColor\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\ttestimonialTitle\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t}\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTgwLmpzIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vLy4vc3JjL2Jsb2Nrcy9ibG9jay10ZXN0aW1vbmlhbC9pbmRleC5qcz9jMzQyIl0sInNvdXJjZXNDb250ZW50IjpbInZhciBfY3JlYXRlQ2xhc3MgPSBmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoXCJ2YWx1ZVwiIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlOyBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBkZXNjcmlwdG9yLmtleSwgZGVzY3JpcHRvcik7IH0gfSByZXR1cm4gZnVuY3Rpb24gKENvbnN0cnVjdG9yLCBwcm90b1Byb3BzLCBzdGF0aWNQcm9wcykgeyBpZiAocHJvdG9Qcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpOyBpZiAoc3RhdGljUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTsgcmV0dXJuIENvbnN0cnVjdG9yOyB9OyB9KCk7XG5cbmZ1bmN0aW9uIF9jbGFzc0NhbGxDaGVjayhpbnN0YW5jZSwgQ29uc3RydWN0b3IpIHsgaWYgKCEoaW5zdGFuY2UgaW5zdGFuY2VvZiBDb25zdHJ1Y3RvcikpIHsgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNhbm5vdCBjYWxsIGEgY2xhc3MgYXMgYSBmdW5jdGlvblwiKTsgfSB9XG5cbmZ1bmN0aW9uIF9wb3NzaWJsZUNvbnN0cnVjdG9yUmV0dXJuKHNlbGYsIGNhbGwpIHsgaWYgKCFzZWxmKSB7IHRocm93IG5ldyBSZWZlcmVuY2VFcnJvcihcInRoaXMgaGFzbid0IGJlZW4gaW5pdGlhbGlzZWQgLSBzdXBlcigpIGhhc24ndCBiZWVuIGNhbGxlZFwiKTsgfSByZXR1cm4gY2FsbCAmJiAodHlwZW9mIGNhbGwgPT09IFwib2JqZWN0XCIgfHwgdHlwZW9mIGNhbGwgPT09IFwiZnVuY3Rpb25cIikgPyBjYWxsIDogc2VsZjsgfVxuXG5mdW5jdGlvbiBfaW5oZXJpdHMoc3ViQ2xhc3MsIHN1cGVyQ2xhc3MpIHsgaWYgKHR5cGVvZiBzdXBlckNsYXNzICE9PSBcImZ1bmN0aW9uXCIgJiYgc3VwZXJDbGFzcyAhPT0gbnVsbCkgeyB0aHJvdyBuZXcgVHlwZUVycm9yKFwiU3VwZXIgZXhwcmVzc2lvbiBtdXN0IGVpdGhlciBiZSBudWxsIG9yIGEgZnVuY3Rpb24sIG5vdCBcIiArIHR5cGVvZiBzdXBlckNsYXNzKTsgfSBzdWJDbGFzcy5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKHN1cGVyQ2xhc3MgJiYgc3VwZXJDbGFzcy5wcm90b3R5cGUsIHsgY29uc3RydWN0b3I6IHsgdmFsdWU6IHN1YkNsYXNzLCBlbnVtZXJhYmxlOiBmYWxzZSwgd3JpdGFibGU6IHRydWUsIGNvbmZpZ3VyYWJsZTogdHJ1ZSB9IH0pOyBpZiAoc3VwZXJDbGFzcykgT2JqZWN0LnNldFByb3RvdHlwZU9mID8gT2JqZWN0LnNldFByb3RvdHlwZU9mKHN1YkNsYXNzLCBzdXBlckNsYXNzKSA6IHN1YkNsYXNzLl9fcHJvdG9fXyA9IHN1cGVyQ2xhc3M7IH1cblxuLyoqXG4gKiBCTE9DSzogQXRvbWljIEJsb2NrcyBUZXN0aW1vbmlhbFxuICovXG5cbi8vIEltcG9ydCBibG9jayBkZXBlbmRlbmNpZXMgYW5kIGNvbXBvbmVudHNcbmltcG9ydCBjbGFzc25hbWVzIGZyb20gJ2NsYXNzbmFtZXMnO1xuaW1wb3J0IEluc3BlY3RvciBmcm9tICcuL2NvbXBvbmVudHMvaW5zcGVjdG9yJztcbmltcG9ydCBUZXN0aW1vbmlhbCBmcm9tICcuL2NvbXBvbmVudHMvdGVzdGltb25pYWwnO1xuaW1wb3J0IGljb25zIGZyb20gJy4vY29tcG9uZW50cy9pY29ucyc7XG5cbi8vIEltcG9ydCBDU1NcbmltcG9ydCAnLi9zdHlsZXMvc3R5bGUuc2Nzcyc7XG5pbXBvcnQgJy4vc3R5bGVzL2VkaXRvci5zY3NzJztcblxuLy8gSW50ZXJuYXRpb25hbGl6YXRpb25cbnZhciBfXyA9IHdwLmkxOG4uX187XG5cbi8vIEV4dGVuZCBjb21wb25lbnRcblxudmFyIENvbXBvbmVudCA9IHdwLmVsZW1lbnQuQ29tcG9uZW50O1xuXG4vLyBSZWdpc3RlciBibG9ja1xuXG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG5cbi8vIFJlZ2lzdGVyIGVkaXRvciBjb21wb25lbnRzXG5cbnZhciBfd3AkZWRpdG9yID0gd3AuZWRpdG9yLFxuICAgIFJpY2hUZXh0ID0gX3dwJGVkaXRvci5SaWNoVGV4dCxcbiAgICBBbGlnbm1lbnRUb29sYmFyID0gX3dwJGVkaXRvci5BbGlnbm1lbnRUb29sYmFyLFxuICAgIEJsb2NrQ29udHJvbHMgPSBfd3AkZWRpdG9yLkJsb2NrQ29udHJvbHMsXG4gICAgQmxvY2tBbGlnbm1lbnRUb29sYmFyID0gX3dwJGVkaXRvci5CbG9ja0FsaWdubWVudFRvb2xiYXIsXG4gICAgTWVkaWFVcGxvYWQgPSBfd3AkZWRpdG9yLk1lZGlhVXBsb2FkO1xuXG4vLyBSZWdpc3RlciBjb21wb25lbnRzXG5cbnZhciBfd3AkY29tcG9uZW50cyA9IHdwLmNvbXBvbmVudHMsXG4gICAgQnV0dG9uID0gX3dwJGNvbXBvbmVudHMuQnV0dG9uLFxuICAgIFNlbGVjdENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5TZWxlY3RDb250cm9sO1xuXG52YXIgQUJUZXN0aW1vbmlhbEJsb2NrID0gZnVuY3Rpb24gKF9Db21wb25lbnQpIHtcblx0X2luaGVyaXRzKEFCVGVzdGltb25pYWxCbG9jaywgX0NvbXBvbmVudCk7XG5cblx0ZnVuY3Rpb24gQUJUZXN0aW1vbmlhbEJsb2NrKCkge1xuXHRcdF9jbGFzc0NhbGxDaGVjayh0aGlzLCBBQlRlc3RpbW9uaWFsQmxvY2spO1xuXG5cdFx0cmV0dXJuIF9wb3NzaWJsZUNvbnN0cnVjdG9yUmV0dXJuKHRoaXMsIChBQlRlc3RpbW9uaWFsQmxvY2suX19wcm90b19fIHx8IE9iamVjdC5nZXRQcm90b3R5cGVPZihBQlRlc3RpbW9uaWFsQmxvY2spKS5hcHBseSh0aGlzLCBhcmd1bWVudHMpKTtcblx0fVxuXG5cdF9jcmVhdGVDbGFzcyhBQlRlc3RpbW9uaWFsQmxvY2ssIFt7XG5cdFx0a2V5OiAncmVuZGVyJyxcblx0XHR2YWx1ZTogZnVuY3Rpb24gcmVuZGVyKCkge1xuXHRcdFx0dmFyIF90aGlzMiA9IHRoaXM7XG5cblx0XHRcdC8vIFNldHVwIHRoZSBhdHRyaWJ1dGVzXG5cdFx0XHR2YXIgX3Byb3BzID0gdGhpcy5wcm9wcyxcblx0XHRcdCAgICBfcHJvcHMkYXR0cmlidXRlcyA9IF9wcm9wcy5hdHRyaWJ1dGVzLFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsTmFtZSA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsTmFtZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbFRpdGxlID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxUaXRsZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbENvbnRlbnQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbENvbnRlbnQsXG5cdFx0XHQgICAgdGVzdGltb25pYWxBbGlnbm1lbnQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbEFsaWdubWVudCxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbEltZ1VSTCA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsSW1nVVJMLFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsSW1nSUQgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbEltZ0lELFxuXHRcdFx0ICAgIHRlc3RpbW9uaWFsQmFja2dyb3VuZENvbG9yID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxCYWNrZ3JvdW5kQ29sb3IsXG5cdFx0XHQgICAgdGVzdGltb25pYWxUZXh0Q29sb3IgPSBfcHJvcHMkYXR0cmlidXRlcy50ZXN0aW1vbmlhbFRleHRDb2xvcixcblx0XHRcdCAgICB0ZXN0aW1vbmlhbEZvbnRTaXplID0gX3Byb3BzJGF0dHJpYnV0ZXMudGVzdGltb25pYWxGb250U2l6ZSxcblx0XHRcdCAgICB0ZXN0aW1vbmlhbENpdGVBbGlnbiA9IF9wcm9wcyRhdHRyaWJ1dGVzLnRlc3RpbW9uaWFsQ2l0ZUFsaWduLFxuXHRcdFx0ICAgIGF0dHJpYnV0ZXMgPSBfcHJvcHMuYXR0cmlidXRlcyxcblx0XHRcdCAgICBpc1NlbGVjdGVkID0gX3Byb3BzLmlzU2VsZWN0ZWQsXG5cdFx0XHQgICAgZWRpdGFibGUgPSBfcHJvcHMuZWRpdGFibGUsXG5cdFx0XHQgICAgY2xhc3NOYW1lID0gX3Byb3BzLmNsYXNzTmFtZSxcblx0XHRcdCAgICBzZXRBdHRyaWJ1dGVzID0gX3Byb3BzLnNldEF0dHJpYnV0ZXM7XG5cblxuXHRcdFx0dmFyIG9uU2VsZWN0SW1hZ2UgPSBmdW5jdGlvbiBvblNlbGVjdEltYWdlKGltZykge1xuXHRcdFx0XHRzZXRBdHRyaWJ1dGVzKHtcblx0XHRcdFx0XHR0ZXN0aW1vbmlhbEltZ0lEOiBpbWcuaWQsXG5cdFx0XHRcdFx0dGVzdGltb25pYWxJbWdVUkw6IGltZy51cmxcblx0XHRcdFx0fSk7XG5cdFx0XHR9O1xuXG5cdFx0XHRyZXR1cm4gW1xuXHRcdFx0Ly8gU2hvdyB0aGUgYWxpZ25tZW50IHRvb2xiYXIgb24gZm9jdXNcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0QmxvY2tDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdjb250cm9scycgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KEFsaWdubWVudFRvb2xiYXIsIHtcblx0XHRcdFx0XHR2YWx1ZTogdGVzdGltb25pYWxBbGlnbm1lbnQsXG5cdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IHRlc3RpbW9uaWFsQWxpZ25tZW50OiB2YWx1ZSB9KTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0pXG5cdFx0XHQpLFxuXHRcdFx0Ly8gU2hvdyB0aGUgYmxvY2sgY29udHJvbHMgb24gZm9jdXNcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChJbnNwZWN0b3IsIE9iamVjdC5hc3NpZ24oeyBzZXRBdHRyaWJ1dGVzOiBzZXRBdHRyaWJ1dGVzIH0sIHRoaXMucHJvcHMpKSxcblx0XHRcdC8vIFNob3cgdGhlIGJsb2NrIG1hcmt1cCBpbiB0aGUgZWRpdG9yXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFRlc3RpbW9uaWFsLFxuXHRcdFx0XHR0aGlzLnByb3BzLFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoUmljaFRleHQsIHtcblx0XHRcdFx0XHR0YWdOYW1lOiAnZGl2Jyxcblx0XHRcdFx0XHRtdWx0aWxpbmU6ICdwJyxcblx0XHRcdFx0XHRwbGFjZWhvbGRlcjogX18oJ0FkZCB0ZXN0aW1vbmlhbCB0ZXh0Li4uJyksXG5cdFx0XHRcdFx0a2VlcFBsYWNlaG9sZGVyT25Gb2N1czogdHJ1ZSxcblx0XHRcdFx0XHR2YWx1ZTogdGVzdGltb25pYWxDb250ZW50LFxuXHRcdFx0XHRcdGZvcm1hdHRpbmdDb250cm9sczogWydib2xkJywgJ2l0YWxpYycsICdzdHJpa2V0aHJvdWdoJywgJ2xpbmsnXSxcblx0XHRcdFx0XHRjbGFzc05hbWU6IGNsYXNzbmFtZXMoJ2FiLXRlc3RpbW9uaWFsLXRleHQnKSxcblx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0dGV4dEFsaWduOiB0ZXN0aW1vbmlhbEFsaWdubWVudFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IHRlc3RpbW9uaWFsQ29udGVudDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRpbmxpbmVUb29sYmFyOiB0cnVlXG5cdFx0XHRcdH0pLFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J2RpdicsXG5cdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW5mbycgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0XHRcdHsgJ2NsYXNzJzogJ2FiLXRlc3RpbW9uaWFsLWF2YXRhci13cmFwJyB9LFxuXHRcdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtaW1hZ2Utd3JhcCcgfSxcblx0XHRcdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KE1lZGlhVXBsb2FkLCB7XG5cdFx0XHRcdFx0XHRcdFx0YnV0dG9uUHJvcHM6IHtcblx0XHRcdFx0XHRcdFx0XHRcdGNsYXNzTmFtZTogJ2NoYW5nZS1pbWFnZSdcblx0XHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRcdG9uU2VsZWN0OiBmdW5jdGlvbiBvblNlbGVjdChpbWcpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0dGVzdGltb25pYWxJbWdJRDogaW1nLmlkLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHR0ZXN0aW1vbmlhbEltZ1VSTDogaW1nLnVybFxuXHRcdFx0XHRcdFx0XHRcdFx0fSk7XG5cdFx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0XHR0eXBlOiAnaW1hZ2UnLFxuXHRcdFx0XHRcdFx0XHRcdHZhbHVlOiB0ZXN0aW1vbmlhbEltZ0lELFxuXHRcdFx0XHRcdFx0XHRcdHJlbmRlcjogZnVuY3Rpb24gcmVuZGVyKF9yZWYpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHZhciBvcGVuID0gX3JlZi5vcGVuO1xuXHRcdFx0XHRcdFx0XHRcdFx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRcdFx0XHRcdFx0QnV0dG9uLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHR7IG9uQ2xpY2s6IG9wZW4gfSxcblx0XHRcdFx0XHRcdFx0XHRcdFx0IXRlc3RpbW9uaWFsSW1nSUQgPyBpY29ucy51cGxvYWQgOiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoJ2ltZycsIHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHQnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtYXZhdGFyJyxcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRzcmM6IHRlc3RpbW9uaWFsSW1nVVJMLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHRcdGFsdDogJ2F2YXRhcidcblx0XHRcdFx0XHRcdFx0XHRcdFx0fSlcblx0XHRcdFx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHR9KVxuXHRcdFx0XHRcdFx0KVxuXHRcdFx0XHRcdCksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFJpY2hUZXh0LCB7XG5cdFx0XHRcdFx0XHR0YWdOYW1lOiAnaDInLFxuXHRcdFx0XHRcdFx0cGxhY2Vob2xkZXI6IF9fKCdBZGQgbmFtZScpLFxuXHRcdFx0XHRcdFx0a2VlcFBsYWNlaG9sZGVyT25Gb2N1czogdHJ1ZSxcblx0XHRcdFx0XHRcdHZhbHVlOiB0ZXN0aW1vbmlhbE5hbWUsXG5cdFx0XHRcdFx0XHRjbGFzc05hbWU6ICdhYi10ZXN0aW1vbmlhbC1uYW1lJyxcblx0XHRcdFx0XHRcdHN0eWxlOiB7XG5cdFx0XHRcdFx0XHRcdGNvbG9yOiB0ZXN0aW1vbmlhbFRleHRDb2xvclxuXHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gX3RoaXMyLnByb3BzLnNldEF0dHJpYnV0ZXMoeyB0ZXN0aW1vbmlhbE5hbWU6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChSaWNoVGV4dCwge1xuXHRcdFx0XHRcdFx0dGFnTmFtZTogJ3NtYWxsJyxcblx0XHRcdFx0XHRcdHBsYWNlaG9sZGVyOiBfXygnQWRkIHRpdGxlJyksXG5cdFx0XHRcdFx0XHRrZWVwUGxhY2Vob2xkZXJPbkZvY3VzOiB0cnVlLFxuXHRcdFx0XHRcdFx0dmFsdWU6IHRlc3RpbW9uaWFsVGl0bGUsXG5cdFx0XHRcdFx0XHRjbGFzc05hbWU6ICdhYi10ZXN0aW1vbmlhbC10aXRsZScsXG5cdFx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0XHRjb2xvcjogdGVzdGltb25pYWxUZXh0Q29sb3Jcblx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIF90aGlzMi5wcm9wcy5zZXRBdHRyaWJ1dGVzKHsgdGVzdGltb25pYWxUaXRsZTogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSlcblx0XHRcdFx0KVxuXHRcdFx0KV07XG5cdFx0fVxuXHR9XSk7XG5cblx0cmV0dXJuIEFCVGVzdGltb25pYWxCbG9jaztcbn0oQ29tcG9uZW50KTtcblxuLy8gUmVnaXN0ZXIgdGhlIGJsb2NrXG5cblxucmVnaXN0ZXJCbG9ja1R5cGUoJ2F0b21pYy1ibG9ja3MvYWItdGVzdGltb25pYWwnLCB7XG5cdHRpdGxlOiBfXygnQUIgVGVzdGltb25pYWwnKSxcblx0ZGVzY3JpcHRpb246IF9fKCdBZGQgYSB1c2VyIHRlc3RpbW9uaWFsIHdpdGggYSBuYW1lIGFuZCB0aXRsZS4nKSxcblx0aWNvbjogJ2Zvcm1hdC1xdW90ZScsXG5cdGNhdGVnb3J5OiAnYXRvbWljLWJsb2NrcycsXG5cdGtleXdvcmRzOiBbX18oJ3Rlc3RpbW9uaWFsJyksIF9fKCdxdW90ZScpLCBfXygnYXRvbWljJyldLFxuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0dGVzdGltb25pYWxOYW1lOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJyxcblx0XHRcdHNlbGVjdG9yOiAnLmFiLXRlc3RpbW9uaWFsLW5hbWUnXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbFRpdGxlOiB7XG5cdFx0XHR0eXBlOiAnYXJyYXknLFxuXHRcdFx0c2VsZWN0b3I6ICcuYWItdGVzdGltb25pYWwtdGl0bGUnLFxuXHRcdFx0c291cmNlOiAnY2hpbGRyZW4nXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbENvbnRlbnQ6IHtcblx0XHRcdHR5cGU6ICdhcnJheScsXG5cdFx0XHRzZWxlY3RvcjogJy5hYi10ZXN0aW1vbmlhbC10ZXh0Jyxcblx0XHRcdHNvdXJjZTogJ2NoaWxkcmVuJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxBbGlnbm1lbnQ6IHtcblx0XHRcdHR5cGU6ICdzdHJpbmcnXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbEltZ1VSTDoge1xuXHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0YXR0cmlidXRlOiAnc3JjJyxcblx0XHRcdHNlbGVjdG9yOiAnaW1nJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxJbWdJRDoge1xuXHRcdFx0dHlwZTogJ251bWJlcidcblx0XHR9LFxuXHRcdHRlc3RpbW9uaWFsQmFja2dyb3VuZENvbG9yOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJyxcblx0XHRcdGRlZmF1bHQ6ICcjZjJmMmYyJ1xuXHRcdH0sXG5cdFx0dGVzdGltb25pYWxUZXh0Q29sb3I6IHtcblx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0ZGVmYXVsdDogJyMzMjM3M2MnXG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbEZvbnRTaXplOiB7XG5cdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdGRlZmF1bHQ6IDE4XG5cdFx0fSxcblx0XHR0ZXN0aW1vbmlhbENpdGVBbGlnbjoge1xuXHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRkZWZhdWx0OiAnbGVmdC1hbGlnbmVkJ1xuXHRcdH1cblx0fSxcblxuXHQvLyBSZW5kZXIgdGhlIGJsb2NrIGNvbXBvbmVudHNcblx0ZWRpdDogQUJUZXN0aW1vbmlhbEJsb2NrLFxuXG5cdC8vIFNhdmUgdGhlIGF0dHJpYnV0ZXMgYW5kIG1hcmt1cFxuXHRzYXZlOiBmdW5jdGlvbiBzYXZlKHByb3BzKSB7XG5cblx0XHQvLyBTZXR1cCB0aGUgYXR0cmlidXRlc1xuXHRcdHZhciBfcHJvcHMkYXR0cmlidXRlczIgPSBwcm9wcy5hdHRyaWJ1dGVzLFxuXHRcdCAgICB0ZXN0aW1vbmlhbE5hbWUgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxOYW1lLFxuXHRcdCAgICB0ZXN0aW1vbmlhbFRpdGxlID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsVGl0bGUsXG5cdFx0ICAgIHRlc3RpbW9uaWFsQ29udGVudCA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbENvbnRlbnQsXG5cdFx0ICAgIHRlc3RpbW9uaWFsQWxpZ25tZW50ID0gX3Byb3BzJGF0dHJpYnV0ZXMyLnRlc3RpbW9uaWFsQWxpZ25tZW50LFxuXHRcdCAgICB0ZXN0aW1vbmlhbEltZ1VSTCA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbEltZ1VSTCxcblx0XHQgICAgdGVzdGltb25pYWxJbWdJRCA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbEltZ0lELFxuXHRcdCAgICB0ZXN0aW1vbmlhbEJhY2tncm91bmRDb2xvciA9IF9wcm9wcyRhdHRyaWJ1dGVzMi50ZXN0aW1vbmlhbEJhY2tncm91bmRDb2xvcixcblx0XHQgICAgdGVzdGltb25pYWxUZXh0Q29sb3IgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxUZXh0Q29sb3IsXG5cdFx0ICAgIHRlc3RpbW9uaWFsRm9udFNpemUgPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxGb250U2l6ZSxcblx0XHQgICAgdGVzdGltb25pYWxDaXRlQWxpZ24gPSBfcHJvcHMkYXR0cmlidXRlczIudGVzdGltb25pYWxDaXRlQWxpZ247XG5cblx0XHQvLyBTYXZlIHRoZSBibG9jayBtYXJrdXAgZm9yIHRoZSBmcm9udCBlbmRcblxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRUZXN0aW1vbmlhbCxcblx0XHRcdHByb3BzLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHQnZGl2Jyxcblx0XHRcdFx0e1xuXHRcdFx0XHRcdGNsYXNzTmFtZTogY2xhc3NuYW1lcygnYWItdGVzdGltb25pYWwtdGV4dCcpLFxuXHRcdFx0XHRcdHN0eWxlOiB7XG5cdFx0XHRcdFx0XHR0ZXh0QWxpZ246IHRlc3RpbW9uaWFsQWxpZ25tZW50XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9LFxuXHRcdFx0XHR0ZXN0aW1vbmlhbENvbnRlbnRcblx0XHRcdCksXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdCdkaXYnLFxuXHRcdFx0XHR7ICdjbGFzcyc6ICdhYi10ZXN0aW1vbmlhbC1pbmZvJyB9LFxuXHRcdFx0XHR0ZXN0aW1vbmlhbEltZ1VSTCAmJiAhIXRlc3RpbW9uaWFsSW1nVVJMLmxlbmd0aCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J2RpdicsXG5cdFx0XHRcdFx0eyAnY2xhc3MnOiAnYWItdGVzdGltb25pYWwtYXZhdGFyLXdyYXAnIH0sXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFx0J2RpdicsXG5cdFx0XHRcdFx0XHR7ICdjbGFzcyc6ICdhYi10ZXN0aW1vbmlhbC1pbWFnZS13cmFwJyB9LFxuXHRcdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdpbWcnLCB7XG5cdFx0XHRcdFx0XHRcdCdjbGFzcyc6ICdhYi10ZXN0aW1vbmlhbC1hdmF0YXInLFxuXHRcdFx0XHRcdFx0XHRzcmM6IHRlc3RpbW9uaWFsSW1nVVJMLFxuXHRcdFx0XHRcdFx0XHRhbHQ6ICdhdmF0YXInXG5cdFx0XHRcdFx0XHR9KVxuXHRcdFx0XHRcdClcblx0XHRcdFx0KSxcblx0XHRcdFx0dGVzdGltb25pYWxOYW1lICYmICEhdGVzdGltb25pYWxOYW1lLmxlbmd0aCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J2gyJyxcblx0XHRcdFx0XHR7ICdjbGFzcyc6ICdhYi10ZXN0aW1vbmlhbC1uYW1lJyxcblx0XHRcdFx0XHRcdHN0eWxlOiB7XG5cdFx0XHRcdFx0XHRcdGNvbG9yOiB0ZXN0aW1vbmlhbFRleHRDb2xvclxuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0dGVzdGltb25pYWxOYW1lXG5cdFx0XHRcdCksXG5cdFx0XHRcdHRlc3RpbW9uaWFsVGl0bGUgJiYgISF0ZXN0aW1vbmlhbFRpdGxlLmxlbmd0aCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3NtYWxsJyxcblx0XHRcdFx0XHR7ICdjbGFzcyc6ICdhYi10ZXN0aW1vbmlhbC10aXRsZScsXG5cdFx0XHRcdFx0XHRzdHlsZToge1xuXHRcdFx0XHRcdFx0XHRjb2xvcjogdGVzdGltb25pYWxUZXh0Q29sb3Jcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHRlc3RpbW9uaWFsVGl0bGVcblx0XHRcdFx0KVxuXHRcdFx0KVxuXHRcdCk7XG5cdH1cbn0pO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL2Jsb2Nrcy9ibG9jay10ZXN0aW1vbmlhbC9pbmRleC5qc1xuLy8gbW9kdWxlIGlkID0gMTgwXG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///180\n");
2064
 
2065
  /***/ }),
2066
  /* 181 */
src/blocks.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Import the blocks
3
+ */
4
+
5
+ import './blocks/block-testimonial/index.js';
6
+ import './blocks/block-author-profile/index.js';
7
+ import './blocks/block-notice/index.js';
8
+ import './blocks/block-drop-cap/index.js';
9
+ import './blocks/block-button/index.js';
10
+ import './blocks/block-spacer/index.js';
11
+ import './blocks/block-accordion/index.js';
12
+ import './blocks/block-cta/index.js';
13
+ import './blocks/block-sharing/index.js';
14
+ import './blocks/block-post-grid/index.js';
15
+ import './blocks/block-container/index.js';
16
+ import './blocks/block-layout-split/index.js';
src/blocks/block-accordion/components/accordion.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Accordion Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a Accordion wrapper Component
13
+ */
14
+ export default class Accordion extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+
22
+ // Setup the attributes
23
+ const { accordionTitle, accordionText, accordionAlignment, accordionFontSize } = this.props.attributes;
24
+
25
+ return (
26
+ <div
27
+ style={ {
28
+
29
+ } }
30
+ className={ classnames(
31
+ this.props.className,
32
+ accordionAlignment,
33
+ 'ab-block-accordion',
34
+ 'ab-font-size-' + accordionFontSize,
35
+ ) }
36
+ >
37
+ { this.props.children }
38
+ </div>
39
+ );
40
+ }
41
+ }
src/blocks/block-accordion/components/icons.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ icons.dismiss = <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns='http://www.w3.org/2000/svg' width="20" height="20" viewBox="0 0 20 20">
15
+ <path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zM15 13l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z"></path>
16
+ </svg>;
17
+
18
+ export default icons;
src/blocks/block-accordion/components/inspector.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ RangeControl,
24
+ ToggleControl,
25
+ } = wp.components;
26
+
27
+ /**
28
+ * Create an Inspector Controls wrapper Component
29
+ */
30
+ export default class Inspector extends Component {
31
+
32
+ constructor( props ) {
33
+ super( ...arguments );
34
+ }
35
+
36
+ render() {
37
+
38
+ // Setup the attributes
39
+ const { accordionTitle, accordionText, accordionFontSize, accordionOpen } = this.props.attributes;
40
+
41
+ return (
42
+ <InspectorControls key="inspector">
43
+ <PanelBody>
44
+ <RangeControl
45
+ label={ __( 'Font Size' ) }
46
+ value={ accordionFontSize }
47
+ onChange={ ( value ) => this.props.setAttributes( { accordionFontSize: value } ) }
48
+ min={ 14 }
49
+ max={ 24 }
50
+ step={ 1 }
51
+ />
52
+
53
+ <ToggleControl
54
+ label={ __( 'Open by default' ) }
55
+ checked={ accordionOpen }
56
+ onChange={ () => this.props.setAttributes( { accordionOpen: ! accordionOpen } ) }
57
+ />
58
+ </PanelBody>
59
+ </InspectorControls>
60
+ );
61
+ }
62
+ }
src/blocks/block-accordion/index.js ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Accordion Block
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import Accordion from './components/accordion';
9
+ import icons from './components/icons';
10
+
11
+ // Import CSS
12
+ import './styles/style.scss';
13
+ import './styles/editor.scss';
14
+
15
+ // Components
16
+ const { __ } = wp.i18n;
17
+
18
+ // Extend component
19
+ const { Component } = wp.element;
20
+
21
+ // Register block
22
+ const { registerBlockType } = wp.blocks;
23
+
24
+ // Register editor components
25
+ const {
26
+ RichText,
27
+ AlignmentToolbar,
28
+ BlockControls,
29
+ BlockAlignmentToolbar,
30
+ } = wp.editor;
31
+
32
+ // Register components
33
+ const {
34
+ Button,
35
+ withFallbackStyles,
36
+ IconButton,
37
+ Dashicon,
38
+ } = wp.components;
39
+
40
+ class ABAccordionBlock extends Component {
41
+
42
+ render() {
43
+
44
+ // Setup the attributes
45
+ const { attributes: { accordionTitle, accordionText, accordionAlignment, accordionFontSize, accordionOpen }, isSelected, className, setAttributes } = this.props;
46
+
47
+ return [
48
+ // Show the block alignment controls on focus
49
+ <BlockControls key="controls">
50
+ <AlignmentToolbar
51
+ value={ accordionAlignment }
52
+ onChange={ ( value ) => this.props.setAttributes( { accordionAlignment: value } ) }
53
+ />
54
+ </BlockControls>,
55
+ // Show the block controls on focus
56
+ <Inspector
57
+ { ...this.props }
58
+ />,
59
+ // Show the button markup in the editor
60
+ <Accordion { ...this.props }>
61
+ <RichText
62
+ tagName="p"
63
+ placeholder={ __( 'Accordion Title' ) }
64
+ keepPlaceholderOnFocus
65
+ value={ accordionTitle }
66
+ className='ab-accordion-title'
67
+ onChange={ ( value ) => this.props.setAttributes( { accordionTitle: value } ) }
68
+ />
69
+
70
+ <RichText
71
+ tagName="p"
72
+ placeholder={ __( 'Accordion Text' ) }
73
+ keepPlaceholderOnFocus
74
+ value={ accordionText }
75
+ isSelected={ isSelected }
76
+ className='ab-accordion-text'
77
+ onChange={ ( value ) => this.props.setAttributes( { accordionText: value } ) }
78
+ inlineToolbar
79
+ />
80
+ </Accordion>
81
+ ];
82
+ }
83
+ }
84
+
85
+ // Register the block
86
+ registerBlockType( 'atomic-blocks/ab-accordion', {
87
+ title: __( 'AB Accordion' ),
88
+ description: __( 'Add accordion block with a title and text.' ),
89
+ icon: 'editor-ul',
90
+ category: 'atomic-blocks',
91
+ keywords: [
92
+ __( 'accordion' ),
93
+ __( 'list' ),
94
+ __( 'atomic' ),
95
+ ],
96
+ attributes: {
97
+ accordionTitle: {
98
+ type: 'string',
99
+ },
100
+ accordionText: {
101
+ type: 'array',
102
+ selector: '.ab-accordion-text',
103
+ source: 'children',
104
+ },
105
+ accordionAlignment: {
106
+ type: 'string',
107
+ },
108
+ accordionFontSize: {
109
+ type: 'number',
110
+ default: 18
111
+ },
112
+ accordionOpen: {
113
+ type: 'boolean',
114
+ default: false
115
+ },
116
+ },
117
+
118
+ // Render the block components
119
+ edit: ABAccordionBlock,
120
+
121
+ // Save the attributes and markup
122
+ save: function( props ) {
123
+
124
+ // Setup the attributes
125
+ const { accordionTitle, accordionText, accordionAlignment, accordionFontSize, accordionOpen } = props.attributes;
126
+
127
+ // Save the block markup for the front end
128
+ return (
129
+ <Accordion { ...props }>
130
+ <details open={accordionOpen}>
131
+ <summary class="ab-accordion-title"><p>{ accordionTitle }</p></summary>
132
+ <p class="ab-accordion-text">{ accordionText }</p>
133
+ </details>
134
+ </Accordion>
135
+ );
136
+ },
137
+ } );
src/blocks/block-accordion/styles/editor.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-accordion {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-accordion"] {
10
+ margin-bottom: 1.2em;
11
+ }
src/blocks/block-accordion/styles/style.scss ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Accordion styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-accordion {
7
+ margin-bottom: 1.2em;
8
+
9
+ .ab-accordion-title {
10
+ background: #f2f2f2;
11
+ padding: 10px 15px;
12
+
13
+ p {
14
+ display: inline;
15
+ }
16
+ }
17
+
18
+ .ab-accordion-text {
19
+ padding: 10px 15px;
20
+
21
+ a {
22
+ color: inherit;
23
+ box-shadow: 0 -1px 0 inset;
24
+ text-decoration: none;
25
+
26
+ &:hover {
27
+ color: inherit;
28
+ box-shadow: 0 -2px 0 inset;
29
+ }
30
+ }
31
+ }
32
+
33
+ a {
34
+ color: inherit;
35
+ box-shadow: 0 -1px 0 inset;
36
+ text-decoration: none;
37
+
38
+ &:hover {
39
+ color: inherit;
40
+ box-shadow: 0 -2px 0 inset;
41
+ }
42
+ }
43
+
44
+ .editor-rich-text .editor-rich-text__inline-toolbar {
45
+ display: block;
46
+ left: 40%;
47
+ }
48
+ }
49
+
50
+ .ab-block-accordion + .ab-block-accordion {
51
+ margin-top: -.6em;
52
+ }
src/blocks/block-author-profile/components/avatar.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Avatar Column Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+ import icons from './icons';
11
+
12
+ // Import block components
13
+ const {
14
+ MediaUpload,
15
+ } = wp.editor;
16
+
17
+ // Create an SocialIcons wrapper Component
18
+ export default class AvatarColumn extends Component {
19
+
20
+ constructor( props ) {
21
+ super( ...arguments );
22
+ }
23
+
24
+ render() {
25
+ return (
26
+ <div class="ab-profile-column ab-profile-avatar-wrap">
27
+ <div class="ab-profile-image-wrap">
28
+ { this.props.children }
29
+ </div>
30
+ </div>
31
+ );
32
+ }
33
+ }
src/blocks/block-author-profile/components/icons.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='32px' height='32px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ export default icons;
src/blocks/block-author-profile/components/inspector.js ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Panel,
19
+ PanelBody,
20
+ PanelRow,
21
+ PanelColor,
22
+ RangeControl,
23
+ SelectControl,
24
+ TextControl,
25
+ } = wp.components;
26
+
27
+ // Create an Inspector Controls wrapper Component
28
+ export default class Inspector extends Component {
29
+
30
+ constructor( props ) {
31
+ super( ...arguments );
32
+ }
33
+
34
+ render() {
35
+
36
+ // Setup the attributes
37
+ const { profileName, profileTitle, profileContent, profileAlignment, profileImgURL, profileImgID, profileFontSize, profileBackgroundColor, profileTextColor, profileLinkColor, twitter, facebook, instagram, pinterest, google, youtube, github, email, website, profileAvatarShape } = this.props.attributes;
38
+
39
+ // Avatar shape options
40
+ const profileAvatarShapeOptions = [
41
+ { value: 'square', label: __( 'Square' ) },
42
+ { value: 'round', label: __( 'Round' ) },
43
+ ];
44
+
45
+ return (
46
+ <InspectorControls key="inspector">
47
+ <PanelBody>
48
+ <RangeControl
49
+ label={ __( 'Font Size' ) }
50
+ value={ profileFontSize }
51
+ onChange={ ( value ) => this.props.setAttributes( { profileFontSize: value } ) }
52
+ min={ 14 }
53
+ max={ 24 }
54
+ step={ 1 }
55
+ />
56
+
57
+ <SelectControl
58
+ label={ __( 'Avatar Shape' ) }
59
+ description={ __( 'Choose between a round or square avatar shape.' ) }
60
+ options={ profileAvatarShapeOptions }
61
+ value={ profileAvatarShape }
62
+ onChange={ ( value ) => this.props.setAttributes( { profileAvatarShape: value } ) }
63
+ />
64
+
65
+ <PanelColor
66
+ title={ __( 'Background Color' ) }
67
+ colorValue={ profileBackgroundColor }
68
+ initialOpen={ false }
69
+ >
70
+ <ColorPalette
71
+ label={ __( 'Background Color' ) }
72
+ value={ profileBackgroundColor }
73
+ onChange={ ( value ) => this.props.setAttributes( { profileBackgroundColor: value } ) }
74
+ />
75
+ </PanelColor>
76
+
77
+ <PanelColor
78
+ title={ __( 'Text Color' ) }
79
+ colorValue={ profileTextColor }
80
+ initialOpen={ false }
81
+ >
82
+ <ColorPalette
83
+ label={ __( 'Background Color' ) }
84
+ value={ profileTextColor }
85
+ onChange={ ( value ) => this.props.setAttributes( { profileTextColor: value } ) }
86
+ />
87
+ </PanelColor>
88
+
89
+ <PanelColor
90
+ title={ __( 'Social Link Color' ) }
91
+ colorValue={ profileLinkColor }
92
+ initialOpen={ false }
93
+ >
94
+ <ColorPalette
95
+ label={ __( 'Link Color' ) }
96
+ value={ profileLinkColor }
97
+ onChange={ ( value ) => this.props.setAttributes( { profileLinkColor: value } ) }
98
+ colors={[
99
+ { color: '#392F43', name: 'black' },
100
+ { color: '#3373dc', name: 'royal blue' },
101
+ { color: '#2DBAA3', name: 'teal' },
102
+ { color: '#209cef', name: 'sky blue' },
103
+ { color: '#2BAD59', name: 'green' },
104
+ { color: '#ff3860', name: 'pink' },
105
+ { color: '#7941b6', name: 'purple' },
106
+ { color: '#F7812B', name: 'orange' },
107
+ ]}
108
+ />
109
+ </PanelColor>
110
+ </PanelBody>
111
+
112
+ <PanelBody title={ __( 'Social Links' ) } initialOpen={ false }>
113
+ <p>{ __( 'Add links to your social media site and they will appear in the bottom of the profile box.' ) }</p>
114
+
115
+ <TextControl
116
+ label={ __( 'Twitter URL' ) }
117
+ type="url"
118
+ value={ twitter }
119
+ onChange={ ( value ) => this.props.setAttributes( { twitter: value } ) }
120
+ />
121
+
122
+ <TextControl
123
+ label={ __( 'Facebook URL' ) }
124
+ type="url"
125
+ value={ facebook }
126
+ onChange={ ( value ) => this.props.setAttributes( { facebook: value } ) }
127
+ />
128
+
129
+ <TextControl
130
+ label={ __( 'Instagram URL' ) }
131
+ type="url"
132
+ value={ instagram }
133
+ onChange={ ( value ) => this.props.setAttributes( { instagram: value } ) }
134
+ />
135
+
136
+ <TextControl
137
+ label={ __( 'Pinterest URL' ) }
138
+ type="url"
139
+ value={ pinterest }
140
+ onChange={ ( value ) => this.props.setAttributes( { pinterest: value } ) }
141
+ />
142
+
143
+ <TextControl
144
+ label={ __( 'Google URL' ) }
145
+ type="url"
146
+ value={ google }
147
+ onChange={ ( value ) => this.props.setAttributes( { google: value } ) }
148
+ />
149
+
150
+ <TextControl
151
+ label={ __( 'YouTube URL' ) }
152
+ type="url"
153
+ value={ youtube }
154
+ onChange={ ( value ) => this.props.setAttributes( { youtube: value } ) }
155
+ />
156
+
157
+ <TextControl
158
+ label={ __( 'Github URL' ) }
159
+ type="url"
160
+ value={ github }
161
+ onChange={ ( value ) => this.props.setAttributes( { github: value } ) }
162
+ />
163
+
164
+ <TextControl
165
+ label={ __( 'Email URL' ) }
166
+ type="url"
167
+ value={ email }
168
+ onChange={ ( value ) => this.props.setAttributes( { email: value } ) }
169
+ />
170
+
171
+ <TextControl
172
+ label={ __( 'Website URL' ) }
173
+ type="url"
174
+ value={ website }
175
+ onChange={ ( value ) => this.props.setAttributes( { website: value } ) }
176
+ />
177
+ </PanelBody>
178
+ </InspectorControls>
179
+ );
180
+ }
181
+ }
src/blocks/block-author-profile/components/profile.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Profile Box Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ // Create a profile box wrapper Component
12
+ export default class ProfileBox extends Component {
13
+
14
+ constructor( props ) {
15
+ super( ...arguments );
16
+ }
17
+
18
+ render() {
19
+
20
+ // Setup the attributes
21
+ const { profileAlignment, profileImgURL, profileFontSize, profileBackgroundColor, profileTextColor, profileAvatarShape } = this.props.attributes;
22
+
23
+ return (
24
+ <div
25
+ style={ {
26
+ backgroundColor: profileBackgroundColor,
27
+ color: profileTextColor,
28
+ } }
29
+ className={ classnames(
30
+ this.props.className,
31
+ profileAlignment,
32
+ profileAvatarShape,
33
+ { 'ab-has-avatar': profileImgURL },
34
+ 'ab-font-size-' + profileFontSize,
35
+ 'ab-block-profile',
36
+ 'ab-profile-columns',
37
+ ) }>
38
+ { this.props.children }
39
+ </div>
40
+ );
41
+ }
42
+ }
src/blocks/block-author-profile/components/social.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Social Media Icons
3
+ */
4
+ const { __ } = wp.i18n;
5
+ const { Component } = wp.element;
6
+
7
+ /**
8
+ * Create an SocialIcons wrapper Component
9
+ */
10
+ export default class SocialIcons extends Component {
11
+
12
+ constructor( props ) {
13
+ super( ...arguments );
14
+ }
15
+
16
+ render() {
17
+ return (
18
+ <ul class="ab-social-links">
19
+ { this.props.attributes.website && !! this.props.attributes.website.length && (
20
+ <li>
21
+ <a href={ this.props.attributes.website } target="_blank">{ __( 'Website' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fas fa-link"></i></a>
22
+ </li>
23
+ ) }
24
+
25
+ { this.props.attributes.twitter && !! this.props.attributes.twitter.length && (
26
+ <li>
27
+ <a href={ this.props.attributes.twitter } target="_blank">{ __( 'Twitter' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-twitter"></i></a>
28
+ </li>
29
+ ) }
30
+
31
+ { this.props.attributes.facebook && !! this.props.attributes.facebook.length && (
32
+ <li>
33
+ <a href={ this.props.attributes.facebook } target="_blank">{ __( 'Facebook' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-facebook-f"></i></a>
34
+ </li>
35
+ ) }
36
+
37
+ { this.props.attributes.instagram && !! this.props.attributes.instagram.length && (
38
+ <li>
39
+ <a href={ this.props.attributes.instagram } target="_blank">{ __( 'Instagram' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-instagram"></i></a>
40
+ </li>
41
+ ) }
42
+
43
+ { this.props.attributes.pinterest && !! this.props.attributes.pinterest.length && (
44
+ <li>
45
+ <a href={ this.props.attributes.pinterest } target="_blank">{ __( 'Pinterest' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-pinterest"></i></a>
46
+ </li>
47
+ ) }
48
+
49
+ { this.props.attributes.google && !! this.props.attributes.google.length && (
50
+ <li>
51
+ <a href={ this.props.attributes.google } target="_blank">{ __( 'Google' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-google"></i></a>
52
+ </li>
53
+ ) }
54
+
55
+ { this.props.attributes.youtube && !! this.props.attributes.youtube.length && (
56
+ <li>
57
+ <a href={ this.props.attributes.youtube } target="_blank">{ __( 'YouTube' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-youtube"></i></a>
58
+ </li>
59
+ ) }
60
+
61
+ { this.props.attributes.github && !! this.props.attributes.github.length && (
62
+ <li>
63
+ <a href={ this.props.attributes.github } target="_blank">{ __( 'Github' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-github"></i></a>
64
+ </li>
65
+ ) }
66
+
67
+ { this.props.attributes.email && !! this.props.attributes.email.length && (
68
+ <li>
69
+ <a href={ this.props.attributes.email } target="_blank">{ __( 'Email' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="far fa-envelope"></i></a>
70
+ </li>
71
+ ) }
72
+ </ul>
73
+ );
74
+ }
75
+ }
src/blocks/block-author-profile/index.js ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Profile Box
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import ProfileBox from './components/profile';
9
+ import SocialIcons from './components/social';
10
+ import AvatarColumn from './components/avatar';
11
+ import icons from './components/icons';
12
+
13
+ // Import styles
14
+ import './styles/style.scss';
15
+ import './styles/editor.scss';
16
+
17
+ // Internationalization
18
+ const { __ } = wp.i18n;
19
+
20
+ // Extend component
21
+ const { Component } = wp.element;
22
+
23
+ // Register block
24
+ const { registerBlockType } = wp.blocks;
25
+
26
+ // Register components
27
+ const {
28
+ RichText,
29
+ AlignmentToolbar,
30
+ BlockControls,
31
+ InspectorControls,
32
+ MediaUpload,
33
+ } = wp.editor;
34
+
35
+ // Register Inspector components
36
+ const {
37
+ Button,
38
+ } = wp.components;
39
+
40
+ const blockAttributes = {
41
+ profileName: {
42
+ type: 'string',
43
+ selector: '.ab-profile-name',
44
+ },
45
+ profileTitle: {
46
+ type: 'string',
47
+ selector: '.ab-profile-title',
48
+ },
49
+ profileContent: {
50
+ type: 'array',
51
+ selector: '.ab-profile-text',
52
+ source: 'children',
53
+ },
54
+ profileAlignment: {
55
+ type: 'string',
56
+ },
57
+ profileImgURL: {
58
+ type: 'string',
59
+ source: 'attribute',
60
+ attribute: 'src',
61
+ selector: 'img',
62
+ },
63
+ profileImgID: {
64
+ type: 'number',
65
+ },
66
+ profileBackgroundColor: {
67
+ type: 'string',
68
+ default: '#f2f2f2'
69
+ },
70
+ profileTextColor: {
71
+ type: 'string',
72
+ default: '#32373c'
73
+ },
74
+ profileLinkColor: {
75
+ type: 'string',
76
+ default: '#392f43'
77
+ },
78
+ profileFontSize: {
79
+ type: 'number',
80
+ default: 18
81
+ },
82
+ profileAvatarShape: {
83
+ type: 'string',
84
+ default: 'square',
85
+ },
86
+ twitter: {
87
+ type: 'url',
88
+ },
89
+ facebook: {
90
+ type: 'url',
91
+ },
92
+ instagram: {
93
+ type: 'url',
94
+ },
95
+ pinterest: {
96
+ type: 'url',
97
+ },
98
+ google: {
99
+ type: 'url',
100
+ },
101
+ youtube: {
102
+ type: 'url',
103
+ },
104
+ github: {
105
+ type: 'url',
106
+ },
107
+ email: {
108
+ type: 'url',
109
+ },
110
+ website: {
111
+ type: 'url',
112
+ },
113
+ };
114
+
115
+ class ABAuthorProfileBlock extends Component {
116
+
117
+ render() {
118
+
119
+ // Setup the attributes
120
+ const {
121
+ attributes: {
122
+ profileName,
123
+ profileTitle,
124
+ profileContent,
125
+ profileAlignment,
126
+ profileImgURL,
127
+ profileImgID,
128
+ profileFontSize,
129
+ profileBackgroundColor,
130
+ profileTextColor,
131
+ profileLinkColor,
132
+ twitter,
133
+ facebook,
134
+ instagram,
135
+ pinterest,
136
+ google,
137
+ youtube,
138
+ github,
139
+ email,
140
+ website,
141
+ profileAvatarShape
142
+ },
143
+ attributes,
144
+ isSelected,
145
+ editable,
146
+ className,
147
+ setAttributes
148
+ } = this.props;
149
+
150
+ const onSelectImage = img => {
151
+ setAttributes( {
152
+ profileImgID: img.id,
153
+ profileImgURL: img.url,
154
+ } );
155
+ };
156
+
157
+ return [
158
+ // Show the block alignment controls on focus
159
+ <BlockControls key="controls">
160
+ <AlignmentToolbar
161
+ value={ profileAlignment }
162
+ onChange={ ( value ) => setAttributes( { profileAlignment: value } ) }
163
+ />
164
+ </BlockControls>,
165
+ // Show the block controls on focus
166
+ <Inspector
167
+ { ...{ setAttributes, ...this.props } }
168
+ />,
169
+ // Show the block markup in the editor
170
+ <ProfileBox { ...this.props }>
171
+ <AvatarColumn { ...this.props }>
172
+ <div class="ab-profile-image-square">
173
+ <MediaUpload
174
+ buttonProps={ {
175
+ className: 'change-image'
176
+ } }
177
+ onSelect={ ( img ) => setAttributes(
178
+ {
179
+ profileImgID: img.id,
180
+ profileImgURL: img.url,
181
+ }
182
+ ) }
183
+ type="image"
184
+ value={ profileImgID }
185
+ render={ ( { open } ) => (
186
+ <Button onClick={ open }>
187
+ { ! profileImgID ? icons.upload : <img
188
+ class="profile-avatar"
189
+ src={ profileImgURL }
190
+ alt="avatar"
191
+ /> }
192
+ </Button>
193
+ ) }
194
+ >
195
+ </MediaUpload>
196
+ </div>
197
+ </AvatarColumn>
198
+
199
+ <div
200
+ className={ classnames(
201
+ 'ab-profile-column ab-profile-content-wrap'
202
+ ) }
203
+ >
204
+ <RichText
205
+ tagName="h2"
206
+ placeholder={ __( 'Add name' ) }
207
+ keepPlaceholderOnFocus
208
+ value={ profileName }
209
+ className='ab-profile-name'
210
+ style={ {
211
+ color: profileTextColor
212
+ } }
213
+ onChange={ ( value ) => setAttributes( { profileName: value } ) }
214
+ />
215
+
216
+ <RichText
217
+ tagName="p"
218
+ placeholder={ __( 'Add title' ) }
219
+ keepPlaceholderOnFocus
220
+ value={ profileTitle }
221
+ className='ab-profile-title'
222
+ style={ {
223
+ color: profileTextColor
224
+ } }
225
+ onChange={ ( value ) => setAttributes( { profileTitle: value } ) }
226
+ />
227
+
228
+ <RichText
229
+ tagName="div"
230
+ className='ab-profile-text'
231
+ multiline="p"
232
+ placeholder={ __( 'Add profile text...' ) }
233
+ keepPlaceholderOnFocus
234
+ value={ profileContent }
235
+ formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link' ] }
236
+ onChange={ ( value ) => setAttributes( { profileContent: value } ) }
237
+ inlineToolbar
238
+ />
239
+
240
+ <SocialIcons { ...this.props } />
241
+ </div>
242
+ </ProfileBox>
243
+ ];
244
+ }
245
+ }
246
+
247
+ // Register the block
248
+ registerBlockType( 'atomic-blocks/ab-profile-box', {
249
+ title: __( 'AB Profile Box' ),
250
+ description: __( 'Add a profile box with bio info and social media links.' ),
251
+ icon: 'admin-users',
252
+ category: 'atomic-blocks',
253
+ keywords: [
254
+ __( 'author' ),
255
+ __( 'profile' ),
256
+ __( 'atomic' ),
257
+ ],
258
+ // Setup the block attributes
259
+ attributes: blockAttributes,
260
+
261
+ // Render the block components
262
+ edit: ABAuthorProfileBlock,
263
+
264
+ // Save the block markup
265
+ save: function( props ) {
266
+
267
+ // Setup the attributes
268
+ const { profileName, profileTitle, profileContent, profileAlignment, profileImgURL, profileImgID, profileFontSize, profileBackgroundColor, profileTextColor, profileLinkColor, twitter, facebook, instagram, pinterest, google, youtube, github, email, website, profileAvatarShape } = props.attributes;
269
+
270
+ return (
271
+ // Save the block markup for the front end
272
+ <ProfileBox { ...props }>
273
+
274
+ { profileImgURL && !! profileImgURL.length && (
275
+ <AvatarColumn { ...props }>
276
+ <div class="ab-profile-image-square">
277
+ <img
278
+ class="ab-profile-avatar"
279
+ src={ profileImgURL }
280
+ alt="avatar"
281
+ />
282
+ </div>
283
+ </AvatarColumn>
284
+ ) }
285
+
286
+ <div
287
+ className={ classnames(
288
+ 'ab-profile-column ab-profile-content-wrap'
289
+ ) }
290
+ >
291
+ { profileName && !! profileName.length && (
292
+ <h2
293
+ className='ab-profile-name'
294
+ style={ {
295
+ color: profileTextColor
296
+ } }
297
+ >{ profileName }</h2>
298
+ ) }
299
+ { profileTitle && !! profileTitle.length && (
300
+ <p
301
+ className='ab-profile-title'
302
+ style={ {
303
+ color: profileTextColor
304
+ } }
305
+ >{ profileTitle }</p>
306
+ ) }
307
+ { profileContent && !! profileContent.length && (
308
+ <div className='ab-profile-text'>
309
+ { profileContent }
310
+ </div>
311
+ ) }
312
+ <SocialIcons { ...props } />
313
+ </div>
314
+ </ProfileBox>
315
+ );
316
+ },
317
+ } );
src/blocks/block-author-profile/styles/editor.scss ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-profile {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-profile-box"] {
10
+ margin-bottom: 1.2em;
11
+ }
12
+
13
+ .ab-profile-image-wrap {
14
+ svg {
15
+ pointer-events: none;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ .components-button:not(:disabled):not([aria-disabled=true]):focus {
20
+ background: none;
21
+ box-shadow: none;
22
+ }
23
+ }
24
+
25
+ .ab-profile-content-wrap {
26
+ h2.editor-rich-text__tinymce {
27
+ line-height: 1.2;
28
+ }
29
+
30
+ p.editor-rich-text__tinymce {
31
+ line-height: 1.6;
32
+ }
33
+ }
src/blocks/block-author-profile/styles/style.scss ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Profile styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-profile {
7
+ background: #f2f2f2;
8
+ color: #293038;
9
+ margin: 0 auto;
10
+ padding: 3%;
11
+ border-radius: 5px;
12
+ margin-bottom: 1.2em;
13
+ display: flex;
14
+ flex-flow: row wrap;
15
+ justify-content: space-around;
16
+ width: 100%;
17
+
18
+ .ab-profile-column {
19
+ display: block;
20
+ padding: 15px;
21
+ flex: 3 0 0;
22
+
23
+ @media only screen and (max-width: 600px) {
24
+ flex: auto;
25
+ }
26
+ }
27
+
28
+ .ab-profile-avatar-wrap {
29
+ position: relative;
30
+ z-index: 0;
31
+ flex: 1 0 0;
32
+
33
+ @media only screen and (max-width: 600px) {
34
+ flex: auto;
35
+ max-width: 210px;
36
+ margin: 0 auto;
37
+ }
38
+ }
39
+
40
+ .ab-profile-content-wrap {
41
+ @media only screen and (max-width: 600px) {
42
+ text-align: center;
43
+ }
44
+ }
45
+
46
+ .ab-profile-text {
47
+ font-size: 18px;
48
+ padding-top: 1em;
49
+
50
+ a {
51
+ color: inherit;
52
+ box-shadow: 0 -1px 0 inset;
53
+ text-decoration: none;
54
+
55
+ &:hover {
56
+ color: inherit;
57
+ box-shadow: 0 -2px 0 inset;
58
+ }
59
+ }
60
+
61
+ p {
62
+ line-height: 1.6;
63
+
64
+ &:last-child {
65
+ margin-bottom: 0;
66
+ }
67
+ }
68
+ }
69
+
70
+ .ab-profile-name {
71
+ font-size: 1.4em;
72
+ font-weight: bold;
73
+ line-height: 1.2;
74
+ margin: 0;
75
+ }
76
+
77
+ .ab-profile-title {
78
+ opacity: .8;
79
+ padding-top: 5px;
80
+ margin-bottom: 0;
81
+ }
82
+
83
+ .ab-profile-image-square {
84
+ position: absolute;
85
+ top: 0;
86
+ left: 0;
87
+ height: 100%;
88
+ width: 100%;
89
+ z-index: 5;
90
+ }
91
+
92
+ .ab-profile-text:empty,
93
+ .ab-profile-title:empty,
94
+ .ab-profile-name:empty {
95
+ display: none;
96
+ }
97
+
98
+ .ab-profile-image-wrap {
99
+ width: 100%;
100
+ background: #ddd;
101
+ position: relative;
102
+ width: 100%;
103
+
104
+ &:before {
105
+ content: '';
106
+ display: inline-block;
107
+ padding-top: 100%;
108
+ }
109
+
110
+ button {
111
+ position: absolute;
112
+ left: 0;
113
+ z-index: 50;
114
+ padding: 0;
115
+ height: 100%;
116
+ width: 100%;
117
+ }
118
+
119
+ button:focus {
120
+ background: none;
121
+ border: none;
122
+ outline: none;
123
+ box-shadow: none;
124
+ }
125
+
126
+ img {
127
+ object-fit: cover;
128
+ height: 100%;
129
+ width: 100%;
130
+ position: relative;
131
+ z-index: 5;
132
+ }
133
+ }
134
+
135
+ .ab-social-links {
136
+ list-style: none;
137
+ margin: 0 0 0 0;
138
+ padding: 5% 0 0 0;
139
+ font-size: 0;
140
+
141
+ &:empty {
142
+ display: none;
143
+ }
144
+
145
+ li {
146
+ display: inline-block;
147
+ margin: 0 8px 0 0;
148
+ padding: 0;
149
+
150
+ a {
151
+ border: none;
152
+
153
+ &:hover {
154
+ opacity: .9;
155
+ }
156
+ }
157
+
158
+ i {
159
+ font-size: 18px;
160
+ background: #0393e3;
161
+ color: #fff;
162
+ padding: 10px;
163
+ border-radius: 100px;
164
+ height: 38px;
165
+ width: 38px;
166
+ text-align: center;
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ .right .ab-profile-avatar-wrap {
173
+ order: 2;
174
+ }
175
+
176
+ .round .ab-profile-image-wrap {
177
+ border-radius: 500px;
178
+ overflow: hidden;
179
+
180
+ &:before {
181
+ content: '';
182
+ display: inline-block;
183
+ padding-top: 92%;
184
+ }
185
+
186
+ img {
187
+ border-radius: 500px;
188
+ }
189
+ }
190
+
191
+ #editor .ab-has-avatar {
192
+
193
+ .ab-profile-image-square {
194
+ &:hover {
195
+ cursor: pointer;
196
+
197
+ img {
198
+ opacity: .7 !important;
199
+ }
200
+ }
201
+
202
+ button {
203
+ top: 0;
204
+ left: 0;
205
+ opacity: 1 !important;
206
+ height: 100%;
207
+ }
208
+ }
209
+
210
+ }
src/blocks/block-button/components/button.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Button Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a Button wrapper Component
13
+ */
14
+ export default class customButton extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+
22
+ return (
23
+ <div
24
+ style={ {
25
+ textAlign: this.props.attributes.buttonAlignment,
26
+ } }
27
+ className={ classnames(
28
+ this.props.className,
29
+ 'ab-block-button'
30
+ ) }
31
+ >
32
+ { this.props.children }
33
+ </div>
34
+ );
35
+ }
36
+ }
src/blocks/block-button/components/icons.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ icons.dismiss = <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns='http://www.w3.org/2000/svg' width="20" height="20" viewBox="0 0 20 20">
15
+ <path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zM15 13l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z"></path>
16
+ </svg>;
17
+
18
+ export default icons;
src/blocks/block-button/components/inspector.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ FormToggle,
24
+ RangeControl,
25
+ SelectControl,
26
+ ToggleControl,
27
+ } = wp.components;
28
+
29
+ /**
30
+ * Create an Inspector Controls wrapper Component
31
+ */
32
+ export default class Inspector extends Component {
33
+
34
+ constructor( props ) {
35
+ super( ...arguments );
36
+ }
37
+
38
+ render() {
39
+
40
+ // Setup the attributes
41
+ const { buttonText, buttonUrl, buttonAlignment, buttonBackgroundColor, buttonTextColor, buttonSize, buttonShape, buttonTarget } = this.props.attributes;
42
+
43
+ // Button size values
44
+ const buttonSizeOptions = [
45
+ { value: 'ab-button-size-small', label: __( 'Small' ) },
46
+ { value: 'ab-button-size-medium', label: __( 'Medium' ) },
47
+ { value: 'ab-button-size-large', label: __( 'Large' ) },
48
+ { value: 'ab-button-size-extralarge', label: __( 'Extra Large' ) },
49
+ ];
50
+
51
+ // Button shape
52
+ const buttonShapeOptions = [
53
+ { value: 'ab-button-shape-square', label: __( 'Square' ) },
54
+ { value: 'ab-button-shape-rounded', label: __( 'Rounded Square' ) },
55
+ { value: 'ab-button-shape-circular', label: __( 'Circular' ) },
56
+ ];
57
+
58
+ return (
59
+ <InspectorControls key="inspector">
60
+ <PanelBody>
61
+ <ToggleControl
62
+ label={ __( 'Open link in new window' ) }
63
+ checked={ buttonTarget }
64
+ onChange={ () => this.props.setAttributes( { buttonTarget: ! buttonTarget } ) }
65
+ />
66
+
67
+ <SelectControl
68
+ label={ __( 'Button Size' ) }
69
+ value={ buttonSize }
70
+ options={ buttonSizeOptions.map( ({ value, label }) => ( {
71
+ value: value,
72
+ label: label,
73
+ } ) ) }
74
+ onChange={ ( value ) => { this.props.setAttributes( { buttonSize: value } ) } }
75
+ />
76
+
77
+ <SelectControl
78
+ label={ __( 'Button Shape' ) }
79
+ value={ buttonShape }
80
+ options={ buttonShapeOptions.map( ({ value, label }) => ( {
81
+ value: value,
82
+ label: label,
83
+ } ) ) }
84
+ onChange={ ( value ) => { this.props.setAttributes( { buttonShape: value } ) } }
85
+ />
86
+
87
+ <PanelColor
88
+ title={ __( 'Button Color' ) }
89
+ colorValue={ buttonBackgroundColor }
90
+ initialOpen={ false }
91
+ >
92
+ <ColorPalette
93
+ label={ __( 'Button Color' ) }
94
+ value={ buttonBackgroundColor }
95
+ onChange={ ( value ) => { this.props.setAttributes( { buttonBackgroundColor: value } ) } }
96
+ colors={[
97
+ { color: '#00d1b2', name: 'teal' },
98
+ { color: '#3373dc', name: 'royal blue' },
99
+ { color: '#209cef', name: 'sky blue' },
100
+ { color: '#22d25f', name: 'green' },
101
+ { color: '#ffdd57', name: 'yellow' },
102
+ { color: '#ff3860', name: 'pink' },
103
+ { color: '#7941b6', name: 'purple' },
104
+ { color: '#392F43', name: 'black' },
105
+ ]}
106
+ />
107
+ </PanelColor>
108
+
109
+ <PanelColor
110
+ title={ __( 'Button Text Color' ) }
111
+ colorValue={ buttonTextColor }
112
+ initialOpen={ false }
113
+ >
114
+ <ColorPalette
115
+ label={ __( 'Button Text Color' ) }
116
+ value={ buttonTextColor }
117
+ onChange={ ( value ) => { this.props.setAttributes( { buttonTextColor: value } ) } }
118
+ colors={[
119
+ { color: '#fff', name: 'white' },
120
+ { color: '#32373c', name: 'black' },
121
+ ]}
122
+ />
123
+ </PanelColor>
124
+ </PanelBody>
125
+ </InspectorControls>
126
+ );
127
+ }
128
+ }
src/blocks/block-button/index.js ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Button
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import CustomButton from './components/button';
9
+ import icons from './components/icons';
10
+
11
+ // Import CSS
12
+ import './styles/style.scss';
13
+ import './styles/editor.scss';
14
+
15
+ // Components
16
+ const { __ } = wp.i18n;
17
+
18
+ // Extend component
19
+ const { Component } = wp.element;
20
+
21
+ // Register block
22
+ const { registerBlockType } = wp.blocks;
23
+
24
+ // Register editor components
25
+ const {
26
+ RichText,
27
+ AlignmentToolbar,
28
+ BlockControls,
29
+ BlockAlignmentToolbar,
30
+ URLInput,
31
+ } = wp.editor;
32
+
33
+ // Register components
34
+ const {
35
+ Button,
36
+ withFallbackStyles,
37
+ IconButton,
38
+ Dashicon,
39
+ } = wp.components;
40
+
41
+ class ABButtonBlock extends Component {
42
+
43
+ render() {
44
+
45
+ // Setup the attributes
46
+ const { attributes: { buttonText, buttonUrl, buttonAlignment, buttonBackgroundColor, buttonTextColor, buttonSize, buttonShape, buttonTarget }, isSelected, className, setAttributes } = this.props;
47
+
48
+ return [
49
+ // Show the alignment toolbar on focus
50
+ <BlockControls key="controls">
51
+ <AlignmentToolbar
52
+ value={ buttonAlignment }
53
+ onChange={ ( value ) => {
54
+ setAttributes( { buttonAlignment: value } );
55
+ } }
56
+ />
57
+ </BlockControls>,
58
+ // Show the block controls on focus
59
+ <Inspector
60
+ { ...this.props }
61
+ />,
62
+ // Show the button markup in the editor
63
+ <CustomButton { ...this.props }>
64
+ <RichText
65
+ tagName="span"
66
+ placeholder={ __( 'Button text...' ) }
67
+ keepPlaceholderOnFocus
68
+ value={ buttonText }
69
+ formattingControls={ [] }
70
+ className={ classnames(
71
+ 'ab-button',
72
+ buttonShape,
73
+ buttonSize,
74
+ ) }
75
+ style={ {
76
+ color: buttonTextColor,
77
+ backgroundColor: buttonBackgroundColor,
78
+ } }
79
+ onChange={ (value) => setAttributes( { buttonText: value } ) }
80
+ />
81
+ </CustomButton>,
82
+ isSelected && (
83
+ <form
84
+ key="form-link"
85
+ className={ `blocks-button__inline-link ab-button-${buttonAlignment}`}
86
+ onSubmit={ event => event.preventDefault() }
87
+ style={ {
88
+ textAlign: buttonAlignment,
89
+ } }
90
+ >
91
+ <Dashicon icon={ 'admin-links' } />
92
+ <URLInput
93
+ className="button-url"
94
+ value={ buttonUrl }
95
+ onChange={ ( value ) => setAttributes( { buttonUrl: value } ) }
96
+ />
97
+ <IconButton
98
+ icon="editor-break"
99
+ label={ __( 'Apply' ) }
100
+ type="submit"
101
+ />
102
+ </form>
103
+ )
104
+ ];
105
+ }
106
+ }
107
+
108
+ // Register the block
109
+ registerBlockType( 'atomic-blocks/ab-button', {
110
+ title: __( 'AB Button' ),
111
+ description: __( 'Add a customizable button.' ),
112
+ icon: 'admin-links',
113
+ category: 'atomic-blocks',
114
+ keywords: [
115
+ __( 'button' ),
116
+ __( 'link' ),
117
+ __( 'atomic' ),
118
+ ],
119
+ attributes: {
120
+ buttonText: {
121
+ type: 'string',
122
+ },
123
+ buttonUrl: {
124
+ type: 'string',
125
+ source: 'attribute',
126
+ selector: 'a',
127
+ attribute: 'href',
128
+ },
129
+ buttonAlignment: {
130
+ type: 'string',
131
+ },
132
+ buttonBackgroundColor: {
133
+ type: 'string',
134
+ default: '#3373dc'
135
+ },
136
+ buttonTextColor: {
137
+ type: 'string',
138
+ default: '#ffffff'
139
+ },
140
+ buttonSize: {
141
+ type: 'string',
142
+ default: 'ab-button-size-medium'
143
+ },
144
+ buttonShape: {
145
+ type: 'string',
146
+ default: 'ab-button-shape-rounded'
147
+ },
148
+ buttonTarget: {
149
+ type: 'boolean',
150
+ default: false
151
+ },
152
+ },
153
+
154
+ // Render the block components
155
+ edit: ABButtonBlock,
156
+
157
+ // Save the attributes and markup
158
+ save: function( props ) {
159
+
160
+ // Setup the attributes
161
+ const { buttonText, buttonUrl, buttonAlignment, buttonBackgroundColor, buttonTextColor, buttonSize, buttonShape, buttonTarget } = props.attributes;
162
+
163
+ // Save the block markup for the front end
164
+ return (
165
+ <CustomButton { ...props }>
166
+ { // Check if there is button text and output
167
+ buttonText && (
168
+ <a
169
+ href={ buttonUrl }
170
+ target={ buttonTarget ? '_blank' : '_self' }
171
+ className={ classnames(
172
+ 'ab-button',
173
+ buttonShape,
174
+ buttonSize,
175
+ ) }
176
+ style={ {
177
+ color: buttonTextColor,
178
+ backgroundColor: buttonBackgroundColor,
179
+ } }
180
+ >
181
+ { buttonText }
182
+ </a>
183
+ ) }
184
+ </CustomButton>
185
+ );
186
+ },
187
+ } );
src/blocks/block-button/styles/editor.scss ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-button {
6
+ &+ .blocks-button__inline-link .editor-url-input {
7
+ display: inline-block;
8
+ width: auto;
9
+ }
10
+
11
+ &+ .blocks-button__inline-link .components-button {
12
+ display: inline-block;
13
+ }
14
+
15
+ &+ .blocks-button__inline-link svg {
16
+ vertical-align: middle;
17
+ margin-right: 8px;
18
+ }
19
+ }
src/blocks/block-button/styles/style.scss ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Button styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-button {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+
10
+ .blocks-rich-text {
11
+ display: inline-flex;
12
+ }
13
+
14
+ .components-autocomplete {
15
+ display: inline-block;
16
+ width: auto;
17
+ margin: 0 auto;
18
+ position: relative;
19
+ }
20
+
21
+ .ab-button {
22
+ text-align: center;
23
+ font-size: 18px;
24
+ line-height: 1 !important;
25
+ background-color: #32373c;
26
+ border: none;
27
+ border-radius: 50px;
28
+ box-shadow: none;
29
+ color: #fff;
30
+ cursor: pointer;
31
+ padding: .6em 1em;
32
+ text-decoration: none;
33
+ word-break: break-word;
34
+ transition: .3s ease;
35
+ display: inline-block;
36
+
37
+ &:hover {
38
+ box-shadow: inset 0 0 200px rgba(255, 255, 255, 0.15);
39
+ }
40
+ }
41
+
42
+ .ab-button-shape-square {
43
+ border-radius: 0;
44
+ }
45
+
46
+ .ab-button-shape-rounded {
47
+ border-radius: 5px;
48
+ }
49
+
50
+ .ab-button-shape-circular {
51
+ border-radius: 100px;
52
+ }
53
+
54
+ .ab-button-size-small {
55
+ font-size: 14px;
56
+ }
57
+
58
+ .ab-button-size-medium {
59
+ font-size: 20px;
60
+ }
61
+
62
+ .ab-button-size-large {
63
+ font-size: 26px;
64
+ padding: .8em 1.2em;
65
+ }
66
+
67
+ .ab-button-size-extralarge {
68
+ font-size: 34px;
69
+ padding: .8em 1.2em;
70
+ }
71
+ }
72
+
73
+ .ab-button-right {
74
+ transform: translateX(-100%);
75
+ left: 100%;
76
+ position: relative;
77
+ }
78
+
79
+ .ab-button-center {
80
+ margin: 0 auto;
81
+ }
src/blocks/block-container/components/container.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Container wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a Button wrapper Component
13
+ */
14
+ export default class Container extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+ // Setup the attributes
22
+ const { attributes: { containerBackgroundColor, containerAlignment, containerPaddingTop, containerPaddingRight, containerPaddingBottom, containerPaddingLeft, containerMarginTop, containerMarginBottom, containerWidth, containerMaxWidth } } = this.props;
23
+
24
+ return (
25
+ <div
26
+ style={ {
27
+ backgroundColor: containerBackgroundColor,
28
+ textAlign: containerAlignment,
29
+ paddingLeft: `${containerPaddingLeft}%`,
30
+ paddingRight: `${containerPaddingRight}%`,
31
+ paddingBottom: `${containerPaddingBottom}%`,
32
+ paddingTop: `${containerPaddingTop}%`,
33
+ marginTop: `${containerMarginTop}%`,
34
+ marginBottom: `${containerMarginBottom}%`,
35
+ } }
36
+ className={ classnames(
37
+ this.props.className,
38
+ `align${containerWidth}`,
39
+ 'ab-block-container',
40
+ ) }
41
+ >{ this.props.children }</div>
42
+ );
43
+ }
44
+ }
src/blocks/block-container/components/inspector.js ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ MediaUpload,
15
+ } = wp.editor;
16
+
17
+ // Import Inspector components
18
+ const {
19
+ Toolbar,
20
+ Button,
21
+ PanelBody,
22
+ PanelRow,
23
+ PanelColor,
24
+ FormToggle,
25
+ RangeControl,
26
+ SelectControl,
27
+ ToggleControl,
28
+ IconButton,
29
+ } = wp.components;
30
+
31
+ /**
32
+ * Create an Inspector Controls wrapper Component
33
+ */
34
+ export default class Inspector extends Component {
35
+
36
+ constructor( props ) {
37
+ super( ...arguments );
38
+ }
39
+
40
+ render() {
41
+
42
+ // Setup the attributes
43
+ const { containerPaddingTop, containerPaddingRight, containerPaddingBottom, containerPaddingLeft, containerMarginTop, containerMarginBottom, containerMaxWidth, containerBackgroundColor, containerDimRatio, containerImgURL, containerImgID, containerImgAlt } = this.props.attributes;
44
+ const { setAttributes } = this.props;
45
+
46
+ const onSelectImage = img => {
47
+ setAttributes( {
48
+ containerImgID: img.id,
49
+ containerImgURL: img.url,
50
+ containerImgAlt: img.alt,
51
+ } );
52
+ };
53
+
54
+ const onRemoveImage = () => {
55
+ setAttributes({
56
+ containerImgID: null,
57
+ containerImgURL: null,
58
+ containerImgAlt: null,
59
+ });
60
+ }
61
+
62
+ return (
63
+ <InspectorControls key="inspector">
64
+ <PanelBody title={ __( 'Container Options' ) } initialOpen={ true }>
65
+ <RangeControl
66
+ label={ __( 'Padding Top (%)' ) }
67
+ value={ containerPaddingTop }
68
+ onChange={ ( value ) => this.props.setAttributes( { containerPaddingTop: value } ) }
69
+ min={ 0 }
70
+ max={ 20 }
71
+ step={ 1 }
72
+ />
73
+
74
+ <RangeControl
75
+ label={ __( 'Padding Bottom (%)' ) }
76
+ value={ containerPaddingBottom }
77
+ onChange={ ( value ) => this.props.setAttributes( { containerPaddingBottom: value } ) }
78
+ min={ 0 }
79
+ max={ 20 }
80
+ step={ .5 }
81
+ />
82
+
83
+ <RangeControl
84
+ label={ __( 'Padding Left (%)' ) }
85
+ value={ containerPaddingLeft }
86
+ onChange={ ( value ) => this.props.setAttributes( { containerPaddingLeft: value } ) }
87
+ min={ 0 }
88
+ max={ 20 }
89
+ step={ 1 }
90
+ />
91
+
92
+ <RangeControl
93
+ label={ __( 'Padding Right (%)' ) }
94
+ value={ containerPaddingRight }
95
+ onChange={ ( value ) => this.props.setAttributes( { containerPaddingRight: value } ) }
96
+ min={ 0 }
97
+ max={ 20 }
98
+ step={ .5 }
99
+ />
100
+
101
+ <RangeControl
102
+ label={ __( 'Margin Top (%)' ) }
103
+ value={ containerMarginTop }
104
+ onChange={ ( value ) => this.props.setAttributes( { containerMarginTop: value } ) }
105
+ min={ 0 }
106
+ max={ 20 }
107
+ step={ 1 }
108
+ />
109
+
110
+ <RangeControl
111
+ label={ __( 'Margin Bottom (%)' ) }
112
+ value={ containerMarginBottom }
113
+ onChange={ ( value ) => this.props.setAttributes( { containerMarginBottom: value } ) }
114
+ min={ 0 }
115
+ max={ 20 }
116
+ step={ .5 }
117
+ />
118
+
119
+ <RangeControl
120
+ label={ __( 'Inside Container Max Width (px)' ) }
121
+ value={ containerMaxWidth }
122
+ onChange={ ( value ) => this.props.setAttributes( { containerMaxWidth: value } ) }
123
+ min={ 500 }
124
+ max={ 1600 }
125
+ step={ 1 }
126
+ />
127
+ </PanelBody>
128
+
129
+ <PanelBody title={ __( 'Background Options' ) } initialOpen={ false }>
130
+ <p>{ __( 'Select a background image:' ) }</p>
131
+ <MediaUpload
132
+ onSelect={ onSelectImage }
133
+ type="image"
134
+ value={ containerImgID }
135
+ render={ ( { open } ) => (
136
+ <div>
137
+ <IconButton
138
+ className="ab-container-inspector-media"
139
+ label={ __( 'Edit image' ) }
140
+ icon="format-image"
141
+ onClick={ open }
142
+ >
143
+ { __( 'Select Image' ) }
144
+ </IconButton>
145
+
146
+ { containerImgURL && !! containerImgURL.length && (
147
+ <IconButton
148
+ className="ab-container-inspector-media"
149
+ label={ __( 'Remove Image' ) }
150
+ icon="dismiss"
151
+ onClick={ onRemoveImage }
152
+ >
153
+ { __( 'Remove' ) }
154
+ </IconButton>
155
+ ) }
156
+ </div>
157
+ ) }
158
+ >
159
+ </MediaUpload>
160
+
161
+ { containerImgURL && !! containerImgURL.length && (
162
+ <RangeControl
163
+ label={ __( 'Image Opacity' ) }
164
+ value={ containerDimRatio }
165
+ onChange={ ( value ) => this.props.setAttributes( { containerDimRatio: value } ) }
166
+ min={ 0 }
167
+ max={ 100 }
168
+ step={ 10 }
169
+ />
170
+ ) }
171
+
172
+ <PanelColor
173
+ title={ __( 'Background Color' ) }
174
+ colorValue={ containerBackgroundColor }
175
+ initialOpen={ false }
176
+ >
177
+ <ColorPalette
178
+ label={ __( 'Background Color' ) }
179
+ value={ containerBackgroundColor }
180
+ onChange={ ( value ) => this.props.setAttributes( { containerBackgroundColor: value } ) }
181
+ />
182
+ </PanelColor>
183
+ </PanelBody>
184
+ </InspectorControls>
185
+ );
186
+ }
187
+ }
src/blocks/block-container/index.js ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Container
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import Container from './components/container';
9
+
10
+ // Import CSS
11
+ import './styles/style.scss';
12
+ import './styles/editor.scss';
13
+
14
+ // Components
15
+ const { __ } = wp.i18n;
16
+
17
+ // Extend component
18
+ const { Component } = wp.element;
19
+
20
+ // Register block
21
+ const { registerBlockType } = wp.blocks;
22
+
23
+ // Register editor components
24
+ const {
25
+ AlignmentToolbar,
26
+ BlockControls,
27
+ BlockAlignmentToolbar,
28
+ MediaUpload,
29
+ RichText,
30
+ InnerBlocks,
31
+ } = wp.editor;
32
+
33
+ // Register components
34
+ const {
35
+ Button,
36
+ withFallbackStyles,
37
+ IconButton,
38
+ Dashicon,
39
+ withState,
40
+ Toolbar,
41
+ } = wp.components;
42
+
43
+ const blockAttributes = {
44
+ containerPaddingTop: {
45
+ type: 'number',
46
+ default: 0,
47
+ },
48
+ containerPaddingRight: {
49
+ type: 'number',
50
+ default: 0,
51
+ },
52
+ containerPaddingBottom: {
53
+ type: 'number',
54
+ default: 0,
55
+ },
56
+ containerPaddingLeft: {
57
+ type: 'number',
58
+ default: 0,
59
+ },
60
+ containerMarginTop: {
61
+ type: 'number',
62
+ default: 0,
63
+ },
64
+ containerMarginBottom: {
65
+ type: 'number',
66
+ default: 0,
67
+ },
68
+ containerWidth: {
69
+ type: 'string',
70
+ default: 'center',
71
+ },
72
+ containerMaxWidth: {
73
+ type: 'number',
74
+ default: 1600,
75
+ },
76
+ containerBackgroundColor: {
77
+ type: 'string',
78
+ default: '#fff',
79
+ },
80
+ containerImgURL: {
81
+ type: 'string',
82
+ source: 'attribute',
83
+ attribute: 'src',
84
+ selector: 'img',
85
+ },
86
+ containerImgID: {
87
+ type: 'number',
88
+ },
89
+ containerImgAlt: {
90
+ type: 'string',
91
+ source: 'attribute',
92
+ attribute: 'alt',
93
+ selector: 'img',
94
+ },
95
+ containerDimRatio: {
96
+ type: 'number',
97
+ default: 50,
98
+ },
99
+ };
100
+
101
+ class ABContainerBlock extends Component {
102
+
103
+ render() {
104
+
105
+ // Setup the attributes
106
+ const {
107
+ attributes: {
108
+ containerPaddingTop,
109
+ containerPaddingRight,
110
+ containerPaddingBottom,
111
+ containerPaddingLeft,
112
+ containerMarginTop,
113
+ containerMarginBottom,
114
+ containerWidth,
115
+ containerMaxWidth,
116
+ containerBackgroundColor,
117
+ containerImgURL,
118
+ containerImgID,
119
+ containerImgAlt,
120
+ containerDimRatio,
121
+ },
122
+ attributes,
123
+ isSelected,
124
+ editable,
125
+ className,
126
+ setAttributes
127
+ } = this.props;
128
+
129
+ const onSelectImage = img => {
130
+ setAttributes( {
131
+ containerImgID: img.id,
132
+ containerImgURL: img.url,
133
+ containerImgAlt: img.alt,
134
+ } );
135
+ };
136
+
137
+ return [
138
+ // Show the alignment toolbar on focus
139
+ <BlockControls>
140
+ <BlockAlignmentToolbar
141
+ value={ containerWidth }
142
+ onChange={ containerWidth => setAttributes( { containerWidth } ) }
143
+ controls={ [ 'center', 'full' ] }
144
+ />
145
+ </BlockControls>,
146
+ // Show the block controls on focus
147
+ <Inspector
148
+ { ...{ setAttributes, ...this.props } }
149
+ />,
150
+ // Show the container markup in the editor
151
+ <Container { ...this.props }>
152
+ <div class="ab-container-inside">
153
+ { containerImgURL && !! containerImgURL.length && (
154
+ <div class="ab-container-image-wrap">
155
+ <img
156
+ className={ classnames(
157
+ 'ab-container-image',
158
+ dimRatioToClass( containerDimRatio ),
159
+ {
160
+ 'has-background-dim': containerDimRatio !== 0,
161
+ }
162
+ ) }
163
+ src={ containerImgURL }
164
+ alt={ containerImgAlt }
165
+ />
166
+ </div>
167
+ ) }
168
+
169
+ <div
170
+ class="ab-container-content"
171
+ style={ {
172
+ maxWidth: `${containerMaxWidth}px`,
173
+ } }
174
+ >
175
+ <InnerBlocks />
176
+ </div>
177
+ </div>
178
+ </Container>
179
+ ];
180
+ }
181
+ }
182
+
183
+ // Register the block
184
+ registerBlockType( 'atomic-blocks/ab-container', {
185
+ title: __( 'AB Container' ),
186
+ description: __( 'Add a container block to wrap several blocks in a parent container.' ),
187
+ icon: 'editor-table',
188
+ category: 'atomic-blocks',
189
+ keywords: [
190
+ __( 'container' ),
191
+ __( 'section' ),
192
+ __( 'atomic' ),
193
+ ],
194
+
195
+ attributes: blockAttributes,
196
+
197
+ getEditWrapperProps( { containerWidth } ) {
198
+ if ( 'left' === containerWidth || 'right' === containerWidth || 'full' === containerWidth ) {
199
+ return { 'data-align': containerWidth };
200
+ }
201
+ },
202
+
203
+ // Render the block components
204
+ edit: ABContainerBlock,
205
+
206
+ // Save the attributes and markup
207
+ save: function( props ) {
208
+
209
+ // Setup the attributes
210
+ const {
211
+ containerPaddingTop,
212
+ containerPaddingRight,
213
+ containerPaddingBottom,
214
+ containerPaddingLeft,
215
+ containerMarginTop,
216
+ containerMarginBottom,
217
+ containerWidth,
218
+ containerMaxWidth,
219
+ containerBackgroundColor,
220
+ containerImgURL,
221
+ containerImgID,
222
+ containerImgAlt,
223
+ containerDimRatio,
224
+ } = props.attributes;
225
+
226
+ // Save the block markup for the front end
227
+ return (
228
+ <Container { ...props }>
229
+ <div class="ab-container-inside">
230
+ { containerImgURL && !! containerImgURL.length && (
231
+ <div class="ab-container-image-wrap">
232
+ <img
233
+ className={ classnames(
234
+ 'ab-container-image',
235
+ dimRatioToClass( containerDimRatio ),
236
+ {
237
+ 'has-background-dim': containerDimRatio !== 0,
238
+ }
239
+ ) }
240
+ src={ containerImgURL }
241
+ alt={ containerImgAlt }
242
+ />
243
+ </div>
244
+ ) }
245
+
246
+ <div
247
+ class="ab-container-content"
248
+ style={ {
249
+ maxWidth: `${containerMaxWidth}px`,
250
+ } }
251
+ >
252
+ <InnerBlocks.Content />
253
+ </div>
254
+ </div>
255
+ </Container>
256
+ );
257
+ },
258
+ } );
259
+
260
+ function dimRatioToClass( ratio ) {
261
+ return ( ratio === 0 || ratio === 50 ) ?
262
+ null :
263
+ 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) );
264
+ }
265
+
266
+ function backgroundImageStyles( url ) {
267
+ return url ?
268
+ { backgroundImage: `url(${ url })` } :
269
+ undefined;
270
+ }
src/blocks/block-container/styles/editor.scss ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-cta {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-cta"] {
10
+ margin-bottom: 1.2em;
11
+ }
12
+
13
+ .ab-cta-button {
14
+ .blocks-button__inline-link .editor-url-input {
15
+ display: inline-block;
16
+ width: auto;
17
+ }
18
+
19
+ .blocks-button__inline-link .components-button {
20
+ display: inline-block;
21
+ }
22
+
23
+ .blocks-button__inline-link svg {
24
+ vertical-align: middle;
25
+ margin-right: 8px;
26
+ }
27
+ }
28
+
29
+ .ab-block-cta h2.editor-rich-text__tinymce.mce-content-body {
30
+ line-height: 1.2;
31
+ }
src/blocks/block-container/styles/style.scss ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * CTA styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-container {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+ background: #fff;
10
+ color: #293038;
11
+ padding: 0;
12
+
13
+ &.alignfull {
14
+ .editor-block-list__block {
15
+ max-width: 100%;
16
+ }
17
+ }
18
+
19
+ &.alignfull .ab-container-inside,
20
+ &.alignfull .ab-container-inside {
21
+ @media only screen and (min-width: 768px) {
22
+ max-width: 100%;
23
+ margin: 0 auto;
24
+ }
25
+ }
26
+
27
+ &.alignwide .ab-container-inside,
28
+ &.alignwide .ab-container-inside {
29
+ @media only screen and (min-width: 768px) {
30
+ max-width: 80%;
31
+ margin: 0 auto;
32
+ }
33
+ }
34
+
35
+ .ab-container-image-wrap {
36
+ position: absolute;
37
+ top: 0;
38
+ right: 0;
39
+ left: 0;
40
+ height: 100%;
41
+ z-index: 0;
42
+ }
43
+
44
+ .ab-container-image {
45
+ object-fit: cover;
46
+ height: 100%;
47
+ width: 100%;
48
+ transition: .3s ease;
49
+ }
50
+
51
+ .ab-container-content {
52
+ margin: 0 auto;
53
+ position: relative;
54
+ }
55
+
56
+ .ab-container-image:not(.has-background-dim) {
57
+ opacity: 0;
58
+ }
59
+
60
+ .has-background-dim {
61
+ opacity: .5;
62
+ }
63
+
64
+ .has-background-dim-10 {
65
+ opacity: .1;
66
+ }
67
+
68
+ .has-background-dim-20 {
69
+ opacity: .2;
70
+ }
71
+
72
+ .has-background-dim-30 {
73
+ opacity: .3;
74
+ }
75
+
76
+ .has-background-dim-40 {
77
+ opacity: .4;
78
+ }
79
+
80
+ .has-background-dim-50 {
81
+ opacity: .5;
82
+ }
83
+
84
+ .has-background-dim-60 {
85
+ opacity: .6;
86
+ }
87
+
88
+ .has-background-dim-70 {
89
+ opacity: .7;
90
+ }
91
+
92
+ .has-background-dim-80 {
93
+ opacity: .8;
94
+ }
95
+
96
+ .has-background-dim-90 {
97
+ opacity: .9;
98
+ }
99
+
100
+ .has-background-dim-100 {
101
+ opacity: 1;
102
+ }
103
+ }
src/blocks/block-cta/components/cta.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * CTA Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a CallToAction wrapper Component
13
+ */
14
+ export default class CallToAction extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+ // Setup the attributes
22
+ const { attributes: { buttonText, buttonUrl, buttonAlignment, buttonBackgroundColor, buttonTextColor, buttonSize, buttonShape, buttonTarget, ctaTitle, ctaText, ctaTitleFontSize, ctaTextFontSize, ctaWidth, ctaBackgroundColor, ctaTextColor } } = this.props;
23
+
24
+ return (
25
+ <div
26
+ style={ {
27
+ backgroundColor: ctaBackgroundColor,
28
+ textAlign: buttonAlignment,
29
+ } }
30
+ className={ classnames(
31
+ this.props.className,
32
+ `align${ctaWidth}`,
33
+ 'ab-block-cta',
34
+ 'ab-font-size-' + ctaTextFontSize,
35
+ ) }
36
+ >{ this.props.children }</div>
37
+ );
38
+ }
39
+ }
src/blocks/block-cta/components/inspector.js ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ MediaUpload,
15
+ } = wp.editor;
16
+
17
+ // Import Inspector components
18
+ const {
19
+ Toolbar,
20
+ Button,
21
+ PanelBody,
22
+ PanelRow,
23
+ PanelColor,
24
+ FormToggle,
25
+ RangeControl,
26
+ SelectControl,
27
+ ToggleControl,
28
+ IconButton,
29
+ } = wp.components;
30
+
31
+ /**
32
+ * Create an Inspector Controls wrapper Component
33
+ */
34
+ export default class Inspector extends Component {
35
+
36
+ constructor( props ) {
37
+ super( ...arguments );
38
+ }
39
+
40
+ render() {
41
+
42
+ // Setup the attributes
43
+ const { buttonText, buttonUrl, buttonAlignment, buttonBackgroundColor, buttonTextColor, buttonSize, buttonShape, buttonTarget, ctaTitle, ctaText, ctaTitleFontSize, ctaTextFontSize, ctaBackgroundColor, ctaTextColor, dimRatio, imgURL, imgID, imgAlt } = this.props.attributes;
44
+ const { setAttributes } = this.props;
45
+
46
+ // Button size values
47
+ const buttonSizeOptions = [
48
+ { value: 'ab-button-size-small', label: __( 'Small' ) },
49
+ { value: 'ab-button-size-medium', label: __( 'Medium' ) },
50
+ { value: 'ab-button-size-large', label: __( 'Large' ) },
51
+ { value: 'ab-button-size-extralarge', label: __( 'Extra Large' ) },
52
+ ];
53
+
54
+ // Button shape
55
+ const buttonShapeOptions = [
56
+ { value: 'ab-button-shape-square', label: __( 'Square' ) },
57
+ { value: 'ab-button-shape-rounded', label: __( 'Rounded Square' ) },
58
+ { value: 'ab-button-shape-circular', label: __( 'Circular' ) },
59
+ ];
60
+
61
+ const onSelectImage = img => {
62
+ setAttributes( {
63
+ imgID: img.id,
64
+ imgURL: img.url,
65
+ imgAlt: img.alt,
66
+ } );
67
+ };
68
+
69
+ const onRemoveImage = () => {
70
+ setAttributes({
71
+ imgID: null,
72
+ imgURL: null,
73
+ imgAlt: null,
74
+ });
75
+ }
76
+
77
+ return (
78
+ <InspectorControls key="inspector">
79
+ <PanelBody title={ __( 'Text Options' ) } initialOpen={ true }>
80
+ <RangeControl
81
+ label={ __( 'Title Font Size' ) }
82
+ value={ ctaTitleFontSize }
83
+ onChange={ ( value ) => this.props.setAttributes( { ctaTitleFontSize: value } ) }
84
+ min={ 24 }
85
+ max={ 60 }
86
+ step={ 2 }
87
+ />
88
+
89
+ <RangeControl
90
+ label={ __( 'Text Font Size' ) }
91
+ value={ ctaTextFontSize }
92
+ onChange={ ( value ) => this.props.setAttributes( { ctaTextFontSize: value } ) }
93
+ min={ 14 }
94
+ max={ 24 }
95
+ step={ 2 }
96
+ />
97
+
98
+ <PanelColor
99
+ title={ __( 'Text Color' ) }
100
+ colorValue={ ctaTextColor }
101
+ initialOpen={ false }
102
+ >
103
+ <ColorPalette
104
+ label={ __( 'Text Color' ) }
105
+ value={ ctaTextColor }
106
+ onChange={ ( value ) => this.props.setAttributes( { ctaTextColor: value } ) }
107
+ />
108
+ </PanelColor>
109
+ </PanelBody>
110
+
111
+ <PanelBody title={ __( 'Background Options' ) } initialOpen={ false }>
112
+ <p>{ __( 'Select a background image:' ) }</p>
113
+ <MediaUpload
114
+ onSelect={ onSelectImage }
115
+ type="image"
116
+ value={ imgID }
117
+ render={ ( { open } ) => (
118
+ <div>
119
+ <IconButton
120
+ className="ab-cta-inspector-media"
121
+ label={ __( 'Edit image' ) }
122
+ icon="format-image"
123
+ onClick={ open }
124
+ >
125
+ { __( 'Select Image' ) }
126
+ </IconButton>
127
+
128
+ { imgURL && !! imgURL.length && (
129
+ <IconButton
130
+ className="ab-cta-inspector-media"
131
+ label={ __( 'Remove Image' ) }
132
+ icon="dismiss"
133
+ onClick={ onRemoveImage }
134
+ >
135
+ { __( 'Remove' ) }
136
+ </IconButton>
137
+ ) }
138
+ </div>
139
+ ) }
140
+ >
141
+ </MediaUpload>
142
+
143
+ { imgURL && !! imgURL.length && (
144
+ <RangeControl
145
+ label={ __( 'Image Opacity' ) }
146
+ value={ dimRatio }
147
+ onChange={ ( value ) => this.props.setAttributes( { dimRatio: value } ) }
148
+ min={ 0 }
149
+ max={ 100 }
150
+ step={ 10 }
151
+ />
152
+ ) }
153
+
154
+ <PanelColor
155
+ title={ __( 'Background Color' ) }
156
+ colorValue={ ctaBackgroundColor }
157
+ initialOpen={ false }
158
+ >
159
+ <ColorPalette
160
+ label={ __( 'Background Color' ) }
161
+ value={ ctaBackgroundColor }
162
+ onChange={ ( value ) => this.props.setAttributes( { ctaBackgroundColor: value } ) }
163
+ />
164
+ </PanelColor>
165
+ </PanelBody>
166
+
167
+ <PanelBody title={ __( 'Button Options' ) } initialOpen={ false }>
168
+ <ToggleControl
169
+ label={ __( 'Open link in new window' ) }
170
+ checked={ buttonTarget }
171
+ onChange={ () => this.props.setAttributes( { buttonTarget: ! buttonTarget } ) }
172
+ />
173
+
174
+ <SelectControl
175
+ label={ __( 'Button Size' ) }
176
+ value={ buttonSize }
177
+ options={ buttonSizeOptions.map( ({ value, label }) => ( {
178
+ value: value,
179
+ label: label,
180
+ } ) ) }
181
+ onChange={ ( value ) => { this.props.setAttributes( { buttonSize: value } ) } }
182
+ />
183
+
184
+ <SelectControl
185
+ label={ __( 'Button Shape' ) }
186
+ value={ buttonShape }
187
+ options={ buttonShapeOptions.map( ({ value, label }) => ( {
188
+ value: value,
189
+ label: label,
190
+ } ) ) }
191
+ onChange={ ( value ) => { this.props.setAttributes( { buttonShape: value } ) } }
192
+ />
193
+
194
+ <PanelColor
195
+ title={ __( 'Button Color' ) }
196
+ colorValue={ buttonBackgroundColor }
197
+ initialOpen={ false }
198
+ >
199
+ <ColorPalette
200
+ label={ __( 'Button Color' ) }
201
+ value={ buttonBackgroundColor }
202
+ onChange={ ( value ) => { this.props.setAttributes( { buttonBackgroundColor: value } ) } }
203
+ colors={[
204
+ { color: '#392F43', name: 'black' },
205
+ { color: '#3373dc', name: 'royal blue' },
206
+ { color: '#2DBAA3', name: 'teal' },
207
+ { color: '#209cef', name: 'sky blue' },
208
+ { color: '#2BAD59', name: 'green' },
209
+ { color: '#ff3860', name: 'pink' },
210
+ { color: '#7941b6', name: 'purple' },
211
+ { color: '#F7812B', name: 'orange' },
212
+ ]}
213
+ />
214
+ </PanelColor>
215
+
216
+ <PanelColor
217
+ title={ __( 'Button Text Color' ) }
218
+ colorValue={ buttonTextColor }
219
+ initialOpen={ false }
220
+ >
221
+ <ColorPalette
222
+ label={ __( 'Button Text Color' ) }
223
+ value={ buttonTextColor }
224
+ onChange={ ( value ) => { this.props.setAttributes( { buttonTextColor: value } ) } }
225
+ colors={[
226
+ { color: '#32373c', name: 'black' },
227
+ { color: '#fff', name: 'white' },
228
+ ]}
229
+ />
230
+ </PanelColor>
231
+ </PanelBody>
232
+ </InspectorControls>
233
+ );
234
+ }
235
+ }
src/blocks/block-cta/index.js ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Call-To-Action
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import CallToAction from './components/cta';
9
+
10
+ // Import CSS
11
+ import './styles/style.scss';
12
+ import './styles/editor.scss';
13
+
14
+ // Components
15
+ const { __ } = wp.i18n;
16
+
17
+ // Extend component
18
+ const { Component } = wp.element;
19
+
20
+ // Register block
21
+ const { registerBlockType } = wp.blocks;
22
+
23
+ // Register editor components
24
+ const {
25
+ AlignmentToolbar,
26
+ URLInput,
27
+ BlockControls,
28
+ BlockAlignmentToolbar,
29
+ MediaUpload,
30
+ RichText,
31
+ } = wp.editor;
32
+
33
+ // Register components
34
+ const {
35
+ Button,
36
+ withFallbackStyles,
37
+ IconButton,
38
+ Dashicon,
39
+ Toolbar,
40
+ } = wp.components;
41
+
42
+ const blockAttributes = {
43
+ buttonText: {
44
+ type: 'string',
45
+ },
46
+ buttonUrl: {
47
+ type: 'string',
48
+ source: 'attribute',
49
+ selector: 'a',
50
+ attribute: 'href',
51
+ },
52
+ buttonAlignment: {
53
+ type: 'string',
54
+ default: 'center'
55
+ },
56
+ buttonBackgroundColor: {
57
+ type: 'string',
58
+ default: '#3373dc'
59
+ },
60
+ buttonTextColor: {
61
+ type: 'string',
62
+ default: '#ffffff'
63
+ },
64
+ buttonSize: {
65
+ type: 'string',
66
+ default: 'ab-button-size-medium'
67
+ },
68
+ buttonShape: {
69
+ type: 'string',
70
+ default: 'ab-button-shape-rounded'
71
+ },
72
+ buttonTarget: {
73
+ type: 'boolean',
74
+ default: false
75
+ },
76
+ ctaTitle: {
77
+ type: 'string',
78
+ },
79
+ ctaTitleFontSize: {
80
+ type: 'string',
81
+ default: '32'
82
+ },
83
+ ctaTextFontSize: {
84
+ type: 'string',
85
+ default: '20'
86
+ },
87
+ ctaText: {
88
+ type: 'array',
89
+ selector: '.ab-cta-text',
90
+ source: 'children',
91
+ },
92
+ ctaWidth: {
93
+ type: 'string',
94
+ default: 'center',
95
+ },
96
+ ctaBackgroundColor: {
97
+ type: 'string',
98
+ default: '#f2f2f2'
99
+ },
100
+ ctaTextColor: {
101
+ type: 'string',
102
+ default: '#32373c'
103
+ },
104
+ imgURL: {
105
+ type: 'string',
106
+ source: 'attribute',
107
+ attribute: 'src',
108
+ selector: 'img',
109
+ },
110
+ imgID: {
111
+ type: 'number',
112
+ },
113
+ imgAlt: {
114
+ type: 'string',
115
+ source: 'attribute',
116
+ attribute: 'alt',
117
+ selector: 'img',
118
+ },
119
+ dimRatio: {
120
+ type: 'number',
121
+ default: 50,
122
+ },
123
+ };
124
+
125
+ class ABCTABlock extends Component {
126
+
127
+ render() {
128
+
129
+ // Setup the attributes
130
+ const {
131
+ attributes: {
132
+ buttonText,
133
+ buttonUrl,
134
+ buttonAlignment,
135
+ buttonBackgroundColor,
136
+ buttonTextColor,
137
+ buttonSize,
138
+ buttonShape,
139
+ buttonTarget,
140
+ ctaTitle,
141
+ ctaText,
142
+ ctaTitleFontSize,
143
+ ctaTextFontSize,
144
+ ctaWidth,
145
+ ctaBackgroundColor,
146
+ ctaTextColor,
147
+ imgURL,
148
+ imgID,
149
+ imgAlt,
150
+ dimRatio,
151
+ },
152
+ attributes,
153
+ isSelected,
154
+ editable,
155
+ className,
156
+ setAttributes
157
+ } = this.props;
158
+
159
+ const onSelectImage = img => {
160
+ setAttributes( {
161
+ imgID: img.id,
162
+ imgURL: img.url,
163
+ imgAlt: img.alt,
164
+ } );
165
+ };
166
+
167
+ return [
168
+ // Show the alignment toolbar on focus
169
+ <BlockControls>
170
+ <BlockAlignmentToolbar
171
+ value={ ctaWidth }
172
+ onChange={ ctaWidth => setAttributes( { ctaWidth } ) }
173
+ controls={ [ 'center', 'wide', 'full' ] }
174
+ />
175
+ <AlignmentToolbar
176
+ value={ buttonAlignment }
177
+ onChange={ ( value ) => {
178
+ setAttributes( { buttonAlignment: value } );
179
+ } }
180
+ />
181
+ </BlockControls>,
182
+ // Show the block controls on focus
183
+ <Inspector
184
+ { ...{ setAttributes, ...this.props } }
185
+ />,
186
+ // Show the button markup in the editor
187
+ <CallToAction { ...this.props }>
188
+ { imgURL && !! imgURL.length && (
189
+ <div class="ab-cta-image-wrap">
190
+ <img
191
+ className={ classnames(
192
+ 'ab-cta-image',
193
+ dimRatioToClass( dimRatio ),
194
+ {
195
+ 'has-background-dim': dimRatio !== 0,
196
+ }
197
+ ) }
198
+ src={ imgURL }
199
+ alt={ imgAlt }
200
+ />
201
+ </div>
202
+ ) }
203
+
204
+ <div class="ab-cta-content">
205
+ <RichText
206
+ tagName="h2"
207
+ placeholder={ __( 'Call-To-Action Title' ) }
208
+ keepPlaceholderOnFocus
209
+ value={ ctaTitle }
210
+ className={ classnames(
211
+ 'ab-cta-title',
212
+ 'ab-font-size-' + ctaTitleFontSize,
213
+ ) }
214
+ style={ {
215
+ color: ctaTextColor,
216
+ } }
217
+ onChange={ (value) => setAttributes( { ctaTitle: value } ) }
218
+ />
219
+ <RichText
220
+ tagName="div"
221
+ multiline="p"
222
+ placeholder={ __( 'Call To Action Text' ) }
223
+ keepPlaceholderOnFocus
224
+ value={ ctaText }
225
+ className={ classnames(
226
+ 'ab-cta-text',
227
+ 'ab-font-size-' + ctaTextFontSize,
228
+ ) }
229
+ style={ {
230
+ color: ctaTextColor,
231
+ } }
232
+ onChange={ ( value ) => setAttributes( { ctaText: value } ) }
233
+ inlineToolbar
234
+ />
235
+ </div>
236
+ <div class="ab-cta-button">
237
+ <RichText
238
+ tagName="span"
239
+ placeholder={ __( 'Button text...' ) }
240
+ keepPlaceholderOnFocus
241
+ value={ buttonText }
242
+ formattingControls={ [] }
243
+ className={ classnames(
244
+ 'ab-button',
245
+ buttonShape,
246
+ buttonSize,
247
+ ) }
248
+ style={ {
249
+ color: buttonTextColor,
250
+ backgroundColor: buttonBackgroundColor,
251
+ } }
252
+ onChange={ (value) => setAttributes( { buttonText: value } ) }
253
+ />
254
+ { isSelected && (
255
+ <form
256
+ key="form-link"
257
+ className={ `blocks-button__inline-link ab-button-${buttonAlignment}`}
258
+ onSubmit={ event => event.preventDefault() }
259
+ style={ {
260
+ textAlign: buttonAlignment,
261
+ } }
262
+ >
263
+ <Dashicon icon={ 'admin-links' } />
264
+ <URLInput
265
+ className="button-url"
266
+ value={ buttonUrl }
267
+ onChange={ ( value ) => setAttributes( { buttonUrl: value } ) }
268
+ />
269
+ <IconButton
270
+ icon="editor-break"
271
+ label={ __( 'Apply' ) }
272
+ type="submit"
273
+ />
274
+ </form>
275
+ ) }
276
+ </div>
277
+ </CallToAction>
278
+ ];
279
+ }
280
+ }
281
+
282
+ // Register the block
283
+ registerBlockType( 'atomic-blocks/ab-cta', {
284
+ title: __( 'AB Call To Action' ),
285
+ description: __( 'Add a call to action section with a title, text, and a button.' ),
286
+ icon: 'megaphone',
287
+ category: 'atomic-blocks',
288
+ keywords: [
289
+ __( 'call to action' ),
290
+ __( 'cta' ),
291
+ __( 'atomic' ),
292
+ ],
293
+
294
+ attributes: blockAttributes,
295
+
296
+ getEditWrapperProps( { ctaWidth } ) {
297
+ if ( 'left' === ctaWidth || 'right' === ctaWidth || 'full' === ctaWidth ) {
298
+ return { 'data-align': ctaWidth };
299
+ }
300
+ },
301
+
302
+ // Render the block components
303
+ edit: ABCTABlock,
304
+
305
+ // Save the attributes and markup
306
+ save: function( props ) {
307
+
308
+ // Setup the attributes
309
+ const {
310
+ buttonText,
311
+ buttonUrl,
312
+ buttonAlignment,
313
+ buttonBackgroundColor,
314
+ buttonTextColor,
315
+ buttonSize,
316
+ buttonShape,
317
+ buttonTarget,
318
+ ctaTitle,
319
+ ctaText,
320
+ ctaTitleFontSize,
321
+ ctaTextFontSize,
322
+ ctaWidth,
323
+ ctaBackgroundColor,
324
+ ctaTextColor,
325
+ imgURL,
326
+ imgID,
327
+ imgAlt,
328
+ dimRatio,
329
+ } = props.attributes;
330
+
331
+ // Save the block markup for the front end
332
+ return (
333
+ <CallToAction { ...props }>
334
+ { imgURL && !! imgURL.length && (
335
+ <div class="ab-cta-image-wrap">
336
+ <img
337
+ className={ classnames(
338
+ 'ab-cta-image',
339
+ dimRatioToClass( dimRatio ),
340
+ {
341
+ 'has-background-dim': dimRatio !== 0,
342
+ }
343
+ ) }
344
+ src={ imgURL }
345
+ alt={ imgAlt }
346
+ />
347
+ </div>
348
+ ) }
349
+
350
+ <div class="ab-cta-content">
351
+ { ctaTitle && !! ctaTitle.length && (
352
+ <h2
353
+ className={ classnames(
354
+ 'ab-cta-title',
355
+ 'ab-font-size-' + ctaTitleFontSize,
356
+ ) }
357
+ style={ {
358
+ color: ctaTextColor,
359
+ } }
360
+ >{ ctaTitle }</h2>
361
+ ) }
362
+ { ctaText && !! ctaText.length && (
363
+ <div
364
+ className={ classnames(
365
+ 'ab-cta-text',
366
+ 'ab-font-size-' + ctaTextFontSize,
367
+ ) }
368
+ style={ {
369
+ color: ctaTextColor,
370
+ } }
371
+ >{ ctaText }</div>
372
+ ) }
373
+ </div>
374
+ { buttonText && !! buttonText.length && (
375
+ <div class="ab-cta-button">
376
+ <a
377
+ href={ buttonUrl }
378
+ target={ buttonTarget ? '_blank' : '_self' }
379
+ className={ classnames(
380
+ 'ab-button',
381
+ buttonShape,
382
+ buttonSize,
383
+ ) }
384
+ style={ {
385
+ color: buttonTextColor,
386
+ backgroundColor: buttonBackgroundColor,
387
+ } }
388
+ >{ buttonText }</a>
389
+ </div>
390
+ ) }
391
+ </CallToAction>
392
+ );
393
+ },
394
+ } );
395
+
396
+ function dimRatioToClass( ratio ) {
397
+ return ( ratio === 0 || ratio === 50 ) ?
398
+ null :
399
+ 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) );
400
+ }
401
+
402
+ function backgroundImageStyles( url ) {
403
+ return url ?
404
+ { backgroundImage: `url(${ url })` } :
405
+ undefined;
406
+ }
src/blocks/block-cta/styles/editor.scss ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-cta {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-cta"] {
10
+ margin-bottom: 1.2em;
11
+ }
12
+
13
+ .ab-cta-button {
14
+ .blocks-button__inline-link .editor-url-input {
15
+ display: inline-block;
16
+ width: auto;
17
+ }
18
+
19
+ .blocks-button__inline-link .components-button {
20
+ display: inline-block;
21
+ }
22
+
23
+ .blocks-button__inline-link svg {
24
+ vertical-align: middle;
25
+ margin-right: 8px;
26
+ }
27
+ }
28
+
29
+ .ab-block-cta h2.editor-rich-text__tinymce.mce-content-body {
30
+ line-height: 1.2;
31
+ }
src/blocks/block-cta/styles/style.scss ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * CTA styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-cta {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+ background: #f2f2f2;
10
+ color: #293038;
11
+ padding: 5% 3%;
12
+ border-radius: 5px;
13
+
14
+ .components-autocomplete {
15
+ display: inline-block;
16
+ width: auto;
17
+ margin: 0 auto;
18
+ position: relative;
19
+ }
20
+
21
+ * {
22
+ z-index: 10;
23
+ position: relative;
24
+ }
25
+
26
+ &.alignfull {
27
+ border-radius: 0;
28
+ padding: 8% 3%;
29
+ }
30
+
31
+ @media only screen and (max-width: 768px) {
32
+ padding: 8% 6%;
33
+ }
34
+
35
+ &.alignfull .ab-cta-content,
36
+ &.alignfull .ab-cta-button {
37
+ @media only screen and (min-width: 768px) {
38
+ max-width: 60%;
39
+ margin: 0 auto;
40
+ }
41
+ }
42
+
43
+ &.alignwide .ab-cta-content,
44
+ &.alignwide .ab-cta-button {
45
+ @media only screen and (min-width: 768px) {
46
+ max-width: 80%;
47
+ margin: 0 auto;
48
+ }
49
+ }
50
+
51
+ .ab-cta-image-wrap {
52
+ position: absolute;
53
+ top: 0;
54
+ right: 0;
55
+ left: 0;
56
+ height: 100%;
57
+ z-index: 1;
58
+ }
59
+
60
+ .ab-cta-image {
61
+ object-fit: cover;
62
+ height: 100%;
63
+ width: 100%;
64
+ transition: .3s ease;
65
+ }
66
+
67
+ .ab-cta-title {
68
+ display: inline-block;
69
+ width: 100%;
70
+ margin-bottom: .3em;
71
+ line-height: 1.2;
72
+ }
73
+
74
+ .ab-cta-text {
75
+ line-height: 1.4;
76
+
77
+ a {
78
+ color: inherit;
79
+ box-shadow: 0 -1px 0 inset;
80
+ text-decoration: none;
81
+
82
+ &:hover {
83
+ color: inherit;
84
+ box-shadow: 0 -2px 0 inset;
85
+ }
86
+ }
87
+ }
88
+
89
+ .ab-cta-button .blocks-rich-text {
90
+ display: inline-flex;
91
+ }
92
+
93
+ .ab-button {
94
+ text-align: center;
95
+ font-size: 18px;
96
+ line-height: 1 !important;
97
+ background-color: #32373c;
98
+ border: none;
99
+ border-radius: 50px;
100
+ box-shadow: none;
101
+ color: #fff;
102
+ cursor: pointer;
103
+ padding: .6em 1em;
104
+ text-decoration: none;
105
+ word-break: break-word;
106
+ transition: .3s ease;
107
+ display: inline-block;
108
+
109
+ &:hover {
110
+ box-shadow: inset 0 0 200px rgba(255, 255, 255, 0.15);
111
+ }
112
+ }
113
+
114
+ .ab-cta-title:empty,
115
+ .ab-cta-text:empty,
116
+ .ab-cta-button:empty,
117
+ .ab-button:empty {
118
+ display: none;
119
+ }
120
+
121
+ .ab-button-shape-square {
122
+ border-radius: 0;
123
+ }
124
+
125
+ .ab-button-shape-rounded {
126
+ border-radius: 5px;
127
+ }
128
+
129
+ .ab-button-shape-circular {
130
+ border-radius: 100px;
131
+ }
132
+
133
+ .ab-button-size-small {
134
+ font-size: 14px;
135
+ }
136
+
137
+ .ab-button-size-medium {
138
+ font-size: 20px;
139
+ }
140
+
141
+ .ab-button-size-large {
142
+ font-size: 26px;
143
+ padding: .8em 1.2em;
144
+ }
145
+
146
+ .ab-button-size-extralarge {
147
+ font-size: 32px;
148
+ padding: .8em 1.2em;
149
+ }
150
+
151
+ .ab-font-size-24.ab-cta-title {
152
+ font-size: 24px;
153
+ }
154
+
155
+ .ab-font-size-26.ab-cta-title {
156
+ font-size: 26px;
157
+ }
158
+
159
+ .ab-font-size-28.ab-cta-title {
160
+ font-size: 28px;
161
+ }
162
+
163
+ .ab-font-size-30.ab-cta-title {
164
+ font-size: 30px;
165
+ }
166
+
167
+ .ab-font-size-32.ab-cta-title {
168
+ font-size: 32px;
169
+ }
170
+
171
+ .ab-font-size-34.ab-cta-title {
172
+ font-size: 34px;
173
+ }
174
+
175
+ .ab-font-size-36.ab-cta-title {
176
+ font-size: 36px;
177
+ }
178
+
179
+ .ab-font-size-38.ab-cta-title {
180
+ font-size: 38px;
181
+ }
182
+
183
+ .ab-font-size-40.ab-cta-title {
184
+ font-size: 40px;
185
+ }
186
+
187
+ .ab-font-size-42.ab-cta-title {
188
+ font-size: 42px;
189
+ }
190
+
191
+ .ab-font-size-44.ab-cta-title {
192
+ font-size: 44px;
193
+ }
194
+
195
+ .ab-font-size-46.ab-cta-title {
196
+ font-size: 46px;
197
+ }
198
+
199
+ .ab-font-size-48.ab-cta-title {
200
+ font-size: 48px;
201
+ }
202
+
203
+ .ab-font-size-50.ab-cta-title {
204
+ font-size: 50px;
205
+ }
206
+
207
+ .ab-font-size-52.ab-cta-title {
208
+ font-size: 52px;
209
+ }
210
+
211
+ .ab-font-size-54.ab-cta-title {
212
+ font-size: 54px;
213
+ }
214
+
215
+ .ab-font-size-56.ab-cta-title {
216
+ font-size: 56px;
217
+ }
218
+
219
+ .ab-font-size-58.ab-cta-title {
220
+ font-size: 58px;
221
+ }
222
+
223
+ .ab-font-size-60.ab-cta-title {
224
+ font-size: 60px;
225
+ }
226
+
227
+ .blocks-button__inline-link {
228
+ margin-top: 15px;
229
+ }
230
+
231
+ .ab-cta-image:not(.has-background-dim) {
232
+ opacity: 0;
233
+ }
234
+
235
+ .has-background-dim {
236
+ opacity: .5;
237
+ }
238
+
239
+ .has-background-dim-10 {
240
+ opacity: .1;
241
+ }
242
+
243
+ .has-background-dim-20 {
244
+ opacity: .2;
245
+ }
246
+
247
+ .has-background-dim-30 {
248
+ opacity: .3;
249
+ }
250
+
251
+ .has-background-dim-40 {
252
+ opacity: .4;
253
+ }
254
+
255
+ .has-background-dim-50 {
256
+ opacity: .5;
257
+ }
258
+
259
+ .has-background-dim-60 {
260
+ opacity: .6;
261
+ }
262
+
263
+ .has-background-dim-70 {
264
+ opacity: .7;
265
+ }
266
+
267
+ .has-background-dim-80 {
268
+ opacity: .8;
269
+ }
270
+
271
+ .has-background-dim-90 {
272
+ opacity: .9;
273
+ }
274
+
275
+ .has-background-dim-100 {
276
+ opacity: 1;
277
+ }
278
+ }
279
+
280
+ .ab-button-right {
281
+ transform: translateX(-100%);
282
+ left: 100%;
283
+ position: relative;
284
+ }
285
+
286
+ .ab-button-center {
287
+ margin: 0 auto;
288
+ }
289
+
290
+ .ab-cta-inspector-media.components-button {
291
+ vertical-align: top;
292
+ border: 1px solid #e2e4e7;
293
+ background-color: #fff;
294
+ display: inline-flex;
295
+ border-radius: 3px;
296
+ margin-bottom: 15px;
297
+
298
+ &:hover {
299
+ box-shadow: none !important;
300
+ border: solid 1px #555d66;
301
+ }
302
+
303
+ &:first-child {
304
+ margin-right: 8px;
305
+ }
306
+
307
+ svg {
308
+ margin-right: 5px;
309
+ }
310
+ }
src/blocks/block-drop-cap/components/dropcap.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Testimonial Block Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a drop cap wrapper Component
13
+ */
14
+ export default class DropCap extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+
22
+ // Setup the attributes
23
+ const { dropCapAlignment, dropCapTextColor, dropCapFontSize, dropCapStyle } = this.props.attributes;
24
+
25
+ return (
26
+ <div
27
+ style={ {
28
+ color: dropCapTextColor,
29
+ textAlign: dropCapAlignment,
30
+ } }
31
+ className={ classnames(
32
+ this.props.className,
33
+ dropCapStyle,
34
+ 'ab-font-size-' + dropCapFontSize,
35
+ 'ab-block-drop-cap',
36
+ ) }
37
+ >
38
+ { this.props.children }
39
+ </div>
40
+ );
41
+ }
42
+ }
src/blocks/block-drop-cap/components/icons.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ export default icons;
src/blocks/block-drop-cap/components/inspector.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ RangeControl,
21
+ SelectControl,
22
+ PanelBody,
23
+ } = wp.components;
24
+
25
+ /**
26
+ * Create an Inspector Controls wrapper Component
27
+ */
28
+ export default class Inspector extends Component {
29
+
30
+ constructor( props ) {
31
+ super( ...arguments );
32
+ }
33
+
34
+ render() {
35
+
36
+ // Setup the attributes
37
+ const { dropCapFontSize, dropCapStyle } = this.props.attributes;
38
+
39
+ // Drop cap style options
40
+ const dropCapOptions = [
41
+ { value: 'ab-drop-cap-letter', label: __( 'Letter' ) },
42
+ { value: 'ab-drop-cap-square', label: __( 'Square' ) },
43
+ { value: 'ab-drop-cap-border', label: __( 'Border' ) },
44
+ ];
45
+
46
+ return (
47
+ <InspectorControls key="inspector">
48
+ <PanelBody>
49
+ <RangeControl
50
+ label={ __( 'Drop Cap Size' ) }
51
+ value={ dropCapFontSize }
52
+ onChange={ ( value ) => this.props.setAttributes( { dropCapFontSize: value } ) }
53
+ min={ 1 }
54
+ max={ 6 }
55
+ step={ 1 }
56
+ />
57
+
58
+ <SelectControl
59
+ label={ __( 'Drop Cap Style' ) }
60
+ description={ __( 'Choose the style of the drop cap in your paragraph.' ) }
61
+ options={ dropCapOptions }
62
+ value={ dropCapStyle }
63
+ onChange={ ( value ) => this.props.setAttributes( { dropCapStyle: value } ) }
64
+ />
65
+ </PanelBody>
66
+ </InspectorControls>
67
+ );
68
+ }
69
+ }
src/blocks/block-drop-cap/index.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Drop Cap
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import DropCap from './components/dropcap';
9
+ import icons from './components/icons';
10
+
11
+ // Import CSS
12
+ import './styles/style.scss';
13
+ import './styles/editor.scss';
14
+
15
+ // Internationalization
16
+ const { __ } = wp.i18n;
17
+
18
+ // Extend component
19
+ const { Component } = wp.element;
20
+
21
+ // Register block
22
+ const { registerBlockType } = wp.blocks;
23
+
24
+ // Register editor components
25
+ const {
26
+ RichText,
27
+ AlignmentToolbar,
28
+ BlockControls,
29
+ BlockAlignmentToolbar,
30
+ MediaUpload,
31
+ } = wp.editor;
32
+
33
+ // Register components
34
+ const {
35
+ Button,
36
+ SelectControl,
37
+ } = wp.components;
38
+
39
+ class ABDropCapBlock extends Component {
40
+
41
+ render() {
42
+
43
+ // Setup the attributes
44
+ const { attributes: { dropCapContent, dropCapAlignment, dropCapBackgroundColor, dropCapTextColor, dropCapFontSize, dropCapStyle }, isSelected, className, setAttributes } = this.props;
45
+
46
+ return [
47
+ // Show the alignment toolbar on focus
48
+ <BlockControls key="controls">
49
+ <AlignmentToolbar
50
+ value={ dropCapAlignment }
51
+ onChange={ ( value ) => this.props.setAttributes( { dropCapAlignment: value } ) }
52
+ />
53
+ </BlockControls>,
54
+ // Show the block controls on focus
55
+ <Inspector
56
+ { ...this.props }
57
+ />,
58
+ // Show the block markup in the editor
59
+ <DropCap { ...this.props }>
60
+ <RichText
61
+ tagName="div"
62
+ multiline="p"
63
+ placeholder={ __( 'Add paragraph text...' ) }
64
+ keepPlaceholderOnFocus
65
+ value={ dropCapContent }
66
+ formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link' ] }
67
+ className={ classnames(
68
+ 'ab-drop-cap-text',
69
+ 'ab-font-size-' + dropCapFontSize,
70
+ ) }
71
+ onChange={ ( value ) => this.props.setAttributes( { dropCapContent: value } ) }
72
+ />
73
+ </DropCap>
74
+ ];
75
+ }
76
+ }
77
+
78
+ // Register the block
79
+ registerBlockType( 'atomic-blocks/ab-drop-cap', {
80
+ title: __( 'AB Drop Cap' ),
81
+ description: __( 'Add a styled drop cap to the beginning of your paragraph.' ),
82
+ icon: 'format-quote',
83
+ category: 'atomic-blocks',
84
+ keywords: [
85
+ __( 'drop cap' ),
86
+ __( 'quote' ),
87
+ __( 'atomic' ),
88
+ ],
89
+ attributes: {
90
+ dropCapContent: {
91
+ type: 'array',
92
+ selector: '.ab-drop-cap-text',
93
+ source: 'children',
94
+ },
95
+ dropCapAlignment: {
96
+ type: 'string',
97
+ },
98
+ dropCapBackgroundColor: {
99
+ type: 'string',
100
+ default: '#f2f2f2'
101
+ },
102
+ dropCapTextColor: {
103
+ type: 'string',
104
+ default: '#32373c'
105
+ },
106
+ dropCapFontSize: {
107
+ type: 'number',
108
+ default: 3,
109
+ },
110
+ dropCapStyle: {
111
+ type: 'string',
112
+ default: 'drop-cap-letter',
113
+ },
114
+ },
115
+
116
+ // Render the block components
117
+ edit: ABDropCapBlock,
118
+
119
+ // Save the attributes and markup
120
+ save: function( props ) {
121
+
122
+ const { dropCapContent, dropCapAlignment, dropCapBackgroundColor, dropCapTextColor, dropCapFontSize, dropCapStyle } = props.attributes;
123
+
124
+ // Save the block markup for the front end
125
+ return (
126
+ <DropCap { ...props }>
127
+ { // Check if there is text and output
128
+ dropCapContent && (
129
+ <div
130
+ className={ classnames(
131
+ 'ab-drop-cap-text'
132
+ ) }
133
+ >
134
+ { dropCapContent }
135
+ </div>
136
+ ) }
137
+ </DropCap>
138
+ );
139
+ },
140
+ } );
src/blocks/block-drop-cap/styles/editor.scss ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-drop-cap {
6
+ .ab-drop-cap-text:not( :focus ):first-letter {
7
+ float: left;
8
+ font-size: 75px;
9
+ line-height: .7em;
10
+ margin-top: .15em;
11
+ margin-right: 25px;
12
+ margin-bottom: 25px;
13
+ font-weight: bold;
14
+ text-transform: uppercase;
15
+ }
16
+
17
+ &.ab-drop-cap-square .ab-drop-cap-text:not( :focus ):first-letter {
18
+ background: #32373c;
19
+ color: #fff;
20
+ padding: .2em;
21
+ }
22
+
23
+ &.ab-drop-cap-border .ab-drop-cap-text:not( :focus ):first-letter {
24
+ color: #32373c;
25
+ padding: .2em;
26
+ border: solid 4px;
27
+ }
28
+
29
+ /* Font size styles */
30
+ @media only screen and (min-width: 600px) {
31
+ .ab-font-size-1.ab-drop-cap-text:not( :focus ):first-letter {
32
+ font-size: 75px;
33
+ }
34
+
35
+ .ab-font-size-2.ab-drop-cap-text:not( :focus ):first-letter {
36
+ font-size: 85px;
37
+ }
38
+
39
+ .ab-font-size-3.ab-drop-cap-text:not( :focus ):first-letter {
40
+ font-size: 95px;
41
+ }
42
+
43
+ .ab-font-size-4.ab-drop-cap-text:not( :focus ):first-letter {
44
+ font-size: 105px;
45
+ }
46
+
47
+ .ab-font-size-5.ab-drop-cap-text:not( :focus ):first-letter {
48
+ font-size: 115px;
49
+ }
50
+
51
+ .ab-font-size-6.ab-drop-cap-text:not( :focus ):first-letter {
52
+ font-size: 125px;
53
+ }
54
+ }
55
+ }
src/blocks/block-drop-cap/styles/style.scss ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Drop cap styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .entry-content .ab-block-drop-cap {
7
+ .ab-drop-cap-text p:first-letter {
8
+ float: left;
9
+ font-size: 75px;
10
+ line-height: .7em;
11
+ margin-top: .15em;
12
+ margin-right: 25px;
13
+ margin-bottom: 25px;
14
+ font-weight: bold;
15
+ text-transform: uppercase;
16
+ }
17
+
18
+ &.ab-drop-cap-square .ab-drop-cap-text p:first-letter {
19
+ background: #32373c;
20
+ color: #fff;
21
+ padding: .2em;
22
+ }
23
+
24
+ &.ab-drop-cap-border .ab-drop-cap-text p:first-letter {
25
+ color: #32373c;
26
+ padding: .2em;
27
+ border: solid 4px;
28
+ }
29
+
30
+ /* Font size styles */
31
+ @media only screen and (min-width: 600px) {
32
+ &.ab-font-size-1 .ab-drop-cap-text:first-letter {
33
+ font-size: 75px;
34
+ }
35
+
36
+ &.ab-font-size-2 .ab-drop-cap-text:first-letter {
37
+ font-size: 85px;
38
+ }
39
+
40
+ &.ab-font-size-3 .ab-drop-cap-text:first-letter {
41
+ font-size: 95px;
42
+ }
43
+
44
+ &.ab-font-size-4 .ab-drop-cap-text:first-letter {
45
+ font-size: 105px;
46
+ }
47
+
48
+ &.ab-font-size-5 .ab-drop-cap-text:first-letter {
49
+ font-size: 115px;
50
+ }
51
+
52
+ &.ab-font-size-6 .ab-drop-cap-text p:first-letter {
53
+ font-size: 125px;
54
+ }
55
+ }
56
+ }
57
+
58
+ .ab-block-drop-cap {
59
+ &:before,
60
+ &:after {
61
+ content: '';
62
+ display: table;
63
+ }
64
+
65
+ &:after {
66
+ clear: both;
67
+ }
68
+
69
+ a {
70
+ color: inherit;
71
+ box-shadow: 0 -1px 0 inset;
72
+ text-decoration: none;
73
+
74
+ &:hover {
75
+ color: inherit;
76
+ box-shadow: 0 -2px 0 inset;
77
+ }
78
+ }
79
+ }
src/blocks/block-layout-split/components/icons.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='32px' height='32px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ export default icons;
src/blocks/block-layout-split/components/inspector.js ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Panel,
19
+ PanelBody,
20
+ PanelRow,
21
+ PanelColor,
22
+ RangeControl,
23
+ SelectControl,
24
+ TextControl,
25
+ ToggleControl,
26
+ } = wp.components;
27
+
28
+ // Create an Inspector Controls wrapper Component
29
+ export default class Inspector extends Component {
30
+
31
+ constructor( props ) {
32
+ super( ...arguments );
33
+ }
34
+
35
+ render() {
36
+
37
+ // Setup the attributes
38
+ const { profileName, profileTitle, profileContent, profileAlignment, profileImgURL, profileImgID, profileFontSize, profileBackgroundColor, profileTextColor, profileLinkColor, twitter, facebook, instagram, pinterest, google, youtube, github, email, website, profileAvatarShape, layoutToggle } = this.props.attributes;
39
+
40
+ // Avatar shape options
41
+ const profileAvatarShapeOptions = [
42
+ { value: 'top', label: __( 'Top' ) },
43
+ { value: 'center', label: __( 'Center' ) },
44
+ { value: 'flex', label: __( 'Flex' ) },
45
+ ];
46
+
47
+ return (
48
+ <InspectorControls key="inspector">
49
+ <PanelBody>
50
+ <ToggleControl
51
+ label={ __( 'Reverse Layout' ) }
52
+ checked={ !! layoutToggle }
53
+ onChange={ () => this.props.setAttributes( { layoutToggle: ! layoutToggle } ) }
54
+ />
55
+
56
+ <RangeControl
57
+ label={ __( 'Font Size' ) }
58
+ value={ profileFontSize }
59
+ onChange={ ( value ) => this.props.setAttributes( { profileFontSize: value } ) }
60
+ min={ 14 }
61
+ max={ 24 }
62
+ step={ 1 }
63
+ />
64
+
65
+ <SelectControl
66
+ label={ __( 'Avatar Shape' ) }
67
+ description={ __( 'Choose between a round or square avatar shape.' ) }
68
+ options={ profileAvatarShapeOptions }
69
+ value={ profileAvatarShape }
70
+ onChange={ ( value ) => this.props.setAttributes( { profileAvatarShape: value } ) }
71
+ />
72
+
73
+ <PanelColor
74
+ title={ __( 'Background Color' ) }
75
+ colorValue={ profileBackgroundColor }
76
+ initialOpen={ false }
77
+ >
78
+ <ColorPalette
79
+ label={ __( 'Background Color' ) }
80
+ value={ profileBackgroundColor }
81
+ onChange={ ( value ) => this.props.setAttributes( { profileBackgroundColor: value } ) }
82
+ />
83
+ </PanelColor>
84
+ </PanelBody>
85
+ </InspectorControls>
86
+ );
87
+ }
88
+ }
src/blocks/block-layout-split/components/profile.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Profile Box Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ // Create a profile box wrapper Component
12
+ export default class ProfileBox extends Component {
13
+
14
+ constructor( props ) {
15
+ super( ...arguments );
16
+ }
17
+
18
+ render() {
19
+
20
+ // Setup the attributes
21
+ const { profileAlignment, profileImgURL, profileFontSize, profileBackgroundColor, profileTextColor, profileAvatarShape, layoutToggle } = this.props.attributes;
22
+
23
+ return (
24
+ <div
25
+ style={ {
26
+ backgroundColor: profileBackgroundColor,
27
+ color: profileTextColor,
28
+ } }
29
+ className={ classnames(
30
+ this.props.className,
31
+ profileAlignment,
32
+ layoutToggle && 'ab-layout-split-flip',
33
+ 'ab-layout-split-image-' + profileAvatarShape,
34
+ { 'ab-has-avatar': profileImgURL },
35
+ 'ab-font-size-' + profileFontSize,
36
+ 'ab-layout-split',
37
+ 'ab-profile-columns',
38
+ ) }>
39
+ { this.props.children }
40
+ </div>
41
+ );
42
+ }
43
+ }
src/blocks/block-layout-split/components/social.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Social Media Icons
3
+ */
4
+ const { __ } = wp.i18n;
5
+ const { Component } = wp.element;
6
+
7
+ /**
8
+ * Create an SocialIcons wrapper Component
9
+ */
10
+ export default class SocialIcons extends Component {
11
+
12
+ constructor( props ) {
13
+ super( ...arguments );
14
+ }
15
+
16
+ render() {
17
+ return (
18
+ <ul class="ab-social-links">
19
+ { this.props.attributes.website && !! this.props.attributes.website.length && (
20
+ <li>
21
+ <a href={ this.props.attributes.website } target="_blank">{ __( 'Website' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fas fa-link"></i></a>
22
+ </li>
23
+ ) }
24
+
25
+ { this.props.attributes.twitter && !! this.props.attributes.twitter.length && (
26
+ <li>
27
+ <a href={ this.props.attributes.twitter } target="_blank">{ __( 'Twitter' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-twitter"></i></a>
28
+ </li>
29
+ ) }
30
+
31
+ { this.props.attributes.facebook && !! this.props.attributes.facebook.length && (
32
+ <li>
33
+ <a href={ this.props.attributes.facebook } target="_blank">{ __( 'Facebook' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-facebook-f"></i></a>
34
+ </li>
35
+ ) }
36
+
37
+ { this.props.attributes.instagram && !! this.props.attributes.instagram.length && (
38
+ <li>
39
+ <a href={ this.props.attributes.instagram } target="_blank">{ __( 'Instagram' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-instagram"></i></a>
40
+ </li>
41
+ ) }
42
+
43
+ { this.props.attributes.pinterest && !! this.props.attributes.pinterest.length && (
44
+ <li>
45
+ <a href={ this.props.attributes.pinterest } target="_blank">{ __( 'Pinterest' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-pinterest"></i></a>
46
+ </li>
47
+ ) }
48
+
49
+ { this.props.attributes.google && !! this.props.attributes.google.length && (
50
+ <li>
51
+ <a href={ this.props.attributes.google } target="_blank">{ __( 'Google' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-google"></i></a>
52
+ </li>
53
+ ) }
54
+
55
+ { this.props.attributes.youtube && !! this.props.attributes.youtube.length && (
56
+ <li>
57
+ <a href={ this.props.attributes.youtube } target="_blank">{ __( 'YouTube' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-youtube"></i></a>
58
+ </li>
59
+ ) }
60
+
61
+ { this.props.attributes.github && !! this.props.attributes.github.length && (
62
+ <li>
63
+ <a href={ this.props.attributes.github } target="_blank">{ __( 'Github' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="fab fa-github"></i></a>
64
+ </li>
65
+ ) }
66
+
67
+ { this.props.attributes.email && !! this.props.attributes.email.length && (
68
+ <li>
69
+ <a href={ this.props.attributes.email } target="_blank">{ __( 'Email' ) } <i style={ { backgroundColor:this.props.attributes.profileLinkColor } } class="far fa-envelope"></i></a>
70
+ </li>
71
+ ) }
72
+ </ul>
73
+ );
74
+ }
75
+ }
src/blocks/block-layout-split/index.js ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Profile Box
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import ProfileBox from './components/profile';
9
+ import SocialIcons from './components/social';
10
+ import icons from './components/icons';
11
+
12
+ // Import styles
13
+ import './styles/style.scss';
14
+ import './styles/editor.scss';
15
+
16
+ // Internationalization
17
+ const { __ } = wp.i18n;
18
+
19
+ // Extend component
20
+ const { Component } = wp.element;
21
+
22
+ // Register block
23
+ const { registerBlockType } = wp.blocks;
24
+
25
+ // Register components
26
+ const {
27
+ RichText,
28
+ AlignmentToolbar,
29
+ BlockControls,
30
+ InspectorControls,
31
+ MediaUpload,
32
+ MediaPlaceholder,
33
+ InnerBlocks,
34
+ } = wp.editor;
35
+
36
+ // Register Inspector components
37
+ const {
38
+ Button,
39
+ Toolbar,
40
+ IconButton,
41
+ } = wp.components;
42
+
43
+ const blockAttributes = {
44
+ profileName: {
45
+ type: 'string',
46
+ selector: '.ab-profile-name',
47
+ },
48
+ profileTitle: {
49
+ type: 'string',
50
+ selector: '.ab-profile-title',
51
+ },
52
+ profileContent: {
53
+ type: 'array',
54
+ selector: '.ab-profile-text',
55
+ source: 'children',
56
+ },
57
+ profileAlignment: {
58
+ type: 'string',
59
+ },
60
+ profileImgURL: {
61
+ type: 'string',
62
+ source: 'attribute',
63
+ attribute: 'src',
64
+ selector: 'img',
65
+ },
66
+ profileImgID: {
67
+ type: 'number',
68
+ },
69
+ profileBackgroundColor: {
70
+ type: 'string',
71
+ default: '#f2f2f2'
72
+ },
73
+ profileTextColor: {
74
+ type: 'string',
75
+ default: '#32373c'
76
+ },
77
+ profileLinkColor: {
78
+ type: 'string',
79
+ default: '#392f43'
80
+ },
81
+ profileFontSize: {
82
+ type: 'number',
83
+ default: 18
84
+ },
85
+ profileAvatarShape: {
86
+ type: 'string',
87
+ default: 'top',
88
+ },
89
+ layoutToggle: {
90
+ type: 'boolean',
91
+ defaut: true,
92
+ },
93
+ facebook: {
94
+ type: 'url',
95
+ },
96
+ instagram: {
97
+ type: 'url',
98
+ },
99
+ pinterest: {
100
+ type: 'url',
101
+ },
102
+ google: {
103
+ type: 'url',
104
+ },
105
+ youtube: {
106
+ type: 'url',
107
+ },
108
+ github: {
109
+ type: 'url',
110
+ },
111
+ email: {
112
+ type: 'url',
113
+ },
114
+ website: {
115
+ type: 'url',
116
+ },
117
+ };
118
+
119
+ class ABLayoutSplit extends Component {
120
+
121
+ render() {
122
+
123
+ // Setup the attributes
124
+ const {
125
+ attributes: {
126
+ profileName,
127
+ profileTitle,
128
+ profileContent,
129
+ profileAlignment,
130
+ profileImgURL,
131
+ profileImgID,
132
+ profileFontSize,
133
+ profileBackgroundColor,
134
+ profileTextColor,
135
+ profileLinkColor,
136
+ twitter,
137
+ facebook,
138
+ instagram,
139
+ pinterest,
140
+ google,
141
+ youtube,
142
+ github,
143
+ email,
144
+ website,
145
+ profileAvatarShape,
146
+ layoutToggle
147
+ },
148
+ attributes,
149
+ isSelected,
150
+ editable,
151
+ className,
152
+ setAttributes
153
+ } = this.props;
154
+
155
+ const onSelectImage = img => {
156
+ setAttributes( {
157
+ profileImgID: img.id,
158
+ profileImgURL: img.url,
159
+ } );
160
+ };
161
+
162
+ const onRemoveImage = () => {
163
+ setAttributes({
164
+ profileImgID: null,
165
+ profileImgURL: null,
166
+ });
167
+ }
168
+
169
+ return [
170
+ // Show the block alignment controls on focus
171
+ <BlockControls key="controls">
172
+ <Toolbar>
173
+ <MediaUpload
174
+ onSelect={ onSelectImage }
175
+ type="image"
176
+ value={ profileImgID }
177
+ render={ ( { open } ) => (
178
+ <IconButton
179
+ className="components-toolbar__control"
180
+ label={ __( 'Edit image' ) }
181
+ icon="format-image"
182
+ onClick={ open }
183
+ />
184
+ ) }
185
+ />
186
+ </Toolbar>
187
+
188
+ { profileImgURL && (
189
+ <Toolbar>
190
+ <IconButton
191
+ className="components-toolbar__control"
192
+ label={ __( 'Remove image' ) }
193
+ icon="no"
194
+ onClick={ onRemoveImage }
195
+ />
196
+ </Toolbar>
197
+ ) }
198
+ </BlockControls>,
199
+ // Show the block controls on focus
200
+ <Inspector
201
+ { ...{ setAttributes, ...this.props } }
202
+ />,
203
+ // Show the block markup in the editor
204
+ <ProfileBox { ...this.props }>
205
+ <div class="ab-layout-split-column ab-layout-split-image">
206
+ { profileImgID && <img
207
+ class="profile-avatar"
208
+ src={ profileImgURL }
209
+ alt="avatar"
210
+ /> }
211
+ { ! profileImgURL && (
212
+ <MediaPlaceholder
213
+ className={ classnames(
214
+ 'ab-layout-split-media'
215
+ ) }
216
+ labels={ {
217
+ title: __( 'test' ),
218
+ name: __( 'an image' ),
219
+ } }
220
+ onSelect={ onSelectImage }
221
+ accept="image/*"
222
+ type="image"
223
+ />
224
+ ) }
225
+ </div>
226
+
227
+ <div
228
+ className={ classnames(
229
+ 'ab-layout-split-column ab-layout-split-content'
230
+ ) }
231
+ >
232
+ <InnerBlocks />
233
+ </div>
234
+ </ProfileBox>
235
+ ];
236
+ }
237
+ }
238
+
239
+ // Register the block
240
+ registerBlockType( 'atomic-blocks/ab-layout-split', {
241
+ title: __( 'AB Layout - Split' ),
242
+ description: __( 'Add a profile box with bio info and social media links.' ),
243
+ icon: 'admin-users',
244
+ category: 'atomic-blocks',
245
+ keywords: [
246
+ __( 'author' ),
247
+ __( 'profile' ),
248
+ __( 'atomic' ),
249
+ ],
250
+ // Setup the block attributes
251
+ attributes: blockAttributes,
252
+
253
+ // Render the block components
254
+ edit: ABLayoutSplit,
255
+
256
+ // Save the block markup
257
+ save: function( props ) {
258
+
259
+ // Setup the attributes
260
+ const { profileName, profileTitle, profileContent, profileAlignment, profileImgURL, profileImgID, profileFontSize, profileBackgroundColor, profileTextColor, profileLinkColor, twitter, facebook, instagram, pinterest, google, youtube, github, email, website, profileAvatarShape, layoutToggle } = props.attributes;
261
+
262
+ return (
263
+ // Save the block markup for the front end
264
+ <ProfileBox { ...props }>
265
+
266
+ { profileImgURL && !! profileImgURL.length && (
267
+ <div class="ab-layout-split-column ab-layout-split-image">
268
+ <img
269
+ class="ab-profile-avatar"
270
+ src={ profileImgURL }
271
+ alt="avatar"
272
+ />
273
+ </div>
274
+ ) }
275
+
276
+ <div
277
+ className={ classnames(
278
+ 'ab-layout-split-column ab-layout-split-content'
279
+ ) }
280
+ >
281
+ <InnerBlocks.Content />
282
+ </div>
283
+ </ProfileBox>
284
+ );
285
+ },
286
+ } );
src/blocks/block-layout-split/styles/editor.scss ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-profile {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-profile-box"] {
10
+ margin-bottom: 1.2em;
11
+ }
12
+
13
+ .ab-profile-image-wrap {
14
+ svg {
15
+ pointer-events: none;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ .components-button:not(:disabled):not([aria-disabled=true]):focus {
20
+ background: none;
21
+ box-shadow: none;
22
+ }
23
+ }
24
+
25
+ .ab-profile-content-wrap {
26
+ h2.editor-rich-text__tinymce {
27
+ line-height: 1.2;
28
+ }
29
+
30
+ p.editor-rich-text__tinymce {
31
+ line-height: 1.6;
32
+ }
33
+ }
src/blocks/block-layout-split/styles/style.scss ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Loads on front end and back end
3
+ */
4
+
5
+ .ab-layout-split {
6
+ background: #f2f2f2;
7
+ color: #293038;
8
+ margin: 0 auto;
9
+ padding: 0;
10
+ margin-bottom: 1.2em;
11
+ display: flex;
12
+ flex-flow: row wrap;
13
+ justify-content: space-around;
14
+ align-items: center;
15
+ width: 100%;
16
+
17
+ .ab-layout-split-column {
18
+ flex: 2 0 0;
19
+ }
20
+
21
+ .ab-layout-split-content {
22
+ padding: 5%;
23
+ }
24
+
25
+ .ab-layout-split-image-wrap {
26
+ position: relative;
27
+ }
28
+
29
+ img + .ab-layout-split-media {
30
+ display: none;
31
+ }
32
+
33
+ .ab-layout-split-media {
34
+ height: 100%;
35
+ }
36
+ }
37
+
38
+ .ab-layout-split-image-flex {
39
+ align-items: stretch;
40
+
41
+ img,
42
+ .gutenberg__editor & img {
43
+ object-fit: cover;
44
+ height: 100%;
45
+ width: 100%;
46
+ }
47
+ }
48
+
49
+ .ab-layout-split-image-top {
50
+ align-items: flex-start;
51
+ }
52
+
53
+ .ab-layout-split-image-center {
54
+ align-items: center;
55
+ }
56
+
57
+ .ab-layout-split-flip {
58
+ flex-direction: row-reverse;
59
+ }
src/blocks/block-notice/components/button.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Notice Box Dismiss Button
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Register components
9
+ const {
10
+ Button
11
+ } = wp.components;
12
+
13
+ // Import block dependencies and components
14
+ import classnames from 'classnames';
15
+
16
+ /**
17
+ * Create a button wrapper Component
18
+ */
19
+ export default class DismissButton extends Component {
20
+
21
+ constructor( props ) {
22
+ super( ...arguments );
23
+ }
24
+
25
+ render() {
26
+
27
+ // Setup the attributes
28
+ const { attributes: { noticeTitleColor } } = this.props;
29
+
30
+ return (
31
+ <Button
32
+ className="ab-notice-dismiss"
33
+ style={ {
34
+ fill: noticeTitleColor,
35
+ color: noticeTitleColor,
36
+ } }>
37
+ { this.props.children }
38
+ </Button>
39
+ );
40
+ }
41
+ }
src/blocks/block-notice/components/icons.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ icons.dismiss = <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns='http://www.w3.org/2000/svg' width="20" height="20" viewBox="0 0 20 20">
15
+ <path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zM15 13l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z"></path>
16
+ </svg>;
17
+
18
+ export default icons;
src/blocks/block-notice/components/inspector.js ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ BlockDescription,
12
+ ColorPalette,
13
+ InspectorControls,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ FormToggle,
24
+ RangeControl,
25
+ SelectControl,
26
+ } = wp.components;
27
+
28
+ /**
29
+ * Create an Inspector Controls wrapper Component
30
+ */
31
+ export default class Inspector extends Component {
32
+
33
+ constructor( props ) {
34
+ super( ...arguments );
35
+ }
36
+
37
+ render() {
38
+
39
+ // Notice dismiss options
40
+ const noticeDismissOptions = [
41
+ { value: null, label: __( 'Always Show' ) },
42
+ { value: 'ab-dismissable', label: __( 'Dismissable' ) },
43
+ ];
44
+
45
+ // Setup the attributes
46
+ const { attributes: { noticeTitle, noticeContent, noticeAlignment, noticeBackgroundColor, noticeTextColor, noticeTitleColor, noticeFontSize, noticeDismiss } } = this.props;
47
+
48
+ return (
49
+ <InspectorControls key="inspector">
50
+ <PanelBody>
51
+ <RangeControl
52
+ label={ __( 'Font Size' ) }
53
+ value={ noticeFontSize }
54
+ onChange={ ( value ) => this.props.setAttributes( { noticeFontSize: value } ) }
55
+ min={ 14 }
56
+ max={ 24 }
57
+ step={ 1 }
58
+ />
59
+
60
+ <SelectControl
61
+ label={ __( 'Notice Display' ) }
62
+ description={ __( 'Do you want the message to always show or dismissable?' ) }
63
+ options={ noticeDismissOptions }
64
+ value={ noticeDismiss }
65
+ onChange={ ( value ) => this.props.setAttributes( { noticeDismiss: value } ) }
66
+ />
67
+
68
+ <PanelColor
69
+ title={ __( 'Notice Color' ) }
70
+ colorValue={ noticeBackgroundColor }
71
+ initialOpen={ false }
72
+ >
73
+ <ColorPalette
74
+ label={ __( 'Notice Color' ) }
75
+ value={ noticeBackgroundColor }
76
+ onChange={ ( value ) => this.props.setAttributes( { noticeBackgroundColor: value } ) }
77
+ colors={[
78
+ { color: '#00d1b2', name: 'teal' },
79
+ { color: '#3373dc', name: 'royal blue' },
80
+ { color: '#209cef', name: 'sky blue' },
81
+ { color: '#22d25f', name: 'green' },
82
+ { color: '#ffdd57', name: 'yellow' },
83
+ { color: '#ff3860', name: 'pink' },
84
+ { color: '#7941b6', name: 'purple' },
85
+ { color: '#392F43', name: 'black' },
86
+ ]}
87
+ />
88
+ </PanelColor>
89
+
90
+ <PanelColor
91
+ title={ __( 'Title Color' ) }
92
+ colorValue={ noticeTitleColor }
93
+ initialOpen={ false }
94
+ >
95
+ <ColorPalette
96
+ label={ __( 'Title Color' ) }
97
+ value={ noticeTitleColor }
98
+ onChange={ ( value ) => this.props.setAttributes( { noticeTitleColor: value } ) }
99
+ colors={[
100
+ { color: '#fff', name: 'white' },
101
+ { color: '#32373c', name: 'black' },
102
+ ]}
103
+ />
104
+ </PanelColor>
105
+
106
+ <PanelColor
107
+ title={ __( 'Text Color' ) }
108
+ colorValue={ noticeTextColor }
109
+ initialOpen={ false }
110
+ >
111
+ <ColorPalette
112
+ label={ __( 'Background Color' ) }
113
+ value={ noticeTextColor }
114
+ onChange={ ( value ) => this.props.setAttributes( { noticeTextColor: value } ) }
115
+ colors={[
116
+ { color: '#fff', name: 'white' },
117
+ { color: '#32373c', name: 'black' },
118
+ ]}
119
+ />
120
+ </PanelColor>
121
+ </PanelBody>
122
+ </InspectorControls>
123
+ );
124
+ }
125
+ }
src/blocks/block-notice/components/notice.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Notice Box Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+ import * as uniqueID from './../../../utils/helper';
11
+
12
+ /**
13
+ * Create a Notice wrapper Component
14
+ */
15
+ export default class NoticeBox extends Component {
16
+
17
+ constructor( props ) {
18
+ super( ...arguments );
19
+ }
20
+
21
+ render() {
22
+
23
+ // Setup the attributes
24
+ const { attributes: { noticeTitle, noticeAlignment, noticeBackgroundColor, noticeTextColor, noticeFontSize, noticeDismiss } } = this.props;
25
+
26
+ // Generate a unique ID for the dismissable notice
27
+ const blockID = uniqueID.generateUniqueID( noticeDismiss + noticeTitle )
28
+
29
+ return (
30
+ <div
31
+ style={ {
32
+ color: noticeTextColor,
33
+ textAlign: noticeAlignment,
34
+ backgroundColor: noticeBackgroundColor,
35
+ } }
36
+ className={ classnames(
37
+ this.props.className,
38
+ noticeDismiss,
39
+ 'ab-font-size-' + noticeFontSize,
40
+ 'ab-block-notice'
41
+ )
42
+ }
43
+ data-id={ blockID }
44
+ >
45
+ { this.props.children }
46
+ </div>
47
+ );
48
+ }
49
+ }
src/blocks/block-notice/index.js ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Notice
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import NoticeBox from './components/notice';
9
+ import DismissButton from './components/button';
10
+ import icons from './components/icons';
11
+ import * as uniqueID from './../../utils/helper';
12
+ import md5 from 'md5';
13
+
14
+ // Import CSS
15
+ import './styles/style.scss';
16
+ import './styles/editor.scss';
17
+
18
+ // Internationalization
19
+ const { __ } = wp.i18n;
20
+
21
+ // Extend component
22
+ const { Component } = wp.element;
23
+
24
+ // Register block
25
+ const { registerBlockType } = wp.blocks;
26
+
27
+ // Register editor components
28
+ const {
29
+ RichText,
30
+ AlignmentToolbar,
31
+ BlockControls,
32
+ BlockAlignmentToolbar,
33
+ MediaUpload,
34
+ } = wp.editor;
35
+
36
+ // Register components
37
+ const {
38
+ Button,
39
+ SelectControl,
40
+ withFallbackStyles,
41
+ withState,
42
+ } = wp.components;
43
+
44
+ class ABNoticeBlock extends Component {
45
+
46
+ render() {
47
+
48
+ // Setup the attributes
49
+ const {
50
+ attributes: {
51
+ noticeTitle,
52
+ noticeContent,
53
+ noticeAlignment,
54
+ noticeBackgroundColor,
55
+ noticeTextColor,
56
+ noticeTitleColor,
57
+ noticeFontSize,
58
+ noticeDismiss
59
+ },
60
+ attributes,
61
+ isSelected,
62
+ editable,
63
+ className,
64
+ setAttributes
65
+ } = this.props;
66
+
67
+ const onSelectImage = img => {
68
+ setAttributes( {
69
+ imgID: img.id,
70
+ imgURL: img.url,
71
+ imgAlt: img.alt,
72
+ } );
73
+ };
74
+
75
+ return [
76
+ // Show the alignment toolbar on focus
77
+ <BlockControls key="controls">
78
+ <AlignmentToolbar
79
+ value={ noticeAlignment }
80
+ onChange={ ( value ) => setAttributes( { noticeAlignment: value } ) }
81
+ />
82
+ </BlockControls>,
83
+ // Show the block controls on focus
84
+ <Inspector
85
+ { ...{ setAttributes, ...this.props } }
86
+ />,
87
+ // Show the block markup in the editor
88
+ <NoticeBox { ...this.props }>
89
+ { // Check if the notice is dismissable and output the button
90
+ noticeDismiss && (
91
+ <DismissButton { ...this.props }>
92
+ { icons.dismiss }
93
+ </DismissButton>
94
+ ) }
95
+
96
+ <RichText
97
+ tagName="p"
98
+ placeholder={ __( 'Notice Title' ) }
99
+ keepPlaceholderOnFocus
100
+ value={ noticeTitle }
101
+ className={ classnames(
102
+ 'ab-notice-title'
103
+ ) }
104
+ style={ {
105
+ color: noticeTitleColor,
106
+ } }
107
+ onChange={ ( value ) => setAttributes( { noticeTitle: value } ) }
108
+ />
109
+
110
+ <RichText
111
+ tagName="div"
112
+ multiline="p"
113
+ placeholder={ __( 'Add notice text...' ) }
114
+ keepPlaceholderOnFocus
115
+ value={ noticeContent }
116
+ formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link' ] }
117
+ className={ classnames(
118
+ 'ab-notice-text'
119
+ ) }
120
+ style={ {
121
+ borderColor: noticeBackgroundColor,
122
+ } }
123
+ onChange={ ( value ) => setAttributes( { noticeContent: value } ) }
124
+ inlineToolbar
125
+ />
126
+ </NoticeBox>
127
+ ];
128
+ }
129
+ }
130
+
131
+ // Register the block
132
+ registerBlockType( 'atomic-blocks/ab-notice', {
133
+ title: __( 'AB Notice' ),
134
+ description: __( 'Add a stylized text notice.' ),
135
+ icon: 'format-aside',
136
+ category: 'atomic-blocks',
137
+ keywords: [
138
+ __( 'notice' ),
139
+ __( 'message' ),
140
+ __( 'atomic' ),
141
+ ],
142
+ attributes: {
143
+ noticeTitle: {
144
+ type: 'string',
145
+ selector: '.ab-notice-title',
146
+ },
147
+ noticeContent: {
148
+ type: 'array',
149
+ selector: '.ab-notice-text',
150
+ source: 'children',
151
+ },
152
+ noticeAlignment: {
153
+ type: 'string',
154
+ },
155
+ noticeBackgroundColor: {
156
+ type: 'string',
157
+ default: '#00d1b2'
158
+ },
159
+ noticeTextColor: {
160
+ type: 'string',
161
+ default: '#32373c'
162
+ },
163
+ noticeTitleColor: {
164
+ type: 'string',
165
+ default: '#fff'
166
+ },
167
+ noticeFontSize: {
168
+ type: 'number',
169
+ default: 18
170
+ },
171
+ noticeDismiss: {
172
+ type: 'string',
173
+ default: '',
174
+ },
175
+ },
176
+
177
+ // Render the block components
178
+ edit: ABNoticeBlock,
179
+
180
+ // Save the attributes and markup
181
+ save: function( props ) {
182
+
183
+ // Setup the attributes
184
+ const {
185
+ noticeTitle,
186
+ noticeContent,
187
+ noticeAlignment,
188
+ noticeBackgroundColor,
189
+ noticeTextColor,
190
+ noticeTitleColor,
191
+ noticeFontSize,
192
+ noticeDismiss
193
+ } = props.attributes;
194
+
195
+ // Save the block markup for the front end
196
+ return (
197
+ <NoticeBox { ...props }>
198
+ { noticeDismiss && !! noticeDismiss.length && (
199
+ <DismissButton { ...props }>
200
+ { icons.dismiss }
201
+ </DismissButton>
202
+ ) }
203
+
204
+ { noticeTitle && !! noticeTitle.length && (
205
+ <div
206
+ class="ab-notice-title"
207
+ style={ {
208
+ color: noticeTitleColor
209
+ } }
210
+ >
211
+ <p>{ noticeTitle }</p>
212
+ </div>
213
+ ) }
214
+
215
+ { noticeContent && !! noticeContent.length && (
216
+ <div
217
+ class="ab-notice-text"
218
+ style={ {
219
+ borderColor: noticeBackgroundColor
220
+ } }
221
+ >
222
+ { noticeContent }
223
+ </div>
224
+ ) }
225
+ </NoticeBox>
226
+ );
227
+ },
228
+ } );
src/blocks/block-notice/styles/editor.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-notice {
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ .editor-block-list__layout [data-type="atomic-blocks/ab-notice"] {
10
+ margin-bottom: 1.2em;
11
+ }
src/blocks/block-notice/styles/style.scss ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Notice styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-notice {
7
+ border-radius: 5px;
8
+ position: relative;
9
+ margin-bottom: 1.2em;
10
+
11
+ .ab-notice-dismiss {
12
+ position: absolute;
13
+ top: 13px;
14
+ right: 13px;
15
+ opacity: .8;
16
+ padding: 0;
17
+ background: none;
18
+ transition: .3s ease;
19
+
20
+ &:hover {
21
+ opacity: 1;
22
+ cursor: pointer;
23
+ box-shadow: none;
24
+ }
25
+ }
26
+
27
+ .ab-notice-title {
28
+ font-weight: bold;
29
+ padding: .5em 1em;
30
+ color: #fff;
31
+ border-top-right-radius: 5px;
32
+ border-top-left-radius: 5px;
33
+ width: 100%;
34
+ display: inline-block;
35
+
36
+ p {
37
+ margin-bottom: 0;
38
+ }
39
+
40
+ &:empty {
41
+ display: none;
42
+ }
43
+ }
44
+
45
+ .ab-notice-text {
46
+ padding: 1em;
47
+ border: solid 2px #333;
48
+ border-radius: 5px;
49
+ background: #fff;
50
+
51
+ a {
52
+ color: inherit;
53
+ box-shadow: 0 -1px 0 inset;
54
+ text-decoration: none;
55
+
56
+ &:hover {
57
+ color: inherit;
58
+ box-shadow: 0 -2px 0 inset;
59
+ }
60
+ }
61
+
62
+ p:last-child {
63
+ margin-bottom: 0;
64
+ }
65
+ }
66
+
67
+ .ab-notice-title:not(:empty) + .notice-text,
68
+ .blocks-rich-text + .blocks-rich-text .ab-notice-text {
69
+ border-top-right-radius: 0;
70
+ border-top-left-radius: 0;
71
+ border-bottom-right-radius: 5px;
72
+ border-bottom-left-radius: 5px;
73
+ }
74
+ }
75
+
76
+ body:not(.wp-admin) .ab-block-notice.ab-dismissable {
77
+ display: none;
78
+ }
src/blocks/block-post-grid/edit.js ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+
5
+ import get from 'lodash/get';
6
+ import isUndefined from 'lodash/isUndefined';
7
+ import pickBy from 'lodash/pickBy';
8
+ import moment from 'moment';
9
+ import classnames from 'classnames';
10
+ import { stringify } from 'querystringify';
11
+
12
+ const { Component, Fragment } = wp.element;
13
+
14
+ const { __ } = wp.i18n;
15
+
16
+ const { decodeEntities } = wp.htmlEntities;
17
+
18
+ const {
19
+ PanelBody,
20
+ Placeholder,
21
+ QueryControls,
22
+ RangeControl,
23
+ SelectControl,
24
+ Spinner,
25
+ ToggleControl,
26
+ Toolbar,
27
+ withAPIData,
28
+ } = wp.components;
29
+
30
+ const {
31
+ InspectorControls,
32
+ BlockAlignmentToolbar,
33
+ BlockControls,
34
+ } = wp.editor;
35
+
36
+ const MAX_POSTS_COLUMNS = 4;
37
+
38
+ class LatestPostsBlock extends Component {
39
+ constructor() {
40
+ super( ...arguments );
41
+
42
+ this.toggleDisplayPostDate = this.toggleDisplayPostDate.bind( this );
43
+ this.toggleDisplayPostExcerpt = this.toggleDisplayPostExcerpt.bind( this );
44
+ this.toggleDisplayPostAuthor = this.toggleDisplayPostAuthor.bind( this );
45
+ this.toggleDisplayPostImage = this.toggleDisplayPostImage.bind( this );
46
+ this.toggleDisplayPostLink = this.toggleDisplayPostLink.bind( this );
47
+ }
48
+
49
+ toggleDisplayPostDate() {
50
+ const { displayPostDate } = this.props.attributes;
51
+ const { setAttributes } = this.props;
52
+
53
+ setAttributes( { displayPostDate: ! displayPostDate } );
54
+ }
55
+
56
+ toggleDisplayPostExcerpt() {
57
+ const { displayPostExcerpt } = this.props.attributes;
58
+ const { setAttributes } = this.props;
59
+
60
+ setAttributes( { displayPostExcerpt: ! displayPostExcerpt } );
61
+ }
62
+
63
+ toggleDisplayPostAuthor() {
64
+ const { displayPostAuthor } = this.props.attributes;
65
+ const { setAttributes } = this.props;
66
+
67
+ setAttributes( { displayPostAuthor: ! displayPostAuthor } );
68
+ }
69
+
70
+ toggleDisplayPostImage() {
71
+ const { displayPostImage } = this.props.attributes;
72
+ const { setAttributes } = this.props;
73
+
74
+ setAttributes( { displayPostImage: ! displayPostImage } );
75
+ }
76
+
77
+ toggleDisplayPostLink() {
78
+ const { displayPostLink } = this.props.attributes;
79
+ const { setAttributes } = this.props;
80
+
81
+ setAttributes( { displayPostLink: ! displayPostLink } );
82
+ }
83
+
84
+ render() {
85
+ const latestPosts = this.props.latestPosts.data;
86
+ const { attributes, categoriesList, setAttributes } = this.props;
87
+ const { displayPostDate, displayPostExcerpt, displayPostAuthor, displayPostImage,displayPostLink, align, postLayout, columns, order, orderBy, categories, postsToShow, width, imageCrop } = attributes;
88
+
89
+ // Thumbnail options
90
+ const imageCropOptions = [
91
+ { value: 'landscape', label: __( 'Landscape' ) },
92
+ { value: 'square', label: __( 'Square' ) },
93
+ ];
94
+
95
+ const isLandscape = imageCrop === 'landscape';
96
+
97
+ const inspectorControls = (
98
+ <InspectorControls>
99
+ <PanelBody title={ __( 'Post Grid Settings' ) }>
100
+ <QueryControls
101
+ { ...{ order, orderBy } }
102
+ numberOfItems={ postsToShow }
103
+ categoriesList={ get( categoriesList, [ 'data' ], {} ) }
104
+ selectedCategoryId={ categories }
105
+ onOrderChange={ ( value ) => setAttributes( { order: value } ) }
106
+ onOrderByChange={ ( value ) => setAttributes( { orderBy: value } ) }
107
+ onCategoryChange={ ( value ) => setAttributes( { categories: '' !== value ? value : undefined } ) }
108
+ onNumberOfItemsChange={ ( value ) => setAttributes( { postsToShow: value } ) }
109
+ />
110
+ { postLayout === 'grid' &&
111
+ <RangeControl
112
+ label={ __( 'Columns' ) }
113
+ value={ columns }
114
+ onChange={ ( value ) => setAttributes( { columns: value } ) }
115
+ min={ 2 }
116
+ max={ ! hasPosts ? MAX_POSTS_COLUMNS : Math.min( MAX_POSTS_COLUMNS, latestPosts.length ) }
117
+ />
118
+ }
119
+ <ToggleControl
120
+ label={ __( 'Display Featured Image' ) }
121
+ checked={ displayPostImage }
122
+ onChange={ this.toggleDisplayPostImage }
123
+ />
124
+ { displayPostImage &&
125
+ <SelectControl
126
+ label={ __( 'Featured Image Style' ) }
127
+ options={ imageCropOptions }
128
+ value={ imageCrop }
129
+ onChange={ ( value ) => this.props.setAttributes( { imageCrop: value } ) }
130
+ />
131
+ }
132
+ <ToggleControl
133
+ label={ __( 'Display Post Author' ) }
134
+ checked={ displayPostAuthor }
135
+ onChange={ this.toggleDisplayPostAuthor }
136
+ />
137
+ <ToggleControl
138
+ label={ __( 'Display Post Date' ) }
139
+ checked={ displayPostDate }
140
+ onChange={ this.toggleDisplayPostDate }
141
+ />
142
+ <ToggleControl
143
+ label={ __( 'Display Post Excerpt' ) }
144
+ checked={ displayPostExcerpt }
145
+ onChange={ this.toggleDisplayPostExcerpt }
146
+ />
147
+ <ToggleControl
148
+ label={ __( 'Display Continue Reading Link' ) }
149
+ checked={ displayPostLink }
150
+ onChange={ this.toggleDisplayPostLink }
151
+ />
152
+
153
+ </PanelBody>
154
+ </InspectorControls>
155
+ );
156
+
157
+ const hasPosts = Array.isArray( latestPosts ) && latestPosts.length;
158
+ if ( ! hasPosts ) {
159
+ return (
160
+ <Fragment>
161
+ { inspectorControls }
162
+ <Placeholder
163
+ icon="admin-post"
164
+ label={ __( 'Atomic Blocks Post Grid' ) }
165
+ >
166
+ { ! Array.isArray( latestPosts ) ?
167
+ <Spinner /> :
168
+ __( 'No posts found.' )
169
+ }
170
+ </Placeholder>
171
+ </Fragment>
172
+ );
173
+ }
174
+
175
+ // Removing posts from display should be instant.
176
+ const displayPosts = latestPosts.length > postsToShow ?
177
+ latestPosts.slice( 0, postsToShow ) :
178
+ latestPosts;
179
+
180
+ const layoutControls = [
181
+ {
182
+ icon: 'grid-view',
183
+ title: __( 'Grid View' ),
184
+ onClick: () => setAttributes( { postLayout: 'grid' } ),
185
+ isActive: postLayout === 'grid',
186
+ },
187
+ {
188
+ icon: 'list-view',
189
+ title: __( 'List View' ),
190
+ onClick: () => setAttributes( { postLayout: 'list' } ),
191
+ isActive: postLayout === 'list',
192
+ },
193
+ ];
194
+
195
+ return (
196
+ <Fragment>
197
+ { inspectorControls }
198
+ <BlockControls>
199
+ <BlockAlignmentToolbar
200
+ value={ align }
201
+ onChange={ ( value ) => {
202
+ setAttributes( { align: value } );
203
+ } }
204
+ controls={ [ 'center', 'wide' ] }
205
+ />
206
+ <Toolbar controls={ layoutControls } />
207
+ </BlockControls>
208
+ <div
209
+ className={ classnames(
210
+ this.props.className,
211
+ 'ab-block-post-grid',
212
+ ) }
213
+ >
214
+ <div
215
+ className={ classnames( {
216
+ 'is-grid': postLayout === 'grid',
217
+ 'is-list': postLayout === 'list',
218
+ [ `columns-${ columns }` ]: postLayout === 'grid',
219
+ 'ab-post-grid-items' : 'ab-post-grid-items'
220
+ } ) }
221
+ >
222
+ { displayPosts.map( ( post, i ) =>
223
+ <article
224
+ key={ i }
225
+ className={ classnames(
226
+ post.featured_image_src && displayPostImage ? 'has-thumb' : 'no-thumb'
227
+ ) }
228
+ >
229
+ {
230
+ displayPostImage && post.featured_image_src !== undefined && post.featured_image_src ? (
231
+ <div class="ab-block-post-grid-image">
232
+ <a href={ post.link } target="_blank" rel="bookmark">
233
+ <img
234
+ src={ isLandscape ? post.featured_image_src : post.featured_image_src_square }
235
+ alt={ decodeEntities( post.title.rendered.trim() ) || __( '(Untitled)' ) }
236
+ />
237
+ </a>
238
+ </div>
239
+ ) : (
240
+ null
241
+ )
242
+ }
243
+
244
+ { console.log(post) }
245
+
246
+ <div class="ab-block-post-grid-text">
247
+ <h2 class="entry-title"><a href={ post.link } target="_blank" rel="bookmark">{ decodeEntities( post.title.rendered.trim() ) || __( '(Untitled)' ) }</a></h2>
248
+
249
+ <div class="ab-block-post-grid-byline">
250
+ { displayPostAuthor && post.author_info.display_name &&
251
+ <div class="ab-block-post-grid-author"><a class="ab-text-link" target="_blank" href={ post.author_info.author_link }>{ post.author_info.display_name }</a></div>
252
+ }
253
+
254
+ { displayPostDate && post.date_gmt &&
255
+ <time dateTime={ moment( post.date_gmt ).utc().format() } className={ 'ab-block-post-grid-date' }>
256
+ { moment( post.date_gmt ).local().format( 'MMMM DD, Y' ) }
257
+ </time>
258
+ }
259
+ </div>
260
+
261
+ <div class="ab-block-post-grid-excerpt">
262
+ { displayPostExcerpt && post.excerpt &&
263
+ <div dangerouslySetInnerHTML={ { __html: post.excerpt.rendered } } />
264
+ }
265
+
266
+ { displayPostLink &&
267
+ <p><a class="ab-block-post-grid-link ab-text-link" href={ post.link } target="_blank" rel="bookmark">{ __( 'Continue Reading', 'atomic-blocks' ) }</a></p>
268
+ }
269
+ </div>
270
+ </div>
271
+ </article>
272
+ ) }
273
+ </div>
274
+ </div>
275
+ </Fragment>
276
+ );
277
+ }
278
+ }
279
+
280
+ export default withAPIData( ( props ) => {
281
+ const { postsToShow, order, orderBy, categories } = props.attributes;
282
+ const latestPostsQuery = stringify( pickBy( {
283
+ categories,
284
+ order,
285
+ orderby: orderBy,
286
+ per_page: postsToShow,
287
+ _fields: [ 'date_gmt', 'link', 'title', 'featured_media', 'featured_image_src', 'featured_image_src_square', 'excerpt', 'author_info' ],
288
+ _embed: 'embed',
289
+ }, ( value ) => ! isUndefined( value ) ) );
290
+ const categoriesListQuery = stringify( {
291
+ per_page: 100,
292
+ _fields: [ 'id', 'name', 'parent' ],
293
+ } );
294
+ return {
295
+ latestPosts: `/wp/v2/posts?${ latestPostsQuery }`,
296
+ categoriesList: `/wp/v2/categories?${ categoriesListQuery }`,
297
+ };
298
+ } )( LatestPostsBlock );
src/blocks/block-post-grid/index.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Page Grid
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import edit from './edit';
8
+
9
+ // Import CSS
10
+ import './styles/style.scss';
11
+ import './styles/editor.scss';
12
+
13
+ // Components
14
+ const { __ } = wp.i18n;
15
+
16
+ // Extend component
17
+ const { Component } = wp.element;
18
+
19
+ // Register block controls
20
+ const {
21
+ registerBlockType,
22
+ } = wp.blocks;
23
+
24
+ // Register alignments
25
+ const validAlignments = [ 'center', 'wide' ];
26
+
27
+ export const name = 'core/latest-posts';
28
+
29
+ // Register the block
30
+ registerBlockType( 'atomic-blocks/ab-post-grid', {
31
+ title: __( 'AB Post Grid' ),
32
+ description: __( 'Add a grid or list of customizable posts to your page.' ),
33
+ icon: 'grid-view',
34
+ category: 'atomic-blocks',
35
+ keywords: [
36
+ __( 'post' ),
37
+ __( 'grid' ),
38
+ __( 'atomic' ),
39
+ ],
40
+
41
+ getEditWrapperProps( attributes ) {
42
+ const { align } = attributes;
43
+ if ( -1 !== validAlignments.indexOf( align ) ) {
44
+ return { 'data-align': align };
45
+ }
46
+ },
47
+
48
+ edit,
49
+
50
+ // Render via PHP
51
+ save() {
52
+ return null;
53
+ },
54
+ } );
src/blocks/block-post-grid/styles/editor.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-post-grid {
6
+ h2 a {
7
+ text-decoration: none;
8
+ }
9
+ }
10
+
11
+ div[data-type="atomic-blocks/ab-post-grid"] .editor-block-list__block-edit:before {
12
+
13
+ }
14
+
src/blocks/block-post-grid/styles/style.scss ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Post grid styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-post-grid {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+
10
+ .is-grid {
11
+ display: grid;
12
+ grid-template-columns: 1fr 1fr;
13
+ grid-template-rows: 1fr;
14
+ grid-gap: 0 2em;
15
+
16
+ article {
17
+ margin-bottom: 2.5em;
18
+ }
19
+ }
20
+
21
+ .is-grid.columns-2 {
22
+ grid-template-columns: 1fr 1fr;
23
+ }
24
+
25
+ .is-grid.columns-3 {
26
+ grid-template-columns: 1fr 1fr 1fr;
27
+ }
28
+
29
+ .is-grid.columns-4 {
30
+ grid-template-columns: 1fr 1fr 1fr 1fr;
31
+ }
32
+
33
+ div[class*="columns"].is-grid {
34
+ @media only screen and (max-width: 600px) {
35
+ grid-template-columns: 1fr;
36
+ }
37
+ }
38
+
39
+ .ab-block-post-grid-image {
40
+ margin-bottom: 1.2em;
41
+
42
+ img {
43
+ display: block;
44
+ width: 100%;
45
+ }
46
+ }
47
+
48
+ .ab-block-post-grid-text {
49
+ text-align: left;
50
+ }
51
+
52
+ h2 {
53
+ margin-top: 0;
54
+ margin-bottom: 15px;
55
+ font-size: 28px;
56
+ line-height: 1.2;
57
+
58
+ a {
59
+ color: $black;
60
+ box-shadow: none;
61
+ transition: .3s ease;
62
+
63
+ &:hover {
64
+ box-shadow: inset 0 -2px 0 $accent;
65
+ color: $accent;
66
+ }
67
+ }
68
+ }
69
+
70
+ .ab-block-post-grid-byline {
71
+ text-transform: uppercase;
72
+ font-size: 13px;
73
+ letter-spacing: 1px;
74
+ color: $lightgray;
75
+ margin-bottom: 15px;
76
+ }
77
+
78
+ .ab-block-post-grid-author,
79
+ .ab-block-post-grid-date {
80
+ display: inline-block;
81
+
82
+ &:not(:last-child):after {
83
+ content: "\B7";
84
+ vertical-align: middle;
85
+ margin: 0 5px;
86
+ line-height: 1;
87
+ }
88
+ }
89
+
90
+ .ab-block-post-grid-author a {
91
+ box-shadow: none;
92
+
93
+ &:hover {
94
+ color: inherit;
95
+ box-shadow: 0 -1px 0 inset;
96
+ color: $accent;
97
+ }
98
+ }
99
+
100
+ .ab-block-post-grid-text p {
101
+ margin: 0 0 15px 0;
102
+ line-height: 1.5;
103
+ font-size: 18px;
104
+
105
+ @media only screen and (max-width: 600px) {
106
+ font-size: 16px;
107
+ }
108
+
109
+ &:last-of-type {
110
+ margin-bottom: 0;
111
+ }
112
+ }
113
+
114
+ .ab-block-post-grid-link {
115
+ display: inline-block;
116
+ box-shadow: none;
117
+ transition: .3s ease;
118
+ font-weight: bold;
119
+ color: $black;
120
+
121
+ &:hover {
122
+ box-shadow: 0 -2px 0 inset;
123
+ }
124
+ }
125
+
126
+ .ab-block-post-grid-excerpt div + p {
127
+ margin-top: 15px;
128
+ }
129
+
130
+ .is-list {
131
+ article {
132
+ display: grid;
133
+ grid-template-columns: 30% 1fr;
134
+ grid-template-rows: 1fr;
135
+ grid-gap: 0 2em;
136
+
137
+ &:not(:last-child) {
138
+ margin-bottom: 5%;
139
+ padding-bottom: 5%;
140
+ }
141
+
142
+ @media only screen and (min-width: 600px) {
143
+ &:not(:last-child) {
144
+ border-bottom: solid 1px #eee;
145
+ }
146
+ }
147
+
148
+ @media only screen and (max-width: 600px) {
149
+ grid-template-columns: 1fr;
150
+ }
151
+ }
152
+
153
+ .ab-block-post-grid-image {
154
+ margin-bottom: 0;
155
+
156
+ @media only screen and (max-width: 600px) {
157
+ margin-bottom: 5%;
158
+ }
159
+ }
160
+
161
+ .ab-block-post-grid-title {
162
+ @media only screen and (min-width: 600px) {
163
+ font-size: 34px;
164
+ }
165
+ }
166
+
167
+ .no-thumb .ab-block-post-grid-text {
168
+ grid-column: span 2;
169
+ }
170
+ }
171
+ }
src/blocks/block-sharing/components/inspector.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ FormToggle,
24
+ RangeControl,
25
+ SelectControl,
26
+ ToggleControl,
27
+ } = wp.components;
28
+
29
+ /**
30
+ * Create an Inspector Controls wrapper Component
31
+ */
32
+ export default class Inspector extends Component {
33
+
34
+ constructor( props ) {
35
+ super( ...arguments );
36
+ }
37
+
38
+ render() {
39
+
40
+ // Setup the attributes
41
+ const {
42
+ twitter,
43
+ facebook,
44
+ google,
45
+ linkedin,
46
+ pinterest,
47
+ email,
48
+ reddit,
49
+ shareAlignment,
50
+ shareButtonStyle,
51
+ shareButtonShape,
52
+ shareButtonSize,
53
+ shareButtonColor,
54
+ } = this.props.attributes;
55
+
56
+ // Button style values
57
+ const buttonStyleOptions = [
58
+ { value: 'ab-share-icon-text', label: __( 'Icon and Text' ) },
59
+ { value: 'ab-share-icon-only', label: __( 'Icon Only' ) },
60
+ { value: 'ab-share-text-only', label: __( 'Text Only' ) },
61
+ ];
62
+
63
+ // Button shape values
64
+ const buttonShapeOptions = [
65
+ { value: 'ab-share-shape-square', label: __( 'Square' ) },
66
+ { value: 'ab-share-shape-rounded', label: __( 'Rounded Square' ) },
67
+ { value: 'ab-share-shape-circular', label: __( 'Circular' ) },
68
+ ];
69
+
70
+ // Button size values
71
+ const shareButtonSizeOptions = [
72
+ { value: 'ab-share-size-small', label: __( 'Small' ) },
73
+ { value: 'ab-share-size-medium', label: __( 'Medium' ) },
74
+ { value: 'ab-share-size-large', label: __( 'Large' ) },
75
+ ];
76
+
77
+ // Button color values
78
+ const shareButtonColorOptions = [
79
+ { value: 'ab-share-color-standard', label: __( 'Standard' ) },
80
+ { value: 'ab-share-color-social', label: __( 'Social Colors' ) },
81
+ ];
82
+
83
+ return (
84
+ <InspectorControls key="inspector">
85
+ <PanelBody>
86
+ <p>{ __( 'Enable or disable the sharing links you want to output.' ) }</p>
87
+
88
+ <ToggleControl
89
+ label={ __( 'Twitter' ) }
90
+ checked={ !! twitter }
91
+ onChange={ () => this.props.setAttributes( { twitter: ! twitter } ) }
92
+ />
93
+ <ToggleControl
94
+ label={ __( 'Facebook' ) }
95
+ checked={ !! facebook }
96
+ onChange={ () => this.props.setAttributes( { facebook: ! facebook } ) }
97
+ />
98
+ <ToggleControl
99
+ label={ __( 'Google' ) }
100
+ checked={ !! google }
101
+ onChange={ () => this.props.setAttributes( { google: ! google } ) }
102
+ />
103
+ <ToggleControl
104
+ label={ __( 'Pinterest' ) }
105
+ checked={ !! pinterest }
106
+ onChange={ () => this.props.setAttributes( { pinterest: ! pinterest } ) }
107
+ />
108
+ <ToggleControl
109
+ label={ __( 'LinkedIn' ) }
110
+ checked={ !! linkedin }
111
+ onChange={ () => this.props.setAttributes( { linkedin: ! linkedin } ) }
112
+ />
113
+ <ToggleControl
114
+ label={ __( 'Reddit' ) }
115
+ checked={ !! reddit }
116
+ onChange={ () => this.props.setAttributes( { reddit: ! reddit } ) }
117
+ />
118
+ <ToggleControl
119
+ label={ __( 'Email' ) }
120
+ checked={ !! email }
121
+ onChange={ () => this.props.setAttributes( { email: ! email } ) }
122
+ />
123
+ </PanelBody>
124
+
125
+ <PanelBody title={ __( 'Sharing Button Options' ) } initialOpen={ false }>
126
+ <SelectControl
127
+ label={ __( 'Button Style' ) }
128
+ value={ shareButtonStyle }
129
+ options={ buttonStyleOptions.map( ({ value, label }) => ( {
130
+ value: value,
131
+ label: label,
132
+ } ) ) }
133
+ onChange={ ( value ) => { this.props.setAttributes( { shareButtonStyle: value } ) } }
134
+ />
135
+
136
+ <SelectControl
137
+ label={ __( 'Button Shape' ) }
138
+ value={ shareButtonShape }
139
+ options={ buttonShapeOptions.map( ({ value, label }) => ( {
140
+ value: value,
141
+ label: label,
142
+ } ) ) }
143
+ onChange={ ( value ) => { this.props.setAttributes( { shareButtonShape: value } ) } }
144
+ />
145
+
146
+ <SelectControl
147
+ label={ __( 'Button Size' ) }
148
+ value={ shareButtonSize }
149
+ options={ shareButtonSizeOptions.map( ({ value, label }) => ( {
150
+ value: value,
151
+ label: label,
152
+ } ) ) }
153
+ onChange={ ( value ) => { this.props.setAttributes( { shareButtonSize: value } ) } }
154
+ />
155
+
156
+ <SelectControl
157
+ label={ __( 'Button Color' ) }
158
+ value={ shareButtonColor }
159
+ options={ shareButtonColorOptions.map( ({ value, label }) => ( {
160
+ value: value,
161
+ label: label,
162
+ } ) ) }
163
+ onChange={ ( value ) => { this.props.setAttributes( { shareButtonColor: value } ) } }
164
+ />
165
+ </PanelBody>
166
+ </InspectorControls>
167
+ );
168
+ }
169
+ }
src/blocks/block-sharing/components/sharing.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Sharing Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a ShareLinks wrapper Component
13
+ */
14
+ export default class ShareLinks extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+
22
+ return (
23
+ <div
24
+ className={ classnames(
25
+ this.props.className,
26
+ this.props.attributes.shareButtonStyle,
27
+ this.props.attributes.shareButtonShape,
28
+ this.props.attributes.shareButtonSize,
29
+ this.props.attributes.shareButtonColor,
30
+ this.props.attributes.shareAlignment,
31
+ 'ab-block-sharing'
32
+ ) }
33
+ >
34
+ { this.props.children }
35
+ </div>
36
+ );
37
+ }
38
+ }
src/blocks/block-sharing/index.js ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Sharing
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import ShareLinks from './components/sharing';
9
+
10
+ // Import CSS
11
+ import './styles/style.scss';
12
+ import './styles/editor.scss';
13
+
14
+ // Components
15
+ const { __ } = wp.i18n;
16
+
17
+ // Extend component
18
+ const { Component } = wp.element;
19
+
20
+ // Register block
21
+ const { registerBlockType } = wp.blocks;
22
+
23
+ // Register editor components
24
+ const {
25
+ RichText,
26
+ AlignmentToolbar,
27
+ BlockControls,
28
+ BlockAlignmentToolbar,
29
+ } = wp.editor;
30
+
31
+ // Register components
32
+ const {
33
+ Button,
34
+ withFallbackStyles,
35
+ IconButton,
36
+ Dashicon,
37
+ } = wp.components;
38
+
39
+ // Register the block
40
+ registerBlockType( 'atomic-blocks/ab-sharing', {
41
+ title: __( 'AB Sharing' ),
42
+ description: __( 'Add sharing buttons to your posts and pages.' ),
43
+ icon: 'admin-links',
44
+ category: 'atomic-blocks',
45
+ keywords: [
46
+ __( 'sharing' ),
47
+ __( 'social' ),
48
+ __( 'atomic' ),
49
+ ],
50
+
51
+ // Render the block components
52
+ edit: props => {
53
+
54
+ // Setup the props
55
+ const {
56
+ attributes,
57
+ isSelected,
58
+ editable,
59
+ className,
60
+ setAttributes
61
+ } = props;
62
+
63
+ const {
64
+ twitter,
65
+ facebook,
66
+ google,
67
+ linkedin,
68
+ pinterest,
69
+ email,
70
+ reddit,
71
+ shareAlignment,
72
+ shareButtonStyle,
73
+ shareButtonShape,
74
+ shareButtonColor,
75
+ } = props.attributes;
76
+
77
+ return [
78
+ // Show the alignment toolbar on focus
79
+ <BlockControls key="controls">
80
+ <AlignmentToolbar
81
+ value={ shareAlignment }
82
+ onChange={ ( value ) => {
83
+ setAttributes( { shareAlignment: value } );
84
+ } }
85
+ />
86
+ </BlockControls>,
87
+ // Show the block controls on focus
88
+ <Inspector
89
+ { ...props }
90
+ />,
91
+ // Show the button markup in the editor
92
+ <ShareLinks { ...props }>
93
+ <ul class="ab-share-list">
94
+ { twitter &&
95
+ <li>
96
+ <a className='ab-share-twitter'>
97
+ <i class="fab fa-twitter"></i>
98
+ <span className={ 'ab-social-text' }>
99
+ { __( 'Share on Twitter' ) }
100
+ </span>
101
+ </a>
102
+ </li>
103
+ }
104
+
105
+ { facebook &&
106
+ <li>
107
+ <a className='ab-share-facebook'>
108
+ <i class="fab fa-facebook-f"></i>
109
+ <span className={ 'ab-social-text' }>
110
+ { __( 'Share on Facebook' ) }
111
+ </span>
112
+ </a>
113
+ </li>
114
+ }
115
+
116
+ { google &&
117
+ <li>
118
+ <a className='ab-share-google'>
119
+ <i class="fab fa-google"></i>
120
+ <span className={ 'ab-social-text' }>
121
+ { __( 'Share on Google' ) }
122
+ </span>
123
+ </a>
124
+ </li>
125
+ }
126
+
127
+ { pinterest &&
128
+ <li>
129
+ <a className='ab-share-pinterest'>
130
+ <i class="fab fa-pinterest-p"></i>
131
+ <span className={ 'ab-social-text' }>
132
+ { __( 'Share on Pinterest' ) }
133
+ </span>
134
+ </a>
135
+ </li>
136
+ }
137
+
138
+ { linkedin &&
139
+ <li>
140
+ <a className='ab-share-linkedin'>
141
+ <i class="fab fa-linkedin"></i>
142
+ <span className={ 'ab-social-text' }>
143
+ { __( 'Share on LinkedIn' ) }
144
+ </span>
145
+ </a>
146
+ </li>
147
+ }
148
+
149
+ { reddit &&
150
+ <li>
151
+ <a className='ab-share-reddit'>
152
+ <i class="fab fa-reddit-alien"></i>
153
+ <span className={ 'ab-social-text' }>
154
+ { __( 'Share on reddit' ) }
155
+ </span>
156
+ </a>
157
+ </li>
158
+ }
159
+
160
+ { email &&
161
+ <li>
162
+ <a className='ab-share-email'>
163
+ <i class="fas fa-envelope"></i>
164
+ <span className={ 'ab-social-text' }>
165
+ { __( 'Share via Email' ) }
166
+ </span>
167
+ </a>
168
+ </li>
169
+ }
170
+ </ul>
171
+ </ShareLinks>
172
+ ];
173
+ },
174
+
175
+ // Render via PHP
176
+ save() {
177
+ return null;
178
+ },
179
+ } );
src/blocks/block-sharing/styles/editor.scss ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-sharing {
6
+ margin-bottom: 0;
7
+ }
src/blocks/block-sharing/styles/style.scss ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Sharing styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-sharing {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+
10
+ .blocks-rich-text {
11
+ display: inline-flex;
12
+ }
13
+
14
+ .ab-share-list {
15
+ margin: 0;
16
+ padding: 0;
17
+
18
+ li {
19
+ list-style: none;
20
+ display: inline-block;
21
+ margin: 0 5px 5px 0;
22
+ }
23
+
24
+ a {
25
+ background: #272c30;
26
+ color: #fff;
27
+ padding: 10px 15px;
28
+ text-align: center;
29
+ display: block;
30
+ line-height: 1;
31
+ font-size: 16px;
32
+ transition: .3s ease;
33
+
34
+ &:hover {
35
+ box-shadow: inset 0 0 200px rgba(255, 255, 255, 0.15);
36
+ }
37
+ }
38
+ }
39
+
40
+ &.ab-share-icon-text {
41
+ i {
42
+ margin-right: 5px;
43
+ }
44
+ }
45
+
46
+ &.ab-share-icon-only {
47
+ a {
48
+ padding: 10px 11px;
49
+ min-width: 37px;
50
+ }
51
+
52
+ .ab-social-text {
53
+ border: 0;
54
+ clip: rect(1px, 1px, 1px, 1px);
55
+ clip-path: inset(50%);
56
+ height: 1px;
57
+ margin: -1px;
58
+ overflow: hidden;
59
+ padding: 0;
60
+ position: absolute !important;
61
+ width: 1px;
62
+ word-wrap: normal !important;
63
+ }
64
+ }
65
+
66
+ &.ab-share-text-only {
67
+ i {
68
+ display: none;
69
+ }
70
+ }
71
+
72
+ &.ab-share-shape-square {
73
+ a {
74
+ border-radius: 0;
75
+ }
76
+ }
77
+
78
+ &.ab-share-shape-rounded {
79
+ a {
80
+ border-radius: 5px;
81
+ }
82
+ }
83
+
84
+ &.ab-share-shape-circular {
85
+ a {
86
+ border-radius: 100px;
87
+ }
88
+ }
89
+
90
+ &.ab-share-size-small {
91
+ a {
92
+ font-size: 13px;
93
+ }
94
+ }
95
+
96
+ &.ab-share-size-small.ab-share-icon-only {
97
+ a {
98
+ padding: 7px 6px;
99
+ min-width: 28px;
100
+ }
101
+ }
102
+
103
+ &.ab-share-size-medium {
104
+ a {
105
+ font-size: 16px;
106
+ }
107
+ }
108
+
109
+ &.ab-share-size-large {
110
+ a {
111
+ font-size: 20px;
112
+ }
113
+ }
114
+
115
+ &.ab-share-size-large.ab-share-icon-only {
116
+ a {
117
+ font-size: 26px;
118
+ min-width: 48px;
119
+ }
120
+ }
121
+
122
+ &.ab-share-size-large.ab-share-icon-text {
123
+ i {
124
+ margin-right: 10px;
125
+ }
126
+ }
127
+
128
+ &.ab-share-color-social {
129
+ a {
130
+ color: #fff;
131
+ }
132
+
133
+ .ab-share-twitter {
134
+ background: #1ca1f3;
135
+ }
136
+
137
+ .ab-share-facebook {
138
+ background: #3b5999;
139
+ }
140
+
141
+ .ab-share-google {
142
+ background: #dc4b45;
143
+ }
144
+
145
+ .ab-share-pinterest {
146
+ background: #bd091c;
147
+ }
148
+
149
+ .ab-share-linkedin {
150
+ background: #0077b5;
151
+ }
152
+
153
+ .ab-share-reddit {
154
+ background: #ff4500;
155
+ }
156
+ }
157
+ }
158
+
159
+ .ab-button-right {
160
+ transform: translateX(-100%);
161
+ left: 100%;
162
+ position: relative;
163
+ }
164
+
165
+ .ab-button-center {
166
+ margin: 0 auto;
167
+ }
src/blocks/block-spacer/components/icons.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ icons.dismiss = <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns='http://www.w3.org/2000/svg' width="20" height="20" viewBox="0 0 20 20">
15
+ <path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zM15 13l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z"></path>
16
+ </svg>;
17
+
18
+ export default icons;
src/blocks/block-spacer/components/inspector.js ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ RangeControl,
24
+ ToggleControl,
25
+ SelectControl,
26
+ } = wp.components;
27
+
28
+ /**
29
+ * Create an Inspector Controls wrapper Component
30
+ */
31
+ export default class Inspector extends Component {
32
+
33
+ constructor( props ) {
34
+ super( ...arguments );
35
+ }
36
+
37
+ render() {
38
+
39
+ // Setup the attributes
40
+ const { spacerHeight, spacerDivider, spacerDividerStyle, spacerDividerColor, spacerDividerHeight } = this.props.attributes;
41
+
42
+ // Button size values
43
+ const spacerStyleOptions = [
44
+ { value: 'ab-divider-solid', label: __( 'Solid' ) },
45
+ { value: 'ab-divider-dashed', label: __( 'Dashed' ) },
46
+ { value: 'ab-divider-dotted', label: __( 'Dotted' ) },
47
+ ];
48
+
49
+ return (
50
+ <InspectorControls key="inspector">
51
+ <PanelBody>
52
+ <RangeControl
53
+ label={ __( 'Spacer Height' ) }
54
+ value={ spacerHeight || '' }
55
+ onChange={ ( value ) => this.props.setAttributes( { spacerHeight: value } ) }
56
+ min={ 50 }
57
+ max={ 600 }
58
+ />
59
+
60
+ <ToggleControl
61
+ label={ __( 'Add Divider' ) }
62
+ checked={ spacerDivider }
63
+ onChange={ () => this.props.setAttributes( { spacerDivider: ! spacerDivider } ) }
64
+ />
65
+
66
+ { spacerDivider ?
67
+ <PanelBody>
68
+ <SelectControl
69
+ label={ __( 'Divider Style' ) }
70
+ value={ spacerDividerStyle }
71
+ options={ spacerStyleOptions.map( ({ value, label }) => ( {
72
+ value: value,
73
+ label: label,
74
+ } ) ) }
75
+ onChange={ ( value ) => { this.props.setAttributes( { spacerDividerStyle: value } ) } }
76
+ />
77
+
78
+ <RangeControl
79
+ label={ __( 'Divider Height' ) }
80
+ value={ spacerDividerHeight || '' }
81
+ onChange={ ( value ) => this.props.setAttributes( { spacerDividerHeight: value } ) }
82
+ min={ 1 }
83
+ max={ 5 }
84
+ />
85
+
86
+ <PanelColor
87
+ title={ __( 'Divider Color' ) }
88
+ colorValue={ spacerDividerColor }
89
+ initialOpen={ false }
90
+ >
91
+ <ColorPalette
92
+ label={ __( 'Divider Color' ) }
93
+ value={ spacerDividerColor }
94
+ onChange={ ( value ) => { this.props.setAttributes( { spacerDividerColor: value } ) } }
95
+ colors={[
96
+ { color: '#ddd', name: 'white' },
97
+ { color: '#333', name: 'black' },
98
+ { color: '#3373dc', name: 'royal blue' },
99
+ { color: '#22d25f', name: 'green' },
100
+ { color: '#ffdd57', name: 'yellow' },
101
+ { color: '#ff3860', name: 'pink' },
102
+ { color: '#7941b6', name: 'purple' },
103
+ ]}
104
+ />
105
+ </PanelColor>
106
+ </PanelBody>
107
+ : null }
108
+ </PanelBody>
109
+ </InspectorControls>
110
+ );
111
+ }
112
+ }
src/blocks/block-spacer/components/spacer.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Button Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+
11
+ /**
12
+ * Create a Button wrapper Component
13
+ */
14
+ export default class Spacer extends Component {
15
+
16
+ constructor( props ) {
17
+ super( ...arguments );
18
+ }
19
+
20
+ render() {
21
+
22
+ // Setup the attributes
23
+ const { spacerHeight, spacerDivider, spacerDividerStyle, spacerDividerColor, spacerDividerHeight } = this.props.attributes;
24
+
25
+ return (
26
+ <div
27
+ style={ {
28
+ color: spacerDividerColor
29
+ } }
30
+ className={ classnames(
31
+ this.props.className,
32
+ 'ab-block-spacer',
33
+ spacerDividerStyle,
34
+ { 'ab-spacer-divider': spacerDivider },
35
+ 'ab-divider-size-' + spacerDividerHeight,
36
+ ) }
37
+ >
38
+ { this.props.children }
39
+ </div>
40
+ );
41
+ }
42
+ }
src/blocks/block-spacer/index.js ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Button
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import Spacer from './components/spacer';
9
+ import icons from './components/icons';
10
+ import Resizable from 're-resizable';
11
+
12
+ // Import CSS
13
+ import './styles/style.scss';
14
+ import './styles/editor.scss';
15
+
16
+ // Components
17
+ const { __ } = wp.i18n;
18
+
19
+ // Extend component
20
+ const { Component } = wp.element;
21
+
22
+ // Register block
23
+ const { registerBlockType } = wp.blocks;
24
+
25
+ // Register editor components
26
+ const {
27
+ RichText,
28
+ AlignmentToolbar,
29
+ BlockControls,
30
+ BlockAlignmentToolbar,
31
+ UrlInput,
32
+ } = wp.editor;
33
+
34
+ // Register components
35
+ const {
36
+ Button,
37
+ withFallbackStyles,
38
+ IconButton,
39
+ Dashicon,
40
+ } = wp.components;
41
+
42
+ class ABSpacerBlock extends Component {
43
+
44
+ render() {
45
+
46
+ // Setup the attributes
47
+ const { attributes: { spacerHeight, spacerDivider, spacerDividerStyle, spacerDividerColor }, isSelected, className, setAttributes, toggleSelection, spacerDividerHeight } = this.props;
48
+
49
+ return [
50
+ // Show the block controls on focus
51
+ <Inspector
52
+ { ...this.props }
53
+ />,
54
+ // Show the button markup in the editor
55
+ <Spacer { ...this.props }>
56
+ <Resizable
57
+ className={ classnames( className, 'ab-spacer-handle' ) }
58
+ style={ {
59
+ color: spacerDividerColor
60
+ } }
61
+ size={ {
62
+ width: '100%',
63
+ height: spacerHeight,
64
+ } }
65
+ minWidth= { '100%' }
66
+ maxWidth= { '100%' }
67
+ minHeight= { '100%' }
68
+ handleClasses={ {
69
+ bottomLeft: 'ab-spacer-control__resize-handle',
70
+ } }
71
+ enable={ { top: false, right: false, bottom: true, left: false, topRight: false, bottomRight: false, bottomLeft: true, topLeft: false } }
72
+ onResizeStart={ () => {
73
+ toggleSelection( false );
74
+ } }
75
+ onResizeStop={ ( event, direction, elt, delta ) => {
76
+ setAttributes( {
77
+ spacerHeight: parseInt( spacerHeight + delta.height, 10 ),
78
+ } );
79
+ toggleSelection( true );
80
+ } }
81
+ >
82
+ </Resizable>
83
+ </Spacer>
84
+ ];
85
+ }
86
+ }
87
+
88
+ // Register the block
89
+ registerBlockType( 'atomic-blocks/ab-spacer', {
90
+ title: __( 'AB Spacer' ),
91
+ description: __( 'Add a spacer and divider between your blocks.' ),
92
+ icon: 'image-flip-vertical',
93
+ category: 'atomic-blocks',
94
+ keywords: [
95
+ __( 'spacer' ),
96
+ __( 'divider' ),
97
+ __( 'atomic' ),
98
+ ],
99
+ attributes: {
100
+ spacerHeight: {
101
+ type: 'number',
102
+ default: 30,
103
+ },
104
+ spacerDivider: {
105
+ type: 'boolean',
106
+ default: false
107
+ },
108
+ spacerDividerStyle: {
109
+ type: 'string',
110
+ default: 'ab-divider-solid'
111
+ },
112
+ spacerDividerColor: {
113
+ type: 'string',
114
+ default: '#ddd'
115
+ },
116
+ spacerDividerHeight: {
117
+ type: 'number',
118
+ default: 1,
119
+ },
120
+ },
121
+
122
+ // Render the block components
123
+ edit: ABSpacerBlock,
124
+
125
+ // Save the attributes and markup
126
+ save: function( props ) {
127
+
128
+ // Setup the attributes
129
+ const { spacerHeight, spacerDivider, spacerDividerStyle, spacerDividerColor, spacerDividerHeight } = props.attributes;
130
+
131
+ // Save the block markup for the front end
132
+ return (
133
+ <Spacer { ...props }>
134
+ <hr style={ { height: spacerHeight ? spacerHeight + 'px' : undefined } }></hr>
135
+ </Spacer>
136
+ );
137
+ },
138
+ } );
src/blocks/block-spacer/styles/editor.scss ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .editor-block-list__layout [data-type="atomic-blocks/ab-spacer"] {
6
+ margin-bottom: 1.2em;
7
+ }
8
+
9
+ .ab-block-spacer {
10
+ border: dashed 1px #ddd;
11
+ margin-bottom: 0;
12
+
13
+ .ab-spacer-handle {
14
+ margin-bottom: 0;
15
+ }
16
+
17
+ &.is-selected::before {
18
+ outline: none;
19
+ }
20
+
21
+ .editor-block-list__block-edit {
22
+ outline: 1px dashed #ddd;
23
+ }
24
+
25
+ &.is-selected .editor-block-list__block-edit,
26
+ &.is-hovered .editor-block-list__block-edit {
27
+ outline: 1px dotted #ddd;
28
+ }
29
+
30
+ &.is-selected .editor-block-list__block-edit {
31
+ outline: 1px solid #ddd;
32
+ }
33
+ }
34
+
35
+ .ab-spacer-control__resize-handle {
36
+ background: none;
37
+ background: none;
38
+ border-radius: 0;
39
+ bottom: -15px !important;
40
+ cursor: row-resize !important;
41
+ display: none;
42
+ height: 32px !important;
43
+ left: 0 !important;
44
+ width: 100% !important;
45
+ z-index: 999;
46
+
47
+ .editor-block-list__block[data-type="atomic-blocks/ab-spacer"].is-selected &,
48
+ .editor-block-list__block[data-type="atomic-blocks/ab-spacer"].is-hovered & {
49
+ display: block;
50
+ }
51
+
52
+ &:after {
53
+ background-color: #32373c;
54
+ border-radius: 50px;
55
+ border: 2px solid #fff;
56
+ content: '';
57
+ display: block;
58
+ height: 12px;
59
+ left: 50%;
60
+ margin-left: -8px;
61
+ margin-top: -6px;
62
+ position: absolute;
63
+ top: 50%;
64
+ width: 12px;
65
+ }
66
+ }
src/blocks/block-spacer/styles/style.scss ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Spacer styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-spacer {
7
+ margin: 0 0 1.2em 0;
8
+ position: relative;
9
+
10
+ hr {
11
+ border: none;
12
+ margin: 0;
13
+ }
14
+
15
+ &.ab-spacer-divider:after {
16
+ content: " ";
17
+ width: 100%;
18
+ height: 1px;
19
+ border-top: solid 1px;
20
+ position: absolute;
21
+ top: 50%;
22
+ }
23
+
24
+ &.ab-divider-solid.ab-spacer-divider:after {
25
+ border-top-style: solid;
26
+ }
27
+
28
+ &.ab-divider-dotted.ab-spacer-divider:after {
29
+ border-top-style: dotted;
30
+ }
31
+
32
+ &.ab-divider-dashed.ab-spacer-divider:after {
33
+ border-top-style: dashed;
34
+ }
35
+
36
+ &.ab-divider-size-1.ab-spacer-divider:after {
37
+ border-top-width: 1px;
38
+ }
39
+
40
+ &.ab-divider-size-2.ab-spacer-divider:after {
41
+ border-top-width: 2px;
42
+ }
43
+
44
+ &.ab-divider-size-3.ab-spacer-divider:after {
45
+ border-top-width: 3px;
46
+ }
47
+
48
+ &.ab-divider-size-4.ab-spacer-divider:after {
49
+ border-top-width: 4px;
50
+ }
51
+
52
+ &.ab-divider-size-5.ab-spacer-divider:after {
53
+ border-top-width: 5px;
54
+ }
55
+ }
src/blocks/block-testimonial/components/icons.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const icons = {};
2
+
3
+ icons.upload = <svg width='20px' height='20px' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
4
+ <path d='m77.945 91.453h-72.371c-3.3711 0-5.5742-2.3633-5.5742-5.2422v-55.719c0-3.457 2.1172-6.0703 5.5742-6.0703h44.453v11.051l-38.98-0.003906v45.008h60.977v-17.133l11.988-0.007812v22.875c0 2.8789-2.7812 5.2422-6.0664 5.2422z'
5
+ />
6
+ <path d='m16.543 75.48l23.25-22.324 10.441 9.7773 11.234-14.766 5.5039 10.539 0.039063 16.773z'
7
+ />
8
+ <path d='m28.047 52.992c-3.168 0-5.7422-2.5742-5.7422-5.7461 0-3.1758 2.5742-5.75 5.7422-5.75 3.1797 0 5.7539 2.5742 5.7539 5.75 0 3.1719-2.5742 5.7461-5.7539 5.7461z'
9
+ />
10
+ <path d='m84.043 30.492v22.02h-12.059l-0.015625-22.02h-15.852l21.941-21.945 21.941 21.945z'
11
+ />
12
+ </svg>;
13
+
14
+ export default icons;
src/blocks/block-testimonial/components/inspector.js ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Inspector Controls
3
+ */
4
+
5
+ // Setup the block
6
+ const { __ } = wp.i18n;
7
+ const { Component } = wp.element;
8
+
9
+ // Import block components
10
+ const {
11
+ InspectorControls,
12
+ BlockDescription,
13
+ ColorPalette,
14
+ } = wp.editor;
15
+
16
+ // Import Inspector components
17
+ const {
18
+ Toolbar,
19
+ Button,
20
+ PanelBody,
21
+ PanelRow,
22
+ PanelColor,
23
+ FormToggle,
24
+ RangeControl,
25
+ SelectControl,
26
+ } = wp.components;
27
+
28
+ /**
29
+ * Create an Inspector Controls wrapper Component
30
+ */
31
+ export default class Inspector extends Component {
32
+
33
+ constructor( props ) {
34
+ super( ...arguments );
35
+ }
36
+
37
+ render() {
38
+
39
+ // Cite Alignment Options
40
+ const citeAlignOptions = [
41
+ { value: 'left-aligned', label: __( 'Left Aligned' ) },
42
+ { value: 'right-aligned', label: __( 'Right Aligned' ) },
43
+ ];
44
+
45
+ // Setup the attributes
46
+ const { attributes: { testimonialName, testimonialTitle, testimonialContent, testimonialAlignment, testimonialImgURL, testimonialImgID, testimonialBackgroundColor, testimonialTextColor, testimonialFontSize, testimonialCiteAlign }, isSelected, className, setAttributes } = this.props;
47
+
48
+ return (
49
+ <InspectorControls key="inspector">
50
+ <PanelBody>
51
+ <RangeControl
52
+ label={ __( 'Font Size' ) }
53
+ value={ testimonialFontSize }
54
+ onChange={ ( value ) => this.props.setAttributes( { testimonialFontSize: value } ) }
55
+ min={ 14 }
56
+ max={ 24 }
57
+ step={ 1 }
58
+ />
59
+
60
+ <SelectControl
61
+ label={ __( 'Cite Alignment' ) }
62
+ description={ __( 'Left or right align the cite name and title.' ) }
63
+ options={ citeAlignOptions }
64
+ value={ testimonialCiteAlign }
65
+ onChange={ ( value ) => this.props.setAttributes( { testimonialCiteAlign: value } ) }
66
+ />
67
+
68
+ <PanelColor
69
+ title={ __( 'Background Color' ) }
70
+ colorValue={ testimonialBackgroundColor }
71
+ initialOpen={ false }
72
+ >
73
+ <ColorPalette
74
+ label={ __( 'Background Color' ) }
75
+ value={ testimonialBackgroundColor }
76
+ onChange={ ( value ) => this.props.setAttributes( { testimonialBackgroundColor: value } ) }
77
+ colors={[
78
+ { color: '#00d1b2', name: 'teal' },
79
+ { color: '#3373dc', name: 'royal blue' },
80
+ { color: '#209cef', name: 'sky blue' },
81
+ { color: '#22d25f', name: 'green' },
82
+ { color: '#ffdd57', name: 'yellow' },
83
+ { color: '#ff3860', name: 'pink' },
84
+ { color: '#7941b6', name: 'purple' },
85
+ { color: '#392F43', name: 'black' },
86
+ ]}
87
+ />
88
+ </PanelColor>
89
+
90
+ <PanelColor
91
+ title={ __( 'Text Color' ) }
92
+ colorValue={ testimonialTextColor }
93
+ initialOpen={ false }
94
+ >
95
+ <ColorPalette
96
+ label={ __( 'Text Color' ) }
97
+ value={ testimonialTextColor }
98
+ onChange={ ( value ) => this.props.setAttributes( { testimonialTextColor: value } ) }
99
+ colors={[
100
+ { color: '#fff', name: 'white' },
101
+ { color: '#32373c', name: 'black' },
102
+ ]}
103
+ />
104
+ </PanelColor>
105
+ </PanelBody>
106
+ </InspectorControls>
107
+ );
108
+ }
109
+ }
src/blocks/block-testimonial/components/testimonial.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Testimonial Block Wrapper
3
+ */
4
+
5
+ // Setup the block
6
+ const { Component } = wp.element;
7
+
8
+ // Import block dependencies and components
9
+ import classnames from 'classnames';
10
+ import * as fontSize from './../../../utils/helper';
11
+
12
+ /**
13
+ * Create a Testimonial wrapper Component
14
+ */
15
+ export default class Testimonial extends Component {
16
+
17
+ constructor( props ) {
18
+ super( ...arguments );
19
+ }
20
+
21
+ render() {
22
+
23
+ // Setup the attributes
24
+ const { attributes: { testimonialAlignment, testimonialImgURL, testimonialBackgroundColor, testimonialTextColor, testimonialFontSize, testimonialCiteAlign } } = this.props;
25
+
26
+ return (
27
+ <div
28
+ style={ {
29
+ backgroundColor: testimonialBackgroundColor,
30
+ color: testimonialTextColor,
31
+ } }
32
+ className={ classnames(
33
+ this.props.className,
34
+ testimonialCiteAlign,
35
+ { 'ab-has-avatar': testimonialImgURL },
36
+ 'ab-font-size-' + testimonialFontSize,
37
+ 'ab-block-testimonial'
38
+ ) }
39
+ >
40
+ { this.props.children }
41
+ </div>
42
+ );
43
+ }
44
+ }
src/blocks/block-testimonial/index.js ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * BLOCK: Atomic Blocks Testimonial
3
+ */
4
+
5
+ // Import block dependencies and components
6
+ import classnames from 'classnames';
7
+ import Inspector from './components/inspector';
8
+ import Testimonial from './components/testimonial';
9
+ import icons from './components/icons';
10
+
11
+ // Import CSS
12
+ import './styles/style.scss';
13
+ import './styles/editor.scss';
14
+
15
+ // Internationalization
16
+ const { __ } = wp.i18n;
17
+
18
+ // Extend component
19
+ const { Component } = wp.element;
20
+
21
+ // Register block
22
+ const { registerBlockType } = wp.blocks;
23
+
24
+ // Register editor components
25
+ const {
26
+ RichText,
27
+ AlignmentToolbar,
28
+ BlockControls,
29
+ BlockAlignmentToolbar,
30
+ MediaUpload,
31
+ } = wp.editor;
32
+
33
+ // Register components
34
+ const {
35
+ Button,
36
+ SelectControl,
37
+ } = wp.components;
38
+
39
+ class ABTestimonialBlock extends Component {
40
+
41
+ render() {
42
+
43
+ // Setup the attributes
44
+ const {
45
+ attributes: {
46
+ testimonialName,
47
+ testimonialTitle,
48
+ testimonialContent,
49
+ testimonialAlignment,
50
+ testimonialImgURL,
51
+ testimonialImgID,
52
+ testimonialBackgroundColor,
53
+ testimonialTextColor,
54
+ testimonialFontSize,
55
+ testimonialCiteAlign
56
+ },
57
+ attributes,
58
+ isSelected,
59
+ editable,
60
+ className,
61
+ setAttributes
62
+ } = this.props;
63
+
64
+ const onSelectImage = img => {
65
+ setAttributes( {
66
+ testimonialImgID: img.id,
67
+ testimonialImgURL: img.url,
68
+ } );
69
+ };
70
+
71
+ return [
72
+ // Show the alignment toolbar on focus
73
+ <BlockControls key="controls">
74
+ <AlignmentToolbar
75
+ value={ testimonialAlignment }
76
+ onChange={ ( value ) => setAttributes( { testimonialAlignment: value } ) }
77
+ />
78
+ </BlockControls>,
79
+ // Show the block controls on focus
80
+ <Inspector
81
+ { ...{ setAttributes, ...this.props } }
82
+ />,
83
+ // Show the block markup in the editor
84
+ <Testimonial { ...this.props }>
85
+ <RichText
86
+ tagName="div"
87
+ multiline="p"
88
+ placeholder={ __( 'Add testimonial text...' ) }
89
+ keepPlaceholderOnFocus
90
+ value={ testimonialContent }
91
+ formattingControls={ [ 'bold', 'italic', 'strikethrough', 'link' ] }
92
+ className={ classnames(
93
+ 'ab-testimonial-text'
94
+ ) }
95
+ style={ {
96
+ textAlign: testimonialAlignment,
97
+ } }
98
+ onChange={ ( value ) => setAttributes( { testimonialContent: value } ) }
99
+ inlineToolbar
100
+ />
101
+
102
+ <div class="ab-testimonial-info">
103
+ <div class="ab-testimonial-avatar-wrap">
104
+ <div class="ab-testimonial-image-wrap">
105
+ <MediaUpload
106
+ buttonProps={ {
107
+ className: 'change-image'
108
+ } }
109
+ onSelect={ ( img ) => setAttributes(
110
+ {
111
+ testimonialImgID: img.id,
112
+ testimonialImgURL: img.url,
113
+ }
114
+ ) }
115
+ type="image"
116
+ value={ testimonialImgID }
117
+ render={ ( { open } ) => (
118
+ <Button onClick={ open }>
119
+ { ! testimonialImgID ? icons.upload : <img
120
+ class="ab-testimonial-avatar"
121
+ src={ testimonialImgURL }
122
+ alt="avatar"
123
+ /> }
124
+ </Button>
125
+ ) }
126
+ >
127
+ </MediaUpload>
128
+ </div>
129
+ </div>
130
+
131
+ <RichText
132
+ tagName="h2"
133
+ placeholder={ __( 'Add name' ) }
134
+ keepPlaceholderOnFocus
135
+ value={ testimonialName }
136
+ className='ab-testimonial-name'
137
+ style={ {
138
+ color: testimonialTextColor
139
+ } }
140
+ onChange={ ( value ) => this.props.setAttributes( { testimonialName: value } ) }
141
+ />
142
+
143
+ <RichText
144
+ tagName="small"
145
+ placeholder={ __( 'Add title' ) }
146
+ keepPlaceholderOnFocus
147
+ value={ testimonialTitle }
148
+ className='ab-testimonial-title'
149
+ style={ {
150
+ color: testimonialTextColor
151
+ } }
152
+ onChange={ ( value ) => this.props.setAttributes( { testimonialTitle: value } ) }
153
+ />
154
+ </div>
155
+ </Testimonial>
156
+ ];
157
+ }
158
+ }
159
+
160
+ // Register the block
161
+ registerBlockType( 'atomic-blocks/ab-testimonial', {
162
+ title: __( 'AB Testimonial' ),
163
+ description: __( 'Add a user testimonial with a name and title.' ),
164
+ icon: 'format-quote',
165
+ category: 'atomic-blocks',
166
+ keywords: [
167
+ __( 'testimonial' ),
168
+ __( 'quote' ),
169
+ __( 'atomic' ),
170
+ ],
171
+ attributes: {
172
+ testimonialName: {
173
+ type: 'string',
174
+ selector: '.ab-testimonial-name',
175
+ },
176
+ testimonialTitle: {
177
+ type: 'array',
178
+ selector: '.ab-testimonial-title',
179
+ source: 'children',
180
+ },
181
+ testimonialContent: {
182
+ type: 'array',
183
+ selector: '.ab-testimonial-text',
184
+ source: 'children',
185
+ },
186
+ testimonialAlignment: {
187
+ type: 'string',
188
+ },
189
+ testimonialImgURL: {
190
+ type: 'string',
191
+ source: 'attribute',
192
+ attribute: 'src',
193
+ selector: 'img',
194
+ },
195
+ testimonialImgID: {
196
+ type: 'number',
197
+ },
198
+ testimonialBackgroundColor: {
199
+ type: 'string',
200
+ default: '#f2f2f2'
201
+ },
202
+ testimonialTextColor: {
203
+ type: 'string',
204
+ default: '#32373c'
205
+ },
206
+ testimonialFontSize: {
207
+ type: 'number',
208
+ default: 18,
209
+ },
210
+ testimonialCiteAlign: {
211
+ type: 'string',
212
+ default: 'left-aligned',
213
+ },
214
+ },
215
+
216
+ // Render the block components
217
+ edit: ABTestimonialBlock,
218
+
219
+ // Save the attributes and markup
220
+ save: function( props ) {
221
+
222
+ // Setup the attributes
223
+ const {
224
+ testimonialName,
225
+ testimonialTitle,
226
+ testimonialContent,
227
+ testimonialAlignment,
228
+ testimonialImgURL,
229
+ testimonialImgID,
230
+ testimonialBackgroundColor,
231
+ testimonialTextColor,
232
+ testimonialFontSize,
233
+ testimonialCiteAlign
234
+ } = props.attributes;
235
+
236
+ // Save the block markup for the front end
237
+ return (
238
+ <Testimonial { ...props }>
239
+ <div
240
+ className={ classnames(
241
+ 'ab-testimonial-text',
242
+ ) }
243
+ style={ {
244
+ textAlign: testimonialAlignment,
245
+ } }
246
+ >
247
+ { testimonialContent }
248
+ </div>
249
+
250
+ <div class="ab-testimonial-info">
251
+ { testimonialImgURL && !! testimonialImgURL.length && (
252
+ <div class="ab-testimonial-avatar-wrap">
253
+ <div class="ab-testimonial-image-wrap">
254
+ <img
255
+ class="ab-testimonial-avatar"
256
+ src={ testimonialImgURL }
257
+ alt="avatar"
258
+ />
259
+ </div>
260
+ </div>
261
+ ) }
262
+
263
+ { testimonialName && !! testimonialName.length && (
264
+ <h2 class="ab-testimonial-name"
265
+ style={ {
266
+ color: testimonialTextColor
267
+ } }
268
+ >{ testimonialName }</h2>
269
+ ) }
270
+
271
+ { testimonialTitle && !! testimonialTitle.length && (
272
+ <small class="ab-testimonial-title"
273
+ style={ {
274
+ color: testimonialTextColor
275
+ } }
276
+ >{ testimonialTitle }</small>
277
+ ) }
278
+ </div>
279
+ </Testimonial>
280
+ );
281
+ },
282
+ } );
src/blocks/block-testimonial/styles/editor.scss ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Editor styles for the admin
3
+ */
4
+
5
+ .ab-block-testimonial {
6
+ margin-bottom: 0;
7
+
8
+ .components-button:not(:disabled):not([aria-disabled=true]):focus {
9
+ background: none;
10
+ box-shadow: none;
11
+ }
12
+ }
13
+
14
+ .editor-block-list__layout [data-type="atomic-blocks/ab-testimonial"] {
15
+ margin-bottom: 1.2em;
16
+ }
17
+
18
+ .ab-testimonial-info {
19
+ h2.editor-rich-text__tinymce {
20
+ line-height: 1.2;
21
+ }
22
+
23
+ p.editor-rich-text__tinymce {
24
+ line-height: 1.6;
25
+ }
26
+
27
+ .ab-testimonial-title + .ab-testimonial-title {
28
+ line-height: 1.8;
29
+ }
30
+ }
src/blocks/block-testimonial/styles/style.scss ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Testimonial styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ .ab-block-testimonial {
7
+ background: #f2f2f2;
8
+ color: #293038;
9
+ margin: 0 auto;
10
+ padding: 5%;
11
+ border-radius: 5px;
12
+ margin-bottom: 1.2em;
13
+
14
+ .ab-testimonial-info {
15
+ position: relative;
16
+ display: inline-block;
17
+ width: 100%;
18
+ margin-top: 15px;
19
+ min-height: 55px;
20
+ padding-top: 5px;
21
+ line-height: 1.4;
22
+ }
23
+
24
+ .ab-testimonial-info {
25
+ .blocks-editable {
26
+ padding-left: 0;
27
+ }
28
+
29
+ .ab-testimonial-avatar-wrap {
30
+ position: absolute;
31
+ left: 0;
32
+ top: 0;
33
+ }
34
+ }
35
+
36
+ .ab-testimonial-avatar-wrap + .ab-testimonial-name,
37
+ .ab-testimonial-avatar-wrap + .ab-testimonial-name + .ab-testimonial-title,
38
+ .ab-testimonial-avatar-wrap + .ab-testimonial-title,
39
+ .ab-testimonial-avatar-wrap + .editor-rich-text,
40
+ .ab-testimonial-avatar-wrap + .editor-rich-text + .editor-rich-text {
41
+ margin-left: 70px;
42
+ padding-left: 0;
43
+ }
44
+
45
+ .ab-testimonial-text {
46
+ p {
47
+ line-height: 1.6;
48
+ }
49
+
50
+ a {
51
+ color: inherit;
52
+ box-shadow: 0 -1px 0 inset;
53
+ text-decoration: none;
54
+
55
+ &:hover {
56
+ color: inherit;
57
+ box-shadow: 0 -2px 0 inset;
58
+ }
59
+ }
60
+ }
61
+
62
+ .ab-testimonial-name {
63
+ font-size: 1em;
64
+ font-weight: bold;
65
+ line-height: 1.2;
66
+ margin: 0;
67
+ padding: 0;
68
+ }
69
+
70
+ .ab-testimonial-title {
71
+ opacity: .8;
72
+ }
73
+
74
+ .ab-testimonial-avatar {
75
+ border-radius: 200px;
76
+ max-width: 100px;
77
+ }
78
+
79
+ .ab-testimonial-image-wrap {
80
+ height: 55px;
81
+ width: 55px;
82
+ background: #ddd;
83
+ border-radius: 200px;
84
+ position: relative;
85
+
86
+ button {
87
+ position: absolute;
88
+ left: 19px;
89
+ top: 16px;
90
+ z-index: 50;
91
+ padding: 0;
92
+ }
93
+
94
+ button:focus {
95
+ background: none;
96
+ border: none;
97
+ outline: none;
98
+ box-shadow: none;
99
+ }
100
+
101
+ img {
102
+ object-fit: cover;
103
+ height: 100%;
104
+ width: 100%;
105
+ position: relative;
106
+ z-index: 10;
107
+ border-radius: 40px;
108
+ z-index: 5;
109
+ }
110
+ }
111
+ }
112
+
113
+ #editor .ab-has-avatar {
114
+ .ab-testimonial-image-wrap {
115
+ button {
116
+ top: 0;
117
+ left: 0;
118
+ opacity: 1 !important;
119
+ height: 100%;
120
+ }
121
+
122
+ &:hover {
123
+ cursor: pointer;
124
+
125
+ img {
126
+ opacity: .7;
127
+ }
128
+
129
+ button {
130
+ opacity: 1;
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ .right-aligned {
137
+ .ab-testimonial-info {
138
+ text-align: right;
139
+
140
+ h2 {
141
+ left: 0;
142
+ }
143
+
144
+ .ab-testimonial-name,
145
+ .ab-testimonial-title {
146
+ margin-right: 70px;
147
+ margin-left: 0;
148
+ }
149
+
150
+ .ab-testimonial-avatar-wrap {
151
+ left: auto;
152
+ right: 0;
153
+ }
154
+ }
155
+ }
src/common.scss ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Common styles
3
+ * Loads on front end and back end
4
+ */
5
+
6
+ // Variables
7
+ $black: #293038;
8
+ $accent: #5a3fd6;
9
+ $lightgray: #626e81;
10
+
11
+ /* Font size styles */
12
+ @media only screen and (min-width: 600px) {
13
+ div[class*="wp-block-atomic"].ab-font-size-14 {
14
+ &.ab-block-testimonial p,
15
+ &.ab-block-notice p,
16
+ &.ab-block-profile p,
17
+ &.ab-block-accordion p,
18
+ &.ab-block-cta p {
19
+ font-size: 14px;
20
+ }
21
+ }
22
+
23
+ div[class*="wp-block-atomic"].ab-font-size-15 {
24
+ &.ab-block-testimonial p,
25
+ &.ab-block-notice p,
26
+ &.ab-block-profile p,
27
+ &.ab-block-accordion p,
28
+ &.ab-block-cta p {
29
+ font-size: 15px;
30
+ }
31
+ }
32
+
33
+ div[class*="wp-block-atomic"].ab-font-size-16 {
34
+ &.ab-block-testimonial p,
35
+ &.ab-block-notice p,
36
+ &.ab-block-profile p,
37
+ &.ab-block-accordion p,
38
+ &.ab-block-cta p {
39
+ font-size: 16px;
40
+ }
41
+ }
42
+
43
+ div[class*="wp-block-atomic"].ab-font-size-17 {
44
+ &.ab-block-testimonial p,
45
+ &.ab-block-notice p,
46
+ &.ab-block-profile p,
47
+ &.ab-block-accordion p,
48
+ &.ab-block-cta p {
49
+ font-size: 17px;
50
+ }
51
+ }
52
+
53
+ div[class*="wp-block-atomic"].ab-font-size-18 {
54
+ &.ab-block-testimonial p,
55
+ &.ab-block-notice p,
56
+ &.ab-block-profile p,
57
+ &.ab-block-accordion p,
58
+ &.ab-block-cta p {
59
+ font-size: 18px;
60
+ }
61
+ }
62
+
63
+ div[class*="wp-block-atomic"].ab-font-size-19 {
64
+ &.ab-block-testimonial p,
65
+ &.ab-block-notice p,
66
+ &.ab-block-profile p,
67
+ &.ab-block-accordion p,
68
+ &.ab-block-cta p {
69
+ font-size: 19px;
70
+ }
71
+ }
72
+
73
+ div[class*="wp-block-atomic"].ab-font-size-20 {
74
+ &.ab-block-testimonial p,
75
+ &.ab-block-notice p,
76
+ &.ab-block-profile p,
77
+ &.ab-block-accordion p,
78
+ &.ab-block-cta p {
79
+ font-size: 20px;
80
+ }
81
+ }
82
+
83
+ div[class*="wp-block-atomic"].ab-font-size-21 {
84
+ &.ab-block-testimonial p,
85
+ &.ab-block-notice p,
86
+ &.ab-block-profile p,
87
+ &.ab-block-accordion p,
88
+ &.ab-block-cta p {
89
+ font-size: 21px;
90
+ }
91
+ }
92
+
93
+ div[class*="wp-block-atomic"].ab-font-size-22 {
94
+ &.ab-block-testimonial p,
95
+ &.ab-block-notice p,
96
+ &.ab-block-profile p,
97
+ &.ab-block-accordion p,
98
+ &.ab-block-cta p {
99
+ font-size: 22px;
100
+ }
101
+ }
102
+
103
+ div[class*="wp-block-atomic"].ab-font-size-23 {
104
+ &.ab-block-testimonial p,
105
+ &.ab-block-notice p,
106
+ &.ab-block-profile p,
107
+ &.ab-block-accordion p,
108
+ &.ab-block-cta p {
109
+ font-size: 23px;
110
+ }
111
+ }
112
+
113
+ div[class*="wp-block-atomic"].ab-font-size-24 {
114
+ &.ab-block-testimonial p,
115
+ &.ab-block-notice p,
116
+ &.ab-block-profile p,
117
+ &.ab-block-accordion p,
118
+ &.ab-block-cta p {
119
+ font-size: 24px;
120
+ }
121
+ }
122
+ }
123
+
124
+ .center {
125
+ text-align: center;
126
+ }
127
+
128
+ .left {
129
+ text-align: left;
130
+ }
131
+
132
+ .right {
133
+ text-align: right;
134
+ }
135
+
136
+ @media only screen and (min-width: 600px) {
137
+ .wp-block-columns .layout-column-1,
138
+ .wp-block-columns .layout-column-2 {
139
+ margin-right: 5%;
140
+ }
141
+ }
142
+
143
+ .wp-block-image {
144
+ margin-bottom: 1.2em;
145
+ }
146
+
147
+ .ab-text-link {
148
+ color: inherit;
149
+ box-shadow: 0 -1px 0 inset;
150
+ text-decoration: none;
151
+ transition: .3s ease;
152
+
153
+ &:hover {
154
+ color: inherit;
155
+ box-shadow: 0 -2px 0 inset;
156
+ color: $accent;
157
+ }
158
+ }
src/utils/helper.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Helper Functions
3
+ */
4
+
5
+ // Import helper dependencies
6
+ import md5 from 'md5';
7
+
8
+ // Calculate the font size
9
+ export function fontRatioToClass( ratio ) {
10
+ return ( ratio === 0 || ratio === 50 ) ?
11
+ null :
12
+ 'font-size-' + ( 1 * Math.round( ratio / 1 ) );
13
+ }
14
+
15
+ // Generate a unique ID for the notice block
16
+ export function generateUniqueID( input ) {
17
+ return md5( input ).substr( 0, 6 );
18
+ }