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"
sodipodi:role="line"><tspan
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
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"

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",
"query-string": "6.2.0",
"remark": "13",
"remark-frontmatter": "^4.0.1",
"remark-parse": "^9.0.0",
"remark-plain-text": "^0.2.0",
"rimraf": "2.6.2",
"sharp": "^0.29.3"
"sharp": "^0.29.3",
"yaml": "^1.10.2"
},
"devDependencies": {
"backpack-core": "0.7.0"

View file

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

View file

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

2378
yarn.lock

File diff suppressed because it is too large Load diff