1
0
Fork 0

feat(dev): Generate OG images at build time

This commit is contained in:
Joost De Cock 2022-10-15 05:24:18 +02:00
parent 6030252518
commit 55e376647e
11 changed files with 401 additions and 59 deletions

1
.gitignore vendored
View file

@ -111,6 +111,7 @@ sites/*/out
sites/*/public/mdx sites/*/public/mdx
sites/*/prebuild sites/*/prebuild
!sites/shared/prebuild !sites/shared/prebuild
sites/dev/public/og
# misc # misc
.DS_Store .DS_Store

View file

@ -5,6 +5,7 @@
*.md *.md
*.mustache *.mustache
*.png *.png
*.svg
yarn.lock yarn.lock
.husky/pre-commit .husky/pre-commit
.prettierignore .prettierignore

View file

@ -7,10 +7,22 @@
<stop stop-color="#262626" offset="0"/> <stop stop-color="#262626" offset="0"/>
<stop stop-color="#262626" stop-opacity="0" offset="1"/> <stop stop-color="#262626" stop-opacity="0" offset="1"/>
</linearGradient> </linearGradient>
<linearGradient id="d" x1="0%" x2="160%" y1="0%" y2="75%" gradientUnits="userSpaceOnUse">
<stop stop-color="#f87171" offset="0%"/>
<stop stop-color="#fb923c" offset="11%"/>
<stop stop-color="#facc15" offset="22%"/>
<stop stop-color="#a3e635" offset="33%"/>
<stop stop-color="#4ade80" offset="44%"/>
<stop stop-color="#22d3ee" offset="55%"/>
<stop stop-color="#60a5fa" offset="66%"/>
<stop stop-color="#818cf8" offset="77%"/>
<stop stop-color="#a78bfa" offset="88%"/>
<stop stop-color="#c084fc" offset="100%"/>
</linearGradient>
</defs> </defs>
<style> <style>
.mono { font-family: 'Ubuntu Mono'; } text, tspan { font-family: 'Ubuntu Mono' }
.pacifico { font-family: Pacifico; font-size: 16px; }}
.tiny { font-size: 8px; } .tiny { font-size: 8px; }
.mini { font-size: 9px; } .mini { font-size: 9px; }
.small { font-size: 11px; } .small { font-size: 11px; }
@ -30,11 +42,11 @@
<rect x="-5" y="-5" width="330" height="180" class="gray-800"/> <rect x="-5" y="-5" width="330" height="180" class="gray-800"/>
</g> </g>
<g transform="translate(0,-149.83637)"> <g transform="translate(0,-149.83637)">
<text class="mono gray-300 small italic"> <text class="gray-300 small italic">
<tspan x="15" y="273">intro_1</tspan> <tspan x="15" y="273">intro_1</tspan>
<tspan x="15" y="285">intro_2</tspan> <tspan x="15" y="285">intro_2</tspan>
</text> </text>
<text class="mono gray-200 big bold"> <text class="gray-200 big bold">
<tspan x="15" y="230">1title_1</tspan> <tspan x="15" y="230">1title_1</tspan>
<tspan x="15" y="215">2title_1</tspan> <tspan x="15" y="215">2title_1</tspan>
<tspan x="15" y="239">2title_2</tspan> <tspan x="15" y="239">2title_2</tspan>
@ -42,13 +54,22 @@
<tspan x="15" y="227">3title_2</tspan> <tspan x="15" y="227">3title_2</tspan>
<tspan x="15" y="251">3title_3</tspan> <tspan x="15" y="251">3title_3</tspan>
</text> </text>
<text> <text class="bold" x="15" y="173">
<tspan x="15" y="173" class="pacifico gray-500">FreeSewing</tspan> <tspan dx="0" fill="#f87171">F</tspan>
<tspan dx="-7" fill="#fb923c">r</tspan>
<tspan dx="-7" fill="#facc15">e</tspan>
<tspan dx="-7" fill="#a3e635">e</tspan>
<tspan dx="-7" fill="#4ade80">S</tspan>
<tspan dx="-7" fill="#22d3ee">e</tspan>
<tspan dx="-7" fill="#60a5fa">w</tspan>
<tspan dx="-7" fill="#818cf8">i</tspan>
<tspan dx="-7" fill="#a78bfa">n</tspan>
<tspan dx="-7" fill="#c084fc">g</tspan>
</text> </text>
<text class="gray1 mono mini gray-400"> <text class="gray1 mini gray-400">
<tspan x="15" y="305">sub_1sub_2</tspan> <tspan x="15" y="305">sub_1sub_2</tspan>
</text> </text>
<text class="gray1 mono tiny gray-400 bold"> <text class="gray1 gray-400 small">
<tspan x="113" y="173">lead_1</tspan> <tspan x="113" y="173">lead_1</tspan>
</text> </text>
</g> </g>
@ -58,12 +79,7 @@
<path class="gray-300" d="m 275.12441,297.89232 c -1.76172,0.0243 -3.38909,0.72065 -4.59761,1.98566 -0.84215,0.88156 -1.39167,1.86474 -1.71411,3.06577 -0.1427,0.53073 -0.16298,0.72762 -0.16159,1.58141 0.002,0.85181 0.0227,1.05405 0.16829,1.60363 0.6355,2.39683 2.50583,4.23663 4.91669,4.83665 0.20753,0.0516 0.46213,0.1059 0.56588,0.12029 0.45929,0.0636 1.57081,0.0368 2.06569,-0.0495 2.03747,-0.35518 3.84722,-1.66952 4.77625,-3.46847 1.08425,-2.09884 0.94104,-4.76458 -0.36141,-6.72319 -1.10619,-1.66368 -2.89699,-2.73214 -4.8955,-2.92121 -0.25636,-0.0242 -0.51089,-0.0344 -0.76258,-0.031 z m 15.29524,0.0129 c -1.83068,0 -3.38051,0.63901 -4.64871,1.91648 -1.3014,1.32146 -1.9516,2.88517 -1.9516,4.69107 0,1.80588 0.6502,3.35892 1.9516,4.65852 1.30137,1.29933 2.85122,1.94954 4.64871,1.94954 1.81963,0 3.39678,-0.65512 4.73081,-1.96606 1.25714,-1.24429 1.88604,-2.79159 1.88604,-4.642 0,-1.85042 -0.63931,-3.41359 -1.91805,-4.69107 -1.27982,-1.27747 -2.84597,-1.91648 -4.6988,-1.91648 z m 15.34067,0.43936 a 6.6085787,6.6085787 0 0 0 -2.53243,12.77625 l 1.5458,-4.02763 a 2.2946513,2.2946513 0 1 1 1.6444,0 l 1.5458,4.02763 a 6.6085787,6.6085787 0 0 0 -2.20357,-12.77625 z m -15.32415,0.74966 c 1.49996,0 2.77357,0.52869 3.82111,1.58607 1.05858,1.04604 1.58811,2.32353 1.58811,3.83246 0,1.51969 -0.51861,2.78132 -1.55508,3.7834 -1.09231,1.0795 -2.37631,1.6191 -3.85414,1.6191 -1.47836,0 -2.75252,-0.53414 -3.82161,-1.60257 -1.07022,-1.06845 -1.60361,-2.33526 -1.60361,-3.79993 0,-1.46496 0.53942,-2.74219 1.62013,-3.83246 1.03646,-1.05739 2.30516,-1.58607 3.80509,-1.58607 z m -15.22194,0.008 c 0.87575,-0.002 1.36156,0.087 2.1256,0.3929 1.53377,0.61428 2.73611,2.00492 3.17832,3.67654 0.18496,0.69932 0.18206,2.04756 -0.006,2.74514 h -5.3e-4 c -0.53236,1.97276 -2.15218,3.47823 -4.19645,3.90008 -3.18718,0.65792 -6.29289,-1.75346 -6.49086,-5.04006 -0.12807,-2.12276 1.15704,-4.25275 3.13029,-5.18772 0.74718,-0.35407 1.34887,-0.48378 2.2593,-0.48688 z m 15.18115,1.14359 c -0.49811,0.0175 -0.89249,0.42633 -0.89216,0.92469 -3e-5,0.5107 0.41394,0.92466 0.92471,0.92469 0.51073,-3e-5 0.92471,-0.41399 0.92466,-0.92469 5e-5,-0.51068 -0.41393,-0.92467 -0.92466,-0.92469 -0.0108,-1.8e-4 -0.0217,-1.8e-4 -0.0325,0 z m -1.31965,2.20458 c -0.23506,0 -0.42644,0.1906 -0.42644,0.42647 v 2.70331 h 0.7548 v 3.20052 h 2.04816 v -3.20052 h 0.75378 v -2.70331 c 0,-0.23586 -0.1914,-0.42647 -0.42644,-0.42647 z m -11.75913,0.0263 c -0.31972,-0.007 -0.63903,0.0434 -0.87563,0.15075 -0.81562,0.37031 -1.23263,1.24043 -1.09094,2.27583 0.0977,0.71469 0.55214,1.32252 1.14669,1.53339 0.14243,0.0505 0.48819,0.0941 0.76823,0.0971 0.44804,0.005 0.55539,-0.0181 0.89632,-0.18844 0.33309,-0.16637 0.82814,-0.62807 0.82814,-0.77186 0,-0.022 -0.17236,-0.12712 -0.38311,-0.23336 l -0.38361,-0.19362 -0.19878,0.2282 c -0.44232,0.50754 -1.22169,0.35725 -1.43528,-0.27673 -0.10488,-0.31193 -0.0899,-0.99607 0.0279,-1.27525 0.13167,-0.31204 0.3756,-0.46105 0.75482,-0.46105 0.27548,0 0.32914,0.0242 0.53026,0.24369 l 0.22355,0.24421 0.43212,-0.2091 c 0.23778,-0.11492 0.43216,-0.22666 0.43216,-0.24834 0,-0.12757 -0.49613,-0.58333 -0.79304,-0.72849 -0.23984,-0.11743 -0.56004,-0.18031 -0.87976,-0.1869 z m -3.86757,0.003 c -0.31465,-0.002 -0.62483,0.0471 -0.84519,0.14714 -0.81324,0.36944 -1.21243,1.1924 -1.09145,2.2526 0.0813,0.71532 0.5437,1.3433 1.1467,1.55715 h 5.3e-4 c 0.14232,0.0503 0.48755,0.0935 0.76823,0.0966 0.44805,0.005 0.55549,-0.0181 0.8963,-0.18846 0.40105,-0.20034 0.89968,-0.70905 0.79096,-0.80696 -0.0314,-0.0283 -0.20233,-0.1311 -0.37947,-0.22871 l -0.32218,-0.17762 -0.24989,0.26332 c -0.24793,0.2608 -0.35374,0.29661 -0.77288,0.26434 -0.19934,-0.0153 -0.4106,-0.14975 -0.53643,-0.34179 -0.24256,-0.37018 -0.22501,-1.31046 0.0309,-1.67022 0.15482,-0.21742 0.49684,-0.34514 0.78269,-0.29119 0.11531,0.0215 0.30508,0.13677 0.42906,0.26073 l 0.222,0.22253 0.41666,-0.20549 c 0.22873,-0.11282 0.41613,-0.22336 0.41613,-0.24576 0,-0.13611 -0.54252,-0.62585 -0.83123,-0.75068 -0.23779,-0.10279 -0.55687,-0.15564 -0.87152,-0.15748 z" /> <path class="gray-300" d="m 275.12441,297.89232 c -1.76172,0.0243 -3.38909,0.72065 -4.59761,1.98566 -0.84215,0.88156 -1.39167,1.86474 -1.71411,3.06577 -0.1427,0.53073 -0.16298,0.72762 -0.16159,1.58141 0.002,0.85181 0.0227,1.05405 0.16829,1.60363 0.6355,2.39683 2.50583,4.23663 4.91669,4.83665 0.20753,0.0516 0.46213,0.1059 0.56588,0.12029 0.45929,0.0636 1.57081,0.0368 2.06569,-0.0495 2.03747,-0.35518 3.84722,-1.66952 4.77625,-3.46847 1.08425,-2.09884 0.94104,-4.76458 -0.36141,-6.72319 -1.10619,-1.66368 -2.89699,-2.73214 -4.8955,-2.92121 -0.25636,-0.0242 -0.51089,-0.0344 -0.76258,-0.031 z m 15.29524,0.0129 c -1.83068,0 -3.38051,0.63901 -4.64871,1.91648 -1.3014,1.32146 -1.9516,2.88517 -1.9516,4.69107 0,1.80588 0.6502,3.35892 1.9516,4.65852 1.30137,1.29933 2.85122,1.94954 4.64871,1.94954 1.81963,0 3.39678,-0.65512 4.73081,-1.96606 1.25714,-1.24429 1.88604,-2.79159 1.88604,-4.642 0,-1.85042 -0.63931,-3.41359 -1.91805,-4.69107 -1.27982,-1.27747 -2.84597,-1.91648 -4.6988,-1.91648 z m 15.34067,0.43936 a 6.6085787,6.6085787 0 0 0 -2.53243,12.77625 l 1.5458,-4.02763 a 2.2946513,2.2946513 0 1 1 1.6444,0 l 1.5458,4.02763 a 6.6085787,6.6085787 0 0 0 -2.20357,-12.77625 z m -15.32415,0.74966 c 1.49996,0 2.77357,0.52869 3.82111,1.58607 1.05858,1.04604 1.58811,2.32353 1.58811,3.83246 0,1.51969 -0.51861,2.78132 -1.55508,3.7834 -1.09231,1.0795 -2.37631,1.6191 -3.85414,1.6191 -1.47836,0 -2.75252,-0.53414 -3.82161,-1.60257 -1.07022,-1.06845 -1.60361,-2.33526 -1.60361,-3.79993 0,-1.46496 0.53942,-2.74219 1.62013,-3.83246 1.03646,-1.05739 2.30516,-1.58607 3.80509,-1.58607 z m -15.22194,0.008 c 0.87575,-0.002 1.36156,0.087 2.1256,0.3929 1.53377,0.61428 2.73611,2.00492 3.17832,3.67654 0.18496,0.69932 0.18206,2.04756 -0.006,2.74514 h -5.3e-4 c -0.53236,1.97276 -2.15218,3.47823 -4.19645,3.90008 -3.18718,0.65792 -6.29289,-1.75346 -6.49086,-5.04006 -0.12807,-2.12276 1.15704,-4.25275 3.13029,-5.18772 0.74718,-0.35407 1.34887,-0.48378 2.2593,-0.48688 z m 15.18115,1.14359 c -0.49811,0.0175 -0.89249,0.42633 -0.89216,0.92469 -3e-5,0.5107 0.41394,0.92466 0.92471,0.92469 0.51073,-3e-5 0.92471,-0.41399 0.92466,-0.92469 5e-5,-0.51068 -0.41393,-0.92467 -0.92466,-0.92469 -0.0108,-1.8e-4 -0.0217,-1.8e-4 -0.0325,0 z m -1.31965,2.20458 c -0.23506,0 -0.42644,0.1906 -0.42644,0.42647 v 2.70331 h 0.7548 v 3.20052 h 2.04816 v -3.20052 h 0.75378 v -2.70331 c 0,-0.23586 -0.1914,-0.42647 -0.42644,-0.42647 z m -11.75913,0.0263 c -0.31972,-0.007 -0.63903,0.0434 -0.87563,0.15075 -0.81562,0.37031 -1.23263,1.24043 -1.09094,2.27583 0.0977,0.71469 0.55214,1.32252 1.14669,1.53339 0.14243,0.0505 0.48819,0.0941 0.76823,0.0971 0.44804,0.005 0.55539,-0.0181 0.89632,-0.18844 0.33309,-0.16637 0.82814,-0.62807 0.82814,-0.77186 0,-0.022 -0.17236,-0.12712 -0.38311,-0.23336 l -0.38361,-0.19362 -0.19878,0.2282 c -0.44232,0.50754 -1.22169,0.35725 -1.43528,-0.27673 -0.10488,-0.31193 -0.0899,-0.99607 0.0279,-1.27525 0.13167,-0.31204 0.3756,-0.46105 0.75482,-0.46105 0.27548,0 0.32914,0.0242 0.53026,0.24369 l 0.22355,0.24421 0.43212,-0.2091 c 0.23778,-0.11492 0.43216,-0.22666 0.43216,-0.24834 0,-0.12757 -0.49613,-0.58333 -0.79304,-0.72849 -0.23984,-0.11743 -0.56004,-0.18031 -0.87976,-0.1869 z m -3.86757,0.003 c -0.31465,-0.002 -0.62483,0.0471 -0.84519,0.14714 -0.81324,0.36944 -1.21243,1.1924 -1.09145,2.2526 0.0813,0.71532 0.5437,1.3433 1.1467,1.55715 h 5.3e-4 c 0.14232,0.0503 0.48755,0.0935 0.76823,0.0966 0.44805,0.005 0.55549,-0.0181 0.8963,-0.18846 0.40105,-0.20034 0.89968,-0.70905 0.79096,-0.80696 -0.0314,-0.0283 -0.20233,-0.1311 -0.37947,-0.22871 l -0.32218,-0.17762 -0.24989,0.26332 c -0.24793,0.2608 -0.35374,0.29661 -0.77288,0.26434 -0.19934,-0.0153 -0.4106,-0.14975 -0.53643,-0.34179 -0.24256,-0.37018 -0.22501,-1.31046 0.0309,-1.67022 0.15482,-0.21742 0.49684,-0.34514 0.78269,-0.29119 0.11531,0.0215 0.30508,0.13677 0.42906,0.26073 l 0.222,0.22253 0.41666,-0.20549 c 0.22873,-0.11282 0.41613,-0.22336 0.41613,-0.24576 0,-0.13611 -0.54252,-0.62585 -0.83123,-0.75068 -0.23779,-0.10279 -0.55687,-0.15564 -0.87152,-0.15748 z" />
<rect transform="rotate(-15)" x="151.66" y="229.61" width="21.734" height="145.14" fill="url(#a)"/> <rect transform="rotate(-15)" x="151.66" y="229.61" width="21.734" height="145.14" fill="url(#a)"/>
<g transform="matrix(.075655 .28235 -.12714 .034066 214.18 150.21)"> <g transform="matrix(.075655 .28235 -.12714 .034066 214.18 150.21)">
<rect x="2.8422e-14" y="-54.459" width="100" height="20" fill="#ef4444"/> <rect x="0" y="-54.459" width="600" height="20" fill="url(#d)"/>
<rect x="100" y="-54.459" width="100" height="20" fill="#f97316"/>
<rect x="200" y="-54.459" width="100" height="20" fill="#facc15"/>
<rect x="300" y="-54.459" width="100" height="20" fill="#22c55e"/>
<rect x="400" y="-54.459" width="100" height="20" fill="#3b82f6"/>
<rect x="500" y="-54.459" width="100" height="20" fill="#8b5cf6"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -29,11 +29,7 @@ const MdxPage = (props) => {
<meta property="og:type" content="article" key="type" /> <meta property="og:type" content="article" key="type" />
<meta property="og:description" content={props.intro} key="type" /> <meta property="og:description" content={props.intro} key="type" />
<meta property="og:article:author" content="Joost De Cock" key="author" /> <meta property="og:article:author" content="Joost De Cock" key="author" />
<meta <meta property="og:image" content={`/og/${props.page.slug}/og.png`} key="image" />
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/dev/${props.page.slug}`}
key="image"
/>
<meta property="og:image:type" content="image/png" /> <meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" /> <meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" /> <meta property="og:image:height" content="630" />

View file

@ -14,7 +14,9 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.6.6", "@headlessui/react": "^1.6.6",
"@resvg/resvg-js": "^2.1.0",
"@tailwindcss/typography": "^0.5.0", "@tailwindcss/typography": "^0.5.0",
"Buffer": "^0.0.0",
"d3-dispatch": "^3.0.1", "d3-dispatch": "^3.0.1",
"d3-drag": "^3.0.0", "d3-drag": "^3.0.0",
"d3-selection": "^3.0.0", "d3-selection": "^3.0.0",
@ -38,6 +40,7 @@
"remark-extract-frontmatter": "^3.2.0", "remark-extract-frontmatter": "^3.2.0",
"remark-frontmatter": "^4.0.1", "remark-frontmatter": "^4.0.1",
"remark-smartypants": "^2.0.0", "remark-smartypants": "^2.0.0",
"sharp": "^0.31.1",
"svg-to-pdfkit": "^0.1.8", "svg-to-pdfkit": "^0.1.8",
"to-vfile": "^7.2.2", "to-vfile": "^7.2.2",
"unist-util-visit": "^4.1.0", "unist-util-visit": "^4.1.0",

View file

@ -3,27 +3,20 @@ import { denyList } from '../../../packages/i18n/scripts/prebuilder.mjs'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
const writeJson = async (site, locale, namespace, content) => fs.writeFileSync( const writeJson = async (site, locale, namespace, content) =>
path.resolve( fs.writeFileSync(
'..', path.resolve('..', site, 'public', 'locales', locale, `${namespace}.json`),
site, JSON.stringify(content)
'public', )
'locales',
locale,
`${namespace}.json`
),
JSON.stringify(content)
)
export const prebuildI18n = async (site, only=false) => { export const prebuildI18n = async (site, only = false) => {
const filter = site === 'dev' ? (loc) => loc === 'en' : (loc) => denyList.indexOf(loc) === -1
const filter = site === 'dev' ? (loc => loc === 'en') : (loc => denyList.indexOf(loc) === -1)
const locales = await build(filter, only) const locales = await build(filter, only)
console.log (`copying them to ${site}`, Object.keys(locales)) console.log(`copying them to ${site}`, Object.keys(locales))
const languages = {} const languages = {}
Object.keys(locales).forEach(l => languages[l] = locales[l].i18n[l]) Object.keys(locales).forEach((l) => (languages[l] = locales[l].i18n[l]))
for (const locale in locales) { for (const locale in locales) {
// Only English for dev site // Only English for dev site
const loc = locales[locale] const loc = locales[locale]

View file

@ -5,16 +5,25 @@ import { prebuildContributors } from './contributors.mjs'
import { prebuildPatrons } from './patrons.mjs' import { prebuildPatrons } from './patrons.mjs'
import { prebuildI18n } from './i18n.mjs' import { prebuildI18n } from './i18n.mjs'
import { prebuildLab } from './lab.mjs' import { prebuildLab } from './lab.mjs'
import { generateOgImage } from './og/index.mjs'
const SITE = process.env.SITE || 'lab'
const run = async () => { const run = async () => {
const SITE = process.env.SITE || 'lab'
if (SITE === 'org') { if (SITE === 'org') {
const mdxPages = await prebuildMdx(SITE) const mdxPages = await prebuildMdx(SITE)
const [posts] = await prebuildStrapi(SITE) const [posts] = await prebuildStrapi(SITE)
prebuildNavigation(mdxPages, posts, SITE) prebuildNavigation(mdxPages, posts, SITE)
} else if (SITE === 'dev') { } else if (SITE === 'dev') {
const mdxPages = await prebuildMdx(SITE) const mdxPages = await prebuildMdx(SITE)
if (process.env.GENERATE_OG_IMAGES) {
// Create og image for home page
const img = await generateOgImage({
lang: 'en',
site: SITE,
slug: '',
title: 'FreeSewing.dev',
})
}
prebuildNavigation(mdxPages, false, SITE) prebuildNavigation(mdxPages, false, SITE)
} else await prebuildLab() } else await prebuildLab()

View file

@ -0,0 +1,79 @@
// We need fs and path to read from disk
import fs from 'fs'
import path from 'path'
// Remark
import { remark } from 'remark'
import { visit } from 'unist-util-visit'
// Remark plugins we want to use
import remarkParse from 'remark-parse'
import remarkFrontmatter from 'remark-frontmatter'
const asText = ['text', 'strong', 'emphasis', 'inlineCode']
// Pulls the intro from a markdown document
const remarkIntroPlugin = (opts = {}) => {
// Pulls text out of a paragraph
const extractIntro = (node) => {
const text = []
for (const child of node.children) {
if (asText.indexOf(child.type) !== -1) text.push(child.value)
else if (child.type === 'link') text.push(child.children[0].value)
}
return text.map((item) => (item ? item.trim() : '')).join(' ')
}
// Pulls the first paragraph out of a root node
const extractFirstParagraph = (node) => {
for (const child of node.children) {
if (child.type === 'paragraph') return extractIntro(child)
}
return 'FIXME_no_intro'
}
// Tree visitor
const visitor = (node, i, parent) => {
if (node.type === 'root') {
node.children = [
{
type: 'text',
value: extractFirstParagraph(node).split('\n').join(' ').trim(),
},
]
}
}
// Transformer method using the visitor pattern
const transform = (tree) => {
visit(tree, 'root', visitor)
}
return transform
}
/*
* Summary: Loads markdown from disk and return the intro as plain text
*
* @param (string) language - language to load (eg: 'en')
* @param (string) site - site to load (either 'dev' or 'org')
* @param (string) slug - slug of the page (eg: 'guides/patterns')
*
* @return (string) intro - the document's intro
*/
export const mdIntro = async (language, site, slug) => {
// TODO: Will this work on Windows?
const md = await fs.promises.readFile(
path.resolve(`../../markdown/${site}/${slug}/${language}.md`),
'utf-8'
)
const intro = []
const result = await remark()
.use(remarkFrontmatter)
.use(remarkParse)
.use([remarkIntroPlugin, { intro }])
.process(md)
return result.toString()
}

