[markdown] Switch to Remarkable (#153)

* Switch to Remarkable
* Clean up references to custom code blocks
* Remove valdiateDOMNesting warning
* Add syntax highlighting
* Add Reason support
* Breaking change: prismColor to codeColor, remove CompLibrary.Prism, expose hljs
* Completely remove Prism and associated CSS rules
* Support loading plugins and scripts
* Remove CSS rules, allowing Highlight.js theme to be used entirely
* Remove unnecessary webplayer script
This commit is contained in:
Héctor Ramos 2017-10-24 09:45:21 -07:00 committed by Joel Marcey
parent 58613545b6
commit b832176dc6
18 changed files with 231 additions and 2744 deletions

View file

@ -8,12 +8,10 @@
const Marked = require("./Marked.js");
const Container = require("./Container.js");
const GridBlock = require("./GridBlock.js");
const Prism = require("./Prism.js");
// collection of other components to provide to users
module.exports = {
Marked: Marked,
Container: Container,
GridBlock: GridBlock,
Prism: Prism
GridBlock: GridBlock
};

View file

@ -10,15 +10,24 @@ const React = require("react");
// html head for each page
class Head extends React.Component {
render() {
let links = this.props.config.headerLinks;
const links = this.props.config.headerLinks;
let hasBlog = false;
links.map(link => {
if (link.blog) hasBlog = true;
});
let sourceCodeButton = this.props.config.sourceCodeButton;
// defaults to github, but other values may be allowed in the future
let includeGithubButton =
const sourceCodeButton = this.props.config.sourceCodeButton;
// defaults to GitHub, but other values may be allowed in the future
const includeGitHubButton =
sourceCodeButton === "github" || sourceCodeButton == null;
const highlightDefaultVersion = '9.12.0';
const highlightConfig = this.props.config.highlight
|| { version: highlightDefaultVersion, theme: 'default' };
const highlightVersion = highlightConfig.version || highlightDefaultVersion;
const highlightTheme = highlightConfig.theme || 'default';
const hasCustomScripts = this.props.config.scripts;
return (
<head>
<meta charSet="utf-8" />
@ -64,7 +73,7 @@ class Head extends React.Component {
href={this.props.config.url + "/blog/atom.xml"}
title={this.props.config.title + " Blog ATOM Feed"}
/>
)}{" "}
)}
{hasBlog && (
<link
rel="alternate"
@ -73,14 +82,18 @@ class Head extends React.Component {
title={this.props.config.title + " Blog RSS Feed"}
/>
)}
{includeGithubButton && (
{includeGitHubButton && (
<script async defer src="https://buttons.github.io/buttons.js" />
)}
<script
type="text/javascript"
src={this.props.config.baseUrl + "js/webplayer.js"}
<link
rel="stylesheet"
href={`//cdnjs.cloudflare.com/ajax/libs/highlight.js/${highlightVersion}/styles/${highlightTheme}.min.css`}
/>
<script type="text/javascript" src="https://snack.expo.io/embed.js" />
{hasCustomScripts && this.props.config.scripts.map(function(source) {
return (
<script type="text/javascript" src={source} />
);
})}
</head>
);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

93
lib/core/Remarkable.js Normal file
View file

@ -0,0 +1,93 @@
'use strict';
const React = require('react');
const hljs = require('highlight.js')
const Markdown = require('Remarkable');
const toSlug = require("./toSlug.js");
const CWD = process.cwd();
function anchors(md) {
md.renderer.rules.heading_open = function(tokens, idx /*, options, env */) {
return '<h' + tokens[idx].hLevel + '>' + '<a class="anchor" name="' + toSlug(tokens[idx+1].content) + '"></a>';
};
md.renderer.rules.heading_close = function(tokens, idx /*, options, env */) {
return ' <a class="hash-link" href="#' + toSlug(tokens[idx-1].content) + '">#</a>' + '</h' + tokens[idx].hLevel + '>\n';
};
}
class Remarkable extends React.Component {
render() {
var Container = this.props.container;
return (
<Container>
{this.content()}
</Container>
);
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.options !== this.props.options) {
this.md = new Markdown(nextProps.options);
}
}
content() {
if (this.props.source) {
return <span dangerouslySetInnerHTML={{ __html: this.renderMarkdown(this.props.source) }} />;
}
else {
return React.Children.map(this.props.children, child => {
if (typeof child === 'string') {
return <span dangerouslySetInnerHTML={{ __html: this.renderMarkdown(child) }} />;
}
else {
return child;
}
});
}
}
renderMarkdown(source) {
if (!this.md) {
this.md = new Markdown({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(lang, str).value;
} catch (err) {}
}
try {
return hljs.highlightAuto(str).value;
} catch (err) {}
return ''; // use external default escaping
}
});
// Register anchors plugin
this.md.use(anchors);
// Allow client sites to register their own plugins
const siteConfig = require(CWD + "/siteConfig.js");
if (siteConfig.markdownPlugins) {
siteConfig.markdownPlugins.forEach(function(plugin) {
this.md.use(plugin);
}, this);
}
}
return this.md.render(source);
}
}
Remarkable.defaultProps = {
container: 'div',
options: {},
};
module.exports = Remarkable;

View file

