mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-28 16:37:07 +02:00
chore: move to monorepo (#1297)
* chore: move to monorepo * lint all js file * simplify circleCI * fix failing tests * fix tests due to folder rename * fix test since v1 website is renamed
This commit is contained in:
parent
6b1d2e8c9c
commit
1f91d19a8c
619 changed files with 12713 additions and 26817 deletions
77
packages/docusaurus-1.x/lib/core/BlogPageLayout.js
Normal file
77
packages/docusaurus-1.x/lib/core/BlogPageLayout.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const BlogPost = require('./BlogPost.js');
|
||||
const BlogSidebar = require('./BlogSidebar.js');
|
||||
const Container = require('./Container.js');
|
||||
const MetadataBlog = require('./MetadataBlog.js');
|
||||
const Site = require('./Site.js');
|
||||
const utils = require('./utils.js');
|
||||
|
||||
// used to generate entire blog pages, i.e. collection of truncated blog posts
|
||||
class BlogPageLayout extends React.Component {
|
||||
getPageURL(page) {
|
||||
let url = `${this.props.config.baseUrl}blog/`;
|
||||
if (page > 0) {
|
||||
url += `page${page + 1}/`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
const perPage = this.props.metadata.perPage;
|
||||
const page = this.props.metadata.page;
|
||||
return (
|
||||
<Site
|
||||
title="Blog"
|
||||
language="en"
|
||||
config={this.props.config}
|
||||
className="blog"
|
||||
metadata={{blog: true, blogListing: true}}>
|
||||
<div className="docMainWrapper wrapper">
|
||||
<BlogSidebar
|
||||
language={this.props.language}
|
||||
config={this.props.config}
|
||||
/>
|
||||
<Container className="mainContainer postContainer blogContainer">
|
||||
<div className="posts">
|
||||
{MetadataBlog.slice(page * perPage, (page + 1) * perPage).map(
|
||||
post => (
|
||||
<BlogPost
|
||||
post={post}
|
||||
content={post.content}
|
||||
truncate
|
||||
key={
|
||||
utils.getPath(post.path, this.props.config.cleanUrl) +
|
||||
post.title
|
||||
}
|
||||
config={this.props.config}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<div className="docs-prevnext">
|
||||
{page > 0 && (
|
||||
<a className="docs-prev" href={this.getPageURL(page - 1)}>
|
||||
← Prev
|
||||
</a>
|
||||
)}
|
||||
{MetadataBlog.length > (page + 1) * perPage && (
|
||||
<a className="docs-next" href={this.getPageURL(page + 1)}>
|
||||
Next →
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</Site>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlogPageLayout;
|
132
packages/docusaurus-1.x/lib/core/BlogPost.js
Normal file
132
packages/docusaurus-1.x/lib/core/BlogPost.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const MarkdownBlock = require('./MarkdownBlock.js');
|
||||
const utils = require('./utils.js');
|
||||
|
||||
// inner blog component for the article itself, without sidebar/header/footer
|
||||
class BlogPost extends React.Component {
|
||||
renderContent() {
|
||||
if (this.props.truncate) {
|
||||
return (
|
||||
<article className="post-content">
|
||||
<MarkdownBlock>
|
||||
{utils.extractBlogPostBeforeTruncate(this.props.content)}
|
||||
</MarkdownBlock>
|
||||
{utils.blogPostHasTruncateMarker(this.props.content) && (
|
||||
<div className="read-more">
|
||||
<a
|
||||
className="button"
|
||||
href={`${this.props.config.baseUrl}blog/${utils.getPath(
|
||||
this.props.post.path,
|
||||
this.props.config.cleanUrl,
|
||||
)}`}>
|
||||
Read More
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
return <MarkdownBlock>{this.props.content}</MarkdownBlock>;
|
||||
}
|
||||
|
||||
renderAuthorPhoto() {
|
||||
const post = this.props.post;
|
||||
const className = `authorPhoto${
|
||||
post.author && post.authorTitle ? ' authorPhotoBig' : ''
|
||||
}`;
|
||||
if (post.authorFBID || post.authorImageURL) {
|
||||
const authorImageURL = post.authorFBID
|
||||
? `https://graph.facebook.com/${
|
||||
post.authorFBID
|
||||
}/picture/?height=200&width=200`
|
||||
: post.authorImageURL;
|
||||
return (
|
||||
<div className={className}>
|
||||
<a href={post.authorURL} target="_blank" rel="noreferrer noopener">
|
||||
<img src={authorImageURL} alt={post.author} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
const post = this.props.post;
|
||||
return (
|
||||
<h1 className="postHeaderTitle">
|
||||
<a
|
||||
href={`${this.props.config.baseUrl}blog/${utils.getPath(
|
||||
post.path,
|
||||
this.props.config.cleanUrl,
|
||||
)}`}>
|
||||
{post.title}
|
||||
</a>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
renderPostHeader() {
|
||||
const post = this.props.post;
|
||||
const match = post.path.match(/([0-9]+)\/([0-9]+)\/([0-9]+)/);
|
||||
// Because JavaScript sucks at date handling :(
|
||||
const year = match[1];
|
||||
const month = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
][parseInt(match[2], 10) - 1];
|
||||
const day = parseInt(match[3], 10);
|
||||
|
||||
return (
|
||||
<header className="postHeader">
|
||||
{this.renderTitle()}
|
||||
<p className="post-meta">
|
||||
{month} {day}, {year}
|
||||
</p>
|
||||
<div className="authorBlock">
|
||||
{post.author ? (
|
||||
<p className="post-authorName">
|
||||
<a
|
||||
href={post.authorURL}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
{post.author}
|
||||
</a>
|
||||
{post.authorTitle}
|
||||
</p>
|
||||
) : null}
|
||||
{this.renderAuthorPhoto()}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="post">
|
||||
{this.renderPostHeader()}
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlogPost;
|
140
packages/docusaurus-1.x/lib/core/BlogPostLayout.js
Normal file
140
packages/docusaurus-1.x/lib/core/BlogPostLayout.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const classNames = require('classnames');
|
||||
const React = require('react');
|
||||
|
||||
const BlogPost = require('./BlogPost.js');
|
||||
const BlogSidebar = require('./BlogSidebar.js');
|
||||
const Container = require('./Container.js');
|
||||
const Site = require('./Site.js');
|
||||
const OnPageNav = require('./nav/OnPageNav.js');
|
||||
const utils = require('./utils.js');
|
||||
|
||||
// used for entire blog posts, i.e., each written blog article with sidebar with site header/footer
|
||||
class BlogPostLayout extends React.Component {
|
||||
getDescription() {
|
||||
const descLines = this.props.children.trim().split('\n');
|
||||
for (let i = 0; i < descLines.length; i++) {
|
||||
// Don't want blank lines or descriptions that are raw image rendering strings.
|
||||
if (descLines[i] && !descLines[i].startsWith('![')) {
|
||||
return descLines[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSocialButtons() {
|
||||
const post = this.props.metadata;
|
||||
post.path = utils.getPath(post.path, this.props.config.cleanUrl);
|
||||
|
||||
const fbComment = this.props.config.facebookAppId &&
|
||||
this.props.config.facebookComments && (
|
||||
<div className="blogSocialSectionItem">
|
||||
{/* Facebook SDK require 'fb-comments' class */}
|
||||
<div
|
||||
className="fb-comments"
|
||||
data-href={`${this.props.config.url +
|
||||
this.props.config.baseUrl}blog/${post.path}`}
|
||||
data-width="100%"
|
||||
data-numposts="5"
|
||||
data-order-by="time"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const fbLike = this.props.config.facebookAppId && (
|
||||
<div className="blogSocialSectionItem">
|
||||
{/* Facebook SDK require 'fb-like' class */}
|
||||
<div
|
||||
className="fb-like"
|
||||
data-href={`${this.props.config.url +
|
||||
this.props.config.baseUrl}blog/${post.path}`}
|
||||
data-layout="standard"
|
||||
data-share="true"
|
||||
data-width="225"
|
||||
data-show-faces="false"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const twitterShare = this.props.config.twitter && (
|
||||
<div className="blogSocialSectionItem">
|
||||
<a
|
||||
href="https://twitter.com/share"
|
||||
className="twitter-share-button"
|
||||
data-text={post.title}
|
||||
data-url={`${this.props.config.url + this.props.config.baseUrl}blog/${
|
||||
post.path
|
||||
}`}
|
||||
data-related={this.props.config.twitter}
|
||||
data-via={post.authorTwitter}
|
||||
data-show-count="false">
|
||||
Tweet
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="blogSocialSection">
|
||||
{twitterShare}
|
||||
{fbLike}
|
||||
{fbComment}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasOnPageNav = this.props.config.onPageNav === 'separate';
|
||||
const post = this.props.metadata;
|
||||
post.path = utils.getPath(post.path, this.props.config.cleanUrl);
|
||||
const blogSidebarTitleConfig = this.props.config.blogSidebarTitle || {};
|
||||
return (
|
||||
<Site
|
||||
className={classNames('sideNavVisible', {
|
||||
separateOnPageNav: hasOnPageNav,
|
||||
})}
|
||||
url={`blog/${post.path}`}
|
||||
title={this.props.metadata.title}
|
||||
language="en"
|
||||
description={this.getDescription()}
|
||||
config={this.props.config}
|
||||
metadata={{blog: true}}>
|
||||
<div className="docMainWrapper wrapper">
|
||||
<BlogSidebar
|
||||
language="en"
|
||||
current={post}
|
||||
config={this.props.config}
|
||||
/>
|
||||
<Container className="mainContainer postContainer blogContainer">
|
||||
<div className="lonePost">
|
||||
<BlogPost
|
||||
post={post}
|
||||
content={this.props.children}
|
||||
language="en"
|
||||
config={this.props.config}
|
||||
/>
|
||||
{this.renderSocialButtons()}
|
||||
</div>
|
||||
<div className="blog-recent">
|
||||
<a className="button" href={`${this.props.config.baseUrl}blog`}>
|
||||
{blogSidebarTitleConfig.default || 'Recent Posts'}
|
||||
</a>
|
||||
</div>
|
||||
</Container>
|
||||
{hasOnPageNav && (
|
||||
<nav className="onPageNav">
|
||||
<OnPageNav rawContent={this.props.children} />
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</Site>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlogPostLayout;
|
58
packages/docusaurus-1.x/lib/core/BlogSidebar.js
Normal file
58
packages/docusaurus-1.x/lib/core/BlogSidebar.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const Container = require('./Container.js');
|
||||
const SideNav = require('./nav/SideNav.js');
|
||||
|
||||
const MetadataBlog = require('./MetadataBlog.js');
|
||||
|
||||
class BlogSidebar extends React.Component {
|
||||
render() {
|
||||
let blogSidebarCount = 5;
|
||||
const blogSidebarTitleConfig = this.props.config.blogSidebarTitle || {};
|
||||
let blogSidebarTitle = blogSidebarTitleConfig.default || 'Recent Posts';
|
||||
if (this.props.config.blogSidebarCount) {
|
||||
if (this.props.config.blogSidebarCount === 'ALL') {
|
||||
blogSidebarCount = MetadataBlog.length;
|
||||
blogSidebarTitle = blogSidebarTitleConfig.all || 'All Blog Posts';
|
||||
} else {
|
||||
blogSidebarCount = this.props.config.blogSidebarCount;
|
||||
}
|
||||
}
|
||||
|
||||
const contents = [
|
||||
{
|
||||
type: 'CATEGORY',
|
||||
title: blogSidebarTitle,
|
||||
children: MetadataBlog.slice(0, blogSidebarCount).map(item => ({
|
||||
type: 'LINK',
|
||||
item,
|
||||
})),
|
||||
},
|
||||
];
|
||||
const title = this.props.current && this.props.current.title;
|
||||
|
||||
const current = {
|
||||
id: title || '',
|
||||
category: blogSidebarTitle,
|
||||
};
|
||||
return (
|
||||
<Container className="docsNavContainer" id="docsNav" wrapper={false}>
|
||||
<SideNav
|
||||
language={this.props.language}
|
||||
root={`${this.props.config.baseUrl}blog/`}
|
||||
title="Blog"
|
||||
contents={contents}
|
||||
current={current}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlogSidebar;
|
66
packages/docusaurus-1.x/lib/core/CodeTabsMarkdownBlock.js
Normal file
66
packages/docusaurus-1.x/lib/core/CodeTabsMarkdownBlock.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
const React = require('react');
|
||||
const Remarkable = require('./Remarkable');
|
||||
|
||||
/**
|
||||
* The MarkdownBlock component is used to parse markdown and render to HTML.
|
||||
*/
|
||||
class MarkdownBlock extends React.Component {
|
||||
render() {
|
||||
const groupId = _.uniqueId();
|
||||
|
||||
const tabs = this.props.children.map(({title, content}) => ({
|
||||
id: _.uniqueId(),
|
||||
groupId,
|
||||
label: title,
|
||||
lang: title,
|
||||
panelContent: <Remarkable source={content} />,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
<div className="nav-tabs">
|
||||
{tabs.map((t, i) => {
|
||||
const tabId = `tab-group-${groupId}-tab-${t.id}`;
|
||||
const contentId = `tab-group-${groupId}-content-${t.id}`;
|
||||
return (
|
||||
<div
|
||||
id={tabId}
|
||||
key={tabId}
|
||||
className={`nav-link${i === 0 ? ' active' : ''}`}
|
||||
data-group={`group_${t.groupId}`}
|
||||
data-tab={contentId}>
|
||||
{t.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="tab-content">
|
||||
{tabs.map((t, i) => {
|
||||
const id = `tab-group-${groupId}-content-${t.id}`;
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
key={id}
|
||||
className={`tab-pane${i === 0 ? ' active' : ''}`}
|
||||
data-group={`group_${t.groupId}`}
|
||||
tabIndex="-1">
|
||||
{t.panelContent}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MarkdownBlock;
|
17
packages/docusaurus-1.x/lib/core/CompLibrary.js
Normal file
17
packages/docusaurus-1.x/lib/core/CompLibrary.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const MarkdownBlock = require('./MarkdownBlock.js');
|
||||
const Container = require('./Container.js');
|
||||
const GridBlock = require('./GridBlock.js');
|
||||
|
||||
// A collection of components to provide to users
|
||||
module.exports = {
|
||||
MarkdownBlock,
|
||||
Container,
|
||||
GridBlock,
|
||||
};
|
44
packages/docusaurus-1.x/lib/core/Container.js
Normal file
44
packages/docusaurus-1.x/lib/core/Container.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
|
||||
class Container extends React.Component {
|
||||
render() {
|
||||
const containerClasses = classNames('container', this.props.className, {
|
||||
darkBackground: this.props.background === 'dark',
|
||||
highlightBackground: this.props.background === 'highlight',
|
||||
lightBackground: this.props.background === 'light',
|
||||
paddingAll: this.props.padding.indexOf('all') >= 0,
|
||||
paddingBottom: this.props.padding.indexOf('bottom') >= 0,
|
||||
paddingLeft: this.props.padding.indexOf('left') >= 0,
|
||||
paddingRight: this.props.padding.indexOf('right') >= 0,
|
||||
paddingTop: this.props.padding.indexOf('top') >= 0,
|
||||
});
|
||||
let wrappedChildren;
|
||||
|
||||
if (this.props.wrapper) {
|
||||
wrappedChildren = <div className="wrapper">{this.props.children}</div>;
|
||||
} else {
|
||||
wrappedChildren = this.props.children;
|
||||
}
|
||||
return (
|
||||
<div className={containerClasses} id={this.props.id}>
|
||||
{wrappedChildren}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Container.defaultProps = {
|
||||
background: null,
|
||||
padding: [],
|
||||
wrapper: true,
|
||||
};
|
||||
|
||||
module.exports = Container;
|
143
packages/docusaurus-1.x/lib/core/Doc.js
Normal file
143
packages/docusaurus-1.x/lib/core/Doc.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const {renderToStaticMarkup} = require('react-dom/server');
|
||||
const MarkdownBlock = require('./MarkdownBlock.js');
|
||||
const CodeTabsMarkdownBlock = require('./CodeTabsMarkdownBlock.js');
|
||||
|
||||
const translate = require('../server/translate.js').translate;
|
||||
|
||||
const editThisDoc = translate(
|
||||
'Edit this Doc|recruitment message asking to edit the doc source',
|
||||
);
|
||||
const translateThisDoc = translate(
|
||||
'Translate this Doc|recruitment message asking to translate the docs',
|
||||
);
|
||||
|
||||
const splitTabsToTitleAndContent = content => {
|
||||
const titles = content.match(/<!--(.*?)-->/gms);
|
||||
const tabs = content.split(/<!--.*?-->/gms);
|
||||
if (!titles || !tabs || !titles.length || !tabs.length) {
|
||||
return [];
|
||||
}
|
||||
tabs.shift();
|
||||
return titles.map((title, idx) => ({
|
||||
title: title.substring(4, title.length - 3).trim(),
|
||||
content: tabs[idx],
|
||||
}));
|
||||
};
|
||||
|
||||
const cleanTheCodeTag = content => {
|
||||
const contents = content.split(/(<pre>)(.*?)(<\/pre>)/gms);
|
||||
let inCodeBlock = false;
|
||||
const cleanContents = contents.map(c => {
|
||||
if (c === '<pre>') {
|
||||
inCodeBlock = true;
|
||||
return c;
|
||||
}
|
||||
if (c === '</pre>') {
|
||||
inCodeBlock = false;
|
||||
return c;
|
||||
}
|
||||
if (inCodeBlock) {
|
||||
return c.replace(/\n/g, '<br />');
|
||||
}
|
||||
return c;
|
||||
});
|
||||
return cleanContents.join('');
|
||||
};
|
||||
|
||||
// inner doc component for article itself
|
||||
class Doc extends React.Component {
|
||||
renderContent() {
|
||||
const {content} = this.props;
|
||||
let inCodeTabs = false;
|
||||
const contents = content.split(
|
||||
/(<!--DOCUSAURUS_CODE_TABS-->\n)(.*?)(\n<!--END_DOCUSAURUS_CODE_TABS-->)/gms,
|
||||
);
|
||||
|
||||
const renderResult = contents.map(c => {
|
||||
if (c === '<!--DOCUSAURUS_CODE_TABS-->\n') {
|
||||
inCodeTabs = true;
|
||||
return '';
|
||||
}
|
||||
if (c === '\n<!--END_DOCUSAURUS_CODE_TABS-->') {
|
||||
inCodeTabs = false;
|
||||
return '';
|
||||
}
|
||||
if (inCodeTabs) {
|
||||
const codeTabsMarkdownBlock = renderToStaticMarkup(
|
||||
<CodeTabsMarkdownBlock>
|
||||
{splitTabsToTitleAndContent(c)}
|
||||
</CodeTabsMarkdownBlock>,
|
||||
);
|
||||
return cleanTheCodeTag(codeTabsMarkdownBlock);
|
||||
}
|
||||
return c;
|
||||
});
|
||||
|
||||
return renderResult.join('');
|
||||
}
|
||||
|
||||
render() {
|
||||
let docSource = this.props.source;
|
||||
|
||||
if (this.props.version && this.props.version !== 'next') {
|
||||
// If versioning is enabled and the current version is not next, we need to trim out "version-*" from the source if we want a valid edit link.
|
||||
docSource = docSource.match(new RegExp(/version-.*?\/(.*\.md)/, 'i'))[1];
|
||||
}
|
||||
|
||||
const editUrl =
|
||||
this.props.metadata.custom_edit_url ||
|
||||
(this.props.config.editUrl && this.props.config.editUrl + docSource);
|
||||
let editLink = editUrl && (
|
||||
<a
|
||||
className="edit-page-link button"
|
||||
href={editUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
{editThisDoc}
|
||||
</a>
|
||||
);
|
||||
|
||||
// If internationalization is enabled, show Recruiting link instead of Edit Link.
|
||||
if (
|
||||
this.props.language &&
|
||||
this.props.language !== 'en' &&
|
||||
this.props.config.translationRecruitingLink
|
||||
) {
|
||||
editLink = (
|
||||
<a
|
||||
className="edit-page-link button"
|
||||
href={`${this.props.config.translationRecruitingLink}/${
|
||||
this.props.language
|
||||
}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
{translateThisDoc}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="post">
|
||||
<header className="postHeader">
|
||||
{editLink}
|
||||
{!this.props.hideTitle && (
|
||||
<h1 className="postHeaderTitle">{this.props.title}</h1>
|
||||
)}
|
||||
</header>
|
||||
<article>
|
||||
<MarkdownBlock>{this.renderContent()}</MarkdownBlock>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Doc;
|
165
packages/docusaurus-1.x/lib/core/DocsLayout.js
Normal file
165
packages/docusaurus-1.x/lib/core/DocsLayout.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const classNames = require('classnames');
|
||||
const path = require('path');
|
||||
const React = require('react');
|
||||
const url = require('url');
|
||||
|
||||
const Container = require('./Container.js');
|
||||
const Doc = require('./Doc.js');
|
||||
const DocsSidebar = require('./DocsSidebar.js');
|
||||
const OnPageNav = require('./nav/OnPageNav.js');
|
||||
const renderMarkdown = require('./renderMarkdown');
|
||||
const Site = require('./Site.js');
|
||||
const translation = require('../server/translation.js');
|
||||
const docs = require('../server/docs.js');
|
||||
const {idx, getGitLastUpdatedTime, getGitLastUpdatedBy} = require('./utils.js');
|
||||
|
||||
// component used to generate whole webpage for docs, including sidebar/header/footer
|
||||
class DocsLayout extends React.Component {
|
||||
getRelativeURL = (from, to) => {
|
||||
const extension = this.props.config.cleanUrl ? '' : '.html';
|
||||
const relativeHref =
|
||||
path
|
||||
.relative(from, to)
|
||||
.replace('\\', '/')
|
||||
.replace(/^\.\.\//, '') + extension;
|
||||
return url.resolve(
|
||||
`${this.props.config.baseUrl}${this.props.metadata.permalink}`,
|
||||
relativeHref,
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const metadata = this.props.metadata;
|
||||
const content = this.props.children;
|
||||
const i18n = translation[metadata.language];
|
||||
const id = metadata.localized_id;
|
||||
const defaultTitle = metadata.title;
|
||||
let DocComponent = Doc;
|
||||
|
||||
if (this.props.Doc) {
|
||||
DocComponent = this.props.Doc;
|
||||
}
|
||||
const filepath = docs.getFilePath(metadata);
|
||||
|
||||
const updateTime = this.props.config.enableUpdateTime
|
||||
? getGitLastUpdatedTime(filepath)
|
||||
: null;
|
||||
const updateAuthor = this.props.config.enableUpdateBy
|
||||
? getGitLastUpdatedBy(filepath)
|
||||
: null;
|
||||
|
||||
const title =
|
||||
idx(i18n, ['localized-strings', 'docs', id, 'title']) || defaultTitle;
|
||||
const hasOnPageNav = this.props.config.onPageNav === 'separate';
|
||||
|
||||
const previousTitle =
|
||||
idx(i18n, [
|
||||
'localized-strings',
|
||||
'docs',
|
||||
metadata.previous_id,
|
||||
'sidebar_label',
|
||||
]) ||
|
||||
idx(i18n, ['localized-strings', 'docs', metadata.previous_id, 'title']) ||
|
||||
idx(i18n, ['localized-strings', 'previous']) ||
|
||||
metadata.previous_title ||
|
||||
'Previous';
|
||||
const nextTitle =
|
||||
idx(i18n, [
|
||||
'localized-strings',
|
||||
'docs',
|
||||
metadata.next_id,
|
||||
'sidebar_label',
|
||||
]) ||
|
||||
idx(i18n, ['localized-strings', 'docs', metadata.next_id, 'title']) ||
|
||||
idx(i18n, ['localized-strings', 'next']) ||
|
||||
metadata.next_title ||
|
||||
'Next';
|
||||
|
||||
return (
|
||||
<Site
|
||||
config={this.props.config}
|
||||
className={classNames('sideNavVisible', {
|
||||
separateOnPageNav: hasOnPageNav,
|
||||
})}
|
||||
title={title}
|
||||
description={renderMarkdown(content.trim().split('\n')[0])}
|
||||
language={metadata.language}
|
||||
version={metadata.version}
|
||||
metadata={metadata}>
|
||||
<div className="docMainWrapper wrapper">
|
||||
<DocsSidebar metadata={metadata} />
|
||||
<Container className="mainContainer">
|
||||
<DocComponent
|
||||
metadata={metadata}
|
||||
content={content}
|
||||
config={this.props.config}
|
||||
source={metadata.source}
|
||||
hideTitle={metadata.hide_title}
|
||||
title={title}
|
||||
version={metadata.version}
|
||||
language={metadata.language}
|
||||
/>
|
||||
{(updateTime || updateAuthor) && (
|
||||
<div className="docLastUpdate">
|
||||
<em>
|
||||
Last updated
|
||||
{updateTime && ` on ${updateTime}`}
|
||||
{updateAuthor && ` by ${updateAuthor}`}
|
||||
</em>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="docs-prevnext">
|
||||
{metadata.previous_id && (
|
||||
<a
|
||||
className="docs-prev button"
|
||||
href={this.getRelativeURL(
|
||||
metadata.localized_id,
|
||||
metadata.previous_id,
|
||||
)}>
|
||||
<span className="arrow-prev">← </span>
|
||||
<span
|
||||
className={
|
||||
previousTitle.match(/[a-z][A-Z]/) &&
|
||||
'function-name-prevnext'
|
||||
}>
|
||||
{previousTitle}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
{metadata.next_id && (
|
||||
<a
|
||||
className="docs-next button"
|
||||
href={this.getRelativeURL(
|
||||
metadata.localized_id,
|
||||
metadata.next_id,
|
||||
)}>
|
||||
<span
|
||||
className={
|
||||
nextTitle.match(/[a-z][A-Z]/) && 'function-name-prevnext'
|
||||
}>
|
||||
{nextTitle}
|
||||
</span>
|
||||
<span className="arrow-next"> →</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
{hasOnPageNav && (
|
||||
<nav className="onPageNav">
|
||||
<OnPageNav rawContent={content} />
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
</Site>
|
||||
);
|
||||
}
|
||||
}
|
||||
module.exports = DocsLayout;
|
53
packages/docusaurus-1.x/lib/core/DocsSidebar.js
Normal file
53
packages/docusaurus-1.x/lib/core/DocsSidebar.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const fs = require('fs');
|
||||
const Container = require('./Container.js');
|
||||
const SideNav = require('./nav/SideNav.js');
|
||||
const Metadata = require('../core/metadata.js');
|
||||
const readCategories = require('../server/readCategories.js');
|
||||
|
||||
let languages;
|
||||
|
||||
const CWD = process.cwd();
|
||||
if (fs.existsSync(`${CWD}/languages.js`)) {
|
||||
languages = require(`${CWD}/languages.js`);
|
||||
} else {
|
||||
languages = [
|
||||
{
|
||||
enabled: true,
|
||||
name: 'English',
|
||||
tag: 'en',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class DocsSidebar extends React.Component {
|
||||
render() {
|
||||
const {category, sidebar} = this.props.metadata;
|
||||
const docsCategories = readCategories(sidebar, Metadata, languages);
|
||||
|
||||
if (!category) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="docsNavContainer" id="docsNav" wrapper={false}>
|
||||
<SideNav
|
||||
language={this.props.metadata.language}
|
||||
root={this.props.root}
|
||||
title={this.props.title}
|
||||
contents={docsCategories[this.props.metadata.language]}
|
||||
current={this.props.metadata}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocsSidebar;
|
104
packages/docusaurus-1.x/lib/core/GridBlock.js
Normal file
104
packages/docusaurus-1.x/lib/core/GridBlock.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const MarkdownBlock = require('./MarkdownBlock.js');
|
||||
|
||||
class GridBlock extends React.Component {
|
||||
renderBlock(origBlock) {
|
||||
const blockDefaults = {
|
||||
imageAlign: 'left',
|
||||
};
|
||||
|
||||
const block = {
|
||||
...blockDefaults,
|
||||
...origBlock,
|
||||
};
|
||||
|
||||
const blockClasses = classNames('blockElement', this.props.className, {
|
||||
alignCenter: this.props.align === 'center',
|
||||
alignRight: this.props.align === 'right',
|
||||
fourByGridBlock: this.props.layout === 'fourColumn',
|
||||
imageAlignSide:
|
||||
block.image &&
|
||||
(block.imageAlign === 'left' || block.imageAlign === 'right'),
|
||||
imageAlignTop: block.image && block.imageAlign === 'top',
|
||||
imageAlignRight: block.image && block.imageAlign === 'right',
|
||||
imageAlignBottom: block.image && block.imageAlign === 'bottom',
|
||||
imageAlignLeft: block.image && block.imageAlign === 'left',
|
||||
threeByGridBlock: this.props.layout === 'threeColumn',
|
||||
twoByGridBlock: this.props.layout === 'twoColumn',
|
||||
});
|
||||
|
||||
const topLeftImage =
|
||||
(block.imageAlign === 'top' || block.imageAlign === 'left') &&
|
||||
this.renderBlockImage(block.image, block.imageLink, block.imageAlt);
|
||||
|
||||
const bottomRightImage =
|
||||
(block.imageAlign === 'bottom' || block.imageAlign === 'right') &&
|
||||
this.renderBlockImage(block.image, block.imageLink, block.imageAlt);
|
||||
|
||||
return (
|
||||
<div className={blockClasses} key={block.title}>
|
||||
{topLeftImage}
|
||||
<div className="blockContent">
|
||||
{this.renderBlockTitle(block.title)}
|
||||
<MarkdownBlock>{block.content}</MarkdownBlock>
|
||||
</div>
|
||||
{bottomRightImage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBlockImage(image, imageLink, imageAlt) {
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="blockImage">
|
||||
{imageLink ? (
|
||||
<a href={imageLink}>
|
||||
<img src={image} alt={imageAlt} />
|
||||
</a>
|
||||
) : (
|
||||
<img src={image} alt={imageAlt} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBlockTitle(title) {
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<h2>
|
||||
<MarkdownBlock>{title}</MarkdownBlock>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="gridBlock">
|
||||
{this.props.contents.map(this.renderBlock, this)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GridBlock.defaultProps = {
|
||||
align: 'left',
|
||||
contents: [],
|
||||
layout: 'twoColumn',
|
||||
};
|
||||
|
||||
module.exports = GridBlock;
|
192
packages/docusaurus-1.x/lib/core/Head.js
Normal file
192
packages/docusaurus-1.x/lib/core/Head.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
|
||||
// html head for each page
|
||||
class Head extends React.Component {
|
||||
render() {
|
||||
const links = this.props.config.headerLinks;
|
||||
const hasBlog = links.some(link => link.blog);
|
||||
|
||||
const highlight = {
|
||||
version: '9.12.0',
|
||||
theme: 'default',
|
||||
...this.props.config.highlight,
|
||||
};
|
||||
|
||||
// Use user-provided themeUrl if it exists, else construct one from version and theme.
|
||||
const highlightThemeURL = highlight.themeUrl
|
||||
? highlight.themeUrl
|
||||
: `//cdnjs.cloudflare.com/ajax/libs/highlight.js/${
|
||||
highlight.version
|
||||
}/styles/${highlight.theme}.min.css`;
|
||||
|
||||
// ensure the siteUrl variable ends with a single slash
|
||||
const siteUrl = `${(
|
||||
this.props.config.url + this.props.config.baseUrl
|
||||
).replace(/\/+$/, '')}/`;
|
||||
|
||||
return (
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>{this.props.title}</title>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content="Docusaurus" />
|
||||
<meta name="description" content={this.props.description} />
|
||||
{this.props.version && (
|
||||
<meta name="docsearch:version" content={this.props.version} />
|
||||
)}
|
||||
{this.props.language && (
|
||||
<meta name="docsearch:language" content={this.props.language} />
|
||||
)}
|
||||
<meta property="og:title" content={this.props.title} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={this.props.url} />
|
||||
<meta property="og:description" content={this.props.description} />
|
||||
{this.props.config.ogImage && (
|
||||
<meta
|
||||
property="og:image"
|
||||
content={siteUrl + this.props.config.ogImage}
|
||||
/>
|
||||
)}
|
||||
<meta name="twitter:card" content="summary" />
|
||||
{this.props.config.twitterImage && (
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content={siteUrl + this.props.config.twitterImage}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.noIndex && <meta name="robots" content="noindex" />}
|
||||
{this.props.redirect && (
|
||||
<meta httpEquiv="refresh" content={`0; URL=${this.props.redirect}`} />
|
||||
)}
|
||||
{this.props.config.manifest && (
|
||||
<link rel="manifest" href={siteUrl + this.props.config.manifest} />
|
||||
)}
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href={this.props.config.baseUrl + this.props.config.favicon}
|
||||
/>
|
||||
{this.props.config.algolia && (
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css"
|
||||
/>
|
||||
)}
|
||||
<link rel="stylesheet" href={highlightThemeURL} />
|
||||
{hasBlog && (
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/atom+xml"
|
||||
href={`${siteUrl}blog/atom.xml`}
|
||||
title={`${this.props.config.title} Blog ATOM Feed`}
|
||||
/>
|
||||
)}
|
||||
{hasBlog && (
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
href={`${siteUrl}blog/feed.xml`}
|
||||
title={`${this.props.config.title} Blog RSS Feed`}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.gaTrackingId && this.props.config.gaGtag && (
|
||||
<script
|
||||
async
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${
|
||||
this.props.config.gaTrackingId
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.gaTrackingId && this.props.config.gaGtag && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments); }
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${this.props.config.gaTrackingId}');
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.gaTrackingId && !this.props.config.gaGtag && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '${this.props.config.gaTrackingId}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* External resources */}
|
||||
{this.props.config.stylesheets &&
|
||||
this.props.config.stylesheets.map(source =>
|
||||
source.href ? (
|
||||
<link rel="stylesheet" key={source.href} {...source} />
|
||||
) : (
|
||||
<link rel="stylesheet" key={source} href={source} />
|
||||
),
|
||||
)}
|
||||
{this.props.config.scripts &&
|
||||
this.props.config.scripts.map(source =>
|
||||
source.src ? (
|
||||
<script type="text/javascript" key={source.src} {...source} />
|
||||
) : (
|
||||
<script type="text/javascript" src={source} key={source} />
|
||||
),
|
||||
)}
|
||||
|
||||
{this.props.config.scrollToTop && (
|
||||
<script src="https://unpkg.com/vanilla-back-to-top@7.1.14/dist/vanilla-back-to-top.min.js" />
|
||||
)}
|
||||
{this.props.config.scrollToTop && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
addBackToTop(
|
||||
${JSON.stringify(
|
||||
Object.assign(
|
||||
{},
|
||||
{zIndex: 100},
|
||||
this.props.config.scrollToTopOptions,
|
||||
),
|
||||
)}
|
||||
)
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.usePrism && (
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href={`${this.props.config.baseUrl}css/prism.css`}
|
||||
/>
|
||||
)}
|
||||
{/* Site defined code. Keep these at the end to avoid overriding. */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href={`${this.props.config.baseUrl}css/main.css`}
|
||||
/>
|
||||
<script src={`${this.props.config.baseUrl}js/codetabs.js`} />
|
||||
</head>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Head;
|
20
packages/docusaurus-1.x/lib/core/MarkdownBlock.js
Normal file
20
packages/docusaurus-1.x/lib/core/MarkdownBlock.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const Remarkable = require('./Remarkable');
|
||||
|
||||
/**
|
||||
* The MarkdownBlock component is used to parse markdown and render to HTML.
|
||||
*/
|
||||
class MarkdownBlock extends React.Component {
|
||||
render() {
|
||||
return <Remarkable source={this.props.children} />;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MarkdownBlock;
|
56
packages/docusaurus-1.x/lib/core/Redirect.js
Normal file
56
packages/docusaurus-1.x/lib/core/Redirect.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const Head = require('./Head.js');
|
||||
const translation = require('../server/translation.js');
|
||||
const {idx} = require('./utils.js');
|
||||
|
||||
// Component used to provide same head, header, footer, other scripts to all pages
|
||||
class Redirect extends React.Component {
|
||||
render() {
|
||||
const tagline =
|
||||
idx(translation, [this.props.language, 'localized-strings', 'tagline']) ||
|
||||
this.props.config.tagline;
|
||||
const title = this.props.title
|
||||
? `${this.props.title} · ${this.props.config.title}`
|
||||
: (!this.props.config.disableTitleTagline &&
|
||||
`${this.props.config.title} · ${tagline}`) ||
|
||||
this.props.config.title;
|
||||
const description = this.props.description || tagline;
|
||||
const url =
|
||||
this.props.config.url +
|
||||
this.props.config.baseUrl +
|
||||
(this.props.url || 'index.html');
|
||||
|
||||
const redirect = this.props.redirect || false;
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<Head
|
||||
config={this.props.config}
|
||||
description={description}
|
||||
title={title}
|
||||
url={url}
|
||||
redirect={redirect}
|
||||
/>
|
||||
<body className={this.props.className}>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
<!--
|
||||
window.location.href = "${this.props.redirect}";
|
||||
// -->
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
module.exports = Redirect;
|
44
packages/docusaurus-1.x/lib/core/Remarkable.js
Normal file
44
packages/docusaurus-1.x/lib/core/Remarkable.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const renderMarkdown = require('./renderMarkdown.js');
|
||||
|
||||
class Remarkable extends React.Component {
|
||||
content() {
|
||||
if (this.props.source) {
|
||||
return (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderMarkdown(this.props.source),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return React.Children.map(this.props.children, child => {
|
||||
if (typeof child === 'string') {
|
||||
return (
|
||||
<span dangerouslySetInnerHTML={{__html: renderMarkdown(child)}} />
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const Container = this.props.container;
|
||||
return <Container>{this.content()}</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
Remarkable.defaultProps = {
|
||||
container: 'div',
|
||||
};
|
||||
|
||||
module.exports = Remarkable;
|
196
packages/docusaurus-1.x/lib/core/Site.js
Normal file
196
packages/docusaurus-1.x/lib/core/Site.js
Normal file
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const fs = require('fs');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const HeaderNav = require('./nav/HeaderNav.js');
|
||||
const Head = require('./Head.js');
|
||||
|
||||
const Footer = require(`${process.cwd()}/core/Footer.js`);
|
||||
const translation = require('../server/translation.js');
|
||||
const env = require('../server/env.js');
|
||||
const liveReloadServer = require('../server/liveReloadServer.js');
|
||||
const {idx, getPath} = require('./utils.js');
|
||||
|
||||
const CWD = process.cwd();
|
||||
|
||||
// Component used to provide same head, header, footer, other scripts to all pages
|
||||
class Site extends React.Component {
|
||||
mobileNavHasOneRow(headerLinks) {
|
||||
const hasLanguageDropdown =
|
||||
env.translation.enabled && env.translation.enabledLanguages().length > 1;
|
||||
const hasOrdinaryHeaderLinks = headerLinks.some(
|
||||
link => !(link.languages || link.search),
|
||||
);
|
||||
return !(hasLanguageDropdown || hasOrdinaryHeaderLinks);
|
||||
}
|
||||
|
||||
render() {
|
||||
const tagline =
|
||||
idx(translation, [this.props.language, 'localized-strings', 'tagline']) ||
|
||||
this.props.config.tagline;
|
||||
const title = this.props.title
|
||||
? `${this.props.title} · ${this.props.config.title}`
|
||||
: (!this.props.config.disableTitleTagline &&
|
||||
`${this.props.config.title} · ${tagline}`) ||
|
||||
this.props.config.title;
|
||||
const description = this.props.description || tagline;
|
||||
const path = getPath(
|
||||
this.props.config.baseUrl + (this.props.url || 'index.html'),
|
||||
this.props.config.cleanUrl,
|
||||
);
|
||||
const url = this.props.config.url + path;
|
||||
let docsVersion = this.props.version;
|
||||
|
||||
const liveReloadScriptUrl = liveReloadServer.getReloadScriptUrl();
|
||||
|
||||
if (!docsVersion && fs.existsSync(`${CWD}/versions.json`)) {
|
||||
const latestVersion = require(`${CWD}/versions.json`)[0];
|
||||
docsVersion = latestVersion;
|
||||
}
|
||||
|
||||
const navPusherClasses = classNames('navPusher', {
|
||||
singleRowMobileNav: this.mobileNavHasOneRow(
|
||||
this.props.config.headerLinks,
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<html lang={this.props.language}>
|
||||
<Head
|
||||
config={this.props.config}
|
||||
description={description}
|
||||
title={title}
|
||||
url={url}
|
||||
language={this.props.language}
|
||||
version={this.props.version}
|
||||
/>
|
||||
<body className={this.props.className}>
|
||||
<HeaderNav
|
||||
config={this.props.config}
|
||||
baseUrl={this.props.config.baseUrl}
|
||||
title={this.props.config.title}
|
||||
language={this.props.language}
|
||||
version={this.props.version}
|
||||
current={this.props.metadata}
|
||||
/>
|
||||
<div className={navPusherClasses}>
|
||||
{this.props.children}
|
||||
<Footer config={this.props.config} language={this.props.language} />
|
||||
</div>
|
||||
{this.props.config.algolia && (
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"
|
||||
/>
|
||||
)}
|
||||
{this.props.config.facebookAppId && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.fbAsyncInit = function() {FB.init({appId:'${
|
||||
this.props.config.facebookAppId
|
||||
}',xfbml:true,version:'v2.7'});};(function(d, s, id){var js, fjs = d.getElementsByTagName(s)[0];if (d.getElementById(id)) {return;}js = d.createElement(s); js.id = id;js.src = '//connect.facebook.net/en_US/sdk.js';fjs.parentNode.insertBefore(js, fjs);}(document, 'script','facebook-jssdk'));
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.facebookPixelId && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '${this.props.config.facebookPixelId}');
|
||||
fbq('track', 'PageView');
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(this.props.config.twitter || this.props.config.twitterUsername) && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `window.twttr=(function(d,s, id){var js,fjs=d.getElementsByTagName(s)[0],t=window.twttr||{};if(d.getElementById(id))return t;js=d.createElement(s);js.id=id;js.src='https://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js, fjs);t._e = [];t.ready = function(f) {t._e.push(f);};return t;}(document, 'script', 'twitter-wjs'));`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.algolia && (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
document.addEventListener('keyup', function(e) {
|
||||
if (e.target !== document.body) {
|
||||
return;
|
||||
}
|
||||
// keyCode for '/' (slash)
|
||||
if (e.keyCode === 191) {
|
||||
const search = document.getElementById('search_input_react');
|
||||
search && search.focus();
|
||||
}
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{this.props.config.algolia &&
|
||||
(this.props.config.algolia.algoliaOptions ? (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var search = docsearch({
|
||||
${
|
||||
this.props.config.algolia.appId
|
||||
? `appId: '${this.props.config.algolia.appId}',`
|
||||
: ''
|
||||
}
|
||||
apiKey: '${this.props.config.algolia.apiKey}',
|
||||
indexName: '${this.props.config.algolia.indexName}',
|
||||
inputSelector: '#search_input_react',
|
||||
algoliaOptions: ${JSON.stringify(
|
||||
this.props.config.algolia.algoliaOptions,
|
||||
)
|
||||
.replace('VERSION', docsVersion)
|
||||
.replace('LANGUAGE', this.props.language)}
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var search = docsearch({
|
||||
${
|
||||
this.props.config.algolia.appId
|
||||
? `appId: '${this.props.config.algolia.appId}',`
|
||||
: ''
|
||||
}
|
||||
apiKey: '${this.props.config.algolia.apiKey}',
|
||||
indexName: '${this.props.config.algolia.indexName}',
|
||||
inputSelector: '#search_input_react'
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{process.env.NODE_ENV === 'development' && liveReloadScriptUrl && (
|
||||
<script src={liveReloadScriptUrl} />
|
||||
)}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
module.exports = Site;
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: Truncation Example
|
||||
---
|
||||
|
||||
All this will be part of the blog post summary.
|
||||
|
||||
Even this.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
But anything from here on down will not be.
|
||||
|
||||
Not this.
|
||||
|
||||
Or this.
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: Non-truncation Example
|
||||
---
|
||||
|
||||
All this will be part of the blog post summary.
|
||||
|
||||
Even this.
|
||||
|
||||
And anything from here on down will still be.
|
||||
|
||||
And this.
|
||||
|
||||
And this too.
|
|
@ -0,0 +1,20 @@
|
|||
## foo
|
||||
### foo
|
||||
### foo 1
|
||||
## foo 1
|
||||
## foo 2
|
||||
### foo
|
||||
#### 4th level headings
|
||||
All 4th level headings should not be shown by default
|
||||
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
4th level heading should be ignored by default, but is should be always taken
|
||||
into account, when generating slugs
|
||||
### `bar`
|
||||
#### `bar`
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
## bar
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: pokemon-commands
|
||||
title: Pokemon Commands
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
<AUTOGENERATED_TABLE_OF_CONTENTS>
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
### `pokemon-run`
|
||||
|
||||
Alias: `run`.
|
||||
|
||||
### `pokemon-fight`
|
||||
|
||||
Alias: `fight`
|
||||
|
||||
### `pokemon-bag`
|
||||
|
||||
Alias: `bag`
|
||||
|
||||
### `pokemon-rename`
|
||||
|
||||
Alias: `rename`
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Don't edit this
|
||||
---
|
||||
|
||||
Do not edit this file
|
|
@ -0,0 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Anchors rendering 1`] = `"<h1><a class=\\"anchor\\" aria-hidden=\\"true\\" id=\\"hello-world\\"></a><a href=\\"#hello-world\\" aria-hidden=\\"true\\" class=\\"hash-link\\"><svg class=\\"hash-link-icon\\" aria-hidden=\\"true\\" height=\\"16\\" version=\\"1.1\\" viewBox=\\"0 0 16 16\\" width=\\"16\\"><path fill-rule=\\"evenodd\\" d=\\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\\"></path></svg></a>"`;
|
||||
|
||||
exports[`Anchors rendering 2`] = `"<h2><a class=\\"anchor\\" aria-hidden=\\"true\\" id=\\"hello-small-world\\"></a><a href=\\"#hello-small-world\\" aria-hidden=\\"true\\" class=\\"hash-link\\"><svg class=\\"hash-link-icon\\" aria-hidden=\\"true\\" height=\\"16\\" version=\\"1.1\\" viewBox=\\"0 0 16 16\\" width=\\"16\\"><path fill-rule=\\"evenodd\\" d=\\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\\"></path></svg></a>"`;
|
|
@ -0,0 +1,241 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getTOC with custom heading levels 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-1",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-1",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
],
|
||||
"content": "foo",
|
||||
"hashLink": "foo",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-2",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-3",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "4th level headings",
|
||||
"hashLink": "4th-level-headings",
|
||||
"rawContent": "4th level headings",
|
||||
},
|
||||
],
|
||||
"content": "foo 2",
|
||||
"hashLink": "foo-2",
|
||||
"rawContent": "foo 2",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-1",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-2",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-3",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-4",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-6",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-7",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-5",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-8",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`getTOC with defaults 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-1",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-1",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
],
|
||||
"content": "foo",
|
||||
"hashLink": "foo",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo 1",
|
||||
"hashLink": "foo-1-2",
|
||||
"rawContent": "foo 1",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "foo",
|
||||
"hashLink": "foo-3",
|
||||
"rawContent": "foo",
|
||||
},
|
||||
],
|
||||
"content": "foo 2",
|
||||
"hashLink": "foo-2",
|
||||
"rawContent": "foo 2",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-1",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "<code>bar</code>",
|
||||
"hashLink": "bar-3",
|
||||
"rawContent": "\`bar\`",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-6",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-5",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"content": "bar",
|
||||
"hashLink": "bar-8",
|
||||
"rawContent": "bar",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS does not exist 1`] = `
|
||||
"## foo
|
||||
### foo
|
||||
### foo 1
|
||||
## foo 1
|
||||
## foo 2
|
||||
### foo
|
||||
#### 4th level headings
|
||||
All 4th level headings should not be shown by default
|
||||
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
4th level heading should be ignored by default, but is should be always taken
|
||||
into account, when generating slugs
|
||||
### \`bar\`
|
||||
#### \`bar\`
|
||||
## bar
|
||||
### bar
|
||||
#### bar
|
||||
## bar
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`insertTOC AUTOGENERATED_TABLE_OF_CONTENTS exists 1`] = `
|
||||
"
|
||||
## Commands
|
||||
|
||||
- [\`pokemon-run\`](#pokemon-run)
|
||||
- [\`pokemon-fight\`](#pokemon-fight)
|
||||
- [\`pokemon-bag\`](#pokemon-bag)
|
||||
- [\`pokemon-rename\`](#pokemon-rename)
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
### \`pokemon-run\`
|
||||
|
||||
Alias: \`run\`.
|
||||
|
||||
### \`pokemon-fight\`
|
||||
|
||||
Alias: \`fight\`
|
||||
|
||||
### \`pokemon-bag\`
|
||||
|
||||
Alias: \`bag\`
|
||||
|
||||
### \`pokemon-rename\`
|
||||
|
||||
Alias: \`rename\`"
|
||||
`;
|
|
@ -0,0 +1,30 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`utils extractBlogPostBeforeTruncate 1`] = `
|
||||
"---
|
||||
title: Truncation Example
|
||||
---
|
||||
|
||||
All this will be part of the blog post summary.
|
||||
|
||||
Even this.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`utils extractBlogPostBeforeTruncate 2`] = `
|
||||
"---
|
||||
title: Non-truncation Example
|
||||
---
|
||||
|
||||
All this will be part of the blog post summary.
|
||||
|
||||
Even this.
|
||||
|
||||
And anything from here on down will still be.
|
||||
|
||||
And this.
|
||||
|
||||
And this too.
|
||||
"
|
||||
`;
|
119
packages/docusaurus-1.x/lib/core/__tests__/anchors.test.js
Normal file
119
packages/docusaurus-1.x/lib/core/__tests__/anchors.test.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const rules = require('remarkable/lib/rules');
|
||||
const anchors = require('../anchors');
|
||||
|
||||
const md = {
|
||||
renderer: {
|
||||
rules: {
|
||||
heading_open: rules.heading_open,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
anchors(md);
|
||||
|
||||
const render = md.renderer.rules.heading_open;
|
||||
|
||||
test('Anchors rendering', () => {
|
||||
expect(
|
||||
render([{hLevel: 1}, {content: 'Hello world'}], 0, {}, {}),
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
render([{hLevel: 2}, {content: 'Hello small world'}], 0, {}, {}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Each anchor is unique across rendered document', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading 2'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain(
|
||||
'id="almost-unique-heading"',
|
||||
);
|
||||
expect(render(tokens, 2, options, env)).toContain(
|
||||
'id="almost-unique-heading-1"',
|
||||
);
|
||||
expect(render(tokens, 4, options, env)).toContain(
|
||||
'id="almost-unique-heading-1-1"',
|
||||
);
|
||||
expect(render(tokens, 6, options, env)).toContain(
|
||||
'id="almost-unique-heading-1-2"',
|
||||
);
|
||||
expect(render(tokens, 8, options, env)).toContain(
|
||||
'id="almost-unique-heading-2"',
|
||||
);
|
||||
expect(render(tokens, 10, options, env)).toContain(
|
||||
'id="almost-unique-heading-3"',
|
||||
);
|
||||
});
|
||||
|
||||
test('Each anchor is unique across rendered document. Case 2', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'foo'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo 1'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo'},
|
||||
{hLevel: 1},
|
||||
{content: 'foo 1'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain('id="foo"');
|
||||
expect(render(tokens, 2, options, env)).toContain('id="foo-1"');
|
||||
expect(render(tokens, 4, options, env)).toContain('id="foo-2"');
|
||||
expect(render(tokens, 6, options, env)).toContain('id="foo-1-1"');
|
||||
});
|
||||
|
||||
test('Anchor index resets on each render', () => {
|
||||
const tokens = [
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
{hLevel: 1},
|
||||
{content: 'Almost unique heading'},
|
||||
];
|
||||
const options = {};
|
||||
const env = {};
|
||||
const env2 = {};
|
||||
|
||||
expect(render(tokens, 0, options, env)).toContain(
|
||||
'id="almost-unique-heading"',
|
||||
);
|
||||
expect(render(tokens, 2, options, env)).toContain(
|
||||
'id="almost-unique-heading-1"',
|
||||
);
|
||||
|
||||
expect(render(tokens, 0, options, env2)).toContain(
|
||||
'id="almost-unique-heading"',
|
||||
);
|
||||
expect(render(tokens, 2, options, env2)).toContain(
|
||||
'id="almost-unique-heading-1"',
|
||||
);
|
||||
});
|
||||
|
||||
test('Anchor uses default renderer when empty', () => {
|
||||
expect(render([{hLevel: 1}, {content: null}], 0, {}, {})).toEqual('<h1>');
|
||||
expect(render([{hLevel: 2}, {content: ''}], 0, {}, {})).toEqual('<h2>');
|
||||
});
|
36
packages/docusaurus-1.x/lib/core/__tests__/toSlug.test.js
Normal file
36
packages/docusaurus-1.x/lib/core/__tests__/toSlug.test.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const toSlug = require('../toSlug');
|
||||
|
||||
[
|
||||
['Hello world! ', 'hello-world'],
|
||||
['React 16', 'react-16'],
|
||||
['Hello. // (world?)! ', 'hello-world'],
|
||||
['Привет мир! ', 'привет-мир'],
|
||||
['Über Café.', 'uber-cafe'],
|
||||
['Someting long ...', 'someting-long-'],
|
||||
].forEach(([input, output]) => {
|
||||
test(`toSlug('${input}') -> '${output}'`, () => {
|
||||
expect(toSlug(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
test('unique slugs if `context` argument passed', () => {
|
||||
[
|
||||
['foo', 'foo'],
|
||||
['foo', 'foo-1'],
|
||||
['foo 1', 'foo-1-1'],
|
||||
['foo 1', 'foo-1-2'],
|
||||
['foo 2', 'foo-2'],
|
||||
['foo', 'foo-3'],
|
||||
].reduce((context, [input, output]) => {
|
||||
expect(toSlug(input, context)).toBe(output);
|
||||
|
||||
return context;
|
||||
}, {});
|
||||
});
|
60
packages/docusaurus-1.x/lib/core/__tests__/toc.test.js
Normal file
60
packages/docusaurus-1.x/lib/core/__tests__/toc.test.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const readFileSync = require('fs').readFileSync;
|
||||
const {getTOC, insertTOC} = require('../toc');
|
||||
const {extractMetadata} = require('../../server/metadataUtils');
|
||||
|
||||
const getTOCmd = readFileSync(
|
||||
path.join(__dirname, '__fixtures__', 'getTOC.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const insertTOCmd = readFileSync(
|
||||
path.join(__dirname, '__fixtures__', 'insertTOC.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
describe('getTOC', () => {
|
||||
test('with defaults', () => {
|
||||
const headings = getTOC(getTOCmd);
|
||||
const headingsJson = JSON.stringify(headings);
|
||||
|
||||
expect(headings).toMatchSnapshot();
|
||||
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
|
||||
expect(headingsJson).not.toContain('4th level headings');
|
||||
});
|
||||
|
||||
test('with custom heading levels', () => {
|
||||
const headings = getTOC(getTOCmd, 'h2', ['h3', 'h4']);
|
||||
const headingsJson = JSON.stringify(headings);
|
||||
|
||||
expect(headings).toMatchSnapshot();
|
||||
expect(headingsJson).toContain('bar-8'); // maximum unique bar index is 8
|
||||
expect(headingsJson).toContain('4th level headings');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertTOC', () => {
|
||||
test('null or undefined content', () => {
|
||||
expect(insertTOC(null)).toBeNull();
|
||||
expect(insertTOC(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('AUTOGENERATED_TABLE_OF_CONTENTS does not exist', () => {
|
||||
const rawContent = extractMetadata(getTOCmd).rawContent;
|
||||
expect(insertTOC(rawContent)).toMatchSnapshot();
|
||||
expect(insertTOC(rawContent)).toEqual(rawContent);
|
||||
});
|
||||
|
||||
test('AUTOGENERATED_TABLE_OF_CONTENTS exists', () => {
|
||||
const rawContent = extractMetadata(insertTOCmd).rawContent;
|
||||
expect(insertTOC(rawContent)).toMatchSnapshot();
|
||||
expect(insertTOC(rawContent)).not.toEqual(rawContent);
|
||||
});
|
||||
});
|
176
packages/docusaurus-1.x/lib/core/__tests__/utils.test.js
Normal file
176
packages/docusaurus-1.x/lib/core/__tests__/utils.test.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const shell = require('shelljs');
|
||||
const utils = require('../utils');
|
||||
|
||||
const blogPostWithTruncateContents = fs.readFileSync(
|
||||
path.join(__dirname, '__fixtures__', 'blog-post-with-truncate.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const blogPostWithoutTruncateContents = fs.readFileSync(
|
||||
path.join(__dirname, '__fixtures__', 'blog-post-without-truncate.md'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
describe('utils', () => {
|
||||
test('blogPostHasTruncateMarker', () => {
|
||||
expect(utils.blogPostHasTruncateMarker(blogPostWithTruncateContents)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
utils.blogPostHasTruncateMarker(blogPostWithoutTruncateContents),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('extractBlogPostBeforeTruncate', () => {
|
||||
expect(
|
||||
utils.extractBlogPostBeforeTruncate(blogPostWithTruncateContents),
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
utils.extractBlogPostBeforeTruncate(blogPostWithoutTruncateContents),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('getPath', () => {
|
||||
// does not change/transform path
|
||||
expect(utils.getPath('/en/users.html', false)).toBe('/en/users.html');
|
||||
expect(utils.getPath('/docs/en/versioning.html', false)).toBe(
|
||||
'/docs/en/versioning.html',
|
||||
);
|
||||
expect(utils.getPath(undefined, false)).toBeUndefined();
|
||||
expect(utils.getPath(null, false)).toBeNull();
|
||||
|
||||
// transform to pretty/clean path
|
||||
const cleanPath = pathStr => utils.getPath(pathStr, true);
|
||||
expect(cleanPath('/en/users')).toBe('/en/users');
|
||||
expect(cleanPath('/docs/versioning.html')).toBe('/docs/versioning');
|
||||
expect(cleanPath('/en/users.html')).toBe('/en/users');
|
||||
expect(cleanPath('/docs/en/asd/index.html')).toBe('/docs/en/asd/');
|
||||
expect(cleanPath('/en/help/index.html')).toBe('/en/help/');
|
||||
expect(cleanPath('/index.html')).toBe('/');
|
||||
expect(cleanPath('/react/index.html')).toBe('/react/');
|
||||
expect(cleanPath('/en/help.a.b.c.d.e.html')).toBe('/en/help.a.b.c.d.e');
|
||||
expect(cleanPath('/en/help.js')).toBe('/en/help.js');
|
||||
expect(cleanPath('/test.md')).toBe('/test.md');
|
||||
expect(cleanPath('/blog/7.0.0')).toBe('/blog/7.0.0');
|
||||
expect(cleanPath('/test/5.html.2')).toBe('/test/5.html.2');
|
||||
expect(cleanPath('/docs/en/5.2')).toBe('/docs/en/5.2');
|
||||
});
|
||||
|
||||
test('removeExtension', () => {
|
||||
expect(utils.removeExtension('/endiliey.html')).toBe('/endiliey');
|
||||
expect(utils.removeExtension('/a.b/')).toBe('/a.b/');
|
||||
expect(utils.removeExtension('/a.b/c.png')).toBe('/a.b/c');
|
||||
expect(utils.removeExtension('/a.b/c.d.e')).toBe('/a.b/c.d');
|
||||
expect(utils.removeExtension('/docs/test')).toBe('/docs/test');
|
||||
expect(utils.removeExtension('pages.js')).toBe('pages');
|
||||
});
|
||||
|
||||
test('getGitLastUpdatedTime', () => {
|
||||
// existing test file in repository with git timestamp
|
||||
const existingFilePath = path.join(__dirname, '__fixtures__', 'test.md');
|
||||
const gitLastUpdatedTime = utils.getGitLastUpdatedTime(existingFilePath);
|
||||
expect(typeof gitLastUpdatedTime).toBe('string');
|
||||
expect(Date.parse(gitLastUpdatedTime)).not.toBeNaN();
|
||||
expect(gitLastUpdatedTime).not.toBeNull();
|
||||
|
||||
// non existing file
|
||||
const nonExistingFilePath = path.join(
|
||||
__dirname,
|
||||
'__fixtures__',
|
||||
'.nonExisting',
|
||||
);
|
||||
expect(utils.getGitLastUpdatedTime(null)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(undefined)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(nonExistingFilePath)).toBeNull();
|
||||
|
||||
// temporary created file that has no git timestamp
|
||||
const tempFilePath = path.join(__dirname, '__fixtures__', '.temp');
|
||||
fs.writeFileSync(tempFilePath, 'Lorem ipsum :)');
|
||||
expect(utils.getGitLastUpdatedTime(tempFilePath)).toBeNull();
|
||||
fs.unlinkSync(tempFilePath);
|
||||
|
||||
// test renaming and moving file
|
||||
|
||||
const tempFilePath2 = path.join(__dirname, '__fixtures__', '.temp2');
|
||||
const tempFilePath3 = path.join(
|
||||
__dirname,
|
||||
'__fixtures__',
|
||||
'test',
|
||||
'.temp3',
|
||||
);
|
||||
|
||||
// create new file
|
||||
shell.exec = jest.fn(() => ({
|
||||
stdout:
|
||||
'1539502055, Yangshun Tay\n' +
|
||||
'\n' +
|
||||
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||
}));
|
||||
const createTime = utils.getGitLastUpdatedTime(tempFilePath2);
|
||||
expect(typeof createTime).toBe('string');
|
||||
|
||||
// rename / move the file
|
||||
shell.exec = jest.fn(() => ({
|
||||
stdout:
|
||||
'1539502056, Joel Marcey\n' +
|
||||
'\n' +
|
||||
' rename v1/lib/core/__tests__/__fixtures__/{.temp2 => test/.temp3} (100%)\n' +
|
||||
'1539502055, Yangshun Tay\n' +
|
||||
'\n' +
|
||||
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||
}));
|
||||
const lastUpdateTime = utils.getGitLastUpdatedTime(tempFilePath3);
|
||||
// should only consider file content change
|
||||
expect(lastUpdateTime).toEqual(createTime);
|
||||
});
|
||||
|
||||
test('idx', () => {
|
||||
const a = {};
|
||||
const b = {hello: 'world'};
|
||||
const env = {
|
||||
translation: {
|
||||
enabled: true,
|
||||
enabledLanguages: [
|
||||
{
|
||||
enabled: true,
|
||||
name: 'English',
|
||||
tag: 'en',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
name: '日本語',
|
||||
tag: 'ja',
|
||||
},
|
||||
],
|
||||
},
|
||||
versioning: {
|
||||
enabled: false,
|
||||
versions: [],
|
||||
},
|
||||
};
|
||||
const variable = 'enabledLanguages';
|
||||
expect(utils.idx(a, [('b', 'c')])).toBeUndefined();
|
||||
expect(utils.idx(b, ['hello'])).toEqual('world');
|
||||
expect(utils.idx(b, 'hello')).toEqual('world');
|
||||
expect(utils.idx(env, 'typo')).toBeUndefined();
|
||||
expect(utils.idx(env, 'versioning')).toEqual({
|
||||
enabled: false,
|
||||
versions: [],
|
||||
});
|
||||
expect(utils.idx(env, ['translation', 'enabled'])).toEqual(true);
|
||||
expect(
|
||||
utils.idx(env, ['translation', variable]).map(lang => lang.tag),
|
||||
).toEqual(['en', 'ja']);
|
||||
expect(utils.idx(undefined)).toBeUndefined();
|
||||
expect(utils.idx(null)).toBeNull();
|
||||
});
|
||||
});
|
31
packages/docusaurus-1.x/lib/core/anchors.js
Normal file
31
packages/docusaurus-1.x/lib/core/anchors.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const toSlug = require('./toSlug.js');
|
||||
|
||||
/**
|
||||
* The anchors plugin adds GFM-style anchors to headings.
|
||||
*/
|
||||
function anchors(md) {
|
||||
const originalRender = md.renderer.rules.heading_open;
|
||||
|
||||
md.renderer.rules.heading_open = function(tokens, idx, options, env) {
|
||||
const textToken = tokens[idx + 1];
|
||||
|
||||
if (textToken.content) {
|
||||
const anchor = toSlug(textToken.content, env);
|
||||
|
||||
return `<h${
|
||||
tokens[idx].hLevel
|
||||
}><a class="anchor" aria-hidden="true" id="${anchor}"></a><a href="#${anchor}" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>`;
|
||||
}
|
||||
|
||||
return originalRender(tokens, idx, options, env);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = anchors;
|
339
packages/docusaurus-1.x/lib/core/nav/HeaderNav.js
Normal file
339
packages/docusaurus-1.x/lib/core/nav/HeaderNav.js
Normal file
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const CWD = process.cwd();
|
||||
|
||||
const React = require('react');
|
||||
const fs = require('fs');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const loadConfig = require('../../server/config');
|
||||
|
||||
const siteConfig = loadConfig(`${CWD}/siteConfig.js`);
|
||||
const translation = require('../../server/translation.js');
|
||||
const env = require('../../server/env.js');
|
||||
|
||||
const translate = require('../../server/translate.js').translate;
|
||||
const setLanguage = require('../../server/translate.js').setLanguage;
|
||||
|
||||
const readMetadata = require('../../server/readMetadata.js');
|
||||
|
||||
readMetadata.generateMetadataDocs();
|
||||
const Metadata = require('../metadata.js');
|
||||
const {idx, getPath} = require('../utils.js');
|
||||
|
||||
const extension = siteConfig.cleanUrl ? '' : '.html';
|
||||
|
||||
// language dropdown nav item for when translations are enabled
|
||||
class LanguageDropDown extends React.Component {
|
||||
render() {
|
||||
setLanguage(this.props.language || 'en');
|
||||
const helpTranslateString = translate(
|
||||
'Help Translate|recruit community translators for your project',
|
||||
);
|
||||
const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`;
|
||||
// add all enabled languages to dropdown
|
||||
const enabledLanguages = env.translation
|
||||
.enabledLanguages()
|
||||
.filter(lang => lang.tag !== this.props.language)
|
||||
.map(lang => {
|
||||
// build the href so that we try to stay in current url but change the language.
|
||||
let href = siteConfig.baseUrl + lang.tag;
|
||||
if (
|
||||
this.props.current &&
|
||||
this.props.current.permalink &&
|
||||
this.props.language
|
||||
) {
|
||||
href =
|
||||
siteConfig.baseUrl +
|
||||
this.props.current.permalink.replace(
|
||||
new RegExp(`^${docsPart}${this.props.language}/`),
|
||||
`${docsPart}${lang.tag}/`,
|
||||
);
|
||||
} else if (this.props.current.id && this.props.current.id !== 'index') {
|
||||
href = `${siteConfig.baseUrl + lang.tag}/${this.props.current.id}`;
|
||||
}
|
||||
return (
|
||||
<li key={lang.tag}>
|
||||
<a href={getPath(href, this.props.cleanUrl)}>{lang.name}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
// if no languages are enabled besides English, return null
|
||||
if (enabledLanguages.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the current language full name for display in the header nav
|
||||
const currentLanguage = env.translation
|
||||
.enabledLanguages()
|
||||
.filter(lang => lang.tag === this.props.language)
|
||||
.map(lang => lang.name);
|
||||
|
||||
// add Crowdin project recruiting link
|
||||
if (siteConfig.translationRecruitingLink) {
|
||||
enabledLanguages.push(
|
||||
<li key="recruiting">
|
||||
<a
|
||||
href={siteConfig.translationRecruitingLink}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
{helpTranslateString}
|
||||
</a>
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<li key="languages">
|
||||
<a id="languages-menu" href="#">
|
||||
<img
|
||||
className="languages-icon"
|
||||
src={`${this.props.baseUrl}img/language.svg`}
|
||||
alt="Languages icon"
|
||||
/>
|
||||
{currentLanguage}
|
||||
</a>
|
||||
<div id="languages-dropdown" className="hide">
|
||||
<ul id="languages-dropdown-items">{enabledLanguages}</ul>
|
||||
</div>
|
||||
</li>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
const languagesMenuItem = document.getElementById("languages-menu");
|
||||
const languagesDropDown = document.getElementById("languages-dropdown");
|
||||
languagesMenuItem.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (languagesDropDown.className == "hide") {
|
||||
languagesDropDown.className = "visible";
|
||||
} else {
|
||||
languagesDropDown.className = "hide";
|
||||
}
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// header navbar used by all pages generated with docusaurus
|
||||
class HeaderNav extends React.Component {
|
||||
// function to generate each header link, used with each object in siteConfig.headerLinks
|
||||
makeLinks(link) {
|
||||
let href;
|
||||
let docItemActive = false;
|
||||
let docGroupActive = false;
|
||||
if (link.search && this.props.config.algolia) {
|
||||
// return algolia search bar
|
||||
const placeholder = this.props.config.algolia.placeholder || 'Search';
|
||||
return (
|
||||
<li className="navSearchWrapper reactNavSearchWrapper" key="search">
|
||||
<input
|
||||
id="search_input_react"
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
title={placeholder}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
if (link.languages) {
|
||||
if (
|
||||
env.translation.enabled &&
|
||||
env.translation.enabledLanguages().length > 1
|
||||
) {
|
||||
return (
|
||||
<LanguageDropDown
|
||||
baseUrl={this.props.baseUrl}
|
||||
language={this.props.language}
|
||||
current={this.props.current}
|
||||
cleanUrl={this.props.config.cleanUrl}
|
||||
key="languagedropdown"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (link.doc) {
|
||||
// set link to document with current page's language/version
|
||||
const langPart = env.translation.enabled
|
||||
? `${this.props.language || 'en'}-`
|
||||
: '';
|
||||
const versionPart =
|
||||
env.versioning.enabled && this.props.version !== 'next'
|
||||
? `version-${this.props.version || env.versioning.defaultVersion}-`
|
||||
: '';
|
||||
const id = langPart + versionPart + link.doc;
|
||||
if (!Metadata[id]) {
|
||||
let errorStr = `Processing the following \`doc\` field in \`headerLinks\` within \`siteConfig.js\`: '${
|
||||
link.doc
|
||||
}'`;
|
||||
if (id === link.doc) {
|
||||
errorStr +=
|
||||
' It looks like there is no document with that id that exists in your docs directory. Please double check the spelling of your `doc` field and the `id` fields of your docs.';
|
||||
} else {
|
||||
errorStr += `${'. Check the spelling of your `doc` field. If that seems sane, and a document in your docs folder exists with that `id` value, \nthen this is likely a bug in Docusaurus.' +
|
||||
' Docusaurus thinks one or both of translations (currently set to: '}${
|
||||
env.translation.enabled
|
||||
}) or versioning (currently set to: ${
|
||||
env.versioning.enabled
|
||||
}) is enabled when maybe they should not be. \nThus my internal id for this doc is: '${id}'. Please file an issue for this possible bug on GitHub.`;
|
||||
}
|
||||
throw new Error(errorStr);
|
||||
}
|
||||
href =
|
||||
this.props.config.baseUrl +
|
||||
getPath(Metadata[id].permalink, this.props.config.cleanUrl);
|
||||
|
||||
const {id: currentID, sidebar} = this.props.current;
|
||||
docItemActive = currentID && currentID === id;
|
||||
docGroupActive = sidebar && sidebar === Metadata[id].sidebar;
|
||||
} else if (link.page) {
|
||||
// set link to page with current page's language if appropriate
|
||||
const language = this.props.language || '';
|
||||
if (fs.existsSync(`${CWD}/pages/en/${link.page}.js`)) {
|
||||
href =
|
||||
siteConfig.baseUrl +
|
||||
(env.translation.enabled ? `${language}/` : '') +
|
||||
link.page +
|
||||
extension;
|
||||
} else {
|
||||
href = siteConfig.baseUrl + link.page + extension;
|
||||
}
|
||||
} else if (link.href) {
|
||||
// set link to specified href
|
||||
href = link.href;
|
||||
} else if (link.blog) {
|
||||
// set link to blog url
|
||||
href = `${this.props.baseUrl}blog/`;
|
||||
}
|
||||
const itemClasses = classNames({
|
||||
siteNavGroupActive:
|
||||
(link.doc && docGroupActive) || (link.blog && this.props.current.blog),
|
||||
siteNavItemActive:
|
||||
docItemActive ||
|
||||
(link.blog && this.props.current.blogListing) ||
|
||||
(link.page && link.page === this.props.current.id),
|
||||
});
|
||||
const i18n = translation[this.props.language];
|
||||
return (
|
||||
<li key={`${link.label}page`} className={itemClasses}>
|
||||
<a href={href} target={link.external ? '_blank' : '_self'}>
|
||||
{idx(i18n, ['localized-strings', 'links', link.label]) || link.label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
renderResponsiveNav() {
|
||||
const headerLinks = this.props.config.headerLinks;
|
||||
// add language drop down to end if location not specified
|
||||
let languages = false;
|
||||
headerLinks.forEach(link => {
|
||||
if (link.languages) {
|
||||
languages = true;
|
||||
}
|
||||
});
|
||||
if (!languages) {
|
||||
headerLinks.push({languages: true});
|
||||
}
|
||||
let search = false;
|
||||
headerLinks.forEach(link => {
|
||||
if (
|
||||
link.doc &&
|
||||
!fs.existsSync(`${CWD}/../${readMetadata.getDocsPath()}/`)
|
||||
) {
|
||||
throw new Error(
|
||||
`You have 'doc' in your headerLinks, but no '${readMetadata.getDocsPath()}' folder exists one level up from ` +
|
||||
`'website' folder. Did you run \`docusaurus-init\` or \`npm run examples\`? If so, ` +
|
||||
`make sure you rename 'docs-examples-from-docusaurus' to 'docs'.`,
|
||||
);
|
||||
}
|
||||
if (link.blog && !fs.existsSync(`${CWD}/blog/`)) {
|
||||
throw new Error(
|
||||
"You have 'blog' in your headerLinks, but no 'blog' folder exists in your " +
|
||||
"'website' folder. Did you run `docusaurus-init` or `npm run examples`? If so, " +
|
||||
"make sure you rename 'blog-examples-from-docusaurus' to 'blog'.",
|
||||
);
|
||||
}
|
||||
if (link.page && !fs.existsSync(`${CWD}/pages/`)) {
|
||||
throw new Error(
|
||||
"You have 'page' in your headerLinks, but no 'pages' folder exists in your " +
|
||||
"'website' folder.",
|
||||
);
|
||||
}
|
||||
// We will add search bar to end if location not specified
|
||||
if (link.search) {
|
||||
search = true;
|
||||
}
|
||||
});
|
||||
if (!search && this.props.config.algolia) {
|
||||
headerLinks.push({search: true});
|
||||
}
|
||||
return (
|
||||
<div className="navigationWrapper navigationSlider">
|
||||
<nav className="slidingNav">
|
||||
<ul className="nav-site nav-site-internal">
|
||||
{headerLinks.map(this.makeLinks, this)}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const headerClass = siteConfig.headerIcon
|
||||
? 'headerTitleWithLogo'
|
||||
: 'headerTitle';
|
||||
const versionsLink =
|
||||
this.props.baseUrl +
|
||||
(env.translation.enabled
|
||||
? `${this.props.language}/versions${extension}`
|
||||
: `versions${extension}`);
|
||||
return (
|
||||
<div className="fixedHeaderContainer">
|
||||
<div className="headerWrapper wrapper">
|
||||
<header>
|
||||
<a
|
||||
href={
|
||||
this.props.baseUrl +
|
||||
(env.translation.enabled ? this.props.language : '')
|
||||
}>
|
||||
{siteConfig.headerIcon && (
|
||||
<img
|
||||
className="logo"
|
||||
src={this.props.baseUrl + siteConfig.headerIcon}
|
||||
alt={siteConfig.title}
|
||||
/>
|
||||
)}
|
||||
{!this.props.config.disableHeaderTitle && (
|
||||
<h2 className={headerClass}>{this.props.title}</h2>
|
||||
)}
|
||||
</a>
|
||||
{env.versioning.enabled && (
|
||||
<a href={versionsLink}>
|
||||
<h3>{this.props.version || env.versioning.defaultVersion}</h3>
|
||||
</a>
|
||||
)}
|
||||
{this.renderResponsiveNav()}
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HeaderNav.defaultProps = {
|
||||
current: {},
|
||||
};
|
||||
|
||||
module.exports = HeaderNav;
|
47
packages/docusaurus-1.x/lib/core/nav/OnPageNav.js
Normal file
47
packages/docusaurus-1.x/lib/core/nav/OnPageNav.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
|
||||
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
|
||||
const {getTOC} = require('../toc');
|
||||
|
||||
const Link = ({hashLink, content}) => (
|
||||
<a
|
||||
href={`#${hashLink}`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: content,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const Headings = ({headings}) => {
|
||||
if (!headings.length) return null;
|
||||
return (
|
||||
<ul className="toc-headings">
|
||||
{headings.map(heading => (
|
||||
<li key={heading.hashLink}>
|
||||
<Link hashLink={heading.hashLink} content={heading.content} />
|
||||
<Headings headings={heading.children} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
class OnPageNav extends React.Component {
|
||||
render() {
|
||||
const customTags = siteConfig.onPageNavHeadings;
|
||||
const headings = customTags
|
||||
? getTOC(this.props.rawContent, customTags.topLevel, customTags.sub)
|
||||
: getTOC(this.props.rawContent);
|
||||
|
||||
return <Headings headings={headings} />;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OnPageNav;
|
220
packages/docusaurus-1.x/lib/core/nav/SideNav.js
Normal file
220
packages/docusaurus-1.x/lib/core/nav/SideNav.js
Normal file
|
@ -0,0 +1,220 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const siteConfig = require(`${process.cwd()}/siteConfig.js`);
|
||||
const translation = require('../../server/translation.js');
|
||||
const {getPath, idx} = require('../utils.js');
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// return appropriately translated category string
|
||||
getLocalizedCategoryString(category) {
|
||||
const categoryString =
|
||||
idx(translation, [
|
||||
this.props.language,
|
||||
'localized-strings',
|
||||
'categories',
|
||||
category,
|
||||
]) || category;
|
||||
return categoryString;
|
||||
}
|
||||
|
||||
// return appropriately translated label to use for doc/blog in sidebar
|
||||
getLocalizedString(metadata) {
|
||||
let localizedString;
|
||||
const i18n = translation[this.props.language];
|
||||
const id = metadata.localized_id;
|
||||
const sbTitle = metadata.sidebar_label;
|
||||
|
||||
if (sbTitle) {
|
||||
localizedString =
|
||||
idx(i18n, ['localized-strings', 'docs', id, 'sidebar_label']) ||
|
||||
sbTitle;
|
||||
} else {
|
||||
localizedString =
|
||||
idx(i18n, ['localized-strings', 'docs', id, 'title']) || metadata.title;
|
||||
}
|
||||
return localizedString;
|
||||
}
|
||||
|
||||
// return link to doc in sidebar
|
||||
getLink(metadata) {
|
||||
if (metadata.permalink) {
|
||||
const targetLink = getPath(metadata.permalink, siteConfig.cleanUrl);
|
||||
if (targetLink.match(/^https?:/)) {
|
||||
return targetLink;
|
||||
}
|
||||
return siteConfig.baseUrl + targetLink;
|
||||
}
|
||||
if (metadata.path) {
|
||||
return `${siteConfig.baseUrl}blog/${getPath(
|
||||
metadata.path,
|
||||
siteConfig.cleanUrl,
|
||||
)}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderCategory = categoryItem => {
|
||||
let ulClassName = '';
|
||||
let categoryClassName = 'navGroupCategoryTitle';
|
||||
let arrow;
|
||||
|
||||
if (siteConfig.docsSideNavCollapsible) {
|
||||
categoryClassName += ' collapsible';
|
||||
ulClassName = 'hide';
|
||||
arrow = (
|
||||
<span className="arrow">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="#565656"
|
||||
d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"
|
||||
/>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="navGroup" key={categoryItem.title}>
|
||||
<h3 className={categoryClassName}>
|
||||
{this.getLocalizedCategoryString(categoryItem.title)}
|
||||
{arrow}
|
||||
</h3>
|
||||
<ul className={ulClassName}>
|
||||
{categoryItem.children.map(item => {
|
||||
switch (item.type) {
|
||||
case 'LINK':
|
||||
return this.renderItemLink(item);
|
||||
case 'SUBCATEGORY':
|
||||
return this.renderSubcategory(item);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
renderSubcategory = subcategoryItem => (
|
||||
<div className="navGroup subNavGroup" key={subcategoryItem.title}>
|
||||
<h4 className="navGroupSubcategoryTitle">
|
||||
{this.getLocalizedCategoryString(subcategoryItem.title)}
|
||||
</h4>
|
||||
<ul>{subcategoryItem.children.map(this.renderItemLink)}</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
renderItemLink = linkItem => {
|
||||
const linkMetadata = linkItem.item;
|
||||
const itemClasses = classNames('navListItem', {
|
||||
navListItemActive: linkMetadata.id === this.props.current.id,
|
||||
});
|
||||
return (
|
||||
<li className={itemClasses} key={linkMetadata.id}>
|
||||
<a className="navItem" href={this.getLink(linkMetadata)}>
|
||||
{this.getLocalizedString(linkMetadata)}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav className="toc">
|
||||
<div className="toggleNav">
|
||||
<section className="navWrapper wrapper">
|
||||
<div className="navBreadcrumb wrapper">
|
||||
<div className="navToggle" id="navToggler">
|
||||
<i />
|
||||
</div>
|
||||
<h2>
|
||||
<i>›</i>
|
||||
<span>
|
||||
{this.getLocalizedCategoryString(this.props.current.category)}
|
||||
</span>
|
||||
</h2>
|
||||
{siteConfig.onPageNav === 'separate' && (
|
||||
<div className="tocToggler" id="tocToggler">
|
||||
<i className="icon-toc" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="navGroups">
|
||||
{this.props.contents.map(this.renderCategory)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
var coll = document.getElementsByClassName('collapsible');
|
||||
var checkActiveCategory = true;
|
||||
for (var i = 0; i < coll.length; i++) {
|
||||
var links = coll[i].nextElementSibling.getElementsByTagName('*');
|
||||
if (checkActiveCategory){
|
||||
for (var j = 0; j < links.length; j++) {
|
||||
if (links[j].classList.contains('navListItemActive')){
|
||||
coll[i].nextElementSibling.classList.toggle('hide');
|
||||
coll[i].childNodes[1].classList.toggle('rotate');
|
||||
checkActiveCategory = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coll[i].addEventListener('click', function() {
|
||||
var arrow = this.childNodes[1];
|
||||
arrow.classList.toggle('rotate');
|
||||
var content = this.nextElementSibling;
|
||||
content.classList.toggle('hide');
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
createToggler('#navToggler', '#docsNav', 'docsSliderActive');
|
||||
createToggler('#tocToggler', 'body', 'tocActive');
|
||||
|
||||
const headings = document.querySelector('.toc-headings');
|
||||
headings && headings.addEventListener('click', function(event) {
|
||||
if (event.target.tagName === 'A') {
|
||||
document.body.classList.remove('tocActive');
|
||||
}
|
||||
}, false);
|
||||
|
||||
function createToggler(togglerSelector, targetSelector, className) {
|
||||
var toggler = document.querySelector(togglerSelector);
|
||||
var target = document.querySelector(targetSelector);
|
||||
|
||||
if (!toggler) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggler.onclick = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
target.classList.toggle(className);
|
||||
};
|
||||
}
|
||||
});
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SideNav.defaultProps = {
|
||||
contents: [],
|
||||
};
|
||||
|
||||
module.exports = SideNav;
|
123
packages/docusaurus-1.x/lib/core/renderMarkdown.js
Normal file
123
packages/docusaurus-1.x/lib/core/renderMarkdown.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const hljs = require('highlight.js');
|
||||
const Markdown = require('remarkable');
|
||||
const prismjs = require('prismjs');
|
||||
const deepmerge = require('deepmerge');
|
||||
const chalk = require('chalk');
|
||||
const anchors = require('./anchors.js');
|
||||
|
||||
const CWD = process.cwd();
|
||||
|
||||
const alias = {
|
||||
js: 'jsx',
|
||||
html: 'markup',
|
||||
sh: 'bash',
|
||||
md: 'markdown',
|
||||
};
|
||||
|
||||
class MarkdownRenderer {
|
||||
constructor() {
|
||||
const siteConfig = require(`${CWD}/siteConfig.js`);
|
||||
let markdownOptions = {
|
||||
// Highlight.js expects hljs css classes on the code element.
|
||||
// This results in <pre><code class="hljs css languages-jsx">
|
||||
langPrefix: 'hljs css language-',
|
||||
highlight(str, lang) {
|
||||
// User's own custom highlighting function
|
||||
if (siteConfig.highlight && siteConfig.highlight.hljs) {
|
||||
siteConfig.highlight.hljs(hljs);
|
||||
}
|
||||
// Fallback to default language
|
||||
lang =
|
||||
lang || (siteConfig.highlight && siteConfig.highlight.defaultLang);
|
||||
if (lang === 'text') {
|
||||
return str;
|
||||
}
|
||||
if (lang) {
|
||||
try {
|
||||
if (
|
||||
siteConfig.usePrism === true ||
|
||||
(siteConfig.usePrism &&
|
||||
siteConfig.usePrism.length > 0 &&
|
||||
siteConfig.usePrism.indexOf(lang) !== -1)
|
||||
) {
|
||||
const language = alias[lang] || lang;
|
||||
try {
|
||||
// Currently people using prismjs on Node have to individually require()
|
||||
// every single language (https://github.com/PrismJS/prism/issues/593)
|
||||
require(`prismjs/components/prism-${language}.min`);
|
||||
return prismjs.highlight(str, prismjs.languages[language]);
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
const unsupportedLanguageError = chalk.yellow(
|
||||
`Warning: ${chalk.red(
|
||||
language,
|
||||
)} is not supported by prismjs.` +
|
||||
'\nPlease refer to https://prismjs.com/#languages-list for the list of supported languages.',
|
||||
);
|
||||
console.log(unsupportedLanguageError);
|
||||
} else console.error(err);
|
||||
}
|
||||
}
|
||||
if (hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(lang, str).value;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return hljs.highlightAuto(str).value;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
html: true,
|
||||
linkify: true,
|
||||
};
|
||||
|
||||
// Allow overriding default options
|
||||
if (siteConfig.markdownOptions) {
|
||||
markdownOptions = deepmerge(
|
||||
{},
|
||||
markdownOptions,
|
||||
siteConfig.markdownOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const md = new Markdown(markdownOptions);
|
||||
|
||||
// Register anchors plugin
|
||||
md.use(anchors);
|
||||
|
||||
// Allow client sites to register their own plugins
|
||||
if (siteConfig.markdownPlugins) {
|
||||
siteConfig.markdownPlugins.forEach(plugin => {
|
||||
md.use(plugin);
|
||||
});
|
||||
}
|
||||
|
||||
this.md = md;
|
||||
}
|
||||
|
||||
toHtml(source) {
|
||||
const html = this.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">');
|
||||
}
|
||||
}
|
||||
|
||||
const renderMarkdown = new MarkdownRenderer();
|
||||
|
||||
module.exports = source => renderMarkdown.toHtml(source);
|
77
packages/docusaurus-1.x/lib/core/toSlug.js
Normal file
77
packages/docusaurus-1.x/lib/core/toSlug.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// ES2015 does not support regexp with unicode categories,
|
||||
// so we need to list all the unicode ranges manually
|
||||
// to get analog of [\P{L}\P{N}] from ES2018
|
||||
// see: https://github.com/danielberndt/babel-plugin-utf-8-regex
|
||||
const letters =
|
||||
'\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC';
|
||||
const numbers =
|
||||
'\u0030-\u0039\u00B2\u00B3\u00B9\u00BC-\u00BE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19';
|
||||
const exceptAlphanum = new RegExp(`[^${[letters, numbers].join('')}]`, 'g');
|
||||
|
||||
/**
|
||||
* Converts a string to a slug, that can be used in heading anchors
|
||||
*
|
||||
* @param {string} string
|
||||
* @param {Object} [context={}] - an optional context to track used slugs and
|
||||
* ensure that new slug will be unique
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
module.exports = (string, context = {}) => {
|
||||
// var accents = "àáäâèéëêìíïîòóöôùúüûñç";
|
||||
const accents =
|
||||
'\u00e0\u00e1\u00e4\u00e2\u00e8' +
|
||||
'\u00e9\u00eb\u00ea\u00ec\u00ed\u00ef' +
|
||||
'\u00ee\u00f2\u00f3\u00f6\u00f4\u00f9' +
|
||||
'\u00fa\u00fc\u00fb\u00f1\u00e7';
|
||||
|
||||
const without = 'aaaaeeeeiiiioooouuuunc';
|
||||
|
||||
let slug = string
|
||||
.toString()
|
||||
// Handle uppercase characters
|
||||
.toLowerCase()
|
||||
// Handle accentuated characters
|
||||
.replace(new RegExp(`[${accents}]`, 'g'), c =>
|
||||
without.charAt(accents.indexOf(c)),
|
||||
)
|
||||
// Replace `.`, `(` and `?` with blank string like Github does
|
||||
.replace(/\.|\(|\?/g, '')
|
||||
// Dash special characters
|
||||
.replace(exceptAlphanum, '-')
|
||||
// Compress multiple dash
|
||||
.replace(/-+/g, '-')
|
||||
// Trim dashes
|
||||
.replace(/^-|-$/g, '');
|
||||
|
||||
// Add trailing `-` if string contains ` ...` in the end like Github does
|
||||
if (/\s[.]{1,}/.test(string)) {
|
||||
slug += '-';
|
||||
}
|
||||
|
||||
if (!context.slugStats) {
|
||||
context.slugStats = {};
|
||||
}
|
||||
|
||||
if (typeof context.slugStats[slug] === 'number') {
|
||||
// search for an index, that will not clash with an existing headings
|
||||
while (
|
||||
typeof context.slugStats[`${slug}-${++context.slugStats[slug]}`] ===
|
||||
'number'
|
||||
);
|
||||
slug += `-${context.slugStats[slug]}`;
|
||||
}
|
||||
|
||||
// we are tracking both original anchors and suffixed to avoid future name
|
||||
// clashing with headings with numbers e.g. `#Foo 1` may clash with the second `#Foo`
|
||||
context.slugStats[slug] = 0;
|
||||
|
||||
return slug;
|
||||
};
|
76
packages/docusaurus-1.x/lib/core/toc.js
Normal file
76
packages/docusaurus-1.x/lib/core/toc.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const Remarkable = require('remarkable');
|
||||
const mdToc = require('markdown-toc');
|
||||
const toSlug = require('./toSlug');
|
||||
|
||||
const tocRegex = new RegExp('<AUTOGENERATED_TABLE_OF_CONTENTS>', 'i');
|
||||
|
||||
/**
|
||||
* Returns a table of content from the headings
|
||||
*
|
||||
* @return array
|
||||
* Array of heading objects with `hashLink`, `content` and `children` fields
|
||||
*
|
||||
*/
|
||||
function getTOC(content, headingTags = 'h2', subHeadingTags = 'h3') {
|
||||
const tagToLevel = tag => Number(tag.slice(1));
|
||||
const headingLevels = [].concat(headingTags).map(tagToLevel);
|
||||
const subHeadingLevels = subHeadingTags
|
||||
? [].concat(subHeadingTags).map(tagToLevel)
|
||||
: [];
|
||||
const allowedHeadingLevels = headingLevels.concat(subHeadingLevels);
|
||||
const md = new Remarkable();
|
||||
const headings = mdToc(content).json;
|
||||
const toc = [];
|
||||
const context = {};
|
||||
let current;
|
||||
|
||||
headings.forEach(heading => {
|
||||
// we need always generate slugs to ensure, that we will have consistent
|
||||
// slug indexes for headings with the same names
|
||||
const hashLink = toSlug(heading.content, context);
|
||||
if (!allowedHeadingLevels.includes(heading.lvl)) {
|
||||
return;
|
||||
}
|
||||
const rawContent = mdToc.titleize(heading.content);
|
||||
const entry = {
|
||||
hashLink,
|
||||
rawContent,
|
||||
content: md.renderInline(rawContent),
|
||||
children: [],
|
||||
};
|
||||
if (headingLevels.includes(heading.lvl)) {
|
||||
toc.push(entry);
|
||||
current = entry;
|
||||
} else if (current) {
|
||||
current.children.push(entry);
|
||||
}
|
||||
});
|
||||
return toc;
|
||||
}
|
||||
|
||||
// takes the content of a doc article and returns the content with a table of
|
||||
// contents inserted
|
||||
function insertTOC(rawContent) {
|
||||
if (!rawContent || !tocRegex.test(rawContent)) {
|
||||
return rawContent;
|
||||
}
|
||||
const filterRe = /^`[^`]*`/;
|
||||
const headers = getTOC(rawContent, 'h3', null);
|
||||
const tableOfContents = headers
|
||||
.filter(header => filterRe.test(header.rawContent))
|
||||
.map(header => ` - [${header.rawContent}](#${header.hashLink})`)
|
||||
.join('\n');
|
||||
return rawContent.replace(tocRegex, tableOfContents);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTOC,
|
||||
insertTOC,
|
||||
};
|
25
packages/docusaurus-1.x/lib/core/unindent.js
Normal file
25
packages/docusaurus-1.x/lib/core/unindent.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Remove the indentation introduced by JSX
|
||||
function unindent(code) {
|
||||
const lines = code.split('\n');
|
||||
if (lines[0] === '') {
|
||||
lines.shift();
|
||||
}
|
||||
if (lines.length <= 1) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const indent = lines[0].match(/^\s*/)[0];
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
lines[i] = lines[i].replace(new RegExp(`^${indent}`), '');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
module.exports = unindent;
|
122
packages/docusaurus-1.x/lib/core/utils.js
Normal file
122
packages/docusaurus-1.x/lib/core/utils.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
const shell = require('shelljs');
|
||||
|
||||
const TRUNCATE_MARKER = /<!--\s*truncate\s*-->/;
|
||||
|
||||
function blogPostHasTruncateMarker(content) {
|
||||
return TRUNCATE_MARKER.test(content);
|
||||
}
|
||||
|
||||
function extractBlogPostBeforeTruncate(content) {
|
||||
return content.split(TRUNCATE_MARKER)[0];
|
||||
}
|
||||
|
||||
function removeExtension(pathStr) {
|
||||
return pathStr.replace(/\.[^/.]+$/, '');
|
||||
}
|
||||
|
||||
function getPath(pathStr, cleanUrl = false) {
|
||||
if (!pathStr || !cleanUrl || !pathStr.endsWith('.html')) {
|
||||
return pathStr;
|
||||
}
|
||||
return pathStr.endsWith('/index.html')
|
||||
? pathStr.replace(/index\.html$/, '')
|
||||
: removeExtension(pathStr);
|
||||
}
|
||||
|
||||
function idx(target, keyPaths) {
|
||||
return (
|
||||
target &&
|
||||
(Array.isArray(keyPaths)
|
||||
? keyPaths.reduce((obj, key) => obj && obj[key], target)
|
||||
: target[keyPaths])
|
||||
);
|
||||
}
|
||||
|
||||
function getGitLastUpdated(filepath) {
|
||||
const timestampAndAuthorRegex = /^(\d+), (.+)$/;
|
||||
|
||||
function isTimestampAndAuthor(str) {
|
||||
return timestampAndAuthorRegex.test(str);
|
||||
}
|
||||
|
||||
function getTimestampAndAuthor(str) {
|
||||
if (!str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const temp = str.match(timestampAndAuthorRegex);
|
||||
return !temp || temp.length < 3
|
||||
? null
|
||||
: {timestamp: temp[1], author: temp[2]};
|
||||
}
|
||||
|
||||
// Wrap in try/catch in case the shell commands fail (e.g. project doesn't use Git, etc).
|
||||
try {
|
||||
// To differentiate between content change and file renaming / moving, use --summary
|
||||
// To follow the file history until before it is moved (when we create new version), use
|
||||
// --follow.
|
||||
const silentState = shell.config.silent; // Save old silent state.
|
||||
shell.config.silent = true;
|
||||
const result = shell
|
||||
.exec(`git log --follow --summary --format="%ct, %an" ${filepath}`)
|
||||
.stdout.trim();
|
||||
shell.config.silent = silentState;
|
||||
|
||||
// Format the log results to be
|
||||
// ['1234567890, Yangshun Tay', 'rename ...', '1234567880,
|
||||
// 'Joel Marcey', 'move ...', '1234567870', '1234567860']
|
||||
const records = result
|
||||
.toString('utf-8')
|
||||
.replace(/\n\s*\n/g, '\n')
|
||||
.split('\n')
|
||||
.filter(String);
|
||||
const lastContentModifierCommit = records.find((item, index, arr) => {
|
||||
const currentItemIsTimestampAndAuthor = isTimestampAndAuthor(item);
|
||||
const isLastTwoItem = index + 2 >= arr.length;
|
||||
const nextItemIsTimestampAndAuthor = isTimestampAndAuthor(arr[index + 1]);
|
||||
return (
|
||||
currentItemIsTimestampAndAuthor &&
|
||||
(isLastTwoItem || nextItemIsTimestampAndAuthor)
|
||||
);
|
||||
});
|
||||
|
||||
return lastContentModifierCommit
|
||||
? getTimestampAndAuthor(lastContentModifierCommit)
|
||||
: null;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGitLastUpdatedTime(filepath) {
|
||||
const commit = getGitLastUpdated(filepath);
|
||||
|
||||
if (commit && commit.timestamp) {
|
||||
const date = new Date(parseInt(commit.timestamp, 10) * 1000);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGitLastUpdatedBy(filepath) {
|
||||
const commit = getGitLastUpdated(filepath);
|
||||
return commit ? commit.author : null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
blogPostHasTruncateMarker,
|
||||
extractBlogPostBeforeTruncate,
|
||||
getGitLastUpdatedTime,
|
||||
getGitLastUpdatedBy,
|
||||
getPath,
|
||||
removeExtension,
|
||||
idx,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue