Add dark theme
This commit is contained in:
parent
6a92ef3781
commit
3287736872
|
@ -1,4 +1,6 @@
|
||||||
import { toClipboard } from 'https://cdn.jsdelivr.net/npm/copee@1.0.6/dist/copee.mjs';
|
import { toClipboard } from 'https://cdn.jsdelivr.net/npm/copee@1.0.6/dist/copee.mjs';
|
||||||
|
const nowBlack = 'https://assets.zeit.co/image/upload/front/assets/design/now-black.svg';
|
||||||
|
const nowWhite = 'https://assets.zeit.co/image/upload/front/assets/design/now-white.svg';
|
||||||
|
|
||||||
function debounce(func, wait) {
|
function debounce(func, wait) {
|
||||||
var timeout;
|
var timeout;
|
||||||
|
@ -86,6 +88,11 @@ const Toast = ({ show, message }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const themeOptions = [
|
||||||
|
{ text: 'Light', value: 'light' },
|
||||||
|
{ text: 'Dark', value: 'dark' },
|
||||||
|
];
|
||||||
|
|
||||||
const fileTypeOptions = [
|
const fileTypeOptions = [
|
||||||
{ text: 'PNG', value: 'png' },
|
{ text: 'PNG', value: 'png' },
|
||||||
{ text: 'JPEG', value: 'jpeg' },
|
{ text: 'JPEG', value: 'jpeg' },
|
||||||
|
@ -103,19 +110,24 @@ const markdownOptions = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const App = (props, state, setState) => {
|
const App = (props, state, setState) => {
|
||||||
const setLoadingState = (newState) => setState({ ...newState, loading: true });
|
const setLoadingState = (newState) => {
|
||||||
|
|
||||||
|
setState({ ...newState, loading: true });
|
||||||
|
};
|
||||||
const {
|
const {
|
||||||
fileType = 'png',
|
fileType = 'png',
|
||||||
fontSize = '75px',
|
fontSize = '75px',
|
||||||
|
theme = 'light',
|
||||||
md = '1',
|
md = '1',
|
||||||
text = '**Hello** World',
|
text = '**Hello** World',
|
||||||
images=['https://assets.zeit.co/image/upload/front/assets/design/now-black.svg'],
|
images=[nowBlack],
|
||||||
showToast = false,
|
showToast = false,
|
||||||
messageToast = '',
|
messageToast = '',
|
||||||
loading = true
|
loading = true
|
||||||
} = state;
|
} = state;
|
||||||
const url = new URL(window.location.hostname === 'localhost' ? 'https://og-image.now.sh' : window.location.origin);
|
const url = new URL(window.location.hostname === 'localhost' ? 'https://og-image.now.sh' : window.location.origin);
|
||||||
url.pathname = `${encodeURIComponent(text)}.${fileType}`;
|
url.pathname = `${encodeURIComponent(text)}.${fileType}`;
|
||||||
|
url.searchParams.append('theme', theme);
|
||||||
url.searchParams.append('md', md);
|
url.searchParams.append('md', md);
|
||||||
url.searchParams.append('fontSize', fontSize);
|
url.searchParams.append('fontSize', fontSize);
|
||||||
for (let image of images) {
|
for (let image of images) {
|
||||||
|
@ -127,6 +139,21 @@ const App = (props, state, setState) => {
|
||||||
H('div',
|
H('div',
|
||||||
{ className: 'pull-left' },
|
{ className: 'pull-left' },
|
||||||
H('div',
|
H('div',
|
||||||
|
H(Field, {
|
||||||
|
label: 'Theme',
|
||||||
|
input: H(Dropdown, {
|
||||||
|
options: themeOptions,
|
||||||
|
value: theme,
|
||||||
|
onchange: val => {
|
||||||
|
if (images[0] === nowBlack && val === 'dark') {
|
||||||
|
images[0] = nowWhite;
|
||||||
|
} else if (images[0] === nowWhite && val === 'light') {
|
||||||
|
images[0] = nowBlack;
|
||||||
|
}
|
||||||
|
setLoadingState({ theme: val, images: [...images] });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
H(Field, {
|
H(Field, {
|
||||||
label: 'File Type',
|
label: 'File Type',
|
||||||
input: H(Dropdown, { options: fileTypeOptions, value: fileType, onchange: val => setLoadingState({fileType: val}) })
|
input: H(Dropdown, { options: fileTypeOptions, value: fileType, onchange: val => setLoadingState({fileType: val}) })
|
||||||
|
|
|
@ -4,10 +4,15 @@ import { parse } from 'url';
|
||||||
export function parseRequest(req: IncomingMessage) {
|
export function parseRequest(req: IncomingMessage) {
|
||||||
console.log('HTTP ' + req.url);
|
console.log('HTTP ' + req.url);
|
||||||
const { pathname = '/', query = {} } = parse(req.url || '', true);
|
const { pathname = '/', query = {} } = parse(req.url || '', true);
|
||||||
const { fontSize, images, md } = query;
|
const { fontSize, images, theme, md } = query;
|
||||||
if (Array.isArray(fontSize)) {
|
if (Array.isArray(fontSize)) {
|
||||||
throw new Error('Expected a single fontSize');
|
throw new Error('Expected a single fontSize');
|
||||||
}
|
}
|
||||||
|
if (Array.isArray(theme)) {
|
||||||
|
throw new Error('Expected a single theme');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const arr = pathname.slice(1).split('.');
|
const arr = pathname.slice(1).split('.');
|
||||||
let extension = '';
|
let extension = '';
|
||||||
let text = '';
|
let text = '';
|
||||||
|
@ -23,11 +28,20 @@ export function parseRequest(req: IncomingMessage) {
|
||||||
const parsedRequest: ParsedRequest = {
|
const parsedRequest: ParsedRequest = {
|
||||||
type: extension === 'jpeg' ? extension : 'png',
|
type: extension === 'jpeg' ? extension : 'png',
|
||||||
text: decodeURIComponent(text),
|
text: decodeURIComponent(text),
|
||||||
|
theme: theme === 'dark' ? 'dark' : 'light',
|
||||||
md: md === '1' || md === 'true',
|
md: md === '1' || md === 'true',
|
||||||
fontSize: fontSize || '75px',
|
fontSize: fontSize || '75px',
|
||||||
images: Array.isArray(images) && images.length > 0
|
images: Array.isArray(images) ? images : [images],
|
||||||
? images
|
|
||||||
: ['https://assets.zeit.co/image/upload/front/assets/design/now-black.svg'],
|
|
||||||
};
|
};
|
||||||
|
parsedRequest.images = getDefaultImages(parsedRequest.images, parsedRequest.theme);
|
||||||
return parsedRequest;
|
return parsedRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultImages(images: string[], theme: Theme) {
|
||||||
|
if (images.length > 0 && /^http/.test(images[0])) {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
return theme === 'light'
|
||||||
|
? ['https://assets.zeit.co/image/upload/front/assets/design/now-black.svg']
|
||||||
|
: ['https://assets.zeit.co/image/upload/front/assets/design/now-white.svg'];
|
||||||
|
}
|
|
@ -8,6 +8,6 @@ const entityMap: { [key: string]: string } = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function sanitizeHtml(html: string) {
|
export function sanitizeHtml(html: string) {
|
||||||
return html.replace(/[&<>"'\/]/g, key => entityMap[key]);
|
return String(html).replace(/[&<>"'\/]/g, key => entityMap[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,15 @@ import { readFileSync } from 'fs';
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
import { sanitizeHtml } from './sanitizer';
|
import { sanitizeHtml } from './sanitizer';
|
||||||
|
|
||||||
function getCss(fontSize: string) {
|
function getCss(theme: string, fontSize: string) {
|
||||||
const regular = readFileSync(`${__dirname}/../fonts/Inter-UI-Regular.woff2`).toString('base64');
|
const regular = readFileSync(`${__dirname}/../fonts/Inter-UI-Regular.woff2`).toString('base64');
|
||||||
const bold = readFileSync(`${__dirname}/../fonts/Inter-UI-Bold.woff2`).toString('base64');
|
const bold = readFileSync(`${__dirname}/../fonts/Inter-UI-Bold.woff2`).toString('base64');
|
||||||
|
let background = 'white';
|
||||||
|
let foreground = 'black';
|
||||||
|
|
||||||
|
if (theme === 'dark') {
|
||||||
|
[foreground, background] = [background, foreground];
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Inter UI';
|
font-family: 'Inter UI';
|
||||||
|
@ -22,7 +28,7 @@ function getCss(fontSize: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: white;
|
background: ${background};
|
||||||
background-image: radial-gradient(lightgray 5%, transparent 0);
|
background-image: radial-gradient(lightgray 5%, transparent 0);
|
||||||
background-size: 60px 60px;
|
background-size: 60px 60px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -59,18 +65,19 @@ function getCss(fontSize: string) {
|
||||||
font-family: 'Inter UI', sans-serif;
|
font-family: 'Inter UI', sans-serif;
|
||||||
font-size: ${sanitizeHtml(fontSize)};
|
font-size: ${sanitizeHtml(fontSize)};
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
color: ${foreground}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHtml(parsedReq: ParsedRequest) {
|
export function getHtml(parsedReq: ParsedRequest) {
|
||||||
const { text, md, fontSize, images } = parsedReq;
|
const { text, theme, md, fontSize, images } = parsedReq;
|
||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Generated Image</title>
|
<title>Generated Image</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
${getCss(fontSize)}
|
${getCss(theme, fontSize)}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
type ScreenshotType = 'png' | 'jpeg';
|
type ScreenshotType = 'png' | 'jpeg';
|
||||||
|
type Theme = 'light' | 'dark';
|
||||||
|
|
||||||
interface ParsedRequest {
|
interface ParsedRequest {
|
||||||
type: ScreenshotType;
|
type: ScreenshotType;
|
||||||
text: string;
|
text: string;
|
||||||
|
theme: Theme;
|
||||||
md: boolean;
|
md: boolean;
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
images: string[];
|
images: string[];
|
||||||
|
|
Loading…
Reference in New Issue