sanity-plugin-seofields 1.8.0 → 1.9.0
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 +437 -26
- package/dist/SeoHealthTool-2COI27KI.cjs +8 -0
- package/dist/SeoHealthTool-2COI27KI.cjs.map +1 -0
- package/dist/SeoHealthTool-CWI3KB2V.js +8 -0
- package/dist/SeoHealthTool-CWI3KB2V.js.map +1 -0
- package/dist/{SeoPreview-3EXR6FD4.cjs → SeoPreview-LMZAWWOE.cjs} +35 -38
- package/dist/SeoPreview-LMZAWWOE.cjs.map +1 -0
- package/dist/{SeoPreview-4PPAF4NO.js → SeoPreview-UNQBKTQO.js} +14 -11
- package/dist/SeoPreview-UNQBKTQO.js.map +1 -0
- package/dist/chunk-3OK3S2F7.cjs +673 -0
- package/dist/chunk-3OK3S2F7.cjs.map +1 -0
- package/dist/{chunk-B5IVI5LP.cjs → chunk-5XTQRILL.cjs} +286 -236
- package/dist/chunk-5XTQRILL.cjs.map +1 -0
- package/dist/{chunk-254YHUN3.cjs → chunk-7N4MLTMR.js} +8 -6
- package/dist/chunk-7N4MLTMR.js.map +1 -0
- package/dist/{chunk-DDAAVRWG.js → chunk-A57XNQC7.cjs} +9 -4
- package/dist/chunk-A57XNQC7.cjs.map +1 -0
- package/dist/chunk-BWLJDK5J.js +624 -0
- package/dist/chunk-BWLJDK5J.js.map +1 -0
- package/dist/chunk-FUUWBQE2.cjs +195 -0
- package/dist/chunk-FUUWBQE2.cjs.map +1 -0
- package/dist/{chunk-ZBHLMQTS.cjs → chunk-HHO2AKAP.js} +44 -17
- package/dist/chunk-HHO2AKAP.js.map +1 -0
- package/dist/chunk-KIEO6QG3.js +195 -0
- package/dist/chunk-KIEO6QG3.js.map +1 -0
- package/dist/chunk-R2U7JF7U.cjs +624 -0
- package/dist/chunk-R2U7JF7U.cjs.map +1 -0
- package/dist/{chunk-VR44E62I.js → chunk-XZKQWV2H.js} +204 -33
- package/dist/chunk-XZKQWV2H.js.map +1 -0
- package/dist/{chunk-HDZZQCH7.js → chunk-Y46ACXM4.cjs} +45 -5
- package/dist/chunk-Y46ACXM4.cjs.map +1 -0
- package/dist/chunk-Z5AOUU4H.js +673 -0
- package/dist/chunk-Z5AOUU4H.js.map +1 -0
- package/dist/cli.js +27 -27
- package/dist/{component-DEXwtemT.d.ts → component-CjT1hvxh.d.ts} +2 -2
- package/dist/{component-BzI-LHPw.d.cts → component-WYh0z8as.d.cts} +2 -2
- package/dist/define-cli.cjs +2 -4
- package/dist/define-cli.cjs.map +1 -1
- package/dist/define-cli.js +4 -4
- package/dist/define-cli.js.map +1 -1
- package/dist/head.cjs +14 -0
- package/dist/head.cjs.map +1 -0
- package/dist/head.d.cts +254 -0
- package/dist/head.d.ts +254 -0
- package/dist/head.js +14 -0
- package/dist/head.js.map +1 -0
- package/dist/index.cjs +978 -367
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -374
- package/dist/index.d.ts +6 -374
- package/dist/index.js +920 -286
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +204 -455
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +8 -206
- package/dist/next.d.ts +8 -206
- package/dist/next.js +181 -111
- package/dist/next.js.map +1 -1
- package/dist/plugin-DQdThyxG.d.ts +449 -0
- package/dist/plugin-DoGeZP79.d.cts +449 -0
- package/dist/schema/next.cjs +173 -327
- package/dist/schema/next.cjs.map +1 -1
- package/dist/schema/next.d.cts +4 -4
- package/dist/schema/next.d.ts +4 -4
- package/dist/schema/next.js +172 -8
- package/dist/schema/next.js.map +1 -1
- package/dist/schema.cjs +376 -517
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +5 -5
- package/dist/schema.d.ts +5 -5
- package/dist/schema.js +216 -11
- package/dist/schema.js.map +1 -1
- package/dist/server.cjs +149 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +102 -0
- package/dist/server.d.ts +102 -0
- package/dist/server.js +149 -0
- package/dist/server.js.map +1 -0
- package/dist/{types-CW8qFdAn.d.ts → types-BAdN1RMu.d.ts} +2 -2
- package/dist/{types-B_vjMPtM.d.cts → types-C5i3KMgs.d.cts} +2 -2
- package/dist/{types-BXSCbl6p.d.cts → types-DP-DiW6f.d.cts} +2 -2
- package/dist/{types-C19cQx5D.d.ts → types-DRLUGLtX.d.ts} +2 -2
- package/dist/{types-yVmQfby9.d.cts → types-DxlPXihz.d.cts} +1 -1
- package/dist/{types-yVmQfby9.d.ts → types-DxlPXihz.d.ts} +1 -1
- package/package.json +25 -4
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js +0 -4
- package/dist/SeoHealthDashboard-AEKVVMUR-7NWV3C3Y.js.map +0 -1
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs +0 -10
- package/dist/SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs.map +0 -1
- package/dist/SeoHealthTool-2KKQJLJL.cjs +0 -11
- package/dist/SeoHealthTool-2KKQJLJL.cjs.map +0 -1
- package/dist/SeoHealthTool-3CUKGADZ.js +0 -5
- package/dist/SeoHealthTool-3CUKGADZ.js.map +0 -1
- package/dist/SeoPreview-3EXR6FD4.cjs.map +0 -1
- package/dist/SeoPreview-4PPAF4NO.js.map +0 -1
- package/dist/chunk-254YHUN3.cjs.map +0 -1
- package/dist/chunk-27XSYENH.js +0 -2215
- package/dist/chunk-27XSYENH.js.map +0 -1
- package/dist/chunk-B5IVI5LP.cjs.map +0 -1
- package/dist/chunk-BNXMFD3T.cjs +0 -431
- package/dist/chunk-BNXMFD3T.cjs.map +0 -1
- package/dist/chunk-DDAAVRWG.js.map +0 -1
- package/dist/chunk-HDZZQCH7.js.map +0 -1
- package/dist/chunk-P6IOWIO2.cjs +0 -480
- package/dist/chunk-P6IOWIO2.cjs.map +0 -1
- package/dist/chunk-UOCZFYYP.js +0 -407
- package/dist/chunk-UOCZFYYP.js.map +0 -1
- package/dist/chunk-VR44E62I.js.map +0 -1
- package/dist/chunk-WKXHX3GO.js +0 -424
- package/dist/chunk-WKXHX3GO.js.map +0 -1
- package/dist/chunk-XZDLJ2N6.cjs +0 -2224
- package/dist/chunk-XZDLJ2N6.cjs.map +0 -1
- package/dist/chunk-ZBHLMQTS.cjs.map +0 -1
package/dist/chunk-BNXMFD3T.cjs
DELETED
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkXZDLJ2N6_cjs = require('./chunk-XZDLJ2N6.cjs');
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var sanity = require('sanity');
|
|
6
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
-
|
|
8
|
-
var _scoreTitle = (title) => {
|
|
9
|
-
if (title && title.length >= 50 && title.length <= 60) return { score: 15, issues: [] };
|
|
10
|
-
if (title && title.length > 0) {
|
|
11
|
-
const issues = [];
|
|
12
|
-
if (title.length < 50) issues.push("Meta title too short (< 50 chars)");
|
|
13
|
-
if (title.length > 60) issues.push("Meta title too long (> 60 chars)");
|
|
14
|
-
return { score: 10, issues };
|
|
15
|
-
}
|
|
16
|
-
return { score: 0, issues: ["Missing meta title"] };
|
|
17
|
-
};
|
|
18
|
-
var _scoreDesc = (description) => {
|
|
19
|
-
if (description && description.length >= 120 && description.length <= 160)
|
|
20
|
-
return { score: 15, issues: [] };
|
|
21
|
-
if (description && description.length > 0) {
|
|
22
|
-
const issues = [];
|
|
23
|
-
if (description.length < 120) issues.push("Meta description too short (< 120 chars)");
|
|
24
|
-
if (description.length > 160) issues.push("Meta description too long (> 160 chars)");
|
|
25
|
-
return { score: 10, issues };
|
|
26
|
-
}
|
|
27
|
-
return { score: 0, issues: ["Missing meta description"] };
|
|
28
|
-
};
|
|
29
|
-
var _scoreOg = (og) => {
|
|
30
|
-
if (!og) return { score: 0, issues: ["Open Graph not configured"] };
|
|
31
|
-
const issues = [];
|
|
32
|
-
let score = 0;
|
|
33
|
-
if (og.title) score += 6;
|
|
34
|
-
else issues.push("Missing OG title");
|
|
35
|
-
if (og.description) score += 6;
|
|
36
|
-
else issues.push("Missing OG description");
|
|
37
|
-
if (og.image) score += 6;
|
|
38
|
-
else issues.push("Missing OG image");
|
|
39
|
-
if (og.type) score += 7;
|
|
40
|
-
else issues.push("Missing OG type");
|
|
41
|
-
return { score, issues };
|
|
42
|
-
};
|
|
43
|
-
var _scoreTwitter = (tw) => {
|
|
44
|
-
if (!tw) return { score: 0, issues: ["Twitter Card not configured"] };
|
|
45
|
-
const issues = [];
|
|
46
|
-
let score = 0;
|
|
47
|
-
if (tw.title) score += 5;
|
|
48
|
-
else issues.push("Missing Twitter title");
|
|
49
|
-
if (tw.description) score += 5;
|
|
50
|
-
else issues.push("Missing Twitter description");
|
|
51
|
-
if (tw.image) score += 5;
|
|
52
|
-
else issues.push("Missing Twitter image");
|
|
53
|
-
return { score, issues };
|
|
54
|
-
};
|
|
55
|
-
var calculateHealthScore = (doc) => {
|
|
56
|
-
var _a, _b, _c, _d;
|
|
57
|
-
if (!doc.seo) return { score: 0, issues: ["SEO fields not configured"] };
|
|
58
|
-
const { seo } = doc;
|
|
59
|
-
let score = 0;
|
|
60
|
-
const issues = [];
|
|
61
|
-
const t = _scoreTitle(seo.title);
|
|
62
|
-
score += t.score;
|
|
63
|
-
issues.push(...t.issues);
|
|
64
|
-
const d = _scoreDesc(seo.description);
|
|
65
|
-
score += d.score;
|
|
66
|
-
issues.push(...d.issues);
|
|
67
|
-
if (seo.metaImage) score += 10;
|
|
68
|
-
else issues.push("Missing meta image");
|
|
69
|
-
const kw = seo.keywords;
|
|
70
|
-
if (kw && kw.length > 0) score += 10;
|
|
71
|
-
else issues.push("No keywords defined");
|
|
72
|
-
const robots = seo.robots;
|
|
73
|
-
if (robots && !robots.noIndex) score += 5;
|
|
74
|
-
else if (!robots) score += 5;
|
|
75
|
-
const og = _scoreOg(seo.openGraph);
|
|
76
|
-
score += og.score;
|
|
77
|
-
issues.push(...og.issues);
|
|
78
|
-
const tw = _scoreTwitter(seo.twitter);
|
|
79
|
-
score += tw.score;
|
|
80
|
-
issues.push(...tw.issues);
|
|
81
|
-
const hasAll = !!(seo.metaImage && ((_a = seo.openGraph) == null ? void 0 : _a.image) && ((_b = seo.twitter) == null ? void 0 : _b.image));
|
|
82
|
-
if (hasAll) {
|
|
83
|
-
score += 5;
|
|
84
|
-
} else {
|
|
85
|
-
const missing = [];
|
|
86
|
-
if (!seo.metaImage) missing.push("meta image");
|
|
87
|
-
if (!((_c = seo.openGraph) == null ? void 0 : _c.image)) missing.push("OG image");
|
|
88
|
-
if (!((_d = seo.twitter) == null ? void 0 : _d.image)) missing.push("Twitter image");
|
|
89
|
-
issues.push(`Missing images for full score: ${missing.join(", ")}`);
|
|
90
|
-
}
|
|
91
|
-
return { score, issues };
|
|
92
|
-
};
|
|
93
|
-
var VALIDATION_ENDPOINT = "https://sanity-plugin-seofields.thehardik.in/api/validate-license";
|
|
94
|
-
async function fetchLicenseValidity(licenseKey, projectId) {
|
|
95
|
-
const cacheKey = `seofields_license_${projectId}`;
|
|
96
|
-
try {
|
|
97
|
-
const cached = sessionStorage.getItem(cacheKey);
|
|
98
|
-
if (cached) {
|
|
99
|
-
const parsed = JSON.parse(cached);
|
|
100
|
-
if (typeof parsed.valid === "boolean") return parsed.valid;
|
|
101
|
-
}
|
|
102
|
-
} catch (e) {
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
const res = await fetch(VALIDATION_ENDPOINT, {
|
|
106
|
-
method: "POST",
|
|
107
|
-
headers: { "Content-Type": "application/json" },
|
|
108
|
-
body: JSON.stringify({ licenseKey, projectId })
|
|
109
|
-
});
|
|
110
|
-
const valid = res.ok;
|
|
111
|
-
try {
|
|
112
|
-
sessionStorage.setItem(cacheKey, JSON.stringify({ valid }));
|
|
113
|
-
} catch (e) {
|
|
114
|
-
}
|
|
115
|
-
return valid;
|
|
116
|
-
} catch (e) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function getFieldValue(seo, dotPath) {
|
|
121
|
-
const parts = dotPath.split(".");
|
|
122
|
-
let cur = seo;
|
|
123
|
-
for (const part of parts) {
|
|
124
|
-
if (cur == null || typeof cur !== "object") return void 0;
|
|
125
|
-
cur = cur[part];
|
|
126
|
-
}
|
|
127
|
-
return cur;
|
|
128
|
-
}
|
|
129
|
-
function isFilled(value) {
|
|
130
|
-
if (value == null) return false;
|
|
131
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
132
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
133
|
-
if (typeof value === "object") return !!value.asset;
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
function getMissingRequiredFields(seo, requiredFields) {
|
|
137
|
-
return requiredFields.filter((field) => !isFilled(getFieldValue(seo, field))).map((field) => `Required field missing: ${field}`);
|
|
138
|
-
}
|
|
139
|
-
function buildReason(score, allIssues, gate) {
|
|
140
|
-
if (typeof gate.message === "function") return gate.message(score, allIssues);
|
|
141
|
-
if (typeof gate.message === "string") return gate.message;
|
|
142
|
-
return `SEO score ${score}/100 too low. Fix issues to publish.`;
|
|
143
|
-
}
|
|
144
|
-
function createSeoPublishGateAction(OriginalAction, gate) {
|
|
145
|
-
var _a;
|
|
146
|
-
const minScore = (_a = gate.minScore) != null ? _a : 60;
|
|
147
|
-
function SeoPublishGateAction(props) {
|
|
148
|
-
var _a2;
|
|
149
|
-
const workspace = sanity.useWorkspace();
|
|
150
|
-
const [licenseStatus, setLicenseStatus] = react.useState("loading");
|
|
151
|
-
const [dialogOpen, setDialogOpen] = react.useState(false);
|
|
152
|
-
const warnedRef = react.useRef(false);
|
|
153
|
-
react.useEffect(() => {
|
|
154
|
-
var _a3;
|
|
155
|
-
const projectId = (_a3 = workspace.projectId) != null ? _a3 : "";
|
|
156
|
-
fetchLicenseValidity(gate.licenseKey, projectId).then((valid) => {
|
|
157
|
-
setLicenseStatus(valid ? "valid" : "invalid");
|
|
158
|
-
if (!valid && !warnedRef.current) {
|
|
159
|
-
warnedRef.current = true;
|
|
160
|
-
console.warn(
|
|
161
|
-
"[sanity-plugin-seofields] publishGate: invalid or missing license key \u2014 gate disabled."
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}, [workspace.projectId]);
|
|
166
|
-
const original = OriginalAction(props);
|
|
167
|
-
if (licenseStatus !== "valid") return original;
|
|
168
|
-
if (!original) return null;
|
|
169
|
-
const draft = props.draft;
|
|
170
|
-
if (!(draft == null ? void 0 : draft.seo)) return original;
|
|
171
|
-
const metrics = calculateHealthScore({ seo: draft.seo });
|
|
172
|
-
const fieldViolations = gate.requiredFields ? getMissingRequiredFields(draft.seo, gate.requiredFields) : [];
|
|
173
|
-
const scoreFails = metrics.score < minScore;
|
|
174
|
-
const fieldsFail = fieldViolations.length > 0;
|
|
175
|
-
if (!scoreFails && !fieldsFail) return original;
|
|
176
|
-
const allIssues = [
|
|
177
|
-
...scoreFails ? [`SEO score is ${metrics.score}/100 (minimum: ${minScore})`] : [],
|
|
178
|
-
...fieldViolations
|
|
179
|
-
];
|
|
180
|
-
const reason = buildReason(metrics.score, allIssues, gate);
|
|
181
|
-
if (((_a2 = gate.mode) != null ? _a2 : "block") === "block") {
|
|
182
|
-
return chunkXZDLJ2N6_cjs.__spreadProps(chunkXZDLJ2N6_cjs.__spreadValues({}, original), {
|
|
183
|
-
label: original.label,
|
|
184
|
-
disabled: true,
|
|
185
|
-
title: reason,
|
|
186
|
-
onHandle: void 0
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
return chunkXZDLJ2N6_cjs.__spreadProps(chunkXZDLJ2N6_cjs.__spreadValues({}, original), {
|
|
190
|
-
label: original.label,
|
|
191
|
-
onHandle: () => setDialogOpen(true),
|
|
192
|
-
dialog: dialogOpen ? {
|
|
193
|
-
type: "confirm",
|
|
194
|
-
tone: "caution",
|
|
195
|
-
message: reason,
|
|
196
|
-
confirmButtonText: "Publish anyway",
|
|
197
|
-
cancelButtonText: "Go back",
|
|
198
|
-
onConfirm: () => {
|
|
199
|
-
var _a3;
|
|
200
|
-
setDialogOpen(false);
|
|
201
|
-
(_a3 = original == null ? void 0 : original.onHandle) == null ? void 0 : _a3.call(original);
|
|
202
|
-
},
|
|
203
|
-
onCancel: () => setDialogOpen(false)
|
|
204
|
-
} : false
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
SeoPublishGateAction.action = "publish";
|
|
208
|
-
SeoPublishGateAction.displayName = "SeoPublishGateAction";
|
|
209
|
-
return SeoPublishGateAction;
|
|
210
|
-
}
|
|
211
|
-
var SeoHealthTool = (props) => {
|
|
212
|
-
return /* @__PURE__ */ jsxRuntime.jsx(chunkXZDLJ2N6_cjs.SeoHealthDashboard_default, chunkXZDLJ2N6_cjs.__spreadValues({}, props));
|
|
213
|
-
};
|
|
214
|
-
var SeoHealthTool_default = SeoHealthTool;
|
|
215
|
-
var LazySeoHealthDashboard = react.lazy(() => import('./SeoHealthDashboard-AEKVVMUR-XWKJUH24.cjs'));
|
|
216
|
-
function createSeoHealthPane(optionsOrS, optionsWhenS) {
|
|
217
|
-
const S = optionsOrS;
|
|
218
|
-
const _a = optionsWhenS != null ? optionsWhenS : {}, { query, openInPane = true, title: paneTitle } = _a, rest = chunkXZDLJ2N6_cjs.__objRest(_a, ["query", "openInPane", "title"]);
|
|
219
|
-
const SeoHealthPane = () => /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
-
LazySeoHealthDashboard,
|
|
221
|
-
chunkXZDLJ2N6_cjs.__spreadValues({
|
|
222
|
-
customQuery: query,
|
|
223
|
-
openInPane,
|
|
224
|
-
title: paneTitle
|
|
225
|
-
}, rest)
|
|
226
|
-
) });
|
|
227
|
-
SeoHealthPane.displayName = "SeoHealthPane";
|
|
228
|
-
return S.component(SeoHealthPane).title(paneTitle != null ? paneTitle : "SEO Health").child((docId, { params }) => {
|
|
229
|
-
const builder = S.document().documentId(docId);
|
|
230
|
-
return (params == null ? void 0 : params.type) ? builder.schemaType(params.type) : builder;
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
function GEOChecklistProChecks({ checks }) {
|
|
234
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: checks.map((check) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
235
|
-
"div",
|
|
236
|
-
{
|
|
237
|
-
style: {
|
|
238
|
-
display: "flex",
|
|
239
|
-
alignItems: "flex-start",
|
|
240
|
-
gap: 10,
|
|
241
|
-
padding: "9px 12px",
|
|
242
|
-
borderRadius: 7,
|
|
243
|
-
backgroundColor: check.pass ? "transparent" : "#fdf4ff",
|
|
244
|
-
border: `1px solid ${check.pass ? "transparent" : "#f3e8ff"}`
|
|
245
|
-
},
|
|
246
|
-
children: [
|
|
247
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
248
|
-
"div",
|
|
249
|
-
{
|
|
250
|
-
style: {
|
|
251
|
-
width: 20,
|
|
252
|
-
height: 20,
|
|
253
|
-
borderRadius: "50%",
|
|
254
|
-
backgroundColor: check.pass ? "#dcfce7" : "#f3e8ff",
|
|
255
|
-
display: "flex",
|
|
256
|
-
alignItems: "center",
|
|
257
|
-
justifyContent: "center",
|
|
258
|
-
flexShrink: 0,
|
|
259
|
-
marginTop: 1
|
|
260
|
-
},
|
|
261
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
262
|
-
"span",
|
|
263
|
-
{
|
|
264
|
-
style: {
|
|
265
|
-
fontSize: 10,
|
|
266
|
-
fontWeight: 800,
|
|
267
|
-
color: check.pass ? "#16a34a" : "#7c3aed",
|
|
268
|
-
lineHeight: 1
|
|
269
|
-
},
|
|
270
|
-
children: check.pass ? "\u2713" : "\u2717"
|
|
271
|
-
}
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
),
|
|
275
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
276
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
277
|
-
"div",
|
|
278
|
-
{
|
|
279
|
-
style: {
|
|
280
|
-
fontSize: 12,
|
|
281
|
-
fontWeight: 500,
|
|
282
|
-
color: check.pass ? "#374151" : "#1f2937",
|
|
283
|
-
lineHeight: 1.4
|
|
284
|
-
},
|
|
285
|
-
children: check.label
|
|
286
|
-
}
|
|
287
|
-
),
|
|
288
|
-
!check.pass && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginTop: 3, lineHeight: 1.5 }, children: check.hint })
|
|
289
|
-
] })
|
|
290
|
-
]
|
|
291
|
-
},
|
|
292
|
-
check.id
|
|
293
|
-
)) });
|
|
294
|
-
}
|
|
295
|
-
var PLACEHOLDER_WIDTHS = ["62%", "78%", "55%", "70%", "66%"];
|
|
296
|
-
function GEOLockedTeaser() {
|
|
297
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: PLACEHOLDER_WIDTHS.map((width, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
298
|
-
"div",
|
|
299
|
-
{
|
|
300
|
-
style: {
|
|
301
|
-
display: "flex",
|
|
302
|
-
alignItems: "center",
|
|
303
|
-
gap: 10,
|
|
304
|
-
padding: "9px 12px",
|
|
305
|
-
borderRadius: 7,
|
|
306
|
-
backgroundColor: "#f9fafb"
|
|
307
|
-
},
|
|
308
|
-
children: [
|
|
309
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
310
|
-
"div",
|
|
311
|
-
{
|
|
312
|
-
style: {
|
|
313
|
-
width: 20,
|
|
314
|
-
height: 20,
|
|
315
|
-
borderRadius: "50%",
|
|
316
|
-
backgroundColor: "#e5e7eb",
|
|
317
|
-
flexShrink: 0,
|
|
318
|
-
filter: "blur(1px)"
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
),
|
|
322
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
323
|
-
"div",
|
|
324
|
-
{
|
|
325
|
-
style: {
|
|
326
|
-
height: 10,
|
|
327
|
-
width,
|
|
328
|
-
borderRadius: 6,
|
|
329
|
-
backgroundColor: "#e5e7eb",
|
|
330
|
-
filter: "blur(3px)",
|
|
331
|
-
opacity: 0.7
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
),
|
|
335
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
336
|
-
"span",
|
|
337
|
-
{
|
|
338
|
-
style: {
|
|
339
|
-
marginLeft: "auto",
|
|
340
|
-
fontSize: 10,
|
|
341
|
-
color: "#c4b5fd",
|
|
342
|
-
fontWeight: 700,
|
|
343
|
-
flexShrink: 0,
|
|
344
|
-
letterSpacing: "0.05em"
|
|
345
|
-
},
|
|
346
|
-
children: "PRO"
|
|
347
|
-
}
|
|
348
|
-
)
|
|
349
|
-
]
|
|
350
|
-
},
|
|
351
|
-
i
|
|
352
|
-
)) });
|
|
353
|
-
}
|
|
354
|
-
function buildGEOProChecks(seoFields, rootDoc) {
|
|
355
|
-
var _a, _b, _c, _d, _e;
|
|
356
|
-
const title = (seoFields == null ? void 0 : seoFields.title) || "";
|
|
357
|
-
const desc = (seoFields == null ? void 0 : seoFields.description) || "";
|
|
358
|
-
const kw = ((seoFields == null ? void 0 : seoFields.focusKeyword) || "").toLowerCase();
|
|
359
|
-
const schemaOrg = rootDoc == null ? void 0 : rootDoc.schemaOrg;
|
|
360
|
-
const keywordCheck = (() => {
|
|
361
|
-
if (!kw) {
|
|
362
|
-
return {
|
|
363
|
-
id: "keyword_coverage",
|
|
364
|
-
label: "Focus keyword in title + description",
|
|
365
|
-
pass: false,
|
|
366
|
-
hint: "Set a focus keyword \u2014 AI systems weight pages that clearly signal their topic."
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
return {
|
|
370
|
-
id: "keyword_coverage",
|
|
371
|
-
label: "Focus keyword in title + description",
|
|
372
|
-
pass: title.toLowerCase().includes(kw) && desc.toLowerCase().includes(kw),
|
|
373
|
-
hint: "Include the focus keyword in both title and description for topical clarity."
|
|
374
|
-
};
|
|
375
|
-
})();
|
|
376
|
-
const structuredDataCheck = (() => {
|
|
377
|
-
let pass = false;
|
|
378
|
-
if (Array.isArray(schemaOrg)) {
|
|
379
|
-
pass = schemaOrg.length > 0;
|
|
380
|
-
} else if (schemaOrg && typeof schemaOrg === "object") {
|
|
381
|
-
pass = !!schemaOrg._type;
|
|
382
|
-
}
|
|
383
|
-
return {
|
|
384
|
-
id: "structured_data_present",
|
|
385
|
-
label: "Schema.org structured data present",
|
|
386
|
-
pass,
|
|
387
|
-
hint: "Add Schema.org markup so AI systems can parse your content semantically."
|
|
388
|
-
};
|
|
389
|
-
})();
|
|
390
|
-
const faqHowToCheck = (() => {
|
|
391
|
-
let pass = false;
|
|
392
|
-
if (Array.isArray(schemaOrg)) {
|
|
393
|
-
pass = schemaOrg.some((item) => {
|
|
394
|
-
const t = (item._type || "").toLowerCase();
|
|
395
|
-
return t.includes("faqpage") || t.includes("howto");
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
return {
|
|
399
|
-
id: "faq_howto_schema",
|
|
400
|
-
label: "FAQ or HowTo schema present",
|
|
401
|
-
pass,
|
|
402
|
-
hint: "FAQ or HowTo schema is strongly preferred for AI Overview citation."
|
|
403
|
-
};
|
|
404
|
-
})();
|
|
405
|
-
return [
|
|
406
|
-
keywordCheck,
|
|
407
|
-
structuredDataCheck,
|
|
408
|
-
faqHowToCheck,
|
|
409
|
-
{
|
|
410
|
-
id: "canonical_configured",
|
|
411
|
-
label: "Canonical URL configured",
|
|
412
|
-
pass: !!((seoFields == null ? void 0 : seoFields.canonicalUrl) && seoFields.canonicalUrl.length > 0),
|
|
413
|
-
hint: "Set a canonical URL to prevent duplicate content diluting AI crawl priority."
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
id: "og_fully_configured",
|
|
417
|
-
label: "Open Graph fully configured",
|
|
418
|
-
pass: !!(((_a = seoFields == null ? void 0 : seoFields.openGraph) == null ? void 0 : _a.title) && ((_b = seoFields == null ? void 0 : seoFields.openGraph) == null ? void 0 : _b.description) && (((_d = (_c = seoFields == null ? void 0 : seoFields.openGraph) == null ? void 0 : _c.image) == null ? void 0 : _d.asset) || ((_e = seoFields == null ? void 0 : seoFields.openGraph) == null ? void 0 : _e.imageUrl))),
|
|
419
|
-
hint: "Complete OG tags help AI systems understand page context from link previews."
|
|
420
|
-
}
|
|
421
|
-
];
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
exports.GEOChecklistProChecks = GEOChecklistProChecks;
|
|
425
|
-
exports.GEOLockedTeaser = GEOLockedTeaser;
|
|
426
|
-
exports.SeoHealthTool_default = SeoHealthTool_default;
|
|
427
|
-
exports.buildGEOProChecks = buildGEOProChecks;
|
|
428
|
-
exports.createSeoHealthPane = createSeoHealthPane;
|
|
429
|
-
exports.createSeoPublishGateAction = createSeoPublishGateAction;
|
|
430
|
-
//# sourceMappingURL=chunk-BNXMFD3T.cjs.map
|
|
431
|
-
//# sourceMappingURL=chunk-BNXMFD3T.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/seofields-pro/src/actions/SeoPublishGateAction.tsx","../node_modules/seofields-pro/src/components/SeoHealthTool.tsx","../node_modules/seofields-pro/src/components/SeoHealthPane.tsx","../node_modules/seofields-pro/src/components/geo/GEOChecklistProChecks.tsx"],"names":["_a","useWorkspace","useState","useRef","useEffect","__spreadProps","__spreadValues","SeoHealthDashboard_default","lazy","__objRest","jsx","Suspense","jsxs"],"mappings":"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","file":"chunk-BNXMFD3T.cjs","sourcesContent":["import {useEffect, useRef, useState} from 'react'\nimport {useWorkspace} from 'sanity'\nimport type {DocumentActionComponent, DocumentActionDescription, DocumentActionProps} from 'sanity'\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type PublishGateRequiredField =\n | 'title'\n | 'description'\n | 'metaImage'\n | 'keywords'\n | 'canonicalUrl'\n | 'openGraph.title'\n | 'openGraph.description'\n | 'openGraph.image'\n | 'openGraph.type'\n | 'twitter.title'\n | 'twitter.description'\n | 'twitter.image'\n | 'twitter.card'\n\nexport interface PublishGateOptions {\n licenseKey: string\n mode?: 'block' | 'warn'\n minScore?: number\n requiredFields?: PublishGateRequiredField[]\n types?: string[]\n message?: string | ((score: number, issues: string[]) => string)\n}\n\ntype LicenseStatus = 'loading' | 'valid' | 'invalid'\n\n// ── Inline scoring (mirrors npm/src/utils/scoring.ts) ─────────────────────\n\ninterface SeoHealthMetrics {\n score: number\n issues: string[]\n}\n\nconst _scoreTitle = (title?: string): {score: number; issues: string[]} => {\n if (title && title.length >= 50 && title.length <= 60) return {score: 15, issues: []}\n if (title && title.length > 0) {\n const issues: string[] = []\n if (title.length < 50) issues.push('Meta title too short (< 50 chars)')\n if (title.length > 60) issues.push('Meta title too long (> 60 chars)')\n return {score: 10, issues}\n }\n return {score: 0, issues: ['Missing meta title']}\n}\n\nconst _scoreDesc = (description?: string): {score: number; issues: string[]} => {\n if (description && description.length >= 120 && description.length <= 160)\n return {score: 15, issues: []}\n if (description && description.length > 0) {\n const issues: string[] = []\n if (description.length < 120) issues.push('Meta description too short (< 120 chars)')\n if (description.length > 160) issues.push('Meta description too long (> 160 chars)')\n return {score: 10, issues}\n }\n return {score: 0, issues: ['Missing meta description']}\n}\n\nconst _scoreOg = (og?: Record<string, unknown>): {score: number; issues: string[]} => {\n if (!og) return {score: 0, issues: ['Open Graph not configured']}\n const issues: string[] = []\n let score = 0\n if (og.title) score += 6; else issues.push('Missing OG title')\n if (og.description) score += 6; else issues.push('Missing OG description')\n if (og.image) score += 6; else issues.push('Missing OG image')\n if (og.type) score += 7; else issues.push('Missing OG type')\n return {score, issues}\n}\n\nconst _scoreTwitter = (tw?: Record<string, unknown>): {score: number; issues: string[]} => {\n if (!tw) return {score: 0, issues: ['Twitter Card not configured']}\n const issues: string[] = []\n let score = 0\n if (tw.title) score += 5; else issues.push('Missing Twitter title')\n if (tw.description) score += 5; else issues.push('Missing Twitter description')\n if (tw.image) score += 5; else issues.push('Missing Twitter image')\n return {score, issues}\n}\n\nconst calculateHealthScore = (doc: {seo?: Record<string, unknown>}): SeoHealthMetrics => {\n if (!doc.seo) return {score: 0, issues: ['SEO fields not configured']}\n const {seo} = doc\n let score = 0\n const issues: string[] = []\n const t = _scoreTitle(seo.title as string | undefined)\n score += t.score; issues.push(...t.issues)\n const d = _scoreDesc(seo.description as string | undefined)\n score += d.score; issues.push(...d.issues)\n if (seo.metaImage) score += 10; else issues.push('Missing meta image')\n const kw = seo.keywords as string[] | undefined\n if (kw && kw.length > 0) score += 10; else issues.push('No keywords defined')\n const robots = seo.robots as {noIndex?: boolean} | undefined\n if (robots && !robots.noIndex) score += 5; else if (!robots) score += 5\n const og = _scoreOg(seo.openGraph as Record<string, unknown> | undefined)\n score += og.score; issues.push(...og.issues)\n const tw = _scoreTwitter(seo.twitter as Record<string, unknown> | undefined)\n score += tw.score; issues.push(...tw.issues)\n const hasAll = !!(seo.metaImage && (seo.openGraph as Record<string, unknown> | undefined)?.image && (seo.twitter as Record<string, unknown> | undefined)?.image)\n if (hasAll) {\n score += 5\n } else {\n const missing: string[] = []\n if (!seo.metaImage) missing.push('meta image')\n if (!(seo.openGraph as Record<string, unknown> | undefined)?.image) missing.push('OG image')\n if (!(seo.twitter as Record<string, unknown> | undefined)?.image) missing.push('Twitter image')\n issues.push(`Missing images for full score: ${missing.join(', ')}`)\n }\n return {score, issues}\n}\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\nconst VALIDATION_ENDPOINT =\n 'https://sanity-plugin-seofields.thehardik.in/api/validate-license'\n\n// ── License validation (shared cache with dashboard) ──────────────────────\n\nasync function fetchLicenseValidity(licenseKey: string, projectId: string): Promise<boolean> {\n const cacheKey = `seofields_license_${projectId}`\n try {\n const cached = sessionStorage.getItem(cacheKey)\n if (cached) {\n const parsed = JSON.parse(cached) as {valid?: boolean}\n if (typeof parsed.valid === 'boolean') return parsed.valid\n }\n } catch {\n // ignore storage errors\n }\n\n try {\n const res = await fetch(VALIDATION_ENDPOINT, {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({licenseKey, projectId}),\n })\n const valid = res.ok\n try {\n sessionStorage.setItem(cacheKey, JSON.stringify({valid}))\n } catch {\n // ignore storage errors\n }\n return valid\n } catch {\n return false\n }\n}\n\n// ── Required-field presence checker ───────────────────────────────────────\n\nfunction getFieldValue(seo: Record<string, unknown>, dotPath: string): unknown {\n const parts = dotPath.split('.')\n let cur: unknown = seo\n for (const part of parts) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\nfunction isFilled(value: unknown): boolean {\n if (value == null) return false\n if (typeof value === 'string') return value.trim().length > 0\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'object') return !!(value as Record<string, unknown>).asset\n return true\n}\n\nfunction getMissingRequiredFields(\n seo: Record<string, unknown>,\n requiredFields: PublishGateRequiredField[],\n): string[] {\n return requiredFields\n .filter((field) => !isFilled(getFieldValue(seo, field)))\n .map((field) => `Required field missing: ${field}`)\n}\n\n// ── Reason message builder ─────────────────────────────────────────────────\n\nfunction buildReason(\n score: number,\n allIssues: string[],\n gate: PublishGateOptions,\n): string {\n if (typeof gate.message === 'function') return gate.message(score, allIssues)\n if (typeof gate.message === 'string') return gate.message\n return `SEO score ${score}/100 too low. Fix issues to publish.`\n}\n\n// ── Factory ────────────────────────────────────────────────────────────────\n\nexport function createSeoPublishGateAction(\n OriginalAction: DocumentActionComponent,\n gate: PublishGateOptions,\n): DocumentActionComponent {\n const minScore = gate.minScore ?? 60\n\n function SeoPublishGateAction(props: DocumentActionProps): DocumentActionDescription | null {\n const workspace = useWorkspace()\n const [licenseStatus, setLicenseStatus] = useState<LicenseStatus>('loading')\n const [dialogOpen, setDialogOpen] = useState(false)\n const warnedRef = useRef(false)\n\n useEffect(() => {\n const projectId = workspace.projectId ?? ''\n fetchLicenseValidity(gate.licenseKey, projectId).then((valid) => {\n setLicenseStatus(valid ? 'valid' : 'invalid')\n if (!valid && !warnedRef.current) {\n warnedRef.current = true\n console.warn(\n '[sanity-plugin-seofields] publishGate: invalid or missing license key — gate disabled.',\n )\n }\n })\n }, [workspace.projectId])\n\n // Always call the original hook unconditionally (Rules of Hooks)\n const original = OriginalAction(props)\n\n // Pass through while loading or if license is invalid\n if (licenseStatus !== 'valid') return original\n\n // If original action is absent, nothing to gate\n if (!original) return null\n\n // No seo field → skip gate (document doesn't use the plugin)\n const draft = props.draft as (Record<string, unknown> & {seo?: Record<string, unknown>}) | null\n if (!draft?.seo) return original\n\n // Score + required field violations\n const metrics = calculateHealthScore({seo: draft.seo})\n const fieldViolations =\n gate.requiredFields ? getMissingRequiredFields(draft.seo, gate.requiredFields) : []\n\n const scoreFails = metrics.score < minScore\n const fieldsFail = fieldViolations.length > 0\n\n if (!scoreFails && !fieldsFail) return original\n\n const allIssues = [\n ...(scoreFails ? [`SEO score is ${metrics.score}/100 (minimum: ${minScore})`] : []),\n ...fieldViolations,\n ]\n const reason = buildReason(metrics.score, allIssues, gate)\n\n // ── Block mode ─────────────────────────────────────────────────────────\n if ((gate.mode ?? 'block') === 'block') {\n return {\n ...original,\n label: original.label,\n disabled: true,\n title: reason,\n onHandle: undefined,\n }\n }\n\n // ── Warn mode ──────────────────────────────────────────────────────────\n return {\n ...original,\n label: original.label,\n onHandle: () => setDialogOpen(true),\n dialog: dialogOpen\n ? {\n type: 'confirm' as const,\n tone: 'caution',\n message: reason,\n confirmButtonText: 'Publish anyway',\n cancelButtonText: 'Go back',\n onConfirm: () => {\n setDialogOpen(false)\n original?.onHandle?.()\n },\n onCancel: () => setDialogOpen(false),\n }\n : false,\n }\n }\n\n // Preserve the .action marker so Sanity can still identify this as the publish action\n SeoPublishGateAction.action = 'publish' as const\n SeoPublishGateAction.displayName = 'SeoPublishGateAction'\n\n return SeoPublishGateAction\n}\n","import SeoHealthDashboard, {SeoHealthDashboardProps} from './SeoHealthDashboard'\n\n/**\n * Sanity Tool component for the SEO Health Dashboard\n * This component wraps the SeoHealthDashboard for use as a custom tool in Sanity Studio\n */\nconst SeoHealthTool = (props: SeoHealthDashboardProps) => {\n return <SeoHealthDashboard {...props} />\n}\n\nexport default SeoHealthTool\n","import {lazy, Suspense} from 'react'\nimport type {ComponentBuilder, StructureBuilder} from 'sanity/structure'\n\nimport type {SeoHealthDashboardProps} from './SeoHealthDashboard'\n\nconst LazySeoHealthDashboard = lazy(() => import('./SeoHealthDashboard'))\n\n/**\n * Options accepted by `createSeoHealthPane`.\n * All props from `SeoHealthDashboardProps` are supported.\n *\n * `licenseKey` is **required** — the dashboard will not render without it.\n */\nexport interface SeoHealthPaneOptions extends Omit<SeoHealthDashboardProps, 'customQuery'> {\n /** Required license key (format: `SEOF-XXXX-XXXX-XXXX`). */\n licenseKey: string\n /**\n * A fully custom GROQ query used to fetch documents for the dashboard.\n * The query must return documents with at least: `_id`, `_type`, `title`, `seo`, `_updatedAt`.\n *\n * Takes precedence over `queryTypes` when both are provided.\n *\n * @example\n * query: `*[_type in [\"post\",\"page\"] && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`\n */\n query?: string\n}\n\n// function isStructureBuilder(arg: unknown): arg is StructureBuilder {\n// return (\n// arg !== null &&\n// typeof arg === 'object' &&\n// typeof (arg as StructureBuilder).component === 'function' &&\n// typeof (arg as StructureBuilder).document === 'function'\n// )\n// }\n\n/**\n * Creates a desk-structure pane for the SEO Health Dashboard.\n *\n * Returns a **`ComponentBuilder`** with a built-in `.child()` resolver so that\n * clicking any document row opens the document editor as a split pane to the right.\n *\n * Use it **directly** as the `.child()` value — do **not** wrap it in `S.component()`.\n *\n * ```ts\n * // sanity.config.ts\n * structure: (S) =>\n * S.list().items([\n * S.listItem()\n * .title('SEO Health')\n * .child(\n * createSeoHealthPane(S, {\n * licenseKey: 'SEOF-XXXX-XXXX-XXXX',\n * query: `*[_type == \"post\" && defined(seo)]{ _id, _type, title, slug, seo, _updatedAt }`,\n * })\n * ),\n * ])\n * ```\n */\nexport function createSeoHealthPane(\n optionsOrS: StructureBuilder,\n optionsWhenS: SeoHealthPaneOptions,\n): ComponentBuilder {\n // ── Two-arg form: structure builder passed as first arg ──────────────────\n const S = optionsOrS\n const {query, openInPane = true, title: paneTitle, ...rest} = optionsWhenS ?? {}\n\n const SeoHealthPane = () => (\n <Suspense fallback={null}>\n <LazySeoHealthDashboard\n customQuery={query}\n openInPane={openInPane}\n title={paneTitle}\n {...rest}\n />\n </Suspense>\n )\n SeoHealthPane.displayName = 'SeoHealthPane'\n\n // Wire up the child resolver so ChildLink URLs resolve to the document editor\n return (S.component(SeoHealthPane) as ComponentBuilder)\n .title(paneTitle ?? 'SEO Health')\n .child((docId: string, {params}: {params: Record<string, string | undefined>}) => {\n const builder = S.document().documentId(docId)\n return params?.type ? builder.schemaType(params.type) : builder\n })\n}\n\nexport default createSeoHealthPane\n","import {type ReactElement} from 'react'\n\nexport interface GEOProCheck {\n id: string\n label: string\n pass: boolean\n hint: string\n}\n\ninterface Props {\n checks: GEOProCheck[]\n}\n\nexport function GEOChecklistProChecks({checks}: Props): ReactElement {\n return (\n <div style={{display: 'flex', flexDirection: 'column', gap: 6}}>\n {checks.map((check) => (\n <div\n key={check.id}\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: 10,\n padding: '9px 12px',\n borderRadius: 7,\n backgroundColor: check.pass ? 'transparent' : '#fdf4ff',\n border: `1px solid ${check.pass ? 'transparent' : '#f3e8ff'}`,\n }}\n >\n <div\n style={{\n width: 20,\n height: 20,\n borderRadius: '50%',\n backgroundColor: check.pass ? '#dcfce7' : '#f3e8ff',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n marginTop: 1,\n }}\n >\n <span\n style={{\n fontSize: 10,\n fontWeight: 800,\n color: check.pass ? '#16a34a' : '#7c3aed',\n lineHeight: 1,\n }}\n >\n {check.pass ? '✓' : '✗'}\n </span>\n </div>\n\n <div style={{flex: 1, minWidth: 0}}>\n <div\n style={{\n fontSize: 12,\n fontWeight: 500,\n color: check.pass ? '#374151' : '#1f2937',\n lineHeight: 1.4,\n }}\n >\n {check.label}\n </div>\n {!check.pass && (\n <div style={{fontSize: 11, color: '#9ca3af', marginTop: 3, lineHeight: 1.5}}>\n {check.hint}\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n )\n}\n\n// Widths for the blurred placeholder bars — varied to look natural\nconst PLACEHOLDER_WIDTHS = ['62%', '78%', '55%', '70%', '66%']\n\nexport function GEOLockedTeaser(): ReactElement {\n return (\n <div style={{display: 'flex', flexDirection: 'column', gap: 6}}>\n {PLACEHOLDER_WIDTHS.map((width, i) => (\n <div\n key={i}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '9px 12px',\n borderRadius: 7,\n backgroundColor: '#f9fafb',\n }}\n >\n {/* Blurred circle */}\n <div\n style={{\n width: 20,\n height: 20,\n borderRadius: '50%',\n backgroundColor: '#e5e7eb',\n flexShrink: 0,\n filter: 'blur(1px)',\n }}\n />\n {/* Blurred text bar */}\n <div\n style={{\n height: 10,\n width,\n borderRadius: 6,\n backgroundColor: '#e5e7eb',\n filter: 'blur(3px)',\n opacity: 0.7,\n }}\n />\n {/* PRO badge */}\n <span\n style={{\n marginLeft: 'auto',\n fontSize: 10,\n color: '#c4b5fd',\n fontWeight: 700,\n flexShrink: 0,\n letterSpacing: '0.05em',\n }}\n >\n PRO\n </span>\n </div>\n ))}\n </div>\n )\n}\n\nexport function buildGEOProChecks(\n seoFields: {\n focusKeyword?: string\n title?: string\n description?: string\n canonicalUrl?: string\n openGraph?: {\n title?: string\n description?: string\n image?: {asset?: unknown} | null\n imageUrl?: string\n } | null\n } | null,\n rootDoc: {\n schemaOrg?: Array<{_type?: string}> | {_type?: string} | null\n } | null,\n): GEOProCheck[] {\n const title = seoFields?.title || ''\n const desc = seoFields?.description || ''\n const kw = (seoFields?.focusKeyword || '').toLowerCase()\n const schemaOrg = rootDoc?.schemaOrg\n\n const keywordCheck: GEOProCheck = (() => {\n if (!kw) {\n return {\n id: 'keyword_coverage',\n label: 'Focus keyword in title + description',\n pass: false,\n hint: 'Set a focus keyword — AI systems weight pages that clearly signal their topic.',\n }\n }\n return {\n id: 'keyword_coverage',\n label: 'Focus keyword in title + description',\n pass: title.toLowerCase().includes(kw) && desc.toLowerCase().includes(kw),\n hint: 'Include the focus keyword in both title and description for topical clarity.',\n }\n })()\n\n const structuredDataCheck: GEOProCheck = (() => {\n let pass = false\n if (Array.isArray(schemaOrg)) {\n pass = schemaOrg.length > 0\n } else if (schemaOrg && typeof schemaOrg === 'object') {\n pass = !!(schemaOrg as {_type?: string})._type\n }\n return {\n id: 'structured_data_present',\n label: 'Schema.org structured data present',\n pass,\n hint: 'Add Schema.org markup so AI systems can parse your content semantically.',\n }\n })()\n\n const faqHowToCheck: GEOProCheck = (() => {\n let pass = false\n if (Array.isArray(schemaOrg)) {\n pass = schemaOrg.some((item) => {\n const t = (item._type || '').toLowerCase()\n return t.includes('faqpage') || t.includes('howto')\n })\n }\n return {\n id: 'faq_howto_schema',\n label: 'FAQ or HowTo schema present',\n pass,\n hint: 'FAQ or HowTo schema is strongly preferred for AI Overview citation.',\n }\n })()\n\n return [\n keywordCheck,\n structuredDataCheck,\n faqHowToCheck,\n {\n id: 'canonical_configured',\n label: 'Canonical URL configured',\n pass: !!(seoFields?.canonicalUrl && seoFields.canonicalUrl.length > 0),\n hint: 'Set a canonical URL to prevent duplicate content diluting AI crawl priority.',\n },\n {\n id: 'og_fully_configured',\n label: 'Open Graph fully configured',\n pass: !!(\n seoFields?.openGraph?.title &&\n seoFields?.openGraph?.description &&\n (seoFields?.openGraph?.image?.asset || seoFields?.openGraph?.imageUrl)\n ),\n hint: 'Complete OG tags help AI systems understand page context from link previews.',\n },\n ]\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-DDAAVRWG.js"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/seoUtils.ts"],"names":[],"mappings":"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","file":"chunk-HDZZQCH7.js","sourcesContent":["import {FeedbackType} from '../types'\n\nexport const stopWords = ['the', 'a', 'an', 'and', 'or', 'but']\n\nexport const hasMatchingKeyword = (title: string, keywordList: string[]): boolean => {\n if (!title || keywordList.length === 0) return false\n const lowerTitle = title.toLowerCase()\n return keywordList.some((keyword) => keyword && lowerTitle.includes(keyword.toLowerCase()))\n}\n\nexport const hasKeywordOveruse = (\n title: string,\n keywordList: string[],\n maxOccurrences = 3,\n): boolean => {\n if (!title || keywordList.length === 0) return false\n const lowerTitle = title.toLowerCase()\n return keywordList.some((keyword) => {\n if (!keyword) return false\n const matches = lowerTitle.match(new RegExp(keyword.toLowerCase(), 'g'))\n return matches ? matches.length > maxOccurrences : false\n })\n}\n\nexport const startsWithStopWord = (title: string): boolean => {\n if (!title) return false\n const firstWord = title.trim().split(' ')[0].toLowerCase()\n return stopWords.includes(firstWord)\n}\n\nexport const primaryKeywordAtStart = (title: string, keywords: string[]): boolean => {\n if (!title || keywords.length === 0) return true\n return title.toLowerCase().startsWith(keywords[0].toLowerCase())\n}\n\nexport const truncate = (text: string, maxLength: number): string =>\n text.length > maxLength ? `${text.slice(0, maxLength)}…` : text\n\nexport const hasExcessivePunctuation = (title: string): boolean => /[!@#$%^&*]{2,}/.test(title)\n\nexport const getMetaTitleValidationMessages = (\n title: string,\n keywords: string[],\n isParentseoField: boolean,\n suffixLength = 0,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n const minChar = 50\n const maxChar = 60\n const charCount = title?.length || 0\n const combinedLength = charCount + suffixLength\n const suffixMessage = suffixLength > 0 ? ` Suffix value is included (${suffixLength} chars).` : ''\n\n // Empty check\n if (!title?.trim()) {\n feedback.push({text: 'Meta Title is empty. Add content to improve SEO.', color: 'red'})\n return feedback\n }\n\n // Length check (evaluated against combined title + suffix length)\n if (combinedLength < minChar)\n feedback.push({\n text: `Title is ${combinedLength} characters — below recommended ${minChar}.${suffixMessage}`,\n color: 'orange',\n })\n else if (combinedLength > maxChar)\n feedback.push({\n text: `Title is ${combinedLength} characters — exceeds recommended ${maxChar}.${suffixMessage}`,\n color: 'red',\n })\n else\n feedback.push({\n text: `Title length (${combinedLength}) looks good for SEO.${suffixMessage}`,\n color: 'green',\n })\n\n // Keyword checks\n if (isParentseoField) {\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(title, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in title — good job!'\n : 'Keywords defined but missing in title.',\n color: hasKeyword ? 'green' : 'red',\n })\n\n if (hasKeywordOveruse(title, keywords)) {\n feedback.push({\n text: 'Keyword appears too many times — avoid keyword stuffing.',\n color: 'orange',\n })\n }\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Stop word check\n if (startsWithStopWord(title))\n feedback.push({text: 'Title starts with a stop word — consider rephrasing.', color: 'orange'})\n\n // Punctuation check\n if (hasExcessivePunctuation(title))\n feedback.push({text: 'Title contains excessive punctuation — simplify it.', color: 'orange'})\n\n return feedback\n}\n\nexport const getMetaDescriptionValidationMessages = (\n description: string,\n keywords: string[],\n isParentseoField: boolean,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n const minChar = 120\n const maxChar = 160\n const charCount = description?.length || 0\n\n if (!description?.trim()) {\n feedback.push({text: 'Meta description is empty. Add content to improve SEO.', color: 'red'})\n return feedback\n }\n\n // Length check\n if (charCount < minChar)\n feedback.push({\n text: `Description is ${charCount} chars — below recommended ${minChar}.`,\n color: 'orange',\n })\n else if (charCount > maxChar)\n feedback.push({\n text: `Description is ${charCount} chars — exceeds recommended ${maxChar}.`,\n color: 'red',\n })\n else\n feedback.push({text: `Description length (${charCount}) looks good for SEO.`, color: 'green'})\n\n // Keyword checks\n if (isParentseoField) {\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(description, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in description — good job!'\n : 'Keywords defined but missing in description.',\n color: hasKeyword ? 'green' : 'red',\n })\n\n if (hasKeywordOveruse(description, keywords)) {\n feedback.push({\n text: 'Keyword appears too many times — avoid keyword stuffing.',\n color: 'orange',\n })\n }\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Stop word / filler check\n if (startsWithStopWord(description))\n feedback.push({\n text: 'Description starts with a stop word — consider rephrasing.',\n color: 'orange',\n })\n\n // Punctuation / special characters\n if (hasExcessivePunctuation(description))\n feedback.push({\n text: 'Description contains excessive punctuation — simplify it.',\n color: 'orange',\n })\n\n return feedback\n}\n\nexport const getOgTitleValidation = (\n title: string,\n keywords: string[] = [],\n isParentseoField: boolean,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n const min = 40\n const max = 60\n const count = title?.length || 0\n\n // Empty check\n if (!title?.trim()) {\n feedback.push({text: 'OG Title is empty. Add content for better social preview.', color: 'red'})\n return feedback\n }\n\n // Length check\n if (count < min)\n feedback.push({\n text: `OG Title is ${count} chars — shorter than recommended ${min}.`,\n color: 'orange',\n })\n else if (count > max)\n feedback.push({text: `OG Title is ${count} chars — exceeds recommended ${max}.`, color: 'red'})\n else feedback.push({text: `OG Title length (${count}) looks good.`, color: 'green'})\n\n if (isParentseoField) {\n // Keyword checks\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(title, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in OG title — good job!'\n : 'Keywords defined but missing in OG title.',\n color: hasKeyword ? 'green' : 'red',\n })\n\n if (hasKeywordOveruse(title, keywords)) {\n feedback.push({\n text: 'Keyword appears too many times in OG title — avoid keyword stuffing.',\n color: 'orange',\n })\n }\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Additional OG-specific checks\n if (startsWithStopWord(title))\n feedback.push({\n text: 'OG Title starts with a stop word — consider rephrasing.',\n color: 'orange',\n })\n\n if (hasExcessivePunctuation(title))\n feedback.push({text: 'OG Title contains excessive punctuation — simplify it.', color: 'orange'})\n\n return feedback\n}\n\nexport const getOgDescriptionValidation = (\n desc: string,\n keywords: string[] = [],\n isParentseoField: boolean,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n const min = 90\n const max = 120\n const count = desc?.length || 0\n\n // Empty check\n if (!desc?.trim()) {\n feedback.push({\n text: 'OG Description is empty. Add content for better social preview.',\n color: 'red',\n })\n return feedback\n }\n\n // Length check\n if (count < min)\n feedback.push({\n text: `OG Description is ${count} chars — shorter than recommended ${min}.`,\n color: 'orange',\n })\n else if (count > max)\n feedback.push({\n text: `OG Description is ${count} chars — exceeds recommended ${max}.`,\n color: 'red',\n })\n else feedback.push({text: `OG Description length (${count}) looks good.`, color: 'green'})\n\n // Keyword checks\n if (isParentseoField) {\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(desc, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in OG description — good job!'\n : 'Keywords defined but missing in OG description.',\n color: hasKeyword ? 'green' : 'red',\n })\n\n if (hasKeywordOveruse(desc, keywords)) {\n feedback.push({\n text: 'Keyword appears too many times in OG description — avoid keyword stuffing.',\n color: 'orange',\n })\n }\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Additional OG-specific checks\n if (startsWithStopWord(desc))\n feedback.push({\n text: 'OG Description starts with a stop word — consider rephrasing.',\n color: 'orange',\n })\n\n if (hasExcessivePunctuation(desc))\n feedback.push({\n text: 'OG Description contains excessive punctuation — simplify it.',\n color: 'orange',\n })\n\n return feedback\n}\n\nexport const getTwitterTitleValidation = (\n title: string,\n keywords: string[] = [],\n isParentseoField: boolean,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n const min = 30\n const max = 70\n const count = title?.length || 0\n\n if (!title?.trim()) {\n feedback.push({text: 'X Title is empty. Add content for better SEO.', color: 'red'})\n return feedback\n }\n\n // Length check\n if (count < min)\n feedback.push({\n text: `X Title is ${count} chars — shorter than recommended ${min}.`,\n color: 'orange',\n })\n else if (count > max)\n feedback.push({\n text: `X Title is ${count} chars — exceeds recommended ${max}.`,\n color: 'red',\n })\n else feedback.push({text: `X Title length (${count}) looks good.`, color: 'green'})\n\n if (isParentseoField) {\n // Keyword checks\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(title, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in X title — good job!'\n : 'Keywords defined but missing in X title.',\n color: hasKeyword ? 'green' : 'red',\n })\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Punctuation check\n if (/[!@#$%^&*]{2,}/.test(title))\n feedback.push({text: 'X Title has excessive punctuation — simplify it.', color: 'orange'})\n\n return feedback\n}\n\nexport const getTwitterDescriptionValidation = (\n desc: string,\n keywords: string[] = [],\n isParentseoField: boolean,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n const min = 50\n const max = 200\n const count = desc?.length || 0\n\n if (!desc?.trim()) {\n feedback.push({text: 'X Description is empty. Add content for better SEO.', color: 'red'})\n return feedback\n }\n\n // Length check\n if (count < min)\n feedback.push({\n text: `X Description is ${count} chars — shorter than recommended ${min}.`,\n color: 'orange',\n })\n else if (count > max)\n feedback.push({\n text: `X Description is ${count} chars — exceeds recommended ${max}.`,\n color: 'red',\n })\n else feedback.push({text: `X Description length (${count}) looks good.`, color: 'green'})\n\n if (isParentseoField) {\n // Keyword checks\n if (keywords.length > 0) {\n const hasKeyword = hasMatchingKeyword(desc, keywords)\n feedback.push({\n text: hasKeyword\n ? 'Keyword found in X description — good job!'\n : 'Keywords defined but missing in X description.',\n color: hasKeyword ? 'green' : 'red',\n })\n } else {\n feedback.push({\n text: 'No keywords defined. Consider adding relevant keywords.',\n color: 'orange',\n })\n }\n }\n\n // Punctuation check\n if (/[!@#$%^&*]{2,}/.test(desc))\n feedback.push({\n text: 'X Description has excessive punctuation — simplify it.',\n color: 'orange',\n })\n\n return feedback\n}\n\n// ── Image Validation Helpers ──\n\n/** Check if an image is set in an OG/Twitter sub-object (handles upload vs URL) */\nexport const isSubImageSet = (subObj: Record<string, unknown> | null | undefined): boolean => {\n if (!subObj) return false\n if (subObj.imageType === 'url') return !!(subObj.imageUrl as string)?.trim()\n const img = subObj.image as Record<string, unknown> | undefined\n return !!img?.asset\n}\n\n/** Check if metaImage asset is set */\nconst isMetaImageSet = (seoParent: Record<string, unknown> | null | undefined): boolean => {\n if (!seoParent) return false\n const metaImage = seoParent.metaImage as Record<string, unknown> | undefined\n return !!metaImage?.asset\n}\n\nexport const getMetaImageValidation = (\n hasImage: boolean,\n seoParent: Record<string, unknown> | null | undefined,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n if (!hasImage) {\n feedback.push({\n text: 'No meta image provided. Adding an image improves click-through rates.',\n color: 'red',\n })\n return feedback\n }\n\n feedback.push({text: 'Meta image is set — great for SEO and social sharing.', color: 'green'})\n\n const ogSet = isSubImageSet(seoParent?.openGraph as Record<string, unknown> | undefined)\n const twSet = isSubImageSet(seoParent?.twitter as Record<string, unknown> | undefined)\n\n if (!ogSet && !twSet) {\n feedback.push({\n text: 'OG and Twitter images are missing — add them for full social coverage.',\n color: 'orange',\n })\n } else if (!ogSet) {\n feedback.push({\n text: 'OG image is missing — add it for better Facebook/LinkedIn previews.',\n color: 'orange',\n })\n } else if (twSet) {\n feedback.push({text: 'All images set (Meta, OG, Twitter) — full coverage!', color: 'green'})\n } else {\n feedback.push({\n text: 'Twitter image is missing — add it for better X (Twitter) cards.',\n color: 'orange',\n })\n }\n\n return feedback\n}\n\nexport const getOgImageValidation = (\n hasImage: boolean,\n altText: string | undefined,\n seoParent: Record<string, unknown> | null | undefined,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n if (!hasImage) {\n feedback.push({\n text: 'No OG image provided. Social shares will lack a visual preview.',\n color: 'red',\n })\n return feedback\n }\n\n feedback.push({text: 'OG image is set — good for social sharing.', color: 'green'})\n\n if (altText?.trim()) {\n feedback.push({text: 'Alt text is set — good for accessibility.', color: 'green'})\n } else {\n feedback.push({text: 'Consider adding alt text for better accessibility.', color: 'orange'})\n }\n\n const metaSet = isMetaImageSet(seoParent)\n const twSet = isSubImageSet(seoParent?.twitter as Record<string, unknown> | undefined)\n\n if (metaSet && twSet) {\n feedback.push({text: 'All images set (Meta, OG, Twitter) — full coverage!', color: 'green'})\n } else {\n if (!metaSet)\n feedback.push({\n text: 'Meta image is missing — add it for search engine results.',\n color: 'orange',\n })\n if (!twSet)\n feedback.push({\n text: 'Twitter image is missing — add it for X (Twitter) cards.',\n color: 'orange',\n })\n }\n\n return feedback\n}\n\nexport const getOgImageUrlValidation = (\n imageUrl: string | undefined,\n seoParent: Record<string, unknown> | null | undefined,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n if (!imageUrl?.trim()) {\n feedback.push({\n text: 'No OG image URL provided. Social shares will lack a visual preview.',\n color: 'red',\n })\n return feedback\n }\n\n feedback.push({text: 'OG image URL is set — good for social sharing.', color: 'green'})\n\n const metaSet = isMetaImageSet(seoParent)\n const twSet = isSubImageSet(seoParent?.twitter as Record<string, unknown> | undefined)\n\n if (metaSet && twSet) {\n feedback.push({text: 'All images set (Meta, OG, Twitter) — full coverage!', color: 'green'})\n } else {\n if (!metaSet)\n feedback.push({\n text: 'Meta image is missing — add it for search engine results.',\n color: 'orange',\n })\n if (!twSet)\n feedback.push({\n text: 'Twitter image is missing — add it for X (Twitter) cards.',\n color: 'orange',\n })\n }\n\n return feedback\n}\n\nexport const getTwitterImageValidation = (\n hasImage: boolean,\n altText: string | undefined,\n seoParent: Record<string, unknown> | null | undefined,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n if (!hasImage) {\n feedback.push({\n text: 'No Twitter image provided. Posts on X will lack a visual.',\n color: 'red',\n })\n return feedback\n }\n\n feedback.push({text: 'Twitter image is set — good for X sharing.', color: 'green'})\n\n if (altText?.trim()) {\n feedback.push({text: 'Alt text is set — good for accessibility.', color: 'green'})\n } else {\n feedback.push({text: 'Consider adding alt text for better accessibility.', color: 'orange'})\n }\n\n const metaSet = isMetaImageSet(seoParent)\n const ogSet = isSubImageSet(seoParent?.openGraph as Record<string, unknown> | undefined)\n\n if (metaSet && ogSet) {\n feedback.push({text: 'All images set (Meta, OG, Twitter) — full coverage!', color: 'green'})\n } else {\n if (!metaSet)\n feedback.push({\n text: 'Meta image is missing — add it for search engine results.',\n color: 'orange',\n })\n if (!ogSet)\n feedback.push({\n text: 'OG image is missing — add it for Facebook/LinkedIn sharing.',\n color: 'orange',\n })\n }\n\n return feedback\n}\n\nexport const getTwitterImageUrlValidation = (\n imageUrl: string | undefined,\n seoParent: Record<string, unknown> | null | undefined,\n): FeedbackType[] => {\n const feedback: FeedbackType[] = []\n\n if (!imageUrl?.trim()) {\n feedback.push({\n text: 'No Twitter image URL provided. Posts on X will lack a visual.',\n color: 'red',\n })\n return feedback\n }\n\n feedback.push({text: 'Twitter image URL is set — good for X sharing.', color: 'green'})\n\n const metaSet = isMetaImageSet(seoParent)\n const ogSet = isSubImageSet(seoParent?.openGraph as Record<string, unknown> | undefined)\n\n if (metaSet && ogSet) {\n feedback.push({text: 'All images set (Meta, OG, Twitter) — full coverage!', color: 'green'})\n } else {\n if (!metaSet)\n feedback.push({\n text: 'Meta image is missing — add it for search engine results.',\n color: 'orange',\n })\n if (!ogSet)\n feedback.push({\n text: 'OG image is missing — add it for Facebook/LinkedIn sharing.',\n color: 'orange',\n })\n }\n\n return feedback\n}\n"]}
|