From d5e49fecb898370b6107496681c63bfd8ff8376c Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sun, 3 Dec 2017 15:55:09 -0600 Subject: [PATCH 01/56] Start a blocks scaffold --- blocks/blocks.php | 16 ++++++++++++++++ blocks/group-list/block.js | 34 ++++++++++++++++++++++++++++++++++ blocks/group-list/editor.css | 5 +++++ phpcs.xml.dist | 1 + vs_meetup.php | 2 ++ 5 files changed, 58 insertions(+) create mode 100755 blocks/blocks.php create mode 100755 blocks/group-list/block.js create mode 100755 blocks/group-list/editor.css diff --git a/blocks/blocks.php b/blocks/blocks.php new file mode 100755 index 0000000..fb88b29 --- /dev/null +++ b/blocks/blocks.php @@ -0,0 +1,16 @@ + + diff --git a/vs_meetup.php b/vs_meetup.php index 120575f..54f273c 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -51,3 +51,5 @@ function meetup_widgets_start() { return register_widget( 'VsMeetUserListWidget' ); } ); + +require_once( 'blocks/blocks.php' ); From e57dc884f9257bd53cca0914473663516502597e Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 4 Dec 2017 12:24:52 -0600 Subject: [PATCH 02/56] Set up with babel --- .babelrc | 28 ++++++++++++++++++++++++++++ .gitignore | 12 ++++++++++++ blocks/blocks.php | 10 ++++------ blocks/group-list/block.js | 34 ---------------------------------- blocks/group-list/editor.css | 5 ----- blocks/src/group-list.js | 21 +++++++++++++++++++++ blocks/src/index.js | 6 ++++++ blocks/src/single.js | 21 +++++++++++++++++++++ package.json | 20 ++++++++++++++++++++ 9 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 .babelrc delete mode 100755 blocks/group-list/block.js delete mode 100755 blocks/group-list/editor.css create mode 100755 blocks/src/group-list.js create mode 100644 blocks/src/index.js create mode 100644 blocks/src/single.js create mode 100644 package.json diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..197676b --- /dev/null +++ b/.babelrc @@ -0,0 +1,28 @@ +{ + "presets": [ + [ "env", { + "modules": false, + "targets": { + "browsers": [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", + "last 2 Opera versions", + "last 2 iOS versions", + "last 1 Android version", + "last 1 ChromeAndroid version", + "ie 11", + "> 1%" + ] + } + } ] + ], + "plugins": [ + "lodash", + "transform-object-rest-spread", + [ "transform-react-jsx", { + "pragma": "wp.element.createElement" + } ] + ] +} diff --git a/.gitignore b/.gitignore index 81628f8..3296222 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ +# Directories/files used for wp.org .svn screenshot-1.png screenshot-2.png + +# Directories/files that may be generated by this project +node_modules +blocks/build + +# Directories/files that may appear in your environment +.DS_Store +*.log +phpcs.xml +yarn.lock +package-lock.json diff --git a/blocks/blocks.php b/blocks/blocks.php index fb88b29..0be0993 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -2,15 +2,13 @@ function vs_meetup_widgets_enqueue_block_assets() { $dir = dirname( __FILE__ ); - $block_js = 'group-list/block.js'; - $editor_css = 'group-list/editor.css'; - wp_enqueue_script( 'group-list-block', plugins_url( $block_js, __FILE__ ), array( + wp_enqueue_script( 'group-list-block', plugins_url( 'build/index.js', __FILE__ ), array( 'wp-blocks', 'wp-i18n', 'wp-element', - ), filemtime( "$dir/$block_js" ) ); - wp_enqueue_style( 'group-list-block', plugins_url( $editor_css, __FILE__ ), array( + ), filemtime( "$dir/build/block.js" ) ); + wp_enqueue_style( 'group-list-block', plugins_url( 'src/editor.css', __FILE__ ), array( 'wp-blocks', - ), filemtime( "$dir/$editor_css" ) ); + ), filemtime( "$dir/src/editor.css" ) ); } add_action( 'enqueue_block_editor_assets', 'vs_meetup_widgets_enqueue_block_assets' ); diff --git a/blocks/group-list/block.js b/blocks/group-list/block.js deleted file mode 100755 index 926aee1..0000000 --- a/blocks/group-list/block.js +++ /dev/null @@ -1,34 +0,0 @@ -( function( wp ) { - var el = wp.element.createElement; - var __ = wp.i18n.__; - - // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API - wp.blocks.registerBlockType( 'meetup-widgets/group-list', { - title: __( 'Meetup Widgets', 'meetup-widgets' ), - - icon: 'editor-ul', - - category: 'embed', - - // Remove to make block editable in HTML mode. - supportHTML: false, - - edit: function( props ) { - return el( - 'p', - { className: props.className }, - __( 'Replace with your content!', 'meetup-widgets' ) - ); - }, - - save: function() { - return el( - 'p', - {}, - __( 'Replace with your content!', 'meetup-widgets' ) - ); - } - } ); -} )( - window.wp -); diff --git a/blocks/group-list/editor.css b/blocks/group-list/editor.css deleted file mode 100755 index 83c2b07..0000000 --- a/blocks/group-list/editor.css +++ /dev/null @@ -1,5 +0,0 @@ -.wp-block-meetup-widgets-group-list { - /* Replace with your styles. */ - background-color: #000; - color: #fff; -} diff --git a/blocks/src/group-list.js b/blocks/src/group-list.js new file mode 100755 index 0000000..e42d03b --- /dev/null +++ b/blocks/src/group-list.js @@ -0,0 +1,21 @@ +const { __ } = wp.i18n; + +// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API +export default { + title: __( 'Meetup.com List', 'meetup-widgets' ), + + icon: 'editor-ul', + + category: 'embed', + + // Remove to make block editable in HTML mode. + supportHTML: false, + + edit: props => { + return __( 'Group list here! šŸŽŠ', 'meetup-widgets' ); + }, + + save: () => { + return null + } +}; diff --git a/blocks/src/index.js b/blocks/src/index.js new file mode 100644 index 0000000..8551baf --- /dev/null +++ b/blocks/src/index.js @@ -0,0 +1,6 @@ +const { registerBlockType } = wp.blocks; +import singleWidgetOptions from './single'; +import groupWidgetOptions from './group-list'; + +registerBlockType( 'meetup-widgets/single', singleWidgetOptions ); +registerBlockType( 'meetup-widgets/group-list', groupWidgetOptions ); diff --git a/blocks/src/single.js b/blocks/src/single.js new file mode 100644 index 0000000..90cc07a --- /dev/null +++ b/blocks/src/single.js @@ -0,0 +1,21 @@ +const { __ } = wp.i18n; + +// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API +export default { + title: __( 'Meetup.com Event', 'meetup-widgets' ), + + icon: 'groups', + + category: 'embed', + + // Remove to make block editable in HTML mode. + supportHTML: false, + + edit: props => { + return __( 'Single event here! šŸŽŠ', 'meetup-widgets' ); + }, + + save: () => { + return null + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..9a9ca98 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "meetup-widgets", + "version": "2.3.0", + "description": "Adds widgets displaying information from a meetup.com group.", + "main": "index.js", + "scripts": { + "build": "NODE_ENV=production babel blocks/src/index.js --out-file blocks/build/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ryelle/Meetup-Widgets.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/ryelle/Meetup-Widgets/issues" + }, + "homepage": "https://github.com/ryelle/Meetup-Widgets#readme" +} From 8e19a0a4ef63a4caf74b0c04dd9133c85f5cb06e Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 4 Dec 2017 14:08:44 -0600 Subject: [PATCH 03/56] Add webpack --- .babelrc | 45 +++++++++++++++++++++------------------ blocks/blocks.php | 6 +++--- blocks/src/index.js | 6 ++++-- blocks/src/style.css | 0 package.json | 14 ++++++++++++- webpack.config.js | 50 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 blocks/src/style.css create mode 100644 webpack.config.js diff --git a/.babelrc b/.babelrc index 197676b..484371e 100644 --- a/.babelrc +++ b/.babelrc @@ -1,28 +1,33 @@ { "presets": [ - [ "env", { - "modules": false, - "targets": { - "browsers": [ - "last 2 Chrome versions", - "last 2 Firefox versions", - "last 2 Safari versions", - "last 2 Edge versions", - "last 2 Opera versions", - "last 2 iOS versions", - "last 1 Android version", - "last 1 ChromeAndroid version", - "ie 11", - "> 1%" - ] + [ + "env", + { + "modules": false, + "targets": { + "browsers": [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", + "last 2 Opera versions", + "last 2 iOS versions", + "last 1 Android version", + "last 1 ChromeAndroid version", + "ie 11", + "> 1%" + ] + } } - } ] + ] ], "plugins": [ - "lodash", "transform-object-rest-spread", - [ "transform-react-jsx", { - "pragma": "wp.element.createElement" - } ] + [ + "transform-react-jsx", + { + "pragma": "wp.element.createElement" + } + ] ] } diff --git a/blocks/blocks.php b/blocks/blocks.php index 0be0993..5d9631a 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -6,9 +6,9 @@ function vs_meetup_widgets_enqueue_block_assets() { 'wp-blocks', 'wp-i18n', 'wp-element', - ), filemtime( "$dir/build/block.js" ) ); - wp_enqueue_style( 'group-list-block', plugins_url( 'src/editor.css', __FILE__ ), array( + ), filemtime( "$dir/build/index.js" ) ); + wp_enqueue_style( 'group-list-block', plugins_url( 'build/editor.css', __FILE__ ), array( 'wp-blocks', - ), filemtime( "$dir/src/editor.css" ) ); + ), filemtime( "$dir/build/editor.css" ) ); } add_action( 'enqueue_block_editor_assets', 'vs_meetup_widgets_enqueue_block_assets' ); diff --git a/blocks/src/index.js b/blocks/src/index.js index 8551baf..bec145d 100644 --- a/blocks/src/index.js +++ b/blocks/src/index.js @@ -2,5 +2,7 @@ const { registerBlockType } = wp.blocks; import singleWidgetOptions from './single'; import groupWidgetOptions from './group-list'; -registerBlockType( 'meetup-widgets/single', singleWidgetOptions ); -registerBlockType( 'meetup-widgets/group-list', groupWidgetOptions ); +import './style.css'; + +registerBlockType('meetup-widgets/single', singleWidgetOptions); +registerBlockType('meetup-widgets/group-list', groupWidgetOptions); diff --git a/blocks/src/style.css b/blocks/src/style.css new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 9a9ca98..34b0008 100644 --- a/package.json +++ b/package.json @@ -16,5 +16,17 @@ "bugs": { "url": "https://github.com/ryelle/Meetup-Widgets/issues" }, - "homepage": "https://github.com/ryelle/Meetup-Widgets#readme" + "homepage": "https://github.com/ryelle/Meetup-Widgets#readme", + "devDependencies": { + "autoprefixer": "^7.2.1", + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "babel-plugin-transform-object-rest-spread": "^6.26.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-preset-env": "^1.6.1", + "extract-text-webpack-plugin": "^3.0.2", + "postcss-loader": "^2.0.9", + "raw-loader": "^0.5.1", + "webpack": "^3.10.0" + } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..eacff01 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,50 @@ +const webpack = require('webpack'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); + +// CSS loader for styles specific to blocks in general. +const blocksCSSPlugin = new ExtractTextPlugin({ + filename: './blocks/build/style.css', +}); + +// Configuration for the ExtractTextPlugin. +const extractConfig = { + use: [ + { loader: 'raw-loader' }, + { + loader: 'postcss-loader', + options: { + plugins: [require('autoprefixer')], + }, + }, + ], +}; + +module.exports = { + entry: __dirname + '/blocks/src/index.js', + output: { + filename: 'blocks/build/index.js', + path: __dirname, + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: 'babel-loader', + }, + { + test: /style\.css$/, + include: [/blocks/], + use: blocksCSSPlugin.extract(extractConfig), + }, + ], + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify( + process.env.NODE_ENV || 'development', + ), + }), + blocksCSSPlugin, + ], +}; From 1366fa4ebd8ccca09aca1388e74c2391e5d0570a Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Fri, 8 Dec 2017 18:57:42 -0500 Subject: [PATCH 04/56] Build out block controls --- blocks/src/group-list.js | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/blocks/src/group-list.js b/blocks/src/group-list.js index e42d03b..38a94e8 100755 --- a/blocks/src/group-list.js +++ b/blocks/src/group-list.js @@ -1,8 +1,15 @@ const { __ } = wp.i18n; +const { + BlockDescription, + InspectorControls, + InspectorControls: { TextControl }, +} = wp.blocks; + +const translate = str => __(str, 'meetup-widgets'); // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { - title: __( 'Meetup.com List', 'meetup-widgets' ), + title: translate('Meetup.com List'), icon: 'editor-ul', @@ -11,11 +18,28 @@ export default { // Remove to make block editable in HTML mode. supportHTML: false, - edit: props => { - return __( 'Group list here! šŸŽŠ', 'meetup-widgets' ); + edit: ({ attributes, setAttributes, focus, setFocus }) => { + console.log(attributes); + return [ +
+

{translate('Text!')}

+
, + !!focus && ( + + +

{translate('Meetup.com ?')}

+
+ setAttributes({ test: value })} + /> +
+ ), + ]; }, save: () => { - return null - } + return null; + }, }; From 34e0cb1ca7a99727ff8d3d88a777c4ef1ac5bf99 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 25 Dec 2017 10:00:32 -0500 Subject: [PATCH 05/56] Add file & function comments for PHPCS --- blocks/blocks.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/blocks/blocks.php b/blocks/blocks.php index 5d9631a..d2da55b 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -1,5 +1,14 @@ Date: Mon, 25 Dec 2017 10:16:55 -0500 Subject: [PATCH 06/56] Add hot reloading --- blocks/blocks.php | 22 ++++++++++++++-------- package.json | 3 ++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/blocks/blocks.php b/blocks/blocks.php index d2da55b..3b525bf 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -11,13 +11,19 @@ */ function vs_meetup_widgets_enqueue_block_assets() { $dir = dirname( __FILE__ ); - wp_enqueue_script( 'group-list-block', plugins_url( 'build/index.js', __FILE__ ), array( - 'wp-blocks', - 'wp-i18n', - 'wp-element', - ), filemtime( "$dir/build/index.js" ) ); - wp_enqueue_style( 'group-list-block', plugins_url( 'build/editor.css', __FILE__ ), array( - 'wp-blocks', - ), filemtime( "$dir/build/editor.css" ) ); + $js_file = 'http://localhost:8081/blocks/build/index.js'; + $css_file = 'http://localhost:8081/blocks/build/style.css'; + $js_version = false; + $css_version = false; + + if ( ! WP_DEBUG ) { + $js_file = plugins_url( 'build/index.js', __FILE__ ); + $css_file = plugins_url( 'build/editor.css', __FILE__ ); + $js_version = filemtime( "$dir/build/index.js" ); + $css_version = filemtime( "$dir/build/editor.css" ); + } + + wp_enqueue_script( 'meetup-blocks', $js_file, [ 'wp-blocks', 'wp-i18n', 'wp-element' ], $js_version ); + wp_enqueue_style( 'meetup-blocks', $css_file, [ 'wp-blocks' ], $css_version ); } add_action( 'enqueue_block_editor_assets', 'vs_meetup_widgets_enqueue_block_assets' ); diff --git a/package.json b/package.json index 34b0008..fd89488 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Adds widgets displaying information from a meetup.com group.", "main": "index.js", "scripts": { - "build": "NODE_ENV=production babel blocks/src/index.js --out-file blocks/build/index.js", + "start": "webpack-dev-server", + "build": "NODE_ENV=production webpack", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { From b7ead1093a8329d19fe34ce6b82f64d0389791b9 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 25 Dec 2017 10:27:34 -0500 Subject: [PATCH 07/56] Update build CSS name --- blocks/blocks.php | 2 +- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/blocks.php b/blocks/blocks.php index 3b525bf..c4153b1 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -12,7 +12,7 @@ function vs_meetup_widgets_enqueue_block_assets() { $dir = dirname( __FILE__ ); $js_file = 'http://localhost:8081/blocks/build/index.js'; - $css_file = 'http://localhost:8081/blocks/build/style.css'; + $css_file = 'http://localhost:8081/blocks/build/editor.css'; $js_version = false; $css_version = false; diff --git a/webpack.config.js b/webpack.config.js index eacff01..492154d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,7 +3,7 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin'); // CSS loader for styles specific to blocks in general. const blocksCSSPlugin = new ExtractTextPlugin({ - filename: './blocks/build/style.css', + filename: './blocks/build/editor.css', }); // Configuration for the ExtractTextPlugin. From 2bd4104b19314e39bc564625e434703847b34b24 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 25 Dec 2017 16:56:01 -0500 Subject: [PATCH 08/56] Set port 8081 as a config param --- package.json | 3 ++- webpack.config.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fd89488..360cc3a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "extract-text-webpack-plugin": "^3.0.2", "postcss-loader": "^2.0.9", "raw-loader": "^0.5.1", - "webpack": "^3.10.0" + "webpack": "^3.10.0", + "webpack-dev-server": "^2.9.7" } } diff --git a/webpack.config.js b/webpack.config.js index 492154d..5057875 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -47,4 +47,7 @@ module.exports = { }), blocksCSSPlugin, ], + devServer: { + port: 8081, + }, }; From 4a65924352105123c9dc896514be88723e1d6b49 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 28 Dec 2017 20:14:21 -0500 Subject: [PATCH 09/56] Add config for the group block, set up for fetching data from API --- blocks/src/group-list.js | 101 ++++++++++++++++++++++++++++++++------- package.json | 5 ++ 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/blocks/src/group-list.js b/blocks/src/group-list.js index 38a94e8..f51f40f 100755 --- a/blocks/src/group-list.js +++ b/blocks/src/group-list.js @@ -1,15 +1,45 @@ +import 'whatwg-fetch'; +import { stringify as querystring } from 'qs'; +import debounce from 'debounce'; const { __ } = wp.i18n; const { BlockDescription, + Editable, InspectorControls, - InspectorControls: { TextControl }, + InspectorControls: { RangeControl, TextControl }, } = wp.blocks; const translate = str => __(str, 'meetup-widgets'); +const getMeetupEvents = ({ limit, group }) => { + if (!group || limit <= 0) { + return; + } + // Set up args for the API endpoint + const args = { + status: 'upcoming', + page: limit, + group_urlname: group, + }; + // Make API request, set response + // Use debounce somewhere + return []; +}; + +const defaultAttributes = { + title: false, + limit: 5, + group: '', + response: false, + formattedEvents: null, +}; + // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { title: translate('Meetup.com List'), + description: translate( + 'This is a list of events for a given group on Meetup.com', + ), icon: 'editor-ul', @@ -19,27 +49,62 @@ export default { supportHTML: false, edit: ({ attributes, setAttributes, focus, setFocus }) => { - console.log(attributes); + // Inject default attributes + attributes = { ...defaultAttributes, ...attributes }; + getMeetupEvents(attributes); + + const onChangeEditable = field => value => + setAttributes({ [field]: value }); + + const onFocus = field => focus => + setFocus({ ...focus, editable: field }); + + const focusedEditable = focus ? focus.editable || 'title' : null; + + const controls = focus && ( + + + + + ); + return [ -
-

{translate('Text!')}

+ controls, +
+ +
    + {attributes.formattedEvents} +
, - !!focus && ( - - -

{translate('Meetup.com ?')}

-
- setAttributes({ test: value })} - /> -
- ), ]; }, - save: () => { - return null; + save: ({ attributes }) => { + return ( +
+

{attributes.title}

+
    + {attributes.formattedEvents} +
+
+ ); }, }; diff --git a/package.json b/package.json index 360cc3a..415609e 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,10 @@ "raw-loader": "^0.5.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.9.7" + }, + "dependencies": { + "debounce": "^1.1.0", + "qs": "6.5.1", + "whatwg-fetch": "^2.0.3" } } From f14ef28d0d08df214fbbde916e6901196655bd86 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 28 Dec 2017 21:53:24 -0500 Subject: [PATCH 10/56] Add API endpoint to get group events list --- api/class-meetup-rest-group-controller.php | 141 +++++++++++++++++++++ vs_meetup.php | 3 + 2 files changed, 144 insertions(+) create mode 100644 api/class-meetup-rest-group-controller.php diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php new file mode 100644 index 0000000..abeab3f --- /dev/null +++ b/api/class-meetup-rest-group-controller.php @@ -0,0 +1,141 @@ +[\S]+)', array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => '__return_true', // Any site visitor can read this + ), + ) ); + + register_rest_route( $namespace, '/' . $base . '/schema', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_public_item_schema' ), + ) ); + } + + /** + * Get a collection of items + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_items( $request ) { + $vsm = new Meetup_Widget(); + $count = intval( $request['count'] ) || 3; + $id = $request['id']; + $args = array( + 'status' => 'upcoming', + 'page' => $count, + ); + if ( preg_match( '/^[0-9]+$/', $id ) ) { + $args['group_id'] = $id; + } else { + $args['group_urlname'] = $id; + } + + // switch to v3? + $items = $vsm->get_data( $args, 'vsm_group_events_' . $id . '_' . $count ); + if ( ! $items ) { + return []; + } + if ( is_wp_error( $items ) ) { + return $items; + } + + $data = array(); + foreach ( $items as $item ) { + $itemdata = $this->prepare_item_for_response( $item, $request ); + $data[] = $this->prepare_response_for_collection( $itemdata ); + } + + return new WP_REST_Response( $data, 200 ); + } + + /** + * Get one item from the collection + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_item( $request ) { + // get parameters from request + $params = $request->get_params(); + $item = array(); // do a query, call another class, etc + $data = $this->prepare_item_for_response( $item, $request ); + + // return a response or error based on some conditional + if ( 1 == 1 ) { + return new WP_REST_Response( $data, 200 ); + } else { + return new WP_Error( 'code', __( 'message', 'text-domain' ) ); + } + } + + /** + * Prepare the item for the REST response + * + * @param mixed $item Meetup.com representation of the event. + * @param WP_REST_Request $request Request object. + * @return mixed + */ + public function prepare_item_for_response( $item, $request ) { + $venue = wp_parse_args( + (array) $item->venue, + array( + 'name' => '', + 'address_1' => '', + 'address_2' => '', + 'address_3' => '', + 'city' => '', + 'state' => '', + 'country' => '', + ) + ); + $venue_str = sprintf( + '%1$s ā€“ %2$s, %3$s, %4$s', + $venue['name'], + $venue['address_1'], + $venue['city'], + $venue['state'] + ); + $link = sprintf( + '%2$s', + $venue_str, + $venue['name'] + ); + + return array( + 'name' => $item->name, + 'description' => $item->description, + 'event_url' => $item->event_url, + 'maps_link' => $link, + 'time' => date( 'M d, g:ia', intval( $item->time / 1000 + $item->utc_offset / 1000 ) ), + 'venue' => $venue, + 'venue_display' => $venue_str, + 'yes_rsvp_count' => $item->yes_rsvp_count, + ); + } +} diff --git a/vs_meetup.php b/vs_meetup.php index 54f273c..3b6e46a 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -22,12 +22,15 @@ require_once( 'class-meetup-widgets-admin.php' ); require_once( 'class-meetup-widget.php' ); +require_once( 'api/class-meetup-rest-group-controller.php' ); /** * Initialize Meetup Widgets */ function meetup_widgets_start() { new Meetup_Widgets_Admin(); + $group_controller = new Meetup_REST_Group_Controller(); + $group_controller->register_routes(); } add_action( 'init', 'meetup_widgets_start' ); From 7627f690e22d6bc38b0bf503f0cd6463e3950ac3 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Fri, 29 Dec 2017 21:55:13 -0500 Subject: [PATCH 11/56] Add permission check, enabling endpoint for logged in users only --- api/class-meetup-rest-group-controller.php | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index abeab3f..b0f9ad1 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -26,7 +26,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => '__return_true', // Any site visitor can read this + 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), ) ); @@ -138,4 +138,25 @@ public function prepare_item_for_response( $item, $request ) { 'yes_rsvp_count' => $item->yes_rsvp_count, ); } + + /** + * Check permissions for this endpoint. + * + * Only logged-in users can use this proxy, to prevent anonymous users from + * spamming the meetup.com API with the site-owner's API key. + * + * @param WP_REST_Request $request Current request. + */ + public function get_items_permissions_check( $request ) { + if ( ! current_user_can( 'edit_posts' ) ) { + return new WP_Error( + 'rest_forbidden', + esc_html__( 'You cannot view the post resource.', 'meetup-widgets' ), + array( + 'status' => is_user_logged_in() ? 403 : 401, + ) + ); + } + return true; + } } From bb5074d8d1752196f00377482f8fe69185b2011c Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 12:24:52 -0500 Subject: [PATCH 12/56] =?UTF-8?q?Add=20a=20handler-class=20for=20the=20v3?= =?UTF-8?q?=20endpoints=20of=20meetup.com=E2=80=99s=20API,=20use=20this=20?= =?UTF-8?q?in=20our=20proxy=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/class-meetup-rest-group-controller.php | 25 +++---- class-meetup-api-v3.php | 79 ++++++++++++++++++++++ vs_meetup.php | 1 + 3 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 class-meetup-api-v3.php diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index b0f9ad1..b75c3d1 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -43,21 +43,15 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { - $vsm = new Meetup_Widget(); - $count = intval( $request['count'] ) || 3; + $api = new Meetup_API_V3(); + $count = $request['count'] ? intval( $request['count'] ) : 3; $id = $request['id']; $args = array( 'status' => 'upcoming', 'page' => $count, ); - if ( preg_match( '/^[0-9]+$/', $id ) ) { - $args['group_id'] = $id; - } else { - $args['group_urlname'] = $id; - } - // switch to v3? - $items = $vsm->get_data( $args, 'vsm_group_events_' . $id . '_' . $count ); + $items = $api->get_events( $id, $args, 'vsm_v3_group_' . $id . '_' . $count ); if ( ! $items ) { return []; } @@ -121,18 +115,15 @@ public function prepare_item_for_response( $item, $request ) { $venue['city'], $venue['state'] ); - $link = sprintf( - '%2$s', - $venue_str, - $venue['name'] - ); return array( 'name' => $item->name, 'description' => $item->description, - 'event_url' => $item->event_url, - 'maps_link' => $link, - 'time' => date( 'M d, g:ia', intval( $item->time / 1000 + $item->utc_offset / 1000 ) ), + 'url' => $item->link, + 'google_maps' => "http://maps.google.com/maps?q={$venue_str}&z=17", + 'date' => date( 'M d, g:ia', intval( $item->time / 1000 + $item->utc_offset / 1000 ) ), + 'raw_date' => $item->time, + 'status' => $item->status, 'venue' => $venue, 'venue_display' => $venue_str, 'yes_rsvp_count' => $item->yes_rsvp_count, diff --git a/class-meetup-api-v3.php b/class-meetup-api-v3.php new file mode 100644 index 0000000..376fb1a --- /dev/null +++ b/class-meetup-api-v3.php @@ -0,0 +1,79 @@ +api_key = $options['vs_meetup_api_key']; + } + + /** + * Given arguments & a transient name, grab data from the events API + * + * @param string $group Group name used to fetch events. + * @param array $args Query params to send to events call. + * @param string $transient The transient name (if empty, no transient stored). + * @return array Event data (list of events) + */ + public function get_events( $group = false, $args = [], $transient = '' ) { + if ( ! $group ) { + return new WP_Error( 'undefined_group', __( 'Requested group name missing.', 'meetup-widgets' ) ); + } + $data = false; + if ( $transient ) { + $data = get_transient( $transient ); + } + + $defaults = array( + 'key' => $this->api_key, + ); + + if ( false === $data ) { + $args = wp_parse_args( $args, $defaults ); + $url = sprintf( '%s/%s/events', $this->base_url, $group ); + $url = add_query_arg( $args, $url ); + $events_response = wp_remote_get( $url ); + if ( is_wp_error( $events_response ) ) { + return $events_response; + } + $data = json_decode( $events_response['body'] ); + if ( isset( $data->errors ) ) { + $err = array_shift( $data->errors ); + return new WP_Error( $err->code, $err->message ); + } + if ( ! is_array( $data ) ) { + return new WP_Error( 'response_error', __( 'Response is not formatted correctly', 'meetup-widgets' ) ); + } + + if ( $transient ) { + set_transient( $transient, $data, 2 * HOUR_IN_SECONDS ); + } + } + + return $data; + } +} diff --git a/vs_meetup.php b/vs_meetup.php index 3b6e46a..73c9248 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -22,6 +22,7 @@ require_once( 'class-meetup-widgets-admin.php' ); require_once( 'class-meetup-widget.php' ); +require_once( 'class-meetup-api-v3.php' ); require_once( 'api/class-meetup-rest-group-controller.php' ); /** From 57452b4afaf5504d4ec49b644f988661f5f39b71 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 12:44:46 -0500 Subject: [PATCH 13/56] Pass event ID through prepared object --- api/class-meetup-rest-group-controller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index b75c3d1..4486ea4 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -117,6 +117,7 @@ public function prepare_item_for_response( $item, $request ) { ); return array( + 'id' => $item->id, 'name' => $item->name, 'description' => $item->description, 'url' => $item->link, From 0f5a9c16a7720cf9d460b6d40d6da46db54ec878 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 12:44:58 -0500 Subject: [PATCH 14/56] Add a single-event endpoint --- api/class-meetup-rest-group-controller.php | 40 +++++++++++++------ class-meetup-api-v3.php | 45 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index 4486ea4..0584a45 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -22,7 +22,7 @@ class Meetup_REST_Group_Controller extends WP_REST_Controller { public function register_routes() { $namespace = 'meetup/v1'; $base = 'group'; - register_rest_route( $namespace, '/' . $base . '/(?P[\S]+)', array( + register_rest_route( $namespace, '/' . $base . '/(?P[^/]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), @@ -30,6 +30,14 @@ public function register_routes() { ), ) ); + register_rest_route( $namespace, '/' . $base . '/(?P[^/]+)/(?P[^/]+)', array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + ) ); + register_rest_route( $namespace, '/' . $base . '/schema', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_public_item_schema' ), @@ -43,10 +51,11 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { - $api = new Meetup_API_V3(); - $count = $request['count'] ? intval( $request['count'] ) : 3; - $id = $request['id']; - $args = array( + $api = new Meetup_API_V3(); + $params = $request->get_params(); + $count = isset( $params['count'] ) ? intval( $params['count'] ) : 3; + $id = $params['id']; + $args = array( 'status' => 'upcoming', 'page' => $count, ); @@ -75,17 +84,24 @@ public function get_items( $request ) { * @return WP_Error|WP_REST_Response */ public function get_item( $request ) { - // get parameters from request + $api = new Meetup_API_V3(); $params = $request->get_params(); - $item = array(); // do a query, call another class, etc + $item = $api->get_event( + $params['group_id'], + $params['event_id'], + 'vsm_v3_event_' . $params['group_id'] . '_' . $params['event_id'] + ); + if ( ! $item ) { + return []; + } + if ( is_wp_error( $item ) ) { + return $item; + } + $data = $this->prepare_item_for_response( $item, $request ); // return a response or error based on some conditional - if ( 1 == 1 ) { - return new WP_REST_Response( $data, 200 ); - } else { - return new WP_Error( 'code', __( 'message', 'text-domain' ) ); - } + return new WP_REST_Response( $data, 200 ); } /** diff --git a/class-meetup-api-v3.php b/class-meetup-api-v3.php index 376fb1a..db7827e 100644 --- a/class-meetup-api-v3.php +++ b/class-meetup-api-v3.php @@ -76,4 +76,49 @@ public function get_events( $group = false, $args = [], $transient = '' ) { return $data; } + + /** + * Given arguments & a transient name, grab data from the events API + * + * @param string $group The parent group name. + * @param string $event The event ID to fetch. + * @param string $transient The transient name (if empty, no transient stored). + * @return array Event data (single event) + */ + public function get_event( $group = false, $event = false, $transient = '' ) { + if ( ! $group ) { + return new WP_Error( 'undefined_group', __( 'Requested group name missing.', 'meetup-widgets' ) ); + } + if ( ! $event ) { + return new WP_Error( 'undefined_event', __( 'Requested event ID missing.', 'meetup-widgets' ) ); + } + $data = false; + if ( $transient ) { + $data = get_transient( $transient ); + } + + $args = array( + 'key' => $this->api_key, + ); + + if ( false === $data ) { + $url = sprintf( '%s/%s/events/%s', $this->base_url, $group, $event ); + $url = add_query_arg( $args, $url ); + $event_response = wp_remote_get( $url ); + if ( is_wp_error( $event_response ) ) { + return $event_response; + } + $data = json_decode( $event_response['body'] ); + if ( isset( $data->errors ) ) { + $err = array_shift( $data->errors ); + return new WP_Error( $err->code, $err->message ); + } + + if ( $transient ) { + set_transient( $transient, $data, 2 * HOUR_IN_SECONDS ); + } + } + + return $data; + } } From 1e0a988bf7f72f0b95e835675d0dc539a82b4fd9 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 12:59:11 -0500 Subject: [PATCH 15/56] Add schema + validation (and a default) to the count param --- api/class-meetup-rest-group-controller.php | 27 +++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index 0584a45..bce5149 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -27,6 +27,7 @@ public function register_routes() { 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_endpoint_args(), ), ) ); @@ -37,11 +38,6 @@ public function register_routes() { 'permission_callback' => array( $this, 'get_items_permissions_check' ), ), ) ); - - register_rest_route( $namespace, '/' . $base . '/schema', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_public_item_schema' ), - ) ); } /** @@ -53,7 +49,7 @@ public function register_routes() { public function get_items( $request ) { $api = new Meetup_API_V3(); $params = $request->get_params(); - $count = isset( $params['count'] ) ? intval( $params['count'] ) : 3; + $count = intval( $params['count'] ); $id = $params['id']; $args = array( 'status' => 'upcoming', @@ -167,4 +163,23 @@ public function get_items_permissions_check( $request ) { } return true; } + + /** + * Get the argument schema for this example endpoint. + */ + function get_endpoint_args() { + $args = array(); + + // Here we add our PHP representation of JSON Schema. + $args['count'] = array( + 'description' => esc_html__( 'Number of events to show.', 'meetup-widgets' ), + 'type' => 'integer', + 'validate_callback' => 'absint', + 'sanitize_callback' => 'absint', + 'required' => false, + 'default' => 3, + ); + + return $args; + } } From 3bcb8674033690b7ea6c4100d7a96d37aa9fcae4 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 13:09:08 -0500 Subject: [PATCH 16/56] =?UTF-8?q?Add=20endpoint=20for=20getting=20API=20us?= =?UTF-8?q?er=E2=80=99s=20upcoming=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/class-meetup-rest-group-controller.php | 20 +++++++++-- class-meetup-api-v3.php | 42 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-group-controller.php index bce5149..cefc409 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-group-controller.php @@ -21,7 +21,16 @@ class Meetup_REST_Group_Controller extends WP_REST_Controller { */ public function register_routes() { $namespace = 'meetup/v1'; - $base = 'group'; + $base = 'events'; + register_rest_route( $namespace, '/' . $base . '/self', array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_endpoint_args(), + ), + ) ); + register_rest_route( $namespace, '/' . $base . '/(?P[^/]+)', array( array( 'methods' => WP_REST_Server::READABLE, @@ -50,13 +59,18 @@ public function get_items( $request ) { $api = new Meetup_API_V3(); $params = $request->get_params(); $count = intval( $params['count'] ); - $id = $params['id']; $args = array( 'status' => 'upcoming', 'page' => $count, ); - $items = $api->get_events( $id, $args, 'vsm_v3_group_' . $id . '_' . $count ); + if ( isset( $params['id'] ) ) { + $id = $params['id']; + $items = $api->get_events( $id, $args, 'vsm_v3_group_' . $id . '_' . $count ); + } else { + $items = $api->get_self_events( $args, 'vsm_v3_self_' . $count ); + } + if ( ! $items ) { return []; } diff --git a/class-meetup-api-v3.php b/class-meetup-api-v3.php index db7827e..d53dc04 100644 --- a/class-meetup-api-v3.php +++ b/class-meetup-api-v3.php @@ -77,6 +77,48 @@ public function get_events( $group = false, $args = [], $transient = '' ) { return $data; } + /** + * Given arguments & a transient name, grab data from the events API + * + * @param array $args Query params to send to events call. + * @param string $transient The transient name (if empty, no transient stored). + * @return array Event data (list of events) + */ + public function get_self_events( $args = [], $transient = '' ) { + $data = false; + if ( $transient ) { + $data = get_transient( $transient ); + } + + $defaults = array( + 'key' => $this->api_key, + ); + + if ( false === $data ) { + $args = wp_parse_args( $args, $defaults ); + $url = sprintf( '%s/self/events', $this->base_url ); + $url = add_query_arg( $args, $url ); + $events_response = wp_remote_get( $url ); + if ( is_wp_error( $events_response ) ) { + return $events_response; + } + $data = json_decode( $events_response['body'] ); + if ( isset( $data->errors ) ) { + $err = array_shift( $data->errors ); + return new WP_Error( $err->code, $err->message ); + } + if ( ! is_array( $data ) ) { + return new WP_Error( 'response_error', __( 'Response is not formatted correctly', 'meetup-widgets' ) ); + } + + if ( $transient ) { + set_transient( $transient, $data, 2 * HOUR_IN_SECONDS ); + } + } + + return $data; + } + /** * Given arguments & a transient name, grab data from the events API * From f61f25519c58bed09c3782a2c0b4e4207a9145f2 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 30 Dec 2017 13:10:04 -0500 Subject: [PATCH 17/56] Rename API controller class & file --- ...controller.php => class-meetup-rest-events-controller.php} | 2 +- vs_meetup.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename api/{class-meetup-rest-group-controller.php => class-meetup-rest-events-controller.php} (98%) diff --git a/api/class-meetup-rest-group-controller.php b/api/class-meetup-rest-events-controller.php similarity index 98% rename from api/class-meetup-rest-group-controller.php rename to api/class-meetup-rest-events-controller.php index cefc409..bdd7d21 100644 --- a/api/class-meetup-rest-group-controller.php +++ b/api/class-meetup-rest-events-controller.php @@ -14,7 +14,7 @@ /** * Handle all group-related event fetching */ -class Meetup_REST_Group_Controller extends WP_REST_Controller { +class Meetup_REST_Events_Controller extends WP_REST_Controller { /** * Register the routes for the objects of the controller. diff --git a/vs_meetup.php b/vs_meetup.php index 73c9248..ce6c67a 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -23,14 +23,14 @@ require_once( 'class-meetup-widgets-admin.php' ); require_once( 'class-meetup-widget.php' ); require_once( 'class-meetup-api-v3.php' ); -require_once( 'api/class-meetup-rest-group-controller.php' ); +require_once( 'api/class-meetup-rest-events-controller.php' ); /** * Initialize Meetup Widgets */ function meetup_widgets_start() { new Meetup_Widgets_Admin(); - $group_controller = new Meetup_REST_Group_Controller(); + $group_controller = new Meetup_REST_Events_Controller(); $group_controller->register_routes(); } add_action( 'init', 'meetup_widgets_start' ); From 90747673572b840ca4b2cd50a2e40cc3c304ee14 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sun, 31 Dec 2017 12:34:55 -0500 Subject: [PATCH 18/56] Add prettier & eslint --- .eslintrc.json | 41 +++++++++++++++++++++++++++++ blocks/src/group-list.js | 57 +++++++++++----------------------------- blocks/src/index.js | 6 +++-- blocks/src/single.js | 6 +++-- package.json | 9 ++++++- 5 files changed, 72 insertions(+), 47 deletions(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..867709e --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,41 @@ +{ + "root": true, + "parser": "babel-eslint", + "extends": [ + "wordpress", + "plugin:react/recommended" + ], + "env": { + "browser": false, + "es6": true, + "node": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "globals": { + "wp": true, + "wpApiSettings": true, + "window": true, + "document": true + }, + "plugins": [ + "react" + ], + "settings": { + "react": { + "pragma": "wp" + } + }, + "rules": { + "camelcase": 0, + "comma-dangle": [ 2, "always-multiline" ], + "no-unused-vars": 2, + "react/display-name": 0, + "react/prop-types": 0, + "space-in-parens": [ 2, "always" ] + } +} diff --git a/blocks/src/group-list.js b/blocks/src/group-list.js index f51f40f..a10bd6f 100755 --- a/blocks/src/group-list.js +++ b/blocks/src/group-list.js @@ -1,45 +1,20 @@ -import 'whatwg-fetch'; -import { stringify as querystring } from 'qs'; -import debounce from 'debounce'; +/** + * Core WP Dependencies + * + * @format + */ const { __ } = wp.i18n; -const { - BlockDescription, - Editable, - InspectorControls, - InspectorControls: { RangeControl, TextControl }, -} = wp.blocks; +const translate = str => __( str, 'meetup-widgets' ); -const translate = str => __(str, 'meetup-widgets'); - -const getMeetupEvents = ({ limit, group }) => { - if (!group || limit <= 0) { - return; - } - // Set up args for the API endpoint - const args = { - status: 'upcoming', - page: limit, - group_urlname: group, - }; - // Make API request, set response - // Use debounce somewhere - return []; -}; - -const defaultAttributes = { - title: false, - limit: 5, - group: '', - response: false, - formattedEvents: null, -}; +/** + * Internal Dependencies + */ +import { GroupListBlock } from './blocks/group-list'; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { - title: translate('Meetup.com List'), - description: translate( - 'This is a list of events for a given group on Meetup.com', - ), + title: translate( 'Meetup.com List' ), + description: translate( 'This is a list of events for a given group on Meetup.com' ), icon: 'editor-ul', @@ -97,13 +72,11 @@ export default { ]; }, - save: ({ attributes }) => { + save: ( { attributes } ) => { return (
-

