(this.container = c)} />;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/NewEditor/NewEditor.tsx:
--------------------------------------------------------------------------------
1 | import { basicSetup } from 'codemirror';
2 | import { BabelReponseInterface } from '../../types';
3 | import { EditorView, hoverTooltip } from '@codemirror/view';
4 | import { syntaxHighlighting } from '@codemirror/language';
5 | import { javascript } from '@codemirror/lang-javascript';
6 | import { useEffect } from 'react';
7 | import { codeString, parseCode } from '../../App';
8 |
9 | import {
10 | oneDark,
11 | oneDarkHighlightStyle,
12 | oneDarkTheme,
13 | } from '@codemirror/theme-one-dark';
14 | import { underlineSelection } from './editorUtils';
15 | import { getSentenceContent } from '../../utils';
16 | import { popOverStyles } from './editorStyles';
17 |
18 | const syntaxExtension = syntaxHighlighting(oneDarkHighlightStyle);
19 |
20 | const NewEditor = () => {
21 | useEffect(() => {
22 | let parsedASTData: BabelReponseInterface;
23 |
24 | /*
25 | Logic that decides if a keyword can be hovered to
26 | render our popover.
27 |
28 | TODO: Right now the we are parsing data of our AST is obviously
29 | very static, gotta make this a generic function.
30 | */
31 | const wordHover = hoverTooltip((view, pos, side) => {
32 | let { from, to, text, number: lineNumber } = view.state.doc.lineAt(pos);
33 |
34 | let start = pos,
35 | end = pos;
36 |
37 | while (start > from && /\w/.test(text[start - from - 1])) start--;
38 | while (end < to && /\w/.test(text[end - from])) end++;
39 | if ((start == pos && side < 0) || (end == pos && side > 0)) return null;
40 | const keyword = text.slice(start - from, end - from);
41 |
42 | let returnedObject = null;
43 | parsedASTData.useEffect.forEach(({ startLineNumber }, index) => {
44 | if (lineNumber === startLineNumber) {
45 | const content = getSentenceContent(
46 | parsedASTData.useEffect[index],
47 | parsedASTData
48 | );
49 |
50 | returnedObject = {
51 | pos: start,
52 | end,
53 | above: true,
54 |
55 | create() {
56 | let dom = document.createElement('div');
57 | dom.style.color = 'black';
58 | dom.classList.add(popOverStyles());
59 | dom.innerHTML = content;
60 | return { dom };
61 | },
62 | };
63 | }
64 | });
65 | return returnedObject;
66 | });
67 |
68 | // Initiate codemirror with default extensions
69 | const view = new EditorView({
70 | doc: codeString,
71 | extensions: [
72 | basicSetup,
73 | javascript(),
74 | wordHover,
75 | oneDark,
76 | oneDarkTheme,
77 | syntaxExtension,
78 | ],
79 | parent: document.querySelector('#editor')!,
80 | });
81 |
82 | parseCode(codeString).then((res) => {
83 | parsedASTData = res;
84 | /*
85 | Takes all the AST data from babel and loops through to it
86 | underline all the selections
87 | */
88 | parsedASTData.useEffect.forEach((data) => {
89 | underlineSelection(view, data.start, data.end);
90 | });
91 | });
92 | return () => {
93 | view.destroy();
94 | };
95 | }, []);
96 |
97 | return
;
98 | };
99 |
100 | export default NewEditor;
101 |
--------------------------------------------------------------------------------
/src/components/NewEditor/editorStyles.ts:
--------------------------------------------------------------------------------
1 | import { css, keyframes } from '@stitches/react';
2 |
3 | import {
4 | slideDownAndFade,
5 | slideLeftAndFade,
6 | slideRightAndFade,
7 | slideUpAndFade,
8 | } from '../Popover/Popover';
9 |
10 | const fade = keyframes({
11 | '0%': { opacity: 0 },
12 | '100%': { opacity: 1 },
13 | });
14 |
15 | export const popOverStyles = css({
16 | color: 'Black',
17 | borderRadius: 6,
18 | border: '1px solid #e3e3e3',
19 | padding: '10px 20px',
20 | width: 300,
21 | backgroundColor: 'white',
22 | zIndex: 4,
23 | boxShadow:
24 | 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
25 | '@media (prefers-reduced-motion: no-preference)': {
26 | animationDuration: '400ms',
27 | animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
28 | willChange: 'transform, opacity',
29 | animation: `${fade} 0.2s ease`,
30 | '&[data-state="open"]': {
31 | '&[data-side="top"]': { animationName: slideDownAndFade },
32 | '&[data-side="right"]': { animationName: slideLeftAndFade },
33 | '&[data-side="bottom"]': { animationName: slideUpAndFade },
34 | '&[data-side="left"]': { animationName: slideRightAndFade },
35 | },
36 | },
37 |
38 | '& code': {
39 | border: '1px solid #e3e3e3',
40 | boxShadow: 'rgb(0 0 0 / 4%) 0px 2px 0px',
41 | padding: '1px 3px',
42 | borderRadius: '4px',
43 | background: '#f5f5f51a',
44 | },
45 |
46 | '& p': {
47 | marginTop: 0,
48 | },
49 | });
50 |
--------------------------------------------------------------------------------
/src/components/NewEditor/editorUtils.ts:
--------------------------------------------------------------------------------
1 | import { StateEffect, StateField } from '@codemirror/state';
2 | import { Decoration, DecorationSet, hoverTooltip } from '@codemirror/view';
3 | import { EditorView } from 'codemirror';
4 | import { BabelReponseInterface } from '../../types';
5 | import { getSentenceContent } from '../../utils';
6 | import { popOverStyles } from './editorStyles';
7 |
8 | /*
9 | Logic for Underlining the keywords we want to
10 | highlight that can be hovered
11 | */
12 | export const underlineTheme = EditorView.baseTheme({
13 | '.cm-underline': { paddingBottom: 2, borderBottom: '2px dashed #13d21d' },
14 | });
15 | const addUnderline = StateEffect.define<{ from: number; to: number }>({
16 | map: ({ from, to }, change) => ({
17 | from: change.mapPos(from),
18 | to: change.mapPos(to),
19 | }),
20 | });
21 | export const underlineMark = Decoration.mark({ class: 'cm-underline' });
22 | export const underlineField = StateField.define
({
23 | create() {
24 | return Decoration.none;
25 | },
26 | update(underlines, tr) {
27 | underlines = underlines.map(tr.changes);
28 | for (let e of tr.effects)
29 | if (e.is(addUnderline)) {
30 | underlines = underlines.update({
31 | add: [underlineMark.range(e.value.from, e.value.to)],
32 | });
33 | }
34 | return underlines;
35 | },
36 | provide: (f) => EditorView.decorations.from(f),
37 | });
38 |
39 | export function underlineSelection(view: EditorView, from: number, to: number) {
40 | let effects: StateEffect[] = [addUnderline.of({ from, to })];
41 | if (!effects.length) return false;
42 |
43 | if (!view.state.field(underlineField, false))
44 | effects.push(StateEffect.appendConfig.of([underlineField, underlineTheme]));
45 | view.dispatch({ effects });
46 | return true;
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Popover/Popover.tsx:
--------------------------------------------------------------------------------
1 | import { styled, keyframes } from '@stitches/react';
2 | import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
3 |
4 | export const slideUpAndFade = keyframes({
5 | '0%': { opacity: 0, transform: 'translateY(2px)' },
6 | '100%': { opacity: 1, transform: 'translateY(0)' },
7 | });
8 |
9 | export const slideRightAndFade = keyframes({
10 | '0%': { opacity: 0, transform: 'translateX(-2px)' },
11 | '100%': { opacity: 1, transform: 'translateX(0)' },
12 | });
13 |
14 | export const slideDownAndFade = keyframes({
15 | '0%': { opacity: 0, transform: 'translateY(-2px)' },
16 | '100%': { opacity: 1, transform: 'translateY(0)' },
17 | });
18 |
19 | export const slideLeftAndFade = keyframes({
20 | '0%': { opacity: 0, transform: 'translateX(2px)' },
21 | '100%': { opacity: 1, transform: 'translateX(0)' },
22 | });
23 |
24 | const StyledContent = styled(HoverCardPrimitive.Content, {
25 | color: 'Black',
26 | borderRadius: 6,
27 | border: '1px solid #e3e3e3',
28 | padding: '10px 20px',
29 | width: 300,
30 | backgroundColor: 'white',
31 | zIndex: 4,
32 | boxShadow:
33 | 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
34 | '@media (prefers-reduced-motion: no-preference)': {
35 | animationDuration: '400ms',
36 | animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
37 | willChange: 'transform, opacity',
38 | '&[data-state="open"]': {
39 | '&[data-side="top"]': { animationName: slideDownAndFade },
40 | '&[data-side="right"]': { animationName: slideLeftAndFade },
41 | '&[data-side="bottom"]': { animationName: slideUpAndFade },
42 | '&[data-side="left"]': { animationName: slideRightAndFade },
43 | },
44 | },
45 |
46 | '& code': {
47 | border: '1px solid #e3e3e3',
48 | boxShadow: 'rgb(0 0 0 / 4%) 0px 2px 0px',
49 | padding: '1px 3px',
50 | borderRadius: '4px',
51 | background: '#f5f5f51a',
52 | },
53 | });
54 |
55 | const StyledArrow = styled(HoverCardPrimitive.Arrow, {
56 | fill: 'white',
57 | });
58 |
59 | const Content: React.FC<{ children: JSX.Element }> = ({
60 | children,
61 | ...props
62 | }) => {
63 | return (
64 |
65 |
66 | {children}
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export const HoverCard = HoverCardPrimitive.Root;
74 | export const HoverCardTrigger = HoverCardPrimitive.Trigger;
75 | export const HoverCardContent = Content;
76 |
77 | export const HoverCardDemo = ({
78 | children,
79 | content,
80 | }: {
81 | content: string;
82 | children?: React.ReactNode;
83 | }) => (
84 |
85 | {children}
86 |
87 |
88 |
89 |
90 | );
91 |
--------------------------------------------------------------------------------
/src/highlight.css:
--------------------------------------------------------------------------------
1 | .highlight {
2 | background-color: #efefef;
3 | padding: 7px 7px 7px 10px;
4 | border: 1px solid #ddd;
5 | overflow: hidden;
6 | }
7 |
8 | .code {
9 | font-family:'Bitstream Vera Sans Mono','Courier', monospace;
10 | }
11 |
12 | .highlight .c { color: #586E75 } /* Comment */
13 | .highlight .err { color: #93A1A1 } /* Error */
14 | .highlight .g { color: #93A1A1 } /* Generic */
15 | .highlight .k { color: #859900 } /* Keyword */
16 | .highlight .l { color: #93A1A1 } /* Literal */
17 | .highlight .n { color: #93A1A1 } /* Name */
18 | .highlight .o { color: #859900 } /* Operator */
19 | .highlight .x { color: #CB4B16 } /* Other */
20 | .highlight .p { color: #93A1A1 } /* Punctuation */
21 | .highlight .cm { color: #586E75 } /* Comment.Multiline */
22 | .highlight .cp { color: #859900 } /* Comment.Preproc */
23 | .highlight .c1 { color: #586E75 } /* Comment.Single */
24 | .highlight .cs { color: #859900 } /* Comment.Special */
25 | .highlight .gd { color: #2AA198 } /* Generic.Deleted */
26 | .highlight .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */
27 | .highlight .gr { color: #DC322F } /* Generic.Error */
28 | .highlight .gh { color: #CB4B16 } /* Generic.Heading */
29 | .highlight .gi { color: #859900 } /* Generic.Inserted */
30 | .highlight .go { color: #93A1A1 } /* Generic.Output */
31 | .highlight .gp { color: #93A1A1 } /* Generic.Prompt */
32 | .highlight .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */
33 | .highlight .gu { color: #CB4B16 } /* Generic.Subheading */
34 | .highlight .gt { color: #93A1A1 } /* Generic.Traceback */
35 | .highlight .kc { color: #CB4B16 } /* Keyword.Constant */
36 | .highlight .kd { color: #268BD2 } /* Keyword.Declaration */
37 | .highlight .kn { color: #859900 } /* Keyword.Namespace */
38 | .highlight .kp { color: #859900 } /* Keyword.Pseudo */
39 | .highlight .kr { color: #268BD2 } /* Keyword.Reserved */
40 | .highlight .kt { color: #DC322F } /* Keyword.Type */
41 | .highlight .ld { color: #93A1A1 } /* Literal.Date */
42 | .highlight .m { color: #2AA198 } /* Literal.Number */
43 | .highlight .s { color: #2AA198 } /* Literal.String */
44 | .highlight .na { color: #93A1A1 } /* Name.Attribute */
45 | .highlight .nb { color: #B58900 } /* Name.Builtin */
46 | .highlight .nc { color: #268BD2 } /* Name.Class */
47 | .highlight .no { color: #CB4B16 } /* Name.Constant */
48 | .highlight .nd { color: #268BD2 } /* Name.Decorator */
49 | .highlight .ni { color: #CB4B16 } /* Name.Entity */
50 | .highlight .ne { color: #CB4B16 } /* Name.Exception */
51 | .highlight .nf { color: #268BD2 } /* Name.Function */
52 | .highlight .nl { color: #93A1A1 } /* Name.Label */
53 | .highlight .nn { color: #93A1A1 } /* Name.Namespace */
54 | .highlight .nx { color: #555 } /* Name.Other */
55 | .highlight .py { color: #93A1A1 } /* Name.Property */
56 | .highlight .nt { color: #268BD2 } /* Name.Tag */
57 | .highlight .nv { color: #268BD2 } /* Name.Variable */
58 | .highlight .ow { color: #859900 } /* Operator.Word */
59 | .highlight .w { color: #93A1A1 } /* Text.Whitespace */
60 | .highlight .mf { color: #2AA198 } /* Literal.Number.Float */
61 | .highlight .mh { color: #2AA198 } /* Literal.Number.Hex */
62 | .highlight .mi { color: #2AA198 } /* Literal.Number.Integer */
63 | .highlight .mo { color: #2AA198 } /* Literal.Number.Oct */
64 | .highlight .sb { color: #586E75 } /* Literal.String.Backtick */
65 | .highlight .sc { color: #2AA198 } /* Literal.String.Char */
66 | .highlight .sd { color: #93A1A1 } /* Literal.String.Doc */
67 | .highlight .s2 { color: #2AA198 } /* Literal.String.Double */
68 | .highlight .se { color: #CB4B16 } /* Literal.String.Escape */
69 | .highlight .sh { color: #93A1A1 } /* Literal.String.Heredoc */
70 | .highlight .si { color: #2AA198 } /* Literal.String.Interpol */
71 | .highlight .sx { color: #2AA198 } /* Literal.String.Other */
72 | .highlight .sr { color: #DC322F } /* Literal.String.Regex */
73 | .highlight .s1 { color: #2AA198 } /* Literal.String.Single */
74 | .highlight .ss { color: #2AA198 } /* Literal.String.Symbol */
75 | .highlight .bp { color: #268BD2 } /* Name.Builtin.Pseudo */
76 | .highlight .vc { color: #268BD2 } /* Name.Variable.Class */
77 | .highlight .vg { color: #268BD2 } /* Name.Variable.Global */
78 | .highlight .vi { color: #268BD2 } /* Name.Variable.Instance */
79 | .highlight .il { color: #2AA198 } /* Literal.Number.Integer.Long */
80 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import "./highlight.css";
2 |
3 | :root {
4 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
5 | font-size: 16px;
6 | line-height: 24px;
7 | font-weight: 400;
8 |
9 | color-scheme: light dark;
10 | color: rgba(255, 255, 255, 0.87);
11 | background-color: #242424;
12 |
13 | font-synthesis: none;
14 | text-rendering: optimizeLegibility;
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | -webkit-text-size-adjust: 100%;
18 | }
19 |
20 | a {
21 | font-weight: 500;
22 | color: #646cff;
23 | text-decoration: inherit;
24 | }
25 |
26 | a:hover {
27 | color: #535bf2;
28 | }
29 |
30 | body {
31 | margin: 0;
32 | display: flex;
33 | place-items: center;
34 | min-width: 320px;
35 | min-height: 100vh;
36 | }
37 |
38 | h1 {
39 | font-size: 3.2em;
40 | line-height: 1.1;
41 | }
42 |
43 | button {
44 | border-radius: 8px;
45 | border: 1px solid transparent;
46 | padding: 0.6em 1.2em;
47 | font-size: 1em;
48 | font-weight: 500;
49 | font-family: inherit;
50 | background-color: #1a1a1a;
51 | cursor: pointer;
52 | transition: border-color 0.25s;
53 | }
54 |
55 | button:hover {
56 | border-color: #646cff;
57 | }
58 |
59 | button:focus,
60 | button:focus-visible {
61 | outline: 4px auto -webkit-focus-ring-color;
62 | }
63 |
64 | @media (prefers-color-scheme: light) {
65 | :root {
66 | color: #213547;
67 | background-color: #ffffff;
68 | }
69 |
70 | a:hover {
71 | color: #747bff;
72 | }
73 |
74 | button {
75 | background-color: #f9f9f9;
76 | }
77 | }
78 |
79 |
80 | /* ------------- ================ --------------- */
81 |
82 |
83 |
84 |
85 |
86 |
87 | html {
88 | font-size: 14px;
89 | box-sizing: border-box;
90 | font-family: Verdana, sans-serif;
91 | }
92 |
93 | body {
94 | margin: 0;
95 | }
96 |
97 | .CodeMirror-line {
98 | text-align: left;
99 | }
100 |
101 | #page {
102 | display: flex;
103 | flex-direction: column;
104 | height: 100vh;
105 | /* prevents the page to growing larger than the viewport*/
106 | max-height: 100vh;
107 | }
108 |
109 | #container {
110 | flex: 1;
111 | /* for Firefox, otherwise it overflows the parent*/
112 | min-height: 0;
113 | }
114 |
115 | #main {
116 | display: flex;
117 | flex-direction: column;
118 | height: 100%;
119 | }
120 |
121 | #contribution {
122 | font-size: 0.9em;
123 | color: #555;
124 | height: 25px;
125 | text-align: center;
126 | line-height: 25px;
127 | background-color: #efefef;
128 | border-top: 1px solid #ddd;
129 | }
130 |
131 | .cover {
132 | position: fixed;
133 | top: 0;
134 | bottom: 0;
135 | left: 0;
136 | right: 0;
137 | z-index: 200;
138 | background-color: rgba(255, 255, 255, 0.7);
139 | display: flex;
140 | align-items: center;
141 | justify-content: center;
142 | }
143 |
144 | .hasError {
145 | filter: blur(3px);
146 | }
147 |
148 | .loadingIndicator {
149 | font-size: 200%;
150 | color: #555;
151 | }
152 |
153 | .dropIndicator {
154 | position: fixed;
155 | top: 0;
156 | bottom: 0;
157 | left: 0;
158 | right: 0;
159 | z-index: 200;
160 | background-color: rgba(255, 255, 255, 0.7);
161 | display: flex;
162 | align-items: center;
163 | justify-content: center;
164 | padding: 20px;
165 | border: 3px dashed #888;
166 | border-radius: 10px;
167 | }
168 |
169 | .dropIndicator>div {
170 | max-width: 90%;
171 | color: #888;
172 | font-size: 32px;
173 | }
174 |
175 | .banner {
176 | background-color: #b8dff7;
177 | border-right: 1px solid #ddd;
178 | padding: 8px;
179 | text-align: center;
180 | }
181 |
182 | #Toolbar {
183 | flex: 0 0 auto;
184 | font-family: monospace;
185 | line-height: 32px;
186 | border-bottom: 1px solid #ddd;
187 | color: #454545;
188 | padding-left: 10px;
189 | padding-right: 10px;
190 | z-index: 200;
191 | display: flex;
192 | flex-wrap: wrap;
193 | }
194 |
195 | #Toolbar>* {
196 | flex: 0 0 auto;
197 | border-right: 1px solid #ddd;
198 | }
199 |
200 | #Toolbar,
201 | #Toolbar .menuButton ul {
202 | background-color: #efefef;
203 | }
204 |
205 | #Toolbar>*,
206 | #Toolbar>.menuButton>span,
207 | #Toolbar button {
208 | background-color: transparent;
209 | box-sizing: border-box;
210 | color: inherit;
211 | font-family: inherit;
212 | font-size: 16px;
213 | min-width: 90px;
214 | outline: none;
215 | }
216 |
217 | #Toolbar>.menuButton>span {
218 | cursor: default;
219 | padding: 0px 6px;
220 | }
221 |
222 | #Toolbar>* button {
223 | height: 100%;
224 | border: none;
225 | cursor: pointer;
226 | }
227 |
228 | #Toolbar>h1 {
229 | padding: 0;
230 | padding-right: 10px;
231 | margin: 0;
232 | font-size: 18px;
233 | }
234 |
235 | #Toolbar>a {
236 | color: inherit;
237 | text-decoration: none;
238 | }
239 |
240 | #Toolbar .menuButton:hover>ul {
241 | display: block;
242 | }
243 |
244 | #Toolbar .menuButton ul {
245 | position: fixed;
246 | padding: 0;
247 | margin: 0;
248 | list-style: none;
249 | display: none;
250 | border: 1px solid #ddd;
251 | max-height: calc(100vh - 65px);
252 | overflow-y: auto;
253 | z-index: 500;
254 | }
255 |
256 | #Toolbar .menuButton ul li {
257 | white-space: nowrap;
258 | }
259 |
260 | #Toolbar .menuButton ul button {
261 | line-height: 28px;
262 | padding-top: 2px;
263 | padding-bottom: 2px;
264 | /* for scrollbar */
265 | padding-right: 15px;
266 | text-align: left;
267 | width: 100%;
268 | }
269 |
270 | #Toolbar>*.disabled,
271 | #Toolbar button:disabled,
272 | #Toolbar button:disabled:hover,
273 | #Toolbar button:disabled:active {
274 | background-color: transparent;
275 | color: #888;
276 | cursor: default;
277 | }
278 |
279 | #Toolbar .menuButton li.selected {
280 | background-color: rgba(0, 0, 0, 0.05);
281 | }
282 |
283 | #Toolbar>a:hover,
284 | #Toolbar>.button:hover,
285 | #Toolbar ul button:hover {
286 | background-color: rgba(0, 0, 0, 0.1);
287 | }
288 |
289 | #Toolbar>a:active,
290 | #Toolbar button:active {
291 | background-color: rgba(0, 0, 0, 0.3);
292 | }
293 |
294 | #info {
295 | color: #898989;
296 | cursor: default;
297 | border: none;
298 | margin-left: auto;
299 | }
300 |
301 | #info.small {
302 | font-size: 12px;
303 | line-height: 1.3em;
304 | }
305 |
306 | .errorMessage {
307 | font-family: Verdana, non-serif;
308 | font-size: 1.2em;
309 | }
310 |
311 | .errorMessage h3 {
312 | padding-top: 0;
313 | margin-top: 0;
314 | color: #CC0000;
315 | }
316 |
317 | .splitpane-content {
318 | flex: 1;
319 | /* for Firefox, otherwise it overflows the parent*/
320 | min-height: 0;
321 | min-width: 0;
322 | }
323 |
324 | .splitpane {
325 | flex: 1;
326 | /* for Firefox, otherwise it overflows the parent*/
327 | min-height: 0;
328 | min-width: 0;
329 | }
330 |
331 | .splitpane-divider {
332 | background-color: #ddd;
333 | }
334 |
335 | .splitpane-divider.horizontal {
336 | width: 5px;
337 | }
338 |
339 | .splitpane-divider.vertical {
340 | height: 5px;
341 | }
342 |
343 | .splitpane-divider:hover {
344 | background-color: #999;
345 | cursor: col-resize;
346 | }
347 |
348 | .splitpane-divider.vertical:hover {
349 | cursor: row-resize;
350 | }
351 |
352 | .output {
353 | flex: 1;
354 | display: flex;
355 | flex-direction: column;
356 | border: none;
357 | padding: 0;
358 | }
359 |
360 | .output .toolbar {
361 | font-size: 14px;
362 | margin-left: -1px;
363 | border-bottom: 1px solid #ddd;
364 | }
365 |
366 | .output .toolbar>button {
367 | margin: 0;
368 | height: 100%;
369 | min-width: 90px;
370 | border: 1px solid transparent;
371 | border-left: 1px solid #ddd;
372 | border-right: 1px solid #ddd;
373 | font-size: 14px;
374 | background-color: transparent;
375 | display: inline-block;
376 | vertical-align: top;
377 | outline: none;
378 | cursor: pointer;
379 | }
380 |
381 | .output .toolbar>button.active {
382 | border-color: #999;
383 | background-color: #999;
384 | color: #f5f5f5;
385 | }
386 |
387 | .output .toolbar .time {
388 | float: right;
389 | margin-right: 10px;
390 | font-size: 10px;
391 | line-height: 25px;
392 | }
393 |
394 | .output>.container {
395 | overflow: auto;
396 | flex: 1;
397 | display: flex;
398 | }
399 |
400 | .output>.no-toolbar {
401 | top: 0;
402 | }
403 |
404 | #JSONEditor .CodeMirror {
405 | font-size: 0.9em;
406 | }
407 |
408 | #JSONEditor .CodeMirror,
409 | #JSONEditor .CodeMirror-gutters {
410 | background-color: #efefef;
411 | }
412 |
413 | .editor {
414 | display: flex;
415 | flex: 1;
416 | /* needed to make editor at most as wide as the parent splitpane*/
417 | min-width: 0;
418 | min-height: 0;
419 | }
420 |
421 | li.entry {
422 | margin: 0;
423 | list-style: none;
424 | padding: 5px;
425 | position: relative;
426 | }
427 |
428 | .CodeMirror .marked,
429 | .entry.highlighted {
430 | border-radius: 2px;
431 | background-color: rgba(255, 240, 6, 0.4);
432 | }
433 |
434 | .entry>.value {
435 | white-space: pre-wrap;
436 | }
437 |
438 | .entry>.value .s {
439 | cursor: text;
440 | -webkit-user-select: text;
441 | -khtml-user-select: text;
442 | -moz-user-select: text;
443 | -ms-user-select: text;
444 | user-select: text;
445 | }
446 |
447 | .entry.toggable::before {
448 | content: '+';
449 | color: green;
450 | position: absolute;
451 | left: -10px;
452 | }
453 |
454 | .entry.toggable.open::before {
455 | content: '-';
456 | color: red;
457 | }
458 |
459 | .entry .invokeable {
460 | cursor: pointer;
461 | }
462 |
463 | .entry .invokeable:hover {
464 | text-decoration: underline;
465 | }
466 |
467 | .placeholder {
468 | font-size: 0.9em;
469 | }
470 |
471 | .compact,
472 | .tokenName,
473 | .entry.toggable>.key {
474 | cursor: pointer;
475 | }
476 |
477 | .compact:hover,
478 | .tokenName:hover,
479 | .entry.toggable>.key:hover>.name {
480 | text-decoration: underline;
481 | }
482 |
483 | .CodeMirror {
484 | height: auto;
485 | flex: 1;
486 | }
487 |
488 | .CodeMirror-scroll {
489 | overflow: auto;
490 | }
491 |
492 | .editor .CodeMirror-gutters {
493 | background-color: white;
494 | border: none;
495 | }
496 |
497 | .CodeMirror .ErrorGutter {
498 | width: .7em;
499 | }
500 |
501 | .CodeMirror pre.errorMarker {
502 | background-color: #EB9494;
503 | }
504 |
505 | /* Dialog */
506 | .dialog {
507 | align-items: center;
508 | background-color: rgba(255, 255, 255, 0.7);
509 | bottom: 0;
510 | color: #333;
511 | display: flex;
512 | justify-content: center;
513 | left: 0;
514 | position: absolute;
515 | right: 0;
516 | top: 0;
517 | z-index: 1000;
518 | }
519 |
520 | .dialog .inner {
521 | max-height: 90vh;
522 | background-color: white;
523 | box-shadow: 0px 0px 10px #555;
524 | border-radius: 3px;
525 | min-width: 400px;
526 | display: flex;
527 | flex-direction: column;
528 | }
529 |
530 | .dialog .header {
531 | flex-shrink: 0;
532 | padding: 10px 10px 0px 10px;
533 | }
534 |
535 | .dialog .body {
536 | overflow: auto;
537 | padding: 10px;
538 | }
539 |
540 | .dialog .footer {
541 | flex-shrink: 0;
542 | padding: 0 10px 10px 10px;
543 | text-align: right;
544 | }
545 |
546 | .dialog .inner h3 {
547 | margin: 0 0 10px 0;
548 | padding: 0;
549 | }
550 |
551 | #SettingsDialog ul.settings {
552 | margin: 0;
553 | padding: 0;
554 | list-style: none;
555 | }
556 |
557 | #SettingsDialog ul.settings li {
558 | padding: 3px 0;
559 | }
560 |
561 |
562 | body .CodeMirror-hints,
563 | body .CodeMirror-Tern-tooltip {
564 | z-index: 1000;
565 | }
566 |
567 | .shareInfo dd {
568 | margin: 0;
569 | margin-top: 5px;
570 | margin-bottom: 10px;
571 | }
572 |
573 | .shareInfo input {
574 | font-size: 15px;
575 | padding: 5px;
576 | width: calc(100% - 10px);
577 | }
578 |
579 | .toggleBtn {
580 | position: absolute;
581 | right: 0;
582 | z-index: 10;
583 | cursor: pointer;
584 | outline: none;
585 | }
586 |
587 | .toggleBtn>.btnText {
588 | padding-left: 5px;
589 | font-size: 12px;
590 | }
591 |
592 | .settings-drawer__expanded {
593 | width: 200px;
594 | border-right: 1px solid #ddd;
595 | }
596 |
597 | .settings-drawer__collapsed {
598 | width: 20px;
599 | background-color: #ddd;
600 | }
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 | import './index.css';
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7 |
8 | );
9 |
--------------------------------------------------------------------------------
/src/plugin.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import jsx from '@babel/plugin-syntax-jsx';
3 |
4 | export default () => {
5 | return {
6 | inherits: jsx,
7 | visitor: {
8 | Program: {
9 | enter() {
10 | this.tree = [];
11 | this.data = {};
12 | },
13 | exit(_, state) {
14 | // state.opts.onTreeReady(this.tree[0]);
15 | state.opts.onLineNumbersReady(this.data);
16 | },
17 | },
18 | JSXElement: {
19 | enter(path) {
20 | this.tree.push({
21 | name: path.node.openingElement.name.name,
22 | start: path.node.start,
23 | end: path.node.end,
24 | children: [],
25 | });
26 | },
27 | exit() {
28 | if (this.tree.length > 1) {
29 | const child = this.tree.pop();
30 | const parent = this.tree[this.tree.length - 1];
31 | parent.children.push(child);
32 | }
33 | },
34 | },
35 | CallExpression: {
36 | enter(path) {
37 | // console.log(path, 'PATHHH');
38 | if (path.node.callee.name === 'useEffect') {
39 | console.log('DETECTED USEEFFECT', path);
40 |
41 | const dependencyTree = { ...(this.data.dependencyTree || {}) };
42 | path.node.arguments[1].elements.forEach(({ name }) => {
43 | dependencyTree[name] = dependencyTree[name] + 1 || 1;
44 | });
45 |
46 | // checking if depen array is present
47 | if (path.node.arguments.length > 1) {
48 | this.data.dependencyTree = dependencyTree;
49 | this.data['useEffect'] = [
50 | ...(this.data['useEffect'] || []),
51 | {
52 | dependencies: [...path.node.arguments[1].elements],
53 | startLineNumber: path.node.loc.start.line,
54 | endLineNumber: path.node.loc.end.line,
55 | start: path.node.callee.start,
56 | end: path.node.callee.end,
57 | },
58 | ];
59 | }
60 | }
61 | },
62 | },
63 | },
64 | };
65 | };
66 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type UseEffectBabelResponse = {
2 | startLineNumber: number;
3 | endLineNumber: number;
4 | start: number;
5 | end: number;
6 | dependencies: { name: string }[];
7 | };
8 |
9 | export interface BabelReponseInterface {
10 | useEffect: UseEffectBabelResponse[];
11 | dependencyTree: { [key: string]: number };
12 | }
13 |
14 | export type HighlighterStateInterface = {
15 | top: number | string;
16 | height: number | string;
17 | content: string;
18 | };
19 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { micromark } from 'micromark';
3 | import {
4 | BabelReponseInterface,
5 | HighlighterStateInterface,
6 | UseEffectBabelResponse,
7 | } from './types';
8 |
9 | export const getHighlighterCoords = (res: BabelReponseInterface) => {
10 | const lineNumberElements = document.querySelectorAll(
11 | '.CodeMirror-linenumber'
12 | );
13 | const EditorElement = document.querySelector('.editor');
14 | const finalUseEffectPositionArray: HighlighterStateInterface[] = [];
15 |
16 | res.useEffect.forEach((effect) => {
17 | const startLineNumberElement =
18 | lineNumberElements[effect.startLineNumber - 1];
19 |
20 | const endLineNumberElement = lineNumberElements[effect.endLineNumber - 1];
21 |
22 | const startLineNumberPositionObject =
23 | startLineNumberElement.getBoundingClientRect();
24 | const EditorElementPositionObject = EditorElement?.getBoundingClientRect();
25 |
26 | console.log(getSentenceContent(effect, res), 'content');
27 | finalUseEffectPositionArray.push({
28 | top:
29 | startLineNumberPositionObject.top -
30 | (EditorElementPositionObject?.top || 0),
31 | height:
32 | (effect.endLineNumber - effect.startLineNumber + 1) *
33 | startLineNumberPositionObject.height,
34 | content: getSentenceContent(effect, res) || '',
35 | });
36 | });
37 |
38 | return finalUseEffectPositionArray;
39 | };
40 |
41 | export const getSentenceContent = (
42 | useEffectData: UseEffectBabelResponse,
43 | res: BabelReponseInterface
44 | ) => {
45 | const dependencyTree = res.dependencyTree;
46 | let finalContent = `
47 | ## useEffect
48 | The Effect Hook lets you perform side effects in function components.
49 |
50 | This useEffect depends on `;
51 | const dependenciesArray = useEffectData.dependencies;
52 | console.log(dependenciesArray, 'DEPE ARRAY', res);
53 |
54 | // TODO: make this work for more than 3 items in array
55 | if (dependenciesArray.length < 2) {
56 | return micromark(`${finalContent} \`${dependenciesArray[0].name}\``);
57 | } else {
58 | return micromark(
59 | `${finalContent} \`${dependenciesArray[0].name}\` and \`${
60 | dependenciesArray[1].name
61 | }\`
62 |
63 | ${
64 | dependencyTree[dependenciesArray[0].name] > 1
65 | ? dependencyTree[dependenciesArray[0].name] +
66 | 'useEffects are dependedant on' +
67 | dependenciesArray[0].name
68 | : ''
69 | }
70 | ${
71 | dependencyTree[dependenciesArray[1].name] > 1
72 | ? dependencyTree[dependenciesArray[1].name] +
73 | ' useEffects are dependedant on `' +
74 | dependenciesArray[1].name +
75 | '`'
76 | : ''
77 | }
78 | `
79 | );
80 | }
81 | };
82 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | });
8 |
--------------------------------------------------------------------------------