Repository: satya164/monaco-editor-boilerplate Branch: master Commit: 3237471d2efb Files: 22 Total size: 3.2 MB Directory structure: gitextract_lphjetof/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package.json ├── serve.config.js ├── src/ │ ├── App.js │ ├── Editor.css │ ├── Editor.js │ ├── config/ │ │ └── eslint.json │ ├── index.js │ ├── themes/ │ │ ├── dark.js │ │ └── light.js │ ├── vendor/ │ │ └── eslint.bundle.js │ └── workers/ │ ├── eslint.worker.js │ └── typings.worker.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [["env", { "modules": false }], "react", "stage-2"] } ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] # change these settings to your own preference indent_style = space indent_size = 2 # we recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false [{package,bower}.json] indent_style = space indent_size = 2 ================================================ FILE: .eslintignore ================================================ **/dist/* **/node_modules/* ================================================ FILE: .eslintrc ================================================ { "extends": "eslint-config-satya164", "env": { "browser": true, "node": true, "es6": true, } } ================================================ FILE: .flowconfig ================================================ [ignore] [include] [libs] [options] munge_underscores=true esproposal.class_static_fields=enable esproposal.class_instance_fields=enable module.name_mapper='^monaco-editor$' -> 'monaco-editor/esm/vs/editor/editor.main.js' module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf|ttf|otf\)$' -> 'ImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe suppress_comment=\\(.\\|\n\\)*\\$FlowIssue suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe ================================================ FILE: .gitignore ================================================ *~ .DS_Store .sass-cache npm-debug.log node_modules/ bower_components/ dist/ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Satyajit Sahoo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ Monaco Editor boilerplate ========================= A simple boilerplate for Monaco editor with React. ================================================ FILE: index.html ================================================ Monaco Editor Sample
================================================ FILE: package.json ================================================ { "name": "monaco-editor-boilerplate", "version": "1.0.0", "private": true, "description": "Boilerplate for projects using ES2015 and React", "main": "index.js", "repository": { "type": "git", "url": "git://github.com/satya164/react-boilerplate.git" }, "scripts": { "start": "cross-env NODE_ENV=development webpack-serve", "flow": "flow", "lint": "eslint .", "build": "cross-env NODE_ENV=production webpack -p", "prebuild": "del dist" }, "author": "Satyajit Sahoo (https://github.com/satya164/)", "license": "MIT", "devDependencies": { "babel-core": "^6.26.3", "babel-eslint": "^9.0.0", "babel-loader": "^7.1.5", "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "cross-env": "^5.2.0", "css-loader": "^1.0.0", "del-cli": "^1.1.0", "eslint": "^5.5.0", "eslint-config-satya164": "^2.0.1", "flow-bin": "^0.80.0", "mini-css-extract-plugin": "^0.4.2", "style-loader": "^0.23.0", "webpack": "^4.17.2", "webpack-command": "^0.4.1", "webpack-serve": "^2.0.2", "worker-loader": "^2.0.0" }, "dependencies": { "babel-polyfill": "^6.26.0", "dedent": "^0.7.0", "idb-keyval": "^3.1.0", "lodash": "^4.17.10", "monaco-editor": "^0.14.3", "prettier": "^1.14.2", "react": "^16.5.0", "react-dom": "^16.5.0" } } ================================================ FILE: serve.config.js ================================================ /* eslint-disable import/no-commonjs */ module.exports = { port: 3000, devMiddleware: { publicPath: '/dist/' }, }; ================================================ FILE: src/App.js ================================================ /* @flow */ import 'babel-polyfill'; import * as React from 'react'; import dedent from 'dedent'; import Editor from './Editor'; const files = { 'App.js': dedent`import React, { Component } from 'react'; import { Text, View, StyleSheet } from 'react-native'; import { Constants } from 'expo'; import AssetExample from './AssetExample'; export default class App extends Component { render() { return ( Change code in the editor and watch it change on your phone! Save to get a shareable url. ); } } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingTop: Constants.statusBarHeight, backgroundColor: '#ecf0f1', }, paragraph: { margin: 24, fontSize: 18, fontWeight: 'bold', textAlign: 'center', color: '#34495e', }, });`, 'AssetExample.js': dedent`import React, { Component } from 'react'; import { Text, View, StyleSheet, Image } from 'react-native'; export default class AssetExample extends Component { render() { return ( Local files and assets can be imported by dragging and dropping them into the editor ); } } const styles = StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', }, paragraph: { margin: 24, marginTop: 0, fontSize: 14, fontWeight: 'bold', textAlign: 'center', color: '#34495e', }, logo: { backgroundColor: "#056ecf", height: 128, width: 128, } });`, }; type State = { files: { [name: string]: string, }, current: string, }; export default class App extends React.Component<{}, State> { state = { files, current: 'App.js', }; _handleValueChange = code => this.setState(state => ({ files: { ...state.files, [state.current]: code, }, })); _handleOpenPath = path => this.setState({ current: path }); render() { return (
{Object.keys(this.state.files).map(name => (
this._handleOpenPath(name)} > {name}
))}
); } } ================================================ FILE: src/Editor.css ================================================ /* Common overrides */ .monaco-editor .line-numbers { color: currentColor; opacity: .5; } /* Light theme overrides */ .ayu-light .JsxText { color: #5c6773; } .ayu-light .JsxSelfClosingElement, .ayu-light .JsxOpeningElement, .ayu-light .JsxClosingElement, .ayu-light .tagName-of-JsxOpeningElement, .ayu-light .tagName-of-JsxClosingElement, .ayu-light .tagName-of-JsxSelfClosingElement { color: #41a6d9; } .ayu-light .name-of-JsxAttribute { color: #f08c36; } .ayu-light .name-of-PropertyAssignment { color: #86b300; } .ayu-light .name-of-PropertyAccessExpression { color: #f08c36; } /* Dark theme overrides */ .ayu-dark .JsxText { color: #d9d7ce; } .ayu-dark .JsxSelfClosingElement, .ayu-dark .JsxOpeningElement, .ayu-dark .JsxClosingElement, .ayu-dark .tagName-of-JsxOpeningElement, .ayu-dark .tagName-of-JsxClosingElement, .ayu-dark .tagName-of-JsxSelfClosingElement { color: #5ccfe6; } .ayu-dark .name-of-JsxAttribute { color: #ffcf71; } .ayu-dark .name-of-PropertyAssignment { color: #bae67e; } .ayu-dark .name-of-PropertyAccessExpression { color: #ffcf71; } ================================================ FILE: src/Editor.js ================================================ /* @flow */ import * as monaco from 'monaco-editor/esm/vs/editor/editor.main'; import { SimpleEditorModelResolverService } from 'monaco-editor/esm/vs/editor/standalone/browser/simpleServices'; import { StaticServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'; import * as React from 'react'; import debounce from 'lodash/debounce'; import TypingsWorker from './workers/typings.worker'; import ESLintWorker from './workers/eslint.worker'; import light from './themes/light'; import dark from './themes/dark'; import './Editor.css'; /** * Monkeypatch to make 'Find All References' work across multiple files * https://github.com/Microsoft/monaco-editor/issues/779#issuecomment-374258435 */ SimpleEditorModelResolverService.prototype.findModel = function( editor, resource ) { return monaco.editor .getModels() .find(model => model.uri.toString() === resource.toString()); }; global.MonacoEnvironment = { getWorker(moduleId, label) { let MonacoWorker; switch (label) { case 'json': /* $FlowFixMe */ MonacoWorker = require('worker-loader!monaco-editor/esm/vs/language/json/json.worker'); break; case 'typescript': case 'javascript': /* $FlowFixMe */ MonacoWorker = require('worker-loader!monaco-editor/esm/vs/language/typescript/ts.worker'); break; default: /* $FlowFixMe */ MonacoWorker = require('worker-loader!monaco-editor/esm/vs/editor/editor.worker'); } return new MonacoWorker(); }, }; monaco.editor.defineTheme('ayu-light', light); monaco.editor.defineTheme('ayu-dark', dark); /** * Disable typescript's diagnostics for JavaScript files. * This suppresses errors when using Flow syntax. * It's also unnecessary since we use ESLint for error checking. */ monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true, noSyntaxValidation: true, }); /** * Use prettier to format JavaScript code. * This will replace the default formatter. */ monaco.languages.registerDocumentFormattingEditProvider('javascript', { async provideDocumentFormattingEdits(model) { const prettier = await import('prettier/standalone'); const babylon = await import('prettier/parser-babylon'); const text = prettier.format(model.getValue(), { parser: 'babylon', plugins: [babylon], singleQuote: true, }); return [ { range: model.getFullModelRange(), text, }, ]; }, }); /** * Sync all the models to the worker eagerly. * This enables intelliSense for all files without needing an `addExtraLib` call. */ monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true); monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true); /** * Configure the typescript compiler to detect JSX and load type definitions */ const compilerOptions = { allowJs: true, allowSyntheticDefaultImports: true, alwaysStrict: true, jsx: 'React', jsxFactory: 'React.createElement', }; monaco.languages.typescript.typescriptDefaults.setCompilerOptions( compilerOptions ); monaco.languages.typescript.javascriptDefaults.setCompilerOptions( compilerOptions ); type Props = { files: { [path: string]: string }, path: string, value: string, onOpenPath: (path: string) => mixed, onValueChange: (value: string) => mixed, lineNumbers?: 'on' | 'off', wordWrap: 'off' | 'on' | 'wordWrapColumn' | 'bounded', scrollBeyondLastLine?: boolean, minimap?: { enabled?: boolean, maxColumn?: number, renderCharacters?: boolean, showSlider?: 'always' | 'mouseover', side?: 'right' | 'left', }, theme: 'ayu-light' | 'ayu-dark', }; // Store editor states such as cursor position, selection and scroll position for each model const editorStates = new Map(); // Store details about typings we have loaded const extraLibs = new Map(); const codeEditorService = StaticServices.codeEditorService.get(); export default class Editor extends React.Component { static defaultProps = { lineNumbers: 'on', wordWrap: 'on', scrollBeyondLastLine: false, minimap: { enabled: false, }, theme: 'ayu-light', }; static removePath(path: string) { // Remove editor states editorStates.delete(path); // Remove associated models const model = monaco.editor .getModels() .find(model => model.uri.path === path); model && model.dispose(); } static renamePath(oldPath: string, newPath: string) { const selection = editorStates.get(oldPath); editorStates.delete(oldPath); editorStates.set(newPath, selection); this.removePath(oldPath); } componentDidMount() { // Intialize the linter this._linterWorker = new ESLintWorker(); this._linterWorker.addEventListener('message', ({ data }: any) => this._updateMarkers(data) ); // Intialize the type definitions worker this._typingsWorker = new TypingsWorker(); this._typingsWorker.addEventListener('message', ({ data }: any) => this._addTypings(data) ); // Fetch some definitions const dependencies = { expo: '29.0.0', react: '16.3.1', 'react-native': '0.55.4', }; Object.keys(dependencies).forEach(name => this._typingsWorker.postMessage({ name, version: dependencies[name], }) ); const { path, value, ...rest } = this.props; this._editor = monaco.editor.create(this._node, rest, { codeEditorService: Object.assign(Object.create(codeEditorService), { openCodeEditor: async ({ resource, options }, editor) => { // Open the file with this path // This should set the model with the path and value this.props.onOpenPath(resource.path); // Move cursor to the desired position editor.setSelection(options.selection); // Scroll the editor to bring the desired line into focus editor.revealLine(options.selection.startLineNumber); return Promise.resolve({ getControl: () => editor, }); } }), }); Object.keys(this.props.files).forEach(path => this._initializeFile(path, this.props.files[path]) ); this._openFile(path, value); this._phantom.contentWindow.addEventListener('resize', this._handleResize); } componentDidUpdate(prevProps: Props) { const { path, value, ...rest } = this.props; this._editor.updateOptions(rest); if (path !== prevProps.path) { editorStates.set(prevProps.path, this._editor.saveViewState()); this._openFile(path, value); } else if (value !== this._editor.getModel().getValue()) { const model = this._editor.getModel(); if (value !== model.getValue()) { model.pushEditOperations( [], [ { range: model.getFullModelRange(), text: value, }, ] ); } } } componentWillUnmount() { this._linterWorker && this._linterWorker.terminate(); this._typingsWorker && this._typingsWorker.termnate(); this._subscription && this._subscription.dispose(); this._editor && this._editor.dispose(); this._phantom && this._phantom.contentWindow.removeEventListener( 'resize', this._handleResize ); } clearSelection() { const selection = this._editor.getSelection(); this._editor.setSelection( new monaco.Selection( selection.startLineNumber, selection.startColumn, selection.startLineNumber, selection.startColumn ) ); } _initializeFile = (path: string, value: string) => { let model = monaco.editor .getModels() .find(model => model.uri.path === path); if (model) { // If a model exists, we need to update it's value // This is needed because the content for the file might have been modified externally // Use `pushEditOperations` instead of `setValue` or `applyEdits` to preserve undo stack model.pushEditOperations( [], [ { range: model.getFullModelRange(), text: value, }, ] ); } else { model = monaco.editor.createModel( value, 'javascript', new monaco.Uri().with({ path }) ); model.updateOptions({ tabSize: 2, insertSpaces: true, }); } }; _openFile = (path: string, value: string) => { this._initializeFile(path, value); const model = monaco.editor .getModels() .find(model => model.uri.path === path); this._editor.setModel(model); // Restore the editor state for the file const editorState = editorStates.get(path); if (editorState) { this._editor.restoreViewState(editorState); } this._editor.focus(); // Subscribe to change in value so we can notify the parent this._subscription && this._subscription.dispose(); this._subscription = this._editor.getModel().onDidChangeContent(() => { const value = this._editor.getModel().getValue(); this.props.onValueChange(value); this._lintCode(value); }); }; _lintCode = code => { const model = this._editor.getModel(); monaco.editor.setModelMarkers(model, 'eslint', []); this._linterWorker.postMessage({ code, version: model.getVersionId(), }); }; _addTypings = ({ typings }) => { Object.keys(typings).forEach(path => { let extraLib = extraLibs.get(path); extraLib && extraLib.dispose(); extraLib = monaco.languages.typescript.javascriptDefaults.addExtraLib( typings[path], path ); extraLibs.set(path, extraLib); }); }; _updateMarkers = ({ markers, version }: any) => { requestAnimationFrame(() => { const model = this._editor.getModel(); if (model && model.getVersionId() === version) { monaco.editor.setModelMarkers(model, 'eslint', markers); } }); }; _handleResize = debounce(() => this._editor.layout(), 100, { leading: true, trailing: true, }); _linterWorker: Worker; _typingsWorker: Worker; _subscription: any; _editor: any; _phantom: any; _node: any; render() { return (