├── .gitignore ├── CHANGELOG.md ├── README.md ├── lerna.json ├── package.json └── packages ├── cli ├── CHANGELOG.md ├── package-lock.json ├── package.json ├── src │ ├── commands │ │ └── serve.ts │ └── index.ts └── tsconfig.json ├── local-api ├── CHANGELOG.md ├── package-lock.json ├── package.json ├── src │ ├── index.ts │ └── routes │ │ └── cellsRouter.ts └── tsconfig.json └── local-client ├── CHANGELOG.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── bundler │ ├── index.ts │ └── plugins │ │ ├── fetchPlugin.ts │ │ └── unpkgPathPlugin.ts ├── components │ ├── ActionBar │ │ ├── ActionBar.style.ts │ │ ├── ActionBar.tsx │ │ └── index.ts │ ├── ActionButton │ │ ├── ActionButton.tsx │ │ └── index.ts │ ├── AddCellDivider │ │ ├── AddCellDivider.style.ts │ │ ├── AddCellDivider.tsx │ │ └── index.ts │ ├── CellList │ │ ├── CellList.style.ts │ │ ├── CellList.tsx │ │ └── index.ts │ ├── CellListItem │ │ ├── CellListItem.style.ts │ │ ├── CellListItem.tsx │ │ └── index.ts │ ├── CodeCell │ │ ├── CodeCell.style.ts │ │ ├── CodeCell.tsx │ │ └── index.ts │ ├── CodeEditor │ │ ├── CodeEditor.style.ts │ │ ├── CodeEditor.tsx │ │ └── index.ts │ ├── Loading │ │ ├── Loading.style.ts │ │ ├── Loading.tsx │ │ └── index.ts │ ├── Preview │ │ ├── Preview.style.ts │ │ ├── Preview.tsx │ │ └── index.ts │ ├── Resizable │ │ ├── Resizable.style.ts │ │ ├── Resizable.tsx │ │ └── index.ts │ └── TextEditor │ │ ├── TextEditor.style.ts │ │ ├── TextEditor.tsx │ │ └── index.ts ├── hooks │ ├── index.ts │ ├── useActions.ts │ ├── useCumulativeCode.ts │ └── useTypedSelector.ts ├── index.tsx ├── react-app-env.d.ts ├── state │ ├── Cell.ts │ ├── actionCreators │ │ ├── bundleActionsCreaters.ts │ │ ├── cellActionCreaters.ts │ │ └── index.ts │ ├── actionTypes │ │ ├── BundleActionType.ts │ │ ├── CellActionType.ts │ │ └── index.ts │ ├── actions │ │ ├── BundleAction.ts │ │ ├── CellAction.ts │ │ └── index.ts │ ├── index.ts │ ├── middlewares │ │ ├── index.ts │ │ └── persistMiddleware.ts │ ├── reducers │ │ ├── bundlesReducer.ts │ │ ├── cellsReducer.ts │ │ └── index.ts │ └── store.ts └── types.d.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | build 14 | /dist 15 | dist 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | *log -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 1.0.1 (2021-08-21) 7 | 8 | **Note:** Version bump only for package root 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JNotebook 2 | 3 | This is an interactive coding environment. You can write Javascript, see it executed, and write comprehensive documentation using markdown. 4 | 5 | - Click any text cell to edit it 6 | - The code in each code editor is all joined together into one file. If you define a variable in cell #1, you can refer to it in any following cell! 7 | - You can use any library in npm by importing it 8 | - You can show any React component, string, number, or anything else by calling the `show` function. This is a function built into this environment. Call show multiple times to show multiple values 9 | - Re-order or delete cells using the buttons on the top right 10 | - Add new cells by hovering on the divider between each cell 11 | 12 | All of your changes get saved to the file you opened JNotebook with. So if you ran `npx jnotebook serve test.js`, all of the text and code you write will be saved to the test.js file. 13 | 14 | ## Command Options 15 | 16 | ### `-p, --port ` 17 | 18 | ``` 19 | npx jnotebook serve jnotebook.js -p 5000 20 | # or 21 | npx jnotebook serve jnotebook.js --port 5000 22 | # or 23 | npx jnotebook serve -p 5000 24 | # or 25 | npx jnotebook serve --port 5000 26 | 27 | # Default port = 4005 28 | # Default file name = notebook.js 29 | ``` 30 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "1.1.2" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "^4.0.0" 6 | }, 7 | "scripts": { 8 | "dev": "lerna run start --parallel", 9 | "new-version": "lerna version --conventional-commits --yes" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 1.0.1 (2021-08-21) 7 | 8 | **Note:** Version bump only for package jpen 9 | -------------------------------------------------------------------------------- /packages/cli/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jnotebook", 3 | "version": "1.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "16.6.1", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz", 10 | "integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw==", 11 | "dev": true 12 | }, 13 | "commander": { 14 | "version": "8.1.0", 15 | "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", 16 | "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==" 17 | }, 18 | "esbuild": { 19 | "version": "0.12.21", 20 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.21.tgz", 21 | "integrity": "sha512-7hyXbU3g94aREufI/5nls7Xcc+RGQeZWZApm6hoBaFvt2BPtpT4TjFMQ9Tb1jU8XyBGz00ShmiyflCogphMHFQ==", 22 | "dev": true 23 | }, 24 | "typescript": { 25 | "version": "4.3.5", 26 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 27 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 28 | "dev": true 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jnotebook", 3 | "version": "1.1.2", 4 | "description": "A CLI to launch an interactive development environment for writing and documenting code", 5 | "bin": "dist/index.js", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "start": "tsc --watch --preserveWatchOutput", 14 | "prepublishOnly": "esbuild --target=node12 src/index.ts --platform=node --outfile=dist/index.js --bundle --minify --define:process.env.NODE_ENV=\\\"production\\\"" 15 | }, 16 | "keywords": [], 17 | "author": "Aykut Ulış", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@jnotebook/local-client": "^1.1.1" 21 | }, 22 | "devDependencies": { 23 | "@jnotebook/local-api": "^1.1.2", 24 | "@types/node": "^16.6.1", 25 | "commander": "^8.1.0", 26 | "esbuild": "^0.12.21", 27 | "typescript": "^4.3.5" 28 | }, 29 | "gitHead": "a2b81fd1e490a09362b335359fb1842eb096bd3b" 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/commands/serve.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Command } from 'commander'; 3 | import { serve } from '@jnotebook/local-api'; 4 | 5 | const isProduction = process.env.NODE_ENV === 'production'; 6 | 7 | export const serveCommand = new Command() 8 | .command('serve [filename]') 9 | .description('Open a file for editing') 10 | .option('-p, --port ', 'Port to run server on', '4005') 11 | .action(async (filename = 'notebook.js', options: { port: string }) => { 12 | try { 13 | const dir = path.join(process.cwd(), path.dirname(filename)); 14 | await serve(parseInt(options.port), path.basename(filename), dir, !isProduction); 15 | console.log(`Opened ${filename}. Navigate to http://localhost:${options.port} to edit the file.`); 16 | } catch (error) { 17 | if (error.code === 'EADDRINUSE') { 18 | console.error('Port is in use. Try running on a different port.'); 19 | } else { 20 | console.log('Heres the problem', error.message); 21 | } 22 | process.exit(1); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /packages/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { program } from 'commander'; 3 | import { serveCommand } from './commands/serve'; 4 | 5 | program.addCommand(serveCommand); 6 | 7 | program.parse(process.argv); 8 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/local-api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 1.0.1 (2021-08-21) 7 | 8 | **Note:** Version bump only for package @jpen/local-api 9 | -------------------------------------------------------------------------------- /packages/local-api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local-api", 3 | "version": "1.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "1.19.1", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", 10 | "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", 11 | "dev": true, 12 | "requires": { 13 | "@types/connect": "*", 14 | "@types/node": "*" 15 | } 16 | }, 17 | "@types/connect": { 18 | "version": "3.4.35", 19 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 20 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 21 | "dev": true, 22 | "requires": { 23 | "@types/node": "*" 24 | } 25 | }, 26 | "@types/cors": { 27 | "version": "2.8.12", 28 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 29 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", 30 | "dev": true 31 | }, 32 | "@types/express": { 33 | "version": "4.17.13", 34 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 35 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 36 | "dev": true, 37 | "requires": { 38 | "@types/body-parser": "*", 39 | "@types/express-serve-static-core": "^4.17.18", 40 | "@types/qs": "*", 41 | "@types/serve-static": "*" 42 | } 43 | }, 44 | "@types/express-serve-static-core": { 45 | "version": "4.17.24", 46 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", 47 | "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", 48 | "dev": true, 49 | "requires": { 50 | "@types/node": "*", 51 | "@types/qs": "*", 52 | "@types/range-parser": "*" 53 | } 54 | }, 55 | "@types/http-proxy": { 56 | "version": "1.17.7", 57 | "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", 58 | "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", 59 | "requires": { 60 | "@types/node": "*" 61 | } 62 | }, 63 | "@types/mime": { 64 | "version": "1.3.2", 65 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 66 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", 67 | "dev": true 68 | }, 69 | "@types/node": { 70 | "version": "16.6.1", 71 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz", 72 | "integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw==" 73 | }, 74 | "@types/qs": { 75 | "version": "6.9.7", 76 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 77 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", 78 | "dev": true 79 | }, 80 | "@types/range-parser": { 81 | "version": "1.2.4", 82 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 83 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", 84 | "dev": true 85 | }, 86 | "@types/serve-static": { 87 | "version": "1.13.10", 88 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 89 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 90 | "dev": true, 91 | "requires": { 92 | "@types/mime": "^1", 93 | "@types/node": "*" 94 | } 95 | }, 96 | "accepts": { 97 | "version": "1.3.7", 98 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 99 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 100 | "requires": { 101 | "mime-types": "~2.1.24", 102 | "negotiator": "0.6.2" 103 | } 104 | }, 105 | "array-flatten": { 106 | "version": "1.1.1", 107 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 108 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 109 | }, 110 | "body-parser": { 111 | "version": "1.19.0", 112 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 113 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 114 | "requires": { 115 | "bytes": "3.1.0", 116 | "content-type": "~1.0.4", 117 | "debug": "2.6.9", 118 | "depd": "~1.1.2", 119 | "http-errors": "1.7.2", 120 | "iconv-lite": "0.4.24", 121 | "on-finished": "~2.3.0", 122 | "qs": "6.7.0", 123 | "raw-body": "2.4.0", 124 | "type-is": "~1.6.17" 125 | } 126 | }, 127 | "braces": { 128 | "version": "3.0.2", 129 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 130 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 131 | "requires": { 132 | "fill-range": "^7.0.1" 133 | } 134 | }, 135 | "bytes": { 136 | "version": "3.1.0", 137 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 138 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 139 | }, 140 | "content-disposition": { 141 | "version": "0.5.3", 142 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 143 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 144 | "requires": { 145 | "safe-buffer": "5.1.2" 146 | } 147 | }, 148 | "content-type": { 149 | "version": "1.0.4", 150 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 151 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 152 | }, 153 | "cookie": { 154 | "version": "0.4.0", 155 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 156 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 157 | }, 158 | "cookie-signature": { 159 | "version": "1.0.6", 160 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 161 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 162 | }, 163 | "cors": { 164 | "version": "2.8.5", 165 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 166 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 167 | "requires": { 168 | "object-assign": "^4", 169 | "vary": "^1" 170 | } 171 | }, 172 | "debug": { 173 | "version": "2.6.9", 174 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 175 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 176 | "requires": { 177 | "ms": "2.0.0" 178 | } 179 | }, 180 | "depd": { 181 | "version": "1.1.2", 182 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 183 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 184 | }, 185 | "destroy": { 186 | "version": "1.0.4", 187 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 188 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 189 | }, 190 | "ee-first": { 191 | "version": "1.1.1", 192 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 193 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 194 | }, 195 | "encodeurl": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 198 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 199 | }, 200 | "escape-html": { 201 | "version": "1.0.3", 202 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 203 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 204 | }, 205 | "etag": { 206 | "version": "1.8.1", 207 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 208 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 209 | }, 210 | "eventemitter3": { 211 | "version": "4.0.7", 212 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 213 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 214 | }, 215 | "express": { 216 | "version": "4.17.1", 217 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 218 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 219 | "requires": { 220 | "accepts": "~1.3.7", 221 | "array-flatten": "1.1.1", 222 | "body-parser": "1.19.0", 223 | "content-disposition": "0.5.3", 224 | "content-type": "~1.0.4", 225 | "cookie": "0.4.0", 226 | "cookie-signature": "1.0.6", 227 | "debug": "2.6.9", 228 | "depd": "~1.1.2", 229 | "encodeurl": "~1.0.2", 230 | "escape-html": "~1.0.3", 231 | "etag": "~1.8.1", 232 | "finalhandler": "~1.1.2", 233 | "fresh": "0.5.2", 234 | "merge-descriptors": "1.0.1", 235 | "methods": "~1.1.2", 236 | "on-finished": "~2.3.0", 237 | "parseurl": "~1.3.3", 238 | "path-to-regexp": "0.1.7", 239 | "proxy-addr": "~2.0.5", 240 | "qs": "6.7.0", 241 | "range-parser": "~1.2.1", 242 | "safe-buffer": "5.1.2", 243 | "send": "0.17.1", 244 | "serve-static": "1.14.1", 245 | "setprototypeof": "1.1.1", 246 | "statuses": "~1.5.0", 247 | "type-is": "~1.6.18", 248 | "utils-merge": "1.0.1", 249 | "vary": "~1.1.2" 250 | } 251 | }, 252 | "fill-range": { 253 | "version": "7.0.1", 254 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 255 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 256 | "requires": { 257 | "to-regex-range": "^5.0.1" 258 | } 259 | }, 260 | "finalhandler": { 261 | "version": "1.1.2", 262 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 263 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 264 | "requires": { 265 | "debug": "2.6.9", 266 | "encodeurl": "~1.0.2", 267 | "escape-html": "~1.0.3", 268 | "on-finished": "~2.3.0", 269 | "parseurl": "~1.3.3", 270 | "statuses": "~1.5.0", 271 | "unpipe": "~1.0.0" 272 | } 273 | }, 274 | "follow-redirects": { 275 | "version": "1.14.1", 276 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", 277 | "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" 278 | }, 279 | "forwarded": { 280 | "version": "0.2.0", 281 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 282 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 283 | }, 284 | "fresh": { 285 | "version": "0.5.2", 286 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 287 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 288 | }, 289 | "http-errors": { 290 | "version": "1.7.2", 291 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 292 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 293 | "requires": { 294 | "depd": "~1.1.2", 295 | "inherits": "2.0.3", 296 | "setprototypeof": "1.1.1", 297 | "statuses": ">= 1.5.0 < 2", 298 | "toidentifier": "1.0.0" 299 | } 300 | }, 301 | "http-proxy": { 302 | "version": "1.18.1", 303 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 304 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 305 | "requires": { 306 | "eventemitter3": "^4.0.0", 307 | "follow-redirects": "^1.0.0", 308 | "requires-port": "^1.0.0" 309 | } 310 | }, 311 | "http-proxy-middleware": { 312 | "version": "2.0.1", 313 | "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", 314 | "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", 315 | "requires": { 316 | "@types/http-proxy": "^1.17.5", 317 | "http-proxy": "^1.18.1", 318 | "is-glob": "^4.0.1", 319 | "is-plain-obj": "^3.0.0", 320 | "micromatch": "^4.0.2" 321 | } 322 | }, 323 | "iconv-lite": { 324 | "version": "0.4.24", 325 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 326 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 327 | "requires": { 328 | "safer-buffer": ">= 2.1.2 < 3" 329 | } 330 | }, 331 | "inherits": { 332 | "version": "2.0.3", 333 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 334 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 335 | }, 336 | "ipaddr.js": { 337 | "version": "1.9.1", 338 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 339 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 340 | }, 341 | "is-extglob": { 342 | "version": "2.1.1", 343 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 344 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 345 | }, 346 | "is-glob": { 347 | "version": "4.0.1", 348 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 349 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 350 | "requires": { 351 | "is-extglob": "^2.1.1" 352 | } 353 | }, 354 | "is-number": { 355 | "version": "7.0.0", 356 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 357 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 358 | }, 359 | "is-plain-obj": { 360 | "version": "3.0.0", 361 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", 362 | "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" 363 | }, 364 | "media-typer": { 365 | "version": "0.3.0", 366 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 367 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 368 | }, 369 | "merge-descriptors": { 370 | "version": "1.0.1", 371 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 372 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 373 | }, 374 | "methods": { 375 | "version": "1.1.2", 376 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 377 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 378 | }, 379 | "micromatch": { 380 | "version": "4.0.4", 381 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 382 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 383 | "requires": { 384 | "braces": "^3.0.1", 385 | "picomatch": "^2.2.3" 386 | } 387 | }, 388 | "mime": { 389 | "version": "1.6.0", 390 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 391 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 392 | }, 393 | "mime-db": { 394 | "version": "1.49.0", 395 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", 396 | "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" 397 | }, 398 | "mime-types": { 399 | "version": "2.1.32", 400 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", 401 | "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", 402 | "requires": { 403 | "mime-db": "1.49.0" 404 | } 405 | }, 406 | "ms": { 407 | "version": "2.0.0", 408 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 409 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 410 | }, 411 | "negotiator": { 412 | "version": "0.6.2", 413 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 414 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 415 | }, 416 | "object-assign": { 417 | "version": "4.1.1", 418 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 419 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 420 | }, 421 | "on-finished": { 422 | "version": "2.3.0", 423 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 424 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 425 | "requires": { 426 | "ee-first": "1.1.1" 427 | } 428 | }, 429 | "parseurl": { 430 | "version": "1.3.3", 431 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 432 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 433 | }, 434 | "path-to-regexp": { 435 | "version": "0.1.7", 436 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 437 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 438 | }, 439 | "picomatch": { 440 | "version": "2.3.0", 441 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 442 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" 443 | }, 444 | "proxy-addr": { 445 | "version": "2.0.7", 446 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 447 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 448 | "requires": { 449 | "forwarded": "0.2.0", 450 | "ipaddr.js": "1.9.1" 451 | } 452 | }, 453 | "qs": { 454 | "version": "6.7.0", 455 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 456 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 457 | }, 458 | "range-parser": { 459 | "version": "1.2.1", 460 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 461 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 462 | }, 463 | "raw-body": { 464 | "version": "2.4.0", 465 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 466 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 467 | "requires": { 468 | "bytes": "3.1.0", 469 | "http-errors": "1.7.2", 470 | "iconv-lite": "0.4.24", 471 | "unpipe": "1.0.0" 472 | } 473 | }, 474 | "requires-port": { 475 | "version": "1.0.0", 476 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 477 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 478 | }, 479 | "safe-buffer": { 480 | "version": "5.1.2", 481 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 482 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 483 | }, 484 | "safer-buffer": { 485 | "version": "2.1.2", 486 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 487 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 488 | }, 489 | "send": { 490 | "version": "0.17.1", 491 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 492 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 493 | "requires": { 494 | "debug": "2.6.9", 495 | "depd": "~1.1.2", 496 | "destroy": "~1.0.4", 497 | "encodeurl": "~1.0.2", 498 | "escape-html": "~1.0.3", 499 | "etag": "~1.8.1", 500 | "fresh": "0.5.2", 501 | "http-errors": "~1.7.2", 502 | "mime": "1.6.0", 503 | "ms": "2.1.1", 504 | "on-finished": "~2.3.0", 505 | "range-parser": "~1.2.1", 506 | "statuses": "~1.5.0" 507 | }, 508 | "dependencies": { 509 | "ms": { 510 | "version": "2.1.1", 511 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 512 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 513 | } 514 | } 515 | }, 516 | "serve-static": { 517 | "version": "1.14.1", 518 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 519 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 520 | "requires": { 521 | "encodeurl": "~1.0.2", 522 | "escape-html": "~1.0.3", 523 | "parseurl": "~1.3.3", 524 | "send": "0.17.1" 525 | } 526 | }, 527 | "setprototypeof": { 528 | "version": "1.1.1", 529 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 530 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 531 | }, 532 | "statuses": { 533 | "version": "1.5.0", 534 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 535 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 536 | }, 537 | "to-regex-range": { 538 | "version": "5.0.1", 539 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 540 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 541 | "requires": { 542 | "is-number": "^7.0.0" 543 | } 544 | }, 545 | "toidentifier": { 546 | "version": "1.0.0", 547 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 548 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 549 | }, 550 | "type-is": { 551 | "version": "1.6.18", 552 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 553 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 554 | "requires": { 555 | "media-typer": "0.3.0", 556 | "mime-types": "~2.1.24" 557 | } 558 | }, 559 | "typescript": { 560 | "version": "4.3.5", 561 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 562 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 563 | "dev": true 564 | }, 565 | "unpipe": { 566 | "version": "1.0.0", 567 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 568 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 569 | }, 570 | "utils-merge": { 571 | "version": "1.0.1", 572 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 573 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 574 | }, 575 | "vary": { 576 | "version": "1.1.2", 577 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 578 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 579 | } 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /packages/local-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jnotebook/local-api", 3 | "version": "1.1.2", 4 | "description": "", 5 | "files": [ 6 | "dist" 7 | ], 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "main": "dist/index.js", 12 | "types": "dist/index.d.ts", 13 | "scripts": { 14 | "start": "tsc --watch --preserveWatchOutput", 15 | "prepublishOnly": "tsc" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@types/cors": "^2.8.12", 22 | "@types/express": "^4.17.13", 23 | "typescript": "^4.3.5" 24 | }, 25 | "dependencies": { 26 | "@jnotebook/local-client": "^1.1.1", 27 | "cors": "^2.8.5", 28 | "express": "^4.17.1", 29 | "http-proxy-middleware": "^2.0.1" 30 | }, 31 | "gitHead": "a2b81fd1e490a09362b335359fb1842eb096bd3b" 32 | } 33 | -------------------------------------------------------------------------------- /packages/local-api/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'path'; 3 | import { createProxyMiddleware } from 'http-proxy-middleware'; 4 | import { createCellsRouter } from './routes/cellsRouter'; 5 | 6 | export const serve = (port: number, filename: string, dir: string, useProxy: boolean) => { 7 | const app = express(); 8 | 9 | app.use(createCellsRouter(filename, dir)); 10 | 11 | if (useProxy) { 12 | app.use( 13 | createProxyMiddleware({ 14 | target: 'http://localhost:3000', 15 | ws: true, 16 | logLevel: 'silent', 17 | }) 18 | ); 19 | } else { 20 | const packagePath = require.resolve('@jnotebook/local-client/build/index.html'); 21 | app.use(express.static(path.dirname(packagePath))); 22 | } 23 | 24 | return new Promise((resolve, reject) => { 25 | app.listen(port, resolve).on('error', reject); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/local-api/src/routes/cellsRouter.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import fs from 'fs/promises'; 3 | import path from 'path'; 4 | 5 | interface Cell { 6 | id: string; 7 | content: string; 8 | type: 'text' | 'code'; 9 | } 10 | 11 | const initialContent = [ 12 | { 13 | content: 14 | '# JNotebook\nThis is an interactive coding environment. You can write Javascript, see it executed, and write comprehensive documentation using markdown.\n\n- Click any text cell (including this one) to edit it\n- The code in each code editor is all joined together into one file. If you define a variable in cell #1, you can refer to it in any following cell!\n- You can use any library in npm by importing it\n- You can show any React component, string, number, or anything else by calling the `show` function. This is a function built into this environment. Call show multiple times to show multiple values\n- Re-order or delete cells using the buttons on the top right\n- Add new cells by hovering on the divider between each cell\n\nAll of your changes get saved to the file you opened JNotebook with. So if you ran `npx jnotebook serve test.js`, all of the text and code you write will be saved to the test.js file.', 15 | type: 'text', 16 | id: 'mnfhh', 17 | }, 18 | { 19 | content: 20 | "import { useState } from 'react';\r\n\r\nconst Counter = () => {\r\n const [count, setCount] = useState(0);\r\n\r\n return (\r\n
\r\n \r\n

