feat: code block tab (#1063)

* Try to find a way to recognize the codeblock tabs, its title, and
where it ends
- I try using DOCUSAURUS_CODE_TABS to mark the start
- Use TAB_TITLE to mark the title of the tab
- END_TAB to mark the end of that tab
- END_DOCUSAURUS_CODE_TABS to mark the end of the whole code blocks
then parse using regex and render accordingly

* Added on click hook

* Added example on how to write it, how to use it, how it will look like can be reverted later

* Fix css error

* - Move addEventListener part to lib/static
- Remove comments

* Add documentation

* Remove examples

* Change syntax of the codetabs
This commit is contained in:
Fienny Angelina 2019-01-24 13:48:01 +08:00 committed by Yangshun Tay
parent 4ce7ae2c98
commit 5ce85e5b5e
6 changed files with 207 additions and 3 deletions

View file

@ -132,6 +132,42 @@ will lead to a table of contents of the functions:
and each function will link to their corresponding sections in the page.
### Code tabs
Docusaurus provides code tabs by default. To use code tabs, first, mark the start and end of a code tabs group, by using `<!--DOCUSAURUS_CODE_TABS-->` and `<!--END_DOCUSAURUS_CODE_TABS-->` respectively.
Secondly, start each tab with `<!--[TAB_TITLE]-->`.
Example:
<!--DOCUSAURUS_CODE_TABS-->
<!--Javascript-->
```js
console.log("Hello, world!");
```
<!--Python-->
```py
print("Hello, world!")
```
<!--C-->
```C
#include
int main(void)
{
puts("Hello, world!");
}
```
<!--Pascal-->
```Pascal
program HelloWorld;
begin
WriteLn('Hello, world!');
end.
```
<!--END_DOCUSAURUS_CODE_TABS-->
## Syntax Highlighting
Syntax highlighting is enabled by default on fenced code blocks. The language should be detected automatically, but you can sometimes get better results by specifying the language. You can do so using an [info string](https://github.github.com/gfm/#example-111), following the three opening backticks. The following JavaScript example...

View file

@ -0,0 +1,50 @@
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) => (
<div
className={`nav-link${i === 0 ? ' active' : ''}`}
id={`${t.id}-tab`}
data-group={`group_${t.groupId}`}
data-tab={`tabpanel_${t.id}`}>
{t.label}
</div>
))}
</div>
<div className="tab-content">
{tabs.map((t, i) => (
<div
className={`tab-pane${i === 0 ? ' active' : ''}`}
data-group={`group_${t.groupId}`}
tabIndex="-1"
id={`tabpanel_${t.id}`}>
{t.panelContent}
</div>
))}
</div>
</div>
);
}
}
module.exports = MarkdownBlock;

View file

@ -7,6 +7,7 @@
const React = require('react');
const MarkdownBlock = require('./MarkdownBlock.js');
const CodeTabsMarkdownBlock = require('./CodeTabsMarkdownBlock.js');
const translate = require('../server/translate.js').translate;
@ -17,8 +18,48 @@ 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();
const result = titles.map((title, idx) => ({
title: title.substring(4, title.length - 3),
content: tabs[idx],
}));
return result;
};
// 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 null;
}
if (c === '\n<!--END_DOCUSAURUS_CODE_TABS-->') {
inCodeTabs = false;
return null;
}
if (inCodeTabs) {
return (
<CodeTabsMarkdownBlock>
{splitTabsToTitleAndContent(c)}
</CodeTabsMarkdownBlock>
);
}
return <MarkdownBlock>{c}</MarkdownBlock>;
});
return renderResult;
}
render() {
let docSource = this.props.source;
@ -67,9 +108,7 @@ class Doc extends React.Component {
<h1 className="postHeaderTitle">{this.props.title}</h1>
)}
</header>
<article>
<MarkdownBlock>{this.props.content}</MarkdownBlock>
</article>
<article>{this.renderContent()}</article>
</div>
);
}

View file

@ -188,6 +188,7 @@ class Head extends React.Component {
rel="stylesheet"
href={`${this.props.config.baseUrl}css/main.css`}
/>
<script src={`${this.props.config.baseUrl}js/codetabs.js`} />
</head>
);
}

View file

@ -2361,3 +2361,57 @@ input::placeholder {
padding: 5px 0;
}
/* End of Footer */
.tabs {
border-top: 1px solid #cfcfcf;
}
.nav-tabs {
display: flex;
border-bottom: 4px solid #e0e0e0;
width: 100%;
padding: 0;
overflow-x: auto;
white-space: nowrap;
max-height: 100%;
}
.nav-tabs::-webkit-scrollbar {
display: none;
}
.tabs .tab-pane:focus {
outline: none;
}
.tabs .nav-tabs > div {
font-size: 14px;
line-height: 1.14286;
padding: 12px 16px;
text-decoration: none;
display: block;
cursor: pointer;
}
.tabs .nav-tabs > div.active {
border-bottom: 4px solid $primaryColor;
}
.tab-pane {
display: none;
}
.tab-pane.active {
display: block;
}
.tab-pane > pre {
white-space: pre-wrap;
}
.tab-pane > pre > code {
margin-top: 0;
border-radius: 0;
box-shadow: none;
}

View file

@ -0,0 +1,24 @@
// Turn off ESLint for this file because it's sent down to users as-is.
/* eslint-disable */
window.addEventListener('load', function() {
// add event listener for all tab
document.querySelectorAll('.nav-link').forEach(function(el) {
el.addEventListener('click', function(e) {
const groupId = e.target.getAttribute('data-group');
document
.querySelectorAll(`.nav-link[data-group=${groupId}]`)
.forEach(function(el) {
el.classList.remove('active');
});
document
.querySelectorAll(`.tab-pane[data-group=${groupId}]`)
.forEach(function(el) {
el.classList.remove('active');
});
e.target.classList.add('active');
document
.querySelector(`#${e.target.getAttribute('data-tab')}`)
.classList.add('active');
});
});
});