@asteroidcms/core-utils 0.1.7 → 0.1.8

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/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var client = require('@apollo/client');
4
4
  var context = require('@apollo/client/link/context');
5
5
  var error = require('@apollo/client/link/error');
6
+ var jsxRuntime = require('react/jsx-runtime');
6
7
 
7
8
  // src/apollo/createApolloClient.ts
8
9
  function createErrorLink(onError) {
@@ -1064,8 +1065,384 @@ function extractHeadingsFromElement(root, options = {}) {
1064
1065
  return out;
1065
1066
  }
1066
1067
 
1068
+ // src/seo/seo.builders.ts
1069
+ function applyTitleTemplate(config, title) {
1070
+ return config.titleTemplate ? config.titleTemplate(title) : `${title} | ${config.siteName}`;
1071
+ }
1072
+ function buildOgImageUrl(config, params) {
1073
+ if (config.getOgImageUrl) {
1074
+ return config.getOgImageUrl(params);
1075
+ }
1076
+ const palette = config.ogImage?.palette;
1077
+ if (!palette) return void 0;
1078
+ const apiPath = config.ogImage?.apiPath ?? "/api/og";
1079
+ const base = config.baseUrl.replace(/\/$/, "");
1080
+ const searchParams = new URLSearchParams({
1081
+ title: params.title,
1082
+ type: params.type ?? "article",
1083
+ siteName: config.siteName,
1084
+ bg: palette.background,
1085
+ fg: palette.foreground,
1086
+ accent: palette.accent
1087
+ });
1088
+ if (params.subtitle?.trim()) searchParams.set("subtitle", params.subtitle.trim());
1089
+ if (params.eyebrow?.trim()) searchParams.set("eyebrow", params.eyebrow.trim());
1090
+ if (palette.accentMuted) searchParams.set("accentMuted", palette.accentMuted);
1091
+ if (palette.mutedText) searchParams.set("muted", palette.mutedText);
1092
+ return `${base}${apiPath}?${searchParams.toString()}`;
1093
+ }
1094
+ function buildPageSeoValues(config, options) {
1095
+ const base = config.baseUrl.replace(/\/$/, "");
1096
+ const path = options.path ?? "/";
1097
+ const url = `${base}${path.startsWith("/") ? path : `/${path}`}`;
1098
+ const description = options.description?.trim() || config.defaultDescription || `${options.title} - ${config.siteName}.`;
1099
+ return {
1100
+ title: applyTitleTemplate(config, options.title),
1101
+ siteName: config.siteName,
1102
+ twitter: config.twitter ?? "",
1103
+ description,
1104
+ url,
1105
+ keywords: options.keywords ?? config.defaultKeywords ?? options.title,
1106
+ image: options.image ?? buildOgImageUrl(config, {
1107
+ title: options.title,
1108
+ subtitle: description,
1109
+ eyebrow: options.eyebrow,
1110
+ type: options.ogType === "article" ? "article" : "listing"
1111
+ }),
1112
+ noindex: options.noindex ?? config.noindex,
1113
+ manifestUrl: config.manifestUrl
1114
+ };
1115
+ }
1116
+ function resolveArticleImage(post, config) {
1117
+ const featuredImage = config.cmsUrl ? cmsImage(post.featured_image, { cmsUrl: config.cmsUrl }) : "";
1118
+ if (featuredImage) return featuredImage;
1119
+ const description = post.meta_description?.trim() || post.description?.trim() || config.defaultDescription;
1120
+ return buildOgImageUrl(config, {
1121
+ title: post.title,
1122
+ subtitle: description,
1123
+ eyebrow: config.contentLabel ?? "Article",
1124
+ type: "article"
1125
+ });
1126
+ }
1127
+ function buildArticleSeoValues(post, config, slug, options) {
1128
+ const articlePath = config.articlePath ?? "/blog";
1129
+ const url = `${config.baseUrl.replace(/\/$/, "")}${articlePath}/${slug}`;
1130
+ const description = post.meta_description?.trim() || post.description?.trim() || config.defaultDescription || `Read the latest from ${config.siteName}.`;
1131
+ return {
1132
+ title: applyTitleTemplate(config, post.title),
1133
+ siteName: config.siteName,
1134
+ twitter: config.twitter ?? "",
1135
+ description,
1136
+ url,
1137
+ keywords: config.defaultKeywords ?? post.title,
1138
+ image: resolveArticleImage(post, config),
1139
+ noindex: options?.noindex ?? config.noindex,
1140
+ manifestUrl: config.manifestUrl
1141
+ };
1142
+ }
1143
+ function buildArticleListingSeoValues(config, options) {
1144
+ const articlePath = config.articlePath ?? "/blog";
1145
+ const base = config.baseUrl.replace(/\/$/, "");
1146
+ const label = config.contentLabel ?? "Articles";
1147
+ const categoryName = options?.categoryName?.trim();
1148
+ const categorySlug = options?.categorySlug?.trim();
1149
+ const titleText = categoryName ? `${categoryName} ${label}` : label;
1150
+ const description = categoryName ? `Explore ${categoryName} ${label.toLowerCase()}, guides, and the latest updates from ${config.siteName}.` : config.defaultDescription || `Browse ${label.toLowerCase()}, insights, and the latest updates from ${config.siteName}.`;
1151
+ const url = categorySlug ? `${base}${articlePath}/category/${categorySlug}` : `${base}${articlePath}`;
1152
+ return {
1153
+ title: applyTitleTemplate(config, titleText),
1154
+ siteName: config.siteName,
1155
+ twitter: config.twitter ?? "",
1156
+ description,
1157
+ url,
1158
+ keywords: config.defaultKeywords ?? (categoryName ? `${categoryName}, ${config.siteName}` : `${config.siteName} ${label.toLowerCase()}`),
1159
+ image: buildOgImageUrl(config, {
1160
+ title: titleText,
1161
+ subtitle: description,
1162
+ eyebrow: categoryName ? "Category" : label,
1163
+ type: "listing"
1164
+ }),
1165
+ noindex: options?.noindex ?? config.noindex,
1166
+ manifestUrl: config.manifestUrl
1167
+ };
1168
+ }
1169
+ function seoValuesToClientProps(values) {
1170
+ return {
1171
+ title: values.title,
1172
+ description: values.description,
1173
+ url: values.url,
1174
+ siteName: values.siteName,
1175
+ keywords: values.keywords,
1176
+ twitter: values.twitter,
1177
+ image: values.image,
1178
+ noindex: values.noindex
1179
+ };
1180
+ }
1181
+
1182
+ // src/seo/jsonld.ts
1183
+ function buildSiteJsonLd(config) {
1184
+ const siteUrl = config.baseUrl.replace(/\/$/, "");
1185
+ const org = config.organization;
1186
+ const organizationNode = {
1187
+ "@type": "Organization",
1188
+ "@id": `${siteUrl}/#organization`,
1189
+ name: config.siteName,
1190
+ legalName: config.siteName,
1191
+ url: siteUrl
1192
+ };
1193
+ if (org?.logoUrl) {
1194
+ organizationNode.logo = { "@type": "ImageObject", url: org.logoUrl };
1195
+ }
1196
+ if (org?.contactPhone || org?.contactEmail) {
1197
+ organizationNode.contactPoint = {
1198
+ "@type": "ContactPoint",
1199
+ ...org.contactPhone ? { telephone: org.contactPhone } : {},
1200
+ ...org.contactEmail ? { email: org.contactEmail } : {},
1201
+ contactType: "customer service"
1202
+ };
1203
+ }
1204
+ if (org?.address) {
1205
+ organizationNode.address = {
1206
+ "@type": "PostalAddress",
1207
+ ...org.address.street ? { streetAddress: org.address.street } : {},
1208
+ ...org.address.city ? { addressLocality: org.address.city } : {},
1209
+ ...org.address.country ? { addressCountry: org.address.country } : {}
1210
+ };
1211
+ }
1212
+ if (org?.socials?.length) {
1213
+ organizationNode.sameAs = org.socials;
1214
+ }
1215
+ const websiteNode = {
1216
+ "@type": "WebSite",
1217
+ "@id": `${siteUrl}/#website`,
1218
+ url: siteUrl,
1219
+ name: config.siteName,
1220
+ publisher: { "@id": `${siteUrl}/#organization` },
1221
+ inLanguage: "en-US"
1222
+ };
1223
+ if (config.defaultDescription) {
1224
+ websiteNode.description = config.defaultDescription;
1225
+ }
1226
+ return {
1227
+ "@context": "https://schema.org",
1228
+ "@graph": [organizationNode, websiteNode, ...config.extraJsonLdNodes ?? []]
1229
+ };
1230
+ }
1231
+ function buildArticleJsonLd(props) {
1232
+ return {
1233
+ "@context": "https://schema.org",
1234
+ "@type": props.articleType ?? "Article",
1235
+ headline: props.title,
1236
+ description: props.description,
1237
+ url: props.url,
1238
+ ...props.image ? { image: props.image } : {},
1239
+ ...props.publishedTime ? { datePublished: props.publishedTime } : {},
1240
+ ...props.category ? { articleSection: props.category } : {},
1241
+ ...props.tags && props.tags.length > 0 ? { keywords: props.tags.join(", ") } : {},
1242
+ author: { "@type": "Person", name: props.authorName || props.siteName },
1243
+ publisher: { "@id": `${props.siteUrl}/#organization` },
1244
+ isPartOf: { "@id": `${props.siteUrl}/#website` },
1245
+ inLanguage: "en-US"
1246
+ };
1247
+ }
1248
+ function buildCollectionJsonLd(props) {
1249
+ return {
1250
+ "@context": "https://schema.org",
1251
+ "@type": "CollectionPage",
1252
+ name: props.name,
1253
+ description: props.description,
1254
+ url: props.url,
1255
+ isPartOf: { "@id": `${props.siteUrl}/#website` },
1256
+ publisher: { "@id": `${props.siteUrl}/#organization` },
1257
+ inLanguage: "en-US"
1258
+ };
1259
+ }
1260
+ function buildWebPageJsonLd(props) {
1261
+ return {
1262
+ "@context": "https://schema.org",
1263
+ "@type": "WebPage",
1264
+ name: props.name,
1265
+ description: props.description,
1266
+ url: props.url,
1267
+ isPartOf: { "@id": `${props.siteUrl}/#website` },
1268
+ publisher: { "@id": `${props.siteUrl}/#organization` },
1269
+ inLanguage: "en-US"
1270
+ };
1271
+ }
1272
+ var DEFAULT_PALETTE = {
1273
+ background: "#0C0D10",
1274
+ foreground: "#FFFFFF",
1275
+ accent: "#EDB435",
1276
+ accentMuted: "rgba(237,180,53,0.22)",
1277
+ mutedText: "rgba(255,255,255,0.65)"
1278
+ };
1279
+ function parseOgImageSearchParams(searchParams) {
1280
+ const palette = {
1281
+ background: searchParams.get("bg") ?? DEFAULT_PALETTE.background,
1282
+ foreground: searchParams.get("fg") ?? DEFAULT_PALETTE.foreground,
1283
+ accent: searchParams.get("accent") ?? DEFAULT_PALETTE.accent,
1284
+ accentMuted: searchParams.get("accentMuted") ?? DEFAULT_PALETTE.accentMuted,
1285
+ mutedText: searchParams.get("muted") ?? DEFAULT_PALETTE.mutedText
1286
+ };
1287
+ return {
1288
+ title: searchParams.get("title")?.trim() || "Articles",
1289
+ subtitle: searchParams.get("subtitle")?.trim() || void 0,
1290
+ eyebrow: searchParams.get("eyebrow")?.trim() || void 0,
1291
+ siteName: searchParams.get("siteName")?.trim() || void 0,
1292
+ variant: searchParams.get("type") === "listing" ? "listing" : "article",
1293
+ palette
1294
+ };
1295
+ }
1296
+ function truncateText(value, maxLength) {
1297
+ const trimmed = value.trim();
1298
+ if (trimmed.length <= maxLength) return trimmed;
1299
+ return `${trimmed.slice(0, maxLength - 3).trimEnd()}...`;
1300
+ }
1301
+ function toSatoriColor(color) {
1302
+ const rgbaMatch = color.match(
1303
+ /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i
1304
+ );
1305
+ if (!rgbaMatch) return color;
1306
+ const [, r, g, b, a = "1"] = rgbaMatch;
1307
+ const hex = [r, g, b].map((channel) => Number(channel).toString(16).padStart(2, "0")).join("");
1308
+ const alpha = Math.round(parseFloat(a) * 255).toString(16).padStart(2, "0");
1309
+ return `#${hex}${alpha}`;
1310
+ }
1311
+ function OgImageContent({
1312
+ title,
1313
+ subtitle,
1314
+ eyebrow,
1315
+ siteName,
1316
+ palette,
1317
+ variant = "article"
1318
+ }) {
1319
+ const displayTitle = truncateText(title, 120);
1320
+ const displaySubtitle = subtitle ? truncateText(subtitle, 160) : void 0;
1321
+ const displayEyebrow = eyebrow?.trim() || (variant === "listing" ? "Articles" : "Article");
1322
+ const background = toSatoriColor(palette.background);
1323
+ const foreground = toSatoriColor(palette.foreground);
1324
+ const accent = toSatoriColor(palette.accent);
1325
+ const accentMuted = toSatoriColor(palette.accentMuted ?? palette.accent);
1326
+ const mutedText = toSatoriColor(palette.mutedText ?? palette.foreground);
1327
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1328
+ "div",
1329
+ {
1330
+ style: {
1331
+ width: 1200,
1332
+ height: 630,
1333
+ display: "flex",
1334
+ flexDirection: "column",
1335
+ background,
1336
+ color: foreground,
1337
+ fontFamily: "sans-serif"
1338
+ },
1339
+ children: [
1340
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 6, background: accent } }),
1341
+ /* @__PURE__ */ jsxRuntime.jsxs(
1342
+ "div",
1343
+ {
1344
+ style: {
1345
+ display: "flex",
1346
+ flexDirection: "column",
1347
+ justifyContent: "space-between",
1348
+ flex: 1,
1349
+ padding: "66px 80px 72px"
1350
+ },
1351
+ children: [
1352
+ /* @__PURE__ */ jsxRuntime.jsx(
1353
+ "div",
1354
+ {
1355
+ style: {
1356
+ display: "flex",
1357
+ alignItems: "center",
1358
+ padding: "10px 18px",
1359
+ borderRadius: 999,
1360
+ border: `1px solid ${accentMuted}`,
1361
+ color: accent,
1362
+ fontSize: 22,
1363
+ fontWeight: 700,
1364
+ letterSpacing: 0,
1365
+ textTransform: "uppercase",
1366
+ alignSelf: "flex-start"
1367
+ },
1368
+ children: displayEyebrow
1369
+ }
1370
+ ),
1371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", maxWidth: 980 }, children: [
1372
+ /* @__PURE__ */ jsxRuntime.jsx(
1373
+ "div",
1374
+ {
1375
+ style: {
1376
+ fontSize: displayTitle.length > 70 ? 52 : 64,
1377
+ fontWeight: 800,
1378
+ lineHeight: 1.05,
1379
+ letterSpacing: 0,
1380
+ color: foreground
1381
+ },
1382
+ children: displayTitle
1383
+ }
1384
+ ),
1385
+ displaySubtitle ? /* @__PURE__ */ jsxRuntime.jsx(
1386
+ "div",
1387
+ {
1388
+ style: {
1389
+ marginTop: 24,
1390
+ fontSize: 28,
1391
+ lineHeight: 1.4,
1392
+ color: mutedText,
1393
+ maxWidth: 900
1394
+ },
1395
+ children: displaySubtitle
1396
+ }
1397
+ ) : null
1398
+ ] }),
1399
+ siteName ? /* @__PURE__ */ jsxRuntime.jsxs(
1400
+ "div",
1401
+ {
1402
+ style: {
1403
+ display: "flex",
1404
+ alignItems: "center",
1405
+ justifyContent: "space-between",
1406
+ fontSize: 24,
1407
+ fontWeight: 700,
1408
+ color: accent
1409
+ },
1410
+ children: [
1411
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: siteName }),
1412
+ /* @__PURE__ */ jsxRuntime.jsx(
1413
+ "div",
1414
+ {
1415
+ style: {
1416
+ width: 72,
1417
+ height: 4,
1418
+ borderRadius: 999,
1419
+ background: accent
1420
+ }
1421
+ }
1422
+ )
1423
+ ]
1424
+ }
1425
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex" } })
1426
+ ]
1427
+ }
1428
+ )
1429
+ ]
1430
+ }
1431
+ );
1432
+ }
1433
+
1434
+ exports.OgImageContent = OgImageContent;
1435
+ exports.applyTitleTemplate = applyTitleTemplate;
1436
+ exports.buildArticleJsonLd = buildArticleJsonLd;
1437
+ exports.buildArticleListingSeoValues = buildArticleListingSeoValues;
1438
+ exports.buildArticleSeoValues = buildArticleSeoValues;
1067
1439
  exports.buildCmsMutation = buildCmsMutation;
