ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-12 13:01:03
Editing: site-switcher.jsx
Cancel
Save Changes
/** * Site Switcher for Command Palette * Adds a dynamic "Switch to Site" command that searches across all user's WordPress.com sites * * @package */ import apiFetch from '@wordpress/api-fetch'; import { useCommandLoader } from '@wordpress/commands'; import { useMemo, useState, useEffect } from '@wordpress/element'; import { sprintf, __ } from '@wordpress/i18n'; import { siteLogo } from '@wordpress/icons'; const userId = window.jetpackSiteSwitcherConfig?.userId || 0; const CACHE_KEY = `jetpack_site_switcher_sites_${ userId }`; const CACHE_DURATION = 3600000; // 1 hour in milliseconds /** * Get cached sites from localStorage */ function getCachedSites() { try { const cached = localStorage.getItem( CACHE_KEY ); if ( ! cached ) { return null; } const { sites, timestamp } = JSON.parse( cached ); // Check if cache is still valid if ( Date.now() - timestamp < CACHE_DURATION ) { return sites; } // Cache expired, remove it localStorage.removeItem( CACHE_KEY ); return null; } catch { // If localStorage is not available or JSON parsing fails, return null return null; } } /** * Save sites to localStorage cache */ function setCachedSites( sites ) { try { localStorage.setItem( CACHE_KEY, JSON.stringify( { sites, timestamp: Date.now(), } ) ); } catch { // Silently fail if localStorage is not available (e.g., private browsing) } } /** * Fetch compact sites list from WordPress.com API */ async function fetchSitesFromWordPressCom() { // Check localStorage cache first const cachedSites = getCachedSites(); if ( cachedSites ) { return cachedSites; } const apiPath = window.jetpackSiteSwitcherConfig?.apiPath; try { const data = await apiFetch( { path: apiPath, method: 'GET', global: true, } ); const sites = data.sites || []; setCachedSites( sites ); return sites; } catch { return []; } } /** * Safely extract hostname from a URL string * * @param {string} urlString - The URL to parse * @return {string} The hostname, or empty string if invalid */ function getHostnameFromURL( urlString ) { if ( ! urlString ) { return ''; } try { return new URL( urlString ).hostname; } catch { return ''; } } /** * Remove trailing slash from a URL string * * @param {string} url - The URL to process * @return {string} URL without trailing slash */ function untrailingslashit( url ) { return url ? url.replace( /\/+$/, '' ) : url; } /** * Escape special regex characters in a string * * @param {string} str - String to escape * @return {string} Escaped string safe for use in RegExp */ function escapeRegex( str ) { return str.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); } /** * Custom hook to load site-switching commands based on search term * * @param {Object} props - Hook properties * @param {string} props.search - Search term to filter sites * @return {Object} Object containing commands array and loading state */ function useSiteSwitcherCommandLoader( { search } ) { const [ sites, setSites ] = useState( [] ); const [ isLoading, setIsLoading ] = useState( true ); // Fetch sites on mount useEffect( () => { fetchSitesFromWordPressCom() .then( fetchedSites => { setSites( fetchedSites ); setIsLoading( false ); } ) .catch( () => { setIsLoading( false ); } ); }, [] ); // Generate and filter commands based on search term const commands = useMemo( () => { if ( ! sites || sites.length === 0 ) { return []; } const searchLower = search ? search.toLowerCase() : ''; // Strip generic keywords from search to allow queries like "site dean" to find sites with "dean" const genericKeywords = [ __( 'site', 'jetpack' ).toLowerCase(), __( 'switch', 'jetpack' ).toLowerCase(), __( 'switch site', 'jetpack' ).toLowerCase(), ]; let cleanedSearch = searchLower; genericKeywords.forEach( keyword => { cleanedSearch = cleanedSearch.replace( new RegExp( `\\b${ escapeRegex( keyword ) }\\b`, 'g' ), ' ' ); } ); cleanedSearch = cleanedSearch.trim().replace( /\s+/g, ' ' ); // Check if the search is a prefix of any generic keyword (e.g., "swit" matches "switch") // If so, treat it as a generic search and show all sites const isGenericKeywordPrefix = cleanedSearch && genericKeywords.some( keyword => keyword.startsWith( cleanedSearch ) ); // If search is empty after stripping generic keywords, or is a prefix of a generic keyword, show all sites const filteredSites = ! cleanedSearch || isGenericKeywordPrefix ? sites : sites.filter( site => { const domain = getHostnameFromURL( site.URL ); return ( ( site.name && site.name.toLowerCase().includes( cleanedSearch ) ) || domain.toLowerCase().includes( cleanedSearch ) ); } ); // Filter out sites with invalid URLs (can't navigate to them anyway) const validSites = filteredSites.filter( site => { return site.URL && getHostnameFromURL( site.URL ) !== ''; } ); // Exclude the current site from the list const currentURL = untrailingslashit( window.location.href.toLowerCase() ); const otherSites = validSites.filter( site => { // Normalize site URL for comparison const siteURL = untrailingslashit( site.URL.toLowerCase() ); // Check if current URL starts with site URL (handles multisite subdirectory installs) // e.g., current: example.com/site1/wp-admin matches site: example.com/site1 return ! currentURL.startsWith( siteURL ); } ); return otherSites.map( site => { // Extract domain from URL for display - don't want to display the protocol. const domain = getHostnameFromURL( site.URL ); const iconElement = site.icon?.img ? <img src={ site.icon.img } alt="" /> : siteLogo; // Use site name if available, otherwise just show domain const label = site.name ? sprintf( /* translators: %1$s: site name, %2$s: site domain */ __( 'Switch to %1$s (%2$s)', 'jetpack' ), site.name, domain ) : sprintf( /* translators: %s: site domain */ __( 'Switch to %s', 'jetpack' ), domain ); return { name: `jetpack/switch-to-site-${ domain }`, label, icon: iconElement, callback: ( { close } ) => { try { window.location.href = new URL( '/wp-admin', site.URL ).href; } catch { // If URL is malformed, don't navigate } close(); }, keywords: [ site.name, domain, __( 'site', 'jetpack' ), __( 'switch site', 'jetpack' ), ].filter( Boolean ), }; } ); }, [ sites, search ] ); return { commands, isLoading, }; } /** * Component that registers the site switcher command loader */ function JetpackSiteSwitcher() { useCommandLoader( { name: 'jetpack/site-switcher', hook: useSiteSwitcherCommandLoader, } ); return null; } // Render the site switcher into the wp-admin command palette. if ( typeof window !== 'undefined' && window.wp && window.wp.element && window.wp.commands ) { const { createRoot, createElement } = window.wp.element; // Create a container for our site switcher const container = document.createElement( 'div' ); container.id = 'jetpack-site-switcher'; container.style.display = 'none'; // Hidden, as we only need the hooks to run // Wait for DOM to be ready if ( document.readyState === 'loading' ) { document.addEventListener( 'DOMContentLoaded', () => { document.body.appendChild( container ); createRoot( container ).render( createElement( JetpackSiteSwitcher ) ); } ); } else { document.body.appendChild( container ); createRoot( container ).render( createElement( JetpackSiteSwitcher ) ); } }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete