Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 206x 47x 193x 4x 36x 3x 11x 11x 14x 35x 3x 33x 33x 133x 21x 12x 12x 3x 7x 5x 2x 2x 2x 2x 2x | import { VALID_SECTIONS } from '../constants';
// Gutenberg 23.6 removed the `core/guidelines` store (the Guidelines
// singleton was dissolved into per-scope wp_knowledge rows, #79263), so the
// page's live draft text now exists only in React component state. The DOM
// textareas are the one place that state is observable from this bundle:
// reads snapshot them, and reactivity comes from a document-level `input`
// listener plus a MutationObserver that re-checks the watched values on DOM
// churn — Gutenberg writes some values through React with no input event
// (e.g. Clear guidelines resetting the form), but those updates always come
// with sibling DOM activity (confirm dialog closing, snackbar, button state).
/**
* The textarea holding a section's draft, or null while the section
* isn't rendered.
*
* @param {string} slug - Section slug (see VALID_SECTIONS).
* @return {HTMLTextAreaElement|null} The section's textarea.
*/
export function getSectionTextarea( slug ) {
return document.querySelector( `.guidelines__list-item[data-slug="${ slug }"] form textarea` );
}
/**
* The block guideline modal's textarea, or null when absent.
*
* @param {HTMLElement|null} blockModal - The block guideline modal element.
* @return {HTMLTextAreaElement|null} The modal's textarea.
*/
export function getBlockModalTextarea( blockModal ) {
return blockModal?.querySelector( '.components-textarea-control__input' ) ?? null;
}
/**
* The current draft text of a section, '' when absent.
*
* @param {string} slug - Section slug.
* @return {string} The draft text.
*/
export function readSectionDraft( slug ) {
return getSectionTextarea( slug )?.value || '';
}
/**
* Current draft text for every valid section, keyed by slug.
*
* @return {Object} Slug-keyed draft map.
*/
export function readAllSectionDrafts() {
return Object.fromEntries( VALID_SECTIONS.map( slug => [ slug, readSectionDraft( slug ) ] ) );
}
/**
* Whether every section's draft is empty.
*
* @return {boolean} True when no section has text.
*/
export function areAllSectionDraftsEmpty() {
return VALID_SECTIONS.every( slug => ! readSectionDraft( slug ) );
}
const listeners = new Set();
/**
* Subscribe to draft-change notifications.
*
* @param {Function} callback - Invoked on every potential draft change.
* @return {Function} Unsubscribe.
*/
export function subscribeToDrafts( callback ) {
listeners.add( callback );
return () => listeners.delete( callback );
}
/**
* Notify subscribers that drafts may have changed. Subscribers re-read the
* DOM, so spurious notifications are cheap no-ops.
*/
export function notifyDraftChange() {
listeners.forEach( listener => listener() );
}
// Every watched value, in a fixed order; lets the change checks skip
// notifications for events that didn't touch any draft. Kept as an array and
// compared element-wise — joining would copy up to ~25KB of guideline text
// per check (5000 chars per section, see wp_guideline_max_length()).
function watchedValues() {
return [
...VALID_SECTIONS.map( readSectionDraft ),
getBlockModalTextarea( document.querySelector( '.block-guideline-modal' ) )?.value || '',
];
}
let lastWatched = [];
// Notify only when a watched value actually changed. Both event sources
// funnel through here, so typing in unrelated fields (the block combobox,
// admin search) and non-draft DOM churn cost one read pass and no fan-out.
function checkAndNotify() {
const current = watchedValues();
if (
current.length === lastWatched.length &&
current.every( ( value, i ) => value === lastWatched[ i ] )
) {
return;
}
lastWatched = current;
notifyDraftChange();
}
let tracking = false;
/**
* Start observing draft edits. Typing and our own setTextareaValue() both
* dispatch bubbling `input` events (captured so a stopPropagation in the
* page can't hide them). Value changes Gutenberg makes without input events
* are caught by re-checking the watched values whenever the DOM mutates.
* Either way, subscribers are notified only when a watched value changed.
*/
export function startDraftTracking() {
if ( tracking ) {
return;
}
tracking = true;
document.addEventListener( 'input', checkAndNotify, true );
const observer = new MutationObserver( checkAndNotify );
observer.observe( document.body, { childList: true, subtree: true, attributes: true } );
lastWatched = watchedValues();
}
|