Skip to content

Commit 6a69d71

Browse files
committed
feat(sanity,syndication): serve Dev.to and syndication images from Sanity Media Library CDN
1 parent d2e48bf commit 6a69d71

5 files changed

Lines changed: 650 additions & 10 deletions

File tree

‎.github/workflows/syndicate-post.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ jobs:
3939
run: |
4040
if [ "$TARGET_DOCUMENT_ID" = "backfill-dailydev" ]; then
4141
node scripts/backfill-dailydev-and-links.mjs
42+
elif [ "$TARGET_DOCUMENT_ID" = "backfill-devto-media-library" ]; then
43+
node scripts/backfill-devto-media-library.mjs
4244
elif [ "$TARGET_DOCUMENT_ID" = "enrich-aeo-seo" ]; then
4345
SANITY_STUDIO_DATASET=production node scripts/enrich-all-posts-aeo-seo-links.mjs
4446
SANITY_STUDIO_DATASET=dev node scripts/enrich-all-posts-aeo-seo-links.mjs

‎apps/sanity/functions/syndicate-content/index.ts‎

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,45 @@ export const handler = documentEventHandler<ContentDocument>(
7373
? `https://codingcat.dev/${data._type}/${slug}`
7474
: `https://dev.codingcat.dev/${data._type}/${slug}`;
7575

76-
const imgBuilder = createImageUrlBuilder({ projectId, dataset });
76+
const DEFAULT_MEDIA_LIBRARY_ID =
77+
process.env.SANITY_MEDIA_LIBRARY_ID ||
78+
process.env.PUBLIC_SANITY_MEDIA_LIBRARY_ID ||
79+
"mlttp0BZbCpV";
80+
const datasetImgBuilder = createImageUrlBuilder({ projectId, dataset });
81+
const mediaLibraryBuilders = new Map<
82+
string,
83+
ReturnType<typeof createImageUrlBuilder>
84+
>();
85+
const getMediaLibraryBuilder = (libraryId: string) => {
86+
let b = mediaLibraryBuilders.get(libraryId);
87+
if (!b) {
88+
b = createImageUrlBuilder({
89+
mediaLibraryId: libraryId,
90+
} as unknown as { projectId: string; dataset: string });
91+
mediaLibraryBuilders.set(libraryId, b);
92+
}
93+
return b;
94+
};
95+
const extractMediaLibraryId = (source: any): string | undefined => {
96+
if (!source || typeof source !== "object") return undefined;
97+
const mediaRef = source?.media?._ref;
98+
if (
99+
typeof mediaRef === "string" &&
100+
mediaRef.startsWith("media-library:")
101+
) {
102+
const [, libraryId] = mediaRef.split(":");
103+
if (libraryId) return libraryId;
104+
}
105+
return undefined;
106+
};
77107
const urlForImage = (source: any) => {
78108
if (!source || !source.asset) return undefined;
79-
return imgBuilder.image(source).auto("format").fit("max");
109+
const mediaLibraryId =
110+
extractMediaLibraryId(source) || DEFAULT_MEDIA_LIBRARY_ID;
111+
const builder = mediaLibraryId
112+
? getMediaLibraryBuilder(mediaLibraryId)
113+
: datasetImgBuilder;
114+
return builder.image(source).auto("format").fit("max");
80115
};
81116

82117
const extractYouTubeId = (url: string | undefined): string | null => {
@@ -128,7 +163,40 @@ export const handler = documentEventHandler<ContentDocument>(
128163
"```" + (value?.language || "") + "\n" + (value?.code || "") + "\n```",
129164
image: ({ value }) => {
130165
const url = urlForImage(value)?.url();
131-
return url ? `![](${url})` : "";
166+
const alt = (value?.alt || "").replace(/[\[\]\r\n]/g, " ").trim();
167+
return url ? `![${alt}](${url})` : "";
168+
},
169+
table: ({ value }) => {
170+
const rows = Array.isArray(value?.rows) ? value.rows : [];
171+
if (rows.length === 0) return "";
172+
const formatRow = (cells: any[]) =>
173+
`| ${(Array.isArray(cells) ? cells : [])
174+
.map((c) =>
175+
String(c ?? "")
176+
.replace(/\r?\n/g, " ")
177+
.replace(/\|/g, "\\|")
178+
.trim(),
179+
)
180+
.join(" | ")} |`;
181+
const headerCells = Array.isArray(rows[0]?.cells)
182+
? rows[0].cells
183+
: [];
184+
if (headerCells.length === 0) return "";
185+
const headerLine = formatRow(headerCells);
186+
const sepLine = `| ${headerCells.map(() => "---").join(" | ")} |`;
187+
const bodyLines = rows
188+
.slice(1)
189+
.map((r: any) => formatRow(r?.cells || []));
190+
const titlePrefix = value?.title ? `### ${value.title}\n\n` : "";
191+
return `${titlePrefix}${[headerLine, sepLine, ...bodyLines].join("\n")}`;
192+
},
193+
htmlBlock: ({ value }) => {
194+
const rawHtml = String(value?.html || "");
195+
if (!rawHtml) return "";
196+
if (/<style|<svg|<script/i.test(rawHtml)) {
197+
return `*[Interactive diagram available in the full article on CodingCat.dev](${canonicalUrl})*`;
198+
}
199+
return rawHtml;
132200
},
133201
youtube: ({ value }) =>
134202
formatYouTubeBlock(value?.youtube || value?.url || "", targetPlatform),
@@ -143,7 +211,21 @@ export const handler = documentEventHandler<ContentDocument>(
143211
const id = value?.url?.split("https://codesandbox.io/p/sandbox/")?.at(-1);
144212
return `{% codesandbox ${id || value?.url || ""} %}`;
145213
},
146-
twitter: ({ value }) => `{% twitter ${value?.id || ""} %}`,
214+
twitter: ({ value }) => {
215+
const id =
216+
value?.id ||
217+
value?.url?.match(/status(?:es)?\/(\d+)/i)?.[1] ||
218+
"";
219+
if (targetPlatform === "devto" && id) {
220+
return `{% twitter ${id} %}`;
221+
}
222+
const url =
223+
value?.url || (id ? `https://x.com/i/status/${id}` : "");
224+
if (targetPlatform === "hashnode" && url) {
225+
return `%[${url}]`;
226+
}
227+
return url ? `[View post on X](${url})` : "";
228+
},
147229
quote: ({ value }) => `> ${serializeContent(value?.content, targetPlatform)}`,
148230
},
149231
});

