-
Notifications
You must be signed in to change notification settings - Fork 368
/
code-explainer.js
143 lines (119 loc) · 4.98 KB
/
code-explainer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import React, { useState, useEffect } from "react";
import { Github } from "./codetabs";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Admonition from '@theme/Admonition';
export function ExplainCode({ children, alternativeURL }) {
const [lineNumber, setLineNumber] = useState(0);
const [activeBlock, setActiveBlock] = useState(0);
const [selectedFile, setSelectedFile] = useState(null);
const [isWideEnough, setIsWideEnough] = useState(true);
let blocks = [];
let files = []
for (let child of children) {
if (child.type === Block) {
blocks.push({ text: child.props.children, highlight: child.props.highlights, fname: child.props.fname });
} else {
files.push({ ...child.props })
}
}
useEffect(() => {
// scroll to the highlighted line
const highlightedLine = document.querySelector('.theme-code-block-highlighted-line');
if (highlightedLine) {
const files = document.getElementById('files');
const scrollTo = highlightedLine.offsetTop - files.clientHeight / 2;
files.scrollTo({ top: scrollTo, behavior: 'smooth' });
}
}, [lineNumber]);
useEffect(() => {
// check if the window is wide enough to render the code explainer
setIsWideEnough(window.innerWidth > 768);
// #files is sticky, and it "sticks" at the height of the .navbar
const nav = document.querySelector('.navbar');
const files = document.getElementById('files');
files.style.top = `${nav.clientHeight}px`;
// calculate the size of the code explanations
const t0 = document.getElementById(`block0`).getBoundingClientRect().top;
const bN = document.getElementById(`block${blocks.length - 1}`).getBoundingClientRect().bottom;
let blocksHeight = Math.abs(bN - t0);
// the scrollable height is the size of the blocks that we cannot see
// plus an offset if the window is already scrolled
const offset = window.scrollY + t0;
let scrollableHeight
if (blocksHeight < window.innerHeight + offset) {
scrollableHeight = blocksHeight
} else {
scrollableHeight = blocksHeight - window.innerHeight + offset;
}
// add some padding to the end of the blocks so we can always scroll
const oneEm = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (files.clientHeight > blocksHeight) {
const tN = document.getElementById(`block${blocks.length - 1}`).getBoundingClientRect().top;
document.getElementById('extra-padding').style.height = `${files.clientHeight - Math.abs(tN - bN) + oneEm}px`;
}
const handleScroll = () => {
const scrollPercentage = window.scrollY / scrollableHeight;
// select the block that corresponds the percentage of the scroll bar
let acc = 0;
for (let i = 0; i < blocks.length; i++) {
const block = document.getElementById(`block${i}`)
const blockHeight = block.clientHeight;
acc += blockHeight / blocksHeight;
if (acc > scrollPercentage && block.getBoundingClientRect().top < window.innerHeight - 2 * oneEm) {
setLineNumber(blocks[i].highlight);
setActiveBlock(i);
setSelectedFile(blocks[i].fname);
break;
}
}
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<>
<div hidden={isWideEnough}>
<Admonition type="danger">
This page will not render correctly in small screen, consider using the <a href={alternativeURL}>plain text version</a>
</Admonition>
</div>
<div className="row code-explain">
<div className="col-forced--4 col" id="codeblocks">
{blocks.map((block, index) => <InnerBlock selected={activeBlock === index} index={index} text={block.text} />)}
<div id="extra-padding" style={{ width: "100%" }}></div>
</div>
<div className="col-forced--8 col">
<div id="files"
style={{ position: 'sticky', height: '100vh', overflow: 'scroll' }}>
<Tabs className="file-tabs" selectedValue={selectedFile || blocks[0].fname} selectValue={(e) => setSelectedFile(e)}>
{files.map(file =>
<TabItem value={file.fname} >
<InnerFile {...file} lineNumber={lineNumber} />
</TabItem>
)}
</Tabs>
</div>
</div>
</div>
</>
);
}
function InnerBlock({ selected, text, index }) {
const cssState = selected ? 'block-selected' : '';
return <>
<div className={`block ${cssState} padding--sm`} key={index} id={`block${index}`}>
{text}
</div>
</>;
}
function InnerFile({ url, start, end, language, fname, lineNumber }) {
return <>
<Github url={url} start={start} end={end} language={language}
fname={fname} metastring={`{${lineNumber}}`}
/>
</>
}
export function Block({ children }) { return children; }
export function File({ children }) { return children; }
export default { ExplainCode, Block, File };