Showing preview only (1,418K chars total). Download the full file or copy to clipboard to get everything.
Repository: carbon-app/carbon
Branch: main
Commit: 1334429ae63f
Files: 148
Total size: 1.3 MB
Directory structure:
gitextract_k_ip4zh3/
├── .all-contributorsrc
├── .eslintrc.js
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── other.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── ranger.yml
│ └── workflows/
│ └── validate.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .vercelignore
├── LICENSE
├── README.md
├── SECURITY.md
├── bin/
│ └── deploy.sh
├── components/
│ ├── Announcement.js
│ ├── ApiContext.js
│ ├── AuthContext.js
│ ├── BackgroundSelect.js
│ ├── Billing.js
│ ├── Button.js
│ ├── Carbon.js
│ ├── ColorPicker.js
│ ├── ConfirmButton.js
│ ├── CopyMenu.js
│ ├── Dropdown.js
│ ├── Editor.js
│ ├── EditorContainer.js
│ ├── ExportMenu.js
│ ├── FontFace.js
│ ├── FontSelect.js
│ ├── Footer.js
│ ├── Header.js
│ ├── ImagePicker.js
│ ├── Input.js
│ ├── ListSetting.js
│ ├── LoginButton.js
│ ├── MenuButton.js
│ ├── Meta.js
│ ├── Overlay.js
│ ├── Page.js
│ ├── PhotoCredit.js
│ ├── Popout.js
│ ├── Presets.js
│ ├── RandomImage.js
│ ├── ReferralLink.js
│ ├── SelectionEditor.js
│ ├── Settings.js
│ ├── ShareMenu.js
│ ├── Slider.js
│ ├── SnippetToolbar.js
│ ├── Spinner.js
│ ├── ThemeSelect.js
│ ├── Themes/
│ │ ├── GlobalHighlights.js
│ │ ├── ThemeCreate.js
│ │ └── index.js
│ ├── Toasts.js
│ ├── Toggle.js
│ ├── Toolbar.js
│ ├── WidthHandler.js
│ ├── WindowControls.js
│ ├── WindowPointer.js
│ ├── hooks.js
│ ├── style/
│ │ ├── Font.js
│ │ ├── Reset.js
│ │ └── Typography.js
│ └── svg/
│ ├── Arrows.js
│ ├── Checkmark.js
│ ├── Controls.js
│ ├── Copy.js
│ ├── Language.js
│ ├── Logo.js
│ ├── Remove.js
│ ├── Settings.js
│ ├── Theme.js
│ ├── Watermark.js
│ └── WindowThemes.js
├── cypress/
│ ├── config.json
│ ├── integration/
│ │ ├── background-color.spec.js
│ │ ├── basic.spec.js
│ │ ├── embed.spec.js
│ │ ├── gist.spec.js
│ │ ├── localStorage.spec.js
│ │ ├── security.spec.js
│ │ └── visual-testing.spec.js
│ ├── plugins/
│ │ └── index.js
│ ├── support/
│ │ └── index.js
│ └── util.js
├── docs/
│ ├── README.ar.md
│ ├── README.bn.md
│ ├── README.br.pt.md
│ ├── README.cn.zh.md
│ ├── README.de.md
│ ├── README.es.md
│ ├── README.fa.md
│ ├── README.fr.md
│ ├── README.he.md
│ ├── README.hi.md
│ ├── README.in.md
│ ├── README.it.md
│ ├── README.ja.md
│ ├── README.kr.md
│ ├── README.ml.md
│ ├── README.nl.md
│ ├── README.pl.md
│ ├── README.ru.md
│ ├── README.se.md
│ ├── README.ta.md
│ ├── README.tr.md
│ ├── README.tw.zh.md
│ ├── README.ua.md
│ └── README.uz.md
├── lib/
│ ├── api.js
│ ├── client.js
│ ├── constants.js
│ ├── custom/
│ │ ├── autoCloseBrackets.js
│ │ └── modes/
│ │ ├── apache.js
│ │ ├── elixir.js
│ │ ├── graphql.js
│ │ ├── nim.js
│ │ ├── riscv.js
│ │ └── solidity.js
│ ├── dom-to-image.js
│ ├── highlight-languages.js
│ ├── routing.js
│ └── util.js
├── next.config.js
├── package.json
├── pages/
│ ├── [id].js
│ ├── _document.js
│ ├── about.js
│ ├── account.js
│ ├── api/
│ │ ├── image/
│ │ │ └── [id].js
│ │ └── oembed.js
│ ├── embed/
│ │ ├── [id].js
│ │ └── index.js
│ ├── index.js
│ └── snippets.js
├── public/
│ ├── manifest.json
│ ├── robots.txt
│ └── static/
│ └── react-crop.css
├── release.js
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"projectName": "carbon",
"projectOwner": "carbon-app",
"files": [
"README.md",
"docs/README.ar.md",
"docs/README.bn.md",
"docs/README.br.pt.md",
"docs/README.cn.zh.md",
"docs/README.de.md",
"docs/README.es.md",
"docs/README.fr.md",
"docs/README.he.md",
"docs/README.hi.md",
"docs/README.in.md",
"docs/README.it.md",
"docs/README.ja.md",
"docs/README.kr.md",
"docs/README.ml.md",
"docs/README.nl.md",
"docs/README.pl.md",
"docs/README.ru.md",
"docs/README.se.md",
"docs/README.ta.md",
"docs/README.tr.md",
"docs/README.tw.zh.md",
"docs/README.ua.md",
"docs/README.uz.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "briandennis",
"name": "briandennis",
"avatar_url": "https://avatars0.githubusercontent.com/u/10078572?v=4",
"profile": "https://github.com/briandennis",
"contributions": [
"code",
"doc",
"infra",
"review"
]
},
{
"login": "mfix22",
"name": "mfix22",
"avatar_url": "https://avatars0.githubusercontent.com/u/8397708?v=4",
"profile": "https://github.com/mfix22",
"contributions": [
"question",
"code",
"ideas"
]
},
{
"login": "jakedex",
"name": "jakedex",
"avatar_url": "https://avatars1.githubusercontent.com/u/10369094?v=4",
"profile": "https://github.com/jakedex",
"contributions": [
"question",
"code",
"design",
"video"
]
},
{
"login": "andrewda",
"name": "andrewda",
"avatar_url": "https://avatars1.githubusercontent.com/u/10191084?v=4",
"profile": "https://github.com/andrewda",
"contributions": [
"question",
"code",
"bug",
"review"
]
},
{
"login": "yeskunall",
"name": "yeskunall",
"avatar_url": "https://avatars2.githubusercontent.com/u/14703164?v=4",
"profile": "https://github.com/yeskunall",
"contributions": [
"code",
"doc",
"tool",
"bug"
]
},
{
"login": "stoshfabricius",
"name": "stoshfabricius",
"avatar_url": "https://avatars1.githubusercontent.com/u/2652676?v=4",
"profile": "https://github.com/stoshfabricius",
"contributions": [
"code"
]
},
{
"login": "jkling38",
"name": "jkling38",
"avatar_url": "https://avatars1.githubusercontent.com/u/11639896?v=4",
"profile": "https://github.com/jkling38",
"contributions": [
"doc"
]
},
{
"login": "otobrglez",
"name": "otobrglez",
"avatar_url": "https://avatars1.githubusercontent.com/u/225946?v=4",
"profile": "https://github.com/otobrglez",
"contributions": [
"code"
]
},
{
"login": "darahak",
"name": "darahak",
"avatar_url": "https://avatars3.githubusercontent.com/u/11488612?v=4",
"profile": "https://github.com/darahak",
"contributions": [
"doc"
]
},
{
"login": "dom96",
"name": "dom96",
"avatar_url": "https://avatars0.githubusercontent.com/u/246651?v=4",
"profile": "https://github.com/dom96",
"contributions": [
"code"
]
},
{
"login": "elrumordelaluz",
"name": "elrumordelaluz",
"avatar_url": "https://avatars3.githubusercontent.com/u/784056?v=4",
"profile": "https://github.com/elrumordelaluz",
"contributions": [
"code"
]
},
{
"login": "cjb",
"name": "cjb",
"avatar_url": "https://avatars2.githubusercontent.com/u/21217?v=4",
"profile": "https://github.com/cjb",
"contributions": [
"code"
]
},
{
"login": "Krzysztof-Cieslak",
"name": "Krzysztof-Cieslak",
"avatar_url": "https://avatars1.githubusercontent.com/u/5427083?v=4",
"profile": "https://github.com/Krzysztof-Cieslak",
"contributions": [
"code"
]
},
{
"login": "fernahh",
"name": "fernahh",
"avatar_url": "https://avatars0.githubusercontent.com/u/2369851?v=4",
"profile": "https://github.com/fernahh",
"contributions": [
"doc"
]
},
{
"login": "g3r4n",
"name": "g3r4n",
"avatar_url": "https://avatars1.githubusercontent.com/u/10941033?v=4",
"profile": "https://github.com/g3r4n",
"contributions": [
"code"
]
},
{
"login": "Drarok",
"name": "Mat Gadd",
"avatar_url": "https://avatars0.githubusercontent.com/u/55830?v=4",
"profile": "http://drarok.com/",
"contributions": [
"bug",
"code"
]
},
{
"login": "varbrad",
"name": "Brad Davies",
"avatar_url": "https://avatars1.githubusercontent.com/u/11805775?v=4",
"profile": "https://bradlab.co.uk",
"contributions": [
"bug",
"code"
]
},
{
"login": "rafaelcamaram",
"name": "Rafael Câmara",
"avatar_url": "https://avatars1.githubusercontent.com/u/9087886?v=4",
"profile": "http://www.rafaelcamaram.com/",
"contributions": [
"code"
]
},
{
"login": "bahmutov",
"name": "Gleb Bahmutov",
"avatar_url": "https://avatars1.githubusercontent.com/u/2212006?v=4",
"profile": "https://glebbahmutov.com/",
"contributions": [
"test",
"tool"
]
},
{
"login": "warborn",
"name": "Iván Munguía",
"avatar_url": "https://avatars2.githubusercontent.com/u/10677789?v=4",
"profile": "https://ivan-munguia.netlify.com",
"contributions": [
"code"
]
},
{
"login": "dmmulroy",
"name": "Dillon Mulroy",
"avatar_url": "https://avatars1.githubusercontent.com/u/2755722?v=4",
"profile": "https://dillonmulroy.com",
"contributions": [
"code"
]
},
{
"login": "NARKOZ",
"name": "Nihad Abbasov",
"avatar_url": "https://avatars2.githubusercontent.com/u/253398?v=4",
"profile": "https://github.com/NARKOZ",
"contributions": [
"code"
]
},
{
"login": "imbrn",
"name": "Bruno C. Couto",
"avatar_url": "https://avatars1.githubusercontent.com/u/1906977?v=4",
"profile": "https://github.com/imbrn",
"contributions": [
"code"
]
},
{
"login": "molnarmark",
"name": "Mark Molnar",
"avatar_url": "https://avatars2.githubusercontent.com/u/13263073?v=4",
"profile": "https://github.com/molnarmark",
"contributions": [
"code"
]
},
{
"login": "TETRA2000",
"name": "Takahiko Inayama",
"avatar_url": "https://avatars1.githubusercontent.com/u/1459450?v=4",
"profile": "https://www.behance.net/tetra2000",
"contributions": [
"code"
]
},
{
"login": "martinfrancois",
"name": "François Martin",
"avatar_url": "https://avatars1.githubusercontent.com/u/14319020?v=4",
"profile": "https://github.com/martinfrancois",
"contributions": [
"code"
]
},
{
"login": "raphamorim",
"name": "Raphael Amorim",
"avatar_url": "https://avatars3.githubusercontent.com/u/3630346?v=4",
"profile": "http://raphamorim.io",
"contributions": [
"code"
]
},
{
"login": "camflan",
"name": "Camron Flanders",
"avatar_url": "https://avatars0.githubusercontent.com/u/27292?v=4",
"profile": "http://camronflanders.com",
"contributions": [
"code"
]
},
{
"login": "ericadamski",
"name": "Eric Adamski",
"avatar_url": "https://avatars0.githubusercontent.com/u/6516758?v=4",
"profile": "https://github.com/ericadamski",
"contributions": [
"code"
]
},
{
"login": "wistcc",
"name": "Winner Crespo",
"avatar_url": "https://avatars0.githubusercontent.com/u/4671080?v=4",
"profile": "http://winnercrespo.com",
"contributions": [
"code",
"design"
]
},
{
"login": "mixn",
"name": "Milos",
"avatar_url": "https://avatars3.githubusercontent.com/u/672237?v=4",
"profile": "http://twitter.com/mixn",
"contributions": [
"code",
"tool",
"doc",
"translation"
]
},
{
"login": "mittalyashu",
"name": "Yashu Mittal",
"avatar_url": "https://avatars1.githubusercontent.com/u/29014463?v=4",
"profile": "https://mittalyashu.now.sh/",
"contributions": [
"code"
]
},
{
"login": "rachelcarmena",
"name": "Rachel M. Carmena",
"avatar_url": "https://avatars0.githubusercontent.com/u/22792183?v=4",
"profile": "https://twitter.com/bberrycarmen",
"contributions": [
"doc"
]
},
{
"login": "ucdstudent95618",
"name": "Miguel Salazar",
"avatar_url": "https://avatars0.githubusercontent.com/u/19369949?v=4",
"profile": "https://www.linkedin.com/in/miguel-salazar-823b07a1/",
"contributions": [
"doc",
"translation"
]
},
{
"login": "vvyomjjain",
"name": "Vyom Jain",
"avatar_url": "https://avatars3.githubusercontent.com/u/19145803?v=4",
"profile": "https://www.linkedin.com/in/vyom-jain-233a28139",
"contributions": [
"doc",
"translation"
]
},
{
"login": "racaljk",
"name": "racaljk",
"avatar_url": "https://avatars0.githubusercontent.com/u/5010047?v=4",
"profile": "http://www.cnblogs.com/racaljk/",
"contributions": [
"translation"
]
},
{
"login": "raboid",
"name": "Sean",
"avatar_url": "https://avatars3.githubusercontent.com/u/3892149?v=4",
"profile": "https://lastblocklabs.com",
"contributions": [
"code"
]
},
{
"login": "izabelacborges",
"name": "Izabela Borges",
"avatar_url": "https://avatars0.githubusercontent.com/u/19255077?v=4",
"profile": "http://izabelacborges.com/",
"contributions": [
"translation"
]
},
{
"login": "shinilms",
"name": "Shinil M S",
"avatar_url": "https://avatars2.githubusercontent.com/u/18573510?v=4",
"profile": "https://ghuser.io/shinilms",
"contributions": [
"translation"
]
},
{
"login": "berkeatac",
"name": "Berke Atac",
"avatar_url": "https://avatars1.githubusercontent.com/u/10579633?v=4",
"profile": "https://github.com/berkeatac",
"contributions": [
"translation"
]
},
{
"login": "wooooooak",
"name": "LEE YONGJUN",
"avatar_url": "https://avatars3.githubusercontent.com/u/18481078?v=4",
"profile": "https://wooooooak.github.io/",
"contributions": [
"translation"
]
},
{
"login": "MatthewNielsen27",
"name": "Matthew Nielsen",
"avatar_url": "https://avatars2.githubusercontent.com/u/35040439?v=4",
"profile": "http://matthewnielsen.ca",
"contributions": [
"code"
]
},
{
"login": "boyvanamstel",
"name": "Boy",
"avatar_url": "https://avatars2.githubusercontent.com/u/225410?v=4",
"profile": "https://www.boy.sh",
"contributions": [
"platform"
]
},
{
"login": "vetrivelcsamy",
"name": "Vetrivel Chinnasamy",
"avatar_url": "https://avatars2.githubusercontent.com/u/26738977?v=4",
"profile": "https://vetrivelcsamy.xyz",
"contributions": [
"translation"
]
},
{
"login": "farskid",
"name": "Farzad YZ",
"avatar_url": "https://avatars2.githubusercontent.com/u/8332043?s=460&v=4",
"profile": "https://farzadyz.com",
"contributions": [
"code",
"ideas"
]
},
{
"login": "yannickl",
"name": "Yannick Loriot",
"avatar_url": "https://avatars0.githubusercontent.com/u/798235?s=460&v=4",
"profile": "https://github.com/yannickl",
"contributions": [
"translation"
]
},
{
"login": "Joel-hanson",
"name": "Joel Hanson",
"avatar_url": "https://avatars2.githubusercontent.com/u/17215044?s=460&v=4",
"profile": "https://github.com/Joel-hanson",
"contributions": [
"code"
]
},
{
"login": "muhammadmuzzammil1998",
"name": "Muhammad Muzzammil",
"avatar_url": "https://avatars2.githubusercontent.com/u/12321712?v=4",
"profile": "https://muzzammil.xyz/?ref=github",
"contributions": [
"code"
]
},
{
"login": "souppower",
"name": "souppower",
"avatar_url": "https://avatars2.githubusercontent.com/u/19849611?v=4",
"profile": "https://github.com/souppower",
"contributions": [
"infra"
]
},
{
"login": "uraway",
"name": "Masato Urai (@uraway_)",
"avatar_url": "https://avatars3.githubusercontent.com/u/15242484?v=4",
"profile": "http://uraway.hatenablog.com/",
"contributions": [
"translation"
]
},
{
"login": "techinpark",
"name": "Fernando",
"avatar_url": "https://avatars3.githubusercontent.com/u/45546296?v=4",
"profile": "http://techinpark.com",
"contributions": [
"translation"
]
},
{
"login": "megsachdev",
"name": "Megha Sachdev",
"avatar_url": "https://avatars1.githubusercontent.com/u/22325351?v=4",
"profile": "https://github.com/megsachdev",
"contributions": [
"code",
"test"
]
},
{
"login": "anudeepreddy",
"name": "Anudeep Reddy",
"avatar_url": "https://avatars0.githubusercontent.com/u/6022231?v=4",
"profile": "https://techgeekhub.ml",
"contributions": [
"infra"
]
},
{
"login": "munierujp",
"name": "Munieru",
"avatar_url": "https://avatars2.githubusercontent.com/u/20086673?v=4",
"profile": "https://munieru.jp",
"contributions": [
"translation"
]
},
{
"login": "etoxin",
"name": "Adam Lusted",
"avatar_url": "https://avatars0.githubusercontent.com/u/393002?v=4",
"profile": "http://www.etoxin.net",
"contributions": [
"code"
]
},
{
"login": "JoseNoriegaa",
"name": "Jose Noriega",
"avatar_url": "https://avatars2.githubusercontent.com/u/28733681?v=4",
"profile": "https://github.com/JoseNoriegaa",
"contributions": [
"translation"
]
},
{
"login": "Merlintor",
"name": "Merlin Fuchs",
"avatar_url": "https://avatars2.githubusercontent.com/u/33966852?v=4",
"profile": "https://discord.club",
"contributions": [
"translation"
]
},
{
"login": "majouji",
"name": "Ramy Majouji",
"avatar_url": "https://avatars0.githubusercontent.com/u/23482161?v=4",
"profile": "https://glossier.com",
"contributions": [
"code"
]
},
{
"login": "nemesv",
"name": "Viktor Nemes",
"avatar_url": "https://avatars0.githubusercontent.com/u/251330?v=4",
"profile": "http://stackoverflow.com/users/872395/nemesv",
"contributions": [
"code"
]
},
{
"login": "ericwbailey",
"name": "Eric Bailey",
"avatar_url": "https://avatars3.githubusercontent.com/u/634191?v=4",
"profile": "https://ericwbailey.design/",
"contributions": [
"code"
]
},
{
"login": "Nazeeefa",
"name": "Nazeefa",
"avatar_url": "https://avatars0.githubusercontent.com/u/6730853?v=4",
"profile": "http://rsg-sweden.iscbsc.org",
"contributions": [
"translation"
]
},
{
"login": "pratikbutani",
"name": "Pratik Butani",
"avatar_url": "https://avatars2.githubusercontent.com/u/3306366?v=4",
"profile": "https://medium.com/@pratikbutani/carbon-create-and-share-beautiful-images-of-your-source-code-d31dedfe64bd",
"contributions": [
"blog"
]
},
{
"login": "baktiaditya",
"name": "Bakti Aditya",
"avatar_url": "https://avatars0.githubusercontent.com/u/2070906?v=4",
"profile": "https://github.com/baktiaditya",
"contributions": [
"code"
]
},
{
"login": "aquaductape",
"name": "Caleb Taylor",
"avatar_url": "https://avatars1.githubusercontent.com/u/29286430?v=4",
"profile": "https://github.com/aquaductape",
"contributions": [
"code"
]
},
{
"login": "rjmunhoz",
"name": "Rogério Munhoz",
"avatar_url": "https://avatars3.githubusercontent.com/u/3948961?v=4",
"profile": "http://about.rmunhoz.me",
"contributions": [
"code"
]
},
{
"login": "technoknol",
"name": "Technoknol",
"avatar_url": "https://avatars0.githubusercontent.com/u/6429418?v=4",
"profile": "https://github.com/technoknol",
"contributions": [
"code"
]
},
{
"login": "tmakowski",
"name": "Tymoteusz Makowski",
"avatar_url": "https://avatars3.githubusercontent.com/u/38053499?v=4",
"profile": "https://github.com/tmakowski",
"contributions": [
"code"
]
},
{
"login": "nisarhassan12",
"name": "Nisar Hassan Naqvi",
"avatar_url": "https://avatars3.githubusercontent.com/u/46004116?v=4",
"profile": "https://nisar.dev",
"contributions": [
"bug"
]
},
{
"login": "ilyaskarim",
"name": "Ilyas Karim",
"avatar_url": "https://avatars2.githubusercontent.com/u/42450390?v=4",
"profile": "https://www.wapgee.com",
"contributions": [
"bug"
]
},
{
"login": "njfix6",
"name": "Nick Fix",
"avatar_url": "https://avatars2.githubusercontent.com/u/6845581?v=4",
"profile": "http://nickfix.me",
"contributions": [
"ideas"
]
},
{
"login": "MelSumner",
"name": "Melanie Sumner",
"avatar_url": "https://avatars0.githubusercontent.com/u/4587451?v=4",
"profile": "https://noti.st/melsumner",
"contributions": [
"ideas"
]
},
{
"login": "b6pzeusbc54tvhw5jgpyw8pwz2x6gs",
"name": "aluc",
"avatar_url": "https://avatars2.githubusercontent.com/u/15520015?v=4",
"profile": "https://aluc.io/",
"contributions": [
"code"
]
},
{
"login": "mearns",
"name": "B. Mearns",
"avatar_url": "https://avatars1.githubusercontent.com/u/5140254?v=4",
"profile": "https://github.com/mearns",
"contributions": [
"ideas"
]
},
{
"login": "neighborhood999",
"name": "Peng Jie",
"avatar_url": "https://avatars3.githubusercontent.com/u/10325111?v=4",
"profile": "http://jiepeng.me",
"contributions": [
"code"
]
},
{
"login": "b3u",
"name": "Binyamin Aron Green",
"avatar_url": "https://avatars3.githubusercontent.com/u/39805353?v=4",
"profile": "https://binyam.in",
"contributions": [
"code"
]
},
{
"login": "mbiesiad",
"name": "Michal",
"avatar_url": "https://avatars0.githubusercontent.com/u/18367606?v=4",
"profile": "https://dev.to/mbiesiad",
"contributions": [
"translation"
]
},
{
"login": "qw-in",
"name": "Quinn Blenkinsop",
"avatar_url": "https://avatars0.githubusercontent.com/u/19194187?v=4",
"profile": "https://github.com/qw-in",
"contributions": [
"code"
]
},
{
"login": "seagalputra",
"name": "Dwiferdio Seagal Putra",
"avatar_url": "https://avatars0.githubusercontent.com/u/15377132?v=4",
"profile": "https://github.com/seagalputra",
"contributions": [
"code"
]
},
{
"login": "ashwoodall",
"name": "Ashley Woodall Clark",
"avatar_url": "https://avatars3.githubusercontent.com/u/14588617?v=4",
"profile": "https://github.com/ashwoodall",
"contributions": [
"code"
]
},
{
"login": "timwienk",
"name": "Tim Wienk",
"avatar_url": "https://avatars0.githubusercontent.com/u/150598?v=4",
"profile": "https://tim.wienk.name/",
"contributions": [
"translation"
]
},
{
"login": "george1410",
"name": "George McCarron",
"avatar_url": "https://avatars2.githubusercontent.com/u/9155723?v=4",
"profile": "http://georgemccarron.com/",
"contributions": [
"doc"
]
},
{
"login": "artmxra7",
"name": "Erwin Rahayu",
"avatar_url": "https://avatars.githubusercontent.com/u/23070604?v=4",
"profile": "https://github.com/artmxra7",
"contributions": [
"translation",
"doc"
]
},
{
"login": "mlucap",
"name": "Luca",
"avatar_url": "https://avatars.githubusercontent.com/u/36577976?v=4",
"profile": "https://github.com/mlucap",
"contributions": [
"code"
]
},
{
"login": "adghayes",
"name": "Andrew Hayes",
"avatar_url": "https://avatars.githubusercontent.com/u/37960853?v=4",
"profile": "https://github.com/adghayes",
"contributions": [
"code"
]
},
{
"login": "heybereket",
"name": "Bereket Semagn",
"avatar_url": "https://avatars.githubusercontent.com/u/68391329?v=4",
"profile": "https://github.com/heybereket",
"contributions": [
"code"
]
},
{
"login": "LorenzoLancia",
"name": "Lorenzo Lancia",
"avatar_url": "https://avatars.githubusercontent.com/u/44911690?v=4",
"profile": "https://github.com/LorenzoLancia",
"contributions": [
"translation"
]
},
{
"login": "Guy-Adler",
"name": "Guy Adler",
"avatar_url": "https://avatars.githubusercontent.com/u/44903310?v=4",
"profile": "https://github.com/Guy-Adler",
"contributions": [
"translation"
]
},
{
"login": "danBamikiya",
"name": "Dan Bamikiya",
"avatar_url": "https://avatars.githubusercontent.com/u/58262528?v=4",
"profile": "https://github.com/danBamikiya",
"contributions": [
"ideas"
]
},
{
"login": "kewang",
"name": "kewang",
"avatar_url": "https://avatars.githubusercontent.com/u/795839?v=4",
"profile": "https://github.com/kewang",
"contributions": [
"translation"
]
},
{
"login": "rizdaprasetya",
"name": "Rizda Dwi Prasetya",
"avatar_url": "https://avatars.githubusercontent.com/u/13027142?v=4",
"profile": "https://rizda.win/",
"contributions": [
"content"
]
},
{
"login": "AbreuY",
"name": "AbreuY",
"avatar_url": "https://avatars.githubusercontent.com/u/5095949?v=4",
"profile": "https://github.com/AbreuY",
"contributions": [
"translation"
]
},
{
"login": "rizkytegar",
"name": ">_Rizky.dev",
"avatar_url": "https://avatars.githubusercontent.com/u/55475891?v=4",
"profile": "https://www.rizkytegar.com/",
"contributions": [
"translation",
"doc"
]
},
{
"login": "OpeAbidemi",
"name": "Abidemi Harry",
"avatar_url": "https://avatars.githubusercontent.com/u/58413594?v=4",
"profile": "https://github.com/OpeAbidemi",
"contributions": [
"code"
]
},
{
"login": "mcognetta",
"name": "Marco",
"avatar_url": "https://avatars.githubusercontent.com/u/6856636?v=4",
"profile": "https://github.com/mcognetta",
"contributions": [
"translation"
]
},
{
"login": "samrobbins85",
"name": "Sam Robbins",
"avatar_url": "https://avatars.githubusercontent.com/u/29740136?v=4",
"profile": "https://github.com/samrobbins85",
"contributions": [
"doc"
]
},
{
"login": "alceil",
"name": "alceil",
"avatar_url": "https://avatars.githubusercontent.com/u/47685349?v=4",
"profile": "https://github.com/alceil",
"contributions": [
"code"
]
},
{
"login": "hatsu38",
"name": "hatsu",
"avatar_url": "https://avatars.githubusercontent.com/u/16137809?v=4",
"profile": "https://github.com/hatsu38",
"contributions": [
"code"
]
},
{
"login": "praveenscience",
"name": "Praveen Kumar Purushothaman",
"avatar_url": "https://avatars.githubusercontent.com/u/1830380?v=4",
"profile": "https://github.com/praveenscience",
"contributions": [
"translation"
]
},
{
"login": "Amirosagan",
"name": "Amir Elsagan",
"avatar_url": "https://avatars.githubusercontent.com/u/53624184?v=4",
"profile": "https://github.com/Amirosagan",
"contributions": [
"translation"
]
},
{
"login": "korzck",
"name": "korzck",
"avatar_url": "https://avatars.githubusercontent.com/u/87325587?v=4",
"profile": "https://github.com/korzck",
"contributions": [
"translation"
]
},
{
"login": "sakibulalam",
"name": "Sakibul Alam",
"avatar_url": "https://avatars.githubusercontent.com/u/4949223?v=4",
"profile": "https://github.com/sakibulalam",
"contributions": [
"translation"
]
}
],
"repoType": "github",
"repoHost": "https://github.com",
"contributorsPerLine": 7,
"commitConvention": "none",
"skipCi": true
}
================================================
FILE: .eslintrc.js
================================================
module.exports = {
env: { es6: true, jest: true },
extends: ['eslint:recommended', 'plugin:jsx-a11y/recommended', 'next'],
rules: {
'import/no-unresolved': 'error',
'no-duplicate-imports': 'error',
'react/display-name': 'off',
'react/jsx-no-target-blank': 'error',
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'jsx-a11y/click-events-have-key-events': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
'no-console': ['error', { allow: ['error'] }],
// TODO re-enable these
'@next/next/no-img-element': 'off',
'@next/next/no-html-link-for-pages': 'off',
'@next/next/link-passhref': 'off',
},
}
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
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.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
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.
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.
## Scope
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.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at carbon.now.sh@gmail.com. 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.
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.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
If you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub. You don't need to be assigned an issue in order to work on it—consider all issues free rein.
If you'd like to make some changes yourself, see the following:
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
1. Make sure yarn is globally installed (`npm install -g yarn`)
1. Run `yarn` to download required packages
1. Build and start the application: `yarn dev`
1. Finally, submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) with your changes!
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification, and uses the [@all-contributors](https://allcontributors.org/docs/en/bot/usage) bot to add new contributors.
Contributions of any kind are welcome!
### Adding themes/languages/fonts
We are not currently accepting new themes, languages, or fonts into Carbon, except for in extenuating circumstances. Instead, we want to continue to provide ways for users to add their own themes and presets. Please feel free to still open an issue or PR for consideration, but know that there is a chance it will get closed without addition.
## Stats

================================================
FILE: .github/FUNDING.yml
================================================
github: [carbon-app, mfix22] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
open_collective: # carbon-app/contribute
patreon: # Replace with a single Patreon username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: [] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help fix something about Carbon
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '…'
2. Click on '…'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Info (please complete the following information):**
- OS [e.g. macOS, Linux, Windows, iOS]:
- Browser [e.g. Chrome, Safari, Firefox]:
- Carbon URL [e.g. carbon.now.sh?bg=pink]:
<details>
<summary>Code snippet</summary>
<pre>
<!-- Paste an example code snippet, if applicable -->
</pre>
</details>
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/ISSUE_TEMPLATE/other.md
================================================
---
name: Other
about: Let us know about something else
title: ''
labels: ''
assignees: ''
---
**Describe the change**
A clear and concise description of your proposal.
**Screenshots**
If applicable, add screenshots to help explain the issue.
**Info (please complete the following information):**
- OS [e.g. macOS, Linux, Windows, iOS]:
- Browser [e.g. Chrome, Safari, Firefox]:
<details>
<summary>Code snippet</summary>
<pre>
<!-- Paste an example code snippet, if applicable -->
</pre>
</details>
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!---
Provide a general summary of your changes in the Title above
Expand on it in the description below (if applicable)
Attach a screenshot (if applicable)
-->
- [ ] Integration tests (if applicable)
Closes #
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
# Configuration for npm
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
allow:
dependency-type: 'development'
# labels:
# - 'dependencies'
# ignore:
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-update
================================================
FILE: .github/ranger.yml
================================================
_extends: reporanger/superpowers
labels:
dependencies: merge
contributor:
action: comment
delay: 0s
message: '@all-contributors add @$AUTHOR for code'
translator:
action: comment
delay: 0s
message: '@all-contributors add @$AUTHOR for translation'
duplicate:
action: close
delay: 2 days
comment: ⚠️ This has been marked to be closed in $DELAY.
'theme/language':
action: close
delay: 3 days
comment: |
This issue has been marked "$LABEL". As of Carbon `4.0.0`, the Carbon core team is no longer implementing new themes or languages, except for in extenuating circumstances. You can create your own theme in the "Themes" dropdown.
This issue will remain open for $DELAY for further consideration.
commits:
- action: label
pattern: 'upgrade dep'
labels:
- maintenance
- action: label
user: 'allcontributors[bot]'
labels:
- 'squash when passing'
================================================
FILE: .github/workflows/validate.yml
================================================
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
- name: npm install, lint, build, and test
run: |
yarn
npm run lint --if-present
npm run build --if-present
npm start & npx wait-on http://localhost:3000 && npm run cy:run -- --config baseUrl=http://localhost:3000
# --record --key 26c0b9eb-40f9-4ca6-b91d-a39f03652011
env:
CI: true
CYPRESS_CI: true
================================================
FILE: .gitignore
================================================
node_modules
.env*
.next
dist
out
cypress/videos
cypress/screenshots
.idea
.DS_Store
packaged
coverage
public/service-worker.js
private-key.json
.now
.vercel
*.log
================================================
FILE: .npmrc
================================================
package-lock=false
registry=https://registry.npmjs.org
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true,
"printWidth": 100,
"semi": false,
"arrowParens": "avoid"
}
================================================
FILE: .vercelignore
================================================
.github
LICENSE
README.md
bin
node_modules
cypress
cypress.json
docs
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Carbon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<img width="100%" src="https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11e7-86af-fa5ea3d7dbac.png" />
</p>
<p align="center">
<a href="https://reporanger.com">
<img src="https://img.shields.io/badge/maintained%20with-Ranger-1f93f3.svg" alt="maintained with Ranger" />
</a>
<a href="#contributors">
<img src="https://img.shields.io/badge/all_contributors-160-orange.svg" alt="All Contributors" />
</a>
<a href="/LICENSE">
<img src="https://img.shields.io/github/license/carbon-app/carbon.svg" alt="MIT License" />
</a>
<a href="https://app.fossa.io/projects/git%2Bgithub.com%2Fcarbon-app%2Fcarbon?ref=badge_shield">
<img src="https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcarbon-app%2Fcarbon.svg?type=shield" alt="FOSSA Status" />
</a>
</p>
<br></br>
##### Translations
<table>
<tbody>
<tr>
<td>
<a href="/docs/README.es.md">Español</a>
</td>
<td>
<a href="/docs/README.hi.md">हिंदी</a>
</td>
<td>
<a href="/docs/README.de.md">Deutsch</a>
</td>
<td>
<a href="/docs/README.cn.zh.md">简体中文</a>
</td>
<td>
<a href="/docs/README.br.pt.md">Português</a>
</td>
</tr>
<tr>
<td>
<a href="/docs/README.ml.md">മലയാളം</a>
</td>
<td>
<a href="/docs/README.tr.md">Türkçe</a>
</td>
<td>
<a href="/docs/README.kr.md">한국어</a>
</td>
<td>
<a href="/docs/README.ta.md">தமிழ்</a>
</td>
<td>
<a href="/docs/README.fr.md">Français</a>
</td>
</tr>
<tr>
<td>
<a href="/docs/README.ja.md">日本語</a>
</td>
<td>
<a href="/docs/README.se.md">Svenska</a>
</td>
<td>
<a href="/docs/README.pl.md">Polski</a>
</td>
<td>
<a href="/docs/README.nl.md">Nederlands</a>
</td>
<td>
<a href="/docs/README.tw.zh.md">正體中文</a>
</td>
</tr>
<tr>
<td>
<a href="/docs/README.in.md">Indonesia</a>
</td>
<td>
<a href="/docs/README.ua.md">Українська</a>
</td>
<td>
<a href="/docs/README.it.md">Italiano</a>
</td>
<td>
<a href="/docs/README.he.md" dir="rtl">עברית</a>
</td>
<td>
<a href="/docs/README.ru.md">Русский</a>
</td>
</tr>
<tr>
<td>
<a href="/docs/README.ar.md">العربية</a>
</td>
<td>
<a href="/docs/README.bn.md">বাংলা</a>
</td>
<td>
<a href="/docs/README.fa.md">فارسی</a>
</td>
<td>
<a href="https://github.com/carbon-app/carbon/new/main/docs?filename=docs/README.%3Cnew%20language%3E.md">Add +</a>
</td>
</tr>
</tbody>
</table>
## Introduction
You know [all](https://twitter.com/dan_abramov/status/890191815567175680) [of those](https://twitter.com/reactjs/status/890511993261654017) [code screenshots](https://twitter.com/notquiteleo/status/873483329345028096) you see on Twitter? Though the code's usually impressive, we saw room for improvement in the aesthetic department. Carbon makes it easy to create and share beautiful images of your source code. So what are you waiting for? Go impress all of your followers with your newfound design prowess.
<p align="center">
<img width="100%" alt="Carbon example" src="https://user-images.githubusercontent.com/8397708/63456416-b27d1a80-c403-11e9-9572-105b089be885.png">
</p>
## Features
- **Customization**. Customize things like your image's syntax theme, font style, and more
- **Share quickly**. Save your image or
a link with one click
- **Save snippets**. Create an account to save snippets for later. Shared snippets are automatically unfurled on Twitter and Slack.
## Usage
#### Import
There are a few different ways to import code into Carbon:
- Drop a file onto the editor
- Append a GitHub gist ID to the URL (e.g. [`carbon.now.sh/<gist_id_goes_here>`](https://carbon.now.sh/3208813b324d82a9ebd197e4b1c3bae8))
- Or just start typing!
#### Customization
Once you've got all of your code into Carbon, you can customize your image by changing the syntax theme, background color, window theme, padding, shadows, fonts, and more.
#### Exporting & Sharing
After you've customized your image you can share your snippet in a number of ways
##### Create a saved snippet
Sharing a saved snippet will automatically unfurl the image on platforms like Twitter and Slack. This lets users see your creation, while also giving them access to the source code via the link. Better yet, if you need to make an update, simply follow the link yourself to edit the snippet directly.
To create a saved snippet:
1. Login using the "Sign in/Sign up" button
2. Edit as you normally would — your snippet will get saved automatically
3. Copy the URL from your browser window and share away!
##### Embed a snippet
This is the recommended method for sharing Carbon on your own website or blog. Readers can even copy the code with the click of a button.
You can embed any Carbon snippet in your website directly using the `carbon.now.sh/embed` URL. The "Copy Menu" lets you quickly copy the correct iFrame snippet, or the encoded URL for embedding on platforms like Medium.
Finally, you can also embed saved snippets or GitHub gists using `carbon.now.sh/embed/:id`.
##### Use the Tweet button
The Tweet button will not only share the image on Twitter, but it will also correctly encode the `alt` text to ensure your images are accessible. However, if you want to tweet image yourself, please check out [how to make your Twitter images accessible](https://help.twitter.com/en/using-twitter/picture-descriptions).
##### Download the image directly
Carbon supports downloading your image as a PNG and SVG. You can also click `Export → Open` to open your image directly in the browser. Finally, you can copy the Carbon image directly to your clipboard by going to `Copy → Image`.
#### Installing Carbon for Desktop (Offline)
If you are using Google Chrome, or another browser that supports Progressive Web Apps, you can install Carbon for use offline by:
1. Visit [carbon.now.sh](https://carbon.now.sh)
2. Click your browser's settings menu
3. Click "Install Carbon..."
## Community
Check out these projects our awesome community has created:
##### Editor Plugins
- [IntelliJ IDEA `carbon-now-sh`](https://plugins.jetbrains.com/plugin/10469-carbon-now-sh) - Open up the selection in your current IntelliJ IDEA file in Carbon through a context menu
- [Atom `carbon-now-sh`](https://atom.io/packages/carbon-now-sh) - Open up your current Atom file in Carbon with `shift-cmd-A`
- [VS Code `carbon-now-sh`](https://marketplace.visualstudio.com/items?itemName=ericadamski.carbon-now-sh) - Open up your current VS Code file in Carbon with command `carbon`
- [Sublime Text 3 `carbon-now-sh`](https://github.com/molnarmark/carbonSublime) - Open up the selection in your current Sublime Text 3 file with a custom bound key
- [Vim `carbon-now-sh`](https://github.com/kristijanhusak/vim-carbon-now-sh) - Open up the selection in your current Vim/Neovim using function `CarbonNowSh()`
- [Emacs `carbon-now-sh`](https://github.com/veelenga/carbon-now-sh.el) - Open up the selection in your current Emacs using interactive function `carbon-now-sh`
- [Xcode `carbon-now-sh`](https://github.com/StevenMagdy/CarboNow4Xcode) - Open up your current selection in `carbon.now.sh`
- [Xcode `nef`](https://github.com/bow-swift/nef-plugin) - This Xcode extension enables you to export a code selection as a Carbon snippet in a single action
##### Tools
- [CLI `carbon-now-cli`](https://github.com/mixn/carbon-now-cli) - Open a file in Carbon or download it directly using `carbon-now`, featuring an interactive mode, selective highlighting and more
- [CodeExpander](https://codeexpander.com) - A smart GitHub gist client with the TextExpander features
- [`nef`](https://github.com/bow-swift/nef#-exporting-carbon-code-snippets) - Export multiple Carbon code snippets from `Xcode Playground`.
- [`@carbonshbot`](https://t.me/carbonshbot) - A Telegram chatbot which takes in a code snippet or gist URL and generates an Carbon image
- [R `carbonate`](https://yonicd.github.io/carbonate/) - Iteratively manipulate image aesthetics in `R` and either open in Carbon or download directly.
- [Carbon for Slack](https://github.com/faisalsayed10/carbon-slack) - Use Carbon directly in Slack. Just invoke the `/carbon` command.
##### Citations
- ["CS 101 - An Introduction to Computational Thinking"](https://itunes.apple.com/us/book/id1435714196) - a computer science textbook by Sarbo Roy.
## Authors
Carbon is a project by:
- Mike Fix ([@mfix22](https://twitter.com/fixitup2))
- Brian Dennis ([@briandennis](https://github.com/briandennis))
- Jake Dexheimer ([@jakedex](https://github.com/jakedex))
#### License
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fcarbon-app%2Fcarbon?ref=badge_large)
<br />
<br />
---
## Contribute & Support
Pull requests are welcome! Please see our [contributing guidelines](/.github/CONTRIBUTING.md) for more details.
### Sponsors & Backers
[](https://fossa.com)
[](https://vercel.com?utm_source=carbon-app&utm_campaign=oss)
<a href="https://opencollective.com/carbon-app/backers/0/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/0/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/1/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/1/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/2/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/2/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/3/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/3/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/4/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/4/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/5/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/5/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/6/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/6/avatar"></a>
<a href="https://opencollective.com/carbon-app/backers/7/website" target="_blank"><img src="https://opencollective.com/carbon-app/backers/7/avatar"></a>
### Thanks To
[▲ Vercel](https://vercel.com?utm_source=carbon-app&utm_campaign=oss) for sponsoring Carbon's hosting.
### All Contributors
Thanks goes out to all these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/briandennis"><img src="https://avatars0.githubusercontent.com/u/10078572?v=4?s=100" width="100px;" alt=""/><br /><sub><b>briandennis</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=briandennis" title="Code">💻</a> <a href="https://github.com/carbon-app/carbon/commits?author=briandennis" title="Documentation">📖</a> <a href="#infra-briandennis" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/carbon-app/carbon/pulls?q=is%3Apr+reviewed-by%3Abriandennis" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/mfix22"><img src="https://avatars0.githubusercontent.com/u/8397708?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mfix22</b></sub></a><br /><a href="#question-mfix22" title="Answering Questions">💬</a> <a href="https://github.com/carbon-app/carbon/commits?author=mfix22" title="Code">💻</a> <a href="#ideas-mfix22" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/jakedex"><img src="https://avatars1.githubusercontent.com/u/10369094?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jakedex</b></sub></a><br /><a href="#question-jakedex" title="Answering Questions">💬</a> <a href="https://github.com/carbon-app/carbon/commits?author=jakedex" title="Code">💻</a> <a href="#design-jakedex" title="Design">🎨</a> <a href="#video-jakedex" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/andrewda"><img src="https://avatars1.githubusercontent.com/u/10191084?v=4?s=100" width="100px;" alt=""/><br /><sub><b>andrewda</b></sub></a><br /><a href="#question-andrewda" title="Answering Questions">💬</a> <a href="https://github.com/carbon-app/carbon/commits?author=andrewda" title="Code">💻</a> <a href="https://github.com/carbon-app/carbon/issues?q=author%3Aandrewda" title="Bug reports">🐛</a> <a href="https://github.com/carbon-app/carbon/pulls?q=is%3Apr+reviewed-by%3Aandrewda" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/yeskunall"><img src="https://avatars2.githubusercontent.com/u/14703164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yeskunall</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=yeskunall" title="Code">💻</a> <a href="https://github.com/carbon-app/carbon/commits?author=yeskunall" title="Documentation">📖</a> <a href="#tool-yeskunall" title="Tools">🔧</a> <a href="https://github.com/carbon-app/carbon/issues?q=author%3Ayeskunall" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/stoshfabricius"><img src="https://avatars1.githubusercontent.com/u/2652676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>stoshfabricius</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=stoshfabricius" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/jkling38"><img src="https://avatars1.githubusercontent.com/u/11639896?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jkling38</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=jkling38" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/otobrglez"><img src="https://avatars1.githubusercontent.com/u/225946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>otobrglez</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=otobrglez" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/darahak"><img src="https://avatars3.githubusercontent.com/u/11488612?v=4?s=100" width="100px;" alt=""/><br /><sub><b>darahak</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=darahak" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/dom96"><img src="https://avatars0.githubusercontent.com/u/246651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>dom96</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=dom96" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/elrumordelaluz"><img src="https://avatars3.githubusercontent.com/u/784056?v=4?s=100" width="100px;" alt=""/><br /><sub><b>elrumordelaluz</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=elrumordelaluz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/cjb"><img src="https://avatars2.githubusercontent.com/u/21217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>cjb</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=cjb" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Krzysztof-Cieslak"><img src="https://avatars1.githubusercontent.com/u/5427083?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krzysztof-Cieslak</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=Krzysztof-Cieslak" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/fernahh"><img src="https://avatars0.githubusercontent.com/u/2369851?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fernahh</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=fernahh" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/g3r4n"><img src="https://avatars1.githubusercontent.com/u/10941033?v=4?s=100" width="100px;" alt=""/><br /><sub><b>g3r4n</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=g3r4n" title="Code">💻</a></td>
<td align="center"><a href="http://drarok.com/"><img src="https://avatars0.githubusercontent.com/u/55830?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mat Gadd</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/issues?q=author%3ADrarok" title="Bug reports">🐛</a> <a href="https://github.com/carbon-app/carbon/commits?author=Drarok" title="Code">💻</a></td>
<td align="center"><a href="https://bradlab.co.uk"><img src="https://avatars1.githubusercontent.com/u/11805775?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brad Davies</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/issues?q=author%3Avarbrad" title="Bug reports">🐛</a> <a href="https://github.com/carbon-app/carbon/commits?author=varbrad" title="Code">💻</a></td>
<td align="center"><a href="http://www.rafaelcamaram.com/"><img src="https://avatars1.githubusercontent.com/u/9087886?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rafael Câmara</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=rafaelcamaram" title="Code">💻</a></td>
<td align="center"><a href="https://glebbahmutov.com/"><img src="https://avatars1.githubusercontent.com/u/2212006?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gleb Bahmutov</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=bahmutov" title="Tests">⚠️</a> <a href="#tool-bahmutov" title="Tools">🔧</a></td>
<td align="center"><a href="https://ivan-munguia.netlify.com"><img src="https://avatars2.githubusercontent.com/u/10677789?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Iván Munguía</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=warborn" title="Code">💻</a></td>
<td align="center"><a href="https://dillonmulroy.com"><img src="https://avatars1.githubusercontent.com/u/2755722?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dillon Mulroy</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=dmmulroy" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/NARKOZ"><img src="https://avatars2.githubusercontent.com/u/253398?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nihad Abbasov</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=NARKOZ" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/imbrn"><img src="https://avatars1.githubusercontent.com/u/1906977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bruno C. Couto</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=imbrn" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/molnarmark"><img src="https://avatars2.githubusercontent.com/u/13263073?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mark Molnar</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=molnarmark" title="Code">💻</a></td>
<td align="center"><a href="https://www.behance.net/tetra2000"><img src="https://avatars1.githubusercontent.com/u/1459450?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Takahiko Inayama</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=TETRA2000" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/martinfrancois"><img src="https://avatars1.githubusercontent.com/u/14319020?v=4?s=100" width="100px;" alt=""/><br /><sub><b>François Martin</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=martinfrancois" title="Code">💻</a></td>
<td align="center"><a href="http://raphamorim.io"><img src="https://avatars3.githubusercontent.com/u/3630346?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Raphael Amorim</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=raphamorim" title="Code">💻</a></td>
<td align="center"><a href="http://camronflanders.com"><img src="https://avatars0.githubusercontent.com/u/27292?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Camron Flanders</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=camflan" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ericadamski"><img src="https://avatars0.githubusercontent.com/u/6516758?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Adamski</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=ericadamski" title="Code">💻</a></td>
<td align="center"><a href="http://winnercrespo.com"><img src="https://avatars0.githubusercontent.com/u/4671080?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Winner Crespo</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=wistcc" title="Code">💻</a> <a href="#design-wistcc" title="Design">🎨</a></td>
<td align="center"><a href="http://twitter.com/mixn"><img src="https://avatars3.githubusercontent.com/u/672237?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Milos</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=mixn" title="Code">💻</a> <a href="#tool-mixn" title="Tools">🔧</a> <a href="https://github.com/carbon-app/carbon/commits?author=mixn" title="Documentation">📖</a> <a href="#translation-mixn" title="Translation">🌍</a></td>
<td align="center"><a href="https://mittalyashu.now.sh/"><img src="https://avatars1.githubusercontent.com/u/29014463?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yashu Mittal</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=mittalyashu" title="Code">💻</a></td>
<td align="center"><a href="https://twitter.com/bberrycarmen"><img src="https://avatars0.githubusercontent.com/u/22792183?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rachel M. Carmena</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=rachelcarmena" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.linkedin.com/in/miguel-salazar-823b07a1/"><img src="https://avatars0.githubusercontent.com/u/19369949?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Miguel Salazar</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=ucdstudent95618" title="Documentation">📖</a> <a href="#translation-ucdstudent95618" title="Translation">🌍</a></td>
<td align="center"><a href="https://www.linkedin.com/in/vyom-jain-233a28139"><img src="https://avatars3.githubusercontent.com/u/19145803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vyom Jain</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=vvyomjjain" title="Documentation">📖</a> <a href="#translation-vvyomjjain" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="http://www.cnblogs.com/racaljk/"><img src="https://avatars0.githubusercontent.com/u/5010047?v=4?s=100" width="100px;" alt=""/><br /><sub><b>racaljk</b></sub></a><br /><a href="#translation-racaljk" title="Translation">🌍</a></td>
<td align="center"><a href="https://lastblocklabs.com"><img src="https://avatars3.githubusercontent.com/u/3892149?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=raboid" title="Code">💻</a></td>
<td align="center"><a href="http://izabelacborges.com/"><img src="https://avatars0.githubusercontent.com/u/19255077?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Izabela Borges</b></sub></a><br /><a href="#translation-izabelacborges" title="Translation">🌍</a></td>
<td align="center"><a href="https://ghuser.io/shinilms"><img src="https://avatars2.githubusercontent.com/u/18573510?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shinil M S</b></sub></a><br /><a href="#translation-shinilms" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/berkeatac"><img src="https://avatars1.githubusercontent.com/u/10579633?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Berke Atac</b></sub></a><br /><a href="#translation-berkeatac" title="Translation">🌍</a></td>
<td align="center"><a href="https://wooooooak.github.io/"><img src="https://avatars3.githubusercontent.com/u/18481078?v=4?s=100" width="100px;" alt=""/><br /><sub><b>LEE YONGJUN</b></sub></a><br /><a href="#translation-wooooooak" title="Translation">🌍</a></td>
<td align="center"><a href="http://matthewnielsen.ca"><img src="https://avatars2.githubusercontent.com/u/35040439?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthew Nielsen</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=MatthewNielsen27" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.boy.sh"><img src="https://avatars2.githubusercontent.com/u/225410?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Boy</b></sub></a><br /><a href="#platform-boyvanamstel" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://vetrivelcsamy.xyz"><img src="https://avatars2.githubusercontent.com/u/26738977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vetrivel Chinnasamy</b></sub></a><br /><a href="#translation-vetrivelcsamy" title="Translation">🌍</a></td>
<td align="center"><a href="https://farzadyz.com"><img src="https://avatars2.githubusercontent.com/u/8332043?s=460&v=4?s=100" width="100px;" alt=""/><br /><sub><b>Farzad YZ</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=farskid" title="Code">💻</a> <a href="#ideas-farskid" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/yannickl"><img src="https://avatars0.githubusercontent.com/u/798235?s=460&v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yannick Loriot</b></sub></a><br /><a href="#translation-yannickl" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Joel-hanson"><img src="https://avatars2.githubusercontent.com/u/17215044?s=460&v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joel Hanson</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=Joel-hanson" title="Code">💻</a></td>
<td align="center"><a href="https://muzzammil.xyz/?ref=github"><img src="https://avatars2.githubusercontent.com/u/12321712?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Muzzammil</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=muhammadmuzzammil1998" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/souppower"><img src="https://avatars2.githubusercontent.com/u/19849611?v=4?s=100" width="100px;" alt=""/><br /><sub><b>souppower</b></sub></a><br /><a href="#infra-souppower" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center"><a href="http://uraway.hatenablog.com/"><img src="https://avatars3.githubusercontent.com/u/15242484?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Masato Urai (@uraway_)</b></sub></a><br /><a href="#translation-uraway" title="Translation">🌍</a></td>
<td align="center"><a href="http://techinpark.com"><img src="https://avatars3.githubusercontent.com/u/45546296?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fernando</b></sub></a><br /><a href="#translation-techinpark" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/megsachdev"><img src="https://avatars1.githubusercontent.com/u/22325351?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Megha Sachdev</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=megsachdev" title="Code">💻</a> <a href="https://github.com/carbon-app/carbon/commits?author=megsachdev" title="Tests">⚠️</a></td>
<td align="center"><a href="https://techgeekhub.ml"><img src="https://avatars0.githubusercontent.com/u/6022231?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anudeep Reddy</b></sub></a><br /><a href="#infra-anudeepreddy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://munieru.jp"><img src="https://avatars2.githubusercontent.com/u/20086673?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Munieru</b></sub></a><br /><a href="#translation-munierujp" title="Translation">🌍</a></td>
<td align="center"><a href="http://www.etoxin.net"><img src="https://avatars0.githubusercontent.com/u/393002?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Lusted</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=etoxin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/JoseNoriegaa"><img src="https://avatars2.githubusercontent.com/u/28733681?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jose Noriega</b></sub></a><br /><a href="#translation-JoseNoriegaa" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://discord.club"><img src="https://avatars2.githubusercontent.com/u/33966852?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Merlin Fuchs</b></sub></a><br /><a href="#translation-Merlintor" title="Translation">🌍</a></td>
<td align="center"><a href="https://glossier.com"><img src="https://avatars0.githubusercontent.com/u/23482161?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ramy Majouji</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=majouji" title="Code">💻</a></td>
<td align="center"><a href="http://stackoverflow.com/users/872395/nemesv"><img src="https://avatars0.githubusercontent.com/u/251330?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viktor Nemes</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=nemesv" title="Code">💻</a></td>
<td align="center"><a href="https://ericwbailey.design/"><img src="https://avatars3.githubusercontent.com/u/634191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Bailey</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=ericwbailey" title="Code">💻</a></td>
<td align="center"><a href="http://rsg-sweden.iscbsc.org"><img src="https://avatars0.githubusercontent.com/u/6730853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nazeefa</b></sub></a><br /><a href="#translation-Nazeeefa" title="Translation">🌍</a></td>
<td align="center"><a href="https://medium.com/@pratikbutani/carbon-create-and-share-beautiful-images-of-your-source-code-d31dedfe64bd"><img src="https://avatars2.githubusercontent.com/u/3306366?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pratik Butani</b></sub></a><br /><a href="#blog-pratikbutani" title="Blogposts">📝</a></td>
<td align="center"><a href="https://github.com/baktiaditya"><img src="https://avatars0.githubusercontent.com/u/2070906?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bakti Aditya</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=baktiaditya" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/aquaductape"><img src="https://avatars1.githubusercontent.com/u/29286430?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Caleb Taylor</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=aquaductape" title="Code">💻</a></td>
<td align="center"><a href="http://about.rmunhoz.me"><img src="https://avatars3.githubusercontent.com/u/3948961?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rogério Munhoz</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=rjmunhoz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/technoknol"><img src="https://avatars0.githubusercontent.com/u/6429418?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Technoknol</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=technoknol" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tmakowski"><img src="https://avatars3.githubusercontent.com/u/38053499?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tymoteusz Makowski</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=tmakowski" title="Code">💻</a></td>
<td align="center"><a href="https://nisar.dev"><img src="https://avatars3.githubusercontent.com/u/46004116?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nisar Hassan Naqvi</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/issues?q=author%3Anisarhassan12" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://www.wapgee.com"><img src="https://avatars2.githubusercontent.com/u/42450390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ilyas Karim</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/issues?q=author%3Ailyaskarim" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://nickfix.me"><img src="https://avatars2.githubusercontent.com/u/6845581?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nick Fix</b></sub></a><br /><a href="#ideas-njfix6" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center"><a href="https://noti.st/melsumner"><img src="https://avatars0.githubusercontent.com/u/4587451?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Melanie Sumner</b></sub></a><br /><a href="#ideas-MelSumner" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://aluc.io/"><img src="https://avatars2.githubusercontent.com/u/15520015?v=4?s=100" width="100px;" alt=""/><br /><sub><b>aluc</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=b6pzeusbc54tvhw5jgpyw8pwz2x6gs" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mearns"><img src="https://avatars1.githubusercontent.com/u/5140254?v=4?s=100" width="100px;" alt=""/><br /><sub><b>B. Mearns</b></sub></a><br /><a href="#ideas-mearns" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://jiepeng.me"><img src="https://avatars3.githubusercontent.com/u/10325111?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peng Jie</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=neighborhood999" title="Code">💻</a></td>
<td align="center"><a href="https://binyam.in"><img src="https://avatars3.githubusercontent.com/u/39805353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Binyamin Aron Green</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=b3u" title="Code">💻</a></td>
<td align="center"><a href="https://dev.to/mbiesiad"><img src="https://avatars0.githubusercontent.com/u/18367606?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michal</b></sub></a><br /><a href="#translation-mbiesiad" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/qw-in"><img src="https://avatars0.githubusercontent.com/u/19194187?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Quinn Blenkinsop</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=qw-in" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/seagalputra"><img src="https://avatars0.githubusercontent.com/u/15377132?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dwiferdio Seagal Putra</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=seagalputra" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ashwoodall"><img src="https://avatars3.githubusercontent.com/u/14588617?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ashley Woodall Clark</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=ashwoodall" title="Code">💻</a></td>
<td align="center"><a href="https://tim.wienk.name/"><img src="https://avatars0.githubusercontent.com/u/150598?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Wienk</b></sub></a><br /><a href="#translation-timwienk" title="Translation">🌍</a></td>
<td align="center"><a href="http://georgemccarron.com/"><img src="https://avatars2.githubusercontent.com/u/9155723?v=4?s=100" width="100px;" alt=""/><br /><sub><b>George McCarron</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=george1410" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/artmxra7"><img src="https://avatars.githubusercontent.com/u/23070604?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Erwin Rahayu</b></sub></a><br /><a href="#translation-artmxra7" title="Translation">🌍</a> <a href="https://github.com/carbon-app/carbon/commits?author=artmxra7" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/mlucap"><img src="https://avatars.githubusercontent.com/u/36577976?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Luca</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=mlucap" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/adghayes"><img src="https://avatars.githubusercontent.com/u/37960853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Hayes</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=adghayes" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/heybereket"><img src="https://avatars.githubusercontent.com/u/68391329?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bereket Semagn</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=heybereket" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/LorenzoLancia"><img src="https://avatars.githubusercontent.com/u/44911690?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lorenzo Lancia</b></sub></a><br /><a href="#translation-LorenzoLancia" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Guy-Adler"><img src="https://avatars.githubusercontent.com/u/44903310?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Guy Adler</b></sub></a><br /><a href="#translation-Guy-Adler" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/danBamikiya"><img src="https://avatars.githubusercontent.com/u/58262528?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dan Bamikiya</b></sub></a><br /><a href="#ideas-danBamikiya" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/kewang"><img src="https://avatars.githubusercontent.com/u/795839?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kewang</b></sub></a><br /><a href="#translation-kewang" title="Translation">🌍</a></td>
<td align="center"><a href="https://rizda.win/"><img src="https://avatars.githubusercontent.com/u/13027142?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rizda Dwi Prasetya</b></sub></a><br /><a href="#content-rizdaprasetya" title="Content">🖋</a></td>
<td align="center"><a href="https://github.com/AbreuY"><img src="https://avatars.githubusercontent.com/u/5095949?v=4?s=100" width="100px;" alt=""/><br /><sub><b>AbreuY</b></sub></a><br /><a href="#translation-AbreuY" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.rizkytegar.com/"><img src="https://avatars.githubusercontent.com/u/55475891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>>_Rizky.dev</b></sub></a><br /><a href="#translation-rizkytegar" title="Translation">🌍</a> <a href="https://github.com/carbon-app/carbon/commits?author=rizkytegar" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/OpeAbidemi"><img src="https://avatars.githubusercontent.com/u/58413594?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abidemi Harry</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=OpeAbidemi" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mcognetta"><img src="https://avatars.githubusercontent.com/u/6856636?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Marco</b></sub></a><br /><a href="#translation-mcognetta" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/samrobbins85"><img src="https://avatars.githubusercontent.com/u/29740136?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sam Robbins</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=samrobbins85" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/alceil"><img src="https://avatars.githubusercontent.com/u/47685349?v=4?s=100" width="100px;" alt=""/><br /><sub><b>alceil</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=alceil" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/hatsu38"><img src="https://avatars.githubusercontent.com/u/16137809?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hatsu</b></sub></a><br /><a href="https://github.com/carbon-app/carbon/commits?author=hatsu38" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/praveenscience"><img src="https://avatars.githubusercontent.com/u/1830380?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Praveen Kumar Purushothaman</b></sub></a><br /><a href="#translation-praveenscience" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Amirosagan"><img src="https://avatars.githubusercontent.com/u/53624184?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Amir Elsagan</b></sub></a><br /><a href="#translation-Amirosagan" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/korzck"><img src="https://avatars.githubusercontent.com/u/87325587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>korzck</b></sub></a><br /><a href="#translation-korzck" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/sakibulalam"><img src="https://avatars.githubusercontent.com/u/4949223?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sakibul Alam</b></sub></a><br /><a href="#translation-sakibulalam" title="Translation">🌍</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
================================================
FILE: SECURITY.md
================================================
# Security Policy
Please contact us at [carbon.now.sh+security@gmail.com](mailto:carbon.now.sh+security@gmail.com) to privately inform us of any security vulnerability or issue.
================================================
FILE: bin/deploy.sh
================================================
#!/usr/bin/env bash
set -e
vercel switch carbon-app
VERCEL_URL=$(vercel)
yarn cy:run --config baseUrl="$VERCEL_URL"
echo "$VERCEL_URL"| tee /dev/tty | pbcopy
read -p "Deploy to production (y/N)?" -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
vercel alias "$VERCEL_URL" carbon.now.sh
fi
================================================
FILE: components/Announcement.js
================================================
import React from 'react'
// Feature flag
const ACTIVE = false
const key = 'CARBON_CTA_4'
function Toast() {
const [open, setState] = React.useState(false)
React.useEffect(() => {
window.localStorage.removeItem('CARBON_CTA_2')
window.localStorage.removeItem('CARBON_CTA_3')
if (!window.localStorage.getItem(key)) {
setState(true)
}
}, [])
if (process.env.NODE_ENV !== 'production') {
return null
}
if (!ACTIVE) {
return null
}
if (!open) {
return null
}
function close() {
setState(false)
window.localStorage.setItem(key, true)
}
return (
<div className="toast">
<div className="toast-content">
<p>Black Lives Matter.</p>
<a href="https://www.joincampaignzero.org" target="_blank" rel="noreferrer noopener">
Help end police violence in America →
</a>
<button className="close-toast" onClick={close}>
×
</button>
</div>
<style jsx>
{`
@keyframes slide {
from {
transform: translateY(-128px);
}
to {
transform: translateY(0px);
}
}
.toast {
margin: -4.6rem auto var(--x4);
padding: 8px 16px;
color: #fff;
border: 1px solid #fff;
border-radius: 3px;
animation-name: slide;
animation-duration: 600ms;
}
.toast-content {
display: flex;
align-items: center;
}
a {
text-decoration: underline;
margin-left: 8px;
}
.close-toast {
padding-left: 0;
padding-right: 0;
background: transparent;
color: white;
border: none;
font-size: 100%;
margin-left: 16px;
text-decoration: none;
cursor: pointer;
}
p {
margin: 0;
}
@media (max-width: 840px) {
.toast {
display: none;
}
}
`}
</style>
</div>
)
}
export default Toast
================================================
FILE: components/ApiContext.js
================================================
import React from 'react'
import api from '../lib/api'
const Context = React.createContext(api)
export function useAPI() {
return React.useContext(Context)
}
export default Context
================================================
FILE: components/AuthContext.js
================================================
import React from 'react'
import firebase from '../lib/client'
// IDEA: just read from firebase store at request time?
import { client } from '../lib/api'
export const Context = React.createContext(null)
export function useAuth() {
return React.useContext(Context)
}
function AuthContext(props) {
const [user, setState] = React.useState(null)
React.useEffect(() => {
if (firebase) {
firebase.auth().onAuthStateChanged(newUser => setState(newUser))
}
}, [])
React.useEffect(() => {
if (user) {
user.getIdToken().then(jwt => {
client.defaults.headers['Authorization'] = jwt ? `Bearer ${jwt}` : undefined
})
} else {
delete client.defaults.headers['Authorization']
}
}, [user])
return <Context.Provider value={user}>{props.children}</Context.Provider>
}
export default AuthContext
================================================
FILE: components/BackgroundSelect.js
================================================
import React from 'react'
import ImagePicker from './ImagePicker'
import ColorPicker from './ColorPicker'
import Button from './Button'
import Popout, { managePopout } from './Popout'
import { COLORS, DEFAULT_BG_COLOR } from '../lib/constants'
import { stringifyColor } from '../lib/util'
function validateColor(str) {
if (/#\d{3,6}|rgba{0,1}\(.*?\)/gi.test(str) || /\w+/gi.test(str)) {
return str
}
}
class BackgroundSelect extends React.PureComponent {
selectTab = name => {
if (this.props.mode !== name) {
this.props.onChange({ backgroundMode: name })
}
}
handlePickColor = color => this.props.onChange({ backgroundColor: stringifyColor(color) })
render() {
const {
color,
mode,
image,
onChange,
isVisible,
toggleVisibility,
carbonRef,
updateHighlights,
} = this.props
const background = validateColor(color) ? color : DEFAULT_BG_COLOR
const aspectRatio = carbonRef ? carbonRef.clientWidth / carbonRef.clientHeight : 1
return (
<div className="bg-select-container">
<Button
title="Background Menu"
border
center
selected={isVisible}
className={`bg-color-container bg-select-display ${isVisible ? 'is-visible' : ''}`}
onClick={toggleVisibility}
background="white"
hoverBackground="white"
data-cy="display"
>
<div className="bg-color-alpha" />
<div className="bg-color" />
</Button>
<Popout
id="bg-select-pickers"
pointerLeft="15px"
hidden={!isVisible}
style={{ width: '222px' }}
>
<div className="picker-tabs">
{['color', 'image'].map(tab => (
<Button
key={tab}
padding="8px 0"
center
className="capitalize"
onClick={this.selectTab.bind(null, tab)}
background={mode === tab ? COLORS.BLACK : COLORS.DARK_GRAY}
>
{tab}
</Button>
))}
</div>
<div className="picker-tabs-contents">
<div hidden={mode !== 'color'}>
<ColorPicker color={color} onChange={this.handlePickColor} />
</div>
<div hidden={mode !== 'image'}>
<ImagePicker
onChange={onChange}
imageDataURL={image}
aspectRatio={aspectRatio}
updateHighlights={updateHighlights}
/>
</div>
</div>
</Popout>
<style jsx>
{`
.bg-select-container {
height: 100%;
}
.bg-select-container :global(.bg-select-display) {
position: relative;
overflow: hidden;
height: 100%;
width: 40px;
border: 1px solid ${COLORS.SECONDARY};
}
.bg-select-container :global(.bg-select-display.is-visible),
.bg-select-container :global(.bg-select-display:focus) {
border-width: 2px;
}
.bg-color {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
${mode === 'image'
? `background: url(${image});
background-size: cover;
background-repeat: no-repeat;`
: `background: ${background};`};
}
.bg-color-alpha {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==);
}
.picker-tabs {
display: flex;
border-bottom: 2px solid ${COLORS.SECONDARY};
}
.picker-tabs :global(button) {
border-right: 1px solid ${COLORS.SECONDARY};
}
.picker-tabs :global(button:last-child) {
border-right: none;
}
`}
</style>
</div>
)
}
}
export default managePopout(BackgroundSelect)
================================================
FILE: components/Billing.js
================================================
import React from 'react'
import { useAsyncCallback } from 'actionsack'
import Button from './Button'
import Input from './Input'
import { useAuth } from './AuthContext'
import LoginButton from './LoginButton'
import { COLORS } from '../lib/constants'
const X = (
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17">
<path
className="base"
fill="white"
d="M8.5,17 C3.80557963,17 0,13.1944204 0,8.5 C0,3.80557963 3.80557963,0 8.5,0 C13.1944204,0 17,3.80557963 17,8.5 C17,13.1944204 13.1944204,17 8.5,17 Z"
/>
<path
className="glyph"
fill={COLORS.BLACK}
d="M8.5,7.29791847 L6.12604076,4.92395924 C5.79409512,4.59201359 5.25590488,4.59201359 4.92395924,4.92395924 C4.59201359,5.25590488 4.59201359,5.79409512 4.92395924,6.12604076 L7.29791847,8.5 L4.92395924,10.8739592 C4.59201359,11.2059049 4.59201359,11.7440951 4.92395924,12.0760408 C5.25590488,12.4079864 5.79409512,12.4079864 6.12604076,12.0760408 L8.5,9.70208153 L10.8739592,12.0760408 C11.2059049,12.4079864 11.7440951,12.4079864 12.0760408,12.0760408 C12.4079864,11.7440951 12.4079864,11.2059049 12.0760408,10.8739592 L9.70208153,8.5 L12.0760408,6.12604076 C12.4079864,5.79409512 12.4079864,5.25590488 12.0760408,4.92395924 C11.7440951,4.59201359 11.2059049,4.59201359 10.8739592,4.92395924 L8.5,7.29791847 L8.5,7.29791847 Z"
/>
</svg>
)
export default function Billing() {
const user = useAuth()
const [submit, { error, loading }] = useAsyncCallback(() => true)
const success = true
if (!user) {
return (
<div className="login">
<div>
<LoginButton />
</div>
<style jsx>
{`
.login {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
`}
</style>
</div>
)
}
return (
<div className="checkout">
{success ? (
<div className="column">
<h4>Thank you for supporting Carbon!</h4>
<p className="success">
However, Carbon Diamond is not quite ready yet.
<br />
{/* Your card has <u>not</u> been charged or saved today. */}
<br />
We greatly appreciate your support, and will let you know when premium features launch!
</p>
<p className="success">
— the Carbon Team{' '}
<span role="img" aria-label="Black and yellow hearts">
💛🖤
</span>
</p>
</div>
) : (
<div className="column">
<h4>
Upgrade to <span>Diamond</span>
<br />
<span className="tag">($5.00 / month)</span>
</h4>
<p>Please enter a credit or debit card:</p>
<form onSubmit={submit}>
<fieldset>
{/** Insert Stripe element here */}
<hr />
<Input placeholder="Cardholders's name…" name="name" required />
</fieldset>
<small>
(By clicking subscribe, you are accepting the{' '}
<a href="/terms">terms and conditions</a>)
</small>
<br />
<Button
display="inline-flex"
border
large
padding="8px 16px"
margin="8px 0 0"
type="submit"
color="rgba(255, 255, 255, 0.7)"
>
{loading ? 'Sending…' : 'Subscribe'}
</Button>
<div className={`error ${error ? 'visible' : ''}`} role="alert">
{X}
<span className="message">{error}</span>
</div>
</form>
</div>
)}
<style jsx>
{`
.checkout {
position: relative;
font-size: 16px;
font-weight: 500;
border-radius: 4px;
padding: 1rem 1.5rem;
color: white;
background-color: black;
}
a {
text-decoration: underline;
}
p {
margin: 0 0 8px;
font-size: 12px;
font-weight: normal;
}
small {
font-size: 10px;
}
h4 {
font-size: 32px;
margin: 0 0 2rem;
}
.tag {
display: block;
font-weight: lighter;
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
margin-top: 0.25rem;
}
hr {
border: 0;
height: 1px;
margin: 0.5rem 0 1rem;
background: ${COLORS.SECONDARY};
}
fieldset {
width: 100%;
margin: 0 0 2.5rem;
padding: 0.5rem 0.5rem 0.75rem;
border: 1px solid ${COLORS.SECONDARY};
border-radius: 4px;
}
fieldset :global(input) {
text-align: left;
font-size: 16px;
color: ${COLORS.BLUE};
}
fieldset :global(input::placeholder) {
opacity: 1;
color: rgba(255, 255, 255, 0.7);
}
fieldset :global(.StripeElement) {
width: 100%;
padding: 12px 16px 12px 0;
}
form:valid :global(button) {
color: ${COLORS.BLUE};
box-shadow: inset 0px 0px 0px 1px ${COLORS.BLUE};
}
.error {
display: inline-flex;
justify-content: flex-start;
align-items: center;
position: relative;
top: +3px;
opacity: 0;
margin-left: 1rem;
font-size: 12px;
transform: translateY(20px);
transition-property: opacity, transform;
transition-duration: 0.35s;
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}
.error.visible {
opacity: 1;
transform: none;
}
.error svg {
margin-top: -1px;
}
.error .message {
margin-left: 8px;
font-size: inherit;
color: ${COLORS.RED};
}
.success {
font-size: 16px;
line-height: 1.5;
margin: 0 0 2rem;
}
`}
</style>
</div>
)
}
================================================
FILE: components/Button.js
================================================
import React from 'react'
import VisuallyHidden from '@reach/visually-hidden'
import { COLORS } from '../lib/constants'
const Button = ({
id,
onClick = () => {},
background = COLORS.BLACK,
color = COLORS.SECONDARY,
textColor,
hoverBackground = COLORS.HOVER,
hoverColor,
disabled,
selected,
children,
border,
center,
large,
flex = 1,
padding = 0,
margin = 0,
title,
Component = 'button',
display,
...props
}) => (
<Component id={id} onClick={onClick} disabled={disabled} {...props}>
{title && <VisuallyHidden>{title}</VisuallyHidden>}
{children}
<style jsx>
{`
${Component} {
display: ${display || 'flex'};
flex: ${flex};
background-color: ${background};
color: ${textColor || color};
box-shadow: ${border ? `inset 0px 0px 0px ${selected ? 2 : 1}px ${color}` : 'initial'};
cursor: ${disabled ? 'not-allowed' : 'pointer'};
outline: none;
border: none;
padding: ${padding};
margin: ${margin};
border-radius: ${border ? '3px' : 0};
user-select: none;
justify-content: ${center ? 'center' : 'initial'};
align-items: ${center ? 'center' : 'initial'};
align-self: stretch;
font-size: ${large ? '14px' : '12px'};
}
${Component}:hover, ${Component}:focus {
background-color: ${hoverBackground} !important;
color: ${hoverColor || color};
}
${Component}:focus {
box-shadow: ${border ? `inset 0px 0px 0px 2px ${color}` : 'initial'};
}
`}
</style>
</Component>
)
export default Button
================================================
FILE: components/Carbon.js
================================================
import React from 'react'
import ReactDOM from 'react-dom'
import dynamic from 'next/dynamic'
import hljs from 'highlight.js/lib/core'
import javascript from 'highlight.js/lib/languages/javascript'
import debounce from 'lodash.debounce'
import ms from 'ms'
import { Controlled as CodeMirror } from 'react-codemirror2'
hljs.registerLanguage('javascript', javascript)
import { Spinner } from './Spinner'
import WindowControls from './WindowControls'
import WidthHandler from './WidthHandler'
import {
COLORS,
LANGUAGES,
LANGUAGE_MODE_HASH,
LANGUAGE_NAME_HASH,
LANGUAGE_MIME_HASH,
DEFAULT_SETTINGS,
THEMES_HASH,
} from '../lib/constants'
const SelectionEditor = dynamic(() => import('./SelectionEditor'), {
loading: () => null,
})
const Watermark = dynamic(() => import('./svg/Watermark'), {
loading: () => null,
})
function searchLanguage(l) {
return LANGUAGE_NAME_HASH[l] || LANGUAGE_MODE_HASH[l] || LANGUAGE_MIME_HASH[l]
}
function noop() {}
function getUnderline(underline) {
switch (underline) {
case 1:
return 'underline'
case 2:
/**
* Chrome will only round to the nearest wave, causing visual inconsistencies
* https://stackoverflow.com/questions/57559588/how-to-make-the-wavy-underline-extend-cover-all-the-characters-in-chrome
*/
return `${COLORS.RED} wavy underline; text-decoration-skip-ink: none`
}
return 'initial'
}
class Carbon extends React.PureComponent {
static defaultProps = {
onChange: noop,
onGutterClick: noop,
}
state = {}
handleLanguageChange = debounce(
(newCode, language) => {
if (language === 'auto') {
// try to set the language
const detectedLanguage = hljs.highlightAuto(newCode).language
const languageMode = searchLanguage(detectedLanguage)
if (languageMode) {
return languageMode.mime || languageMode.mode
}
}
const languageMode = searchLanguage(language)
if (languageMode) {
return languageMode.mime || languageMode.mode
}
return language
},
ms('300ms'),
{
leading: true,
trailing: true,
}
)
onBeforeChange = (editor, meta, code) => {
if (!this.props.readOnly) {
this.props.onChange(code)
}
}
onSelection = (ed, data) => {
if (this.props.readOnly) {
return
}
const selection = data.ranges[0]
if (
selection.head.line === selection.anchor.line &&
selection.head.ch === selection.anchor.ch
) {
return (this.currentSelection = null)
}
if (selection.head.line + selection.head.ch > selection.anchor.line + selection.anchor.ch) {
this.currentSelection = {
from: selection.anchor,
to: selection.head,
}
} else {
this.currentSelection = {
from: selection.head,
to: selection.anchor,
}
}
}
onMouseUp = () => {
if (this.currentSelection) {
this.setState({ selectionAt: this.currentSelection }, () => {
this.currentSelection = null
})
} else {
this.setState({ selectionAt: null })
}
}
onSelectionChange = changes => {
if (this.state.selectionAt) {
const css = [
changes.bold != null && `font-weight: ${changes.bold ? 'bold' : 'initial'}`,
changes.italics != null && `font-style: ${changes.italics ? 'italic' : 'initial'}`,
changes.underline != null && `text-decoration: ${getUnderline(changes.underline)}`,
changes.color != null && `color: ${changes.color} !important`,
]
.filter(Boolean)
.join('; ')
if (css) {
this.props.editorRef.current.editor.doc.markText(
this.state.selectionAt.from,
this.state.selectionAt.to,
{ css }
)
}
}
}
render() {
const config = { ...DEFAULT_SETTINGS, ...this.props.config }
const languageMode = this.handleLanguageChange(
this.props.children,
config.language && config.language.toLowerCase()
)
const options = {
screenReaderLabel: 'Code editor',
lineNumbers: config.lineNumbers,
firstLineNumber: config.firstLineNumber,
mode: languageMode || 'plaintext',
theme: config.theme,
scrollbarStyle: null,
viewportMargin: Infinity,
lineWrapping: true,
smartIndent: true,
extraKeys: {
'Shift-Tab': 'indentLess',
},
readOnly: this.props.readOnly,
showInvisibles: config.hiddenCharacters,
autoCloseBrackets: true,
}
const backgroundImage =
(this.props.config.backgroundImage && this.props.config.backgroundImageSelection) ||
this.props.config.backgroundImage
const themeConfig = this.props.theme || THEMES_HASH[config.theme]
const light = themeConfig && themeConfig.light
/* eslint-disable jsx-a11y/no-static-element-interactions */
const selectionNode =
!this.props.readOnly &&
!!this.state.selectionAt &&
document.getElementById('style-editor-button')
return (
<div className="section">
<div
ref={this.props.innerRef}
id="export-container"
className="export-container"
onMouseUp={this.onMouseUp}
>
{this.props.loading ? (
// TODO investigate removing these hard-coded values
<div style={{ width: 876, height: 240 }}>
<Spinner />
</div>
) : (
<div className="container">
{config.windowControls ? (
<WindowControls
titleBar={this.props.titleBar}
onTitleBarChange={this.props.onTitleBarChange}
theme={config.windowTheme}
code={this.props.children}
copyable={this.props.copyable}
light={light}
/>
) : null}
<CodeMirror
ref={this.props.editorRef}
className={`CodeMirror__container window-theme__${config.windowTheme}`}
value={this.props.children}
options={options}
onBeforeChange={this.onBeforeChange}
onGutterClick={this.props.onGutterClick}
onSelection={this.onSelection}
/>
{config.watermark && <Watermark light={light} />}
<div className="container-bg">
<div className="white eliminateOnRender" />
<div className="alpha eliminateOnRender" />
<div className="bg" />
</div>
{/* TODO pass in this child as a prop to Carbon */}
<WidthHandler
innerRef={this.props.innerRef}
onChange={this.props.updateWidth}
onChangeComplete={this.props.updateWidthConfirm}
paddingHorizontal={config.paddingHorizontal}
paddingVertical={config.paddingVertical}
/>
</div>
)}
</div>
{selectionNode &&
ReactDOM.createPortal(
<SelectionEditor onChange={this.onSelectionChange} />,
// TODO: don't use portal?
selectionNode
)}
<style jsx>
{`
.container {
position: relative;
min-width: ${config.widthAdjustment ? '90px' : 'auto'};
max-width: ${config.widthAdjustment ? '1024px' : 'none'};
${config.widthAdjustment ? '' : `width: ${config.width}px;`}
padding: ${config.paddingVertical} ${config.paddingHorizontal};
}
.container :global(.watermark) {
fill-opacity: 0.75;
position: absolute;
z-index: 2;
bottom: calc(${config.paddingVertical} + 16px);
right: calc(${config.paddingHorizontal} + 16px);
}
.container .container-bg {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.container .white {
background: #fff;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.container .bg {
${this.props.config.backgroundMode === 'image'
? `background: url(${backgroundImage});
background-size: cover;
background-repeat: no-repeat;`
: `background: ${this.props.config.backgroundColor || config.backgroundColor};
background-size: auto;
background-repeat: repeat;`}
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.container .alpha {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==);
}
.container :global(.CodeMirror-gutters) {
background-color: unset;
border-right: none;
}
.container :global(.CodeMirror__container) {
min-width: inherit;
position: relative;
z-index: 1;
border-radius: 5px;
${config.dropShadow
? `box-shadow: 0 ${config.dropShadowOffsetY} ${config.dropShadowBlurRadius} rgba(0, 0, 0, 0.55)`
: ''};
}
.container :global(.CodeMirror__container .CodeMirror) {
height: auto;
min-width: inherit;
padding: 18px 18px;
padding-left: 12px;
${config.lineNumbers ? 'padding-left: 12px;' : ''} border-radius: 5px;
font-family: ${config.fontFamily}, monospace !important;
font-size: ${config.fontSize};
line-height: ${config.lineHeight};
font-variant-ligatures: contextual;
font-feature-settings: 'calt' 1;
user-select: none;
}
.container :global(.CodeMirror-scroll),
.container :global(.CodeMirror-hscrollbar) {
overflow: hidden !important;
}
.container :global(.window-theme__sharp > .CodeMirror) {
border-radius: 0px;
}
.container :global(.window-theme__bw > .CodeMirror) {
border: 2px solid ${COLORS.SECONDARY};
}
.container :global(.window-controls + .CodeMirror__container > .CodeMirror) {
padding-top: 48px;
}
.container :global(.CodeMirror-linenumber) {
cursor: pointer;
}
.container :global(.CodeMirror-cursor) {
visibility: ${this.props.readOnly ? 'hidden' : ''};
}
@media (max-width: 768px) {
/* show cursor on mobile */
.container :global([contenteditable='true']) {
user-select: text;
}
.container {
max-width: 480px;
}
}
.section,
.export-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
max-width: 100%;
}
`}
</style>
</div>
)
}
}
let modesLoaded = false
function useModeLoader() {
React.useEffect(() => {
if (!modesLoaded) {
// Load Codemirror add-ons
require('../lib/custom/autoCloseBrackets')
// Load Codemirror modes
LANGUAGES.filter(
language => language.mode && language.mode !== 'auto' && language.mode !== 'text'
).forEach(language => {
language.custom
? require(`../lib/custom/modes/${language.mode}`)
: require(`codemirror/mode/${language.mode}/${language.mode}`)
})
modesLoaded = true
}
}, [])
}
let highLightsLoaded = false
function useHighlightLoader() {
React.useEffect(() => {
if (!highLightsLoaded) {
import('../lib/highlight-languages')
.then(res => res.default.map(config => hljs.registerLanguage(config[0], config[1])))
.then(() => {
highLightsLoaded = true
})
}
}, [])
}
function selectedLinesReducer(
{ prevLine, selected },
{ type, lineNumber, numLines, selectedLines }
) {
const newState = {}
switch (type) {
case 'GROUP': {
if (prevLine) {
for (let i = Math.min(prevLine, lineNumber); i < Math.max(prevLine, lineNumber) + 1; i++) {
newState[i] = selected[prevLine]
}
}
break
}
case 'MULTILINE': {
for (let i = 0; i < selectedLines.length; i++) {
newState[selectedLines[i] - 1] = true
}
break
}
default: {
for (let i = 0; i < numLines; i++) {
if (i != lineNumber) {
if (prevLine == null) {
newState[i] = false
}
} else {
newState[lineNumber] = selected[lineNumber] === true ? false : true
}
}
}
}
return {
selected: { ...selected, ...newState },
prevLine: lineNumber,
}
}
function useSelectedLines(props, editorRef) {
const [state, dispatch] = React.useReducer(selectedLinesReducer, {
prevLine: null,
selected: {},
})
React.useEffect(() => {
if (editorRef.current && Object.keys(state.selected).length > 0) {
editorRef.current.editor.display.view.forEach((line, i) => {
if (line.text) {
line.text.style.opacity = state.selected[i] === true ? 1 : 0.5
}
if (line.gutter) {
line.gutter.style.opacity = state.selected[i] === true ? 1 : 0.5
}
})
}
}, [state.selected, props.children, props.config, editorRef])
React.useEffect(() => {
if (props.config.selectedLines) {
dispatch({
type: 'MULTILINE',
selectedLines: props.config.selectedLines,
})
}
}, [props.config.selectedLines])
return React.useCallback(function onGutterClick(editor, lineNumber, gutter, e) {
const numLines = editor.display.view.length
const type = e.shiftKey ? 'GROUP' : 'LINE'
dispatch({ type, lineNumber, numLines })
}, [])
}
function useShowInvisiblesLoader() {
React.useEffect(() => void require('cm-show-invisibles'), [])
}
function CarbonContainer(props, ref) {
useModeLoader()
useHighlightLoader()
useShowInvisiblesLoader()
const editorRef = React.createRef()
const onGutterClick = useSelectedLines(props, editorRef)
return <Carbon {...props} innerRef={ref} editorRef={editorRef} onGutterClick={onGutterClick} />
}
export default React.forwardRef(CarbonContainer)
================================================
FILE: components/ColorPicker.js
================================================
import React from 'react'
import SketchPicker from 'react-color/lib/Sketch'
import { COLORS } from '../lib/constants'
import { stringifyColor } from '../lib/util'
const pickerStyle = {
backgroundColor: COLORS.BLACK,
padding: '8px 8px 0',
margin: '0 auto 1px',
}
export default function ColorPicker(props) {
const [color, setColor] = React.useState(props.color)
const { onChange = () => {}, presets, style, disableAlpha } = props
return (
<React.Fragment>
<SketchPicker
styles={{ picker: style || pickerStyle }}
onChange={setColor}
color={typeof color === 'string' ? color : stringifyColor(color)}
onChangeComplete={onChange}
presetColors={presets}
disableAlpha={disableAlpha}
/>
<style jsx>
{`
/* react-color overrides */
:global(.sketch-picker > div:nth-child(3) > div > div > input) {
width: 100% !important;
box-shadow: none;
outline: none;
border-radius: 2px;
background: ${COLORS.DARK_GRAY};
color: #fff !important;
}
:global(.sketch-picker
> div:nth-child(2)
> div:nth-child(1)
> div:nth-child(2), .sketch-picker > div:nth-child(2) > div:nth-child(2)) {
background: #fff;
}
:global(.sketch-picker label) {
color: ${COLORS.SECONDARY} !important;
}
`}
</style>
</React.Fragment>
)
}
================================================
FILE: components/ConfirmButton.js
================================================
import React from 'react'
import Button from './Button'
export default function ConfirmButton(props) {
const [confirmed, setConfirmed] = React.useState(false)
return (
<Button
{...props}
onClick={e => {
if (confirmed) {
props.onClick(e)
setConfirmed(false)
} else {
setConfirmed(true)
}
}}
onBlur={() => setConfirmed(false)}
>
{confirmed ? 'Are you sure?' : props.children}
</Button>
)
}
================================================
FILE: components/CopyMenu.js
================================================
import React from 'react'
import { useRouter } from 'next/router'
import { useCopyTextHandler, useAsyncCallback, useKeyboardListener } from 'actionsack'
import morph from 'morphmorph'
import { COLORS } from '../lib/constants'
import Button from './Button'
import Popout, { managePopout } from './Popout'
import CopySVG from './svg/Copy'
const toIFrame = (url, width, height) =>
`<iframe
src="${location.origin}/embed${url.replace(/^\/\?/, '?')}"
style="width: ${width || 1024}px; height: ${
height || 473
}px; border:0; transform: scale(1); overflow:hidden;"
sandbox="allow-scripts allow-same-origin">
</iframe>
`
const toURL = url => `${location.origin}${url}`
// Medium does not handle asterisks correctly - https://github.com/carbon-app/carbon/issues/1067
const replaceAsterisks = string => string.replace(/\*/g, '%2A')
const toEncodedURL = morph.compose(encodeURI, replaceAsterisks, toURL)
function CopyButton(props) {
return (
<Button
{...props}
hoverColor={COLORS.SECONDARY}
color="rgba(255, 255, 255, 0.7)"
padding="8px"
/>
)
}
function CopyEmbed({ mapper, title }) {
const { asPath } = useRouter()
const text = React.useMemo(() => mapper(asPath), [mapper, asPath])
const { onClick, copied } = useCopyTextHandler(text)
return <CopyButton onClick={onClick}>{copied ? 'Copied!' : title}</CopyButton>
}
const popoutStyle = { width: '140px', right: 0 }
function useClipboardSupport() {
const [isClipboardSupported, setClipboardSupport] = React.useState(false)
React.useEffect(() => {
setClipboardSupport(
window.navigator && window.navigator.clipboard && typeof ClipboardItem === 'function'
)
}, [])
return isClipboardSupported
}
function CopyMenu({ isVisible, toggleVisibility, copyImage, carbonRef }) {
const clipboardSupported = useClipboardSupport()
const [showCopied, { loading: copied }] = useAsyncCallback(
() => new Promise(res => setTimeout(res, 1000))
)
const [copy, { loading }] = useAsyncCallback(async (...args) => {
if (clipboardSupported) {
await copyImage(...args)
showCopied()
}
})
useKeyboardListener('⌘-⇧-c', e => {
e.preventDefault()
copy(e)
})
return (
<div className="copy-menu-container">
<div className="flex">
<Button
center
border
large
padding="0 16px"
margin="0 8px 0 0"
onClick={toggleVisibility}
color={COLORS.SECONDARY}
title="Copy menu"
>
<CopySVG size={16} color={COLORS.SECONDARY} />
</Button>
</div>
<Popout
hidden={!isVisible}
borderColor={COLORS.SECONDARY}
pointerRight="24px"
style={popoutStyle}
>
<div className="copy-row flex">
<span>Copy to clipboard</span>
{clipboardSupported && (
<CopyButton id="export-clipboard" onClick={copy} disabled={loading}>
{loading ? 'Copying…' : copied ? 'Copied!' : 'Image'}
</CopyButton>
)}
<CopyEmbed title="Medium.com" mapper={toEncodedURL} />
<CopyEmbed
title="IFrame"
mapper={url => toIFrame(url, carbonRef.clientWidth, carbonRef.clientHeight)}
/>
<CopyEmbed title="Plain URL" mapper={toURL} />
</div>
</Popout>
<style jsx>
{`
.copy-menu-container {
position: relative;
color: ${COLORS.SECONDARY};
flex: 1;
max-width: 40px;
}
.copy-row {
flex-direction: column;
justify-content: space-between;
border-bottom: 1px solid ${COLORS.SECONDARY};
}
.copy-row :global(button) {
border-top: 1px solid ${COLORS.SECONDARY};
}
.copy-row > span {
padding: 8px;
margin: 0 auto;
}
`}
</style>
</div>
)
}
export default managePopout(React.memo(CopyMenu))
================================================
FILE: components/Dropdown.js
================================================
import React from 'react'
import Downshift from 'downshift'
import { matchSorter } from 'match-sorter'
import VisuallyHidden from '@reach/visually-hidden'
import { Down as ArrowDown } from './svg/Arrows'
import CheckMark from './svg/Checkmark'
import { COLORS } from '../lib/constants'
class Dropdown extends React.PureComponent {
state = {
inputValue: this.props.selected.name,
itemsToShow: this.props.list,
}
onUserAction = changes => {
this.setState(({ inputValue, itemsToShow }) => {
if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) {
if (changes.type === Downshift.stateChangeTypes.keyDownEscape) {
inputValue = this.userInputtedValue
} else if (changes.type === Downshift.stateChangeTypes.changeInput) {
inputValue = changes.inputValue
this.userInputtedValue = changes.inputValue
} else {
inputValue = changes.inputValue
}
}
itemsToShow = this.userInputtedValue
? matchSorter(this.props.list, this.userInputtedValue, { keys: ['name'] })
: this.props.list
if (
Object.prototype.hasOwnProperty.call(changes, 'highlightedIndex') &&
(changes.type === Downshift.stateChangeTypes.keyDownArrowUp ||
changes.type === Downshift.stateChangeTypes.keyDownArrowDown)
) {
inputValue = itemsToShow[changes.highlightedIndex].name
this.props.onChange(itemsToShow[changes.highlightedIndex])
}
if (Object.prototype.hasOwnProperty.call(changes, 'isOpen')) {
this.userInputtedValue = ''
// clear on open
if (changes.isOpen) {
inputValue = ''
this.props.onOpen && this.props.onOpen()
} else if (changes.isOpen === false && !inputValue) {
// set on close
inputValue = this.props.selected.name
}
}
return { inputValue, itemsToShow }
})
}
userInputtedValue = ''
render() {
const { innerRef, color, selected, onChange, itemWrapper, icon, disableInput, title } =
this.props
const { itemsToShow, inputValue } = this.state
const labelId = title ? `${title.toLowerCase()}-dropdown` : undefined
return (
<Downshift
ref={innerRef}
inputValue={inputValue}
selectedItem={selected}
defaultHighlightedIndex={itemsToShow.findIndex(it => it === selected)}
itemToString={item => item.name}
onChange={onChange}
onUserAction={this.onUserAction}
labelId={labelId}
>
{renderDropdown({
color,
list: itemsToShow,
selected,
itemWrapper,
icon,
disableInput,
title,
labelId,
})}
</Downshift>
)
}
}
const renderDropdown =
({ color, list, itemWrapper, icon, disableInput, title, labelId }) =>
({
isOpen,
highlightedIndex,
selectedItem,
getRootProps,
getToggleButtonProps,
getInputProps,
getItemProps,
}) => {
return (
<DropdownContainer {...getRootProps({ refKey: 'innerRef' })}>
{title && <VisuallyHidden id={labelId}>{title}</VisuallyHidden>}
<DropdownIcon isOpen={isOpen}>{icon}</DropdownIcon>
<SelectedItem
getToggleButtonProps={getToggleButtonProps}
getInputProps={getInputProps}
isOpen={isOpen}
color={color}
hasIcon={!!icon}
disabled={disableInput}
>
{selectedItem.name}
</SelectedItem>
{isOpen ? (
<ListItems color={color}>
{list.map((item, index) => (
<ListItem
key={index}
color={color}
item={item}
itemWrapper={itemWrapper}
{...getItemProps({
item,
isSelected: selectedItem.name === item.name,
isHighlighted: highlightedIndex === index,
})}
>
{item.name}
</ListItem>
))}
</ListItems>
) : null}
</DropdownContainer>
)
}
const DropdownContainer = ({ children, innerRef, ...rest }) => {
return (
<div {...rest} ref={innerRef} className="dropdown-container">
{children}
<style jsx>
{`
.dropdown-container {
position: relative;
cursor: pointer;
user-select: none;
margin-left: 40px;
}
`}
</style>
</div>
)
}
const DropdownIcon = ({ children, isOpen }) => {
if (children) {
return (
<div className="dropdown-icon">
{children}
<style jsx>
{`
.dropdown-icon {
position: absolute;
left: -${isOpen ? 38 : 39}px;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
box-shadow: inset 0px 0px 0px ${isOpen ? 2 : 1}px white;
border-radius: 3px 0 0 3px;
cursor: initial;
}
`}
</style>
</div>
)
} else {
return null
}
}
const SelectedItem = ({
getToggleButtonProps,
getInputProps,
children,
isOpen,
color,
hasIcon,
disabled,
}) => {
const itemColor = color || COLORS.SECONDARY
return (
<span
{...getToggleButtonProps({ tabIndex: 0 })}
className={`dropdown-display ${isOpen ? 'is-open' : ''}`}
data-cy="theme-selector-button"
>
<input
{...getInputProps({ placeholder: children, id: `downshift-input-${children}`, disabled })}
className="dropdown-display-text"
spellCheck={false}
/>
<div className="dropdown-arrow">
<ArrowDown color={itemColor} />
</div>
<style jsx>
{`
.dropdown-display {
display: flex;
align-items: center;
height: 40px;
padding: 0 16px;
box-shadow: inset 0px 0px 0px 1px ${itemColor};
border-radius: ${hasIcon ? '0 3px 3px 0' : '3px'};
outline: none;
}
.dropdown-display:hover,
.dropdown-display:focus {
background: ${COLORS.HOVER};
}
.dropdown-display.is-open {
border-radius: ${hasIcon ? '0 3px 0 0' : '3px 3px 0 0'};
box-shadow: inset 0px 0px 0px 2px ${itemColor};
}
.dropdown-display-text {
flex-grow: 1;
color: ${itemColor};
background: transparent;
border: none;
outline: none;
font-size: inherit;
font-family: inherit;
}
.dropdown-arrow {
display: flex;
}
.is-open > .dropdown-arrow {
transform: rotate(180deg);
}
`}
</style>
</span>
)
}
const ListItems = ({ children, color }) => {
return (
<ul role="listbox" className="dropdown-list">
{children}
<style jsx>
{`
.dropdown-list {
margin-top: -2px;
border: 2px solid ${color || COLORS.SECONDARY};
border-radius: 0 0 3px 3px;
max-height: 350px;
overflow-y: scroll;
}
`}
</style>
</ul>
)
}
const ListItem = ({ children, color, isHighlighted, isSelected, itemWrapper, item, ...rest }) => {
const itemColor = color || COLORS.SECONDARY
return (
<li {...rest} className="dropdown-list-item" data-cy="dropdown-item">
{itemWrapper ? (
itemWrapper({ children, color: itemColor, item, isSelected })
) : (
<span className="dropdown-list-item-text">{children}</span>
)}
{isSelected ? <CheckMark /> : null}
<style jsx>
{`
.dropdown-list-item {
display: flex;
align-items: center;
background: ${isHighlighted ? COLORS.HOVER : COLORS.BLACK};
padding: 8px 16px;
border-bottom: 1px solid ${itemColor};
}
.dropdown-list-item:last-child {
border-bottom: none;
}
.dropdown-list-item:hover {
background: ${COLORS.HOVER};
}
.dropdown-list-item-text {
flex-grow: 1;
color: ${itemColor};
}
`}
</style>
</li>
)
}
export default Dropdown
================================================
FILE: components/Editor.js
================================================
// Theirs
import React from 'react'
import Dropzone from 'dropperx'
import debounce from 'lodash.debounce'
import dynamic from 'next/dynamic'
// Ours
import ApiContext from './ApiContext'
import Dropdown from './Dropdown'
import Settings from './Settings'
import Toolbar from './Toolbar'
import Overlay from './Overlay'
import BackgroundSelect from './BackgroundSelect'
import Carbon from './Carbon'
import ExportMenu from './ExportMenu'
import ShareMenu from './ShareMenu'
import CopyMenu from './CopyMenu'
import Themes from './Themes'
import FontFace from './FontFace'
import LanguageIcon from './svg/Language'
import {
LANGUAGES,
LANGUAGE_MIME_HASH,
LANGUAGE_MODE_HASH,
LANGUAGE_NAME_HASH,
DEFAULT_EXPORT_SIZE,
COLORS,
EXPORT_SIZES_HASH,
DEFAULT_CODE,
DEFAULT_SETTINGS,
DEFAULT_LANGUAGE,
DEFAULT_THEME,
FONTS,
} from '../lib/constants'
import { getRouteState } from '../lib/routing'
import { getSettings, unescapeHtml, formatCode, omit } from '../lib/util'
import domtoimage from '../lib/dom-to-image'
const languageIcon = <LanguageIcon />
const SnippetToolbar = dynamic(() => import('./SnippetToolbar'), {
loading: () => null,
})
const getConfig = omit(['code', 'titleBar'])
const unsplashPhotographerCredit = /\n\n\/\/ Photo by.+?on Unsplash/
class Editor extends React.Component {
static contextType = ApiContext
state = {
...DEFAULT_SETTINGS,
...this.props.snippet,
loading: true,
}
async componentDidMount() {
const { queryState } = getRouteState(this.props.router)
const newState = {
// IDEA: we could create an interface for loading this config, so that it looks identical
// whether config is loaded from localStorage, gist, or even something like IndexDB
// Load options from gist or localStorage
...(this.props.snippet ? null : getSettings(localStorage)),
// and then URL params
...queryState,
loading: false,
}
// Makes sure the slash in 'application/X' is decoded
if (newState.language) {
newState.language = unescapeHtml(newState.language)
}
if (newState.fontFamily && !FONTS.find(({ id }) => id === newState.fontFamily)) {
newState.fontFamily = DEFAULT_SETTINGS.fontFamily
}
this.setState(newState)
}
carbonNode = React.createRef()
getTheme = () => this.props.themes.find(t => t.id === this.state.theme) || DEFAULT_THEME
onUpdate = debounce(updates => this.props.onUpdate(updates), 750, {
trailing: true,
leading: true,
})
sync = () => this.onUpdate(this.state)
updateState = updates => this.setState(updates, this.sync)
updateCode = code => this.updateState({ code })
updateTitleBar = titleBar => this.updateState({ titleBar })
updateWidth = width => this.setState({ widthAdjustment: false, width })
getCarbonImage = async (
{
format,
squared = this.state.squaredImage,
exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value,
} = { format: 'png' }
) => {
const node = this.carbonNode.current
const width = node.offsetWidth * exportSize
const height = squared ? node.offsetWidth * exportSize : node.offsetHeight * exportSize
const config = {
style: {
transform: `scale(${exportSize})`,
transformOrigin: 'top left',
background: squared ? this.state.backgroundColor : 'none',
alignItems: 'start',
justifyContent: 'start',
},
filter: n => {
if (n.className) {
const className = String(n.className)
if (className.includes('eliminateOnRender')) {
return false
}
if (className.includes('CodeMirror-cursors')) {
return false
}
}
return true
},
width,
height,
}
if (format === 'svg') {
return domtoimage
.toSvg(node, config)
.then(dataURL =>
dataURL
.replace(/ /g, ' ')
// https://github.com/tsayen/dom-to-image/blob/fae625bce0970b3a039671ea7f338d05ecb3d0e8/src/dom-to-image.js#L551
.replace(/%23/g, '#')
.replace(/%0A/g, '\n')
// https://stackoverflow.com/questions/7604436/xmlparseentityref-no-name-warnings-while-loading-xml-into-a-php-file
.replace(/&(?!#?[a-z0-9]+;)/g, '&')
// remove other fonts which are not used
.replace(
// current font-family used
new RegExp(
'@font-face\\s+{\\s+font-family: (?!"*' + this.state.fontFamily + ').*?}',
'g'
),
''
)
)
.then(uri => uri.slice(uri.indexOf(',') + 1))
.then(data => new Blob([data], { type: 'image/svg+xml' }))
}
if (format === 'blob') {
return domtoimage.toBlob(node, config)
}
// Twitter and Imgur needs regular dataURLs
return domtoimage.toPng(node, config)
}
tweet = () => {
this.getCarbonImage({ format: 'png' }).then(
this.context.tweet.bind(null, this.state.code || DEFAULT_CODE)
)
}
imgur = () => {
const prefix = this.state.name || 'carbon'
return this.getCarbonImage({ format: 'png' }).then(data => this.context.imgur(data, prefix))
}
exportImage = (format = 'blob', options = {}) => {
const link = document.createElement('a')
const prefix = options.filename || this.state.name || 'carbon'
return this.getCarbonImage({ format })
.then(blob => window.URL.createObjectURL(blob))
.then(url => {
if (!options.open) {
link.download = `${prefix}.${format === 'svg' ? 'svg' : 'png'}`
}
if (
// isFirefox
window.navigator.userAgent.indexOf('Firefox') !== -1 &&
window.navigator.userAgent.indexOf('Chrome') === -1
) {
link.target = '_blank'
}
link.href = url
document.body.appendChild(link)
link.click()
link.remove()
})
}
copyImage = () =>
this.getCarbonImage({ format: 'blob' })
.then(blob =>
navigator.clipboard.write([
new window.ClipboardItem({
[blob.type]: blob,
}),
])
)
.catch(console.error)
updateSetting = (key, value) => {
this.updateState({ [key]: value })
if (Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, key)) {
this.updateState({ preset: null })
}
}
resetDefaultSettings = () => {
this.updateState(DEFAULT_SETTINGS)
this.props.onReset()
}
onDrop = ([file]) => {
if (file.type.split('/')[0] === 'image') {
this.updateState({
backgroundImage: file.content,
backgroundImageSelection: null,
backgroundMode: 'image',
preset: null,
})
} else {
this.updateState({ code: file.content, language: 'auto' })
}
}
updateLanguage = language => {
if (language) {
this.updateSetting('language', language.mime || language.mode)
}
}
updateBackground = ({ photographer, ...changes } = {}) => {
if (photographer) {
this.updateState(({ code = DEFAULT_CODE }) => ({
...changes,
code:
code.replace(unsplashPhotographerCredit, '') +
`\n\n// Photo by ${photographer.name} on Unsplash`,
preset: null,
}))
} else {
this.updateState({ ...changes, preset: null })
}
}
updateTheme = theme => this.updateState({ theme, highlights: null })
updateHighlights = updates =>
this.setState(({ highlights = {} }) => ({
highlights: {
...highlights,
...updates,
},
}))
createTheme = theme => {
this.props.updateThemes(themes => [theme, ...themes])
this.updateTheme(theme.id)
}
removeTheme = id => {
this.props.updateThemes(themes => themes.filter(t => t.id !== id))
if (this.state.theme.id === id) {
this.updateTheme(DEFAULT_THEME.id)
}
}
applyPreset = ({ id: preset, ...settings }) => this.updateState({ preset, ...settings })
format = () =>
formatCode(this.state.code)
.then(this.updateCode)
.catch(() => {
// create toast here in the future
})
handleSnippetCreate = () =>
this.context.snippet
.create(this.state)
.then(data => this.props.setSnippet(data))
.then(() =>
this.props.setToasts({
type: 'SET',
toasts: [{ children: 'Snippet created', timeout: 3000 }],
})
)
handleSnippetUpdate = () =>
this.context.snippet.update(this.props.snippet.id, this.state).then(() =>
this.props.setToasts({
type: 'SET',
toasts: [{ children: 'Snippet saved', timeout: 3000 }],
})
)
handleSnippetDelete = () =>
this.context.snippet
.delete(this.props.snippet.id)
.then(() => this.props.setSnippet(null))
.then(() =>
this.props.setToasts({
type: 'SET',
toasts: [{ children: 'Snippet deleted', timeout: 3000 }],
})
)
render() {
const {
highlights,
language,
backgroundColor,
backgroundImage,
backgroundMode,
code,
exportSize,
titleBar,
} = this.state
const config = getConfig(this.state)
const theme = this.getTheme()
return (
<div className="editor">
<Toolbar>
<Themes
theme={theme}
highlights={highlights}
update={this.updateTheme}
updateHighlights={this.updateHighlights}
remove={this.removeTheme}
create={this.createTheme}
themes={this.props.themes}
/>
<Dropdown
title="Language"
icon={languageIcon}
selected={
LANGUAGE_NAME_HASH[language] ||
LANGUAGE_MIME_HASH[language] ||
LANGUAGE_MODE_HASH[language] ||
LANGUAGE_MODE_HASH[DEFAULT_LANGUAGE]
}
list={LANGUAGES}
onChange={this.updateLanguage}
/>
<div className="toolbar-second-row">
<div className="setting-buttons">
<BackgroundSelect
onChange={this.updateBackground}
updateHighlights={this.updateHighlights}
mode={backgroundMode}
color={backgroundColor}
image={backgroundImage}
carbonRef={this.carbonNode.current}
/>
<Settings
{...config}
onChange={this.updateSetting}
resetDefaultSettings={this.resetDefaultSettings}
format={this.format}
applyPreset={this.applyPreset}
getCarbonImage={this.getCarbonImage}
/>
<CopyMenu copyImage={this.copyImage} carbonRef={this.carbonNode.current} />
</div>
<div id="style-editor-button" />
<div className="share-buttons">
<ShareMenu tweet={this.tweet} imgur={this.imgur} />
<ExportMenu
onChange={this.updateSetting}
exportImage={this.exportImage}
exportSize={exportSize}
/>
</div>
</div>
</Toolbar>
<Dropzone accept="image/*, text/*, application/*" onDrop={this.onDrop}>
{({ canDrop }) => (
<Overlay
isOver={canDrop}
title={`Drop your file here to import ${canDrop ? '✋' : '✊'}`}
>
{/*key ensures Carbon's internal language state is updated when it's changed by Dropdown*/}
<Carbon
key={language}
ref={this.carbonNode}
config={this.state}
onChange={this.updateCode}
updateWidth={this.updateWidth}
updateWidthConfirm={this.sync}
loading={this.state.loading}
theme={theme}
titleBar={titleBar}
onTitleBarChange={this.updateTitleBar}
>
{code != null ? code : DEFAULT_CODE}
</Carbon>
</Overlay>
)}
</Dropzone>
<SnippetToolbar
state={this.state}
snippet={this.props.snippet}
onCreate={this.handleSnippetCreate}
onDelete={this.handleSnippetDelete}
onUpdate={this.handleSnippetUpdate}
name={config.name}
onChange={this.updateSetting}
/>
<FontFace {...config} />
<style jsx>
{`
.editor {
background: ${COLORS.BLACK};
border: 3px solid ${COLORS.SECONDARY};
border-radius: 8px;
padding: 16px;
}
.share-buttons,
.setting-buttons {
display: flex;
height: 40px;
}
.share-buttons {
margin-left: auto;
}
.toolbar-second-row {
display: flex;
flex: 1 1 auto;
}
.setting-buttons > :global(div) {
margin-right: 0.5rem;
}
#style-editor-button {
display: flex;
align-items: center;
}
@media (max-width: 768px) {
.toolbar-second-row {
display: block;
}
#style-editor-button {
margin-top: 0.5rem;
}
}
`}
</style>
</div>
)
}
}
Editor.defaultProps = {
onUpdate: () => {},
onReset: () => {},
}
export default Editor
================================================
FILE: components/EditorContainer.js
================================================
// Theirs
import React from 'react'
import Router from 'next/router'
import Editor from './Editor'
import Toasts from './Toasts'
import { useAuth } from './AuthContext'
import { THEMES } from '../lib/constants'
import { updateRouteState } from '../lib/routing'
import { getThemes, saveThemes, clearSettings, saveSettings } from '../lib/util'
function onReset() {
clearSettings()
updateRouteState(Router, {})
if (window.navigator && navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => {
registration.unregister()
})
})
}
}
function toastsReducer(curr, action) {
switch (action.type) {
case 'ADD': {
return curr.concat(action.toast)
}
case 'SET': {
return action.toasts
}
}
throw new Error('Unsupported action')
}
function EditorContainer(props) {
const [themes, updateThemes] = React.useState(THEMES)
const user = useAuth()
React.useEffect(() => {
const storedThemes = getThemes(localStorage) || []
if (storedThemes) {
updateThemes(currentThemes => [...storedThemes, ...currentThemes])
}
}, [])
React.useEffect(() => {
saveThemes(themes.filter(({ custom }) => custom))
}, [themes])
// XXX use context
const [snippet, setSnippet] = React.useState(props.snippet || null)
// TODO update this reducer to only take one action
const [toasts, setToasts] = React.useReducer(toastsReducer, [])
const snippetId = snippet && snippet.id
React.useEffect(() => {
const snippetPath = '/' + (snippetId || '')
if (snippetPath === props.router.asPath) {
return
}
// Reloads only if the snipped.id is different from before. Otherwise returns from above.
props.router.push(
{
pathname: '/[id]',
query: { id: snippetId },
},
snippetPath,
{
shallow: true,
scroll: false
}
)
}, [snippetId, props.router])
function onEditorUpdate(state) {
if (user) {
return
}
updateRouteState(props.router, state)
saveSettings(state)
}
return (
<>
<Toasts toasts={toasts} />
<Editor
{...props}
themes={themes}
updateThemes={updateThemes}
snippet={snippet}
setSnippet={setSnippet}
setToasts={setToasts}
onUpdate={onEditorUpdate}
onReset={onReset}
/>
</>
)
}
export default EditorContainer
================================================
FILE: components/ExportMenu.js
================================================
import React from 'react'
import { useKeyboardListener, useAsyncCallback } from 'actionsack'
import { COLORS, EXPORT_SIZES } from '../lib/constants'
import Button from './Button'
import Input from './Input'
import Popout, { managePopout } from './Popout'
import { Down as ArrowDown } from './svg/Arrows'
const popoutStyle = { width: '256px', right: 0 }
function preventDefault(fn) {
return e => {
e.preventDefault()
return fn(e)
}
}
function ExportMenu({ onChange, exportSize, isVisible, toggleVisibility, exportImage: exp }) {
const input = React.useRef()
const [exportImage, { loading }] = useAsyncCallback(exp)
const handleExportSizeChange = selectedSize => () => onChange('exportSize', selectedSize)
const handleExport = format => () =>
exportImage(format, { filename: input.current && input.current.value })
useKeyboardListener('⌘-⇧-e', preventDefault(handleExport('blob')))
useKeyboardListener('⌘-⇧-s', preventDefault(handleExport('svg')))
return (
<div className="export-menu-container">
<div className="flex">
<Button
border
large
center
color={COLORS.PURPLE}
onClick={handleExport('blob')}
data-cy="quick-export-button"
style={{ minWidth: 92, borderBottomRightRadius: 0, borderTopRightRadius: 0 }}
title="Quick export"
>
{loading ? 'Exporting…' : 'Export'}
</Button>
<Button
id="export-menu"
border
large
center
color={COLORS.PURPLE}
padding="0 8px"
onClick={toggleVisibility}
data-cy="export-button"
margin="0 0 0 -1px"
style={{
borderBottomLeftRadius: 0,
borderTopLeftRadius: 0,
maxWidth: '26px',
}}
title="Export menu dropdown"
>
<ArrowDown color={COLORS.PURPLE} />
</Button>
</div>
<Popout
hidden={!isVisible}
borderColor={COLORS.PURPLE}
pointerRight="6px"
style={popoutStyle}
>
<div className="export-row">
<span className="filename">File name</span>
<Input ref={input} title="filename" placeholder="carbon" color={COLORS.PURPLE} />
</div>
<div className="export-row">
<span>Size</span>
<div className="flex">
{EXPORT_SIZES.map(({ name }, i) => (
<Button
center
key={name}
hoverColor={COLORS.PURPLE}
margin={i === EXPORT_SIZES.length - 1 ? 0 : '0 10px 0 0'}
color={exportSize === name ? COLORS.PURPLE : COLORS.DARK_PURPLE}
onClick={handleExportSizeChange(name)}
>
{name}
</Button>
))}
</div>
</div>
<div className="export-row">
<Button
center
color={COLORS.PURPLE}
onClick={() =>
exportImage('blob', { filename: input.current && input.current.value, open: true })
}
>
Open
</Button>
<div className="save-container">
<span>Download</span>
<div>
<Button
center
margin="0 8px 0 0"
hoverColor={COLORS.PURPLE}
color={COLORS.DARK_PURPLE}
onClick={handleExport('blob')}
id="export-png"
disabled={loading}
>
PNG
</Button>
<Button
center
hoverColor={COLORS.PURPLE}
color={COLORS.DARK_PURPLE}
onClick={handleExport('svg')}
id="export-svg"
disabled={loading}
>
SVG
</Button>
</div>
</div>
</div>
</Popout>
<style jsx>
{`
.export-menu-container {
position: relative;
color: ${COLORS.PURPLE};
flex: 1;
}
.export-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border-bottom: 1px solid ${COLORS.PURPLE};
}
.export-row > :global(button) {
border-right: 1px solid ${COLORS.PURPLE};
}
.export-row:last-child {
border-bottom: none;
padding: 0;
}
.filename {
flex-basis: 72px;
}
.save-container {
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 12px 16px;
}
.save-container > div {
margin-top: 6px;
display: flex;
flex: 1;
}
.save-container:first-of-type {
padding: 12px 12px;
border-right: 1px solid ${COLORS.PURPLE};
}
`}
</style>
</div>
)
}
export default managePopout(React.memo(ExportMenu))
================================================
FILE: components/FontFace.js
================================================
import React from 'react'
export default function FontFace(config) {
return (
<style jsx global>
{`
@font-face {
font-family: ${config.fontUrl ? config.fontFamily : ''};
src: url(${config.fontUrl || ''}) format('woff');
font-display: swap;
}
`}
</style>
)
}
================================================
FILE: components/FontSelect.js
================================================
import React from 'react'
import ListSetting from './ListSetting'
import ReferralLink from './ReferralLink'
import { FONTS } from '../lib/constants'
import { fileToDataURL as blobToUrl } from '../lib/util'
const EXTENSIONS = ['.otf', '.ttf', '.woff']
const Font = ({ id, name, link }) => (
<React.Fragment>
<span style={id === 'upload' ? { textAlign: 'center', width: '100%' } : { fontFamily: id }}>
{name}
</span>
{link && (
<ReferralLink href={link}>
<span style={id === 'upload' ? { textAlign: 'center', width: '100%' } : { fontFamily: id }}>
Purchase
</span>
</ReferralLink>
)}
</React.Fragment>
)
function FontSelect(props) {
const inputEl = React.useRef(null)
function onChange(id) {
if (id === 'upload') {
inputEl.current.click()
} else {
props.onChange(id)
}
}
async function onFiles(e) {
const { files } = e.target
const name = files[0].name.split('.')[0]
const url = await blobToUrl(files[0])
props.onUpload(name, url)
}
return (
<div>
<ListSetting
title="Font"
items={[{ id: 'upload', name: 'Upload +' }, ...FONTS]}
{...props}
onChange={onChange}
>
{Font}
</ListSetting>
<input
hidden
ref={inputEl}
type="file"
multiple
accept={EXTENSIONS.join(',')}
onChange={onFiles}
/>
</div>
)
}
export default FontSelect
================================================
FILE: components/Footer.js
================================================
import React from 'react'
import Link from 'next/link'
import { COLORS } from '../lib/constants'
const Footer = () => (
<footer role="contentinfo" className="mt3">
<nav className="mt3">
<Link href="/about" prefetch={false}>
<a className="link" href="/about">
about
</a>
</Link>
<a className="link" href="https://github.com/carbon-app/carbon">
source
</a>
<a className="link" href="/terms">
terms
</a>
<a className="link" href="/privacy">
privacy
</a>
<a className="link" href="/offsets">
offsets
</a>
{/* <span className="new">New</span> */}
</nav>
<div className="mt2 mb2">
created by{' '}
<a className="author-link" href="https://twitter.com/carbon_app">
@carbon_app
</a>{' '}
¬
</div>
<style jsx>
{`
footer {
font-size: 14px;
text-align: center;
}
footer > div {
text-align: center;
color: ${COLORS.GRAY};
}
nav {
margin: 0 auto;
}
a {
margin-right: 1rem;
}
a:last-child {
margin-right: 0;
}
.new {
position: absolute;
margin: -4px 0 0 -12px;
padding: 1px 3px;
color: ${COLORS.SECONDARY};
background: #cd3f45; /* COLORS.DARK_RED? */
border-radius: 3px;
font-size: 8px;
font-weight: 600;
line-height: 1.3;
text-transform: uppercase;
user-select: none;
}
.author-link {
color: ${COLORS.PRIMARY};
text-decoration: none;
}
.author-link:hover {
color: #fff;
}
`}
</style>
</footer>
)
export default Footer
================================================
FILE: components/Header.js
================================================
import React from 'react'
import Logo from './svg/Logo'
const Header = ({ enableHeroText }) => (
<header role="banner" className="mb4">
<div className="header-content">
<a id="link-home" href="/" aria-label="Home">
<Logo />
</a>
{enableHeroText ? (
<h2 className="mt3">
Create and share beautiful images of your source code.
<br />
Start typing or drop a file into the text area to get started.
</h2>
) : null}
</div>
<style jsx>
{`
.header-content {
display: flex;
flex-direction: column;
align-items: center;
}
.header-content a {
height: 64px;
max-width: 96vw;
}
h2 {
text-align: center;
}
@media (max-width: 768px) {
header {
margin-bottom: var(--x3);
}
h2 {
font-size: 13px;
}
}
`}
</style>
</header>
)
export default Header
================================================
FILE: components/ImagePicker.js
================================================
import React from 'react'
import ReactCrop, { makeAspectCrop } from 'react-image-crop'
import { useLocalStorage } from 'actionsack'
import RandomImage from './RandomImage'
import PhotoCredit from './PhotoCredit'
import Input from './Input'
import Toggle from './Toggle'
import { Link } from './Meta'
import { fileToDataURL } from '../lib/util'
import ApiContext from './ApiContext'
const getCroppedImg = (imageDataURL, pixelCrop) => {
const canvas = document.createElement('canvas')
canvas.width = pixelCrop.width
canvas.height = pixelCrop.height
const ctx = canvas.getContext('2d')
return new Promise(resolve => {
const image = new Image()
image.src = imageDataURL
image.onload = () => {
ctx.drawImage(
image,
pixelCrop.x,
pixelCrop.y,
pixelCrop.width,
pixelCrop.height,
0,
0,
pixelCrop.width,
pixelCrop.height
)
resolve(canvas.toDataURL('image/jpeg'))
}
})
}
const INITIAL_STATE = {
mode: 'file',
crop: null,
imageAspectRatio: null,
pixelCrop: null,
photographer: null,
dataURL: null,
}
export default class ImagePicker extends React.Component {
static contextType = ApiContext
static getDerivedStateFromProps(nextProps, state) {
if (state.crop) {
// update crop for editor container aspect-ratio change
return {
crop: makeAspectCrop(
{
...state.crop,
aspect: nextProps.aspectRatio,
},
state.imageAspectRatio
),
}
}
return null
}
state = INITIAL_STATE
selectMode = mode => this.setState({ mode })
onDragEnd = async () => {
if (this.state.pixelCrop) {
const croppedImg = await getCroppedImg(this.state.dataURL, this.state.pixelCrop)
this.props.onChange({ backgroundImageSelection: croppedImg })
}
}
onCropChange = (crop, pixelCrop) => {
this.setState({
crop: { ...crop, aspect: this.props.aspectRatio },
pixelCrop,
})
}
onImageLoaded = image => {
const imageAspectRatio = image.width / image.height
const initialCrop = {
x: 0,
y: 0,
width: 100,
aspect: this.props.aspectRatio,
}
this.setState({
imageAspectRatio,
crop: makeAspectCrop(initialCrop, imageAspectRatio),
})
}
handleImageChange = (url, dataURL, photographer) => {
this.setState({ dataURL, photographer }, () => {
this.props.onChange({
backgroundImage: url,
backgroundImageSelection: null,
photographer,
})
})
}
handleURLInput = e => {
e.preventDefault()
const url = e.target[0].value
return this.context
.downloadThumbnailImage({ url })
.then(res => res.dataURL)
.then(dataURL => this.handleImageChange(url, dataURL))
.catch(err => {
if (err.message.indexOf('Network Error') > -1) {
this.setState({
error:
'Fetching the image failed. This is probably a CORS-related issue. You can either enable CORS in your browser, or use another image.',
})
}
})
}
uploadImage = async e => {
const dataURL = await fileToDataURL(e.target.files[0])
return this.handleImageChange(dataURL, dataURL)
}
selectImage = async image => {
// TODO use React suspense for loading this asset
const { dataURL } = await this.context.downloadThumbnailImage(image)
this.handleImageChange(image.url, dataURL, image.photographer)
if (image.palette && image.palette.length && this.generateColorPalette) {
/*
* Background is first, which is either the lightest or darkest color
* and the rest are sorted by highest contrast w/ the background
*/
const palette = image.palette.map(c => c.hex)
/*
* Contributors, please feel free to adjust this algorithm to create the most
* readable or aesthetically pleasing syntax highlighting.
*/
this.props.updateHighlights({
background: palette[0],
text: palette[1],
variable: palette[2],
attribute: palette[3],
definition: palette[4],
keyword: palette[5],
property: palette[6],
string: palette[7],
number: palette[8],
operator: palette[9],
meta: palette[10],
tag: palette[11],
comment: palette[12],
})
}
}
removeImage = () => {
this.setState(INITIAL_STATE, () => {
this.props.onChange({
backgroundImage: null,
backgroundImageSelection: null,
})
})
}
render() {
let content = (
<div>
<div className="choose-image">
<span>Upload a background image:</span>
<button
className={this.state.mode === 'file' ? 'active' : 'none'}
onClick={this.selectMode.bind(this, 'file')}
>
File
</button>
<button
className={this.state.mode === 'url' ? 'active' : 'none'}
onClick={this.selectMode.bind(this, 'url')}
>
URL
</button>
{this.state.mode === 'file' ? (
<Input
type="file"
accept="image/png,image/x-png,image/jpeg,image/jpg"
onChange={this.uploadImage}
/>
) : (
<form onSubmit={this.handleURLInput}>
<Input type="text" title="Background Image" placeholder="Image URL…" align="left" />
<button type="submit">Upload</button>
</form>
)}
{this.state.error && <span className="error">{this.state.error}</span>}
</div>
<hr />
<div className="random-image">
<span>
Or use a random <a href="https://unsplash.com/">Unsplash</a> image:
</span>
<RandomImage onChange={this.selectImage} />
<GeneratePaletteSetting onChange={value => (this.generateColorPalette = value)} />
</div>
<style jsx>
{`
button {
display: inline-block;
}
.choose-image,
.random-image {
padding: 8px;
}
.choose-image > button {
cursor: pointer;
color: white;
background: transparent;
border: none;
outline: none;
padding: 0;
margin: 0 8px 8px 0;
}
.choose-image > button:not(.active) {
opacity: 0.4;
}
.choose-image > button:focus {
text-decoration: underline;
}
form {
display: flex;
justify-content: space-between;
}
form > button {
padding: 1px 18px 2px 7px;
}
span {
display: block;
margin-bottom: 16px;
}
a {
text-decoration: underline;
}
hr {
border-bottom: none;
margin-bottom: 0;
margin-top: 0;
}
.error {
color: red;
margin-top: 8px;
}
`}
</style>
</div>
)
if (this.state.dataURL) {
content = (
<div className="settings-container">
<div className="image-container">
<div className="label">
<span>Background image</span>
<button onClick={this.removeImage}>×</button>
</div>
<ReactCrop
src={this.state.dataURL}
onImageLoaded={this.onImageLoaded}
crop={this.state.crop}
onChange={this.onCropChange}
onDragEnd={this.onDragEnd}
minHeight={10}
minWidth={10}
keepSelection
/>
{this.state.photographer && <PhotoCredit photographer={this.state.photographer} />}
</div>
<style jsx>
{`
button {
cursor: pointer;
color: inherit;
appearance: none;
border: none;
background: none;
display: block;
padding: 0;
margin: 0;
line-height: 16px;
}
.settings-container img {
width: 100%;
}
.label {
user-select: none;
margin-bottom: 4px;
}
:global(.ReactCrop__image) {
user-select: none;
user-drag: none;
}
.image-container {
padding: 8px;
}
.image-container .label {
display: flex;
justify-content: space-between;
align-items: center;
}
`}
</style>
</div>
)
}
return (
<div className="image-picker-container">
<Link href="/static/react-crop.css" />
{content}
<style jsx>
{`
.image-picker-container {
font-size: 12px;
}
`}
</style>
</div>
)
}
}
function GeneratePaletteSetting({ onChange }) {
const [enabled, setEnabled] = useLocalStorage('CARBON_GENERATE_COLOR_PALETTE')
React.useEffect(() => void onChange(enabled), [enabled, onChange])
return (
<Toggle
label="Generate color palette (beta)"
enabled={enabled}
onChange={setEnabled}
padding="8px 0 0"
/>
)
}
================================================
FILE: components/Input.js
================================================
import React from 'react'
import { COLORS } from '../lib/constants'
const Input = React.forwardRef(
(
{
color = COLORS.SECONDARY,
align = 'right',
width = '100%',
fontSize = '12px',
label,
...props
},
ref
) => (
<React.Fragment>
{label && <label>{label}</label>}
<input ref={ref} {...props} />
<style jsx>
{`
input {
width: ${width};
font-size: ${fontSize};
color: ${color};
background: transparent;
border: none;
outline: none;
text-align: ${align};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
input::placeholder {
color: ${color};
opacity: 0.4;
}
input[type='file'] {
cursor: pointer;
}
input[type='file']:focus {
outline: initial;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
`}
</style>
</React.Fragment>
)
)
export default Input
================================================
FILE: components/ListSetting.js
================================================
import React from 'react'
import Checkmark from './svg/Checkmark'
import { COLORS } from '../lib/constants'
import { toggle } from '../lib/util'
class ListSetting extends React.Component {
static defaultProps = {
onOpen: () => {},
onClose: () => {},
}
state = { isVisible: false }
select = id => {
if (this.props.selected !== id) {
this.props.onChange(id)
}
}
toggle = () => {
const handler = this.state.isVisible ? this.props.onClose : this.props.onOpen
handler()
this.setState(toggle('isVisible'))
}
renderListItems() {
return this.props.items.map(item => (
<div
role="button"
tabIndex={0}
className="list-item"
key={item.id}
onClick={this.select.bind(null, item.id)}
>
{this.props.children(item, this.props.selected)}
{this.props.selected === item.id ? <Checkmark /> : null}
<style jsx>
{`
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
padding: 8px 16px;
border-bottom: 1px solid ${COLORS.SECONDARY};
background: ${COLORS.DARK_GRAY};
}
.list-item:first-of-type {
border-top: 1px solid ${COLORS.SECONDARY};
}
.list-item:last-of-type {
border-bottom: none;
}
`}
</style>
</div>
))
}
render() {
const { items, selected, title, children } = this.props
const { isVisible } = this.state
const selectedItem = items.filter(item => item.id === selected)[0] || {}
return (
<div className="list-select-container">
<div
role="button"
tabIndex={0}
className={`display ${isVisible ? 'is-visible' : ''}`}
onClick={this.toggle}
>
<span className="label">{title}</span>
{children(selectedItem)}
</div>
<div className="list">{this.renderListItems()}</div>
<style jsx>
{`
.display {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
padding: 8px;
}
.list {
display: none;
margin-top: -1px;
max-height: 160px;
overflow-y: scroll;
}
.is-visible + .list {
display: block;
}
`}
</style>
</div>
)
}
}
export default ListSetting
================================================
FILE: components/LoginButton.js
================================================
import React from 'react'
import Link from 'next/link'
import firebase, { logout, loginGitHub } from '../lib/client'
import Button from './Button'
import Popout, { managePopout } from './Popout'
import { useAuth } from './AuthContext'
function Drawer(props) {
return (
<Popout hidden={!props.isVisible} pointerRight="14px" style={{ width: '160px', right: 0 }}>
<div className="flex column">
<Link href="/snippets">
<Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}>
<img src="/static/svg/snippets.svg" alt="Snippets page" width="16px" /> Snippets{' '}
</Button>
</Link>
<Link href="/account">
<Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}>
<img
src="/static/svg/person.svg"
alt="Account"
width="16px"
style={{ left: '-2px', marginRight: 'calc(1rem - 3px)' }}
/>{' '}
Account
</Button>
</Link>
<Button large center padding="0.5rem 0" onClick={logout}>
Sign Out
</Button>
</div>
<style jsx>
{`
.column {
flex-direction: column;
}
img {
position: relative;
margin-right: 1rem;
}
`}
</style>
</Popout>
)
}
function LoginButton({ isVisible, toggleVisibility }) {
const user = useAuth()
if (!firebase) {
return null
}
return (
<div>
<Button
center
border
large
padding="0 16px"
color="white"
className="profile-button"
onClick={() => {
if (!user) {
loginGitHub()
} else {
toggleVisibility()
}
}}
>
<img
height={20}
src={user ? user.photoURL : '/static/svg/github.svg'}
alt={user ? user.displayName : 'GitHub'}
/>
<span>{user ? user.displayName : 'Sign in/up'}</span>
</Button>
<Drawer isVisible={user && isVisible} />
<style jsx>
{`
div,
div :global(.profile-button) {
position: relative;
height: 100%;
}
div :global(.profile-button) {
max-width: 218px;
min-height: 40px;
}
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
img {
border-radius: 50%;
margin-right: 16px;
}
`}
</style>
</div>
)
}
export default managePopout(LoginButton)
================================================
FILE: components/MenuButton.js
================================================
import React from 'react'
import Button from './Button'
import { COLORS } from '../lib/constants'
import * as Arrows from './svg/Arrows'
const MenuButton = React.memo(({ name, select, selected, noArrows }) => {
return (
<div className="menu-button">
<Button
padding="8px"
onClick={select(name)}
background={selected === name ? COLORS.BLACK : COLORS.DARK_GRAY}
>
{name}
{!noArrows && (
<div className="arrow-icon">
<Arrows.Right />
</div>
)}
</Button>
<style jsx>
{`
.menu-button {
display: flex;
height: 33px;
border-bottom: 1px solid ${COLORS.SECONDARY};
position: relative;
align-items: center;
}
.menu-button:last-child {
${selected === 'Misc' ? 'border-bottom: none;' : ''};
}
.arrow-icon {
position: absolute;
right: 14px;
top: 11px;
}
`}
</style>
</div>
)
})
export default MenuButton
================================================
FILE: components/Meta.js
================================================
import React from 'react'
import Head from 'next/head'
import { THEMES, THEMES_HASH, COLORS } from '../lib/constants'
import Reset from './style/Reset'
import Font from './style/Font'
import Typography from './style/Typography'
const CODEMIRROR_VERSION = '5.65.5'
export const HIGHLIGHTS_ONLY = ['shades-of-purple', 'vscode', 'a11y-dark']
const LOCAL_STYLESHEETS = ['one-light', 'one-dark', 'verminal', 'night-owl', 'nord', 'synthwave-84']
const CDN_STYLESHEETS = THEMES.filter(
t => LOCAL_STYLESHEETS.indexOf(t.id) < 0 && HIGHLIGHTS_ONLY.indexOf(t.id) < 0
)
export function Link({ href }) {
return (
<Head>
<link rel="preload" as="style" href={href} />
<link rel="stylesheet" href={href} />
</Head>
)
}
export const StylesheetLink = ({ theme }) => {
let href
if (LOCAL_STYLESHEETS.indexOf(theme) > -1) {
href = `/static/themes/${theme}.min.css`
} else {
const themeDef = THEMES_HASH[theme]
href = `//cdnjs.cloudflare.com/ajax/libs/codemirror/${CODEMIRROR_VERSION}/theme/${
themeDef && (themeDef.link || themeDef.id)
}.min.css`
}
return <Link href={href} />
}
export const CodeMirrorLink = () => (
<Link
href={`//cdnjs.cloudflare.com/ajax/libs/codemirror/${CODEMIRROR_VERSION}/codemirror.min.css`}
/>
)
const title = 'Carbon'
const description =
'Carbon is the easiest way to create and share beautiful images of your source code.'
export const MetaTags = React.memo(() => (
<Head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content={description} />
<meta name="application-name" content={title} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@carbon_app" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content="https://carbon.now.sh/static/brand/banner.png" />
<meta name="og:title" content={title} />
<meta name="og:description" content={description} />
<meta name="og:image" content="/static/brand/banner.png" />
<meta name="theme-color" content={COLORS.BLACK} />
<meta name="apple-mobile-web-app-status-bar-style" content={COLORS.BLACK} />
<title>{title} | Create and share beautiful images of your source code</title>
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/static/brand/apple-touch-icon.png" />
</Head>
))
export const MetaLinks = React.memo(() => {
return (
<React.Fragment>
<Link
href={`//cdnjs.cloudflare.com/ajax/libs/codemirror/${CODEMIRROR_VERSION}/theme/seti.min.css`}
/>
<CodeMirrorLink />
{LOCAL_STYLESHEETS.map(id => (
<Link key={id} href={`/static/themes/${id}.min.css`} />
))}
{CDN_STYLESHEETS.map(themeDef => {
const href = `//cdnjs.cloudflare.com/ajax/libs/codemirror/${CODEMIRROR_VERSION}/theme/${
themeDef && (themeDef.link || themeDef.id)
}.min.css`
return <Link key={themeDef.id} href={href} />
})}
</React.Fragment>
)
})
export default React.memo(function Meta() {
return (
<React.Fragment>
<MetaTags />
<Reset />
<Font />
<Typography />
</React.Fragment>
)
})
================================================
FILE: components/Overlay.js
================================================
import React from 'react'
const Overlay = props => (
<div className="dnd-container">
{props.isOver ? <div className="dnd-overlay">{props.title}</div> : null}
{props.children}
<style jsx>
{`
.dnd-container {
position: relative;
}
.dnd-overlay {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
z-index: 999;
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.85);
}
`}
</style>
</div>
)
export default Overlay
================================================
FILE: components/Page.js
================================================
import React from 'react'
import AuthContext from './AuthContext'
import Meta from './Meta'
import Header from './Header'
import Footer from './Footer'
import Announcement from './Announcement'
import LoginButton from './LoginButton'
const COLUMN = `
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
`
class Page extends React.Component {
render() {
const { children, enableHeroText, flex } = this.props
return (
<main className="main mb3">
<Meta />
<AuthContext>
<Announcement />
<Header enableHeroText={enableHeroText} />
<div className="login-button-container">
<LoginButton />
</div>
<div className="page">{children}</div>
</AuthContext>
<Footer />
<style jsx>
{`
.main {
${flex ? COLUMN : ''}
margin-top: 6rem;
}
.login-button-container {
position: absolute;
top: 1.4rem;
right: 1rem;
}
.page {
max-width: 100%;
padding: 0 1rem;
}
@media (min-width: 1024px) {
.main {
${COLUMN};
}
.page {
padding: 0;
}
}
`}
</style>
</main>
)
}
}
export default Page
================================================
FILE: components/PhotoCredit.js
================================================
import React from 'react'
export default function PhotoCredit({ photographer }) {
return (
<div className="photo-credit">
Photo by{' '}
<a href={`${photographer.profile_url}?utm_source=carbon&utm_medium=referral`}>
{photographer.name}
</a>
<style jsx>
{`
.photo-credit {
cursor: unset;
user-select: none;
text-align: left;
font-size: 10px;
color: #aaa;
margin-bottom: -2px;
}
.photo-credit a {
cursor: pointer;
text-decoration: underline;
}
`}
</style>
</div>
)
}
================================================
FILE: components/Popout.js
================================================
import React from 'react'
import enhanceWithClickOutside from 'react-click-outside'
import WindowPointer from './WindowPointer'
import { COLORS } from '../lib/constants'
import { toggle } from '../lib/util'
export const managePopout = WrappedComponent => {
class PopoutManager extends React.Component {
state = {
isVisible: false,
}
toggleVisibility = () => this.setState(toggle('isVisible'))
handleClickOutside = () => this.setState({ isVisible: false })
handleKeyDown = e => {
if (e.key === 'Escape') {
this.handleClickOutside()
}
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown)
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown)
}
render() {
return (
<WrappedComponent
{...this.props}
isVisible={this.state.isVisible}
toggleVisibility={this.toggleVisibility}
/>
)
}
}
return enhanceWithClickOutside(PopoutManager)
}
class Popout extends React.PureComponent {
static defaultProps = {
borderColor: COLORS.SECONDARY,
style: {},
}
render() {
const { id, children, borderColor, style, hidden, pointerLeft, pointerRight } = this.props
if (hidden) {
return null
}
return (
<div id={id} className="popout" style={style}>
<WindowPointer fromLeft={pointerLeft} fromRight={pointerRight} color={borderColor} />
{children}
<style jsx>
{`
.popout {
position: absolute;
background-color: ${COLORS.BLACK};
border: 2px solid ${borderColor};
border-radius: 3px;
margin-top: 10px;
font-size: 12px;
z-index: 1;
}
`}
</style>
</div>
)
}
}
export default Popout
================================================
FILE: components/Presets.js
================================================
import React from 'react'
import Button from './Button'
import { COLORS, DEFAULT_PRESETS } from '../lib/constants'
import * as Arrows from './svg/Arrows'
import Remove from './svg/Remove'
const removeButtonStyles = {
position: 'absolute',
top: '6px',
right: '6px',
width: '11px',
height: '11px',
borderRadius: '999px',
}
const Preset = React.memo(({ remove, apply, selected, preset }) => {
const isSelected = preset.id === selected
return (
<div className="preset-container">
<Button
onClick={() => apply(preset)}
disabled={isSelected}
border={isSelected}
selected={isSelected}
hoverBackground={preset.backgroundColor}
style={{
height: '96px',
borderRadius: '3px',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
backgroundSize: 'contain',
backgroundImage: `url('${preset.icon}')`,
backgroundColor: preset.backgroundColor,
}}
/>
{preset.custom && (
<Button
center
hoverBackground={COLORS.SECONDARY}
background={COLORS.SECONDARY}
onClick={() => remove(preset.id)}
style={removeButtonStyles}
>
<Remove />
</Button>
)}
<style jsx>
{`
.preset-container {
display: flex;
position: relative;
flex: 0 0 96px;
margin-right: 8px;
}
.preset-container :global(button:focus) {
box-shadow: inset 0px 0px 0px 1px ${COLORS.SECONDARY};
}
`}
</style>
</div>
)
})
const arrowButtonStyle = {
position: 'absolute',
top: 0,
right: '16px',
height: '100%',
}
const Presets = React.memo(
({ show, create, toggle, undo, presets, selected, remove, apply, applied, contentRef }) => {
const customPresetsLength = presets.length - DEFAULT_PRESETS.length
const disabledCreate = selected != null
return (
<div className="settings-presets">
<div className="settings-presets-header">
<span>Presets</span>
{show && (
<Button
margin="0 0 0 8px"
flex="0 0 54px"
color={COLORS.GRAY}
hoverBackground="transparent"
hoverColor={disabledCreate ? COLORS.GRAY : COLORS.SECONDARY}
onClick={create}
disabled={disabledCreate}
>
create +
</Button>
)}
<Button center onClick={toggle} style={arrowButtonStyle} hoverBackground={COLORS.BLACK}>
{show ? <Arrows.Up /> : <Arrows.Down />}
</Button>
</div>
{show && (
<div className="settings-presets-content" ref={contentRef}>
{presets
.filter(p => p.custom)
.map(preset => (
<Preset
key={preset.id}
remove={remove}
apply={apply}
preset={preset}
selected={selected}
/>
))}
{customPresetsLength > 0 ? <div className="settings-presets-divider" /> : null}
{presets
.filter(p => !p.custom)
.map(preset => (
<Preset key={preset.id} apply={apply} preset={preset} selected={selected} />
))}
</div>
)}
{show && applied && (
<div className="settings-presets-applied">
<span>Preset applied!</span>
<Button
center
flex="0"
onClick={undo}
color={COLORS.BLACK}
hoverBackground="transparent"
background="transparent"
>
undo <span>↩</span>
</Button>
</div>
)}
<style jsx>
{`
.settings-presets {
border-bottom: 2px solid ${COLORS.SECONDARY};
}
.settings-presets-header {
display: flex;
padding: 8px;
position: relative;
color: ${COLORS.SECONDARY};
width: 100%;
align-items: center;
}
.settings-presets-content {
display: flex;
overflow-x: scroll;
margin: 0 8px 12px 8px;
align-items: center;
/* https://iamsteve.me/blog/entry/using-flexbox-for-horizontal-scrolling-navigation */
flex-wrap: nowrap;
-webkit-overflow-scrolling: touch;
}
.settings-presets-divider {
height: 72px;
padding: 1px;
border-radius: 3px;
margin-right: 8px;
background-color: ${COLORS.DARK_GRAY};
}
.settings-presets-applied {
display: flex;
justify-content: space-between;
background-color: ${COLORS.SECONDARY};
color: ${COLORS.BLACK};
padding: 4px 8px;
}
.settings-presets-applied span {
float: right;
margin: 2px 0 0 2px;
}
`}
</style>
</div>
)
}
)
export default Presets
================================================
FILE: components/RandomImage.js
================================================
import React from 'react'
import { useAsyncCallback } from 'actionsack'
import { Spinner } from './Spinner'
import { useAPI } from './ApiContext'
import PhotoCredit from './PhotoCredit'
function RandomImage(props) {
const cacheRef = React.useRef([])
const [cacheIndex, updateIndex] = React.useState(0)
const api = useAPI()
const [selectImage, { loading: selecting }] = useAsyncCallback(() => {
const image = cacheRef.current[cacheIndex]
return api.unsplash.download(image.id).then(data => props.onChange({ ...image, ...data }))
})
const [updateCache, { loading: updating, error, data: imgs }] = useAsyncCallback(
api.unsplash.random
)
const needsFetch = !error && !updating && (!imgs || cacheIndex > cacheRef.current.length - 2)
React.useEffect(() => {
if (needsFetch) {
updateCache()
}
}, [needsFetch, updateCache])
React.useEffect(() => {
if (imgs) {
cacheRef.current.push(...imgs)
}
}, [imgs])
const loading = updating || selecting
const cache = cacheRef.current
const photographer = cache[cacheIndex] && cache[cacheIndex].photographer
const bgImage = cache[cacheIndex] && cache[cacheIndex].dataURL
return (
<div className="random-image-container">
<div className="controls">
<button disabled={loading} onClick={selectImage}>
Use Image
</button>
<button disabled={loading} onClick={() => updateIndex(i => i + 1)}>
Try Another
</button>
</div>
<div className="image">{loading && <Spinner />}</div>
{photographer && <PhotoCredit photographer={photographer} />}
<style jsx>
{`
.image {
width: 100%;
height: 140px;
background: url(${bgImage});
background-size: cover;
background-repeat: no-repeat;
margin-bottom: 4px;
}
.controls {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
}
button {
opacity: ${loading ? 0.5 : 1};
cursor: ${loading ? 'not-allowed' : 'pointer'};
user-select: none;
appearance: none;
border: none;
background: none;
color: inherit;
font-size: inherit;
padding: 0;
}
button:focus {
outline: none;
text-decoration: underline;
}
`}
</style>
</div>
)
}
export default RandomImage
================================================
FILE: components/ReferralLink.js
================================================
import React from 'react'
import { COLORS } from '../lib/constants'
export default function ReferralLink(props) {
return (
<a {...props} target="_blank" rel="noopener noreferrer">
{props.children}
<style jsx>
{`
/* Prod Only */
a {
display: block;
color: ${COLORS.BLACK};
background: ${COLORS.PRIMARY};
border-radius: 2px;
padding: 2px 3px;
font-weight: bold;
font-size: 10px;
}
`}
</style>
</a>
)
}
================================================
FILE: components/SelectionEditor.js
================================================
import React from 'react'
import { useKeyboardListener } from 'actionsack'
import Popout from './Popout'
import Button from './Button'
import ColorPicker from './ColorPicker'
import { COLORS } from '../lib/constants'
function ModifierButton(props) {
return (
<Button
flex={0}
padding="0"
center
margin="0 8px 0 0"
style={{ borderBottom: `1px solid ${props.selected ? 'white' : 'transparent'}` }}
onClick={props.onClick}
color={props.color}
>
{props.children}
</Button>
)
}
function reducer(state, action) {
switch (action.type) {
case 'BOLD': {
return {
...state,
bold: !state.bold,
}
}
case 'ITALICS': {
return {
...state,
italics: !state.italics,
}
}
case 'UNDERLINE': {
return {
...state,
underline: Number(state.underline + 1) % 3,
}
}
case 'COLOR': {
return {
...state,
color: action.color,
}
}
}
throw new Error('Invalid action')
}
function SelectionEditor({ onChange }) {
const [open, setOpen] = React.useState(false)
useKeyboardListener('Escape', () => setOpen(false))
const [state, dispatch] = React.useReducer(reducer, {
bold: null,
italics: null,
underline: null,
color: null,
})
React.useEffect(() => {
onChange(state)
}, [onChange, state])
return (
<div style={{ position: 'relative' }}>
<div className="colorizer">
<div className="modifier">
<ModifierButton selected={state.bold} onClick={() => dispatch({ type: 'BOLD' })}>
<b>B</b>
</ModifierButton>
<ModifierButton selected={state.italics} onClick={() => dispatch({ type: 'ITALICS' })}>
<i>I</i>
</ModifierButton>
<ModifierButton
selected={state.underline}
onClick={() => dispatch({ type: 'UNDERLINE' })}
color={state.underline === 2 ? COLORS.RED : undefined}
>
<u>U</u>
</ModifierButton>
<button className="color-square" onClick={() => setOpen(o => !o)} />
</div>
<Popout hidden={!open} pointerLeft="16px" style={{ left: 82 }}>
<div className="color-picker-container">
<ColorPicker
color={state.color || COLORS.PRIMARY}
disableAlpha={true}
onChange={d => dispatch({ type: 'COLOR', color: d.hex })}
/>
</div>
</Popout>
</div>
<style jsx>
{`
.modifier {
padding: 0px 8px;
display: flex;
}
.colorizer b {
font-weight: bold;
}
.colorizer i {
font-style: italic;
}
.colorizer :global(button) {
min-width: 20px;
}
.color-square {
cursor: pointer;
appearance: none;
outline: none;
border: none;
border-radius: 3px;
padding: 12px;
margin: 4px 0 4px auto;
background: ${state.color || COLORS.PRIMARY};
box-shadow: ${`inset 0px 0px 0px ${open ? 2 : 1}px ${COLORS.SECONDARY}`};
}
.color-picker-container {
width: 218px;
border-top: 2px solid ${COLORS.SECONDARY};
}
`}
</style>
</div>
)
}
export default SelectionEditor
================================================
FILE: components/Settings.js
================================================
import React from 'react'
import omitBy from 'lodash.omitby'
import { useKeyboardListener } from 'actionsack'
import ThemeSelect from './ThemeSelect'
import FontSelect from './FontSelect'
import Slider from './Slider'
import Input from './Input'
import Toggle from './Toggle'
import Popout, { managePopout } from './Popout'
import Button from './Button'
import Presets from './Presets'
import MenuButton from './MenuButton'
import { COLORS, DEFAULT_PRESETS, DEFAULT_SETTINGS, DEFAULT_WIDTHS } from '../lib/constants'
import { toggle, getPresets, savePresets, generateId, fileToJSON } from '../lib/util'
import SettingsIcon from './svg/Settings'
function KeyboardShortcut({ trigger, handle }) {
useKeyboardListener(trigger, handle)
return null
}
function WindowSettings({
onChange,
windowTheme,
paddingHorizontal,
paddingVertical,
dropShadow,
dropShadowBlurRadius,
dropShadowOffsetY,
windowControls,
widthAdjustment,
width,
watermark,
onWidthChanging,
onWidthChanged,
}) {
return (
<div className="settings-content">
<ThemeSelect
selected={windowTheme || 'none'}
windowControls={windowControls}
onChange={onChange}
/>
<div className="row">
<Slider
label="Padding (vert)"
value={paddingVertical}
maxValue={200}
onChange={onChange.bind(null, 'paddingVertical')}
/>
<Slider
label="Padding (horiz)"
value={paddingHorizontal}
onChange={onChange.bind(null, 'paddingHorizontal')}
onMouseDown={onWidthChanging}
onMouseUp={onWidthChanged}
/>
</div>
<Toggle
label="Drop shadow"
enabled={dropShadow}
onChange={onChange.bind(null, 'dropShadow')}
/>
{dropShadow && (
<div className="row drop-shadow-options">
<Slider
label="(offset-y)"
value={dropShadowOffsetY}
onChange={onChange.bind(null, 'dropShadowOffsetY')}
/>
<Slider
label="(blur-radius)"
value={dropShadowBlurRadius}
onChange={onChange.bind(null, 'dropShadowBlurRadius')}
/>
</div>
)}
<Toggle
label="Auto-adjust width"
enabled={widthAdjustment}
onChange={onChange.bind(null, 'widthAdjustment')}
/>
{!widthAdjustment && (
<div className="row settings-row width-row">
<Input
label="Width"
type="number"
value={width}
min={DEFAULT_WIDTHS.minWidth}
max={DEFAULT_WIDTHS.maxWidth}
onChange={e => onChange('width', e.target.value)}
width="50%"
/>
</div>
)}
<Toggle label="Watermark" enabled={watermark} onChange={onChange.bind(null, 'watermark')} />
<style jsx>
{`
.width-row {
justify-content: space-between;
padding: 8px 12px 8px 8px;
}
.row > :global(div:first-child) {
border-right: 1px solid ${COLORS.SECONDARY};
}
.drop-shadow-options :global(.slider-bg),
.drop-shadow-options :global(label) {
opacity: 0.5;
}
.settings-content :global(.settings-row:foc
gitextract_k_ip4zh3/ ├── .all-contributorsrc ├── .eslintrc.js ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── other.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── ranger.yml │ └── workflows/ │ └── validate.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── .vercelignore ├── LICENSE ├── README.md ├── SECURITY.md ├── bin/ │ └── deploy.sh ├── components/ │ ├── Announcement.js │ ├── ApiContext.js │ ├── AuthContext.js │ ├── BackgroundSelect.js │ ├── Billing.js │ ├── Button.js │ ├── Carbon.js │ ├── ColorPicker.js │ ├── ConfirmButton.js │ ├── CopyMenu.js │ ├── Dropdown.js │ ├── Editor.js │ ├── EditorContainer.js │ ├── ExportMenu.js │ ├── FontFace.js │ ├── FontSelect.js │ ├── Footer.js │ ├── Header.js │ ├── ImagePicker.js │ ├── Input.js │ ├── ListSetting.js │ ├── LoginButton.js │ ├── MenuButton.js │ ├── Meta.js │ ├── Overlay.js │ ├── Page.js │ ├── PhotoCredit.js │ ├── Popout.js │ ├── Presets.js │ ├── RandomImage.js │ ├── ReferralLink.js │ ├── SelectionEditor.js │ ├── Settings.js │ ├── ShareMenu.js │ ├── Slider.js │ ├── SnippetToolbar.js │ ├── Spinner.js │ ├── ThemeSelect.js │ ├── Themes/ │ │ ├── GlobalHighlights.js │ │ ├── ThemeCreate.js │ │ └── index.js │ ├── Toasts.js │ ├── Toggle.js │ ├── Toolbar.js │ ├── WidthHandler.js │ ├── WindowControls.js │ ├── WindowPointer.js │ ├── hooks.js │ ├── style/ │ │ ├── Font.js │ │ ├── Reset.js │ │ └── Typography.js │ └── svg/ │ ├── Arrows.js │ ├── Checkmark.js │ ├── Controls.js │ ├── Copy.js │ ├── Language.js │ ├── Logo.js │ ├── Remove.js │ ├── Settings.js │ ├── Theme.js │ ├── Watermark.js │ └── WindowThemes.js ├── cypress/ │ ├── config.json │ ├── integration/ │ │ ├── background-color.spec.js │ │ ├── basic.spec.js │ │ ├── embed.spec.js │ │ ├── gist.spec.js │ │ ├── localStorage.spec.js │ │ ├── security.spec.js │ │ └── visual-testing.spec.js │ ├── plugins/ │ │ └── index.js │ ├── support/ │ │ └── index.js │ └── util.js ├── docs/ │ ├── README.ar.md │ ├── README.bn.md │ ├── README.br.pt.md │ ├── README.cn.zh.md │ ├── README.de.md │ ├── README.es.md │ ├── README.fa.md │ ├── README.fr.md │ ├── README.he.md │ ├── README.hi.md │ ├── README.in.md │ ├── README.it.md │ ├── README.ja.md │ ├── README.kr.md │ ├── README.ml.md │ ├── README.nl.md │ ├── README.pl.md │ ├── README.ru.md │ ├── README.se.md │ ├── README.ta.md │ ├── README.tr.md │ ├── README.tw.zh.md │ ├── README.ua.md │ └── README.uz.md ├── lib/ │ ├── api.js │ ├── client.js │ ├── constants.js │ ├── custom/ │ │ ├── autoCloseBrackets.js │ │ └── modes/ │ │ ├── apache.js │ │ ├── elixir.js │ │ ├── graphql.js │ │ ├── nim.js │ │ ├── riscv.js │ │ └── solidity.js │ ├── dom-to-image.js │ ├── highlight-languages.js │ ├── routing.js │ └── util.js ├── next.config.js ├── package.json ├── pages/ │ ├── [id].js │ ├── _document.js │ ├── about.js │ ├── account.js │ ├── api/ │ │ ├── image/ │ │ │ └── [id].js │ │ └── oembed.js │ ├── embed/ │ │ ├── [id].js │ │ └── index.js │ ├── index.js │ └── snippets.js ├── public/ │ ├── manifest.json │ ├── robots.txt │ └── static/ │ └── react-crop.css ├── release.js └── vercel.json
SYMBOL INDEX (230 symbols across 70 files)
FILE: components/Announcement.js
constant ACTIVE (line 4) | const ACTIVE = false
function Toast (line 8) | function Toast() {
FILE: components/ApiContext.js
function useAPI (line 6) | function useAPI() {
FILE: components/AuthContext.js
function useAuth (line 8) | function useAuth() {
function AuthContext (line 12) | function AuthContext(props) {
FILE: components/BackgroundSelect.js
function validateColor (line 10) | function validateColor(str) {
class BackgroundSelect (line 16) | class BackgroundSelect extends React.PureComponent {
method render (line 25) | render() {
FILE: components/Billing.js
function Billing (line 26) | function Billing() {
FILE: components/Carbon.js
function searchLanguage (line 33) | function searchLanguage(l) {
function noop (line 37) | function noop() {}
function getUnderline (line 38) | function getUnderline(underline) {
class Carbon (line 52) | class Carbon extends React.PureComponent {
method render (line 148) | render() {
function useModeLoader (line 386) | function useModeLoader() {
function useHighlightLoader (line 405) | function useHighlightLoader() {
function selectedLinesReducer (line 417) | function selectedLinesReducer(
function useSelectedLines (line 457) | function useSelectedLines(props, editorRef) {
function useShowInvisiblesLoader (line 492) | function useShowInvisiblesLoader() {
function CarbonContainer (line 496) | function CarbonContainer(props, ref) {
FILE: components/ColorPicker.js
function ColorPicker (line 13) | function ColorPicker(props) {
FILE: components/ConfirmButton.js
function ConfirmButton (line 4) | function ConfirmButton(props) {
FILE: components/CopyMenu.js
function CopyButton (line 26) | function CopyButton(props) {
function CopyEmbed (line 37) | function CopyEmbed({ mapper, title }) {
function useClipboardSupport (line 47) | function useClipboardSupport() {
function CopyMenu (line 59) | function CopyMenu({ isVisible, toggleVisibility, copyImage, carbonRef }) {
FILE: components/Dropdown.js
class Dropdown (line 9) | class Dropdown extends React.PureComponent {
method render (line 60) | render() {
FILE: components/Editor.js
class Editor (line 48) | class Editor extends React.Component {
method componentDidMount (line 57) | async componentDidMount() {
method render (line 321) | render() {
FILE: components/EditorContainer.js
function onReset (line 13) | function onReset() {
function toastsReducer (line 26) | function toastsReducer(curr, action) {
function EditorContainer (line 38) | function EditorContainer(props) {
FILE: components/ExportMenu.js
function preventDefault (line 13) | function preventDefault(fn) {
function ExportMenu (line 20) | function ExportMenu({ onChange, exportSize, isVisible, toggleVisibility,...
FILE: components/FontFace.js
function FontFace (line 3) | function FontFace(config) {
FILE: components/FontSelect.js
constant EXTENSIONS (line 7) | const EXTENSIONS = ['.otf', '.ttf', '.woff']
function FontSelect (line 24) | function FontSelect(props) {
FILE: components/ImagePicker.js
constant INITIAL_STATE (line 40) | const INITIAL_STATE = {
class ImagePicker (line 49) | class ImagePicker extends React.Component {
method getDerivedStateFromProps (line 51) | static getDerivedStateFromProps(nextProps, state) {
method render (line 174) | render() {
function GeneratePaletteSetting (line 354) | function GeneratePaletteSetting({ onChange }) {
FILE: components/ListSetting.js
class ListSetting (line 7) | class ListSetting extends React.Component {
method renderListItems (line 27) | renderListItems() {
method render (line 62) | render() {
FILE: components/LoginButton.js
function Drawer (line 9) | function Drawer(props) {
function LoginButton (line 48) | function LoginButton({ isVisible, toggleVisibility }) {
FILE: components/Meta.js
constant CODEMIRROR_VERSION (line 8) | const CODEMIRROR_VERSION = '5.65.5'
constant HIGHLIGHTS_ONLY (line 10) | const HIGHLIGHTS_ONLY = ['shades-of-purple', 'vscode', 'a11y-dark']
constant LOCAL_STYLESHEETS (line 11) | const LOCAL_STYLESHEETS = ['one-light', 'one-dark', 'verminal', 'night-o...
constant CDN_STYLESHEETS (line 12) | const CDN_STYLESHEETS = THEMES.filter(
function Link (line 16) | function Link({ href }) {
FILE: components/Page.js
constant COLUMN (line 9) | const COLUMN = `
class Page (line 15) | class Page extends React.Component {
method render (line 16) | render() {
FILE: components/PhotoCredit.js
function PhotoCredit (line 3) | function PhotoCredit({ photographer }) {
FILE: components/Popout.js
class PopoutManager (line 9) | class PopoutManager extends React.Component {
method componentDidMount (line 24) | componentDidMount() {
method componentWillUnmount (line 28) | componentWillUnmount() {
method render (line 32) | render() {
class Popout (line 46) | class Popout extends React.PureComponent {
method render (line 52) | render() {
FILE: components/RandomImage.js
function RandomImage (line 8) | function RandomImage(props) {
FILE: components/ReferralLink.js
function ReferralLink (line 5) | function ReferralLink(props) {
FILE: components/SelectionEditor.js
function ModifierButton (line 8) | function ModifierButton(props) {
function reducer (line 24) | function reducer(state, action) {
function SelectionEditor (line 54) | function SelectionEditor({ onChange }) {
FILE: components/Settings.js
function KeyboardShortcut (line 18) | function KeyboardShortcut({ trigger, handle }) {
function WindowSettings (line 23) | function WindowSettings({
function EditorSettings (line 123) | function EditorSettings({
function MiscSettings (line 195) | function MiscSettings({ format, reset, applyPreset, settings }) {
class Settings (line 264) | class Settings extends React.PureComponent {
method componentDidMount (line 276) | componentDidMount() {
method render (line 420) | render() {
FILE: components/ShareMenu.js
function ShareMenu (line 12) | function ShareMenu({ isVisible, toggleVisibility, tweet, imgur }) {
FILE: components/Slider.js
class Slider (line 5) | class Slider extends React.Component {
method render (line 16) | render() {
FILE: components/SnippetToolbar.js
function DeleteButton (line 16) | function DeleteButton(props) {
function DuplicateButton (line 35) | function DuplicateButton(props) {
function SaveButton (line 55) | function SaveButton({ loading, onClick, sameUser }) {
function SnippetToolbar (line 85) | function SnippetToolbar({ toggleVisibility, isVisible, snippet, ...props...
FILE: components/Spinner.js
function Spinner (line 3) | function Spinner({ size = 24 }) {
FILE: components/ThemeSelect.js
constant WINDOW_THEMES_MAP (line 6) | const WINDOW_THEMES_MAP = { none: None, sharp: Sharp, bw: BW, boxy: Boxy }
class ThemeSelect (line 8) | class ThemeSelect extends React.Component {
method renderThemes (line 15) | renderThemes() {
method render (line 56) | render() {
FILE: components/Themes/GlobalHighlights.js
function GlobalHighlights (line 4) | function GlobalHighlights({ highlights }) {
FILE: components/Themes/index.js
class Themes (line 60) | class Themes extends React.PureComponent {
method getDerivedStateFromProps (line 67) | static getDerivedStateFromProps(props) {
method render (line 95) | render() {
FILE: components/Toasts.js
function Toast (line 3) | function Toast(props) {
function ToastContainer (line 93) | function ToastContainer(props) {
FILE: components/Toggle.js
class Toggle (line 6) | class Toggle extends React.PureComponent {
method render (line 13) | render() {
FILE: components/WidthHandler.js
function clamp (line 6) | function clamp(value, min, max) {
function WidthHandler (line 16) | function WidthHandler({
FILE: components/WindowControls.js
constant WINDOW_THEMES_MAP (line 39) | const WINDOW_THEMES_MAP = { bw: <ControlsBW />, boxy: <ControlsBoxy /> }
function TitleBar (line 41) | function TitleBar({ light, value, onChange }) {
function WindowControls (line 82) | function WindowControls({
FILE: components/WindowPointer.js
function WindowPointer (line 3) | function WindowPointer({ fromLeft, fromRight, color = '#fff' }) {
FILE: components/hooks.js
function userTiming (line 3) | function userTiming({ category, status, value }) {
function usePerformanceMeasurement (line 15) | function usePerformanceMeasurement() {
FILE: components/style/Font.js
function Font (line 8) | function Font() {
FILE: components/style/Reset.js
function Reset (line 4) | function Reset() {
FILE: components/style/Typography.js
function Typography (line 3) | function Typography() {
FILE: components/svg/Checkmark.js
function Checkmark (line 3) | function Checkmark({ width = 18, height = 18, color = '#FFFFFF' }) {
FILE: components/svg/Copy.js
constant SVG_RATIO (line 3) | const SVG_RATIO = 0.81
FILE: components/svg/Language.js
function Language (line 3) | function Language() {
FILE: components/svg/Logo.js
function Logo (line 3) | function Logo() {
FILE: components/svg/Remove.js
function Remove (line 3) | function Remove({ color = 'black', style }) {
FILE: components/svg/Settings.js
function Settings (line 3) | function Settings() {
FILE: components/svg/Theme.js
function Theme (line 3) | function Theme() {
FILE: components/svg/Watermark.js
function Watermark (line 3) | function Watermark({ light }) {
FILE: lib/api.js
function tweet (line 16) | function tweet(content, encodedImage) {
constant RATE_LIMIT_CODE (line 31) | const RATE_LIMIT_CODE = 420
function checkIfRateLimited (line 32) | function checkIfRateLimited(err) {
function openTwitterUrl (line 43) | function openTwitterUrl(twitterUrl) {
method download (line 62) | download(id) {
method random (line 65) | async random() {
function getSnippet (line 92) | function getSnippet(uid = '', { host, filename } = {}) {
function listSnippets (line 105) | function listSnippets(page) {
function updateSnippet (line 128) | function updateSnippet(uid, state) {
function deleteSnippet (line 152) | function deleteSnippet(uid) {
FILE: lib/client.js
function logout (line 18) | function logout() {
function login (line 22) | function login(provider) {
function loginGitHub (line 30) | function loginGitHub() {
FILE: lib/constants.js
constant FONTS (line 3) | const FONTS = [
constant HIGHLIGHT_KEYS (line 19) | const HIGHLIGHT_KEYS = [
constant THEMES (line 34) | const THEMES = [
constant THEMES_HASH (line 622) | const THEMES_HASH = toHash(THEMES)
constant LANGUAGES (line 624) | const LANGUAGES = [
constant EXPORT_SIZES (line 1046) | const EXPORT_SIZES = [
constant EXPORT_SIZES_HASH (line 1052) | const EXPORT_SIZES_HASH = toHash(EXPORT_SIZES)
constant LANGUAGE_MIME_HASH (line 1054) | const LANGUAGE_MIME_HASH = toHash(LANGUAGES, 'mime')
constant LANGUAGE_MODE_HASH (line 1055) | const LANGUAGE_MODE_HASH = toHash(LANGUAGES, 'mode')
constant LANGUAGE_NAME_HASH (line 1056) | const LANGUAGE_NAME_HASH = toHash(LANGUAGES, 'short')
constant DEFAULT_LANGUAGE (line 1058) | const DEFAULT_LANGUAGE = 'auto'
constant DEFAULT_THEME (line 1059) | const DEFAULT_THEME = THEMES_HASH.seti
constant DEFAULT_BG_COLOR (line 1060) | const DEFAULT_BG_COLOR = 'rgba(171, 184, 195, 1)'
constant DEFAULT_EXPORT_SIZE (line 1061) | const DEFAULT_EXPORT_SIZE = EXPORT_SIZES_HASH['2x']
constant COLORS (line 1063) | const COLORS = {
constant DEFAULT_CODE (line 1077) | const DEFAULT_CODE = `const pluckDeep = key => obj => key.split('.').red...
constant DEFAULT_SETTINGS (line 1089) | const DEFAULT_SETTINGS = {
constant DEFAULT_WIDTHS (line 1117) | const DEFAULT_WIDTHS = {
constant DEFAULT_PRESETS (line 1122) | const DEFAULT_PRESETS = [
FILE: lib/custom/autoCloseBrackets.js
function getOption (line 26) | function getOption(conf, name) {
function ensureBound (line 33) | function ensureBound(chars) {
function handler (line 42) | function handler(ch) {
function getConfig (line 48) | function getConfig(cm) {
function handleBackspace (line 55) | function handleBackspace(cm) {
function handleEnter (line 72) | function handleEnter(cm) {
function moveSel (line 96) | function moveSel(cm, dir) {
function contractSelection (line 112) | function contractSelection(sel) {
function handleChar (line 120) | function handleChar(cm, ch) {
function charsAround (line 203) | function charsAround(cm, pos) {
function stringStartsAfter (line 208) | function stringStartsAfter(cm, pos) {
FILE: lib/custom/modes/nim.js
function wordRegexp (line 6) | function wordRegexp(words) {
function tokenBase (line 343) | function tokenBase(stream, state) {
function tokenStringFactory (line 449) | function tokenStringFactory(delimiter) {
function indent (line 482) | function indent(stream, state, type) {
function dedent (line 506) | function dedent(stream, state, type) {
function tokenLexer (line 539) | function tokenLexer(stream, state) {
FILE: lib/custom/modes/riscv.js
function regexFromWords (line 14) | function regexFromWords(words, ins) {
function normal (line 605) | function normal(stream, state) {
function string (line 640) | function string(quote) {
FILE: lib/custom/modes/solidity.js
function tokenBase (line 207) | function tokenBase(stream, state) {
function tokenString (line 373) | function tokenString(quote) {
function tokenComment (line 390) | function tokenComment(stream, state) {
function isVersion (line 403) | function isVersion(stream, state) {
function isNumber (line 414) | function isNumber(ch, stream) {
function isValidInteger (line 427) | function isValidInteger(token) {
function isValidBytes (line 435) | function isValidBytes(token) {
function isValidFixed (line 443) | function isValidFixed(token) {
function updateHexLiterals (line 451) | function updateHexLiterals(token, stream) {
function updateGarmmer (line 473) | function updateGarmmer(ch, state) {
function Context (line 507) | function Context(indented, column, type, align, prev) {
function pushContext (line 514) | function pushContext(state, col, type) {
function popContext (line 517) | function popContext(state) {
FILE: lib/dom-to-image.js
function toSvg (line 54) | function toSvg(node, options) {
function toPixelData (line 93) | function toPixelData(node, options) {
function toPng (line 104) | function toPng(node, options) {
function toJpeg (line 117) | function toJpeg(node, options) {
function toBlob (line 129) | function toBlob(node, options) {
function copyOptions (line 135) | function copyOptions(options) {
function draw (line 150) | function draw(domNode, options) {
function cloneNode (line 177) | function cloneNode(node, filter, root) {
function embedFonts (line 311) | function embedFonts(node) {
function inlineImages (line 320) | function inlineImages(node) {
function makeSvgDataUri (line 326) | function makeSvgDataUri(node, width, height, escapePercentSign) {
function newUtil (line 354) | function newUtil() {
function newInliner (line 582) | function newInliner() {
function newFontFaces (line 648) | function newFontFaces() {
function newImages (line 714) | function newImages() {
FILE: lib/routing.js
constant URL_LIMIT (line 6) | const URL_LIMIT = 4e3
function getQueryStringState (line 146) | function getQueryStringState(query) {
function fixAsPathEncoding (line 151) | function fixAsPathEncoding(asPath) {
FILE: lib/util.js
constant SETTINGS_KEY (line 5) | const SETTINGS_KEY = 'CARBON_STATE'
constant PRESETS_KEY (line 6) | const PRESETS_KEY = 'CARBON_PRESETS'
constant THEMES_KEY (line 7) | const THEMES_KEY = 'CARBON_THEMES'
FILE: next.config.js
method headers (line 35) | headers() {
method rewrites (line 62) | rewrites() {
method redirects (line 65) | redirects() {
FILE: pages/[id].js
function getServerSideProps (line 8) | async function getServerSideProps({ req, res, query }) {
FILE: pages/_document.js
class Doc (line 4) | class Doc extends Document {
method render (line 5) | render() {
FILE: pages/about.js
function Contributors (line 4) | function Contributors() {
function About (line 43) | function About() {
FILE: pages/account.js
function logoutThunk (line 18) | function logoutThunk() {
function Plan (line 24) | function Plan({ selectBilling }) {
function Settings (line 177) | function Settings() {
function SettingsPage (line 248) | function SettingsPage() {
FILE: pages/api/image/[id].js
constant DOM_TO_IMAGE_URL (line 7) | const DOM_TO_IMAGE_URL = 'https://unpkg.com/dom-to-image@2.6.0/dist/dom-...
constant NOTO_COLOR_EMOJI_URL (line 8) | const NOTO_COLOR_EMOJI_URL =
function id (line 19) | async function id(req, res) {
FILE: pages/embed/[id].js
function getServerSideProps (line 8) | async function getServerSideProps({ req, res, query }) {
FILE: pages/embed/index.js
class Embed (line 41) | class Embed extends React.Component {
method componentDidMount (line 53) | async componentDidMount() {
method render (line 98) | render() {
FILE: pages/index.js
class Index (line 11) | class Index extends React.Component {
method componentDidMount (line 12) | componentDidMount() {
method componentWillUnmount (line 17) | componentWillUnmount() {
method render (line 27) | render() {
FILE: pages/snippets.js
function correctTimestamp (line 20) | function correctTimestamp(n) {
function Snippet (line 27) | function Snippet(props) {
function ActionButton (line 155) | function ActionButton(props) {
function useOnMount (line 169) | function useOnMount() {
function SnippetsPage (line 178) | function SnippetsPage() {
function Snippets (line 245) | function Snippets() {
Condensed preview — 148 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,468K chars).
[
{
"path": ".all-contributorsrc",
"chars": 26139,
"preview": "{\n \"projectName\": \"carbon\",\n \"projectOwner\": \"carbon-app\",\n \"files\": [\n \"README.md\",\n \"docs/README.ar.md\",\n "
},
{
"path": ".eslintrc.js",
"chars": 712,
"preview": "module.exports = {\n env: { es6: true, jest: true },\n extends: ['eslint:recommended', 'plugin:jsx-a11y/recommended', 'n"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3220,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 1518,
"preview": "# Contributing\n\nIf you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub. You d"
},
{
"path": ".github/FUNDING.yml",
"chars": 663,
"preview": "github: [carbon-app, mfix22] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\nopen_collecti"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 764,
"preview": "---\nname: Bug report\nabout: Create a report to help fix something about Carbon\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 604,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is"
},
{
"path": ".github/ISSUE_TEMPLATE/other.md",
"chars": 513,
"preview": "---\nname: Other\nabout: Let us know about something else\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the change**"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 223,
"preview": "<!---\r\nProvide a general summary of your changes in the Title above\r\n\r\nExpand on it in the description below (if applica"
},
{
"path": ".github/dependabot.yml",
"chars": 394,
"preview": "version: 2\nupdates:\n # Configuration for npm\n - package-ecosystem: 'npm'\n directory: '/'\n schedule:\n interv"
},
{
"path": ".github/ranger.yml",
"chars": 947,
"preview": "_extends: reporanger/superpowers\n\nlabels:\n dependencies: merge\n contributor:\n action: comment\n delay: 0s\n mes"
},
{
"path": ".github/workflows/validate.yml",
"chars": 562,
"preview": "name: CI\n\non: [push]\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v3\n - nam"
},
{
"path": ".gitignore",
"chars": 163,
"preview": "node_modules\n.env*\n.next\ndist\nout\ncypress/videos\ncypress/screenshots\n.idea\n.DS_Store\npackaged\ncoverage\npublic/service-wo"
},
{
"path": ".npmrc",
"chars": 55,
"preview": "package-lock=false\nregistry=https://registry.npmjs.org\n"
},
{
"path": ".prettierrc",
"chars": 90,
"preview": "{\n \"singleQuote\": true,\n \"printWidth\": 100,\n \"semi\": false,\n \"arrowParens\": \"avoid\"\n}\n"
},
{
"path": ".vercelignore",
"chars": 69,
"preview": ".github\nLICENSE\nREADME.md\nbin\nnode_modules\ncypress\ncypress.json\ndocs\n"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2022 Carbon\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 42679,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "SECURITY.md",
"chars": 180,
"preview": "# Security Policy\n\nPlease contact us at [carbon.now.sh+security@gmail.com](mailto:carbon.now.sh+security@gmail.com) to p"
},
{
"path": "bin/deploy.sh",
"chars": 285,
"preview": "#!/usr/bin/env bash\nset -e\n\nvercel switch carbon-app\n\nVERCEL_URL=$(vercel)\n\nyarn cy:run --config baseUrl=\"$VERCEL_URL\"\n\n"
},
{
"path": "components/Announcement.js",
"chars": 2213,
"preview": "import React from 'react'\n\n// Feature flag\nconst ACTIVE = false\n\nconst key = 'CARBON_CTA_4'\n\nfunction Toast() {\n const "
},
{
"path": "components/ApiContext.js",
"chars": 186,
"preview": "import React from 'react'\nimport api from '../lib/api'\n\nconst Context = React.createContext(api)\n\nexport function useAPI"
},
{
"path": "components/AuthContext.js",
"chars": 851,
"preview": "import React from 'react'\nimport firebase from '../lib/client'\n// IDEA: just read from firebase store at request time?\ni"
},
{
"path": "components/BackgroundSelect.js",
"chars": 4415,
"preview": "import React from 'react'\n\nimport ImagePicker from './ImagePicker'\nimport ColorPicker from './ColorPicker'\nimport Button"
},
{
"path": "components/Billing.js",
"chars": 6486,
"preview": "import React from 'react'\nimport { useAsyncCallback } from 'actionsack'\n\nimport Button from './Button'\nimport Input from"
},
{
"path": "components/Button.js",
"chars": 1687,
"preview": "import React from 'react'\nimport VisuallyHidden from '@reach/visually-hidden'\n\nimport { COLORS } from '../lib/constants'"
},
{
"path": "components/Carbon.js",
"chars": 15157,
"preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport dynamic from 'next/dynamic'\nimport hljs from 'highligh"
},
{
"path": "components/ColorPicker.js",
"chars": 1511,
"preview": "import React from 'react'\nimport SketchPicker from 'react-color/lib/Sketch'\n\nimport { COLORS } from '../lib/constants'\ni"
},
{
"path": "components/ConfirmButton.js",
"chars": 491,
"preview": "import React from 'react'\nimport Button from './Button'\n\nexport default function ConfirmButton(props) {\n const [confirm"
},
{
"path": "components/CopyMenu.js",
"chars": 4028,
"preview": "import React from 'react'\nimport { useRouter } from 'next/router'\nimport { useCopyTextHandler, useAsyncCallback, useKeyb"
},
{
"path": "components/Dropdown.js",
"chars": 8488,
"preview": "import React from 'react'\nimport Downshift from 'downshift'\nimport { matchSorter } from 'match-sorter'\nimport VisuallyHi"
},
{
"path": "components/Editor.js",
"chars": 13704,
"preview": "// Theirs\nimport React from 'react'\nimport Dropzone from 'dropperx'\nimport debounce from 'lodash.debounce'\nimport dynami"
},
{
"path": "components/EditorContainer.js",
"chars": 2479,
"preview": "// Theirs\nimport React from 'react'\nimport Router from 'next/router'\n\nimport Editor from './Editor'\nimport Toasts from '"
},
{
"path": "components/ExportMenu.js",
"chars": 5235,
"preview": "import React from 'react'\nimport { useKeyboardListener, useAsyncCallback } from 'actionsack'\n\nimport { COLORS, EXPORT_SI"
},
{
"path": "components/FontFace.js",
"chars": 329,
"preview": "import React from 'react'\n\nexport default function FontFace(config) {\n return (\n <style jsx global>\n {`\n "
},
{
"path": "components/FontSelect.js",
"chars": 1473,
"preview": "import React from 'react'\nimport ListSetting from './ListSetting'\nimport ReferralLink from './ReferralLink'\nimport { FON"
},
{
"path": "components/Footer.js",
"chars": 1849,
"preview": "import React from 'react'\nimport Link from 'next/link'\n\nimport { COLORS } from '../lib/constants'\n\nconst Footer = () => "
},
{
"path": "components/Header.js",
"chars": 1033,
"preview": "import React from 'react'\nimport Logo from './svg/Logo'\n\nconst Header = ({ enableHeroText }) => (\n <header role=\"banner"
},
{
"path": "components/ImagePicker.js",
"chars": 9700,
"preview": "import React from 'react'\nimport ReactCrop, { makeAspectCrop } from 'react-image-crop'\nimport { useLocalStorage } from '"
},
{
"path": "components/Input.js",
"chars": 1262,
"preview": "import React from 'react'\n\nimport { COLORS } from '../lib/constants'\n\nconst Input = React.forwardRef(\n (\n {\n co"
},
{
"path": "components/ListSetting.js",
"chars": 2714,
"preview": "import React from 'react'\n\nimport Checkmark from './svg/Checkmark'\nimport { COLORS } from '../lib/constants'\nimport { to"
},
{
"path": "components/LoginButton.js",
"chars": 2693,
"preview": "import React from 'react'\nimport Link from 'next/link'\nimport firebase, { logout, loginGitHub } from '../lib/client'\n\nim"
},
{
"path": "components/MenuButton.js",
"chars": 1102,
"preview": "import React from 'react'\n\nimport Button from './Button'\nimport { COLORS } from '../lib/constants'\nimport * as Arrows fr"
},
{
"path": "components/Meta.js",
"chars": 3357,
"preview": "import React from 'react'\nimport Head from 'next/head'\nimport { THEMES, THEMES_HASH, COLORS } from '../lib/constants'\nim"
},
{
"path": "components/Overlay.js",
"chars": 669,
"preview": "import React from 'react'\n\nconst Overlay = props => (\n <div className=\"dnd-container\">\n {props.isOver ? <div classNa"
},
{
"path": "components/Page.js",
"chars": 1444,
"preview": "import React from 'react'\nimport AuthContext from './AuthContext'\nimport Meta from './Meta'\nimport Header from './Header"
},
{
"path": "components/PhotoCredit.js",
"chars": 668,
"preview": "import React from 'react'\n\nexport default function PhotoCredit({ photographer }) {\n return (\n <div className=\"photo-"
},
{
"path": "components/Popout.js",
"chars": 1904,
"preview": "import React from 'react'\nimport enhanceWithClickOutside from 'react-click-outside'\n\nimport WindowPointer from './Window"
},
{
"path": "components/Presets.js",
"chars": 5342,
"preview": "import React from 'react'\n\nimport Button from './Button'\nimport { COLORS, DEFAULT_PRESETS } from '../lib/constants'\nimpo"
},
{
"path": "components/RandomImage.js",
"chars": 2539,
"preview": "import React from 'react'\nimport { useAsyncCallback } from 'actionsack'\n\nimport { Spinner } from './Spinner'\nimport { us"
},
{
"path": "components/ReferralLink.js",
"chars": 562,
"preview": "import React from 'react'\n\nimport { COLORS } from '../lib/constants'\n\nexport default function ReferralLink(props) {\n re"
},
{
"path": "components/SelectionEditor.js",
"chars": 3463,
"preview": "import React from 'react'\nimport { useKeyboardListener } from 'actionsack'\nimport Popout from './Popout'\nimport Button f"
},
{
"path": "components/Settings.js",
"chars": 13893,
"preview": "import React from 'react'\nimport omitBy from 'lodash.omitby'\nimport { useKeyboardListener } from 'actionsack'\n\nimport Th"
},
{
"path": "components/ShareMenu.js",
"chars": 2355,
"preview": "import React from 'react'\nimport { useAsyncCallback, useOnline as useOnlineListener } from 'actionsack'\n\nimport { useAPI"
},
{
"path": "components/Slider.js",
"chars": 2088,
"preview": "import React from 'react'\n\nimport { COLORS } from '../lib/constants'\n\nclass Slider extends React.Component {\n static de"
},
{
"path": "components/SnippetToolbar.js",
"chars": 3883,
"preview": "import React from 'react'\nimport { useAsyncCallback, useOnline, useKeyboardListener } from 'actionsack'\n\nimport Button f"
},
{
"path": "components/Spinner.js",
"chars": 1131,
"preview": "import React from 'react'\n\nexport function Spinner({ size = 24 }) {\n return (\n <div className=\"bounce\">\n <div c"
},
{
"path": "components/ThemeSelect.js",
"chars": 2370,
"preview": "import React from 'react'\nimport Toggle from './Toggle'\nimport { None, BW, Sharp, Boxy } from './svg/WindowThemes'\nimpor"
},
{
"path": "components/Themes/GlobalHighlights.js",
"chars": 2525,
"preview": "// Theirs\nimport React from 'react'\n\nexport default function GlobalHighlights({ highlights }) {\n return (\n <style js"
},
{
"path": "components/Themes/ThemeCreate.js",
"chars": 5143,
"preview": "import React from 'react'\n\nimport Input from '../Input'\nimport Button from '../Button'\nimport ListSetting from '../ListS"
},
{
"path": "components/Themes/index.js",
"chars": 3618,
"preview": "import React from 'react'\nimport dynamic from 'next/dynamic'\n\nimport GlobalHighlights from './GlobalHighlights'\nimport D"
},
{
"path": "components/Toasts.js",
"chars": 2606,
"preview": "import React from 'react'\n\nfunction Toast(props) {\n const [display, on] = React.useState(true)\n\n function off() {\n "
},
{
"path": "components/Toggle.js",
"chars": 1771,
"preview": "import React from 'react'\n\nimport Checkmark from './svg/Checkmark'\nimport { COLORS } from '../lib/constants'\n\nclass Togg"
},
{
"path": "components/Toolbar.js",
"chars": 807,
"preview": "import React from 'react'\n\nconst Toolbar = props => (\n <div className=\"toolbar\" style={props.style}>\n {props.childre"
},
{
"path": "components/WidthHandler.js",
"chars": 2076,
"preview": "import React from 'react'\nimport { DEFAULT_WIDTHS, COLORS } from '../lib/constants'\n\nconst { minWidth, maxWidth } = DEFA"
},
{
"path": "components/WindowControls.js",
"chars": 2980,
"preview": "import React from 'react'\nimport { useCopyTextHandler } from 'actionsack'\n\nimport { COLORS } from '../lib/constants'\nimp"
},
{
"path": "components/WindowPointer.js",
"chars": 622,
"preview": "import React from 'react'\n\nexport default function WindowPointer({ fromLeft, fromRight, color = '#fff' }) {\n return (\n "
},
{
"path": "components/hooks.js",
"chars": 1860,
"preview": "import React from 'react'\n\nfunction userTiming({ category, status, value }) {\n try {\n window.gtag('event', status, {"
},
{
"path": "components/style/Font.js",
"chars": 4996,
"preview": "/*\n * See https://developers.google.com/web/updates/2016/02/font-display and\n * https://css-tricks.com/font-display-mass"
},
{
"path": "components/style/Reset.js",
"chars": 4563,
"preview": "import React from 'react'\nimport { COLORS } from '../../lib/constants'\n\nexport default function Reset() {\n return (\n "
},
{
"path": "components/style/Typography.js",
"chars": 2155,
"preview": "import React from 'react'\n\nexport default function Typography() {\n return (\n <style jsx global>\n {`\n /* "
},
{
"path": "components/svg/Arrows.js",
"chars": 632,
"preview": "import React from 'react'\n\nconst Up = ({ color = 'white' }) => (\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"n"
},
{
"path": "components/svg/Checkmark.js",
"chars": 932,
"preview": "import React from 'react'\n\nexport default function Checkmark({ width = 18, height = 18, color = '#FFFFFF' }) {\n return "
},
{
"path": "components/svg/Controls.js",
"chars": 1419,
"preview": "import React from 'react'\n\nexport const Controls = () => (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"54\" height=\""
},
{
"path": "components/svg/Copy.js",
"chars": 1206,
"preview": "import React from 'react'\n\nconst SVG_RATIO = 0.81\n\nconst Copy = ({ size, color }) => {\n const width = size * SVG_RATIO\n"
},
{
"path": "components/svg/Language.js",
"chars": 2822,
"preview": "import React from 'react'\n\nexport default function Language() {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" w"
},
{
"path": "components/svg/Logo.js",
"chars": 15543,
"preview": "import React from 'react'\n\nexport default function Logo() {\n return (\n <svg\n role=\"img\"\n aria-label=\"Carbo"
},
{
"path": "components/svg/Remove.js",
"chars": 465,
"preview": "import React from 'react'\n\nexport default function Remove({ color = 'black', style }) {\n return (\n <svg\n width="
},
{
"path": "components/svg/Settings.js",
"chars": 2768,
"preview": "import React from 'react'\n\nexport default function Settings() {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" w"
},
{
"path": "components/svg/Theme.js",
"chars": 1727,
"preview": "import React from 'react'\n\nexport default function Theme() {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" widt"
},
{
"path": "components/svg/Watermark.js",
"chars": 38191,
"preview": "import React from 'react'\n\nexport default function Watermark({ light }) {\n return (\n <svg\n className=\"watermark"
},
{
"path": "components/svg/WindowThemes.js",
"chars": 5022,
"preview": "import React from 'react'\n\nexport const Sharp = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"60\"\n "
},
{
"path": "cypress/config.json",
"chars": 85,
"preview": "{\n \"baseUrl\": \"https://carbon.now.sh/\",\n \"projectId\": \"p2tbx4\",\n \"video\": false\n}\n"
},
{
"path": "cypress/integration/background-color.spec.js",
"chars": 1748,
"preview": "/* global cy */\nimport { editorVisible } from '../support'\n\n// usually we can visit the page before each test\n// but the"
},
{
"path": "cypress/integration/basic.spec.js",
"chars": 2063,
"preview": "/* global cy */\nimport { editorVisible } from '../support'\ndescribe('Basic', () => {\n it('Should open editor with the c"
},
{
"path": "cypress/integration/embed.spec.js",
"chars": 247,
"preview": "/* global cy */\ndescribe('Embed', () => {\n it('Should render the Carbon editor but no toolbar', () => {\n cy.visit('/"
},
{
"path": "cypress/integration/gist.spec.js",
"chars": 663,
"preview": "/* global cy Cypress */\nimport { editorVisible } from '../support'\n\ndescribe('Gist', () => {\n const test = Cypress.env("
},
{
"path": "cypress/integration/localStorage.spec.js",
"chars": 1166,
"preview": "/* global cy */\nimport { editorVisible } from '../support'\n\n// usually we can visit the page before each test\n// but the"
},
{
"path": "cypress/integration/security.spec.js",
"chars": 1411,
"preview": "/* global cy */\nimport { editorVisible } from '../support'\n\ndescribe('security', () => {\n it('should not alert from bg "
},
{
"path": "cypress/integration/visual-testing.spec.js",
"chars": 2604,
"preview": "/* global cy, before, after */\nimport { environment } from '../util'\n\ndescribe.skip('Visual regression testing', () => {"
},
{
"path": "cypress/plugins/index.js",
"chars": 193,
"preview": "module.exports = (/* on, config */) => {\n // `on` is used to hook into various events Cypress emits\n // `config` is th"
},
{
"path": "cypress/support/index.js",
"chars": 136,
"preview": "/* global cy */\n// import '@applitools/eyes-cypress/commands'\nexport const editorVisible = () => cy.get('.editor').shoul"
},
{
"path": "cypress/util.js",
"chars": 72,
"preview": "export const environment = { width: 1000, height: 768, name: 'chrome' }\n"
},
{
"path": "docs/README.ar.md",
"chars": 10931,
"preview": "<p align=\"center\">\n\n<img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11e"
},
{
"path": "docs/README.bn.md",
"chars": 41517,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.br.pt.md",
"chars": 37015,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.cn.zh.md",
"chars": 38506,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.de.md",
"chars": 36883,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.es.md",
"chars": 42646,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.fa.md",
"chars": 41495,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.fr.md",
"chars": 37723,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.he.md",
"chars": 41793,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.hi.md",
"chars": 36537,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.in.md",
"chars": 42731,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.it.md",
"chars": 41172,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.ja.md",
"chars": 36629,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.kr.md",
"chars": 35875,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.ml.md",
"chars": 37062,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.nl.md",
"chars": 42120,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.pl.md",
"chars": 41893,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.ru.md",
"chars": 40001,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.se.md",
"chars": 40186,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.ta.md",
"chars": 36987,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.tr.md",
"chars": 36546,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.tw.zh.md",
"chars": 38733,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.ua.md",
"chars": 40961,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "docs/README.uz.md",
"chars": 35560,
"preview": "<p align=\"center\">\n <img width=\"100%\" src=\"https://user-images.githubusercontent.com/10369094/31211729-591d059c-a950-11"
},
{
"path": "lib/api.js",
"chars": 4166,
"preview": "import axios from 'axios'\nimport debounce from 'lodash.debounce'\nimport ms from 'ms'\n\nimport { fileToDataURL } from './u"
},
{
"path": "lib/client.js",
"chars": 1278,
"preview": "import firebase from 'firebase/app'\nimport 'firebase/auth'\n\nif (firebase.apps.length === 0) {\n if (process.env.NEXT_PUB"
},
{
"path": "lib/constants.js",
"chars": 23648,
"preview": "import toHash from 'tohash'\n\nexport const FONTS = [\n { id: 'Anonymous Pro', name: 'Anonymous Pro' },\n { id: 'Cascadia "
},
{
"path": "lib/custom/autoCloseBrackets.js",
"chars": 6525,
"preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/"
},
{
"path": "lib/custom/modes/apache.js",
"chars": 3381,
"preview": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/L"
},
{
"path": "lib/custom/modes/elixir.js",
"chars": 104,
"preview": "// Require Codemirror elixir mode from npm modules and register it here\nimport 'codemirror-mode-elixir'\n"
},
{
"path": "lib/custom/modes/graphql.js",
"chars": 33,
"preview": "import 'codemirror-graphql/mode'\n"
},
{
"path": "lib/custom/modes/nim.js",
"chars": 12453,
"preview": "const CodeMirror = require('codemirror')\n\nCodeMirror.defineMode('nim', function (conf, parserConf) {\n var ERRORCLASS = "
},
{
"path": "lib/custom/modes/riscv.js",
"chars": 12831,
"preview": "/*\n RISC-V Code Mirror Mode\n\n Based on the mode present in the Venus Simulator\n Author: kvakil\n Source: https://"
},
{
"path": "lib/custom/modes/solidity.js",
"chars": 15211,
"preview": "/**\n * codemirror-solidity by alincode — https://github.com/alincode/codemirror-solidity\n * Distributed under MIT\n */\n/*"
},
{
"path": "lib/dom-to-image.js",
"chars": 22770,
"preview": "/* eslint-disable */\n/**\n * https://github.com/tsayen/dom-to-image/blob/master/src/dom-to-image.js\n */\n;(function (globa"
},
{
"path": "lib/highlight-languages.js",
"chars": 192,
"preview": "import { LANGUAGES } from './constants'\n\nexport default LANGUAGES.filter(l => l.highlight)\n .map(l => l.short || l.mode"
},
{
"path": "lib/routing.js",
"chars": 5062,
"preview": "import Morph from 'morphmorph'\nimport url from 'url'\n\nimport { escapeHtml } from './util'\n\nconst URL_LIMIT = 4e3\nconst m"
},
{
"path": "lib/util.js",
"chars": 2492,
"preview": "import morph from 'morphmorph'\nimport omitBy from 'lodash.omitby'\nimport { htmlUnescape } from 'escape-goat'\n\nconst SETT"
},
{
"path": "next.config.js",
"chars": 2522,
"preview": "const bundleAnalyzer = require('@next/bundle-analyzer')\nconst withOffline = require('next-pwa')\n\nconst withBundleAnalyze"
},
{
"path": "package.json",
"chars": 2969,
"preview": "{\n \"name\": \"carbon\",\n \"description\": \"Create and share beautiful images of your source code\",\n \"version\": \"4.9.10\",\n "
},
{
"path": "pages/[id].js",
"chars": 796,
"preview": "import React from 'react'\nimport Router from 'next/router'\n\nimport IndexPage from './index'\n\nimport api from '../lib/api"
},
{
"path": "pages/_document.js",
"chars": 309,
"preview": "import React from 'react'\nimport Document, { Html, Head, Main, NextScript } from 'next/document'\n\nexport default class D"
},
{
"path": "pages/about.js",
"chars": 6924,
"preview": "import React from 'react'\nimport Page from '../components/Page'\n\nfunction Contributors() {\n const [contributors, setCon"
},
{
"path": "pages/account.js",
"chars": 6149,
"preview": "// Theirs\nimport React from 'react'\nimport dynamic from 'next/dynamic'\n\n// Ours\nimport Button from '../components/Button"
},
{
"path": "pages/api/image/[id].js",
"chars": 3657,
"preview": "/* global domtoimage */\nimport qs from 'querystring'\nimport chrome from 'chrome-aws-lambda'\nimport puppeteer from 'puppe"
},
{
"path": "pages/api/oembed.js",
"chars": 1336,
"preview": "/*\n * See oEmbed standard here: https://oembed.com/\n */\nconst url = require('url')\n\nconst toIFrame = (url, width, height"
},
{
"path": "pages/embed/[id].js",
"chars": 834,
"preview": "import React from 'react'\nimport Router from 'next/router'\n\nimport EmbedPage from './index'\n\nimport api from '../../lib/"
},
{
"path": "pages/embed/index.js",
"chars": 2851,
"preview": "// Theirs\nimport React from 'react'\nimport Head from 'next/head'\nimport { withRouter } from 'next/router'\n\n// Ours\nimpor"
},
{
"path": "pages/index.js",
"chars": 1131,
"preview": "// Theirs\nimport React from 'react'\nimport { withRouter } from 'next/router'\nimport Either from 'eitherx'\n\n// Ours\nimpor"
},
{
"path": "pages/snippets.js",
"chars": 6410,
"preview": "// Theirs\nimport React from 'react'\nimport Link from 'next/link'\nimport Router from 'next/router'\nimport formatDistanceT"
},
{
"path": "public/manifest.json",
"chars": 522,
"preview": "{\n \"name\": \"Carbon\",\n \"short_name\": \"Carbon\",\n \"background_color\": \"#121212\",\n \"theme_color\": \"#121212\",\n \"descript"
},
{
"path": "public/robots.txt",
"chars": 72,
"preview": "User-agent: Twitterbot\nAllow: /*\n\nUser-agent: *\nDisallow: /*?*\nAllow: /*"
},
{
"path": "public/static/react-crop.css",
"chars": 4728,
"preview": ".ReactCrop .ord-s,.ReactCrop .ord-se,.ReactCrop .ord-sw{margin-bottom:-5px;bottom:0}.ReactCrop{position:relative;display"
},
{
"path": "release.js",
"chars": 40,
"preview": "module.exports = require('now-release')\n"
},
{
"path": "vercel.json",
"chars": 258,
"preview": "{\n \"alias\": \"carbon.now.sh\",\n \"version\": 2,\n \"public\": true,\n \"github\": {\n \"autoAlias\": false\n },\n \"functions\":"
}
]
About this extraction
This page contains the full source code of the carbon-app/carbon GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 148 files (1.3 MB), approximately 453.3k tokens, and a symbol index with 230 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.