‎scripts/backfill-dailydev-and-links.mjs‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,36 @@ const prodClient = createClient({
1717
useCdn: false,
1818
});
1919

20-
const imgBuilder = createImageUrlBuilder({ projectId, dataset: "production" });
20+
const DEFAULT_MEDIA_LIBRARY_ID =
21+
process.env.SANITY_MEDIA_LIBRARY_ID ||
22+
process.env.PUBLIC_SANITY_MEDIA_LIBRARY_ID ||
23+
"mlttp0BZbCpV";
24+
const datasetImgBuilder = createImageUrlBuilder({ projectId, dataset: "production" });
25+
const mediaLibraryBuilders = new Map();
26+
const getMediaLibraryBuilder = (libraryId) => {
27+
let b = mediaLibraryBuilders.get(libraryId);
28+
if (!b) {
29+
b = createImageUrlBuilder({ mediaLibraryId: libraryId });
30+
mediaLibraryBuilders.set(libraryId, b);
31+
}
32+
return b;
33+
};
34+
const extractMediaLibraryId = (source) => {
35+
if (!source || typeof source !== "object") return undefined;
36+
const mediaRef = source?.media?._ref;
37+
if (typeof mediaRef === "string" && mediaRef.startsWith("media-library:")) {
38+
const [, libraryId] = mediaRef.split(":");
39+
if (libraryId) return libraryId;
40+
}
41+
return undefined;
42+
};
2143
const urlForImage = (source) => {
2244
if (!source || !source.asset) return undefined;
23-
return imgBuilder.image(source).auto("format").fit("max");
45+
const mediaLibraryId = extractMediaLibraryId(source) || DEFAULT_MEDIA_LIBRARY_ID;
46+
const builder = mediaLibraryId
47+
? getMediaLibraryBuilder(mediaLibraryId)
48+
: datasetImgBuilder;
49+
return builder.image(source).auto("format").fit("max");
2450
};
2551

2652
function extractYouTubeId(url) {

0 commit comments

Comments
 (0)