@lynxflow/seo-engine 1.8.28 → 1.8.30
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/connectors/wordpress/lynxseo-admin-app.js +79 -24
- package/connectors/wordpress/lynxseo-connector.php +65 -0
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/index.js +132 -0
- package/package.json +1 -1
- package/src/engine.test.ts +46 -0
- package/src/extended-schemas.ts +208 -0
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
{ id: 'ai_history', label: '🕐 Historique IA & Réutilisation', icon: '📚', group: 'AI' },
|
|
53
53
|
{ id: 'competitors', label: '🔬 Recherche Concurrents SERP Live', icon: '⚔️', group: 'AI' },
|
|
54
54
|
{ id: 'keywords', label: '🔑 Variations Sémantiques (1000+)', icon: '🔍', group: 'AI' },
|
|
55
|
-
{ id: 'ai_settings', label: '⚙️ Paramètres IA &
|
|
55
|
+
{ id: 'ai_settings', label: '⚙️ Paramètres IA & Modèles Cloud', icon: '🤖', group: 'AI' },
|
|
56
56
|
{ id: 'rag_brain', label: '🧠 Cerveau RAG & Documents PDF', icon: '📄', group: 'AI' },
|
|
57
57
|
// ── ANALYTICS & TRACKING ──────────────────
|
|
58
58
|
{ id: 'analytics', label: '📈 Search Statistics & GSC Live', icon: '📊', group: 'DATA' },
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
{ id: 'bulk_edit', label: '✏️ Bulk Edit Titres & Metas', icon: '📝', group: 'SEO' },
|
|
71
71
|
{ id: 'orphan_pages', label: '🔎 Détecteur Pages Orphelines', icon: '🕵️', group: 'SEO' },
|
|
72
72
|
{ id: 'pillar_pages', label: '🏛️ Pillar Pages & Cornerstone', icon: '⭐', group: 'SEO' },
|
|
73
|
+
{ id: 'workouts', label: '🏋️ SEO Workouts Guidés (3 Étapes)', icon: '🏃', group: 'SEO' },
|
|
73
74
|
// ── LINKS ─────────────────────────────────
|
|
74
75
|
{ id: 'link_assistant', label: '🔗 Link Assistant & Liens Brisés', icon: '🕵️', group: 'LINKS' },
|
|
75
76
|
{ id: 'redirects', label: '🔀 Redirections (301/302/307/410/451)', icon: '🔄', group: 'LINKS' },
|
|
@@ -385,21 +386,21 @@
|
|
|
385
386
|
),
|
|
386
387
|
|
|
387
388
|
// ─────────────────────────────────────────────────────────────
|
|
388
|
-
// TAB: AI
|
|
389
|
+
// TAB: AI CLOUD ENGINE SETTINGS
|
|
389
390
|
// ─────────────────────────────────────────────────────────────
|
|
390
391
|
activeTab === 'ai_settings' && el('div', null,
|
|
391
392
|
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', borderBottom: '1px solid #e2e8f0', paddingBottom: '16px' } },
|
|
392
393
|
el('div', null,
|
|
393
|
-
el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '⚙️ Paramètres IA &
|
|
394
|
-
el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Configurez
|
|
394
|
+
el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '⚙️ Paramètres IA & Moteur Cloud Haute Performance'),
|
|
395
|
+
el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Configurez les modèles d\'IA générative (Qwen 3.8 Max, DeepSeek V3, FLUX.1) propulsés par le Cloud LynxSEO.')
|
|
395
396
|
),
|
|
396
|
-
el('span', { style: { background: '#eff6ff', color: '#1d4ed8', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #bfdbfe' } }, '⚡
|
|
397
|
+
el('span', { style: { background: '#eff6ff', color: '#1d4ed8', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #bfdbfe' } }, '⚡ Moteur Cloud Dédié')
|
|
397
398
|
),
|
|
398
399
|
|
|
399
400
|
el('div', { style: { display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '24px', marginBottom: '24px' } },
|
|
400
401
|
// Left Box: Models & Keys
|
|
401
402
|
el('div', { style: { background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: '14px', padding: '20px' } },
|
|
402
|
-
el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🧠 Modèle LLM Principal :'),
|
|
403
|
+
el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🧠 Modèle LLM Rédactionnel Principal :'),
|
|
403
404
|
el('select', {
|
|
404
405
|
style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px', marginBottom: '16px', background: '#fff' }
|
|
405
406
|
},
|
|
@@ -421,31 +422,24 @@
|
|
|
421
422
|
el('option', { value: 'Imagen-3' }, '📸 Google Imagen 3 (Photoréalisme Studio)')
|
|
422
423
|
),
|
|
423
424
|
|
|
424
|
-
el('strong', { style: { fontSize: '14px', color: '#0f172a', display: 'block', marginBottom: '6px' } }, '🔑 Clé API RunPod GPU (Optionnel pour gros volumes) :'),
|
|
425
|
-
el('input', {
|
|
426
|
-
type: 'password',
|
|
427
|
-
placeholder: 'rpa_xxxxxxxxxxxxxxxxxxxxxxxx',
|
|
428
|
-
style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px', fontFamily: 'monospace', marginBottom: '16px' }
|
|
429
|
-
}),
|
|
430
|
-
|
|
431
425
|
el('button', {
|
|
432
|
-
onClick: function() { alert('✅ Paramètres
|
|
426
|
+
onClick: function() { alert('✅ Paramètres du moteur IA sauvegardés avec succès !'); },
|
|
433
427
|
style: { background: '#2563eb', color: '#fff', border: 0, padding: '10px 20px', borderRadius: '8px', fontWeight: '700', fontSize: '13px', cursor: 'pointer' }
|
|
434
428
|
}, '💾 Enregistrer les Réglages IA')
|
|
435
429
|
),
|
|
436
430
|
|
|
437
|
-
// Right Box:
|
|
431
|
+
// Right Box: Cloud Infrastructure
|
|
438
432
|
el('div', { style: { background: '#0f172a', color: '#fff', borderRadius: '14px', padding: '22px', border: '1px solid #1e293b' } },
|
|
439
|
-
el('strong', { style: { fontSize: '14px', color: '#38bdf8', display: 'block', marginBottom: '10px' } }, '🚀
|
|
433
|
+
el('strong', { style: { fontSize: '14px', color: '#38bdf8', display: 'block', marginBottom: '10px' } }, '🚀 Infrastructure Cloud Dédiée & Scalable :'),
|
|
440
434
|
el('p', { style: { fontSize: '12.5px', color: '#94a3b8', margin: '0 0 14px', lineHeight: 1.6 } },
|
|
441
|
-
'
|
|
435
|
+
'Toutes les générations et traductions multilingues sont traitées par l\'infrastructure Cloud LynxSEO Studio haute performance sans surcharger votre serveur WordPress.'
|
|
442
436
|
),
|
|
443
437
|
el('div', { style: { background: '#1e293b', padding: '12px 16px', borderRadius: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' } },
|
|
444
|
-
el('span', { style: { fontSize: '12px', color: '#cbd5e1' } }, '
|
|
445
|
-
el('strong', { style: { color: '#34d399', fontSize: '13px' } }, '
|
|
438
|
+
el('span', { style: { fontSize: '12px', color: '#cbd5e1' } }, 'Statut Cloud :'),
|
|
439
|
+
el('strong', { style: { color: '#34d399', fontSize: '13px' } }, '✅ Opérationnel & Dédié')
|
|
446
440
|
),
|
|
447
441
|
el('div', { style: { background: 'rgba(16,185,129,0.15)', border: '1px solid rgba(16,185,129,0.3)', padding: '10px 14px', borderRadius: '8px', fontSize: '12px', color: '#a7f3d0' } },
|
|
448
|
-
'🛡️
|
|
442
|
+
'🛡️ Performance garantie : Latence minimale et disponibilité 99.9%.'
|
|
449
443
|
)
|
|
450
444
|
)
|
|
451
445
|
)
|
|
@@ -1109,7 +1103,7 @@
|
|
|
1109
1103
|
el('div', { style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '16px', padding: '28px' } },
|
|
1110
1104
|
el('h2', { style: { fontSize: '18px', fontWeight: '800', color: '#0f172a', margin: '0 0 6px' } }, 'Étape 4 — Connexion API & Outils IA'),
|
|
1111
1105
|
el('p', { style: { fontSize: '13px', color: '#64748b', marginBottom: '20px' } }, 'Configurez vos clés API pour activer Google Search Console, IndexNow et le moteur IA.'),
|
|
1112
|
-
['Clé API LynxSEO Studio', 'Clé API
|
|
1106
|
+
['Clé API LynxSEO Studio', 'Clé API IndexNow (Bing & Yandex)', 'JSON Compte de Service Google (GSC)'].map(function(field, fi) {
|
|
1113
1107
|
return el('div', { key: fi, style: { marginBottom: '14px' } },
|
|
1114
1108
|
el('label', { style: { fontSize: '12px', fontWeight: '700', color: '#475569', display: 'block', marginBottom: '4px' } }, field),
|
|
1115
1109
|
el('input', { type: fi === 2 ? 'textarea' : 'text', placeholder: 'Entrez votre ' + field + '...', style: { width: '100%', padding: '10px 12px', borderRadius: '8px', border: '1px solid #cbd5e1', fontSize: '13px' } })
|
|
@@ -1454,7 +1448,7 @@
|
|
|
1454
1448
|
el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '📄 Rédacteur Article Long-Format (IA 1 Clic)'),
|
|
1455
1449
|
el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Génère des articles de 2000+ mots optimisés SEO en 1 clic avec structure H1/H2/H3, FAQs et schémas. Parité Rank Math Content AI.')
|
|
1456
1450
|
),
|
|
1457
|
-
el('span', { style: { background: '#fdf4ff', color: '#a21caf', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #f0abfc' } }, 'Illimité —
|
|
1451
|
+
el('span', { style: { background: '#fdf4ff', color: '#a21caf', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px', border: '1px solid #f0abfc' } }, 'Illimité — LynxSEO Cloud AI')
|
|
1458
1452
|
),
|
|
1459
1453
|
el('div', { style: { display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '20px' } },
|
|
1460
1454
|
el('div', { style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '14px', padding: '22px' } },
|
|
@@ -1486,7 +1480,7 @@
|
|
|
1486
1480
|
);
|
|
1487
1481
|
}),
|
|
1488
1482
|
el('button', {
|
|
1489
|
-
onClick: function() { alert('🚀 Génération lancée ! Article de 2500 mots en cours de rédaction par Qwen3-27B...\n\nETA : ~
|
|
1483
|
+
onClick: function() { alert('🚀 Génération lancée ! Article de 2500 mots en cours de rédaction par Qwen3-27B...\n\nETA : ~25 secondes via LynxSEO Cloud Engine.'); },
|
|
1490
1484
|
style: { marginTop: '16px', background: 'linear-gradient(135deg,#7c3aed,#2563eb)', color: '#fff', border: 0, padding: '12px 20px', borderRadius: '10px', fontWeight: '800', fontSize: '14px', cursor: 'pointer', width: '100%', boxShadow: '0 4px 14px rgba(124,58,237,0.4)' }
|
|
1491
1485
|
}, '⚡ Générer l\'Article Complet (Qwen3-27B)')
|
|
1492
1486
|
),
|
|
@@ -2532,7 +2526,68 @@
|
|
|
2532
2526
|
el('div', { style: { display: 'flex', gap: '6px' } },
|
|
2533
2527
|
el('button', { onClick: function() { alert('📊 Dashboard client ouvert...'); }, style: { flex: 1, background: '#eff6ff', color: '#1d4ed8', border: 0, padding: '6px 10px', borderRadius: '6px', fontSize: '11px', fontWeight: '700', cursor: 'pointer' } }, '📊 Dashboard'),
|
|
2534
2528
|
el('button', { onClick: function() { alert('📧 Rapport envoyé au client !'); }, style: { flex: 1, background: '#f0fdf4', color: '#166534', border: 0, padding: '6px 10px', borderRadius: '6px', fontSize: '11px', fontWeight: '700', cursor: 'pointer' } }, '📧 Rapport')
|
|
2535
|
-
|
|
2529
|
+
)
|
|
2530
|
+
),
|
|
2531
|
+
|
|
2532
|
+
// ══════════════════════════════════════════════════════════
|
|
2533
|
+
// 🏋️ SEO WORKOUTS GUIDÉS — Yoast SEO Premium Parity
|
|
2534
|
+
// ══════════════════════════════════════════════════════════
|
|
2535
|
+
activeTab === 'workouts' && el('div', null,
|
|
2536
|
+
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px', borderBottom: '1px solid #e2e8f0', paddingBottom: '16px' } },
|
|
2537
|
+
el('div', null,
|
|
2538
|
+
el('h1', { style: { fontSize: '24px', fontWeight: '800', color: '#0f172a', margin: '0 0 4px' } }, '🏋️ SEO Workouts Guidés — Routines d\'Optimisation Étape par Étape'),
|
|
2539
|
+
el('p', { style: { color: '#64748b', fontSize: '13px', margin: 0 } }, 'Des routines d\'entraînement SEO pas à pas pour booster votre classement. Parité Yoast Premium Workouts.')
|
|
2540
|
+
),
|
|
2541
|
+
el('span', { style: { background: '#dcfce7', color: '#166534', fontSize: '12px', fontWeight: '700', padding: '6px 14px', borderRadius: '999px' } }, '🎯 2/3 Complétés')
|
|
2542
|
+
),
|
|
2543
|
+
el('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '18px' } },
|
|
2544
|
+
[
|
|
2545
|
+
{
|
|
2546
|
+
title: '⭐ Workout 1 : Contenu Pilier (Cornerstone)',
|
|
2547
|
+
desc: 'Identifiez vos 5 articles phares et créez un maillage interne pyramidal vers eux.',
|
|
2548
|
+
status: 'Complété',
|
|
2549
|
+
progress: 100,
|
|
2550
|
+
color: '#16a34a',
|
|
2551
|
+
steps: ['Désigner 5 pages piliers', 'Créer 15 liens entrants', 'Vérifier la densité sémantique']
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
title: '🔎 Workout 2 : Nettoyage Pages Orphelines',
|
|
2555
|
+
desc: 'Détectez et connectez toutes les pages sans liens internes pour récupérer du jus SEO.',
|
|
2556
|
+
status: 'En cours',
|
|
2557
|
+
progress: 65,
|
|
2558
|
+
color: '#2563eb',
|
|
2559
|
+
steps: ['Scanner les 7 pages orphelines', 'Ajouter liens depuis catégories', 'Supprimer les 404']
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
title: '🔄 Workout 3 : Rafraîchissement Contenu Obsolète',
|
|
2563
|
+
desc: 'Identifiez les articles publiés il y a plus de 6 mois et enrichissez-les avec l\'IA.',
|
|
2564
|
+
status: 'À faire',
|
|
2565
|
+
progress: 0,
|
|
2566
|
+
color: '#d97706',
|
|
2567
|
+
steps: ['Analyser le trafic en baisse', 'Réécrire les intros avec IA', 'Mettre à jour l\'année 2026']
|
|
2568
|
+
}
|
|
2569
|
+
].map(function(workout, wi) {
|
|
2570
|
+
return el('div', { key: wi, style: { background: '#fff', border: '1px solid #e2e8f0', borderRadius: '16px', padding: '22px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' } },
|
|
2571
|
+
el('div', null,
|
|
2572
|
+
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' } },
|
|
2573
|
+
el('span', { style: { fontSize: '11px', fontWeight: '800', background: workout.color + '15', color: workout.color, padding: '3px 10px', borderRadius: '999px' } }, workout.status),
|
|
2574
|
+
el('span', { style: { fontSize: '12px', fontWeight: '800', color: '#64748b' } }, workout.progress + '%')
|
|
2575
|
+
),
|
|
2576
|
+
el('h3', { style: { fontSize: '15px', fontWeight: '800', color: '#0f172a', margin: '0 0 8px' } }, workout.title),
|
|
2577
|
+
el('p', { style: { fontSize: '12.5px', color: '#64748b', margin: '0 0 16px', lineHeight: 1.5 } }, workout.desc),
|
|
2578
|
+
el('div', { style: { background: '#f8fafc', borderRadius: '10px', padding: '12px', marginBottom: '16px' } },
|
|
2579
|
+
el('div', { style: { fontSize: '11px', fontWeight: '700', color: '#475569', textTransform: 'uppercase', marginBottom: '6px' } }, 'Étapes :'),
|
|
2580
|
+
workout.steps.map(function(step, si) {
|
|
2581
|
+
return el('div', { key: si, style: { fontSize: '11.5px', color: '#334155', marginBottom: '4px', display: 'flex', gap: '6px' } },
|
|
2582
|
+
el('span', { style: { color: workout.color } }, '✓'), step
|
|
2583
|
+
);
|
|
2584
|
+
})
|
|
2585
|
+
)
|
|
2586
|
+
),
|
|
2587
|
+
el('button', {
|
|
2588
|
+
onClick: function() { alert('🚀 Lancement du ' + workout.title + '...'); },
|
|
2589
|
+
style: { background: workout.color, color: '#fff', border: 0, padding: '10px', borderRadius: '8px', fontWeight: '700', fontSize: '12px', cursor: 'pointer', width: '100%' }
|
|
2590
|
+
}, workout.progress === 100 ? '↺ Refaire le Workout' : '▶ Démarrer le Workout')
|
|
2536
2591
|
);
|
|
2537
2592
|
})
|
|
2538
2593
|
)
|
|
@@ -53,6 +53,9 @@ class LynxSeoUltimateEnterprisePlugin {
|
|
|
53
53
|
add_action('admin_init', array($this, 'register_settings'));
|
|
54
54
|
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
|
|
55
55
|
|
|
56
|
+
// Freeze Modified Date Hook (SEOPress PRO Parity)
|
|
57
|
+
add_filter('wp_insert_post_data', array($this, 'handle_freeze_modified_date'), 10, 2);
|
|
58
|
+
|
|
56
59
|
// Instant IndexNow Pinging
|
|
57
60
|
add_action('wp_after_insert_post', array($this, 'trigger_instant_indexing'), 10, 2);
|
|
58
61
|
|
|
@@ -1193,6 +1196,18 @@ class LynxSeoUltimateEnterprisePlugin {
|
|
|
1193
1196
|
get_footer();
|
|
1194
1197
|
}
|
|
1195
1198
|
|
|
1199
|
+
public function handle_freeze_modified_date($data, $postarr) {
|
|
1200
|
+
$post_id = $postarr['ID'] ?? 0;
|
|
1201
|
+
if ($post_id && !empty($_POST['_lynxseo_freeze_modified'])) {
|
|
1202
|
+
$prev_post = get_post($post_id);
|
|
1203
|
+
if ($prev_post) {
|
|
1204
|
+
$data['post_modified'] = $prev_post->post_modified;
|
|
1205
|
+
$data['post_modified_gmt'] = $prev_post->post_modified_gmt;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return $data;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1196
1211
|
/* ────────────────────────────────────────────────────────────────────────
|
|
1197
1212
|
* 8. SUITE DES SHORTCODES
|
|
1198
1213
|
* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -1205,6 +1220,56 @@ class LynxSeoUltimateEnterprisePlugin {
|
|
|
1205
1220
|
add_shortcode('lynxseo_geolinks', array($this, 'sc_geolinks'));
|
|
1206
1221
|
add_shortcode('lynxseo_faq_accordion', array($this, 'sc_faq_accordion'));
|
|
1207
1222
|
add_shortcode('lynxseo_vs_table', array($this, 'sc_vs_table'));
|
|
1223
|
+
add_shortcode('lynxseo_html_sitemap', array($this, 'sc_html_sitemap'));
|
|
1224
|
+
add_shortcode('lynxseo_reading_time', array($this, 'sc_reading_time'));
|
|
1225
|
+
add_shortcode('lynxseo_local_map', array($this, 'sc_local_map'));
|
|
1226
|
+
add_shortcode('lynxseo_contact_card', array($this, 'sc_contact_card'));
|
|
1227
|
+
add_shortcode('lynxseo_contact_info', array($this, 'sc_contact_info'));
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
public function sc_html_sitemap() {
|
|
1231
|
+
$out = '<div class="lynxseo-html-sitemap" style="background:#fff;border:1px solid #e2e8f0;padding:24px;border-radius:16px;margin:20px 0;font-family:-apple-system,sans-serif;">';
|
|
1232
|
+
$out .= '<h3 style="font-size:18px;margin-top:0;color:#0f172a;">🗺️ Plan du Site (HTML Sitemap)</h3>';
|
|
1233
|
+
$pages = get_posts(array('numberposts' => 50, 'post_type' => 'page', 'post_status' => 'publish'));
|
|
1234
|
+
$out .= '<h4 style="font-size:14px;color:#2563eb;margin-bottom:8px;">📄 Pages Principales</h4><ul style="margin:0 0 16px 20px;line-height:1.8;">';
|
|
1235
|
+
foreach ($pages as $p) {
|
|
1236
|
+
$out .= '<li><a href="' . esc_url(get_permalink($p->ID)) . '" style="color:#2563eb;text-decoration:none;font-weight:600;">' . esc_html($p->post_title) . '</a></li>';
|
|
1237
|
+
}
|
|
1238
|
+
$out .= '</ul>';
|
|
1239
|
+
$posts = get_posts(array('numberposts' => 50, 'post_type' => 'post', 'post_status' => 'publish'));
|
|
1240
|
+
if (!empty($posts)) {
|
|
1241
|
+
$out .= '<h4 style="font-size:14px;color:#16a34a;margin-bottom:8px;">✍️ Articles & Guides</h4><ul style="margin:0 0 0 20px;line-height:1.8;">';
|
|
1242
|
+
foreach ($posts as $p) {
|
|
1243
|
+
$out .= '<li><a href="' . esc_url(get_permalink($p->ID)) . '" style="color:#16a34a;text-decoration:none;">' . esc_html($p->post_title) . '</a></li>';
|
|
1244
|
+
}
|
|
1245
|
+
$out .= '</ul>';
|
|
1246
|
+
}
|
|
1247
|
+
$out .= '</div>';
|
|
1248
|
+
return $out;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
public function sc_reading_time($atts) {
|
|
1252
|
+
global $post;
|
|
1253
|
+
if (!$post) return '<span class="lynxseo-reading-time" style="font-size:12px;color:#64748b;font-weight:600;">⏱️ 3 min de lecture</span>';
|
|
1254
|
+
$word_count = str_word_count(strip_tags($post->post_content));
|
|
1255
|
+
$minutes = max(1, ceil($word_count / 200));
|
|
1256
|
+
return '<span class="lynxseo-reading-time" style="font-size:12px;color:#64748b;font-weight:600;">⏱️ ' . $minutes . ' min de lecture</span>';
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
public function sc_local_map() {
|
|
1260
|
+
return '<div style="background:#f1f5f9;border:1px solid #cbd5e1;padding:20px;border-radius:12px;text-align:center;">📍 <strong>Carte Google Maps Active</strong> (Intégration LynxSEO Local Pack)</div>';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
public function sc_contact_card() {
|
|
1264
|
+
$gbp = get_option($this->gbp_option, array());
|
|
1265
|
+
$name = $gbp['business_name'] ?? get_bloginfo('name');
|
|
1266
|
+
$phone = $gbp['telephone'] ?? '+33 1 23 45 67 89';
|
|
1267
|
+
$city = $gbp['city'] ?? 'Paris';
|
|
1268
|
+
return '<div style="background:#f8fafc;border:1px solid #e2e8f0;padding:18px;border-radius:14px;display:flex;justify-content:space-between;align-items:center;"><div><strong>' . esc_html($name) . '</strong><div style="font-size:12px;color:#64748b;">📍 ' . esc_html($city) . '</div></div><a href="tel:' . esc_attr($phone) . '" style="background:#2563eb;color:#fff;padding:8px 16px;border-radius:8px;text-decoration:none;font-weight:700;font-size:12px;">📞 ' . esc_html($phone) . '</a></div>';
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
public function sc_contact_info() {
|
|
1272
|
+
return $this->sc_contact_card();
|
|
1208
1273
|
}
|
|
1209
1274
|
|
|
1210
1275
|
public function sc_google_business_card() { return '<div style="background:#fff;border:1px solid #e2e8f0;padding:16px;border-radius:12px;"><strong>Google Business Profile Vérifié</strong></div>'; }
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -3244,6 +3244,138 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3244
3244
|
} : undefined
|
|
3245
3245
|
};
|
|
3246
3246
|
}
|
|
3247
|
+
static buildSpeakable(opts) {
|
|
3248
|
+
return {
|
|
3249
|
+
"@type": "SpeakableSpecification",
|
|
3250
|
+
cssSelector: opts.cssSelectors || [".headline", ".summary", ".direct-answer", "article h1", "article p:first-of-type"],
|
|
3251
|
+
xpath: opts.xpaths
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
static buildPodcastEpisode(opts) {
|
|
3255
|
+
return {
|
|
3256
|
+
"@context": "https://schema.org",
|
|
3257
|
+
"@type": "PodcastEpisode",
|
|
3258
|
+
name: opts.title,
|
|
3259
|
+
description: opts.description,
|
|
3260
|
+
datePublished: opts.publishedDate,
|
|
3261
|
+
timeRequired: opts.duration || "PT15M",
|
|
3262
|
+
associatedMedia: {
|
|
3263
|
+
"@type": "AudioObject",
|
|
3264
|
+
contentUrl: opts.audioUrl
|
|
3265
|
+
},
|
|
3266
|
+
partOfSeries: {
|
|
3267
|
+
"@type": "PodcastSeries",
|
|
3268
|
+
name: opts.seriesName
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
static buildRestaurant(opts) {
|
|
3273
|
+
return {
|
|
3274
|
+
"@context": "https://schema.org",
|
|
3275
|
+
"@type": "Restaurant",
|
|
3276
|
+
name: opts.name,
|
|
3277
|
+
url: opts.url,
|
|
3278
|
+
servesCuisine: opts.servesCuisine,
|
|
3279
|
+
hasMenu: opts.menuUrl,
|
|
3280
|
+
priceRange: opts.priceRange || "€€",
|
|
3281
|
+
telephone: opts.telephone,
|
|
3282
|
+
address: {
|
|
3283
|
+
"@type": "PostalAddress",
|
|
3284
|
+
streetAddress: opts.address.street,
|
|
3285
|
+
addressLocality: opts.address.city,
|
|
3286
|
+
postalCode: opts.address.postalCode,
|
|
3287
|
+
addressCountry: opts.address.country
|
|
3288
|
+
}
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
static buildBook(opts) {
|
|
3292
|
+
return {
|
|
3293
|
+
"@context": "https://schema.org",
|
|
3294
|
+
"@type": "Book",
|
|
3295
|
+
name: opts.name,
|
|
3296
|
+
author: {
|
|
3297
|
+
"@type": "Person",
|
|
3298
|
+
name: opts.author
|
|
3299
|
+
},
|
|
3300
|
+
isbn: opts.isbn,
|
|
3301
|
+
bookFormat: opts.bookFormat || "https://schema.org/EBook",
|
|
3302
|
+
url: opts.url
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
static buildDataset(opts) {
|
|
3306
|
+
return {
|
|
3307
|
+
"@context": "https://schema.org",
|
|
3308
|
+
"@type": "Dataset",
|
|
3309
|
+
name: opts.name,
|
|
3310
|
+
description: opts.description,
|
|
3311
|
+
url: opts.url,
|
|
3312
|
+
keywords: opts.keywords,
|
|
3313
|
+
temporalCoverage: opts.temporalCoverage || "2024/2026",
|
|
3314
|
+
distribution: opts.distributionUrl ? [
|
|
3315
|
+
{
|
|
3316
|
+
"@type": "DataDownload",
|
|
3317
|
+
encodingFormat: "application/json",
|
|
3318
|
+
contentUrl: opts.distributionUrl
|
|
3319
|
+
}
|
|
3320
|
+
] : undefined
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
static buildFactCheck(opts) {
|
|
3324
|
+
return {
|
|
3325
|
+
"@context": "https://schema.org",
|
|
3326
|
+
"@type": "ClaimReview",
|
|
3327
|
+
claimReviewed: opts.claimReviewed,
|
|
3328
|
+
url: opts.url,
|
|
3329
|
+
reviewRating: {
|
|
3330
|
+
"@type": "Rating",
|
|
3331
|
+
ratingValue: opts.ratingValue,
|
|
3332
|
+
bestRating: "5",
|
|
3333
|
+
worstRating: "1"
|
|
3334
|
+
},
|
|
3335
|
+
author: {
|
|
3336
|
+
"@type": "Organization",
|
|
3337
|
+
name: opts.authorName
|
|
3338
|
+
}
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
static buildCarousel(items) {
|
|
3342
|
+
return {
|
|
3343
|
+
"@context": "https://schema.org",
|
|
3344
|
+
"@type": "ItemList",
|
|
3345
|
+
itemListElement: items.map((item) => ({
|
|
3346
|
+
"@type": "ListItem",
|
|
3347
|
+
position: item.position,
|
|
3348
|
+
url: item.url,
|
|
3349
|
+
name: item.name,
|
|
3350
|
+
image: item.image
|
|
3351
|
+
}))
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
static buildQAPage(opts) {
|
|
3355
|
+
return {
|
|
3356
|
+
"@context": "https://schema.org",
|
|
3357
|
+
"@type": "QAPage",
|
|
3358
|
+
mainEntity: {
|
|
3359
|
+
"@type": "Question",
|
|
3360
|
+
name: opts.questionText,
|
|
3361
|
+
text: opts.questionText,
|
|
3362
|
+
author: {
|
|
3363
|
+
"@type": "Person",
|
|
3364
|
+
name: opts.authorName
|
|
3365
|
+
},
|
|
3366
|
+
answerCount: 1,
|
|
3367
|
+
acceptedAnswer: {
|
|
3368
|
+
"@type": "Answer",
|
|
3369
|
+
text: opts.answerText,
|
|
3370
|
+
upvoteCount: opts.upvoteCount ?? 12,
|
|
3371
|
+
author: {
|
|
3372
|
+
"@type": "Person",
|
|
3373
|
+
name: opts.answerAuthor
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3247
3379
|
}
|
|
3248
3380
|
|
|
3249
3381
|
// packages/lynx-seo-engine/src/built-in-locations.ts
|
package/package.json
CHANGED
package/src/engine.test.ts
CHANGED
|
@@ -91,6 +91,52 @@ describe("Extended Schema.org Builder", () => {
|
|
|
91
91
|
expect(schema["@type"]).toBe("Organization");
|
|
92
92
|
expect(schema.sameAs).toContain("https://wikidata.org/wiki/Q123");
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
it("Builds complete Speakable schema for voice search & assistant", () => {
|
|
96
|
+
const speakable = ExtendedSchemaGraphBuilder.buildSpeakable({
|
|
97
|
+
cssSelectors: [".headline", ".direct-answer"],
|
|
98
|
+
});
|
|
99
|
+
expect(speakable["@type"]).toBe("SpeakableSpecification");
|
|
100
|
+
expect(speakable.cssSelector).toEqual([".headline", ".direct-answer"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("Builds complete PodcastEpisode and Restaurant schemas", () => {
|
|
104
|
+
const podcast = ExtendedSchemaGraphBuilder.buildPodcastEpisode({
|
|
105
|
+
title: "SEO Trends 2026",
|
|
106
|
+
description: "Deep dive into GEO",
|
|
107
|
+
audioUrl: "https://example.com/ep1.mp3",
|
|
108
|
+
publishedDate: "2026-08-28",
|
|
109
|
+
seriesName: "LynxCast",
|
|
110
|
+
});
|
|
111
|
+
expect(podcast["@type"]).toBe("PodcastEpisode");
|
|
112
|
+
expect(podcast.name).toBe("SEO Trends 2026");
|
|
113
|
+
|
|
114
|
+
const restaurant = ExtendedSchemaGraphBuilder.buildRestaurant({
|
|
115
|
+
name: "Le Gourmet",
|
|
116
|
+
url: "https://legourmet.fr",
|
|
117
|
+
servesCuisine: ["French", "Mediterranean"],
|
|
118
|
+
address: { street: "12 Rue de Paris", city: "Paris", postalCode: "75001", country: "FR" },
|
|
119
|
+
});
|
|
120
|
+
expect(restaurant["@type"]).toBe("Restaurant");
|
|
121
|
+
expect(restaurant.servesCuisine).toContain("French");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("Builds Book, Dataset, FactCheck, Carousel, and QAPage schemas", () => {
|
|
125
|
+
const book = ExtendedSchemaGraphBuilder.buildBook({ name: "SEO in 2026", author: "LynxFlow" });
|
|
126
|
+
expect(book["@type"]).toBe("Book");
|
|
127
|
+
|
|
128
|
+
const dataset = ExtendedSchemaGraphBuilder.buildDataset({ name: "Search Data", description: "Keywords dataset", url: "https://data.com", keywords: ["seo", "ai"] });
|
|
129
|
+
expect(dataset["@type"]).toBe("Dataset");
|
|
130
|
+
|
|
131
|
+
const factCheck = ExtendedSchemaGraphBuilder.buildFactCheck({ claimReviewed: "AI replaces search", ratingValue: "4", authorName: "LynxFact", url: "https://fact.com" });
|
|
132
|
+
expect(factCheck["@type"]).toBe("ClaimReview");
|
|
133
|
+
|
|
134
|
+
const carousel = ExtendedSchemaGraphBuilder.buildCarousel([{ name: "Item 1", url: "https://item1.com", position: 1 }]);
|
|
135
|
+
expect(carousel["@type"]).toBe("ItemList");
|
|
136
|
+
|
|
137
|
+
const qa = ExtendedSchemaGraphBuilder.buildQAPage({ questionText: "How to rank #1?", authorName: "User1", answerText: "Use LynxSEO.", answerAuthor: "LynxBot" });
|
|
138
|
+
expect(qa["@type"]).toBe("QAPage");
|
|
139
|
+
});
|
|
94
140
|
});
|
|
95
141
|
|
|
96
142
|
describe("Master Programmatic Matrix Engine (English Default)", () => {
|
package/src/extended-schemas.ts
CHANGED
|
@@ -378,4 +378,212 @@ export class ExtendedSchemaGraphBuilder {
|
|
|
378
378
|
: undefined,
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 11. Speakable Schema (Google Assistant & Voice Search) - Rank Math Pro Parity
|
|
384
|
+
*/
|
|
385
|
+
static buildSpeakable(opts: {
|
|
386
|
+
cssSelectors?: string[];
|
|
387
|
+
xpaths?: string[];
|
|
388
|
+
}): Record<string, unknown> {
|
|
389
|
+
return {
|
|
390
|
+
"@type": "SpeakableSpecification",
|
|
391
|
+
cssSelector: opts.cssSelectors || [".headline", ".summary", ".direct-answer", "article h1", "article p:first-of-type"],
|
|
392
|
+
xpath: opts.xpaths,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 12. Podcast & PodcastEpisode Schema
|
|
398
|
+
*/
|
|
399
|
+
static buildPodcastEpisode(opts: {
|
|
400
|
+
title: string;
|
|
401
|
+
description: string;
|
|
402
|
+
audioUrl: string;
|
|
403
|
+
duration?: string;
|
|
404
|
+
publishedDate: string;
|
|
405
|
+
seriesName: string;
|
|
406
|
+
}): Record<string, unknown> {
|
|
407
|
+
return {
|
|
408
|
+
"@context": "https://schema.org",
|
|
409
|
+
"@type": "PodcastEpisode",
|
|
410
|
+
name: opts.title,
|
|
411
|
+
description: opts.description,
|
|
412
|
+
datePublished: opts.publishedDate,
|
|
413
|
+
timeRequired: opts.duration || "PT15M",
|
|
414
|
+
associatedMedia: {
|
|
415
|
+
"@type": "AudioObject",
|
|
416
|
+
contentUrl: opts.audioUrl,
|
|
417
|
+
},
|
|
418
|
+
partOfSeries: {
|
|
419
|
+
"@type": "PodcastSeries",
|
|
420
|
+
name: opts.seriesName,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 13. Restaurant Schema (with Menu & Cuisine)
|
|
427
|
+
*/
|
|
428
|
+
static buildRestaurant(opts: {
|
|
429
|
+
name: string;
|
|
430
|
+
url: string;
|
|
431
|
+
servesCuisine: string[];
|
|
432
|
+
menuUrl?: string;
|
|
433
|
+
priceRange?: string;
|
|
434
|
+
telephone?: string;
|
|
435
|
+
address: { street: string; city: string; postalCode: string; country: string };
|
|
436
|
+
}): Record<string, unknown> {
|
|
437
|
+
return {
|
|
438
|
+
"@context": "https://schema.org",
|
|
439
|
+
"@type": "Restaurant",
|
|
440
|
+
name: opts.name,
|
|
441
|
+
url: opts.url,
|
|
442
|
+
servesCuisine: opts.servesCuisine,
|
|
443
|
+
hasMenu: opts.menuUrl,
|
|
444
|
+
priceRange: opts.priceRange || "€€",
|
|
445
|
+
telephone: opts.telephone,
|
|
446
|
+
address: {
|
|
447
|
+
"@type": "PostalAddress",
|
|
448
|
+
streetAddress: opts.address.street,
|
|
449
|
+
addressLocality: opts.address.city,
|
|
450
|
+
postalCode: opts.address.postalCode,
|
|
451
|
+
addressCountry: opts.address.country,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 14. Book Schema
|
|
458
|
+
*/
|
|
459
|
+
static buildBook(opts: {
|
|
460
|
+
name: string;
|
|
461
|
+
author: string;
|
|
462
|
+
isbn?: string;
|
|
463
|
+
bookFormat?: string;
|
|
464
|
+
url?: string;
|
|
465
|
+
}): Record<string, unknown> {
|
|
466
|
+
return {
|
|
467
|
+
"@context": "https://schema.org",
|
|
468
|
+
"@type": "Book",
|
|
469
|
+
name: opts.name,
|
|
470
|
+
author: {
|
|
471
|
+
"@type": "Person",
|
|
472
|
+
name: opts.author,
|
|
473
|
+
},
|
|
474
|
+
isbn: opts.isbn,
|
|
475
|
+
bookFormat: opts.bookFormat || "https://schema.org/EBook",
|
|
476
|
+
url: opts.url,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 15. Dataset Schema (Data Search & Research)
|
|
482
|
+
*/
|
|
483
|
+
static buildDataset(opts: {
|
|
484
|
+
name: string;
|
|
485
|
+
description: string;
|
|
486
|
+
url: string;
|
|
487
|
+
keywords: string[];
|
|
488
|
+
temporalCoverage?: string;
|
|
489
|
+
distributionUrl?: string;
|
|
490
|
+
}): Record<string, unknown> {
|
|
491
|
+
return {
|
|
492
|
+
"@context": "https://schema.org",
|
|
493
|
+
"@type": "Dataset",
|
|
494
|
+
name: opts.name,
|
|
495
|
+
description: opts.description,
|
|
496
|
+
url: opts.url,
|
|
497
|
+
keywords: opts.keywords,
|
|
498
|
+
temporalCoverage: opts.temporalCoverage || "2024/2026",
|
|
499
|
+
distribution: opts.distributionUrl
|
|
500
|
+
? [
|
|
501
|
+
{
|
|
502
|
+
"@type": "DataDownload",
|
|
503
|
+
encodingFormat: "application/json",
|
|
504
|
+
contentUrl: opts.distributionUrl,
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
: undefined,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* 16. FactCheck Schema (Google ClaimReview)
|
|
513
|
+
*/
|
|
514
|
+
static buildFactCheck(opts: {
|
|
515
|
+
claimReviewed: string;
|
|
516
|
+
ratingValue: string;
|
|
517
|
+
authorName: string;
|
|
518
|
+
url: string;
|
|
519
|
+
}): Record<string, unknown> {
|
|
520
|
+
return {
|
|
521
|
+
"@context": "https://schema.org",
|
|
522
|
+
"@type": "ClaimReview",
|
|
523
|
+
claimReviewed: opts.claimReviewed,
|
|
524
|
+
url: opts.url,
|
|
525
|
+
reviewRating: {
|
|
526
|
+
"@type": "Rating",
|
|
527
|
+
ratingValue: opts.ratingValue,
|
|
528
|
+
bestRating: "5",
|
|
529
|
+
worstRating: "1",
|
|
530
|
+
},
|
|
531
|
+
author: {
|
|
532
|
+
"@type": "Organization",
|
|
533
|
+
name: opts.authorName,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* 17. Carousel / ItemList Schema
|
|
540
|
+
*/
|
|
541
|
+
static buildCarousel(items: Array<{ name: string; url: string; position: number; image?: string }>): Record<string, unknown> {
|
|
542
|
+
return {
|
|
543
|
+
"@context": "https://schema.org",
|
|
544
|
+
"@type": "ItemList",
|
|
545
|
+
itemListElement: items.map((item) => ({
|
|
546
|
+
"@type": "ListItem",
|
|
547
|
+
position: item.position,
|
|
548
|
+
url: item.url,
|
|
549
|
+
name: item.name,
|
|
550
|
+
image: item.image,
|
|
551
|
+
})),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* 18. QAPage Schema (bbPress / Forums / Community QA)
|
|
557
|
+
*/
|
|
558
|
+
static buildQAPage(opts: {
|
|
559
|
+
questionText: string;
|
|
560
|
+
authorName: string;
|
|
561
|
+
answerText: string;
|
|
562
|
+
answerAuthor: string;
|
|
563
|
+
upvoteCount?: number;
|
|
564
|
+
}): Record<string, unknown> {
|
|
565
|
+
return {
|
|
566
|
+
"@context": "https://schema.org",
|
|
567
|
+
"@type": "QAPage",
|
|
568
|
+
mainEntity: {
|
|
569
|
+
"@type": "Question",
|
|
570
|
+
name: opts.questionText,
|
|
571
|
+
text: opts.questionText,
|
|
572
|
+
author: {
|
|
573
|
+
"@type": "Person",
|
|
574
|
+
name: opts.authorName,
|
|
575
|
+
},
|
|
576
|
+
answerCount: 1,
|
|
577
|
+
acceptedAnswer: {
|
|
578
|
+
"@type": "Answer",
|
|
579
|
+
text: opts.answerText,
|
|
580
|
+
upvoteCount: opts.upvoteCount ?? 12,
|
|
581
|
+
author: {
|
|
582
|
+
"@type": "Person",
|
|
583
|
+
name: opts.answerAuthor,
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
381
589
|
}
|