@ -32,6 +32,10 @@ class Site extends React.Component {
(this.props.url || "index.html");
let latestVersion;
const highlightDefaultVersion = '9.12.0';
const highlightConfig = this.props.config.highlight
|| { version: highlightDefaultVersion, theme: 'default' };
const highlightVersion = highlightConfig.version || highlightDefaultVersion;
if (fs.existsSync(CWD + "/versions.json")) {
latestVersion = require(CWD + "/versions.json")[0];
}
@ -124,6 +128,12 @@ class Site extends React.Component {
}}
/>
))}
<script src={`//cdnjs.cloudflare.com/ajax/libs/highlight.js/${highlightVersion}/highlight.min.js`}></script>
<script
dangerouslySetInnerHTML={{
__html: `hljs.initHighlightingOnLoad();`
}}
/>
</body>
</html>
);

View file

@ -1,139 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @providesModule SnackPlayer
*/
'use strict';
var Prism = require('./Prism');
var React = require('react');
const PropTypes = require('prop-types');
const LatestSDKVersion = '21.0.0';
var ReactNativeToExpoSDKVersionMap = {
'0.48': '21.0.0',
'0.47': '20.0.0',
'0.46': '19.0.0',
'0.45': '18.0.0',
'0.44': '17.0.0',
'0.43': '16.0.0',
'0.42': '15.0.0',
'0.41': '14.0.0',
};
/**
* Use the SnackPlayer by including a ```SnackPlayer``` block in markdown.
*
* Optionally, include url parameters directly after the block's language.
* Valid options are name, description, and platform.
*
* E.g.
* ```SnackPlayer?platform=android&name=Hello%20world!
* import React from 'react';
* import { Text } from 'react-native';
*
* export default class App extends React.Component {
* render() {
* return <Text>Hello World!</Text>;
* }
* }
* ```
*/
class SnackPlayer extends React.Component {
constructor(props, context) {
super(props, context);
this.parseParams = this.parseParams.bind(this);
}
componentDidMount() {
window.ExpoSnack && window.ExpoSnack.initialize();
}
render() {
var code = encodeURIComponent(this.props.children);
var params = this.parseParams(this.props.params);
var platform = params.platform
? params.platform
: 'ios';
var name = params.name
? decodeURIComponent(params.name)
: 'Example';
var description = params.description
? decodeURIComponent(params.description)
: 'Example usage';
var optionalProps = {};
var { version } = this.context;
if (version === 'next') {
optionalProps[
'data-snack-sdk-version'
] = LatestSDKVersion;
} else {
optionalProps[
'data-snack-sdk-version'
] = ReactNativeToExpoSDKVersionMap[version] ||
LatestSDKVersion;
}
return (
<div className="snack-player">
<div
className="mobile-friendly-snack"
style={{ display: 'none' }}
>
<Prism>
{this.props.children}
</Prism>
</div>
<div
className="desktop-friendly-snack"
style={{ marginTop: 15, marginBottom: 15 }}
>
<div
data-snack-name={name}
data-snack-description={description}
data-snack-code={code}
data-snack-platform={platform}
data-snack-preview="true"
{...optionalProps}
style={{
overflow: 'hidden',
background: '#fafafa',
border: '1px solid rgba(0,0,0,.16)',
borderRadius: '4px',
height: '514px',
width: '880px',
}}
/>
</div>
</div>
);
}
parseParams(paramString) {
var params = {};
if (paramString) {
var pairs = paramString.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
params[pair[0]] = pair[1];
}
}
return params;
}
}
SnackPlayer.contextTypes = {
version: PropTypes.number.isRequired,
};
module.exports = SnackPlayer;

View file

@ -1,75 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule WebPlayer
*/
'use strict';
var Prism = require('./Prism');
var React = require('react');
var WEB_PLAYER_VERSION = '1.10.0';
/**
* Use the WebPlayer by including a ```ReactNativeWebPlayer``` block in markdown.
*
* Optionally, include url parameters directly after the block's language. For
* the complete list of url parameters, see: https://github.com/dabbott/react-native-web-player
*
* E.g.
* ```ReactNativeWebPlayer?platform=android
* import React from 'react';
* import { AppRegistry, Text } from 'react-native';
*
* const App = () => <Text>Hello World!</Text>;
*
* AppRegistry.registerComponent('MyApp', () => App);
* ```
*/
class WebPlayer extends React.Component {
constructor(props, context) {
super(props, context);
this.parseParams = this.parseParams.bind(this);
}
parseParams(paramString) {
var params = {};
if (paramString) {
var pairs = paramString.split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
params[pair[0]] = pair[1];
}
}
return params;
}
render() {
var hash = `#code=${encodeURIComponent(this.props.children)}`;
if (this.props.params) {
hash += `&${this.props.params}`;
}
return (
<div className={'web-player'}>
<Prism>{this.props.children}</Prism>
<iframe
style={{marginTop: 4}}
width="880"
height={this.parseParams(this.props.params).platform === 'android' ? '425' : '420'}
data-src={`//cdn.rawgit.com/dabbott/react-native-web-player/gh-v${WEB_PLAYER_VERSION}/index.html${hash}`}
frameBorder="0"
/>
</div>
);
}
}
module.exports = WebPlayer;