@pixelersagency/okno-bridge 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,592 @@
1
+ /*!
2
+ * okno-bridge v5 — standalone (auto-init).
3
+ * Généré par bridge/build.js depuis src/index.js — ne pas éditer à la main.
4
+ *
5
+ * Chargement recommandé (le bridge ne sert qu'à l'éditeur) :
6
+ * <script>
7
+ * if (window.self !== window.top) {
8
+ * var s = document.createElement('script');
9
+ * s.src = '/okno-bridge.js';
10
+ * s.setAttribute('data-wp-origin', 'https://admin.monsite.fr');
11
+ * document.head.appendChild(s);
12
+ * }
13
+ * </script>
14
+ */
15
+ ( function () {
16
+ 'use strict';
17
+
18
+ /**
19
+ * @pixelersagency/okno-bridge v5 — pont postMessage entre un site et l'éditeur Okno.
20
+ *
21
+ * Fonctionne avec n'importe quel front : HTML statique, Astro, Next.js, Remix,
22
+ * React (SPA ou rendu serveur), Vue/Nuxt, Svelte/SvelteKit.
23
+ *
24
+ * Usage (ESM) :
25
+ * if ( window.self !== window.top ) {
26
+ * const { initOknoBridge } = await import( '@pixelersagency/okno-bridge' );
27
+ * initOknoBridge( { wpOrigin: 'https://admin.monsite.fr' } );
28
+ * }
29
+ *
30
+ * React : import { OknoBridge } from '@pixelersagency/okno-bridge/react' — voir src/react.js.
31
+ *
32
+ * Le bridge ne fait RIEN hors iframe. Il n'appelle jamais l'API WordPress :
33
+ * les valeurs arrivent de wp-admin par postMessage, avec vérification stricte
34
+ * de l'origine des deux côtés.
35
+ *
36
+ * Annotations :
37
+ * data-wp-post="12" conteneur : ID du contenu WordPress
38
+ * data-wp-field="chemin" élément éditable (chemins pointés : cartes.0.titre)
39
+ * data-okno-apply="html" la valeur est du HTML (balisage interne conservé)
40
+ * data-okno-section="Libellé" section logique (arbre de structure)
41
+ * data-okno-layout="sections.2" section portée par une ligne de flexible content
42
+ * data-okno-refresh="save" pas d'aperçu instantané, rechargement après enregistrement
43
+ * data-okno-style="prop" champ couleur : propriété CSS ciblée
44
+ * data-okno-managed="menu" contenu géré ailleurs : l'éditeur explique où le modifier
45
+ * data-okno-managed-label="…" libellé de la région gérée
46
+ * data-okno-edit-url="…" écran wp-admin où modifier ce contenu
47
+ *
48
+ * Sites React / SPA — ce qui change par rapport à une page statique :
49
+ * - aucun écouteur n'est posé sur les éléments : tout passe par délégation
50
+ * sur document, donc un nœud recréé par un re-render reste cliquable ;
51
+ * - les valeurs d'aperçu sont gardées en mémoire et réappliquées après chaque
52
+ * mutation du DOM : un re-render qui remet la valeur serveur ne l'efface pas ;
53
+ * - le texte est écrit dans le nœud texte existant (nodeValue) plutôt qu'en
54
+ * remplaçant les enfants, pour ne pas casser la réconciliation de React ;
55
+ * - la navigation client (pushState, popstate) déclenche un nouveau handshake.
56
+ */
57
+
58
+ const NS = 'okno';
59
+ const VERSION = 5;
60
+ const HOVER_CLASS = 'okno-hover';
61
+ const MANAGED_CLASS = 'okno-managed-hover';
62
+ const CLOAK_CLASS = 'okno-cloak';
63
+
64
+ /** Attente de stabilisation du DOM avant re-collecte (rendus successifs de React). */
65
+ const SETTLE_MS = 120;
66
+
67
+ /** Noms lisibles des fournisseurs de contenu courants. */
68
+ const PROVIDERS = {
69
+ wordpress: 'WordPress',
70
+ menu: 'Menus WordPress',
71
+ acf: 'ACF',
72
+ woocommerce: 'WooCommerce',
73
+ jetengine: 'JetEngine',
74
+ };
75
+
76
+ /** Balises retirées de tout HTML injecté dans l'aperçu. */
77
+ const BANNED_TAGS = [ 'script', 'style', 'iframe', 'object', 'embed', 'link', 'meta', 'base', 'form' ];
78
+
79
+ function initOknoBridge( options ) {
80
+ if ( typeof window === 'undefined' || window.self === window.top ) {
81
+ return; // Jamais actif en navigation publique ni côté serveur.
82
+ }
83
+
84
+ // Idempotent : React StrictMode monte les effets deux fois en développement.
85
+ if ( window.__oknoBridge ) {
86
+ return;
87
+ }
88
+
89
+ const wpOrigin = options && options.wpOrigin;
90
+ if ( ! wpOrigin ) {
91
+ console.warn( '[okno] wpOrigin manquant : bridge inactif.' );
92
+ return;
93
+ }
94
+
95
+ if ( document.referrer ) {
96
+ try {
97
+ if ( new URL( document.referrer ).origin !== wpOrigin ) {
98
+ return;
99
+ }
100
+ } catch ( e ) {
101
+ /* referrer illisible : les contrôles d'origine suffisent. */
102
+ }
103
+ }
104
+
105
+ window.__oknoBridge = { version: VERSION };
106
+
107
+ /* ------------------------------------------------------------------ *
108
+ * État
109
+ * ------------------------------------------------------------------ */
110
+
111
+ /** Valeurs d'aperçu en cours : "postId:path" -> { value, fieldType }. */
112
+ const overrides = new Map();
113
+
114
+ /** Dernière collecte : champs et sections présents dans le DOM. */
115
+ let fields = new Map();
116
+ let sections = [];
117
+ let signature = '';
118
+ let currentUrl = window.location.href;
119
+ let applying = false;
120
+ let settleTimer = null;
121
+
122
+ /* ------------------------------------------------------------------ *
123
+ * Cloak : page masquée jusqu'aux valeurs de l'éditeur (chargement initial)
124
+ * ------------------------------------------------------------------ */
125
+
126
+ const style = document.createElement( 'style' );
127
+ style.textContent =
128
+ '.' + CLOAK_CLASS + '{opacity:0 !important}' +
129
+ '.' + HOVER_CLASS + '{outline:2px solid #5b5bd6 !important;outline-offset:2px;cursor:pointer !important}' +
130
+ '.' + MANAGED_CLASS + '{outline:2px dashed #b7791f !important;outline-offset:2px}';
131
+ document.head.appendChild( style );
132
+
133
+ let cloakTimer = null;
134
+ const cloakDelay = options && 'cloakTimeout' in options ? Number( options.cloakTimeout ) : 4000;
135
+
136
+ function uncloak() {
137
+ clearTimeout( cloakTimer );
138
+ document.documentElement.classList.remove( CLOAK_CLASS );
139
+ }
140
+
141
+ if ( cloakDelay > 0 ) {
142
+ document.documentElement.classList.add( CLOAK_CLASS );
143
+ cloakTimer = setTimeout( () => {
144
+ uncloak();
145
+ send( { type: 'cloakTimeout' } );
146
+ console.warn( '[okno] wp-admin n’a pas répondu : aperçu affiché sans hydratation.' );
147
+ }, cloakDelay );
148
+ }
149
+
150
+ /* ------------------------------------------------------------------ *
151
+ * Protocole
152
+ * ------------------------------------------------------------------ */
153
+
154
+ function send( message ) {
155
+ message.ns = NS;
156
+ window.parent.postMessage( message, wpOrigin );
157
+ }
158
+
159
+ /* ------------------------------------------------------------------ *
160
+ * Collecte
161
+ * ------------------------------------------------------------------ */
162
+
163
+ function postIdOf( el ) {
164
+ const host = el.closest( '[data-wp-post]' );
165
+ return host ? host.getAttribute( 'data-wp-post' ) : '';
166
+ }
167
+
168
+ function sectionLabel( el, index ) {
169
+ const explicit = el.getAttribute( 'data-okno-section' ) || el.getAttribute( 'aria-label' );
170
+ if ( explicit ) {
171
+ return explicit;
172
+ }
173
+ const tag = el.tagName.toLowerCase();
174
+ if ( 'header' === tag ) {
175
+ return 'En-tête';
176
+ }
177
+ if ( 'footer' === tag ) {
178
+ return 'Pied de page';
179
+ }
180
+ const heading = el.querySelector( 'h1, h2, h3' );
181
+ if ( heading && heading.textContent.trim() ) {
182
+ const text = heading.textContent.trim().replace( /\s+/g, ' ' );
183
+ return text.length > 48 ? text.slice( 0, 45 ) + '…' : text;
184
+ }
185
+ return 'Section ' + ( index + 1 );
186
+ }
187
+
188
+ function sectionLayout( el ) {
189
+ const raw = el.getAttribute( 'data-okno-layout' );
190
+ if ( ! raw ) {
191
+ return null;
192
+ }
193
+ const parts = raw.split( '.' );
194
+ const index = Number( parts[ parts.length - 1 ] );
195
+ if ( parts.length < 2 || isNaN( index ) ) {
196
+ return null;
197
+ }
198
+ return { post: postIdOf( el ) || null, path: parts.slice( 0, -1 ).join( '.' ), index };
199
+ }
200
+
201
+ function collect() {
202
+ const nextFields = new Map();
203
+ const nextSections = [];
204
+
205
+ document.querySelectorAll( '[data-okno-sid]' ).forEach( ( el ) => el.removeAttribute( 'data-okno-sid' ) );
206
+
207
+ // 1. Sections annotées explicitement : elles priment, même à l'intérieur
208
+ // d'une balise <section> générique (sauf imbriquées dans une autre annotée).
209
+ const EXPLICIT = '[data-okno-section], [data-okno-layout]';
210
+ const explicit = [ ...document.querySelectorAll( EXPLICIT ) ].filter(
211
+ ( el ) => ! ( el.parentElement && el.parentElement.closest( EXPLICIT ) )
212
+ );
213
+
214
+ // 2. Détection automatique (section, header, footer) là où rien n'est annoté :
215
+ // ni à l'intérieur d'une section retenue, ni autour d'une section annotée.
216
+ const auto = [];
217
+ document.querySelectorAll( 'section, header, footer' ).forEach( ( el ) => {
218
+ if ( el.matches( EXPLICIT ) || el.closest( EXPLICIT ) || el.querySelector( EXPLICIT ) ) {
219
+ return;
220
+ }
221
+ if ( auto.some( ( kept ) => kept.contains( el ) ) ) {
222
+ return;
223
+ }
224
+ auto.push( el );
225
+ } );
226
+
227
+ // Ordre du document, identifiants positionnels.
228
+ explicit
229
+ .concat( auto )
230
+ .sort( ( a, b ) => ( a.compareDocumentPosition( b ) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1 ) )
231
+ .forEach( ( el ) => {
232
+ const id = 's' + nextSections.length;
233
+ el.setAttribute( 'data-okno-sid', id );
234
+ nextSections.push( {
235
+ id,
236
+ label: sectionLabel( el, nextSections.length ),
237
+ layout: sectionLayout( el ),
238
+ el,
239
+ fields: [],
240
+ } );
241
+ } );
242
+
243
+ document.querySelectorAll( '[data-wp-field]' ).forEach( ( el ) => {
244
+ const postId = postIdOf( el );
245
+ const path = el.getAttribute( 'data-wp-field' );
246
+ if ( ! postId || ! path ) {
247
+ return;
248
+ }
249
+ const key = postId + ':' + path;
250
+ let entry = nextFields.get( key );
251
+ if ( ! entry ) {
252
+ entry = {
253
+ postId,
254
+ path,
255
+ refresh: 'save' === el.getAttribute( 'data-okno-refresh' ),
256
+ els: [],
257
+ };
258
+ nextFields.set( key, entry );
259
+
260
+ const host = el.closest( '[data-okno-sid]' );
261
+ const section = host && nextSections.find( ( s ) => s.id === host.getAttribute( 'data-okno-sid' ) );
262
+ if ( section ) {
263
+ section.fields.push( { post: postId, path, refresh: entry.refresh } );
264
+ }
265
+ }
266
+ entry.els.push( el );
267
+ } );
268
+
269
+ fields = nextFields;
270
+ sections = nextSections;
271
+ }
272
+
273
+ function computeSignature() {
274
+ return JSON.stringify( [
275
+ window.location.pathname,
276
+ [ ...fields.keys() ].sort(),
277
+ sections.map( ( s ) => [ s.label, s.layout && s.layout.path, s.layout && s.layout.index ] ),
278
+ ] );
279
+ }
280
+
281
+ /* ------------------------------------------------------------------ *
282
+ * Application des valeurs
283
+ * ------------------------------------------------------------------ */
284
+
285
+ function sanitizeHtml( html ) {
286
+ const doc = document.implementation.createHTMLDocument( '' );
287
+ doc.body.innerHTML = String( null == html ? '' : html );
288
+ doc.body.querySelectorAll( BANNED_TAGS.join( ',' ) ).forEach( ( el ) => el.remove() );
289
+ doc.body.querySelectorAll( '*' ).forEach( ( el ) => {
290
+ [ ...el.attributes ].forEach( ( attr ) => {
291
+ const name = attr.name.toLowerCase();
292
+ const value = attr.value.replace( /\s+/g, '' ).toLowerCase();
293
+ if ( 0 === name.indexOf( 'on' ) ) {
294
+ el.removeAttribute( attr.name );
295
+ } else if ( ( 'href' === name || 'src' === name || 'xlink:href' === name ) && 0 === value.indexOf( 'javascript:' ) ) {
296
+ el.removeAttribute( attr.name );
297
+ }
298
+ } );
299
+ } );
300
+ return doc.body.innerHTML;
301
+ }
302
+
303
+ /**
304
+ * Écrit un texte sans détruire les nœuds que React référence : si l'élément
305
+ * contient un seul nœud texte, on change sa valeur en place.
306
+ */
307
+ function setText( el, value ) {
308
+ const text = null == value ? '' : String( value );
309
+ const only = el.childNodes.length === 1 ? el.firstChild : null;
310
+ if ( only && only.nodeType === Node.TEXT_NODE ) {
311
+ if ( only.nodeValue !== text ) {
312
+ only.nodeValue = text;
313
+ }
314
+ } else if ( el.textContent !== text ) {
315
+ el.textContent = text;
316
+ }
317
+ }
318
+
319
+ function applyToElement( el, value, fieldType ) {
320
+ if ( 'image' === fieldType ) {
321
+ const img = 'IMG' === el.tagName ? el : el.querySelector( 'img' );
322
+ if ( img ) {
323
+ if ( img.getAttribute( 'src' ) !== ( value || '' ) ) {
324
+ img.setAttribute( 'src', value || '' );
325
+ img.removeAttribute( 'srcset' );
326
+ }
327
+ } else {
328
+ el.style.backgroundImage = value ? 'url("' + value + '")' : '';
329
+ }
330
+ } else if ( 'color_picker' === fieldType ) {
331
+ el.style.setProperty( el.getAttribute( 'data-okno-style' ) || 'color', value || '' );
332
+ } else if ( 'link' === fieldType ) {
333
+ const link = 'A' === el.tagName ? el : el.querySelector( 'a' );
334
+ const data = value && 'object' === typeof value ? value : { url: value };
335
+ if ( link ) {
336
+ link.setAttribute( 'href', data.url || '' );
337
+ if ( data.target ) {
338
+ link.setAttribute( 'target', data.target );
339
+ } else {
340
+ link.removeAttribute( 'target' );
341
+ }
342
+ if ( data.title ) {
343
+ setText( link, data.title );
344
+ }
345
+ }
346
+ } else if ( 'wysiwyg' === fieldType || 'html' === el.getAttribute( 'data-okno-apply' ) ) {
347
+ const html = sanitizeHtml( value );
348
+ if ( el.innerHTML !== html ) {
349
+ el.innerHTML = html;
350
+ }
351
+ } else {
352
+ setText( el, value );
353
+ }
354
+ }
355
+
356
+ function applyOverrides() {
357
+ applying = true;
358
+ try {
359
+ overrides.forEach( ( item, key ) => {
360
+ const entry = fields.get( key );
361
+ if ( entry && ! entry.refresh ) {
362
+ entry.els.forEach( ( el ) => applyToElement( el, item.value, item.fieldType ) );
363
+ }
364
+ } );
365
+ } finally {
366
+ // Les mutations que l'on vient de provoquer arrivent en microtâche.
367
+ Promise.resolve().then( () => {
368
+ applying = false;
369
+ } );
370
+ }
371
+ }
372
+
373
+ /* ------------------------------------------------------------------ *
374
+ * Cycle de vie : handshake, mutations, navigation client
375
+ * ------------------------------------------------------------------ */
376
+
377
+ function announce() {
378
+ const posts = {};
379
+ fields.forEach( ( entry ) => {
380
+ if ( ! posts[ entry.postId ] ) {
381
+ posts[ entry.postId ] = [];
382
+ }
383
+ posts[ entry.postId ].push( { path: entry.path, refresh: entry.refresh, ratio: slotRatio( entry ) } );
384
+ } );
385
+ signature = computeSignature();
386
+ send( {
387
+ type: 'ready',
388
+ version: VERSION,
389
+ url: window.location.href,
390
+ posts,
391
+ sections: sections.map( ( s ) => ( { id: s.id, label: s.label, layout: s.layout, fields: s.fields } ) ),
392
+ } );
393
+ }
394
+
395
+ function slotRatio( entry ) {
396
+ const el = entry.els[ 0 ];
397
+ if ( ! el ) {
398
+ return 0;
399
+ }
400
+ const target = 'IMG' === el.tagName ? el : el.querySelector( 'img' ) || el;
401
+ const box = target.getBoundingClientRect();
402
+ if ( box.width < 8 || box.height < 8 ) {
403
+ return 0;
404
+ }
405
+ return Math.round( ( box.width / box.height ) * 1000 ) / 1000;
406
+ }
407
+
408
+ function settle() {
409
+ clearTimeout( settleTimer );
410
+ settleTimer = setTimeout( () => {
411
+ if ( window.location.href !== currentUrl ) {
412
+ // Changement de page côté client : les valeurs d'aperçu de la page
413
+ // précédente ne concernent plus rien.
414
+ currentUrl = window.location.href;
415
+ overrides.clear();
416
+ }
417
+ collect();
418
+ applyOverrides();
419
+ if ( computeSignature() !== signature ) {
420
+ announce();
421
+ }
422
+ }, SETTLE_MS );
423
+ }
424
+
425
+ const observer = new MutationObserver( () => {
426
+ if ( ! applying ) {
427
+ settle();
428
+ }
429
+ } );
430
+
431
+ [ 'pushState', 'replaceState' ].forEach( ( method ) => {
432
+ const original = window.history[ method ];
433
+ window.history[ method ] = function () {
434
+ const result = original.apply( this, arguments );
435
+ settle();
436
+ return result;
437
+ };
438
+ } );
439
+ window.addEventListener( 'popstate', settle );
440
+
441
+ /* ------------------------------------------------------------------ *
442
+ * Interactions, par délégation (survivent aux re-renders)
443
+ * ------------------------------------------------------------------ */
444
+
445
+ function fieldTarget( node ) {
446
+ const el = node && node.closest ? node.closest( '[data-wp-field]' ) : null;
447
+ return el && postIdOf( el ) ? el : null;
448
+ }
449
+
450
+ document.addEventListener( 'mouseover', ( event ) => {
451
+ const field = fieldTarget( event.target );
452
+ const managed = ! field && event.target.closest ? event.target.closest( '[data-okno-managed]' ) : null;
453
+ document.querySelectorAll( '.' + HOVER_CLASS + ', .' + MANAGED_CLASS ).forEach( ( el ) => {
454
+ if ( el !== field && el !== managed ) {
455
+ el.classList.remove( HOVER_CLASS, MANAGED_CLASS );
456
+ }
457
+ } );
458
+ if ( field ) {
459
+ field.classList.add( HOVER_CLASS );
460
+ } else if ( managed ) {
461
+ managed.classList.add( MANAGED_CLASS );
462
+ }
463
+ } );
464
+
465
+ document.addEventListener(
466
+ 'click',
467
+ ( event ) => {
468
+ const target = event.target;
469
+ if ( ! target || ! target.closest ) {
470
+ return;
471
+ }
472
+
473
+ // 1. Un champ éditable, même à l'intérieur d'un lien : c'est lui qu'on vise.
474
+ const field = fieldTarget( target );
475
+ if ( field ) {
476
+ event.preventDefault();
477
+ event.stopPropagation();
478
+ send( { type: 'fieldFocus', postId: postIdOf( field ), path: field.getAttribute( 'data-wp-field' ) } );
479
+ return;
480
+ }
481
+
482
+ // 2. Un lien : l'éditeur décide (bascule de page, lien externe…). Capté en
483
+ // phase de capture, donc avant le routeur du framework (Next, React Router).
484
+ const link = target.closest( 'a' );
485
+ if ( link ) {
486
+ event.preventDefault();
487
+ event.stopPropagation();
488
+ const href = link.getAttribute( 'href' ) || '';
489
+ if ( ! href || 0 === href.indexOf( '#' ) ) {
490
+ return;
491
+ }
492
+ let url;
493
+ try {
494
+ url = new URL( href, window.location.href );
495
+ } catch ( e ) {
496
+ return;
497
+ }
498
+ send( { type: 'navigate', href: url.href, path: url.pathname, external: url.origin !== window.location.origin } );
499
+ return;
500
+ }
501
+
502
+ // 3. Un contenu géré ailleurs.
503
+ const managed = target.closest( '[data-okno-managed]' );
504
+ if ( managed ) {
505
+ event.preventDefault();
506
+ event.stopPropagation();
507
+ const provider = managed.getAttribute( 'data-okno-managed' ) || '';
508
+ send( {
509
+ type: 'managedFocus',
510
+ provider,
511
+ label: managed.getAttribute( 'data-okno-managed-label' ) || 'Géré dans ' + ( PROVIDERS[ provider ] || provider || 'WordPress' ),
512
+ editUrl: managed.getAttribute( 'data-okno-edit-url' ) || '',
513
+ } );
514
+ return;
515
+ }
516
+
517
+ // 4. Une section.
518
+ const section = target.closest( '[data-okno-sid]' );
519
+ if ( section ) {
520
+ event.preventDefault();
521
+ send( { type: 'sectionFocus', id: section.getAttribute( 'data-okno-sid' ) } );
522
+ }
523
+ },
524
+ true
525
+ );
526
+
527
+ // Les formulaires du site ne doivent rien envoyer depuis l'éditeur.
528
+ document.addEventListener( 'submit', ( event ) => event.preventDefault(), true );
529
+
530
+ function flash( el ) {
531
+ el.scrollIntoView( { behavior: 'smooth', block: 'center' } );
532
+ el.classList.add( HOVER_CLASS );
533
+ setTimeout( () => el.classList.remove( HOVER_CLASS ), 1500 );
534
+ }
535
+
536
+ window.addEventListener( 'message', ( event ) => {
537
+ if ( event.origin !== wpOrigin || ! event.data || event.data.ns !== NS ) {
538
+ return;
539
+ }
540
+ const data = event.data;
541
+
542
+ if ( 'init' === data.type ) {
543
+ overrides.clear();
544
+ Object.keys( data.values || {} ).forEach( ( postId ) => {
545
+ const values = data.values[ postId ];
546
+ Object.keys( values ).forEach( ( path ) => {
547
+ overrides.set( postId + ':' + path, values[ path ] );
548
+ } );
549
+ } );
550
+ applyOverrides();
551
+ uncloak();
552
+ } else if ( 'fieldUpdate' === data.type ) {
553
+ overrides.set( data.postId + ':' + data.path, { value: data.value, fieldType: data.fieldType } );
554
+ applyOverrides();
555
+ } else if ( 'focusField' === data.type ) {
556
+ const entry = fields.get( data.postId + ':' + data.path );
557
+ if ( entry && entry.els[ 0 ] ) {
558
+ flash( entry.els[ 0 ] );
559
+ }
560
+ } else if ( 'focusSection' === data.type ) {
561
+ const section = sections.find( ( s ) => s.id === data.id );
562
+ if ( section ) {
563
+ flash( section.el );
564
+ }
565
+ }
566
+ } );
567
+
568
+ function start() {
569
+ collect();
570
+ announce();
571
+ observer.observe( document.body, { childList: true, subtree: true, characterData: true } );
572
+ }
573
+
574
+ if ( 'loading' === document.readyState ) {
575
+ document.addEventListener( 'DOMContentLoaded', start );
576
+ } else {
577
+ start();
578
+ }
579
+ }
580
+
581
+ window.initOknoBridge = initOknoBridge;
582
+
583
+ // Auto-init depuis l'attribut du tag <script>.
584
+ var script = document.currentScript;
585
+ var origin = script && script.getAttribute( 'data-wp-origin' );
586
+ if ( origin ) {
587
+ initOknoBridge( {
588
+ wpOrigin: origin,
589
+ cloakTimeout: Number( script.getAttribute( 'data-okno-cloak-timeout' ) ) || undefined,
590
+ } );
591
+ }
592
+ } )();
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@pixelersagency/okno-bridge",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Pont postMessage de l'éditeur visuel Okno : rend n'importe quel site (React, Next.js, Remix, Vite, Nuxt, SvelteKit, Astro, HTML) éditable depuis WordPress. Zéro dépendance, inactif hors de l'éditeur.",
5
+ "keywords": [
6
+ "wordpress",
7
+ "headless",
8
+ "acf",
9
+ "visual-editing",
10
+ "react",
11
+ "nextjs",
12
+ "astro",
13
+ "remix",
14
+ "vite",
15
+ "nuxt",
16
+ "sveltekit"
17
+ ],
18
+ "license": "GPL-2.0-or-later",
19
+ "author": "Pixelers (https://pixelers.fr)",
20
+ "type": "module",
21
+ "main": "./dist/okno-bridge.js",
22
+ "module": "./src/index.js",
23
+ "exports": {
24
+ ".": {
25
+ "import": "./src/index.js",
26
+ "default": "./dist/okno-bridge.js"
27
+ },
28
+ "./react": {
29
+ "import": "./src/react.js"
30
+ },
31
+ "./standalone": "./dist/okno-bridge.js"
32
+ },
33
+ "scripts": {
34
+ "build": "node build.js"
35
+ },
36
+ "files": [
37
+ "src",
38
+ "dist",
39
+ "README.md"
40
+ ],
41
+ "sideEffects": false,
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/pixelersagency/okno.git",
45
+ "directory": "bridge"
46
+ },
47
+ "homepage": "https://github.com/pixelersagency/okno#readme",
48
+ "peerDependencies": {
49
+ "react": ">=17"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "react": {
53
+ "optional": true
54
+ }
55
+ }
56
+ }