$maps Remote maps keyed by id, oldest first. * @return list */ public static function map_choices( array $maps ): array { return MapChoices::options( $maps, static fn ( int $ts ): string => (string) wp_date( (string) get_option( 'date_format' ), $ts ) ); } /** * Returns the shared API client. */ public function api(): Client { return $this->api; } /** * Returns the shared map config repository. */ public function maps(): MapConfigRepository { return $this->maps; } /** * Returns the shared coordinate storage. */ public function coordinates(): Coordinates { return $this->coordinates; } /** * Returns the shared embed renderer. * * The one thing a theme needs, and the reason it is public: without it the * only way to put a map in a template is `do_shortcode()`. Prefer the * `mapsemble_map()` template function, which guards against being called * before the plugin has booted. */ public function embed(): Embed { return $this->embed; } /** * Registers the Gutenberg block and its editor script. * * @param Embed $embed Shared embed renderer. */ private function register_block( Embed $embed ): void { // Registered on `init` because block.json names this handle as its // editorScript, so it has to exist by the time the block does. The // map list it needs is *not* built here: this runs on every request, // front end included, and listing the maps is a query. wp_register_script( 'mapsemble-block-editor', plugins_url( 'assets/js/block-editor.js', MAPSEMBLE_PLUGIN_FILE ), array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-hooks', 'wp-api-fetch' ), MAPSEMBLE_VERSION, true ); // Without this binding wp.i18n has no table for the domain and the // inspector stays English on a translated site. wp_set_script_translations( 'mapsemble-block-editor', 'mapsemble', MAPSEMBLE_PLUGIN_DIR . '/languages' ); add_action( 'enqueue_block_editor_assets', array( $this, 'localize_block_editor' ) ); register_block_type( MAPSEMBLE_PLUGIN_DIR . '/blocks/map', array( 'render_callback' => static function ( array $attributes ) use ( $embed ): string { $args = array( 'height' => (string) ( $attributes['height'] ?? '' ), 'scroll_capture' => (string) ( $attributes['scrollCapture'] ?? '' ), 'scroll_offset_top' => (string) ( $attributes['scrollOffsetTop'] ?? '' ), 'scroll_offset_bottom' => (string) ( $attributes['scrollOffsetBottom'] ?? '' ), // The default (true) says nothing; only the // opt-out reaches the widget. 'fill_viewport' => ( $attributes['fillViewport'] ?? true ) ? '' : 'false', 'filters' => (string) ( $attributes['filters'] ?? '' ), ); // New selections store the Mapsemble map id; configId is // the pre-0.3 shape and keeps rendering unchanged. Such // a block stored no remoteMapId, so the attribute default // ('sample') fills it in — and must not outrank the // binding. Choosing the sample on one of those blocks // zeroes configId, so the two never genuinely coexist. // A block saved before 0.4.0 with nothing chosen at all // (no configId either) gets the same default and now // renders the sample where it used to render nothing. $remote = (string) ( $attributes['remoteMapId'] ?? '' ); $config_id = (int) ( $attributes['configId'] ?? 0 ); if ( $config_id > 0 && 'sample' === $remote ) { $remote = ''; } $inner = '' !== $remote ? $embed->render_by_map_id( $remote, $args ) : $embed->render( $config_id, $args ); // The wrapper carries the block's classes — alignwide / // alignfull above all, which is what the theme widens — // so the alignment chosen in the editor reaches the page. // A visitor's HTML comment stays bare: an empty wrapper // would still take up layout space. if ( '' === $inner || str_starts_with( ltrim( $inner ), '