Skip to content

Commit

Permalink
feat(umi-plugin): add code sandbox link for previewer (#38) (#13)
Browse files Browse the repository at this point in the history
* feat: add open in codesandbox

* fix: csb 按钮调正
  • Loading branch information
ttys026 authored and PeachScript committed Dec 13, 2019
1 parent a61940c commit 312aef4
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 4 deletions.
4 changes: 3 additions & 1 deletion packages/umi-plugin-father-doc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"hast-util-to-html": "^6.0.2",
"js-yaml": "^3.13.1",
"lodash": "^4.17.15",
"lz-string": "^1.4.4",
"mdast-util-to-hast": "^7.0.0",
"react-clipboard.js": "^2.0.16",
"rehype-autolink-headings": "^2.0.5",
Expand All @@ -55,7 +56,8 @@
"upath": "^1.2.0"
},
"devDependencies": {
"@types/history": "^4.7.3"
"@types/history": "^4.7.3",
"@types/lz-string": "^1.3.33"
},
"license": "MIT"
}
23 changes: 23 additions & 0 deletions packages/umi-plugin-father-doc/src/themes/default/csbButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

interface ButtonProps {
type: 'tsx' | 'jsx',
base64: string;
children: React.ReactNode;
}

export default ({ type, base64, children }: ButtonProps ) => {
return (
<form
style={{ display: 'flex' }}
method="POST"
action={`https://codesandbox.io/api/v1/sandboxes/define?query=module=/demo.${
type
}`}
target="_blank"
>
<input type="hidden" value={base64} name="parameters" />
{children}
</form>
)
}
89 changes: 86 additions & 3 deletions packages/umi-plugin-father-doc/src/themes/default/previewer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component } from 'react';
import Clipboard from 'react-clipboard.js'
import finaliseCSB, { parseImport, issueLink } from '../../utils/codesandbox';
import './previewer.less';
import CsbButton from './csbButton';

