diff --git a/.jshintrc b/.jshintrc index 0123783..5dbad30 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,32 +1,50 @@ { - "bitwise": true, + "boss": true, "curly": true, "eqeqeq": true, + "eqnull": true, "esversion": 11, + "expr": true, + "immed": true, + "noarg": true, + "nonbsp": true, + "onevar": true, + "quotmark": "single", + "trailing": true, + "undef": true, + "unused": true, + + "browser": true, + + "globals": { + "_": false, + "Backbone": false, + "jQuery": false, + "JSON": false, + "wp": false, + "export": false, + "module": false, + "require": false, + "WorkerGlobalScope": false, + "self": false, + "OffscreenCanvas": false, + "Promise": false + }, + "bitwise": true, "forin": false, "freeze": true, "futurehostile": true, "latedef": "nofunc", "leanswitch": true, - "noarg": true, "nocomma": true, - "nonbsp": true, "nonew": true, "noreturnawait": true, "predef": [ "tippy", - "lc_select", "FullCalendar" ], "shadow": false, "singleGroups": false, "trailingcomma": false, - "undef": true, - "unused": true, - "varstmt": true, - - "boss": true, - "eqnull": true, - - "browser": true + "varstmt": true } diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 7e6c812..379c262 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -8,9 +8,21 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/en/1.0.0/[Keep a Changelog], and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning]. -== {compare}/v1.4.1\...main[Unreleased] +== {compare}/v3.0.0\...main[Unreleased] -== {compare}/v1.4.0\...main[Unreleased] +== {compare}/v2.1.2\...v3.0.0[3.0.0] + +=== Changed + +* Use meilisearch and aurora API instead of elastic cloud +* Get event owners from meilisearch on admin page +* Get events by aurora events.json api +* Move all requests to client +* JavaScript package updates + +== {compare}/v1.4.1\...v2.1.2[2.1.2] + +== {compare}/v1.4.0\...v1.4.1[1.4.1] === Changed diff --git a/README.adoc b/README.adoc index 3c39481..69ba61b 100644 --- a/README.adoc +++ b/README.adoc @@ -25,7 +25,7 @@ https://wordpress.org/[WordPress] plugin for displaying appointments from Elasti == ⭐ Features -* Display events from Elastic that are allowed to be visible +* Display events from aurora that are allowed to be visible * Multiple event owners / event sources * Multiple views: list view, month view * Customizable colors per event owner / event source @@ -88,9 +88,9 @@ IMPORTANT: The plugin's latest version is already installed at https://hosting.r /** * Rotaract Params */ -define( 'ROTARACT_APPOINTMENTS_CLOUD_ID', '' ); -define( 'ROTARACT_APPOINTMENTS_API_ID', '' ); -define( 'ROTARACT_APPOINTMENTS_API_KEY', '' ); +define( 'ROTARACT_APPOINTMENTS_SEARCH_URL', '' ); +define( 'ROTARACT_APPOINTMENTS_SEARCH_KEY', '' ); +define( 'ROTARACT_APPOINTMENTS_AURORA_URL', '' ); ---- . Navigate to setting page 'Rotaract' within the https://wordpress.org/support/article/administration-screens/[Administration Screen] . Add at least one owner, select a color and save changes diff --git a/admin/class-rotaract-appointments-admin.php b/admin/class-rotaract-appointments-admin.php index 899e64e..4c47496 100644 --- a/admin/class-rotaract-appointments-admin.php +++ b/admin/class-rotaract-appointments-admin.php @@ -41,36 +41,34 @@ class Rotaract_Appointments_Admin { private string $version; /** - * The version of the JavaScript dependency LC-select. + * The version of the JavaScript dependency Instantsearch. * - * @since 1.0.0 + * @since 3.0.0 * @access private - * @var string $lc_select_version The current version of lc_select. + * @var string $instantsearch_version The current version of Meilisearch. */ - private string $lc_select_version = '1.1.7'; + private string $instantsearch_version = '4.60.0'; /** - * The Elasticsearch caller. + * The version of the JavaScript dependency Meilisearch. * - * @since 1.0.0 + * @since 3.0.0 * @access private - * @var Rotaract_Elastic_Caller $elastic_caller The object that handles search calls to the Elasticsearch instance. + * @var string $instant_meilisearch_version The current version of Meilisearch. */ - private Rotaract_Elastic_Caller $elastic_caller; + private string $instant_meilisearch_version = '0.13.6'; /** * Initialize the class and set its properties. * - * @param string $rotaract_appointments The name of this plugin. - * @param string $version The version of this plugin. - * @param Rotaract_Elastic_Caller $elastic_caller Elasticsearch call handler. + * @param string $rotaract_appointments The name of this plugin. + * @param string $version The version of this plugin. * @since 1.0.0 */ - public function __construct( string $rotaract_appointments, string $version, Rotaract_Elastic_Caller $elastic_caller ) { + public function __construct( string $rotaract_appointments, string $version ) { $this->rotaract_appointments = $rotaract_appointments; $this->version = $version; - $this->elastic_caller = $elastic_caller; } /** @@ -78,10 +76,9 @@ public function __construct( string $rotaract_appointments, string $version, Rot * * @since 1.0.0 */ - public function enqueue_styles() { + public function enqueue_styles(): void { wp_enqueue_style( '$this->rotaract_appointments', plugins_url( 'css/admin.css', __FILE__ ), array(), $this->version, 'all' ); - wp_enqueue_style( 'lc-select-light', plugins_url( 'node_modules/lc-select/themes/light.css', __DIR__ ), array(), $this->lc_select_version, 'all' ); } /** @@ -89,24 +86,13 @@ public function enqueue_styles() { * * @since 1.0.0 */ - public function enqueue_scripts() { + public function enqueue_scripts(): void { - // Including the lc_select script in footer results in broken owner selection in appointments submenu page. - wp_enqueue_script( 'lc-select', plugins_url( 'node_modules/lc-select/lc_select.min.js', __DIR__ ), array(), $this->lc_select_version, true ); + // Including the Meilisearch script in footer results in broken owner selection in appointments submenu page. + wp_enqueue_script( 'instantsearch', plugins_url( 'node_modules/instantsearch.js/dist/instantsearch.production.min.js', __DIR__ ), array(), $this->instantsearch_version, true ); + wp_enqueue_script( 'instant-meilisearch', plugins_url( 'node_modules/@meilisearch/instant-meilisearch/dist/instant-meilisearch.umd.min.js', __DIR__ ), array( 'instantsearch' ), $this->instant_meilisearch_version, true ); - wp_enqueue_script( $this->rotaract_appointments, plugins_url( 'js/settings.js', __FILE__ ), array( 'lc-select' ), $this->version, true ); - wp_localize_script( - $this->rotaract_appointments, - 'lcData', - array( - 'labels' => array( - esc_attr__( 'Search', 'rotaract-appointments' ), - esc_attr__( 'Add', 'rotaract-appointments' ), - esc_attr__( 'Select', 'rotaract-appointments' ), - esc_attr__( 'Nothing found.', 'rotaract-appointments' ), - ), - ) - ); + wp_enqueue_script( $this->rotaract_appointments, plugins_url( 'js/settings.js', __FILE__ ), array( 'instantsearch', 'instant-meilisearch' ), $this->version, true ); } /** @@ -122,17 +108,17 @@ private function get_partial( string $filename ): string { } /** - * HTML notice that elasticsearch configuration is missing. + * HTML notice that meilisearch configuration is missing. */ - public function elastic_missing_notice() { + public function meilisearch_missing_notice(): void { - include $this->get_partial( 'notice-elastic-missing.php' ); + include $this->get_partial( 'notice-meilisearch-missing.php' ); } /** * Adds setting fields for this plugin. */ - public function admin_init() { + public function admin_init(): void { // Register our settings. register_setting( @@ -198,7 +184,7 @@ public function admin_init() { /** * Adds setting menu and submenu page for this plugin. */ - public function admin_menu() { + public function admin_menu(): void { if ( empty( $GLOBALS['admin_page_hooks']['rotaract'] ) ) { add_menu_page( @@ -216,7 +202,7 @@ public function admin_menu() { /** * Builds the HTML for the appointments submenu page. */ - public function rotaract_settings_html() { + public function rotaract_settings_html(): void { // Check user capabilities. if ( ! current_user_can( 'manage_options' ) ) { return; @@ -242,7 +228,7 @@ public function rotaract_settings_html() { * * @return array */ - private function get_palette() { + private function get_palette(): array { return array( '#d41367' => __( 'Cranberry', 'rotaract-appointments' ), '#0050a2' => __( 'Azure', 'rotaract-appointments' ), @@ -260,7 +246,7 @@ private function get_palette() { * * @param array $args The settings array, defining title, id, callback. */ - public function rotaract_appointment_section( array $args ) { // phpcs:ignore + public function rotaract_appointment_section( array $args ): void { // phpcs:ignore include $this->get_partial( 'section-rotaract-appointments.php' ); } @@ -270,7 +256,7 @@ public function rotaract_appointment_section( array $args ) { // phpcs:ignore * * @param array $args The settings array, defining ... */ - public function appointment_owners_field( array $args ) { // phpcs:ignore + public function appointment_owners_field( array $args ): void { // phpcs:ignore // Get the value of the setting we've registered with register_setting(). $selected_owners = get_option( 'rotaract_appointment_owners' ); @@ -281,13 +267,14 @@ public function appointment_owners_field( array $args ) { // phpcs:ignore /** * Builds select tag containing grouped appointment options. * - * @param bool $is_new True if this intends to be a new owner. + * @param bool $is_prototype Flag for identifying create/update. * @param int $index Index of the parameter. * @param string|null $owner_name The owner's name. + * @param string|null $owner_slug The owner's slug. + * @param string|null $owner_type The owner's type. * @param string|null $owner_color Selected color. */ - private function print_appointment_owners_line( bool $is_new, int $index, string $owner_name = null, string $owner_color = null ) { // phpcs:ignore - $owners = $this->elastic_caller->get_all_owners(); + private function print_appointment_owners_line( bool $is_prototype, int $index = -1, string $owner_name = null, string $owner_slug = null, string $owner_type = null, string $owner_color = null ): void { // phpcs:ignore $color_palette = $this->get_palette(); include $this->get_partial( 'field-appointment-owner.php' ); @@ -298,7 +285,7 @@ private function print_appointment_owners_line( bool $is_new, int $index, string * * @param array $args The settings array, defining ... */ - public function appointment_ics_field( array $args ) { // phpcs:ignore + public function appointment_ics_field( array $args ): void { // phpcs:ignore // Get the value of the setting we've registered with register_setting(). $ics_feeds = get_option( 'rotaract_appointment_ics' ); @@ -310,11 +297,11 @@ public function appointment_ics_field( array $args ) { // phpcs:ignore * * @param bool $is_new True if this intends to be a new owner. * @param int $index Index of the parameter. - * @param string|null $feed_name The feed's name. - * @param string|null $feed_url The feed's url. + * @param string|null $feed_name The owner's name. + * @param string|null $feed_url The ICS URL. * @param string|null $feed_color Selected color. */ - private function print_ics_line( bool $is_new, int $index, string $feed_name = null, string $feed_url = null, string $feed_color = null ) { // phpcs:ignore + private function print_ics_line( bool $is_new, int $index, string $feed_name = null, string $feed_url = null, string $feed_color = null ): void { // phpcs:ignore $color_palette = $this->get_palette(); include $this->get_partial( 'field-ics-line.php' ); @@ -332,12 +319,16 @@ public function sanitize_rotaract_appointment_owners( ?array $input ): array { // Re-indexing the array. foreach ( $input as $owner ) { $name = sanitize_text_field( $owner['name'] ); + $type = sanitize_text_field( $owner['type'] ); + $slug = sanitize_text_field( $owner['slug'] ); $color = sanitize_hex_color( $owner['color'] ); - if ( empty( $name ) || empty( $color ) ) { + if ( empty( $name ) || empty( $type ) || empty( $slug ) || empty( $color ) ) { continue; } $new_input[] = array( 'name' => $name, + 'type' => $type, + 'slug' => $slug, 'color' => $color, ); } @@ -375,7 +366,7 @@ public function sanitize_rotaract_appointment_ics( ?array $input ): array { * * @param array $args The settings array, defining ... */ - public function appointment_owners_shortcode_manual( array $args ) { // phpcs:ignore + public function appointment_owners_shortcode_manual( array $args ): void { // phpcs:ignore include $this->get_partial( 'field-shortcode-manual.php' ); } diff --git a/admin/css/admin.css b/admin/css/admin.css index b9fe828..7d95e06 100644 --- a/admin/css/admin.css +++ b/admin/css/admin.css @@ -1,3 +1,7 @@ +.prototype { + display: none !important; +} + .button .dashicons { line-height: 1.5em; } @@ -33,6 +37,64 @@ padding: 1rem; } -select.lc-select { - min-width: 150px; +.modal-bg { + display: none; + background-color: rgba(0, 0, 0, 0.2); + padding: 4rem 1rem; + position: fixed; + bottom: 0; + left: 0; + right: 0; + top: 0; + overflow: scroll; +} +.modal-bg.show { + display: block; +} + +.modal-bg .modal { + background-color: #ffffff; + border-radius: 0.5rem; + margin: 0 auto; + max-width: 480px; + padding: 1rem; +} + +#searchbox > .button { + margin-left: 1rem; +} +#searchbox, .ais-SearchBox-form { + display: flex; + align-items: stretch; +} +.ais-SearchBox, .ais-SearchBox-form .ais-SearchBox-input { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + flex-grow: 1; + margin: 0; +} +.ais-SearchBox-form .button { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.button-primary .ais-SearchBox-submitIcon { + fill: #ffffff; +} + +#hits .ais-Hits-list { + margin: 1rem 0; +} +#hits .ais-Hits-item { + border-top: solid 1px #cccccc; + list-style-type: none; + margin: 0; +} +#hits .ais-Hits-item .button.list-btn { + display: inline-block; + background: none; + border: none; + border-radius: 0; + padding: 0.25rem 1rem; + width: 100%; + text-align: start; } diff --git a/admin/js/settings.js b/admin/js/settings.js index eb7f0b2..daa81fa 100644 --- a/admin/js/settings.js +++ b/admin/js/settings.js @@ -6,39 +6,120 @@ * @subpackage Rotaract_Appointments/admin/js */ -/* globals lcData */ +/* globals instantsearch */ +/* globals instantMeiliSearch */ +/* globals meilisearchCredentials */ -lcSelectInit(); addEventListeners(); - -/** - * Initializes LC-select targeting the select field. - */ -function lcSelectInit() { - lc_select( - 'select.lc-select', - { - enable_search: true, - wrap_width: 'inherit', - pre_placeh_opt: true, - labels: lcData.labels, - on_change: changeColor - } - ); +let search; + +function initSearch() { + if ( ! search || ! search.started ) { + search = instantsearch( + { + indexName: 'Club', + searchClient: instantMeiliSearch( meilisearchCredentials.url, meilisearchCredentials.key ) + } + ); + search.addWidgets( + [ + instantsearch.widgets.configure( + { + attributesToRetrieve: [ 'name', 'slug' ], + hitsPerPage: 10, + length: 10, + limit: 10 + } + ), + instantsearch.widgets.searchBox( + { + container: '#searchbox', + showReset: false, + cssClasses: { + submit: 'button button-primary' + } + } + ), + instantsearch.widgets.hits( + { + container: '#hits-clubs', + templates: { + item: ( hit ) => `` + } + } + ), + instantsearch.widgets + .index( { indexName: 'District' } ) + .addWidgets( + [ + instantsearch.widgets.configure( + { + attributesToRetrieve: [ 'name', 'slug' ], + hitsPerPage: 10, + length: 10, + limit: 10 + } + ), + instantsearch.widgets.hits( + { + container: '#hits-districts', + templates: { + item: ( hit ) => `` + } + } + ) + ] + ), + instantsearch.widgets + .index( { indexName: 'Ressort' } ) + .addWidgets( + [ + instantsearch.widgets.configure( + { + attributesToRetrieve: [ 'name', 'slug' ], + hitsPerPage: 10, + length: 10, + limit: 10 + } + ), + instantsearch.widgets.hits( + { + container: '#hits-ressorts', + templates: { + item: ( hit ) => `` + } + } + ) + ] + ), + instantsearch.widgets + .index( { indexName: 'Mdio' } ) + .addWidgets( + [ + instantsearch.widgets.configure( + { + attributesToRetrieve: [ 'name', 'slug' ], + hitsPerPage: 10, + length: 10, + limit: 10 + } + ), + instantsearch.widgets.hits( + { + container: '#hits-mdios', + templates: { + item: ( hit ) => `` + } + } + ) + ] + ) + ] + ); + search.start(); + } } -/** - * Removes LC-select resulting in plain HTML select field. - */ -function lcSelectDestroy() { - const destroyEvent = new Event( 'lc-select-destroy' ); - const lcSelectElements = document.querySelectorAll( 'select.lc-select' ); - lcSelectElements.forEach( - function (lcSelect) { - lcSelect.dispatchEvent( destroyEvent ); - } - ); -} /** * Registers click events to add or delete appointment owner. @@ -54,53 +135,56 @@ function addEventListeners() { delBtn.addEventListener( 'click', delLine ); } ); + // Add Change Color Listeners. + const colorSelects = document.querySelectorAll( 'select.owner-color, select.feed-color' ); + colorSelects.forEach( + function( colorSelect ) { + colorSelect.addEventListener( 'change', changeColor ); + } + ); - document.querySelector( 'button.add-owner' ) ?.addEventListener( 'click', addOwner ); // phpcs:ignore - document.querySelector( 'button.add-ics' ) ?.addEventListener( 'click', addFeed ); // phpcs:ignore + document.querySelector( 'button.add-owner' )?.addEventListener( + 'click', + function () { + document.querySelector( '.modal-bg' ).classList.toggle( 'show', true ); + initSearch(); + } + ); + document.querySelector( 'button.add-ics' )?.addEventListener( 'click', addFeed ); // phpcs:ignore } /** * Adds new owner whose events to display. */ -function addOwner(event = null) { - if (event) { - event.preventDefault(); - } - - const owners = document.querySelectorAll( '.owner-group select.owner-name' ); +function addOwner( name, slug, type ) { + const owners = document.querySelectorAll( '.owner-group .owner-name' ); let newIndex = 0; owners.forEach( function (owner) { - let i = parseInt( owner.getAttribute( 'name' ).split( /\[|\]/ )[1] ); + let i = parseInt( owner.getAttribute( 'name' ).split( /[\[\]]/ )[1] ); newIndex = Math.max( newIndex, i ); } ); newIndex += 1; - lcSelectDestroy(); - - let newOwner = document.querySelector( '.owner-group' ).cloneNode( true ); - let newSelectName = newOwner.querySelector( 'select.owner-name' ); - let newSelectColor = newOwner.querySelector( 'select.owner-color' ); - - newOwner.style.backgroundColor = null; - newOwner.style.borderColor = null; - - newSelectName.setAttribute( 'name', newSelectName.getAttribute( 'name' ).replace( /\d+/, newIndex ) ); - newSelectColor.setAttribute( 'name', newSelectColor.getAttribute( 'name' ).replace( /\d+/, newIndex ) ); - - newSelectName.value = null; - newSelectColor.value = null; - - newOwner.querySelectorAll( 'option' ).forEach( - function (option) { - option.removeAttribute( 'selected' ); - } - ); - + const newOwner = document.querySelector( '.owner-group.prototype' ).cloneNode( true ); + const nameInput = newOwner.querySelector( 'input.owner-name' ); + const slugInput = newOwner.querySelector( 'input.owner-slug' ); + const typeInput = newOwner.querySelector( 'input.owner-type' ); + const colorSelect = newOwner.querySelector( 'select.owner-color' ); + + nameInput.setAttribute( 'name', nameInput.getAttribute( 'name' ).replace( '-1', newIndex ) ); + nameInput.setAttribute( 'value', name ); + slugInput.setAttribute( 'name', slugInput.getAttribute( 'name' ).replace( '-1', newIndex ) ); + slugInput.setAttribute( 'value', slug ); + typeInput.setAttribute( 'name', typeInput.getAttribute( 'name' ).replace( '-1', newIndex ) ); + typeInput.setAttribute( 'value', type ); + colorSelect.setAttribute( 'name', colorSelect.getAttribute( 'name' ).replace( '-1', newIndex ) ); + + newOwner.classList.remove( 'prototype' ); document.getElementById( 'rotaract-appointment-owner' ).append( newOwner ); - lcSelectInit(); + document.querySelector( '.modal-bg' ).classList.remove( 'show' ); addEventListeners(); } @@ -116,22 +200,17 @@ function addFeed(event = null) { let newIndex = 0; owners.forEach( function (feed) { - let i = parseInt( feed.getAttribute( 'name' ).split( /\[|\]/ )[1] ); + let i = parseInt( feed.getAttribute( 'name' ).split( /[\[\]]/ )[1] ); newIndex = Math.max( newIndex, i ); } ); newIndex += 1; - lcSelectDestroy(); - let newFeed = document.querySelector( '.ics-group' ).cloneNode( true ); let newInputName = newFeed.querySelector( 'input.feed-name' ); let newInputUrl = newFeed.querySelector( 'input.feed-url' ); let newSelectColor = newFeed.querySelector( 'select.feed-color' ); - newFeed.style.backgroundColor = null; - newFeed.style.borderColor = null; - newInputName.setAttribute( 'name', newInputName.getAttribute( 'name' ).replace( /\d+/, newIndex ) ); newInputUrl.setAttribute( 'name', newInputUrl.getAttribute( 'name' ).replace( /\d+/, newIndex ) ); newSelectColor.setAttribute( 'name', newSelectColor.getAttribute( 'name' ).replace( /\d+/, newIndex ) ); @@ -148,7 +227,6 @@ function addFeed(event = null) { document.getElementById( 'rotaract-appointment-ics' ).append( newFeed ); - lcSelectInit(); addEventListeners(); } @@ -164,13 +242,12 @@ function delLine(event) { } /** - * LC-select's on_change callback updating the color. + * Select on_change callback updating the color. * - * @param newValue - * @param targetField + * @param event */ -function changeColor(newValue, targetField) { - const style = targetField.closest( '.appointment-line' ).style; - style.backgroundColor = newValue + '25'; - style.borderColor = newValue; +function changeColor( event ) { + const style = event.target.closest( '.appointment-line' ).style; + style.backgroundColor = event.target.value + '25'; + style.borderColor = event.target.value; } diff --git a/admin/partials/field-appointment-owner.php b/admin/partials/field-appointment-owner.php index dad9c51..3dc58be 100644 --- a/admin/partials/field-appointment-owner.php +++ b/admin/partials/field-appointment-owner.php @@ -14,38 +14,33 @@ ?>
- - + + +