| 1 |
// The clicked block's render-time identity, sent with every save so the server |
| 2 |
// can refuse (409) when its parse-time block count resolves to a different |
| 3 |
// block of the same type — synced patterns, nested navs, and dynamic expansion |
| 4 |
// all desync the two counts past the type guard. |
| 5 |
// |
| 6 |
// Read from the LIVE element the user clicked, never the cached block source: |
| 7 |
// that source is resolved by the same count as the save, so it would echo a |
| 8 |
// misresolve and the check would pass on the wrong block. |
| 9 |
|
| 10 |
// The client reads the block's text from the rendered DOM, where the_content |
| 11 |
// has run wptexturize (straight quotes → curly, -- → dash, ... → ellipsis); |
| 12 |
// the server fingerprints the raw stored markup. Fold those substitutions back |
| 13 |
// to ASCII so an apostrophe alone ("Woody's" vs "Woody’s") can't false a 409. |
| 14 |
// `\s` already covers non-breaking/unicode spaces in JS. Must stay in lockstep |
| 15 |
// with BlockFingerprint::normalize on the PHP side. |
| 16 |
export const normalizeText = (value) => |
| 17 |
String(value ?? '') |
| 18 |
.replace(/[‘’‚‛]/g, "'") |
| 19 |
.replace(/[“”„‟]/g, '"') |
| 20 |
.replace(/[‒–—―]/g, '-') |
| 21 |
.replace(/-{2,}/g, '-') |
| 22 |
.replace(/…/g, '...') |
| 23 |
.replace(/\s+/g, ' ') |
| 24 |
.trim(); |
| 25 |
|
| 26 |
// The live→canvas swap-reveal poll compares the canvas editable's text against |
| 27 |
// the live block's rendered text to know when the editor has caught up. The |
| 28 |
// live text is wptexturize'd (curly) and the editable is raw (straight), so |
| 29 |
// both sides must fold through normalizeText — a whitespace-only compare leaves |
| 30 |
// a smart-quote block forever unequal, stranding the reveal on its frame-cap. |
| 31 |
export const normalizedTextEquals = (a, b) => |
| 32 |
normalizeText(a) === normalizeText(b); |
| 33 |
|
| 34 |
// Omitted (null) when the element has no visible text, so blocks like images |
| 35 |
// fail open instead of 409-ing on an empty match. |
| 36 |
export const textFingerprint = (el) => { |
| 37 |
const text = normalizeText(el?.textContent); |
| 38 |
return text ? { text } : null; |
| 39 |
}; |
| 40 |
|