1068
1440
  exports.buildCmsQuery = buildCmsQuery;
1441
+ exports.buildCollectionJsonLd = buildCollectionJsonLd;
1442
+ exports.buildOgImageUrl = buildOgImageUrl;
1443
+ exports.buildPageSeoValues = buildPageSeoValues;
1444
+ exports.buildSiteJsonLd = buildSiteJsonLd;
1445
+ exports.buildWebPageJsonLd = buildWebPageJsonLd;
1069
1446
  exports.cmsImage = cmsImage;
1070
1447
  exports.cmsMutate = cmsMutate;
1071
1448
  exports.createApolloClient = createApolloClient;
@@ -1073,8 +1450,10 @@ exports.extractHeadingsFromElement = extractHeadingsFromElement;
1073
1450
  exports.extractHeadingsFromHtml = extractHeadingsFromHtml;
1074
1451
  exports.fetchCmsContent = fetchCmsContent;
1075
1452
  exports.getContentReadTime = getContentReadTime;
1453
+ exports.parseOgImageSearchParams = parseOgImageSearchParams;
1076
1454
  exports.parseRichText = parseRichText;
1077
1455
  exports.removeEmptyParagraphs = removeEmptyParagraphs;
1456
+ exports.seoValuesToClientProps = seoValuesToClientProps;
1078
1457
  exports.slugify = slugify;
1079
1458
  //# sourceMappingURL=index.cjs.map
1080
1459
  //# sourceMappingURL=index.cjs.map