├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── __Screenshots__
├── sc1.png
├── sc2.png
└── sc3.png
├── package-lock.json
├── package.json
├── public
├── esbuild.wasm
├── favicon.ico
├── iframes.html
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.tsx
├── Assets
│ └── resize.png
├── Bundler
│ └── index.ts
├── Redux
│ ├── action-creators
│ │ └── index.ts
│ ├── action-types
│ │ └── index.ts
│ ├── actions
│ │ └── index.ts
│ ├── index.ts
│ ├── middlewares
│ │ └── bundlerMiddleware.ts
│ ├── reducers
│ │ ├── bundleReducer.ts
│ │ ├── cellReducer.ts
│ │ └── index.ts
│ └── store.ts
├── components
│ ├── ActionBar
│ │ ├── ActionBar.tsx
│ │ └── styles.ts
│ ├── AddCell
│ │ ├── AddCell.tsx
│ │ └── styles.ts
│ ├── CellList
│ │ ├── CellList.tsx
│ │ ├── CellListItem
│ │ │ ├── CellItem.tsx
│ │ │ └── styles.tsx
│ │ └── styles.ts
│ ├── CodeCell
│ │ ├── Cell.tsx
│ │ └── styles.ts
│ ├── CodeEditor
│ │ ├── Editor.tsx
│ │ ├── styles.ts
│ │ └── types.d.ts
│ ├── MarkdownEditor
│ │ ├── MarkdownEditor.tsx
│ │ ├── resizerReset.css
│ │ └── styles.ts
│ ├── PreviewWindow
│ │ └── PreviewWindow.tsx
│ └── ResizableCell
│ │ ├── ResizableCell.tsx
│ │ └── styles.css
├── hooks
│ ├── useActions.ts
│ └── useTypedSelector.ts
├── index.tsx
├── interfaces
│ ├── ActionBar
│ │ └── ActionBarInterface.ts
│ ├── Bundle
│ │ ├── BundleInterface.ts
│ │ └── BundleMiddlewareInterface.ts
│ ├── Cell
│ │ ├── AddCellInterface.ts
│ │ └── CellInterface.ts
│ ├── CodeEditor
│ │ └── EditorPropsInterface.ts
│ ├── Plugins
│ │ └── unpkgPluginInterface.ts
│ ├── PreviewWindow
│ │ └── PreviewWindowInterface.ts
│ ├── ResizableCell
│ │ └── ResizablePropsInterface.ts
│ └── index.ts
├── plugins
│ ├── index.ts
│ ├── unpkgFetchPackagePlugin.ts
│ └── unpkgPathPlugin.ts
├── react-app-env.d.ts
├── styles.ts
└── theme.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 | 
4 | 
5 |
6 | # JS NOTE: Notebook for javascript
7 |
8 | Execute code, document ideas in full markdown syntax in a fast paced cell interface (like jupiter notebook).
9 | Skip setting up a dev environment and code directly.
10 |
11 | ## Features:
12 |
13 | 🔷 All the stuff happens client side, no servers involved.
14 | 🔷 Dynamic import of NPM modules on the fly!
15 | 🔷 No configurations required!
16 |
17 | ## Built with:
18 |
19 | - React.js with Typescript!
20 | - ES-Build as transpiler and bundler!
21 |
22 | ## Screenshots:
23 | ### Cell view
24 | 
25 | ### Markdown cell
26 | 
27 | ### Code cell
28 | 
29 |
30 | ## Setting up Local Dev Environment:
31 | ### Requirements:
32 | - Node >= 10.x.x
33 |
34 | clone the project in your workspace and run npm i
35 | After installation of dependencies, run npm start
36 |
37 | that's it!
38 |
39 | ## Future plans
40 | - Publishing to npm as a package to facilitate direct invoking of app through terminal.
41 | - Developing a social coding experience like codepen.
42 |
43 |
--------------------------------------------------------------------------------
/__Screenshots__/sc1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/__Screenshots__/sc1.png
--------------------------------------------------------------------------------
/__Screenshots__/sc2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/__Screenshots__/sc2.png
--------------------------------------------------------------------------------
/__Screenshots__/sc3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/__Screenshots__/sc3.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-transpiler-bundler",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.3",
7 | "@material-ui/icons": "^4.11.2",
8 | "@monaco-editor/react": "^4.1.1",
9 | "@testing-library/jest-dom": "^5.11.9",
10 | "@testing-library/react": "^11.2.5",
11 | "@testing-library/user-event": "^12.8.3",
12 | "@types/jest": "^26.0.21",
13 | "@types/node": "^12.20.6",
14 | "@types/prettier": "^2.2.3",
15 | "@types/react": "^17.0.3",
16 | "@types/react-dom": "^17.0.2",
17 | "@types/react-redux": "^7.1.16",
18 | "@types/react-resizable": "^1.7.2",
19 | "@uiw/react-md-editor": "2.1.1",
20 | "axios": "^0.21.1",
21 | "esbuild": "^0.9.6",
22 | "esbuild-wasm": "^0.9.6",
23 | "immer": "^9.0.1",
24 | "localforage": "^1.9.0",
25 | "monaco-editor": "^0.23.0",
26 | "nanoid": "^3.1.22",
27 | "prettier": "^2.2.1",
28 | "react": "^17.0.1",
29 | "react-dom": "^17.0.1",
30 | "react-redux": "^7.2.3",
31 | "react-resizable": "^1.11.1",
32 | "react-scripts": "4.0.3",
33 | "redux": "^4.0.5",
34 | "redux-thunk": "^2.3.0",
35 | "typescript": "^4.2.3",
36 | "web-vitals": "^1.1.1"
37 | },
38 | "scripts": {
39 | "start": "react-scripts start",
40 | "build": "react-scripts build",
41 | "test": "react-scripts test",
42 | "eject": "react-scripts eject"
43 | },
44 | "eslintConfig": {
45 | "extends": [
46 | "react-app",
47 | "react-app/jest"
48 | ]
49 | },
50 | "browserslist": {
51 | "production": [
52 | ">0.2%",
53 | "not dead",
54 | "not op_mini all"
55 | ],
56 | "development": [
57 | "last 1 chrome version",
58 | "last 1 firefox version",
59 | "last 1 safari version"
60 | ]
61 | },
62 | "devDependencies": {
63 | "gh-pages": "^3.1.0",
64 | "redux-devtools-extension": "^2.13.9"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/esbuild.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/public/esbuild.wasm
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/public/favicon.ico
--------------------------------------------------------------------------------
/public/iframes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Iframe
9 |
10 |
11 |
12 | Hi there
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
26 | React App
27 |
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import * as esbuild from "esbuild-wasm";
3 | import CellList from "./components/CellList/CellList";
4 | import makeStyles from "./styles";
5 | import { CssBaseline, ThemeProvider } from "@material-ui/core";
6 | import theme from "./theme";
7 |
8 | const App: React.FC = () => {
9 | const classes = makeStyles();
10 |
11 | /**
12 | *In the old version, .startService() returned a promise that resolved to a service object.
13 | But in the new version, .initialize() returns a promise that resolves to undefined.
14 | This is an intentional design change. There is no more service object.
15 | Instead, you just call transform and build on esbuild's API object itself.
16 | *
17 | */
18 |
19 | // Start es-build service once
20 |
21 | /* I'm stopping this to isolate it during markdown. Turn it back on again */
22 | const startService = async () => {
23 | await esbuild.initialize({
24 | wasmURL: "/esbuild.wasm",
25 | worker: true,
26 | });
27 | };
28 | useEffect(() => {
29 | startService();
30 | }, []);
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default App;
43 |
--------------------------------------------------------------------------------
/src/Assets/resize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoumavaBanerjee/JS-Note/b4250c7f833a0b8d0c727f51a1373d3e2b264192/src/Assets/resize.png
--------------------------------------------------------------------------------
/src/Bundler/index.ts:
--------------------------------------------------------------------------------
1 | import * as esbuild from "esbuild-wasm";
2 | import * as plugins from "../plugins/index";
3 |
4 | // eslint-disable-next-line import/no-anonymous-default-export
5 | const bundle = async (rawCode: string) => {
6 | try {
7 | /**
8 | * @EntryPoint entry file to start bundling,
9 | * @bundle bundling should occur or not
10 | * @write return the file as an in-memory buffer
11 | * @color colored warnings
12 | * @define Define environment variable value
13 | * @plugin Define all custom written plugins
14 | */
15 |
16 | const bunduledCode = await esbuild.build({
17 | entryPoints: ["index.js"],
18 | bundle: true,
19 | write: false,
20 | color: true,
21 | define: {
22 | "process.env.NODE_ENV": '"development"', // set development to a string not a variable.
23 | global: "window",
24 | },
25 | plugins: [
26 | plugins.unpkgPathPlugin(),
27 | plugins.unpkgFetchPackagePlugin(rawCode),
28 | ],
29 | });
30 | console.log(bunduledCode);
31 | return { code: bunduledCode.outputFiles[0].text, error: "" };
32 | } catch (err) {
33 | return { code: "", error: err.message };
34 | }
35 | };
36 |
37 | export default bundle;
38 |
--------------------------------------------------------------------------------
/src/Redux/action-creators/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DeleteCellAction,
3 | InsertCellAction,
4 | MoveCellAction,
5 | UpdateCellAction,
6 | } from "../actions";
7 | import { ActionType } from "../action-types";
8 | import { CellDirection, CellType } from "../../interfaces";
9 |
10 | export const moveCell = (
11 | id: string,
12 | direction: CellDirection
13 | ): MoveCellAction => {
14 | return {
15 | type: ActionType.MOVE_CELL,
16 | payload: {
17 | id,
18 | direction,
19 | },
20 | };
21 | };
22 |
23 | export const deleteCell = (id: string): DeleteCellAction => {
24 | return {
25 | type: ActionType.DELETE_CELL,
26 | payload: id,
27 | };
28 | };
29 |
30 | export const insertCellAfter = (
31 | id: string | null,
32 | type: CellType
33 | ): InsertCellAction => {
34 | return {
35 | type: ActionType.INSERT_CELL_AFTER,
36 | payload: {
37 | id,
38 | type,
39 | },
40 | };
41 | };
42 |
43 | export const updateCell = (id: string, content: string): UpdateCellAction => {
44 | return {
45 | type: ActionType.UPDATE_CELL,
46 | payload: {
47 | id,
48 | content,
49 | },
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/src/Redux/action-types/index.ts:
--------------------------------------------------------------------------------
1 | export enum ActionType {
2 | MOVE_CELL = "move_cell",
3 | DELETE_CELL = "delete_Cell",
4 | INSERT_CELL_AFTER = "insert_cell_after",
5 | UPDATE_CELL = "update_cell",
6 | BUNDLE_CREATED = "bundle_created",
7 | }
8 |
--------------------------------------------------------------------------------
/src/Redux/actions/index.ts:
--------------------------------------------------------------------------------
1 | import { ActionType } from "../action-types/index";
2 | import { CellDirection, CellType } from "../../interfaces/index";
3 |
4 | export interface MoveCellAction {
5 | type: ActionType.MOVE_CELL;
6 | payload: {
7 | id: string;
8 | direction: CellDirection;
9 | };
10 | }
11 |
12 | export interface DeleteCellAction {
13 | type: ActionType.DELETE_CELL;
14 | payload: string;
15 | }
16 |
17 | export interface InsertCellAction {
18 | type: ActionType.INSERT_CELL_AFTER;
19 | payload: {
20 | id: string | null;
21 | type: CellType;
22 | };
23 | }
24 |
25 | export interface UpdateCellAction {
26 | type: ActionType.UPDATE_CELL;
27 | payload: {
28 | id: string;
29 | content: string;
30 | };
31 | }
32 |
33 | export interface BundleCreatedAction {
34 | type: ActionType.BUNDLE_CREATED;
35 | payload: {
36 | cellId: string;
37 | bundle: {
38 | code: string;
39 | error: string;
40 | };
41 | };
42 | }
43 |
44 | export type Action =
45 | | MoveCellAction
46 | | DeleteCellAction
47 | | InsertCellAction
48 | | UpdateCellAction
49 | | BundleCreatedAction;
50 |
--------------------------------------------------------------------------------
/src/Redux/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./store";
2 | export * from "./reducers";
3 | export * as actionCreators from "./action-creators";
4 |
--------------------------------------------------------------------------------
/src/Redux/middlewares/bundlerMiddleware.ts:
--------------------------------------------------------------------------------
1 | import type { Middleware } from "../../interfaces";
2 | import { ActionType } from "../action-types";
3 | import bundle from "../../Bundler";
4 |
5 | let timer: any;
6 | export const bundleMiddleware: Middleware = ({ getState, dispatch }) => (
7 | next
8 | ) => (action) => {
9 | next(action);
10 |
11 | if (action.type !== ActionType.UPDATE_CELL) return;
12 |
13 | // check for text cell-type
14 | const {
15 | cell: { data: cellData },
16 | } = getState();
17 | const cell = cellData[action.payload.id];
18 |
19 | if (cell.type === "text") return;
20 |
21 | clearTimeout(timer);
22 | timer = setTimeout(async () => {
23 | console.log("initiating bundling");
24 | const result = await bundle(action.payload.content);
25 |
26 | dispatch({
27 | type: ActionType.BUNDLE_CREATED,
28 | payload: {
29 | cellId: action.payload.id,
30 | bundle: result,
31 | },
32 | });
33 |
34 | console.log("bundling finished", "action dispatched");
35 | }, 1000);
36 | };
37 |
--------------------------------------------------------------------------------
/src/Redux/reducers/bundleReducer.ts:
--------------------------------------------------------------------------------
1 | import produce from "immer";
2 | import { ActionType } from "../action-types";
3 | import { Action } from "../actions";
4 | import { BundleState } from "../../interfaces";
5 |
6 | const initialState: BundleState = {};
7 |
8 | // sample bundle state will look like this:
9 | // const dummystate = {
10 | // cdHHD_VY: {
11 | // code:
12 | // "console.log(`Hey! If you're reading this, why don't you give me a interview oppurtunity?`);",
13 | // error: "",
14 | // },
15 | // };
16 |
17 | const reducer = produce(
18 | (state: BundleState = initialState, action: Action): BundleState => {
19 | switch (action.type) {
20 | case ActionType.BUNDLE_CREATED:
21 | state[action.payload.cellId] = action.payload.bundle;
22 | return state;
23 | default:
24 | return state;
25 | }
26 | },
27 | initialState
28 | );
29 |
30 | export default reducer;
31 |
--------------------------------------------------------------------------------
/src/Redux/reducers/cellReducer.ts:
--------------------------------------------------------------------------------
1 | import { produce } from "immer";
2 | import { nanoid } from "nanoid";
3 |
4 | import { Action } from "../actions/index";
5 | import { CellInterface } from "../../interfaces/index";
6 | import { ActionType } from "../action-types";
7 |
8 | interface cellState {
9 | loading: boolean;
10 | error: string;
11 | order: string[];
12 | data: {
13 | [keys: string]: CellInterface;
14 | };
15 | }
16 |
17 | const initialState: cellState = {
18 | loading: false,
19 | error: "",
20 | order: [],
21 | data: {},
22 | };
23 |
24 | // PS: data has been modeled like that (instead of an array) to prevent .find()/.filter() calls.
25 | // This simplefies accesing stuff.
26 |
27 | // state will look something like this:
28 | // const stateDummy = [
29 | // {
30 | // loading: false,
31 | // error: "",
32 | // order: ["id1", "id2", "id3", "id4"],
33 | // data: {
34 | // id1: {
35 | // id: "Hs2UHJ_Vw",
36 | // type: "code",
37 | // content: "const xyz = 231283",
38 | // },
39 | // id2: {
40 | // id: "DswZH_B_S",
41 | // type: "text",
42 | // content: "# Header '\n' -rest of the stuff ",
43 | // },
44 | // },
45 | // },
46 | // ];
47 |
48 | const reducer = produce((state: cellState = initialState, action: Action) => {
49 | switch (action.type) {
50 | case ActionType.UPDATE_CELL:
51 | const { content, id } = action.payload;
52 | state.data[id].content = content;
53 | return state;
54 |
55 | case ActionType.MOVE_CELL:
56 | const { direction } = action.payload;
57 | const idIndex = state.order.findIndex((id) => id === action.payload.id);
58 | const swapTargetIndex = direction === "up" ? idIndex - 1 : idIndex + 1;
59 |
60 | // check for swapTargetIndex out of bounds
61 | if (swapTargetIndex < 0 || swapTargetIndex > state.order.length - 1) {
62 | console.error("swapTargetIndex is out of bounds");
63 | return state;
64 | }
65 |
66 | // Swap id orders
67 | state.order[idIndex] = state.order[swapTargetIndex];
68 | state.order[swapTargetIndex] = action.payload.id;
69 |
70 | return state;
71 |
72 | case ActionType.DELETE_CELL:
73 | // from both order and data
74 | delete state.data[action.payload];
75 | const index = state.order.findIndex((id) => id === action.payload);
76 | state.order.splice(index, 1);
77 | return state;
78 |
79 | case ActionType.INSERT_CELL_AFTER:
80 | const { type } = action.payload;
81 | const newCell: CellInterface = {
82 | content: "",
83 | type,
84 | id: nanoid(),
85 | };
86 |
87 | // insert new cell in data
88 | state.data[newCell.id] = newCell;
89 |
90 | // get index of previous cell and insert newCell after it
91 | const prevCellIndex = state.order.findIndex(
92 | (id) => id === action.payload.id
93 | );
94 |
95 | if (prevCellIndex === -1) {
96 | state.order.unshift(newCell.id);
97 | } else {
98 | state.order.splice(prevCellIndex + 1, 0, newCell.id);
99 | }
100 |
101 | return state;
102 | default:
103 | return state;
104 | }
105 | }, initialState);
106 |
107 | export default reducer;
108 |
--------------------------------------------------------------------------------
/src/Redux/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import cellReducer from "./cellReducer";
3 | import bundleReducer from "./bundleReducer";
4 |
5 | const rootReducer = combineReducers({
6 | cell: cellReducer,
7 | bundle: bundleReducer,
8 | });
9 |
10 | export default rootReducer;
11 | export type RootState = ReturnType;
12 |
--------------------------------------------------------------------------------
/src/Redux/store.ts:
--------------------------------------------------------------------------------
1 | //eslint-disable-next-line
2 | import { createStore, applyMiddleware, compose } from "redux";
3 | import thunk from "redux-thunk";
4 | import rootReducer from "./reducers";
5 | import { bundleMiddleware } from "./middlewares/bundlerMiddleware";
6 | import { composeWithDevTools } from "redux-devtools-extension";
7 |
8 | // Testing
9 | import { ActionType } from "./action-types";
10 |
11 | export const Store = createStore(
12 | rootReducer,
13 | {},
14 | composeWithDevTools(applyMiddleware(thunk, bundleMiddleware))
15 | );
16 |
17 | Store.dispatch({
18 | type: ActionType.INSERT_CELL_AFTER,
19 | payload: {
20 | id: null,
21 | type: "text",
22 | },
23 | });
24 |
25 | Store.dispatch({
26 | type: ActionType.INSERT_CELL_AFTER,
27 | payload: {
28 | id: null,
29 | type: "code",
30 | },
31 | });
32 |
33 | Store.dispatch({
34 | type: ActionType.INSERT_CELL_AFTER,
35 | payload: {
36 | id: null,
37 | type: "text",
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/src/components/ActionBar/ActionBar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ActionBarinterface } from "../../interfaces";
3 | import { Button } from "@material-ui/core";
4 | import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
5 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
6 | import CloseIcon from "@material-ui/icons/Close";
7 |
8 | import { useAction } from "../../hooks/useActions";
9 |
10 | import makeStyles from "./styles";
11 |
12 | const ActionBar: React.FC = ({ id }) => {
13 | const classes = makeStyles();
14 |
15 | const { moveCell, deleteCell } = useAction();
16 |
17 | return (
18 |
19 |
30 |
41 |
52 |
53 | );
54 | };
55 |
56 | export default ActionBar;
57 |
--------------------------------------------------------------------------------
/src/components/ActionBar/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | actionBarWrapper: {
5 | display: "flex",
6 | justifyContent: "flex-end",
7 | alignItems: "center",
8 | backgroundColor: theme.palette.background.paper,
9 | marginBottom: "-0.5rem",
10 | },
11 | actionButtons: {
12 | marginRight: theme.spacing(2),
13 | opacity: 0.5,
14 | transition: "opacity 0.3s",
15 | "&:hover": {
16 | opacity: 1,
17 | },
18 | },
19 | }));
20 |
--------------------------------------------------------------------------------
/src/components/AddCell/AddCell.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { AddCellInterface } from "../../interfaces";
4 | import { useAction } from "../../hooks/useActions";
5 |
6 | import makeStyles from "./styles";
7 |
8 | import { Button } from "@material-ui/core";
9 | import AddIcon from "@material-ui/icons/Add";
10 |
11 | const AddCell: React.FC = ({ previousCellId }) => {
12 | const { insertCellAfter } = useAction();
13 | const classes = makeStyles();
14 |
15 | return (
16 |
17 |
18 |
19 |
31 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default AddCell;
49 |
--------------------------------------------------------------------------------
/src/components/AddCell/styles.ts:
--------------------------------------------------------------------------------
1 | import { grey } from "@material-ui/core/colors";
2 | import { makeStyles } from "@material-ui/core/styles";
3 | export default makeStyles((theme) => ({
4 | addCellWrapper: {
5 | display: "flex",
6 | justifyContent: "center",
7 | alignItems: "center",
8 | marginTop: theme.spacing(3),
9 | marginBottom: theme.spacing(3),
10 | opacity: 0,
11 | transition: "opacity 0.2s ease-in 0.1s",
12 | "&:hover": {
13 | opacity: 1,
14 | },
15 | },
16 |
17 | button: {
18 | marginLeft: theme.spacing(2),
19 | marginRight: theme.spacing(2),
20 | },
21 |
22 | linethrough: {
23 | position: "absolute",
24 | height: "1px",
25 | width: "50%",
26 | backgroundColor: grey[400],
27 | zIndex: -1,
28 | },
29 |
30 | buttonWrapper: {
31 | display: "flex",
32 | justifyContent: "center",
33 | width: "20%",
34 | backgroundColor: theme.palette.background.default,
35 | },
36 | }));
37 |
--------------------------------------------------------------------------------
/src/components/CellList/CellList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { useTypedSelector } from "../../hooks/useTypedSelector";
3 | import CellItem from "./CellListItem/CellItem";
4 | import AddCell from "../AddCell/AddCell";
5 |
6 | import makeStyles from "./styles";
7 |
8 | const CellList: React.FC = () => {
9 | const classes = makeStyles();
10 |
11 | // sort data according to order from state
12 | const cells = useTypedSelector(({ cell: { data, order } }) =>
13 | order.map((id) => data[id])
14 | );
15 |
16 | const renderedCells = cells.map((cell) => (
17 |
18 |
19 |
20 |
21 | ));
22 |
23 | return (
24 |
25 | {/* when no cells present, set opacity to 1 */}
26 |
29 | {renderedCells}
30 |
31 | );
32 | };
33 |
34 | export default CellList;
35 |
--------------------------------------------------------------------------------
/src/components/CellList/CellListItem/CellItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { CellListItemInterface } from "../../../interfaces/index";
3 |
4 | import Cell from "../../CodeCell/Cell";
5 | import Editor from "../../MarkdownEditor/MarkdownEditor";
6 | import ActionBar from "../../ActionBar/ActionBar";
7 |
8 | import makeStyles from "./styles";
9 |
10 | const CellItem: React.FC = ({ cell }) => {
11 | let child: JSX.Element;
12 | const classes = makeStyles();
13 |
14 | if (cell.type === "code") {
15 | child = | ;
16 | } else {
17 | child = ;
18 | }
19 |
20 | return (
21 |
25 | );
26 | };
27 |
28 | export default CellItem;
29 |
--------------------------------------------------------------------------------
/src/components/CellList/CellListItem/styles.tsx:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | CellListItemWrapper: {
5 | marginTop: theme.spacing(2),
6 | marginBottom: theme.spacing(2),
7 | },
8 | }));
9 |
--------------------------------------------------------------------------------
/src/components/CellList/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | maxOpacity: {
5 | "& div": {
6 | opacity: 1,
7 | },
8 | },
9 | }));
10 |
--------------------------------------------------------------------------------
/src/components/CodeCell/Cell.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useTypedSelector } from "../../hooks/useTypedSelector";
3 |
4 | import CodeEditor from "../CodeEditor/Editor";
5 | import PreviewWindow from "../PreviewWindow/PreviewWindow";
6 | import ResizableCell from "../ResizableCell/ResizableCell";
7 |
8 | import { Paper } from "@material-ui/core";
9 | import makeStyles from "./styles";
10 |
11 | import { CellListItemInterface } from "../../interfaces";
12 |
13 | import { useAction } from "../../hooks/useActions";
14 |
15 | const Cell: React.FC = ({ cell }) => {
16 | const classes = makeStyles();
17 | const bundle = useTypedSelector((state) => state.bundle[cell.id]);
18 |
19 | const { updateCell } = useAction();
20 |
21 | return (
22 |
23 |
24 | updateCell(cell.id, value)}
27 | />
28 | {!bundle ? (
29 |
30 | ) : (
31 |
32 | )}
33 |
34 |
35 | );
36 | };
37 |
38 | export default Cell;
39 |
--------------------------------------------------------------------------------
/src/components/CodeCell/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | wrapper: {
5 | display: "flex",
6 | flexDirection: "row",
7 | height: "100%",
8 | padding: theme.spacing(2),
9 | },
10 | [theme.breakpoints.down("sm")]: {
11 | wrapper: {
12 | flexDirection: "column",
13 | },
14 | },
15 | gridContainer: {
16 | height: "100%",
17 | marginTop: theme.spacing(2),
18 | marginBottom: theme.spacing(2),
19 | padding: theme.spacing(2),
20 | },
21 | }));
22 |
--------------------------------------------------------------------------------
/src/components/CodeEditor/Editor.tsx:
--------------------------------------------------------------------------------
1 | import MonacoEditor, { Monaco } from "@monaco-editor/react";
2 | import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
3 | import { EditorPropsInterface } from "../../interfaces/index";
4 | import React, { useRef } from "react";
5 | import prettier from "prettier";
6 | import parser from "prettier/parser-babel";
7 |
8 | import { Button } from "@material-ui/core";
9 | import makeStyles from "./styles";
10 |
11 | const Editor: React.FC = ({ onChange, initialValue }) => {
12 | const monacoEditorRef = useRef();
13 | const classes = makeStyles();
14 |
15 | /**
16 | * On change in model, get the value of it's content and
17 | * pass it as an argument to onChange to set the input state
18 | */
19 | const onEditorDidMount = (
20 | editor: monaco.editor.IStandaloneCodeEditor,
21 | monacoInstance: Monaco
22 | ) => {
23 | monacoEditorRef.current = editor;
24 | editor.onDidChangeModelContent(() => {
25 | onChange(editor.getValue());
26 | });
27 | };
28 |
29 | /**
30 | * Get unformatted code, format it and set it to model
31 | */
32 | const onFormatClick = () => {
33 | const rawCode = monacoEditorRef!.current!.getModel()!.getValue();
34 | const formattedCode = prettier
35 | .format(rawCode, {
36 | parser: "babel",
37 | plugins: [parser],
38 | semi: true,
39 | singleQuote: true,
40 | })
41 | .replace(/\n$/, "");
42 |
43 | monacoEditorRef.current?.setValue(formattedCode);
44 | };
45 |
46 | // breaking changes. Revert to Grid conatiner if necessary
47 | return (
48 |
49 |
58 |
76 |
77 | );
78 | };
79 |
80 | export default Editor;
81 |
--------------------------------------------------------------------------------
/src/components/CodeEditor/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | editorFormatBtn: {
5 | position: "absolute",
6 | top: "10px",
7 | right: "10px",
8 | zIndex: 2,
9 | },
10 | editorWrapper: {
11 | position: "relative",
12 | overflow: "hidden",
13 | height: "100%",
14 | width: "100%",
15 | },
16 | }));
17 |
--------------------------------------------------------------------------------
/src/components/CodeEditor/types.d.ts:
--------------------------------------------------------------------------------
1 | // Only to prevent Typescript errors
2 | declare module "monaco-jsx-highlighter";
3 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/MarkdownEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import MDEditor from "@uiw/react-md-editor";
3 | import { Paper } from "@material-ui/core";
4 |
5 | import { CellListItemInterface } from "../../interfaces";
6 | import { useAction } from "../../hooks/useActions";
7 |
8 | import makeStyles from "./styles";
9 |
10 | // re-style the drag handle;
11 | import "./resizerReset.css";
12 |
13 | const MarkdownEditor: React.FC = ({ cell }) => {
14 | const classes = makeStyles();
15 | const [editing, setEditing] = useState(false);
16 | const { updateCell } = useAction();
17 | const markdownRef = useRef(null);
18 |
19 | // Toogle between view mode and editor mode
20 | useEffect(() => {
21 | const listner = (event: MouseEvent) => {
22 | // detect if clicked within editor div
23 | if (
24 | markdownRef.current &&
25 | event.target &&
26 | markdownRef.current.contains(event.target as Node)
27 | ) {
28 | console.log("element clicked within editor");
29 | return;
30 | }
31 |
32 | console.log("element clicked is not within editor");
33 |
34 | setEditing(false);
35 | console.log(event.target);
36 | };
37 |
38 | document.addEventListener("click", listner, { capture: true });
39 |
40 | return () => {
41 | document.removeEventListener("click", listner, { capture: true });
42 | };
43 | }, []);
44 |
45 | if (editing) {
46 | return (
47 |
52 |
55 | updateCell(cell.id, updatedTextValue || "")
56 | }
57 | />
58 |
59 | );
60 | }
61 |
62 | return (
63 | setEditing(true)}
65 | className={`${classes.PaperContainer} text-editor`}
66 | elevation={2}
67 | >
68 |
69 |
70 | );
71 | };
72 |
73 | export default MarkdownEditor;
74 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/resizerReset.css:
--------------------------------------------------------------------------------
1 | .w-md-editor-bar {
2 | display: flex;
3 | background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=")
4 | no-repeat;
5 | background-color: #424242;
6 | background-position: 50%;
7 | position: relative;
8 | margin-top: -2px;
9 | width: 100%;
10 | }
11 |
12 | .w-md-editor-bar svg {
13 | display: none;
14 | }
15 |
16 | .w-md-editor .title {
17 | line-height: unset;
18 | font-size: unset;
19 | font-weight: unset;
20 | color: #d4d4d4 !important;
21 | }
22 |
23 | .text-editor em {
24 | font-style: italic;
25 | }
26 |
27 | .text-editor .wmde-markdown hr {
28 | border-top: 1px solid #dee5ed;
29 | }
30 |
31 | .text-editor .wmde-markdown ol {
32 | list-style: decimal;
33 | }
34 |
35 | .text-editor .w-md-editor-show-live {
36 | /* Hide menu bar buttons to prevent accidental delete */
37 | z-index: 20;
38 | }
39 |
40 | .text-editor .w-md-editor-toolbar {
41 | border-bottom: 1px solid gray;
42 | background-color: hsl(0, 0%, 22%);
43 | }
44 |
45 | .text-editor .w-md-editor-toolbar li button {
46 | color: #8db8d1;
47 | }
48 |
49 | .text-editor .w-md-editor-content {
50 | background-color: #202123;
51 | }
52 |
53 | .text-editor .w-md-editor,
54 | .text-editor .w-md-editor .w-md-editor-text-pre {
55 | color: #d4d4d4;
56 | }
57 |
58 | .text-editor .w-md-editor-text-pre .bold {
59 | color: unset;
60 | }
61 |
62 | .text-editor .token.list.punctuation {
63 | background-color: unset;
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/MarkdownEditor/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | PaperContainer: {
5 | padding: theme.spacing(2),
6 | // Solved Line height problem of code editor
7 | lineHeight: 1,
8 | },
9 | }));
10 |
--------------------------------------------------------------------------------
/src/components/PreviewWindow/PreviewWindow.tsx:
--------------------------------------------------------------------------------
1 | import { PreviewWindowInterface } from "../../interfaces/index";
2 | import { useRef, useEffect } from "react";
3 |
4 | const executableScript = `
5 |
6 |
7 |
8 |
27 |
28 | `;
29 |
30 | const PreviewWindow: React.FC = ({
31 | code,
32 | errorMessage,
33 | }) => {
34 | const iframeRef = useRef();
35 |
36 | // Upon change in code, refresh window and execute the script
37 | // 50ms delay for iframe to load up
38 |
39 | useEffect(() => {
40 | iframeRef.current.srcdoc = executableScript;
41 | setTimeout(() => {
42 | iframeRef.current.contentWindow.postMessage(code, "*");
43 | }, 25);
44 | }, [code]);
45 |
46 | console.error(errorMessage);
47 |
48 | return (
49 | <>
50 |
63 | >
64 | );
65 | };
66 |
67 | export default PreviewWindow;
68 |
--------------------------------------------------------------------------------
/src/components/ResizableCell/ResizableCell.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ResizableBox } from "react-resizable";
3 | import { ResizablePropsInterface } from "../../interfaces/index";
4 |
5 | import "./styles.css";
6 |
7 | const ResizableCell: React.FC = ({
8 | children,
9 | direction,
10 | }) => {
11 | return (
12 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default ResizableCell;
25 |
--------------------------------------------------------------------------------
/src/components/ResizableCell/styles.css:
--------------------------------------------------------------------------------
1 | .react-resizable-handle {
2 | display: block;
3 | background-color: #303030;
4 | background-repeat: no-repeat;
5 | background-position: 50%;
6 | }
7 |
8 | .react-resizable-handle-s {
9 | height: 10px;
10 | width: 100%;
11 | cursor: row-resize;
12 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=");
13 | }
14 |
15 | .react-resizable-handle-e {
16 | width: 10px;
17 | min-width: 10px;
18 | height: 100%;
19 | cursor: col-resize;
20 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==");
21 | }
22 |
--------------------------------------------------------------------------------
/src/hooks/useActions.ts:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import { bindActionCreators } from "redux";
3 | import { actionCreators } from "../Redux";
4 |
5 | export const useAction = () => {
6 | const dispatch = useDispatch();
7 | return bindActionCreators(actionCreators, dispatch);
8 | };
9 |
--------------------------------------------------------------------------------
/src/hooks/useTypedSelector.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useSelector } from "react-redux";
2 | import { RootState } from "../Redux/reducers";
3 |
4 | export const useTypedSelector: TypedUseSelectorHook = useSelector;
5 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import reactDom from "react-dom";
2 | import App from "./App";
3 | import { Provider } from "react-redux";
4 | import { Store } from "./Redux";
5 |
6 | reactDom.render(
7 |
8 |
9 | ,
10 | document.getElementById("root")
11 | );
12 |
--------------------------------------------------------------------------------
/src/interfaces/ActionBar/ActionBarInterface.ts:
--------------------------------------------------------------------------------
1 | export interface ActionBarinterface {
2 | id: string;
3 | }
4 |
--------------------------------------------------------------------------------
/src/interfaces/Bundle/BundleInterface.ts:
--------------------------------------------------------------------------------
1 | export interface BundleState {
2 | [key: string]: {
3 | code: string;
4 | error: string;
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/interfaces/Bundle/BundleMiddlewareInterface.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from "../../Redux/";
2 | import { Action } from "../../Redux/actions/";
3 |
4 | // annotates the type of state and dispatch with the provided generics
5 | interface MiddlewareAPI {
6 | getState(): S;
7 | dispatch(action: A): void;
8 | }
9 |
10 | // annotates type of the next function and action based on provided generics
11 | interface _Middleware {
12 | (api: MiddlewareAPI): (
13 | next: (action: A) => void
14 | ) => (action: A) => void;
15 | }
16 |
17 | export type Middleware = _Middleware;
18 |
--------------------------------------------------------------------------------
/src/interfaces/Cell/AddCellInterface.ts:
--------------------------------------------------------------------------------
1 | export interface AddCellInterface {
2 | previousCellId: string | null;
3 | }
4 |
--------------------------------------------------------------------------------
/src/interfaces/Cell/CellInterface.ts:
--------------------------------------------------------------------------------
1 | export type CellType = "code" | "text";
2 | export type CellDirection = "up" | "down";
3 |
4 | export interface CellInterface {
5 | id: string;
6 | content: string;
7 | type: CellType;
8 | }
9 |
10 | export interface CellListItemInterface {
11 | cell: CellInterface;
12 | }
13 |
--------------------------------------------------------------------------------
/src/interfaces/CodeEditor/EditorPropsInterface.ts:
--------------------------------------------------------------------------------
1 | export interface EditorPropsInterface {
2 | initialValue: string;
3 | onChange(value: string): void;
4 | }
5 |
--------------------------------------------------------------------------------
/src/interfaces/Plugins/unpkgPluginInterface.ts:
--------------------------------------------------------------------------------
1 | export interface onLoadInterface {
2 | importer?: string | undefined;
3 | kind?: string;
4 | namespace: string;
5 | path: string;
6 | pluginData?: string | undefined;
7 | resolveDir?: string;
8 | }
9 |
10 | export interface onResolveInterface {
11 | importer?: string | undefined;
12 | kind?: string;
13 | namespace: string;
14 | path: string;
15 | pluginData?: string | undefined;
16 | resolveDir?: string;
17 | }
18 |
--------------------------------------------------------------------------------
/src/interfaces/PreviewWindow/PreviewWindowInterface.ts:
--------------------------------------------------------------------------------
1 | export interface PreviewWindowInterface {
2 | code: string;
3 | errorMessage: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/interfaces/ResizableCell/ResizablePropsInterface.ts:
--------------------------------------------------------------------------------
1 | export interface ResizablePropsInterface {
2 | direction: "verticle" | "horizontal";
3 | }
4 |
--------------------------------------------------------------------------------
/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./CodeEditor/EditorPropsInterface";
2 | export * from "./Plugins/unpkgPluginInterface";
3 | export * from "./PreviewWindow/PreviewWindowInterface";
4 | export * from "./ResizableCell/ResizablePropsInterface";
5 | export * from "./Cell/CellInterface";
6 | export * from "./Cell/AddCellInterface";
7 | export * from "./ActionBar/ActionBarInterface";
8 | export * from "./Bundle/BundleInterface";
9 | export type { Middleware } from "./Bundle/BundleMiddlewareInterface";
10 |
--------------------------------------------------------------------------------
/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./unpkgFetchPackagePlugin";
2 | export * from "./unpkgPathPlugin";
3 |
--------------------------------------------------------------------------------
/src/plugins/unpkgFetchPackagePlugin.ts:
--------------------------------------------------------------------------------
1 | import * as esbuild from "esbuild-wasm";
2 | import localforage from "localforage";
3 |
4 | import axios from "axios";
5 | import { onLoadInterface } from "../interfaces/index";
6 |
7 | const packageCache = localforage.createInstance({
8 | name: "packageCache",
9 | });
10 |
11 | export const unpkgFetchPackagePlugin = (inputString: string) => {
12 | return {
13 | name: "unpkg-Fetch-Package-plugin",
14 | setup(build: esbuild.PluginBuild) {
15 | build.onLoad({ filter: /(^index\.js$)/ }, (args: onLoadInterface) => {
16 | return {
17 | loader: "jsx",
18 | contents: inputString,
19 | };
20 | });
21 |
22 | // caching layer
23 | build.onLoad({ filter: /.*/ }, async (args: onLoadInterface) => {
24 | const cachedPackage = await packageCache.getItem(
25 | args.path
26 | );
27 | if (cachedPackage) return cachedPackage;
28 | });
29 |
30 | build.onLoad({ filter: /.css$/ }, async (args: onLoadInterface) => {
31 | const { data, request } = await axios.get(args.path);
32 | console.log(args.path);
33 |
34 | // Escape newLines(\n), single-quotes('') and doubleQuotes("") and comments(/**/, // ) in fetched css
35 | const escapedCssString = data
36 | .replace(/\n/g, "")
37 | .replace(/\/\*[\s\S]*?\*\//g, "")
38 | .replace(/"/g, `\\"`)
39 | .replace(/'/g, `\\'`);
40 |
41 | const contents = `
42 | const styleTag = document.createElement('style');
43 | styleTag.innerText = '${escapedCssString}';
44 | document.head.appendChild(styleTag);
45 | `;
46 |
47 | const result: esbuild.OnLoadResult = {
48 | loader: "jsx",
49 | contents,
50 | resolveDir: new URL("./", request.responseURL).pathname,
51 | };
52 |
53 | // set new result in cache
54 | packageCache.setItem(args.path, result);
55 |
56 | return result;
57 | });
58 |
59 | build.onLoad({ filter: /.*/ }, async (args: onLoadInterface) => {
60 | const { data, request } = await axios.get(args.path);
61 | console.log(args.path);
62 |
63 | const result: esbuild.OnLoadResult = {
64 | loader: "jsx",
65 | contents: data,
66 | resolveDir: new URL("./", request.responseURL).pathname,
67 | };
68 |
69 | // set new result in cache
70 | packageCache.setItem(args.path, result);
71 |
72 | return result;
73 | });
74 | },
75 | };
76 | };
77 |
--------------------------------------------------------------------------------
/src/plugins/unpkgPathPlugin.ts:
--------------------------------------------------------------------------------
1 | import * as esbuild from "esbuild-wasm";
2 | import { onResolveInterface } from "../interfaces/index";
3 |
4 | export const unpkgPathPlugin = () => {
5 | return {
6 | name: "unpkg-path-plugin",
7 | setup(build: esbuild.PluginBuild) {
8 | // resolve bundling entry point
9 | build.onResolve(
10 | { filter: /(^index\.js$)/ },
11 | (args: onResolveInterface) => {
12 | if (args.path === "index.js")
13 | return { path: args.path, namespace: "a" };
14 | }
15 | );
16 |
17 | // Resolve sub directories imported in base package
18 | build.onResolve({ filter: /^\.+\// }, (args: onResolveInterface) => {
19 | return {
20 | path: new URL(args.path, `https://unpkg.com/${args.resolveDir}/`)
21 | .href,
22 | namespace: "a",
23 | };
24 | });
25 |
26 | // Resolve base package name
27 | build.onResolve({ filter: /.*/ }, async (args: onResolveInterface) => {
28 | return {
29 | path: `https://unpkg.com/${args.path}`,
30 | namespace: "a",
31 | };
32 | });
33 | },
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core/styles";
2 |
3 | export default makeStyles((theme) => ({
4 | wrapper: {
5 | margin: theme.spacing(2),
6 | padding: theme.spacing(2),
7 | },
8 | }));
9 |
--------------------------------------------------------------------------------
/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from "@material-ui/core/styles";
2 | import { blue } from "@material-ui/core/colors";
3 |
4 | const theme = createMuiTheme({
5 | palette: {
6 | type: "dark",
7 | primary: {
8 | main: blue[400],
9 | },
10 | secondary: {
11 | main: blue[200],
12 | },
13 | },
14 | });
15 |
16 | export default theme;
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------