├── lib
├── custom
│ └── modes
│ │ ├── graphql.js
│ │ ├── elixir.js
│ │ └── apache.js
├── modes.js
├── util.js
├── routing.js
├── api.js
└── colors.js
├── .npmrc
├── .prettierrc
├── static
├── banner.png
├── favicon.ico
├── brand
│ ├── icon.png
│ ├── desktop.png
│ ├── logo-banner.png
│ ├── logo-square.png
│ └── logo-banner-transparent.png
├── presets
│ ├── 0.png
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── 8.png
│ └── 9.png
├── react-spinner.css
├── manifest.json
├── themes
│ ├── verminal.css
│ ├── night-owl.css
│ ├── nord.css
│ ├── one-dark.css
│ └── one-light.css
└── react-crop.css
├── cypress.json
├── next.config.js
├── .nowignore
├── components
├── ApiContext.js
├── svg
│ ├── Remove.js
│ ├── Arrows.js
│ ├── Controls.js
│ ├── Checkmark.js
│ ├── Copy.js
│ ├── Theme.js
│ ├── Settings.js
│ ├── Language.js
│ └── WindowThemes.js
├── FontSelect.js
├── SpinnerWrapper.js
├── WindowPointer.js
├── PhotoCredit.js
├── Toolbar.js
├── Overlay.js
├── TweetButton.js
├── Page.js
├── Header.js
├── Input.js
├── Toggle.js
├── ColorPicker.js
├── Footer.js
├── Button.js
├── Popout.js
├── ThemeSelect.js
├── Slider.js
├── WindowControls.js
├── RandomImage.js
├── Meta.js
├── ListSetting.js
├── style
│ ├── Typography.js
│ ├── Reset.js
│ └── Font.js
├── BackgroundSelect.js
├── Themes
│ ├── ThemeCreate.js
│ └── index.js
├── Presets.js
├── ExportMenu.js
├── Carbon.js
├── Dropdown.js
└── ImagePicker.js
├── .gitignore
├── cypress
├── fixtures
│ └── example.json
├── integration
│ ├── gist.js
│ ├── localStorage-spec.js
│ ├── basic.js
│ └── background-color-spec.js
├── plugins
│ └── index.js
└── support
│ ├── index.js
│ └── commands.js
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ └── bug_report.md
├── ranger.yml
├── CONTRIBUTING.md
└── CODE_OF_CONDUCT.md
├── .travis.yml
├── packages
└── gatsby-remark-embed-carbon
│ ├── package.json
│ ├── index.js
│ ├── yarn.lock
│ └── README.md
├── pages
├── _document.js
├── index.js
├── embed.js
└── about.js
├── release.js
├── now.json
├── .eslintrc.js
├── LICENSE
└── package.json
/lib/custom/modes/graphql.js:
--------------------------------------------------------------------------------
1 | import 'codemirror-graphql/mode'
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 | registry=https://registry.npmjs.org
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | singleQuote: true,
3 | printWidth: 100,
4 | semi: false
5 | }
6 |
--------------------------------------------------------------------------------
/static/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/banner.png
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/favicon.ico
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "https://carbon.now.sh/",
3 | "projectId": "t6c8sb"
4 | }
5 |
--------------------------------------------------------------------------------
/static/brand/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/brand/icon.png
--------------------------------------------------------------------------------
/static/presets/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/0.png
--------------------------------------------------------------------------------
/static/presets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/1.png
--------------------------------------------------------------------------------
/static/presets/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/2.png
--------------------------------------------------------------------------------
/static/presets/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/3.png
--------------------------------------------------------------------------------
/static/presets/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/4.png
--------------------------------------------------------------------------------
/static/presets/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/5.png
--------------------------------------------------------------------------------
/static/presets/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/6.png
--------------------------------------------------------------------------------
/static/presets/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/7.png
--------------------------------------------------------------------------------
/static/presets/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/8.png
--------------------------------------------------------------------------------
/static/presets/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/presets/9.png
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withOffline = require('next-offline')
2 |
3 | module.exports = withOffline()
4 |
--------------------------------------------------------------------------------
/static/brand/desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/brand/desktop.png
--------------------------------------------------------------------------------
/.nowignore:
--------------------------------------------------------------------------------
1 | .github
2 | LICENSE
3 | README.md
4 | bin
5 | node_modules
6 | cypress
7 | cypress.json
8 | docs
9 |
--------------------------------------------------------------------------------
/static/brand/logo-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/brand/logo-banner.png
--------------------------------------------------------------------------------
/static/brand/logo-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/brand/logo-square.png
--------------------------------------------------------------------------------
/components/ApiContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import api from '../lib/api'
3 |
4 | export default React.createContext(api)
5 |
--------------------------------------------------------------------------------
/lib/custom/modes/elixir.js:
--------------------------------------------------------------------------------
1 | // Require Codemirror elixir mode from npm modules and register it here
2 | import 'codemirror-mode-elixir'
3 |
--------------------------------------------------------------------------------
/static/brand/logo-banner-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/carbon/master/static/brand/logo-banner-transparent.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | .next
4 | out
5 | cypress/videos
6 | cypress/screenshots
7 | .idea
8 | .DS_Store
9 | packaged
10 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | ## Description
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10
4 | cache:
5 | yarn: true
6 | directories:
7 | - node_modules
8 | - ~/.cache
9 | install:
10 | - yarn
11 | script:
12 | - yarn lint
13 | - yarn build
14 | - yarn start & wait-on http://localhost:3000 && CYPRESS_CI=true yarn cy:run --config baseUrl=http://localhost:3000
15 |
--------------------------------------------------------------------------------
/components/svg/Remove.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({ color = 'black' }) => (
4 |
5 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/components/FontSelect.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ListSetting from './ListSetting'
3 | import { FONTS } from '../lib/constants'
4 |
5 | const Font = font => {font.name}
6 |
7 | function FontSelect(props) {
8 | return (
9 |
10 | {Font}
11 |
12 | )
13 | }
14 |
15 | export default FontSelect
16 |
--------------------------------------------------------------------------------
/packages/gatsby-remark-embed-carbon/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-remark-embed-carbon",
3 | "version": "0.0.1",
4 | "description": "Gatsby Remark Plugin for Embedding Carbon screenshots",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "keywords": [
8 | "gatsby",
9 | "gatsby-plugin",
10 | "remark",
11 | "carbon"
12 | ],
13 | "dependencies": {
14 | "unist-util-visit": "^1.1.3"
15 | },
16 | "scripts": {}
17 | }
18 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Head, Main, NextScript } from 'next/document'
3 |
4 | export default class extends Document {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/modes.js:
--------------------------------------------------------------------------------
1 | import { LANGUAGES } from './constants'
2 |
3 | const modes = LANGUAGES.filter(
4 | language =>
5 | language.mode &&
6 | language.mode !== 'auto' &&
7 | language.mode !== 'text' &&
8 | language.mode !== 'javascript'
9 | ).forEach(language =>
10 | language.custom
11 | ? require(`./custom/modes/${language.mode}`)
12 | : require(`codemirror/mode/${language.mode}/${language.mode}`)
13 | )
14 |
15 | export default modes
16 |
--------------------------------------------------------------------------------
/cypress/integration/gist.js:
--------------------------------------------------------------------------------
1 | /* global cy Cypress */
2 | import { editorVisible } from '../support'
3 | describe('Gist', () => {
4 | const test = Cypress.env('CI') ? it.skip : it
5 | test('Should pull text from the first Gist file', () => {
6 | cy.visit('/3208813b324d82a9ebd197e4b1c3bae8')
7 | editorVisible()
8 |
9 | cy.contains('Y-Combinator implemented in JavaScript')
10 | cy.get('#downshift-input-JavaScript').should('have.value', 'JavaScript')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/components/SpinnerWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Spinner from 'react-spinner'
3 |
4 | export default function SpinnerWrapper(props) {
5 | if (props.loading) {
6 | return (
7 |
8 |
9 |
17 |
18 | )
19 | }
20 |
21 | return props.children
22 | }
23 |
--------------------------------------------------------------------------------
/static/react-spinner.css:
--------------------------------------------------------------------------------
1 | .react-spinner{z-index: 999;position:relative;width:32px;height:32px;top:50%;left:50%}.react-spinner_bar{-webkit-animation:react-spinner_spin 1.2s linear infinite;-moz-animation:react-spinner_spin 1.2s linear infinite;animation:react-spinner_spin 1.2s linear infinite;border-radius:5px;background-color:#fff;position:absolute;width:20%;height:7.8%;top:-3.9%;left:-10%}@keyframes react-spinner_spin{0%{opacity:1}100%{opacity:.15}}@-moz-keyframes react-spinner_spin{0%{opacity:1}100%{opacity:.15}}@-webkit-keyframes react-spinner_spin{0%{opacity:1}100%{opacity:.15}}
2 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Carbon",
3 | "short_name": "Carbon",
4 | "background_color": "#121212",
5 | "theme_color": "#121212",
6 | "description": "Carbon is the easiest way to create and share beautiful images of your source code.",
7 | "display": "standalone",
8 | "start_url": "/",
9 | "icons": [
10 | {
11 | "src": "/static/brand/icon.png",
12 | "type": "image/png",
13 | "sizes": "448x448"
14 | },
15 | {
16 | "src": "/static/brand/desktop.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/components/WindowPointer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({ fromLeft, fromRight, color = '#fff' }) => (
4 |
22 | )
23 |
--------------------------------------------------------------------------------
/components/PhotoCredit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({ photographer }) => (
4 |
27 | )
28 |
--------------------------------------------------------------------------------
/components/svg/Arrows.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Up = ({ color = 'white' }) => (
4 |
5 |
6 |
7 | )
8 |
9 | const Down = ({ color = 'white' }) => (
10 |
11 |
12 |
13 | )
14 |
15 | const Right = ({ color = 'white' }) => (
16 |
17 |
18 |
19 | )
20 |
21 | export { Up, Down, Right }
22 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (/* on, config */) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/components/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Toolbar = props => (
4 |
5 | {props.children}
6 |
28 |
29 | )
30 |
31 | export default Toolbar
32 |
--------------------------------------------------------------------------------
/packages/gatsby-remark-embed-carbon/index.js:
--------------------------------------------------------------------------------
1 | const visit = require('unist-util-visit')
2 |
3 | module.exports = ({ markdownAST }, { width = '1024px', height = '473px' } = {}) => {
4 | visit(markdownAST, 'text', node => {
5 | const { value } = node
6 | const match = /https:\/\/carbon\.now\.sh\/{0,1}(.*)/.exec(value)
7 | if (match) {
8 | const url = match[1]
9 | node.type = 'html'
10 | node.value = ``
18 | }
19 | })
20 |
21 | return markdownAST
22 | }
23 |
--------------------------------------------------------------------------------
/release.js:
--------------------------------------------------------------------------------
1 | const url = require('url')
2 | const clipboardy = require('clipboardy')
3 |
4 | module.exports = async markdown => {
5 | const URL = process.env.URL || process.env.NOW_URL || clipboardy.readSync()
6 | const { host } = url.parse(URL)
7 |
8 | if (host) {
9 | let name
10 | try {
11 | name = require('./now.json').name
12 | } catch (e) {
13 | /* pass */
14 | }
15 |
16 | if (!name) {
17 | try {
18 | name = require('./package.json').name
19 | } catch (e) {
20 | /* pass */
21 | }
22 | }
23 |
24 | const prefix = name ? `\`${name}\`: ` : ''
25 |
26 | return [`${prefix}https://${host}`, '', markdown].join('\n')
27 | }
28 |
29 | // Use the available data to create a custom release
30 | return markdown
31 | }
32 |
--------------------------------------------------------------------------------
/components/Overlay.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Overlay = props => (
4 |
5 | {props.isOver ?
{props.title}
: null}
6 | {props.children}
7 |
28 |
29 | )
30 |
31 | export default Overlay
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ### Expected Behavior
9 |
10 | ### Actual Behavior
11 |
12 |
13 | Browser:
14 |
15 |
16 |
17 |
18 | Code Snippet (If Applicable)
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. See error
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Info (please complete the following information):**
23 | - OS [e.g. macOS, Linux, Windows, iOS]:
24 | - Browser [e.g. chrome, safari]:
25 | -
26 |
27 | Code Snippet (If Applicable)
28 |
29 |
30 |
--------------------------------------------------------------------------------
/components/TweetButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useAsyncCallback, useOnline as useOnlineListener } from '@dawnlabs/tacklebox'
3 |
4 | import ApiContext from './ApiContext'
5 | import Button from './Button'
6 |
7 | function TweetButton(props) {
8 | const api = React.useContext(ApiContext)
9 | const online = useOnlineListener()
10 | const [onClick, { loading }] = useAsyncCallback(props.onClick)
11 |
12 | if (!api || !api.tweet) {
13 | return null
14 | }
15 |
16 | if (!online) {
17 | return null
18 | }
19 |
20 | return (
21 |
30 | {loading ? 'Loading...' : 'Tweet'}
31 |
32 | )
33 | }
34 |
35 | export default React.memo(TweetButton)
36 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | /* global cy */
17 |
18 | export const editorVisible = () => cy.get('.editor').should('be.visible')
19 |
20 | // Import commands.js using ES2015 syntax:
21 | // import './commands'
22 |
23 | // Alternatively you can use CommonJS syntax:
24 | // require('./commands')
25 |
--------------------------------------------------------------------------------
/components/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Meta from './Meta'
3 | import Header from './Header'
4 | import Footer from './Footer'
5 |
6 | class Page extends React.Component {
7 | render() {
8 | const { children, enableHeroText } = this.props
9 | return (
10 |
11 |
12 |
13 | {children}
14 |
15 |
16 |
17 |
28 |
29 | )
30 | }
31 | }
32 |
33 | export default Page
34 |
--------------------------------------------------------------------------------
/components/svg/Controls.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const Controls = () => (
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export const ControlsBW = () => (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 |
--------------------------------------------------------------------------------
/components/svg/Checkmark.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({ width = 18, height = 18, color = '#FFFFFF' }) => (
4 |
11 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "carbon",
3 | "version": 2,
4 | "public": true,
5 | "builds": [{ "src": "package.json", "use": "@now/static-build" }],
6 | "routes": [
7 | { "src": "^/about(.*)", "dest": "/about" },
8 | { "src": "^/embed(.*)", "dest": "/embed" },
9 | { "src": "^/_next/(.*)", "dest": "/_next/$1" },
10 | {
11 | "src": "^/service-worker.js$",
12 | "dest": "/_next/service-worker.js",
13 | "headers": {
14 | "Service-Worker-Allowed": "/"
15 | }
16 | },
17 | {
18 | "src": "^/static/(.*)",
19 | "dest": "/static/$1",
20 | "headers": {
21 | "cache-control": "public, max-age=43200, immutable"
22 | }
23 | },
24 | { "src": "^/(.*)", "dest": "/index.html" }
25 | ],
26 | "build": {
27 | "env": {
28 | "NODE_ENV": "production",
29 | "API_URL": "@carbon_api_url"
30 | }
31 | },
32 | "github": {
33 | "autoAlias": false,
34 | "silent": true
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'babel-eslint',
3 | env: {
4 | browser: true,
5 | es6: true,
6 | node: true,
7 | mocha: true
8 | },
9 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended'],
10 | parserOptions: {
11 | ecmaFeatures: {
12 | experimentalObjectRestSpread: true,
13 | jsx: true
14 | },
15 | sourceType: 'module'
16 | },
17 | plugins: ['import', 'react', 'jsx-a11y', 'react-hooks'],
18 | rules: {
19 | 'import/no-unresolved': 'error',
20 | 'no-duplicate-imports': 'error',
21 | 'react/prop-types': 'off',
22 | 'react/display-name': 'off',
23 | 'react/jsx-uses-react': 'error',
24 | 'react/jsx-uses-vars': 'error',
25 | 'jsx-a11y/click-events-have-key-events': 'off',
26 | 'react-hooks/rules-of-hooks': 'error',
27 | 'react-hooks/exhaustive-deps': 'error'
28 | },
29 | settings: {
30 | react: {
31 | version: 'detect'
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/ranger.yml:
--------------------------------------------------------------------------------
1 | default:
2 | close:
3 | comment: ⚠️ This issue has been marked as a $LABEL. It will be closed in $DELAY...
4 | delay: 1 days
5 |
6 | labels:
7 | "merge when passing": merge
8 | greenkeeper: merge
9 | wontfix: close
10 | invalid: close
11 | stale: close
12 | duplicate: close
13 | "theme/language":
14 | action: close
15 | delay: 5 days
16 | comment: |
17 | This issue has been marked "$LABEL" and will be closed in $DELAY. As of Carbon `3.0.0`, the Carbon core team is no longer implementing new themes or languages (although the ability to create your own will be implemented soon!). However, PRs to add new ones will be happily accepted.
18 |
19 | Please see https://github.com/dawnlabs/carbon/blob/master/.github/CONTRIBUTING.md#a-note-on-adding-themeslanguages for notes on how to do so 👌.
20 |
21 | comments:
22 | - action: label
23 | pattern: /duplicate of/i
24 | labels:
25 | - duplicate
26 |
27 | merges:
28 | - action: delete_branch
29 |
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Logo from './svg/Logo'
3 |
4 | const Header = ({ enableHeroText }) => (
5 |
39 | )
40 |
41 | export default Header
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Dawn Labs
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 |
--------------------------------------------------------------------------------
/packages/gatsby-remark-embed-carbon/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | unist-util-is@^2.1.2:
6 | version "2.1.2"
7 | resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
8 | integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
9 |
10 | unist-util-visit-parents@^2.0.0:
11 | version "2.0.1"
12 | resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
13 | integrity sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==
14 | dependencies:
15 | unist-util-is "^2.1.2"
16 |
17 | unist-util-visit@^1.1.3:
18 | version "1.4.0"
19 | resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
20 | integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==
21 | dependencies:
22 | unist-util-visit-parents "^2.0.0"
23 |
--------------------------------------------------------------------------------
/components/svg/Copy.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVG_RATIO = 0.81
4 |
5 | const Copy = ({ size, color }) => {
6 | const width = size * SVG_RATIO
7 | const height = size
8 |
9 | return (
10 |
17 |
21 |
22 | )
23 | }
24 |
25 | Copy.defaultProps = {
26 | size: 16
27 | }
28 |
29 | export default Copy
30 |
--------------------------------------------------------------------------------
/components/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { COLORS } from '../lib/constants'
4 |
5 | const Input = ({
6 | accept,
7 | color = COLORS.SECONDARY,
8 | name,
9 | onChange = () => {},
10 | placeholder,
11 | title,
12 | type,
13 | value,
14 | align = 'right',
15 | maxLength
16 | }) => {
17 | return (
18 |
19 |
29 |
55 |
56 | )
57 | }
58 |
59 | export default Input
60 |
--------------------------------------------------------------------------------
/cypress/integration/localStorage-spec.js:
--------------------------------------------------------------------------------
1 | /* global cy */
2 | import { editorVisible } from '../support'
3 |
4 | // usually we can visit the page before each test
5 | // but these tests use the url, which means wasted page load
6 | // so instead visit the desired url in each test
7 |
8 | describe('localStorage', () => {
9 | const themeDropdown = () => cy.get('.toolbar .dropdown-container').first()
10 |
11 | const pickTheme = (name = 'Blackboard') =>
12 | themeDropdown()
13 | .click()
14 | .contains(name)
15 | .click()
16 |
17 | it.skip('is empty initially', () => {
18 | cy.visit('/')
19 | editorVisible()
20 | cy.window()
21 | .its('localStorage')
22 | .should('have.length', 0)
23 | })
24 |
25 | it('saves on theme change', () => {
26 | cy.visit('/')
27 | editorVisible()
28 | pickTheme('Blackboard')
29 | themeDropdown()
30 | .click()
31 | .contains('Blackboard')
32 |
33 | cy.wait(1500) // URL updates are debounced
34 |
35 | cy.window()
36 | .its('localStorage.CARBON_STATE')
37 | .then(JSON.parse)
38 | .its('theme')
39 | .should('equal', 'blackboard')
40 |
41 | // visiting page again restores theme from localStorage
42 | cy.visit('/')
43 | themeDropdown()
44 | .click()
45 | .contains('Blackboard')
46 | cy.url().should('contain', 't=blackboard')
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/packages/gatsby-remark-embed-carbon/README.md:
--------------------------------------------------------------------------------
1 | # gatsby-remark-embed-carbon
2 | > Embed Carbon screenshots in your markdown files
3 |
4 | ## Getting started
5 |
6 | 1. Install plugin to your site:
7 |
8 | ```bash
9 | yarn add gatsby-remark-embed-carbon
10 | ```
11 |
12 | 2. Add `gatsby-remark-embed-carbon` to your `gatsby-transformer-remark` plugins in `gatsby-config.js`:
13 |
14 | ```js
15 | plugins: [
16 | {
17 | resolve: 'gatsby-transformer-remark',
18 | options: {
19 | plugins: ['gatsby-remark-embed-carbon']
20 | }
21 | }
22 | ];
23 | ```
24 |
25 | ## Configuration
26 |
27 | ```js
28 | plugins: [
29 | {
30 | resolve: 'gatsby-transformer-remark',
31 | options: {
32 | plugins: [
33 | {
34 | resolve: 'gatsby-remark-embed-carbon',
35 | options: {
36 | width: '100%', // default is '1024px'
37 | height: '600px' // default is '473px'
38 | }
39 | }
40 | ]
41 | }
42 | }
43 | ]
44 | ```
45 |
46 | ## Example
47 | Add a carbon screenshot directly in your markdown file:
48 |
49 | ```md
50 | # Blog Post
51 |
52 | This link will get replaced with a Carbon iframe:
53 |
54 | https://carbon.now.sh/?bg=red
55 | ```
56 |
57 | ## Thanks
58 | This project was completely inspired by (copied from) @garetmckinley's [`gatsby-remark-embed-spotify`](https://github.com/garetmckinley/gatsby-remark-embed-spotify) :star:
59 |
--------------------------------------------------------------------------------
/components/Toggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Checkmark from './svg/Checkmark'
4 | import { COLORS } from '../lib/constants'
5 |
6 | class Toggle extends React.PureComponent {
7 | static defaultProps = {
8 | className: ''
9 | }
10 |
11 | toggle = () => this.props.onChange(!this.props.enabled)
12 |
13 | render() {
14 | return (
15 |
22 |
{this.props.label}
23 | {this.props.enabled ?
:
}
24 |
43 |
44 | )
45 | }
46 | }
47 |
48 | export default Toggle
49 |
--------------------------------------------------------------------------------
/components/ColorPicker.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SketchPicker } from 'react-color'
3 |
4 | import { COLORS } from '../lib/constants'
5 |
6 | const pickerStyle = {
7 | backgroundColor: COLORS.BLACK,
8 | padding: '8px 8px 0',
9 | margin: '0 auto 1px'
10 | }
11 |
12 | const ColorPicker = ({ onChange = () => {}, color, presets, style }) => (
13 |
14 |
20 |
42 |
43 | )
44 |
45 | export default ColorPicker
46 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | // Theirs
2 | import React from 'react'
3 | import { withRouter } from 'next/router'
4 | import debounce from 'lodash.debounce'
5 |
6 | // Ours
7 | import Editor from '../components/Editor'
8 | import Page from '../components/Page'
9 | import { MetaLinks } from '../components/Meta'
10 | import { updateQueryString } from '../lib/routing'
11 | import { saveSettings, clearSettings, omit } from '../lib/util'
12 |
13 | class Index extends React.Component {
14 | shouldComponentUpdate = () => false
15 |
16 | onEditorUpdate = debounce(
17 | state => {
18 | updateQueryString(this.props.router, state)
19 | saveSettings(
20 | localStorage,
21 | omit(state, ['code', 'backgroundImage', 'backgroundImageSelection', 'filename'])
22 | )
23 | },
24 | 750,
25 | { trailing: true, leading: true }
26 | )
27 |
28 | render() {
29 | return (
30 |
31 |
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | function onReset() {
39 | clearSettings()
40 |
41 | if (window.navigator && navigator.serviceWorker) {
42 | navigator.serviceWorker.getRegistrations().then(registrations => {
43 | for (let registration of registrations) {
44 | registration.unregister()
45 | }
46 | })
47 | }
48 | }
49 |
50 | export default withRouter(Index)
51 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Link from 'next/link'
3 | import { COLORS } from '../lib/constants'
4 |
5 | const Footer = () => (
6 |
57 | )
58 |
59 | export default Footer
60 |
--------------------------------------------------------------------------------
/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { COLORS } from '../lib/constants'
4 |
5 | const Button = ({
6 | id,
7 | onClick = () => {},
8 | className = '',
9 | background = COLORS.BLACK,
10 | color = COLORS.SECONDARY,
11 | hoverBackground = COLORS.HOVER,
12 | hoverColor,
13 | disabled,
14 | selected,
15 | children,
16 | border,
17 | center,
18 | large,
19 | style = {},
20 | flex = 1,
21 | padding = 0,
22 | margin = 0
23 | }) => (
24 |
25 | {children}
26 |
53 |
54 | )
55 |
56 | export default Button
57 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | If you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub.
4 |
5 | If you'd like to make some changes yourself, see the following:
6 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
7 | 2. Make sure yarn is globally installed (`npm install -g yarn`)
8 | 3. Run `yarn` to download required packages.
9 | 4. Build and start the application: `yarn dev`
10 | 5. If you contributed something new, run `yarn contrib:add ` to add yourself [below](#contributors)
11 | 6. Finally, submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with your changes!
12 |
13 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind are welcome!
14 |
15 | ### A note on adding themes/languages
16 | We're happy to accept any PRs adding new themes and languages to Carbon! Currently there are a few ways to do so:
17 |
18 | 1. If the [theme](https://codemirror.net/demo/theme.html) or [language](https://codemirror.net/mode/index.html) is supported in Codemirror, all you have to do is add a [constant](https://github.com/dawnlabs/carbon/blob/master/lib/constants.js) for it.
19 |
20 | 2. If it's not supported, you can add a Codemirror compliant [custom theme](https://github.com/dawnlabs/carbon/tree/master/static/themes) or [custom mode](https://github.com/dawnlabs/carbon/tree/master/lib/custom/modes) to implement it and add a constant like above.
21 |
--------------------------------------------------------------------------------
/components/svg/Theme.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
5 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/components/Popout.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import enhanceWithClickOutside from 'react-click-outside'
3 |
4 | import WindowPointer from './WindowPointer'
5 | import { COLORS } from '../lib/constants'
6 | import { toggle } from '../lib/util'
7 |
8 | export const managePopout = WrappedComponent => {
9 | class PopoutManager extends React.Component {
10 | state = {
11 | isVisible: false
12 | }
13 |
14 | toggleVisibility = () => this.setState(toggle('isVisible'))
15 |
16 | handleClickOutside = () => this.setState({ isVisible: false })
17 |
18 | render() {
19 | return (
20 |
25 | )
26 | }
27 | }
28 |
29 | return enhanceWithClickOutside(PopoutManager)
30 | }
31 |
32 | class Popout extends React.PureComponent {
33 | static defaultProps = {
34 | borderColor: COLORS.SECONDARY,
35 | style: {}
36 | }
37 |
38 | render() {
39 | const { id, children, borderColor, style, hidden, pointerLeft, pointerRight } = this.props
40 |
41 | if (hidden) {
42 | return null
43 | }
44 |
45 | return (
46 |
47 |
48 | {children}
49 |
61 |
62 | )
63 | }
64 | }
65 |
66 | export default Popout
67 |
--------------------------------------------------------------------------------
/cypress/integration/basic.js:
--------------------------------------------------------------------------------
1 | /* global cy */
2 | import { editorVisible } from '../support'
3 | describe('Basic', () => {
4 | it('Should open editor with the correct text encoding', () => {
5 | cy.visit(
6 | '/?code=%250A%252F*%2520Passing%2520Boolean%2520as%2520method%2520to%2520find%2520returns%2520the%250A%2520*%2520first%2520truthy%2520value%2520in%2520the%2520array!%250A%2520*%252F%250A%255Bfalse%252C%2520false%252C%2520%27%27%252C%2520undefined%252C%2520%27qwijo%27%252C%25200%255D.find(Boolean)%2520%252F%252F%2520%27qwijo%27'
7 | )
8 | editorVisible()
9 |
10 | cy.contains(
11 | '.container',
12 | "/* Passing Boolean as method to find returns the * first truthy value in the array! */[false, false, '', undefined, 'qwijo', 0].find(Boolean) // 'qwijo'"
13 | )
14 | })
15 |
16 | it('Should open editor with the correct text even with bad URI component', () => {
17 | cy.visit('/?code=%25')
18 | editorVisible()
19 |
20 | cy.contains('.container', '%')
21 | })
22 |
23 | it("Should contain id's for CLI integrations to use", () => {
24 | cy.get('#export-container').should('have.length', 1)
25 | cy.get('.export-container').should('have.length', 1)
26 | cy.get('#export-menu').should('have.length', 1)
27 | cy.get('#export-menu').click()
28 | cy.get('#export-png').should('have.length', 1)
29 | cy.get('#export-svg').should('have.length', 1)
30 | })
31 |
32 | /*
33 | * This test should only be run locally since it actually downloads a file
34 | * for verification.
35 | */
36 | it.skip('Should download a PNGs and SVGs', () => {
37 | cy.visit('/')
38 | editorVisible()
39 |
40 | cy.contains('span[type="button"]', 'Save Image').click()
41 | cy.get('#downshift-2-item-0').click()
42 |
43 | cy.wait(1000)
44 |
45 | cy.contains('span[type="button"]', 'Save Image').click()
46 | cy.get('#downshift-2-item-1').click()
47 |
48 | cy.wait(1000)
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/cypress/integration/background-color-spec.js:
--------------------------------------------------------------------------------
1 | /* global cy */
2 | import { editorVisible } from '../support'
3 |
4 | // usually we can visit the page before each test
5 | // but these tests use the url, which means wasted page load
6 | // so instead visit the desired url in each test
7 |
8 | describe('background color', () => {
9 | const bgColor = '.bg-color-container .bg-color'
10 | const picker = '#bg-select-pickers'
11 |
12 | const openPicker = () => {
13 | cy.get(bgColor).click()
14 | return cy.get(picker).should('be.visible')
15 | }
16 |
17 | // clicking anywhere else closes it
18 | const closePicker = () => cy.get('body').click()
19 |
20 | it('opens BG color pick', () => {
21 | cy.visit('/')
22 | openPicker()
23 | closePicker()
24 | cy.get(picker).should('not.be.visible')
25 | })
26 |
27 | it('changes background color to dark red', () => {
28 | cy.visit('/')
29 | const darkRed = '#D0021B'
30 | const darkRedTile = `[title="${darkRed}"]`
31 | openPicker()
32 | cy.get(picker)
33 | .find(darkRedTile)
34 | .click()
35 | closePicker()
36 |
37 | // changing background color triggers url change
38 | cy.url().should('contain', '?bg=')
39 |
40 | // confirm color change
41 | cy.get('.container-bg .bg').should('have.css', 'background-color', 'rgb(208, 2, 27)')
42 | })
43 |
44 | it('specifies color in url', () => {
45 | cy.visit('?bg=rgb(255,0,0)')
46 | editorVisible()
47 | cy.get('.container-bg .bg').should('have.css', 'background-color', 'rgb(255, 0, 0)')
48 | })
49 |
50 | it('enters neon pink', () => {
51 | cy.visit('?bg=rgb(255,0,0)')
52 | editorVisible()
53 |
54 | const pink = 'ff00ff'
55 | openPicker()
56 | .find(`input[value="FF0000"]`)
57 | .clear()
58 | .type(`${pink}{enter}`)
59 | closePicker()
60 |
61 | cy.url().should('contain', `?bg=rgba(${encodeURIComponent('255,0,255,1')}`)
62 | cy.get('.container-bg .bg').should('have.css', 'background-color', 'rgb(255, 0, 255)')
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/components/ThemeSelect.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { None, BW, Sharp } from './svg/WindowThemes'
3 | import { COLORS } from '../lib/constants'
4 |
5 | const WINDOW_THEMES_MAP = { none: None, sharp: Sharp, bw: BW }
6 | export const WINDOW_THEMES = Object.keys(WINDOW_THEMES_MAP)
7 |
8 | class ThemeSelect extends React.Component {
9 | select = theme => {
10 | if (this.props.selected !== theme) {
11 | this.props.onChange(theme)
12 | }
13 | }
14 |
15 | renderThemes() {
16 | return WINDOW_THEMES.map(theme => {
17 | const Img = WINDOW_THEMES_MAP[theme]
18 | return (
19 |
26 |
27 |
44 |
45 | )
46 | })
47 | }
48 |
49 | render() {
50 | return (
51 |
52 |
Theme
53 |
{this.renderThemes()}
54 |
72 |
73 | )
74 | }
75 | }
76 |
77 | export default ThemeSelect
78 |
--------------------------------------------------------------------------------
/components/Slider.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { COLORS } from '../lib/constants'
4 |
5 | class Slider extends React.Component {
6 | static defaultProps = {
7 | onMouseDown: () => {},
8 | onMouseUp: () => {}
9 | }
10 |
11 | handleChange = e => {
12 | this.props.onChange(`${e.target.value}${this.props.usePercentage ? '%' : 'px'}`)
13 | }
14 |
15 | render() {
16 | const minValue = this.props.minValue || 0
17 | const maxValue = this.props.maxValue || 100
18 | const step = 'step' in this.props ? this.props.step : 1
19 |
20 | return (
21 |
22 |
30 |
{this.props.label}
31 |
41 |
80 |
81 | )
82 | }
83 | }
84 |
85 | export default Slider
86 |
--------------------------------------------------------------------------------
/components/WindowControls.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useCopyTextHandler } from '@dawnlabs/tacklebox'
3 |
4 | import { COLORS } from '../lib/constants'
5 | import { Controls, ControlsBW } from './svg/Controls'
6 | import CopySVG from './svg/Copy'
7 | import CheckMark from './svg/Checkmark'
8 |
9 | const size = 24
10 |
11 | const CopyButton = React.memo(function CopyButton({ text }) {
12 | const { onClick, copied } = useCopyTextHandler(text)
13 |
14 | return (
15 |
16 | {copied ? (
17 |
18 | ) : (
19 |
20 | )}
21 |
35 |
36 | )
37 | })
38 |
39 | export default ({ theme, copyable, code }) => (
40 |
41 | {theme === 'bw' ?
:
}
42 |
43 |
44 |
45 | {copyable && (
46 |
47 |
48 |
49 | )}
50 |
87 |
88 | )
89 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | import morph from 'morphmorph'
2 | import omitBy from 'lodash.omitby'
3 | import { unescape } from 'escape-goat'
4 |
5 | const SETTINGS_KEY = 'CARBON_STATE'
6 | const PRESETS_KEY = 'CARBON_PRESETS'
7 | const THEMES_KEY = 'CARBON_THEMES'
8 |
9 | const createAssigner = key => {
10 | const assign = morph.assign(key)
11 |
12 | return (window, v) => assign(window, JSON.stringify(v))
13 | }
14 |
15 | export const saveSettings = createAssigner(SETTINGS_KEY)
16 | export const savePresets = createAssigner(PRESETS_KEY)
17 | export const saveThemes = createAssigner(THEMES_KEY)
18 |
19 | const parse = v => {
20 | try {
21 | return JSON.parse(v)
22 | } catch (e) {
23 | // pass
24 | }
25 | }
26 |
27 | export const toggle = stateField => state => ({ [stateField]: !state[stateField] })
28 |
29 | // https://gist.github.com/alexgibson/1704515
30 | // TODO use https://github.com/sindresorhus/escape-goat/
31 | export const escapeHtml = s => {
32 | if (typeof s === 'string') {
33 | return s
34 | .replace(//g, '>')
36 | .replace(/\//g, '/')
37 | }
38 | }
39 |
40 | export const unescapeHtml = s => {
41 | if (typeof s === 'string') {
42 | return unescape(s).replace(///g, '/')
43 | }
44 | }
45 |
46 | export const getSettings = morph.compose(
47 | parse,
48 | escapeHtml,
49 | morph.get(SETTINGS_KEY)
50 | )
51 |
52 | export const getPresets = morph.compose(
53 | parse,
54 | morph.get(PRESETS_KEY)
55 | )
56 |
57 | export const getThemes = morph.compose(
58 | parse,
59 | morph.get(THEMES_KEY)
60 | )
61 |
62 | export const clearSettings = () => localStorage.removeItem(SETTINGS_KEY)
63 |
64 | export const fileToDataURL = blob =>
65 | new Promise(res => {
66 | const reader = new FileReader()
67 | reader.onload = e => res(e.target.result)
68 | reader.readAsDataURL(blob)
69 | })
70 |
71 | export const formatCode = async code => {
72 | const prettier = await import('prettier/standalone')
73 | const babylonParser = await import('prettier/parser-babylon')
74 |
75 | return prettier.format(code, {
76 | parser: 'babylon',
77 | plugins: [babylonParser],
78 | semi: false,
79 | singleQuote: true
80 | })
81 | }
82 |
83 | export const omit = (object, keys) => omitBy(object, (_, k) => keys.indexOf(k) > -1)
84 |
85 | export const stringifyRGBA = obj => `rgba(${obj.r},${obj.g},${obj.b},${obj.a})`
86 |
87 | export const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1)
88 |
89 | export const generateId = () =>
90 | Math.random()
91 | .toString(36)
92 | .slice(2)
93 |
--------------------------------------------------------------------------------
/components/RandomImage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Spinner from 'react-spinner'
3 | import { useAsyncCallback } from '@dawnlabs/tacklebox'
4 |
5 | import ApiContext from './ApiContext'
6 | import PhotoCredit from './PhotoCredit'
7 |
8 | function RandomImage(props) {
9 | const cacheRef = React.useRef([])
10 | const [cacheIndex, updateIndex] = React.useState(0)
11 | const api = React.useContext(ApiContext)
12 |
13 | const [selectImage, { loading: selecting }] = useAsyncCallback(() => {
14 | const image = cacheRef.current[cacheIndex]
15 |
16 | return api.unsplash.download(image.id).then(blob => props.onChange(blob, image))
17 | })
18 |
19 | const [updateCache, { loading: updating, error, data: imgs }] = useAsyncCallback(
20 | api.unsplash.random
21 | )
22 |
23 | const needsFetch = !error && !updating && (!imgs || cacheIndex > cacheRef.current.length - 2)
24 |
25 | React.useEffect(() => {
26 | if (needsFetch) {
27 | updateCache()
28 | }
29 | }, [needsFetch, updateCache])
30 |
31 | React.useEffect(() => {
32 | if (imgs) {
33 | cacheRef.current.push(...imgs)
34 | }
35 | }, [imgs])
36 |
37 | const loading = updating || selecting
38 |
39 | const cache = cacheRef.current
40 | const photographer = cache[cacheIndex] && cache[cacheIndex].photographer
41 | const bgImage = cache[cacheIndex] && cache[cacheIndex].dataURL
42 | return (
43 |
44 |
45 |
46 | Use Image
47 |
48 | updateIndex(i => i + 1)}>
49 | Try Another
50 |
51 |
52 |
{loading && }
53 | {photographer &&
}
54 |
78 |
79 | )
80 | }
81 |
82 | export default RandomImage
83 |
--------------------------------------------------------------------------------
/components/svg/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
5 |
11 |
12 | )
13 |
--------------------------------------------------------------------------------
/components/svg/Language.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
5 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/static/themes/verminal.css:
--------------------------------------------------------------------------------
1 | /*
2 | Name: verminal 1.0.0
3 | Author: Vernon de Goede (http://github.com/vernondegoede)
4 | */
5 |
6 | .CodeMirror.cm-s-verminal {
7 | background: rgba(0, 0, 0, 0.85);
8 | color: white;
9 | font-size: 12px;
10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
11 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
12 | }
13 |
14 | @supports ((-webkit-backdrop-filter: blur(3em)) or (backdrop-filter: blur(3em))) {
15 | .CodeMirror.cm-s-verminal {
16 | -webkit-backdrop-filter: blur(4em);
17 | backdrop-filter: blur(4em);
18 | background: rgba(0, 0, 0, 0.70);
19 | }
20 | }
21 |
22 | /* basic syntax */
23 | .cm-s-verminal .cm-header {
24 | color: #e06c75;
25 | }
26 | .cm-s-verminal .cm-quote {
27 | color: #5c6370;
28 | font-style: italic;
29 | }
30 | .cm-s-verminal .cm-negative {
31 | color: #e06c75;
32 | }
33 | .cm-s-verminal .cm-positive {
34 | color: #e06c75;
35 | }
36 | .cm-s-verminal .cm-strong {
37 | color: #d19a66;
38 | font-weight: bold;
39 | }
40 | .cm-s-verminal .cm-header .cm-strong {
41 | color: #d19a66;
42 | font-weight: bold;
43 | }
44 | .cm-s-verminal .cm-em {
45 | color: #c678dd;
46 | font-style: italic;
47 | }
48 | .cm-s-verminal .cm-header .cm-em {
49 | color: #c678dd;
50 | font-style: italic;
51 | }
52 | .cm-s-verminal .cm-tag {
53 | color: #e06c75;
54 | }
55 | .cm-s-verminal .cm-attribute {
56 | color: #d19a66;
57 | }
58 | .cm-s-verminal .cm-link {
59 | color: #98c379;
60 | border-bottom: solid 1px #98c379;
61 | }
62 | .cm-s-verminal .cm-builtin {
63 | color: #e06c75;
64 | }
65 | .cm-s-verminal .cm-keyword {
66 | color: #9AE1FF;
67 | }
68 | .cm-s-verminal .cm-def {
69 | color: #34B7FF;
70 | }
71 | .cm-s-verminal .cm-atom {
72 | color: #d19a66;
73 | }
74 | .cm-s-verminal .cm-number {
75 | color: #d19a66;
76 | }
77 | .cm-s-verminal .cm-property {
78 | color: #0af;
79 | }
80 | .cm-s-verminal .cm-qualifier {
81 | color: #d19a66;
82 | }
83 | .cm-s-verminal .cm-variable {
84 | color: #ff9ba3;
85 | }
86 | .cm-s-verminal .cm-variable-2 {
87 | color: #fff;
88 | }
89 | .cm-s-verminal .cm-string {
90 | color: #98c379;
91 | }
92 | .cm-s-verminal .cm-punctuation {
93 | color: #abb2bf;
94 | }
95 | .cm-s-verminal .cm-operator {
96 | color: #FA78C3;
97 | }
98 |
99 | .cm-s-verminal .cm-meta {
100 | color: #abb2bf;
101 | }
102 | .cm-s-verminal .cm-bracket {
103 | color: #abb2bf;
104 | }
105 | .cm-s-verminal .cm-comment {
106 | color: #5c6370;
107 | font-style: italic;
108 | }
109 | .cm-s-verminal .cm-error {
110 | color: #e06c75;
111 | }
112 |
113 | .cm-s-verminal .CodeMirror-linenumber,
114 | .cm-s-verminal .CodeMirror-linenumbers {
115 | color: #616161 !important;
116 | background-color: transparent;
117 | }
118 |
--------------------------------------------------------------------------------
/components/Meta.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 | import { THEMES, THEMES_HASH } from '../lib/constants'
4 | import Reset from './style/Reset'
5 | import Font from './style/Font'
6 | import Typography from './style/Typography'
7 |
8 | const LOCAL_STYLESHEETS = ['one-light', 'one-dark', 'verminal', 'night-owl', 'nord']
9 | const CDN_STYLESHEETS = THEMES.filter(t => LOCAL_STYLESHEETS.indexOf(t.id) < 0)
10 |
11 | export function Link({ href }) {
12 | return (
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export const StylesheetLink = ({ theme }) => {
21 | let href
22 | if (LOCAL_STYLESHEETS.indexOf(theme) > -1) {
23 | href = `/static/themes/${theme}.css`
24 | } else {
25 | const themeDef = THEMES_HASH[theme]
26 | href = `//cdnjs.cloudflare.com/ajax/libs/codemirror/5.42.2/theme/${themeDef &&
27 | (themeDef.link || themeDef.id)}.min.css`
28 | }
29 |
30 | return
31 | }
32 |
33 | export const CodeMirrorLink = () => (
34 |
35 | )
36 |
37 | const title = 'Carbon'
38 | const description =
39 | 'Carbon is the easiest way to create and share beautiful images of your source code.'
40 | export const MetaTags = React.memo(() => (
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {title}
54 |
55 |
56 |
57 | ))
58 |
59 | export const MetaLinks = React.memo(() => {
60 | return (
61 |
62 |
63 |
64 | {LOCAL_STYLESHEETS.map(id => (
65 |
66 | ))}
67 | {CDN_STYLESHEETS.map(themeDef => {
68 | const href = `//cdnjs.cloudflare.com/ajax/libs/codemirror/5.42.2/theme/${themeDef &&
69 | (themeDef.link || themeDef.id)}.min.css`
70 | return
71 | })}
72 |
73 | )
74 | })
75 |
76 | export default React.memo(function Meta() {
77 | return (
78 |
79 |
80 |
81 |
82 |
83 |
84 | )
85 | })
86 |
--------------------------------------------------------------------------------
/pages/embed.js:
--------------------------------------------------------------------------------
1 | // Theirs
2 | import React from 'react'
3 | import Head from 'next/head'
4 | import { withRouter } from 'next/router'
5 | import url from 'url'
6 | import morph from 'morphmorph'
7 |
8 | // Ours
9 | import { StylesheetLink, CodeMirrorLink, MetaTags } from '../components/Meta'
10 | import Carbon from '../components/Carbon'
11 | import { DEFAULT_CODE, DEFAULT_SETTINGS } from '../lib/constants'
12 | import { getQueryStringState } from '../lib/routing'
13 |
14 | const isInIFrame = morph.get('parent.window.parent')
15 | const getParent = win => {
16 | const iFrame = isInIFrame(win)
17 |
18 | if (iFrame) {
19 | return iFrame
20 | }
21 |
22 | return win.parent
23 | }
24 |
25 | const Page = props => (
26 |
27 |
28 | Carbon Embeds
29 |
30 |
31 |
32 |
33 | {props.children}
34 |
44 |
45 | )
46 |
47 | class Embed extends React.Component {
48 | state = {
49 | ...DEFAULT_SETTINGS,
50 | code: DEFAULT_CODE,
51 | mounted: false,
52 | readOnly: true
53 | }
54 |
55 | componentDidMount() {
56 | const { asPath = '' } = this.props.router
57 | const { query } = url.parse(asPath, true)
58 | const queryParams = getQueryStringState(query)
59 | const initialState = Object.keys(queryParams).length ? queryParams : {}
60 |
61 | this.setState(
62 | {
63 | ...initialState,
64 | id: query.id,
65 | copyable: queryParams.copy !== false,
66 | readOnly: queryParams.readonly !== false,
67 | mounted: true
68 | },
69 | this.postMessage
70 | )
71 | }
72 |
73 | ref = React.createRef()
74 |
75 | postMessage = () => {
76 | getParent(window).postMessage(
77 | JSON.stringify({
78 | // Used by embed provider
79 | src: window.location.toString(),
80 | context: 'iframe.resize',
81 | height: this.ref.current.offsetHeight
82 | }),
83 | '*'
84 | )
85 | }
86 |
87 | updateCode = code => {
88 | this.setState({ code }, this.postMessage)
89 |
90 | getParent(window).postMessage(
91 | {
92 | id: this.state.id ? `carbon:${this.state.id}` : 'carbon',
93 | code
94 | },
95 | '*'
96 | )
97 | }
98 |
99 | render() {
100 | return (
101 |
102 | {this.state.mounted && (
103 |
110 | {this.state.code}
111 |
112 | )}
113 |
114 | )
115 | }
116 | }
117 |
118 | export default withRouter(Embed)
119 |
--------------------------------------------------------------------------------
/lib/routing.js:
--------------------------------------------------------------------------------
1 | import Morph from 'morphmorph'
2 |
3 | const mapper = new Morph({
4 | types: {
5 | bool: v => {
6 | if (v == null) return undefined
7 | if (v === 'false') return false
8 | return Boolean(v)
9 | }
10 | }
11 | })
12 |
13 | const mappings = [
14 | { field: 'bg:backgroundColor' },
15 | { field: 't:theme' },
16 | { field: 'wt:windowTheme' },
17 | { field: 'l:language' },
18 | { field: 'ds:dropShadow', type: 'bool' },
19 | { field: 'dsyoff:dropShadowOffsetY' },
20 | { field: 'dsblur:dropShadowBlurRadius' },
21 | { field: 'wc:windowControls', type: 'bool' },
22 | { field: 'wa:widthAdjustment', type: 'bool' },
23 | { field: 'pv:paddingVertical' },
24 | { field: 'ph:paddingHorizontal' },
25 | { field: 'ln:lineNumbers', type: 'bool' },
26 | { field: 'fm:fontFamily' },
27 | { field: 'fs:fontSize' },
28 | { field: 'lh:lineHeight' },
29 | { field: 'si:squaredImage', type: 'bool' },
30 | { field: 'code:code' },
31 | { field: 'es:exportSize' },
32 | { field: 'wm:watermark', type: 'bool' },
33 | { field: 'copy', type: 'bool' },
34 | { field: 'readonly', type: 'bool' }
35 | ]
36 |
37 | const reverseMappings = mappings.map(mapping =>
38 | Object.assign({}, mapping, {
39 | field: mapping.field
40 | .split(':')
41 | .reverse()
42 | .join(':')
43 | })
44 | )
45 |
46 | export const serializeState = state => {
47 | const stateString = encodeURIComponent(JSON.stringify(state))
48 |
49 | return encodeURIComponent(
50 | typeof window !== 'undefined' ? btoa(stateString) : Buffer.from(stateString).toString('base64')
51 | )
52 | }
53 |
54 | export const deserializeState = serializedState => {
55 | let stateString
56 | if (typeof window !== 'undefined') {
57 | stateString = atob(serializedState)
58 | } else {
59 | stateString = Buffer.from(serializedState, 'base64').toString()
60 | }
61 |
62 | return JSON.parse(decodeURIComponent(stateString))
63 | }
64 |
65 | export const getQueryStringState = query => {
66 | if (query.state) {
67 | return deserializeState(query.state)
68 | }
69 |
70 | const state = mapper.map(mappings, query)
71 | deserializeCode(state)
72 |
73 | Object.keys(state).forEach(key => {
74 | if (state[key] === '') state[key] = undefined
75 | })
76 |
77 | return state
78 | }
79 |
80 | export const updateQueryString = (router, state) => {
81 | const mappedState = mapper.map(reverseMappings, state)
82 | serializeCode(mappedState)
83 |
84 | router.replace(
85 | {
86 | pathname: router.pathname
87 | },
88 | {
89 | pathname: router.pathname,
90 | query: mappedState
91 | },
92 | { shallow: true }
93 | )
94 | }
95 |
96 | // private
97 | function serializeCode(state) {
98 | try {
99 | if (state.code) state.code = encodeURIComponent(state.code)
100 | } catch (e) {
101 | // encoding errors should not crash the app
102 | }
103 | }
104 |
105 | function deserializeCode(state) {
106 | try {
107 | if (state.code) state.code = decodeURIComponent(state.code)
108 | } catch (e) {
109 | // decoding errors should not crash the app
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/static/themes/night-owl.css:
--------------------------------------------------------------------------------
1 | /*
2 | Name: night-owl 1.1.1
3 | Author: Sarah Drasner (https://github.com/sdras)
4 | Original VS Code theme (https://github.com/sdras/night-owl-vscode-theme)
5 | */
6 | /* basic */
7 | .CodeMirror.cm-s-night-owl {
8 | font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
9 | font-weight: 350;
10 | font-size: 18px;
11 | color: #abb2bf;
12 | background-color: #011627;
13 | }
14 | .cm-s-night-owl .CodeMirror-selected {background-color: #1d3b53 !important;}
15 | .cm-s-night-owl .CodeMirror-gutter,
16 | .cm-s-night-owl .CodeMirror-gutters {
17 | border: none;
18 | background-color: #011627;
19 | }
20 | .cm-s-night-owl .CodeMirror-linenumber,
21 | .cm-s-night-owl .CodeMirror-linenumbers {
22 | color: #5f7e97 !important;
23 | background-color: transparent;
24 | }
25 | .cm-s-night-owl .CodeMirror-lines {
26 | color: #abb2bf !important;
27 | background-color: transparent;
28 | }
29 | .cm-s-night-owl .CodeMirror-cursor {border-left: 2px solid #7e57c2 !important;}
30 | /* addon: edit/machingbrackets.js & addon: edit/matchtags.js */
31 | .cm-s-night-owl .CodeMirror-matchingbracket,
32 | .cm-s-night-owl .CodeMirror-matchingtag {
33 | border-bottom: 2px solid #c792ea;
34 | color: #abb2bf !important;
35 | background-color: transparent;
36 | }
37 | .cm-s-night-owl .CodeMirror-nonmatchingbracket {
38 | border-bottom: 2px solid #e06c75;
39 | color: #abb2bf !important;
40 | background-color: transparent;
41 | }
42 | /* addon: fold/foldgutter.js */
43 | .cm-s-night-owl .CodeMirror-foldmarker,
44 | .cm-s-night-owl .CodeMirror-foldgutter,
45 | .cm-s-night-owl .CodeMirror-foldgutter-open,
46 | .cm-s-night-owl .CodeMirror-foldgutter-folded {
47 | border: none;
48 | text-shadow: none;
49 | color: #5c6370 !important;
50 | background-color: transparent;
51 | }
52 | /* addon: selection/active-line.js */
53 | .cm-s-night-owl .CodeMirror-activeline-background {background-color: #01121f;}
54 | /* basic syntax */
55 | .cm-s-night-owl .cm-quote {color: #5c6370;font-style: italic;}
56 | .cm-s-night-owl .cm-negative {color: #e06c75;}
57 | .cm-s-night-owl .cm-positive {color: #e06c75;}
58 | .cm-s-night-owl .cm-strong {color: #F78C6C;font-weight: bold;}
59 | .cm-s-night-owl .cm-em {color: #c792ea;font-style: italic;}
60 | .cm-s-night-owl .cm-attribute {color: #F78C6C;}
61 | .cm-s-night-owl .cm-link {color: #ecc48d;border-bottom: solid 1px #ecc48d;}
62 | .cm-s-night-owl .cm-keyword {color: #c792ea;font-style: italic;}
63 | .cm-s-night-owl .cm-def {color: #82AAFF;}
64 | .cm-s-night-owl .cm-atom {color: #F78C6C;}
65 | .cm-s-night-owl .cm-number {color: #F78C6C;}
66 | .cm-s-night-owl .cm-property {color: #fff;}
67 | .cm-s-night-owl .cm-qualifier {color: #F78C6C;}
68 | .cm-s-night-owl .cm-variable {color: #82AAFF;}
69 | .cm-s-night-owl .cm-variable-2 {color: #82AAFF;}
70 | .cm-s-night-owl .cm-string {color: #ecc48d;}
71 | .cm-s-night-owl .cm-string-2 {color: #addb67ff;}
72 | .cm-s-night-owl .cm-operator {color: #c792ea;}
73 | .cm-s-night-owl .cm-meta {color: #7fdbca;}
74 | .cm-s-night-owl .cm-comment {color: #5c6370;font-style: italic;}
75 | .cm-s-night-owl .cm-error {color: #e06c75;}
--------------------------------------------------------------------------------
/components/ListSetting.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Checkmark from './svg/Checkmark'
4 | import { COLORS } from '../lib/constants'
5 | import { toggle } from '../lib/util'
6 |
7 | class ListSetting extends React.Component {
8 | static defaultProps = {
9 | onOpen: () => {},
10 | onClose: () => {}
11 | }
12 |
13 | state = { isVisible: false }
14 |
15 | select = id => {
16 | if (this.props.selected !== id) {
17 | this.props.onChange(id)
18 | }
19 | }
20 |
21 | toggle = () => {
22 | const handler = this.state.isVisible ? this.props.onClose : this.props.onOpen
23 | handler()
24 | this.setState(toggle('isVisible'))
25 | }
26 |
27 | renderListItems() {
28 | return this.props.items.map(item => (
29 |
36 | {this.props.children(item)}
37 | {this.props.selected === item.id ? : null}
38 |
58 |
59 | ))
60 | }
61 |
62 | render() {
63 | const { items, selected, title, children } = this.props
64 | const { isVisible } = this.state
65 |
66 | const selectedItem = items.filter(item => item.id === selected)[0] || {}
67 |
68 | return (
69 |
70 |
76 | {title}
77 | {children(selectedItem)}
78 |
79 |
{this.renderListItems()}
80 |
101 |
102 | )
103 | }
104 | }
105 |
106 | export default ListSetting
107 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hi@dawnlabs.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "carbon",
3 | "version": "3.9.8",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "next",
8 | "build": "next build",
9 | "start": "next start",
10 | "export": "next export",
11 | "test": "npm run cy:run --",
12 | "deploy": "now",
13 | "prettier": "prettier --config .prettierrc --write {.,components,lib,pages}/*.js {components,lib,pages,packages}/**/*.js",
14 | "lint": "eslint .",
15 | "contrib:add": "all-contributors add",
16 | "contrib:build": "all-contributors generate",
17 | "cy:run": "cypress run",
18 | "cy:open": "cypress open",
19 | "now-build": "cross-env NODE_ENV=production yarn build && yarn export -o dist"
20 | },
21 | "dependencies": {
22 | "@dawnlabs/tacklebox": "^0.0.6",
23 | "axios": "^0.18.0",
24 | "codemirror": "^5.42.2",
25 | "codemirror-graphql": "^0.8.3",
26 | "codemirror-mode-elixir": "^1.1.2",
27 | "cross-env": "^5.2.0",
28 | "dom-to-image": "^2.5.2",
29 | "downshift": "^3.2.2",
30 | "dropperx": "^1.0.1",
31 | "escape-goat": "^1.3.0",
32 | "graphql": "^14.1.1",
33 | "highlight.js": "^9.14.2",
34 | "lodash.debounce": "^4.0.8",
35 | "lodash.omitby": "^4.6.0",
36 | "match-sorter": "^2.3.0",
37 | "morphmorph": "^0.1.0",
38 | "ms": "^2.0.0",
39 | "next": "^8.0.3",
40 | "next-offline": "^3.3.6",
41 | "prettier": "^1.16.4",
42 | "react": "^16.8.3",
43 | "react-click-outside": "^3.0.0",
44 | "react-codemirror2": "^5.1.0",
45 | "react-color": "^2.17.0",
46 | "react-copy-to-clipboard": "^5.0.1",
47 | "react-dom": "^16.8.3",
48 | "react-image-crop": "^6.0.16",
49 | "react-spinner": "^0.2.7",
50 | "react-syntax-highlight": "^15.3.1",
51 | "tohash": "^1.0.2",
52 | "url": "^0.11.0"
53 | },
54 | "devDependencies": {
55 | "all-contributors-cli": "^6.0.0",
56 | "babel-eslint": "^10.0.1",
57 | "clipboardy": "^1.2.3",
58 | "cypress": "^3.1.5",
59 | "eslint": "^5.13.0",
60 | "eslint-plugin-import": "^2.16.0",
61 | "eslint-plugin-jsx-a11y": "^6.2.1",
62 | "eslint-plugin-react": "^7.12.3",
63 | "eslint-plugin-react-hooks": "^1.4.0",
64 | "husky": "^1.3.1",
65 | "lint-staged": "^8.1.3",
66 | "now": "^14.0.0",
67 | "wait-on": "^3.2.0"
68 | },
69 | "lint-staged": {
70 | "*.js": [
71 | "npm run lint",
72 | "prettier --config .prettierrc --write",
73 | "git add"
74 | ]
75 | },
76 | "greenkeeper": {
77 | "groups": {
78 | "app": {
79 | "packages": [
80 | "package.json"
81 | ],
82 | "ignore": [
83 | "graphql",
84 | "webpack"
85 | ]
86 | },
87 | "integrations": {
88 | "packages": [
89 | "packages/gatsby-remark-embed-carbon/package.json"
90 | ]
91 | }
92 | }
93 | },
94 | "description": "Create and share beautiful images of your source code",
95 | "repository": {
96 | "type": "git",
97 | "url": "git+https://github.com/dawnlabs/carbon.git"
98 | },
99 | "author": "Dawn Labs <@dawnlabs>",
100 | "bugs": {
101 | "url": "https://github.com/dawnlabs/carbon/issues"
102 | },
103 | "homepage": "https://dawnlabs.io/carbon",
104 | "husky": {
105 | "hooks": {
106 | "pre-commit": "npm run contrib:build && git add README.md && lint-staged"
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/static/themes/nord.css:
--------------------------------------------------------------------------------
1 | /*
2 | Name: nord 1.1.1
3 | Author: Arctic Ice Studio (https://github.com/arcticicestudio/)
4 | Original VS Code Theme (https://github.com/arcticicestudio/nord-visual-studio-code)
5 | */
6 | /* basic */
7 | .CodeMirror.cm-s-nord {
8 | color: #d8dee9;
9 | background-color: #2e3440;
10 | }
11 | .cm-s-nord .CodeMirror-selected {
12 | background-color: rgba(67, 76, 94, 0.8);
13 | }
14 | .cm-s-nord .CodeMirror-gutter,
15 | .cm-s-nord .CodeMirror-gutters {
16 | border: none;
17 | background-color: #2e3440;
18 | }
19 | .cm-s-nord .CodeMirror-linenumber,
20 | .cm-s-nord .CodeMirror-linenumbers {
21 | color: rgba(216, 222, 233, 0.4) !important;
22 | background-color: transparent;
23 | }
24 | .cm-s-nord .CodeMirror-lines {
25 | color: #d8dee9 !important;
26 | background-color: transparent;
27 | }
28 | .cm-s-nord .CodeMirror-cursor {
29 | border-left: 2px solid #d8dee9 !important;
30 | }
31 | /* addon: edit/machingbrackets.js & addon: edit/matchtags.js */
32 | .cm-s-nord .CodeMirror-matchingbracket,
33 | .cm-s-nord .CodeMirror-matchingtag {
34 | border-bottom: 2px solid #81A1C1;
35 | color: #d8dee9 !important;
36 | background-color: transparent;
37 | }
38 | .cm-s-nord .CodeMirror-nonmatchingbracket {
39 | border-bottom: 2px solid #bf616a;
40 | color: #d8dee9 !important;
41 | background-color: transparent;
42 | }
43 | /* addon: fold/foldgutter.js */
44 | .cm-s-nord .CodeMirror-foldmarker,
45 | .cm-s-nord .CodeMirror-foldgutter,
46 | .cm-s-nord .CodeMirror-foldgutter-open,
47 | .cm-s-nord .CodeMirror-foldgutter-folded {
48 | border: none;
49 | text-shadow: none;
50 | color: #d8dee9 !important;
51 | background-color: transparent;
52 | }
53 | /* addon: selection/active-line.js */
54 | .cm-s-nord .CodeMirror-activeline-background {
55 | background-color: rgba(67, 76, 94, 0.32);
56 | }
57 | /* basic syntax */
58 | .cm-s-nord .cm-attribute {
59 | color: #8FBCBB;
60 | }
61 | .cm-s-nord .cm-keyword {
62 | color: #81A1C1;
63 | }
64 | .cm-s-nord .cm-def {
65 | color: #D8DEE9;
66 | }
67 | .cm-s-nord .cm-atom {
68 | color: #81A1C1;
69 | }
70 | .cm-s-nord .cm-number {
71 | color: #B48EAD;
72 | }
73 | .cm-s-nord .cm-property {
74 | color: #D8DEE9;
75 | }
76 | .cm-s-nord .cm-qualifier {
77 | color: #88C0D0;
78 | }
79 | .cm-s-nord .cm-variable,
80 | .cm-s-nord .cm-variable-2 {
81 | color: #88C0D0;
82 | }
83 | .cm-s-nord .cm-variable-3 {
84 | color: #D8DEE9;
85 | }
86 | .cm-s-nord .cm-string,
87 | .cm-s-nord .cm-string-2 {
88 | color: #A3BE8C;
89 | }
90 | .cm-s-nord .cm-operator {
91 | color: #81A1C1;
92 | }
93 | .cm-s-nord .cm-meta {
94 | color: #81A1C1;
95 | }
96 | .cm-s-nord .cm-comment {
97 | color: #4C566A;
98 | }
99 | .cm-s-nord .cm-error {
100 | color: #bf616a;
101 | }
102 | /* markdown */
103 | .cm-s-nord .cm-header {
104 | color: #88C0D0;
105 | }
106 | .cm-s-nord .cm-quote {
107 | color: #4C566A;
108 | }
109 | .cm-s-nord .cm-link {
110 | color: #88C0D0;
111 | text-decoration: none;
112 | }
113 | .cm-s-nord .cm-url {
114 | color: #d8dee9;
115 | text-decoration: underline;
116 | }
117 | .cm-s-nord .cm-strong {
118 | font-weight: bold;
119 | }
120 | .cm-s-nord .cm-em {
121 | font-style: italic;
122 | }
123 | /* diff */
124 | .cm-s-nord .cm-negative {
125 | color: #bf616a;
126 | }
127 | .cm-s-nord .cm-positive {
128 | color: #a3be8c;
129 | }
130 | /* html */
131 | .cm-s-nord .cm-tag {
132 | color: #81A1C1;
133 | }
134 |
--------------------------------------------------------------------------------
/lib/api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import debounce from 'lodash.debounce'
3 | import ms from 'ms'
4 |
5 | import { fileToDataURL } from './util'
6 |
7 | const client = axios.create({
8 | baseURL: `${
9 | process.env.API_URL || process.env.NODE_ENV === 'production' ? '' : 'http://localhost:4000'
10 | }/api`
11 | })
12 |
13 | const RATE_LIMIT_CODE = 420
14 |
15 | const gistClient = axios.create({
16 | baseURL: 'https://api.github.com',
17 | timeout: 5000,
18 | headers: {
19 | Accept: 'application/vnd.github.v3+json',
20 | 'Content-Type': 'application/json'
21 | }
22 | })
23 |
24 | function tweet(content, encodedImage) {
25 | const processedData = encodedImage.split(',')[1]
26 |
27 | return client
28 | .post('/twitter', {
29 | imageData: processedData,
30 | altText: content
31 | })
32 | .then(res => res.data.url)
33 | .then(url => encodeURIComponent(`Built with #Carbon, by @dawn_labs ${url}`))
34 | .then(uri => `https://twitter.com/intent/tweet?text=${uri}`)
35 | .then(openTwitterUrl)
36 | .catch(checkIfRateLimited)
37 | }
38 |
39 | function image(state) {
40 | return client.post('/image', { state }).then(res => res.data)
41 | }
42 |
43 | // ~ makes the file come later alphabetically, which is how gists are sorted
44 | const CARBON_STORAGE_KEY = '~carbon.json'
45 | function getGist(uid) {
46 | return gistClient
47 | .get(`/gists/${uid}`)
48 | .then(res => res.data)
49 | .then(({ owner, files }) => {
50 | let config
51 | if (files[CARBON_STORAGE_KEY]) {
52 | try {
53 | config = JSON.parse(files[CARBON_STORAGE_KEY].content)
54 | } catch (error) {
55 | // pass
56 | }
57 | }
58 |
59 | const otherFiles = Object.keys(files).filter(key => key !== CARBON_STORAGE_KEY)
60 |
61 | const snippet = files[otherFiles[0]]
62 |
63 | return {
64 | code: snippet.content,
65 | language: snippet.language,
66 | owner,
67 | config
68 | }
69 | })
70 | }
71 |
72 | // private
73 | function openTwitterUrl(twitterUrl) {
74 | const width = 575
75 | const height = 400
76 | const left = (window.outerWidth - width) / 2
77 | const top = (window.outerHeight - height) / 2
78 | const opts = `status=1,width=${width},height=${height},top=${top},left=${left}`
79 |
80 | window.open(twitterUrl, 'twitter', opts)
81 | }
82 |
83 | function checkIfRateLimited(err) {
84 | if (err.response.status === RATE_LIMIT_CODE) {
85 | alert(
86 | "Oh no! Looks like to many people are trying to tweet right now and we've been rate limited. Try again soon or save and upload manually!"
87 | )
88 | return
89 | }
90 |
91 | throw err
92 | }
93 |
94 | const downloadThumbnailImage = img => {
95 | return client
96 | .get(img.url.replace('http://', 'https://'), { responseType: 'blob' })
97 | .then(res => res.data)
98 | .then(fileToDataURL)
99 | .then(dataURL => Object.assign(img, { dataURL }))
100 | }
101 |
102 | const unsplash = {
103 | download(id) {
104 | return client
105 | .get(`/unsplash/download/${id}`)
106 | .then(res => res.data.url)
107 | .then(url => client.get(url, { responseType: 'blob' }))
108 | .then(res => res.data)
109 | },
110 | async random() {
111 | const imageUrls = await client.get('/unsplash/random')
112 | return Promise.all(imageUrls.data.map(downloadThumbnailImage))
113 | }
114 | }
115 |
116 | export default {
117 | gist: {
118 | get: getGist
119 | },
120 | tweet: debounce(tweet, ms('5s'), { leading: true, trailing: false }),
121 | image: debounce(image, ms('5s'), { leading: true, trailing: false }),
122 | unsplash,
123 | downloadThumbnailImage
124 | }
125 |
--------------------------------------------------------------------------------
/static/themes/one-dark.css:
--------------------------------------------------------------------------------
1 | /*
2 | Name: one-dark 1.1.1
3 | Author: Török Ádám (http://github.com/Aerobird98)
4 | Original Atom One Dark Theme (https://github.com/atom/one-dark-ui & https://github.com/atom/one-dark-syntax)
5 | */
6 | /* basic */
7 | .CodeMirror.cm-s-one-dark {
8 | font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
9 | font-weight: 350;
10 | font-size: 18px;
11 | color: #abb2bf;
12 | background-color: #282c34;
13 | }
14 | .cm-s-one-dark .CodeMirror-selected {background-color: #3e4451;}
15 | .cm-s-one-dark .CodeMirror-gutter,
16 | .cm-s-one-dark .CodeMirror-gutters {
17 | border: none;
18 | background-color: #282c34;
19 | }
20 | .cm-s-one-dark .CodeMirror-linenumber,
21 | .cm-s-one-dark .CodeMirror-linenumbers {
22 | color: #5c6370 !important;
23 | background-color: transparent;
24 | }
25 | .cm-s-one-dark .CodeMirror-lines {
26 | color: #abb2bf !important;
27 | background-color: transparent;
28 | }
29 | .cm-s-one-dark .CodeMirror-cursor {border-left: 2px solid #56b6c2 !important;}
30 | /* addon: edit/machingbrackets.js & addon: edit/matchtags.js */
31 | .cm-s-one-dark .CodeMirror-matchingbracket,
32 | .cm-s-one-dark .CodeMirror-matchingtag {
33 | border-bottom: 2px solid #56b6c2;
34 | color: #abb2bf !important;
35 | background-color: transparent;
36 | }
37 | .cm-s-one-dark .CodeMirror-nonmatchingbracket {
38 | border-bottom: 2px solid #e06c75;
39 | color: #abb2bf !important;
40 | background-color: transparent;
41 | }
42 | /* addon: fold/foldgutter.js */
43 | .cm-s-one-dark .CodeMirror-foldmarker,
44 | .cm-s-one-dark .CodeMirror-foldgutter,
45 | .cm-s-one-dark .CodeMirror-foldgutter-open,
46 | .cm-s-one-dark .CodeMirror-foldgutter-folded {
47 | border: none;
48 | text-shadow: none;
49 | color: #5c6370 !important;
50 | background-color: transparent;
51 | }
52 | /* addon: selection/active-line.js */
53 | .cm-s-one-dark .CodeMirror-activeline-background {background-color: rgba(153, 187, 255, 0.04);}
54 | /* basic syntax */
55 | .cm-s-one-dark .cm-header {color: #e06c75;}
56 | .cm-s-one-dark .cm-quote {color: #5c6370;font-style: italic;}
57 | .cm-s-one-dark .cm-negative {color: #e06c75;}
58 | .cm-s-one-dark .cm-positive {color: #e06c75;}
59 | .cm-s-one-dark .cm-strong {color: #d19a66;font-weight: bold;}
60 | .cm-s-one-dark .cm-header .cm-strong {color: #d19a66;font-weight: bold;}
61 | .cm-s-one-dark .cm-em {color: #c678dd;font-style: italic;}
62 | .cm-s-one-dark .cm-header .cm-em {color: #c678dd;font-style: italic;}
63 | .cm-s-one-dark .cm-tag {color: #e06c75;}
64 | .cm-s-one-dark .cm-attribute {color: #d19a66;}
65 | .cm-s-one-dark .cm-link {color: #98c379;border-bottom: solid 1px #98c379;}
66 | .cm-s-one-dark .cm-builtin {color: #e06c75;}
67 | .cm-s-one-dark .cm-keyword {color: #c678dd;}
68 | .cm-s-one-dark .cm-def {color: #e5c07b;} /* original: #d19a66; */
69 | .cm-s-one-dark .cm-atom {color: #d19a66;}
70 | .cm-s-one-dark .cm-number {color: #d19a66;}
71 | .cm-s-one-dark .cm-property {color: #56b6c2;} /* original: #abb2bf */
72 | .cm-s-one-dark .cm-qualifier {color: #d19a66;}
73 | .cm-s-one-dark .cm-variable {color: #e06c75;}
74 | .cm-s-one-dark .cm-string {color: #98c379;}
75 | .cm-s-one-dark .cm-punctuation {color: #abb2bf;}
76 | .cm-s-one-dark .cm-operator {color: #56b6c2;} /* original: #abb2bf */
77 | .cm-s-one-dark .cm-meta {color: #abb2bf;}
78 | .cm-s-one-dark .cm-bracket {color: #abb2bf;}
79 | .cm-s-one-dark .cm-comment {color: #5c6370;font-style: italic;}
80 | .cm-s-one-dark .cm-error {color: #e06c75;}
81 | /* css syntax corrections */
82 | .cm-s-one-dark .cm-m-css.cm-variable {color: #828997;}
83 | .cm-s-one-dark .cm-m-css.cm-property {color: #abb2bf;}
84 | .cm-s-one-dark .cm-m-css.cm-atom {color: #56b6c2;}
85 | .cm-s-one-dark .cm-m-css.cm-builtin {color: #56b6c2;}
86 | /* lua syntax corrections */
87 | .cm-s-one-dark .cm-m-lua.cm-variable {color: #56b6c2;}
--------------------------------------------------------------------------------
/static/themes/one-light.css:
--------------------------------------------------------------------------------
1 | /*
2 | Name: one-light 1.1.1
3 | Author: zhao zhang (http://github.com/loatheb)
4 | Original Atom One Light Theme (https://github.com/atom/one-light-ui & https://github.com/atom/one-light-syntax)
5 | */
6 | /* basic */
7 | .CodeMirror.cm-s-one-light {
8 | font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
9 | font-weight: 350;
10 | font-size: 18px;
11 | color: #383a42;
12 | background-color: #fafafa;
13 | }
14 |
15 | .cm-s-one-light .CodeMirror-selected {
16 | background-color: #c3c3c3;
17 | }
18 |
19 | .cm-s-one-light .CodeMirror-gutter,
20 | .cm-s-one-light .CodeMirror-gutters {
21 | border: none;
22 | background-color: #fafafa;
23 | }
24 |
25 | .cm-s-one-light .CodeMirror-linenumber,
26 | .cm-s-one-light .CodeMirror-linenumbers {
27 | color: #929292 !important;
28 | background-color: transparent;
29 | }
30 |
31 | .cm-s-one-light .CodeMirror-lines {
32 | color: #27292f !important;
33 | background-color: transparent;
34 | }
35 |
36 | .cm-s-one-light .CodeMirror-cursor {
37 | border-left: 2px solid #526fff !important;
38 | }
39 |
40 | /* basic syntax */
41 | .cm-s-one-light .cm-header {
42 | color: #e45649;
43 | }
44 |
45 | .cm-s-one-light .cm-quote {
46 | color: #a0a1a7;
47 | font-style: italic;
48 | }
49 |
50 | .cm-s-one-light .cm-negative {
51 | color: #e45649;
52 | }
53 |
54 | .cm-s-one-light .cm-positive {
55 | color: #e45649;
56 | }
57 |
58 | .cm-s-one-light .cm-strong {
59 | color: #986801;
60 | font-weight: bold;
61 | }
62 |
63 | .cm-s-one-light .cm-header .cm-strong {
64 | color: #986801;
65 | font-weight: bold;
66 | }
67 |
68 | .cm-s-one-light .cm-em {
69 | color: #a626a4;
70 | font-style: italic;
71 | }
72 |
73 | .cm-s-one-light .cm-header .cm-em {
74 | color: #a626a4;
75 | font-style: italic;
76 | }
77 |
78 | .cm-s-one-light .cm-tag {
79 | color: #e45649;
80 | }
81 |
82 | .cm-s-one-light .cm-attribute {
83 | color: #d19a66;
84 | }
85 |
86 | .cm-s-one-light .cm-link {
87 | color: #4078f2;
88 | border-bottom: solid 1px #4078f2;
89 | }
90 |
91 | .cm-s-one-light .cm-string.cm-url {
92 | color: #0184bc;
93 | border-bottom: solid 1px #0184bc;
94 | }
95 |
96 | .cm-s-one-light .cm-builtin {
97 | color: #e45649;
98 | }
99 |
100 | .cm-s-one-light .cm-keyword {
101 | color: #a626a4;
102 | }
103 |
104 | .cm-s-one-light .cm-def {
105 | color: #4078f2;
106 | }
107 |
108 | .cm-s-one-light .cm-atom {
109 | color: #d19a66;
110 | }
111 |
112 | .cm-s-one-light .cm-number {
113 | color: #986801;
114 | }
115 |
116 | .cm-s-one-light .cm-property {
117 | color: #4078f2;
118 | }
119 |
120 | .cm-s-one-light .cm-qualifier {
121 | color: #986801;
122 | }
123 |
124 | .cm-s-one-light .cm-variable {
125 | color: #e06c75;
126 | }
127 |
128 | .cm-s-one-light .cm-variable-2 {
129 | color: #e45649;
130 | }
131 |
132 | .cm-s-one-light .cm-string {
133 | color: #50a14f;
134 | }
135 |
136 | .cm-s-one-light .cm-punctuation {
137 | color: #383a42;
138 | }
139 |
140 | .cm-s-one-light .cm-operator {
141 | color: #0184bc;
142 | }
143 |
144 | .cm-s-one-light .cm-meta {
145 | color: #383a42;
146 | }
147 |
148 | .cm-s-one-light .cm-bracket {
149 | color: #383a42;
150 | }
151 |
152 | .cm-s-one-light .cm-comment {
153 | color: #a0a1a7;
154 | font-style: italic;
155 | }
156 |
157 | .cm-s-one-light .cm-error {
158 | color: #e06c75;
159 | }
160 |
161 | /* css syntax corrections */
162 | .cm-s-one-light .cm-m-css.cm-variable {
163 | color: #828997;
164 | }
165 |
166 | .cm-s-one-light .cm-m-css.cm-property {
167 | color: #383a42;
168 | }
169 |
170 | .cm-s-one-light .cm-m-css.cm-atom {
171 | color: #0184bc;
172 | }
173 |
174 | .cm-s-one-light .cm-m-css.cm-builtin {
175 | color: #56b6c2;
176 | }
177 |
--------------------------------------------------------------------------------
/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Page from '../components/Page'
3 |
4 | export default () => (
5 |
6 |
7 |
8 |
What does this do?
9 |
10 | You know all of those code screenshots you see on Twitter? Although the code's
11 | usually impressive, we saw room for improvement in the aesthetic department. Carbon is the
12 | easiest way to create beautiful images of your source code. So what are you waiting for?
13 | Go impress all of your followers with your newfound design prowess.{' '}
14 |
15 | 🎨
16 |
17 |
18 |
19 |
20 |
How do I use it?
21 |
Import
22 |
There are a few different ways to import code into Carbon:
23 |
24 | Drop a file into the editor
25 |
26 | Append a GitHub gist id to the url (
27 |
28 | example
29 |
30 | )
31 |
32 | Paste your code directly
33 |
34 |
Customization
35 |
36 | Once you've got all of your code into Carbon, you can customize your image by
37 | changing the syntax theme, background color/image, window theme, or padding.
38 |
39 |
40 | You can even drop an image file onto the editor to set the background to that image. Give
41 | it a try!
42 |
43 |
Export/Sharing
44 |
45 | After you've customized your image you can Tweet a link to the image, or save it
46 | directly.
47 |
48 |
49 | If you use the 'Tweet' button, Carbon will automatically make your image
50 | accessible. However, if you want to manually tweet your carbon image, please check out (
51 |
52 | how to make your Twitter images accessible
53 |
54 | ).
55 |
56 |
57 | If you include a Carbon image in a post, the source code will be invisible to assistive
58 | technology — it will not be possible to enlarge or copy it, etc. Please, think about
59 | adding another element with the source code as text, like (
60 |
64 | an HTML Details Element
65 |
66 | ) below the image.
67 |
68 |
69 |
77 |
78 |
103 |
104 | )
105 |
--------------------------------------------------------------------------------
/components/style/Typography.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default () => (
4 |
182 | )
183 |
--------------------------------------------------------------------------------
/lib/custom/modes/apache.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 | // Apache mode by gloony
4 |
5 | const CodeMirror = require('codemirror')
6 |
7 | CodeMirror.defineMode('apache', function(/* config */) {
8 | return {
9 | token: function(stream, state) {
10 | var sol = stream.sol() || state.afterSection
11 | var eol = stream.eol()
12 |
13 | state.afterSection = false
14 |
15 | if (sol) {
16 | if (state.nextMultiline) {
17 | state.inMultiline = true
18 | state.nextMultiline = false
19 | } else {
20 | state.position = 'def'
21 | }
22 | }
23 |
24 | if (eol && !state.nextMultiline) {
25 | state.inMultiline = false
26 | state.position = 'def'
27 | }
28 |
29 | if (sol) {
30 | while (stream.eatSpace()) {
31 | /* pass */
32 | }
33 | }
34 |
35 | var ch = stream.next()
36 |
37 | if (sol && ch === '#') {
38 | state.position = 'comment'
39 | stream.skipToEnd()
40 | return 'comment'
41 | } else if (ch === '!' && stream.peek() !== ' ') {
42 | return 'number'
43 | } else if (ch === ' ') {
44 | if (stream.peek() === '[') {
45 | if (stream.skipTo(']')) {
46 | stream.next()
47 | } else {
48 | stream.skipToEnd()
49 | }
50 | return 'keyword'
51 | } else if (stream.peek() === '(') {
52 | if (stream.skipTo(')')) {
53 | stream.next()
54 | } else {
55 | stream.skipToEnd()
56 | }
57 | return 'string'
58 | } else {
59 | state.position = 'unit'
60 | return 'unit'
61 | }
62 | } else if (ch === '"') {
63 | if (stream.skipTo('"')) {
64 | stream.next()
65 | } else {
66 | stream.skipToEnd()
67 | }
68 | return 'quote'
69 | } else if (sol && ch === '<') {
70 | if (stream.skipTo('>')) {
71 | stream.next()
72 | } else {
73 | stream.skipToEnd()
74 | }
75 | return 'meta'
76 | } else if (ch === '%') {
77 | if (stream.peek() === '{') {
78 | if (stream.skipTo('}')) {
79 | stream.next()
80 | } else {
81 | stream.skipToEnd()
82 | }
83 | return 'operator'
84 | }
85 | } else if (ch === '$') {
86 | if (!isNaN(stream.peek()) && stream.peek() !== ' ') {
87 | while (!isNaN(stream.peek()) && stream.peek() !== ' ') {
88 | stream.next()
89 | }
90 | return 'operator'
91 | }
92 | } else if (ch === '\\') {
93 | if (stream.peek() === '.') {
94 | if (stream.skipTo(' ')) {
95 | stream.next()
96 | } else {
97 | stream.skipToEnd()
98 | }
99 | return 'string'
100 | }
101 | } else if (ch === '.') {
102 | if (stream.peek() === '*') {
103 | if (stream.skipTo(' ')) {
104 | stream.next()
105 | } else {
106 | stream.skipToEnd()
107 | }
108 | return 'string'
109 | }
110 | } else if (ch === '^') {
111 | if (stream.skipTo(' ')) {
112 | stream.next()
113 | } else {
114 | stream.skipToEnd()
115 | }
116 | return 'string'
117 | }
118 |
119 | return state.position
120 | },
121 |
122 | // electricInput: /<\/[\s\w:]+>$/,
123 | lineComment: '#',
124 | fold: 'brace',
125 |
126 | startState: function() {
127 | return {
128 | position: 'def',
129 | nextMultiline: false,
130 | inMultiline: false,
131 | afterSection: false
132 | }
133 | }
134 | }
135 | })
136 |
137 | CodeMirror.defineMIME('text/apache', 'apache')
138 | CodeMirror.defineMIME('text/htaccess', 'apache')
139 |
--------------------------------------------------------------------------------
/components/svg/WindowThemes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const Sharp = () => (
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
36 |
46 |
47 |
48 |
49 |
50 | )
51 |
52 | export const BW = () => (
53 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 |
84 | export const None = () => (
85 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | )
114 |
--------------------------------------------------------------------------------
/static/react-crop.css:
--------------------------------------------------------------------------------
1 | .ReactCrop .ord-s,.ReactCrop .ord-se,.ReactCrop .ord-sw{margin-bottom:-5px;bottom:0}.ReactCrop{position:relative;display:inline-block;cursor:crosshair;overflow:hidden;max-width:100%;background-color:#000}.ReactCrop:focus{outline:0}.ReactCrop--disabled{cursor:inherit}.ReactCrop__image{display:block;max-width:100%}.ReactCrop--crop-invisible .ReactCrop__image{opacity:.5}.ReactCrop__crop-selection{position:absolute;top:0;left:0;transform:translate3d(0,0,0);box-sizing:border-box;cursor:move;box-shadow:0 0 0 9999em rgba(0,0,0,.5);border:1px solid;border-image-source:url(data:image/gif;base64,R0lGODlhCgAKAJECAAAAAP///////wAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEI5RDc5MTFDNkE2MTFFM0JCMDZEODI2QTI4MzJBOTIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEI5RDc5MTBDNkE2MTFFM0JCMDZEODI2QTI4MzJBOTIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuZGlkOjAyODAxMTc0MDcyMDY4MTE4MDgzQzNDMjA5MzREQ0ZDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAyODAxMTc0MDcyMDY4MTE4MDgzQzNDMjA5MzREQ0ZDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEBQoAAgAsAAAAAAoACgAAAhWEERkn7W3ei7KlagMWF/dKgYeyGAUAIfkEBQoAAgAsAAAAAAoACgAAAg+UYwLJ7RnQm7QmsCyVKhUAIfkEBQoAAgAsAAAAAAoACgAAAhCUYgLJHdiinNSAVfOEKoUCACH5BAUKAAIALAAAAAAKAAoAAAIRVISAdusPo3RAzYtjaMIaUQAAIfkEBQoAAgAsAAAAAAoACgAAAg+MDiem7Q8bSLFaG5il6xQAIfkEBQoAAgAsAAAAAAoACgAAAg+UYRLJ7QnQm7SmsCyVKhUAIfkEBQoAAgAsAAAAAAoACgAAAhCUYBLJDdiinNSEVfOEKoECACH5BAUKAAIALAAAAAAKAAoAAAIRFISBdusPo3RBzYsjaMIaUQAAOw==);border-image-slice:1;border-image-repeat:repeat}.ReactCrop--disabled .ReactCrop__crop-selection{cursor:inherit}.ReactCrop__drag-handle{position:absolute;width:9px;height:9px;background-color:rgba(0,0,0,.2);border:1px solid rgba(255,255,255,.7);box-sizing:border-box;outline:transparent solid 1px}.ReactCrop .ord-nw{top:0;left:0;margin-top:-5px;margin-left:-5px;cursor:nw-resize}.ReactCrop .ord-n{top:0;left:50%;margin-top:-5px;margin-left:-5px;cursor:n-resize}.ReactCrop .ord-ne{top:0;right:0;margin-top:-5px;margin-right:-5px;cursor:ne-resize}.ReactCrop .ord-e{top:50%;right:0;margin-top:-5px;margin-right:-5px;cursor:e-resize}.ReactCrop .ord-se{right:0;margin-right:-5px;cursor:se-resize}.ReactCrop .ord-s{left:50%;margin-left:-5px;cursor:s-resize}.ReactCrop .ord-sw{left:0;margin-left:-5px;cursor:sw-resize}.ReactCrop .ord-w{top:50%;left:0;margin-top:-5px;margin-left:-5px;cursor:w-resize}.ReactCrop__disabled .ReactCrop__drag-handle{cursor:inherit}.ReactCrop__drag-bar{position:absolute}.ReactCrop__drag-bar.ord-n{top:0;left:0;width:100%;height:6px;margin-top:-3px}.ReactCrop__drag-bar.ord-e{right:0;top:0;width:6px;height:100%;margin-right:-3px}.ReactCrop__drag-bar.ord-s{bottom:0;left:0;width:100%;height:6px;margin-bottom:-3px}.ReactCrop__drag-bar.ord-w{top:0;left:0;width:6px;height:100%;margin-left:-3px}.ReactCrop--fixed-aspect .ReactCrop__drag-bar,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,.ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w,.ReactCrop--new-crop .ReactCrop__drag-bar,.ReactCrop--new-crop .ReactCrop__drag-handle{display:none}@media (max-width:768px){.ReactCrop .ord-n,.ReactCrop .ord-nw,.ReactCrop .ord-w{margin-top:-9px;margin-left:-9px}.ReactCrop__drag-handle{width:17px;height:17px}.ReactCrop .ord-e,.ReactCrop .ord-ne{margin-top:-9px;margin-right:-9px}.ReactCrop .ord-se{margin-bottom:-9px;margin-right:-9px}.ReactCrop .ord-s,.ReactCrop .ord-sw{margin-bottom:-9px;margin-left:-9px}.ReactCrop__drag-bar.ord-n{height:14px;margin-top:-7px}.ReactCrop__drag-bar.ord-e{width:14px;margin-right:-7px}.ReactCrop__drag-bar.ord-s{height:14px;margin-bottom:-7px}.ReactCrop__drag-bar.ord-w{width:14px;margin-left:-7px}}
2 |
--------------------------------------------------------------------------------
/components/BackgroundSelect.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { escape } from 'escape-goat'
3 |
4 | import ImagePicker from './ImagePicker'
5 | import ColorPicker from './ColorPicker'
6 | import Popout, { managePopout } from './Popout'
7 | import { COLORS, DEFAULT_BG_COLOR } from '../lib/constants'
8 | import { validateColor } from '../lib/colors'
9 | import { capitalize, stringifyRGBA } from '../lib/util'
10 |
11 | class BackgroundSelect extends React.PureComponent {
12 | selectTab = name => {
13 | if (this.props.mode !== name) {
14 | this.props.onChange({ backgroundMode: name })
15 | }
16 | }
17 |
18 | handlePickColor = ({ rgb }) => this.props.onChange({ backgroundColor: stringifyRGBA(rgb) })
19 |
20 | render() {
21 | const { color, mode, image, onChange, aspectRatio, isVisible, toggleVisibility } = this.props
22 |
23 | let background = typeof color === 'string' ? escape(color).replace(/\//g, '/') : color
24 |
25 | if (!validateColor(background)) {
26 | background = DEFAULT_BG_COLOR
27 | }
28 |
29 | return (
30 |
31 |
37 |
43 |
44 | {['color', 'image'].map(tab => (
45 |
52 | {capitalize(tab)}
53 |
54 | ))}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
137 |
138 | )
139 | }
140 | }
141 |
142 | export default managePopout(BackgroundSelect)
143 |
--------------------------------------------------------------------------------
/components/Themes/ThemeCreate.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Input from '../Input'
4 | import Button from '../Button'
5 | import ListSetting from '../ListSetting'
6 | import Popout from '../Popout'
7 | import ColorPicker from '../ColorPicker'
8 | import { HIGHLIGHT_KEYS, COLORS } from '../../lib/constants'
9 | import { capitalize } from '../../lib/util'
10 |
11 | const colorPickerStyle = {
12 | backgroundColor: COLORS.BLACK,
13 | padding: 0,
14 | margin: '4px'
15 | }
16 |
17 | const colorPresets = []
18 |
19 | const HighlightPicker = ({ title, onChange, color }) => (
20 |
21 |
22 | {title}
23 |
24 |
30 |
47 |
48 | )
49 |
50 | const ThemeCreate = ({
51 | theme,
52 | themes,
53 | highlights,
54 | name,
55 | preset,
56 | selected,
57 | createTheme,
58 | applyPreset,
59 | updateName,
60 | selectHighlight,
61 | updateHighlight
62 | }) => (
63 |
64 |
65 |
66 | Name
67 |
75 |
76 |
77 |
84 | {({ name }) => {name} }
85 |
86 |
87 |
88 | {HIGHLIGHT_KEYS.map(key => (
89 |
90 |
95 |
96 | {capitalize(key)}
97 |
104 |
105 |
106 |
107 | ))}
108 |
109 |
119 | Create +
120 |
121 |
122 | {selected && (
123 |
128 | )}
129 |
186 |
187 | )
188 |
189 | export default ThemeCreate
190 |
--------------------------------------------------------------------------------
/components/style/Reset.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { COLORS } from '../../lib/constants'
3 |
4 | export default () => (
5 |
248 | )
249 |
--------------------------------------------------------------------------------
/components/Presets.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Button from './Button'
4 | import { COLORS, DEFAULT_PRESETS } from '../lib/constants'
5 | import * as Arrows from './svg/Arrows'
6 | import Remove from './svg/Remove'
7 |
8 | const removeButtonStyles = {
9 | position: 'absolute',
10 | top: '6px',
11 | right: '6px',
12 | width: '11px',
13 | height: '11px',
14 | borderRadius: '999px'
15 | }
16 |
17 | const Preset = React.memo(({ remove, apply, selected, preset }) => {
18 | const isSelected = preset.id === selected
19 |
20 | return (
21 |
22 | apply(preset)}
24 | disabled={isSelected}
25 | border={isSelected}
26 | selected={isSelected}
27 | hoverBackground={preset.backgroundColor}
28 | style={{
29 | height: '96px',
30 | borderRadius: '3px',
31 | backgroundRepeat: 'no-repeat',
32 | backgroundPosition: 'center center',
33 | backgroundSize: 'contain',
34 | backgroundImage: `url('${preset.icon}')`,
35 | backgroundColor: preset.backgroundColor
36 | }}
37 | />
38 | {preset.custom && (
39 | remove(preset.id)}
44 | style={removeButtonStyles}
45 | >
46 |
47 |
48 | )}
49 |
59 |
60 | )
61 | })
62 |
63 | const arrowButtonStyle = {
64 | position: 'absolute',
65 | top: 0,
66 | right: '16px',
67 | height: '100%'
68 | }
69 |
70 | const Presets = React.memo(
71 | ({ show, create, toggle, undo, presets, selected, remove, apply, applied, contentRef }) => {
72 | const customPresetsLength = presets.length - DEFAULT_PRESETS.length
73 |
74 | const disabledCreate = selected != null
75 |
76 | return (
77 |
78 |
79 |
Presets
80 | {show && (
81 |
90 | create +
91 |
92 | )}
93 |
94 | {show ? : }
95 |
96 |
97 | {show && (
98 |
99 | {presets
100 | .filter(p => p.custom)
101 | .map(preset => (
102 |
109 | ))}
110 | {customPresetsLength > 0 ?
: null}
111 | {presets
112 | .filter(p => !p.custom)
113 | .map(preset => (
114 |
115 | ))}
116 |
117 | )}
118 | {show && applied && (
119 |
120 | Preset applied!
121 |
129 | undo ↩
130 |
131 |
132 | )}
133 |
180 |
181 | )
182 | }
183 | )
184 |
185 | export default Presets
186 |
--------------------------------------------------------------------------------
/components/style/Font.js:
--------------------------------------------------------------------------------
1 | /*
2 | * See https://developers.google.com/web/updates/2016/02/font-display and
3 | * https://css-tricks.com/font-display-masses/#article-header-id-2
4 | * for `font-display` information
5 | */
6 | import React from 'react'
7 |
8 | export default () => (
9 |
157 | )
158 |
--------------------------------------------------------------------------------
/components/ExportMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withRouter } from 'next/router'
3 | import { useCopyTextHandler, useOnline } from '@dawnlabs/tacklebox'
4 |
5 | import { COLORS, EXPORT_SIZES } from '../lib/constants'
6 | import Button from './Button'
7 | import Input from './Input'
8 | import Popout, { managePopout } from './Popout'
9 |
10 | const toIFrame = url =>
11 | `
16 | `
17 |
18 | const MAX_PAYLOAD_SIZE = 5e6 // bytes
19 | function verifyPayloadSize(str) {
20 | if (typeof str !== 'string') return true
21 |
22 | return new Blob([str]).size < MAX_PAYLOAD_SIZE
23 | }
24 |
25 | const CopyEmbed = withRouter(
26 | React.memo(
27 | ({ router: { asPath } }) => {
28 | const { onClick, copied } = useCopyTextHandler(toIFrame(asPath))
29 |
30 | return (
31 |
32 | {copied ? 'Copied!' : 'Copy Embed'}
33 |
34 | )
35 | },
36 | (prevProps, nextProps) => prevProps.router.asPath === nextProps.router.asPath
37 | )
38 | )
39 |
40 | const popoutStyle = { width: '280px', right: 0 }
41 |
42 | class ExportMenu extends React.PureComponent {
43 | handleInputChange = ({ target: { name, value } }) => this.props.onChange(name, value)
44 |
45 | handleExportSizeChange = selectedSize => () => this.props.onChange('exportSize', selectedSize)
46 |
47 | handleExport = format => () => this.props.export(format)
48 |
49 | render() {
50 | const { exportSize, filename, isVisible, toggleVisibility, disablePNG } = this.props
51 |
52 | return (
53 |
181 | )
182 | }
183 | }
184 |
185 | export default managePopout(function({ backgroundImage, ...props }) {
186 | const tooLarge = React.useMemo(() => !verifyPayloadSize(backgroundImage), [backgroundImage])
187 | const online = useOnline()
188 |
189 | const [isSafari, setSafari] = React.useState(false)
190 | React.useEffect(() => {
191 | setSafari(
192 | window.navigator &&
193 | window.navigator.userAgent.indexOf('Safari') !== -1 &&
194 | window.navigator.userAgent.indexOf('Chrome') === -1
195 | )
196 | }, [])
197 |
198 | const disablePNG = isSafari && (tooLarge || !online)
199 |
200 | return
201 | })
202 |
--------------------------------------------------------------------------------
/components/Themes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import dynamic from 'next/dynamic'
3 |
4 | import Dropdown from '../Dropdown'
5 | import { managePopout } from '../Popout'
6 | import ThemeIcon from '../svg/Theme'
7 | import RemoveIcon from '../svg/Remove'
8 | import { THEMES, COLORS, DEFAULT_THEME } from '../../lib/constants'
9 | import { getThemes, saveThemes, stringifyRGBA, generateId } from '../../lib/util'
10 |
11 | const ThemeCreate = dynamic(() => import('./ThemeCreate'), {
12 | loading: () => null
13 | })
14 |
15 | const ThemeItem = ({ children, item, isSelected, onClick }) => (
16 |
17 | {children}
18 | {item.custom && !isSelected && (
19 |
20 |
21 |
22 | )}
23 |
38 |
39 | )
40 |
41 | const themeIcon =
42 |
43 | class Themes extends React.PureComponent {
44 | selectedTheme = DEFAULT_THEME
45 |
46 | state = {
47 | themes: THEMES,
48 | preset: this.props.theme,
49 | highlights: {},
50 | name: 'Custom Theme',
51 | selected: null
52 | }
53 |
54 | dropdown = React.createRef()
55 |
56 | componentDidMount() {
57 | const storedThemes = getThemes(localStorage) || []
58 |
59 | this.setState(({ themes }) => {
60 | const newThemes = [...storedThemes, ...themes]
61 |
62 | const name = `Custom Theme ${newThemes.filter(({ name }) => name.startsWith('Custom Theme'))
63 | .length + 1}`
64 |
65 | this.selectedTheme = newThemes.find(({ id }) => id === this.props.theme) || DEFAULT_THEME
66 |
67 | return {
68 | themes: newThemes,
69 | highlights: this.selectedTheme.highlights,
70 | name
71 | }
72 | })
73 | }
74 |
75 | applyPreset = preset =>
76 | this.setState(({ themes }) => ({
77 | preset,
78 | highlights: themes.find(({ id }) => id === preset).highlights
79 | }))
80 |
81 | handleChange = ({ id }) => {
82 | if (id === 'create') {
83 | this.props.toggleVisibility()
84 | this.dropdown.current.closeMenu()
85 | } else {
86 | this.props.updateTheme(id)
87 | }
88 | }
89 |
90 | updateName = ({ target: { value: name } }) => this.setState({ name })
91 |
92 | selectHighlight = highlight => () =>
93 | this.setState(({ selected }) => ({
94 | selected: selected === highlight ? null : highlight
95 | }))
96 |
97 | updateHighlight = ({ rgb }) =>
98 | this.setState({
99 | highlights: {
100 | ...this.state.highlights,
101 | [this.state.selected]: stringifyRGBA(rgb)
102 | }
103 | })
104 |
105 | removeTheme = id => event => {
106 | event.stopPropagation()
107 |
108 | const { themes } = this.state
109 |
110 | const newThemes = themes.filter(t => t.id !== id)
111 |
112 | saveThemes(localStorage, newThemes.filter(({ custom }) => custom))
113 |
114 | if (this.props.theme === id) {
115 | this.props.updateTheme(DEFAULT_THEME.id)
116 | } else {
117 | this.setState({ themes: newThemes })
118 | }
119 | }
120 |
121 | createTheme = () => {
122 | const { themes, name, highlights } = this.state
123 |
124 | const id = `theme:${generateId()}`
125 |
126 | const newTheme = {
127 | id,
128 | name,
129 | highlights,
130 | custom: true
131 | }
132 |
133 | const customThemes = [newTheme, ...themes.filter(({ custom }) => custom)]
134 |
135 | saveThemes(localStorage, customThemes)
136 |
137 | this.props.updateTheme(id)
138 | }
139 |
140 | itemWrapper = props =>
141 |
142 | render() {
143 | const { theme, isVisible, toggleVisibility } = this.props
144 | const { name, themes, highlights, selected, preset } = this.state
145 |
146 | const dropdownValue = isVisible ? { name } : { id: theme, name: this.selectedTheme.name }
147 |
148 | const dropdownList = [
149 | {
150 | id: 'create',
151 | name: 'Create +'
152 | },
153 | ...themes
154 | ]
155 |
156 | return (
157 |
158 |
169 | {isVisible && (
170 |
184 | )}
185 |
253 |
254 | )
255 | }
256 | }
257 |
258 | export default managePopout(Themes)
259 |
--------------------------------------------------------------------------------
/components/Carbon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import dynamic from 'next/dynamic'
3 | import * as hljs from 'highlight.js'
4 | import debounce from 'lodash.debounce'
5 | import ms from 'ms'
6 | import { Controlled as CodeMirror } from 'react-codemirror2'
7 | import SpinnerWrapper from './SpinnerWrapper'
8 |
9 | import WindowControls from './WindowControls'
10 | import {
11 | COLORS,
12 | LANGUAGE_MODE_HASH,
13 | LANGUAGE_NAME_HASH,
14 | THEMES_HASH,
15 | DEFAULT_SETTINGS
16 | } from '../lib/constants'
17 |
18 | const Watermark = dynamic(() => import('./svg/Watermark'), {
19 | loading: () => null
20 | })
21 |
22 | class Carbon extends React.PureComponent {
23 | static defaultProps = {
24 | onAspectRatioChange: () => {},
25 | onChange: () => {}
26 | }
27 |
28 | componentDidMount() {
29 | const node = this.props.innerRef.current
30 | this.mo = new MutationObserver(() => {
31 | const ratio = node.clientWidth / node.clientHeight
32 | this.props.onAspectRatioChange(ratio)
33 | })
34 | this.mo.observe(node, { attributes: true, childList: true, subtree: true })
35 | }
36 |
37 | componentWillUnmount() {
38 | this.mo.disconnect()
39 | }
40 |
41 | handleLanguageChange = debounce(
42 | (newCode, language) => {
43 | if (language === 'auto') {
44 | // try to set the language
45 | const detectedLanguage = hljs.highlightAuto(newCode).language
46 | const languageMode =
47 | LANGUAGE_MODE_HASH[detectedLanguage] || LANGUAGE_NAME_HASH[detectedLanguage]
48 |
49 | if (languageMode) {
50 | return languageMode.mime || languageMode.mode
51 | }
52 | }
53 |
54 | return language
55 | },
56 | ms('300ms'),
57 | {
58 | leading: true,
59 | trailing: true
60 | }
61 | )
62 |
63 | onBeforeChange = (editor, meta, code) => {
64 | if (!this.props.readOnly) {
65 | this.props.onChange(code)
66 | }
67 | }
68 |
69 | render() {
70 | const config = { ...DEFAULT_SETTINGS, ...this.props.config }
71 |
72 | const languageMode = this.handleLanguageChange(this.props.children, config.language)
73 |
74 | const options = {
75 | lineNumbers: config.lineNumbers,
76 | mode: languageMode || 'plaintext',
77 | theme: config.theme,
78 | scrollBarStyle: null,
79 | viewportMargin: Infinity,
80 | lineWrapping: true,
81 | extraKeys: {
82 | 'Shift-Tab': 'indentLess'
83 | },
84 | // negative values removes the cursor, undefined means default (530)
85 | cursorBlinkRate: this.props.readOnly ? -1 : undefined,
86 | // needs to be able to refresh every 16ms to hit 60 frames / second
87 | pollInterval: 16
88 | }
89 | const backgroundImage =
90 | (this.props.config.backgroundImage && this.props.config.backgroundImageSelection) ||
91 | this.props.config.backgroundImage
92 |
93 | const content = (
94 |
95 | {config.windowControls ? (
96 |
101 | ) : null}
102 |
108 | {config.watermark &&
}
109 |
114 |
219 |
220 | )
221 |
222 | return (
223 |
224 |
225 |
{content}
226 |
227 |
228 |
248 |
249 | )
250 | }
251 | }
252 |
253 | export default React.forwardRef((props, ref) => )
254 |
--------------------------------------------------------------------------------
/components/Dropdown.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Downshift from 'downshift'
3 | import matchSorter from 'match-sorter'
4 | import { Down as ArrowDown } from './svg/Arrows'
5 | import CheckMark from './svg/Checkmark'
6 | import { COLORS } from '../lib/constants'
7 |
8 | class Dropdown extends React.PureComponent {
9 | state = {
10 | inputValue: this.props.selected.name,
11 | itemsToShow: this.props.list
12 | }
13 |
14 | onUserAction = changes => {
15 | this.setState(({ inputValue, itemsToShow }) => {
16 | if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) {
17 | if (changes.type === Downshift.stateChangeTypes.keyDownEscape) {
18 | inputValue = this.userInputtedValue
19 | } else if (changes.type === Downshift.stateChangeTypes.changeInput) {
20 | inputValue = changes.inputValue
21 | this.userInputtedValue = changes.inputValue
22 | } else {
23 | inputValue = changes.inputValue
24 | }
25 | }
26 |
27 | itemsToShow = this.userInputtedValue
28 | ? matchSorter(this.props.list, this.userInputtedValue, { keys: ['name'] })
29 | : this.props.list
30 |
31 | if (
32 | Object.prototype.hasOwnProperty.call(changes, 'highlightedIndex') &&
33 | (changes.type === Downshift.stateChangeTypes.keyDownArrowUp ||
34 | changes.type === Downshift.stateChangeTypes.keyDownArrowDown)
35 | ) {
36 | inputValue = itemsToShow[changes.highlightedIndex].name
37 | this.props.onChange(itemsToShow[changes.highlightedIndex])
38 | }
39 |
40 | if (Object.prototype.hasOwnProperty.call(changes, 'isOpen')) {
41 | this.userInputtedValue = ''
42 |
43 | // clear on open
44 | if (changes.isOpen) {
45 | inputValue = ''
46 | this.props.onOpen && this.props.onOpen()
47 | } else if (changes.isOpen === false && !inputValue) {
48 | // set on close
49 | inputValue = this.props.selected.name
50 | }
51 | }
52 |
53 | return { inputValue, itemsToShow }
54 | })
55 | }
56 |
57 | userInputtedValue = ''
58 |
59 | render() {
60 | const { innerRef, color, selected, onChange, itemWrapper, icon, disableInput } = this.props
61 | const { itemsToShow, inputValue } = this.state
62 |
63 | const minWidth = calcMinWidth(itemsToShow)
64 |
65 | return (
66 | it === selected)}
71 | itemToString={item => item.name}
72 | onChange={onChange}
73 | onUserAction={this.onUserAction}
74 | >
75 | {renderDropdown({
76 | color,
77 | list: itemsToShow,
78 | selected,
79 | minWidth,
80 | itemWrapper,
81 | icon,
82 | disableInput
83 | })}
84 |
85 | )
86 | }
87 | }
88 |
89 | const renderDropdown = ({ color, list, minWidth, itemWrapper, icon, disableInput }) => ({
90 | isOpen,
91 | highlightedIndex,
92 | selectedItem,
93 | getRootProps,
94 | getToggleButtonProps,
95 | getInputProps,
96 | getItemProps
97 | }) => {
98 | return (
99 |
100 | {icon}
101 |
109 | {selectedItem.name}
110 |
111 | {isOpen ? (
112 |
113 | {list.map((item, index) => (
114 |
125 | {item.name}
126 |
127 | ))}
128 |
129 | ) : null}
130 |
131 | )
132 | }
133 |
134 | const DropdownContainer = ({ children, innerRef, minWidth, ...rest }) => {
135 | return (
136 |
137 | {children}
138 |
149 |
150 | )
151 | }
152 |
153 | const DropdownIcon = ({ children, isOpen }) => {
154 | if (children) {
155 | return (
156 |
157 | {children}
158 |
174 |
175 | )
176 | } else {
177 | return null
178 | }
179 | }
180 |
181 | const SelectedItem = ({
182 | getToggleButtonProps,
183 | getInputProps,
184 | children,
185 | isOpen,
186 | color,
187 | hasIcon,
188 | disabled
189 | }) => {
190 | const itemColor = color || COLORS.SECONDARY
191 |
192 | return (
193 |
197 |
201 |
204 |
243 |
244 | )
245 | }
246 |
247 | const ListItems = ({ children, color }) => {
248 | return (
249 |
250 | {children}
251 |
262 |
263 | )
264 | }
265 |
266 | const ListItem = ({ children, color, isHighlighted, isSelected, itemWrapper, item, ...rest }) => {
267 | const itemColor = color || COLORS.SECONDARY
268 |
269 | return (
270 |
271 | {itemWrapper ? (
272 | itemWrapper({ children, color: itemColor, item, isSelected })
273 | ) : (
274 | {children}
275 | )}
276 | {isSelected ? : null}
277 |
300 |
301 | )
302 | }
303 |
304 | function calcMinWidth(items) {
305 | return items.reduce((max, { name }) => {
306 | const wordSize = name.length * 10 + 32
307 | return wordSize > max ? wordSize : max
308 | }, 0)
309 | }
310 |
311 | export default Dropdown
312 |
--------------------------------------------------------------------------------
/components/ImagePicker.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactCrop, { makeAspectCrop } from 'react-image-crop'
3 |
4 | import RandomImage from './RandomImage'
5 | import PhotoCredit from './PhotoCredit'
6 | import Input from './Input'
7 | import { Link } from './Meta'
8 | import { fileToDataURL } from '../lib/util'
9 | import ApiContext from './ApiContext'
10 |
11 | const getCroppedImg = (imageDataURL, pixelCrop) => {
12 | const canvas = document.createElement('canvas')
13 | canvas.width = pixelCrop.width
14 | canvas.height = pixelCrop.height
15 | const ctx = canvas.getContext('2d')
16 |
17 | return new Promise(resolve => {
18 | const image = new Image()
19 | image.src = imageDataURL
20 | image.onload = () => {
21 | ctx.drawImage(
22 | image,
23 | pixelCrop.x,
24 | pixelCrop.y,
25 | pixelCrop.width,
26 | pixelCrop.height,
27 | 0,
28 | 0,
29 | pixelCrop.width,
30 | pixelCrop.height
31 | )
32 |
33 | resolve(canvas.toDataURL('image/jpeg'))
34 | }
35 | })
36 | }
37 |
38 | const INITIAL_STATE = {
39 | mode: 'file',
40 | crop: null,
41 | imageAspectRatio: null,
42 | pixelCrop: null,
43 | photographer: null
44 | }
45 |
46 | export default class ImagePicker extends React.Component {
47 | static contextType = ApiContext
48 | constructor(props) {
49 | super(props)
50 | this.state = INITIAL_STATE
51 | this.handleURLInput = this.handleURLInput.bind(this)
52 | this.selectImage = this.selectImage.bind(this)
53 | this.removeImage = this.removeImage.bind(this)
54 | this.onImageLoaded = this.onImageLoaded.bind(this)
55 | this.onCropChange = this.onCropChange.bind(this)
56 | this.onDragEnd = this.onDragEnd.bind(this)
57 | this.selectMode = this.selectMode.bind(this)
58 | }
59 |
60 | static getDerivedStateFromProps(nextProps, state) {
61 | if (state.crop) {
62 | // update crop for editor container aspect-ratio change
63 | return {
64 | crop: makeAspectCrop(
65 | {
66 | ...state.crop,
67 | aspect: nextProps.aspectRatio
68 | },
69 | state.imageAspectRatio
70 | )
71 | }
72 | }
73 | return null
74 | }
75 |
76 | async onDragEnd() {
77 | if (this.state.pixelCrop) {
78 | const croppedImg = await getCroppedImg(this.props.imageDataURL, this.state.pixelCrop)
79 | this.props.onChange({ backgroundImageSelection: croppedImg })
80 | }
81 | }
82 |
83 | onCropChange(crop, pixelCrop) {
84 | this.setState({
85 | crop: { ...crop, aspect: this.props.aspectRatio },
86 | pixelCrop
87 | })
88 | }
89 |
90 | onImageLoaded(image) {
91 | const imageAspectRatio = image.width / image.height
92 | const initialCrop = {
93 | x: 0,
94 | y: 0,
95 | width: 100,
96 | aspect: this.props.aspectRatio
97 | }
98 |
99 | this.setState({
100 | imageAspectRatio,
101 | crop: makeAspectCrop(initialCrop, imageAspectRatio)
102 | })
103 | }
104 |
105 | handleURLInput(e) {
106 | e.preventDefault()
107 | const url = e.target[0].value
108 | return this.context
109 | .downloadThumbnailImage({ url })
110 | .then(({ dataURL }) =>
111 | this.props.onChange({
112 | backgroundImage: dataURL,
113 | backgroundImageSelection: null,
114 | photographer: null
115 | })
116 | )
117 | .catch(err => {
118 | if (err.message.indexOf('Network Error') > -1) {
119 | this.setState({
120 | error:
121 | 'Fetching the image failed. This is probably a CORS-related issue. You can either enable CORS in your browser, or use another image.'
122 | })
123 | }
124 | })
125 | }
126 |
127 | selectMode(mode) {
128 | this.setState({ mode })
129 | }
130 |
131 | selectImage(e, { photographer } = {}) {
132 | const file = e.target ? e.target.files[0] : e
133 |
134 | return fileToDataURL(file).then(dataURL =>
135 | this.setState({ photographer }, () => {
136 | this.props.onChange({
137 | backgroundImage: dataURL,
138 | backgroundImageSelection: null,
139 | photographer
140 | })
141 | })
142 | )
143 | }
144 |
145 | removeImage() {
146 | this.setState(INITIAL_STATE, () => {
147 | this.props.onChange({
148 | backgroundImage: null,
149 | backgroundImageSelection: null
150 | })
151 | })
152 | }
153 |
154 | render() {
155 | let content = (
156 |
157 |
158 | Upload a background image:
159 |
163 | File
164 |
165 |
169 | URL
170 |
171 | {this.state.mode === 'file' ? (
172 |
177 | ) : (
178 |
182 | )}
183 | {this.state.error && {this.state.error} }
184 |
185 |
186 |
187 |
188 | Or use a random Unsplash image:
189 |
190 |
191 |
192 |
247 |
248 | )
249 |
250 | if (this.props.imageDataURL) {
251 | content = (
252 |
253 |
254 |
255 | Background image
256 | ×
257 |
258 |
268 | {this.state.photographer &&
}
269 |
270 |
309 |
310 | )
311 | }
312 |
313 | return (
314 |
315 |
316 | {content}
317 |
324 |
325 | )
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/lib/colors.js:
--------------------------------------------------------------------------------
1 | export const colors = new Set([
2 | 'indian red',
3 | 'crimson',
4 | 'lightpink',
5 | 'lightpink 1',
6 | 'lightpink 2',
7 | 'lightpink 3',
8 | 'lightpink 4',
9 | 'pink',
10 | 'pink 1',
11 | 'pink 2',
12 | 'pink 3',
13 | 'pink 4',
14 | 'palevioletred',
15 | 'palevioletred 1',
16 | 'palevioletred 2',
17 | 'palevioletred 3',
18 | 'palevioletred 4',
19 | 'lavenderblush 1',
20 | 'lavenderblush',
21 | 'lavenderblush 2',
22 | 'lavenderblush 3',
23 | 'lavenderblush 4',
24 | 'violetred 1',
25 | 'violetred 2',
26 | 'violetred 3',
27 | 'violetred 4',
28 | 'hotpink',
29 | 'hotpink 1',
30 | 'hotpink 2',
31 | 'hotpink 3',
32 | 'hotpink 4',
33 | 'raspberry',
34 | 'deeppink 1',
35 | 'deeppink',
36 | 'deeppink 2',
37 | 'deeppink 3',
38 | 'deeppink 4',
39 | 'maroon 1',
40 | 'maroon 2',
41 | 'maroon 3',
42 | 'maroon 4',
43 | 'mediumvioletred',
44 | 'violetred',
45 | 'orchid',
46 | 'orchid 1',
47 | 'orchid 2',
48 | 'orchid 3',
49 | 'orchid 4',
50 | 'thistle',
51 | 'thistle 1',
52 | 'thistle 2',
53 | 'thistle 3',
54 | 'thistle 4',
55 | 'plum 1',
56 | 'plum 2',
57 | 'plum 3',
58 | 'plum 4',
59 | 'plum',
60 | 'violet',
61 | 'magenta',
62 | 'fuchsia',
63 | 'magenta 2',
64 | 'magenta 3',
65 | 'magenta 4',
66 | 'darkmagenta',
67 | 'purple',
68 | 'mediumorchid',
69 | 'mediumorchid 1',
70 | 'mediumorchid 2',
71 | 'mediumorchid 3',
72 | 'mediumorchid 4',
73 | 'darkviolet',
74 | 'darkorchid',
75 | 'darkorchid 1',
76 | 'darkorchid 2',
77 | 'darkorchid 3',
78 | 'darkorchid 4',
79 | 'indigo',
80 | 'blueviolet',
81 | 'purple 1',
82 | 'purple 2',
83 | 'purple 3',
84 | 'purple 4',
85 | 'mediumpurple',
86 | 'mediumpurple 1',
87 | 'mediumpurple 2',
88 | 'mediumpurple 3',
89 | 'mediumpurple 4',
90 | 'darkslateblue',
91 | 'lightslateblue',
92 | 'mediumslateblue',
93 | 'slateblue',
94 | 'slateblue 1',
95 | 'slateblue 2',
96 | 'slateblue 3',
97 | 'slateblue 4',
98 | 'ghostwhite',
99 | 'lavender',
100 | 'blue',
101 | 'blue 2',
102 | 'blue 3',
103 | 'mediumblue',
104 | 'blue 4',
105 | 'darkblue',
106 | 'navy',
107 | 'midnightblue',
108 | 'cobalt',
109 | 'royalblue',
110 | 'royalblue 1',
111 | 'royalblue 2',
112 | 'royalblue 3',
113 | 'royalblue 4',
114 | 'cornflowerblue',
115 | 'lightsteelblue',
116 | 'lightsteelblue 1',
117 | 'lightsteelblue 2',
118 | 'lightsteelblue 3',
119 | 'lightsteelblue 4',
120 | 'lightslategray',
121 | 'slategray',
122 | 'slategray 1',
123 | 'slategray 2',
124 | 'slategray 3',
125 | 'slategray 4',
126 | 'dodgerblue 1',
127 | 'dodgerblue',
128 | 'dodgerblue 2',
129 | 'dodgerblue 3',
130 | 'dodgerblue 4',
131 | 'aliceblue',
132 | 'steelblue',
133 | 'steelblue 1',
134 | 'steelblue 2',
135 | 'steelblue 3',
136 | 'steelblue 4',
137 | 'lightskyblue',
138 | 'lightskyblue 1',
139 | 'lightskyblue 2',
140 | 'lightskyblue 3',
141 | 'lightskyblue 4',
142 | 'skyblue 1',
143 | 'skyblue 2',
144 | 'skyblue 3',
145 | 'skyblue 4',
146 | 'skyblue',
147 | 'deepskyblue 1',
148 | 'deepskyblue',
149 | 'deepskyblue 2',
150 | 'deepskyblue 3',
151 | 'deepskyblue 4',
152 | 'peacock',
153 | 'lightblue',
154 | 'lightblue 1',
155 | 'lightblue 2',
156 | 'lightblue 3',
157 | 'lightblue 4',
158 | 'powderblue',
159 | 'cadetblue 1',
160 | 'cadetblue 2',
161 | 'cadetblue 3',
162 | 'cadetblue 4',
163 | 'turquoise 1',
164 | 'turquoise 2',
165 | 'turquoise 3',
166 | 'turquoise 4',
167 | 'cadetblue',
168 | 'darkturquoise',
169 | 'azure 1',
170 | 'azure',
171 | 'azure 2',
172 | 'azure 3',
173 | 'azure 4',
174 | 'lightcyan 1',
175 | 'lightcyan',
176 | 'lightcyan 2',
177 | 'lightcyan 3',
178 | 'lightcyan 4',
179 | 'paleturquoise 1',
180 | 'paleturquoise 2',
181 | 'paleturquoise',
182 | 'paleturquoise 3',
183 | 'paleturquoise 4',
184 | 'darkslategray',
185 | 'darkslategray 1',
186 | 'darkslategray 2',
187 | 'darkslategray 3',
188 | 'darkslategray 4',
189 | 'cyan',
190 | 'aqua',
191 | 'cyan 2',
192 | 'cyan 3',
193 | 'cyan 4',
194 | 'darkcyan',
195 | 'teal',
196 | 'mediumturquoise',
197 | 'lightseagreen',
198 | 'manganeseblue',
199 | 'turquoise',
200 | 'coldgrey',
201 | 'turquoiseblue',
202 | 'aquamarine 1',
203 | 'aquamarine',
204 | 'aquamarine 2',
205 | 'aquamarine 3',
206 | 'mediumaquamarine',
207 | 'aquamarine 4',
208 | 'mediumspringgreen',
209 | 'mintcream',
210 | 'springgreen',
211 | 'springgreen 1',
212 | 'springgreen 2',
213 | 'springgreen 3',
214 | 'mediumseagreen',
215 | 'seagreen 1',
216 | 'seagreen 2',
217 | 'seagreen 3',
218 | 'seagreen 4',
219 | 'seagreen',
220 | 'emeraldgreen',
221 | 'mint',
222 | 'cobaltgreen',
223 | 'honeydew 1',
224 | 'honeydew',
225 | 'honeydew 2',
226 | 'honeydew 3',
227 | 'honeydew 4',
228 | 'darkseagreen',
229 | 'darkseagreen 1',
230 | 'darkseagreen 2',
231 | 'darkseagreen 3',
232 | 'darkseagreen 4',
233 | 'palegreen',
234 | 'palegreen 1',
235 | 'palegreen 2',
236 | 'lightgreen',
237 | 'palegreen 3',
238 | 'palegreen 4',
239 | 'limegreen',
240 | 'forestgreen',
241 | 'green 1',
242 | 'lime',
243 | 'green 2',
244 | 'green 3',
245 | 'green 4',
246 | 'green',
247 | 'darkgreen',
248 | 'sapgreen',
249 | 'lawngreen',
250 | 'chartreuse 1',
251 | 'chartreuse',
252 | 'chartreuse 2',
253 | 'chartreuse 3',
254 | 'chartreuse 4',
255 | 'greenyellow',
256 | 'darkolivegreen 1',
257 | 'darkolivegreen 2',
258 | 'darkolivegreen 3',
259 | 'darkolivegreen 4',
260 | 'darkolivegreen',
261 | 'olivedrab',
262 | 'olivedrab 1',
263 | 'olivedrab 2',
264 | 'olivedrab 3',
265 | 'yellowgreen',
266 | 'olivedrab 4',
267 | 'ivory 1',
268 | 'ivory',
269 | 'ivory 2',
270 | 'ivory 3',
271 | 'ivory 4',
272 | 'beige',
273 | 'lightyellow 1',
274 | 'lightyellow',
275 | 'lightyellow 2',
276 | 'lightyellow 3',
277 | 'lightyellow 4',
278 | 'lightgoldenrodyellow',
279 | 'yellow 1',
280 | 'yellow',
281 | 'yellow 2',
282 | 'yellow 3',
283 | 'yellow 4',
284 | 'warmgrey',
285 | 'olive',
286 | 'darkkhaki',
287 | 'khaki 1',
288 | 'khaki 2',
289 | 'khaki 3',
290 | 'khaki 4',
291 | 'khaki',
292 | 'palegoldenrod',
293 | 'lemonchiffon 1',
294 | 'lemonchiffon',
295 | 'lemonchiffon 2',
296 | 'lemonchiffon 3',
297 | 'lemonchiffon 4',
298 | 'lightgoldenrod 1',
299 | 'lightgoldenrod 2',
300 | 'lightgoldenrod 3',
301 | 'lightgoldenrod 4',
302 | 'banana',
303 | 'gold 1',
304 | 'gold',
305 | 'gold 2',
306 | 'gold 3',
307 | 'gold 4',
308 | 'cornsilk 1',
309 | 'cornsilk',
310 | 'cornsilk 2',
311 | 'cornsilk 3',
312 | 'cornsilk 4',
313 | 'goldenrod',
314 | 'goldenrod 1',
315 | 'goldenrod 2',
316 | 'goldenrod 3',
317 | 'goldenrod 4',
318 | 'darkgoldenrod',
319 | 'darkgoldenrod 1',
320 | 'darkgoldenrod 2',
321 | 'darkgoldenrod 3',
322 | 'darkgoldenrod 4',
323 | 'orange 1',
324 | 'orange',
325 | 'orange 2',
326 | 'orange 3',
327 | 'orange 4',
328 | 'floralwhite',
329 | 'oldlace',
330 | 'wheat',
331 | 'wheat 1',
332 | 'wheat 2',
333 | 'wheat 3',
334 | 'wheat 4',
335 | 'moccasin',
336 | 'papayawhip',
337 | 'blanchedalmond',
338 | 'navajowhite 1',
339 | 'navajowhite',
340 | 'navajowhite 2',
341 | 'navajowhite 3',
342 | 'navajowhite 4',
343 | 'eggshell',
344 | 'tan',
345 | 'brick',
346 | 'cadmiumyellow',
347 | 'antiquewhite',
348 | 'antiquewhite 1',
349 | 'antiquewhite 2',
350 | 'antiquewhite 3',
351 | 'antiquewhite 4',
352 | 'burlywood',
353 | 'burlywood 1',
354 | 'burlywood 2',
355 | 'burlywood 3',
356 | 'burlywood 4',
357 | 'bisque 1',
358 | 'bisque',
359 | 'bisque 2',
360 | 'bisque 3',
361 | 'bisque 4',
362 | 'melon',
363 | 'carrot',
364 | 'darkorange',
365 | 'darkorange 1',
366 | 'darkorange 2',
367 | 'darkorange 3',
368 | 'darkorange 4',
369 | 'tan 1',
370 | 'tan 2',
371 | 'tan 3',
372 | 'peru',
373 | 'tan 4',
374 | 'linen',
375 | 'peachpuff 1',
376 | 'peachpuff',
377 | 'peachpuff 2',
378 | 'peachpuff 3',
379 | 'peachpuff 4',
380 | 'seashell 1',
381 | 'seashell',
382 | 'seashell 2',
383 | 'seashell 3',
384 | 'seashell 4',
385 | 'sandybrown',
386 | 'rawsienna',
387 | 'chocolate',
388 | 'chocolate 1',
389 | 'chocolate 2',
390 | 'chocolate 3',
391 | 'chocolate 4',
392 | 'saddlebrown',
393 | 'ivoryblack',
394 | 'flesh',
395 | 'cadmiumorange',
396 | 'burntsienna',
397 | 'sienna',
398 | 'sienna 1',
399 | 'sienna 2',
400 | 'sienna 3',
401 | 'sienna 4',
402 | 'lightsalmon 1',
403 | 'lightsalmon',
404 | 'lightsalmon 2',
405 | 'lightsalmon 3',
406 | 'lightsalmon 4',
407 | 'coral',
408 | 'orangered 1',
409 | 'orangered',
410 | 'orangered 2',
411 | 'orangered 3',
412 | 'orangered 4',
413 | 'sepia',
414 | 'darksalmon',
415 | 'salmon 1',
416 | 'salmon 2',
417 | 'salmon 3',
418 | 'salmon 4',
419 | 'coral 1',
420 | 'coral 2',
421 | 'coral 3',
422 | 'coral 4',
423 | 'burntumber',
424 | 'tomato 1',
425 | 'tomato',
426 | 'tomato 2',
427 | 'tomato 3',
428 | 'tomato 4',
429 | 'salmon',
430 | 'mistyrose 1',
431 | 'mistyrose',
432 | 'mistyrose 2',
433 | 'mistyrose 3',
434 | 'mistyrose 4',
435 | 'snow 1',
436 | 'snow',
437 | 'snow 2',
438 | 'snow 3',
439 | 'snow 4',
440 | 'rosybrown',
441 | 'rosybrown 1',
442 | 'rosybrown 2',
443 | 'rosybrown 3',
444 | 'rosybrown 4',
445 | 'lightcoral',
446 | 'indianred',
447 | 'indianred 1',
448 | 'indianred 2',
449 | 'indianred 4',
450 | 'indianred 3',
451 | 'brown',
452 | 'brown 1',
453 | 'brown 2',
454 | 'brown 3',
455 | 'brown 4',
456 | 'firebrick',
457 | 'firebrick 1',
458 | 'firebrick 2',
459 | 'firebrick 3',
460 | 'firebrick 4',
461 | 'red 1',
462 | 'red',
463 | 'red 2',
464 | 'red 3',
465 | 'red 4',
466 | 'darkred',
467 | 'maroon',
468 | 'sgi beet',
469 | 'sgi slateblue',
470 | 'sgi lightblue',
471 | 'sgi teal',
472 | 'sgi chartreuse',
473 | 'sgi olivedrab',
474 | 'sgi brightgray',
475 | 'sgi salmon',
476 | 'sgi darkgray',
477 | 'sgi gray 12',
478 | 'sgi gray 16',
479 | 'sgi gray 32',
480 | 'sgi gray 36',
481 | 'sgi gray 52',
482 | 'sgi gray 56',
483 | 'sgi lightgray',
484 | 'sgi gray 72',
485 | 'sgi gray 76',
486 | 'sgi gray 92',
487 | 'sgi gray 96',
488 | 'white',
489 | 'white smoke',
490 | 'gray 96',
491 | 'gainsboro',
492 | 'lightgrey',
493 | 'silver',
494 | 'darkgray',
495 | 'gray',
496 | 'dimgray',
497 | 'gray 42',
498 | 'black',
499 | 'gray 99',
500 | 'gray 98',
501 | 'gray 97',
502 | 'gray 95',
503 | 'gray 94',
504 | 'gray 93',
505 | 'gray 92',
506 | 'gray 91',
507 | 'gray 90',
508 | 'gray 89',
509 | 'gray 88',
510 | 'gray 87',
511 | 'gray 86',
512 | 'gray 85',
513 | 'gray 84',
514 | 'gray 83',
515 | 'gray 82',
516 | 'gray 81',
517 | 'gray 80',
518 | 'gray 79',
519 | 'gray 78',
520 | 'gray 77',
521 | 'gray 76',
522 | 'gray 75',
523 | 'gray 74',
524 | 'gray 73',
525 | 'gray 72',
526 | 'gray 71',
527 | 'gray 70',
528 | 'gray 69',
529 | 'gray 68',
530 | 'gray 67',
531 | 'gray 66',
532 | 'gray 65',
533 | 'gray 64',
534 | 'gray 63',
535 | 'gray 62',
536 | 'gray 61',
537 | 'gray 60',
538 | 'gray 59',
539 | 'gray 58',
540 | 'gray 57',
541 | 'gray 56',
542 | 'gray 55',
543 | 'gray 54',
544 | 'gray 53',
545 | 'gray 52',
546 | 'gray 51',
547 | 'gray 50',
548 | 'gray 49',
549 | 'gray 48',
550 | 'gray 47',
551 | 'gray 46',
552 | 'gray 45',
553 | 'gray 44',
554 | 'gray 43',
555 | 'gray 40',
556 | 'gray 39',
557 | 'gray 38',
558 | 'gray 37',
559 | 'gray 36',
560 | 'gray 35',
561 | 'gray 34',
562 | 'gray 33',
563 | 'gray 32',
564 | 'gray 31',
565 | 'gray 30',
566 | 'gray 29',
567 | 'gray 28',
568 | 'gray 27',
569 | 'gray 26',
570 | 'gray 25',
571 | 'gray 24',
572 | 'gray 23',
573 | 'gray 22',
574 | 'gray 21',
575 | 'gray 20',
576 | 'gray 19',
577 | 'gray 18',
578 | 'gray 17',
579 | 'gray 16',
580 | 'gray 15',
581 | 'gray 14',
582 | 'gray 13',
583 | 'gray 12',
584 | 'gray 11',
585 | 'gray 10',
586 | 'gray 9',
587 | 'gray 8',
588 | 'gray 7',
589 | 'gray 6',
590 | 'gray 5',
591 | 'gray 4',
592 | 'gray 3',
593 | 'gray 2',
594 | 'gray 1',
595 | 'whitesmoke'
596 | ])
597 |
598 | export const validateColor = (str = '') =>
599 | /#\d{3,6}|rgba{0,1}\(.*?\)/gi.test(str) || colors.has(str.toLowerCase())
600 |
--------------------------------------------------------------------------------