1
0
Fork 0

feat(backend): Initial support for open graph images

This commit is contained in:
Joost De Cock 2021-12-29 18:02:26 +01:00
parent 134bcd979e
commit 018a2c461d
5 changed files with 2357 additions and 104 deletions

View file

@ -403,7 +403,7 @@
id="tspan1657" id="tspan1657"
sodipodi:role="line"><tspan sodipodi:role="line"><tspan
style="font-size:8.46666622px;fill:#a3a3a3;fill-opacity:0.53472218" style="font-size:8.46666622px;fill:#a3a3a3;fill-opacity:0.53472218"
id="tspan1665">sub_1 | sub_2</tspan></tspan></text> id="tspan1665">sub_1 sub_2</tspan></tspan></text>
<text <text
xml:space="preserve" xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:13.63457108px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fdf4ff;fill-opacity:1;stroke:none;stroke-width:0.34086427" style="font-style:normal;font-weight:normal;font-size:13.63457108px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#fdf4ff;fill-opacity:1;stroke:none;stroke-width:0.34086427"

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

View file

@ -59,10 +59,12 @@
"passport-jwt": "4.0.0", "passport-jwt": "4.0.0",
"query-string": "6.2.0", "query-string": "6.2.0",
"remark": "13", "remark": "13",
"remark-frontmatter": "^4.0.1",
"remark-parse": "^9.0.0", "remark-parse": "^9.0.0",
"remark-plain-text": "^0.2.0", "remark-plain-text": "^0.2.0",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"sharp": "^0.29.3" "sharp": "^0.29.3",
"yaml": "^1.10.2"
}, },
"devDependencies": { "devDependencies": {
"backpack-core": "0.7.0" "backpack-core": "0.7.0"

View file

@ -1,13 +1,15 @@
import config from "../config"; import config from "../config";
import { log } from "../utils"; import { capitalize, log } from "../utils";
import sharp from 'sharp'; import sharp from 'sharp';
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import axios from 'axios' import axios from 'axios'
import remark from 'remark' import remark from 'remark'
import remarkParse from 'remark-parse' import remarkParse from 'remark-parse'
import remarkFrontmatter from 'remark-frontmatter'
import toString from 'mdast-util-to-string' import toString from 'mdast-util-to-string'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import yaml from 'yaml'
// Sites for which we generate images // Sites for which we generate images
const sites = ['dev', 'org'] const sites = ['dev', 'org']
@ -20,9 +22,8 @@ const template = fs.readFileSync(
'utf-8' 'utf-8'
) )
/* Turns markdown into a syntax tree */ /* Helper method to extract intro from strapi markdown */
/* Helper method to extract intro from markdown */ const introFromStrapiMarkdown = async (md, slug) => {
const introFromMarkdown = async (md, slug) => {
const tree = await remark().use(remarkParse).parse(md) const tree = await remark().use(remarkParse).parse(md)
if (tree.children[0].type !== 'paragraph') if (tree.children[0].type !== 'paragraph')
console.log('Markdown does not start with paragraph', slug) console.log('Markdown does not start with paragraph', slug)
@ -30,6 +31,23 @@ const introFromMarkdown = async (md, slug) => {
return toString(tree.children[0]) return toString(tree.children[0])
} }
/* Helper method to extract title from markdown frontmatter */
const titleAndIntroFromLocalMarkdown = async (md, slug) => {
const tree = await remark()
.use(remarkParse)
.use(remarkFrontmatter, ['yaml'])
.parse(md)
if (tree.children[0].type !== 'yaml')
console.log('Markdown does not start with frontmatter', slug)
else return {
title: titleAsLines(yaml.parse(tree.children[0].value).title),
intro: introAsLines(toString(tree.children.slice(1, 2)))
}
return false
}
/* Helper method to load dev blog post */ /* Helper method to load dev blog post */
const loadDevBlogPost = async (slug) => { const loadDevBlogPost = async (slug) => {
const result = await axios.get( const result = await axios.get(
@ -37,7 +55,7 @@ const loadDevBlogPost = async (slug) => {
) )
if (result.data) return { if (result.data) return {
title: titleAsLines(result.data[0].title), title: titleAsLines(result.data[0].title),
intro: introAsLines(await introFromMarkdown(result.data[0].body, slug)), intro: introAsLines(await introFromStrapiMarkdown(result.data[0].body, slug)),
sub: [ sub: [
result.data[0].author.displayname, result.data[0].author.displayname,
new Date(result.data[0].published_at).toString().split(' ').slice(0,4).join(' '), new Date(result.data[0].published_at).toString().split(' ').slice(0,4).join(' '),
@ -48,6 +66,22 @@ const loadDevBlogPost = async (slug) => {
return false return false
} }
/* Helper method to load markdown file from disk */
const loadMarkdownFile = async (page, site, lang) => fs.promises.readFile(
path.resolve('..', '..', 'markdown', site, ...page.split('/'), `${lang}.md`),
'utf-8'
).then(async (md) => md
? {
...((await titleAndIntroFromLocalMarkdown(md, page))),
sub: [
'freesewing.dev',
page
],
lead: capitalize(page.split('/').shift())
}
: false
)
/* Find longest possible place to split a string */ /* Find longest possible place to split a string */
const splitLine = (line, chars) => { const splitLine = (line, chars) => {
const words = line.split(' ') const words = line.split(' ')
@ -84,28 +118,43 @@ const introAsLines = intro => {
// Two lines it is // Two lines it is
return splitLine(intro, config.og.chars.intro) return splitLine(intro, config.og.chars.intro)
} }
// Get title and intro // Get title and intro
const getMetaData = { const getMetaData = {
dev: async (page) => { dev: async (page) => {
const data = {} const data = {}
const chunks = page.split('/') const chunks = page.split('/')
// Home page
if (chunks.length === 0) return { if (chunks.length === 0) return {
title: 'FreeSewing FIXME', title: 'FreeSewing FIXME',
intro: "FreeSewing's fixme", intro: "FreeSewing's fixme",
sub: ['freesewing.dev', '/fixme'], sub: ['freesewing.dev', '/fixme'],
} }
if (chunks.length === 1) { // Blog index page
if (chunks[0] === 'blog') return { if (chunks.length === 1 && chunks[0] === 'blog') return {
title: titleAsLines('FreeSewing Development Blog'), title: titleAsLines('FreeSewing Developer Blog'),
intro: introAsLines("FreeSewing's blog for developers and contributors"), intro: introAsLines("Contains no sewing news whatsover. Only posts for (aspiring) developers :)"),
sub: ['freesewing.dev', '/blog'], sub: ['freesewing.dev', '/blog'],
lead: 'Developer Blog',
} }
} // Blog post
if (chunks.length === 2 && chunks[0] === 'blog') { if (chunks.length === 2 && chunks[0] === 'blog') {
return await loadDevBlogPost(chunks[1]) return await loadDevBlogPost(chunks[1])
} }
// Other (MDX) page
const md = await loadMarkdownFile(page, 'dev', 'en')
// Return markdown info or default generic data
return md
? md
: {
title: titleAsLines('FreeSewing.dev'),
intro: introAsLines('Documentation, guides, and howtos for contributors and developers alike'),
sub: ['https://freesewing.dev/', '&lt;== Check it out'],
lead: 'freesewing.dev'
}
}, },
org: page => ({}) org: async (page, site, lang) => ({})
} }
/* Hide unused placeholders */ /* Hide unused placeholders */
@ -169,7 +218,7 @@ OgController.prototype.image = async function (req, res) {
if (languages.indexOf(lang) === -1) return res.send({error: 'sorry'}) if (languages.indexOf(lang) === -1) return res.send({error: 'sorry'})
// Load meta data // Load meta data
const data = await getMetaData[site](page) const data = await getMetaData[site](page, site, lang)
// Inject into SVG // Inject into SVG
const svg = decorateSvg(data) const svg = decorateSvg(data)
// Turn into PNG // Turn into PNG

View file

@ -12,6 +12,8 @@ import sharp from "sharp";
export const email = mailer; export const email = mailer;
export const log = logger; export const log = logger;
export const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1);
export const getHash = (email) => { export const getHash = (email) => {
let hash = crypto.createHash("sha256"); let hash = crypto.createHash("sha256");
hash.update(clean(email)); hash.update(clean(email));

2378
yarn.lock

File diff suppressed because it is too large Load diff