Skip to content

Commit

Permalink
feat: Code Language Selector
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed May 7, 2020
1 parent c59de1d commit 7000d59
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 5 deletions.
6 changes: 6 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ const getPlugins = () => {
maxWidth: 1200,
},
},
{
resolve: require.resolve("./plugins/gatsby-plugin-code-tabs"),
options: {
githubRepo: "getsentry/develop"
}
},
{
resolve: "gatsby-remark-prismjs",
options: {
Expand Down
95 changes: 95 additions & 0 deletions plugins/gatsby-plugin-code-tabs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const visit = require("unist-util-visit");

function getFullMeta(node) {
if (node.lang && node.meta) {
return node.lang + node.meta;
}
return node.lang || node.meta;
}

function getFilename(node) {
const meta = getFullMeta(node);
const match = (meta || "").match(/\{filename:([^\}]+)\}/);
return (match && match[1]) || "";
}

function getTabTitle(node) {
const meta = getFullMeta(node);
const match = (meta || "").match(/\{tabTitle:([^\}]+)\}/);
return (match && match[1]) || "";
}

function forcesTabBar(node) {
const meta = getFullMeta(node);
return meta && !!meta.match(/\{forceTabBar\}/);
}

function isFoldWithLast(node) {
const meta = getFullMeta(node);
return meta && !!meta.match(/\{foldWithLast\}/);
}

module.exports = ({ markdownAST }, { className = "code-tabs-wrapper" }) => {
let pendingCode = [];
let toRemove = [];

function flushPendingCode() {
if (pendingCode.length === 0) {
return;
}

const hideTabBar =
pendingCode.length === 1 && !forcesTabBar(pendingCode[0][0]);
const rootNode = pendingCode[0][0];
const children = pendingCode.flatMap(([node]) => [
{
type: "jsx",
value: `<CodeBlock language="${node.lang || ""}" title="${getTabTitle(
node
)}" filename="${getFilename(node)}">`,
},
Object.assign({}, node),
{
type: "jsx",
value: "</CodeBlock>",
},
]);

rootNode.type = "element";
rootNode.data = {
hName: "div",
hProperties: {
className,
},
};
rootNode.children = [
{
type: "jsx",
value: `<CodeTabs hideTabBar={${hideTabBar}}>`,
},
...children,
{
type: "jsx",
value: "</CodeTabs>",
},
];

toRemove = toRemove.concat(pendingCode.splice(1));
}

visit(markdownAST, "code", (node, _index, parent) => {
if (!isFoldWithLast(node)) {
flushPendingCode();
pendingCode = [];
}
pendingCode.push([node, parent]);
});

flushPendingCode();

toRemove.forEach(([node, parent], index) => {
parent.children = parent.children.filter((n) => n !== node);
});

return markdownAST;
};
12 changes: 12 additions & 0 deletions plugins/gatsby-plugin-code-tabs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "gatsby-plugin-code-tabs",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"peerDependencies": {
"gatsby": "^2.0.0"
},
"dependencies": {
"unist-util-visit": "^1.4.1"
}
}
21 changes: 21 additions & 0 deletions src/components/codeBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import PropTypes from "prop-types";

class CodeBlock extends React.Component {
render() {
return (
<div className="code-block">
{this.props.filename && <p class="filename">{this.props.filename}</p>}
{this.props.children}
</div>
);
}
}

CodeBlock.propTypes = {
language: PropTypes.string,
filename: PropTypes.string,
title: PropTypes.string,
};

export default CodeBlock;
85 changes: 85 additions & 0 deletions src/components/codeTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState, useContext } from "react";

// human readable versions of names
const LANGUAGES = {
javascript: "JavaScript",
typescript: "TypeScript",
html: "HTML",
coffee: "CoffeeScript",
powershell: "PowerShell",
json: "JSON",
cpp: "C++",
csharp: "C#",
es6: "JavaScript (ES6)",
yml: "YAML",
yaml: "YAML",
};

export const CodeContext = React.createContext(null);

