Skip to content

Commit d968bee

Browse files
authored
Merge pull request #82 from cmpadden/feat/external-blog-indicator
Adds indicator of blogs hosted externally
2 parents ef090ad + e56a6c0 commit d968bee

8 files changed

Lines changed: 160 additions & 59 deletions

File tree

components/Header.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const route = useRoute();
1010
<HeadlessMenu as="div" class="relative z-50 inline-block text-left">
1111
<div>
1212
<HeadlessMenuButton
13-
class="align-middle transform text-gray-700 transition-all duration-300 ease-in-out hover:scale-110 hover:text-orange-500 active:scale-95 dark:text-white"
13+
class="transform align-middle text-gray-700 transition-all duration-300 ease-in-out hover:scale-110 hover:text-orange-500 active:scale-95 dark:text-white"
1414
>
1515
<svg
1616
xmlns="http://www.w3.org/2000/svg"
@@ -108,7 +108,7 @@ const route = useRoute();
108108
? 'bg-orange-500/50 text-white dark:text-white'
109109
: 'text-gray-700 dark:text-white',
110110
$route.path === '/playground' ? 'bg-orange-500' : '',
111-
'y-2 group flex w-full items-center px-2 text-sm',
111+
'group flex w-full items-center px-2 py-2 text-sm',
112112
]"
113113
>
114114
<svg

