Skip to content

Commit

Permalink
feat: add socials headers config
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Oct 16, 2024
1 parent 6456802 commit 70bb537
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 5 deletions.
4 changes: 2 additions & 2 deletions examples/astro/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import galactiks, { integrationsPreset } from '@galactiks/astro-integration';
import path from 'path';

process.env.CONTENT_PATH = path.join(process.cwd(), '../contentlayer');

import galactiks, { integrationsPreset } from '@galactiks/astro-integration';

// https://astro.build/config
export default /** @type {import('astro').AstroUserConfig} */ {
integrations: [integrationsPreset(), galactiks()],
Expand Down
11 changes: 11 additions & 0 deletions examples/contentlayer/galactiks.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
"default": "en",
"available": ["en", "fr"]
},
"openGraph": {
"siteName": "Galactiks"
},
"facebook": {
"appId": "123456789"
},
"twitter": {
"handle": "@galactiks",
"site": "@galactiks",
"cardType": "summary_large_image"
},
"pages": {
"articles": {
"path": "/{+inLanguage}/a/{identifier}/"
Expand Down
5 changes: 5 additions & 0 deletions packages/adapters/astro/src/components/Head.astro
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const { analytics } = getConfig();
<meta property={property} content={content} {...props} />
))
}
{
content.headers?.facebook?.map(({ property, content, ...props }) => (
<meta property={property} content={content} {...props} />
))
}
{
content.headers?.twitterCard?.map(({ name, content, ...props }) => (
<meta name={name} content={content} {...props} />
Expand Down
4 changes: 4 additions & 0 deletions packages/config/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from 'zod';

import { analyticsConfigSchema } from './analytics.js';
import { getDefaultConfig } from './default.js';
import { facebookConfigSchema, openGraphConfigSchema, twitterConfigSchema } from './socials.js';
import type { WebManifest } from './webmanifest.config.js';

const localesSchema = z.object({
Expand All @@ -29,6 +30,9 @@ const galactiksConfigFileSchema = z.object({
locales: localesSchema.optional(),
template: z.string(),
analytics: analyticsConfigSchema.optional(),
openGraph: openGraphConfigSchema.optional(),
facebook: facebookConfigSchema.optional(),
twitter: twitterConfigSchema.optional(),
trailingSlash: z.enum(['ignore', 'always', 'never']).optional(),
pages: z
.object({
Expand Down
14 changes: 14 additions & 0 deletions packages/config/src/socials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from 'zod';

export const openGraphConfigSchema = z.object({
siteName: z.string().optional(),
});

export const facebookConfigSchema = z.object({
appId: z.string().optional(),
});

export const twitterConfigSchema = z.object({
creator: z.string().optional(),
site: z.string().optional(),
});
2 changes: 2 additions & 0 deletions packages/explorer/src/content/hydrate/missing-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { breadcrumbBuilder } from '../metadata/breadcrumb.js';
import { getBasicHeaders } from '../metadata/headers.js';
import { getTwitterCard } from '../metadata/twitter.js';
import { getOpenGraphObjects } from '../metadata/ogp/og.js';
import { getFacebookObjects } from '../metadata/facebook.js';
import { getStructuredDataSchemas } from '../metadata/schemas/structured-data.js';

export const computeMissingFields =
Expand Down Expand Up @@ -81,6 +82,7 @@ export const computeMissingFields =
contentWithoutHeaders
),
openGraph: getOpenGraphObjects(contentWithoutHeaders),
facebook: getFacebookObjects(),
twitterCard: getTwitterCard(contentWithoutHeaders),
},
};
Expand Down
31 changes: 31 additions & 0 deletions packages/explorer/src/content/metadata/facebook.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getFacebookObjects } from './facebook';
import { getConfig } from '@galactiks/config';

jest.mock('@galactiks/config');

describe('getFacebookObjects', () => {
it('should return an empty array if facebook config is not present', () => {
(getConfig as jest.Mock).mockReturnValue({});

const result = getFacebookObjects();

expect(result).toEqual([]);
});

it('should return an array with fb:app_id if facebook appId is present', () => {
const mockAppId = '1234567890';
(getConfig as jest.Mock).mockReturnValue({ facebook: { appId: mockAppId } });

const result = getFacebookObjects();

expect(result).toEqual([{ property: 'fb:app_id', content: mockAppId }]);
});

it('should return an empty array if facebook config is present but appId is not', () => {
(getConfig as jest.Mock).mockReturnValue({ facebook: {} });

const result = getFacebookObjects();

expect(result).toEqual([]);
});
});
12 changes: 12 additions & 0 deletions packages/explorer/src/content/metadata/facebook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getConfig } from '@galactiks/config';
import type { MetadataHeaders } from '../../types/index.js';

export const getFacebookObjects = (): MetadataHeaders['facebook'] => {
const { facebook } = getConfig();
const headers = [];
if (facebook?.appId) {
headers.push({ property: 'fb:app_id', content: facebook.appId });
}

return headers;
};
7 changes: 7 additions & 0 deletions packages/explorer/src/content/metadata/headers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { getConfig } from '@galactiks/config';
import { Content } from '../../types/index.js';
import { getBasicHeaders } from './headers';

jest.mock('@galactiks/config');

describe('getBasicHeaders', () => {
beforeAll(() => {
(getConfig as jest.Mock).mockReturnValue({});
});

it('should return correct basic headers', () => {
const entry = {
name: 'Test Document',
Expand Down
19 changes: 16 additions & 3 deletions packages/explorer/src/content/metadata/ogp/og.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { getConfig } from '@galactiks/config';
import type { Content, MetadataHeaders } from '../../../types/index.js';

import { getArticle } from './article.js';
import { getWebsite } from './website.js';

export const getOpenGraphObjects = (
document: Content
): MetadataHeaders['openGraph'] => {
const getOpenGraphByType = (document: Content): MetadataHeaders['openGraph'] => {
if (document.type === 'Article') {
return getArticle(document);
}

return getWebsite(document);
}

export const getOpenGraphObjects = (
document: Content
): MetadataHeaders['openGraph'] => {
const headers = getOpenGraphByType(document) || [];

const { webManifest, openGraph } = getConfig();
const siteName = openGraph?.siteName || webManifest?.name;
if (siteName) {
headers.push({ property: 'og:siteName', content: siteName });
}

return headers;
};
75 changes: 75 additions & 0 deletions packages/explorer/src/content/metadata/twitter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { getConfig } from '@galactiks/config';
import { Content } from '../../types/index.js';
import { getTwitterCard } from './twitter';

jest.mock('@galactiks/config');

describe('getTwitterCard', () => {
beforeAll(() => {
(getConfig as jest.Mock).mockReturnValue({});
});

it('should return correct headers when document has an image', () => {
const document = {
name: 'Test Document',
Expand Down Expand Up @@ -37,4 +44,72 @@ describe('getTwitterCard', () => {
{ name: 'twitter:card', content: 'summary' },
]);
});

it('should include twitter:site if configured', () => {
(getConfig as jest.Mock).mockReturnValue({
twitter: {
site: '@testsite',
},
});

const document = {
name: 'Test Document',
description: 'Test Description',
} as Content;

const result = getTwitterCard(document);

expect(result).toEqual([
{ name: 'twitter:title', content: 'Test Document' },
{ name: 'twitter:description', content: 'Test Description' },
{ name: 'twitter:site', content: '@testsite' },
{ name: 'twitter:card', content: 'summary' },
]);
});

it('should include twitter:creator if configured', () => {
(getConfig as jest.Mock).mockReturnValue({
twitter: {
creator: '@testcreator',
},
});

const document = {
name: 'Test Document',
description: 'Test Description',
} as Content;

const result = getTwitterCard(document);

expect(result).toEqual([
{ name: 'twitter:title', content: 'Test Document' },
{ name: 'twitter:description', content: 'Test Description' },
{ name: 'twitter:creator', content: '@testcreator' },
{ name: 'twitter:card', content: 'summary' },
]);
});

it('should include both twitter:site and twitter:creator if configured', () => {
(getConfig as jest.Mock).mockReturnValue({
twitter: {
creator: '@testcreator',
site: '@testsite',
},
});

const document = {
name: 'Test Document',
description: 'Test Description',
} as Content;

const result = getTwitterCard(document);

expect(result).toEqual([
{ name: 'twitter:title', content: 'Test Document' },
{ name: 'twitter:description', content: 'Test Description' },
{ name: 'twitter:site', content: '@testsite' },
{ name: 'twitter:creator', content: '@testcreator' },
{ name: 'twitter:card', content: 'summary' },
]);
});
});
10 changes: 10 additions & 0 deletions packages/explorer/src/content/metadata/twitter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { getConfig } from '@galactiks/config';
import type { Content, MetadataHeaders } from '../../types/index.js';

export const getTwitterCard = (
document: Content
): MetadataHeaders['twitterCard'] => {
const { twitter } = getConfig();

const headers = [
{ name: 'twitter:title', content: document.name },
{ name: 'twitter:description', content: document.description },
];

if (twitter?.site) {
headers.push({ name: 'twitter:site', content: twitter.site });
}
if (twitter?.creator) {
headers.push({ name: 'twitter:creator', content: twitter.creator });
}

if (document.image) {
headers.push(
{ name: 'twitter:card', content: 'summary_large_image' },
Expand Down
8 changes: 8 additions & 0 deletions packages/explorer/src/types/_schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ const metadataHeaders = z
})
)
.optional(),
facebook: z
.array(
z.object({
property: z.string(),
content: z.string(),
})
)
.optional(),
twitterCard: z
.array(
z.object({
Expand Down
1 change: 1 addition & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"cache": false
},
"test": {
"dependsOn": ["^build"],
"outputLogs": "new-only"
}
}
Expand Down

0 comments on commit 70bb537

Please sign in to comment.