Skip to content

Commit 36c1bd6

Browse files
authored
fix: update subtitle block for compatibility with iframed Post Editor
2 parents 94ea682 + f0c478d commit 36c1bd6

File tree

5 files changed

+102
-38
lines changed

5 files changed

+102
-38
lines changed

includes/blocks/subtitle-block/block.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
2+
"$schema": "https://schemas.wp.org/trunk/block.json",
3+
"apiVersion": 3,
24
"name": "newspack-block-theme/article-subtitle",
35
"title": "Article Subtitle",
46
"category": "newspack",
7+
"usesContext": [ "postId", "postType" ],
58
"attributes": {},
69
"supports": {
710
"html": false,

includes/blocks/subtitle-block/class-subtitle-block.php

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ final class Subtitle_Block {
2121
*/
2222
public static function init() {
2323
\add_action( 'init', [ __CLASS__, 'register_block_and_post_meta' ] );
24-
\add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'enqueue_block_editor_assets' ] );
24+
\add_action( 'enqueue_block_assets', [ __CLASS__, 'enqueue_block_assets' ] );
2525
}
2626

2727
/**
2828
* Register the block.
2929
*/
3030
public static function register_block_and_post_meta() {
3131
register_block_type_from_metadata(
32-
__DIR__ . '/block.json',
32+
__DIR__,
3333
[
3434
'render_callback' => [ __CLASS__, 'render_block' ],
3535
]
@@ -52,29 +52,31 @@ public static function register_block_and_post_meta() {
5252
public static function render_block() {
5353
$post_subtitle = get_post_meta( get_the_ID(), self::POST_META_NAME, true );
5454
$wrapper_attributes = get_block_wrapper_attributes();
55-
return sprintf( '<p %1$s>%2$s</p>', $wrapper_attributes, $post_subtitle );
55+
return sprintf( '<p %1$s>%2$s</p>', $wrapper_attributes, esc_html( $post_subtitle ) );
5656
}
5757

5858
/**
59-
* Enqueue block editor ad suppression assets for any post type considered
60-
* "viewable".
59+
* Enqueue block editor subtitle assets for the appropriate editor context.
6160
*/
62-
public static function enqueue_block_editor_assets() {
61+
public static function enqueue_block_assets() {
62+
if ( ! \wp_should_load_block_editor_scripts_and_styles() ) {
63+
return;
64+
}
65+
6366
$script_data = [
6467
'post_meta_name' => self::POST_META_NAME,
6568
];
6669

6770
global $pagenow;
6871
if ( $pagenow === 'site-editor.php' ) {
6972
$handle = 'newspack-block-theme-subtitle-block-site-editor';
70-
\wp_enqueue_script( $handle, \get_theme_file_uri( 'dist/subtitle-block-site-editor.js' ), [], NEWSPACK_BLOCK_THEME_VERSION, true );
73+
$asset = require \get_theme_file_path( 'dist/subtitle-block-site-editor.asset.php' );
74+
\wp_enqueue_script( $handle, \get_theme_file_uri( 'dist/subtitle-block-site-editor.js' ), $asset['dependencies'], $asset['version'], true );
7175
\wp_localize_script( $handle, 'newspack_block_theme_subtitle_block', $script_data );
72-
}
73-
74-
$post_type = \get_current_screen()->post_type;
75-
if ( $post_type === 'post' ) {
76+
} elseif ( \get_current_screen() && \get_current_screen()->post_type === 'post' ) {
7677
$handle = 'newspack-block-theme-subtitle-block-post-editor';
77-
\wp_enqueue_script( $handle, \get_theme_file_uri( 'dist/subtitle-block-post-editor.js' ), [], NEWSPACK_BLOCK_THEME_VERSION, true );
78+
$asset = require \get_theme_file_path( 'dist/subtitle-block-post-editor.asset.php' );
79+
\wp_enqueue_script( $handle, \get_theme_file_uri( 'dist/subtitle-block-post-editor.js' ), $asset['dependencies'], $asset['version'], true );
7880
\wp_localize_script( $handle, 'newspack_block_theme_subtitle_block', $script_data );
7981
}
8082
}

includes/blocks/subtitle-block/post-editor.js

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,36 @@
66
*/
77
import { registerPlugin } from '@wordpress/plugins';
88
import { useSelect, useDispatch } from '@wordpress/data';
9-
import { useEffect } from '@wordpress/element';
9+
import { useEffect, useCallback, useRef } from '@wordpress/element';
1010

1111
const META_FIELD_NAME = newspack_block_theme_subtitle_block.post_meta_name;
1212

1313
const SUBTITLE_ID = 'newspack-post-subtitle-element';
1414
const SUBTITLE_STYLE_ID = 'newspack-post-subtitle-element-style';
1515

16-
const appendSubtitleToTitleDOMElement = ( subtitle, callback ) => {
17-
const titleWrapperEl = document.querySelector( '.edit-post-visual-editor__post-title-wrapper' );
16+
/**
17+
* Get the correct document for the editor canvas.
18+
* In iframe mode, the editor content is inside an iframe with name="editor-canvas".
19+
* In non-iframe mode, falls back to the admin document.
20+
*/
21+
const getEditorCanvas = () => {
22+
const iframe = document.querySelector( 'iframe[name="editor-canvas"]' );
23+
if ( iframe?.contentDocument ) {
24+
return iframe.contentDocument;
25+
}
26+
return document;
27+
};
28+
29+
const appendSubtitleToTitleDOMElement = ( subtitle, editorDoc, callback ) => {
30+
const titleWrapperEl = editorDoc.querySelector( '.edit-post-visual-editor__post-title-wrapper' );
1831

19-
if ( titleWrapperEl && typeof subtitle === 'string' ) {
20-
let subtitleEl = document.getElementById( SUBTITLE_ID );
32+
if ( titleWrapperEl ) {
33+
let subtitleEl = editorDoc.getElementById( SUBTITLE_ID );
2134
const titleParent = titleWrapperEl.parentNode;
2235

23-
if ( ! document.getElementById( SUBTITLE_STYLE_ID ) ) {
24-
const style = document.createElement( 'style' );
36+
if ( ! editorDoc.getElementById( SUBTITLE_STYLE_ID ) ) {
37+
const style = editorDoc.createElement( 'style' );
38+
style.id = SUBTITLE_STYLE_ID;
2539
style.innerHTML = `
2640
#${ SUBTITLE_ID } {
2741
font-style: italic;
@@ -33,19 +47,23 @@ const appendSubtitleToTitleDOMElement = ( subtitle, callback ) => {
3347
padding-right: var(--wp--preset--spacing--30);
3448
}
3549
`;
36-
document.head.appendChild( style );
50+
editorDoc.head.appendChild( style );
3751
}
3852

3953
if ( ! subtitleEl ) {
40-
subtitleEl = document.createElement( 'div' );
54+
subtitleEl = editorDoc.createElement( 'div' );
4155
subtitleEl.setAttribute( 'contenteditable', 'plaintext-only' );
4256
subtitleEl.addEventListener( 'input', () => {
43-
callback( subtitleEl.innerHTML );
57+
callback( subtitleEl.textContent );
4458
} );
4559
subtitleEl.id = SUBTITLE_ID;
4660
titleParent.insertBefore( subtitleEl, titleWrapperEl.nextSibling );
4761
}
48-
subtitleEl.innerHTML = subtitle;
62+
// Only update textContent if it differs, to avoid frustrating fast typists.
63+
const subtitleText = subtitle ?? '';
64+
if ( subtitleEl.textContent !== subtitleText ) {
65+
subtitleEl.textContent = subtitleText;
66+
}
4967
}
5068
};
5169

@@ -56,17 +74,53 @@ const appendSubtitleToTitleDOMElement = ( subtitle, callback ) => {
5674
*/
5775
const NewspackSubtitlePanel = () => {
5876
const subtitle = useSelect( select => select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ META_FIELD_NAME ] );
59-
const dispatch = useDispatch();
60-
const saveSubtitle = updatedSubtitle => {
61-
dispatch( 'core/editor' ).editPost( {
62-
meta: {
63-
[ META_FIELD_NAME ]: updatedSubtitle,
64-
},
65-
} );
66-
};
77+
const { editPost } = useDispatch( 'core/editor' );
78+
const saveSubtitle = useCallback(
79+
updatedSubtitle => {
80+
editPost( {
81+
meta: {
82+
[ META_FIELD_NAME ]: updatedSubtitle,
83+
},
84+
} );
85+
},
86+
[ editPost ]
87+
);
88+
// Keep current subtitle state visible within effect.
89+
const subtitleRef = useRef( subtitle );
90+
useEffect( () => {
91+
subtitleRef.current = subtitle;
92+
}, [ subtitle ] );
93+
// Mount effect: poll for canvas, then create element.
94+
const timeoutRef = useRef();
6795
useEffect( () => {
68-
appendSubtitleToTitleDOMElement( subtitle, saveSubtitle );
69-
}, [] );
96+
let retryCount = 0;
97+
const maxRetries = 50; // 5 seconds at 100ms intervals.
98+
const tryAppend = () => {
99+
const editorDoc = getEditorCanvas();
100+
const titleWrapperEl = editorDoc.querySelector( '.edit-post-visual-editor__post-title-wrapper' );
101+
if ( titleWrapperEl ) {
102+
appendSubtitleToTitleDOMElement( subtitleRef.current, editorDoc, saveSubtitle );
103+
} else if ( retryCount < maxRetries ) {
104+
retryCount++;
105+
timeoutRef.current = setTimeout( tryAppend, 100 );
106+
}
107+
};
108+
tryAppend();
109+
return () => {
110+
clearTimeout( timeoutRef.current );
111+
};
112+
}, [] ); // eslint-disable-line react-hooks/exhaustive-deps
113+
114+
// Sync effect: update element when subtitle changes.
115+
// Extracted from appendSubtitleToTitleDOMElement() above.
116+
useEffect( () => {
117+
const editorDoc = getEditorCanvas();
118+
const subtitleEl = editorDoc.getElementById( SUBTITLE_ID );
119+
const subtitleText = typeof subtitle === 'string' ? subtitle : '';
120+
if ( subtitleEl && subtitleEl.textContent !== subtitleText ) {
121+
subtitleEl.textContent = subtitleText;
122+
}
123+
}, [ subtitle ] );
70124
};
71125

72126
registerPlugin( 'plugin-document-setting-panel-newspack-subtitle', {

includes/blocks/subtitle-block/site-editor.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
* WordPress dependencies
55
*/
66
import { registerBlockType } from '@wordpress/blocks';
7+
import { useBlockProps } from '@wordpress/block-editor';
78
import { __ } from '@wordpress/i18n';
89
import { Icon, listView } from '@wordpress/icons';
910
import { useEntityProp } from '@wordpress/core-data';
1011

1112
import metadata from './block.json';
1213

13-
const EditComponent = ( { context: { postType, postId } } ) => {
14+
const EditComponent = ( { context: { postType, postId } = {} } ) => {
15+
const blockProps = useBlockProps();
1416
const [ postMeta = {} ] = useEntityProp( 'postType', postType, 'meta', postId );
15-
return postMeta[ newspack_block_theme_subtitle_block.post_meta_name ] || __( 'Article subtitle', 'newspack-block-theme' );
17+
const subtitle = postMeta[ newspack_block_theme_subtitle_block.post_meta_name ] || __( 'Article subtitle', 'newspack-block-theme' );
18+
return <p { ...blockProps }>{ subtitle }</p>;
1619
};
1720

1821
const blockData = {
@@ -22,7 +25,6 @@ const blockData = {
2225
foreground: '#36f',
2326
},
2427
edit: EditComponent,
25-
usesContext: [ 'postId', 'postType' ],
2628
...metadata,
2729
};
2830

includes/class-core.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ final class Core {
2020
public static function init() {
2121
\add_action( 'after_setup_theme', [ __CLASS__, 'theme_support' ] );
2222
\add_action( 'wp_enqueue_scripts', [ __CLASS__, 'theme_styles' ] );
23-
\add_action( 'enqueue_block_editor_assets', [ __CLASS__, 'editor_scripts' ] );
23+
\add_action( 'enqueue_block_assets', [ __CLASS__, 'enqueue_block_assets' ] );
2424
\add_filter( 'body_class', [ __CLASS__, 'body_class' ] );
2525
\add_filter( 'block_type_metadata', [ __CLASS__, 'block_variations' ] );
2626
}
@@ -78,7 +78,10 @@ public static function theme_styles() {
7878
/**
7979
* Enqueue editor scripts.
8080
*/
81-
public static function editor_scripts() {
81+
public static function enqueue_block_assets() {
82+
if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
83+
return;
84+
}
8285
// Enqueue editor JavaScript.
8386
wp_enqueue_script( 'editor-script', get_theme_file_uri( '/dist/editor.js' ), array( 'wp-blocks', 'wp-dom' ), wp_get_theme()->get( 'Version' ), true );
8487
}

0 commit comments

Comments
 (0)