212 lines
8.4 KiB
TypeScript
212 lines
8.4 KiB
TypeScript
import * as _ from 'lodash';
|
|
import CircularProgress from 'material-ui/CircularProgress';
|
|
import * as React from 'react';
|
|
import DocumentTitle = require('react-document-title');
|
|
import {
|
|
scroller,
|
|
} from 'react-scroll';
|
|
import {TopBar} from 'ts/components/top_bar';
|
|
import {MarkdownSection} from 'ts/pages/shared/markdown_section';
|
|
import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
|
|
import {SectionHeader} from 'ts/pages/shared/section_header';
|
|
import {Article, ArticlesBySection, HeaderSizes, Styles, WebsitePaths} from 'ts/types';
|
|
import {colors} from 'ts/utils/colors';
|
|
import {configs} from 'ts/utils/configs';
|
|
import {constants} from 'ts/utils/constants';
|
|
import {utils} from 'ts/utils/utils';
|
|
|
|
const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000;
|
|
|
|
export interface WikiProps {
|
|
source: string;
|
|
location: Location;
|
|
}
|
|
|
|
interface WikiState {
|
|
articlesBySection: ArticlesBySection;
|
|
}
|
|
|
|
const styles: Styles = {
|
|
mainContainers: {
|
|
position: 'absolute',
|
|
top: 1,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
overflowZ: 'hidden',
|
|
overflowY: 'scroll',
|
|
minHeight: 'calc(100vh - 1px)',
|
|
WebkitOverflowScrolling: 'touch',
|
|
},
|
|
menuContainer: {
|
|
borderColor: colors.grey300,
|
|
maxWidth: 330,
|
|
marginLeft: 20,
|
|
},
|
|
};
|
|
|
|
export class Wiki extends React.Component<WikiProps, WikiState> {
|
|
private wikiBackoffTimeoutId: number;
|
|
constructor(props: WikiProps) {
|
|
super(props);
|
|
this.state = {
|
|
articlesBySection: undefined,
|
|
};
|
|
}
|
|
public componentWillMount() {
|
|
// tslint:disable-next-line:no-floating-promises
|
|
this.fetchArticlesBySectionAsync();
|
|
}
|
|
public componentWillUnmount() {
|
|
clearTimeout(this.wikiBackoffTimeoutId);
|
|
}
|
|
public render() {
|
|
const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection)
|
|
? {}
|
|
: this.getMenuSubsectionsBySection(this.state.articlesBySection);
|
|
return (
|
|
<div>
|
|
<DocumentTitle title="0x Protocol Wiki"/>
|
|
<TopBar
|
|
blockchainIsLoaded={false}
|
|
location={this.props.location}
|
|
menuSubsectionsBySection={menuSubsectionsBySection}
|
|
shouldFullWidth={true}
|
|
/>
|
|
{_.isUndefined(this.state.articlesBySection) ?
|
|
<div
|
|
className="col col-12"
|
|
style={styles.mainContainers}
|
|
>
|
|
<div
|
|
className="relative sm-px2 sm-pt2 sm-m1"
|
|
style={{height: 122, top: '50%', transform: 'translateY(-50%)'}}
|
|
>
|
|
<div className="center pb2">
|
|
<CircularProgress size={40} thickness={5} />
|
|
</div>
|
|
<div className="center pt2" style={{paddingBottom: 11}}>Loading wiki...</div>
|
|
</div>
|
|
</div> :
|
|
<div
|
|
className="mx-auto flex"
|
|
style={{color: colors.grey800, height: 43}}
|
|
>
|
|
<div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
|
|
<div
|
|
className="border-right absolute pt2"
|
|
style={{...styles.menuContainer, ...styles.mainContainers}}
|
|
>
|
|
<NestedSidebarMenu
|
|
topLevelMenu={menuSubsectionsBySection}
|
|
menuSubsectionsBySection={menuSubsectionsBySection}
|
|
isSectionHeaderClickable={true}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
|
|
<div
|
|
id="documentation"
|
|
style={styles.mainContainers}
|
|
className="absolute"
|
|
>
|
|
<div id="0xProtocolWiki" />
|
|
<h1 className="md-pl2 sm-pl3">
|
|
<a href={constants.URL_GITHUB_WIKI} target="_blank">
|
|
0x Protocol Wiki
|
|
</a>
|
|
</h1>
|
|
<div id="wiki">
|
|
{this.renderWikiArticles()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
);
|
|
}
|
|
private renderWikiArticles(): React.ReactNode {
|
|
const sectionNames = _.keys(this.state.articlesBySection);
|
|
const sections = _.map(sectionNames, sectionName => this.renderSection(sectionName));
|
|
return sections;
|
|
}
|
|
private renderSection(sectionName: string) {
|
|
const articles = this.state.articlesBySection[sectionName];
|
|
const renderedArticles = _.map(articles, (article: Article) => {
|
|
const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`;
|
|
return (
|
|
<div key={`markdown-section-${article.title}`}>
|
|
<MarkdownSection
|
|
sectionName={article.title}
|
|
markdownContent={article.content}
|
|
headerSize={HeaderSizes.H2}
|
|
githubLink={githubLink}
|
|
/>
|
|
<div className="mb4 mt3 p3 center" style={{backgroundColor: colors.lightestGrey}}>
|
|
See a way to make this article better?{' '}
|
|
<a
|
|
href={githubLink}
|
|
target="_blank"
|
|
>
|
|
Edit here →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
return (
|
|
<div
|
|
key={`section-${sectionName}`}
|
|
className="py2 pr3 md-pl2 sm-pl3"
|
|
>
|
|
<SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} />
|
|
{renderedArticles}
|
|
</div>
|
|
);
|
|
}
|
|
private scrollToHash(): void {
|
|
const hashWithPrefix = this.props.location.hash;
|
|
let hash = hashWithPrefix.slice(1);
|
|
if (_.isEmpty(hash)) {
|
|
hash = '0xProtocolWiki'; // scroll to the top
|
|
}
|
|
|
|
scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
|
|
}
|
|
private async fetchArticlesBySectionAsync(): Promise<void> {
|
|
const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`;
|
|
const response = await fetch(endpoint);
|
|
if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) {
|
|
// We need to backoff and try fetching again later
|
|
this.wikiBackoffTimeoutId = window.setTimeout(() => {
|
|
// tslint:disable-next-line:no-floating-promises
|
|
this.fetchArticlesBySectionAsync();
|
|
}, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS);
|
|
return;
|
|
}
|
|
if (response.status !== 200) {
|
|
// TODO: Show the user an error message when the wiki fail to load
|
|
const errMsg = await response.text();
|
|
utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`);
|
|
return;
|
|
}
|
|
const articlesBySection = await response.json();
|
|
this.setState({
|
|
articlesBySection,
|
|
}, () => {
|
|
this.scrollToHash();
|
|
});
|
|
}
|
|
private getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) {
|
|
const sectionNames = _.keys(articlesBySection);
|
|
const menuSubsectionsBySection: {[section: string]: string[]} = {};
|
|
for (const sectionName of sectionNames) {
|
|
const articles = articlesBySection[sectionName];
|
|
const articleNames = _.map(articles, article => article.title);
|
|
menuSubsectionsBySection[sectionName] = articleNames;
|
|
}
|
|
return menuSubsectionsBySection;
|
|
}
|
|
}
|