View file

@ -8,6 +8,8 @@ import remarkFrontmatter from 'remark-frontmatter'
import remarkFrontmatterExtractor from 'remark-extract-frontmatter' import remarkFrontmatterExtractor from 'remark-extract-frontmatter'
import { readSync } from 'to-vfile' import { readSync } from 'to-vfile'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import { mdIntro } from './md-intro.mjs'
import { generateOgImage } from './og/index.mjs'
/* /*
* There's an issue in crowdin where it changes the frontmatter marker: * There's an issue in crowdin where it changes the frontmatter marker:
@ -17,8 +19,8 @@ import yaml from 'js-yaml'
* which breaks stuff. So this method takes the input and replaces all * which breaks stuff. So this method takes the input and replaces all
* - - - with --- * - - - with ---
*/ */
export const fixCrowdinBugs = md => { export const fixCrowdinBugs = (md) => {
md.value = md.value.split("- - -\n").join("---\n") md.value = md.value.split('- - -\n').join('---\n')
return md return md
} }
@ -37,8 +39,7 @@ export const getMdxFileList = async (folder, lang) => {
let allFiles let allFiles
try { try {
allFiles = await rdir(folder) allFiles = await rdir(folder)
} } catch (err) {
catch (err) {
console.log(err) console.log(err)
return false return false
} }
@ -51,7 +52,8 @@ export const getMdxFileList = async (folder, lang) => {
file.slice(-5) === `${lang}.md` && file.slice(-5) === `${lang}.md` &&
file.indexOf('/ui/') === -1 && file.indexOf('/ui/') === -1 &&
file.indexOf('/uimd/') === -1 file.indexOf('/uimd/') === -1
) files.push(file) )
files.push(file)
} }
return files.sort() return files.sort()
@ -60,9 +62,8 @@ export const getMdxFileList = async (folder, lang) => {
/* /*
* Helper method to get the website slug (path) from the file path * Helper method to get the website slug (path) from the file path
*/ */
export const fileToSlug = (file, site, lang) => (file.slice(-6) === `/${lang}.md`) export const fileToSlug = (file, site, lang) =>
? file.split(`/markdown/${site}/`).pop().slice(0, -6) file.slice(-6) === `/${lang}.md` ? file.split(`/markdown/${site}/`).pop().slice(0, -6) : false
: false
/* /*
* Helper method to get the title and meta data from an MDX file * Helper method to get the title and meta data from an MDX file
@ -71,7 +72,7 @@ export const fileToSlug = (file, site, lang) => (file.slice(-6) === `/${lang}.md
* *
* - file: the full path to the file * - file: the full path to the file
*/ */
const mdxMetaInfo = async file => { const mdxMetaInfo = async (file) => {
let result let result
try { try {
result = await unified() result = await unified()
@ -80,8 +81,7 @@ const mdxMetaInfo = async file => {
.use(remarkFrontmatter) .use(remarkFrontmatter)
.use(remarkFrontmatterExtractor, { yaml: yaml.load }) .use(remarkFrontmatterExtractor, { yaml: yaml.load })
.process(fixCrowdinBugs(readSync(file, { encoding: 'utf-8' }))) .process(fixCrowdinBugs(readSync(file, { encoding: 'utf-8' })))
} } catch (err) {
catch (err) {
console.log(err) console.log(err)
} }
@ -91,8 +91,7 @@ const mdxMetaInfo = async file => {
/* /*
* Main method that does what needs doing * Main method that does what needs doing
*/ */
export const prebuildMdx = async(site) => { export const prebuildMdx = async (site) => {
// Say hi // Say hi
console.log() console.log()
console.log(`Prebuilding MDX for freesewing.${site}`) console.log(`Prebuilding MDX for freesewing.${site}`)
@ -100,10 +99,17 @@ export const prebuildMdx = async(site) => {
// Setup MDX root path // Setup MDX root path
const mdxRoot = path.resolve('..', '..', 'markdown', site) const mdxRoot = path.resolve('..', '..', 'markdown', site)
// Inform if we're also generating OG images
if (process.env.GENERATE_OG_IMAGES) {
console.log('⚙ Also generating Open Graph images (this takes a while)')
console.log(' Unset the GENERATE_OG_IMAGES env var to skip this step)')
} else {
console.log('⏩ Not generating Open Graph images as it takes a while')
console.log(' Set GENERATE_OG_IMAGES env var to include this step')
}
// Loop over locales // Loop over locales
const pages = {} const pages = {}
for (const lang of (site === 'dev' ? ['en'] : ['en', 'fr', 'es', 'nl', 'de'])) { for (const lang of site === 'dev' ? ['en'] : ['en', 'fr', 'es', 'nl', 'de']) {
console.log(` - Language: ${lang}`) console.log(` - Language: ${lang}`)
// Get list of filenames // Get list of filenames
@ -119,9 +125,7 @@ export const prebuildMdx = async(site) => {
pages[lang][slug] = { pages[lang][slug] = {
title: meta.data.title, title: meta.data.title,
slug, slug,
order: meta.data.order order: meta.data.order ? `${meta.data.order}${meta.data.title}` : meta.data.title,
? `${meta.data.order}${meta.data.title}`
: meta.data.title
} }
} else { } else {
if (pages.en[slug]) { if (pages.en[slug]) {
@ -132,21 +136,25 @@ export const prebuildMdx = async(site) => {
if (meta.messages.length > 0) console.log(meta.messages) if (meta.messages.length > 0) console.log(meta.messages)
} }
} }
if (process.env.GENERATE_OG_IMAGES) {
// Create og image
const intro = await mdIntro(lang, site, slug)
const img = await generateOgImage({ lang, site, slug, title: meta.data.title, intro })
}
} }
} }
fs.writeFileSync( fs.writeFileSync(
path.resolve('..', site, 'prebuild', `mdx.${lang}.js`), path.resolve('..', site, 'prebuild', `mdx.${lang}.js`),
`export default ${JSON.stringify(pages[lang], null ,2)}` `export default ${JSON.stringify(pages[lang], null, 2)}`
) )
} }
// Write list of all MDX paths (in one language) // Write list of all MDX paths (in one language)
fs.writeFileSync( fs.writeFileSync(
path.resolve('..', site, 'prebuild', `mdx.paths.js`), path.resolve('..', site, 'prebuild', `mdx.paths.js`),
`export default ${JSON.stringify(Object.keys(pages.en), null ,2)}` `export default ${JSON.stringify(Object.keys(pages.en), null, 2)}`
) )
return pages return pages
} }

View file

@ -0,0 +1,153 @@
import fs_ from 'fs'
import path from 'path'
import { Buffer } from 'buffer'
import sharp from 'sharp'
import { capitalize } from '../../utils.mjs'
const fs = fs_.promises
const config = {
template: ['..', '..', 'artwork', 'og', 'template.svg'],
chars: {
title_1: 18,
title_2: 19,
title_3: 20,
intro: 34,
sub: 42,
},
}
// Load template once at startup
const template = fs_.readFileSync(path.resolve(...config.template), 'utf-8')
/* Find longest possible place to split a string */
const splitLine = (line, chars) => {
const words = line.split(' ')
if (words[0].length > chars) {
// Force a word break
return [line.slice(0, chars - 1) + '-', line.slice(chars - 1)]
}
// Glue chunks together until it's too long
let firstLine = ''
let max = false
for (const word of words) {
if (!max && `${firstLine}${word}`.length <= chars) firstLine += `${word} `
else max = true
}
return [firstLine, words.join(' ').slice(firstLine.length)]
}
/* Divide title into lines to fit on image */
const titleAsLines = (title) => {
// Does it fit on one line?
if (title.length <= config.chars.title_1) return [title]
// Does it fit on two lines?
let lines = splitLine(title, config.chars.title_1)
if (lines[1].length <= config.chars.title_2) return lines
// Three lines it is
return [lines[0], ...splitLine(lines[1], config.chars.title_2)]
}
/* Divive intro into lines to fit on image */
const introAsLines = (intro) => {
// Does it fit on one line?
if (intro.length <= config.chars.intro) return [intro]
// Two lines it is
return splitLine(intro, config.chars.intro)
}
// Get title and intro
const getMetaData = async ({ slug, title, intro, site }) => {
const chunks = slug.split('/')
if (site === 'dev') {
// Home page
if (chunks.length === 1 && chunks[0] === '') {
return {
title: ['FreeSewing.dev'],
intro: introAsLines('FreeSewing documentation for developers and contributors'),
sub: ['With guides, tutorials,', "How-to's and more"],
lead: '.dev',
}
} else {
// MDX page
return {
title: titleAsLines(title),
intro: introAsLines(intro),
sub: ['https://freesewing.dev/', slug],
lead: capitalize(chunks[0]),
}
}
} else {
console.log('Unsupported data for OG', data)
}
}
/* Hide unused placeholders */
const hidePlaceholders = (list) => {
let svg = template
for (const i of list) {
svg = svg.replace(`${i}title_1`, '').replace(`${i}title_2`, '').replace(`${i}title_3`, '')
}
return svg
}
/* Place text in SVG template */
const decorateSvg = (data) => {
let svg
// Single title line
if (data.title.length === 1) {
svg = hidePlaceholders([2, 3]).replace(`1title_1`, data.title[0])
}
// Double title line
else if (data.title.length === 2) {
svg = hidePlaceholders([1, 3])
.replace(`2title_1`, data.title[0])
.replace(`2title_2`, data.title[1])
}
// Triple title line
else if (data.title.length === 3) {
svg = hidePlaceholders([1, 2])
.replace(`3title_1`, data.title[0])
.replace(`3title_2`, data.title[1])
.replace(`3title_3`, data.title[2])
}
return svg
.replace('sub_1', data.sub[0] || '')
.replace('sub_2', data.sub[1] || '')
.replace(`intro_1`, data.intro[0] || '')
.replace(`intro_2`, data.intro[1] || '')
.replace('lead_1', data.lead || '')
}
const writeAsPng = async (svg, site, slug) => {
const dir = path.resolve(path.join('..', '..', 'sites', site, 'public', 'og', ...slug.split('/')))
await fs.mkdir(dir, { recursive: true })
// Turn into PNG
sharp(Buffer.from(svg, 'utf-8'))
.resize({ width: 1200 })
.toBuffer(async (err, data, info) => {
if (err) console.log(err)
return await fs.writeFile(path.join(dir, 'og.png'), data)
})
}
/* This generates open graph images
*
* data holds {
* lang,
* site,
* title,
* intro,
* slug
* }
*/
export const generateOgImage = async (data) => {
// Inject into SVG
const meta = await getMetaData(data)
const svg = decorateSvg(meta)
await writeAsPng(svg, data.site, data.slug)
}

View file

@ -3083,6 +3083,84 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
"@resvg/resvg-js-android-arm-eabi@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.1.0.tgz#db9a73969a3d507c4a512953e003088129d17b3a"
integrity sha512-JtvWWtC6bYRhyth1qgUgcPQSP+jkwkmUzok/5b/IqKFb6cattMBFFdHnwM8AS+sgzXJKa8LhW48f3FmFQhfdrA==
"@resvg/resvg-js-android-arm64@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.1.0.tgz#862a423b0396ef057cc5f58e2586fe532503360f"
integrity sha512-QXFEoTpoZJZjkFh4+aSD3l+Ivrij3nzgrr4FTayey0hsQypJXmbzB6nuqB1qZwMrXPYqYZ33BoRiwCFoJUw2Ww==
"@resvg/resvg-js-darwin-arm64@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.1.0.tgz#b767cd8385f55f62ea35b48b805b3eb81e7a1642"
integrity sha512-OrYqlmn2g4Pu/dWr+M5t5W8GDKIX3zk0JxDySU1oNWwhqlmZXBuCrx3TP9dVrTpTYx86E5RQcTZWe64wz8dlIQ==
"@resvg/resvg-js-darwin-x64@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.1.0.tgz#9162cd1ccb2af608ba5a9cb2cb75e0343c48163b"
integrity sha512-95F9BoBS1th79n6Zy1tRMKhPlJuhznnQwAPxRhtw0v4DteRKMzaPFfVH6B9BBaoDCa5VMIxH/wYNKtOxCpYPuw==
"@resvg/resvg-js-linux-arm-gnueabihf@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.1.0.tgz#99b52ab1ed0e22ebc43a852b5ffa9839e9b41ba2"
integrity sha512-8F0ugeAaYGNNZhSCYt+X4YgyKyKcFiH0tqfJmN69+Gqqmu/lmZcn78JVLyTGD/OGHbYfCCYJbxwV+txIOdVNkQ==
"@resvg/resvg-js-linux-arm64-gnu@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.1.0.tgz#81a7f8d19e494b890ec43f4b0fdd6487820cce05"
integrity sha512-RveUS3sqvUp5eoBzz1QlPv7yBUNOjHtcWtbFo55gQrzBGT4XtnCaQzuXkN0q0j2o2ufxlmXmFI3g3e/0EWjNMg==
"@resvg/resvg-js-linux-arm64-musl@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.1.0.tgz#0c4deb3bda1572a23d6c3a93a11b0f74db036fa8"
integrity sha512-DzuRbZj5oVXYFAlo2PVbiaTSb14z/FDUlvgfzVFHiKEw3w6gT/soveLTIAvfeIlRYYkwYNHCiEPxFztyr7x/rw==
"@resvg/resvg-js-linux-x64-gnu@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.1.0.tgz#93f625815a9b478093f7b0b901167b015d97e0be"
integrity sha512-pa4MtKtAEXBj7tl3JXPMQLjgP+KghUYYoXMIX8tlf/xbfJJsOxHpWcwQe/bWPFO4K9hgt/yePkb3G4ydD0uT+g==
"@resvg/resvg-js-linux-x64-musl@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.1.0.tgz#354f8b1d9248ccf7be2706617befe83893e3bff3"
integrity sha512-mkwGe4I9CmQ1GPSnFa22PHwKbE+TZnRk/ViCvO89UOwypW0I+X+KlQVzVbZn9ypvcrbvzotOvl3OkVRq5MgsBA==
"@resvg/resvg-js-win32-arm64-msvc@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.1.0.tgz#e3bfe5b4b9db904827398190455fd8175d44d553"
integrity sha512-DVloJcQsgd3rMAPemy5KjAA6R+RkRz2/xb7zP9px7lr+Gao+xVbNzRQrY7xwCZFM7O7hu9uHvLvkKCttPoL1aA==
"@resvg/resvg-js-win32-ia32-msvc@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.1.0.tgz#1afb29d79c7b61a30965501ad3b4e60559905308"
integrity sha512-RtRQ8loZA4zib8kzD1QjoScb6VAaZTbajB3WU/O6raP2/f2zIk9v4FU2E/hiC0vi5DGhJL5GTmSrsWShbLPjZw==
"@resvg/resvg-js-win32-x64-msvc@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.1.0.tgz#d0239707b5f85ae984dcd8ae09da1939bb02109a"
integrity sha512-NVYuQn9Aj/ZmRufKON7a+1U1XS+jGKMcWO4J8ZH2xhSP3aNVgO7Nfl45DMgqxdCcn0ZzYhzP+mSQFbA/ENE/mg==
"@resvg/resvg-js@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@resvg/resvg-js/-/resvg-js-2.1.0.tgz#d2e939e39d31e97fb5f61efba25ef5ab64bf27d1"
integrity sha512-nR6uVR5ugXLT2jh7U141nhawzgUs4JBl8BpM4XH7/ughSsOA/+WRxVhMUfdtEsz7REpTMKe2Sat+1/eWAuQ04w==
optionalDependencies:
"@resvg/resvg-js-android-arm-eabi" "2.1.0"
"@resvg/resvg-js-android-arm64" "2.1.0"
"@resvg/resvg-js-darwin-arm64" "2.1.0"
"@resvg/resvg-js-darwin-x64" "2.1.0"
"@resvg/resvg-js-linux-arm-gnueabihf" "2.1.0"
"@resvg/resvg-js-linux-arm64-gnu" "2.1.0"
"@resvg/resvg-js-linux-arm64-musl" "2.1.0"
"@resvg/resvg-js-linux-x64-gnu" "2.1.0"
"@resvg/resvg-js-linux-x64-musl" "2.1.0"
"@resvg/resvg-js-win32-arm64-msvc" "2.1.0"
"@resvg/resvg-js-win32-ia32-msvc" "2.1.0"
"@resvg/resvg-js-win32-x64-msvc" "2.1.0"
"@rushstack/eslint-patch@^1.1.3": "@rushstack/eslint-patch@^1.1.3":
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27"
@ -3847,6 +3925,11 @@
dependencies: dependencies:
argparse "^2.0.1" argparse "^2.0.1"
Buffer@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/Buffer/-/Buffer-0.0.0.tgz#82cf8e986a2109ff6d1d6f1c436e47d07127aea4"
integrity sha512-+zdncl8lI5TCkARStn9F1BwcuJYofYmD0oEHe5FNfCvGfeDJwf6+dSikCdQN6BMXXmHMhNNUagBN367WST1AIQ==
JSONStream@^1.0.4: JSONStream@^1.0.4:
version "1.3.5" version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@ -17404,7 +17487,7 @@ shallowequal@^1.1.0:
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
sharp@^0.31.0: sharp@^0.31.0, sharp@^0.31.1:
version "0.31.1" version "0.31.1"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.1.tgz#b2f7076d381a120761aa93700cadefcf90a22458" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.1.tgz#b2f7076d381a120761aa93700cadefcf90a22458"
integrity sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg== integrity sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==