export interface IPreviewerProps {
/**
Expand Down Expand Up @@ -39,13 +41,88 @@ export default class Previewer extends Component<IPreviewerProps> {
showSource: false,
sourceType: '',
copyTimer: null,
jsBase64: '',
tsBase64: '',
}

componentDidMount() {
const { source } = this.props;
const { source, desc, title } = this.props;
const { tsx = '', jsx = '', raw } = source;
// generate csb base64 code;
let tsData = {};
let jsData = {};
// tsx and jsx should have same dependencies, so only parse once
const dep = parseImport(raw);

if(tsx) {
tsData = {
files: {
'index.html': {
content: '<div style="margin: 16px;" id="root"></div>',
},
'demo.tsx': {
content: raw,
},
'index.tsx': {
content: `import React from 'react';
import ReactDOM from 'react-dom';
${dep.antd ? "import 'antd/dist/antd.css';" : ''}
import App from './demo';
${issueLink}`,
},
},
deps: {
...dep,
react: '^16.8.0',
'@babel/runtime': '^7.6.3',
},
devDependencies: {
typescript: '3.3.3',
},
desc,
template: 'create-react-app-typescript',
fileName: 'demo.tsx',
}
}
if(jsx) {
jsData = {
files: {
'index.html': {
content: '<div style="margin: 16px;" id="root"></div>',
},
'demo.jsx': {
content: raw,
},
'index.js': {
content: `import React from 'react';
import ReactDOM from 'react-dom';
${dep.antd ? "import 'antd/dist/antd.css';" : ''}
import App from './demo';
${issueLink}`,
},
},
deps: {
...dep,
react: '^16.8.0',
'@babel/runtime': '^7.6.3',
},
devDependencies: {
typescript: '3.3.3',
},
desc,
template: 'create-react-app',
fileName: 'demo.jsx',
};
}

const jsBase64 = finaliseCSB( jsData, { name: title || 'father-doc-demo' }).parameters;
const tsBase64 = finaliseCSB( tsData, { name: title || 'father-doc-demo' }).parameters;

// prioritize display tsx
this.setState({ sourceType: source.tsx ? 'tsx' : 'jsx' });
this.setState({ sourceType: tsx ? 'tsx' : 'jsx', jsBase64, tsBase64 });

}

handleCopied = () => {
Expand All @@ -59,7 +136,7 @@ export default class Previewer extends Component<IPreviewerProps> {

render() {
const { children, source, title, desc, inline } = this.props;
const { showSource, sourceType, copyTimer } = this.state;
const { showSource, sourceType, copyTimer, jsBase64, tsBase64 } = this.state;

// render directly for inline mode
if (inline) {
Expand All @@ -75,6 +152,12 @@ export default class Previewer extends Component<IPreviewerProps> {
{desc}
</div>
<div className="__father-doc-default-previewer-actions">
<CsbButton type={this.props.source.tsx ? 'tsx' : 'jsx'} base64={this.props.source.tsx ? tsBase64 : jsBase64} >
<button
role='codesandbox'
type="submit"
/>
</CsbButton>
<span />
<Clipboard
button-role={copyTimer ? 'copied' : 'copy'}
Expand Down
12 changes: 12 additions & 0 deletions packages/umi-plugin-father-doc/src/utils/codesandbox/getParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import LZString from 'lz-string';

function compress(input: any) {
return LZString.compressToBase64(input)
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_') // Convert '/' to '_'
.replace(/=+$/, ''); // Remove ending '='
}
function getParameters(files: any) {
return compress(JSON.stringify(files));
}
export default getParameters;
60 changes: 60 additions & 0 deletions packages/umi-plugin-father-doc/src/utils/codesandbox/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import getParameters from './getParams';
import { newpkgJSON } from './template';

const ensureReact = (deps: any) => {
if (!deps.react && !deps['react-dom']) {
deps.react = 'latest';
deps['react-dom'] = 'latest';
} else if (!deps.react) {
deps.react = deps['react-dom'];
} else if (!deps['react-dom']) {
deps['react-dom'] = deps.react;
}
};

export const issueLink = `/** This is an auto-generated demo by father-doc
* if you think it is not working as expected,
* please report the issue at
* https://github.com/umijs/father-doc/issues
**/
ReactDOM.render(
<App />,
document.getElementById('root')
);`

export { parseImport } from './parseImport';

export default ({ files, deps, devDependencies, desc }: any, config: any) => {
if (!config) config = {};
const { extraFiles, extraDependencies, name, template = 'create-react-app' } = config;
let { main } = config;
const dependencies = {
...deps,
...extraDependencies,
};

main = !main && template === 'create-react-app-typescript' ? 'index.tsx' : 'index.js';

ensureReact(dependencies);

const finalFiles = {
...files,
'package.json': {
content: newpkgJSON(dependencies, name, main, devDependencies || {}, desc),
},
'sandbox.config.json': {
content: JSON.stringify({
template,
}),
},
...extraFiles,
};
const parameters = getParameters({
files: finalFiles,
});
return {
files: finalFiles,
dependencies,
parameters,
} as any;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const parseImport = (file: string) => {
const deps: { [key: string]: string } = {};
const importRegex = /import.*['|"]/gm;
(file || '').replace(importRegex, matchedImport => {
const pkgNameRegex = /['|"](.*)['|"]/;
const pkgName = matchedImport.match(pkgNameRegex) || [];
if (pkgName) {
const name = (pkgName[1] || '').trim();
const version = (name.startsWith('@') ? name.split('@')[2] : name.split('@')[1]) || 'latest';
if (name.includes('/')) {
const nameList = name.split('/');
if (name.startsWith('@')) {
deps[`${nameList[0]}/${nameList[1]}`] = version;
} else {
deps[nameList[0]] = version;
}
} else {
deps[name] = version;
}
}
return matchedImport;
});
return deps;
};
22 changes: 22 additions & 0 deletions packages/umi-plugin-father-doc/src/utils/codesandbox/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const newpkgJSON = (
dependencies: any = {},
name = 'father-doc-example',
main = 'index.js',
devDependencies: any = {},
desc = 'An auto generated demo by father-doc',
) => `{
"name": "${name}",
"version": "0.0.0",
"description": "${desc}",
"main": "${main}",
"dependencies": {
${Object.keys(dependencies)
.map(k => `"${k}": "${dependencies[k]}"`)
.join(',\n ')}
},
"devDependencies": {
${Object.keys(devDependencies)
.map(k => `"${k}": "${devDependencies[k]}"`)
.join(',\n ')}
}
}`;

0 comments on commit 312aef4

Please sign in to comment.