@lynxflow/seo-engine 1.8.30 → 2.0.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.
- package/README.md +60 -117
- package/dist/ai-copilot-client.d.ts +2 -0
- package/dist/built-in-locations.d.ts +22 -0
- package/dist/content-ai-arsenal.d.ts +28 -0
- package/dist/existing-media-harvester.d.ts +33 -0
- package/dist/extended-schemas.d.ts +84 -0
- package/dist/features-knowledge-harvester.d.ts +56 -0
- package/dist/geo-citation-scorer.d.ts +34 -0
- package/dist/i18n-detector.d.ts +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +567 -83
- package/dist/index.mjs +2062 -169
- package/dist/internal-pagerank-graph.d.ts +59 -0
- package/dist/knowledge-bank-builder.d.ts +91 -0
- package/dist/marketing-skills-engine.d.ts +74 -0
- package/dist/matrix-engine.d.ts +29 -0
- package/dist/power-words-psychology.d.ts +27 -15
- package/dist/public-manifest-engine.d.ts +58 -0
- package/dist/rag-knowledge-engine.d.ts +50 -0
- package/dist/rate-limited-translator.d.ts +19 -0
- package/dist/token-quota-manager.d.ts +4 -0
- package/package.json +11 -3
- package/TUTORIEL_INTEGRATION_SITE.md +0 -283
- package/connectors/cloudflare-worker/worker.js +0 -106
- package/connectors/laravel/LynxSeoController.php +0 -37
- package/connectors/wordpress/lynxseo-admin-app.js +0 -2611
- package/connectors/wordpress/lynxseo-connector.php +0 -1712
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/src/ad-intelligence-cro.ts +0 -140
- package/src/ai-bots-log-analyzer.ts +0 -127
- package/src/ai-copilot-client.ts +0 -99
- package/src/analytics-client.ts +0 -315
- package/src/auth-key.ts +0 -262
- package/src/backlinks-client.ts +0 -85
- package/src/brand-dna-calendar.ts +0 -120
- package/src/brand-icons.ts +0 -138
- package/src/built-in-locations.ts +0 -170
- package/src/content-ai-arsenal.ts +0 -268
- package/src/copy-frameworks-master.ts +0 -89
- package/src/cro-copywriting-engine.ts +0 -105
- package/src/crosslink-scorer.ts +0 -101
- package/src/embeddable-seo-widget.ts +0 -57
- package/src/engine.test.ts +0 -386
- package/src/engine.ts +0 -813
- package/src/existing-media-harvester.ts +0 -98
- package/src/extended-schemas.ts +0 -589
- package/src/features-knowledge-harvester.ts +0 -190
- package/src/free-public-tools.ts +0 -224
- package/src/geo-mesh-linking.ts +0 -93
- package/src/google-business-profile.ts +0 -280
- package/src/i18n-detector.ts +0 -98
- package/src/i18n-dictionary.ts +0 -422
- package/src/index.ts +0 -164
- package/src/indexnow-client.ts +0 -94
- package/src/instant-matrix-search.ts +0 -48
- package/src/isr-cache-manager.ts +0 -78
- package/src/keyword-permutator.ts +0 -118
- package/src/knowledge-bank-builder.ts +0 -299
- package/src/knowledge-graph-linker.ts +0 -81
- package/src/lago-token-meter.ts +0 -120
- package/src/legal-disclaimers.ts +0 -407
- package/src/llm-content-cleaner.ts +0 -64
- package/src/llm-prompt.ts +0 -323
- package/src/master-marketing-engine.ts +0 -314
- package/src/matrix-engine.ts +0 -1204
- package/src/mcp-seo-server.ts +0 -108
- package/src/ngram-density-analyzer.ts +0 -108
- package/src/og-image-generator.ts +0 -109
- package/src/power-words-psychology.ts +0 -369
- package/src/public-manifest-engine.ts +0 -109
- package/src/rag-knowledge-engine.ts +0 -163
- package/src/rank-math-parity.ts +0 -235
- package/src/rate-limited-translator.ts +0 -110
- package/src/real-reviews-sync.ts +0 -207
- package/src/rss-syndication-feed.ts +0 -56
- package/src/schema-builder.ts +0 -134
- package/src/seo-opportunities-decay.ts +0 -154
- package/src/serp-client.ts +0 -89
- package/src/serp-history-alerts.ts +0 -75
- package/src/site-auditor.ts +0 -83
- package/src/site-crawler.ts +0 -406
- package/src/slug-engine.ts +0 -183
- package/src/social-ads-seo.ts +0 -211
- package/src/social-growth-suite.ts +0 -310
- package/src/social-trend-seo.ts +0 -103
- package/src/social-video-seo.ts +0 -100
- package/src/team-rbac.ts +0 -61
- package/src/technical-rules-auditor.ts +0 -171
- package/src/token-quota-manager.ts +0 -91
- package/src/types.ts +0 -143
- package/src/ui-icons.ts +0 -134
- package/src/urlytics-engine.ts +0 -97
- package/src/video-youtube-analyzer.ts +0 -81
- package/src/yoast-parity.ts +0 -161
- package/tsconfig.json +0 -15
package/src/ui-icons.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🎨 Universal Zero-Dependency UI & Feature SVG Icons
|
|
3
|
-
* Provides crisp, modern Lucide-style vector icons for features, trust badges, and UI components.
|
|
4
|
-
* Includes automatic semantic keyword-to-icon detection for feature descriptions.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface UiIcon {
|
|
8
|
-
name: string;
|
|
9
|
-
viewBox: string;
|
|
10
|
-
svgPath: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const UI_ICONS: Record<string, UiIcon> = {
|
|
14
|
-
zap: {
|
|
15
|
-
name: "Zap / Lightning",
|
|
16
|
-
viewBox: "0 0 24 24",
|
|
17
|
-
svgPath: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
18
|
-
},
|
|
19
|
-
shield: {
|
|
20
|
-
name: "Shield / Security",
|
|
21
|
-
viewBox: "0 0 24 24",
|
|
22
|
-
svgPath: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
23
|
-
},
|
|
24
|
-
star: {
|
|
25
|
-
name: "Star / Rating",
|
|
26
|
-
viewBox: "0 0 24 24",
|
|
27
|
-
svgPath: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor"/>',
|
|
28
|
-
},
|
|
29
|
-
chart: {
|
|
30
|
-
name: "Chart / Analytics",
|
|
31
|
-
viewBox: "0 0 24 24",
|
|
32
|
-
svgPath: '<line x1="18" y1="20" x2="18" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="12" y1="20" x2="12" y2="4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="6" y1="20" x2="6" y2="14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
|
|
33
|
-
},
|
|
34
|
-
calendar: {
|
|
35
|
-
name: "Calendar / Scheduling",
|
|
36
|
-
viewBox: "0 0 24 24",
|
|
37
|
-
svgPath: '<rect x="3" y="4" width="18" height="18" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><line x1="16" y1="2" x2="16" y2="6" stroke="currentColor" stroke-width="2"/><line x1="8" y1="2" x2="8" y2="6" stroke="currentColor" stroke-width="2"/><line x1="3" y1="10" x2="21" y2="10" stroke="currentColor" stroke-width="2"/>',
|
|
38
|
-
},
|
|
39
|
-
users: {
|
|
40
|
-
name: "Users / Team",
|
|
41
|
-
viewBox: "0 0 24 24",
|
|
42
|
-
svgPath: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="9" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M23 21v-2a4 4 0 0 0-3-3.87" fill="none" stroke="currentColor" stroke-width="2"/><path d="M16 3.13a4 4 0 0 1 0 7.75" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
43
|
-
},
|
|
44
|
-
rocket: {
|
|
45
|
-
name: "Rocket / Speed",
|
|
46
|
-
viewBox: "0 0 24 24",
|
|
47
|
-
svgPath: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" fill="none" stroke="currentColor" stroke-width="2"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
48
|
-
},
|
|
49
|
-
sparkles: {
|
|
50
|
-
name: "Sparkles / AI",
|
|
51
|
-
viewBox: "0 0 24 24",
|
|
52
|
-
svgPath: '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>',
|
|
53
|
-
},
|
|
54
|
-
check: {
|
|
55
|
-
name: "Check / Success",
|
|
56
|
-
viewBox: "0 0 24 24",
|
|
57
|
-
svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
58
|
-
},
|
|
59
|
-
lock: {
|
|
60
|
-
name: "Lock / Privacy",
|
|
61
|
-
viewBox: "0 0 24 24",
|
|
62
|
-
svgPath: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
63
|
-
},
|
|
64
|
-
clock: {
|
|
65
|
-
name: "Clock / 24-7",
|
|
66
|
-
viewBox: "0 0 24 24",
|
|
67
|
-
svgPath: '<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/><polyline points="12 6 12 12 16 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
|
|
68
|
-
},
|
|
69
|
-
phone: {
|
|
70
|
-
name: "Phone / Contact",
|
|
71
|
-
viewBox: "0 0 24 24",
|
|
72
|
-
svgPath: '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
73
|
-
},
|
|
74
|
-
mapPin: {
|
|
75
|
-
name: "Map Pin / Local",
|
|
76
|
-
viewBox: "0 0 24 24",
|
|
77
|
-
svgPath: '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="10" r="3" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
78
|
-
},
|
|
79
|
-
euro: {
|
|
80
|
-
name: "Euro / Pricing",
|
|
81
|
-
viewBox: "0 0 24 24",
|
|
82
|
-
svgPath: '<path d="M4 10h12M4 14h9M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12a7.9 7.9 0 0 0 7.8 8 7.7 7.7 0 0 0 5.2-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
|
|
83
|
-
},
|
|
84
|
-
arrowRight: {
|
|
85
|
-
name: "Arrow Right",
|
|
86
|
-
viewBox: "0 0 24 24",
|
|
87
|
-
svgPath: '<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><polyline points="12 5 19 12 12 19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns raw inline SVG markup for a standard UI icon.
|
|
93
|
-
*/
|
|
94
|
-
export function renderUiIconSvg(iconName: string, className: string = "w-5 h-5 inline-block"): string {
|
|
95
|
-
const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
|
|
96
|
-
return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Semantically resolves the most relevant UI icon based on feature text keywords.
|
|
101
|
-
*/
|
|
102
|
-
export function resolveFeatureIcon(featureText: string): string {
|
|
103
|
-
const lower = featureText.toLowerCase();
|
|
104
|
-
|
|
105
|
-
if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
|
|
106
|
-
return "shield";
|
|
107
|
-
}
|
|
108
|
-
if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
|
|
109
|
-
return "zap";
|
|
110
|
-
}
|
|
111
|
-
if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
|
|
112
|
-
return "sparkles";
|
|
113
|
-
}
|
|
114
|
-
if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
|
|
115
|
-
return "chart";
|
|
116
|
-
}
|
|
117
|
-
if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
|
|
118
|
-
return "calendar";
|
|
119
|
-
}
|
|
120
|
-
if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
|
|
121
|
-
return "users";
|
|
122
|
-
}
|
|
123
|
-
if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
|
|
124
|
-
return "euro";
|
|
125
|
-
}
|
|
126
|
-
if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
|
|
127
|
-
return "mapPin";
|
|
128
|
-
}
|
|
129
|
-
if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
|
|
130
|
-
return "clock";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return "zap";
|
|
134
|
-
}
|
package/src/urlytics-engine.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🔍 Universal URL Analytics & Structural Decomposition Engine
|
|
3
|
-
* Inspired by advertools.urlytics
|
|
4
|
-
*
|
|
5
|
-
* Breaks down any URL into structured path segments, query parameters, directory depths,
|
|
6
|
-
* and slug tokens for crawl validation, audit logs, and programmatic matrix matching.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export interface ParsedUrlStructure {
|
|
10
|
-
url: string;
|
|
11
|
-
scheme: string;
|
|
12
|
-
domain: string;
|
|
13
|
-
path: string;
|
|
14
|
-
depth: number;
|
|
15
|
-
dir1?: string;
|
|
16
|
-
dir2?: string;
|
|
17
|
-
dir3?: string;
|
|
18
|
-
lastDir: string;
|
|
19
|
-
queryParams: Record<string, string>;
|
|
20
|
-
hashFragment?: string;
|
|
21
|
-
slugTokens: string[];
|
|
22
|
-
charCount: number;
|
|
23
|
-
hasTrailingSlash: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class UrlyticsEngine {
|
|
27
|
-
/**
|
|
28
|
-
* Parses a single URL into its granular structural components.
|
|
29
|
-
*/
|
|
30
|
-
static parseUrl(rawUrl: string): ParsedUrlStructure {
|
|
31
|
-
if (!rawUrl || typeof rawUrl !== "string") {
|
|
32
|
-
return {
|
|
33
|
-
url: "",
|
|
34
|
-
scheme: "",
|
|
35
|
-
domain: "",
|
|
36
|
-
path: "/",
|
|
37
|
-
depth: 0,
|
|
38
|
-
lastDir: "",
|
|
39
|
-
queryParams: {},
|
|
40
|
-
slugTokens: [],
|
|
41
|
-
charCount: 0,
|
|
42
|
-
hasTrailingSlash: false,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
|
|
48
|
-
const pathClean = parsed.pathname.replace(/\/+$/, "");
|
|
49
|
-
const segments = pathClean.split("/").filter(Boolean);
|
|
50
|
-
const queryParams: Record<string, string> = {};
|
|
51
|
-
|
|
52
|
-
parsed.searchParams.forEach((val, key) => {
|
|
53
|
-
queryParams[key] = val;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
|
|
57
|
-
const slugTokens = lastDir.split("-").filter(Boolean);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
url: rawUrl,
|
|
61
|
-
scheme: parsed.protocol.replace(":", ""),
|
|
62
|
-
domain: parsed.hostname,
|
|
63
|
-
path: parsed.pathname,
|
|
64
|
-
depth: segments.length,
|
|
65
|
-
dir1: segments[0],
|
|
66
|
-
dir2: segments[1],
|
|
67
|
-
dir3: segments[2],
|
|
68
|
-
lastDir,
|
|
69
|
-
queryParams,
|
|
70
|
-
hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
|
|
71
|
-
slugTokens,
|
|
72
|
-
charCount: rawUrl.length,
|
|
73
|
-
hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/"),
|
|
74
|
-
};
|
|
75
|
-
} catch {
|
|
76
|
-
return {
|
|
77
|
-
url: rawUrl,
|
|
78
|
-
scheme: "unknown",
|
|
79
|
-
domain: "",
|
|
80
|
-
path: rawUrl,
|
|
81
|
-
depth: 0,
|
|
82
|
-
lastDir: rawUrl,
|
|
83
|
-
queryParams: {},
|
|
84
|
-
slugTokens: [rawUrl],
|
|
85
|
-
charCount: rawUrl.length,
|
|
86
|
-
hasTrailingSlash: false,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Batch processes an array of URLs for comparative directory analysis.
|
|
93
|
-
*/
|
|
94
|
-
static analyzeUrls(urls: string[]): ParsedUrlStructure[] {
|
|
95
|
-
return urls.map((u) => this.parseUrl(u));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export interface YouTubeVideoMetadata {
|
|
2
|
-
videoId: string;
|
|
3
|
-
title: string;
|
|
4
|
-
description: string;
|
|
5
|
-
uploadDate?: string;
|
|
6
|
-
duration?: string; // ISO 8601 e.g. "PT5M30S"
|
|
7
|
-
thumbnailUrl?: string;
|
|
8
|
-
embedUrl?: string;
|
|
9
|
-
hasChapters?: boolean;
|
|
10
|
-
chapters?: Array<{ time: number; title: string }>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class VideoYouTubeAnalyzer {
|
|
14
|
-
/**
|
|
15
|
-
* Extracts YouTube Video ID from any standard URL or iframe string
|
|
16
|
-
*/
|
|
17
|
-
static extractVideoId(urlOrIframe: string): string | null {
|
|
18
|
-
const regExp = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
19
|
-
const match = urlOrIframe.match(regExp);
|
|
20
|
-
return match ? match[1] : null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generates Schema.org VideoObject JSON-LD for rich snippets on Google Video Carousel
|
|
25
|
-
*/
|
|
26
|
-
static generateVideoObjectSchema(meta: YouTubeVideoMetadata) {
|
|
27
|
-
const videoId = meta.videoId;
|
|
28
|
-
const thumbnail = meta.thumbnailUrl || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
|
29
|
-
const embedUrl = meta.embedUrl || `https://www.youtube.com/embed/${videoId}`;
|
|
30
|
-
|
|
31
|
-
const schema: Record<string, any> = {
|
|
32
|
-
"@context": "https://schema.org",
|
|
33
|
-
"@type": "VideoObject",
|
|
34
|
-
"name": meta.title,
|
|
35
|
-
"description": meta.description,
|
|
36
|
-
"thumbnailUrl": [thumbnail],
|
|
37
|
-
"uploadDate": meta.uploadDate || new Date().toISOString().split("T")[0],
|
|
38
|
-
"embedUrl": embedUrl,
|
|
39
|
-
"contentUrl": `https://www.youtube.com/watch?v=${videoId}`
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
if (meta.duration) {
|
|
43
|
-
schema.duration = meta.duration;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (meta.chapters && meta.chapters.length > 0) {
|
|
47
|
-
schema.hasPart = meta.chapters.map(c => ({
|
|
48
|
-
"@type": "Clip",
|
|
49
|
-
"name": c.title,
|
|
50
|
-
"startOffset": c.time,
|
|
51
|
-
"url": `https://www.youtube.com/watch?v=${videoId}&t=${c.time}s`
|
|
52
|
-
}));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return schema;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Generates responsive, lazy-loaded, SEO-optimized HTML embed with Schema markup
|
|
60
|
-
*/
|
|
61
|
-
static generateSeoVideoEmbedHtml(meta: YouTubeVideoMetadata): string {
|
|
62
|
-
const videoId = meta.videoId;
|
|
63
|
-
const jsonLd = JSON.stringify(this.generateVideoObjectSchema(meta));
|
|
64
|
-
|
|
65
|
-
return `
|
|
66
|
-
<div class="lynxseo-video-wrapper" style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,0.08);margin:24px 0;">
|
|
67
|
-
<iframe
|
|
68
|
-
src="https://www.youtube.com/embed/${videoId}"
|
|
69
|
-
title="${meta.title.replace(/"/g, '"')}"
|
|
70
|
-
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
|
|
71
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
72
|
-
allowfullscreen
|
|
73
|
-
loading="lazy">
|
|
74
|
-
</iframe>
|
|
75
|
-
</div>
|
|
76
|
-
<script type="application/ld+json">
|
|
77
|
-
${jsonLd}
|
|
78
|
-
</script>
|
|
79
|
-
`.trim();
|
|
80
|
-
}
|
|
81
|
-
}
|
package/src/yoast-parity.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🟢 Yoast SEO Parity & Real-Time Readability Engine
|
|
3
|
-
*
|
|
4
|
-
* Implements the iconic Yoast SEO features (13M+ installs):
|
|
5
|
-
* 1. Readability Analysis (Analyse de Lisibilité aux pastilles Vertes/Oranges/Rouges) :
|
|
6
|
-
* - Flesch Reading Ease Formula (Score de lisibilité)
|
|
7
|
-
* - Transition Words Detection (Mots de transition > 30%)
|
|
8
|
-
* - Passive Voice Detection (Voix passive < 10%)
|
|
9
|
-
* - Sentence Length Distribution (Phrases > 20 mots < 25%)
|
|
10
|
-
* - Paragraph Length (< 150 mots par paragraphe)
|
|
11
|
-
* - Subheading Distribution (< 300 mots sous un sous-titre)
|
|
12
|
-
* 2. Google SERP Pixel Width Calculator (Titre max 600px, Description max 960px)
|
|
13
|
-
* 3. Cornerstone Content (Contenu Pilier & Priorisation du Maillage)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export interface YoastReadabilityBullet {
|
|
17
|
-
id: string;
|
|
18
|
-
label: string;
|
|
19
|
-
status: "green" | "orange" | "red"; // Pastille verte, orange ou rouge
|
|
20
|
-
scoreValue: number;
|
|
21
|
-
message: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface YoastAnalysisResult {
|
|
25
|
-
overallReadability: "green" | "orange" | "red";
|
|
26
|
-
fleschReadingScore: number; // 0 to 100 (60-70 = standard/facile)
|
|
27
|
-
fleschGrade: string;
|
|
28
|
-
transitionWordsPercent: number;
|
|
29
|
-
passiveVoicePercent: number;
|
|
30
|
-
longSentencesPercent: number;
|
|
31
|
-
bullets: YoastReadabilityBullet[];
|
|
32
|
-
serpPreview: {
|
|
33
|
-
titlePixelWidth: number;
|
|
34
|
-
titlePixelLimit: 600;
|
|
35
|
-
titleIsTruncated: boolean;
|
|
36
|
-
descPixelWidth: number;
|
|
37
|
-
descPixelLimit: 960;
|
|
38
|
-
descIsTruncated: boolean;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class YoastParityEngine {
|
|
43
|
-
private static frenchTransitionWords = [
|
|
44
|
-
"en effet", "de plus", "par conséquent", "cependant", "ainsi", "notamment", "en outre",
|
|
45
|
-
"d'ailleurs", "toutefois", "en revanche", "néanmoins", "c'est pourquoi", "en conclusion",
|
|
46
|
-
"par exemple", "premièrement", "deuxièmement", "en fait", "grâce à", "donc", "car", "puisque"
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 1. Full Yoast-Style Readability Analysis.
|
|
51
|
-
*/
|
|
52
|
-
static analyzeReadability(text: string, title = "", description = ""): YoastAnalysisResult {
|
|
53
|
-
const plainText = text.replace(/<[^>]+>/g, " ").replace(/[#*`_~]/g, " ").trim();
|
|
54
|
-
const sentences = plainText.split(/[.!?]+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
55
|
-
const words = plainText.split(/\s+/).filter((w) => w.length > 0);
|
|
56
|
-
const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
57
|
-
|
|
58
|
-
const totalWords = Math.max(1, words.length);
|
|
59
|
-
const totalSentences = Math.max(1, sentences.length);
|
|
60
|
-
|
|
61
|
-
// 1. Flesch Reading Ease (Formule adaptée au français : 207 - (1.015 * ASL) - (84.6 * ASW))
|
|
62
|
-
const avgSentenceLength = totalWords / totalSentences;
|
|
63
|
-
// Estimation des syllabes (~1.4 syllabes par mot en français moyen)
|
|
64
|
-
const estimatedSyllables = words.reduce((acc, word) => acc + Math.max(1, word.length / 3), 0);
|
|
65
|
-
const avgSyllablesPerWord = estimatedSyllables / totalWords;
|
|
66
|
-
const fleschScore = Math.max(0, Math.min(100, Math.round(207 - (1.015 * avgSentenceLength) - (84.6 * avgSyllablesPerWord))));
|
|
67
|
-
|
|
68
|
-
let fleschGrade = "Très facile à lire";
|
|
69
|
-
if (fleschScore < 30) fleschGrade = "Très difficile (académique)";
|
|
70
|
-
else if (fleschScore < 50) fleschGrade = "Difficile";
|
|
71
|
-
else if (fleschScore < 60) fleschGrade = "Moyen";
|
|
72
|
-
else if (fleschScore < 70) fleschGrade = "Facile (Idéal pour le web)";
|
|
73
|
-
|
|
74
|
-
// 2. Transition Words Detection (> 30% des phrases)
|
|
75
|
-
let transitionCount = 0;
|
|
76
|
-
for (const sentence of sentences) {
|
|
77
|
-
const lower = sentence.toLowerCase();
|
|
78
|
-
if (this.frenchTransitionWords.some((tw) => lower.includes(tw))) {
|
|
79
|
-
transitionCount++;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const transitionPercent = Math.round((transitionCount / totalSentences) * 100);
|
|
83
|
-
|
|
84
|
-
// 3. Long Sentences (> 20 mots)
|
|
85
|
-
const longSentences = sentences.filter((s) => s.split(/\s+/).length > 20).length;
|
|
86
|
-
const longSentencesPercent = Math.round((longSentences / totalSentences) * 100);
|
|
87
|
-
|
|
88
|
-
// 4. Passive Voice Approximation (formules passives en français : être + participe)
|
|
89
|
-
const passiveRegex = /\b(est|sont|été|fut|furent|sera|seront|était|étaient)\s+([a-zéèêëàâîïôûüç]+(é|és|ée|ées|i|is|ie|ies|u|us|ue|ues))\b/gi;
|
|
90
|
-
const passiveMatches = plainText.match(passiveRegex) || [];
|
|
91
|
-
const passivePercent = Math.round((passiveMatches.length / totalSentences) * 100);
|
|
92
|
-
|
|
93
|
-
// Bullets checklist (Pastilles Yoast)
|
|
94
|
-
const bullets: YoastReadabilityBullet[] = [];
|
|
95
|
-
|
|
96
|
-
// Bullet Flesch
|
|
97
|
-
bullets.push({
|
|
98
|
-
id: "flesch_reading_ease",
|
|
99
|
-
label: "Facilité de lecture Flesch",
|
|
100
|
-
status: fleschScore >= 55 ? "green" : fleschScore >= 45 ? "orange" : "red",
|
|
101
|
-
scoreValue: fleschScore,
|
|
102
|
-
message: `Score Flesch : ${fleschScore}/100 (${fleschGrade}).`,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Bullet Transitions
|
|
106
|
-
bullets.push({
|
|
107
|
-
id: "transition_words",
|
|
108
|
-
label: "Mots de transition",
|
|
109
|
-
status: transitionPercent >= 30 ? "green" : transitionPercent >= 20 ? "orange" : "red",
|
|
110
|
-
scoreValue: transitionPercent,
|
|
111
|
-
message: `${transitionPercent}% des phrases contiennent des mots de transition (Recommandé : > 30%).`,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Bullet Sentence Length
|
|
115
|
-
bullets.push({
|
|
116
|
-
id: "sentence_length",
|
|
117
|
-
label: "Longueur des phrases",
|
|
118
|
-
status: longSentencesPercent <= 25 ? "green" : longSentencesPercent <= 35 ? "orange" : "red",
|
|
119
|
-
scoreValue: longSentencesPercent,
|
|
120
|
-
message: `${longSentencesPercent}% des phrases dépassent 20 mots (Recommandé : < 25%).`,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Bullet Passive Voice
|
|
124
|
-
bullets.push({
|
|
125
|
-
id: "passive_voice",
|
|
126
|
-
label: "Voix passive",
|
|
127
|
-
status: passivePercent <= 10 ? "green" : passivePercent <= 18 ? "orange" : "red",
|
|
128
|
-
scoreValue: passivePercent,
|
|
129
|
-
message: `${passivePercent}% de voix passive (Recommandé : < 10%).`,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Overall status
|
|
133
|
-
const redCount = bullets.filter((b) => b.status === "red").length;
|
|
134
|
-
const orangeCount = bullets.filter((b) => b.status === "orange").length;
|
|
135
|
-
let overallReadability: "green" | "orange" | "red" = "green";
|
|
136
|
-
if (redCount >= 2) overallReadability = "red";
|
|
137
|
-
else if (redCount === 1 || orangeCount >= 2) overallReadability = "orange";
|
|
138
|
-
|
|
139
|
-
// 5. SERP Pixel Width (Approximation : ~9.5px par caractère pour le titre Arial 18px)
|
|
140
|
-
const titlePixelWidth = Math.round(title.length * 9.2);
|
|
141
|
-
const descPixelWidth = Math.round(description.length * 5.8);
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
overallReadability,
|
|
145
|
-
fleschReadingScore: fleschScore,
|
|
146
|
-
fleschGrade,
|
|
147
|
-
transitionWordsPercent: transitionPercent,
|
|
148
|
-
passiveVoicePercent: passivePercent,
|
|
149
|
-
longSentencesPercent: longSentencesPercent,
|
|
150
|
-
bullets,
|
|
151
|
-
serpPreview: {
|
|
152
|
-
titlePixelWidth,
|
|
153
|
-
titlePixelLimit: 600,
|
|
154
|
-
titleIsTruncated: titlePixelWidth > 600,
|
|
155
|
-
descPixelWidth,
|
|
156
|
-
descPixelLimit: 960,
|
|
157
|
-
descIsTruncated: descPixelWidth > 960,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"emitDeclarationOnly": true,
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"outDir": "./dist",
|
|
10
|
-
"strict": true,
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*"],
|
|
14
|
-
"exclude": ["src/**/*.test.ts"]
|
|
15
|
-
}
|