Count: {count}

\r\n
\r\n );\r\n};\r\n\r\n// Display any variable or React component by calling 'show'\r\nshow();", 21 | type: 'code', 22 | id: '08dz9', 23 | }, 24 | { 25 | content: 26 | 'const App = () => {\r\n return (\r\n
\r\n

App Says Hi!

\r\n Counter component will be rendered below...\r\n
\r\n {/* Counter was declared in an earlier cell - we can reference it here! */}\r\n \r\n
\r\n );\r\n};\r\n\r\nshow()', 27 | type: 'code', 28 | id: '5xvar', 29 | }, 30 | ]; 31 | 32 | export const createCellsRouter = (filename: string, dir: string) => { 33 | const router = express.Router(); 34 | router.use(express.json()); 35 | 36 | const fullPath = path.join(dir, filename); 37 | 38 | router.get('/cells', async (req, res) => { 39 | try { 40 | const result = await fs.readFile(fullPath, { encoding: 'utf-8' }); 41 | res.json(JSON.parse(result)); 42 | } catch (error) { 43 | if (error.code === 'ENOENT') { 44 | await fs.writeFile(fullPath, JSON.stringify(initialContent), 'utf-8'); 45 | res.json(initialContent); 46 | } else { 47 | throw error; 48 | } 49 | } 50 | }); 51 | 52 | router.post('/cells', async (req, res) => { 53 | const { cells }: { cells: Cell[] } = req.body; 54 | 55 | await fs.writeFile(fullPath, JSON.stringify(cells), 'utf-8'); 56 | 57 | res.json({ status: 'ok' }); 58 | }); 59 | 60 | return router; 61 | }; 62 | -------------------------------------------------------------------------------- /packages/local-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/local-client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## 1.0.1 (2021-08-21) 7 | 8 | **Note:** Version bump only for package @jpen/local-client 9 | -------------------------------------------------------------------------------- /packages/local-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jnotebook/local-client", 3 | "version": "1.1.1", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "files": [ 8 | "build" 9 | ], 10 | "devDependencies": { 11 | "@fortawesome/fontawesome-free": "^5.15.4", 12 | "@monaco-editor/react": "^4.2.1", 13 | "@testing-library/jest-dom": "^5.14.1", 14 | "@testing-library/react": "^11.2.7", 15 | "@testing-library/user-event": "^12.8.3", 16 | "@types/jest": "^26.0.24", 17 | "@types/jscodeshift": "0.7.2", 18 | "@types/node": "^12.20.16", 19 | "@types/react": "^17.0.14", 20 | "@types/react-dom": "^17.0.9", 21 | "@types/react-resizable": "^1.7.3", 22 | "@types/styled-components": "^5.1.12", 23 | "@uiw/react-md-editor": "^3.4.11", 24 | "axios": "^0.21.1", 25 | "bulmaswatch": "^0.8.1", 26 | "esbuild-wasm": "^0.12.15", 27 | "immer": "^9.0.5", 28 | "jscodeshift": "0.11.0", 29 | "localforage": "^1.9.0", 30 | "monaco-editor": "^0.26.1", 31 | "monaco-jsx-highlighter": "0.0.15", 32 | "prettier": "2.2.1", 33 | "react": "^17.0.2", 34 | "react-dom": "^17.0.2", 35 | "react-redux": "^7.2.4", 36 | "react-resizable": "^3.0.4", 37 | "react-scripts": "4.0.3", 38 | "redux": "^4.1.0", 39 | "redux-devtools-extension": "^2.13.9", 40 | "redux-thunk": "^2.3.0", 41 | "source-map-explorer": "^2.5.2", 42 | "styled-components": "^5.3.0", 43 | "typescript": "^4.3.5", 44 | "web-vitals": "^1.1.2" 45 | }, 46 | "scripts": { 47 | "analyze": "source-map-explorer 'build/static/js/*.js'", 48 | "dev": "react-scripts start", 49 | "build": "react-scripts build", 50 | "test": "react-scripts test", 51 | "eject": "react-scripts eject", 52 | "prepublishOnly": "npm run build" 53 | }, 54 | "eslintConfig": { 55 | "extends": [ 56 | "react-app", 57 | "react-app/jest" 58 | ] 59 | }, 60 | "browserslist": { 61 | "production": [ 62 | ">0.2%", 63 | "not dead", 64 | "not op_mini all" 65 | ], 66 | "development": [ 67 | "last 1 chrome version", 68 | "last 1 firefox version", 69 | "last 1 safari version" 70 | ] 71 | }, 72 | "gitHead": "a2b81fd1e490a09362b335359fb1842eb096bd3b" 73 | } 74 | -------------------------------------------------------------------------------- /packages/local-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykutulis/jnotebook/5823e73d7f000626261c3c566c2cde00907eac6d/packages/local-client/public/favicon.ico -------------------------------------------------------------------------------- /packages/local-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | JNotebook 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/local-client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykutulis/jnotebook/5823e73d7f000626261c3c566c2cde00907eac6d/packages/local-client/public/logo192.png -------------------------------------------------------------------------------- /packages/local-client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykutulis/jnotebook/5823e73d7f000626261c3c566c2cde00907eac6d/packages/local-client/public/logo512.png -------------------------------------------------------------------------------- /packages/local-client/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 | -------------------------------------------------------------------------------- /packages/local-client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/local-client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CellList } from './components/CellList'; 3 | import { Provider } from 'react-redux'; 4 | import { store } from './state'; 5 | 6 | export const App: React.FC = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/local-client/src/bundler/index.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild-wasm/esm/browser'; 2 | import { unpkgPathPlugin } from './plugins/unpkgPathPlugin'; 3 | import { fetchPlugin } from './plugins/fetchPlugin'; 4 | 5 | export const startService = async () => { 6 | await esbuild.initialize({ 7 | worker: true, 8 | wasmURL: 'https://unpkg.com/esbuild-wasm@0.12.15/esbuild.wasm', 9 | }); 10 | }; 11 | 12 | export const bundler = async (rawCode: string) => { 13 | try { 14 | const result = await esbuild.build({ 15 | entryPoints: ['index.js'], 16 | bundle: true, 17 | write: false, 18 | plugins: [unpkgPathPlugin(), fetchPlugin(rawCode)], 19 | define: { 20 | 'process.env.NODE_ENV': '"production"', 21 | global: 'window', 22 | }, 23 | logLimit: 0, 24 | jsxFactory: '_React.createElement', 25 | jsxFragment: '_React.Fragment', 26 | }); 27 | return { 28 | code: result.outputFiles[0].text, 29 | error: '', 30 | }; 31 | } catch (error) { 32 | return { 33 | code: '', 34 | error: error.message, 35 | }; 36 | } 37 | }; 38 | 39 | export default bundler; 40 | -------------------------------------------------------------------------------- /packages/local-client/src/bundler/plugins/fetchPlugin.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild-wasm'; 2 | import axios from 'axios'; 3 | import localForage from 'localforage'; 4 | 5 | const fileCache = localForage.createInstance({ 6 | name: 'filecache', 7 | }); 8 | 9 | export const fetchPlugin = (inputCode: string) => { 10 | return { 11 | name: 'fetch-plugin', 12 | setup: (build: esbuild.PluginBuild) => { 13 | build.onLoad({ filter: /(^index\.js$)/ }, async (args: any) => { 14 | return { 15 | loader: 'jsx', 16 | contents: inputCode, 17 | }; 18 | }); 19 | 20 | build.onLoad({ filter: /.*/ }, async (args: any) => { 21 | const cachedResult = await fileCache.getItem(args.path); 22 | 23 | if (cachedResult) return cachedResult; 24 | }); 25 | 26 | build.onLoad({ filter: /.css$/ }, async (args: any) => { 27 | const { data, request } = await axios.get(args.path); 28 | 29 | const escaped = data.replace(/\n/g, '').replace(/"/g, '\\"').replace(/'/g, "\\'"); 30 | const contents = ` 31 | const style = document.createElement('style'); 32 | style.innerText = '${escaped}'; 33 | document.head.appendChild(style); 34 | `; 35 | 36 | const result: esbuild.OnLoadResult = { 37 | loader: 'jsx', 38 | contents, 39 | resolveDir: new URL('./', request.responseURL).pathname, 40 | }; 41 | await fileCache.setItem(args.path, result); 42 | 43 | return result; 44 | }); 45 | 46 | build.onLoad({ filter: /.*/ }, async (args: any) => { 47 | const { data, request } = await axios.get(args.path); 48 | 49 | const result: esbuild.OnLoadResult = { 50 | loader: 'jsx', 51 | contents: data, 52 | resolveDir: new URL('./', request.responseURL).pathname, 53 | }; 54 | await fileCache.setItem(args.path, result); 55 | 56 | return result; 57 | }); 58 | }, 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/local-client/src/bundler/plugins/unpkgPathPlugin.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild-wasm'; 2 | 3 | export const unpkgPathPlugin = () => { 4 | return { 5 | name: 'unpkg-path-plugin', 6 | setup: (build: esbuild.PluginBuild) => { 7 | // Handle root entry file of 'index.js' 8 | build.onResolve({ filter: /(^index\.js$)/ }, () => { 9 | return { path: 'index.js', namespace: 'a' }; 10 | }); 11 | 12 | // Handle relative paths in a module 13 | build.onResolve({ filter: /^\.+\// }, (args: any) => { 14 | return { 15 | namespace: 'a', 16 | path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href, 17 | }; 18 | }); 19 | 20 | // Handle main file of a module 21 | build.onResolve({ filter: /.*/ }, async (args: any) => { 22 | return { 23 | namespace: 'a', 24 | path: `https://unpkg.com/${args.path}`, 25 | }; 26 | }); 27 | }, 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /packages/local-client/src/components/ActionBar/ActionBar.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledActionBar = styled.div` 4 | position: absolute; 5 | top: 0; 6 | right: 0; 7 | opacity: 0.4; 8 | transition: opacity 0.3s; 9 | 10 | :hover { 11 | opacity: 1; 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /packages/local-client/src/components/ActionBar/ActionBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ActionButton } from '../ActionButton'; 3 | import { useActions } from '../../hooks'; 4 | import { StyledActionBar } from './ActionBar.style'; 5 | 6 | interface ActionBarProps { 7 | id: string; 8 | } 9 | 10 | export const ActionBar: React.FC = ({ id }) => { 11 | const { moveCell, deleteCell } = useActions(); 12 | 13 | return ( 14 | 15 | } onClick={() => moveCell(id, 'up')} /> 16 | } onClick={() => moveCell(id, 'down')} /> 17 | } onClick={() => deleteCell(id)} /> 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/local-client/src/components/ActionBar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActionBar'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/ActionButton/ActionButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ActionButtonProps { 4 | onClick?: React.MouseEventHandler; 5 | iconComponent: JSX.Element; 6 | } 7 | 8 | export const ActionButton: React.FC = ({ onClick, iconComponent, children }) => { 9 | return ( 10 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/local-client/src/components/ActionButton/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ActionButton'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/AddCellDivider/AddCellDivider.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | interface StyledAddCellDividerProps { 4 | forceVisible?: boolean; 5 | } 6 | 7 | export const StyledAddCellDivider = styled.div` 8 | position: relative; 9 | opacity: ${({ forceVisible }) => (forceVisible ? 1 : 0)}; 10 | padding: 15px 0; 11 | transition: opacity 0.3s ease 0.1s; 12 | 13 | :hover { 14 | opacity: 1; 15 | } 16 | 17 | .divider { 18 | position: absolute; 19 | top: 50%; 20 | bottom: 50%; 21 | right: 2.5%; 22 | left: 2.5%; 23 | border-bottom: 1px solid gray; 24 | z-index: -1; 25 | } 26 | 27 | .add-cell-buttons { 28 | display: flex; 29 | justify-content: center; 30 | } 31 | 32 | .add-cell-buttons button { 33 | margin: 0 35px; 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /packages/local-client/src/components/AddCellDivider/AddCellDivider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledAddCellDivider } from './AddCellDivider.style'; 3 | import { useActions } from '../../hooks'; 4 | 5 | interface AddCellDividerProps { 6 | prevCellId: string | null; 7 | forceVisible?: boolean; 8 | } 9 | 10 | export const AddCellDivider: React.FC = ({ prevCellId, forceVisible }) => { 11 | const { insertCellAfter } = useActions(); 12 | 13 | return ( 14 | 15 |
16 | 22 | 28 |
29 |
30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /packages/local-client/src/components/AddCellDivider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddCellDivider'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellList/CellList.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledCellList = styled.div` 4 | margin: 0 25px 25vh; 5 | 6 | .react-draggable-transparent-selection & { 7 | margin-bottom: 100vh; 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellList/CellList.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useMemo, useEffect } from 'react'; 2 | import { useTypedSelector, useActions } from '../../hooks'; 3 | import { CellListItem } from '../CellListItem'; 4 | import { AddCellDivider } from '../AddCellDivider'; 5 | import { StyledCellList } from './CellList.style'; 6 | 7 | export const CellList: React.FC = () => { 8 | const cells = useTypedSelector(({ cells: { data, order } }) => order.map((id) => data[id])); 9 | 10 | const { fetchCells } = useActions(); 11 | 12 | useEffect(() => { 13 | fetchCells(); 14 | }, [fetchCells]); 15 | 16 | const renderedCells = useMemo(() => { 17 | return cells.map((cell) => ( 18 | 19 | 20 | 21 | 22 | )); 23 | }, [cells]); 24 | 25 | return ( 26 | 27 | 28 | {renderedCells} 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CellList'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellListItem/CellListItem.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledCellListItem = styled.div` 4 | position: relative; 5 | 6 | .action-bar-wrapper { 7 | height: 30px; 8 | width: 100%; 9 | background-color: #37414b; 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellListItem/CellListItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Cell } from '../../state'; 3 | import { CodeCell } from '../CodeCell'; 4 | import { TextEditor } from '../TextEditor'; 5 | import { ActionBar } from '../ActionBar'; 6 | import { StyledCellListItem } from './CellListItem.style'; 7 | 8 | interface CellListItemProps { 9 | cell: Cell; 10 | } 11 | 12 | export const CellListItem: React.FC = ({ cell }) => { 13 | const child = useMemo((): JSX.Element => { 14 | if (cell.type === 'code') { 15 | return ( 16 | <> 17 |
18 | 19 |
20 | 21 | 22 | ); 23 | } else { 24 | return ( 25 | <> 26 | 27 | 28 | 29 | ); 30 | } 31 | }, [cell]); 32 | 33 | return {child}; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CellListItem/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CellListItem'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeCell/CodeCell.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledPreviewContainer = styled.div` 4 | flex: 1; 5 | display: flex; 6 | height: 100%; 7 | position: relative; 8 | background-color: #fff; 9 | `; 10 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeCell/CodeCell.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { CodeEditor } from '../CodeEditor'; 3 | import { Preview } from '../Preview'; 4 | import { Resizable } from '../Resizable'; 5 | import { Loading } from '../Loading'; 6 | import { StyledPreviewContainer } from './CodeCell.style'; 7 | import { useActions, useTypedSelector, useCumulativeCode } from '../../hooks'; 8 | import { Cell } from '../../state'; 9 | 10 | interface CodeCellProps { 11 | cell: Cell; 12 | } 13 | 14 | export const CodeCell: React.FC = ({ cell }) => { 15 | const { updateCell, createBundle } = useActions(); 16 | const bundle = useTypedSelector((state) => state.bundles[cell.id]); 17 | const cumulativeCode = useCumulativeCode(cell.id); 18 | 19 | const firstRender = useRef(true); 20 | 21 | useEffect(() => { 22 | if (firstRender.current) { 23 | createBundle(cell.id, cumulativeCode); 24 | firstRender.current = false; 25 | return; 26 | } 27 | 28 | const timer = setTimeout(() => { 29 | createBundle(cell.id, cumulativeCode); 30 | }, 1000); 31 | 32 | return () => clearTimeout(timer); 33 | }, [cell.id, cumulativeCode, createBundle]); 34 | 35 | const handleUpdateCell = (value: string | undefined) => { 36 | updateCell(cell.id, value || ''); 37 | }; 38 | 39 | return ( 40 | 41 |
42 | 43 | 49 | 50 | 51 | 52 | {!bundle || (bundle.loading && )} 53 | 54 |
55 |
56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeCell/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeCell'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeEditor/CodeEditor.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledCodeEditor = styled.div` 4 | position: relative; 5 | width: calc(100% - 10px); 6 | height: 100%; 7 | 8 | .button-format { 9 | position: absolute; 10 | top: 5px; 11 | right: 5px; 12 | z-index: 55; 13 | opacity: 0; 14 | transition: opacity 0.3s ease; 15 | } 16 | 17 | :hover .button-format { 18 | opacity: 1; 19 | } 20 | 21 | /* Syntax Highlighter */ 22 | .mtk1 { 23 | color: #d4d4d4; 24 | } 25 | .mtk2 { 26 | color: #1e1e1e; 27 | } 28 | .mtk3 { 29 | color: #000080; 30 | } 31 | .mtk4 { 32 | color: #6a9955; 33 | } 34 | .mtk5 { 35 | color: #569cd6; 36 | } 37 | .mtk6 { 38 | color: #b5cea8; 39 | } 40 | .mtk7 { 41 | color: #646695; 42 | } 43 | .mtk8 { 44 | color: #c586c0; 45 | } 46 | .mtk9 { 47 | color: #9cdcfe; 48 | } 49 | .mtk10 { 50 | color: #f44747; 51 | } 52 | .mtk11 { 53 | color: #ce9178; 54 | } 55 | .mtk12 { 56 | color: #6796e6; 57 | } 58 | .mtk13 { 59 | color: #808080; 60 | } 61 | .mtk14 { 62 | color: #d16969; 63 | } 64 | .mtk15 { 65 | color: #dcdcaa; 66 | } 67 | .mtk16 { 68 | color: #4ec9b0; 69 | } 70 | .mtk17 { 71 | color: #c586c0; 72 | } 73 | .mtk18 { 74 | color: #4fc1ff; 75 | } 76 | .mtk19 { 77 | color: #c8c8c8; 78 | } 79 | .mtk20 { 80 | color: #cd9731; 81 | } 82 | .mtk21 { 83 | color: #b267e6; 84 | } 85 | .mtki { 86 | font-style: italic; 87 | } 88 | .mtkb { 89 | font-weight: bold; 90 | } 91 | .mtku { 92 | text-decoration: underline; 93 | text-underline-position: under; 94 | } 95 | 96 | .mtk100.Identifier.JsxElement.Bracket { 97 | color: #0080ff; 98 | } 99 | 100 | .mtk1000.Identifier.JsxOpeningElement.Bracket { 101 | color: #808080; 102 | font-weight: bold; 103 | } 104 | 105 | .mtk1001.Identifier.JsxClosingElement.Bracket { 106 | color: #808080; 107 | font-weight: lighter; 108 | } 109 | 110 | .mtk101.Identifier.JsxOpeningElement.Identifier { 111 | color: #569cd6; 112 | } 113 | 114 | .mtk102.Identifier.JsxClosingElement.Identifier { 115 | color: #569cd6; 116 | font-weight: lighter; 117 | } 118 | 119 | .mtk103.Identifier.JsxAttribute.Identifier { 120 | color: #9cdcfe; 121 | } 122 | 123 | .mtk104.JsxElement.JsxText { 124 | color: darkgoldenrod; 125 | } 126 | 127 | .mtk105.glyph.Identifier.JsxElement { 128 | background: #61dafb; 129 | opacity: 0.25; 130 | } 131 | 132 | .mtk12.Identifier.JsxExpression.JsxClosingElement { 133 | color: #ec5f67; 134 | } 135 | 136 | .mtk12.Identifier.JsxSelfClosingElement { 137 | color: #ec5f67; 138 | } 139 | .mtk12.Identifier.VariableStatement.JsxClosingElement { 140 | color: #ec5f67 !important; 141 | } 142 | .mtk12.VariableStatement.JsxSelfClosingElement.Identifier { 143 | color: #ec5f67; 144 | } 145 | .mtk12.Identifier.JsxAttribute.VariableDeclaration { 146 | color: crimson; 147 | } 148 | .mtk12.JsxExpression.VariableStatement { 149 | color: #fac863; 150 | } 151 | .mtk12.VariableStatement.JsxSelfClosingElement { 152 | color: #ede0e0; 153 | } 154 | .mtk12.VariableStatement.JsxClosingElement { 155 | color: #ede0e0; 156 | } 157 | .JsxText { 158 | color: #0c141f; 159 | } 160 | `; 161 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeEditor/CodeEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import Editor, { OnChange, OnMount } from '@monaco-editor/react'; 3 | import { format } from 'prettier'; 4 | import parserBabel from 'prettier/parser-babel'; 5 | import codeShift from 'jscodeshift'; 6 | import Highlighter from 'monaco-jsx-highlighter'; 7 | import { StyledCodeEditor } from './CodeEditor.style'; 8 | 9 | const handleEditorOnMount: OnMount = (monacoEditor) => { 10 | const highlighter = new Highlighter( 11 | // @ts-ignore 12 | window.monaco, 13 | codeShift, 14 | monacoEditor 15 | ); 16 | highlighter.highLightOnDidChangeModelContent( 17 | () => {}, 18 | () => {}, 19 | undefined, 20 | () => {} 21 | ); 22 | }; 23 | 24 | interface CodeEditorProps { 25 | onChange: OnChange; 26 | input: string; 27 | initialValue?: string; 28 | onFormat?: (value: string) => void; 29 | } 30 | 31 | export const CodeEditor: React.FC = ({ onChange, onFormat, input, initialValue }) => { 32 | const handleOnFormat = useCallback(() => { 33 | if (!onFormat) return; 34 | 35 | try { 36 | const formatted = format(input, { 37 | parser: 'babel', 38 | plugins: [parserBabel], 39 | useTabs: false, 40 | semi: true, 41 | singleQuote: true, 42 | }).replace(/\n$/, ''); 43 | onFormat(formatted); 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | }, [onFormat, input]); 48 | 49 | return ( 50 | 51 | 54 | 73 | 74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /packages/local-client/src/components/CodeEditor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeEditor'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/Loading/Loading.style.ts: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | 3 | const fadeInAnimate = keyframes` 4 | 0% { 5 | opacity: 0; 6 | } 7 | 8 | 50% { 9 | opacity: 0; 10 | } 11 | 12 | 100% { 13 | opacity: 1; 14 | } 15 | `; 16 | 17 | export const StyledLoading = styled.div` 18 | position: absolute; 19 | height: 100%; 20 | width: 100%; 21 | top: 0; 22 | backdrop-filter: blur(1.5px); 23 | display: flex; 24 | align-items: center; 25 | padding-left: 10%; 26 | padding-right: 10%; 27 | animation: ${fadeInAnimate} 0.5s; 28 | `; 29 | -------------------------------------------------------------------------------- /packages/local-client/src/components/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyledLoading } from './Loading.style'; 3 | 4 | export const Loading: React.FC = () => { 5 | return ( 6 | 7 | 8 | Loading 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/local-client/src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Loading'; 2 | -------------------------------------------------------------------------------- /packages/local-client/src/components/Preview/Preview.style.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StyledPreview = styled.div` 4 | background-color: #fff; 5 | position: relative; 6 | height: 100%; 7 | flex: 1; 8 | 9 | iframe { 10 | height: 100%; 11 | width: 100%; 12 | } 13 | 14 | .preview-error { 15 | position: absolute; 16 | top: 10px; 17 | left: 10px; 18 | color: red; 19 | } 20 | 21 | .react-draggable-transparent-selection &::after { 22 | content: ''; 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | left: 0; 27 | bottom: 0; 28 | opacity: 0; 29 | } 30 | `; 31 | -------------------------------------------------------------------------------- /packages/local-client/src/components/Preview/Preview.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { StyledPreview } from './Preview.style'; 3 | 4 | interface PreviewProps { 5 | code?: string; 6 | err?: string; 7 | } 8 | 9 | const html = ` 10 | 11 | 12 | 15 | 16 | 17 |
18 | 37 | 38 | 39 | `; 40 | 41 | export const Preview: React.FC = ({ code, err }) => { 42 | const iframe = useRef(); 43 | 44 | useEffect(() => { 45 | if (code || err) { 46 | iframe.current.srcdoc = html; 47 | setTimeout(() => { 48 | iframe.current?.contentWindow?.postMessage(code, '*'); 49 | }, 100); 50 | } 51 | }, [code, err]); 52 | 53 | return ( 54 | 55 |