frontblocks-reading-time-option.jsx
1 month ago
frontblocks-reading-time.css
8 months ago
frontblocks-reading-time.js
1 month ago
frontblocks-reading-time.js
315 lines
| 1 | "use strict"; |
| 2 | |
| 3 | function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } |
| 4 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } |
| 5 | function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } |
| 6 | function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } |
| 7 | function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } |
| 8 | function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } |
| 9 | var registerBlockType = wp.blocks.registerBlockType; |
| 10 | var _wp$element = wp.element, |
| 11 | Fragment = _wp$element.Fragment, |
| 12 | useState = _wp$element.useState, |
| 13 | useEffect = _wp$element.useEffect; |
| 14 | var _wp$blockEditor = wp.blockEditor, |
| 15 | InspectorControls = _wp$blockEditor.InspectorControls, |
| 16 | useBlockProps = _wp$blockEditor.useBlockProps, |
| 17 | ColorPalette = _wp$blockEditor.ColorPalette; |
| 18 | var _wp$components = wp.components, |
| 19 | PanelBody = _wp$components.PanelBody, |
| 20 | ToggleControl = _wp$components.ToggleControl, |
| 21 | TextControl = _wp$components.TextControl, |
| 22 | RangeControl = _wp$components.RangeControl, |
| 23 | SelectControl = _wp$components.SelectControl; |
| 24 | var __ = wp.i18n.__; |
| 25 | var useSelect = wp.data.useSelect; |
| 26 | |
| 27 | /** |
| 28 | * Calculate reading time for content. |
| 29 | * |
| 30 | * @param {string} content - Post content. |
| 31 | * @return {number} Reading time in minutes. |
| 32 | */ |
| 33 | function calculateReadingTime(content) { |
| 34 | // Ensure content is a string. |
| 35 | if (!content || typeof content !== 'string') { |
| 36 | return 5; |
| 37 | } |
| 38 | |
| 39 | // Strip HTML tags. |
| 40 | var textContent = content.replace(/<[^>]*>/g, ''); |
| 41 | |
| 42 | // Count words. |
| 43 | var wordCount = textContent.split(/\s+/).filter(function (word) { |
| 44 | return word.length > 0; |
| 45 | }).length; |
| 46 | |
| 47 | // Average reading speed: 225 words per minute. |
| 48 | var readingTime = Math.ceil(wordCount / 225); |
| 49 | |
| 50 | // Minimum 1 minute. |
| 51 | return Math.max(1, readingTime); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Edit component for Reading Time block. |
| 56 | * |
| 57 | * @param {Object} props - Block properties. |
| 58 | * @return {JSX.Element} Block edit component. |
| 59 | */ |
| 60 | function ReadingTimeEdit(props) { |
| 61 | var attributes = props.attributes, |
| 62 | setAttributes = props.setAttributes; |
| 63 | var postId = attributes.postId, |
| 64 | showIcon = attributes.showIcon, |
| 65 | prefix = attributes.prefix, |
| 66 | suffix = attributes.suffix, |
| 67 | textColor = attributes.textColor, |
| 68 | backgroundColor = attributes.backgroundColor, |
| 69 | fontSize = attributes.fontSize, |
| 70 | iconColor = attributes.iconColor, |
| 71 | alignment = attributes.alignment, |
| 72 | padding = attributes.padding, |
| 73 | borderRadius = attributes.borderRadius; |
| 74 | var _useState = useState(5), |
| 75 | _useState2 = _slicedToArray(_useState, 2), |
| 76 | readingTime = _useState2[0], |
| 77 | setReadingTime = _useState2[1]; |
| 78 | var blockProps = useBlockProps(); |
| 79 | |
| 80 | // Get current post content and calculate reading time. |
| 81 | useEffect(function () { |
| 82 | try { |
| 83 | var _wp$data$select, _wp$data$select$getEd; |
| 84 | // Try to get the post content from the editor. |
| 85 | var postContent = (_wp$data$select = wp.data.select('core/editor')) === null || _wp$data$select === void 0 || (_wp$data$select$getEd = _wp$data$select.getEditedPostContent) === null || _wp$data$select$getEd === void 0 ? void 0 : _wp$data$select$getEd.call(_wp$data$select); |
| 86 | if (postContent && typeof postContent === 'string') { |
| 87 | var calculatedTime = calculateReadingTime(postContent); |
| 88 | setReadingTime(calculatedTime); |
| 89 | } else { |
| 90 | setReadingTime(5); |
| 91 | } |
| 92 | } catch (error) { |
| 93 | // If there's any error, just use default value. |
| 94 | setReadingTime(5); |
| 95 | } |
| 96 | }, [postId]); |
| 97 | var styleVars = { |
| 98 | '--frbl-text-color': textColor, |
| 99 | '--frbl-bg-color': backgroundColor, |
| 100 | '--frbl-font-size': "".concat(fontSize, "px"), |
| 101 | '--frbl-icon-color': iconColor, |
| 102 | '--frbl-padding': "".concat(padding, "px"), |
| 103 | '--frbl-border-radius': "".concat(borderRadius, "px") |
| 104 | }; |
| 105 | var iconSvg = /*#__PURE__*/React.createElement("svg", { |
| 106 | xmlns: "http://www.w3.org/2000/svg", |
| 107 | width: "1em", |
| 108 | height: "1em", |
| 109 | viewBox: "0 0 24 24", |
| 110 | fill: "none", |
| 111 | stroke: "currentColor", |
| 112 | strokeWidth: "2", |
| 113 | strokeLinecap: "round", |
| 114 | strokeLinejoin: "round" |
| 115 | }, /*#__PURE__*/React.createElement("circle", { |
| 116 | cx: "12", |
| 117 | cy: "12", |
| 118 | r: "10" |
| 119 | }), /*#__PURE__*/React.createElement("polyline", { |
| 120 | points: "12 6 12 12 16 14" |
| 121 | })); |
| 122 | var wrapperClass = "frbl-reading-time-wrapper align-".concat(alignment); |
| 123 | return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(InspectorControls, null, /*#__PURE__*/React.createElement(PanelBody, { |
| 124 | title: __('General Settings', 'frontblocks'), |
| 125 | initialOpen: true |
| 126 | }, /*#__PURE__*/React.createElement(ToggleControl, { |
| 127 | label: __('Show Icon', 'frontblocks'), |
| 128 | checked: showIcon, |
| 129 | onChange: function onChange(value) { |
| 130 | return setAttributes({ |
| 131 | showIcon: value |
| 132 | }); |
| 133 | } |
| 134 | }), /*#__PURE__*/React.createElement(TextControl, { |
| 135 | label: __('Prefix', 'frontblocks'), |
| 136 | value: prefix, |
| 137 | onChange: function onChange(value) { |
| 138 | return setAttributes({ |
| 139 | prefix: value |
| 140 | }); |
| 141 | }, |
| 142 | help: __('Text to display before the reading time number. Example: "This blog takes"', 'frontblocks'), |
| 143 | placeholder: __('Example: This blog takes', 'frontblocks') |
| 144 | }), /*#__PURE__*/React.createElement(TextControl, { |
| 145 | label: __('Suffix', 'frontblocks'), |
| 146 | value: suffix, |
| 147 | onChange: function onChange(value) { |
| 148 | return setAttributes({ |
| 149 | suffix: value |
| 150 | }); |
| 151 | }, |
| 152 | help: __('Text to display after the reading time number. Example: "minutes to read"', 'frontblocks'), |
| 153 | placeholder: __('Example: minutes to read', 'frontblocks') |
| 154 | }), /*#__PURE__*/React.createElement(SelectControl, { |
| 155 | label: __('Alignment', 'frontblocks'), |
| 156 | value: alignment, |
| 157 | options: [{ |
| 158 | label: __('Left', 'frontblocks'), |
| 159 | value: 'left' |
| 160 | }, { |
| 161 | label: __('Center', 'frontblocks'), |
| 162 | value: 'center' |
| 163 | }, { |
| 164 | label: __('Right', 'frontblocks'), |
| 165 | value: 'right' |
| 166 | }], |
| 167 | onChange: function onChange(value) { |
| 168 | return setAttributes({ |
| 169 | alignment: value |
| 170 | }); |
| 171 | } |
| 172 | })), /*#__PURE__*/React.createElement(PanelBody, { |
| 173 | title: __('Style Settings', 'frontblocks'), |
| 174 | initialOpen: false |
| 175 | }, /*#__PURE__*/React.createElement(RangeControl, { |
| 176 | label: __('Font Size', 'frontblocks'), |
| 177 | value: fontSize, |
| 178 | onChange: function onChange(value) { |
| 179 | return setAttributes({ |
| 180 | fontSize: value |
| 181 | }); |
| 182 | }, |
| 183 | min: 10, |
| 184 | max: 48 |
| 185 | }), /*#__PURE__*/React.createElement("p", { |
| 186 | style: { |
| 187 | marginBottom: '8px', |
| 188 | fontWeight: '500' |
| 189 | } |
| 190 | }, __('Text Color', 'frontblocks')), /*#__PURE__*/React.createElement(ColorPalette, { |
| 191 | value: textColor, |
| 192 | onChange: function onChange(value) { |
| 193 | return setAttributes({ |
| 194 | textColor: value || 'inherit' |
| 195 | }); |
| 196 | } |
| 197 | }), /*#__PURE__*/React.createElement("p", { |
| 198 | style: { |
| 199 | marginBottom: '8px', |
| 200 | fontWeight: '500' |
| 201 | } |
| 202 | }, __('Background Color', 'frontblocks')), /*#__PURE__*/React.createElement(ColorPalette, { |
| 203 | value: backgroundColor, |
| 204 | onChange: function onChange(value) { |
| 205 | return setAttributes({ |
| 206 | backgroundColor: value || 'transparent' |
| 207 | }); |
| 208 | } |
| 209 | }), /*#__PURE__*/React.createElement("p", { |
| 210 | style: { |
| 211 | marginBottom: '8px', |
| 212 | fontWeight: '500' |
| 213 | } |
| 214 | }, __('Icon Color', 'frontblocks')), /*#__PURE__*/React.createElement(ColorPalette, { |
| 215 | value: iconColor, |
| 216 | onChange: function onChange(value) { |
| 217 | return setAttributes({ |
| 218 | iconColor: value || 'currentColor' |
| 219 | }); |
| 220 | } |
| 221 | }), /*#__PURE__*/React.createElement(RangeControl, { |
| 222 | label: __('Padding', 'frontblocks'), |
| 223 | value: padding, |
| 224 | onChange: function onChange(value) { |
| 225 | return setAttributes({ |
| 226 | padding: value |
| 227 | }); |
| 228 | }, |
| 229 | min: 0, |
| 230 | max: 50 |
| 231 | }), /*#__PURE__*/React.createElement(RangeControl, { |
| 232 | label: __('Border Radius', 'frontblocks'), |
| 233 | value: borderRadius, |
| 234 | onChange: function onChange(value) { |
| 235 | return setAttributes({ |
| 236 | borderRadius: value |
| 237 | }); |
| 238 | }, |
| 239 | min: 0, |
| 240 | max: 50 |
| 241 | }))), /*#__PURE__*/React.createElement("div", blockProps, /*#__PURE__*/React.createElement("div", { |
| 242 | className: wrapperClass |
| 243 | }, /*#__PURE__*/React.createElement("div", { |
| 244 | className: "frbl-reading-time", |
| 245 | style: styleVars |
| 246 | }, showIcon && /*#__PURE__*/React.createElement("span", { |
| 247 | className: "frbl-reading-time-icon" |
| 248 | }, iconSvg), /*#__PURE__*/React.createElement("span", { |
| 249 | className: "frbl-reading-time-text" |
| 250 | }, prefix && "".concat(prefix, " "), readingTime, suffix && " ".concat(suffix)))))); |
| 251 | } |
| 252 | |
| 253 | // Register the block. |
| 254 | registerBlockType('frontblocks/reading-time', { |
| 255 | title: __('FrontBlocks - Reading Time', 'frontblocks'), |
| 256 | description: __('Display estimated reading time for the current post or page.', 'frontblocks'), |
| 257 | category: 'common', |
| 258 | icon: 'clock', |
| 259 | keywords: [__('reading', 'frontblocks'), __('time', 'frontblocks'), __('duration', 'frontblocks'), __('blog', 'frontblocks')], |
| 260 | attributes: { |
| 261 | postId: { |
| 262 | type: 'number', |
| 263 | default: 0 |
| 264 | }, |
| 265 | showIcon: { |
| 266 | type: 'boolean', |
| 267 | default: true |
| 268 | }, |
| 269 | prefix: { |
| 270 | type: 'string', |
| 271 | default: '' |
| 272 | }, |
| 273 | suffix: { |
| 274 | type: 'string', |
| 275 | default: 'min' |
| 276 | }, |
| 277 | className: { |
| 278 | type: 'string', |
| 279 | default: '' |
| 280 | }, |
| 281 | textColor: { |
| 282 | type: 'string', |
| 283 | default: 'inherit' |
| 284 | }, |
| 285 | backgroundColor: { |
| 286 | type: 'string', |
| 287 | default: 'transparent' |
| 288 | }, |
| 289 | fontSize: { |
| 290 | type: 'number', |
| 291 | default: 16 |
| 292 | }, |
| 293 | iconColor: { |
| 294 | type: 'string', |
| 295 | default: 'currentColor' |
| 296 | }, |
| 297 | alignment: { |
| 298 | type: 'string', |
| 299 | default: 'left' |
| 300 | }, |
| 301 | padding: { |
| 302 | type: 'number', |
| 303 | default: 10 |
| 304 | }, |
| 305 | borderRadius: { |
| 306 | type: 'number', |
| 307 | default: 5 |
| 308 | } |
| 309 | }, |
| 310 | edit: ReadingTimeEdit, |
| 311 | save: function save() { |
| 312 | return null; // Dynamic block, render on server side. |
| 313 | } |
| 314 | }); |
| 315 |