feat(backend): Support for generating images
This commit is contained in:
parent
705b6bcecd
commit
41e3cd9cb9
17 changed files with 704 additions and 104 deletions
56
artwork/img/square.svg
Normal file
56
artwork/img/square.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
72
artwork/img/tall.svg
Normal file
72
artwork/img/tall.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
58
artwork/img/wide.svg
Normal file
58
artwork/img/wide.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 18 KiB |
|
@ -30,6 +30,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sesv2": "3.428.0",
|
"@aws-sdk/client-sesv2": "3.428.0",
|
||||||
"@prisma/client": "5.4.2",
|
"@prisma/client": "5.4.2",
|
||||||
|
"@vercel/og": "^0.5.20",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"dotenv": "16.3.1",
|
"dotenv": "16.3.1",
|
||||||
|
|
|
@ -115,6 +115,39 @@ const baseConfig = {
|
||||||
uk: crowdinProject + 'invite?h=' + process.env.BACKEND_CROWDIN_INVITE_UK,
|
uk: crowdinProject + 'invite?h=' + process.env.BACKEND_CROWDIN_INVITE_UK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
img: {
|
||||||
|
sites: ['org', 'dev', 'social'],
|
||||||
|
templates: {
|
||||||
|
folder: ['..', '..', 'artwork', 'img'],
|
||||||
|
sizes: {
|
||||||
|
square: 2000,
|
||||||
|
tall: 1080,
|
||||||
|
wide: 2400,
|
||||||
|
},
|
||||||
|
chars: {
|
||||||
|
wide: {
|
||||||
|
title_1: 24,
|
||||||
|
title_2: 26,
|
||||||
|
title_3: 26,
|
||||||
|
intro: 58,
|
||||||
|
},
|
||||||
|
square: {
|
||||||
|
title_1: 20,
|
||||||
|
title_2: 20,
|
||||||
|
title_3: 20,
|
||||||
|
intro: 52,
|
||||||
|
},
|
||||||
|
tall: {
|
||||||
|
title_1: 20,
|
||||||
|
title_2: 20,
|
||||||
|
title_3: 20,
|
||||||
|
title_4: 20,
|
||||||
|
title_5: 20,
|
||||||
|
intro: 52,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
secretOrKey: encryptionKey,
|
secretOrKey: encryptionKey,
|
||||||
issuer: api,
|
issuer: api,
|
||||||
|
@ -277,6 +310,7 @@ export const github = config.github
|
||||||
export const instance = config.instance
|
export const instance = config.instance
|
||||||
export const exports = config.exports
|
export const exports = config.exports
|
||||||
export const oauth = config.oauth
|
export const oauth = config.oauth
|
||||||
|
export const imgConfig = config.img
|
||||||
|
|
||||||
const vars = {
|
const vars = {
|
||||||
BACKEND_DB_URL: ['required', 'db.url'],
|
BACKEND_DB_URL: ['required', 'db.url'],
|
||||||
|
|
167
sites/backend/src/controllers/img.mjs
Normal file
167
sites/backend/src/controllers/img.mjs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
import sharp from 'sharp'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { imgConfig } from '../config.mjs'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load SVG templates once at startup
|
||||||
|
*/
|
||||||
|
const templates = {}
|
||||||
|
for (const type of Object.keys(imgConfig.templates.sizes)) {
|
||||||
|
templates[type] = fs.readFileSync(
|
||||||
|
path.resolve(...imgConfig.templates.folder, `${type}.svg`),
|
||||||
|
'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.trim(), words.join(' ').slice(firstLine.length).trim()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divide title into lines to fit on image */
|
||||||
|
const titleAsLines = (title, type) => {
|
||||||
|
// Does it fit on one line?
|
||||||
|
if (title.length <= imgConfig.templates.chars[type].title_1) return [title]
|
||||||
|
// Does it fit on two lines?
|
||||||
|
let lines = splitLine(title, imgConfig.templates.chars[type].title_1)
|
||||||
|
if (lines[1].length <= imgConfig.templates.chars[type].title_2) return lines
|
||||||
|
// Does it fit on three lines?
|
||||||
|
lines = [lines[0], ...splitLine(lines[1], imgConfig.templates.chars[type].title_2)]
|
||||||
|
if (lines[2].length <= imgConfig.templates.chars[type].title_3) return lines
|
||||||
|
// Does it fit on four lines?
|
||||||
|
lines = [lines[0], lines[1], ...splitLine(lines[2], imgConfig.templates.chars[type].title_3)]
|
||||||
|
if (lines[3].length <= imgConfig.templates.chars[type].title_4) return lines
|
||||||
|
// Five lines it is
|
||||||
|
return [
|
||||||
|
lines[0],
|
||||||
|
lines[1],
|
||||||
|
lines[2],
|
||||||
|
...splitLine(lines[3], imgConfig.templates.chars[type].title_4),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divive intro into lines to fit on image */
|
||||||
|
const introAsLines = (intro, type) => {
|
||||||
|
// Does it fit on one line?
|
||||||
|
if (intro.length <= imgConfig.templates.chars[type].intro) return [intro]
|
||||||
|
// Two lines it is
|
||||||
|
return splitLine(intro, imgConfig.templates.chars[type].intro)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide unused placeholders */
|
||||||
|
const hidePlaceholders = (list, type) => {
|
||||||
|
let svg = templates[type]
|
||||||
|
for (const i of list) {
|
||||||
|
svg = svg
|
||||||
|
.replace(`${i}title_1`, '')
|
||||||
|
.replace(`${i}title_2`, '')
|
||||||
|
.replace(`${i}title_3`, '')
|
||||||
|
.replace(`${i}title_4`, '')
|
||||||
|
.replace(`${i}title_5`, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return svg
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Place text in SVG template */
|
||||||
|
const decorateSvg = (data) => {
|
||||||
|
let svg
|
||||||
|
// Single title line
|
||||||
|
if (data.title.length === 1) {
|
||||||
|
svg = hidePlaceholders([2, 3, 4, 5], data.type).replace(`1title_1`, data.title[0])
|
||||||
|
}
|
||||||
|
// Double title line
|
||||||
|
else if (data.title.length === 2) {
|
||||||
|
svg = hidePlaceholders([1, 3, 4, 5], data.type)
|
||||||
|
.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, 4, 5], data.type)
|
||||||
|
.replace(`3title_1`, data.title[0])
|
||||||
|
.replace(`3title_2`, data.title[1])
|
||||||
|
.replace(`3title_3`, data.title[2])
|
||||||
|
}
|
||||||
|
// Quadruple title line
|
||||||
|
else if (data.title.length === 4) {
|
||||||
|
svg = hidePlaceholders([1, 2, 3, 5], data.type)
|
||||||
|
.replace(`4title_1`, data.title[0])
|
||||||
|
.replace(`4title_2`, data.title[1])
|
||||||
|
.replace(`4title_3`, data.title[2])
|
||||||
|
.replace(`4title_4`, data.title[3])
|
||||||
|
}
|
||||||
|
// Quintuple title line
|
||||||
|
else if (data.title.length === 5) {
|
||||||
|
svg = hidePlaceholders([1, 2, 3, 4], data.type)
|
||||||
|
.replace(`5title_1`, data.title[0])
|
||||||
|
.replace(`5title_2`, data.title[1])
|
||||||
|
.replace(`5title_3`, data.title[2])
|
||||||
|
.replace(`5title_4`, data.title[3])
|
||||||
|
.replace(`5title_5`, data.title[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
return svg
|
||||||
|
.replace(`intro_1`, data.intro[0] || '')
|
||||||
|
.replace(`intro_2`, data.intro[1] || '')
|
||||||
|
.replace('site', data.site || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImgController() {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate an Open Graph image
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
ImgController.prototype.generate = async (req, res, tools) => {
|
||||||
|
/*
|
||||||
|
* Extract body parameters
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
site = false,
|
||||||
|
title = 'Please provide a title',
|
||||||
|
intro = 'Please provide an intro',
|
||||||
|
type = 'wide',
|
||||||
|
} = req.body
|
||||||
|
if (site && imgConfig.sites.indexOf(site) === -1)
|
||||||
|
return res.status(400).send({ error: 'invalidSite' })
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preformat data for SVG template
|
||||||
|
*/
|
||||||
|
const data = {
|
||||||
|
title: titleAsLines(title, type),
|
||||||
|
intro: introAsLines(intro, type),
|
||||||
|
site: site ? 'FreeSewing.' + site : '',
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inject data into SVG template
|
||||||
|
*/
|
||||||
|
const svg = decorateSvg(data)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert to PNG and return
|
||||||
|
*/
|
||||||
|
sharp(Buffer.from(svg, 'utf-8'))
|
||||||
|
.resize({ width: imgConfig.templates.sizes[type] })
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) console.log(err)
|
||||||
|
return res.type('png').send(data)
|
||||||
|
})
|
||||||
|
}
|
10
sites/backend/src/routes/img.mjs
Normal file
10
sites/backend/src/routes/img.mjs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { ImgController } from '../controllers/img.mjs'
|
||||||
|
|
||||||
|
const Img = new ImgController()
|
||||||
|
|
||||||
|
export function imgRoutes(tools) {
|
||||||
|
const { app } = tools
|
||||||
|
|
||||||
|
// Generate an image
|
||||||
|
app.post('/img', (req, res) => Img.generate(req, res, tools))
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import { curatedSetsRoutes } from './curated-sets.mjs'
|
||||||
import { optionPacksRoutes } from './option-packs.mjs'
|
import { optionPacksRoutes } from './option-packs.mjs'
|
||||||
import { subscribersRoutes } from './subscribers.mjs'
|
import { subscribersRoutes } from './subscribers.mjs'
|
||||||
import { flowsRoutes } from './flows.mjs'
|
import { flowsRoutes } from './flows.mjs'
|
||||||
|
import { imgRoutes } from './img.mjs'
|
||||||
import { adminRoutes } from './admin.mjs'
|
import { adminRoutes } from './admin.mjs'
|
||||||
|
|
||||||
export const routes = {
|
export const routes = {
|
||||||
|
@ -21,5 +22,6 @@ export const routes = {
|
||||||
optionPacksRoutes,
|
optionPacksRoutes,
|
||||||
subscribersRoutes,
|
subscribersRoutes,
|
||||||
flowsRoutes,
|
flowsRoutes,
|
||||||
|
imgRoutes,
|
||||||
adminRoutes,
|
adminRoutes,
|
||||||
}
|
}
|
||||||
|
|
2
sites/backend/test.sh
Normal file
2
sites/backend/test.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
curl -d '{ "title": "I am the title", "intro": "And I am the intro", "site": "dev" }' http://localhost:3000/og
|
||||||
|
|
19
sites/org/components/genimg/en.yaml
Normal file
19
sites/org/components/genimg/en.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
title: Title
|
||||||
|
titleMsg: This will be the main text on the image
|
||||||
|
intro: Intro / Byline / Footer
|
||||||
|
introMsg: This will appear smaller at the bottom
|
||||||
|
type: Variant
|
||||||
|
typeMsg: Pick the variant that best suits your needs
|
||||||
|
site: Site
|
||||||
|
siteMsg: This format can optionally include the site name
|
||||||
|
generate: Generate image
|
||||||
|
generateAgain: Generate another image
|
||||||
|
preview: Preview
|
||||||
|
save: Save Image
|
||||||
|
tall: Tall
|
||||||
|
tallMsg: Generates a tall image, optimized for Instagram stories, TikTok, and other places that prefer portrait mode.
|
||||||
|
wide: Wide
|
||||||
|
wideMsg: Generates a wide image, optimized for posting on a variety of platforms including Facebook, Mastodon, Reddit, and so on. Also suitable as Open Graph image.
|
||||||
|
square: Square
|
||||||
|
squareMsg: Generate a square image optimized for Instagram posts and other places where a square aspect ratio works best.
|
||||||
|
none: None
|
114
sites/org/components/genimg/index.mjs
Normal file
114
sites/org/components/genimg/index.mjs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { nsMerge } from 'shared/utils.mjs'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { StringInput, ListInput, ns as inputNs } from 'shared/components/inputs.mjs'
|
||||||
|
|
||||||
|
export const ns = nsMerge('genimg', inputNs)
|
||||||
|
|
||||||
|
const binaryToData = (binary) => {
|
||||||
|
const img = new Image()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GenerateImage = () => {
|
||||||
|
const backend = useBackend()
|
||||||
|
const { t } = useTranslation(ns)
|
||||||
|
|
||||||
|
const [title, setTitle] = useState('')
|
||||||
|
const [intro, setIntro] = useState('')
|
||||||
|
const [type, setType] = useState('tall')
|
||||||
|
const [site, setSite] = useState(false)
|
||||||
|
const [preview, setPreview] = useState(false)
|
||||||
|
|
||||||
|
const generate = async () => {
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
result = await backend.img({ title, intro, type, site })
|
||||||
|
if (result.success) {
|
||||||
|
const uint8Array = new Uint8Array(result.data)
|
||||||
|
const base64String = btoa(String.fromCharCode.apply(null, uint8Array))
|
||||||
|
setPreview(`data:image/png;base64,${base64String}`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-xl">
|
||||||
|
{preview ? (
|
||||||
|
<>
|
||||||
|
<img src={preview} />
|
||||||
|
<button className="btn btn-primary w-full mt-4" onClick={() => setPreview(false)}>
|
||||||
|
{t('genimg:generateAgain')}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<StringInput
|
||||||
|
label={t('genimg:title')}
|
||||||
|
current={title}
|
||||||
|
update={setTitle}
|
||||||
|
valid={() => true}
|
||||||
|
/>
|
||||||
|
<StringInput
|
||||||
|
label={t('genimg:intro')}
|
||||||
|
current={intro}
|
||||||
|
update={setIntro}
|
||||||
|
valid={() => true}
|
||||||
|
/>
|
||||||
|
<ListInput
|
||||||
|
label={t('genimg:type')}
|
||||||
|
current={type}
|
||||||
|
update={setType}
|
||||||
|
list={[
|
||||||
|
{
|
||||||
|
val: 'tall',
|
||||||
|
label: t('genimg:tall'),
|
||||||
|
desc: t('genimg:tallMsg'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 'square',
|
||||||
|
label: t('genimg:square'),
|
||||||
|
desc: t('genimg:squareMsg'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 'wide',
|
||||||
|
label: t('genimg:wide'),
|
||||||
|
desc: t('genimg:wideMsg'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{type === 'wide' ? (
|
||||||
|
<ListInput
|
||||||
|
label={t('genimg:site')}
|
||||||
|
current={site}
|
||||||
|
update={setSite}
|
||||||
|
list={[
|
||||||
|
{
|
||||||
|
val: false,
|
||||||
|
label: t('genimg:none'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 'org',
|
||||||
|
label: 'FreeSewing.org',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 'dev',
|
||||||
|
label: 'FreeSewing.dev',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: 'social',
|
||||||
|
label: 'FreeSewing.social',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<button onClick={generate} className="btn btn-primary w-full mt-4">
|
||||||
|
{t('genimg:generate')}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
43
sites/org/pages/new/img.mjs
Normal file
43
sites/org/pages/new/img.mjs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { nsMerge } from 'shared/utils.mjs'
|
||||||
|
// Hooks
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||||
|
import { BareLayout } from 'site/components/layouts/bare.mjs'
|
||||||
|
import { GenerateImage, ns as genImgNs } from 'site/components/genimg/index.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const ns = nsMerge(authNs, pageNs, genImgNs, 'account')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
|
* You also MUST spread props.page into this wrapper component
|
||||||
|
* when path and locale come from static props (as here)
|
||||||
|
* or set them manually.
|
||||||
|
*/
|
||||||
|
const NewBlogPage = ({ page }) => {
|
||||||
|
const { t } = useTranslation(ns)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page} title={t('account:imgNew')}>
|
||||||
|
<GenerateImage />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewBlogPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, ns)),
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
path: ['new', 'img'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import {
|
||||||
RssIcon,
|
RssIcon,
|
||||||
CsetIcon,
|
CsetIcon,
|
||||||
OpackIcon,
|
OpackIcon,
|
||||||
|
KioskIcon,
|
||||||
} from 'shared/components/icons.mjs'
|
} from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
// Translation namespaces used on this page
|
// Translation namespaces used on this page
|
||||||
|
@ -47,6 +48,14 @@ const Box = ({ title, Icon, description, href }) => {
|
||||||
<Link {...linkProps}>{inner}</Link>
|
<Link {...linkProps}>{inner}</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
<Box
|
||||||
|
title={t('opackNew')}
|
||||||
|
Icon={OpackIcon}
|
||||||
|
description={t('opackNewInfo')}
|
||||||
|
href="/new/opack"
|
||||||
|
/>
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Each page MUST be wrapped in the PageWrapper component.
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
|
@ -76,24 +85,24 @@ const NewIndexPage = ({ page }) => {
|
||||||
<>
|
<>
|
||||||
<h2>{t('newShare')}</h2>
|
<h2>{t('newShare')}</h2>
|
||||||
<div className="w-full max-w-7xl flex flex-row flex-wrap gap-4">
|
<div className="w-full max-w-7xl flex flex-row flex-wrap gap-4">
|
||||||
<Box
|
|
||||||
title={t('csetNew')}
|
|
||||||
Icon={CsetIcon}
|
|
||||||
description={t('csetNewInfo')}
|
|
||||||
href="/new/cset"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
title={t('opackNew')}
|
|
||||||
Icon={OpackIcon}
|
|
||||||
description={t('opackNewInfo')}
|
|
||||||
href="/new/opack"
|
|
||||||
/>
|
|
||||||
<Box
|
<Box
|
||||||
title={t('showcaseNew')}
|
title={t('showcaseNew')}
|
||||||
Icon={ShowcaseIcon}
|
Icon={ShowcaseIcon}
|
||||||
description={t('showcaseNewInfo')}
|
description={t('showcaseNewInfo')}
|
||||||
href="/new/showcase"
|
href="/new/showcase"
|
||||||
/>
|
/>
|
||||||
|
<Box
|
||||||
|
title={t('imgNew')}
|
||||||
|
Icon={KioskIcon}
|
||||||
|
description={t('imgNewInfo')}
|
||||||
|
href="/new/img"
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
title={t('csetNew')}
|
||||||
|
Icon={CsetIcon}
|
||||||
|
description={t('csetNewInfo')}
|
||||||
|
href="/new/cset"
|
||||||
|
/>
|
||||||
<Box
|
<Box
|
||||||
title={t('blogNew')}
|
title={t('blogNew')}
|
||||||
Icon={RssIcon}
|
Icon={RssIcon}
|
||||||
|
|
|
@ -274,6 +274,9 @@ showcaseNewInfo: If you would like to share something you (or someone else) made
|
||||||
blogNew: Create a new blog post
|
blogNew: Create a new blog post
|
||||||
blogNewInfo: If you would like to write on the FreeSewing blog, you can start a draft blog post here.
|
blogNewInfo: If you would like to write on the FreeSewing blog, you can start a draft blog post here.
|
||||||
|
|
||||||
|
imgNew: Generate a social media image
|
||||||
|
imgNewInfo: Use our generator to create an image you can share on social media, supports wide (classic), square (Instagram), or tall (stories/TikTok) formats.
|
||||||
|
|
||||||
csetNew: Suggest a new curated measurements set
|
csetNew: Suggest a new curated measurements set
|
||||||
csetNewInfo: We curate a collection of vetted measurments sets that we use to test patterns. You can suggest a measurements set here.
|
csetNewInfo: We curate a collection of vetted measurments sets that we use to test patterns. You can suggest a measurements set here.
|
||||||
|
|
||||||
|
|
|
@ -591,10 +591,10 @@ Backend.prototype.adminPing = async function (token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Migrate a v2 account
|
* backend.img: Generate a social media image
|
||||||
*/
|
*/
|
||||||
Backend.prototype.migrate = async function (data) {
|
Backend.prototype.img = async function (data) {
|
||||||
return responseHandler(await api.post(`/migrate`, data))
|
return responseHandler(await api.post('/img', data, { responseType: 'arraybuffer' }))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBackend() {
|
export function useBackend() {
|
||||||
|
|
103
yarn.lock
103
yarn.lock
|
@ -2055,6 +2055,11 @@
|
||||||
"@resvg/resvg-js-win32-ia32-msvc" "2.4.1"
|
"@resvg/resvg-js-win32-ia32-msvc" "2.4.1"
|
||||||
"@resvg/resvg-js-win32-x64-msvc" "2.4.1"
|
"@resvg/resvg-js-win32-x64-msvc" "2.4.1"
|
||||||
|
|
||||||
|
"@resvg/resvg-wasm@2.6.0":
|
||||||
|
version "2.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@resvg/resvg-wasm/-/resvg-wasm-2.6.0.tgz#fa4db659b8c2519715f7f7dacfbb327aad193935"
|
||||||
|
integrity sha512-iDkBM6Ivex8nULtBu8cX670/lfsGxq8U1cuqE+qS9xFpPQP1enPdVm/33Kq3+B+bAldA+AHNZnCgpmlHo/fZrQ==
|
||||||
|
|
||||||
"@rushstack/eslint-patch@^1.3.3":
|
"@rushstack/eslint-patch@^1.3.3":
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz#5f1b518ec5fa54437c0b7c4a821546c64fed6922"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz#5f1b518ec5fa54437c0b7c4a821546c64fed6922"
|
||||||
|
@ -2068,6 +2073,14 @@
|
||||||
domhandler "^5.0.3"
|
domhandler "^5.0.3"
|
||||||
selderee "^0.11.0"
|
selderee "^0.11.0"
|
||||||
|
|
||||||
|
"@shuding/opentype.js@1.4.0-beta.0":
|
||||||
|
version "1.4.0-beta.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz#5d1e7e9e056f546aad41df1c5043f8f85d39e24b"
|
||||||
|
integrity sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==
|
||||||
|
dependencies:
|
||||||
|
fflate "^0.7.3"
|
||||||
|
string.prototype.codepointat "^0.2.1"
|
||||||
|
|
||||||
"@sigstore/bundle@^1.1.0":
|
"@sigstore/bundle@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1"
|
resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1"
|
||||||
|
@ -2825,6 +2838,15 @@
|
||||||
resolved "https://registry.yarnpkg.com/@use-it/event-listener/-/event-listener-0.1.7.tgz#443a9b6df87f2f2961b74d42997ce723a7078623"
|
resolved "https://registry.yarnpkg.com/@use-it/event-listener/-/event-listener-0.1.7.tgz#443a9b6df87f2f2961b74d42997ce723a7078623"
|
||||||
integrity sha512-hgfExDzUU9uTRTPDCpw2s9jWTxcxmpJya3fK5ADpf5VDpSy8WYwY/kh28XE0tUcbsljeP8wfan48QvAQTSSa3Q==
|
integrity sha512-hgfExDzUU9uTRTPDCpw2s9jWTxcxmpJya3fK5ADpf5VDpSy8WYwY/kh28XE0tUcbsljeP8wfan48QvAQTSSa3Q==
|
||||||
|
|
||||||
|
"@vercel/og@^0.5.20":
|
||||||
|
version "0.5.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vercel/og/-/og-0.5.20.tgz#dedd4b433bc3c1fec67d70a577b5ce8569a67838"
|
||||||
|
integrity sha512-zi+ZXSx/peXA+1lq7s/5Vzmm/TTfTSf/5P1qNYnh42+7X+pZmahWoXt0i7SWiq3WagfsNUNA4hUDapDiHRoXqA==
|
||||||
|
dependencies:
|
||||||
|
"@resvg/resvg-wasm" "2.6.0"
|
||||||
|
satori "0.10.9"
|
||||||
|
yoga-wasm-web "0.3.3"
|
||||||
|
|
||||||
"@yarnpkg/lockfile@^1.1.0":
|
"@yarnpkg/lockfile@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
|
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
|
||||||
|
@ -3698,6 +3720,11 @@ camelcase@^6.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
|
||||||
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
|
||||||
|
|
||||||
|
camelize@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"
|
||||||
|
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
|
caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
|
||||||
version "1.0.30001547"
|
version "1.0.30001547"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz#d4f92efc488aab3c7f92c738d3977c2a3180472b"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz#d4f92efc488aab3c7f92c738d3977c2a3180472b"
|
||||||
|
@ -4047,7 +4074,7 @@ color-name@1.1.3:
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||||
|
|
||||||
color-name@^1.0.0, color-name@~1.1.4:
|
color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
@ -4389,6 +4416,21 @@ crypto-js@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
|
||||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||||
|
|
||||||
|
css-background-parser@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-background-parser/-/css-background-parser-0.1.0.tgz#48a17f7fe6d4d4f1bca3177ddf16c5617950741b"
|
||||||
|
integrity sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==
|
||||||
|
|
||||||
|
css-box-shadow@1.0.0-3:
|
||||||
|
version "1.0.0-3"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz#9eaeb7140947bf5d649fc49a19e4bbaa5f602713"
|
||||||
|
integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==
|
||||||
|
|
||||||
|
css-color-keywords@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
||||||
|
integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
|
||||||
|
|
||||||
css-select@^5.1.0:
|
css-select@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
||||||
|
@ -4408,6 +4450,15 @@ css-selector-tokenizer@^0.8:
|
||||||
cssesc "^3.0.0"
|
cssesc "^3.0.0"
|
||||||
fastparse "^1.1.2"
|
fastparse "^1.1.2"
|
||||||
|
|
||||||
|
css-to-react-native@^3.0.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32"
|
||||||
|
integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
|
||||||
|
dependencies:
|
||||||
|
camelize "^1.0.0"
|
||||||
|
css-color-keywords "^1.0.0"
|
||||||
|
postcss-value-parser "^4.0.2"
|
||||||
|
|
||||||
css-what@^6.1.0:
|
css-what@^6.1.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
||||||
|
@ -5443,7 +5494,7 @@ escalade@^3.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||||
|
|
||||||
escape-html@~1.0.3:
|
escape-html@^1.0.3, escape-html@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
||||||
|
@ -6085,6 +6136,11 @@ feed@4.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
xml-js "^1.6.11"
|
xml-js "^1.6.11"
|
||||||
|
|
||||||
|
fflate@^0.7.3:
|
||||||
|
version "0.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
|
||||||
|
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
|
||||||
|
|
||||||
figures@3.2.0, figures@^3.0.0:
|
figures@3.2.0, figures@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
|
||||||
|
@ -7006,6 +7062,11 @@ heap@^0.2.6:
|
||||||
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
|
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
|
||||||
integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
|
integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
|
||||||
|
|
||||||
|
hex-rgb@^4.1.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776"
|
||||||
|
integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==
|
||||||
|
|
||||||
hexoid@^1.0.0:
|
hexoid@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||||
|
@ -8351,7 +8412,7 @@ lilconfig@2.1.0, lilconfig@^2.0.5, lilconfig@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||||
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
||||||
|
|
||||||
linebreak@^1.0.2:
|
linebreak@^1.0.2, linebreak@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b"
|
resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b"
|
||||||
integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==
|
integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==
|
||||||
|
@ -10934,6 +10995,14 @@ parent-module@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites "^3.0.0"
|
callsites "^3.0.0"
|
||||||
|
|
||||||
|
parse-css-color@^0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-css-color/-/parse-css-color-0.2.1.tgz#b687a583f2e42e66ffdfce80a570706966e807c9"
|
||||||
|
integrity sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==
|
||||||
|
dependencies:
|
||||||
|
color-name "^1.1.4"
|
||||||
|
hex-rgb "^4.1.0"
|
||||||
|
|
||||||
parse-entities@^2.0.0:
|
parse-entities@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
|
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
|
||||||
|
@ -11324,7 +11393,7 @@ postcss-simple-vars@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss "^5.0.21"
|
postcss "^5.0.21"
|
||||||
|
|
||||||
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||||
|
@ -12399,6 +12468,22 @@ safe-stable-stringify@^2.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
satori@0.10.9:
|
||||||
|
version "0.10.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/satori/-/satori-0.10.9.tgz#efde2898ab4a5b09c072f0f4e112ac4a7d6a499c"
|
||||||
|
integrity sha512-XU9EELUEZuioT4acLIpCXxHcFzrsC8muvg0MY28d+TlqwxbkTzBmWbw+3+hnCzXT7YZ0Qm8k3eXktDaEu+qmEw==
|
||||||
|
dependencies:
|
||||||
|
"@shuding/opentype.js" "1.4.0-beta.0"
|
||||||
|
css-background-parser "^0.1.0"
|
||||||
|
css-box-shadow "1.0.0-3"
|
||||||
|
css-to-react-native "^3.0.0"
|
||||||
|
emoji-regex "^10.2.1"
|
||||||
|
escape-html "^1.0.3"
|
||||||
|
linebreak "^1.1.0"
|
||||||
|
parse-css-color "^0.2.1"
|
||||||
|
postcss-value-parser "^4.2.0"
|
||||||
|
yoga-wasm-web "^0.3.3"
|
||||||
|
|
||||||
sax@^1.2.4:
|
sax@^1.2.4:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
||||||
|
@ -12897,6 +12982,11 @@ string-width@^6.1.0:
|
||||||
emoji-regex "^10.2.1"
|
emoji-regex "^10.2.1"
|
||||||
strip-ansi "^7.0.1"
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
|
string.prototype.codepointat@^0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
|
||||||
|
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
|
||||||
|
|
||||||
string.prototype.matchall@^4.0.8:
|
string.prototype.matchall@^4.0.8:
|
||||||
version "4.0.10"
|
version "4.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100"
|
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100"
|
||||||
|
@ -14512,6 +14602,11 @@ yocto-queue@^0.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
|
yoga-wasm-web@0.3.3, yoga-wasm-web@^0.3.3:
|
||||||
|
version "0.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba"
|
||||||
|
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
|
||||||
|
|
||||||
zrender@5.4.3:
|
zrender@5.4.3:
|
||||||
version "5.4.3"
|
version "5.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.3.tgz#41ffaf835f3a3210224abd9d6964b48ff01e79f5"
|
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.3.tgz#41ffaf835f3a3210224abd9d6964b48ff01e79f5"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue