feat(v2): render Markdown on server-side instead (#1077)

* feat(v2): render Markdown on server-side instead

* move to folder
This commit is contained in:
Yangshun Tay 2018-10-30 07:45:07 -07:00 committed by Endilie Yacop Sucipto
parent d479f42ff5
commit 6cec7ce982
6 changed files with 173 additions and 177 deletions

View file

@ -8,125 +8,38 @@
/* eslint-disable */ /* eslint-disable */
import React from 'react'; import React from 'react';
import Markdown from 'remarkable';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import hljs from 'highlight.js';
import chalk from 'chalk';
import escapeHtml from 'escape-html';
import anchors from './anchors';
class MarkdownBlock extends React.Component { function Markdown(props) {
content() { const {siteConfig} = props;
if (this.props.source) { const highlight = Object.assign(
return ( {},
<span {
dangerouslySetInnerHTML={{ version: '9.12.0',
__html: this.renderMarkdown(this.props.source), theme: 'atom-one-dark',
}} },
/> siteConfig.highlight,
); );
}
return React.Children.map(this.props.children, child => {
if (typeof child === 'string') {
return (
<span
dangerouslySetInnerHTML={{__html: this.renderMarkdown(child)}}
/>
);
}
return child;
});
}
renderMarkdown(source) { // Use user-provided themeUrl if it exists, else construct one from version and theme.
const alias = { const highlightThemeURL = highlight.themeUrl
js: 'jsx', ? highlight.themeUrl
}; : `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${
const {siteConfig} = this.props; highlight.version
const md = new Markdown({ }/styles/${highlight.theme}.min.css`;
langPrefix: 'hljs css language-',
highlight: function(str, rawLang) {
// Default language fallback
const defaultLang =
siteConfig.highlight && siteConfig.highlight.defaultLang;
// No syntax highlighting return (
if (rawLang === 'text' || (!rawLang && !defaultLang)) { <div>
return escapeHtml(str); <Helmet>
} <link rel="stylesheet" type="text/css" href={highlightThemeURL} />
</Helmet>
// User's own hljs function to register additional languages <div>{props.children}</div>
if (siteConfig.highlight && siteConfig.highlight.hljs) { </div>
siteConfig.highlight.hljs(hljs); );
}
// Syntax highlighting
const lang = rawLang.toLowerCase() || defaultLang;
try {
if (hljs.getLanguage(lang)) {
return hljs.highlight(lang, str).value;
}
} catch (e) {
console.error(
chalk.yellow(
`Highlight.js syntax highlighting for language "${lang}" is not supported.`,
),
);
}
return hljs.highlightAuto(str).value;
},
html: true,
linkify: true,
});
// Register anchors plugin
md.use(anchors);
// Allow client sites to register their own plugins
if (siteConfig.markdownPlugins) {
siteConfig.markdownPlugins.forEach(plugin => {
md.use(plugin);
});
}
const html = md.render(source);
// Ensure fenced code blocks use Highlight.js hljs class
// https://github.com/jonschlinkert/remarkable/issues/224
return html.replace(/<pre><code>/g, '<pre><code class="hljs">');
}
render() {
const {siteConfig} = this.props;
const highlight = Object.assign(
{},
{
version: '9.12.0',
theme: 'atom-one-dark',
},
siteConfig.highlight,
);
// Use user-provided themeUrl if it exists, else construct one from version and theme.
const highlightThemeURL = highlight.themeUrl
? highlight.themeUrl
: `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${
highlight.version
}/styles/${highlight.theme}.min.css`;
return (
<div>
<Helmet>
<link rel="stylesheet" type="text/css" href={highlightThemeURL} />
</Helmet>
{this.content()}
</div>
);
}
} }
MarkdownBlock.defaultProps = { Markdown.defaultProps = {
siteConfig: {}, siteConfig: {},
}; };
export default MarkdownBlock; export default Markdown;

View file

@ -7,10 +7,10 @@
input#search_input_react { input#search_input_react {
align-self: center; align-self: center;
background-color: #c6cbd1; background-color: #f2f3f5;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
color: #fff; color: #000;
font-size: 14px; font-size: 14px;
font-weight: 300; font-weight: 300;
line-height: 20px; line-height: 20px;
@ -44,28 +44,29 @@ input::placeholder {
/* Bottom border of each suggestion */ /* Bottom border of each suggestion */
.algolia-docsearch-suggestion { .algolia-docsearch-suggestion {
border-bottom-color: #3A3DD1; border-bottom-color: #3a3dd1;
} }
/* Main category headers */ /* Main category headers */
.algolia-docsearch-suggestion--category-header { .algolia-docsearch-suggestion--category-header {
background-color: #4B54DE; background-color: #4b54de;
} }
/* Highlighted search terms */ /* Highlighted search terms */
.algolia-docsearch-suggestion--highlight { .algolia-docsearch-suggestion--highlight {
color: #3A33D1; color: #3a33d1;
} }
/* Highligted search terms in the main category headers */ /* Highligted search terms in the main category headers */
.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight { .algolia-docsearch-suggestion--category-header
background-color: #4D47D5; .algolia-docsearch-suggestion--highlight {
background-color: #4d47d5;
} }
/* Currently selected suggestion */ /* Currently selected suggestion */
.aa-cursor .algolia-docsearch-suggestion--content { .aa-cursor .algolia-docsearch-suggestion--content {
color: #272296; color: #272296;
} }
.aa-cursor .algolia-docsearch-suggestion { .aa-cursor .algolia-docsearch-suggestion {
background: #EBEBFB; background: #ebebfb;
} }
/* For bigger screens, when displaying results in two columns */ /* For bigger screens, when displaying results in two columns */
@media (min-width: 768px) { @media (min-width: 768px) {
/* Bottom border of each suggestion */ /* Bottom border of each suggestion */
@ -75,8 +76,8 @@ input::placeholder {
/* Left column, with secondary category header */ /* Left column, with secondary category header */
.algolia-docsearch-suggestion--subcategory-column { .algolia-docsearch-suggestion--subcategory-column {
border-right-color: #7671df; border-right-color: #7671df;
background-color: #F2F2FF; background-color: #f2f2ff;
color: #4E4726; color: #4e4726;
} }
} }
@ -106,12 +107,12 @@ input::placeholder {
.searchbox__input { .searchbox__input {
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
-webkit-transition: box-shadow .4s ease, background .4s ease; -webkit-transition: box-shadow 0.4s ease, background 0.4s ease;
transition: box-shadow .4s ease, background .4s ease; transition: box-shadow 0.4s ease, background 0.4s ease;
border: 0; border: 0;
border-radius: 16px; border-radius: 16px;
box-shadow: inset 0 0 0 1px #CCCCCC; box-shadow: inset 0 0 0 1px #cccccc;
background: #FFFFFF !important; background: #ffffff !important;
padding: 0; padding: 0;
padding-right: 26px; padding-right: 26px;
padding-left: 32px; padding-left: 32px;
@ -121,11 +122,14 @@ input::placeholder {
white-space: normal; white-space: normal;
font-size: 12px; font-size: 12px;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
} }
.searchbox__input::-webkit-search-decoration, .searchbox__input::-webkit-search-cancel-button, .searchbox__input::-webkit-search-results-button, .searchbox__input::-webkit-search-results-decoration { .searchbox__input::-webkit-search-decoration,
.searchbox__input::-webkit-search-cancel-button,
.searchbox__input::-webkit-search-results-button,
.searchbox__input::-webkit-search-results-decoration {
display: none; display: none;
} }
@ -133,26 +137,27 @@ input::placeholder {
box-shadow: inset 0 0 0 1px #b3b3b3; box-shadow: inset 0 0 0 1px #b3b3b3;
} }
.searchbox__input:focus, .searchbox__input:active { .searchbox__input:focus,
.searchbox__input:active {
outline: 0; outline: 0;
box-shadow: inset 0 0 0 1px #AAAAAA; box-shadow: inset 0 0 0 1px #aaaaaa;
background: #FFFFFF; background: #ffffff;
} }
.searchbox__input::-webkit-input-placeholder { .searchbox__input::-webkit-input-placeholder {
color: #AAAAAA; color: #aaaaaa;
} }
.searchbox__input::-moz-placeholder { .searchbox__input::-moz-placeholder {
color: #AAAAAA; color: #aaaaaa;
} }
.searchbox__input:-ms-input-placeholder { .searchbox__input:-ms-input-placeholder {
color: #AAAAAA; color: #aaaaaa;
} }
.searchbox__input::placeholder { .searchbox__input::placeholder {
color: #AAAAAA; color: #aaaaaa;
} }
.searchbox__submit { .searchbox__submit {
@ -169,9 +174,9 @@ input::placeholder {
text-align: center; text-align: center;
font-size: inherit; font-size: inherit;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
right: inherit; right: inherit;
left: 0; left: 0;
} }
@ -184,7 +189,8 @@ input::placeholder {
content: ''; content: '';
} }
.searchbox__submit:hover, .searchbox__submit:active { .searchbox__submit:hover,
.searchbox__submit:active {
cursor: pointer; cursor: pointer;
} }
@ -196,7 +202,7 @@ input::placeholder {
width: 14px; width: 14px;
height: 14px; height: 14px;
vertical-align: middle; vertical-align: middle;
fill: #6D7E96; fill: #6d7e96;
} }
.searchbox__reset { .searchbox__reset {
@ -211,9 +217,9 @@ input::placeholder {
padding: 0; padding: 0;
font-size: inherit; font-size: inherit;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
fill: rgba(0, 0, 0, 0.5); fill: rgba(0, 0, 0, 0.5);
} }
@ -235,20 +241,20 @@ input::placeholder {
.searchbox__input:valid ~ .searchbox__reset { .searchbox__input:valid ~ .searchbox__reset {
display: block; display: block;
-webkit-animation-name: sbx-reset-in; -webkit-animation-name: sbx-reset-in;
animation-name: sbx-reset-in; animation-name: sbx-reset-in;
-webkit-animation-duration: .15s; -webkit-animation-duration: 0.15s;
animation-duration: .15s; animation-duration: 0.15s;
} }
@-webkit-keyframes sbx-reset-in { @-webkit-keyframes sbx-reset-in {
0% { 0% {
-webkit-transform: translate3d(-20%, 0, 0); -webkit-transform: translate3d(-20%, 0, 0);
transform: translate3d(-20%, 0, 0); transform: translate3d(-20%, 0, 0);
opacity: 0; opacity: 0;
} }
100% { 100% {
-webkit-transform: none; -webkit-transform: none;
transform: none; transform: none;
opacity: 1; opacity: 1;
} }
} }
@ -256,17 +262,16 @@ input::placeholder {
@keyframes sbx-reset-in { @keyframes sbx-reset-in {
0% { 0% {
-webkit-transform: translate3d(-20%, 0, 0); -webkit-transform: translate3d(-20%, 0, 0);
transform: translate3d(-20%, 0, 0); transform: translate3d(-20%, 0, 0);
opacity: 0; opacity: 0;
} }
100% { 100% {
-webkit-transform: none; -webkit-transform: none;
transform: none; transform: none;
opacity: 1; opacity: 1;
} }
} }
.algolia-autocomplete .ds-dropdown-menu:before { .algolia-autocomplete .ds-dropdown-menu:before {
display: block; display: block;
position: absolute; position: absolute;
@ -279,7 +284,7 @@ input::placeholder {
border-top: 1px solid #373940; border-top: 1px solid #373940;
border-right: 1px solid #373940; border-right: 1px solid #373940;
-webkit-transform: rotate(-45deg); -webkit-transform: rotate(-45deg);
transform: rotate(-45deg); transform: rotate(-45deg);
border-radius: 2px; border-radius: 2px;
} }
@ -341,7 +346,7 @@ input::placeholder {
cursor: pointer; cursor: pointer;
} }
.algolia-autocomplete .ds-dropdown-menu [class^="ds-dataset-"] { .algolia-autocomplete .ds-dropdown-menu [class^='ds-dataset-'] {
position: relative; position: relative;
border-radius: 4px; border-radius: 4px;
overflow: auto; overflow: auto;
@ -368,13 +373,21 @@ input::placeholder {
padding: 0.1em 0.05em; padding: 0.1em 0.05em;
} }
.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl0 .algolia-docsearch-suggestion--highlight, .algolia-autocomplete
.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl1 .algolia-docsearch-suggestion--highlight { .algolia-docsearch-suggestion--category-header
.algolia-docsearch-suggestion--category-header-lvl0
.algolia-docsearch-suggestion--highlight,
.algolia-autocomplete
.algolia-docsearch-suggestion--category-header
.algolia-docsearch-suggestion--category-header-lvl1
.algolia-docsearch-suggestion--highlight {
color: inherit; color: inherit;
background: inherit; background: inherit;
} }
.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { .algolia-autocomplete
.algolia-docsearch-suggestion--text
.algolia-docsearch-suggestion--highlight {
padding: 0 0 1px; padding: 0 0 1px;
background: inherit; background: inherit;
box-shadow: inset 0 -2px 0 0 rgba(69, 142, 225, 0.8); box-shadow: inset 0 -2px 0 0 rgba(69, 142, 225, 0.8);
@ -445,7 +458,9 @@ input::placeholder {
right: 0; right: 0;
} }
.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight { .algolia-autocomplete
.algolia-docsearch-suggestion--subcategory-column
.algolia-docsearch-suggestion--highlight {
background-color: inherit; background-color: inherit;
color: inherit; color: inherit;
} }
@ -456,7 +471,7 @@ input::placeholder {
.algolia-autocomplete .algolia-docsearch-suggestion--title { .algolia-autocomplete .algolia-docsearch-suggestion--title {
margin-bottom: 4px; margin-bottom: 4px;
color: #02060C; color: #02060c;
font-size: 0.9em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
} }
@ -465,7 +480,7 @@ input::placeholder {
display: block; display: block;
line-height: 1.2em; line-height: 1.2em;
font-size: 0.85em; font-size: 0.85em;
color: #63676D; color: #63676d;
padding-right: 2px; padding-right: 2px;
} }
@ -478,7 +493,9 @@ input::placeholder {
margin-top: -8px; margin-top: -8px;
} }
.algolia-autocomplete .algolia-docsearch-suggestion--no-results .algolia-docsearch-suggestion--text { .algolia-autocomplete
.algolia-docsearch-suggestion--no-results
.algolia-docsearch-suggestion--text {
color: #ffffff; color: #ffffff;
margin-top: 4px; margin-top: 4px;
} }
@ -492,24 +509,31 @@ input::placeholder {
font-size: 90%; font-size: 90%;
border: none; border: none;
color: #222222; color: #222222;
background-color: #EBEBEB; background-color: #ebebeb;
border-radius: 3px; border-radius: 3px;
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
} }
.algolia-autocomplete .algolia-docsearch-suggestion code .algolia-docsearch-suggestion--highlight { .algolia-autocomplete
.algolia-docsearch-suggestion
code
.algolia-docsearch-suggestion--highlight {
background: none; background: none;
} }
.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__main .algolia-docsearch-suggestion--category-header { .algolia-autocomplete
.algolia-docsearch-suggestion.algolia-docsearch-suggestion__main
.algolia-docsearch-suggestion--category-header {
display: block; display: block;
} }
.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary .algolia-docsearch-suggestion--subcategory-column { .algolia-autocomplete
.algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary
.algolia-docsearch-suggestion--subcategory-column {
display: block; display: block;
} }
.algolia-autocomplete .algolia-docsearch-footer { .algolia-autocomplete .algolia-docsearch-footer {
background-color: #fff; background-color: #fff;
width: 100%; width: 100%;
@ -532,4 +556,4 @@ input::placeholder {
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: 5px; margin-right: 5px;
} }

View file

@ -10,7 +10,7 @@ const CSSExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const path = require('path'); const path = require('path');
const mdLoader = require.resolve('./loader/markdown'); const mdLoader = require.resolve('./loaders/markdown');
const CSS_REGEX = /\.css$/; const CSS_REGEX = /\.css$/;
const CSS_MODULE_REGEX = /\.module\.css$/; const CSS_MODULE_REGEX = /\.module\.css$/;

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import toSlug from './toSlug'; const toSlug = require('./toSlug');
export default function anchors(md) { function anchors(md) {
const originalRender = md.renderer.rules.heading_close; const originalRender = md.renderer.rules.heading_close;
// eslint-disable-next-line // eslint-disable-next-line
@ -26,3 +26,5 @@ export default function anchors(md) {
return originalRender(tokens, idx, options, env); return originalRender(tokens, idx, options, env);
}; };
} }
module.exports = anchors;

View file

@ -5,9 +5,15 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
const fm = require('front-matter');
const {getOptions} = require('loader-utils'); const {getOptions} = require('loader-utils');
const path = require('path'); const path = require('path');
const fm = require('front-matter'); const Remarkable = require('remarkable');
const hljs = require('highlight.js');
const chalk = require('chalk');
const escapeHtml = require('escape-html');
const anchors = require('./anchors');
module.exports = function(fileString) { module.exports = function(fileString) {
const options = getOptions(this); const options = getOptions(this);
@ -55,7 +61,7 @@ module.exports = function(fileString) {
const inlineLinks = []; const inlineLinks = [];
const refLinks = []; const refLinks = [];
/* Replace inline-style links e.g: /* Replace inline-style links e.g:
This is [Document 1](doc1.md) -> we replace this doc1.md with correct link This is [Document 1](doc1.md) -> we replace this doc1.md with correct link
*/ */
const inlineRegex = /(?:\]\()(?:\.\/)?([^'")\]\s>]+\.md)/g; const inlineRegex = /(?:\]\()(?:\.\/)?([^'")\]\s>]+\.md)/g;
@ -65,7 +71,7 @@ module.exports = function(fileString) {
inlineMatch = inlineRegex.exec(content); inlineMatch = inlineRegex.exec(content);
} }
/* Replace reference-style links e.g: /* Replace reference-style links e.g:
This is [Document 1][doc1]. This is [Document 1][doc1].
[doc1]: doc1.md -> we replace this doc1.md with correct link [doc1]: doc1.md -> we replace this doc1.md with correct link
*/ */
@ -88,13 +94,64 @@ module.exports = function(fileString) {
content = lines.join('\n'); content = lines.join('\n');
} }
const md = new Remarkable({
langPrefix: 'hljs css language-',
highlight(str, rawLang) {
// Default language fallback
const defaultLang =
siteConfig.highlight && siteConfig.highlight.defaultLang;
// No syntax highlighting
if (rawLang === 'text' || (!rawLang && !defaultLang)) {
return escapeHtml(str);
}
// User's own hljs function to register additional languages
if (siteConfig.highlight && siteConfig.highlight.hljs) {
siteConfig.highlight.hljs(hljs);
}
// Syntax highlighting
const lang = rawLang.toLowerCase() || defaultLang;
try {
if (hljs.getLanguage(lang)) {
return hljs.highlight(lang, str).value;
}
} catch (e) {
console.error(
chalk.yellow(
`Highlight.js syntax highlighting for language "${lang}" is not supported.`,
),
);
}
return hljs.highlightAuto(str).value;
},
html: true,
linkify: true,
});
// Register anchors plugin
md.use(anchors);
// Allow client sites to register their own plugins
if (siteConfig.markdownPlugins) {
siteConfig.markdownPlugins.forEach(plugin => {
md.use(plugin);
});
}
// Ensure fenced code blocks use Highlight.js hljs class
// https://github.com/jonschlinkert/remarkable/issues/224
const html = md
.render(content)
.replace(/<pre><code>/g, '<pre><code class="hljs">');
/* Return a React component */ /* Return a React component */
return ` return `
import React from 'react'; import React from 'react';
import Markdown from '@theme/Markdown'; import Markdown from '@theme/Markdown';
export default () => ( export default () => (
<Markdown siteConfig={${JSON.stringify(siteConfig)}}> <Markdown siteConfig={${JSON.stringify(siteConfig)}}>
{${JSON.stringify(content)}} <div dangerouslySetInnerHTML={{__html: ${JSON.stringify(html)}}} />
</Markdown> </Markdown>
);`; );`;
}; };