66 */
77import { registerPlugin } from '@wordpress/plugins' ;
88import { useSelect , useDispatch } from '@wordpress/data' ;
9- import { useEffect } from '@wordpress/element' ;
9+ import { useEffect , useCallback , useRef } from '@wordpress/element' ;
1010
1111const META_FIELD_NAME = newspack_block_theme_subtitle_block . post_meta_name ;
1212
1313const SUBTITLE_ID = 'newspack-post-subtitle-element' ;
1414const 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 */
5775const 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
72126registerPlugin ( 'plugin-document-setting-panel-newspack-subtitle' , {
0 commit comments