Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import code, {
PageMetadata,
StructuredDataOptions,
BrandingOptions,
SocialPreviewOptions,
SeoOptions,
AnalyticsOptions,
CachingOptions,
Expand Down Expand Up @@ -155,6 +156,13 @@ export default function App() {
twitterHandle: "",
faviconUrl: "",
});
const [socialPreview, setSocialPreview] = useState<SocialPreviewOptions>({
defaultImage: "",
imageWidth: 1200,
imageHeight: 630,
twitterCardType: "summary_large_image",
locale: "",
});
const [seo, setSeo] = useState<SeoOptions>({
aiAttribution: "",
});
Expand Down Expand Up @@ -279,6 +287,17 @@ export default function App() {
setCopied(false);
}

function handleSocialPreviewChange(
field: keyof SocialPreviewOptions,
value: string | number,
): void {
setSocialPreview({
...socialPreview,
[field]: value,
});
setCopied(false);
}

function handleSeoChange(field: keyof SeoOptions, value: string): void {
setSeo({
...seo,
Expand Down Expand Up @@ -442,6 +461,7 @@ export default function App() {
pageMetadata,
structuredData,
branding,
socialPreview,
seo,
analytics,
caching,
Expand Down Expand Up @@ -930,6 +950,99 @@ export default function App() {
/>
</Box>

<Box sx={{ mt: 3, pt: 2, borderTop: 1, borderColor: "grey.300" }}>
<Typography
variant="subtitle2"
color="text.secondary"
gutterBottom
>
Social Preview
</Typography>
<TextField
fullWidth
label="Default OG Image URL"
margin="dense"
placeholder="https://example.com/og-image.jpg"
helperText="Fallback image for pages without a specific OG image"
onChange={(e) =>
handleSocialPreviewChange("defaultImage", e.target.value)
}
value={socialPreview.defaultImage}
variant="outlined"
size="small"
/>
<Stack direction="row" spacing={2}>
<TextField
type="number"
label="Image Width"
margin="dense"
placeholder="1200"
helperText="og:image:width"
onChange={(e) =>
handleSocialPreviewChange(
"imageWidth",
Number(e.target.value),
)
}
value={socialPreview.imageWidth}
variant="outlined"
size="small"
sx={{ flex: 1 }}
/>
<TextField
type="number"
label="Image Height"
margin="dense"
placeholder="630"
helperText="og:image:height"
onChange={(e) =>
handleSocialPreviewChange(
"imageHeight",
Number(e.target.value),
)
}
value={socialPreview.imageHeight}
variant="outlined"
size="small"
sx={{ flex: 1 }}
/>
</Stack>
<FormControl fullWidth size="small" margin="dense">
<InputLabel id="twitterCardTypeLabel">
Twitter Card Type
</InputLabel>
<Select
labelId="twitterCardTypeLabel"
label="Twitter Card Type"
value={socialPreview.twitterCardType}
onChange={(e) =>
handleSocialPreviewChange(
"twitterCardType",
e.target.value,
)
}
>
<MenuItem value="summary_large_image">
summary_large_image (recommended)
</MenuItem>
<MenuItem value="summary">summary</MenuItem>
</Select>
</FormControl>
<TextField
fullWidth
label="Locale"
margin="dense"
placeholder="en_US, ja_JP, etc."
helperText="og:locale for language targeting"
onChange={(e) =>
handleSocialPreviewChange("locale", e.target.value)
}
value={socialPreview.locale}
variant="outlined"
size="small"
/>
</Box>

<Box sx={{ mt: 3, pt: 2, borderTop: 1, borderColor: "grey.300" }}>
<Typography
variant="subtitle2"
Expand Down
43 changes: 40 additions & 3 deletions src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export interface BrandingOptions {
faviconUrl?: string;
}

export interface SocialPreviewOptions {
defaultImage?: string;
imageWidth?: number;
imageHeight?: number;
twitterCardType?: "summary" | "summary_large_image";
locale?: string;
}

export interface SeoOptions {
aiAttribution?: string;
}
Expand Down Expand Up @@ -78,6 +86,7 @@ export interface CodeData {
pageMetadata: Record<string, PageMetadata>;
structuredData: StructuredDataOptions;
branding: BrandingOptions;
socialPreview: SocialPreviewOptions;
seo: SeoOptions;
analytics: AnalyticsOptions;
caching: CachingOptions;
Expand Down Expand Up @@ -111,6 +120,7 @@ export default function code(data: CodeData): string {
pageMetadata,
structuredData,
branding,
socialPreview,
seo,
analytics,
caching,
Expand Down Expand Up @@ -170,6 +180,16 @@ ${slugs
const TWITTER_HANDLE = '${branding?.twitterHandle || ""}';
const FAVICON_URL = '${branding?.faviconUrl || ""}';

/*
* Step 3.3.1: social preview configuration (optional)
* Enhance Open Graph and Twitter Card meta tags for better link previews
*/
const DEFAULT_OG_IMAGE = '${socialPreview?.defaultImage || ""}';
const OG_IMAGE_WIDTH = ${socialPreview?.imageWidth || 1200};
const OG_IMAGE_HEIGHT = ${socialPreview?.imageHeight || 630};
const TWITTER_CARD_TYPE = '${socialPreview?.twitterCardType || "summary_large_image"}';
const OG_LOCALE = '${socialPreview?.locale || ""}';

/*
* Step 3.4: SEO configuration (optional)
* AI attribution for proper citation in AI-generated content
Expand Down Expand Up @@ -582,10 +602,11 @@ ${
element.setAttribute('content', pageDescription);
}
}
// Set custom OG image if specified (Issue #11)
if (ogImage && (element.getAttribute('property') === 'og:image'
// Set custom OG image if specified, fallback to default (Issue #11, #34)
const effectiveOgImage = ogImage || DEFAULT_OG_IMAGE;
if (effectiveOgImage && (element.getAttribute('property') === 'og:image'
|| element.getAttribute('name') === 'twitter:image')) {
element.setAttribute('content', ogImage);
element.setAttribute('content', effectiveOgImage);
}
// Set canonical URL for og:url and twitter:url (Issue #9)
if (element.getAttribute('property') === 'og:url'
Expand Down Expand Up @@ -666,6 +687,22 @@ ${
element.append(\`<meta name="twitter:creator" content="\${TWITTER_HANDLE}">\`, { html: true });
}

// Add enhanced Open Graph and Twitter Card tags (Issue #34)
element.append(\`<meta property="og:type" content="website">\`, { html: true });
element.append(\`<meta name="twitter:card" content="\${TWITTER_CARD_TYPE}">\`, { html: true });

// Add OG image dimensions if image is set
const effectiveOgImage = this.metadata.ogImage || DEFAULT_OG_IMAGE;
if (effectiveOgImage) {
element.append(\`<meta property="og:image:width" content="\${OG_IMAGE_WIDTH}">\`, { html: true });
element.append(\`<meta property="og:image:height" content="\${OG_IMAGE_HEIGHT}">\`, { html: true });
}

// Add locale if configured
if (OG_LOCALE !== '') {
element.append(\`<meta property="og:locale" content="\${OG_LOCALE}">\`, { html: true });
}

// Add AI crawler attribution meta tags (Issue #13)
if (AI_ATTRIBUTION !== '') {
element.append(\`<meta name="ai:source_url" content="\${canonicalUrl}">\`, { html: true });
Expand Down