Skip to content

Commit 1f08a63

Browse files
authored
refactor: create share card from buffer (#309)
1 parent 3a68e2e commit 1f08a63

File tree

6 files changed

+102
-72
lines changed

6 files changed

+102
-72
lines changed

gatsby-node.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const {
88
} = require('./gatsby/create-pages/create-career-pages');
99
const { createFilePath } = require('gatsby-source-filesystem');
1010

11-
exports.onCreateNode = (gatsbyCreateNodeArgs) => {
12-
createPreviewCards(gatsbyCreateNodeArgs);
11+
exports.onCreateNode = async (gatsbyCreateNodeArgs) => {
12+
await createPreviewCards(gatsbyCreateNodeArgs);
1313

1414
const { node, actions, getNode } = gatsbyCreateNodeArgs;
1515
const { createNodeField } = actions;
@@ -27,6 +27,36 @@ exports.onCreateNode = (gatsbyCreateNodeArgs) => {
2727
}
2828
};
2929

30+
/**
31+
* We should use `@link` and link the given foreign key field to the actual node.
32+
* The before known foreign Key `___NODE` notation is deprecated, that's why we need the custom and explicit schema.
33+
*
34+
* You can't replace fields.socialCard hence the creation of the node in the parent.
35+
* This is syntactic sugar and as as an alternative can use
36+
* `schema.buildObjectType` and then resolve the reference manually through `context.nodeModel.getNodeById`
37+
*
38+
* Deprecation Note:
39+
* https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v3-to-v4/#___node-convention-is-deprecated
40+
*
41+
* How to "schema additions"
42+
* https://www.gatsbyjs.com/docs/how-to/plugins-and-themes/creating-a-source-plugin/#create-foreign-key-relationships-between-data
43+
*/
44+
45+
exports.createSchemaCustomization = ({ actions, schema }) => {
46+
const { createTypes } = actions;
47+
48+
const typeDefs = [
49+
`type MarkdownRemark implements Node {
50+
socialCardFile: File @link(from: "fields.socialCardFileId")
51+
}`,
52+
`type SyPersonioJob implements Node {
53+
socialCardFile: File @link(from: "fields.socialCardFileId")
54+
}`,
55+
];
56+
57+
createTypes(typeDefs);
58+
};
59+
3060
exports.createPages = async (createPagesArgs) => {
3161
await createCareerPages(createPagesArgs);
3262
await createBlogPosts(createPagesArgs);

gatsby/create-node/create-preview-cards.js

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,58 @@
1-
const slugify = require('slugify');
2-
const path = require('path');
3-
const { siteMetadata } = require('../../gatsby-config');
41
const {
5-
generateCard,
2+
generateCardToBuffer,
63
} = require('../util/preview-card-generator/generate-card');
74

5+
const { createFileNodeFromBuffer } = require('gatsby-source-filesystem');
6+
87
/**
98
* Create a preview card file and put the url to the Gatsby store to be able
109
* to link it in the page header.
1110
*/
12-
const createPreviewMarkdown = ({ node, _, actions }) => {
13-
const { createNodeField } = actions;
14-
const post = node.frontmatter;
11+
const createPreviewCard = async (
12+
title,
13+
{ node, _, actions, getCache, createNodeId },
14+
) => {
15+
const { createNode, createNodeField } = actions;
1516

1617
// we need the title in order to generate anything
17-
if (!post.title) {
18+
if (!title) {
1819
return;
1920
}
2021

21-
// we create a file name from the path (which is the page slug and more unique) but if none is available take the title
22-
const fileName = `social-card---${slugify(post.path || post.title, {
23-
lower: true,
24-
})}.jpg`;
25-
const outputFile = path.join('public', fileName);
26-
const imagePath = `/${fileName}`;
27-
28-
generateCard({ title: post.title }, outputFile).then(() => {
29-
createNodeField({
30-
node,
31-
name: `socialCard`,
32-
value: imagePath,
33-
});
22+
const buffer = await generateCardToBuffer({ title });
23+
/**
24+
* The util function `createFileNodeFromBuffer` from the official gatsby source plugin `gatsby-source-filesystem`
25+
* creates a file node from a given file buffer. The value of `parentNodeId` creates the necessary relationship
26+
* between the original node and the actual file node so it's not garbage collected.
27+
*
28+
* The actual foreign key relationship is resolved through `createSchemaCustomization`
29+
* in gatsby-node.js for all node types the `createPreviewCard` is invoked for.
30+
*/
31+
const fileNode = await createFileNodeFromBuffer({
32+
name: 'social-card',
33+
buffer,
34+
getCache,
35+
createNode,
36+
createNodeId,
37+
parentNodeId: node.id,
3438
});
35-
};
3639

37-
const createPreviewJob = ({ node, _, actions }) => {
38-
const { createNodeField } = actions;
39-
40-
const fileName = `social-card---career-${slugify(node.name, {
41-
lower: true,
42-
})}.jpg`;
43-
44-
const outputFile = path.join('public', fileName);
45-
const imagePath = `/${fileName}`;
46-
47-
generateCard({ title: node.name }, outputFile).then(() => {
40+
if (fileNode) {
4841
createNodeField({
4942
node,
50-
name: 'socialCard',
51-
value: imagePath,
43+
name: `socialCardFileId`,
44+
value: fileNode.id,
5245
});
53-
});
46+
}
5447
};
5548

56-
const createPreviewCards = ({ node, _, actions }) => {
49+
const createPreviewCards = async ({ node, ...rest }) => {
5750
if (node.internal.type === 'SyPersonioJob') {
58-
createPreviewJob({ node, _, actions });
51+
await createPreviewCard(node.name, { node, ...rest });
5952
}
6053

6154
if (node.internal.type === 'MarkdownRemark') {
62-
createPreviewMarkdown({ node, _, actions });
55+
await createPreviewCard(node.frontmatter.title, { node, ...rest });
6356
}
6457
};
6558

gatsby/util/preview-card-generator/generate-card.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const fs = require('fs');
21
const path = require('path');
32
const { createCanvas, loadImage, registerFont } = require('canvas');
43

@@ -63,7 +62,7 @@ const wrapTextFactory = (context) => (text, x, y, maxWidth, lineHeight) => {
6362
* by composing a background with a title
6463
* and an optional author.
6564
*/
66-
async function generateCard({ title, author }, file) {
65+
async function generateCardToBuffer({ title, author }) {
6766
const canvas = createCanvas(CARD_WIDTH, CARD_HEIGHT);
6867

6968
const context = canvas.getContext('2d');
@@ -95,12 +94,9 @@ async function generateCard({ title, author }, file) {
9594
);
9695
}
9796

98-
const buffer = canvas.toBuffer('image/jpeg');
99-
fs.writeFileSync(file, buffer);
100-
101-
return file;
97+
return canvas.toBuffer('image/jpeg');
10298
}
10399

104100
module.exports = {
105-
generateCard,
101+
generateCardToBuffer,
106102
};

src/pages/career.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import SEO from '../components/layout/seo';
33
import { graphql } from 'gatsby';
44
import { useTranslation } from 'gatsby-plugin-react-i18next';
55
import { CareerPage } from '../components/pages/career/career-page';
6-
import { LocalesQuery, SyPersonioJob } from '../types';
6+
import {
7+
LocalesQuery,
8+
PlainFixedImageSharpSource,
9+
SyPersonioJob,
10+
} from '../types';
711
import { IGatsbyImageData } from 'gatsby-plugin-image';
812

913
interface CareerMarkdownQuery {
1014
htmlAst: string;
11-
fields: {
12-
socialCard: string;
13-
};
15+
socialCardFile: PlainFixedImageSharpSource;
1416
}
1517

1618
interface CareerProps {
@@ -27,12 +29,13 @@ interface CareerProps {
2729

2830
const Career = (props: CareerProps) => {
2931
const { t } = useTranslation();
30-
const socialCard = props.data.markdownRemark?.fields?.socialCard;
32+
const socialCardPath =
33+
props.data.markdownRemark.socialCardFile.childImageSharp.fixed.src;
3134

3235
return (
3336
<>
3437
<SEO
35-
shareImagePath={socialCard}
38+
shareImagePath={socialCardPath}
3639
title={`${t('career.title')} | Satellytes`}
3740
description={t('career.seo.description')}
3841
location={props.location}
@@ -81,8 +84,12 @@ export const CareerPageQuery = graphql`
8184
fileAbsolutePath: { regex: "/(pages/career)/" }
8285
frontmatter: { language: { eq: $language } }
8386
) {
84-
fields {
85-
socialCard
87+
socialCardFile {
88+
childImageSharp {
89+
fixed(width: 1440, height: 760) {
90+
src
91+
}
92+
}
8693
}
8794
}
8895
}

src/templates/career-details.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ interface CareerPageProps {
2222
const CareerPage: React.FC<CareerPageProps> = (props): JSX.Element => {
2323
const { pageContext } = props;
2424
const position = props.data.syPersonioJob;
25-
const socialCardImage = position.fields.socialCard;
25+
const socialCardPath = position.socialCardFile.childImageSharp.fixed.src;
2626
const { t } = useTranslation();
2727

2828
return (
2929
<>
3030
<SEO
31-
shareImagePath={socialCardImage}
31+
shareImagePath={socialCardPath}
3232
title={t('career.seo.title-detail', {
3333
name: position.name,
3434
})}
@@ -51,9 +51,6 @@ const CareerPage: React.FC<CareerPageProps> = (props): JSX.Element => {
5151
export const CareerDetailsPageQuery = graphql`
5252
query ($language: String!, $id: String!) {
5353
syPersonioJob(id: { eq: $id }) {
54-
fields {
55-
socialCard
56-
}
5754
id
5855
lang
5956
jobId
@@ -66,6 +63,13 @@ export const CareerDetailsPageQuery = graphql`
6663
descriptionHtml
6764
description
6865
}
66+
socialCardFile {
67+
childImageSharp {
68+
fixed(width: 1440, height: 760) {
69+
src
70+
}
71+
}
72+
}
6973
}
7074
7175
locales: allLocale(filter: { language: { eq: $language } }) {

src/types.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ export interface SyPersonioJob {
4040
slug: string;
4141
sections: SyPersonioJobSection[];
4242

43-
// now owned by the source plugin, added via `onCreateNode`
44-
fields: {
45-
socialCard: string;
46-
};
43+
// added via `onCreateNode`
44+
socialCardFile: PlainFixedImageSharpSource;
4745
}
4846

4947
export interface SyTeamMember {
@@ -52,6 +50,14 @@ export interface SyTeamMember {
5250
image: IGatsbyImageData;
5351
}
5452

53+
export interface PlainFixedImageSharpSource {
54+
childImageSharp: {
55+
fixed: {
56+
src: string;
57+
};
58+
};
59+
}
60+
5561
export interface BlogPostMarkdown {
5662
excerpt: string;
5763
htmlAst;
@@ -61,13 +67,7 @@ export interface BlogPostMarkdown {
6167
};
6268
};
6369
frontmatter: {
64-
shareImage: {
65-
childImageSharp: {
66-
fixed: {
67-
src: string;
68-
};
69-
};
70-
};
70+
shareImage: PlainFixedImageSharpSource;
7171
attribution: {
7272
creator: string;
7373
source: string;

0 commit comments

Comments
 (0)