{attributes.title}

-
    - {attributes.formattedEvents} -
+

{ attributes.title }

+
    { attributes.formattedEvents }
); }, diff --git a/blocks/src/index.js b/blocks/src/index.js index bec145d..1ecc07a 100644 --- a/blocks/src/index.js +++ b/blocks/src/index.js @@ -1,8 +1,10 @@ +/** @format */ + const { registerBlockType } = wp.blocks; import singleWidgetOptions from './single'; import groupWidgetOptions from './group-list'; import './style.css'; -registerBlockType('meetup-widgets/single', singleWidgetOptions); -registerBlockType('meetup-widgets/group-list', groupWidgetOptions); +registerBlockType( 'meetup-widgets/single', singleWidgetOptions ); +registerBlockType( 'meetup-widgets/group-list', groupWidgetOptions ); diff --git a/blocks/src/single.js b/blocks/src/single.js index 90cc07a..a728866 100644 --- a/blocks/src/single.js +++ b/blocks/src/single.js @@ -1,3 +1,5 @@ +/** @format */ + const { __ } = wp.i18n; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API @@ -16,6 +18,6 @@ export default { }, save: () => { - return null - } + return null; + }, }; diff --git a/package.json b/package.json index 415609e..34ce2f0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "start": "webpack-dev-server", "build": "NODE_ENV=production webpack", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint blocks/src/*", + "reformat": "prettier --write" }, "repository": { "type": "git", @@ -21,12 +23,17 @@ "devDependencies": { "autoprefixer": "^7.2.1", "babel-core": "^6.26.0", + "babel-eslint": "^8.1.2", "babel-loader": "^7.1.2", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-preset-env": "^1.6.1", + "eslint": "^4.14.0", + "eslint-config-wordpress": "^2.0.0", + "eslint-plugin-react": "^7.5.1", "extract-text-webpack-plugin": "^3.0.2", "postcss-loader": "^2.0.9", + "prettier": "git+https://github.com/Automattic/calypso-prettier.git", "raw-loader": "^0.5.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.9.7" From 482b59e9022772c366c4a4293b7d78fe617cb40f Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sun, 31 Dec 2017 16:33:46 -0500 Subject: [PATCH 19/56] Split out edit functionality into component, save/front end render should happen in PHP --- blocks/blocks.php | 46 ++++++++++++++ blocks/src/blocks/group-list.js | 104 ++++++++++++++++++++++++++++++++ blocks/src/group-list.js | 60 +----------------- 3 files changed, 153 insertions(+), 57 deletions(-) create mode 100644 blocks/src/blocks/group-list.js diff --git a/blocks/blocks.php b/blocks/blocks.php index c4153b1..f134ab3 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -27,3 +27,49 @@ function vs_meetup_widgets_enqueue_block_assets() { wp_enqueue_style( 'meetup-blocks', $css_file, [ 'wp-blocks' ], $css_version ); } add_action( 'enqueue_block_editor_assets', 'vs_meetup_widgets_enqueue_block_assets' ); + +/** + * Renders the `core/latest-posts` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the post content with latest posts added. + */ +function vs_meetup_widgets_render_block_group_list( $attributes ) { + $request = new WP_REST_Request( 'GET', '/meetup/v1/events/' . $attributes['group'] ); + $request->set_query_params( array( + 'per_page' => $attributes['per_page'], + ) ); + $response = rest_do_request( $request ); + if ( 200 !== $response->get_status() ) { + return '

' . $attributes['title'] . '

'; + } + + $events = $response->get_data(); + $block_content = [ '

' . $attributes['title'] . '

', '
    ' ]; + array_push( $block_content, '

    ' . $attributes['group'] . '

    ' ); + foreach ( $events as $event ) { + $list_item = '
  • ' . $event['name'] . ' ' . $event['date'] . '
  • '; + array_push( $block_content, $list_item ); + } + array_push( $block_content, '
' ); + + return implode( "\n", $block_content ); +} + +register_block_type( 'meetup-widgets/group-list', array( + 'attributes' => array( + 'title' => array( + 'type' => 'string', + 'default' => __( 'Upcoming Events', 'meetup-widgets' ), + ), + 'group' => array( + 'type' => 'string', + ), + 'per_page' => array( + 'type' => 'number', + 'default' => 5, + ), + ), + 'render_callback' => 'vs_meetup_widgets_render_block_group_list', +) ); diff --git a/blocks/src/blocks/group-list.js b/blocks/src/blocks/group-list.js new file mode 100644 index 0000000..896a53e --- /dev/null +++ b/blocks/src/blocks/group-list.js @@ -0,0 +1,104 @@ +/** + * External Dependencies + * + * @format + */ +import { stringify } from 'qs'; + +/** + * Core WP Dependencies + */ +const { __ } = wp.i18n; +const translate = str => __( str, 'meetup-widgets' ); +const { Component } = wp.element; +const { Editable, InspectorControls, InspectorControls: { RangeControl, TextControl } } = wp.blocks; +const { Placeholder, Spinner, withAPIData } = wp.components; + +class GroupListBlock extends Component { + constructor() { + super( ...arguments ); + this.onChangeEditable = this.onChangeEditable.bind( this ); + this.onFocus = this.onFocus.bind( this ); + } + + onChangeEditable( field ) { + return value => this.props.setAttributes( { [ field ]: value } ); + } + + onFocus( field ) { + return focus => this.props.setFocus( { ...focus, editable: field } ); + } + + renderEventsList() { + const { isLoading, data = [] } = this.props.events || {}; + console.log( isLoading, data ); + if ( isLoading ) { + return ( + + + + ); + } + if ( data.code ) { + return

{ data.message }

; + } + return ( +
    + { data.map( item => ( +
  • + { item.name } { item.date } +
  • + ) ) } +
+ ); + } + + render() { + const { attributes, focus } = this.props; + const focusedEditable = focus ? focus.editable || 'title' : null; + + const controls = focus && ( + + + + + ); + + return [ + controls, +
+ + { this.renderEventsList() } +
, + ]; + } +} + +export default withAPIData( props => { + const { group, per_page } = props.attributes; + if ( ! group ) { + return {}; + } + const queryString = stringify( { per_page } ); + return { + events: `/meetup/v1/events/${ group }?${ queryString }`, + }; +} )( GroupListBlock ); diff --git a/blocks/src/group-list.js b/blocks/src/group-list.js index a10bd6f..97bd67f 100755 --- a/blocks/src/group-list.js +++ b/blocks/src/group-list.js @@ -9,7 +9,7 @@ const translate = str => __( str, 'meetup-widgets' ); /** * Internal Dependencies */ -import { GroupListBlock } from './blocks/group-list'; +import GroupListBlock from './blocks/group-list'; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { @@ -23,61 +23,7 @@ export default { // Remove to make block editable in HTML mode. supportHTML: false, - edit: ({ attributes, setAttributes, focus, setFocus }) => { - // Inject default attributes - attributes = { ...defaultAttributes, ...attributes }; - getMeetupEvents(attributes); + edit: GroupListBlock, - const onChangeEditable = field => value => - setAttributes({ [field]: value }); - - const onFocus = field => focus => - setFocus({ ...focus, editable: field }); - - const focusedEditable = focus ? focus.editable || 'title' : null; - - const controls = focus && ( - - - - - ); - - return [ - controls, -
- -
    - {attributes.formattedEvents} -
-
, - ]; - }, - - save: ( { attributes } ) => { - return ( -
-

{ attributes.title }

-
    { attributes.formattedEvents }
-
- ); - }, + save: () => null, }; From 65c6374ff7e3fb140077112378890753c1889ee6 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Tue, 2 Jan 2018 18:42:03 -0500 Subject: [PATCH 20/56] =?UTF-8?q?Stub=20out=20an=20endpoint=20to=20fetch?= =?UTF-8?q?=20the=20API=20user=E2=80=99s=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/class-meetup-rest-events-controller.php | 2 +- api/class-meetup-rest-groups-controller.php | 102 ++++++++++++++++++++ vs_meetup.php | 6 +- 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 api/class-meetup-rest-groups-controller.php diff --git a/api/class-meetup-rest-events-controller.php b/api/class-meetup-rest-events-controller.php index bdd7d21..78267a1 100644 --- a/api/class-meetup-rest-events-controller.php +++ b/api/class-meetup-rest-events-controller.php @@ -169,7 +169,7 @@ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_forbidden', - esc_html__( 'You cannot view the post resource.', 'meetup-widgets' ), + esc_html__( 'You cannot view the event resource.', 'meetup-widgets' ), array( 'status' => is_user_logged_in() ? 403 : 401, ) diff --git a/api/class-meetup-rest-groups-controller.php b/api/class-meetup-rest-groups-controller.php new file mode 100644 index 0000000..081dc19 --- /dev/null +++ b/api/class-meetup-rest-groups-controller.php @@ -0,0 +1,102 @@ + WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + ) ); + } + + /** + * Get a collection of items + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_items( $request ) { + $items = [ + [ + 'name' => 'Boston WordPress', + 'urlname' => 'boston-wordpress-meetup', + ], [ + 'name' => 'Women Who Code', + 'urlname' => 'Women-Who-Code-Boston', + ], + ]; + + if ( ! $items ) { + return []; + } + if ( is_wp_error( $items ) ) { + return $items; + } + + $data = array(); + foreach ( $items as $item ) { + $itemdata = $this->prepare_item_for_response( $item, $request ); + $data[] = $this->prepare_response_for_collection( $itemdata ); + } + + return new WP_REST_Response( $data, 200 ); + } + + /** + * Prepare the item for the REST response + * + * @param mixed $item Meetup.com representation of the group. + * @param WP_REST_Request $request Request object. + * @return mixed + */ + public function prepare_item_for_response( $item, $request ) { + return array( + 'id' => rand( 0, 20 ), + 'name' => $item['name'], + 'urlname' => $item['urlname'], + ); + } + + /** + * Check permissions for this endpoint. + * + * Only logged-in users can use this proxy, to prevent anonymous users from + * spamming the meetup.com API with the site-owner's API key. + * + * @param WP_REST_Request $request Current request. + */ + public function get_items_permissions_check( $request ) { + if ( ! current_user_can( 'edit_posts' ) ) { + return new WP_Error( + 'rest_forbidden', + esc_html__( 'You cannot view the group resource.', 'meetup-widgets' ), + array( + 'status' => is_user_logged_in() ? 403 : 401, + ) + ); + } + return true; + } +} diff --git a/vs_meetup.php b/vs_meetup.php index ce6c67a..37d34bc 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -24,13 +24,17 @@ require_once( 'class-meetup-widget.php' ); require_once( 'class-meetup-api-v3.php' ); require_once( 'api/class-meetup-rest-events-controller.php' ); +require_once( 'api/class-meetup-rest-groups-controller.php' ); /** * Initialize Meetup Widgets */ function meetup_widgets_start() { new Meetup_Widgets_Admin(); - $group_controller = new Meetup_REST_Events_Controller(); + $event_controller = new Meetup_REST_Events_Controller(); + $event_controller->register_routes(); + + $group_controller = new Meetup_REST_Groups_Controller(); $group_controller->register_routes(); } add_action( 'init', 'meetup_widgets_start' ); From 821969a43def2de0cb48ccd8099cd456a7fc3275 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Tue, 2 Jan 2018 20:56:01 -0500 Subject: [PATCH 21/56] =?UTF-8?q?Fetch=20the=20API=20user=E2=80=99s=20grou?= =?UTF-8?q?ps,=20and=20use=20that=20to=20populate=20a=20dropdown=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blocks/src/blocks/group-list.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/blocks/src/blocks/group-list.js b/blocks/src/blocks/group-list.js index 896a53e..1d13b9f 100644 --- a/blocks/src/blocks/group-list.js +++ b/blocks/src/blocks/group-list.js @@ -11,7 +11,11 @@ import { stringify } from 'qs'; const { __ } = wp.i18n; const translate = str => __( str, 'meetup-widgets' ); const { Component } = wp.element; -const { Editable, InspectorControls, InspectorControls: { RangeControl, TextControl } } = wp.blocks; +const { + Editable, + InspectorControls, + InspectorControls: { RangeControl, SelectControl }, +} = wp.blocks; const { Placeholder, Spinner, withAPIData } = wp.components; class GroupListBlock extends Component { @@ -31,7 +35,7 @@ class GroupListBlock extends Component { renderEventsList() { const { isLoading, data = [] } = this.props.events || {}; - console.log( isLoading, data ); + console.log( 'events', isLoading, data ); if ( isLoading ) { return ( @@ -54,14 +58,24 @@ class GroupListBlock extends Component { } render() { - const { attributes, focus } = this.props; + const { attributes, focus, groups: { data = [] } } = this.props; const focusedEditable = focus ? focus.editable || 'title' : null; + const groupOptions = data.map( group => ( { + label: group.name, + value: group.urlname, + } ) ); + groupOptions.unshift( { + label: translate( 'Select a groupā€¦' ), + value: '', + } ); + const controls = focus && ( - { - const { group, per_page } = props.attributes; - if ( ! group ) { - return {}; - } + const { group, per_page = 3 } = props.attributes; const queryString = stringify( { per_page } ); return { - events: `/meetup/v1/events/${ group }?${ queryString }`, + events: group ? `/meetup/v1/events/${ group }?${ queryString }` : {}, + groups: '/meetup/v1/groups/self', }; } )( GroupListBlock ); From f0890b536a47684510cb7d6870022e93c3006703 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 10:00:08 -0500 Subject: [PATCH 22/56] Ignore the mock-data directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3296222..562c476 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ blocks/build phpcs.xml yarn.lock package-lock.json +mock-data From e3d74f4d593cac1a43e14a9439c9b7780a17b9ab Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 10:00:32 -0500 Subject: [PATCH 23/56] Get user groups from the real API --- api/class-meetup-rest-groups-controller.php | 17 +++------ class-meetup-api-v3.php | 42 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/api/class-meetup-rest-groups-controller.php b/api/class-meetup-rest-groups-controller.php index 081dc19..1900b82 100644 --- a/api/class-meetup-rest-groups-controller.php +++ b/api/class-meetup-rest-groups-controller.php @@ -38,15 +38,8 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { - $items = [ - [ - 'name' => 'Boston WordPress', - 'urlname' => 'boston-wordpress-meetup', - ], [ - 'name' => 'Women Who Code', - 'urlname' => 'Women-Who-Code-Boston', - ], - ]; + $api = new Meetup_API_V3(); + $items = $api->get_self_groups( 'vsm_v3_self_groups' ); if ( ! $items ) { return []; @@ -73,9 +66,9 @@ public function get_items( $request ) { */ public function prepare_item_for_response( $item, $request ) { return array( - 'id' => rand( 0, 20 ), - 'name' => $item['name'], - 'urlname' => $item['urlname'], + 'id' => $item->id, + 'name' => $item->name, + 'urlname' => $item->urlname, ); } diff --git a/class-meetup-api-v3.php b/class-meetup-api-v3.php index d53dc04..815a1c6 100644 --- a/class-meetup-api-v3.php +++ b/class-meetup-api-v3.php @@ -163,4 +163,46 @@ public function get_event( $group = false, $event = false, $transient = '' ) { return $data; } + + /** + * Given arguments & a transient name, grab data from the groups API + * + * @param string $transient The transient name (if empty, no transient stored). + * @return array Event data (list of events) + */ + public function get_self_groups( $transient = '' ) { + $data = false; + if ( $transient ) { + $data = get_transient( $transient ); + } + + $args = array( + 'key' => $this->api_key, + 'page' => 200, + ); + + if ( false === $data ) { + $url = sprintf( '%s/self/groups', $this->base_url ); + $url = add_query_arg( $args, $url ); + $groups_response = wp_remote_get( $url ); + if ( is_wp_error( $groups_response ) ) { + return $groups_response; + } + $data = json_decode( $groups_response['body'] ); + if ( isset( $data->errors ) ) { + $err = array_shift( $data->errors ); + return new WP_Error( $err->code, $err->message ); + } + if ( ! is_array( $data ) ) { + return new WP_Error( 'response_error', __( 'Response is not formatted correctly', 'meetup-widgets' ) ); + } + + if ( $transient ) { + set_transient( $transient, $data, 2 * HOUR_IN_SECONDS ); + } + } + + return $data; + } + } From d0b2f0984c5e2e7c4e6392996ddee000283e122b Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 10:04:42 -0500 Subject: [PATCH 24/56] Add an editable placeholder string for when there are no upcoming events --- blocks/blocks.php | 4 ++++ blocks/src/blocks/group-list.js | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/blocks/blocks.php b/blocks/blocks.php index f134ab3..029d037 100755 --- a/blocks/blocks.php +++ b/blocks/blocks.php @@ -63,6 +63,10 @@ function vs_meetup_widgets_render_block_group_list( $attributes ) { 'type' => 'string', 'default' => __( 'Upcoming Events', 'meetup-widgets' ), ), + 'placeholder' => array( + 'type' => 'string', + 'default' => __( 'No upcoming events.', 'meetup-widgets' ), + ), 'group' => array( 'type' => 'string', ), diff --git a/blocks/src/blocks/group-list.js b/blocks/src/blocks/group-list.js index 1d13b9f..cc4f360 100644 --- a/blocks/src/blocks/group-list.js +++ b/blocks/src/blocks/group-list.js @@ -23,6 +23,7 @@ class GroupListBlock extends Component { super( ...arguments ); this.onChangeEditable = this.onChangeEditable.bind( this ); this.onFocus = this.onFocus.bind( this ); + this.renderEventsList = this.renderEventsList.bind( this ); } onChangeEditable( field ) { @@ -34,7 +35,10 @@ class GroupListBlock extends Component { } renderEventsList() { - const { isLoading, data = [] } = this.props.events || {}; + const { attributes, focus, events = {} } = this.props; + const { isLoading, data = [] } = events; + const focusedEditable = focus ? focus.editable || 'title' : null; + console.log( 'events', isLoading, data ); if ( isLoading ) { return ( @@ -46,6 +50,18 @@ class GroupListBlock extends Component { if ( data.code ) { return

{ data.message }

; } + if ( ! data.length ) { + return ( + + ); + } return (
    { data.map( item => ( From 5113f5f5cc1c774f8c6116e954208fd72744e457 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 10:05:10 -0500 Subject: [PATCH 25/56] Check for properties before using them --- api/class-meetup-rest-events-controller.php | 44 ++++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/api/class-meetup-rest-events-controller.php b/api/class-meetup-rest-events-controller.php index 78267a1..e6c57ce 100644 --- a/api/class-meetup-rest-events-controller.php +++ b/api/class-meetup-rest-events-controller.php @@ -122,30 +122,36 @@ public function get_item( $request ) { * @return mixed */ public function prepare_item_for_response( $item, $request ) { - $venue = wp_parse_args( - (array) $item->venue, - array( - 'name' => '', - 'address_1' => '', - 'address_2' => '', - 'address_3' => '', - 'city' => '', - 'state' => '', - 'country' => '', - ) - ); - $venue_str = sprintf( - '%1$s ā€“ %2$s, %3$s, %4$s', - $venue['name'], - $venue['address_1'], - $venue['city'], - $venue['state'] + $venue_defaults = array( + 'name' => '', + 'address_1' => '', + 'address_2' => '', + 'address_3' => '', + 'city' => '', + 'state' => '', + 'country' => '', ); + if ( isset( $item->venue ) ) { + $venue = wp_parse_args( + (array) $item->venue, + $venue_defaults + ); + $venue_str = sprintf( + '%1$s ā€“ %2$s, %3$s, %4$s', + $venue['name'], + $venue['address_1'], + $venue['city'], + $venue['state'] + ); + } else { + $venue = $venue_defaults; + $venue_str = ''; + } return array( 'id' => $item->id, 'name' => $item->name, - 'description' => $item->description, + 'description' => isset( $item->description ) ? $item->description : '', 'url' => $item->link, 'google_maps' => "http://maps.google.com/maps?q={$venue_str}&z=17", 'date' => date( 'M d, g:ia', intval( $item->time / 1000 + $item->utc_offset / 1000 ) ), From 9253e9bb52493b4f09047b4d01a73b36605e197c Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 18:43:35 -0500 Subject: [PATCH 26/56] Restructure files, yes, again --- .../class-meetup-rest-events-controller.php | 0 .../class-meetup-rest-groups-controller.php | 0 {blocks => includes/blocks}/blocks.php | 0 .../blocks/build/editor.css | 0 includes/blocks/build/index.js | 1458 +++++++++++++++++ .../blocks}/src/blocks/group-list.js | 0 {blocks => includes/blocks}/src/group-list.js | 0 {blocks => includes/blocks}/src/index.js | 0 {blocks => includes/blocks}/src/single.js | 0 includes/blocks/src/style.css | 0 .../class-meetup-api-v3.php | 0 .../class-meetup-widget.php | 0 .../class-meetup-widgets-admin.php | 0 .../templates}/meetup-list.php | 0 .../templates}/meetup-single.php | 0 {widgets => includes/widgets}/group-list.php | 0 {widgets => includes/widgets}/single.php | 0 {widgets => includes/widgets}/user-list.php | 0 vs_meetup.php | 21 +- 19 files changed, 1468 insertions(+), 11 deletions(-) rename {api => includes/api}/class-meetup-rest-events-controller.php (100%) rename {api => includes/api}/class-meetup-rest-groups-controller.php (100%) rename {blocks => includes/blocks}/blocks.php (100%) rename blocks/src/style.css => includes/blocks/build/editor.css (100%) create mode 100644 includes/blocks/build/index.js rename {blocks => includes/blocks}/src/blocks/group-list.js (100%) rename {blocks => includes/blocks}/src/group-list.js (100%) rename {blocks => includes/blocks}/src/index.js (100%) rename {blocks => includes/blocks}/src/single.js (100%) create mode 100644 includes/blocks/src/style.css rename class-meetup-api-v3.php => includes/class-meetup-api-v3.php (100%) rename class-meetup-widget.php => includes/class-meetup-widget.php (100%) rename class-meetup-widgets-admin.php => includes/class-meetup-widgets-admin.php (100%) rename {templates => includes/templates}/meetup-list.php (100%) rename {templates => includes/templates}/meetup-single.php (100%) rename {widgets => includes/widgets}/group-list.php (100%) rename {widgets => includes/widgets}/single.php (100%) rename {widgets => includes/widgets}/user-list.php (100%) diff --git a/api/class-meetup-rest-events-controller.php b/includes/api/class-meetup-rest-events-controller.php similarity index 100% rename from api/class-meetup-rest-events-controller.php rename to includes/api/class-meetup-rest-events-controller.php diff --git a/api/class-meetup-rest-groups-controller.php b/includes/api/class-meetup-rest-groups-controller.php similarity index 100% rename from api/class-meetup-rest-groups-controller.php rename to includes/api/class-meetup-rest-groups-controller.php diff --git a/blocks/blocks.php b/includes/blocks/blocks.php similarity index 100% rename from blocks/blocks.php rename to includes/blocks/blocks.php diff --git a/blocks/src/style.css b/includes/blocks/build/editor.css similarity index 100% rename from blocks/src/style.css rename to includes/blocks/build/editor.css diff --git a/includes/blocks/build/index.js b/includes/blocks/build/index.js new file mode 100644 index 0000000..51e1c26 --- /dev/null +++ b/includes/blocks/build/index.js @@ -0,0 +1,1458 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 2); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var has = Object.prototype.hasOwnProperty; + +var hexTable = (function () { + var array = []; + for (var i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +}()); + +var compactQueue = function compactQueue(queue) { + var obj; + + while (queue.length) { + var item = queue.pop(); + obj = item.obj[item.prop]; + + if (Array.isArray(obj)) { + var compacted = []; + + for (var j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + item.obj[item.prop] = compacted; + } + } + + return obj; +}; + +exports.arrayToObject = function arrayToObject(source, options) { + var obj = options && options.plainObjects ? Object.create(null) : {}; + for (var i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +}; + +exports.merge = function merge(target, source, options) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (Array.isArray(target)) { + target.push(source); + } else if (typeof target === 'object') { + if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (typeof target !== 'object') { + return [target].concat(source); + } + + var mergeTarget = target; + if (Array.isArray(target) && !Array.isArray(source)) { + mergeTarget = exports.arrayToObject(target, options); + } + + if (Array.isArray(target) && Array.isArray(source)) { + source.forEach(function (item, i) { + if (has.call(target, i)) { + if (target[i] && typeof target[i] === 'object') { + target[i] = exports.merge(target[i], item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + var value = source[key]; + + if (has.call(acc, key)) { + acc[key] = exports.merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +}; + +exports.assign = function assignSingleSource(target, source) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +}; + +exports.decode = function (str) { + try { + return decodeURIComponent(str.replace(/\+/g, ' ')); + } catch (e) { + return str; + } +}; + +exports.encode = function encode(str) { + // This code was originally written by Brian White (mscdex) for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + var string = typeof str === 'string' ? str : String(str); + + var out = ''; + for (var i = 0; i < string.length; ++i) { + var c = string.charCodeAt(i); + + if ( + c === 0x2D // - + || c === 0x2E // . + || c === 0x5F // _ + || c === 0x7E // ~ + || (c >= 0x30 && c <= 0x39) // 0-9 + || (c >= 0x41 && c <= 0x5A) // a-z + || (c >= 0x61 && c <= 0x7A) // A-Z + ) { + out += string.charAt(i); + continue; + } + + if (c < 0x80) { + out = out + hexTable[c]; + continue; + } + + if (c < 0x800) { + out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + if (c < 0xD800 || c >= 0xE000) { + out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]); + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; + } + + return out; +}; + +exports.compact = function compact(value) { + var queue = [{ obj: { o: value }, prop: 'o' }]; + var refs = []; + + for (var i = 0; i < queue.length; ++i) { + var item = queue[i]; + var obj = item.obj[item.prop]; + + var keys = Object.keys(obj); + for (var j = 0; j < keys.length; ++j) { + var key = keys[j]; + var val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + return compactQueue(queue); +}; + +exports.isRegExp = function isRegExp(obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + +exports.isBuffer = function isBuffer(obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +}; + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var replace = String.prototype.replace; +var percentTwenties = /%20/g; + +module.exports = { + 'default': 'RFC3986', + formatters: { + RFC1738: function (value) { + return replace.call(value, percentTwenties, '+'); + }, + RFC3986: function (value) { + return value; + } + }, + RFC1738: 'RFC1738', + RFC3986: 'RFC3986' +}; + + +/***/ }), +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__single__ = __webpack_require__(3); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__group_list__ = __webpack_require__(4); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__style_css__ = __webpack_require__(10); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__style_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__style_css__); +var registerBlockType = wp.blocks.registerBlockType; + + + + + + +registerBlockType('meetup-widgets/single', __WEBPACK_IMPORTED_MODULE_0__single__["a" /* default */]); +registerBlockType('meetup-widgets/group-list', __WEBPACK_IMPORTED_MODULE_1__group_list__["a" /* default */]); + +/***/ }), +/* 3 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +var __ = wp.i18n.__; + +// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API + +/* harmony default export */ __webpack_exports__["a"] = ({ + title: __('Meetup.com Event', 'meetup-widgets'), + + icon: 'groups', + + category: 'embed', + + // Remove to make block editable in HTML mode. + supportHTML: false, + + edit: function edit(props) { + return __('Single event here! šŸŽŠ', 'meetup-widgets'); + }, + + save: function save() { + return null; + } +}); + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_whatwg_fetch__ = __webpack_require__(5); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_whatwg_fetch___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_whatwg_fetch__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_qs__ = __webpack_require__(6); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_qs___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_qs__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_debounce__ = __webpack_require__(9); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_debounce___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_debounce__); +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + + + +var __ = wp.i18n.__; +var _wp$blocks = wp.blocks, + BlockDescription = _wp$blocks.BlockDescription, + Editable = _wp$blocks.Editable, + InspectorControls = _wp$blocks.InspectorControls, + _wp$blocks$InspectorC = _wp$blocks.InspectorControls, + RangeControl = _wp$blocks$InspectorC.RangeControl, + TextControl = _wp$blocks$InspectorC.TextControl; + + +var translate = function translate(str) { + return __(str, 'meetup-widgets'); +}; + +var getMeetupEvents = function getMeetupEvents(_ref) { + var limit = _ref.limit, + group = _ref.group; + + if (!group || limit <= 0) { + return; + } + var args = { + status: 'upcoming', + page: limit, + group_urlname: group + }; + return []; +}; + +var defaultAttributes = { + title: false, + limit: 5, + group: '', + response: false, + formattedEvents: null +}; + +// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API +/* harmony default export */ __webpack_exports__["a"] = ({ + title: translate('Meetup.com List'), + description: translate('This is a list of events for a given group on Meetup.com'), + + icon: 'editor-ul', + + category: 'embed', + + // Remove to make block editable in HTML mode. + supportHTML: false, + + edit: function edit(_ref2) { + var attributes = _ref2.attributes, + setAttributes = _ref2.setAttributes, + focus = _ref2.focus, + setFocus = _ref2.setFocus; + + // Inject default attributes + attributes = _extends({}, defaultAttributes, attributes); + getMeetupEvents(attributes); + + var onChangeEditable = function onChangeEditable(field) { + return function (value) { + return setAttributes(_defineProperty({}, field, value)); + }; + }; + + var onFocus = function onFocus(field) { + return function (focus) { + return setFocus(_extends({}, focus, { editable: field })); + }; + }; + + var focusedEditable = focus ? focus.editable || 'title' : null; + + var controls = focus && wp.element.createElement( + InspectorControls, + { key: 'meetup-inspector' }, + wp.element.createElement(TextControl, { + label: translate('Group Name'), + value: attributes.group, + onChange: onChangeEditable('group') + }), + wp.element.createElement(RangeControl, { + label: translate('Number of event to show'), + value: attributes.limit, + onChange: onChangeEditable('limit'), + min: 2, + max: 15 + }) + ); + + return [controls, wp.element.createElement( + 'div', + { className: 'meetup-widgets', key: 'meetup-display' }, + wp.element.createElement(Editable, { + tagName: 'h3', + placeholder: translate('Upcoming Events'), + onChange: onChangeEditable('title'), + focus: focusedEditable === 'title', + onFocus: onFocus('title'), + className: 'meetup-widgets-title', + value: attributes.title + }), + wp.element.createElement( + 'ul', + { className: 'meetup-widgets-list' }, + attributes.formattedEvents + ) + )]; + }, + + save: function save(_ref3) { + var attributes = _ref3.attributes; + + return wp.element.createElement( + 'div', + { className: 'meetup-widgets' }, + wp.element.createElement( + 'h3', + null, + attributes.title + ), + wp.element.createElement( + 'ul', + { className: 'meetup-widgets-list' }, + attributes.formattedEvents + ) + ); + } +}); + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + +(function(self) { + 'use strict'; + + if (self.fetch) { + return + } + + var support = { + searchParams: 'URLSearchParams' in self, + iterable: 'Symbol' in self && 'iterator' in Symbol, + blob: 'FileReader' in self && 'Blob' in self && (function() { + try { + new Blob() + return true + } catch(e) { + return false + } + })(), + formData: 'FormData' in self, + arrayBuffer: 'ArrayBuffer' in self + } + + if (support.arrayBuffer) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ] + + var isDataView = function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj) + } + + var isArrayBufferView = ArrayBuffer.isView || function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 + } + } + + function normalizeName(name) { + if (typeof name !== 'string') { + name = String(name) + } + if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { + throw new TypeError('Invalid character in header field name') + } + return name.toLowerCase() + } + + function normalizeValue(value) { + if (typeof value !== 'string') { + value = String(value) + } + return value + } + + // Build a destructive iterator for the value list + function iteratorFor(items) { + var iterator = { + next: function() { + var value = items.shift() + return {done: value === undefined, value: value} + } + } + + if (support.iterable) { + iterator[Symbol.iterator] = function() { + return iterator + } + } + + return iterator + } + + function Headers(headers) { + this.map = {} + + if (headers instanceof Headers) { + headers.forEach(function(value, name) { + this.append(name, value) + }, this) + } else if (Array.isArray(headers)) { + headers.forEach(function(header) { + this.append(header[0], header[1]) + }, this) + } else if (headers) { + Object.getOwnPropertyNames(headers).forEach(function(name) { + this.append(name, headers[name]) + }, this) + } + } + + Headers.prototype.append = function(name, value) { + name = normalizeName(name) + value = normalizeValue(value) + var oldValue = this.map[name] + this.map[name] = oldValue ? oldValue+','+value : value + } + + Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)] + } + + Headers.prototype.get = function(name) { + name = normalizeName(name) + return this.has(name) ? this.map[name] : null + } + + Headers.prototype.has = function(name) { + return this.map.hasOwnProperty(normalizeName(name)) + } + + Headers.prototype.set = function(name, value) { + this.map[normalizeName(name)] = normalizeValue(value) + } + + Headers.prototype.forEach = function(callback, thisArg) { + for (var name in this.map) { + if (this.map.hasOwnProperty(name)) { + callback.call(thisArg, this.map[name], name, this) + } + } + } + + Headers.prototype.keys = function() { + var items = [] + this.forEach(function(value, name) { items.push(name) }) + return iteratorFor(items) + } + + Headers.prototype.values = function() { + var items = [] + this.forEach(function(value) { items.push(value) }) + return iteratorFor(items) + } + + Headers.prototype.entries = function() { + var items = [] + this.forEach(function(value, name) { items.push([name, value]) }) + return iteratorFor(items) + } + + if (support.iterable) { + Headers.prototype[Symbol.iterator] = Headers.prototype.entries + } + + function consumed(body) { + if (body.bodyUsed) { + return Promise.reject(new TypeError('Already read')) + } + body.bodyUsed = true + } + + function fileReaderReady(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result) + } + reader.onerror = function() { + reject(reader.error) + } + }) + } + + function readBlobAsArrayBuffer(blob) { + var reader = new FileReader() + var promise = fileReaderReady(reader) + reader.readAsArrayBuffer(blob) + return promise + } + + function readBlobAsText(blob) { + var reader = new FileReader() + var promise = fileReaderReady(reader) + reader.readAsText(blob) + return promise + } + + function readArrayBufferAsText(buf) { + var view = new Uint8Array(buf) + var chars = new Array(view.length) + + for (var i = 0; i < view.length; i++) { + chars[i] = String.fromCharCode(view[i]) + } + return chars.join('') + } + + function bufferClone(buf) { + if (buf.slice) { + return buf.slice(0) + } else { + var view = new Uint8Array(buf.byteLength) + view.set(new Uint8Array(buf)) + return view.buffer + } + } + + function Body() { + this.bodyUsed = false + + this._initBody = function(body) { + this._bodyInit = body + if (!body) { + this._bodyText = '' + } else if (typeof body === 'string') { + this._bodyText = body + } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body + } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body + } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString() + } else if (support.arrayBuffer && support.blob && isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer) + // IE 10-11 can't handle a DataView body. + this._bodyInit = new Blob([this._bodyArrayBuffer]) + } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body) + } else { + throw new Error('unsupported BodyInit type') + } + + if (!this.headers.get('content-type')) { + if (typeof body === 'string') { + this.headers.set('content-type', 'text/plain;charset=UTF-8') + } else if (this._bodyBlob && this._bodyBlob.type) { + this.headers.set('content-type', this._bodyBlob.type) + } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') + } + } + } + + if (support.blob) { + this.blob = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob) + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob') + } else { + return Promise.resolve(new Blob([this._bodyText])) + } + } + + this.arrayBuffer = function() { + if (this._bodyArrayBuffer) { + return consumed(this) || Promise.resolve(this._bodyArrayBuffer) + } else { + return this.blob().then(readBlobAsArrayBuffer) + } + } + } + + this.text = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return readBlobAsText(this._bodyBlob) + } else if (this._bodyArrayBuffer) { + return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as text') + } else { + return Promise.resolve(this._bodyText) + } + } + + if (support.formData) { + this.formData = function() { + return this.text().then(decode) + } + } + + this.json = function() { + return this.text().then(JSON.parse) + } + + return this + } + + // HTTP methods whose capitalization should be normalized + var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + function normalizeMethod(method) { + var upcased = method.toUpperCase() + return (methods.indexOf(upcased) > -1) ? upcased : method + } + + function Request(input, options) { + options = options || {} + var body = options.body + + if (input instanceof Request) { + if (input.bodyUsed) { + throw new TypeError('Already read') + } + this.url = input.url + this.credentials = input.credentials + if (!options.headers) { + this.headers = new Headers(input.headers) + } + this.method = input.method + this.mode = input.mode + if (!body && input._bodyInit != null) { + body = input._bodyInit + input.bodyUsed = true + } + } else { + this.url = String(input) + } + + this.credentials = options.credentials || this.credentials || 'omit' + if (options.headers || !this.headers) { + this.headers = new Headers(options.headers) + } + this.method = normalizeMethod(options.method || this.method || 'GET') + this.mode = options.mode || this.mode || null + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(body) + } + + Request.prototype.clone = function() { + return new Request(this, { body: this._bodyInit }) + } + + function decode(body) { + var form = new FormData() + body.trim().split('&').forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form + } + + function parseHeaders(rawHeaders) { + var headers = new Headers() + rawHeaders.split(/\r?\n/).forEach(function(line) { + var parts = line.split(':') + var key = parts.shift().trim() + if (key) { + var value = parts.join(':').trim() + headers.append(key, value) + } + }) + return headers + } + + Body.call(Request.prototype) + + function Response(bodyInit, options) { + if (!options) { + options = {} + } + + this.type = 'default' + this.status = 'status' in options ? options.status : 200 + this.ok = this.status >= 200 && this.status < 300 + this.statusText = 'statusText' in options ? options.statusText : 'OK' + this.headers = new Headers(options.headers) + this.url = options.url || '' + this._initBody(bodyInit) + } + + Body.call(Response.prototype) + + Response.prototype.clone = function() { + return new Response(this._bodyInit, { + status: this.status, + statusText: this.statusText, + headers: new Headers(this.headers), + url: this.url + }) + } + + Response.error = function() { + var response = new Response(null, {status: 0, statusText: ''}) + response.type = 'error' + return response + } + + var redirectStatuses = [301, 302, 303, 307, 308] + + Response.redirect = function(url, status) { + if (redirectStatuses.indexOf(status) === -1) { + throw new RangeError('Invalid status code') + } + + return new Response(null, {status: status, headers: {location: url}}) + } + + self.Headers = Headers + self.Request = Request + self.Response = Response + + self.fetch = function(input, init) { + return new Promise(function(resolve, reject) { + var request = new Request(input, init) + var xhr = new XMLHttpRequest() + + xhr.onload = function() { + var options = { + status: xhr.status, + statusText: xhr.statusText, + headers: parseHeaders(xhr.getAllResponseHeaders() || '') + } + options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') + var body = 'response' in xhr ? xhr.response : xhr.responseText + resolve(new Response(body, options)) + } + + xhr.onerror = function() { + reject(new TypeError('Network request failed')) + } + + xhr.ontimeout = function() { + reject(new TypeError('Network request failed')) + } + + xhr.open(request.method, request.url, true) + + if (request.credentials === 'include') { + xhr.withCredentials = true + } + + if ('responseType' in xhr && support.blob) { + xhr.responseType = 'blob' + } + + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value) + }) + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) + }) + } + self.fetch.polyfill = true +})(typeof self !== 'undefined' ? self : this); + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var stringify = __webpack_require__(7); +var parse = __webpack_require__(8); +var formats = __webpack_require__(1); + +module.exports = { + formats: formats, + parse: parse, + stringify: stringify +}; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(0); +var formats = __webpack_require__(1); + +var arrayPrefixGenerators = { + brackets: function brackets(prefix) { // eslint-disable-line func-name-matching + return prefix + '[]'; + }, + indices: function indices(prefix, key) { // eslint-disable-line func-name-matching + return prefix + '[' + key + ']'; + }, + repeat: function repeat(prefix) { // eslint-disable-line func-name-matching + return prefix; + } +}; + +var toISO = Date.prototype.toISOString; + +var defaults = { + delimiter: '&', + encode: true, + encoder: utils.encode, + encodeValuesOnly: false, + serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching + return toISO.call(date); + }, + skipNulls: false, + strictNullHandling: false +}; + +var stringify = function stringify( // eslint-disable-line func-name-matching + object, + prefix, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly +) { + var obj = object; + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate(obj); + } else if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix; + } + + obj = ''; + } + + if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { + if (encoder) { + var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder); + return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))]; + } + return [formatter(prefix) + '=' + formatter(String(obj))]; + } + + var values = []; + + if (typeof obj === 'undefined') { + return values; + } + + var objKeys; + if (Array.isArray(filter)) { + objKeys = filter; + } else { + var keys = Object.keys(obj); + objKeys = sort ? keys.sort(sort) : keys; + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (skipNulls && obj[key] === null) { + continue; + } + + if (Array.isArray(obj)) { + values = values.concat(stringify( + obj[key], + generateArrayPrefix(prefix, key), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly + )); + } else { + values = values.concat(stringify( + obj[key], + prefix + (allowDots ? '.' + key : '[' + key + ']'), + generateArrayPrefix, + strictNullHandling, + skipNulls, + encoder, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly + )); + } + } + + return values; +}; + +module.exports = function (object, opts) { + var obj = object; + var options = opts ? utils.assign({}, opts) : {}; + + if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; + var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; + var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; + var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; + var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; + var sort = typeof options.sort === 'function' ? options.sort : null; + var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; + var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; + var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; + if (typeof options.format === 'undefined') { + options.format = formats['default']; + } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { + throw new TypeError('Unknown format option provided.'); + } + var formatter = formats.formatters[options.format]; + var objKeys; + var filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (Array.isArray(options.filter)) { + filter = options.filter; + objKeys = filter; + } + + var keys = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + var arrayFormat; + if (options.arrayFormat in arrayPrefixGenerators) { + arrayFormat = options.arrayFormat; + } else if ('indices' in options) { + arrayFormat = options.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + + if (!objKeys) { + objKeys = Object.keys(obj); + } + + if (sort) { + objKeys.sort(sort); + } + + for (var i = 0; i < objKeys.length; ++i) { + var key = objKeys[i]; + + if (skipNulls && obj[key] === null) { + continue; + } + + keys = keys.concat(stringify( + obj[key], + key, + generateArrayPrefix, + strictNullHandling, + skipNulls, + encode ? encoder : null, + filter, + sort, + allowDots, + serializeDate, + formatter, + encodeValuesOnly + )); + } + + var joined = keys.join(delimiter); + var prefix = options.addQueryPrefix === true ? '?' : ''; + + return joined.length > 0 ? prefix + joined : ''; +}; + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(0); + +var has = Object.prototype.hasOwnProperty; + +var defaults = { + allowDots: false, + allowPrototypes: false, + arrayLimit: 20, + decoder: utils.decode, + delimiter: '&', + depth: 5, + parameterLimit: 1000, + plainObjects: false, + strictNullHandling: false +}; + +var parseValues = function parseQueryStringValues(str, options) { + var obj = {}; + var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; + var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; + var parts = cleanStr.split(options.delimiter, limit); + + for (var i = 0; i < parts.length; ++i) { + var part = parts[i]; + + var bracketEqualsPos = part.indexOf(']='); + var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1; + + var key, val; + if (pos === -1) { + key = options.decoder(part, defaults.decoder); + val = options.strictNullHandling ? null : ''; + } else { + key = options.decoder(part.slice(0, pos), defaults.decoder); + val = options.decoder(part.slice(pos + 1), defaults.decoder); + } + if (has.call(obj, key)) { + obj[key] = [].concat(obj[key]).concat(val); + } else { + obj[key] = val; + } + } + + return obj; +}; + +var parseObject = function (chain, val, options) { + var leaf = val; + + for (var i = chain.length - 1; i >= 0; --i) { + var obj; + var root = chain[i]; + + if (root === '[]') { + obj = []; + obj = obj.concat(leaf); + } else { + obj = options.plainObjects ? Object.create(null) : {}; + var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; + var index = parseInt(cleanRoot, 10); + if ( + !isNaN(index) + && root !== cleanRoot + && String(index) === cleanRoot + && index >= 0 + && (options.parseArrays && index <= options.arrayLimit) + ) { + obj = []; + obj[index] = leaf; + } else { + obj[cleanRoot] = leaf; + } + } + + leaf = obj; + } + + return leaf; +}; + +var parseKeys = function parseQueryStringKeys(givenKey, val, options) { + if (!givenKey) { + return; + } + + // Transform dot notation to bracket notation + var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey; + + // The regex chunks + + var brackets = /(\[[^[\]]*])/; + var child = /(\[[^[\]]*])/g; + + // Get the parent + + var segment = brackets.exec(key); + var parent = segment ? key.slice(0, segment.index) : key; + + // Stash the parent if it exists + + var keys = []; + if (parent) { + // If we aren't using plain objects, optionally prefix keys + // that would overwrite object prototype properties + if (!options.plainObjects && has.call(Object.prototype, parent)) { + if (!options.allowPrototypes) { + return; + } + } + + keys.push(parent); + } + + // Loop through children appending to the array until we hit depth + + var i = 0; + while ((segment = child.exec(key)) !== null && i < options.depth) { + i += 1; + if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) { + if (!options.allowPrototypes) { + return; + } + } + keys.push(segment[1]); + } + + // If there's a remainder, just add whatever is left + + if (segment) { + keys.push('[' + key.slice(segment.index) + ']'); + } + + return parseObject(keys, val, options); +}; + +module.exports = function (str, opts) { + var options = opts ? utils.assign({}, opts) : {}; + + if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { + throw new TypeError('Decoder has to be a function.'); + } + + options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; + options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; + options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; + options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; + options.parseArrays = options.parseArrays !== false; + options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; + options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots; + options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; + options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; + options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; + options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; + + if (str === '' || str === null || typeof str === 'undefined') { + return options.plainObjects ? Object.create(null) : {}; + } + + var tempObj = typeof str === 'string' ? parseValues(str, options) : str; + var obj = options.plainObjects ? Object.create(null) : {}; + + // Iterate over the keys and setup the new object + + var keys = Object.keys(tempObj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var newObj = parseKeys(key, tempObj[key], options); + obj = utils.merge(obj, newObj, options); + } + + return utils.compact(obj); +}; + + +/***/ }), +/* 9 */ +/***/ (function(module, exports) { + +/** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. If `immediate` is passed, trigger the function on the + * leading edge, instead of the trailing. The function also has a property 'clear' + * that is a function which will clear the timer to prevent previously scheduled executions. + * + * @source underscore.js + * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/ + * @param {Function} function to wrap + * @param {Number} timeout in ms (`100`) + * @param {Boolean} whether to execute at the beginning (`false`) + * @api public + */ + +module.exports = function debounce(func, wait, immediate){ + var timeout, args, context, timestamp, result; + if (null == wait) wait = 100; + + function later() { + var last = Date.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + + var debounced = function(){ + context = this; + args = arguments; + timestamp = Date.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + + debounced.clear = function() { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + }; + + debounced.flush = function() { + if (timeout) { + result = func.apply(context, args); + context = args = null; + + clearTimeout(timeout); + timeout = null; + } + }; + + return debounced; +}; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports) { + +// removed by extract-text-webpack-plugin + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js similarity index 100% rename from blocks/src/blocks/group-list.js rename to includes/blocks/src/blocks/group-list.js diff --git a/blocks/src/group-list.js b/includes/blocks/src/group-list.js similarity index 100% rename from blocks/src/group-list.js rename to includes/blocks/src/group-list.js diff --git a/blocks/src/index.js b/includes/blocks/src/index.js similarity index 100% rename from blocks/src/index.js rename to includes/blocks/src/index.js diff --git a/blocks/src/single.js b/includes/blocks/src/single.js similarity index 100% rename from blocks/src/single.js rename to includes/blocks/src/single.js diff --git a/includes/blocks/src/style.css b/includes/blocks/src/style.css new file mode 100644 index 0000000..e69de29 diff --git a/class-meetup-api-v3.php b/includes/class-meetup-api-v3.php similarity index 100% rename from class-meetup-api-v3.php rename to includes/class-meetup-api-v3.php diff --git a/class-meetup-widget.php b/includes/class-meetup-widget.php similarity index 100% rename from class-meetup-widget.php rename to includes/class-meetup-widget.php diff --git a/class-meetup-widgets-admin.php b/includes/class-meetup-widgets-admin.php similarity index 100% rename from class-meetup-widgets-admin.php rename to includes/class-meetup-widgets-admin.php diff --git a/templates/meetup-list.php b/includes/templates/meetup-list.php similarity index 100% rename from templates/meetup-list.php rename to includes/templates/meetup-list.php diff --git a/templates/meetup-single.php b/includes/templates/meetup-single.php similarity index 100% rename from templates/meetup-single.php rename to includes/templates/meetup-single.php diff --git a/widgets/group-list.php b/includes/widgets/group-list.php similarity index 100% rename from widgets/group-list.php rename to includes/widgets/group-list.php diff --git a/widgets/single.php b/includes/widgets/single.php similarity index 100% rename from widgets/single.php rename to includes/widgets/single.php diff --git a/widgets/user-list.php b/includes/widgets/user-list.php similarity index 100% rename from widgets/user-list.php rename to includes/widgets/user-list.php diff --git a/vs_meetup.php b/vs_meetup.php index 37d34bc..77f0beb 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -17,14 +17,18 @@ } if ( ! defined( 'VSMEET_TEMPLATE_DIR' ) ) { - define( 'VSMEET_TEMPLATE_DIR', dirname( __FILE__ ) . '/templates/' ); + define( 'VSMEET_TEMPLATE_DIR', dirname( __FILE__ ) . '/includes/templates/' ); } -require_once( 'class-meetup-widgets-admin.php' ); -require_once( 'class-meetup-widget.php' ); -require_once( 'class-meetup-api-v3.php' ); -require_once( 'api/class-meetup-rest-events-controller.php' ); -require_once( 'api/class-meetup-rest-groups-controller.php' ); +require_once( 'includes/class-meetup-widgets-admin.php' ); +require_once( 'includes/class-meetup-widget.php' ); +require_once( 'includes/class-meetup-api-v3.php' ); +require_once( 'includes/api/class-meetup-rest-events-controller.php' ); +require_once( 'includes/api/class-meetup-rest-groups-controller.php' ); +require_once( 'includes/blocks/blocks.php' ); +require_once( 'includes/widgets/single.php' ); +require_once( 'includes/widgets/group-list.php' ); +require_once( 'includes/widgets/user-list.php' ); /** * Initialize Meetup Widgets @@ -39,25 +43,20 @@ function meetup_widgets_start() { } add_action( 'init', 'meetup_widgets_start' ); -require_once( 'widgets/single.php' ); add_action( 'widgets_init', function() { return register_widget( 'VsMeetSingleWidget' ); } ); -require_once( 'widgets/group-list.php' ); add_action( 'widgets_init', function() { return register_widget( 'VsMeetListWidget' ); } ); -require_once( 'widgets/user-list.php' ); add_action( 'widgets_init', function() { return register_widget( 'VsMeetUserListWidget' ); } ); - -require_once( 'blocks/blocks.php' ); From df7614109969a8a37c23579bc667f543b1c85e18 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 18:57:14 -0500 Subject: [PATCH 27/56] Move blocks.php to a class structure --- includes/blocks/blocks.php | 79 ------------------- includes/class-meetup-widgets-blocks.php | 99 ++++++++++++++++++++++++ vs_meetup.php | 5 +- 3 files changed, 102 insertions(+), 81 deletions(-) delete mode 100755 includes/blocks/blocks.php create mode 100755 includes/class-meetup-widgets-blocks.php diff --git a/includes/blocks/blocks.php b/includes/blocks/blocks.php deleted file mode 100755 index 029d037..0000000 --- a/includes/blocks/blocks.php +++ /dev/null @@ -1,79 +0,0 @@ -set_query_params( array( - 'per_page' => $attributes['per_page'], - ) ); - $response = rest_do_request( $request ); - if ( 200 !== $response->get_status() ) { - return '

    ' . $attributes['title'] . '

    '; - } - - $events = $response->get_data(); - $block_content = [ '

    ' . $attributes['title'] . '

    ', '
      ' ]; - array_push( $block_content, '

      ' . $attributes['group'] . '

      ' ); - foreach ( $events as $event ) { - $list_item = '
    • ' . $event['name'] . ' ' . $event['date'] . '
    • '; - array_push( $block_content, $list_item ); - } - array_push( $block_content, '
    ' ); - - return implode( "\n", $block_content ); -} - -register_block_type( 'meetup-widgets/group-list', array( - 'attributes' => array( - 'title' => array( - 'type' => 'string', - 'default' => __( 'Upcoming Events', 'meetup-widgets' ), - ), - 'placeholder' => array( - 'type' => 'string', - 'default' => __( 'No upcoming events.', 'meetup-widgets' ), - ), - 'group' => array( - 'type' => 'string', - ), - 'per_page' => array( - 'type' => 'number', - 'default' => 5, - ), - ), - 'render_callback' => 'vs_meetup_widgets_render_block_group_list', -) ); diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php new file mode 100755 index 0000000..ba653ef --- /dev/null +++ b/includes/class-meetup-widgets-blocks.php @@ -0,0 +1,99 @@ +dir = dirname( dirname( __FILE__ ) ); + + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_assets' ) ); + + register_block_type( 'meetup-widgets/group-list', array( + 'attributes' => array( + 'title' => array( + 'type' => 'string', + 'default' => __( 'Upcoming Events', 'meetup-widgets' ), + ), + 'placeholder' => array( + 'type' => 'string', + 'default' => __( 'No upcoming events.', 'meetup-widgets' ), + ), + 'group' => array( + 'type' => 'string', + ), + 'per_page' => array( + 'type' => 'number', + 'default' => 5, + ), + ), + 'render_callback' => array( $this, 'render_block_group_list' ), + ) ); + } + + /** + * Add Gutenberg block JS & CSS to the editor + */ + public function enqueue_block_assets() { + $js_file = 'http://localhost:8081/build/index.js'; + $css_file = 'http://localhost:8081/build/editor.css'; + $js_version = false; + $css_version = false; + + if ( ! WP_DEBUG ) { + $js_file = plugins_url( 'build/index.js', dirname( __FILE__ ) ); + $css_file = plugins_url( 'build/editor.css', dirname( __FILE__ ) ); + $js_version = filemtime( "{$this->dir}/build/index.js" ); + $css_version = filemtime( "{$this->dir}/build/editor.css" ); + } + + wp_enqueue_script( 'meetup-blocks', $js_file, [ 'wp-blocks', 'wp-i18n', 'wp-element' ], $js_version ); + wp_enqueue_style( 'meetup-blocks', $css_file, [ 'wp-blocks' ], $css_version ); + } + + /** + * Renders the `core/latest-posts` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the post content with latest posts added. + */ + public function render_block_group_list( $attributes ) { + $request = new WP_REST_Request( 'GET', '/meetup/v1/events/' . $attributes['group'] ); + $request->set_query_params( array( + 'per_page' => $attributes['per_page'], + ) ); + $response = rest_do_request( $request ); + if ( 200 !== $response->get_status() ) { + return '

    ' . $attributes['title'] . '

    '; + } + + $events = $response->get_data(); + $block_content = [ '

    ' . $attributes['title'] . '

    ', '
      ' ]; + array_push( $block_content, '

      ' . $attributes['group'] . '

      ' ); + foreach ( $events as $event ) { + $list_item = '
    • ' . $event['name'] . ' ' . $event['date'] . '
    • '; + array_push( $block_content, $list_item ); + } + array_push( $block_content, '
    ' ); + + return implode( "\n", $block_content ); + } +} diff --git a/vs_meetup.php b/vs_meetup.php index 77f0beb..c4fd0fb 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -20,12 +20,12 @@ define( 'VSMEET_TEMPLATE_DIR', dirname( __FILE__ ) . '/includes/templates/' ); } -require_once( 'includes/class-meetup-widgets-admin.php' ); require_once( 'includes/class-meetup-widget.php' ); +require_once( 'includes/class-meetup-widgets-admin.php' ); +require_once( 'includes/class-meetup-widgets-blocks.php' ); require_once( 'includes/class-meetup-api-v3.php' ); require_once( 'includes/api/class-meetup-rest-events-controller.php' ); require_once( 'includes/api/class-meetup-rest-groups-controller.php' ); -require_once( 'includes/blocks/blocks.php' ); require_once( 'includes/widgets/single.php' ); require_once( 'includes/widgets/group-list.php' ); require_once( 'includes/widgets/user-list.php' ); @@ -35,6 +35,7 @@ */ function meetup_widgets_start() { new Meetup_Widgets_Admin(); + new Meetup_Widgets_Blocks(); $event_controller = new Meetup_REST_Events_Controller(); $event_controller->register_routes(); From 2fbfd06d2a35dc223c0b47bb288b2b83c7db68ca Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 18:58:06 -0500 Subject: [PATCH 28/56] Send build files to a folder in plugin root, ignore build --- .gitignore | 1 + includes/blocks/build/editor.css | 0 includes/blocks/build/index.js | 1458 ------------------------------ webpack.config.js | 30 +- 4 files changed, 16 insertions(+), 1473 deletions(-) delete mode 100644 includes/blocks/build/editor.css delete mode 100644 includes/blocks/build/index.js diff --git a/.gitignore b/.gitignore index 562c476..ff34853 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ phpcs.xml yarn.lock package-lock.json mock-data +build diff --git a/includes/blocks/build/editor.css b/includes/blocks/build/editor.css deleted file mode 100644 index e69de29..0000000 diff --git a/includes/blocks/build/index.js b/includes/blocks/build/index.js deleted file mode 100644 index 51e1c26..0000000 --- a/includes/blocks/build/index.js +++ /dev/null @@ -1,1458 +0,0 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 2); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var has = Object.prototype.hasOwnProperty; - -var hexTable = (function () { - var array = []; - for (var i = 0; i < 256; ++i) { - array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); - } - - return array; -}()); - -var compactQueue = function compactQueue(queue) { - var obj; - - while (queue.length) { - var item = queue.pop(); - obj = item.obj[item.prop]; - - if (Array.isArray(obj)) { - var compacted = []; - - for (var j = 0; j < obj.length; ++j) { - if (typeof obj[j] !== 'undefined') { - compacted.push(obj[j]); - } - } - - item.obj[item.prop] = compacted; - } - } - - return obj; -}; - -exports.arrayToObject = function arrayToObject(source, options) { - var obj = options && options.plainObjects ? Object.create(null) : {}; - for (var i = 0; i < source.length; ++i) { - if (typeof source[i] !== 'undefined') { - obj[i] = source[i]; - } - } - - return obj; -}; - -exports.merge = function merge(target, source, options) { - if (!source) { - return target; - } - - if (typeof source !== 'object') { - if (Array.isArray(target)) { - target.push(source); - } else if (typeof target === 'object') { - if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) { - target[source] = true; - } - } else { - return [target, source]; - } - - return target; - } - - if (typeof target !== 'object') { - return [target].concat(source); - } - - var mergeTarget = target; - if (Array.isArray(target) && !Array.isArray(source)) { - mergeTarget = exports.arrayToObject(target, options); - } - - if (Array.isArray(target) && Array.isArray(source)) { - source.forEach(function (item, i) { - if (has.call(target, i)) { - if (target[i] && typeof target[i] === 'object') { - target[i] = exports.merge(target[i], item, options); - } else { - target.push(item); - } - } else { - target[i] = item; - } - }); - return target; - } - - return Object.keys(source).reduce(function (acc, key) { - var value = source[key]; - - if (has.call(acc, key)) { - acc[key] = exports.merge(acc[key], value, options); - } else { - acc[key] = value; - } - return acc; - }, mergeTarget); -}; - -exports.assign = function assignSingleSource(target, source) { - return Object.keys(source).reduce(function (acc, key) { - acc[key] = source[key]; - return acc; - }, target); -}; - -exports.decode = function (str) { - try { - return decodeURIComponent(str.replace(/\+/g, ' ')); - } catch (e) { - return str; - } -}; - -exports.encode = function encode(str) { - // This code was originally written by Brian White (mscdex) for the io.js core querystring library. - // It has been adapted here for stricter adherence to RFC 3986 - if (str.length === 0) { - return str; - } - - var string = typeof str === 'string' ? str : String(str); - - var out = ''; - for (var i = 0; i < string.length; ++i) { - var c = string.charCodeAt(i); - - if ( - c === 0x2D // - - || c === 0x2E // . - || c === 0x5F // _ - || c === 0x7E // ~ - || (c >= 0x30 && c <= 0x39) // 0-9 - || (c >= 0x41 && c <= 0x5A) // a-z - || (c >= 0x61 && c <= 0x7A) // A-Z - ) { - out += string.charAt(i); - continue; - } - - if (c < 0x80) { - out = out + hexTable[c]; - continue; - } - - if (c < 0x800) { - out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]); - continue; - } - - if (c < 0xD800 || c >= 0xE000) { - out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]); - continue; - } - - i += 1; - c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF)); - out += hexTable[0xF0 | (c >> 18)] - + hexTable[0x80 | ((c >> 12) & 0x3F)] - + hexTable[0x80 | ((c >> 6) & 0x3F)] - + hexTable[0x80 | (c & 0x3F)]; - } - - return out; -}; - -exports.compact = function compact(value) { - var queue = [{ obj: { o: value }, prop: 'o' }]; - var refs = []; - - for (var i = 0; i < queue.length; ++i) { - var item = queue[i]; - var obj = item.obj[item.prop]; - - var keys = Object.keys(obj); - for (var j = 0; j < keys.length; ++j) { - var key = keys[j]; - var val = obj[key]; - if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { - queue.push({ obj: obj, prop: key }); - refs.push(val); - } - } - } - - return compactQueue(queue); -}; - -exports.isRegExp = function isRegExp(obj) { - return Object.prototype.toString.call(obj) === '[object RegExp]'; -}; - -exports.isBuffer = function isBuffer(obj) { - if (obj === null || typeof obj === 'undefined') { - return false; - } - - return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); -}; - - -/***/ }), -/* 1 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var replace = String.prototype.replace; -var percentTwenties = /%20/g; - -module.exports = { - 'default': 'RFC3986', - formatters: { - RFC1738: function (value) { - return replace.call(value, percentTwenties, '+'); - }, - RFC3986: function (value) { - return value; - } - }, - RFC1738: 'RFC1738', - RFC3986: 'RFC3986' -}; - - -/***/ }), -/* 2 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__single__ = __webpack_require__(3); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__group_list__ = __webpack_require__(4); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__style_css__ = __webpack_require__(10); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__style_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__style_css__); -var registerBlockType = wp.blocks.registerBlockType; - - - - - - -registerBlockType('meetup-widgets/single', __WEBPACK_IMPORTED_MODULE_0__single__["a" /* default */]); -registerBlockType('meetup-widgets/group-list', __WEBPACK_IMPORTED_MODULE_1__group_list__["a" /* default */]); - -/***/ }), -/* 3 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -var __ = wp.i18n.__; - -// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API - -/* harmony default export */ __webpack_exports__["a"] = ({ - title: __('Meetup.com Event', 'meetup-widgets'), - - icon: 'groups', - - category: 'embed', - - // Remove to make block editable in HTML mode. - supportHTML: false, - - edit: function edit(props) { - return __('Single event here! šŸŽŠ', 'meetup-widgets'); - }, - - save: function save() { - return null; - } -}); - -/***/ }), -/* 4 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_whatwg_fetch__ = __webpack_require__(5); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_whatwg_fetch___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_whatwg_fetch__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_qs__ = __webpack_require__(6); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_qs___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_qs__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_debounce__ = __webpack_require__(9); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_debounce___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_debounce__); -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - - - - -var __ = wp.i18n.__; -var _wp$blocks = wp.blocks, - BlockDescription = _wp$blocks.BlockDescription, - Editable = _wp$blocks.Editable, - InspectorControls = _wp$blocks.InspectorControls, - _wp$blocks$InspectorC = _wp$blocks.InspectorControls, - RangeControl = _wp$blocks$InspectorC.RangeControl, - TextControl = _wp$blocks$InspectorC.TextControl; - - -var translate = function translate(str) { - return __(str, 'meetup-widgets'); -}; - -var getMeetupEvents = function getMeetupEvents(_ref) { - var limit = _ref.limit, - group = _ref.group; - - if (!group || limit <= 0) { - return; - } - var args = { - status: 'upcoming', - page: limit, - group_urlname: group - }; - return []; -}; - -var defaultAttributes = { - title: false, - limit: 5, - group: '', - response: false, - formattedEvents: null -}; - -// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API -/* harmony default export */ __webpack_exports__["a"] = ({ - title: translate('Meetup.com List'), - description: translate('This is a list of events for a given group on Meetup.com'), - - icon: 'editor-ul', - - category: 'embed', - - // Remove to make block editable in HTML mode. - supportHTML: false, - - edit: function edit(_ref2) { - var attributes = _ref2.attributes, - setAttributes = _ref2.setAttributes, - focus = _ref2.focus, - setFocus = _ref2.setFocus; - - // Inject default attributes - attributes = _extends({}, defaultAttributes, attributes); - getMeetupEvents(attributes); - - var onChangeEditable = function onChangeEditable(field) { - return function (value) { - return setAttributes(_defineProperty({}, field, value)); - }; - }; - - var onFocus = function onFocus(field) { - return function (focus) { - return setFocus(_extends({}, focus, { editable: field })); - }; - }; - - var focusedEditable = focus ? focus.editable || 'title' : null; - - var controls = focus && wp.element.createElement( - InspectorControls, - { key: 'meetup-inspector' }, - wp.element.createElement(TextControl, { - label: translate('Group Name'), - value: attributes.group, - onChange: onChangeEditable('group') - }), - wp.element.createElement(RangeControl, { - label: translate('Number of event to show'), - value: attributes.limit, - onChange: onChangeEditable('limit'), - min: 2, - max: 15 - }) - ); - - return [controls, wp.element.createElement( - 'div', - { className: 'meetup-widgets', key: 'meetup-display' }, - wp.element.createElement(Editable, { - tagName: 'h3', - placeholder: translate('Upcoming Events'), - onChange: onChangeEditable('title'), - focus: focusedEditable === 'title', - onFocus: onFocus('title'), - className: 'meetup-widgets-title', - value: attributes.title - }), - wp.element.createElement( - 'ul', - { className: 'meetup-widgets-list' }, - attributes.formattedEvents - ) - )]; - }, - - save: function save(_ref3) { - var attributes = _ref3.attributes; - - return wp.element.createElement( - 'div', - { className: 'meetup-widgets' }, - wp.element.createElement( - 'h3', - null, - attributes.title - ), - wp.element.createElement( - 'ul', - { className: 'meetup-widgets-list' }, - attributes.formattedEvents - ) - ); - } -}); - -/***/ }), -/* 5 */ -/***/ (function(module, exports) { - -(function(self) { - 'use strict'; - - if (self.fetch) { - return - } - - var support = { - searchParams: 'URLSearchParams' in self, - iterable: 'Symbol' in self && 'iterator' in Symbol, - blob: 'FileReader' in self && 'Blob' in self && (function() { - try { - new Blob() - return true - } catch(e) { - return false - } - })(), - formData: 'FormData' in self, - arrayBuffer: 'ArrayBuffer' in self - } - - if (support.arrayBuffer) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ] - - var isDataView = function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj) - } - - var isArrayBufferView = ArrayBuffer.isView || function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 - } - } - - function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { - throw new TypeError('Invalid character in header field name') - } - return name.toLowerCase() - } - - function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value - } - - // Build a destructive iterator for the value list - function iteratorFor(items) { - var iterator = { - next: function() { - var value = items.shift() - return {done: value === undefined, value: value} - } - } - - if (support.iterable) { - iterator[Symbol.iterator] = function() { - return iterator - } - } - - return iterator - } - - function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - } else if (Array.isArray(headers)) { - headers.forEach(function(header) { - this.append(header[0], header[1]) - }, this) - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } - } - - Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var oldValue = this.map[name] - this.map[name] = oldValue ? oldValue+','+value : value - } - - Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] - } - - Headers.prototype.get = function(name) { - name = normalizeName(name) - return this.has(name) ? this.map[name] : null - } - - Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) - } - - Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = normalizeValue(value) - } - - Headers.prototype.forEach = function(callback, thisArg) { - for (var name in this.map) { - if (this.map.hasOwnProperty(name)) { - callback.call(thisArg, this.map[name], name, this) - } - } - } - - Headers.prototype.keys = function() { - var items = [] - this.forEach(function(value, name) { items.push(name) }) - return iteratorFor(items) - } - - Headers.prototype.values = function() { - var items = [] - this.forEach(function(value) { items.push(value) }) - return iteratorFor(items) - } - - Headers.prototype.entries = function() { - var items = [] - this.forEach(function(value, name) { items.push([name, value]) }) - return iteratorFor(items) - } - - if (support.iterable) { - Headers.prototype[Symbol.iterator] = Headers.prototype.entries - } - - function consumed(body) { - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true - } - - function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) - } - - function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsArrayBuffer(blob) - return promise - } - - function readBlobAsText(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsText(blob) - return promise - } - - function readArrayBufferAsText(buf) { - var view = new Uint8Array(buf) - var chars = new Array(view.length) - - for (var i = 0; i < view.length; i++) { - chars[i] = String.fromCharCode(view[i]) - } - return chars.join('') - } - - function bufferClone(buf) { - if (buf.slice) { - return buf.slice(0) - } else { - var view = new Uint8Array(buf.byteLength) - view.set(new Uint8Array(buf)) - return view.buffer - } - } - - function Body() { - this.bodyUsed = false - - this._initBody = function(body) { - this._bodyInit = body - if (!body) { - this._bodyText = '' - } else if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString() - } else if (support.arrayBuffer && support.blob && isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer) - // IE 10-11 can't handle a DataView body. - this._bodyInit = new Blob([this._bodyArrayBuffer]) - } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body) - } else { - throw new Error('unsupported BodyInit type') - } - - if (!this.headers.get('content-type')) { - if (typeof body === 'string') { - this.headers.set('content-type', 'text/plain;charset=UTF-8') - } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set('content-type', this._bodyBlob.type) - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') - } - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - - this.arrayBuffer = function() { - if (this._bodyArrayBuffer) { - return consumed(this) || Promise.resolve(this._bodyArrayBuffer) - } else { - return this.blob().then(readBlobAsArrayBuffer) - } - } - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this - } - - // HTTP methods whose capitalization should be normalized - var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - function normalizeMethod(method) { - var upcased = method.toUpperCase() - return (methods.indexOf(upcased) > -1) ? upcased : method - } - - function Request(input, options) { - options = options || {} - var body = options.body - - if (input instanceof Request) { - if (input.bodyUsed) { - throw new TypeError('Already read') - } - this.url = input.url - this.credentials = input.credentials - if (!options.headers) { - this.headers = new Headers(input.headers) - } - this.method = input.method - this.mode = input.mode - if (!body && input._bodyInit != null) { - body = input._bodyInit - input.bodyUsed = true - } - } else { - this.url = String(input) - } - - this.credentials = options.credentials || this.credentials || 'omit' - if (options.headers || !this.headers) { - this.headers = new Headers(options.headers) - } - this.method = normalizeMethod(options.method || this.method || 'GET') - this.mode = options.mode || this.mode || null - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(body) - } - - Request.prototype.clone = function() { - return new Request(this, { body: this._bodyInit }) - } - - function decode(body) { - var form = new FormData() - body.trim().split('&').forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form - } - - function parseHeaders(rawHeaders) { - var headers = new Headers() - rawHeaders.split(/\r?\n/).forEach(function(line) { - var parts = line.split(':') - var key = parts.shift().trim() - if (key) { - var value = parts.join(':').trim() - headers.append(key, value) - } - }) - return headers - } - - Body.call(Request.prototype) - - function Response(bodyInit, options) { - if (!options) { - options = {} - } - - this.type = 'default' - this.status = 'status' in options ? options.status : 200 - this.ok = this.status >= 200 && this.status < 300 - this.statusText = 'statusText' in options ? options.statusText : 'OK' - this.headers = new Headers(options.headers) - this.url = options.url || '' - this._initBody(bodyInit) - } - - Body.call(Response.prototype) - - Response.prototype.clone = function() { - return new Response(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new Headers(this.headers), - url: this.url - }) - } - - Response.error = function() { - var response = new Response(null, {status: 0, statusText: ''}) - response.type = 'error' - return response - } - - var redirectStatuses = [301, 302, 303, 307, 308] - - Response.redirect = function(url, status) { - if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError('Invalid status code') - } - - return new Response(null, {status: status, headers: {location: url}}) - } - - self.Headers = Headers - self.Request = Request - self.Response = Response - - self.fetch = function(input, init) { - return new Promise(function(resolve, reject) { - var request = new Request(input, init) - var xhr = new XMLHttpRequest() - - xhr.onload = function() { - var options = { - status: xhr.status, - statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || '') - } - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') - var body = 'response' in xhr ? xhr.response : xhr.responseText - resolve(new Response(body, options)) - } - - xhr.onerror = function() { - reject(new TypeError('Network request failed')) - } - - xhr.ontimeout = function() { - reject(new TypeError('Network request failed')) - } - - xhr.open(request.method, request.url, true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } - - if ('responseType' in xhr && support.blob) { - xhr.responseType = 'blob' - } - - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) - - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) - } - self.fetch.polyfill = true -})(typeof self !== 'undefined' ? self : this); - - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var stringify = __webpack_require__(7); -var parse = __webpack_require__(8); -var formats = __webpack_require__(1); - -module.exports = { - formats: formats, - parse: parse, - stringify: stringify -}; - - -/***/ }), -/* 7 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = __webpack_require__(0); -var formats = __webpack_require__(1); - -var arrayPrefixGenerators = { - brackets: function brackets(prefix) { // eslint-disable-line func-name-matching - return prefix + '[]'; - }, - indices: function indices(prefix, key) { // eslint-disable-line func-name-matching - return prefix + '[' + key + ']'; - }, - repeat: function repeat(prefix) { // eslint-disable-line func-name-matching - return prefix; - } -}; - -var toISO = Date.prototype.toISOString; - -var defaults = { - delimiter: '&', - encode: true, - encoder: utils.encode, - encodeValuesOnly: false, - serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching - return toISO.call(date); - }, - skipNulls: false, - strictNullHandling: false -}; - -var stringify = function stringify( // eslint-disable-line func-name-matching - object, - prefix, - generateArrayPrefix, - strictNullHandling, - skipNulls, - encoder, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly -) { - var obj = object; - if (typeof filter === 'function') { - obj = filter(prefix, obj); - } else if (obj instanceof Date) { - obj = serializeDate(obj); - } else if (obj === null) { - if (strictNullHandling) { - return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix; - } - - obj = ''; - } - - if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { - if (encoder) { - var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder); - return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))]; - } - return [formatter(prefix) + '=' + formatter(String(obj))]; - } - - var values = []; - - if (typeof obj === 'undefined') { - return values; - } - - var objKeys; - if (Array.isArray(filter)) { - objKeys = filter; - } else { - var keys = Object.keys(obj); - objKeys = sort ? keys.sort(sort) : keys; - } - - for (var i = 0; i < objKeys.length; ++i) { - var key = objKeys[i]; - - if (skipNulls && obj[key] === null) { - continue; - } - - if (Array.isArray(obj)) { - values = values.concat(stringify( - obj[key], - generateArrayPrefix(prefix, key), - generateArrayPrefix, - strictNullHandling, - skipNulls, - encoder, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly - )); - } else { - values = values.concat(stringify( - obj[key], - prefix + (allowDots ? '.' + key : '[' + key + ']'), - generateArrayPrefix, - strictNullHandling, - skipNulls, - encoder, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly - )); - } - } - - return values; -}; - -module.exports = function (object, opts) { - var obj = object; - var options = opts ? utils.assign({}, opts) : {}; - - if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { - throw new TypeError('Encoder has to be a function.'); - } - - var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; - var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; - var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; - var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; - var sort = typeof options.sort === 'function' ? options.sort : null; - var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; - var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; - var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; - if (typeof options.format === 'undefined') { - options.format = formats['default']; - } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { - throw new TypeError('Unknown format option provided.'); - } - var formatter = formats.formatters[options.format]; - var objKeys; - var filter; - - if (typeof options.filter === 'function') { - filter = options.filter; - obj = filter('', obj); - } else if (Array.isArray(options.filter)) { - filter = options.filter; - objKeys = filter; - } - - var keys = []; - - if (typeof obj !== 'object' || obj === null) { - return ''; - } - - var arrayFormat; - if (options.arrayFormat in arrayPrefixGenerators) { - arrayFormat = options.arrayFormat; - } else if ('indices' in options) { - arrayFormat = options.indices ? 'indices' : 'repeat'; - } else { - arrayFormat = 'indices'; - } - - var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; - - if (!objKeys) { - objKeys = Object.keys(obj); - } - - if (sort) { - objKeys.sort(sort); - } - - for (var i = 0; i < objKeys.length; ++i) { - var key = objKeys[i]; - - if (skipNulls && obj[key] === null) { - continue; - } - - keys = keys.concat(stringify( - obj[key], - key, - generateArrayPrefix, - strictNullHandling, - skipNulls, - encode ? encoder : null, - filter, - sort, - allowDots, - serializeDate, - formatter, - encodeValuesOnly - )); - } - - var joined = keys.join(delimiter); - var prefix = options.addQueryPrefix === true ? '?' : ''; - - return joined.length > 0 ? prefix + joined : ''; -}; - - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = __webpack_require__(0); - -var has = Object.prototype.hasOwnProperty; - -var defaults = { - allowDots: false, - allowPrototypes: false, - arrayLimit: 20, - decoder: utils.decode, - delimiter: '&', - depth: 5, - parameterLimit: 1000, - plainObjects: false, - strictNullHandling: false -}; - -var parseValues = function parseQueryStringValues(str, options) { - var obj = {}; - var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; - var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; - var parts = cleanStr.split(options.delimiter, limit); - - for (var i = 0; i < parts.length; ++i) { - var part = parts[i]; - - var bracketEqualsPos = part.indexOf(']='); - var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1; - - var key, val; - if (pos === -1) { - key = options.decoder(part, defaults.decoder); - val = options.strictNullHandling ? null : ''; - } else { - key = options.decoder(part.slice(0, pos), defaults.decoder); - val = options.decoder(part.slice(pos + 1), defaults.decoder); - } - if (has.call(obj, key)) { - obj[key] = [].concat(obj[key]).concat(val); - } else { - obj[key] = val; - } - } - - return obj; -}; - -var parseObject = function (chain, val, options) { - var leaf = val; - - for (var i = chain.length - 1; i >= 0; --i) { - var obj; - var root = chain[i]; - - if (root === '[]') { - obj = []; - obj = obj.concat(leaf); - } else { - obj = options.plainObjects ? Object.create(null) : {}; - var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root; - var index = parseInt(cleanRoot, 10); - if ( - !isNaN(index) - && root !== cleanRoot - && String(index) === cleanRoot - && index >= 0 - && (options.parseArrays && index <= options.arrayLimit) - ) { - obj = []; - obj[index] = leaf; - } else { - obj[cleanRoot] = leaf; - } - } - - leaf = obj; - } - - return leaf; -}; - -var parseKeys = function parseQueryStringKeys(givenKey, val, options) { - if (!givenKey) { - return; - } - - // Transform dot notation to bracket notation - var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey; - - // The regex chunks - - var brackets = /(\[[^[\]]*])/; - var child = /(\[[^[\]]*])/g; - - // Get the parent - - var segment = brackets.exec(key); - var parent = segment ? key.slice(0, segment.index) : key; - - // Stash the parent if it exists - - var keys = []; - if (parent) { - // If we aren't using plain objects, optionally prefix keys - // that would overwrite object prototype properties - if (!options.plainObjects && has.call(Object.prototype, parent)) { - if (!options.allowPrototypes) { - return; - } - } - - keys.push(parent); - } - - // Loop through children appending to the array until we hit depth - - var i = 0; - while ((segment = child.exec(key)) !== null && i < options.depth) { - i += 1; - if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) { - if (!options.allowPrototypes) { - return; - } - } - keys.push(segment[1]); - } - - // If there's a remainder, just add whatever is left - - if (segment) { - keys.push('[' + key.slice(segment.index) + ']'); - } - - return parseObject(keys, val, options); -}; - -module.exports = function (str, opts) { - var options = opts ? utils.assign({}, opts) : {}; - - if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') { - throw new TypeError('Decoder has to be a function.'); - } - - options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; - options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; - options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; - options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; - options.parseArrays = options.parseArrays !== false; - options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder; - options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots; - options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects; - options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; - options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit; - options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; - - if (str === '' || str === null || typeof str === 'undefined') { - return options.plainObjects ? Object.create(null) : {}; - } - - var tempObj = typeof str === 'string' ? parseValues(str, options) : str; - var obj = options.plainObjects ? Object.create(null) : {}; - - // Iterate over the keys and setup the new object - - var keys = Object.keys(tempObj); - for (var i = 0; i < keys.length; ++i) { - var key = keys[i]; - var newObj = parseKeys(key, tempObj[key], options); - obj = utils.merge(obj, newObj, options); - } - - return utils.compact(obj); -}; - - -/***/ }), -/* 9 */ -/***/ (function(module, exports) { - -/** - * Returns a function, that, as long as it continues to be invoked, will not - * be triggered. The function will be called after it stops being called for - * N milliseconds. If `immediate` is passed, trigger the function on the - * leading edge, instead of the trailing. The function also has a property 'clear' - * that is a function which will clear the timer to prevent previously scheduled executions. - * - * @source underscore.js - * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/ - * @param {Function} function to wrap - * @param {Number} timeout in ms (`100`) - * @param {Boolean} whether to execute at the beginning (`false`) - * @api public - */ - -module.exports = function debounce(func, wait, immediate){ - var timeout, args, context, timestamp, result; - if (null == wait) wait = 100; - - function later() { - var last = Date.now() - timestamp; - - if (last < wait && last >= 0) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - context = args = null; - } - } - }; - - var debounced = function(){ - context = this; - args = arguments; - timestamp = Date.now(); - var callNow = immediate && !timeout; - if (!timeout) timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - context = args = null; - } - - return result; - }; - - debounced.clear = function() { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - }; - - debounced.flush = function() { - if (timeout) { - result = func.apply(context, args); - context = args = null; - - clearTimeout(timeout); - timeout = null; - } - }; - - return debounced; -}; - - -/***/ }), -/* 10 */ -/***/ (function(module, exports) { - -// removed by extract-text-webpack-plugin - -/***/ }) -/******/ ]); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 5057875..61e9a8f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,10 +1,12 @@ -const webpack = require('webpack'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); +/** @format */ + +const webpack = require( 'webpack' ); +const ExtractTextPlugin = require( 'extract-text-webpack-plugin' ); // CSS loader for styles specific to blocks in general. -const blocksCSSPlugin = new ExtractTextPlugin({ - filename: './blocks/build/editor.css', -}); +const blocksCSSPlugin = new ExtractTextPlugin( { + filename: './build/editor.css', +} ); // Configuration for the ExtractTextPlugin. const extractConfig = { @@ -13,16 +15,16 @@ const extractConfig = { { loader: 'postcss-loader', options: { - plugins: [require('autoprefixer')], + plugins: [ require( 'autoprefixer' ) ], }, }, ], }; module.exports = { - entry: __dirname + '/blocks/src/index.js', + entry: __dirname + '/includes/blocks/src/index.js', output: { - filename: 'blocks/build/index.js', + filename: 'build/index.js', path: __dirname, }, module: { @@ -34,17 +36,15 @@ module.exports = { }, { test: /style\.css$/, - include: [/blocks/], - use: blocksCSSPlugin.extract(extractConfig), + include: [ /blocks/ ], + use: blocksCSSPlugin.extract( extractConfig ), }, ], }, plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify( - process.env.NODE_ENV || 'development', - ), - }), + new webpack.DefinePlugin( { + 'process.env.NODE_ENV': JSON.stringify( process.env.NODE_ENV || 'development' ), + } ), blocksCSSPlugin, ], devServer: { From c6af9e4ff77fc564f09ff8632c867cfd84246790 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 3 Jan 2018 19:17:42 -0500 Subject: [PATCH 29/56] Update count param to `per_page` in endpoint --- includes/api/class-meetup-rest-events-controller.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/api/class-meetup-rest-events-controller.php b/includes/api/class-meetup-rest-events-controller.php index e6c57ce..5776267 100644 --- a/includes/api/class-meetup-rest-events-controller.php +++ b/includes/api/class-meetup-rest-events-controller.php @@ -56,19 +56,19 @@ public function register_routes() { * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { - $api = new Meetup_API_V3(); - $params = $request->get_params(); - $count = intval( $params['count'] ); + $api = new Meetup_API_V3(); + $params = $request->get_params(); + $per_page = intval( $params['per_page'] ); $args = array( 'status' => 'upcoming', - 'page' => $count, + 'page' => $per_page, ); if ( isset( $params['id'] ) ) { $id = $params['id']; - $items = $api->get_events( $id, $args, 'vsm_v3_group_' . $id . '_' . $count ); + $items = $api->get_events( $id, $args, 'vsm_v3_group_' . $id . '_' . $per_page ); } else { - $items = $api->get_self_events( $args, 'vsm_v3_self_' . $count ); + $items = $api->get_self_events( $args, 'vsm_v3_self_' . $per_page ); } if ( ! $items ) { From 7e09faaf884ec9b1fb8c903afe5f47c6a4f263ca Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 11:35:42 -0500 Subject: [PATCH 30/56] Clean up templating --- includes/blocks/src/blocks/group-list.js | 19 ++++++++++--------- includes/class-meetup-widgets-blocks.php | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index cc4f360..872dfff 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -62,15 +62,16 @@ class GroupListBlock extends Component { /> ); } - return ( -
      - { data.map( item => ( -
    • - { item.name } { item.date } -
    • - ) ) } -
    - ); + + const renderEvent = item => { + return ( +
  • + { item.name } { item.date } +
  • + ); + }; + + return
      { data.map( renderEvent ) }
    ; } render() { diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index ba653ef..e82d619 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -87,7 +87,6 @@ public function render_block_group_list( $attributes ) { $events = $response->get_data(); $block_content = [ '

    ' . $attributes['title'] . '

    ', '
      ' ]; - array_push( $block_content, '

      ' . $attributes['group'] . '

      ' ); foreach ( $events as $event ) { $list_item = '
    • ' . $event['name'] . ' ' . $event['date'] . '
    • '; array_push( $block_content, $list_item ); From 71b18af8455c4b5588571671b53a550630f477b5 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 12:38:57 -0500 Subject: [PATCH 31/56] =?UTF-8?q?Move=20placeholder=20text=20to=20a=20cont?= =?UTF-8?q?rol,=20was=20causing=20js=20errors=20on=20reload=20because=20th?= =?UTF-8?q?e=20editable=20suddenly=20didn=E2=80=99t=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/blocks/src/blocks/group-list.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index 872dfff..7cdb602 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -14,7 +14,7 @@ const { Component } = wp.element; const { Editable, InspectorControls, - InspectorControls: { RangeControl, SelectControl }, + InspectorControls: { RangeControl, SelectControl, TextControl }, } = wp.blocks; const { Placeholder, Spinner, withAPIData } = wp.components; @@ -51,16 +51,7 @@ class GroupListBlock extends Component { return

      { data.message }

      ; } if ( ! data.length ) { - return ( - - ); + return

      { attributes.placeholder }

      ; } const renderEvent = item => { @@ -102,6 +93,11 @@ class GroupListBlock extends Component { min={ 2 } max={ 15 } /> + ); From bfbe64f0450f9a0d3cd4c6c466db5a7425d1035a Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 17:02:23 -0500 Subject: [PATCH 32/56] Use pug templates --- .../class-meetup-rest-events-controller.php | 15 ++++++- includes/blocks/src/blocks/group-list.js | 41 ++++++++++--------- includes/class-meetup-widgets-blocks.php | 4 ++ includes/templates/meetup-list.pug | 15 +++++++ package.json | 2 + webpack.config.js | 7 ++++ 6 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 includes/templates/meetup-list.pug diff --git a/includes/api/class-meetup-rest-events-controller.php b/includes/api/class-meetup-rest-events-controller.php index 5776267..d3da3f9 100644 --- a/includes/api/class-meetup-rest-events-controller.php +++ b/includes/api/class-meetup-rest-events-controller.php @@ -148,13 +148,24 @@ public function prepare_item_for_response( $item, $request ) { $venue_str = ''; } + $description = ''; + $excerpt = ''; + if ( isset( $item->description ) ) { + $description = $item->description; + $excerpt = wp_trim_words( $item->description, 40, 'ā€¦' ); + } + + $time_seconds = intval( $item->time / 1000 + $item->utc_offset / 1000 ); + return array( 'id' => $item->id, 'name' => $item->name, - 'description' => isset( $item->description ) ? $item->description : '', + 'description' => $description, + 'excerpt' => $excerpt, 'url' => $item->link, 'google_maps' => "http://maps.google.com/maps?q={$venue_str}&z=17", - 'date' => date( 'M d, g:ia', intval( $item->time / 1000 + $item->utc_offset / 1000 ) ), + 'date' => date( 'M d, g:ia', $time_seconds ), + 'long_date' => date( 'l, F d, Y g:ia', $time_seconds ), // Thursday, December 14, 2017 6:30 PM 'raw_date' => $item->time, 'status' => $item->status, 'venue' => $venue, diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index 7cdb602..1e81a5b 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -5,6 +5,11 @@ */ import { stringify } from 'qs'; +/** + * Internal Dependencies + */ +const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.pug' ); + /** * Core WP Dependencies */ @@ -14,7 +19,7 @@ const { Component } = wp.element; const { Editable, InspectorControls, - InspectorControls: { RangeControl, SelectControl, TextControl }, + InspectorControls: { RangeControl, SelectControl, TextControl, ToggleControl }, } = wp.blocks; const { Placeholder, Spinner, withAPIData } = wp.components; @@ -22,6 +27,7 @@ class GroupListBlock extends Component { constructor() { super( ...arguments ); this.onChangeEditable = this.onChangeEditable.bind( this ); + this.onChangeToggle = this.onChangeToggle.bind( this ); this.onFocus = this.onFocus.bind( this ); this.renderEventsList = this.renderEventsList.bind( this ); } @@ -30,16 +36,18 @@ class GroupListBlock extends Component { return value => this.props.setAttributes( { [ field ]: value } ); } + onChangeToggle( field ) { + return () => this.props.setAttributes( { [ field ]: ! this.props.attributes[ field ] } ); + } + onFocus( field ) { return focus => this.props.setFocus( { ...focus, editable: field } ); } renderEventsList() { - const { attributes, focus, events = {} } = this.props; + const { attributes, events = {} } = this.props; const { isLoading, data = [] } = events; - const focusedEditable = focus ? focus.editable || 'title' : null; - console.log( 'events', isLoading, data ); if ( isLoading ) { return ( @@ -47,22 +55,8 @@ class GroupListBlock extends Component { ); } - if ( data.code ) { - return

      { data.message }

      ; - } - if ( ! data.length ) { - return

      { attributes.placeholder }

      ; - } - - const renderEvent = item => { - return ( -
    • - { item.name } { item.date } -
    • - ); - }; - return
        { data.map( renderEvent ) }
      ; + return { __html: runTemplate( { events: data, attributes } ) }; } render() { @@ -86,6 +80,11 @@ class GroupListBlock extends Component { options={ groupOptions } onChange={ this.onChangeEditable( 'group' ) } /> + ); + const list = this.renderEventsList(); + return [ controls,
      @@ -113,7 +114,7 @@ class GroupListBlock extends Component { className="meetup-widgets-title" value={ attributes.title } /> - { this.renderEventsList() } + { list.__html ?
      : list }
      , ]; } diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index e82d619..b91953e 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -43,6 +43,10 @@ public function __construct() { 'type' => 'number', 'default' => 5, ), + 'show_description' => array( + 'type' => 'boolean', + 'default' => false, + ), ), 'render_callback' => array( $this, 'render_block_group_list' ), ) ); diff --git a/includes/templates/meetup-list.pug b/includes/templates/meetup-list.pug new file mode 100644 index 0000000..e20ad6d --- /dev/null +++ b/includes/templates/meetup-list.pug @@ -0,0 +1,15 @@ +if events.code + p= events.message +else if ! events.length + p= attributes.placeholder +else + ul.meetup-list.meetup-group-list + each event in events + li + if attributes.show_description + h4 + a(href=event.url)= event.name + div.event-meta= event.long_date + p= event.excerpt + else + a(href=event.url) !{event.name}, !{event.date} diff --git a/package.json b/package.json index 34ce2f0..e921df2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "extract-text-webpack-plugin": "^3.0.2", "postcss-loader": "^2.0.9", "prettier": "git+https://github.com/Automattic/calypso-prettier.git", + "pug": "^2.0.0-rc.4", + "pug-loader": "^2.3.0", "raw-loader": "^0.5.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.9.7" diff --git a/webpack.config.js b/webpack.config.js index 61e9a8f..371c57b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,6 +2,7 @@ const webpack = require( 'webpack' ); const ExtractTextPlugin = require( 'extract-text-webpack-plugin' ); +const path = require( 'path' ); // CSS loader for styles specific to blocks in general. const blocksCSSPlugin = new ExtractTextPlugin( { @@ -34,6 +35,11 @@ module.exports = { exclude: /node_modules/, use: 'babel-loader', }, + { + test: /\.pug$/, + exclude: /node_modules/, + use: 'pug-loader', + }, { test: /style\.css$/, include: [ /blocks/ ], @@ -44,6 +50,7 @@ module.exports = { plugins: [ new webpack.DefinePlugin( { 'process.env.NODE_ENV': JSON.stringify( process.env.NODE_ENV || 'development' ), + 'TEMPLATE_DIRECTORY': JSON.stringify( __dirname + '/includes/templates' ), } ), blocksCSSPlugin, ], From 7093b00bf7e1fd931575886cfa8b9506af239c6c Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 19:21:26 -0500 Subject: [PATCH 33/56] Add PHP pug support via composer --- .gitignore | 3 +++ composer.json | 14 ++++++++++++++ includes/blocks/src/blocks/group-list.js | 2 +- includes/class-meetup-widgets-blocks.php | 19 ++++++++++--------- includes/templates/meetup-list.pug | 2 +- package.json | 5 +++-- vs_meetup.php | 2 ++ 7 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 composer.json diff --git a/.gitignore b/.gitignore index ff34853..0ecf306 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ yarn.lock package-lock.json mock-data build + +/vendor/ +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..53ad067 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "ryelle/meetup-widgets", + "type": "project", + "require": { + "pug-php/pug": "^3.1" + }, + "license": "GPL-2.0+", + "authors": [ + { + "name": "Kelly Dwan" + } + ], + "minimum-stability": "stable" +} diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index 1e81a5b..8f95405 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -56,7 +56,7 @@ class GroupListBlock extends Component { ); } - return { __html: runTemplate( { events: data, attributes } ) }; + return { __html: runTemplate( { events: data, has_events: !! data.length, attributes } ) }; } render() { diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index b91953e..f10daa9 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -5,7 +5,7 @@ * @package Meetup_Widgets * @since 3.0.0 */ - +use Pug\Pug; /** * Container for block functionality @@ -88,15 +88,16 @@ public function render_block_group_list( $attributes ) { if ( 200 !== $response->get_status() ) { return '

      ' . $attributes['title'] . '

      '; } - $events = $response->get_data(); - $block_content = [ '

      ' . $attributes['title'] . '

      ', '
        ' ]; - foreach ( $events as $event ) { - $list_item = '
      • ' . $event['name'] . ' ' . $event['date'] . '
      • '; - array_push( $block_content, $list_item ); - } - array_push( $block_content, '
      ' ); - return implode( "\n", $block_content ); + $pug = new Pug(); + $vars = [ + 'events' => $events, + 'has_events' => count( $events ) > 0, + 'attributes' => $attributes, + ]; + $output = $pug->render( VSMEET_TEMPLATE_DIR . '/meetup-list.pug', $vars ); + + return $output; } } diff --git a/includes/templates/meetup-list.pug b/includes/templates/meetup-list.pug index e20ad6d..9f2e4eb 100644 --- a/includes/templates/meetup-list.pug +++ b/includes/templates/meetup-list.pug @@ -1,6 +1,6 @@ if events.code p= events.message -else if ! events.length +else if ! has_events p= attributes.placeholder else ul.meetup-list.meetup-group-list diff --git a/package.json b/package.json index e921df2..60cb706 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "type": "git", "url": "git+https://github.com/ryelle/Meetup-Widgets.git" }, - "author": "", - "license": "ISC", + "author": "Kelly Dwan", + "license": "GPL-2.0-or-later", "bugs": { "url": "https://github.com/ryelle/Meetup-Widgets/issues" }, @@ -35,6 +35,7 @@ "postcss-loader": "^2.0.9", "prettier": "git+https://github.com/Automattic/calypso-prettier.git", "pug": "^2.0.0-rc.4", + "pug-cli": "^1.0.0-alpha6", "pug-loader": "^2.3.0", "raw-loader": "^0.5.1", "webpack": "^3.10.0", diff --git a/vs_meetup.php b/vs_meetup.php index c4fd0fb..31afc0e 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -20,6 +20,8 @@ define( 'VSMEET_TEMPLATE_DIR', dirname( __FILE__ ) . '/includes/templates/' ); } +require_once( 'vendor/autoload.php' ); + require_once( 'includes/class-meetup-widget.php' ); require_once( 'includes/class-meetup-widgets-admin.php' ); require_once( 'includes/class-meetup-widgets-blocks.php' ); From b121e5047915d717744384df24dfa2f268a6145f Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 19:22:09 -0500 Subject: [PATCH 34/56] Set block scripts in `register_block_type`, rather than enqueuing directly --- includes/class-meetup-widgets-blocks.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index f10daa9..878af98 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -24,7 +24,7 @@ class Meetup_Widgets_Blocks { public function __construct() { $this->dir = dirname( dirname( __FILE__ ) ); - add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_assets' ) ); + $this->register_block_assets(); register_block_type( 'meetup-widgets/group-list', array( 'attributes' => array( @@ -49,13 +49,15 @@ public function __construct() { ), ), 'render_callback' => array( $this, 'render_block_group_list' ), + 'editor_script' => 'meetup-blocks', + 'editor_style' => 'meetup-blocks', ) ); } /** * Add Gutenberg block JS & CSS to the editor */ - public function enqueue_block_assets() { + public function register_block_assets() { $js_file = 'http://localhost:8081/build/index.js'; $css_file = 'http://localhost:8081/build/editor.css'; $js_version = false; @@ -68,8 +70,8 @@ public function enqueue_block_assets() { $css_version = filemtime( "{$this->dir}/build/editor.css" ); } - wp_enqueue_script( 'meetup-blocks', $js_file, [ 'wp-blocks', 'wp-i18n', 'wp-element' ], $js_version ); - wp_enqueue_style( 'meetup-blocks', $css_file, [ 'wp-blocks' ], $css_version ); + wp_register_script( 'meetup-blocks', $js_file, [ 'wp-blocks', 'wp-i18n', 'wp-element' ], $js_version ); + wp_register_style( 'meetup-blocks', $css_file, [ 'wp-blocks' ], $css_version ); } /** From 4bb0ad424cc45a3f56ec6291e2e95ee67b527098 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 19:22:28 -0500 Subject: [PATCH 35/56] Set minimum php version in composer --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 53ad067..c99ee52 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "name": "ryelle/meetup-widgets", "type": "project", "require": { + "php": ">=5.6", "pug-php/pug": "^3.1" }, "license": "GPL-2.0+", From cdd8c0d6ed31cc7b5544eb14c5125111e9ea55da Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 6 Jan 2018 20:57:37 -0500 Subject: [PATCH 36/56] Convert Pug templates to PHP expressions --- includes/class-meetup-widgets-blocks.php | 8 ++++---- includes/templates/meetup-list.pug | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 878af98..309444d 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -82,17 +82,17 @@ public function register_block_assets() { * @return string Returns the post content with latest posts added. */ public function render_block_group_list( $attributes ) { + $pug = new Pug( array( + 'expressionLanguage' => 'php', + ) ); + $request = new WP_REST_Request( 'GET', '/meetup/v1/events/' . $attributes['group'] ); $request->set_query_params( array( 'per_page' => $attributes['per_page'], ) ); $response = rest_do_request( $request ); - if ( 200 !== $response->get_status() ) { - return '

      ' . $attributes['title'] . '

      '; - } $events = $response->get_data(); - $pug = new Pug(); $vars = [ 'events' => $events, 'has_events' => count( $events ) > 0, diff --git a/includes/templates/meetup-list.pug b/includes/templates/meetup-list.pug index 9f2e4eb..62c46c6 100644 --- a/includes/templates/meetup-list.pug +++ b/includes/templates/meetup-list.pug @@ -1,15 +1,15 @@ -if events.code - p= events.message -else if ! has_events - p= attributes.placeholder +if isset( $events['code'] ) + p= $events['message'] +else if ! $has_events + p= $attributes['placeholder'] else ul.meetup-list.meetup-group-list - each event in events + each $event in $events li - if attributes.show_description + if $attributes['show_description'] h4 - a(href=event.url)= event.name - div.event-meta= event.long_date - p= event.excerpt + a(href=event.url)= $event['name'] + div.event-meta= $event['long_date'] + p= $event['excerpt'] else - a(href=event.url) !{event.name}, !{event.date} + a(href=$event['url']) !{$event['name']}, !{$event['date']} From cb385f6e67f86de2de6cfa9d8ef076145c2c90dc Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sun, 7 Jan 2018 12:18:23 -0500 Subject: [PATCH 37/56] Switch from Pug to Handlebars, cuts out the 2s page render time --- composer.json | 2 +- includes/blocks/src/blocks/group-list.js | 11 +++++++++-- includes/class-meetup-widgets-blocks.php | 24 +++++++++++++++++------- includes/templates/meetup-list.hbs | 23 +++++++++++++++++++++++ includes/templates/meetup-list.pug | 15 --------------- package.json | 5 ++--- webpack.config.js | 7 +++---- 7 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 includes/templates/meetup-list.hbs delete mode 100644 includes/templates/meetup-list.pug diff --git a/composer.json b/composer.json index c99ee52..854b97b 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "project", "require": { "php": ">=5.6", - "pug-php/pug": "^3.1" + "xamin/handlebars.php": "dev-master" }, "license": "GPL-2.0+", "authors": [ diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index 8f95405..8d976e8 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -8,7 +8,7 @@ import { stringify } from 'qs'; /** * Internal Dependencies */ -const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.pug' ); +const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); /** * Core WP Dependencies @@ -56,7 +56,14 @@ class GroupListBlock extends Component { ); } - return { __html: runTemplate( { events: data, has_events: !! data.length, attributes } ) }; + const vars = { + attributes, + events: data, + show_events: !! data.length, + show_events_description: !! data.length && attributes.show_description, + }; + + return { __html: runTemplate( vars ) }; } render() { diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 309444d..5c09d56 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -5,7 +5,7 @@ * @package Meetup_Widgets * @since 3.0.0 */ -use Pug\Pug; +use Handlebars\Handlebars; /** * Container for block functionality @@ -45,7 +45,7 @@ public function __construct() { ), 'show_description' => array( 'type' => 'boolean', - 'default' => false, + 'default' => true, ), ), 'render_callback' => array( $this, 'render_block_group_list' ), @@ -82,8 +82,15 @@ public function register_block_assets() { * @return string Returns the post content with latest posts added. */ public function render_block_group_list( $attributes ) { - $pug = new Pug( array( - 'expressionLanguage' => 'php', + // Initialize handlebars + $loader = new \Handlebars\Loader\FilesystemLoader( + VSMEET_TEMPLATE_DIR, + array( + 'extension' => 'hbs', + ) + ); + $engine = new Handlebars( array( + 'loader' => $loader, ) ); $request = new WP_REST_Request( 'GET', '/meetup/v1/events/' . $attributes['group'] ); @@ -93,12 +100,15 @@ public function render_block_group_list( $attributes ) { $response = rest_do_request( $request ); $events = $response->get_data(); + $has_events = count( $events ) > 0; + $vars = [ - 'events' => $events, - 'has_events' => count( $events ) > 0, 'attributes' => $attributes, + 'events' => $events, + 'show_events' => $has_events, + 'show_events_description' => $has_events && $attributes['show_description'], ]; - $output = $pug->render( VSMEET_TEMPLATE_DIR . '/meetup-list.pug', $vars ); + $output = $engine->render( 'meetup-list', $vars ); return $output; } diff --git a/includes/templates/meetup-list.hbs b/includes/templates/meetup-list.hbs new file mode 100644 index 0000000..a6165fa --- /dev/null +++ b/includes/templates/meetup-list.hbs @@ -0,0 +1,23 @@ +{{#if show_events_description}} +
        + {{#each events}} +
      • +

        {{this.name}}

        +
        {{this.long_date}}
        +

        {{this.excerpt}}

        +
      • + {{/each}} +
      +{{else}} + {{#if show_events}} + + {{else}} +

      {{attributes.placeholder}} + {{/if}} +{{/if}} diff --git a/includes/templates/meetup-list.pug b/includes/templates/meetup-list.pug deleted file mode 100644 index 62c46c6..0000000 --- a/includes/templates/meetup-list.pug +++ /dev/null @@ -1,15 +0,0 @@ -if isset( $events['code'] ) - p= $events['message'] -else if ! $has_events - p= $attributes['placeholder'] -else - ul.meetup-list.meetup-group-list - each $event in $events - li - if $attributes['show_description'] - h4 - a(href=event.url)= $event['name'] - div.event-meta= $event['long_date'] - p= $event['excerpt'] - else - a(href=$event['url']) !{$event['name']}, !{$event['date']} diff --git a/package.json b/package.json index 60cb706..aabff7a 100644 --- a/package.json +++ b/package.json @@ -32,17 +32,16 @@ "eslint-config-wordpress": "^2.0.0", "eslint-plugin-react": "^7.5.1", "extract-text-webpack-plugin": "^3.0.2", + "handlebars-loader": "^1.6.0", "postcss-loader": "^2.0.9", "prettier": "git+https://github.com/Automattic/calypso-prettier.git", - "pug": "^2.0.0-rc.4", - "pug-cli": "^1.0.0-alpha6", - "pug-loader": "^2.3.0", "raw-loader": "^0.5.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.9.7" }, "dependencies": { "debounce": "^1.1.0", + "handlebars": "^4.0.11", "qs": "6.5.1", "whatwg-fetch": "^2.0.3" } diff --git a/webpack.config.js b/webpack.config.js index 371c57b..2f6a741 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,6 @@ const webpack = require( 'webpack' ); const ExtractTextPlugin = require( 'extract-text-webpack-plugin' ); -const path = require( 'path' ); // CSS loader for styles specific to blocks in general. const blocksCSSPlugin = new ExtractTextPlugin( { @@ -36,9 +35,9 @@ module.exports = { use: 'babel-loader', }, { - test: /\.pug$/, + test: /\.hbs$/, exclude: /node_modules/, - use: 'pug-loader', + use: 'handlebars-loader', }, { test: /style\.css$/, @@ -50,7 +49,7 @@ module.exports = { plugins: [ new webpack.DefinePlugin( { 'process.env.NODE_ENV': JSON.stringify( process.env.NODE_ENV || 'development' ), - 'TEMPLATE_DIRECTORY': JSON.stringify( __dirname + '/includes/templates' ), + TEMPLATE_DIRECTORY: JSON.stringify( __dirname + '/includes/templates' ), } ), blocksCSSPlugin, ], From f8f17809831d5dc02d5d1bc952e4b8277e2490bd Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 8 Jan 2018 16:57:01 -0500 Subject: [PATCH 38/56] Move title into template for front-end display --- includes/blocks/src/blocks/group-list.js | 1 + includes/class-meetup-widgets-blocks.php | 1 + includes/templates/meetup-list.hbs | 36 +++++++++++++----------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/blocks/group-list.js index 8d976e8..eb50b5b 100644 --- a/includes/blocks/src/blocks/group-list.js +++ b/includes/blocks/src/blocks/group-list.js @@ -59,6 +59,7 @@ class GroupListBlock extends Component { const vars = { attributes, events: data, + hide_title: true, // title is here, so we hide it in the final template. show_events: !! data.length, show_events_description: !! data.length && attributes.show_description, }; diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 5c09d56..3971574 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -105,6 +105,7 @@ public function render_block_group_list( $attributes ) { $vars = [ 'attributes' => $attributes, 'events' => $events, + 'hide_title' => false, 'show_events' => $has_events, 'show_events_description' => $has_events && $attributes['show_description'], ]; diff --git a/includes/templates/meetup-list.hbs b/includes/templates/meetup-list.hbs index a6165fa..825498f 100644 --- a/includes/templates/meetup-list.hbs +++ b/includes/templates/meetup-list.hbs @@ -1,23 +1,27 @@ -{{#if show_events_description}} -

        +
        + {{#unless hide_title}} +

        {{attributes.title}}

        + {{/unless}} + {{#if show_events_description}} {{#each events}} -
      • +

        {{this.name}}

        + -->
        {{this.long_date}}

        {{this.excerpt}}

        -
      • +
        {{/each}} -
      -{{else}} - {{#if show_events}} - {{else}} -

      {{attributes.placeholder}} + {{#if show_events}} +

      + {{else}} +

      {{attributes.placeholder}} + {{/if}} {{/if}} -{{/if}} +

      From 9b9ad3b1e1bd1cd0095d9b4b8ac15897a1e086f8 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 8 Jan 2018 17:00:26 -0500 Subject: [PATCH 39/56] Rename second blocks folder --- includes/blocks/src/{blocks => components}/group-list.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename includes/blocks/src/{blocks => components}/group-list.js (100%) diff --git a/includes/blocks/src/blocks/group-list.js b/includes/blocks/src/components/group-list.js similarity index 100% rename from includes/blocks/src/blocks/group-list.js rename to includes/blocks/src/components/group-list.js From fe4f36bdbb0ea9050e8298249d79d3d07ec08698 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 8 Jan 2018 17:19:15 -0500 Subject: [PATCH 40/56] Add a user-list block to get events for the current API user --- includes/blocks/src/components/user-list.js | 122 ++++++++++++++++++++ includes/blocks/src/group-list.js | 15 +-- includes/blocks/src/index.js | 8 +- includes/blocks/src/single.js | 23 ---- includes/blocks/src/user-list.js | 28 +++++ includes/class-meetup-widgets-blocks.php | 67 ++++++++++- 6 files changed, 225 insertions(+), 38 deletions(-) create mode 100644 includes/blocks/src/components/user-list.js delete mode 100644 includes/blocks/src/single.js create mode 100644 includes/blocks/src/user-list.js diff --git a/includes/blocks/src/components/user-list.js b/includes/blocks/src/components/user-list.js new file mode 100644 index 0000000..60b64b8 --- /dev/null +++ b/includes/blocks/src/components/user-list.js @@ -0,0 +1,122 @@ +/** + * External Dependencies + * + * @format + */ +import { stringify } from 'qs'; + +/** + * Internal Dependencies + */ +const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); + +/** + * Core WP Dependencies + */ +const { __ } = wp.i18n; +const translate = str => __( str, 'meetup-widgets' ); +const { Component } = wp.element; +const { + Editable, + InspectorControls, + InspectorControls: { RangeControl, TextControl, ToggleControl }, +} = wp.blocks; +const { Placeholder, Spinner, withAPIData } = wp.components; + +class UserListBlock extends Component { + constructor() { + super( ...arguments ); + this.onChangeEditable = this.onChangeEditable.bind( this ); + this.onChangeToggle = this.onChangeToggle.bind( this ); + this.onFocus = this.onFocus.bind( this ); + this.renderEventsList = this.renderEventsList.bind( this ); + } + + onChangeEditable( field ) { + return value => this.props.setAttributes( { [ field ]: value } ); + } + + onChangeToggle( field ) { + return () => this.props.setAttributes( { [ field ]: ! this.props.attributes[ field ] } ); + } + + onFocus( field ) { + return focus => this.props.setFocus( { ...focus, editable: field } ); + } + + renderEventsList() { + const { attributes, events = {} } = this.props; + const { isLoading, data = [] } = events; + + if ( isLoading ) { + return ( + + + + ); + } + + const vars = { + attributes, + events: data, + hide_title: true, // title is here, so we hide it in the final template. + show_events: !! data.length, + show_events_description: !! data.length && attributes.show_description, + }; + + return { __html: runTemplate( vars ) }; + } + + render() { + const { attributes, focus } = this.props; + const focusedEditable = focus ? focus.editable || 'title' : null; + + const controls = focus && ( + + + + + + ); + + const list = this.renderEventsList(); + + return [ + controls, +
      + + { list.__html ?
      : list } +
      , + ]; + } +} + +export default withAPIData( props => { + const { per_page = 3 } = props.attributes; + const queryString = stringify( { per_page } ); + return { + events: `/meetup/v1/events/self?${ queryString }`, + }; +} )( UserListBlock ); diff --git a/includes/blocks/src/group-list.js b/includes/blocks/src/group-list.js index 97bd67f..b037c62 100755 --- a/includes/blocks/src/group-list.js +++ b/includes/blocks/src/group-list.js @@ -9,21 +9,18 @@ const translate = str => __( str, 'meetup-widgets' ); /** * Internal Dependencies */ -import GroupListBlock from './blocks/group-list'; +import GroupListBlock from './components/group-list'; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { title: translate( 'Meetup.com List' ), description: translate( 'This is a list of events for a given group on Meetup.com' ), - - icon: 'editor-ul', - + icon: 'groups', category: 'embed', - - // Remove to make block editable in HTML mode. - supportHTML: false, - + supports: { + anchor: true, + html: false, + }, edit: GroupListBlock, - save: () => null, }; diff --git a/includes/blocks/src/index.js b/includes/blocks/src/index.js index 1ecc07a..3943093 100644 --- a/includes/blocks/src/index.js +++ b/includes/blocks/src/index.js @@ -1,10 +1,10 @@ /** @format */ const { registerBlockType } = wp.blocks; -import singleWidgetOptions from './single'; -import groupWidgetOptions from './group-list'; +import userListOptions from './user-list'; +import groupListOptions from './group-list'; import './style.css'; -registerBlockType( 'meetup-widgets/single', singleWidgetOptions ); -registerBlockType( 'meetup-widgets/group-list', groupWidgetOptions ); +registerBlockType( 'meetup-widgets/user-list', userListOptions ); +registerBlockType( 'meetup-widgets/group-list', groupListOptions ); diff --git a/includes/blocks/src/single.js b/includes/blocks/src/single.js deleted file mode 100644 index a728866..0000000 --- a/includes/blocks/src/single.js +++ /dev/null @@ -1,23 +0,0 @@ -/** @format */ - -const { __ } = wp.i18n; - -// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API -export default { - title: __( 'Meetup.com Event', 'meetup-widgets' ), - - icon: 'groups', - - category: 'embed', - - // Remove to make block editable in HTML mode. - supportHTML: false, - - edit: props => { - return __( 'Single event here! šŸŽŠ', 'meetup-widgets' ); - }, - - save: () => { - return null; - }, -}; diff --git a/includes/blocks/src/user-list.js b/includes/blocks/src/user-list.js new file mode 100644 index 0000000..2674366 --- /dev/null +++ b/includes/blocks/src/user-list.js @@ -0,0 +1,28 @@ +/** + * Core WP Dependencies + * + * @format + */ +const { __ } = wp.i18n; +const translate = str => __( str, 'meetup-widgets' ); + +/** + * Internal Dependencies + */ +import UserListBlock from './components/user-list'; + +// Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API +export default { + title: translate( 'Meetup.com User List' ), + description: translate( + 'This is a list of the upcoming events on Meetup.com for the user that created the API key' + ), + icon: 'groups', + category: 'embed', + supports: { + anchor: true, + html: false, + }, + edit: UserListBlock, + save: () => null, +}; diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 3971574..9748d0d 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -52,6 +52,29 @@ public function __construct() { 'editor_script' => 'meetup-blocks', 'editor_style' => 'meetup-blocks', ) ); + register_block_type( 'meetup-widgets/user-list', array( + 'attributes' => array( + 'title' => array( + 'type' => 'string', + 'default' => __( 'My Events', 'meetup-widgets' ), + ), + 'placeholder' => array( + 'type' => 'string', + 'default' => __( 'No upcoming events.', 'meetup-widgets' ), + ), + 'per_page' => array( + 'type' => 'number', + 'default' => 5, + ), + 'show_description' => array( + 'type' => 'boolean', + 'default' => true, + ), + ), + 'render_callback' => array( $this, 'render_block_user_list' ), + 'editor_script' => 'meetup-blocks', + 'editor_style' => 'meetup-blocks', + ) ); } /** @@ -75,11 +98,11 @@ public function register_block_assets() { } /** - * Renders the `core/latest-posts` block on server. + * Renders the `meetup-widgets/group-list` block on server. * * @param array $attributes The block attributes. * - * @return string Returns the post content with latest posts added. + * @return string Returns the upcoming events for a given group, to be displayed in a post */ public function render_block_group_list( $attributes ) { // Initialize handlebars @@ -113,4 +136,44 @@ public function render_block_group_list( $attributes ) { return $output; } + + /** + * Renders the `meetup-widgets/user-list` block on server. + * + * @param array $attributes The block attributes. + * + * @return string Returns the upcoming events for the current API user, to be displayed in a post + */ + public function render_block_user_list( $attributes ) { + // Initialize handlebars + $loader = new \Handlebars\Loader\FilesystemLoader( + VSMEET_TEMPLATE_DIR, + array( + 'extension' => 'hbs', + ) + ); + $engine = new Handlebars( array( + 'loader' => $loader, + ) ); + + $request = new WP_REST_Request( 'GET', '/meetup/v1/events/self' ); + $request->set_query_params( array( + 'per_page' => $attributes['per_page'], + ) ); + $response = rest_do_request( $request ); + $events = $response->get_data(); + + $has_events = count( $events ) > 0; + + $vars = [ + 'attributes' => $attributes, + 'events' => $events, + 'hide_title' => false, + 'show_events' => $has_events, + 'show_events_description' => $has_events && $attributes['show_description'], + ]; + $output = $engine->render( 'meetup-list', $vars ); + + return $output; + } } From f5103fec986eff15cfefdd0a7b8c8d495719529b Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 8 Jan 2018 17:39:06 -0500 Subject: [PATCH 41/56] Add classes & styling for events list --- .../class-meetup-rest-events-controller.php | 1 + includes/blocks/src/style.css | 7 ++++++ includes/class-meetup-widgets-blocks.php | 5 +++++ includes/templates/meetup-list.hbs | 22 ++++++++++--------- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/includes/api/class-meetup-rest-events-controller.php b/includes/api/class-meetup-rest-events-controller.php index d3da3f9..0b11236 100644 --- a/includes/api/class-meetup-rest-events-controller.php +++ b/includes/api/class-meetup-rest-events-controller.php @@ -166,6 +166,7 @@ public function prepare_item_for_response( $item, $request ) { 'google_maps' => "http://maps.google.com/maps?q={$venue_str}&z=17", 'date' => date( 'M d, g:ia', $time_seconds ), 'long_date' => date( 'l, F d, Y g:ia', $time_seconds ), // Thursday, December 14, 2017 6:30 PM + 'datetime' => date( 'c', $time_seconds ), // 2017-03-19T04:39:44+00:00 'raw_date' => $item->time, 'status' => $item->status, 'venue' => $venue, diff --git a/includes/blocks/src/style.css b/includes/blocks/src/style.css index e69de29..065ede3 100644 --- a/includes/blocks/src/style.css +++ b/includes/blocks/src/style.css @@ -0,0 +1,7 @@ +/** @format */ + +.meetup-events__event-date { + display: block; + color: #6c7781; + font-size: 13px; +} diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 9748d0d..6909e16 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -52,6 +52,7 @@ public function __construct() { 'editor_script' => 'meetup-blocks', 'editor_style' => 'meetup-blocks', ) ); + register_block_type( 'meetup-widgets/user-list', array( 'attributes' => array( 'title' => array( @@ -75,6 +76,10 @@ public function __construct() { 'editor_script' => 'meetup-blocks', 'editor_style' => 'meetup-blocks', ) ); + + add_action( 'wp_enqueue_scripts', function() { + wp_enqueue_style( 'meetup-blocks' ); + } ); } /** diff --git a/includes/templates/meetup-list.hbs b/includes/templates/meetup-list.hbs index 825498f..c0f7549 100644 --- a/includes/templates/meetup-list.hbs +++ b/includes/templates/meetup-list.hbs @@ -1,27 +1,29 @@
      {{#unless hide_title}} -

      {{attributes.title}}

      +

      {{attributes.title}}

      {{/unless}} {{#if show_events_description}} {{#each events}} -
      -

      {{this.name}}

      - --> -
      {{this.long_date}}
      -

      {{this.excerpt}}

      +
      +

      + {{this.name}} +

      + +

      {{this.excerpt}}

      {{/each}} {{else}} {{#if show_events}} -
      From c88707ccc0e502fa482f1d73d705e14aed5c086b Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Tue, 9 Jan 2018 13:49:34 -0500 Subject: [PATCH 42/56] =?UTF-8?q?Add=20an=20exception=20to=20permissions?= =?UTF-8?q?=20check=20for=20requests=20from=20this=20same=20server,=20ID?= =?UTF-8?q?=E2=80=99d=20by=20a=20nonce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/api/class-meetup-rest-events-controller.php | 8 +++++++- includes/api/class-meetup-rest-groups-controller.php | 8 +++++++- includes/class-meetup-widgets-blocks.php | 4 +++- includes/templates/meetup-list.hbs | 6 +++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/includes/api/class-meetup-rest-events-controller.php b/includes/api/class-meetup-rest-events-controller.php index 0b11236..d5ac2a7 100644 --- a/includes/api/class-meetup-rest-events-controller.php +++ b/includes/api/class-meetup-rest-events-controller.php @@ -179,11 +179,17 @@ public function prepare_item_for_response( $item, $request ) { * Check permissions for this endpoint. * * Only logged-in users can use this proxy, to prevent anonymous users from - * spamming the meetup.com API with the site-owner's API key. + * spamming the meetup.com API with the site-owner's API key. The exception + * to this is requests from the server itself, which are ID'd by having the + * nonce `X-MW-Nonce`. This is not revealed client-side, so is safe to use + * as a "security" measure. * * @param WP_REST_Request $request Current request. */ public function get_items_permissions_check( $request ) { + if ( wp_verify_nonce( $request->get_header( 'x-mw-nonce' ), 'meetup-widgets' ) ) { + return true; + } if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_forbidden', diff --git a/includes/api/class-meetup-rest-groups-controller.php b/includes/api/class-meetup-rest-groups-controller.php index 1900b82..99accef 100644 --- a/includes/api/class-meetup-rest-groups-controller.php +++ b/includes/api/class-meetup-rest-groups-controller.php @@ -76,11 +76,17 @@ public function prepare_item_for_response( $item, $request ) { * Check permissions for this endpoint. * * Only logged-in users can use this proxy, to prevent anonymous users from - * spamming the meetup.com API with the site-owner's API key. + * spamming the meetup.com API with the site-owner's API key. The exception + * to this is requests from the server itself, which are ID'd by having the + * nonce `X-MW-Nonce`. This is not revealed client-side, so is safe to use + * as a "security" measure. * * @param WP_REST_Request $request Current request. */ public function get_items_permissions_check( $request ) { + if ( wp_verify_nonce( $request->get_header( 'x-mw-nonce' ), 'meetup-widgets' ) ) { + return true; + } if ( ! current_user_can( 'edit_posts' ) ) { return new WP_Error( 'rest_forbidden', diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 6909e16..c79eac3 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -122,13 +122,14 @@ public function render_block_group_list( $attributes ) { ) ); $request = new WP_REST_Request( 'GET', '/meetup/v1/events/' . $attributes['group'] ); + $request->set_header( 'x-mw-nonce', wp_create_nonce( 'meetup-widgets' ) ); $request->set_query_params( array( 'per_page' => $attributes['per_page'], ) ); $response = rest_do_request( $request ); $events = $response->get_data(); - $has_events = count( $events ) > 0; + $has_events = ! isset( $events['code'] ) && count( $events ) > 0; $vars = [ 'attributes' => $attributes, @@ -162,6 +163,7 @@ public function render_block_user_list( $attributes ) { ) ); $request = new WP_REST_Request( 'GET', '/meetup/v1/events/self' ); + $request->set_header( 'x-mw-nonce', wp_create_nonce( 'meetup-widgets' ) ); $request->set_query_params( array( 'per_page' => $attributes['per_page'], ) ); diff --git a/includes/templates/meetup-list.hbs b/includes/templates/meetup-list.hbs index c0f7549..a15ba70 100644 --- a/includes/templates/meetup-list.hbs +++ b/includes/templates/meetup-list.hbs @@ -23,7 +23,11 @@ {{/each}}
    {{else}} -

    {{attributes.placeholder}} + {{#if events.code }} +

    {{events.message}}

    + {{else}} +

    {{attributes.placeholder}}

    + {{/if}} {{/if}} {{/if}}
From 23d231d9699f05bbded26e5deebaf94793249715 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Tue, 9 Jan 2018 14:20:58 -0500 Subject: [PATCH 43/56] Add READMEs --- includes/api/README.md | 26 ++++++++++++++++++++++++++ includes/blocks/README.md | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 includes/api/README.md create mode 100644 includes/blocks/README.md diff --git a/includes/api/README.md b/includes/api/README.md new file mode 100644 index 0000000..42cf4d8 --- /dev/null +++ b/includes/api/README.md @@ -0,0 +1,26 @@ +API +=== + +This folder contains controllers for Meetup.com proxy endpoints, using the API key set in wp-admin. These endpoints are each accessible by `GET` requests only, and only by logged-in users. + +### Event Lists + + meetup/v1/events/self + +This lists upcoming events for the user who created the API key. + + meetup/v1/events/(?P[^/]+) + +This lists upcoming events for the group passed through. This is the group `urlname`, for example, if the URL for your meetup is `https://www.meetup.com/boston-wordpress-meetup/`, the `urlname` is `boston-wordpress-meetup`. + +### Single Event + + meetup/v1/events/(?P[^/]+)/(?P[^/]+) + +This gets information about a single event in a group. See above for the `group_urlname` description. The event ID is the part after `/events/` in a URL for a single event. + +### Groups List + + meetup/v1/groups/self + +This lists the groups that the API key owner is a member of. diff --git a/includes/blocks/README.md b/includes/blocks/README.md new file mode 100644 index 0000000..441fc8c --- /dev/null +++ b/includes/blocks/README.md @@ -0,0 +1,35 @@ +Blocks +====== + +Meetup-Widgets currently supports two blocks: a list of upcoming events by group, and a list of upcoming events for the API key owner. + +## Project Setup + +To work on the meetup-widgets blocks, you need node, npm & composer installed. After making sure those are installed, install the project's dependencies: + + composer install + npm install + +If you have `WP_DEBUG` set to true, the plugin looks for the files on the webpack dev server, so you'll need to start it: + + npm start + +If you have `WP_DEBUG` set to false, you can just build the files directly: + + npm run build + +## Development + +The editing interface for both blocks lives in `blocks/src/components`. The content returned from the component's `render` function is presented in the editor. This content is _not_ saved to the database. Instead, WordPress calls the PHP render functions in `includes/class-meetup-widgets-blocks.php` for each block. This guarantees up-to-date event results, because the data is queried every page load (with a 2hr API cache). + +### Handlebars + +Since the data is displayed in both JS (when editing) and PHP (when viewing), I've extracted the templates out into handlebars. + +The javascript side uses [handlebars-loader](https://www.npmjs.com/package/handlebars-loader) to pre-compile the template into a function that accepts the event & attribute data & returns HTML. This needs to be "dangerously set" into the react component. + +The PHP side uses [handlebars.php](https://github.com/XaminProject/handlebars.php), which is installed via composer. There are quirks with handlebars when used in this way, some nested `if/each` sections don't behave as expected. This is why the if/else sections are structured as they are. + +### CSS + +The CSS is built from `style.css`, and is currently very minimal. This is loaded in the gutenberg editor, and on the frontend of the site. From a1769bb31b2be7a1c7f90bea5f7523c23d784414 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 10 Jan 2018 00:09:25 -0500 Subject: [PATCH 44/56] Throw error (and handle error state) if the API key is not defined --- includes/blocks/src/components/group-list.js | 19 +++++++++++++++++-- includes/blocks/src/components/user-list.js | 19 +++++++++++++++++-- includes/class-meetup-api-v3.php | 13 +++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index eb50b5b..4c58370 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -21,7 +21,7 @@ const { InspectorControls, InspectorControls: { RangeControl, SelectControl, TextControl, ToggleControl }, } = wp.blocks; -const { Placeholder, Spinner, withAPIData } = wp.components; +const { Dashicon, Placeholder, Spinner, withAPIData } = wp.components; class GroupListBlock extends Component { constructor() { @@ -46,7 +46,22 @@ class GroupListBlock extends Component { renderEventsList() { const { attributes, events = {} } = this.props; - const { isLoading, data = [] } = events; + const { isLoading, error, data = [] } = events; + + /* eslint-disable yoda */ + if ( error && error.status > 200 ) { + let message = translate( 'There was an error loading the API for this block' ); + if ( error.resposeJSON && error.resposeJSON.message ) { + message = error.resposeJSON.message; + } + + return ( + + + + ); + } + /* eslint-enable yoda */ if ( isLoading ) { return ( diff --git a/includes/blocks/src/components/user-list.js b/includes/blocks/src/components/user-list.js index 60b64b8..a0e037d 100644 --- a/includes/blocks/src/components/user-list.js +++ b/includes/blocks/src/components/user-list.js @@ -21,7 +21,7 @@ const { InspectorControls, InspectorControls: { RangeControl, TextControl, ToggleControl }, } = wp.blocks; -const { Placeholder, Spinner, withAPIData } = wp.components; +const { Dashicon, Placeholder, Spinner, withAPIData } = wp.components; class UserListBlock extends Component { constructor() { @@ -46,7 +46,22 @@ class UserListBlock extends Component { renderEventsList() { const { attributes, events = {} } = this.props; - const { isLoading, data = [] } = events; + const { isLoading, error, data = [] } = events; + + /* eslint-disable yoda */ + if ( error && error.status > 200 ) { + let message = translate( 'There was an error loading the API for this block' ); + if ( error.resposeJSON && error.resposeJSON.message ) { + message = error.resposeJSON.message; + } + + return ( + + + + ); + } + /* eslint-enable yoda */ if ( isLoading ) { return ( diff --git a/includes/class-meetup-api-v3.php b/includes/class-meetup-api-v3.php index 815a1c6..664039e 100644 --- a/includes/class-meetup-api-v3.php +++ b/includes/class-meetup-api-v3.php @@ -47,6 +47,9 @@ public function get_events( $group = false, $args = [], $transient = '' ) { if ( $transient ) { $data = get_transient( $transient ); } + if ( empty( $this->api_key ) ) { + return new WP_Error( 'undefined_key', __( 'Please enter an API key.', 'meetup-widgets' ) ); + } $defaults = array( 'key' => $this->api_key, @@ -89,6 +92,9 @@ public function get_self_events( $args = [], $transient = '' ) { if ( $transient ) { $data = get_transient( $transient ); } + if ( empty( $this->api_key ) ) { + return new WP_Error( 'undefined_key', __( 'Please enter an API key.', 'meetup-widgets' ) ); + } $defaults = array( 'key' => $this->api_key, @@ -134,6 +140,10 @@ public function get_event( $group = false, $event = false, $transient = '' ) { if ( ! $event ) { return new WP_Error( 'undefined_event', __( 'Requested event ID missing.', 'meetup-widgets' ) ); } + if ( empty( $this->api_key ) ) { + return new WP_Error( 'undefined_key', __( 'Please enter an API key.', 'meetup-widgets' ) ); + } + $data = false; if ( $transient ) { $data = get_transient( $transient ); @@ -175,6 +185,9 @@ public function get_self_groups( $transient = '' ) { if ( $transient ) { $data = get_transient( $transient ); } + if ( empty( $this->api_key ) ) { + return new WP_Error( 'undefined_key', __( 'Please enter an API key.', 'meetup-widgets' ) ); + } $args = array( 'key' => $this->api_key, From e81f2de3d4e567ac16e8972e9230cc05d9ec9db6 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Wed, 10 Jan 2018 00:09:42 -0500 Subject: [PATCH 45/56] Update changelog & version numbers --- package.json | 2 +- readme.txt | 18 ++++++++++++------ vs_meetup.php | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index aabff7a..ed58691 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meetup-widgets", - "version": "2.3.0", + "version": "3.0.0", "description": "Adds widgets displaying information from a meetup.com group.", "main": "index.js", "scripts": { diff --git a/readme.txt b/readme.txt index 990a9b4..adc83a6 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === Meetup Widgets === Contributors: ryelle -Tags: meetup, meetups, meetup.com, widget, +Tags: meetup, meetups, meetup.com, widget, gutenberg Requires at least: 4.8 Requires PHP: 5.6 Tested up to: 4.9 -Stable tag: 2.3.0 +Stable tag: 3.0.0 Adds widgets displaying information from a meetup.com group. @@ -52,10 +52,16 @@ Previous to version 3.0, this plugin had a feature where you could RSVP to an ev = 3.0.0 = -* Removal of OAuth -* Refactor widgets into new `widgets/*` files -* Move templates into `templates/` folder -* Add PHP CodeSniffer, clean up flagged issues +* NEW: Gutenberg support: 2 new blocks for listing events +* NEW: API endpoints for fetching Meetup.com data +* BREAKING: Removal of OAuth RSVP feature +* BREAKING: Set minimum PHP version to 5.6 +* UPDATE: Refactor basically the entire plugin +* UPDATE: Refactor widgets into new `includes/widgets/*` files +* UPDATE: Move templates into `includes/templates/` folder +* DEVELOPER: Add PHP CodeSniffer, clean up flagged issues +* DEVELOPER: Add webpack, babel, eslint for building gutenberg blocks +* DEVELOPER: Add Handlebars for the gutenberg block templates (widgets still use themeable php templates) = 2.2.1 = diff --git a/vs_meetup.php b/vs_meetup.php index 31afc0e..6df3675 100644 --- a/vs_meetup.php +++ b/vs_meetup.php @@ -2,7 +2,7 @@ /** * Plugin Name: Meetup Widgets * Description: Add widgets displaying information from Meetup.com - * Version: 2.2.1 + * Version: 3.0.0 * Author: Kelly Dwan * Author URI: http://redradar.net * Plugin URI: http://redradar.net/category/plugins/meetup-widgets/ From b15ac90bf06e873492ecc05cf0e84ec5b2e06848 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Fri, 19 Jan 2018 22:02:16 -0500 Subject: [PATCH 46/56] Check for Gutenberg before trying to register the blocks --- includes/class-meetup-widgets-blocks.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index c79eac3..4adb928 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -24,6 +24,12 @@ class Meetup_Widgets_Blocks { public function __construct() { $this->dir = dirname( dirname( __FILE__ ) ); + // Check for Gutenberg + if ( ! function_exists( 'register_block_type' ) ) { + // No Gutenberg šŸ˜­ + return; + } + $this->register_block_assets(); register_block_type( 'meetup-widgets/group-list', array( From f58af462b49650bd90729a45550ad564b2e2b7d7 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 22 Jan 2018 18:34:47 -0500 Subject: [PATCH 47/56] Merge block improvements back into the widgets --- includes/class-meetup-widgets-blocks.php | 27 +++-- includes/templates/meetup-list.php | 23 ----- includes/widgets/group-list.php | 122 ++++++++++++++++------- includes/widgets/user-list.php | 79 +++++++++------ 4 files changed, 157 insertions(+), 94 deletions(-) delete mode 100644 includes/templates/meetup-list.php diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 4adb928..14042a0 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -97,7 +97,7 @@ public function register_block_assets() { $js_version = false; $css_version = false; - if ( ! WP_DEBUG ) { + if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) { $js_file = plugins_url( 'build/index.js', dirname( __FILE__ ) ); $css_file = plugins_url( 'build/editor.css', dirname( __FILE__ ) ); $js_version = filemtime( "{$this->dir}/build/index.js" ); @@ -111,11 +111,18 @@ public function register_block_assets() { /** * Renders the `meetup-widgets/group-list` block on server. * - * @param array $attributes The block attributes. - * + * @param array $attributes { + * The block attributes. + * @type string $title Title to display. + * @type string $group URL name of the group. + * @type int $per_page Number of events to display. + * @type bool $show_description Show the description in the list. Also switches to a more + * verbose display of event data. + * @type string $placeholder A placeholder shown when there are no events. + * } * @return string Returns the upcoming events for a given group, to be displayed in a post */ - public function render_block_group_list( $attributes ) { + public static function render_block_group_list( $attributes ) { // Initialize handlebars $loader = new \Handlebars\Loader\FilesystemLoader( VSMEET_TEMPLATE_DIR, @@ -152,11 +159,17 @@ public function render_block_group_list( $attributes ) { /** * Renders the `meetup-widgets/user-list` block on server. * - * @param array $attributes The block attributes. - * + * @param array $attributes { + * The block attributes. + * @type string $title Title to display. + * @type int $per_page Number of events to display. + * @type bool $show_description Show the description in the list. Also switches to a more + * verbose display of event data. + * @type string $placeholder A placeholder shown when there are no events. + * } * @return string Returns the upcoming events for the current API user, to be displayed in a post */ - public function render_block_user_list( $attributes ) { + public static function render_block_user_list( $attributes ) { // Initialize handlebars $loader = new \Handlebars\Loader\FilesystemLoader( VSMEET_TEMPLATE_DIR, diff --git a/includes/templates/meetup-list.php b/includes/templates/meetup-list.php deleted file mode 100644 index 5d72680..0000000 --- a/includes/templates/meetup-list.php +++ /dev/null @@ -1,23 +0,0 @@ -'; -foreach ( $events as $event ) { - printf( - '
  • %2$s; %3$s
  • ', - esc_url( $event->event_url ), - strip_tags( $event->name ), - date( 'M d, g:ia', intval( $event->time / 1000 + $event->utc_offset / 1000 ) ) - ); -} -echo ''; diff --git a/includes/widgets/group-list.php b/includes/widgets/group-list.php index de8712c..aaeea0c 100644 --- a/includes/widgets/group-list.php +++ b/includes/widgets/group-list.php @@ -23,25 +23,38 @@ function __construct() { ); } + /** + * Normalize and sanitize the widget attribute values + */ + function get_sanitized_values( $attrs ) { + return array( + 'title' => isset( $attrs['title'] ) ? strip_tags( $attrs['title'] ) : '', + 'id' => isset( $attrs['id'] ) ? sanitize_title( $attrs['id'] ) : '', + 'limit' => isset( $attrs['limit'] ) ? filter_var( $attrs['limit'], FILTER_VALIDATE_INT ) : 3, + 'show_desc' => isset( $attrs['show_desc'] ) ? filter_var( $attrs['show_desc'], FILTER_VALIDATE_BOOLEAN ) : false, + ); + } + /** * Display the widget content * * @see WP_Widget::widget */ function widget( $args, $instance ) { - $title = apply_filters( 'widget_title', $instance['title'] ); - $id = $instance['id']; // meetup ID or URL name - $limit = intval( $instance['limit'] ); + $attrs = $this->get_sanitized_values( $instance ); + $title = apply_filters( 'widget_title', $attrs['title'] ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } - if ( $id ) { - $vsm = new Meetup_Widget(); - $html = $vsm->get_group_events( $id, $limit ); - echo $html; - } + echo Meetup_Widgets_Blocks::render_block_group_list( array( + 'title' => '', + 'group' => $attrs['id'], + 'per_page' => $attrs['limit'], + 'show_description' => $attrs['show_desc'], + 'placeholder' => __( 'No upcoming events.', 'meetup-widgets' ), + ) ); echo $args['after_widget']; } @@ -51,16 +64,8 @@ function widget( $args, $instance ) { * @see WP_Widget::update */ function update( $new_instance, $old_instance ) { - $instance = $old_instance; - $instance['title'] = strip_tags( $new_instance['title'] ); - if ( preg_match( '/[a-zA-Z]/', $new_instance['id'] ) ) { - $instance['id'] = sanitize_title( $new_instance['id'] ); - } else { - $instance['id'] = str_replace( ' ', '', $new_instance['id'] ); - } - $instance['limit'] = intval( $new_instance['limit'] ); - - return $instance; + $new_values = $this->get_sanitized_values( $new_instance ); + return array_merge( $old_instance, $new_values ); } /** @@ -69,30 +74,75 @@ function update( $new_instance, $old_instance ) { * @see WP_Widget::form */ function form( $instance ) { - if ( $instance ) { - $title = esc_attr( $instance['title'] ); - $id = esc_attr( $instance['id'] ); // -> it's a name if it contains any a-zA-z, otherwise ID - $limit = intval( $instance['limit'] ); - } else { - $title = ''; - $id = ''; - $limit = 5; - } + $attrs = $this->get_sanitized_values( $instance ); + $groups = $this->get_groups_from_api(); ?> -

    -

    +

    + + +

    +

    + + +

    - + +

    +

    +

    set_header( 'x-mw-nonce', wp_create_nonce( 'meetup-widgets' ) ); + $response = rest_do_request( $request ); + $groups = $response->get_data(); + + if ( isset( $groups['code'] ) || count( $groups ) < 1 ) { + return []; + } + + return $groups; + } } diff --git a/includes/widgets/user-list.php b/includes/widgets/user-list.php index 3b27b2f..ef25a9f 100644 --- a/includes/widgets/user-list.php +++ b/includes/widgets/user-list.php @@ -23,22 +23,36 @@ function __construct() { ); } + /** + * Normalize and sanitize the widget attribute values + */ + function get_sanitized_values( $attrs ) { + return array( + 'title' => isset( $attrs['title'] ) ? strip_tags( $attrs['title'] ) : '', + 'limit' => isset( $attrs['limit'] ) ? filter_var( $attrs['limit'], FILTER_VALIDATE_INT ) : 3, + 'show_desc' => isset( $attrs['show_desc'] ) ? filter_var( $attrs['show_desc'], FILTER_VALIDATE_BOOLEAN ) : false, + ); + } + /** * Display the widget content * * @see WP_Widget::widget */ function widget( $args, $instance ) { - $title = apply_filters( 'widget_title', $instance['title'] ); - $limit = absint( $instance['limit'] ); + $attrs = $this->get_sanitized_values( $instance ); + $title = apply_filters( 'widget_title', $attrs['title'] ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } - $vsm = new Meetup_Widget(); - $html = $vsm->get_user_events( $limit ); - echo $html; + echo Meetup_Widgets_Blocks::render_block_user_list( array( + 'title' => '', + 'per_page' => $attrs['limit'], + 'show_description' => $attrs['show_desc'], + 'placeholder' => __( 'No upcoming events.', 'meetup-widgets' ), + ) ); echo $args['after_widget']; } @@ -48,16 +62,8 @@ function widget( $args, $instance ) { * @see WP_Widget::update */ function update( $new_instance, $old_instance ) { - $instance = $old_instance; - $instance['title'] = strip_tags( $new_instance['title'] ); - $instance['limit'] = absint( $new_instance['limit'] ); - - // remove caching of old event - if ( ! empty( $old_instance['id'] ) ) { - delete_transient( 'vsmeet_user_events_' . $old_instance['id'] ); - } - - return $instance; + $new_values = $this->get_sanitized_values( $new_instance ); + return array_merge( $old_instance, $new_values ); } /** @@ -66,25 +72,42 @@ function update( $new_instance, $old_instance ) { * @see WP_Widget::form */ function form( $instance ) { - if ( $instance ) { - $title = esc_attr( $instance['title'] ); - $limit = absint( $instance['limit'] ); - } else { - $title = ''; - $limit = 5; - } + $attrs = $this->get_sanitized_values( $instance ); ?> -

    +

    + + +

    +

    + +

    - + +

    +

    +

    -

    Date: Mon, 22 Jan 2018 18:36:23 -0500 Subject: [PATCH 48/56] Update changelog --- readme.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index adc83a6..9d0613e 100644 --- a/readme.txt +++ b/readme.txt @@ -53,15 +53,16 @@ Previous to version 3.0, this plugin had a feature where you could RSVP to an ev = 3.0.0 = * NEW: Gutenberg support: 2 new blocks for listing events -* NEW: API endpoints for fetching Meetup.com data +* NEW: Internal API endpoints for fetching Meetup.com data * BREAKING: Removal of OAuth RSVP feature +* BREAKING: Removed themeable meetup-list template in favor of standardizing with gutenberg blocks * BREAKING: Set minimum PHP version to 5.6 * UPDATE: Refactor basically the entire plugin * UPDATE: Refactor widgets into new `includes/widgets/*` files * UPDATE: Move templates into `includes/templates/` folder * DEVELOPER: Add PHP CodeSniffer, clean up flagged issues * DEVELOPER: Add webpack, babel, eslint for building gutenberg blocks -* DEVELOPER: Add Handlebars for the gutenberg block templates (widgets still use themeable php templates) +* DEVELOPER: Add Handlebars for the gutenberg block templates = 2.2.1 = From 232581a76d0eba6206c96cfa2178838a93f00dc2 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 22 Jan 2018 18:36:39 -0500 Subject: [PATCH 49/56] Update block setting title --- includes/blocks/src/components/group-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index 4c58370..be0c632 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -98,7 +98,7 @@ class GroupListBlock extends Component { const controls = focus && ( Date: Thu, 25 Jan 2018 10:55:05 -0500 Subject: [PATCH 50/56] Add script to generate zip file --- package.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ed58691..0824c94 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "NODE_ENV=production webpack", "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint blocks/src/*", - "reformat": "prettier --write" + "reformat": "prettier --write", + "zip": "zip" }, "repository": { "type": "git", @@ -37,12 +38,20 @@ "prettier": "git+https://github.com/Automattic/calypso-prettier.git", "raw-loader": "^0.5.1", "webpack": "^3.10.0", - "webpack-dev-server": "^2.9.7" + "webpack-dev-server": "^2.9.7", + "zip-project": "git+https://github.com/ryelle/zip-project.git" }, "dependencies": { "debounce": "^1.1.0", "handlebars": "^4.0.11", "qs": "6.5.1", "whatwg-fetch": "^2.0.3" - } + }, + "files": [ + "readme.txt", + "vs_meetup.php", + "build/**", + "includes/**", + "vendor/**" + ] } From 9058b3e216d2c0c3e1b8f6c51eeadcd5294a4586 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 27 Jan 2018 14:00:44 -0500 Subject: [PATCH 51/56] Fix typo in label --- includes/blocks/src/components/group-list.js | 2 +- includes/blocks/src/components/user-list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index be0c632..02767d0 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -109,7 +109,7 @@ class GroupListBlock extends Component { onChange={ this.onChangeToggle( 'show_description' ) } /> Date: Sun, 18 Mar 2018 20:02:26 -0400 Subject: [PATCH 52/56] Update Editable to RichText --- includes/blocks/src/components/group-list.js | 4 ++-- includes/blocks/src/components/user-list.js | 4 ++-- vs_meetup.php | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index 02767d0..f601d28 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -17,7 +17,7 @@ const { __ } = wp.i18n; const translate = str => __( str, 'meetup-widgets' ); const { Component } = wp.element; const { - Editable, + RichText, InspectorControls, InspectorControls: { RangeControl, SelectControl, TextControl, ToggleControl }, } = wp.blocks; @@ -128,7 +128,7 @@ class GroupListBlock extends Component { return [ controls,
    - __( str, 'meetup-widgets' ); const { Component } = wp.element; const { - Editable, + RichText, InspectorControls, InspectorControls: { RangeControl, TextControl, ToggleControl }, } = wp.blocks; @@ -113,7 +113,7 @@ class UserListBlock extends Component { return [ controls,
    - Date: Sun, 18 Mar 2018 20:03:54 -0400 Subject: [PATCH 53/56] Allow only showing 1 event --- includes/blocks/src/components/group-list.js | 2 +- includes/blocks/src/components/user-list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index f601d28..38a7157 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -112,7 +112,7 @@ class GroupListBlock extends Component { label={ translate( 'Number of events to show' ) } value={ attributes.per_page } onChange={ this.onChangeEditable( 'per_page' ) } - min={ 2 } + min={ 1 } max={ 15 } /> Date: Mon, 19 Mar 2018 19:17:39 -0400 Subject: [PATCH 54/56] Update eslint config --- .eslintrc.json | 139 ++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 + 2 files changed, 133 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 867709e..2118136 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,8 @@ "parser": "babel-eslint", "extends": [ "wordpress", - "plugin:react/recommended" + "plugin:react/recommended", + "plugin:jsx-a11y/recommended" ], "env": { "browser": false, @@ -17,13 +18,16 @@ } }, "globals": { + "TEMPLATE_DIRECTORY": true, "wp": true, "wpApiSettings": true, "window": true, "document": true }, "plugins": [ - "react" + "wordpress", + "react", + "jsx-a11y" ], "settings": { "react": { @@ -31,11 +35,130 @@ } }, "rules": { - "camelcase": 0, - "comma-dangle": [ 2, "always-multiline" ], - "no-unused-vars": 2, - "react/display-name": 0, - "react/prop-types": 0, - "space-in-parens": [ 2, "always" ] + "array-bracket-spacing": [ "error", "always" ], + "brace-style": [ "error", "1tbs" ], + "camelcase": "off", + "comma-dangle": [ "error", "always-multiline" ], + "comma-spacing": "error", + "comma-style": "error", + "computed-property-spacing": [ "error", "always" ], + "constructor-super": "error", + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "error", + "func-call-spacing": "error", + "indent": [ "error", "tab", { "SwitchCase": 1 } ], + "jsx-a11y/label-has-for": [ "error", { "required": "id" } ], + "jsx-a11y/media-has-caption": "off", + "jsx-a11y/no-noninteractive-tabindex": "off", + "jsx-a11y/role-has-required-aria-props": "off", + "jsx-quotes": "error", + "key-spacing": "error", + "keyword-spacing": "error", + "lines-around-comment": "off", + "no-alert": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-console": "error", + "no-const-assign": "error", + "no-debugger": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "error", + "no-else-return": "error", + "no-eval": "error", + "no-extra-semi": "error", + "no-fallthrough": "error", + "no-lonely-if": "error", + "no-mixed-operators": "error", + "no-mixed-spaces-and-tabs": "error", + "no-multiple-empty-lines": [ "error", { "max": 1 } ], + "no-multi-spaces": "error", + "no-multi-str": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "error", + "no-redeclare": "error", + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])", + "message": "Translate function arguments must be string literals." + }, + { + "selector": "CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])", + "message": "Translate function arguments must be string literals." + }, + { + "selector": "CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])", + "message": "Translate function arguments must be string literals." + }, + { + "selector": "CallExpression[callee.name='__'][arguments.length!=2]", + "message": "Translate function __ must always be invoked with two arguments." + }, + { + "selector": "CallExpression[callee.name='_x'][arguments.length!=3]", + "message": "Translate function _x must always be invoked with three arguments." + }, + { + "selector": "CallExpression[callee.name='_n'][arguments.length!=4]", + "message": "Translate function _n must always be invoked with four arguments." + }, + { + "selector": "CallExpression[callee.name='_nx'][arguments.length!=5]", + "message": "Translate function _nx must always be invoked with five arguments." + } + ], + "no-shadow": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-unreachable": "error", + "no-unsafe-negation": "error", + "no-unused-expressions": "error", + "no-unused-vars": "error", + "no-useless-computed-key": "error", + "no-useless-constructor": "error", + "no-useless-return": "error", + "no-var": "error", + "no-whitespace-before-property": "error", + "object-curly-spacing": [ "error", "always" ], + "padded-blocks": [ "error", "never" ], + "prefer-const": "error", + "quote-props": [ "error", "as-needed" ], + "react/display-name": "off", + "react/jsx-curly-spacing": [ + "error", + { + "when": "always", + "children": true + } + ], + "react/jsx-equals-spacing": "error", + "react/jsx-indent": [ "error", "tab" ], + "react/jsx-indent-props": [ "error", "tab" ], + "react/jsx-key": "error", + "react/jsx-tag-spacing": "error", + "react/no-children-prop": "off", + "react/no-find-dom-node": "warn", + "react/prop-types": "off", + "semi": "error", + "semi-spacing": "error", + "space-before-blocks": [ "error", "always" ], + "space-before-function-paren": [ "error", "never" ], + "space-in-parens": [ "error", "always" ], + "space-infix-ops": [ "error", { "int32Hint": false } ], + "space-unary-ops": [ + "error", + { + "overrides": { + "!": true + } + } + ], + "template-curly-spacing": [ "error", "always" ], + "valid-typeof": "error", + "yoda": "off" } } diff --git a/package.json b/package.json index 0824c94..b749f49 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "babel-preset-env": "^1.6.1", "eslint": "^4.14.0", "eslint-config-wordpress": "^2.0.0", + "eslint-plugin-jsx-a11y": "^6.0.3", "eslint-plugin-react": "^7.5.1", + "eslint-plugin-wordpress": "git+https://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress.git", "extract-text-webpack-plugin": "^3.0.2", "handlebars-loader": "^1.6.0", "postcss-loader": "^2.0.9", From 15b0eeda41e7d7b5c40c78cf6b54feb33600ab8b Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Mon, 19 Mar 2018 19:18:12 -0400 Subject: [PATCH 55/56] Switch to new components, auto format files --- includes/blocks/src/components/group-list.js | 57 +++++++++++++------- includes/blocks/src/components/user-list.js | 52 ++++++++++++------ includes/blocks/src/group-list.js | 11 ++-- includes/blocks/src/user-list.js | 11 ++-- 4 files changed, 84 insertions(+), 47 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index 38a7157..8145fdc 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -1,7 +1,6 @@ +/** @format */ /** * External Dependencies - * - * @format */ import { stringify } from 'qs'; @@ -14,14 +13,18 @@ const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); * Core WP Dependencies */ const { __ } = wp.i18n; -const translate = str => __( str, 'meetup-widgets' ); const { Component } = wp.element; +const { RichText, InspectorControls } = wp.blocks; const { - RichText, - InspectorControls, - InspectorControls: { RangeControl, SelectControl, TextControl, ToggleControl }, -} = wp.blocks; -const { Dashicon, Placeholder, Spinner, withAPIData } = wp.components; + Dashicon, + Placeholder, + RangeControl, + Spinner, + SelectControl, + TextControl, + ToggleControl, + withAPIData, +} = wp.components; class GroupListBlock extends Component { constructor() { @@ -37,7 +40,10 @@ class GroupListBlock extends Component { } onChangeToggle( field ) { - return () => this.props.setAttributes( { [ field ]: ! this.props.attributes[ field ] } ); + return () => + this.props.setAttributes( { + [ field ]: ! this.props.attributes[ field ], + } ); } onFocus( field ) { @@ -50,7 +56,10 @@ class GroupListBlock extends Component { /* eslint-disable yoda */ if ( error && error.status > 200 ) { - let message = translate( 'There was an error loading the API for this block' ); + let message = __( + 'There was an error loading the API for this block', + 'meetup-widgets' + ); if ( error.resposeJSON && error.resposeJSON.message ) { message = error.resposeJSON.message; } @@ -65,7 +74,9 @@ class GroupListBlock extends Component { if ( isLoading ) { return ( - + ); @@ -76,7 +87,8 @@ class GroupListBlock extends Component { events: data, hide_title: true, // title is here, so we hide it in the final template. show_events: !! data.length, - show_events_description: !! data.length && attributes.show_description, + show_events_description: + !! data.length && attributes.show_description, }; return { __html: runTemplate( vars ) }; @@ -91,32 +103,35 @@ class GroupListBlock extends Component { value: group.urlname, } ) ); groupOptions.unshift( { - label: translate( 'Select a groupā€¦' ), + label: __( 'Select a groupā€¦', 'meetup-widgets' ), value: '', } ); const controls = focus && ( @@ -130,14 +145,18 @@ class GroupListBlock extends Component {
    - { list.__html ?
    : list } + { list.__html ? ( +
    + ) : ( + list + ) }
    , ]; } diff --git a/includes/blocks/src/components/user-list.js b/includes/blocks/src/components/user-list.js index 18bfd69..c0236e3 100644 --- a/includes/blocks/src/components/user-list.js +++ b/includes/blocks/src/components/user-list.js @@ -1,7 +1,6 @@ +/** @format */ /** * External Dependencies - * - * @format */ import { stringify } from 'qs'; @@ -14,14 +13,17 @@ const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); * Core WP Dependencies */ const { __ } = wp.i18n; -const translate = str => __( str, 'meetup-widgets' ); const { Component } = wp.element; +const { RichText, InspectorControls } = wp.blocks; const { - RichText, - InspectorControls, - InspectorControls: { RangeControl, TextControl, ToggleControl }, -} = wp.blocks; -const { Dashicon, Placeholder, Spinner, withAPIData } = wp.components; + Dashicon, + Placeholder, + RangeControl, + Spinner, + TextControl, + ToggleControl, + withAPIData, +} = wp.components; class UserListBlock extends Component { constructor() { @@ -37,7 +39,10 @@ class UserListBlock extends Component { } onChangeToggle( field ) { - return () => this.props.setAttributes( { [ field ]: ! this.props.attributes[ field ] } ); + return () => + this.props.setAttributes( { + [ field ]: ! this.props.attributes[ field ], + } ); } onFocus( field ) { @@ -50,7 +55,10 @@ class UserListBlock extends Component { /* eslint-disable yoda */ if ( error && error.status > 200 ) { - let message = translate( 'There was an error loading the API for this block' ); + let message = __( + 'There was an error loading the API for this block', + 'meetup-widgets' + ); if ( error.resposeJSON && error.resposeJSON.message ) { message = error.resposeJSON.message; } @@ -65,7 +73,9 @@ class UserListBlock extends Component { if ( isLoading ) { return ( - + ); @@ -76,7 +86,8 @@ class UserListBlock extends Component { events: data, hide_title: true, // title is here, so we hide it in the final template. show_events: !! data.length, - show_events_description: !! data.length && attributes.show_description, + show_events_description: + !! data.length && attributes.show_description, }; return { __html: runTemplate( vars ) }; @@ -89,19 +100,22 @@ class UserListBlock extends Component { const controls = focus && ( @@ -115,14 +129,18 @@ class UserListBlock extends Component {
    - { list.__html ?
    : list } + { list.__html ? ( +
    + ) : ( + list + ) }
    , ]; } diff --git a/includes/blocks/src/group-list.js b/includes/blocks/src/group-list.js index b037c62..ccf58cd 100755 --- a/includes/blocks/src/group-list.js +++ b/includes/blocks/src/group-list.js @@ -1,10 +1,8 @@ +/** @format */ /** * Core WP Dependencies - * - * @format */ const { __ } = wp.i18n; -const translate = str => __( str, 'meetup-widgets' ); /** * Internal Dependencies @@ -13,8 +11,11 @@ import GroupListBlock from './components/group-list'; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { - title: translate( 'Meetup.com List' ), - description: translate( 'This is a list of events for a given group on Meetup.com' ), + title: __( 'Meetup.com List', 'meetup-widgets' ), + description: __( + 'This is a list of events for a given group on Meetup.com', + 'meetup-widgets' + ), icon: 'groups', category: 'embed', supports: { diff --git a/includes/blocks/src/user-list.js b/includes/blocks/src/user-list.js index 2674366..ade7da6 100644 --- a/includes/blocks/src/user-list.js +++ b/includes/blocks/src/user-list.js @@ -1,10 +1,8 @@ +/** @format */ /** * Core WP Dependencies - * - * @format */ const { __ } = wp.i18n; -const translate = str => __( str, 'meetup-widgets' ); /** * Internal Dependencies @@ -13,9 +11,10 @@ import UserListBlock from './components/user-list'; // Visit https://wordpress.org/gutenberg/handbook/block-api/ to learn about Block API export default { - title: translate( 'Meetup.com User List' ), - description: translate( - 'This is a list of the upcoming events on Meetup.com for the user that created the API key' + title: __( 'Meetup.com User List', 'meetup-widgets' ), + description: __( + 'This is a list of the upcoming events on Meetup.com for the user that created the API key', + 'meetup-widgets' ), icon: 'groups', category: 'embed', From b397067aa9aa8ccef1d7e7b5ea724c74bf1c51b6 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Sat, 19 May 2018 13:17:15 -0400 Subject: [PATCH 56/56] Update RichText component New API returns an array value, so we need to handle that in the PHP output --- includes/blocks/src/components/group-list.js | 101 ++++++++----------- includes/blocks/src/components/user-list.js | 89 +++++++--------- includes/class-meetup-widgets-blocks.php | 20 +++- 3 files changed, 95 insertions(+), 115 deletions(-) diff --git a/includes/blocks/src/components/group-list.js b/includes/blocks/src/components/group-list.js index 8145fdc..2a8beed 100644 --- a/includes/blocks/src/components/group-list.js +++ b/includes/blocks/src/components/group-list.js @@ -14,10 +14,11 @@ const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); */ const { __ } = wp.i18n; const { Component } = wp.element; -const { RichText, InspectorControls } = wp.blocks; +const { RichText, InspectorControls } = wp.editor; const { Dashicon, Placeholder, + PanelBody, RangeControl, Spinner, SelectControl, @@ -31,7 +32,6 @@ class GroupListBlock extends Component { super( ...arguments ); this.onChangeEditable = this.onChangeEditable.bind( this ); this.onChangeToggle = this.onChangeToggle.bind( this ); - this.onFocus = this.onFocus.bind( this ); this.renderEventsList = this.renderEventsList.bind( this ); } @@ -46,20 +46,13 @@ class GroupListBlock extends Component { } ); } - onFocus( field ) { - return focus => this.props.setFocus( { ...focus, editable: field } ); - } - renderEventsList() { const { attributes, events = {} } = this.props; const { isLoading, error, data = [] } = events; /* eslint-disable yoda */ if ( error && error.status > 200 ) { - let message = __( - 'There was an error loading the API for this block', - 'meetup-widgets' - ); + let message = __( 'There was an error loading the API for this block', 'meetup-widgets' ); if ( error.resposeJSON && error.resposeJSON.message ) { message = error.resposeJSON.message; } @@ -74,9 +67,7 @@ class GroupListBlock extends Component { if ( isLoading ) { return ( - + ); @@ -85,18 +76,16 @@ class GroupListBlock extends Component { const vars = { attributes, events: data, - hide_title: true, // title is here, so we hide it in the final template. + hide_title: true, // title is editable here, so we hide it in the final template. show_events: !! data.length, - show_events_description: - !! data.length && attributes.show_description, + show_events_description: !! data.length && attributes.show_description, }; return { __html: runTemplate( vars ) }; } render() { - const { attributes, focus, groups: { data = [] } } = this.props; - const focusedEditable = focus ? focus.editable || 'title' : null; + const { attributes, isSelected, groups: { data = [] } } = this.props; const groupOptions = data.map( group => ( { label: group.name, @@ -107,56 +96,52 @@ class GroupListBlock extends Component { value: '', } ); - const controls = focus && ( + const controls = ( - - - - + + + + + + ); const list = this.renderEventsList(); + const { title } = attributes; return [ controls,
    - - { list.__html ? ( -
    - ) : ( - list + { ( ( title && title.length > 0 ) || isSelected ) && ( + ) } + { list.__html ?
    : list }
    , ]; } diff --git a/includes/blocks/src/components/user-list.js b/includes/blocks/src/components/user-list.js index c0236e3..f09646c 100644 --- a/includes/blocks/src/components/user-list.js +++ b/includes/blocks/src/components/user-list.js @@ -14,10 +14,11 @@ const runTemplate = require( TEMPLATE_DIRECTORY + '/meetup-list.hbs' ); */ const { __ } = wp.i18n; const { Component } = wp.element; -const { RichText, InspectorControls } = wp.blocks; +const { RichText, InspectorControls } = wp.editor; const { Dashicon, Placeholder, + PanelBody, RangeControl, Spinner, TextControl, @@ -30,7 +31,6 @@ class UserListBlock extends Component { super( ...arguments ); this.onChangeEditable = this.onChangeEditable.bind( this ); this.onChangeToggle = this.onChangeToggle.bind( this ); - this.onFocus = this.onFocus.bind( this ); this.renderEventsList = this.renderEventsList.bind( this ); } @@ -45,20 +45,13 @@ class UserListBlock extends Component { } ); } - onFocus( field ) { - return focus => this.props.setFocus( { ...focus, editable: field } ); - } - renderEventsList() { const { attributes, events = {} } = this.props; const { isLoading, error, data = [] } = events; /* eslint-disable yoda */ if ( error && error.status > 200 ) { - let message = __( - 'There was an error loading the API for this block', - 'meetup-widgets' - ); + let message = __( 'There was an error loading the API for this block', 'meetup-widgets' ); if ( error.resposeJSON && error.resposeJSON.message ) { message = error.resposeJSON.message; } @@ -73,9 +66,7 @@ class UserListBlock extends Component { if ( isLoading ) { return ( - + ); @@ -84,63 +75,57 @@ class UserListBlock extends Component { const vars = { attributes, events: data, - hide_title: true, // title is here, so we hide it in the final template. + hide_title: true, // title is editable here, so we hide it in the final template. show_events: !! data.length, - show_events_description: - !! data.length && attributes.show_description, + show_events_description: !! data.length && attributes.show_description, }; return { __html: runTemplate( vars ) }; } render() { - const { attributes, focus } = this.props; - const focusedEditable = focus ? focus.editable || 'title' : null; + const { attributes, isSelected } = this.props; - const controls = focus && ( + const controls = ( - - - + + + + + ); const list = this.renderEventsList(); + const { title } = attributes; return [ controls,
    - - { list.__html ? ( -
    - ) : ( - list + { ( ( title && title.length > 0 ) || isSelected ) && ( + ) } + { list.__html ?
    : list }
    , ]; } diff --git a/includes/class-meetup-widgets-blocks.php b/includes/class-meetup-widgets-blocks.php index 14042a0..3b443be 100755 --- a/includes/class-meetup-widgets-blocks.php +++ b/includes/class-meetup-widgets-blocks.php @@ -35,8 +35,10 @@ public function __construct() { register_block_type( 'meetup-widgets/group-list', array( 'attributes' => array( 'title' => array( - 'type' => 'string', - 'default' => __( 'Upcoming Events', 'meetup-widgets' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string' + ), ), 'placeholder' => array( 'type' => 'string', @@ -62,8 +64,10 @@ public function __construct() { register_block_type( 'meetup-widgets/user-list', array( 'attributes' => array( 'title' => array( - 'type' => 'string', - 'default' => __( 'My Events', 'meetup-widgets' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string' + ), ), 'placeholder' => array( 'type' => 'string', @@ -144,10 +148,13 @@ public static function render_block_group_list( $attributes ) { $has_events = ! isset( $events['code'] ) && count( $events ) > 0; + if ( is_array( $attributes['title'] ) ) { + $attributes['title'] = join( ' ', $attributes['title'] ); + } $vars = [ 'attributes' => $attributes, 'events' => $events, - 'hide_title' => false, + 'hide_title' => empty( $attributes['title'] ), 'show_events' => $has_events, 'show_events_description' => $has_events && $attributes['show_description'], ]; @@ -191,6 +198,9 @@ public static function render_block_user_list( $attributes ) { $has_events = count( $events ) > 0; + if ( is_array( $attributes['title'] ) ) { + $attributes['title'] = join( ' ', $attributes['title'] ); + } $vars = [ 'attributes' => $attributes, 'events' => $events,