components/section/BlogPosts.vue

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,34 @@ const props = defineProps({
1313
<div
1414
class="h-full space-y-2 bg-black/50 p-3 text-white drop-shadow-lg hover:ring-1 hover:ring-white"
1515
>
16-
<div class="flex">
16+
<div class="flex items-center gap-2">
1717
<div
1818
class="line-clamp-1 flex-1 text-lg font-bold md:text-xl"
1919
:title="article.title"
2020
>
2121
{{ article.title }}
2222
</div>
23-
<NuxtTime
24-
v-if="show_dates"
25-
:datetime="article.date"
26-
class="text-sm text-gray-200"
27-
year="numeric"
28-
month="short"
29-
day="2-digit"
30-
/>
23+
<template v-if="article.external_url">
24+
<span
25+
class="rounded border border-orange-500/30 bg-orange-500/20 px-2 py-0.5 text-[10px] uppercase tracking-wide text-orange-300"
26+
>External</span
27+
>
28+
</template>
29+
<template v-if="show_dates">
30+
<NuxtTime
31+
:datetime="article.date"
32+
class="text-sm text-gray-200"
33+
year="numeric"
34+
month="short"
35+
day="2-digit"
36+
/>
37+
</template>
3138
</div>
3239
<div class="line-clamp-3 text-sm text-gray-100">
33-
{{ article.description }}
40+
{{
41+
article.description ||
42+
article.meta?.excerpt?.children?.[0]?.value
43+
}}
3444
</div>
3545
</div>
3646
</NuxtLink>

content.config.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,25 @@ export default defineContentConfig({
66
source: '**/*.md',
77
type: 'page',
88
schema: z.object({
9+
// required
910
tags: z.array(z.string()),
1011
categories: z.array(z.string()),
11-
img: z.string(),
1212
date: z.date(),
13+
14+
// optional/expanded
15+
title: z.string().optional(),
16+
description: z.string().optional(),
17+
cover_image: z.string().optional(),
18+
// legacy compatibility
19+
img: z.string().optional(),
20+
21+
// external linking
22+
external_url: z.string().url().optional(),
23+
external_site: z.string().optional(),
24+
25+
// seo
26+
canonical_url: z.string().url().optional(),
27+
draft: z.boolean().optional(),
1328
})
1429
})
1530
}

content/blog/using-codex-for-education-at-dagster-labs.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ title: 'Using Codex for education at Dagster Labs'
33
date: "2025-10-27"
44
tags: ["ai", "education"]
55
categories: ["ai"]
6+
description: "How we structure docs so AI tools can reason with them effectively."
7+
external_url: "https://developers.openai.com/blog/codex-for-documentation-dagster"
8+
external_site: "OpenAI Developer Blog"
69
---
710

811
I recently had the opportunity to showcase how we use Codex for educational content at Dagster Labs which was featured on the OpenAI developer blog; you can find a full copy of that post below.
912

1013
<!--more-->
1114

12-
https://developers.openai.com/blog/codex-for-documentation-dagster
15+
1316

1417
## Overview
1518

pages/blog/[...slug].vue

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ const route = useRoute();
44
const { data: page } = await useAsyncData(route.path, () => {
55
return queryCollection("content").path(route.path).first();
66
});
7+
8+
watchEffect(() => {
9+
if (!page.value) return;
10+
const isExternal = Boolean((page.value as any).external_url);
11+
const canonical =
12+
(page.value as any).canonical_url || (page.value as any).external_url;
13+
14+
useHead({
15+
meta: [
16+
// avoid duplicate indexing for external summaries
17+
isExternal ? { name: "robots", content: "noindex,follow" } : undefined,
18+
].filter(Boolean) as any,
19+
link: [
20+
canonical ? { rel: "canonical", href: canonical } : undefined,
21+
].filter(Boolean) as any,
22+
});
23+
});
24+
25+
function externalSite(p: any) {
26+
if (!p?.external_url) return "";
27+
if (p?.external_site) return p.external_site;
28+
try {
29+
const u = new URL(p.external_url);
30+
return u.hostname.replace(/^www\./, "");
31+
} catch {
32+
return p.external_url;
33+
}
34+
}
735
</script>
836

937
<template>
@@ -14,8 +42,11 @@ const { data: page } = await useAsyncData(route.path, () => {
1442
<!-- title -->
1543
<div class="flex">
1644
<!-- https://content.nuxt.com/components/content-slot -->
17-
<template v-if="page.cover_image">
18-
<img class="mr-4 h-16 border-2 border-black" :src="page.cover_image" />
45+
<template v-if="page.cover_image || page.img">
46+
<img
47+
class="mr-4 h-16 border-2 border-black"
48+
:src="page.cover_image || page.img"
49+
/>
1950
</template>
2051

2152
<h1 class="text-2xl font-bold md:text-3xl">
@@ -66,6 +97,25 @@ const { data: page } = await useAsyncData(route.path, () => {
6697
</div>
6798
</div>
6899

100+
<!-- external banner and CTA (below title/date, above content) -->
101+
<div
102+
v-if="page.external_url"
103+
class="flex items-center justify-between gap-4 rounded border border-orange-500/30 bg-orange-500/10 p-3"
104+
>
105+
<div class="text-sm text-gray-200">
106+
The original version of this article can be found on the
107+
108+
<a
109+
:href="page.external_url"
110+
target="_blank"
111+
rel="noopener noreferrer"
112+
class="font-semibold text-orange-300"
113+
>
114+
{{ externalSite(page) }} </a
115+
>.
116+
</div>
117+
</div>
118+
69119
<!--
70120
- Remove maximum width of prose content: https://github.com/tailwindlabs/tailwindcss-typography#overriding-max-width
71121
- Use prose-pre:bg-white to work with @nuxt/content syntax highlighting, otherwise background-color defaults to `.prose:where(pre)`

pages/blog/index.vue

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ const paginatedArticles = computed(() => {
6464
return visibleArticles.value.slice(start, start + pageSize);
6565
});
6666
67+
function externalSite(article) {
68+
if (!article?.external_url) return "";
69+
if (article?.external_site) return article.external_site;
70+
try {
71+
const u = new URL(article.external_url);
72+
return u.hostname.replace(/^www\./, "");
73+
} catch {
74+
return article.external_url;
75+
}
76+
}
77+
6778
function toggleTag(tag) {
6879
if (selectedTags.value.includes(tag)) {
6980
selectedTags.value = selectedTags.value.filter((el) => el !== tag);
@@ -201,46 +212,51 @@ watch(
201212
<div class="container mx-auto space-y-8">
202213
<div class="space-y-4">
203214
<div class="grid gap-4">
204-
<NuxtLink
205-
v-for="article in paginatedArticles"
206-
:key="article._id"
207-
:to="article.path"
208-
class="h-full"
209-
>
210-
<div
211-
class="flex flex-col justify-between space-y-3 bg-black/50 p-4 text-white drop-shadow-lg hover:ring-1 hover:ring-white"
212-
>
213-
<div class="space-y-1">
214-
<div
215-
class="flex flex-col gap-1 md:flex-row md:items-center md:gap-3"
216-
>
217-
<p
218-
class="text-lg font-semibold text-orange-400 md:flex-1 md:text-xl"
215+
<template v-for="article in paginatedArticles" :key="article._id">
216+
<NuxtLink :to="article.path" class="h-full">
217+
<div
218+
class="flex flex-col justify-between space-y-3 bg-black/50 p-4 text-white drop-shadow-lg hover:ring-1 hover:ring-white"
219+
>
220+
<div class="space-y-1">
221+
<div
222+
class="flex flex-col gap-1 md:flex-row md:items-center md:gap-3"
219223
>
220-
{{ article.title }}
221-
</p>
222-
<NuxtTime
223-
:datetime="article.date"
224-
class="text-sm text-gray-300 md:text-right"
225-
year="numeric"
226-
month="short"
227-
day="2-digit"
228-
/>
224+
<p
225+
class="text-lg font-semibold text-orange-400 md:flex-1 md:text-xl"
226+
>
227+
{{ article.title }}
228+
</p>
229+
<div class="flex items-center gap-2 md:ml-auto">
230+
<template v-if="article.external_url">
231+
<span
232+
class="rounded border border-orange-500/30 bg-orange-500/20 px-2 py-0.5 text-[10px] uppercase tracking-wide text-orange-300"
233+
>{{ externalSite(article) }}</span
234+
>
235+
</template>
236+
<NuxtTime
237+
:datetime="article.date"
238+
class="text-sm text-gray-300 md:text-right"
239+
year="numeric"
240+
month="short"
241+
day="2-digit"
242+
/>
243+
</div>
244+
</div>
229245
</div>
246+
<p
247+
v-if="article.description"
248+
class="line-clamp-3 text-sm text-gray-100"
249+
>
250+
{{ article.description }}
251+
</p>
252+
<ContentRenderer
253+
v-else-if="article.meta?.excerpt"
254+
:value="article.meta.excerpt"
255+
class="line-clamp-4 text-sm text-gray-100"
256+
/>
230257
</div>
231-
<p
232-
v-if="article.description"
233-
class="line-clamp-3 text-sm text-gray-100"
234-
>
235-
{{ article.description }}
236-
</p>
237-
<ContentRenderer
238-
v-else-if="article.meta?.excerpt"
239-
:value="article.meta.excerpt"
240-
class="line-clamp-4 text-sm text-gray-100"
241-
/>
242-
</div>
243-
</NuxtLink>
258+
</NuxtLink>
259+
</template>
244260
</div>
245261
246262
<div class="flex items-center justify-between text-sm text-gray-300">

server/routes/atom.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,23 @@ export default defineEventHandler(async (event) => {
2929
articles.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
3030

3131
articles.forEach((article) => {
32+
const isExternal = Boolean((article as any).external_url)
33+
const link = isExternal ? (article as any).external_url : `${BASE_URL}${article.path}`
34+
const id = link
35+
const imagePath = (article as any).cover_image || (article as any).img
36+
3237
feed.addItem({
3338
title: article.title ? article.title : "Missing Title",
34-
id: article.path,
35-
link: `${BASE_URL}${article.path}`,
36-
description: article.description,
39+
id,
40+
link,
41+
description: (article as any).description,
3742
author: [
3843
{
3944
name: AUTHOR_NAME,
4045
},
4146
],
4247
date: new Date(article.date),
43-
image: article.cover_image ? `${BASE_URL}/${article.cover_image}` : undefined
48+
image: imagePath ? (imagePath.startsWith('http') ? imagePath : `${BASE_URL}${imagePath.startsWith('/') ? '' : '/'}${imagePath}`) : undefined
4449
});
4550
});
4651

server/routes/sitemap.xml.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ export default defineEventHandler(async (event) => {
1818
sitemap.write({ url: '/playground/french' });
1919
sitemap.write({ url: '/playground/matrix' });
2020

21-
// Dynamically generate routes for Nuxt markdown content
22-
articles.forEach((article) => sitemap.write({ url: article.path, changefreq: 'monthly' }));
21+
// Dynamically generate routes for Nuxt markdown content (exclude external summaries)
22+
articles
23+
.filter((a: any) => !a.external_url)
24+
.forEach((article) => sitemap.write({ url: article.path, changefreq: 'monthly' }));
2325
sitemap.end();
2426

2527
return (await streamToPromise(sitemap));

0 commit comments

Comments
 (0)