├── .cc-metadata.yml ├── .editorconfig ├── .github └── CODEOWNERS ├── .gitignore ├── .prettierrc.js ├── .yarnrc ├── LICENSE ├── README.md ├── api ├── _fonts │ ├── RobotoCondensed-Bold.woff2 │ ├── RobotoCondensed-Regular.woff2 │ ├── SourceSansPro-Bold.woff2 │ └── SourceSansPro-Regular.woff2 ├── _lib │ ├── chromium.ts │ ├── options.ts │ ├── parser.ts │ ├── sanitizer.ts │ ├── template.ts │ └── types.ts ├── index.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── index.html ├── robots.txt ├── style.css └── tweet.png ├── vercel.json ├── web ├── index.ts └── tsconfig.json └── yarn.lock /.cc-metadata.yml: -------------------------------------------------------------------------------- 1 | # Whether this GitHub repo is engineering related 2 | engineering_project: true 3 | # Name of the repository/project in English 4 | english_name: CC Open Graph Image Generator 5 | # All technologies used 6 | technologies: JavaScript 7 | # Whether this repository should be featured on the CC Open Source site 8 | featured: false 9 | # Slack channel name 10 | slack: "cc-developers" 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | * @creativecommons/technology 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .now 2 | .vercel 3 | node_modules 4 | api/dist 5 | public/dist 6 | 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: "es5", 3 | tabWidth: 2, 4 | semi: false, 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix "" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Creative Commons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Graph Image Generator 2 | 3 | Serverless service that generates dynamic Open Graph images that you can embed in your `` tags. 4 | 5 | For each keystroke, headless chromium is used to render an HTML page and take a screenshot of the result which gets cached. 6 | 7 | ## Code of conduct 8 | 9 | [`CODE_OF_CONDUCT.md`][org-coc]: 10 | > The Creative Commons team is committed to fostering a welcoming community. 11 | > This project and all other Creative Commons open source projects are governed 12 | > by our [Code of Conduct][code_of_conduct]. Please report unacceptable 13 | > behavior to [conduct@creativecommons.org](mailto:conduct@creativecommons.org) 14 | > per our [reporting guidelines][reporting_guide]. 15 | 16 | [org-coc]: https://github.com/creativecommons/.github/blob/main/CODE_OF_CONDUCT.md 17 | [code_of_conduct]: https://opensource.creativecommons.org/community/code-of-conduct/ 18 | [reporting_guide]: https://opensource.creativecommons.org/community/code-of-conduct/enforcement/ 19 | 20 | 21 | ## Contributing 22 | 23 | See [`CONTRIBUTING.md`][org-contrib]. 24 | 25 | [org-contrib]: https://github.com/creativecommons/.github/blob/main/CONTRIBUTING.md 26 | 27 | ## What is an Open Graph Image? 28 | 29 | Have you ever posted a hyperlink to Twitter, Facebook, or Slack and seen an image popup? 30 | How did your social network know how to "unfurl" the URL and get an image? 31 | The answer is in your ``. 32 | 33 | The [Open Graph protocol](http://ogp.me) says you can put a `` tag in the `` of a webpage to define this image. 34 | 35 | It looks like the following: 36 | 37 | ```html 38 | 39 | Title 40 | 44 | 45 | ``` 46 | 47 | ## Why use this service? 48 | 49 | The short answer is that it would take a long time to painstakingly design an image for every single blog post and every single documentation page. And we don't want the exact same image for every blog post because that wouldn't make the article stand out when it was shared to Twitter. 50 | 51 | That's where `cc-og-image.vercel.app` comes in. We can simply pass the title of our blog post to our generator service and it will generate the image for us on the fly! 52 | 53 | It looks like the following: 54 | 55 | ```html 56 | 57 | Hello World 58 | 62 | 63 | ``` 64 | 65 | Now try changing the text `Hello%20World` to the title of your choosing and watch the magic happen ✨ 66 | 67 | ## Deploy your own 68 | 69 | You'll want to fork this repository and deploy your own image generator. 70 | 71 | 1. Click the fork button at the top right of GitHub 72 | 2. Clone the repo to your local machine with `git clone URL_OF_FORKED_REPO_HERE` 73 | 3. Change directory with `cd og-image` 74 | 4. Make changes by swapping out images, changing colors, etc (see [contributing](https://github.com/vercel/og-image/blob/main/CONTRIBUTING.md) for more info) 75 | 5. Hobby plan users will need to remove all configuration inside `vercel.json` besides `rewrites` 76 | 6. Run locally with `vercel dev` and visit [localhost:3000](http://localhost:3000) (if nothing happens, run `npm install -g vercel`) 77 | 7. Deploy to the cloud by running `vercel` and you'll get a unique URL 78 | 8. Setup [GitHub](https://vercel.com/github) to autodeploy on push 79 | 80 | If you are using a paid plan, you can do a one-click deploy with the button below. 81 | 82 | [![Deploy to Vercel](https://vercel.com/button)](https://vercel.com/new/project?template=vercel/og-image) 83 | 84 | Once you have an image generator that sparks joy, you can setup [automatic GitHub](https://vercel.com/github) deployments so that pushing to main will deploy to production! 🚀 85 | 86 | ## Credits 87 | 88 | This is a fork of the lovely [Open Graph Image Generator](https://github.com/vercel/og-image) created by [Vercel](https:/vercel.com). Special thanks to their clearly-written and reuse-friendly repository! 89 | -------------------------------------------------------------------------------- /api/_fonts/RobotoCondensed-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/api/_fonts/RobotoCondensed-Bold.woff2 -------------------------------------------------------------------------------- /api/_fonts/RobotoCondensed-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/api/_fonts/RobotoCondensed-Regular.woff2 -------------------------------------------------------------------------------- /api/_fonts/SourceSansPro-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/api/_fonts/SourceSansPro-Bold.woff2 -------------------------------------------------------------------------------- /api/_fonts/SourceSansPro-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/api/_fonts/SourceSansPro-Regular.woff2 -------------------------------------------------------------------------------- /api/_lib/chromium.ts: -------------------------------------------------------------------------------- 1 | import { launch, Page } from "puppeteer-core"; 2 | import { getOptions } from "./options"; 3 | import { FileType } from "./types"; 4 | let _page: Page | null; 5 | 6 | async function getPage(isDev: boolean) { 7 | if (_page) { 8 | return _page; 9 | } 10 | const options = await getOptions(isDev); 11 | const browser = await launch(options); 12 | _page = await browser.newPage(); 13 | return _page; 14 | } 15 | 16 | export async function getScreenshot(html: string, type: FileType, isDev: boolean) { 17 | console.log({ html }); 18 | const page = await getPage(isDev); 19 | await page.setViewport({ width: 2048, height: 1170 }); 20 | await page.setContent(html); 21 | const file = await page.screenshot({ type }); 22 | return file; 23 | } 24 | -------------------------------------------------------------------------------- /api/_lib/options.ts: -------------------------------------------------------------------------------- 1 | import chrome from 'chrome-aws-lambda'; 2 | const exePath = process.platform === 'win32' 3 | ? 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe' 4 | : process.platform === 'linux' 5 | ? '/usr/bin/google-chrome' 6 | : '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; 7 | 8 | interface Options { 9 | args: string[]; 10 | executablePath: string; 11 | headless: boolean; 12 | } 13 | 14 | export async function getOptions(isDev: boolean) { 15 | let options: Options; 16 | if (isDev) { 17 | options = { 18 | args: [], 19 | executablePath: exePath, 20 | headless: true 21 | }; 22 | } else { 23 | options = { 24 | args: chrome.args, 25 | executablePath: await chrome.executablePath, 26 | headless: chrome.headless, 27 | }; 28 | } 29 | return options; 30 | } 31 | -------------------------------------------------------------------------------- /api/_lib/parser.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage } from 'http' 2 | import { parse } from 'url' 3 | import { ParsedRequest, Theme } from './types' 4 | 5 | export function parseRequest(req: IncomingMessage) { 6 | console.log('HTTP ' + req.url) 7 | const { pathname, query } = parse(req.url || '/', true) 8 | const { fontFamily, fontSize, images, widths, heights, theme, md, imageObj } = query || {} 9 | 10 | if (Array.isArray(imageObj)) { 11 | throw new Error('Expected a single image Object'); 12 | } 13 | if (Array.isArray(fontFamily)) { 14 | throw new Error('Expected a single fontFamily') 15 | } 16 | if (Array.isArray(fontSize)) { 17 | throw new Error('Expected a single fontSize') 18 | } 19 | if (Array.isArray(theme)) { 20 | throw new Error('Expected a single theme') 21 | } 22 | 23 | let parsedImages = JSON.parse(imageObj || '{}'); 24 | if (Object.keys(parsedImages).length === 0) { 25 | console.log('Legacy image format'); 26 | parsedImages.images = getArray(images); 27 | parsedImages.widths = getArray(widths); 28 | parsedImages.heights = getArray(heights); 29 | } 30 | 31 | const arr = (pathname || '/').slice(1).split('.') 32 | let extension = '' 33 | let text = '' 34 | if (arr.length === 0) { 35 | text = '' 36 | } else if (arr.length === 1) { 37 | text = arr[0] 38 | } else { 39 | extension = arr.pop() as string 40 | text = arr.join('.') 41 | } 42 | 43 | const parsedRequest: ParsedRequest = { 44 | fileType: extension === 'jpeg' ? extension : 'png', 45 | text: decodeURIComponent(text), 46 | theme: theme === 'dark' ? 'dark' : 'light', 47 | md: md === '1' || md === 'true', 48 | fontFamily: fontFamily === 'roboto-condensed' ? 'Roboto Condensed' : 'Source Sans Pro', 49 | fontSize: fontSize || '96px', 50 | images: parsedImages.images, 51 | widths: parsedImages.widths, 52 | heights: parsedImages.heights, 53 | } 54 | parsedRequest.images = getDefaultImages( 55 | parsedRequest.images, 56 | parsedRequest.theme 57 | ) 58 | return parsedRequest 59 | } 60 | 61 | function getArray(stringOrArray: string[] | string | undefined): string[] { 62 | if (typeof stringOrArray === 'undefined') { 63 | return [] 64 | } else if (Array.isArray(stringOrArray)) { 65 | return stringOrArray 66 | } else { 67 | return [stringOrArray] 68 | } 69 | } 70 | 71 | function getDefaultImages(images: string[], theme: Theme): string[] { 72 | const defaultImage = 73 | theme === 'light' 74 | ? 'https://cc-vocabulary.netlify.app/logos/cc/lettermark.svg#lettermark' 75 | : 'https://cc-vocabulary.netlify.app/logos/cc/lettermark.svg#lettermark' 76 | 77 | if (!images || !images[0]) { 78 | return [defaultImage] 79 | } 80 | 81 | return images 82 | } 83 | -------------------------------------------------------------------------------- /api/_lib/sanitizer.ts: -------------------------------------------------------------------------------- 1 | const entityMap: { [key: string]: string } = { 2 | "&": "&", 3 | "<": "<", 4 | ">": ">", 5 | '"': '"', 6 | "'": ''', 7 | "/": '/' 8 | }; 9 | 10 | export function sanitizeHtml(html: string) { 11 | return String(html).replace(/[&<>"'\/]/g, key => entityMap[key]); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /api/_lib/template.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import marked from 'marked' 3 | import { sanitizeHtml } from './sanitizer' 4 | import { ParsedRequest } from './types' 5 | const twemoji = require('twemoji') 6 | const twOptions = { folder: 'svg', ext: '.svg' } 7 | const emojify = (text: string) => twemoji.parse(text, twOptions) 8 | 9 | const sourceRglr = readFileSync( 10 | `${__dirname}/../_fonts/SourceSansPro-Regular.woff2` 11 | ).toString('base64') 12 | const sourceBold = readFileSync( 13 | `${__dirname}/../_fonts/SourceSansPro-Bold.woff2` 14 | ).toString('base64') 15 | const robotoRglr = readFileSync( 16 | `${__dirname}/../_fonts/RobotoCondensed-Regular.woff2` 17 | ).toString('base64') 18 | const robotoBold = readFileSync( 19 | `${__dirname}/../_fonts/RobotoCondensed-Bold.woff2` 20 | ).toString('base64') 21 | 22 | function getCss(theme: string, fontFamily: string, fontSize: string) { 23 | let background = 'white' 24 | let foreground = 'black' 25 | let bgImage = 26 | 'data:image/png;base64,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' 27 | 28 | if (theme === 'dark') { 29 | background = 'black' 30 | foreground = 'white' 31 | bgImage = 32 | 'data:image/png;base64,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' 33 | } 34 | 35 | let headingStyles = ` 36 | font-weight: normal; 37 | text-transform: none;` 38 | 39 | if (fontFamily === 'Roboto Condensed') { 40 | headingStyles = ` 41 | font-weight: bold; 42 | text-transform: uppercase;` 43 | } 44 | 45 | return ` 46 | @font-face { 47 | font-family: 'Source Sans Pro'; 48 | font-style: normal; 49 | font-weight: normal; 50 | src: url(data:font/woff2;charset=utf-8;base64,${sourceRglr}) format('woff2'); 51 | } 52 | 53 | @font-face { 54 | font-family: 'Source Sans Pro'; 55 | font-style: normal; 56 | font-weight: bold; 57 | src: url(data:font/woff2;charset=utf-8;base64,${sourceBold}) format('woff2'); 58 | } 59 | 60 | @font-face { 61 | font-family: 'Roboto Condensed'; 62 | font-style: normal; 63 | font-weight: normal; 64 | src: url(data:font/woff2;charset=utf-8;base64,${robotoRglr}) format('woff2'); 65 | } 66 | 67 | @font-face { 68 | font-family: 'Roboto Condensed'; 69 | font-style: normal; 70 | font-weight: bold; 71 | src: url(data:font/woff2;charset=utf-8;base64,${robotoBold}) format('woff2'); 72 | } 73 | 74 | body { 75 | background: ${background}; 76 | background-image: url(${bgImage}); 77 | background-size: 300px 300px; 78 | height: 100vh; 79 | display: flex; 80 | text-align: center; 81 | align-items: center; 82 | justify-content: center; 83 | } 84 | 85 | code { 86 | color: #D400FF; 87 | font-family: 'Vera'; 88 | white-space: pre-wrap; 89 | letter-spacing: -5px; 90 | } 91 | 92 | code:before, code:after { 93 | content: '\`'; 94 | } 95 | 96 | .logo-wrapper { 97 | display: flex; 98 | align-items: center; 99 | align-content: center; 100 | justify-content: center; 101 | justify-items: center; 102 | } 103 | 104 | .logo { 105 | margin: 0 75px; 106 | max-width: 100%; 107 | } 108 | 109 | .dark-svg { 110 | filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(12deg) brightness(103%) contrast(103%); 111 | } 112 | 113 | .plus { 114 | color: #BBB; 115 | font-family: Times New Roman, Verdana; 116 | font-size: 100px; 117 | } 118 | 119 | .spacer { 120 | margin: 150px; 121 | } 122 | 123 | .emoji { 124 | height: 1em; 125 | width: 1em; 126 | margin: 0 .05em 0 .1em; 127 | vertical-align: -0.1em; 128 | } 129 | 130 | .heading { 131 | font-family: '${sanitizeHtml(fontFamily)}', sans-serif; 132 | font-size: ${sanitizeHtml(fontSize)}; 133 | color: ${foreground}; 134 | ${headingStyles} 135 | line-height: 1.3; 136 | letter-spacing: 0.02rem; 137 | }` 138 | } 139 | 140 | export function getHtml(parsedReq: ParsedRequest) { 141 | const { 142 | text, 143 | theme, 144 | md, 145 | fontFamily, 146 | fontSize, 147 | images, 148 | widths, 149 | heights, 150 | } = parsedReq 151 | return ` 152 | 153 | 154 | Generated Image 155 | 156 | 159 | 160 |
161 |
162 |
163 | ${images 164 | .map( 165 | (img, i) => 166 | getPlusSign(i) + 167 | getImage(img, theme, widths[i], heights[i]) 168 | ) 169 | .join('')} 170 |
171 |
172 |
${emojify( 173 | md ? marked(text) : sanitizeHtml(text) 174 | )} 175 |
176 |
177 | 178 | ` 179 | } 180 | 181 | function getImage(src: string, theme: string, width = 'auto', height = '225') { 182 | return `` 189 | } 190 | 191 | function getPlusSign(i: number) { 192 | return i === 0 ? '' : '
+
' 193 | } 194 | -------------------------------------------------------------------------------- /api/_lib/types.ts: -------------------------------------------------------------------------------- 1 | export type FileType = 'png' | 'jpeg'; 2 | export type Theme = 'light' | 'dark'; 3 | 4 | export interface ParsedRequest { 5 | fileType: FileType; 6 | text: string; 7 | theme: Theme; 8 | md: boolean; 9 | fontFamily: string; 10 | fontSize: string; 11 | images: string[]; 12 | widths: string[]; 13 | heights: string[]; 14 | } 15 | -------------------------------------------------------------------------------- /api/index.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage, ServerResponse } from 'http'; 2 | import { parseRequest } from './_lib/parser'; 3 | import { getScreenshot } from './_lib/chromium'; 4 | import { getHtml } from './_lib/template'; 5 | 6 | const isDev = !process.env.AWS_REGION; 7 | const isHtmlDebug = process.env.OG_HTML_DEBUG === '1'; 8 | 9 | export default async function handler(req: IncomingMessage, res: ServerResponse) { 10 | try { 11 | const parsedReq = parseRequest(req); 12 | const html = getHtml(parsedReq); 13 | if (isHtmlDebug) { 14 | res.setHeader('Content-Type', 'text/html'); 15 | res.end(html); 16 | return; 17 | } 18 | const { fileType } = parsedReq; 19 | const file = await getScreenshot(html, fileType, isDev); 20 | res.statusCode = 200; 21 | res.setHeader('Content-Type', `image/${fileType}`); 22 | res.setHeader('Cache-Control', `public, immutable, no-transform, s-maxage=31536000, max-age=31536000`); 23 | res.end(file); 24 | } catch (e) { 25 | res.statusCode = 500; 26 | res.setHeader('Content-Type', 'text/html'); 27 | res.end('

Internal Error

Sorry, there was a problem

'); 28 | console.error(e); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "commonjs", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "strict": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noEmitOnError": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "removeComments": true, 16 | "preserveConstEnums": true, 17 | "esModuleInterop": true 18 | }, 19 | "include": [ 20 | "./" 21 | ] 22 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@creativecommons/vocabulary": { 6 | "version": "2020.8.6", 7 | "resolved": "https://registry.npmjs.org/@creativecommons/vocabulary/-/vocabulary-2020.8.6.tgz", 8 | "integrity": "sha512-dGyL5gYLpacybJ5yRwMAFchAe8EOUxDykRj4rQcA92gYEqLbB2JJ2/TKtfQ1r8OBuGB9D72qU1bdL6sFH7lgIw==" 9 | }, 10 | "@types/marked": { 11 | "version": "0.7.3", 12 | "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.7.3.tgz", 13 | "integrity": "sha512-WXdEKuT3azHxLTThd5dwnpLt2Q9QiC8iKj09KZRtVqro3pX8hhY+GbD8FZOae6SBBEJ22yKJn3c7ejL0aucAcA==", 14 | "dev": true 15 | }, 16 | "@types/mime-types": { 17 | "version": "2.1.0", 18 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz", 19 | "integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=" 20 | }, 21 | "@types/node": { 22 | "version": "14.6.0", 23 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", 24 | "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==", 25 | "dev": true 26 | }, 27 | "@types/puppeteer": { 28 | "version": "2.0.1", 29 | "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-2.0.1.tgz", 30 | "integrity": "sha512-G8vEyU83Bios+dzs+DZGpAirDmMqRhfFBJCkFrg+A5+6n5EPPHxwBLImJto3qjh0mrBXbLBCyuahhhtTrAfR5g==", 31 | "dev": true, 32 | "requires": { 33 | "@types/node": "*" 34 | } 35 | }, 36 | "@types/puppeteer-core": { 37 | "version": "2.0.0", 38 | "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-2.0.0.tgz", 39 | "integrity": "sha512-JvoEb7KgEkUet009ZDrtpUER3hheXoHgQByuYpJZ5WWT7LWwMH+0NTqGQXGgoOKzs+G5NA1T4DZwXK79Bhnejw==", 40 | "dev": true, 41 | "requires": { 42 | "@types/puppeteer": "*" 43 | } 44 | }, 45 | "agent-base": { 46 | "version": "5.1.1", 47 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", 48 | "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" 49 | }, 50 | "async-limiter": { 51 | "version": "1.0.1", 52 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 53 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 54 | }, 55 | "balanced-match": { 56 | "version": "1.0.0", 57 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 58 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 59 | }, 60 | "brace-expansion": { 61 | "version": "1.1.11", 62 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 63 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 64 | "requires": { 65 | "balanced-match": "^1.0.0", 66 | "concat-map": "0.0.1" 67 | } 68 | }, 69 | "buffer-crc32": { 70 | "version": "0.2.13", 71 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", 72 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 73 | }, 74 | "buffer-from": { 75 | "version": "1.1.1", 76 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 77 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 78 | }, 79 | "chrome-aws-lambda": { 80 | "version": "2.1.1", 81 | "resolved": "https://registry.npmjs.org/chrome-aws-lambda/-/chrome-aws-lambda-2.1.1.tgz", 82 | "integrity": "sha512-Wer2QuygxsCov5bM2+8CLa6qYpNsc5AxYTlgTne00aFoxFP491LGJRxOQtGnYtsJP6UG4pB0SfrwTyPnLys1Lw==", 83 | "requires": { 84 | "lambdafs": "^1.3.0" 85 | } 86 | }, 87 | "concat-map": { 88 | "version": "0.0.1", 89 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 90 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 91 | }, 92 | "concat-stream": { 93 | "version": "1.6.2", 94 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 95 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 96 | "requires": { 97 | "buffer-from": "^1.0.0", 98 | "inherits": "^2.0.3", 99 | "readable-stream": "^2.2.2", 100 | "typedarray": "^0.0.6" 101 | } 102 | }, 103 | "core-util-is": { 104 | "version": "1.0.2", 105 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 106 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 107 | }, 108 | "debug": { 109 | "version": "4.1.1", 110 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 111 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 112 | "requires": { 113 | "ms": "^2.1.1" 114 | } 115 | }, 116 | "extract-zip": { 117 | "version": "1.7.0", 118 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", 119 | "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", 120 | "requires": { 121 | "concat-stream": "^1.6.2", 122 | "debug": "^2.6.9", 123 | "mkdirp": "^0.5.4", 124 | "yauzl": "^2.10.0" 125 | }, 126 | "dependencies": { 127 | "debug": { 128 | "version": "2.6.9", 129 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 130 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 131 | "requires": { 132 | "ms": "2.0.0" 133 | } 134 | }, 135 | "ms": { 136 | "version": "2.0.0", 137 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 138 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 139 | } 140 | } 141 | }, 142 | "fd-slicer": { 143 | "version": "1.1.0", 144 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", 145 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", 146 | "requires": { 147 | "pend": "~1.2.0" 148 | } 149 | }, 150 | "fs-extra": { 151 | "version": "8.1.0", 152 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 153 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 154 | "requires": { 155 | "graceful-fs": "^4.2.0", 156 | "jsonfile": "^4.0.0", 157 | "universalify": "^0.1.0" 158 | }, 159 | "dependencies": { 160 | "jsonfile": { 161 | "version": "4.0.0", 162 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 163 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 164 | "requires": { 165 | "graceful-fs": "^4.1.6" 166 | } 167 | } 168 | } 169 | }, 170 | "fs.realpath": { 171 | "version": "1.0.0", 172 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 173 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 174 | }, 175 | "glob": { 176 | "version": "7.1.6", 177 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 178 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 179 | "requires": { 180 | "fs.realpath": "^1.0.0", 181 | "inflight": "^1.0.4", 182 | "inherits": "2", 183 | "minimatch": "^3.0.4", 184 | "once": "^1.3.0", 185 | "path-is-absolute": "^1.0.0" 186 | } 187 | }, 188 | "graceful-fs": { 189 | "version": "4.2.4", 190 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 191 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" 192 | }, 193 | "https-proxy-agent": { 194 | "version": "4.0.0", 195 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", 196 | "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", 197 | "requires": { 198 | "agent-base": "5", 199 | "debug": "4" 200 | } 201 | }, 202 | "inflight": { 203 | "version": "1.0.6", 204 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 205 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 206 | "requires": { 207 | "once": "^1.3.0", 208 | "wrappy": "1" 209 | } 210 | }, 211 | "inherits": { 212 | "version": "2.0.4", 213 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 214 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 215 | }, 216 | "isarray": { 217 | "version": "1.0.0", 218 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 219 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 220 | }, 221 | "jsonfile": { 222 | "version": "5.0.0", 223 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", 224 | "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", 225 | "requires": { 226 | "graceful-fs": "^4.1.6", 227 | "universalify": "^0.1.2" 228 | } 229 | }, 230 | "lambdafs": { 231 | "version": "1.3.0", 232 | "resolved": "https://registry.npmjs.org/lambdafs/-/lambdafs-1.3.0.tgz", 233 | "integrity": "sha512-HqRPmEgtkTW4sCYDUjTEuTGkjCHuLvtZU8iM8GkhD7SpjW4AJJbBk86YU4K43sWGuW5Vmzp1lVCx4ab/kJsuBw==", 234 | "requires": { 235 | "tar-fs": "^2.0.0" 236 | }, 237 | "dependencies": { 238 | "bl": { 239 | "version": "3.0.0", 240 | "bundled": true, 241 | "requires": { 242 | "readable-stream": "^3.0.1" 243 | } 244 | }, 245 | "chownr": { 246 | "version": "1.1.2", 247 | "bundled": true 248 | }, 249 | "end-of-stream": { 250 | "version": "1.4.1", 251 | "bundled": true, 252 | "requires": { 253 | "once": "^1.4.0" 254 | } 255 | }, 256 | "fs-constants": { 257 | "version": "1.0.0", 258 | "bundled": true 259 | }, 260 | "inherits": { 261 | "version": "2.0.4", 262 | "bundled": true 263 | }, 264 | "minimist": { 265 | "version": "0.0.8", 266 | "bundled": true 267 | }, 268 | "mkdirp": { 269 | "version": "0.5.1", 270 | "bundled": true, 271 | "requires": { 272 | "minimist": "0.0.8" 273 | } 274 | }, 275 | "once": { 276 | "version": "1.4.0", 277 | "bundled": true, 278 | "requires": { 279 | "wrappy": "1" 280 | } 281 | }, 282 | "pump": { 283 | "version": "3.0.0", 284 | "bundled": true, 285 | "requires": { 286 | "end-of-stream": "^1.1.0", 287 | "once": "^1.3.1" 288 | } 289 | }, 290 | "readable-stream": { 291 | "version": "3.4.0", 292 | "bundled": true, 293 | "requires": { 294 | "inherits": "^2.0.3", 295 | "string_decoder": "^1.1.1", 296 | "util-deprecate": "^1.0.1" 297 | } 298 | }, 299 | "safe-buffer": { 300 | "version": "5.2.0", 301 | "bundled": true 302 | }, 303 | "string_decoder": { 304 | "version": "1.3.0", 305 | "bundled": true, 306 | "requires": { 307 | "safe-buffer": "~5.2.0" 308 | } 309 | }, 310 | "tar-fs": { 311 | "version": "2.0.0", 312 | "bundled": true, 313 | "requires": { 314 | "chownr": "^1.1.1", 315 | "mkdirp": "^0.5.1", 316 | "pump": "^3.0.0", 317 | "tar-stream": "^2.0.0" 318 | } 319 | }, 320 | "tar-stream": { 321 | "version": "2.1.0", 322 | "bundled": true, 323 | "requires": { 324 | "bl": "^3.0.0", 325 | "end-of-stream": "^1.4.1", 326 | "fs-constants": "^1.0.0", 327 | "inherits": "^2.0.3", 328 | "readable-stream": "^3.1.1" 329 | } 330 | }, 331 | "util-deprecate": { 332 | "version": "1.0.2", 333 | "bundled": true 334 | }, 335 | "wrappy": { 336 | "version": "1.0.2", 337 | "bundled": true 338 | } 339 | } 340 | }, 341 | "marked": { 342 | "version": "0.8.2", 343 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", 344 | "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==" 345 | }, 346 | "mime": { 347 | "version": "2.4.6", 348 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", 349 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" 350 | }, 351 | "mime-db": { 352 | "version": "1.44.0", 353 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 354 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 355 | }, 356 | "mime-types": { 357 | "version": "2.1.27", 358 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 359 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 360 | "requires": { 361 | "mime-db": "1.44.0" 362 | } 363 | }, 364 | "minimatch": { 365 | "version": "3.0.4", 366 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 367 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 368 | "requires": { 369 | "brace-expansion": "^1.1.7" 370 | } 371 | }, 372 | "minimist": { 373 | "version": "1.2.5", 374 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 375 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 376 | }, 377 | "mkdirp": { 378 | "version": "0.5.5", 379 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 380 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 381 | "requires": { 382 | "minimist": "^1.2.5" 383 | } 384 | }, 385 | "ms": { 386 | "version": "2.1.2", 387 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 388 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 389 | }, 390 | "once": { 391 | "version": "1.4.0", 392 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 393 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 394 | "requires": { 395 | "wrappy": "1" 396 | } 397 | }, 398 | "path-is-absolute": { 399 | "version": "1.0.1", 400 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 401 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 402 | }, 403 | "pend": { 404 | "version": "1.2.0", 405 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 406 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 407 | }, 408 | "process-nextick-args": { 409 | "version": "2.0.1", 410 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 411 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 412 | }, 413 | "progress": { 414 | "version": "2.0.3", 415 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 416 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 417 | }, 418 | "proxy-from-env": { 419 | "version": "1.1.0", 420 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 421 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 422 | }, 423 | "puppeteer-core": { 424 | "version": "2.1.1", 425 | "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", 426 | "integrity": "sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==", 427 | "requires": { 428 | "@types/mime-types": "^2.1.0", 429 | "debug": "^4.1.0", 430 | "extract-zip": "^1.6.6", 431 | "https-proxy-agent": "^4.0.0", 432 | "mime": "^2.0.3", 433 | "mime-types": "^2.1.25", 434 | "progress": "^2.0.1", 435 | "proxy-from-env": "^1.0.0", 436 | "rimraf": "^2.6.1", 437 | "ws": "^6.1.0" 438 | } 439 | }, 440 | "readable-stream": { 441 | "version": "2.3.7", 442 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 443 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 444 | "requires": { 445 | "core-util-is": "~1.0.0", 446 | "inherits": "~2.0.3", 447 | "isarray": "~1.0.0", 448 | "process-nextick-args": "~2.0.0", 449 | "safe-buffer": "~5.1.1", 450 | "string_decoder": "~1.1.1", 451 | "util-deprecate": "~1.0.1" 452 | } 453 | }, 454 | "rimraf": { 455 | "version": "2.7.1", 456 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 457 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 458 | "requires": { 459 | "glob": "^7.1.3" 460 | } 461 | }, 462 | "safe-buffer": { 463 | "version": "5.1.2", 464 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 465 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 466 | }, 467 | "string_decoder": { 468 | "version": "1.1.1", 469 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 470 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 471 | "requires": { 472 | "safe-buffer": "~5.1.0" 473 | } 474 | }, 475 | "twemoji": { 476 | "version": "12.1.5", 477 | "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.5.tgz", 478 | "integrity": "sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ==", 479 | "requires": { 480 | "fs-extra": "^8.0.1", 481 | "jsonfile": "^5.0.0", 482 | "twemoji-parser": "12.1.3", 483 | "universalify": "^0.1.2" 484 | } 485 | }, 486 | "twemoji-parser": { 487 | "version": "12.1.3", 488 | "resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-12.1.3.tgz", 489 | "integrity": "sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q==" 490 | }, 491 | "typedarray": { 492 | "version": "0.0.6", 493 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 494 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 495 | }, 496 | "typescript": { 497 | "version": "3.8.3", 498 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", 499 | "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", 500 | "dev": true 501 | }, 502 | "universalify": { 503 | "version": "0.1.2", 504 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 505 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 506 | }, 507 | "util-deprecate": { 508 | "version": "1.0.2", 509 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 510 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 511 | }, 512 | "wrappy": { 513 | "version": "1.0.2", 514 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 515 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 516 | }, 517 | "ws": { 518 | "version": "6.2.1", 519 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", 520 | "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", 521 | "requires": { 522 | "async-limiter": "~1.0.0" 523 | } 524 | }, 525 | "yauzl": { 526 | "version": "2.10.0", 527 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", 528 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", 529 | "requires": { 530 | "buffer-crc32": "~0.2.3", 531 | "fd-slicer": "~1.1.0" 532 | } 533 | } 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "og-image-generator", 3 | "description": "An open graph image generator for Creative Commons projects", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/creativecommons/og-image-generator.git" 7 | }, 8 | "bugs": { 9 | "url": "https://github.com/creativecommons/og-image-generator/issues" 10 | }, 11 | "author": "zackkrida", 12 | "scripts": { 13 | "build": "tsc -p api/tsconfig.json && tsc -p web/tsconfig.json" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@creativecommons/vocabulary": "^2020.8.6", 18 | "chrome-aws-lambda": "2.1.1", 19 | "marked": "0.8.2", 20 | "puppeteer-core": "2.1.1", 21 | "twemoji": "12.1.5" 22 | }, 23 | "devDependencies": { 24 | "@types/marked": "0.7.3", 25 | "@types/puppeteer": "2.0.1", 26 | "@types/puppeteer-core": "2.0.0", 27 | "typescript": "3.8.3" 28 | }, 29 | "engines": { 30 | "node": "12.x" 31 | }, 32 | "volta": { 33 | "node": "12.18.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 23 | 25 | Creative Commons Open Graph Image Generator 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 51 |
52 |
53 | Loading... 54 |
55 |
56 | 57 |
58 |
59 |
60 |

What is this?

61 |

This is a service that generates dynamic Open Graph images that you can 62 | embed in your <meta> tags.

63 |

For each keystroke, headless chromium is used to render an HTML page and take a screenshot of the 64 | result which gets cached.

65 |
66 |
67 |
68 | 69 |
70 | 71 | 72 | 169 | 170 | 171 | 172 | 173 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | button { 2 | appearance: none; 3 | align-items: center; 4 | color: #fff; 5 | background: #000; 6 | display: inline-flex; 7 | width: 100px; 8 | height: 40px; 9 | padding: 0 25px; 10 | outline: none; 11 | border: 1px solid #000; 12 | font-size: 12px; 13 | justify-content: center; 14 | text-transform: uppercase; 15 | cursor: pointer; 16 | text-align: center; 17 | user-select: none; 18 | font-weight: 100; 19 | position: relative; 20 | overflow: hidden; 21 | transition: border 0.2s, background 0.2s, color 0.2s ease-out; 22 | border-radius: 5px; 23 | white-space: nowrap; 24 | text-decoration: none; 25 | line-height: 0; 26 | 27 | height: 24px; 28 | width: 100px; 29 | padding: 0 10px; 30 | font-size: 12px; 31 | background: #fff; 32 | border: 1px solid #eaeaea; 33 | color: #666; 34 | } 35 | 36 | button:hover { 37 | color: #000; 38 | border-color: #000; 39 | background: #fff; 40 | } 41 | 42 | .input-outer-wrapper { 43 | align-items: center; 44 | border-radius: 5px; 45 | border: 1px solid #e1e1e1; 46 | display: inline-flex; 47 | height: 37px; 48 | position: relative; 49 | transition: border 0.2s ease, color 0.2s ease; 50 | vertical-align: middle; 51 | width: 100%; 52 | } 53 | 54 | .input-outer-wrapper:hover { 55 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); 56 | border-color: #ddd; 57 | } 58 | 59 | .input-inner-wrapper { 60 | display: block; 61 | margin: 4px 10px; 62 | position: relative; 63 | width: 100%; 64 | } 65 | 66 | .input-inner-wrapper input { 67 | background-color: transparent; 68 | border-radius: 0; 69 | border: none; 70 | box-shadow: none; 71 | box-sizing: border-box; 72 | display: block; 73 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 74 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 75 | sans-serif; 76 | font-size: 14px; 77 | line-height: 27px; 78 | outline: 0; 79 | width: 100%; 80 | } 81 | 82 | .select-wrapper { 83 | appearance: none; 84 | color: #000; 85 | background: #fff; 86 | display: inline-flex; 87 | height: 40px; 88 | outline: none; 89 | border: 1px solid #eaeaea; 90 | font-size: 12px; 91 | text-transform: uppercase; 92 | user-select: none; 93 | font-weight: 100; 94 | position: relative; 95 | overflow: hidden; 96 | transition: border 0.2s, background 0.2s, color 0.2s ease-out, 97 | box-shadow 0.2s ease; 98 | border-radius: 5px; 99 | white-space: nowrap; 100 | line-height: 0; 101 | width: auto; 102 | min-width: 100%; 103 | } 104 | 105 | .select-wrapper:hover { 106 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); 107 | border-color: #ddd; 108 | } 109 | 110 | .select-wrapper.small { 111 | height: 24px; 112 | min-width: 100px; 113 | width: 100px; 114 | } 115 | 116 | .select-wrapper.small select { 117 | padding-right: 21px; 118 | } 119 | 120 | .select-arrow { 121 | border-left: 1px solid #eaeaea; 122 | background: #fff; 123 | width: 40px; 124 | height: 100%; 125 | position: absolute; 126 | right: 0; 127 | pointer-events: none; 128 | display: flex; 129 | align-items: center; 130 | justify-content: center; 131 | } 132 | 133 | .select-arrow.small { 134 | width: 22px; 135 | } 136 | 137 | select { 138 | height: 100%; 139 | border: none; 140 | box-shadow: none; 141 | background: transparent; 142 | background-image: none; 143 | color: #000; 144 | line-height: 40px; 145 | font-size: 14px; 146 | margin-right: -20px; 147 | width: calc(100% + 20px); 148 | padding: 0 76px 0 16px; 149 | text-transform: none; 150 | } 151 | 152 | .field-flex { 153 | display: flex; 154 | margin-top: 10px; 155 | } 156 | 157 | .custom-label { 158 | width: 70px; 159 | display: inline-block; 160 | margin-right: 20px; 161 | text-align: right; 162 | } 163 | 164 | .field-value { 165 | flex-grow: 1; 166 | display: inline-block; 167 | } 168 | 169 | label { 170 | display: flex; 171 | align-items: center; 172 | } 173 | 174 | .action-buttons > button { 175 | margin-right: 10px; 176 | } 177 | 178 | .download-image:hover { 179 | text-decoration: none; 180 | } 181 | 182 | .toast-area { 183 | position: fixed; 184 | bottom: 10px; 185 | right: 20px; 186 | max-width: 420px; 187 | z-index: 2000; 188 | transition: transform 0.4s ease; 189 | } 190 | 191 | .toast-outer { 192 | width: 320px; 193 | height: 72px; 194 | position: absolute; 195 | bottom: 0; 196 | right: 0; 197 | transition: all 0.4s ease; 198 | transform: translate3d(0, 130%, 0px) scale(1); 199 | animation: show-jsx-1861505484 0.4s ease forwards; 200 | opacity: 1; 201 | } 202 | 203 | .toast-inner { 204 | width: 320px; 205 | background: #fff; 206 | color: #000; 207 | border: 0; 208 | border-radius: 5px; 209 | height: 60px; 210 | align-items: center; 211 | justify-content: space-between; 212 | 213 | box-shadow: 0 4px 9px rgba(0, 0, 0, 0.12); 214 | font-size: 14px; 215 | display: flex; 216 | } 217 | 218 | .toast-message { 219 | text-overflow: ellipsis; 220 | white-space: nowrap; 221 | width: 100%; 222 | overflow: hidden; 223 | margin-top: -1px; 224 | margin-left: 20px; 225 | } 226 | 227 | img { 228 | max-width: 100%; 229 | transition: all 0.3s ease-in 0s; 230 | } 231 | 232 | .image-wrapper { 233 | display: block; 234 | cursor: pointer; 235 | text-decoration: none; 236 | background: #fff; 237 | box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.12); 238 | border-radius: 5px; 239 | margin-bottom: 50px; 240 | transition: all 0.2s ease; 241 | max-width: 100%; 242 | line-height: 0; 243 | overflow: hidden; 244 | } 245 | 246 | .image-wrapper:hover { 247 | box-shadow: 0 1px 16px rgba(0, 0, 0, 0.1); 248 | border-color: #ddd; 249 | } 250 | 251 | @media (max-width: 1000px) { 252 | .field { 253 | margin: 20px 10px; 254 | } 255 | } 256 | 257 | @media (max-width: 900px) { 258 | .custom-label { 259 | width: 60px; 260 | font-size: 13px; 261 | } 262 | } 263 | 264 | .github-corner:hover .octo-arm { 265 | animation: octocat-wave 560ms ease-in-out; 266 | } 267 | 268 | @keyframes octocat-wave { 269 | 0%, 270 | 100% { 271 | transform: rotate(0); 272 | } 273 | 20%, 274 | 60% { 275 | transform: rotate(-25deg); 276 | } 277 | 40%, 278 | 80% { 279 | transform: rotate(10deg); 280 | } 281 | } 282 | 283 | @media (max-width: 500px) { 284 | .github-corner:hover .octo-arm { 285 | animation: none; 286 | } 287 | .github-corner .octo-arm { 288 | animation: octocat-wave 560ms ease-in-out; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /public/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creativecommons/og-image-generator/ac3a11ce906de174dfc15ce2cecad42fd6642ad3/public/tweet.png -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "api/**": { 4 | "memory": 3008 5 | } 6 | }, 7 | "rewrites": [{ "source": "/(.+)", "destination": "/api" }] 8 | } 9 | -------------------------------------------------------------------------------- /web/index.ts: -------------------------------------------------------------------------------- 1 | import { ParsedRequest, Theme, FileType } from '../api/_lib/types' 2 | const { H, R, copee } = window as any 3 | let timeout = -1 4 | 5 | interface ImagePreviewProps { 6 | src: string 7 | onclick: () => void 8 | onload: () => void 9 | onerror: () => void 10 | loading: boolean 11 | } 12 | 13 | const ImagePreview = ({ 14 | src, 15 | onclick, 16 | onload, 17 | onerror, 18 | loading, 19 | }: ImagePreviewProps) => { 20 | const style = { 21 | filter: loading ? 'blur(5px)' : '', 22 | opacity: loading ? 0.1 : 1, 23 | } 24 | const title = 'Click to copy image URL to clipboard' 25 | return H( 26 | 'a', 27 | { className: 'image-wrapper', href: src, onclick }, 28 | H('img', { src, onload, onerror, style, title }) 29 | ) 30 | } 31 | 32 | interface DropdownOption { 33 | text: string 34 | value: string 35 | } 36 | 37 | interface DropdownProps { 38 | options: DropdownOption[] 39 | value: string 40 | onchange: (val: string) => void 41 | small: boolean 42 | } 43 | 44 | const Dropdown = ({ options, value, onchange, small }: DropdownProps) => { 45 | const wrapper = small ? 'select-wrapper small' : 'select-wrapper' 46 | const arrow = small ? 'select-arrow small' : 'select-arrow' 47 | return H( 48 | 'div', 49 | { className: wrapper }, 50 | H( 51 | 'select', 52 | { onchange: (e: any) => onchange(e.target.value) }, 53 | options.map((o) => 54 | H('option', { value: o.value, selected: value === o.value }, o.text) 55 | ) 56 | ), 57 | H('div', { className: arrow }, '▼') 58 | ) 59 | } 60 | 61 | interface TextInputProps { 62 | value: string 63 | oninput: (val: string) => void 64 | } 65 | 66 | const TextInput = ({ value, oninput }: TextInputProps) => { 67 | return H( 68 | 'div', 69 | { className: 'input-outer-wrapper' }, 70 | H( 71 | 'div', 72 | { className: 'input-inner-wrapper' }, 73 | H('input', { 74 | type: 'text', 75 | value, 76 | oninput: (e: any) => oninput(e.target.value), 77 | }) 78 | ) 79 | ) 80 | } 81 | 82 | interface ButtonProps { 83 | label: string 84 | onclick: () => void 85 | } 86 | 87 | const Button = ({ label, onclick }: ButtonProps) => { 88 | return H('button', { onclick }, label) 89 | } 90 | 91 | interface ActionsProps { 92 | href: string 93 | copyImage: () => void 94 | copyMetaTag: () => void 95 | } 96 | 97 | const ActionButtons = ({ href, copyImage, copyMetaTag }: ActionsProps) => { 98 | return H( 99 | 'div', 100 | { className: 'action-buttons' }, 101 | H(Button, { label: 'Copy Image Url', onclick: copyImage }), 102 | H(Button, { label: 'Copy Meta Tag', onclick: copyMetaTag }), 103 | H( 104 | 'a', 105 | { className: 'download-image', href, download: 'og-image' }, 106 | H(Button, { 107 | label: 'Download Image', 108 | }) 109 | ) 110 | ) 111 | } 112 | 113 | interface FieldProps { 114 | label: string 115 | input: any 116 | } 117 | 118 | const Field = ({ label, input }: FieldProps) => { 119 | return H( 120 | 'div', 121 | { className: 'field' }, 122 | H( 123 | 'label', 124 | H('div', { className: 'custom-label' }, label), 125 | H('div', { className: 'field-value' }, input) 126 | ) 127 | ) 128 | } 129 | 130 | interface ToastProps { 131 | show: boolean 132 | message: string 133 | } 134 | 135 | const Toast = ({ show, message }: ToastProps) => { 136 | const style = { transform: show ? 'translate3d(0,-0px,-0px) scale(1)' : '' } 137 | return H( 138 | 'div', 139 | { className: 'toast-area' }, 140 | H( 141 | 'div', 142 | { className: 'toast-outer', style }, 143 | H( 144 | 'div', 145 | { className: 'toast-inner' }, 146 | H('div', { className: 'toast-message' }, message) 147 | ) 148 | ) 149 | ) 150 | } 151 | 152 | const themeOptions: DropdownOption[] = [ 153 | { text: 'Light', value: 'light' }, 154 | { text: 'Dark', value: 'dark' }, 155 | ] 156 | 157 | const fileTypeOptions: DropdownOption[] = [ 158 | { text: 'PNG', value: 'png' }, 159 | { text: 'JPEG', value: 'jpeg' }, 160 | ] 161 | 162 | const fontFamilyOptions: DropdownOption[] = [ 163 | { text: 'Source Sans Pro', value: 'source-sans-pro'}, 164 | { text: 'Roboto Condensed', value: 'roboto-condensed'}, 165 | ] 166 | 167 | const fontSizeOptions: DropdownOption[] = Array.from({ length: 10 }) 168 | .map((_, i) => i * 25) 169 | .filter((n) => n > 0) 170 | .map((n) => ({ text: n + 'px', value: n + 'px' })) 171 | 172 | const markdownOptions: DropdownOption[] = [ 173 | { text: 'Plain Text', value: '0' }, 174 | { text: 'Markdown', value: '1' }, 175 | ] 176 | 177 | const imageLightOptions: DropdownOption[] = [ 178 | { 179 | text: 'main logomark', 180 | value: 'https://cc-vocabulary.netlify.app/logos/cc/logomark.svg#logomark', 181 | }, 182 | { 183 | text: 'main lettermark', 184 | value: 185 | 'https://cc-vocabulary.netlify.app/logos/cc/lettermark.svg#lettermark', 186 | }, 187 | { 188 | text: 'letterheart', 189 | value: 190 | 'https://cc-vocabulary.netlify.app/logos/cc/lettermark.svg#letterheart', 191 | }, 192 | { 193 | text: 'certificates', 194 | value: 195 | 'https://cc-vocabulary.netlify.app/logos/products/certificates.svg#certificates', 196 | }, 197 | { 198 | text: 'chooser', 199 | value: 200 | 'https://cc-vocabulary.netlify.app/logos/products/chooser.svg#chooser', 201 | }, 202 | { 203 | text: 'globalnetwork', 204 | value: 205 | 'https://cc-vocabulary.netlify.app/logos/products/global_network.svg#globalnetwork', 206 | }, 207 | { 208 | text: 'globalsummit', 209 | value: 210 | 'https://cc-vocabulary.netlify.app/logos/products/global_summit.svg#globalsummit', 211 | }, 212 | { 213 | text: 'legaldatabase', 214 | value: 215 | 'https://cc-vocabulary.netlify.app/logos/products/legal_database.svg#legaldatabase', 216 | }, 217 | { 218 | text: 'opensource', 219 | value: 220 | 'https://cc-vocabulary.netlify.app/logos/products/open_source.svg#opensource', 221 | }, 222 | { 223 | text: 'search', 224 | value: 'https://cc-vocabulary.netlify.app/logos/products/search.svg#search', 225 | }, 226 | { 227 | text: 'stateofthecommons', 228 | value: 229 | 'https://cc-vocabulary.netlify.app/logos/products/state_of_the_commons.svg#stateofthecommons', 230 | }, 231 | { 232 | text: 'vocabulary', 233 | value: 234 | 'https://cc-vocabulary.netlify.app/logos/products/vocabulary.svg#vocabulary', 235 | }, 236 | ] 237 | 238 | const widthOptions = [ 239 | { text: 'width', value: 'auto' }, 240 | { text: '50', value: '50' }, 241 | { text: '100', value: '100' }, 242 | { text: '150', value: '150' }, 243 | { text: '200', value: '200' }, 244 | { text: '250', value: '250' }, 245 | { text: '300', value: '300' }, 246 | { text: '350', value: '350' }, 247 | ] 248 | 249 | const heightOptions = [ 250 | { text: 'height', value: 'auto' }, 251 | { text: '50', value: '50' }, 252 | { text: '100', value: '100' }, 253 | { text: '150', value: '150' }, 254 | { text: '200', value: '200' }, 255 | { text: '250', value: '250' }, 256 | { text: '300', value: '300' }, 257 | { text: '350', value: '350' }, 258 | ] 259 | 260 | interface AppState extends ParsedRequest { 261 | loading: boolean 262 | showToast: boolean 263 | messageToast: string 264 | selectedImageIndex: number 265 | widths: string[] 266 | heights: string[] 267 | overrideUrl: URL | null 268 | } 269 | 270 | type SetState = (state: Partial) => void 271 | 272 | const App = (_: any, state: AppState, setState: SetState) => { 273 | const setLoadingState = (newState: Partial) => { 274 | window.clearTimeout(timeout) 275 | if (state.overrideUrl && state.overrideUrl !== newState.overrideUrl) { 276 | newState.overrideUrl = state.overrideUrl 277 | } 278 | if (newState.overrideUrl) { 279 | timeout = window.setTimeout(() => setState({ overrideUrl: null }), 200) 280 | } 281 | 282 | setState({ ...newState, loading: true }) 283 | } 284 | 285 | const copyImageURL = (e: Event) => { 286 | e.preventDefault() 287 | const success = copee.toClipboard(url.href) 288 | if (success) { 289 | setState({ 290 | showToast: true, 291 | messageToast: 'Copied image URL to clipboard', 292 | }) 293 | setTimeout(() => setState({ showToast: false }), 3000) 294 | } else { 295 | window.open(url.href, '_blank') 296 | } 297 | return false 298 | } 299 | 300 | const copyMetaTag = (e: Event) => { 301 | e.preventDefault() 302 | const html = `` 303 | const success = copee.toClipboard(html) 304 | if (success) { 305 | setState({ 306 | showToast: true, 307 | messageToast: 'Copied Meta tag to clipboard', 308 | }) 309 | setTimeout(() => setState({ showToast: false }), 3000) 310 | } 311 | return false 312 | } 313 | 314 | const { 315 | fileType = 'png', 316 | fontFamily = 'source-sans-pro', 317 | fontSize = '100px', 318 | theme = 'light', 319 | md = true, 320 | text = 'Introducing **New** Feature', 321 | images = [imageLightOptions[0].value], 322 | widths = [], 323 | heights = [], 324 | showToast = false, 325 | messageToast = '', 326 | loading = true, 327 | selectedImageIndex = 0, 328 | overrideUrl = null, 329 | } = state 330 | 331 | const mdValue = md ? '1' : '0' 332 | const imageOptions = imageLightOptions 333 | const url = new URL(window.location.origin) 334 | url.pathname = `${encodeURIComponent(text)}.${fileType}` 335 | url.searchParams.append('theme', theme) 336 | url.searchParams.append('md', mdValue) 337 | url.searchParams.append('fontFamily', fontFamily) 338 | url.searchParams.append('fontSize', fontSize) 339 | 340 | url.searchParams.append( 341 | 'imageObj', 342 | JSON.stringify({ images: images, widths: widths, heights: heights }) 343 | ) 344 | 345 | return H( 346 | 'div', 347 | { className: 'columns is-vcentered is-variable is-8' }, 348 | H( 349 | 'div', 350 | { className: 'column is-half margin-top-large margin-bottom-large' }, 351 | H( 352 | 'div', 353 | H(Field, { 354 | label: 'Theme', 355 | input: H(Dropdown, { 356 | options: themeOptions, 357 | value: theme, 358 | onchange: (val: Theme) => { 359 | const options = imageLightOptions 360 | let clone = [...images] 361 | clone[0] = options[selectedImageIndex].value 362 | setLoadingState({ theme: val, images: clone }) 363 | }, 364 | }), 365 | }), 366 | H(Field, { 367 | label: 'File Type', 368 | input: H(Dropdown, { 369 | options: fileTypeOptions, 370 | value: fileType, 371 | onchange: (val: FileType) => setLoadingState({ fileType: val }), 372 | }), 373 | }), 374 | H(Field, { 375 | label: 'Font Family', 376 | input: H(Dropdown, { 377 | options: fontFamilyOptions, 378 | value: fontFamily, 379 | onchange: (val: string) => setLoadingState({ fontFamily: val }), 380 | }), 381 | }), 382 | H(Field, { 383 | label: 'Font Size', 384 | input: H(Dropdown, { 385 | options: fontSizeOptions, 386 | value: fontSize, 387 | onchange: (val: string) => setLoadingState({ fontSize: val }), 388 | }), 389 | }), 390 | H(Field, { 391 | label: 'Text Type', 392 | input: H(Dropdown, { 393 | options: markdownOptions, 394 | value: mdValue, 395 | onchange: (val: string) => setLoadingState({ md: val === '1' }), 396 | }), 397 | }), 398 | H(Field, { 399 | label: 'Text Input', 400 | input: H(TextInput, { 401 | value: text, 402 | oninput: (val: string) => { 403 | console.log('oninput ' + val) 404 | setLoadingState({ text: val, overrideUrl: url }) 405 | }, 406 | }), 407 | }), 408 | H(Field, { 409 | label: 'Image 1', 410 | input: H( 411 | 'div', 412 | H(Dropdown, { 413 | options: imageOptions, 414 | value: imageOptions[selectedImageIndex].value, 415 | onchange: (val: string) => { 416 | let clone = [...images] 417 | clone[0] = val 418 | const selected = imageOptions.map((o) => o.value).indexOf(val) 419 | setLoadingState({ images: clone, selectedImageIndex: selected }) 420 | }, 421 | }), 422 | H( 423 | 'div', 424 | { className: 'field-flex' }, 425 | H(Dropdown, { 426 | options: widthOptions, 427 | value: widths[0], 428 | small: true, 429 | onchange: (val: string) => { 430 | let clone = [...widths] 431 | clone[0] = val 432 | setLoadingState({ widths: clone }) 433 | }, 434 | }), 435 | H(Dropdown, { 436 | options: heightOptions, 437 | value: heights[0], 438 | small: true, 439 | onchange: (val: string) => { 440 | let clone = [...heights] 441 | clone[0] = val 442 | setLoadingState({ heights: clone }) 443 | }, 444 | }) 445 | ) 446 | ), 447 | }), 448 | ...images.slice(1).map((image, i) => 449 | H(Field, { 450 | label: `Image ${i + 2}`, 451 | input: H( 452 | 'div', 453 | H(TextInput, { 454 | value: image, 455 | oninput: (val: string) => { 456 | let clone = [...images] 457 | clone[i + 1] = val 458 | setLoadingState({ images: clone, overrideUrl: url }) 459 | }, 460 | }), 461 | H( 462 | 'div', 463 | { className: 'field-flex' }, 464 | H(Dropdown, { 465 | options: widthOptions, 466 | value: widths[i + 1], 467 | small: true, 468 | onchange: (val: string) => { 469 | let clone = [...widths] 470 | clone[i + 1] = val 471 | setLoadingState({ widths: clone }) 472 | }, 473 | }), 474 | H(Dropdown, { 475 | options: heightOptions, 476 | value: heights[i + 1], 477 | small: true, 478 | onchange: (val: string) => { 479 | let clone = [...heights] 480 | clone[i + 1] = val 481 | setLoadingState({ heights: clone }) 482 | }, 483 | }) 484 | ) 485 | ), 486 | }) 487 | ), 488 | H(Field, { 489 | label: `Image ${images.length + 1}`, 490 | input: H(Button, { 491 | label: `Add Image ${images.length + 1}`, 492 | onclick: () => { 493 | const nextImage = 494 | images.length === 1 495 | ? 'https://cdn.jsdelivr.net/gh/remojansen/logo.ts@master/ts.svg' 496 | : '' 497 | setLoadingState({ images: [...images, nextImage] }) 498 | }, 499 | }), 500 | }), 501 | H(ActionButtons, { 502 | href: url.href, 503 | copyImage: copyImageURL, 504 | copyMetaTag: copyMetaTag, 505 | }) 506 | ) 507 | ), 508 | H( 509 | 'div', 510 | { className: 'column is-half margin-top-large' }, 511 | H(ImagePreview, { 512 | src: overrideUrl ? overrideUrl.href : url.href, 513 | loading: loading, 514 | onload: () => setState({ loading: false }), 515 | onerror: () => { 516 | setState({ showToast: true, messageToast: 'Oops, an error occurred' }) 517 | setTimeout(() => setState({ showToast: false }), 2000) 518 | }, 519 | onclick: (e: Event) => copyImageURL(e), 520 | }) 521 | ), 522 | H(Toast, { 523 | message: messageToast, 524 | show: showToast, 525 | }) 526 | ) 527 | } 528 | 529 | R(H(App), document.getElementById('app')) 530 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../api/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "outDir": "../public/dist" 6 | }, 7 | "include": [ 8 | "./" 9 | ] 10 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/marked@0.7.3": 6 | version "0.7.3" 7 | resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.7.3.tgz#3859f6fea52a2b73f42283018bd34b03f3c4fb3f" 8 | integrity sha512-WXdEKuT3azHxLTThd5dwnpLt2Q9QiC8iKj09KZRtVqro3pX8hhY+GbD8FZOae6SBBEJ22yKJn3c7ejL0aucAcA== 9 | 10 | "@types/mime-types@^2.1.0": 11 | version "2.1.0" 12 | resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" 13 | integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= 14 | 15 | "@types/node@*": 16 | version "11.10.4" 17 | resolved "https://registry.yarnpkg.com/@types/node/-/node-11.10.4.tgz#3f5fc4f0f322805f009e00ab35a2ff3d6b778e42" 18 | integrity sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg== 19 | 20 | "@types/puppeteer-core@2.0.0": 21 | version "2.0.0" 22 | resolved "https://registry.yarnpkg.com/@types/puppeteer-core/-/puppeteer-core-2.0.0.tgz#3b7fbbac53d56b566f5ef096116e1d60d504aa45" 23 | integrity sha512-JvoEb7KgEkUet009ZDrtpUER3hheXoHgQByuYpJZ5WWT7LWwMH+0NTqGQXGgoOKzs+G5NA1T4DZwXK79Bhnejw== 24 | dependencies: 25 | "@types/puppeteer" "*" 26 | 27 | "@types/puppeteer@*": 28 | version "1.12.1" 29 | resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.1.tgz#f61f9e0da45e36cdbbeab3b088995a8a2cad47a4" 30 | integrity sha512-6qpe7XXM93iWh8quEP8Ay516Vmfc2r+ZAxFH3Mt6fx3vzmZz+4Q+hYxc9PxeEIXJhWLAAPYAgAiM/vLHEUwGpw== 31 | dependencies: 32 | "@types/node" "*" 33 | 34 | "@types/puppeteer@2.0.1": 35 | version "2.0.1" 36 | resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-2.0.1.tgz#83a1d7f0a1c2e0edbbb488b4d8fb54b14ec9d455" 37 | integrity sha512-G8vEyU83Bios+dzs+DZGpAirDmMqRhfFBJCkFrg+A5+6n5EPPHxwBLImJto3qjh0mrBXbLBCyuahhhtTrAfR5g== 38 | dependencies: 39 | "@types/node" "*" 40 | 41 | agent-base@5: 42 | version "5.1.1" 43 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" 44 | integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== 45 | 46 | async-limiter@~1.0.0: 47 | version "1.0.0" 48 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 49 | integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== 50 | 51 | balanced-match@^1.0.0: 52 | version "1.0.0" 53 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 54 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 55 | 56 | bl@^3.0.0: 57 | version "3.0.0" 58 | resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" 59 | integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== 60 | dependencies: 61 | readable-stream "^3.0.1" 62 | 63 | brace-expansion@^1.1.7: 64 | version "1.1.11" 65 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 66 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 67 | dependencies: 68 | balanced-match "^1.0.0" 69 | concat-map "0.0.1" 70 | 71 | buffer-from@^1.0.0: 72 | version "1.1.1" 73 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 74 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 75 | 76 | chownr@^1.1.1: 77 | version "1.1.3" 78 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" 79 | integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== 80 | 81 | chrome-aws-lambda@2.1.1: 82 | version "2.1.1" 83 | resolved "https://registry.yarnpkg.com/chrome-aws-lambda/-/chrome-aws-lambda-2.1.1.tgz#2aeb0c97fb67e908d06dc8d92d92c7d4fb58467c" 84 | integrity sha512-Wer2QuygxsCov5bM2+8CLa6qYpNsc5AxYTlgTne00aFoxFP491LGJRxOQtGnYtsJP6UG4pB0SfrwTyPnLys1Lw== 85 | dependencies: 86 | lambdafs "^1.3.0" 87 | 88 | concat-map@0.0.1: 89 | version "0.0.1" 90 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 91 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 92 | 93 | concat-stream@1.6.2: 94 | version "1.6.2" 95 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 96 | integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 97 | dependencies: 98 | buffer-from "^1.0.0" 99 | inherits "^2.0.3" 100 | readable-stream "^2.2.2" 101 | typedarray "^0.0.6" 102 | 103 | core-util-is@~1.0.0: 104 | version "1.0.2" 105 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 106 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 107 | 108 | debug@2.6.9: 109 | version "2.6.9" 110 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 111 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 112 | dependencies: 113 | ms "2.0.0" 114 | 115 | debug@4, debug@^4.1.0: 116 | version "4.1.1" 117 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 118 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 119 | dependencies: 120 | ms "^2.1.1" 121 | 122 | end-of-stream@^1.1.0, end-of-stream@^1.4.1: 123 | version "1.4.4" 124 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 125 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 126 | dependencies: 127 | once "^1.4.0" 128 | 129 | extract-zip@^1.6.6: 130 | version "1.6.7" 131 | resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" 132 | integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= 133 | dependencies: 134 | concat-stream "1.6.2" 135 | debug "2.6.9" 136 | mkdirp "0.5.1" 137 | yauzl "2.4.1" 138 | 139 | fd-slicer@~1.0.1: 140 | version "1.0.1" 141 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" 142 | integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= 143 | dependencies: 144 | pend "~1.2.0" 145 | 146 | fs-constants@^1.0.0: 147 | version "1.0.0" 148 | resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 149 | integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 150 | 151 | fs-extra@^8.0.1: 152 | version "8.1.0" 153 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 154 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 155 | dependencies: 156 | graceful-fs "^4.2.0" 157 | jsonfile "^4.0.0" 158 | universalify "^0.1.0" 159 | 160 | fs.realpath@^1.0.0: 161 | version "1.0.0" 162 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 163 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 164 | 165 | glob@^7.1.3: 166 | version "7.1.3" 167 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 168 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 169 | dependencies: 170 | fs.realpath "^1.0.0" 171 | inflight "^1.0.4" 172 | inherits "2" 173 | minimatch "^3.0.4" 174 | once "^1.3.0" 175 | path-is-absolute "^1.0.0" 176 | 177 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 178 | version "4.2.3" 179 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 180 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 181 | 182 | https-proxy-agent@^4.0.0: 183 | version "4.0.0" 184 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" 185 | integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== 186 | dependencies: 187 | agent-base "5" 188 | debug "4" 189 | 190 | inflight@^1.0.4: 191 | version "1.0.6" 192 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 193 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 194 | dependencies: 195 | once "^1.3.0" 196 | wrappy "1" 197 | 198 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 199 | version "2.0.3" 200 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 201 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 202 | 203 | isarray@~1.0.0: 204 | version "1.0.0" 205 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 206 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 207 | 208 | jsonfile@^4.0.0: 209 | version "4.0.0" 210 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 211 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 212 | optionalDependencies: 213 | graceful-fs "^4.1.6" 214 | 215 | jsonfile@^5.0.0: 216 | version "5.0.0" 217 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922" 218 | integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w== 219 | dependencies: 220 | universalify "^0.1.2" 221 | optionalDependencies: 222 | graceful-fs "^4.1.6" 223 | 224 | lambdafs@^1.3.0: 225 | version "1.3.0" 226 | resolved "https://registry.yarnpkg.com/lambdafs/-/lambdafs-1.3.0.tgz#7e369cedc9a09623bb365fa99a1113c2ab2fc7ae" 227 | integrity sha512-HqRPmEgtkTW4sCYDUjTEuTGkjCHuLvtZU8iM8GkhD7SpjW4AJJbBk86YU4K43sWGuW5Vmzp1lVCx4ab/kJsuBw== 228 | dependencies: 229 | tar-fs "^2.0.0" 230 | 231 | marked@0.8.2: 232 | version "0.8.2" 233 | resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" 234 | integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== 235 | 236 | mime-db@1.43.0: 237 | version "1.43.0" 238 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 239 | integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== 240 | 241 | mime-types@^2.1.25: 242 | version "2.1.26" 243 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" 244 | integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== 245 | dependencies: 246 | mime-db "1.43.0" 247 | 248 | mime@^2.0.3: 249 | version "2.4.0" 250 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" 251 | integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== 252 | 253 | minimatch@^3.0.4: 254 | version "3.0.4" 255 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 256 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 257 | dependencies: 258 | brace-expansion "^1.1.7" 259 | 260 | minimist@0.0.8: 261 | version "0.0.8" 262 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 263 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 264 | 265 | mkdirp@0.5.1, mkdirp@^0.5.1: 266 | version "0.5.1" 267 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 268 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 269 | dependencies: 270 | minimist "0.0.8" 271 | 272 | ms@2.0.0: 273 | version "2.0.0" 274 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 275 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 276 | 277 | ms@^2.1.1: 278 | version "2.1.1" 279 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 280 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 281 | 282 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 283 | version "1.4.0" 284 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 285 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 286 | dependencies: 287 | wrappy "1" 288 | 289 | path-is-absolute@^1.0.0: 290 | version "1.0.1" 291 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 292 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 293 | 294 | pend@~1.2.0: 295 | version "1.2.0" 296 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 297 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 298 | 299 | process-nextick-args@~2.0.0: 300 | version "2.0.0" 301 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 302 | integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== 303 | 304 | progress@^2.0.1: 305 | version "2.0.3" 306 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 307 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 308 | 309 | proxy-from-env@^1.0.0: 310 | version "1.0.0" 311 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" 312 | integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= 313 | 314 | pump@^3.0.0: 315 | version "3.0.0" 316 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 317 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 318 | dependencies: 319 | end-of-stream "^1.1.0" 320 | once "^1.3.1" 321 | 322 | puppeteer-core@2.1.1: 323 | version "2.1.1" 324 | resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-2.1.1.tgz#e9b3fbc1237b4f66e25999832229e9db3e0b90ed" 325 | integrity sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w== 326 | dependencies: 327 | "@types/mime-types" "^2.1.0" 328 | debug "^4.1.0" 329 | extract-zip "^1.6.6" 330 | https-proxy-agent "^4.0.0" 331 | mime "^2.0.3" 332 | mime-types "^2.1.25" 333 | progress "^2.0.1" 334 | proxy-from-env "^1.0.0" 335 | rimraf "^2.6.1" 336 | ws "^6.1.0" 337 | 338 | readable-stream@^2.2.2: 339 | version "2.3.6" 340 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 341 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== 342 | dependencies: 343 | core-util-is "~1.0.0" 344 | inherits "~2.0.3" 345 | isarray "~1.0.0" 346 | process-nextick-args "~2.0.0" 347 | safe-buffer "~5.1.1" 348 | string_decoder "~1.1.1" 349 | util-deprecate "~1.0.1" 350 | 351 | readable-stream@^3.0.1, readable-stream@^3.1.1: 352 | version "3.4.0" 353 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" 354 | integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== 355 | dependencies: 356 | inherits "^2.0.3" 357 | string_decoder "^1.1.1" 358 | util-deprecate "^1.0.1" 359 | 360 | rimraf@^2.6.1: 361 | version "2.6.3" 362 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 363 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 364 | dependencies: 365 | glob "^7.1.3" 366 | 367 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 368 | version "5.1.2" 369 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 370 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 371 | 372 | safe-buffer@~5.2.0: 373 | version "5.2.0" 374 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 375 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 376 | 377 | string_decoder@^1.1.1: 378 | version "1.3.0" 379 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 380 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 381 | dependencies: 382 | safe-buffer "~5.2.0" 383 | 384 | string_decoder@~1.1.1: 385 | version "1.1.1" 386 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 387 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 388 | dependencies: 389 | safe-buffer "~5.1.0" 390 | 391 | tar-fs@^2.0.0: 392 | version "2.0.0" 393 | resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" 394 | integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== 395 | dependencies: 396 | chownr "^1.1.1" 397 | mkdirp "^0.5.1" 398 | pump "^3.0.0" 399 | tar-stream "^2.0.0" 400 | 401 | tar-stream@^2.0.0: 402 | version "2.1.0" 403 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" 404 | integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== 405 | dependencies: 406 | bl "^3.0.0" 407 | end-of-stream "^1.4.1" 408 | fs-constants "^1.0.0" 409 | inherits "^2.0.3" 410 | readable-stream "^3.1.1" 411 | 412 | twemoji-parser@12.1.3: 413 | version "12.1.3" 414 | resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-12.1.3.tgz#916c0153e77bd5f1011e7a99cbeacf52e43c9371" 415 | integrity sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q== 416 | 417 | twemoji@12.1.5: 418 | version "12.1.5" 419 | resolved "https://registry.yarnpkg.com/twemoji/-/twemoji-12.1.5.tgz#a961fb65a1afcb1f729ad7e59391f9fe969820b9" 420 | integrity sha512-B0PBVy5xomwb1M/WZxf/IqPZfnoIYy1skXnlHjMwLwTNfZ9ljh8VgWQktAPcJXu8080WoEh6YwQGPVhDVqvrVQ== 421 | dependencies: 422 | fs-extra "^8.0.1" 423 | jsonfile "^5.0.0" 424 | twemoji-parser "12.1.3" 425 | universalify "^0.1.2" 426 | 427 | typedarray@^0.0.6: 428 | version "0.0.6" 429 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 430 | integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 431 | 432 | typescript@3.8.3: 433 | version "3.8.3" 434 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" 435 | integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== 436 | 437 | universalify@^0.1.0, universalify@^0.1.2: 438 | version "0.1.2" 439 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 440 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 441 | 442 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 443 | version "1.0.2" 444 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 445 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 446 | 447 | wrappy@1: 448 | version "1.0.2" 449 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 450 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 451 | 452 | ws@^6.1.0: 453 | version "6.1.4" 454 | resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" 455 | integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== 456 | dependencies: 457 | async-limiter "~1.0.0" 458 | 459 | yauzl@2.4.1: 460 | version "2.4.1" 461 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" 462 | integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= 463 | dependencies: 464 | fd-slicer "~1.0.1" 465 | --------------------------------------------------------------------------------