├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── jest.config.js ├── package.json ├── src ├── __tests__ │ ├── prefill-embed-test.tsx │ ├── prefill-lang-test.tsx │ ├── strip-indent-test.ts │ └── use-codepen-embed-test.tsx ├── components │ ├── prefill-embed.tsx │ └── prefill-lang.tsx ├── config.ts ├── index.ts ├── lib │ ├── strip-indent.ts │ └── use-codepen-embed.ts ├── test-setup.ts ├── types │ └── lang.ts └── util │ └── render-string.ts ├── tsconfig.json ├── tslint.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "bracketSpacing": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alex Zaworski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React CodePen Prefill Embed 2 | 3 | An unofficial, TypeScript-powered React wrapper around CodePen's very-official [Prefill Embed API.](https://blog.codepen.io/documentation/prefill-embeds/) 4 | 5 | ## Is this for me? 6 | 7 | From CodePen's docs: 8 | 9 | > Here's why Prefill Embeds are useful: say you have a blog or documentation site displaying important bits of code within `
` tags. Perhaps you need to show a bit of HTML, CSS, or JavaScript.
10 |
11 | > ...
12 |
13 | > CodePen Prefill Embeds are designed to render that code for you in a safe and isolated environment to help your users see, understand, and play with it more easily.
14 |
15 | This wrapper might be a good fit for you if you're already using React to power something like a blog (maybe with [Gatsby](https://www.gatsbyjs.org/) or [Next.js](https://nextjs.org/)) and don't wanna fuss about with things like [escaping markup](https://blog.codepen.io/documentation/prefill-embeds/#what-could-go-wrong-2).
16 |
17 | ## Installation
18 |
19 | ### From your favorite package registry
20 |
21 | - `yarn add react-codepen-prefill-embed`
22 | - `npm install --save react-codepen-prefill-embed`
23 |
24 | ```
25 | import {
26 | PrefillEmbed,
27 | PrefillLang,
28 | useCodePenEmbed,
29 | stripIndent
30 | } = from 'react-codepen-prefill-embed';
31 | // ...
32 | ```
33 |
34 | ### From a CDN
35 |
36 | ```html
37 |
41 |
42 |
43 |
44 |
45 |
49 |
50 |
51 |
52 |
61 | ```
62 |
63 | ### Use on CodePen
64 |
65 | [Here's a template](https://codepen.io/pen?template=e0925944542ee1f8bb7d108f2015def1) that has all the scripts loaded for you.
66 |
67 | ## Usage example
68 |
69 | ```jsx
70 | const App = () => {
71 | useCodePenEmbed();
72 | return (
73 |
84 | }
85 | scripts={[
86 | 'https://unpkg.com/react@16.8.6/umd/react.development.js',
87 | 'https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.js',
88 | ]}
89 | stylesheets={['https://unpkg.com/normalize.css@8.0.1/normalize.css']}
90 | >
91 |
92 | {stripIndent`
93 |
94 | `}
95 |
96 |
97 | {stripIndent`
98 | $bg: #eee;
99 | body {
100 | background: $bg;
101 | }
102 | `}
103 |
104 |
105 | {stripIndent`
106 | const App = () => Hello
;
107 | ReactDOM.render(
108 | ,
109 | document.getElementById('root')
110 | );
111 | `}
112 |
113 |
114 | );
115 | };
116 | ```
117 |
118 | ## Components
119 |
120 | ### ``
121 |
122 | The root of your embed. This is where you set Pen-specific settings like description or theme.
123 |
124 | #### Props
125 |
126 | [Type annotations here](/src/components/prefill-embed.tsx) if you prefer. Any props not listed will be passed to the wrapping `` element. All `` props are optional.
127 |
128 | ##### Related to the Pen:
129 |
130 | | Prop | Type | Default | Description |
131 | | ----------- | ------ | ------- | ------------------------------------------------------------ |
132 | | penTitle | string | -- | Title of your Pen |
133 | | description | string | -- | Description of your Pen |
134 | | tags | array | -- | Tags for your Pen. Max 5. |
135 | | htmlClasses | array | -- | Classes to add to the html element of your Pen |
136 | | stylesheets | array | -- | Stylesheets to include as external resources |
137 | | scripts | array | -- | Scripts to include as external resources |
138 | | head | node | -- | Content for the `` of the document |
139 | | prefillData | object | -- | Any additional keys/values you want passed to `data-prefill` |
140 |
141 | ##### Related to the embed:
142 |
143 | | Prop | Type | Default | Description |
144 | | ----------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145 | | embedHeight | string | -- | Height of the generated iframe |
146 | | themeId | string | -- | [Theme](https://blog.codepen.io/documentation/features/embedded-pens/#embed-themes-1) id to use in the embed |
147 | | editable | bool | false | Whether or not the embed should be editable |
148 | | defaultTabs | array | ['result'] | Default open tab(s) |
149 | | className | string | 'codepen' | Class for the wrapping ``. If you change this, you'll be responsible for [invoking the prefill API](https://blog.codepen.io/documentation/prefill-embeds/#enhance-pre-blocks-into-editable-embeds-on-click-or-on-any-other-event-3) |
150 |
151 | ### ``
152 |
153 | Used as children of ``— your HTML, CSS, and JS tabs.
154 |
155 | #### Props
156 |
157 | `lang` is the only prop, everything else is passed to the wrapping `` element.
158 |
159 | | Prop | Type | Default | Required | Description |
160 | | ---- | ------ | ------- | -------- | ------------------------------------------ |
161 | | lang | string | -- | ⚠️ | What kinda code is it? Support table below |
162 |
163 | ##### Supported languages
164 |
165 | HTML: `html`, `slim`, `haml`, `markdown`, `pug`
166 |
167 | JS: `js`, `babel`, `typescript`, `coffeescript`, `livescript`
168 |
169 | CSS: `css`, `scss`, `sass`, `less`, `stylus`
170 |
171 | ## Hooks and Utilities
172 |
173 | ### `usePrefillEmbed`
174 |
175 | A [hook](https://reactjs.org/docs/hooks-overview.html) to load CodePen's prefill API. This may be useful if you don't want to deal with adding a CDN-only script to your build process, but isn't recommended in the browser.
176 |
177 | The script is loaded from `https://static.codepen.io/assets/embed/ei.js` and is `async` by default.
178 |
179 | #### Options
180 |
181 | | Option | Type | Default | Description |
182 | | ----------- | ------- | ------- | ------------------------------------------ |
183 | | async | boolean | true | Load the script with the `async` attribute |
184 | | srcOverride | string | -- | Override the source of the loaded script |
185 |
186 | ### `stripIndent`
187 |
188 | Multi-line strings are easiest to work with in [template literals.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) This can cause headaches when dealing with indented JSX code, however— consider this:
189 |
190 | ```jsx
191 |
192 |
193 |
194 | {`
195 |
196 | Hello
197 |
198 | `}
199 |
200 |
201 |
202 | ```
203 |
204 | Which will yield the following markup in your Pen:
205 |
206 | ```text
207 |
208 | Hello
209 |
210 | ```
211 |
212 | In order for whitespace to look normal you'd need to do something like this:
213 |
214 | ```jsx
215 |
216 |
217 |
218 | {`
219 | Hello
220 | `}
221 |
222 |
223 |
224 | ```
225 |
226 | And that's a bummer. Instead, you can use the provided [template tag](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) to strip leading indents and leading/trailing newlines.
227 |
228 | Whitespace to strip is determined based on the first line of text after removing a leading newline. Tabs or spaces should both be fine so long as you're consistent.
229 |
230 | ```jsx
231 |
232 |
233 |
234 | {stripIndent`
235 |
236 | Hello
237 |
238 | `}
239 |
240 |
241 |
242 | ```
243 |
244 | ...would become:
245 |
246 | ```
247 |
248 | Hello
249 |
250 | ```
251 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import types from './dist/cjs';
2 | export default types;
3 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/cjs');
2 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | setupFilesAfterEnv: ['src/test-setup.ts'],
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-codepen-prefill-embed",
3 | "version": "0.0.2",
4 | "description": "A React wrapper for CodePen's prefill embed API",
5 | "main": "index.js",
6 | "files": [
7 | "dist"
8 | ],
9 | "types": "./dist/cjs/index.d.ts",
10 | "author": "Alex Zaworski (https://zawor.ski)",
11 | "license": "MIT",
12 | "peerDependencies": {
13 | "react": ">=16.8",
14 | "react-dom": ">=16.8"
15 | },
16 | "scripts": {
17 | "test": "jest",
18 | "build": "rm -rf ./dist && webpack"
19 | },
20 | "sideEffects": false,
21 | "devDependencies": {
22 | "@babel/core": "^7.5.5",
23 | "@babel/preset-env": "^7.5.5",
24 | "@types/enzyme": "^3.9.4",
25 | "@types/enzyme-adapter-react-16": "^1.0.5",
26 | "@types/jest": "^24.0.15",
27 | "@types/jsdom": "^12.2.4",
28 | "@types/node": "^12.0.10",
29 | "@types/prettier": "^1.16.4",
30 | "@types/react": "^16.8.22",
31 | "@types/react-dom": "^16.8.4",
32 | "babel-loader": "^8.0.6",
33 | "enzyme": "^3.10.0",
34 | "enzyme-adapter-react-16": "^1.14.0",
35 | "jest": "^24.8.0",
36 | "jsdom": "^15.1.1",
37 | "react": "^16.8.6",
38 | "react-dom": "^16.8.6",
39 | "ts-jest": "^24.0.2",
40 | "ts-loader": "^6.0.4",
41 | "tslint": "^5.18.0",
42 | "tslint-config-prettier": "^1.18.0",
43 | "tslint-loader": "^3.5.4",
44 | "typescript": "^3.5.2",
45 | "webpack": "^4.34.0",
46 | "webpack-cli": "^3.3.4"
47 | },
48 | "dependencies": {}
49 | }
50 |
--------------------------------------------------------------------------------
/src/__tests__/prefill-embed-test.tsx:
--------------------------------------------------------------------------------
1 | import {shallow} from 'enzyme';
2 | import * as React from 'react';
3 |
4 | import PrefillEmbed from '../components/prefill-embed';
5 |
6 | const embed = shallow(
7 | }
13 | stylesheets={['some-file.css']}
14 | scripts={['first-style-sheet.css', 'another-style-sheet.css']}
15 | embedHeight="400"
16 | editable
17 | defaultTabs={['js', 'result']}
18 | themeId="1234"
19 | prefillData={{someCustomJSON: 'foo'}}
20 | data-custom-thing="foo"
21 | />
22 | );
23 |
24 | const expectedJSON = {
25 | description: 'This tests some stuff',
26 | head: '',
27 | html_classes: ['a-class'],
28 | scripts: ['first-style-sheet.css', 'another-style-sheet.css'],
29 | someCustomJSON: 'foo',
30 | stylesheets: ['some-file.css'],
31 | tags: ['react'],
32 | title: 'A test',
33 | };
34 |
35 | const expectedData = {
36 | 'data-custom-thing': 'foo',
37 | 'data-default-tab': 'js,result',
38 | 'data-editable': 'true',
39 | 'data-height': '400',
40 | 'data-theme-id': '1234',
41 | };
42 |
43 | describe('PrefillLang component', () => {
44 | const embedProps = embed.props();
45 | const parsedPrefillProp = JSON.parse(embedProps['data-prefill']);
46 |
47 | const jsonValuePairs = Object.keys(expectedJSON).map((key: keyof object) => {
48 | return [
49 | key,
50 | JSON.stringify(parsedPrefillProp[key]),
51 | JSON.stringify(expectedJSON[key]),
52 | ];
53 | });
54 |
55 | const dataValuePairs = Object.keys(expectedData).map(
56 | (attribute: keyof object) => {
57 | return [attribute, embedProps[attribute], expectedData[attribute]];
58 | }
59 | );
60 |
61 | describe('Generates expected prefill JSON', () => {
62 | test.each(jsonValuePairs)('%s', (_key, actualVal, expectedVal) => {
63 | expect(actualVal).toBe(expectedVal);
64 | });
65 | });
66 |
67 | describe('Generates expected data-* attributes', () => {
68 | test.each(dataValuePairs)('%s', (_key, actualVal, expectedVal) => {
69 | expect(actualVal).toBe(expectedVal);
70 | });
71 | });
72 |
73 | describe('Renders default props', () => {
74 | const noPropsEmbed = shallow( );
75 | expect(noPropsEmbed.prop('className')).toBe('codepen');
76 | expect(noPropsEmbed.prop('data-default-tab')).toBe('result');
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/src/__tests__/prefill-lang-test.tsx:
--------------------------------------------------------------------------------
1 | import {shallow} from 'enzyme';
2 | import * as React from 'react';
3 |
4 | import PrefillLang from '../components/prefill-lang';
5 |
6 | describe('PrefillLang component', () => {
7 | it('Renders a single element', () => {
8 | const wrapper = shallow(
9 |
10 | Inner pre
11 |
12 | );
13 | expect(wrapper.find('pre')).toHaveLength(1);
14 | });
15 |
16 | it('Escapes html in children', () => {
17 | const wrapper = shallow(
18 |
19 | hello world
20 |
21 | );
22 | expect(wrapper.html()).toMatch('<p>');
23 | });
24 |
25 | it('Supports unknown props', () => {
26 | const wrapper = shallow(
27 |
28 | {`body: {color: red;}`}
29 |
30 | );
31 | expect(wrapper.find('[data-options-autoprefixer="true"]')).toHaveLength(1);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/__tests__/strip-indent-test.ts:
--------------------------------------------------------------------------------
1 | import stripIndent from '../lib/strip-indent';
2 |
3 | // Mostly declaring these up here 'cause working with
4 | // whitespace in template tags suuuuucks
5 |
6 | const inputSpaces = stripIndent`
7 |
8 | Hello
9 |
10 | `;
11 |
12 | const expectedSpaces = `
13 | Hello
14 | `;
15 |
16 | const inputTabs = stripIndent`
17 | \t
18 | \t\tHello
19 | \t
20 | `;
21 |
22 | const expectedTabs = `
23 | \tHello
24 | `;
25 |
26 | const noIndentInput = stripIndent`
27 |
28 | Hello
29 |
30 | `;
31 |
32 | describe('Strip whitespace', () => {
33 | it('Works with spaces', () => {
34 | expect(inputSpaces).toBe(expectedSpaces);
35 | });
36 |
37 | it('Works with tabs', () => {
38 | expect(inputTabs).toBe(expectedTabs);
39 | });
40 |
41 | it('Does nothing if indentation is not detected', () => {
42 | expect(noIndentInput).toBe(expectedSpaces);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/src/__tests__/use-codepen-embed-test.tsx:
--------------------------------------------------------------------------------
1 | import {mount} from 'enzyme';
2 | import {JSDOM} from 'jsdom';
3 | import * as React from 'react';
4 |
5 | import {EMBED_SCRIPT_ID} from '../config';
6 | import useCodePenEmbed from '../lib/use-codepen-embed';
7 |
8 | const FAKE_SRC = 'http://foo.bar';
9 |
10 | const ComponentWithHook = () => {
11 | useCodePenEmbed();
12 | return hello;
13 | };
14 |
15 | const ComponentWithHookOptions = () => {
16 | useCodePenEmbed({async: false, srcOverride: FAKE_SRC});
17 | return hello;
18 | };
19 |
20 | beforeEach(() => {
21 | const jsdom = new JSDOM();
22 | const {window} = jsdom;
23 | (global as any).window = window;
24 | (global as any).document = window.document;
25 | });
26 |
27 | describe('useCodePenEmbed hook', () => {
28 | it('Adds a script tag to the document body', () => {
29 | mount( );
30 | const scripts = document.body.querySelectorAll(`#${EMBED_SCRIPT_ID}`);
31 | expect(scripts.length).toBe(1);
32 | });
33 |
34 | it('Only appends a single script tag', () => {
35 | mount( );
36 | mount( );
37 | const scripts = document.querySelectorAll(`#${EMBED_SCRIPT_ID}`);
38 | expect(scripts.length).toBe(1);
39 | });
40 |
41 | it('Uses async by default', () => {
42 | mount( );
43 | const script = document.querySelector('script');
44 | expect(script.getAttribute('async')).toBe('true');
45 | });
46 |
47 | it('Respects srcOverride option', () => {
48 | mount( );
49 | const script = document.querySelector('script');
50 | expect(script.getAttribute('src')).toBe(FAKE_SRC);
51 | });
52 |
53 | it('Respects async option', () => {
54 | mount( );
55 | const script = document.querySelector('script');
56 | expect(script.hasAttribute('async')).toBe(false);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/src/components/prefill-embed.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import Lang from '../types/lang';
4 | import renderString from '../util/render-string';
5 |
6 | interface PrefillEmbedProps extends React.HTMLAttributes {
7 | penTitle?: string;
8 | embedHeight?: string;
9 | themeId?: string;
10 | editable?: boolean;
11 | description?: string;
12 | tags?: [string?, string?, string?, string?, string?];
13 | htmlClasses?: string[];
14 | stylesheets?: string[];
15 | scripts?: string[];
16 | head?: React.ReactNode;
17 | defaultTabs?: [Lang?, 'result'?] | ['result'?, Lang?];
18 | prefillData?: object;
19 | }
20 |
21 | const PrefillEmbed: React.FunctionComponent = ({
22 | penTitle,
23 | embedHeight,
24 | themeId,
25 | editable,
26 | description,
27 | tags,
28 | htmlClasses,
29 | stylesheets,
30 | scripts,
31 | head,
32 | defaultTabs = ['result'],
33 | prefillData,
34 | children,
35 | className = 'codepen',
36 | ...embedProps
37 | }) => {
38 | const prefillJSON = JSON.stringify({
39 | description,
40 | head: renderString(head),
41 | html_classes: htmlClasses,
42 | scripts,
43 | stylesheets,
44 | tags,
45 | title: penTitle,
46 | ...prefillData,
47 | });
48 |
49 | return (
50 |
59 | {children}
60 |
61 | );
62 | };
63 |
64 | export default PrefillEmbed;
65 |
--------------------------------------------------------------------------------
/src/components/prefill-lang.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import Lang from '../types/lang';
4 | import renderString from '../util/render-string';
5 |
6 | export interface PrefillLangProps extends React.HTMLAttributes {
7 | lang: Lang;
8 | }
9 |
10 | const PrefillLang: React.FunctionComponent = ({
11 | lang,
12 | children,
13 | ...rest
14 | }) => {
15 | return (
16 |
17 | {renderString(children)}
18 |
19 | );
20 | };
21 |
22 | export default PrefillLang;
23 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | export const EMBED_SCRIPT_SRC = 'https://static.codepen.io/assets/embed/ei.js';
2 | export const EMBED_SCRIPT_ID = '__react-codepen-prefill-embed';
3 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import PrefillEmbed from './components/prefill-embed';
2 | import PrefillLang from './components/prefill-lang';
3 | import stripIndent from './lib/strip-indent';
4 | import useCodePenEmbed from './lib/use-codepen-embed';
5 |
6 | export {PrefillEmbed, PrefillLang, stripIndent, useCodePenEmbed};
7 |
--------------------------------------------------------------------------------
/src/lib/strip-indent.ts:
--------------------------------------------------------------------------------
1 | const stripIndent = (strings: TemplateStringsArray): string => {
2 | const string = strings.join('');
3 | const lines = string
4 | .replace(/^\n/, '')
5 | .replace(/\n$/, '')
6 | .split('\n');
7 |
8 | const firstLineWhitespace = lines[0].match(/^\s+/);
9 | if (!firstLineWhitespace) return lines.join('\n');
10 |
11 | const stringToReplace = firstLineWhitespace[0];
12 | const replaceRE = new RegExp(`^${stringToReplace}`);
13 | return lines.map(line => line.replace(replaceRE, '')).join('\n');
14 | };
15 |
16 | export default stripIndent;
17 |
--------------------------------------------------------------------------------
/src/lib/use-codepen-embed.ts:
--------------------------------------------------------------------------------
1 | import {useEffect} from 'react';
2 | import {EMBED_SCRIPT_ID, EMBED_SCRIPT_SRC} from '../config';
3 |
4 | interface HookOptions {
5 | async: boolean;
6 | srcOverride?: string;
7 | }
8 |
9 | const defaultOptions: HookOptions = {
10 | async: true,
11 | };
12 |
13 | const useCodePenEmbed = (userOptions?: HookOptions): void => {
14 | const options = {...defaultOptions, ...userOptions};
15 | useEffect(() => {
16 | /**
17 | * Checking the window here in case someone loads the script
18 | * themselves and tries to use the hook regardless.
19 | *
20 | * Won't save us if someone adds the script manually and
21 | * the hook is called before it loads but meh.
22 | */
23 | const existsInWindow = window.hasOwnProperty('__CPEmbed');
24 | const existsInDOM = Boolean(document.getElementById(EMBED_SCRIPT_ID));
25 | const exists = existsInWindow || existsInDOM;
26 |
27 | if (exists) return;
28 |
29 | const s = document.createElement('script');
30 | const {async, srcOverride} = options;
31 |
32 | if (async) s.setAttribute('async', 'true');
33 | s.setAttribute('src', srcOverride || EMBED_SCRIPT_SRC);
34 | s.setAttribute('id', EMBED_SCRIPT_ID);
35 |
36 | document.body.appendChild(s);
37 | });
38 | };
39 |
40 | export default useCodePenEmbed;
41 |
--------------------------------------------------------------------------------
/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import {configure} from 'enzyme';
2 | import * as Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({adapter: new Adapter()});
5 |
--------------------------------------------------------------------------------
/src/types/lang.ts:
--------------------------------------------------------------------------------
1 | type LangHTML = 'html' | 'slim' | 'haml' | 'markdown' | 'pug';
2 | type LangCSS = 'css' | 'scss' | 'sass' | 'less' | 'stylus';
3 | type LangJS = 'js' | 'babel' | 'typescript' | 'coffeescript' | 'livescript';
4 |
5 | type Lang = LangHTML | LangCSS | LangJS;
6 |
7 | export default Lang;
8 |
--------------------------------------------------------------------------------
/src/util/render-string.ts:
--------------------------------------------------------------------------------
1 | import {ReactElement} from 'react';
2 | import {renderToStaticMarkup} from 'react-dom/server';
3 |
4 | const renderString = (node: React.ReactNode): string => {
5 | return typeof node === 'string'
6 | ? node
7 | : renderToStaticMarkup(node as ReactElement);
8 | };
9 |
10 | export default renderString;
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/cjs",
4 | "noImplicitAny": true,
5 | "moduleResolution": "node",
6 | "target": "es6",
7 | "module": "es6",
8 | "jsx": "react",
9 | "declaration": true,
10 | "noUnusedLocals": true,
11 | "noUnusedParameters": true
12 | },
13 | "include": ["./src"],
14 | "exclude": ["./src/__tests__", "./src/test-setup.ts", "./src/config.ts"]
15 | }
16 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-config-prettier"],
3 | "rules": {
4 | "quotemark": [true, "single", "jsx-double"],
5 | "trailing-comma": [false],
6 | "curly": [true, "ignore-same-line"],
7 | "interface-name": [true, "never-prefix"],
8 | "object-literal-sort-keys": [true],
9 | "variable-name": [true, "allow-leading-underscore", "allow-pascal-case"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const shared = {
4 | mode: 'production',
5 | entry: {
6 | index: './src/index.ts',
7 | },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.tsx$/,
12 | enforce: 'pre',
13 | use: [
14 | {
15 | loader: 'tslint-loader',
16 | },
17 | ],
18 | },
19 | {
20 | test: /\.tsx?$/,
21 | use: 'ts-loader',
22 | },
23 | ],
24 | },
25 | resolve: {
26 | extensions: ['.ts', '.tsx', '.js'],
27 | },
28 | };
29 |
30 | module.exports = [
31 | {
32 | target: 'node',
33 | output: {
34 | path: path.resolve(__dirname, 'dist/cjs'),
35 | filename: '[name].js',
36 | libraryTarget: 'commonjs2',
37 | },
38 | externals: {
39 | react: 'react',
40 | 'react-dom/server': 'react-dom/server',
41 | },
42 | ...shared,
43 | },
44 | {
45 | output: {
46 | path: path.resolve(__dirname, 'dist/umd'),
47 | filename: '[name].js',
48 | libraryTarget: 'umd',
49 | library: 'ReactCodePenPrefillEmbed',
50 | },
51 | externals: {
52 | react: 'React',
53 | 'react-dom/server': 'ReactDOMServer',
54 | },
55 | ...shared,
56 | },
57 | ];
58 |
--------------------------------------------------------------------------------