diff --git a/assets/css/admin-pull-table.scss b/assets/css/admin-pull-table.scss index a5d37a417..32786cb55 100644 --- a/assets/css/admin-pull-table.scss +++ b/assets/css/admin-pull-table.scss @@ -32,3 +32,74 @@ list-style: disc; padding-left: 20px; } + +.searchable-select { + position: relative; + display: inline-block; + width: 300px; + vertical-align: top; +} + +.searchable-select__input-container { + display: flex; + align-items: center; + box-shadow: 0 0 0 transparent; + border-radius: 4px; + border: 1px solid #8c8f94; + background-color: #fff; + color: #2c3338; + cursor: text; + overflow: hidden; +} + +.searchable-select__input { + flex-grow: 1; + border: none; + padding: 8px; + outline: none; + border: none !important; + width: 100%; + + &:focus { + outline: none; + border: none !important; + box-shadow: none !important; + } +} + +.searchable-select__icon { + padding: 8px; + background-color: #f0f0f0; + cursor: pointer; +} + +.searchable-select__input-container:focus-within { + border-color: #007bff; +} + +.searchable-select__dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + border: 1px solid #ccc; + border-top: none; + max-height: 200px; + overflow-y: auto; + background-color: white; + display: none; + font-size: small; +} + +.searchable-select__item { + padding: 8px; + cursor: pointer; +} + +.searchable-select__item:hover { + background-color: #f0f0f0; +} + +.searchable-select__item.selected { + background-color: #e0e0e0; +} diff --git a/assets/js/admin-pull.js b/assets/js/admin-pull.js index 3de9f9255..682209240 100755 --- a/assets/js/admin-pull.js +++ b/assets/js/admin-pull.js @@ -6,7 +6,9 @@ import { __ } from '@wordpress/i18n'; const { document } = window; -const chooseConnection = document.getElementById( 'pull_connections' ); +const chooseConnection = document.getElementsByClassName( + 'searchable-select__input' +)[ 0 ]; const choosePostType = document.getElementById( 'pull_post_type' ); const choosePostTypeBtn = document.getElementById( 'pull_post_type_submit' ); const searchField = document.getElementById( 'post-search-input' ); @@ -15,15 +17,6 @@ const form = document.getElementById( 'posts-filter' ); const asDraftCheckboxes = document.querySelectorAll( '[name=dt_as_draft]' ); const pullLinks = document.querySelectorAll( '.distributor_page_pull .pull a' ); -jQuery( chooseConnection ).on( 'change', ( event ) => { - document.location = - event.currentTarget.options[ - event.currentTarget.selectedIndex - ].getAttribute( 'data-pull-url' ); - - document.body.className += ' ' + 'dt-loading'; -} ); - if ( chooseConnection && choosePostType && form ) { if ( choosePostTypeBtn ) { jQuery( choosePostTypeBtn ).on( 'click', ( event ) => { @@ -84,10 +77,7 @@ if ( chooseConnection && choosePostType && form ) { const getURL = () => { const postType = choosePostType.options[ choosePostType.selectedIndex ].value; - const baseURL = - chooseConnection.options[ chooseConnection.selectedIndex ].getAttribute( - 'data-pull-url' - ); + const baseURL = chooseConnection.getAttribute( 'data-pull-url' ); let status = 'new'; if ( -1 < ` ${ form.className } `.indexOf( ' status-skipped ' ) ) { @@ -98,3 +88,123 @@ const getURL = () => { return `${ baseURL }&pull_post_type=${ postType }&status=${ status }`; }; + +document.addEventListener( 'DOMContentLoaded', async function () { + const container = document.querySelector( '.searchable-select' ); + const inputContainer = container.querySelector( + '.searchable-select__input-container' + ); + const input = container.querySelector( '.searchable-select__input' ); + const icon = container.querySelector( + '.searchable-select__input-container > .dashicons-arrow-down' + ); + const dropdown = container.querySelector( '.searchable-select__dropdown' ); + + const pullConnectionItems = await fetch( '/wp-admin/admin-ajax.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: 'action=dt_load_connections_pull', + } ) + .then( ( response ) => response.json() ) + .then( ( data ) => { + return data.data; + } ); + + function htmlDecode( inputText ) { + const doc = new DOMParser().parseFromString( inputText, 'text/html' ); // eslint-disable-line no-undef + return doc.documentElement.textContent; + } + + function setInputDefault() { + const params = new URL( document.location.toString() ).searchParams; + const connection_id = params.get( 'connection_id' ); + + if ( connection_id ) { + const connection = pullConnectionItems.find( + ( item ) => item.id === connection_id + ); + + if ( connection ) { + input.value = connection.name; + input.setAttribute( + 'data-pull-url', + htmlDecode( connection.pull_url ) + ); + } + } + } + + setInputDefault(); + + function createDropdownItems( items ) { + dropdown.innerHTML = ''; + items.forEach( ( item ) => { + const div = document.createElement( 'div' ); + div.classList.add( 'searchable-select__item' ); + // Display name and URL in the dropdown item + div.textContent = `${ item.name } (${ item.url })`; + div.setAttribute( 'data-url', item.url ); + + div.addEventListener( 'click', () => { + // Display name and URL in the input field + input.value = `${ item.name } (${ item.url })`; + // But set only URL as the actual value + input.setAttribute( 'data-url', item.url ); + input.setAttribute( + 'data-pull-url', + htmlDecode( item.pull_url ) + ); + document.location = getURL(); + + hideDropdown(); + } ); + dropdown.appendChild( div ); + } ); + } + + function showDropdown() { + createDropdownItems( pullConnectionItems ); + dropdown.style.display = 'block'; + } + + function hideDropdown() { + dropdown.style.display = 'none'; + } + + function filterItems( searchTerm ) { + const searchTermLower = searchTerm.toLowerCase(); + const filteredItems = pullConnectionItems.filter( ( item ) => { + // Search on both name and URL + return ( + item.name.toLowerCase().includes( searchTermLower ) || + item.url.toLowerCase().includes( searchTermLower ) + ); + } ); + createDropdownItems( filteredItems ); + } + + inputContainer.addEventListener( 'click', function () { + input.focus(); + showDropdown(); + } ); + + icon.addEventListener( 'click', function ( event ) { + event.stopPropagation(); + input.focus(); + showDropdown(); + } ); + + input.addEventListener( 'input', function () { + filterItems( this.value ); + } ); + + input.addEventListener( 'focus', showDropdown ); + + document.addEventListener( 'click', function ( event ) { + if ( ! container.contains( event.target ) ) { + hideDropdown(); + } + } ); +} ); diff --git a/assets/js/push.js b/assets/js/push.js index 4846c8185..ecec1b291 100755 --- a/assets/js/push.js +++ b/assets/js/push.js @@ -356,7 +356,7 @@ jQuery( window ).on( 'load', () => { distributorPushWrapper.classList.add( 'loaded' ); const data = { - action: 'dt_load_connections', + action: 'dt_load_connections_push', loadConnectionsNonce: dt.loadConnectionsNonce, postId: dt.postId, }; diff --git a/includes/pull-ui.php b/includes/pull-ui.php index 5261b3a34..aca910741 100644 --- a/includes/pull-ui.php +++ b/includes/pull-ui.php @@ -21,6 +21,7 @@ function setup() { function() { add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\admin_enqueue_scripts' ); + add_action( 'wp_ajax_dt_load_connections_pull', __NAMESPACE__ . '\get_connections' ); add_action( 'load-distributor_page_pull', __NAMESPACE__ . '\setup_list_table' ); add_filter( 'set-screen-option', __NAMESPACE__ . '\set_screen_option', 10, 3 ); } @@ -425,51 +426,14 @@ function dashboard() { ?> - +
+
+ + +
+
+
+ pull_post_type = ''; @@ -624,3 +588,63 @@ function output_pull_errors() { get_registered()['networkblog'] ) ) { + $sites = \Distributor\InternalConnections\NetworkSiteConnection::get_available_authorized_sites( 'pull' ); + + foreach ( $sites as $site_array ) { + $internal_connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site_array['site'] ); + + $connections[] = [ + 'id' => $internal_connection->site->blog_id, + 'name' => untrailingslashit( $internal_connection->site->blogname ), + 'url' => untrailingslashit( preg_replace( '#(https?:\/\/|www\.)#i', '', get_site_url( $internal_connection->site->blog_id ) ) ), + 'pull_url' => esc_url( admin_url( 'admin.php?page=pull&connection_type=internal&connection_id=' . $internal_connection->site->blog_id ) ), + 'type' => 'internal', + ]; + } + } + + $external_connections = new \WP_Query( + array( + 'post_type' => 'dt_ext_connection', + 'fields' => 'ids', + 'no_found_rows' => true, + 'posts_per_page' => -1, + ) + ); + + foreach ( $external_connections->posts as $external_connection_id ) { + $external_connection_type = get_post_meta( $external_connection_id, 'dt_external_connection_type', true ); + + if ( empty( \Distributor\Connections::factory()->get_registered()[ $external_connection_type ] ) ) { + continue; + } + + $external_connection_status = get_post_meta( $external_connection_id, 'dt_external_connections', true ); + + if ( empty( $external_connection_status ) || empty( $external_connection_status['can_get'] ) ) { + continue; + } + + $external_connection = \Distributor\ExternalConnection::instantiate( $external_connection_id ); + + if ( ! is_wp_error( $external_connection ) ) { + $connections[] = [ + 'id' => $external_connection->id, + 'name' => $external_connection->name, + 'url' => $external_connection->base_url, + 'pull_url' => esc_url( admin_url( 'admin.php?page=pull&connection_type=external&connection_id=' . $external_connection->id ) ), + 'type' => 'external', + ]; + } + } + + wp_send_json_success( $connections ); +} diff --git a/includes/push-ui.php b/includes/push-ui.php index db071c1a4..7d3576bfe 100644 --- a/includes/push-ui.php +++ b/includes/push-ui.php @@ -23,7 +23,9 @@ function() { add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' ); add_filter( 'amp_dev_mode_element_xpaths', __NAMESPACE__ . '\add_element_xpaths' ); add_filter( 'script_loader_tag', __NAMESPACE__ . '\add_dev_mode_to_assets', 10, 2 ); - add_action( 'wp_ajax_dt_load_connections', __NAMESPACE__ . '\get_connections' ); + // Deprecated action, use wp_ajax_dt_load_connections_push instead + add_action( 'wp_ajax_dt_load_connections', __NAMESPACE__ . '\deprecated_get_connections' ); + add_action( 'wp_ajax_dt_load_connections_push', __NAMESPACE__ . '\get_connections' ); add_action( 'wp_ajax_dt_push', __NAMESPACE__ . '\ajax_push' ); add_action( 'admin_bar_menu', __NAMESPACE__ . '\menu_button', 999 ); add_action( 'wp_footer', __NAMESPACE__ . '\menu_content', 10, 1 ); @@ -100,6 +102,22 @@ function syndicatable() { return true; } +/** + * Get available connections for use in the Push UI. + * + * This is a deprecated function that forwards to get_connections(). + * The action 'wp_ajax_dt_load_connections' is deprecated in favor of 'wp_ajax_dt_load_connections_push'. + * + * @since x.x.x + * @deprecated x.x.x Use Distributor\PushUI\get_connections() instead. + * @see Distributor\PushUI\get_connections() + */ +function deprecated_get_connections() { + _deprecated_function( __FUNCTION__, 'x.x.x', __NAMESPACE__ . '\get_connections' ); + + get_connections(); +} + /** * Get available connections for use in the Push UI. *