-
Notifications
You must be signed in to change notification settings - Fork 401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nova lib para geração das thumbnails #1425
Changes from 3 commits
882bbae
bec851d
5da8f89
6aa7db0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
import { join, resolve } from 'path'; | ||
import { renderToStaticMarkup } from 'react-dom/server'; | ||
/* eslint-disable jsx-a11y/alt-text */ | ||
/* eslint-disable @next/next/no-img-element */ | ||
import fs from 'node:fs'; | ||
import { join, resolve } from 'node:path'; | ||
import { renderAsync } from '@resvg/resvg-js'; | ||
import removeMarkdown from 'models/remove-markdown'; | ||
import satori from 'satori'; | ||
import content from 'models/content.js'; | ||
import removeMarkdown from 'models/remove-markdown'; | ||
|
||
async function asPng(contentObject) { | ||
const parentContentObject = await content.findOne({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Provavelmente a maioria dos conteúdos compartilhados em redes sociais são "root", então compensa verificar se existe |
||
|
@@ -11,24 +14,34 @@ async function asPng(contentObject) { | |
}, | ||
}); | ||
const parsedContent = parseContent(contentObject, parentContentObject); | ||
const svg = renderToStaticMarkup(renderTemplate(parsedContent)); | ||
|
||
const renderBuffer = await renderAsync(svg, { | ||
fitTo: { | ||
mode: 'width', | ||
value: 1280, | ||
}, | ||
font: { | ||
fontFiles: [ | ||
join(resolve('.'), 'fonts', 'Roboto-Regular.ttf'), | ||
join(resolve('.'), 'fonts', 'Roboto-Bold.ttf'), | ||
join(resolve('.'), 'fonts', 'NotoEmoji-Bold.ttf'), | ||
], | ||
loadSystemFonts: false, | ||
defaultFontFamily: 'Roboto', | ||
}, | ||
const svg = await satori(renderTemplate(parsedContent), { | ||
width: 1200, | ||
height: 630, | ||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Se não me engano, existem dimensões que funcionam melhor do que outras em algumas redes, mas não sei quais são os valores. A mudança leva isso em consideração? Eu também não sei se os valores anteriores consideravam o que era melhor para alguma rede específica. Obs. Sei que a Vercel recomenda usar 1200x630, mas será legal se der para saber o motivo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Procurei um pouco a respeito disso e não achei muita informação, o único consenso foi a proporção da imagem ser Esse novo valor (1200x630) é aparentemente o mínimo recomendado por todos os sites que busquei, acho que faz sentido utilizar o valor mínimo porque o conteúdo ainda é exibido sem nenhum problema e a imagem é mais "leve". |
||
fonts: [ | ||
{ | ||
name: 'Roboto', | ||
data: fs.readFileSync(join(resolve('.'), 'fonts', 'Roboto-Regular.ttf')), | ||
weight: 400, | ||
style: 'normal', | ||
}, | ||
{ | ||
name: 'Roboto', | ||
data: fs.readFileSync(join(resolve('.'), 'fonts', 'Roboto-Bold.ttf')), | ||
weight: 700, | ||
style: 'normal', | ||
}, | ||
{ | ||
name: 'NotoEmoji', | ||
data: fs.readFileSync(join(resolve('.'), 'fonts', 'NotoEmoji-Bold.ttf')), | ||
weight: 700, | ||
style: 'normal', | ||
}, | ||
], | ||
}); | ||
|
||
const renderBuffer = await renderAsync(svg); | ||
|
||
return renderBuffer.asPng(); | ||
} | ||
|
||
|
@@ -39,145 +52,151 @@ export function parseContent(content, parentContent) { | |
title = removeMarkdown(content.body, { maxLength: 120 }); | ||
} | ||
|
||
// Regex to wrap text: https://stackoverflow.com/a/51506718 | ||
if (content.parent_id) { | ||
title = title.replace(/(?![^\n]{1,30}$)([^\n]{1,30})\s/g, '$1_').split('_'); | ||
} else { | ||
title = title.replace(/(?![^\n]{1,24}$)([^\n]{1,24})\s/g, '$1_').split('_'); | ||
} | ||
|
||
title = title.length <= 3 ? title : [title[0], title[1], title[2] + '...']; | ||
|
||
let parent_title = parentContent?.title; | ||
let parent_title = parentContent?.title?.substring(0, 60); | ||
|
||
if (content.parent_id) { | ||
parent_title = (parent_title ?? parentContent.owner_username).substring(0, 60); | ||
} | ||
|
||
parent_title = parent_title?.length > 50 ? parent_title.substring(0, 50) + '...' : parent_title; | ||
|
||
const date = new Date(content.published_at).toLocaleDateString('pt-BR'); | ||
|
||
// Measure author text width: https://bl.ocks.org/tophtucker/62f93a4658387bb61e4510c37e2e97cf | ||
function measureText(string, fontSize = 32) { | ||
const widths = [ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.246875, | ||
0.2578125, 0.3203125, 0.61640625, 0.56171875, 0.7328125, 0.621875, 0.175, 0.3421875, 0.34765625, 0.43125, | ||
0.5671875, 0.196875, 0.2765625, 0.26328125, 0.4125, 0.56171875, 0.56171875, 0.56171875, 0.56171875, 0.56171875, | ||
0.56171875, 0.56171875, 0.56171875, 0.56171875, 0.56171875, 0.2421875, 0.21171875, 0.50859375, 0.54921875, | ||
0.52265625, 0.47265625, 0.8984375, 0.65234375, 0.62265625, 0.6515625, 0.65625, 0.56875, 0.553125, 0.68125, | ||
0.71328125, 0.27265625, 0.55234375, 0.62744140625, 0.53828125, 0.8734375, 0.71328125, 0.6875, 0.63125, 0.6875, | ||
0.61640625, 0.59375, 0.596875, 0.6484375, 0.63671875, 0.8875, 0.62734375, 0.60078125, 0.59921875, 0.265625, | ||
0.41015625, 0.265625, 0.41796875, 0.4515625, 0.309375, 0.54453125, 0.56171875, 0.5234375, 0.5640625, 0.53046875, | ||
0.3486328125, 0.56171875, 0.55078125, 0.24296875, 0.27080078125, 0.50703125, 0.24296875, 0.8765625, 0.55234375, | ||
0.5703125, 0.56171875, 0.56875, 0.3390625, 0.515625, 0.32734375, 0.5515625, 0.484375, 0.7515625, 0.49609375, | ||
0.4734375, 0.49609375, 0.3390625, 0.24375, 0.3390625, 0.68046875, | ||
]; | ||
const avg = 0.5117845394736842; | ||
return ( | ||
string | ||
.split('') | ||
.map((c) => (c.charCodeAt(0) < widths.length ? widths[c.charCodeAt(0)] : avg)) | ||
.reduce((cur, acc) => acc + cur) * fontSize | ||
); | ||
} | ||
|
||
return { | ||
title, | ||
parentTitle: parent_title, | ||
username: content.owner_username, | ||
usernameWidth: measureText(content.owner_username), | ||
comments: content.children_deep_count, | ||
date, | ||
}; | ||
} | ||
|
||
export function renderTemplate({ title, parentTitle, username, usernameWidth, comments, date }) { | ||
function renderPostHeader(title) { | ||
return ( | ||
<text y="54" fill="#212529" fontSize="75" fontWeight="bold"> | ||
{title.map((line, index) => ( | ||
<tspan x="60" dy={100} key={index}> | ||
{line} | ||
</tspan> | ||
))} | ||
</text> | ||
); | ||
} | ||
|
||
function renderCommentHeader(title, parentTitle) { | ||
return ( | ||
<> | ||
{/* reference */} | ||
<text fill="#424C56" fontSize="32"> | ||
<tspan x="60" y="123.938"> | ||
Em resposta a | ||
</tspan> | ||
</text> | ||
<text fill="#424C56" fontSize="32" textDecoration="underline"> | ||
<tspan x="275" y="123.938"> | ||
{parentTitle} | ||
</tspan> | ||
</text> | ||
|
||
{/* title */} | ||
<text y="155" fill="#212529" fontSize="75" fontWeight="bold"> | ||
{title.map((line, index) => ( | ||
<tspan x={60} dy={90} key={index}> | ||
{line} | ||
</tspan> | ||
))} | ||
</text> | ||
</> | ||
); | ||
} | ||
|
||
export function renderTemplate({ title, parentTitle, username, comments, date }) { | ||
return ( | ||
<svg width="1200" height="628" xmlns="http://www.w3.org/2000/svg"> | ||
{/* background */} | ||
<rect width="1200" height="628" fill="#F5F5F5" /> | ||
|
||
{/* title */} | ||
{!!parentTitle ? renderCommentHeader(title, parentTitle) : renderPostHeader(title)} | ||
|
||
{/* tabnews icon */} | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M1103.37 91.5C1110.21 91.5 1115.75 97.0405 1115.75 103.875V145.125C1115.75 151.96 1110.21 157.5 1103.37 157.5H1045.62C1038.79 157.5 1033.25 151.96 1033.25 145.125V103.875C1033.25 97.0405 1038.79 91.5 1045.62 91.5H1103.37ZM1107.5 114.702H1078.63C1076.35 114.702 1073.86 112.971 1073.06 110.835L1068.95 99.75H1045.62C1043.35 99.75 1041.5 101.597 1041.5 103.875V145.125C1041.5 147.403 1043.35 149.25 1045.62 149.25H1103.37C1105.65 149.25 1107.5 147.403 1107.5 145.125V114.702Z" | ||
fill="#212529" | ||
/> | ||
|
||
{/* username container */} | ||
<rect x="60" y="511" width={usernameWidth + 20} height="68" rx="8" fill="#C7D9EC" /> | ||
|
||
{/* username */} | ||
<text x="70" y="555" fill="#424C56" fontSize="32"> | ||
<tspan>{username}</tspan> | ||
</text> | ||
|
||
{/* comments icon */} | ||
<path | ||
d="M774 532.333C765.163 532.333 758 538.431 758 545.951C758 549.137 759.243 552.072 761.236 554.169L758 561.667L768.084 558.653C780.744 562.117 790 554.319 790 545.951C790 538.431 782.837 532.333 774 532.333Z" | ||
fill="#8EA1B4" | ||
/> | ||
|
||
{/* comments */} | ||
<text x="800" y="555" fill="#424C56" fontSize="32"> | ||
<tspan>{comments}</tspan> | ||
</text> | ||
|
||
{/* calendar icon */} | ||
<path | ||
d="M909.667 547.333H904.333V542H909.667V547.333ZM917.667 542H912.333V547.333H917.667V542ZM901.667 550H896.333V555.333H901.667V550ZM909.667 550H904.333V555.333H909.667V550ZM901.667 542H896.333V547.333H901.667V542ZM923 531.333V549.181C923 552.369 914.136 562 909.919 562H891V531.333H923ZM920.333 539.333H893.667V559.333H908.816C914.357 559.333 912.333 551.333 912.333 551.333C912.333 551.333 920.333 553.533 920.333 548.057V539.333Z" | ||
fill="#8EA1B4" | ||
/> | ||
|
||
{/* date */} | ||
<text x="940" y="555" fill="#424C56" fontSize="32"> | ||
<tspan>{date}</tspan> | ||
</text> | ||
</svg> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
width: '100%', | ||
height: '100%', | ||
padding: '80px 60px 60px', | ||
fontFamily: 'Roboto', | ||
backgroundColor: '#F5F5F5', | ||
}}> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
width: '100%', | ||
}}> | ||
<div | ||
style={{ | ||
display: 'flex', | ||
flex: 1, | ||
flexDirection: 'column', | ||
}}> | ||
{!!parentTitle && ( | ||
<div | ||
style={{ | ||
display: 'flex', | ||
paddingBottom: 32, | ||
gap: 12, | ||
color: '#424C56', | ||
}}> | ||
<div | ||
style={{ | ||
display: 'block', | ||
fontSize: 32, | ||
}}> | ||
Em resposta a | ||
</div> | ||
<div | ||
style={{ | ||
display: 'block', | ||
flex: 1, | ||
fontSize: 32, | ||
lineClamp: 1, | ||
textDecoration: 'underline', | ||
}}> | ||
{parentTitle} | ||
</div> | ||
</div> | ||
)} | ||
|
||
<div | ||
style={{ | ||
display: 'block', | ||
fontSize: 74, | ||
lineHeight: 1.25, | ||
fontWeight: 'bold', | ||
lineClamp: 3, | ||
color: '#212529', | ||
}}> | ||
{title} | ||
</div> | ||
</div> | ||
|
||
<img | ||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODMiIGhlaWdodD0iNjciIHZpZXdCb3g9IjAgMCA4MyA2NyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03MC4zNzQ2IDAuNUM3Ny4yMDkzIDAuNSA4Mi43NDk2IDYuMDQwNDkgODIuNzQ5NiAxMi44NzVWNTQuMTI1QzgyLjc0OTYgNjAuOTU5NyA3Ny4yMDkzIDY2LjUgNzAuMzc0NiA2Ni41SDEyLjYyNDhDNS43OTAyMSA2Ni41IDAuMjQ5NzU2IDYwLjk1OTcgMC4yNDk3NTYgNTQuMTI1VjEyLjg3NUMwLjI0OTc1NiA2LjA0MDQ5IDUuNzkwMjEgMC41IDEyLjYyNDggMC41SDcwLjM3NDZaTTc0LjQ5OTYgMjMuNzAyM0g0NS42MjVDNDMuMzQ3MiAyMy43MDIzIDQwLjg1NzMgMjEuOTcwOSA0MC4wNjQ1IDE5LjgzNTJMMzUuOTQ4NiA4Ljc1SDEyLjYyNDhDMTAuMzQ2NiA4Ljc1IDguNDk5NzYgMTAuNTk2OCA4LjQ5OTc2IDEyLjg3NVY1NC4xMjVDOC40OTk3NiA1Ni40MDMyIDEwLjM0NjYgNTguMjUgMTIuNjI0OCA1OC4yNUg3MC4zNzQ2QzcyLjY1MjggNTguMjUgNzQuNDk5NiA1Ni40MDMyIDc0LjQ5OTYgNTQuMTI1VjIzLjcwMjNaIiBmaWxsPSIjMjEyNTI5Ii8+Cjwvc3ZnPgo=" | ||
width={82.5} | ||
height={66} | ||
style={{ marginLeft: 40 }} | ||
/> | ||
</div> | ||
|
||
<div | ||
style={{ | ||
display: 'flex', | ||
flex: 1, | ||
}}></div> | ||
|
||
<div | ||
style={{ | ||
display: 'flex', | ||
alignItems: 'center', | ||
width: '100%', | ||
}}> | ||
<div | ||
style={{ | ||
padding: 20, | ||
backgroundColor: '#C7D9EC', | ||
color: '#424C56', | ||
borderRadius: 6, | ||
fontSize: 32, | ||
}}> | ||
{username} | ||
</div> | ||
|
||
<div | ||
style={{ | ||
display: 'flex', | ||
flex: 1, | ||
}}></div> | ||
|
||
<div | ||
style={{ | ||
display: 'flex', | ||
alignItems: 'center', | ||
fontSize: 32, | ||
gap: 20, | ||
color: '#424C56', | ||
}}> | ||
<img | ||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMiAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2IDAuMzMzMzI4QzcuMTYyNjcgMC4zMzMzMjggMCA2LjQzMDY2IDAgMTMuOTUwN0MwIDE3LjEzNzMgMS4yNDI2NyAyMC4wNzIgMy4yMzYgMjIuMTY5M0wwIDI5LjY2NjdMMTAuMDg0IDI2LjY1MzNDMjIuNzQ0IDMwLjExNzMgMzIgMjIuMzE4NyAzMiAxMy45NTA3QzMyIDYuNDMwNjYgMjQuODM3MyAwLjMzMzMyOCAxNiAwLjMzMzMyOFoiIGZpbGw9IiM4RUExQjQiLz4KPC9zdmc+Cg==" | ||
width={32} | ||
height={30} | ||
/> | ||
<div>{comments}</div> | ||
|
||
<div></div> | ||
|
||
<img | ||
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzEiIHZpZXdCb3g9IjAgMCAzMiAzMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4LjY2NjcgMTYuMzMzM0gxMy4zMzMzVjExSDE4LjY2NjdWMTYuMzMzM1pNMjYuNjY2NyAxMUgyMS4zMzMzVjE2LjMzMzNIMjYuNjY2N1YxMVpNMTAuNjY2NyAxOUg1LjMzMzMzVjI0LjMzMzNIMTAuNjY2N1YxOVpNMTguNjY2NyAxOUgxMy4zMzMzVjI0LjMzMzNIMTguNjY2N1YxOVpNMTAuNjY2NyAxMUg1LjMzMzMzVjE2LjMzMzNIMTAuNjY2N1YxMVpNMzIgMC4zMzMzMjhWMTguMTgxM0MzMiAyMS4zNjkzIDIzLjEzNiAzMSAxOC45MTg3IDMxSDBWMC4zMzMzMjhIMzJaTTI5LjMzMzMgOC4zMzMzM0gyLjY2NjY3VjI4LjMzMzNIMTcuODE2QzIzLjM1NzMgMjguMzMzMyAyMS4zMzMzIDIwLjMzMzMgMjEuMzMzMyAyMC4zMzMzQzIxLjMzMzMgMjAuMzMzMyAyOS4zMzMzIDIyLjUzMzMgMjkuMzMzMyAxNy4wNTczVjguMzMzMzNaIiBmaWxsPSIjOEVBMUI0Ii8+Cjwvc3ZnPgo=" | ||
width={32} | ||
height={30} | ||
/> | ||
|
||
<div>{date}</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Se não forem muitos pontos problemáticos, é melhor desativar apenas na linha em questão, pois assim ficam mais claras as decisões de ignorar o lint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Desativar apenas na linha foi minha primeira tentativa mas como é um trecho de jsx a única solução que encontrei foi:
Além de ficar muito estranho, se usar a formatação do Prettier esse trecho é alterado e o comentário não funciona: