@pixelersagency/okno-bridge 1.0.0-beta.1 → 1.0.0-beta.11
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.
- package/bin/okno.js +55 -0
- package/dist/okno-bridge.js +20 -23
- package/package.json +14 -3
- package/src/content.js +170 -0
- package/src/index.js +20 -23
package/bin/okno.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* okno — outils du front pour un site Okno.
|
|
4
|
+
*
|
|
5
|
+
* npx okno types [--url https://cms.example.com] [--out src/okno.d.ts]
|
|
6
|
+
* npx okno pull [--url https://cms.example.com] [--out .okno/content.json]
|
|
7
|
+
*
|
|
8
|
+
* L'adresse du CMS et le jeton se lisent aussi dans OKNO_URL et OKNO_TOKEN.
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { exportUrl, loadOknoContent } from '../src/content.js';
|
|
13
|
+
|
|
14
|
+
const [ command, ...rest ] = process.argv.slice( 2 );
|
|
15
|
+
const args = {};
|
|
16
|
+
for ( let i = 0; i < rest.length; i++ ) {
|
|
17
|
+
if ( rest[ i ].startsWith( '--' ) ) {
|
|
18
|
+
const [ key, inline ] = rest[ i ].slice( 2 ).split( '=' );
|
|
19
|
+
args[ key ] = inline ?? rest[ ++i ];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const url = args.url || process.env.OKNO_URL;
|
|
24
|
+
const token = args.token || process.env.OKNO_TOKEN || '';
|
|
25
|
+
|
|
26
|
+
async function types() {
|
|
27
|
+
const endpoint = exportUrl( url ).replace( /\/export$/, '/types' );
|
|
28
|
+
const headers = token ? { 'X-Okno-Token': token } : {};
|
|
29
|
+
const response = await fetch( endpoint, { headers } );
|
|
30
|
+
if ( ! response.ok ) {
|
|
31
|
+
throw new Error( 'HTTP ' + response.status + ' on ' + endpoint );
|
|
32
|
+
}
|
|
33
|
+
const out = args.out || 'src/okno.d.ts';
|
|
34
|
+
await fs.mkdir( path.dirname( out ), { recursive: true } );
|
|
35
|
+
await fs.writeFile( out, await response.text() );
|
|
36
|
+
console.log( 'okno: types written to ' + out );
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function pull() {
|
|
40
|
+
const out = args.out || '.okno/content.json';
|
|
41
|
+
const { data, source } = await loadOknoContent( { url, token, snapshot: out, fallback: 'error' } );
|
|
42
|
+
console.log( 'okno: ' + data.items.length + ' items (' + source + ') in ' + out );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const commands = { types, pull };
|
|
46
|
+
|
|
47
|
+
if ( ! commands[ command ] ) {
|
|
48
|
+
console.log( 'Usage: okno <types|pull> [--url https://cms.example.com] [--out file] [--token …]' );
|
|
49
|
+
process.exit( command ? 1 : 0 );
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
commands[ command ]().catch( ( error ) => {
|
|
53
|
+
console.error( 'okno: ' + error.message );
|
|
54
|
+
process.exit( 1 );
|
|
55
|
+
} );
|
package/dist/okno-bridge.js
CHANGED
|
@@ -64,15 +64,6 @@
|
|
|
64
64
|
/** Attente de stabilisation du DOM avant re-collecte (rendus successifs de React). */
|
|
65
65
|
const SETTLE_MS = 120;
|
|
66
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
67
|
/** Balises retirées de tout HTML injecté dans l'aperçu. */
|
|
77
68
|
const BANNED_TAGS = [ 'script', 'style', 'iframe', 'object', 'embed', 'link', 'meta', 'base', 'form' ];
|
|
78
69
|
|
|
@@ -88,7 +79,7 @@
|
|
|
88
79
|
|
|
89
80
|
const wpOrigin = options && options.wpOrigin;
|
|
90
81
|
if ( ! wpOrigin ) {
|
|
91
|
-
console.warn( '[okno] wpOrigin
|
|
82
|
+
console.warn( '[okno] Missing wpOrigin: bridge inactive.' );
|
|
92
83
|
return;
|
|
93
84
|
}
|
|
94
85
|
|
|
@@ -126,7 +117,7 @@
|
|
|
126
117
|
const style = document.createElement( 'style' );
|
|
127
118
|
style.textContent =
|
|
128
119
|
'.' + CLOAK_CLASS + '{opacity:0 !important}' +
|
|
129
|
-
'.' + HOVER_CLASS + '{outline:2px solid #
|
|
120
|
+
'.' + HOVER_CLASS + '{outline:2px solid #86c400 !important;outline-offset:2px;cursor:pointer !important}' +
|
|
130
121
|
'.' + MANAGED_CLASS + '{outline:2px dashed #b7791f !important;outline-offset:2px}';
|
|
131
122
|
document.head.appendChild( style );
|
|
132
123
|
|
|
@@ -143,7 +134,7 @@
|
|
|
143
134
|
cloakTimer = setTimeout( () => {
|
|
144
135
|
uncloak();
|
|
145
136
|
send( { type: 'cloakTimeout' } );
|
|
146
|
-
console.warn( '[okno] wp-admin
|
|
137
|
+
console.warn( '[okno] wp-admin did not respond: preview shown without hydration.' );
|
|
147
138
|
}, cloakDelay );
|
|
148
139
|
}
|
|
149
140
|
|
|
@@ -165,24 +156,29 @@
|
|
|
165
156
|
return host ? host.getAttribute( 'data-wp-post' ) : '';
|
|
166
157
|
}
|
|
167
158
|
|
|
168
|
-
|
|
159
|
+
// Libellé vide = libellé par défaut, composé et traduit côté éditeur
|
|
160
|
+
// (le bridge ne connaît pas la langue de wp-admin) ; voir sectionAuto().
|
|
161
|
+
function sectionLabel( el ) {
|
|
169
162
|
const explicit = el.getAttribute( 'data-okno-section' ) || el.getAttribute( 'aria-label' );
|
|
170
163
|
if ( explicit ) {
|
|
171
164
|
return explicit;
|
|
172
165
|
}
|
|
173
166
|
const tag = el.tagName.toLowerCase();
|
|
174
|
-
if ( 'header' === tag ) {
|
|
175
|
-
return '
|
|
176
|
-
}
|
|
177
|
-
if ( 'footer' === tag ) {
|
|
178
|
-
return 'Pied de page';
|
|
167
|
+
if ( 'header' === tag || 'footer' === tag ) {
|
|
168
|
+
return '';
|
|
179
169
|
}
|
|
180
170
|
const heading = el.querySelector( 'h1, h2, h3' );
|
|
181
171
|
if ( heading && heading.textContent.trim() ) {
|
|
182
172
|
const text = heading.textContent.trim().replace( /\s+/g, ' ' );
|
|
183
173
|
return text.length > 48 ? text.slice( 0, 45 ) + '…' : text;
|
|
184
174
|
}
|
|
185
|
-
return '
|
|
175
|
+
return '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Nature du libellé par défaut : 'header', 'footer' ou 'section' (numérotée). */
|
|
179
|
+
function sectionAuto( el ) {
|
|
180
|
+
const tag = el.tagName.toLowerCase();
|
|
181
|
+
return 'header' === tag || 'footer' === tag ? tag : 'section';
|
|
186
182
|
}
|
|
187
183
|
|
|
188
184
|
function sectionLayout( el ) {
|
|
@@ -233,7 +229,8 @@
|
|
|
233
229
|
el.setAttribute( 'data-okno-sid', id );
|
|
234
230
|
nextSections.push( {
|
|
235
231
|
id,
|
|
236
|
-
label: sectionLabel( el
|
|
232
|
+
label: sectionLabel( el ),
|
|
233
|
+
auto: sectionAuto( el ),
|
|
237
234
|
layout: sectionLayout( el ),
|
|
238
235
|
el,
|
|
239
236
|
fields: [],
|
|
@@ -274,7 +271,7 @@
|
|
|
274
271
|
return JSON.stringify( [
|
|
275
272
|
window.location.pathname,
|
|
276
273
|
[ ...fields.keys() ].sort(),
|
|
277
|
-
sections.map( ( s ) => [ s.label, s.layout && s.layout.path, s.layout && s.layout.index ] ),
|
|
274
|
+
sections.map( ( s ) => [ s.label, s.auto, s.layout && s.layout.path, s.layout && s.layout.index ] ),
|
|
278
275
|
] );
|
|
279
276
|
}
|
|
280
277
|
|
|
@@ -388,7 +385,7 @@
|
|
|
388
385
|
version: VERSION,
|
|
389
386
|
url: window.location.href,
|
|
390
387
|
posts,
|
|
391
|
-
sections: sections.map( ( s ) => ( { id: s.id, label: s.label, layout: s.layout, fields: s.fields } ) ),
|
|
388
|
+
sections: sections.map( ( s ) => ( { id: s.id, label: s.label, auto: s.auto, layout: s.layout, fields: s.fields } ) ),
|
|
392
389
|
} );
|
|
393
390
|
}
|
|
394
391
|
|
|
@@ -508,7 +505,7 @@
|
|
|
508
505
|
send( {
|
|
509
506
|
type: 'managedFocus',
|
|
510
507
|
provider,
|
|
511
|
-
label: managed.getAttribute( 'data-okno-managed-label' ) || '
|
|
508
|
+
label: managed.getAttribute( 'data-okno-managed-label' ) || '',
|
|
512
509
|
editUrl: managed.getAttribute( 'data-okno-edit-url' ) || '',
|
|
513
510
|
} );
|
|
514
511
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixelersagency/okno-bridge",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.11",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"wordpress",
|
|
@@ -28,14 +28,19 @@
|
|
|
28
28
|
"./react": {
|
|
29
29
|
"import": "./src/react.js"
|
|
30
30
|
},
|
|
31
|
-
"./standalone": "./dist/okno-bridge.js"
|
|
31
|
+
"./standalone": "./dist/okno-bridge.js",
|
|
32
|
+
"./content": {
|
|
33
|
+
"import": "./src/content.js"
|
|
34
|
+
}
|
|
32
35
|
},
|
|
33
36
|
"scripts": {
|
|
34
|
-
"build": "node build.js"
|
|
37
|
+
"build": "node build.js",
|
|
38
|
+
"test": "node --test"
|
|
35
39
|
},
|
|
36
40
|
"files": [
|
|
37
41
|
"src",
|
|
38
42
|
"dist",
|
|
43
|
+
"bin",
|
|
39
44
|
"README.md"
|
|
40
45
|
],
|
|
41
46
|
"sideEffects": false,
|
|
@@ -52,5 +57,11 @@
|
|
|
52
57
|
"react": {
|
|
53
58
|
"optional": true
|
|
54
59
|
}
|
|
60
|
+
},
|
|
61
|
+
"bin": {
|
|
62
|
+
"okno": "./bin/okno.js"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18"
|
|
55
66
|
}
|
|
56
67
|
}
|
package/src/content.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client de l'API de contenu Okno, pour le build du front (Node 18+).
|
|
3
|
+
*
|
|
4
|
+
* import { loadOknoContent, indexOknoContent } from '@pixelersagency/okno-bridge/content';
|
|
5
|
+
*
|
|
6
|
+
* const { data } = await loadOknoContent( { url: 'https://cms.example.com' } );
|
|
7
|
+
* const content = indexOknoContent( data );
|
|
8
|
+
* content.byPath( '/services/audit' );
|
|
9
|
+
*
|
|
10
|
+
* Une seule requête (/wp-json/okno/v1/export), avec reprises. Chaque réponse
|
|
11
|
+
* réussie est gardée dans un instantané local : si WordPress ne répond pas
|
|
12
|
+
* (base saturée, maintenance), le build continue avec le dernier contenu
|
|
13
|
+
* connu au lieu d'échouer, et le signale. Un contenu inchangé renvoie un 304 :
|
|
14
|
+
* rien n'est retéléchargé.
|
|
15
|
+
*
|
|
16
|
+
* Le contenu vit dans WordPress et nulle part ailleurs : pas de JSON local qui
|
|
17
|
+
* le double, l'instantané n'est qu'un cache.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DEFAULT_SNAPSHOT = '.okno/content.json';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* URL de l'export depuis l'adresse du CMS (ou l'URL complète de l'endpoint).
|
|
24
|
+
*
|
|
25
|
+
* @param {string} url
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function exportUrl( url ) {
|
|
29
|
+
if ( ! url ) {
|
|
30
|
+
throw new Error( 'Okno: missing CMS url (option `url` or OKNO_URL).' );
|
|
31
|
+
}
|
|
32
|
+
const clean = String( url ).replace( /\/+$/, '' );
|
|
33
|
+
if ( clean.includes( '/wp-json/' ) || clean.includes( 'rest_route=' ) ) {
|
|
34
|
+
return clean;
|
|
35
|
+
}
|
|
36
|
+
return clean + '/wp-json/okno/v1/export';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Charge tout le contenu publié.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} [options]
|
|
43
|
+
* @param {string} [options.url] Adresse du CMS. Défaut : process.env.OKNO_URL.
|
|
44
|
+
* @param {string} [options.token] Jeton (accès restreint). Défaut : process.env.OKNO_TOKEN.
|
|
45
|
+
* @param {string|false} [options.snapshot] Fichier d'instantané, false pour s'en passer.
|
|
46
|
+
* @param {'snapshot'|'error'} [options.fallback] Si WordPress ne répond pas : instantané (défaut) ou échec.
|
|
47
|
+
* @param {number} [options.retries] Tentatives (défaut 3).
|
|
48
|
+
* @param {number} [options.timeout] Délai par tentative en ms (défaut 30 000).
|
|
49
|
+
* @param {Function} [options.fetch] fetch à utiliser (tests).
|
|
50
|
+
* @param {{warn:Function}} [options.logger]
|
|
51
|
+
* @returns {Promise<{data: object, source: 'network'|'unchanged'|'snapshot', stale: boolean}>}
|
|
52
|
+
*/
|
|
53
|
+
export async function loadOknoContent( options = {} ) {
|
|
54
|
+
const env = typeof process !== 'undefined' ? process.env : {};
|
|
55
|
+
const url = exportUrl( options.url || env.OKNO_URL );
|
|
56
|
+
const token = options.token ?? env.OKNO_TOKEN ?? '';
|
|
57
|
+
const snapshotPath = options.snapshot === false ? null : options.snapshot || DEFAULT_SNAPSHOT;
|
|
58
|
+
const fallback = options.fallback || 'snapshot';
|
|
59
|
+
const retries = Math.max( 1, options.retries ?? 3 );
|
|
60
|
+
const timeout = options.timeout ?? 30000;
|
|
61
|
+
const doFetch = options.fetch || globalThis.fetch;
|
|
62
|
+
const logger = options.logger || console;
|
|
63
|
+
|
|
64
|
+
const snapshot = snapshotPath ? await readSnapshot( snapshotPath ) : null;
|
|
65
|
+
|
|
66
|
+
const headers = { Accept: 'application/json' };
|
|
67
|
+
if ( token ) {
|
|
68
|
+
headers[ 'X-Okno-Token' ] = token;
|
|
69
|
+
}
|
|
70
|
+
if ( snapshot && snapshot.version ) {
|
|
71
|
+
headers[ 'If-None-Match' ] = '"' + snapshot.version + '"';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let lastError = null;
|
|
75
|
+
for ( let attempt = 1; attempt <= retries; attempt++ ) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await doFetch( url, {
|
|
78
|
+
headers,
|
|
79
|
+
signal: typeof AbortSignal !== 'undefined' && AbortSignal.timeout ? AbortSignal.timeout( timeout ) : undefined,
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
if ( response.status === 304 && snapshot ) {
|
|
83
|
+
return { data: snapshot, source: 'unchanged', stale: false };
|
|
84
|
+
}
|
|
85
|
+
if ( response.status === 401 || response.status === 403 ) {
|
|
86
|
+
// Inutile d'insister : le jeton est faux ou absent.
|
|
87
|
+
throw Object.assign( new Error( 'Okno: access refused (' + response.status + '). Check OKNO_TOKEN.' ), { fatal: true } );
|
|
88
|
+
}
|
|
89
|
+
if ( ! response.ok ) {
|
|
90
|
+
throw new Error( 'Okno: HTTP ' + response.status + ' on ' + url );
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
if ( ! data || ! Array.isArray( data.items ) ) {
|
|
95
|
+
throw new Error( 'Okno: unexpected response from ' + url + ' (is Okno 1.0.0-beta.6+ active?)' );
|
|
96
|
+
}
|
|
97
|
+
if ( snapshotPath ) {
|
|
98
|
+
await writeSnapshot( snapshotPath, data );
|
|
99
|
+
}
|
|
100
|
+
return { data, source: 'network', stale: false };
|
|
101
|
+
} catch ( error ) {
|
|
102
|
+
lastError = error;
|
|
103
|
+
if ( error.fatal ) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
if ( attempt < retries ) {
|
|
107
|
+
await sleep( 1000 * 2 ** ( attempt - 1 ) );
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ( snapshot && fallback === 'snapshot' && ! lastError?.fatal ) {
|
|
113
|
+
logger.warn(
|
|
114
|
+
'[okno] WordPress did not answer (' + ( lastError && lastError.message ) + '). ' +
|
|
115
|
+
'Using the snapshot from ' + ( snapshot.generated || 'an earlier build' ) + '.'
|
|
116
|
+
);
|
|
117
|
+
return { data: snapshot, source: 'snapshot', stale: true };
|
|
118
|
+
}
|
|
119
|
+
throw lastError || new Error( 'Okno: no content.' );
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Index de recherche sur un export.
|
|
124
|
+
*
|
|
125
|
+
* @param {{items: Array<object>}} data
|
|
126
|
+
*/
|
|
127
|
+
export function indexOknoContent( data ) {
|
|
128
|
+
const items = ( data && data.items ) || [];
|
|
129
|
+
const byId = new Map();
|
|
130
|
+
const byPath = new Map();
|
|
131
|
+
for ( const item of items ) {
|
|
132
|
+
byId.set( item.id, item );
|
|
133
|
+
byPath.set( normalizePath( item.path ), item );
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
items,
|
|
137
|
+
byId: ( id ) => byId.get( Number( id ) ) || null,
|
|
138
|
+
byPath: ( path ) => byPath.get( normalizePath( path ) ) || null,
|
|
139
|
+
byType: ( type ) => items.filter( ( item ) => item.type === type ),
|
|
140
|
+
bySlug: ( type, slug ) => items.find( ( item ) => item.type === type && item.slug === slug ) || null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizePath( path ) {
|
|
145
|
+
const clean = '/' + String( path || '' ).split( /[?#]/ )[ 0 ].replace( /^\/+|\/+$/g, '' );
|
|
146
|
+
return clean === '/' ? '/' : clean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function readSnapshot( file ) {
|
|
150
|
+
const fs = await import( 'node:fs/promises' );
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse( await fs.readFile( file, 'utf8' ) );
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function writeSnapshot( file, data ) {
|
|
159
|
+
const fs = await import( 'node:fs/promises' );
|
|
160
|
+
const path = await import( 'node:path' );
|
|
161
|
+
await fs.mkdir( path.dirname( file ), { recursive: true } );
|
|
162
|
+
// Écriture atomique : un build interrompu ne laisse pas d'instantané tronqué.
|
|
163
|
+
const tmp = file + '.' + process.pid + '.tmp';
|
|
164
|
+
await fs.writeFile( tmp, JSON.stringify( data ) );
|
|
165
|
+
await fs.rename( tmp, file );
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sleep( ms ) {
|
|
169
|
+
return new Promise( ( resolve ) => setTimeout( resolve, ms ) );
|
|
170
|
+
}
|
package/src/index.js
CHANGED
|
@@ -47,15 +47,6 @@ const CLOAK_CLASS = 'okno-cloak';
|
|
|
47
47
|
/** Attente de stabilisation du DOM avant re-collecte (rendus successifs de React). */
|
|
48
48
|
const SETTLE_MS = 120;
|
|
49
49
|
|
|
50
|
-
/** Noms lisibles des fournisseurs de contenu courants. */
|
|
51
|
-
const PROVIDERS = {
|
|
52
|
-
wordpress: 'WordPress',
|
|
53
|
-
menu: 'Menus WordPress',
|
|
54
|
-
acf: 'ACF',
|
|
55
|
-
woocommerce: 'WooCommerce',
|
|
56
|
-
jetengine: 'JetEngine',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
50
|
/** Balises retirées de tout HTML injecté dans l'aperçu. */
|
|
60
51
|
const BANNED_TAGS = [ 'script', 'style', 'iframe', 'object', 'embed', 'link', 'meta', 'base', 'form' ];
|
|
61
52
|
|
|
@@ -71,7 +62,7 @@ export function initOknoBridge( options ) {
|
|
|
71
62
|
|
|
72
63
|
const wpOrigin = options && options.wpOrigin;
|
|
73
64
|
if ( ! wpOrigin ) {
|
|
74
|
-
console.warn( '[okno] wpOrigin
|
|
65
|
+
console.warn( '[okno] Missing wpOrigin: bridge inactive.' );
|
|
75
66
|
return;
|
|
76
67
|
}
|
|
77
68
|
|
|
@@ -109,7 +100,7 @@ export function initOknoBridge( options ) {
|
|
|
109
100
|
const style = document.createElement( 'style' );
|
|
110
101
|
style.textContent =
|
|
111
102
|
'.' + CLOAK_CLASS + '{opacity:0 !important}' +
|
|
112
|
-
'.' + HOVER_CLASS + '{outline:2px solid #
|
|
103
|
+
'.' + HOVER_CLASS + '{outline:2px solid #86c400 !important;outline-offset:2px;cursor:pointer !important}' +
|
|
113
104
|
'.' + MANAGED_CLASS + '{outline:2px dashed #b7791f !important;outline-offset:2px}';
|
|
114
105
|
document.head.appendChild( style );
|
|
115
106
|
|
|
@@ -126,7 +117,7 @@ export function initOknoBridge( options ) {
|
|
|
126
117
|
cloakTimer = setTimeout( () => {
|
|
127
118
|
uncloak();
|
|
128
119
|
send( { type: 'cloakTimeout' } );
|
|
129
|
-
console.warn( '[okno] wp-admin
|
|
120
|
+
console.warn( '[okno] wp-admin did not respond: preview shown without hydration.' );
|
|
130
121
|
}, cloakDelay );
|
|
131
122
|
}
|
|
132
123
|
|
|
@@ -148,24 +139,29 @@ export function initOknoBridge( options ) {
|
|
|
148
139
|
return host ? host.getAttribute( 'data-wp-post' ) : '';
|
|
149
140
|
}
|
|
150
141
|
|
|
151
|
-
|
|
142
|
+
// Libellé vide = libellé par défaut, composé et traduit côté éditeur
|
|
143
|
+
// (le bridge ne connaît pas la langue de wp-admin) ; voir sectionAuto().
|
|
144
|
+
function sectionLabel( el ) {
|
|
152
145
|
const explicit = el.getAttribute( 'data-okno-section' ) || el.getAttribute( 'aria-label' );
|
|
153
146
|
if ( explicit ) {
|
|
154
147
|
return explicit;
|
|
155
148
|
}
|
|
156
149
|
const tag = el.tagName.toLowerCase();
|
|
157
|
-
if ( 'header' === tag ) {
|
|
158
|
-
return '
|
|
159
|
-
}
|
|
160
|
-
if ( 'footer' === tag ) {
|
|
161
|
-
return 'Pied de page';
|
|
150
|
+
if ( 'header' === tag || 'footer' === tag ) {
|
|
151
|
+
return '';
|
|
162
152
|
}
|
|
163
153
|
const heading = el.querySelector( 'h1, h2, h3' );
|
|
164
154
|
if ( heading && heading.textContent.trim() ) {
|
|
165
155
|
const text = heading.textContent.trim().replace( /\s+/g, ' ' );
|
|
166
156
|
return text.length > 48 ? text.slice( 0, 45 ) + '…' : text;
|
|
167
157
|
}
|
|
168
|
-
return '
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Nature du libellé par défaut : 'header', 'footer' ou 'section' (numérotée). */
|
|
162
|
+
function sectionAuto( el ) {
|
|
163
|
+
const tag = el.tagName.toLowerCase();
|
|
164
|
+
return 'header' === tag || 'footer' === tag ? tag : 'section';
|
|
169
165
|
}
|
|
170
166
|
|
|
171
167
|
function sectionLayout( el ) {
|
|
@@ -216,7 +212,8 @@ export function initOknoBridge( options ) {
|
|
|
216
212
|
el.setAttribute( 'data-okno-sid', id );
|
|
217
213
|
nextSections.push( {
|
|
218
214
|
id,
|
|
219
|
-
label: sectionLabel( el
|
|
215
|
+
label: sectionLabel( el ),
|
|
216
|
+
auto: sectionAuto( el ),
|
|
220
217
|
layout: sectionLayout( el ),
|
|
221
218
|
el,
|
|
222
219
|
fields: [],
|
|
@@ -257,7 +254,7 @@ export function initOknoBridge( options ) {
|
|
|
257
254
|
return JSON.stringify( [
|
|
258
255
|
window.location.pathname,
|
|
259
256
|
[ ...fields.keys() ].sort(),
|
|
260
|
-
sections.map( ( s ) => [ s.label, s.layout && s.layout.path, s.layout && s.layout.index ] ),
|
|
257
|
+
sections.map( ( s ) => [ s.label, s.auto, s.layout && s.layout.path, s.layout && s.layout.index ] ),
|
|
261
258
|
] );
|
|
262
259
|
}
|
|
263
260
|
|
|
@@ -371,7 +368,7 @@ export function initOknoBridge( options ) {
|
|
|
371
368
|
version: VERSION,
|
|
372
369
|
url: window.location.href,
|
|
373
370
|
posts,
|
|
374
|
-
sections: sections.map( ( s ) => ( { id: s.id, label: s.label, layout: s.layout, fields: s.fields } ) ),
|
|
371
|
+
sections: sections.map( ( s ) => ( { id: s.id, label: s.label, auto: s.auto, layout: s.layout, fields: s.fields } ) ),
|
|
375
372
|
} );
|
|
376
373
|
}
|
|
377
374
|
|
|
@@ -491,7 +488,7 @@ export function initOknoBridge( options ) {
|
|
|
491
488
|
send( {
|
|
492
489
|
type: 'managedFocus',
|
|
493
490
|
provider,
|
|
494
|
-
label: managed.getAttribute( 'data-okno-managed-label' ) || '
|
|
491
|
+
label: managed.getAttribute( 'data-okno-managed-label' ) || '',
|
|
495
492
|
editUrl: managed.getAttribute( 'data-okno-edit-url' ) || '',
|
|
496
493
|
} );
|
|
497
494
|
return;
|