export function makeCodeContextState() {
return useState(null);
}

function CodeTabs({ children, hideTabBar = false }) {
if (!Array.isArray(children)) {
children = [children];
}

const [sharedSelection, setSharedSelection] = useContext(CodeContext);
const [localSelection, setLocalSelection] = useState(null);

const possibleChoices = children.map((x) => {
const { title, language } = x.props;
return (
title ||
LANGUAGES[language] ||
(language ? language[0].toUpperCase() + language.substr(1) : "Text")
);
});

const sharedSelectionChoice = sharedSelection
? possibleChoices.find((x) => x === sharedSelection)
: null;
const localSelectionChoice = localSelection
? possibleChoices.find((x) => x === localSelection)
: null;

const finalSelection =
sharedSelectionChoice || localSelectionChoice || possibleChoices[0];

if (localSelection !== finalSelection) {
setLocalSelection(finalSelection);
}

let code = null;

const names = possibleChoices.map((choice, idx) => {
const isSelected = choice === finalSelection;
if (isSelected) {
code = children[idx];
}

return (
<button
className={isSelected ? "active" : ""}
onClick={() => {
setSharedSelection(choice);
setLocalSelection(choice);
}}
key={idx}
>
{choice}
</button>
);
});

return (
<div className="code-tabs">
{!hideTabBar && <div class="tab-bar">{names}</div>}
<div class="tab-content">{code}</div>
</div>
);
}

export default CodeTabs;
18 changes: 14 additions & 4 deletions src/components/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ import Header from "./header";
import Sidebar from "./sidebar";
import Navbar from "./navbar";
import SmartLink from "./smartLink";
import CodeBlock from "./codeBlock";
import CodeTabs, { CodeContext, makeCodeContextState } from "./codeTabs";

import "prismjs/themes/prism-tomorrow.css";
import "../css/screen.scss";

const mdxComponents = { Alert, a: SmartLink, Link: SmartLink };
const mdxComponents = {
Alert,
a: SmartLink,
Link: SmartLink,
CodeBlock,
CodeTabs,
};

const TableOfContents = ({ toc: { items } }) => {
if (!items) return null;
Expand Down Expand Up @@ -90,9 +98,11 @@ const Layout = ({
>
<h1 className="mb-3">{mdx.frontmatter.title}</h1>
<div id="main">
<MDXProvider components={mdxComponents}>
<MDXRenderer>{mdx.body}</MDXRenderer>
</MDXProvider>
<CodeContext.Provider value={makeCodeContextState()}>
<MDXProvider components={mdxComponents}>
<MDXRenderer>{mdx.body}</MDXRenderer>
</MDXProvider>
</CodeContext.Provider>

<GitHubCTA
sourceInstanceName={file.sourceInstanceName}
Expand Down
42 changes: 42 additions & 0 deletions src/css/_includes/code-blocks.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* support for code blocks and tabs */
.code-tabs {
border: 1px solid $lightPurple;
margin-bottom: 1rem;

.tab-bar {
background: $lightPurple;

button {
padding: 3px 6px;
display: inline-block;
cursor: pointer;
border: none;
font-size: 0.95rem;
background: transparent;
}

button.active {
background: white;
}
}

.tab-content {
position: relative;

.filename {
position: absolute;
right: 0;
margin: 0;
padding: 0 6px;
background: $flame8;
font-size: 0.85rem;
border-bottom-left-radius: 3px;
}

pre {
margin: 0;
padding: 5px 10px;
border-radius: 0;
}
}
}
4 changes: 3 additions & 1 deletion src/css/screen.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@
@import "_includes/index";
@import "_includes/api";
@import "_includes/user-content-ui";
@import "_includes/code-blocks";

code[class*="language-"] {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier,
monospace;
}

pre[class*="language-"] {
font-size: 0.85em;
border: 0;
Expand All @@ -71,4 +73,4 @@ a .icon {
width: 14px;
height: 14px;
}
}
}

0 comments on commit 7000d59

Please sign in to comment.