├── .babelrc ├── .dockerignore ├── .editorconfig ├── .env.tpl ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .i18nGenerator.js ├── .jestfilemock.js ├── .plugins.json ├── .travis.yml ├── .yarnrc ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── CodingSDK.js ├── IDE │ ├── IdeEnvironment.js │ └── index.js ├── backendAPI │ ├── fileAPI.js │ ├── gitAPI.js │ ├── index.js │ ├── packageAPI.js │ ├── websocketClients.js │ └── workspaceAPI.js ├── commands │ ├── CommandPalette │ │ ├── component.jsx │ │ ├── getPaletteItems.js │ │ ├── index.js │ │ └── items.js │ ├── commandBindings │ │ ├── editor.js │ │ ├── file.js │ │ ├── git.js │ │ ├── index.js │ │ ├── misc.js │ │ └── tab.js │ ├── dispatchCommand.js │ ├── index.js │ ├── keymaps.js │ └── lib │ │ ├── helpers.js │ │ ├── keycodes.js │ │ ├── keymapper.js │ │ └── keymapper2.js ├── commons │ ├── File │ │ ├── actions.js │ │ ├── index.js │ │ ├── state.js │ │ ├── store.js │ │ └── subscribeToFileChange.js │ ├── Menu │ │ └── state.js │ ├── Pane │ │ └── state.js │ ├── Tab │ │ ├── TabBar.jsx │ │ ├── TabContent.jsx │ │ ├── TabLabel.jsx │ │ ├── index.js │ │ └── state.js │ ├── Tree │ │ ├── TreeNode.jsx │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── WorkSpace │ │ └── state.js │ └── exports.js ├── components │ ├── Accordion │ │ ├── Accordion.jsx │ │ └── index.js │ ├── ContextMenu │ │ ├── ContextMenu.jsx │ │ ├── ContextMenuContainer.jsx │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── DragAndDrop.jsx │ ├── Editor │ │ ├── EditorWrapper.jsx │ │ ├── actions.js │ │ ├── codemirrorDefaultOptions.js │ │ ├── components │ │ │ ├── CodeEditor │ │ │ │ ├── BaseCodeEditor.jsx │ │ │ │ ├── CodeEditor.jsx │ │ │ │ ├── TablessCodeEditor.jsx │ │ │ │ ├── addMixinMechanism.jsx │ │ │ │ ├── addons │ │ │ │ │ ├── index.js │ │ │ │ │ └── mode │ │ │ │ │ │ ├── findMode.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── loadMode.js │ │ │ │ │ │ └── modeInfos.js │ │ │ │ ├── index.js │ │ │ │ ├── lib │ │ │ │ │ └── formatting.js │ │ │ │ └── mixins │ │ │ │ │ ├── basicMixin.jsx │ │ │ │ │ ├── eslintMixin │ │ │ │ │ ├── codemirror-eslint.js │ │ │ │ │ ├── eslintMixin.js │ │ │ │ │ ├── eslintService.js │ │ │ │ │ ├── eslintServiceCommandTemplate.js │ │ │ │ │ └── index.js │ │ │ │ │ └── gitBlameMixin.jsx │ │ │ ├── CodeMirrorEditor │ │ │ │ └── index.jsx │ │ │ ├── EditorWidgets │ │ │ │ ├── EditorWidgets.jsx │ │ │ │ ├── EncodingWidget.jsx │ │ │ │ ├── LineWidget.jsx │ │ │ │ ├── LinterWidget.jsx │ │ │ │ ├── ModeWidget.jsx │ │ │ │ ├── encodings.js │ │ │ │ └── index.js │ │ │ ├── HtmlEditor │ │ │ │ ├── actions.js │ │ │ │ ├── htmlMixin.js │ │ │ │ ├── index.jsx │ │ │ │ └── state.js │ │ │ ├── ImageEditor.jsx │ │ │ ├── MarkdownEditor │ │ │ │ ├── actions.js │ │ │ │ ├── index.jsx │ │ │ │ ├── mdMixin.js │ │ │ │ ├── reducer.js │ │ │ │ ├── state.js │ │ │ │ └── utils.js │ │ │ ├── UnknownEditor.jsx │ │ │ └── WelcomeEditor.jsx │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── FileTree │ │ ├── FileTree.jsx │ │ ├── actions.js │ │ ├── contextMenuItems.js │ │ ├── fileTreeToFileBinding.js │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── Git │ │ ├── GitBranchWidget.jsx │ │ ├── GitCommitView.jsx │ │ ├── GitCommitViewFlat.jsx │ │ ├── GitFileTree.jsx │ │ ├── GitGraph │ │ │ ├── GitGraph.jsx │ │ │ ├── GitGraphTable.jsx │ │ │ ├── actions.js │ │ │ ├── helpers │ │ │ │ ├── CommitsState.js │ │ │ │ ├── RandColors.js │ │ │ │ ├── chroma.js │ │ │ │ ├── index.js │ │ │ │ └── roundVertices.js │ │ │ ├── index.js │ │ │ └── state.js │ │ ├── GitHistoryView.jsx │ │ ├── actions.js │ │ ├── index.js │ │ ├── modals │ │ │ ├── checkout.jsx │ │ │ ├── checkoutStash.jsx │ │ │ ├── commitDiff.jsx │ │ │ ├── diffFile.jsx │ │ │ ├── merge.jsx │ │ │ ├── mergeFile.jsx │ │ │ ├── newBranch.jsx │ │ │ ├── rebaseInput.jsx │ │ │ ├── rebasePrepare.jsx │ │ │ ├── rebaseStart.jsx │ │ │ ├── reset.jsx │ │ │ ├── resolveConflicts.jsx │ │ │ ├── stash.jsx │ │ │ ├── tag.jsx │ │ │ └── unstash.jsx │ │ └── reducer.js │ ├── Mask │ │ ├── Mask.jsx │ │ ├── actions.js │ │ ├── index.js │ │ └── state.js │ ├── Menu │ │ ├── Menu.jsx │ │ ├── MenuContextTypes.js │ │ ├── MenuItem.jsx │ │ ├── MenuSheet.jsx │ │ └── index.js │ ├── MenuBar │ │ ├── MenuBar.jsx │ │ ├── actions.js │ │ ├── index.js │ │ ├── menuBarItems.jsx │ │ ├── state.js │ │ └── store.js │ ├── Modal │ │ ├── FilePalette │ │ │ ├── component.jsx │ │ │ └── index.js │ │ ├── FileSelector │ │ │ └── index.js │ │ ├── Modal.jsx │ │ ├── actions.js │ │ ├── index.js │ │ ├── modals │ │ │ ├── Alert.jsx │ │ │ ├── Confirm.jsx │ │ │ ├── Form.jsx │ │ │ ├── Prompt.jsx │ │ │ ├── index.js │ │ │ └── modalCache.js │ │ └── state.js │ ├── Notification │ │ ├── actions.js │ │ ├── index.jsx │ │ └── state.js │ ├── Offline │ │ └── Offline.jsx │ ├── Pane │ │ ├── Pane.jsx │ │ ├── PaneAxis.jsx │ │ ├── PanesContainer.jsx │ │ ├── actions.js │ │ ├── index.js │ │ └── state.js │ ├── Panel │ │ ├── Panel.jsx │ │ ├── PanelAxis.jsx │ │ ├── PanelContent.jsx │ │ ├── PanelsContainer.jsx │ │ ├── SideBar │ │ │ ├── SideBar.jsx │ │ │ ├── SidePanel.jsx │ │ │ └── actions.js │ │ ├── actions.js │ │ ├── index.js │ │ └── state.js │ ├── Plugins │ │ ├── actions.js │ │ ├── component.jsx │ │ ├── constants.js │ │ ├── extensionList.jsx │ │ ├── index.js │ │ └── store.js │ ├── Prompt │ │ └── Prompt.jsx │ ├── ResizeBar.jsx │ ├── ResizeBar2.jsx │ ├── Setting │ │ ├── EditorSetting.jsx │ │ ├── FormInputGroup.jsx │ │ ├── KeymapSetting.jsx │ │ ├── SettingForm.jsx │ │ ├── index.jsx │ │ └── state.js │ ├── StatusBar │ │ ├── StatusBar.jsx │ │ ├── UploadWidgets.jsx │ │ ├── actions.js │ │ ├── index.js │ │ └── state.js │ ├── Tab │ │ ├── TabContainer.jsx │ │ ├── WelcomePage.jsx │ │ ├── actions.js │ │ ├── fileList.jsx │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── Terminal │ │ ├── Terminal.jsx │ │ ├── TerminalContainer.jsx │ │ ├── Xterm.jsx │ │ ├── actions.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── state.js │ │ └── terminal-client.js │ ├── Tooltip │ │ ├── TooltipTrigger.jsx │ │ ├── Tooltips.jsx │ │ ├── index.js │ │ └── state.js │ ├── TopBar │ │ ├── Breadcrumbs.jsx │ │ ├── TopBar.jsx │ │ └── index.js │ └── exports.js ├── config.js ├── containers │ ├── GlobalPrompt.jsx │ ├── IDE.jsx │ ├── Initialize │ │ ├── index.jsx │ │ └── state.js │ ├── Login.jsx │ ├── Root │ │ ├── actions.js │ │ ├── index.jsx │ │ ├── login.jsx │ │ └── reducer.js │ └── Utilities.jsx ├── exports.js ├── i18n │ ├── en_US │ │ ├── file.json │ │ ├── fileTree.json │ │ ├── git.json │ │ ├── global.json │ │ ├── index.js │ │ ├── login.json │ │ ├── menuBarItems.json │ │ ├── modal.json │ │ ├── panel.json │ │ ├── settings.json │ │ └── tab.json │ ├── index.json │ └── zh_CN │ │ ├── file.json │ │ ├── fileTree.json │ │ ├── git.json │ │ ├── global.json │ │ ├── index.js │ │ ├── login.json │ │ ├── menuBarItems.json │ │ ├── modal.json │ │ ├── panel.json │ │ ├── settings.json │ │ └── tab.json ├── index.html ├── index.jsx ├── initialize │ ├── index.js │ └── state.js ├── js.config.json ├── jsconfig.json ├── localStoreCache.js ├── login.html ├── login.jsx ├── mobxStore.js ├── persist.js ├── plugin │ ├── index.js │ └── placeholder.js ├── settings.js ├── states │ ├── index.js │ └── initialData.js ├── store.js ├── store.spec.js ├── styles │ ├── base-theme │ │ ├── index.styl │ │ └── styles │ │ │ ├── accordion.styl │ │ │ ├── bars.styl │ │ │ ├── editor.styl │ │ │ ├── filelist.styl │ │ │ ├── filetree.styl │ │ │ ├── git.styl │ │ │ ├── mask.styl │ │ │ ├── menu.styl │ │ │ ├── panes.styl │ │ │ ├── tabs.styl │ │ │ ├── term.styl │ │ │ └── ui-variables.styl │ ├── core-ui │ │ ├── Accordion.styl │ │ ├── Bar.styl │ │ ├── Breadcrumbs.styl │ │ ├── CommandPalette.styl │ │ ├── DragAndDrop.styl │ │ ├── Editor.styl │ │ ├── FileList.styl │ │ ├── FileTree.styl │ │ ├── Git.styl │ │ ├── GitMerge.styl │ │ ├── History.styl │ │ ├── Initialize.styl │ │ ├── Markdown.styl │ │ ├── Mask.styl │ │ ├── Menu.styl │ │ ├── MenuBar.styl │ │ ├── Modal.styl │ │ ├── Offline.styl │ │ ├── PanelAndPane.styl │ │ ├── Settings.styl │ │ ├── StatusBar.styl │ │ ├── Tab.styl │ │ ├── Term.styl │ │ ├── Tooltip.styl │ │ ├── Welcome.styl │ │ ├── Workspace.styl │ │ └── index.styl │ ├── dark │ │ ├── index.styl │ │ └── styles │ │ │ ├── access.styl │ │ │ ├── accordion.styl │ │ │ ├── bars.styl │ │ │ ├── base.styl │ │ │ ├── collaborators.styl │ │ │ ├── diff.styl │ │ │ ├── editor.styl │ │ │ ├── env.styl │ │ │ ├── filelist.styl │ │ │ ├── filetree.styl │ │ │ ├── form.styl │ │ │ ├── git-merge.styl │ │ │ ├── git.styl │ │ │ ├── history.styl │ │ │ ├── java.styl │ │ │ ├── markdown.styl │ │ │ ├── mask.styl │ │ │ ├── menu.styl │ │ │ ├── modal.styl │ │ │ ├── panes.styl │ │ │ ├── tabs.styl │ │ │ ├── term.styl │ │ │ ├── ui-variables.styl │ │ │ ├── weapp.styl │ │ │ └── welcome.styl │ ├── lib.styl │ ├── main.styl │ ├── mixins │ │ ├── color.styl │ │ ├── index.styl │ │ ├── misc.styl │ │ ├── padding-margin.styl │ │ ├── position.styl │ │ ├── scrollbar.styl │ │ └── z-index.styl │ ├── normalize.styl │ ├── scaffolding.styl │ ├── ui-variables.styl │ └── workstation.styl ├── utils │ ├── RandColors.js │ ├── actions │ │ ├── createAction.js │ │ ├── dispatch.js │ │ ├── emitterMiddleware.js │ │ ├── handleAction.js │ │ ├── handleActions.js │ │ ├── index.js │ │ └── registerAction.js │ ├── assignProps.js │ ├── codingPackageJsonp.js │ ├── colors │ │ ├── chroma.js │ │ ├── hueFromString.js │ │ └── index.js │ ├── composeReducers.js │ ├── createI18n.js │ ├── decorators │ │ ├── defaultProps.js │ │ ├── index.js │ │ ├── mapEntity.js │ │ └── protectedObservable.js │ ├── dnd.js │ ├── dynamicStyle.js │ ├── editorConfig.js │ ├── emitter │ │ └── index.js │ ├── exports.js │ ├── extendObservableStrict.js │ ├── getBackoff.js │ ├── getCookie.js │ ├── getTabType.js │ ├── handleActions.js │ ├── hasVimium.js │ ├── immutableUpdate.js │ ├── index.js │ ├── is.js │ ├── loadStyle.js │ ├── multiline.js │ ├── path.js │ ├── plugins.js │ ├── promise.prototype.finalCatch.js │ ├── qs.js │ ├── request.js │ ├── setSelectionRange.js │ ├── stepFactory.js │ ├── toJS.js │ └── withTheme.js ├── workspaces_standalone │ ├── WorkspaceList.jsx │ ├── actions.js │ ├── backendAPI.js │ ├── bootstrapping.js │ ├── index.html │ ├── index.jsx │ ├── reducer.js │ └── store.js ├── workstation.jsx └── workstation │ ├── api.js │ ├── index.js │ ├── initialize.js │ ├── state.js │ ├── styles │ └── workstation.styl │ ├── workstation.jsx │ └── workstationFull.jsx ├── cdn.js ├── dist ├── terminal.js └── workstation.js ├── nginx.conf ├── package.json ├── packageListServer.js ├── static ├── favicon.ico ├── loading-logo.svg └── logo.svg ├── task.yaml.tpl ├── webpack.config.js ├── webpack_configs ├── common.config.js ├── devServer.config.js ├── loaders │ └── regexp-replace-loader.js ├── stylesheet.config.js ├── stylesheet.lib.config.js ├── uglify.config.js ├── webpack.dev.config.js ├── webpack.lib.config.js ├── webpack.lib.dev.config.js ├── webpack.prod.config.js ├── webpack.staging.config.js ├── workstation.config.js └── workstation.dev.config.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !build/ 3 | !nginx.conf 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.tpl: -------------------------------------------------------------------------------- 1 | BACKEND_URL= 2 | WS_URL= 3 | STATIC_SERVING_URL= 4 | RUN_MODE=platform 5 | PACKAGE_SERVER= 6 | PACKAGE_DEV= -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | static/* 3 | *.spec.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true 6 | }, 7 | "rules": { 8 | "comma-dangle": 0, 9 | "global-require": 0, 10 | "no-param-reassign": 0, 11 | "new-cap": 0, 12 | "no-eval": 0, 13 | "no-plusplus": 0, 14 | "no-return-assign": 0, 15 | "no-underscore-dangle": 0, 16 | "react/require-default-props": 0, 17 | "no-multi-assign": 0, 18 | "semi": ["error", "never"], 19 | "one-var": 0, 20 | "one-var-declaration-per-line": 0, 21 | "space-before-function-paren": ["error", { 22 | "anonymous": "always", 23 | "named": "always", 24 | "asyncArrow": "ignore" 25 | }], 26 | "jsx-quotes": ["error", "prefer-single"], 27 | "react/jsx-first-prop-new-line": 0, 28 | "react/forbid-prop-types": 0, 29 | "jsx-a11y/no-static-element-interactions": 0 30 | }, 31 | "settings": { 32 | "import/resolver": "webpack" 33 | }, 34 | "globals": { 35 | "i18n": false, 36 | "__PACKAGE_PORTS__": false, 37 | "extension": false, 38 | "__DEV__": false, 39 | "__PACKAGE_SERVER__": false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/fbjs/.* 3 | 4 | [options] 5 | module.system.node.resolve_dirname=node_modules 6 | module.system.node.resolve_dirname=app 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .idea/ 4 | .vscode/ 5 | .env 6 | app/config.js 7 | *.bak 8 | coverage/ 9 | task.yaml -------------------------------------------------------------------------------- /.jestfilemock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /.plugins.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org/" 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.11.9-alpine 2 | 3 | ADD build/ /usr/share/nginx/html/ 4 | ADD nginx.conf /etc/nginx/conf.d/default.conf 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 CODING(https://coding.net/). 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /app/IDE/IdeEnvironment.js: -------------------------------------------------------------------------------- 1 | 2 | const IdeEnvironment = () => ({ 3 | editors: {} 4 | }) 5 | export default IdeEnvironment 6 | -------------------------------------------------------------------------------- /app/IDE/index.js: -------------------------------------------------------------------------------- 1 | // This is the access point to the API that Coding WebIDE exposes 2 | // Idea taken from atom, this is the equivalence of `window.atom` 3 | // 4 | // see: https://github.com/atom/atom/blob/master/src/atom-environment.coffee 5 | 6 | import IdeEnvironment from './IdeEnvironment' 7 | export default window.ide = IdeEnvironment() 8 | -------------------------------------------------------------------------------- /app/backendAPI/index.js: -------------------------------------------------------------------------------- 1 | import * as fileAPI from './fileAPI' 2 | import * as gitAPI from './gitAPI' 3 | import * as packageAPI from './packageAPI' 4 | import * as workspaceAPI from './workspaceAPI' 5 | import * as websocketClients from './websocketClients' 6 | 7 | export default { 8 | ...fileAPI, 9 | ...gitAPI, 10 | ...packageAPI, 11 | ...workspaceAPI 12 | } 13 | 14 | export { websocketClients } 15 | -------------------------------------------------------------------------------- /app/commands/CommandPalette/index.js: -------------------------------------------------------------------------------- 1 | import CommandPalette from './component' 2 | import commandPaletteItems from './items' 3 | import getPaletteItems from './getPaletteItems' 4 | 5 | export { CommandPalette, commandPaletteItems, getPaletteItems } 6 | -------------------------------------------------------------------------------- /app/commands/CommandPalette/items.js: -------------------------------------------------------------------------------- 1 | const commandPaletteItems = [ 2 | { name: 'File: New File', command: 'file:new_file' }, 3 | { name: 'Git: Commit', command: 'git:commit' } 4 | ] 5 | 6 | export default commandPaletteItems 7 | -------------------------------------------------------------------------------- /app/commands/commandBindings/editor.js: -------------------------------------------------------------------------------- 1 | import store, { dispatch as $d } from '../../store' 2 | import api from '../../backendAPI' 3 | import * as Pane from '../../components/Pane/actions' 4 | 5 | export default { 6 | 'editor:split_pane_1': (c) => { 7 | Pane.split(1) 8 | }, 9 | 'editor:split_pane_vertical_2': (c) => { 10 | Pane.split(2, 'row') 11 | }, 12 | 'editor:split_pane_horizontal_2': (c) => { 13 | Pane.split(2, 'column') 14 | }, 15 | 'editor:split_pane_vertical_3': (c) => { 16 | Pane.split(3, 'row') 17 | }, 18 | 'editor:split_pane_horizontal_3': (c) => { 19 | Pane.split(3, 'column') 20 | }, 21 | 'editor:split_pane_vertical_4': (c) => { 22 | Pane.split(4, 'row') 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/commands/commandBindings/index.js: -------------------------------------------------------------------------------- 1 | import git from './git' 2 | import file from './file' 3 | import misc from './misc' 4 | import editor from './editor' 5 | import tab from './tab' 6 | 7 | export default { 8 | ...git, 9 | ...file, 10 | ...misc, 11 | ...editor, 12 | ...tab 13 | } 14 | -------------------------------------------------------------------------------- /app/commands/commandBindings/tab.js: -------------------------------------------------------------------------------- 1 | import { dispatch as $d } from '../../store' 2 | import store from 'mobxStore' 3 | import * as Tab from 'components/Tab/actions' 4 | import * as PaneActions from 'components/Pane/actions' 5 | 6 | export default { 7 | 'tab:close': (c) => { 8 | Tab.removeTab(c.context.id) 9 | }, 10 | 11 | 'tab:close_other': (c) => { 12 | Tab.removeOtherTab(c.context.id) 13 | }, 14 | 15 | 'tab:close_all': (c) => { 16 | Tab.removeAllTab(c.context.id) 17 | }, 18 | 19 | 'tab:split_v': (c) => { 20 | const panes = store.PaneState.panes 21 | const pane = panes.values().find(pane => 22 | pane.contentId === c.context.tabGroupId 23 | ) 24 | PaneActions.splitTo(pane.id, 'bottom').then(newPaneId => 25 | Tab.moveTabToPane(c.context.id, newPaneId) 26 | ) 27 | }, 28 | 29 | 'tab:split_h': (c) => { 30 | const panes = store.PaneState.panes 31 | const pane = panes.values().find(pane => 32 | pane.contentId === c.context.tabGroupId 33 | ) 34 | PaneActions.splitTo(pane.id, 'right').then(newPaneId => 35 | Tab.moveTabToPane(c.context.id, newPaneId) 36 | ) 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /app/commands/dispatchCommand.js: -------------------------------------------------------------------------------- 1 | import { emitter } from 'utils' 2 | import { isArray } from 'utils/is' 3 | 4 | let _context = null 5 | 6 | export function setContext (context) { _context = context } 7 | 8 | 9 | export function addCommand (obj) { 10 | if (isArray(obj)) { 11 | obj.forEach((element) => { 12 | addCommand(element) 13 | }) 14 | } else { 15 | const key = Object.keys(obj)[0] 16 | emitter.on(key, obj[key]) 17 | } 18 | } 19 | 20 | function dispatchCommand (commandType, data) { 21 | let command 22 | if (typeof commandType === 'object') command = commandType 23 | else if (typeof commandType === 'string') command = { type: commandType, data } 24 | else return 25 | // bind context to command before run 26 | command.context = _context 27 | emitter.emit(command.type, command) 28 | } 29 | 30 | window.dispatchCommand = dispatchCommand 31 | 32 | export default dispatchCommand 33 | -------------------------------------------------------------------------------- /app/commands/index.js: -------------------------------------------------------------------------------- 1 | import { emitter } from 'utils' 2 | import Keymapper from './lib/keymapper' 3 | import keymaps from './keymaps' 4 | import commandBindings from './commandBindings' 5 | import dispatchCommand, { setContext, addCommand } from './dispatchCommand' 6 | import { CommandPalette } from './CommandPalette' 7 | 8 | const key = new Keymapper({ dispatchCommand }) 9 | key.loadKeymaps(keymaps) 10 | 11 | Object.keys(commandBindings).map((commandType) => { 12 | emitter.on(commandType, commandBindings[commandType]) 13 | }) 14 | 15 | export { dispatchCommand, setContext, CommandPalette, addCommand } 16 | -------------------------------------------------------------------------------- /app/commands/lib/helpers.js: -------------------------------------------------------------------------------- 1 | const keycodes = require('./keycodes') 2 | const MODIFIERS_LIST = ['meta', 'ctrl', 'shift', 'alt'] 3 | 4 | export function keyEventToKeyCombination (e, combinator) { 5 | // ensure comb always in the order spec by MODIFIERS_LIST 6 | const modString = MODIFIERS_LIST.filter(mod => e[`${mod}Key`]).join(combinator) 7 | if (modString) { 8 | return [modString, keycodes.keyCodeToKey[e.keyCode]].join(combinator) 9 | } 10 | return keycodes.keyCodeToKey[e.keyCode] 11 | } 12 | 13 | export function normalizeKeys (keys, combinator='+', delimiter=' ') { 14 | // validate keys spec, if valid, also unify order of modifiers as in MODIFIERS_LIST 15 | return keys.toLowerCase().split(delimiter).map((keyCombo) => { 16 | const keyEventObj = {} 17 | keyCombo.split(combinator).forEach((key) => { 18 | // 'meta' is also aliased as 'cmd' or 'super' 19 | if (key === 'cmd' || key === 'command' || key === 'super') key = 'meta' 20 | if (MODIFIERS_LIST.indexOf(key) > -1) { 21 | keyEventObj[`${key}Key`] = true 22 | } else { 23 | keyEventObj.keyCode = keycodes.keyToKeyCode[key] 24 | } 25 | }) 26 | if (typeof keyEventObj.keyCode !== 'number') { 27 | throw Error(`Keymapper: Unrecognized key combination \`${keyCombo}\``) 28 | } 29 | return keyEventToKeyCombination(keyEventObj, combinator) 30 | }) 31 | .join(delimiter) 32 | } 33 | -------------------------------------------------------------------------------- /app/commands/lib/keymapper.js: -------------------------------------------------------------------------------- 1 | import Mousetrap from 'mousetrap' 2 | import { normalizeKeys } from './helpers' 3 | 4 | Mousetrap.prototype.stopCallback = function () { return false } 5 | 6 | class Keymapper { 7 | constructor ({ dispatchCommand }) { 8 | this.dispatchCommand = dispatchCommand 9 | } 10 | 11 | loadKeymaps (keymaps) { 12 | Object.entries(keymaps).map(([keycombo, commandType]) => { 13 | Mousetrap.bind(normalizeKeys(keycombo), (e) => { 14 | this.dispatchCommand(commandType) 15 | e.preventDefault(); e.stopPropagation() 16 | }) 17 | }) 18 | } 19 | } 20 | 21 | export default Keymapper 22 | -------------------------------------------------------------------------------- /app/commons/File/index.js: -------------------------------------------------------------------------------- 1 | import FileState from './state' 2 | import * as actions from './actions' 3 | import store from './store' 4 | 5 | export { 6 | store, 7 | FileState, 8 | actions, 9 | } 10 | -------------------------------------------------------------------------------- /app/commons/File/store.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import state, { FileNode } from './state' 3 | 4 | class FileStore { 5 | constructor () { 6 | Object.assign(this, actions) 7 | } 8 | 9 | getState () { return state } 10 | 11 | get (path) { 12 | const file = state.entities.get(path) 13 | return file 14 | } 15 | 16 | isValid (instance) { 17 | return (instance instanceof FileNode && state.entities.has(instance.id)) 18 | } 19 | } 20 | 21 | const store = new FileStore() 22 | export default store 23 | -------------------------------------------------------------------------------- /app/commons/Pane/state.js: -------------------------------------------------------------------------------- 1 | import { extendObservable, observable, computed } from 'mobx' 2 | 3 | function PaneScope () { 4 | const state = observable({ 5 | entities: observable.map({}) 6 | }) 7 | 8 | class BasePane { 9 | @computed 10 | get isRoot () { 11 | return !this.parentId 12 | } 13 | 14 | @computed 15 | get parent () { 16 | const parent = state.entities.get(this.parentId) 17 | if (parent === this) throw Error(`Pane/Panel ${this.id} is parent of itself.`) 18 | return parent 19 | } 20 | 21 | @computed 22 | get views () { 23 | return state.entities.values() 24 | .filter(pane => pane.parentId === this.id) 25 | .sort((a, b) => a.index - b.index) 26 | } 27 | 28 | @computed 29 | get siblings () { 30 | if (!this.parent) return [this] 31 | return this.parent.views 32 | } 33 | 34 | @computed 35 | get leafChildren () { 36 | if (!this.views.length) return [this] 37 | return this.views.reduce((acc, child) => 38 | acc.concat(child.leafChildren) 39 | , []) 40 | } 41 | 42 | @computed 43 | get prev () { 44 | return this.siblings[this.index - 1] 45 | } 46 | 47 | @computed 48 | get next () { 49 | return this.siblings[this.index + 1] 50 | } 51 | } 52 | 53 | return { BasePane, state } 54 | } 55 | 56 | export default PaneScope 57 | -------------------------------------------------------------------------------- /app/commons/Tab/TabContent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import cx from 'classnames' 4 | import { observer } from 'mobx-react' 5 | 6 | export const TabContent = ({ children }) => ( 7 |
8 |
{children}
9 |
10 | ) 11 | 12 | export const TabContentItem = observer(({ tab, children }) => { 13 | if (tab.isActive && tab.onActive) { 14 | tab.onActive() 15 | } 16 | return ( 17 |
18 | {children} 19 |
20 | ) 21 | }) 22 | 23 | TabContentItem.propTypes = { 24 | tab: PropTypes.object.isRequired, 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/commons/Tab/index.js: -------------------------------------------------------------------------------- 1 | import TabStateScope from './state' 2 | import TabBar from './TabBar' 3 | import { TabContent, TabContentItem } from './TabContent' 4 | 5 | export { TabBar, TabContent, TabContentItem, TabStateScope } 6 | -------------------------------------------------------------------------------- /app/commons/Tree/index.js: -------------------------------------------------------------------------------- 1 | import TreeNode from './TreeNode' 2 | import TreeNodeScope from './state' 3 | export { TreeNode, TreeNodeScope } 4 | -------------------------------------------------------------------------------- /app/commons/Tree/store.js: -------------------------------------------------------------------------------- 1 | import { registerAction } from 'utils/actions' 2 | import api from 'backendAPI' 3 | 4 | const selectNode = registerAction('FILETREE_SELECT_NODE', 5 | (nodeOrOffset, multiSelect) => ({ nodeOrOffset, multiSelect }), 6 | ({ nodeOrOffset, multiSelect = false }) => { 7 | let offset, node 8 | if (typeof nodeOrOffset === 'number') { 9 | offset = nodeOrOffset 10 | } else { 11 | node = nodeOrOffset 12 | } 13 | 14 | if (offset === 1) { 15 | node = state.focusedNodes[0].next(true) 16 | } else if (offset === -1) { 17 | node = state.focusedNodes[0].prev(true) 18 | } 19 | 20 | if (!multiSelect) { 21 | state.root.unfocus() 22 | state.root.forEachDescendant(childNode => childNode.unfocus()) 23 | } 24 | 25 | node.focus() 26 | } 27 | ) 28 | 29 | const openNode = registerAction('open_node', 30 | (node, shouldBeFolded, deep) => ({ node, shouldBeFolded, deep }), 31 | ({ node, shouldBeFolded = null, deep = false }) => { 32 | 33 | } 34 | ) 35 | 36 | -------------------------------------------------------------------------------- /app/commons/WorkSpace/state.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/WebIDE-Frontend/fab3d991cd90be0c04ec7c57332bca32c6e9cddf/app/commons/WorkSpace/state.js -------------------------------------------------------------------------------- /app/commons/exports.js: -------------------------------------------------------------------------------- 1 | import * as File from './File' 2 | 3 | export { 4 | File, 5 | } 6 | -------------------------------------------------------------------------------- /app/components/Accordion/index.js: -------------------------------------------------------------------------------- 1 | import { Accordion, AccordionGroup } from './Accordion' 2 | 3 | export { Accordion, AccordionGroup } 4 | -------------------------------------------------------------------------------- /app/components/ContextMenu/ContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import cx from 'classnames' 4 | import Menu from '../Menu' 5 | import {setContext} from '../../commands/dispatchCommand' 6 | 7 | const ContextMenu = (props) => { 8 | const {items, context, isActive, pos, deactivate, className} = props 9 | if (!isActive) return null 10 | // add a `pos` related key to Menu can force it to re-render at change of pos 11 | 12 | setContext(context) // <- each time invoked, update command's context 13 | 14 | return ( 15 |
16 | 21 |
22 | ) 23 | } 24 | 25 | export default ContextMenu 26 | -------------------------------------------------------------------------------- /app/components/ContextMenu/ContextMenuContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import { action } from 'mobx' 4 | import ContextMenu from './ContextMenu' 5 | import state from './state' 6 | import store from './store' 7 | 8 | const ContextMenuContainer = observer(() => { 9 | return ( 10 | 18 | ) 19 | }) 20 | 21 | export default ContextMenuContainer 22 | -------------------------------------------------------------------------------- /app/components/ContextMenu/index.js: -------------------------------------------------------------------------------- 1 | import ContextMenu from './ContextMenu' 2 | import ContextMenuContainer from './ContextMenuContainer' 3 | import store from './store' 4 | 5 | export default ContextMenu 6 | export { ContextMenuContainer, store } 7 | -------------------------------------------------------------------------------- /app/components/ContextMenu/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | isActive: false, 5 | pos: { x: 0, y: 0 }, 6 | contextNode: observable.ref(null), 7 | items: observable.ref([]), 8 | className: '', 9 | }) 10 | 11 | export default state 12 | -------------------------------------------------------------------------------- /app/components/Editor/EditorWrapper.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { observer } from 'mobx-react' 4 | import { when } from 'mobx' 5 | import CodeEditor from './components/CodeEditor' 6 | import MarkdownEditor from './components/MarkdownEditor' 7 | import ImageEditor from './components/ImageEditor' 8 | import UnknownEditor from './components/UnknownEditor' 9 | import WelcomeEditor from './components/WelcomeEditor' 10 | import HtmlEditor from './components/HtmlEditor' 11 | import config from '../../config' 12 | 13 | const EditorWrapper = observer(({ tab, active }) => { 14 | const { editor } = tab 15 | const editorType = editor.editorType || 'default' 16 | const file = editor.file || {} 17 | // key is crutial here, it decides whether 18 | // the component should re-construct or 19 | // keep using the existing instance. 20 | const key = `editor_${file.path}` 21 | switch (editorType) { 22 | case 'htmlEditor': 23 | return React.createElement(HtmlEditor, { editor, key, tab, active }) 24 | case 'default': 25 | return React.createElement(CodeEditor, { editor, key, tab, active }) 26 | case 'editorWithPreview': 27 | return React.createElement(MarkdownEditor, { editor, key, tab, active }) 28 | case 'imageEditor': 29 | return React.createElement(ImageEditor, { path: file.path, key, tab, active }) 30 | default: 31 | return React.createElement(UnknownEditor, { path: file.path, size: file.size, key, tab, active }) 32 | } 33 | }) 34 | 35 | EditorWrapper.propTypes = { 36 | tab: PropTypes.object 37 | } 38 | 39 | EditorWrapper.contextTypes = { 40 | i18n: PropTypes.func 41 | } 42 | 43 | export default EditorWrapper 44 | -------------------------------------------------------------------------------- /app/components/Editor/actions.js: -------------------------------------------------------------------------------- 1 | import { registerAction } from 'utils/actions' 2 | import mobxStore from '../../mobxStore' 3 | 4 | const getCurrentCM = () => { 5 | const { EditorTabState } = mobxStore 6 | const activeTab = EditorTabState.activeTab 7 | const cm = activeTab ? activeTab.editor.cm : null 8 | return cm 9 | } 10 | 11 | export const formatCode = registerAction('edit:toggle_format', () => { 12 | const cm = getCurrentCM() 13 | if (!cm) return 14 | let range = { from: cm.getCursor(true), to: cm.getCursor(false) } 15 | if (range.from.ch === range.to.ch && range.from.line === range.to.line) { 16 | cm.execCommand('selectAll') 17 | range = { from: cm.getCursor(true), to: cm.getCursor(false) } 18 | } 19 | cm.autoFormatRange(range.from, range.to) 20 | }) 21 | 22 | export const toggleComment = registerAction('edit:toggle_comment', () => { 23 | const cm = getCurrentCM() 24 | if (!cm) return 25 | const range = { from: cm.getCursor(true), to: cm.getCursor(false) } 26 | // cm.toggleComment(range.from, range.to, { indent: true }) 27 | if (range.from.line === range.to.line) { 28 | if (!cm.uncomment(range.from, range.to)) { 29 | cm.lineComment(range.from, range.to, { indent: true }) 30 | } 31 | } else if (!cm.uncomment(range.from, range.to)) { 32 | cm.blockComment(range.from, range.to, { fullLines: false }) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/CodeEditor.jsx: -------------------------------------------------------------------------------- 1 | import BaseCodeEditor from './BaseCodeEditor' 2 | import addMixinMechanism from './addMixinMechanism' 3 | import basicMixin from './mixins/basicMixin' 4 | import gitBlameMixin from './mixins/gitBlameMixin' 5 | import eslintMixin from './mixins/eslintMixin' 6 | 7 | class CodeEditor extends BaseCodeEditor { 8 | componentWillReceiveProps (newProps) { 9 | if (newProps.editor && this.editor === newProps.editor) return 10 | this.editor = newProps.editor 11 | this.cm = this.editor.cm 12 | this.cmDOM = this.cm.getWrapperElement() 13 | this.dom.removeChild(this.dom.children[0]) 14 | this.dom.appendChild(this.cmDOM) 15 | this.cm.setSize('100%', '100%') 16 | this.cm.refresh() 17 | } 18 | } 19 | addMixinMechanism(CodeEditor, BaseCodeEditor) 20 | 21 | CodeEditor.use(basicMixin) 22 | CodeEditor.use(gitBlameMixin) 23 | CodeEditor.use(eslintMixin) 24 | 25 | export default CodeEditor 26 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/TablessCodeEditor.jsx: -------------------------------------------------------------------------------- 1 | import TabStore from 'components/Tab/store' 2 | import BaseCodeEditor from './BaseCodeEditor' 3 | 4 | class TablessCodeEditor extends BaseCodeEditor { 5 | getEventListeners () { 6 | return { 7 | change: (cm) => { 8 | TabStore.createTab({ 9 | flags: { modified: true }, 10 | tabGroup: { id: this.props.tabGroupId }, 11 | editor: { 12 | content: cm.getValue(), 13 | cm, 14 | }, 15 | }) 16 | } 17 | } 18 | } 19 | 20 | componentDidMount () { 21 | super.componentDidMount() 22 | const eventListeners = this.getEventListeners() 23 | const disposers = Object.keys(eventListeners).reduce((acc, eventType) => { 24 | this.cm.on(eventType, eventListeners[eventType]) 25 | return acc.concat(() => this.cm.off(eventType, eventListeners[eventType])) 26 | }, []) 27 | 28 | this.cmRemoveEventListeners = function cmRemoveEventListeners () { 29 | disposers.forEach(disposer => disposer()) 30 | } 31 | } 32 | 33 | componentWillUnmount () { 34 | this.cmRemoveEventListeners() 35 | this.editor.destroy() 36 | } 37 | } 38 | 39 | export default TablessCodeEditor 40 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/addons/index.js: -------------------------------------------------------------------------------- 1 | /* import offical codemirror addons */ 2 | 3 | // diaglog 4 | import 'codemirror/addon/dialog/dialog.js' 5 | import 'codemirror/addon/dialog/dialog.css' 6 | // search 7 | import 'codemirror/addon/search/search.js' 8 | 9 | // comment 10 | import 'codemirror/addon/comment/comment.js' 11 | 12 | // bracket 13 | import 'codemirror/addon/edit/matchbrackets.js' 14 | import 'codemirror/addon/edit/closebrackets.js' 15 | 16 | // code fold 17 | import 'codemirror/addon/fold/foldcode.js' 18 | import 'codemirror/addon/fold/foldgutter.js' 19 | 20 | // hint 21 | // import 'codemirror/addon/hint/show-hint.js' 22 | 23 | // lint 24 | import '../lib/formatting' 25 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/addons/mode/index.js: -------------------------------------------------------------------------------- 1 | import modeInfos from './modeInfos' 2 | import loadMode from './loadMode' 3 | 4 | export { modeInfos, loadMode } 5 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/index.js: -------------------------------------------------------------------------------- 1 | import CodeEditor from './CodeEditor' 2 | import TablessCodeEditor from './TablessCodeEditor' 3 | import './addons' 4 | 5 | export default CodeEditor 6 | export { TablessCodeEditor } 7 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/eslintMixin/eslintMixin.js: -------------------------------------------------------------------------------- 1 | import 'codemirror/addon/lint/lint.js' 2 | import 'codemirror/addon/lint/lint.css' 3 | import { extendObservable } from 'mobx' 4 | import linterFactory from './codemirror-eslint' 5 | import { notify, NOTIFY_TYPE } from 'components/Notification/actions' 6 | 7 | function handleLinterError (error, cm) { 8 | cm.setOption('lint', { 9 | name: 'ESLint', 10 | enabled: false, 11 | error, 12 | retry () { 13 | cm.setOption('lint', lintOption) 14 | } 15 | }) 16 | } 17 | 18 | const lintOption = { 19 | name: 'ESLint', 20 | enabled: true, 21 | async: true, 22 | getAnnotations: linterFactory(handleLinterError), 23 | toggle (cm) { 24 | this.enabled = !this.enabled 25 | cm && cm.performLint() 26 | }, 27 | } 28 | 29 | export default { 30 | key: 'eslint', 31 | shouldMount () { 32 | const editor = this.editor 33 | if (editor.modeInfo && (editor.modeInfo.mode === 'javascript' || editor.modeInfo.mode === 'jsx')) return true 34 | }, 35 | componentDidMount () { 36 | const cm = this.cm 37 | const gutters = cm.options.gutters 38 | if (gutters.findIndex(item => item === 'CodeMirror-lint-markers') < 0) { 39 | cm.setOption('gutters', ['CodeMirror-lint-markers', ...gutters]) 40 | } 41 | // cm.setOption('gutters', ['CodeMirror-lint-markers']) 42 | cm.setOption('lint', lintOption) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/eslintMixin/eslintService.js: -------------------------------------------------------------------------------- 1 | import api from 'backendAPI' 2 | import baseNodeScript from '!raw-loader!uglify-loader?{"compress":{"booleans": false}}!./eslintServiceCommandTemplate.js' 3 | 4 | const getCommand = (runOnFile, filePathOrText) => { 5 | let nodeScript = runOnFile ? baseNodeScript.replace('BOOL,PARAMS', `true, "${filePathOrText}"`) 6 | : baseNodeScript.replace('BOOL,PARAMS', `false, "${JSON.stringify(filePathOrText)}"`) 7 | return `cd /home/coding/workspace && node -e ${JSON.stringify(nodeScript)}` 8 | } 9 | 10 | const eslintService = { 11 | executeOnFile (filePath) { 12 | return api.execShellCommand(getCommand(true, filePath)).then(data => data.stdout) 13 | }, 14 | 15 | executeOnText (text) { 16 | return api.execShellCommand(getCommand(false, text)).then(data => data.stdout) 17 | }, 18 | } 19 | 20 | export default eslintService 21 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/eslintMixin/eslintServiceCommandTemplate.js: -------------------------------------------------------------------------------- 1 | console.log(JSON.stringify((function (runOnFile, filePathOrText) { 2 | function not (boolean) { return Boolean(boolean) === false } 3 | try { 4 | var NO_MODULE_ERROR = { error: true, type: 'NO_MODULE', name: 'eslint' }; 5 | var NO_FILE_ERROR = { error: true, type: 'NO_FILE' }; 6 | var UNKNOWN_ERROR = { error: true, type: 'UNKNOWN' }; 7 | 8 | var eslint = require('eslint'); 9 | var engine = new eslint.CLIEngine({ useEslintrc: true, cacheFile: '.eslintcache', ignore: true, allowInlineConfig: true }); 10 | var report = runOnFile ? engine.executeOnFiles(['./' + filePathOrText]) : engine.executeOnText(filePathOrText); 11 | var singleFileResult = report.results[0]; 12 | if (not(singleFileResult)) return NO_FILE_ERROR; 13 | delete singleFileResult.source; 14 | singleFileResult.messages.forEach(function (message) { 15 | delete message.source; 16 | delete message.fix; 17 | delete message.nodeType; 18 | }); 19 | return singleFileResult; 20 | 21 | } catch (err) { 22 | var errorMessage = String(err); 23 | var noModuleKeyword = 'Cannot find module '; 24 | if (errorMessage.indexOf(noModuleKeyword) > -1) { 25 | NO_MODULE_ERROR.message = errorMessage; 26 | return NO_MODULE_ERROR; 27 | } else { 28 | UNKNOWN_ERROR.message = errorMessage; 29 | return UNKNOWN_ERROR 30 | } 31 | } 32 | })(BOOL,PARAMS))); 33 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/eslintMixin/index.js: -------------------------------------------------------------------------------- 1 | import eslintMixin from './eslintMixin' 2 | export default eslintMixin 3 | -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/gitBlameMixin.jsx: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import { autorun } from 'mobx' 3 | import mtln from 'utils/multiline' 4 | 5 | export default { 6 | key: 'gitBlameGutter', 7 | componentDidMount () { 8 | this.dispose = autorun('renderGitBlameGutter', () => { 9 | // set gutter first 10 | const gitBlameGutterId = 'git-blame-gutter' 11 | const editor = this.editor 12 | const cm = this.cm 13 | const gutters = editor.options.gutters 14 | 15 | if (!editor.gitBlame.show) { 16 | if (!gutters.includes(gitBlameGutterId)) return 17 | cm.clearGutter(gitBlameGutterId) 18 | editor.options.gutters = gutters.filter(id => id !== gitBlameGutterId) 19 | cm.refresh() 20 | return 21 | } 22 | 23 | const gitBlameData = editor.gitBlame.data || [] 24 | 25 | if (gutters.indexOf(gitBlameGutterId) === -1) { 26 | editor.options.gutters = [...gutters, gitBlameGutterId] 27 | } 28 | 29 | gitBlameData.forEach(({ author, shortName: commitHash }, ln) => { 30 | if (!commitHash) return 31 | const fullMessage = mtln` 32 | commit: ${commitHash} 33 | time: ${moment(author.when).format('YYYY-MM-DD hh:mm:ss')} 34 | author: ${author.name}<${author.emailAddress}>` 35 | const blameText = document.createElement('div') 36 | blameText.innerHTML = `
${commitHash} ${moment(author.when).format('YYYY-MM-DD')} ${author.name}
` 37 | cm.setGutterMarker(ln, 'git-blame-gutter', blameText) 38 | }) 39 | 40 | cm.refresh() 41 | }) 42 | }, 43 | componentWillUnmount () { 44 | this.dispose() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/components/Editor/components/EditorWidgets/EditorWidgets.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer, inject } from 'mobx-react' 3 | import ModeWidget from './ModeWidget' 4 | import LineWidget from './LineWidget' 5 | import LinterWidget from './LinterWidget' 6 | import EncodingWidget from './EncodingWidget' 7 | 8 | @inject(({ EditorTabState }) => { 9 | const activeTab = EditorTabState.activeTab 10 | if (!activeTab || !activeTab.editor) return { editor: null } 11 | return { editor: activeTab.editor } 12 | }) 13 | @observer 14 | class EditorWidgets extends Component { 15 | render () { 16 | const editor = this.props.editor 17 | if (!editor) return null 18 | return ( 19 |
20 | 21 | {editor.file && } 22 | 23 | {editor.options.lint && } 24 |
25 | ) 26 | } 27 | } 28 | 29 | export default EditorWidgets 30 | -------------------------------------------------------------------------------- /app/components/Editor/components/EditorWidgets/LinterWidget.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import cx from 'classnames' 4 | import TooltipTrigger from 'components/Tooltip' 5 | 6 | @observer 7 | export default class LinterWidget extends Component { 8 | render () { 9 | const editor = this.props.editor 10 | const lintOptions = editor.options.lint 11 | const lintError = lintOptions.error 12 | return ( 13 | Boolean(lintError)} 15 | placement='top' 16 | content={lintError && {lintError.message}} 17 | > 18 |
lintOptions.toggle && lintOptions.toggle(editor.cm)} 20 | > 21 | ESLint 23 |
24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/components/Editor/components/EditorWidgets/ModeWidget.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import cx from 'classnames' 4 | import modeInfos from 'components/Editor/components/CodeEditor/addons/mode/modeInfos' 5 | import Menu from 'components/Menu' 6 | 7 | @observer 8 | export default class ModeWidget extends Component { 9 | constructor (props) { 10 | super(props) 11 | this.state = { 12 | isActive: false 13 | } 14 | } 15 | 16 | toggleActive (isActive, isTogglingEnabled) { 17 | if (isTogglingEnabled) { isActive = !this.state.isActive } 18 | this.setState({ isActive }) 19 | } 20 | 21 | setMode (name) { 22 | this.props.editor.setMode(name) 23 | } 24 | 25 | makeModeMenuItems () { 26 | return modeInfos.map((mode) => ({ 27 | key: mode.name, 28 | name: mode.name, 29 | command: () => { 30 | this.setMode(mode.name) 31 | }, 32 | })) 33 | } 34 | 35 | render () { 36 | const editor = this.props.editor 37 | return ( 38 |
{ this.toggleActive(true, true) }} 40 | > 41 | {editor.mode} 42 | {this.state.isActive ? 43 |
44 | 52 |
53 | : null} 54 |
55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/components/Editor/components/EditorWidgets/index.js: -------------------------------------------------------------------------------- 1 | import EditorWidgets from './EditorWidgets' 2 | 3 | export default EditorWidgets 4 | -------------------------------------------------------------------------------- /app/components/Editor/components/HtmlEditor/actions.js: -------------------------------------------------------------------------------- 1 | import registerAction from 'utils/actions/registerAction' 2 | // import state from './state' 3 | 4 | export const HTML_EDITOR_RESIZE = 'HTML_EDITOR_RESIZE' 5 | export const editorResize = registerAction(HTML_EDITOR_RESIZE, 6 | (__, dX, dY, state) => ({ dX, dY, state }), 7 | ({ dX, dY, state }) => { 8 | const leftDom = document.getElementById('editor_preview_markdown_editor') 9 | const rightDom = document.getElementById('editor_html_preview') 10 | state.leftGrow = state.leftGrow * (leftDom.offsetWidth - dX) / leftDom.offsetWidth 11 | state.rightGrow = state.rightGrow * (rightDom.offsetWidth + dX) / rightDom.offsetWidth 12 | } 13 | ) 14 | 15 | export const HTML_EDITOR_TOGGLE_PREVIEW = 'HTML_EDITOR_TOGGLE_PREVIEW' 16 | export const togglePreview = registerAction(HTML_EDITOR_TOGGLE_PREVIEW, 17 | ({ state }) => state.showPreview = !state.showPreview 18 | ) 19 | 20 | export const HTML_EDITOR_TOGGLE_SIZE = 'HTML_EDITOR_TOGGLE_SIZE' 21 | export const togglePreviewSize = registerAction(HTML_EDITOR_TOGGLE_SIZE, 22 | ({ state }) => state.showBigSize = !state.showBigSize 23 | ) 24 | -------------------------------------------------------------------------------- /app/components/Editor/components/HtmlEditor/htmlMixin.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import debounce from 'lodash/debounce' 3 | 4 | export default { 5 | key: 'java_mixin', 6 | getEventListeners () { 7 | return { 8 | change: debounce((cm) => { 9 | }, 1200), 10 | } 11 | }, 12 | componentWillMount () {}, 13 | componentDidMount () {}, 14 | } 15 | -------------------------------------------------------------------------------- /app/components/Editor/components/HtmlEditor/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | leftGrow: 50, 5 | rightGrow: 50, 6 | showBigSize: false, 7 | showPreview: false, 8 | previewUniqueId: '1', 9 | isResizing: false, 10 | }) 11 | 12 | export default state 13 | -------------------------------------------------------------------------------- /app/components/Editor/components/MarkdownEditor/actions.js: -------------------------------------------------------------------------------- 1 | import registerAction from 'utils/actions/registerAction' 2 | // import state from './state' 3 | 4 | export const MARKDOWN_EDITOR_RESIZE = 'EDITOR_RESIZE' 5 | export const editorResize = registerAction(MARKDOWN_EDITOR_RESIZE, 6 | (__, dX, dY, state) => ({ dX, dY, state }), 7 | ({ dX, dY, state }) => { 8 | const leftDom = document.getElementById('editor_preview_markdown_editor') 9 | const rightDom = document.getElementById('editor_preview_preview') 10 | state.leftGrow = state.leftGrow * (leftDom.offsetWidth - dX) / leftDom.offsetWidth 11 | state.rightGrow = state.rightGrow * (rightDom.offsetWidth + dX) / rightDom.offsetWidth 12 | } 13 | ) 14 | 15 | export const MARKDOWN_EDITOR_TOGGLE_PREVIEW = 'MARKDOWN_EDITOR_TOGGLE_PREVIEW' 16 | export const togglePreview = registerAction(MARKDOWN_EDITOR_TOGGLE_PREVIEW, 17 | ({ state }) => state.showPreview = !state.showPreview 18 | ) 19 | 20 | export const MARKDOWN_EDITOR_TOGGLE_SIZE = 'MARKDOWN_EDITOR_TOGGLE_SIZE' 21 | export const togglePreviewSize = registerAction(MARKDOWN_EDITOR_TOGGLE_SIZE, 22 | ({ state }) => state.showBigSize = !state.showBigSize 23 | ) 24 | -------------------------------------------------------------------------------- /app/components/Editor/components/MarkdownEditor/reducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | 3 | import { 4 | MARKDOWN_EDITOR_RESIZE, 5 | MARKDOWN_EDITOR_TOGGLE_PREVIEW, 6 | MARKDOWN_EDITOR_TOGGLE_SIZE 7 | } from './actions' 8 | 9 | const defaultState = { 10 | leftGrow: 50, 11 | rightGrow: 50, 12 | showBigSize: false, 13 | showPreview: true, 14 | } 15 | 16 | export default handleActions({ 17 | [MARKDOWN_EDITOR_RESIZE]: (state, action) => { 18 | const { sectionId, dX, dY } = action.payload 19 | const leftDom = document.getElementById('editor_preview_markdown_editor') 20 | const rightDom = document.getElementById('editor_preview_preview') 21 | return ({ 22 | ...state, 23 | leftGrow: state.leftGrow * (leftDom.offsetWidth - dX) / leftDom.offsetWidth, 24 | rightGrow: state.rightGrow * (rightDom.offsetWidth + dX) / rightDom.offsetWidth, 25 | }) 26 | }, 27 | [MARKDOWN_EDITOR_TOGGLE_PREVIEW]: (state, action) => ({ 28 | ...state, 29 | showPreview: !state.showPreview, 30 | }), 31 | [MARKDOWN_EDITOR_TOGGLE_SIZE]: (state, action) => ({ 32 | ...state, 33 | showBigSize: !state.showBigSize, 34 | }), 35 | }, defaultState) 36 | -------------------------------------------------------------------------------- /app/components/Editor/components/MarkdownEditor/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | leftGrow: 50, 5 | rightGrow: 50, 6 | showBigSize: false, 7 | showPreview: true, 8 | }) 9 | 10 | export default state 11 | -------------------------------------------------------------------------------- /app/components/Editor/components/WelcomeEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function () { 4 | return
Welcome
5 | } 6 | -------------------------------------------------------------------------------- /app/components/Editor/index.js: -------------------------------------------------------------------------------- 1 | import EditorWrapper from './EditorWrapper' 2 | import CodeEditor from './components/CodeEditor' 3 | import EditorWidgets from './components/EditorWidgets' 4 | import modeInfos from './components/CodeEditor/addons/mode/modeInfos' 5 | import * as actions from './actions' 6 | 7 | export default EditorWrapper 8 | export { CodeEditor, EditorWidgets, modeInfos, actions } 9 | -------------------------------------------------------------------------------- /app/components/Editor/store.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import state, { Editor } from './state' 3 | import FileStore from 'commons/File/store' 4 | 5 | class EditorStore { 6 | constructor () { 7 | Object.assign(this, actions) 8 | } 9 | 10 | getState () { return state } 11 | 12 | get (key) { return state.entities.get(key) } 13 | 14 | isValid (instance) { 15 | return (instance instanceof Editor && state.entities.has(instance.id)) 16 | } 17 | 18 | create (config) { 19 | return new Editor(config) 20 | } 21 | } 22 | 23 | const store = new EditorStore() 24 | export default store 25 | -------------------------------------------------------------------------------- /app/components/FileTree/contextMenuItems.js: -------------------------------------------------------------------------------- 1 | import { gitBlameNode } from './actions' 2 | import i18n from 'utils/createI18n' 3 | 4 | const divider = { isDivider: true } 5 | 6 | const items = [ 7 | { 8 | name: i18n`fileTree.contextMenu.newFile`, 9 | icon: 'fa fa-file-text-o', 10 | command: 'file:new_file', 11 | id: 'filetree_menu_new_file', 12 | }, 13 | { 14 | name: i18n`fileTree.contextMenu.newFolder`, 15 | icon: 'fa fa-folder-o', 16 | command: 'file:new_folder', 17 | 18 | }, 19 | divider, 20 | { 21 | name: i18n`fileTree.contextMenu.delete`, 22 | icon: 'fa fa-trash-o', 23 | command: 'file:delete', 24 | id: 'filetree_menu_delete', 25 | }, { 26 | name: i18n`fileTree.contextMenu.rename`, 27 | icon: 'fa', 28 | command: 'file:rename', 29 | }, 30 | divider, 31 | { 32 | name: i18n`fileTree.contextMenu.download`, 33 | icon: 'fa fa-download', 34 | command: 'file:download' 35 | }, 36 | { 37 | name: i18n`fileTree.contextMenu.upload`, 38 | icon: 'fa fa-upload', 39 | command: () => { 40 | // reset the files selected from last round 41 | document.getElementById('filetree-hidden-input-form').reset() 42 | const input = document.getElementById('filetree-hidden-input') 43 | input.dispatchEvent(new MouseEvent('click')) 44 | }, 45 | getIsHidden: ctx => !ctx.isDir, 46 | 47 | }, 48 | divider, 49 | { 50 | name: i18n`fileTree.contextMenu.gitBlame`, 51 | icon: 'fa', 52 | command: (c) => { 53 | gitBlameNode(c) 54 | }, 55 | id: 'filetree_menu_gitBlame', 56 | } 57 | ] 58 | 59 | export default items 60 | -------------------------------------------------------------------------------- /app/components/FileTree/index.js: -------------------------------------------------------------------------------- 1 | import FileTree from './FileTree' 2 | import * as actions from './actions' 3 | import state from './state' 4 | 5 | export default FileTree 6 | export { actions, state } 7 | -------------------------------------------------------------------------------- /app/components/FileTree/store.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import state from './state' 3 | 4 | class FileTreeStore { 5 | constructor () { 6 | Object.assign(this, actions) 7 | } 8 | 9 | getState () { return state } 10 | 11 | get (key) { return state.entities.get(key) } 12 | 13 | } 14 | 15 | const store = new FileTreeStore() 16 | export default store 17 | -------------------------------------------------------------------------------- /app/components/Git/GitGraph/helpers/RandColors.js: -------------------------------------------------------------------------------- 1 | const COLORS = [ 2 | // the git tower palettes [mod] 3 | '#58c6f3', // blue 4 | '#6de6e9', // aqua 5 | '#ace09e', // grean 6 | '#59b38a', // olive 7 | '#f7d56b', // yellow 8 | '#f5a95f', // orange 9 | '#fe7874', // red 10 | '#967acc', // purple 11 | '#ea75f3', // violet 12 | '#d3d7ed', // light purplish gray 13 | ] 14 | 15 | export default class RandColors { 16 | constructor () { 17 | this.colors = COLORS.slice() 18 | this._index = 0 19 | this._period = this.colors.length 20 | } 21 | 22 | get () { 23 | return this.colors[this._index++ % this._period] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/components/Git/GitGraph/helpers/index.js: -------------------------------------------------------------------------------- 1 | import chroma from './chroma' 2 | import RandColors from './RandColors' 3 | 4 | export { chroma, RandColors } 5 | -------------------------------------------------------------------------------- /app/components/Git/GitGraph/helpers/roundVertices.js: -------------------------------------------------------------------------------- 1 | const getLength = (p1, p2) => Math.sqrt(Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2)) 2 | 3 | export default function roundVertices (data) { 4 | const DI = 4 5 | const multiplier = 0.7 6 | if (data.length >= 3) { 7 | // mod first and last data point 8 | data = data.slice() 9 | const first = data.shift() 10 | data.unshift([first[0], first[1] - DI]) 11 | first[1] += DI * multiplier 12 | data.unshift(first) 13 | const last = data.pop() 14 | data.push([last[0], last[1] + DI]) 15 | last[1] -= DI * multiplier 16 | data.push(last) 17 | // end mod 18 | return data.reduce((acc, currentPoint, index, data) => { 19 | // first and last point, noop 20 | if (index === 0 || data.length - 1 === index) { 21 | acc.push(currentPoint) 22 | } else { 23 | const prevPoint = data[index - 1] 24 | const nextPoint = data[index + 1] 25 | const [x_p, y_p] = prevPoint 26 | const [x_n, y_n] = nextPoint 27 | const [x_c, y_c] = currentPoint 28 | 29 | const r_cp = getLength(currentPoint, prevPoint) 30 | const r_cn = getLength(currentPoint, nextPoint) 31 | 32 | const unitVec_cp = [(x_p - x_c) / r_cp, (y_p - y_c) / r_cp] 33 | const unitVec_cn = [(x_n - x_c) / r_cn, (y_n - y_c) / r_cn] 34 | 35 | const curveStartPoint = [unitVec_cp[0] * DI + x_c, unitVec_cp[1] * DI + y_c] 36 | const curveEndPoint = [unitVec_cn[0] * DI + x_c, unitVec_cn[1] * DI + y_c] 37 | 38 | acc.push([curveStartPoint, currentPoint, curveEndPoint]) 39 | } 40 | 41 | return acc 42 | }, []) 43 | } else { 44 | return data 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/components/Git/GitGraph/index.js: -------------------------------------------------------------------------------- 1 | import GitGraphTable from './GitGraphTable' 2 | export default GitGraphTable 3 | -------------------------------------------------------------------------------- /app/components/Git/GitGraph/state.js: -------------------------------------------------------------------------------- 1 | import { observable, computed, action, autorun, autorunAsync, runInAction } from 'mobx' 2 | import { RandColors } from './helpers' 3 | import CommitsState from './helpers/CommitsState' 4 | 5 | const randColors = new RandColors() 6 | const state = observable({ 7 | refs: observable.map({}), 8 | rawCommits: observable.map({}), 9 | commitsState: observable.shallowObject({}), 10 | }) 11 | 12 | autorun('parse commits', () => { 13 | state.commitsState = new CommitsState({ 14 | rawCommits: state.rawCommits.values(), 15 | }) 16 | }) 17 | 18 | autorun('connect refs', () => { 19 | state.commitsState.refs = state.refs 20 | }) 21 | 22 | export default state 23 | -------------------------------------------------------------------------------- /app/components/Git/index.js: -------------------------------------------------------------------------------- 1 | import GitBranchWidget from './GitBranchWidget' 2 | import GitCommitView from './GitCommitView' 3 | 4 | export { GitBranchWidget, GitCommitView } 5 | -------------------------------------------------------------------------------- /app/components/Mask/Mask.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { observer } from 'mobx-react' 4 | import state from './state' 5 | 6 | @observer 7 | class Mask extends Component { 8 | render () { 9 | if (state.operating) { 10 | return ( 11 |
12 |
13 | 14 | {state.operatingMessage} 15 |
16 |
17 | ) 18 | } 19 | return
20 | } 21 | } 22 | 23 | export default Mask 24 | -------------------------------------------------------------------------------- /app/components/Mask/actions.js: -------------------------------------------------------------------------------- 1 | import { registerAction } from 'utils/actions' 2 | import state from './state' 3 | 4 | export const showMask = registerAction('mask:show_mask', ({ message = 'Working...' }) => { 5 | state.operating = true 6 | state.operatingMessage = message 7 | }) 8 | 9 | export const hideMask = registerAction('mask:hide_mask', () => { 10 | state.operating = false 11 | state.operatingMessage = '' 12 | }) 13 | -------------------------------------------------------------------------------- /app/components/Mask/index.js: -------------------------------------------------------------------------------- 1 | import Mask from './Mask' 2 | import * as maskActions from './actions' 3 | 4 | export { maskActions } 5 | export default Mask 6 | 7 | -------------------------------------------------------------------------------- /app/components/Mask/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | operating: false, 5 | operatingMessage: '', 6 | }) 7 | 8 | export default state 9 | -------------------------------------------------------------------------------- /app/components/Menu/MenuContextTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | export default { 4 | subscribe: PropTypes.func, 5 | setFocus: PropTypes.func, 6 | getFocus: PropTypes.func, 7 | menuContext: PropTypes.any.isRequired, 8 | deactivateTopLevelMenu: PropTypes.func, 9 | activatePrevTopLevelMenuItem: PropTypes.func, 10 | activateNextTopLevelMenuItem: PropTypes.func, 11 | } 12 | -------------------------------------------------------------------------------- /app/components/Menu/index.js: -------------------------------------------------------------------------------- 1 | import Menu from './Menu' 2 | 3 | export default Menu 4 | -------------------------------------------------------------------------------- /app/components/MenuBar/actions.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/WebIDE-Frontend/fab3d991cd90be0c04ec7c57332bca32c6e9cddf/app/components/MenuBar/actions.js -------------------------------------------------------------------------------- /app/components/MenuBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import MenuBar from './MenuBar' 4 | import menuBarItems from './menuBarItems' 5 | import state from './state' 6 | 7 | const MenuBarContainer = observer((() => )) 8 | 9 | export default MenuBarContainer 10 | export { menuBarItems } 11 | -------------------------------------------------------------------------------- /app/components/MenuBar/state.js: -------------------------------------------------------------------------------- 1 | import menuBarItems from './menuBarItems' 2 | import MenuScope from 'commons/Menu/state' 3 | import { autorun } from 'mobx' 4 | import { observable } from 'mobx' 5 | 6 | const { state, MenuItem } = MenuScope(menuBarItems) 7 | 8 | autorun(() => { 9 | state.items = observable.shallowArray(menuBarItems.map((opts) => { 10 | return new MenuItem(opts) 11 | })) 12 | }) 13 | 14 | export default state 15 | -------------------------------------------------------------------------------- /app/components/MenuBar/store.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import { extendObservable, observable } from 'mobx' 3 | import state from './state' 4 | 5 | 6 | class MenuBarStore { 7 | constructor () { 8 | Object.assign(this, actions) 9 | this.labels = observable.map({}) 10 | } 11 | 12 | getState () { return state } 13 | 14 | get (path) { 15 | return this.getMenuItemByPath(path) 16 | } 17 | } 18 | 19 | 20 | const store = new MenuBarStore() 21 | export default (store) 22 | -------------------------------------------------------------------------------- /app/components/Modal/FilePalette/index.js: -------------------------------------------------------------------------------- 1 | import FilePalette from './component' 2 | 3 | export { FilePalette } 4 | -------------------------------------------------------------------------------- /app/components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import Modal from './Modal' 2 | import * as actions from './actions' 3 | 4 | export default Modal 5 | export { actions } 6 | -------------------------------------------------------------------------------- /app/components/Modal/modals/Alert.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from 'utils/createI18n' 3 | import { dismissModal } from '../actions' 4 | 5 | const Alert = (props) => { 6 | const { meta, content } = props 7 | return ( 8 |
9 | { content.header ? 10 |
{content.header}
11 | : null } 12 | 13 | { content.message ? 14 |
{content.message}
15 | : null } 16 | 17 | { content.statusMessage ? 18 |
{content.statusMessage}
19 | : null } 20 | 21 |
22 | 28 |
29 |
30 | ) 31 | } 32 | 33 | export default Alert 34 | -------------------------------------------------------------------------------- /app/components/Modal/modals/Confirm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from 'utils/createI18n' 3 | 4 | const Confirm = (props) => { 5 | const { meta, content } = props 6 | return ( 7 |
8 | { content.header ? 9 |
{content.header}
10 | : null } 11 | 12 | { content.message ? 13 |
{content.message}
14 | : null } 15 | 16 | { content.statusMessage ? 17 |
{content.statusMessage}
18 | : null } 19 | 20 |
21 | 22 | 23 |
24 |
25 | ) 26 | } 27 | 28 | export default Confirm 29 | -------------------------------------------------------------------------------- /app/components/Modal/modals/Form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from 'utils/createI18n' 3 | 4 | const Form = (props) => { 5 | const { meta, content } = props 6 | return ( 7 |
8 | { content.header ? 9 |
{content.header}
10 | : null } 11 | 12 | { content.message ? 13 |
{content.message}
14 | : null } 15 | 16 | { content.statusMessage ? 17 |
{content.statusMessage}
18 | : null } 19 | 20 |
21 | 30 | 33 |
34 |
35 | ) 36 | } 37 | 38 | export default Form 39 | -------------------------------------------------------------------------------- /app/components/Modal/modals/Prompt.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { setSelectionRange } from 'utils' 3 | import { dismissModal } from '../actions' 4 | 5 | class Prompt extends Component { 6 | constructor (props) { 7 | super(props) 8 | this.state = { value: props.content.defaultValue || '' } 9 | } 10 | 11 | componentDidMount () { 12 | if (this.props.content.selectionRange) { 13 | setSelectionRange(this.input, ...this.props.content.selectionRange) 14 | } 15 | } 16 | 17 | render () { 18 | const {meta, content} = this.props 19 | return ( 20 |
21 | { content.message ? 22 |
{content.message}
23 | : null } 24 | this.input =r} 27 | onChange={e => this.setState({ value: e.target.value })} 28 | onKeyDown={this.onKeyDown} 29 | value={this.state.value} 30 | placeholder={content.placeholder} 31 | /> 32 | { content.statusMessage ? 33 |
34 | 35 | {content.statusMessage} 36 |
37 | : null } 38 |
39 | ) 40 | } 41 | 42 | confirm (value) { 43 | this.props.meta.resolve(value) 44 | } 45 | 46 | cancel = () => { 47 | this.props.meta.reject() 48 | } 49 | 50 | onKeyDown = e => { 51 | if (e.keyCode === 13) { 52 | this.confirm(e.target.value) 53 | } 54 | } 55 | } 56 | 57 | export default Prompt 58 | -------------------------------------------------------------------------------- /app/components/Modal/modals/index.js: -------------------------------------------------------------------------------- 1 | export Prompt from './Prompt' 2 | export Confirm from './Confirm' 3 | export Alert from './Alert' 4 | export Form from './Form' 5 | export { GitCommitView } from '../../Git' 6 | export { CommandPalette } from '../../../commands' 7 | export { FilePalette } from '../FilePalette' 8 | export GitStashView from '../../Git/modals/stash' 9 | export GitUnstashView from '../../Git/modals/unstash' 10 | export GitResetView from '../../Git/modals/reset' 11 | export GitTagView from '../../Git/modals/tag' 12 | export GitMergeView from '../../Git/modals/merge' 13 | export GitNewBranchView from '../../Git/modals/newBranch' 14 | export SettingsView from '../../Setting' 15 | export GitRebaseStart from '../../Git/modals/rebaseStart' 16 | export GitResolveConflictsView from '../../Git/modals/resolveConflicts' 17 | export GitMergeFileView from '../../Git/modals/mergeFile' 18 | export GitDiffFileView from '../../Git/modals/diffFile' 19 | export GitRebasePrepare from '../../Git/modals/rebasePrepare' 20 | export GitRebaseInput from '../../Git/modals/rebaseInput' 21 | export GitCommitDiffView from '../../Git/modals/commitDiff' 22 | export GitCheckoutView from '../../Git/modals/checkout' 23 | export GitCheckoutStashView from '../../Git/modals/checkoutStash' 24 | export FileSelectorView from '../FileSelector' 25 | -------------------------------------------------------------------------------- /app/components/Modal/modals/modalCache.js: -------------------------------------------------------------------------------- 1 | // import React, { Component } from 'react' 2 | // import cx from 'classnames' 3 | import { observable } from 'mobx' 4 | import { observer, inject } from 'mobx-react' 5 | 6 | import { 7 | Prompt, 8 | Confirm, 9 | Alert, 10 | SettingsView, 11 | CommandPalette, 12 | FilePalette, 13 | GitCommitView, 14 | GitStashView, 15 | GitUnstashView, 16 | GitResetView, 17 | GitTagView, 18 | GitMergeView, 19 | GitNewBranchView, 20 | GitRebaseStart, 21 | GitResolveConflictsView, 22 | GitMergeFileView, 23 | GitDiffFileView, 24 | GitRebasePrepare, 25 | GitRebaseInput, 26 | GitCommitDiffView, 27 | GitCheckoutView, 28 | GitCheckoutStashView, 29 | FileSelectorView, 30 | Form, 31 | } from './index' 32 | 33 | 34 | const modalCache = observable.map({ 35 | Form, 36 | GitCommit: GitCommitView, 37 | GitResolveConflicts: GitResolveConflictsView, 38 | GitCommitDiff: GitCommitDiffView, 39 | GitStash: GitStashView, 40 | GitUnstash: GitUnstashView, 41 | GitTag: GitTagView, 42 | GitMerge: GitMergeView, 43 | GitNewBranch: GitNewBranchView, 44 | GitResetHead: GitResetView, 45 | GitRebaseStart, 46 | GitRebasePrepare, 47 | GitRebaseInput, 48 | GitMergeFile: GitMergeFileView, 49 | GitCheckout: GitCheckoutView, 50 | GitCheckoutStash: GitCheckoutStashView, 51 | Prompt, 52 | Confirm, 53 | Alert, 54 | CommandPalette, 55 | FilePalette, 56 | Settings: SettingsView, 57 | FileSelectorView, 58 | GitDiffFile: GitDiffFileView, 59 | }) 60 | 61 | window.modalCache = modalCache 62 | 63 | export default modalCache 64 | -------------------------------------------------------------------------------- /app/components/Modal/state.js: -------------------------------------------------------------------------------- 1 | import { observable, extendObservable } from 'mobx' 2 | 3 | class Modal { 4 | // make modal.content observable, to faciliate modal content updating 5 | 6 | constructor (opt) { 7 | let resolve, reject 8 | const promise = new Promise((rsv, rjt) => { 9 | resolve = rsv 10 | reject = rjt 11 | }) 12 | const defaults = { 13 | id: 0, 14 | isActive: false, 15 | showBackdrop: false, 16 | position: 'top', 17 | content: {}, 18 | meta: { promise, resolve, reject }, 19 | } 20 | extendObservable(this, defaults, opt) 21 | } 22 | 23 | update (content) { 24 | let resolve, reject 25 | const promise = new Promise((rsv, rjt) => { 26 | resolve = rsv 27 | reject = rjt 28 | }) 29 | this.content = { ...this.content, ...content } 30 | this.meta = { promise, resolve, reject } 31 | } 32 | } 33 | 34 | const state = observable({ 35 | stack: observable.shallowArray(), 36 | }) 37 | 38 | export default state 39 | export { Modal } 40 | -------------------------------------------------------------------------------- /app/components/Notification/actions.js: -------------------------------------------------------------------------------- 1 | import { registerAction } from 'utils/actions' 2 | import state from './state' 3 | 4 | export const NOTIFY_TYPE = { 5 | ERROR: 'error', 6 | INFO: 'info', 7 | } 8 | 9 | export const NOTIFICATION_REMOVE = 'NOTIFICATION_REMOVE' 10 | export const removeNotification = registerAction.normal(NOTIFICATION_REMOVE, (notifKey) => { 11 | const notifToRemove = state.notifications.find(notif => notif.key === notifKey) 12 | state.notifications.remove(notifToRemove) 13 | }) 14 | 15 | const NOTIFICATION_ADD = 'NOTIFICATION_ADD' 16 | export const addNotification = registerAction.normal(NOTIFICATION_ADD, 17 | (notification) => { 18 | const notifKey = Date.now() 19 | let defaultNotification = { 20 | message: '', 21 | action: 'Dismiss', 22 | key: notifKey, 23 | dismissAfter: 6000, 24 | onClick: () => removeNotification(notifKey) 25 | } 26 | 27 | if (notification.notifyType === NOTIFY_TYPE.ERROR) { 28 | defaultNotification = { 29 | ...defaultNotification, 30 | barStyle: { backgroundColor: 'red' }, 31 | actionStyle: { color: 'white' }, 32 | } 33 | } 34 | 35 | return { ...defaultNotification, ...notification } 36 | }, 37 | (notification) => { 38 | state.notifications.push(notification) 39 | } 40 | ) 41 | 42 | export const notify = addNotification 43 | -------------------------------------------------------------------------------- /app/components/Notification/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { inject } from 'mobx-react' 3 | import { NotificationStack } from 'react-notification' 4 | import * as actions from './actions' 5 | import state from './state' 6 | 7 | const barStyleFactory = (index, style) => { 8 | return Object.assign({}, style, { 9 | left: 'initial', 10 | right: '-100%', 11 | bottom: 'initial', 12 | top: `${2 + index * 4}rem`, 13 | fontSize: '12px', 14 | padding: '8px', 15 | }) 16 | } 17 | 18 | const activeBarStyleFactory = (index, style) => { 19 | return Object.assign({}, style, { 20 | left: 'initial', 21 | right: '1rem', 22 | bottom: 'initial', 23 | top: `${2 + index * 4}rem`, 24 | fontSize: '12px', 25 | padding: '8px', 26 | }) 27 | } 28 | 29 | class Notification extends Component { 30 | constructor (props) { 31 | super() 32 | } 33 | render () { 34 | const { notifications } = this.props 35 | return ( 36 | actions.removeNotification(notification.key)} 39 | barStyleFactory={barStyleFactory} 40 | activeBarStyleFactory={activeBarStyleFactory} 41 | /> 42 | ) 43 | } 44 | } 45 | 46 | export default inject(() => { 47 | const notifications = state.notifications.toJS() 48 | return { notifications } 49 | })(Notification) 50 | 51 | export { actions, state } 52 | -------------------------------------------------------------------------------- /app/components/Notification/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | notifications: [], 5 | }) 6 | 7 | export default state 8 | -------------------------------------------------------------------------------- /app/components/Pane/Pane.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { observer } from 'mobx-react' 4 | import cx from 'classnames' 5 | import { confirmResize } from './actions' 6 | import TabContainer from 'components/Tab' 7 | import PaneAxis from './PaneAxis' 8 | import ResizeBar from '../ResizeBar' 9 | 10 | 11 | const Pane = observer(props => { 12 | const { pane, parentFlexDirection } = props 13 | const style = { flexGrow: pane.size, display: pane.display } 14 | const closePane = pane.isRoot ? null : pane.destroy.bind(pane) 15 | 16 | return ( 17 |
{pane.views.length // priortize `pane.views` over `pane.content` 26 | ? 27 | :
28 | 29 |
30 | } 31 | 34 |
35 | ) 36 | }) 37 | 38 | Pane.propTypes = { 39 | pane: PropTypes.object, 40 | parentFlexDirection: PropTypes.string, 41 | } 42 | 43 | export default Pane 44 | -------------------------------------------------------------------------------- /app/components/Pane/PaneAxis.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { observer } from 'mobx-react' 4 | import cx from 'classnames' 5 | import Pane from './Pane' 6 | 7 | @observer 8 | class PaneAxis extends Component { 9 | static propTypes = { 10 | id: PropTypes.string, 11 | pane: PropTypes.object, 12 | }; 13 | 14 | static childContextTypes = { onResizing: PropTypes.func }; 15 | 16 | constructor (props) { 17 | super(props) 18 | this.resizingListeners = [] 19 | } 20 | 21 | getChildContext () { return { onResizing: this.onResizing.bind(this) } } 22 | onResizing (listener) { if (typeof listener === 'function') { this.resizingListeners.push(listener) } } 23 | 24 | render () { 25 | const selfPane = this.props.pane 26 | let Subviews 27 | if (selfPane.views.length) { 28 | Subviews = selfPane.views.map(pane => 29 | 30 | ) 31 | } else { 32 | Subviews = 33 | } 34 | 35 | return ( 36 |
{Subviews} 40 |
41 | ) 42 | } 43 | } 44 | 45 | export default PaneAxis 46 | -------------------------------------------------------------------------------- /app/components/Pane/PanesContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { inject } from 'mobx-react' 3 | import PaneAxis from './PaneAxis' 4 | 5 | var PrimaryPaneAxis = inject(state => { 6 | let rootPane = state.PaneState.rootPane 7 | return { pane: rootPane } 8 | })(PaneAxis) 9 | 10 | var PanesContainer = () => { 11 | return 12 | } 13 | 14 | export default PanesContainer 15 | -------------------------------------------------------------------------------- /app/components/Pane/index.js: -------------------------------------------------------------------------------- 1 | import PanesContainer from './PanesContainer' 2 | 3 | export default PanesContainer 4 | -------------------------------------------------------------------------------- /app/components/Panel/Panel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { observer } from 'mobx-react' 4 | import cx from 'classnames' 5 | import PanelAxis from './PanelAxis' 6 | import PanelContent from './PanelContent' 7 | import { confirmResize } from './actions' 8 | import ResizeBar from '../ResizeBar' 9 | 10 | const Panel = observer((props) => { 11 | const { panel, parentFlexDirection } = props 12 | const style = {} 13 | if (panel.resizable) { 14 | style.flexGrow = panel.size 15 | } else { 16 | style.flexGrow = 0 17 | style.flexBasis = 'auto' 18 | } 19 | if (panel.hidden) style.display = 'none' 20 | if (panel.disabled) style.display = 'none' 21 | if (panel.overflow) style.overflow = panel.overflow 22 | 23 | return ( 24 |
{ panel.views.length 32 | ? 33 | :
34 | } 35 | {panel.disableResizeBar 36 | ? null 37 | : 40 | } 41 |
42 | ) 43 | }) 44 | 45 | Panel.propTypes = { 46 | panel: PropTypes.object.isRequired, 47 | parentFlexDirection: PropTypes.string.isRequired, 48 | } 49 | 50 | export default Panel 51 | -------------------------------------------------------------------------------- /app/components/Panel/PanelAxis.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import cx from 'classnames' 4 | import Panel from './Panel' 5 | 6 | class PanelAxis extends Component { 7 | static propTypes = { 8 | id: PropTypes.string, 9 | panel: PropTypes.object, 10 | className: PropTypes.string, 11 | style: PropTypes.object, 12 | }; 13 | 14 | render () { 15 | const { panel, className, style } = this.props 16 | let subviews 17 | if (panel.views.length) { 18 | subviews = panel.views.map(childPanel => 19 | 20 | ) 21 | } else { 22 | subviews = 23 | } 24 | 25 | return ( 26 |
{subviews}
30 | ) 31 | } 32 | } 33 | 34 | export default PanelAxis 35 | -------------------------------------------------------------------------------- /app/components/Panel/PanelsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { inject } from 'mobx-react' 3 | import PanelState from './state' 4 | import PanelAxis from './PanelAxis' 5 | 6 | 7 | const PrimaryPanelAxis = inject(() => 8 | ({ panel: PanelState.rootPanel }) 9 | )(PanelAxis) 10 | 11 | const PanelsContainer = () => { 12 | return () 13 | } 14 | 15 | export default PanelsContainer 16 | -------------------------------------------------------------------------------- /app/components/Panel/SideBar/SideBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import cx from 'classnames' 4 | import PluginArea from 'components/Plugins/component' 5 | import { SIDEBAR } from 'components/Plugins/constants' 6 | import { observer } from 'mobx-react' 7 | import { toggleSidePanelView } from './actions' 8 | 9 | 10 | /* shape of label 11 | label = { 12 | text: String, 13 | icon: String, 14 | viewId: String, 15 | } 16 | */ 17 | 18 | 19 | const SideBarLabel = ({ label, isActive, onClick }) => ( 20 |
26 |
27 |
28 | 29 | {label.text} 30 |
31 |
32 |
33 | ) 34 | 35 | SideBarLabel.propTypes = { 36 | isActive: PropTypes.bool, 37 | onClick: PropTypes.func 38 | } 39 | 40 | const SideBar = observer(({ side }) => ( 41 | !plugin.status.hidden} 45 | getChildView={plugin => ( 46 | toggleSidePanelView(plugin.viewId)} 50 | isActive={plugin.status.get('active')} 51 | /> 52 | )} 53 | />)) 54 | 55 | 56 | SideBar.propTypes = { 57 | // labels: labelsShape, 58 | side: PropTypes.string, 59 | activeViewId: PropTypes.string, 60 | activateView: PropTypes.func 61 | } 62 | 63 | 64 | export default SideBar 65 | -------------------------------------------------------------------------------- /app/components/Panel/actions.js: -------------------------------------------------------------------------------- 1 | import { registerAction } from 'utils/actions' 2 | import _ from 'lodash' 3 | import state from './state' 4 | 5 | 6 | export const PANEL_CONFIRM_RESIZE = 'PANEL_CONFIRM_RESIZE' 7 | export const confirmResize = registerAction(PANEL_CONFIRM_RESIZE, 8 | (leftViewId, leftSize, rightViewId, rightSize) => ({ 9 | leftView: { id: leftViewId, size: leftSize }, 10 | rightView: { id: rightViewId, size: rightSize }, 11 | }), 12 | ({ leftView, rightView }) => { 13 | state.panels.get(leftView.id).size = leftView.size 14 | state.panels.get(rightView.id).size = rightView.size 15 | } 16 | ) 17 | 18 | export const PANEL_TOGGLE_LAYOUT = 'PANEL_TOGGLE_LAYOUT' 19 | export const togglePanelLayout = registerAction(PANEL_TOGGLE_LAYOUT, 20 | (selectors, shouldShow) => ({ selectors, shouldShow }), 21 | ({ selectors, shouldShow }) => { 22 | const selectedPanels = selectors.map(panelId => state.panels.get(panelId)) 23 | selectedPanels.forEach((panel) => { 24 | if (shouldShow === undefined) { 25 | panel.hidden ? panel.show() : panel.hide() 26 | } else if (shouldShow) { 27 | panel.show() 28 | } else { 29 | panel.hide() 30 | } 31 | }) 32 | } 33 | ) 34 | 35 | export const PRIMIARY_PANEL_BLUR_CONTROLER = 'PRIMIARY_PANEL_BLUR_CONTROLER' 36 | 37 | export const primiaryPanelBlurControler = registerAction(PRIMIARY_PANEL_BLUR_CONTROLER, 38 | (shouldShow) => { 39 | state.primaryPanelAxis.blur = shouldShow 40 | }) 41 | 42 | -------------------------------------------------------------------------------- /app/components/Panel/index.js: -------------------------------------------------------------------------------- 1 | import PanelsContainer from './PanelsContainer' 2 | 3 | export default PanelsContainer 4 | -------------------------------------------------------------------------------- /app/components/Plugins/component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import store from './store' 4 | 5 | function getChildren (children) { 6 | if (!children) return [] 7 | return Array.isArray(children) ? children : [children] 8 | } 9 | 10 | 11 | const PluginArea = observer(({ position = '', childProps = {}, children, getChildView, filter, ...others }) => { 12 | const pluginsArray = store.plugins.values().filter(plugin => plugin.position === position) 13 | 14 | const pluginComponents = pluginsArray 15 | .filter(filter || (() => true)) 16 | .sort((pluginA, pluginB) => (pluginA.label.weight || 0) < (pluginB.label.weight || 0) ? 1 : -1) 17 | .map((plugin) => { 18 | const view = store.views[plugin.viewId] 19 | return getChildView ? getChildView(plugin, view) : 20 | React[React.isValidElement(view) ? 'cloneElement' : 'createElement'](view, { 21 | key: plugin.viewId, 22 | ...childProps, 23 | }) 24 | }) 25 | // 允许提供children的必有不可插拔项 26 | return ( 27 |
28 | {getChildren(children).concat(pluginComponents)} 29 |
30 | ) 31 | }) 32 | 33 | 34 | export default PluginArea 35 | -------------------------------------------------------------------------------- /app/components/Plugins/constants.js: -------------------------------------------------------------------------------- 1 | // 菜单栏 2 | export const MENUBAR = { 3 | // 菜单栏右上角的 widget 4 | WIDGET: 'MENUBAR.WIDGET' 5 | } 6 | // 侧边烂区域 7 | export const SIDEBAR = { 8 | RIGHT: 'SIDEBAR.RIGHT', 9 | LEFT: 'SIDEBAR.LEFT', 10 | BOTTOM: 'SIDEBAR.BOTTOM' 11 | } 12 | 13 | export const TOPBAR = { 14 | WIDGET: 'TOPBAR.WIDGET' 15 | } 16 | // containers 最外层区域 17 | export const CONTAINERS = { 18 | UTILITIES: 'CONTAINERS.UTILITIES' 19 | } 20 | 21 | export const TERMINAL = { 22 | ENV: 'TERMINAL.ENV' 23 | } 24 | -------------------------------------------------------------------------------- /app/components/Plugins/index.js: -------------------------------------------------------------------------------- 1 | import PluginArea from './component' 2 | import * as actions from './actions' 3 | import * as position from './constants' 4 | 5 | export default { PluginArea, actions, position } 6 | -------------------------------------------------------------------------------- /app/components/Plugins/store.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const store = { 4 | views: {}, 5 | plugins: observable.map({}), 6 | list: observable([]), 7 | toJS () { 8 | const requiredList = this.list.toJS().filter(obj => obj.enabled && obj.requirement !== 'Required') 9 | return requiredList 10 | } 11 | } 12 | 13 | // for test 14 | window.pluginStore = store 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /app/components/Prompt/Prompt.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class Prompts extends Component { 5 | render () { 6 | const { prompts, handleClose } = this.props 7 | return ( 8 |
9 | {prompts.map((prompt, idx) => ( 10 |
15 | {prompt.content} 16 | handleClose(prompt.id, prompt.type)}>× 17 |
18 | ))} 19 |
20 | ) 21 | } 22 | } 23 | 24 | Prompts.propTypes = { 25 | prompts: PropTypes.array, 26 | handleClose: PropTypes.func, 27 | } 28 | 29 | export default Prompts 30 | -------------------------------------------------------------------------------- /app/components/Setting/EditorSetting.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import i18n from 'utils/createI18n' 4 | import { dismissModal } from 'components/Modal/actions' 5 | import { openFile } from 'commands/commandBindings/file' 6 | import TabStore from 'components/Tab/store' 7 | import FormInputGroup from './FormInputGroup' 8 | 9 | const editorconfigDefaultContent = `# See: http://editorconfig.org 10 | root = true 11 | 12 | [*] 13 | indent_style = 14 | indent_size = 15 | tab_width = 16 | trim_trailing_whitespace = 17 | insert_final_newline = 18 | ` 19 | 20 | const openEditorConfigFile = (e) => { 21 | e.preventDefault() 22 | dismissModal() 23 | openFile({ path: '/.editorconfig' }).catch((err) => { 24 | TabStore.createTab({ 25 | icon: 'fa fa-file-text-o', 26 | title: '.editorconfig', 27 | editor: { content: editorconfigDefaultContent } 28 | }) 29 | }) 30 | } 31 | 32 | export default observer(({ content }) => ( 33 |
34 |

{i18n`settings.editor.main`}

35 |
{i18n`settings.editor.editorconfigNote`}  36 | 37 | {i18n`settings.editor.editorconfigNoteLink`} 38 | 39 |
40 |
41 | {content.items.map(settingItem => 42 | 46 | )} 47 |
48 |
49 | )) 50 | -------------------------------------------------------------------------------- /app/components/Setting/KeymapSetting.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import i18n from 'utils/createI18n' 4 | import FormInputGroup from './FormInputGroup' 5 | 6 | export default observer(({ content }) => ( 7 |
8 |

{i18n`settings.keymap.main`}

9 |
10 | {content.items.map(settingItem => 11 | 15 | )} 16 |
17 |
18 | )) 19 | -------------------------------------------------------------------------------- /app/components/Setting/SettingForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import FormInputGroup from './FormInputGroup' 4 | 5 | export default observer(({ header, content }) => ( 6 |
7 |

{ header }

8 |
9 | {content.items.map(settingItem => 10 | 15 | )} 16 |
17 |
18 | )) 19 | -------------------------------------------------------------------------------- /app/components/StatusBar/StatusBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { GitBranchWidget } from 'components/Git' 3 | import { dispatchCommand } from 'commands' 4 | import { EditorWidgets } from 'components/Editor' 5 | import { observer } from 'mobx-react' 6 | import UploadWidgets from './UploadWidgets' 7 | 8 | const StatusBar = observer(({ messages=[] }) => { 9 | return ( 10 |
11 |
12 |
dispatchCommand('view:toggle_bars')} >
13 |
14 | 15 |
16 | {messages.map(message =>
{message}
)} 17 |
18 |
19 | 20 | window.refs.GitBranchWidget = com} 21 | />
22 |
23 | ) 24 | }) 25 | 26 | export default StatusBar 27 | -------------------------------------------------------------------------------- /app/components/StatusBar/actions.js: -------------------------------------------------------------------------------- 1 | import isObject from 'lodash/isObject' 2 | import { registerAction } from 'utils/actions' 3 | import state from './state' 4 | 5 | export const STATUS_ADD_MESSAGE = 'status:add_message' 6 | export const addMessage = registerAction(STATUS_ADD_MESSAGE, 7 | (messageKey, messageText, autoDismiss) => { 8 | if (isObject(messageKey)) return messageKey 9 | return { key: messageKey, text: messageText, autoDismiss } 10 | }, 11 | (message) => { 12 | const { text, key, autoDismiss } = message 13 | state.messages.set(key, text) 14 | const DISMISS_AFTER_MS = 3000 15 | if (autoDismiss) { 16 | setTimeout(() => { 17 | state.messages.delete(key) 18 | }, DISMISS_AFTER_MS) 19 | } 20 | } 21 | ) 22 | 23 | export const STATUS_REMOVE_MESSAGE = 'status:remove_message' 24 | export const removeMessage = registerAction(STATUS_REMOVE_MESSAGE, (messageKey) => { 25 | if (!messageKey) messageKey = state.messages.keys().pop() 26 | state.messages.delete(messageKey) 27 | }) 28 | -------------------------------------------------------------------------------- /app/components/StatusBar/index.js: -------------------------------------------------------------------------------- 1 | import { inject, observer } from 'mobx-react' 2 | import StatusBar from './StatusBar' 3 | import state from './state' 4 | 5 | export default inject(() => ({ messages: state.messages.values() }))(StatusBar) 6 | -------------------------------------------------------------------------------- /app/components/StatusBar/state.js: -------------------------------------------------------------------------------- 1 | import { observable, autorun } from 'mobx' 2 | 3 | const state = observable({ 4 | messages: observable.map(), 5 | uploadList: observable.map({ 6 | // 'aaa.txt': { percentCompleted: 0, size: 100020303 }, 7 | // 'bbb.txt': { percentCompleted: 50, size: 200020303 }, 8 | }), 9 | setFileUploadInfo: ({ path, info }) => { 10 | const { percentCompleted, size } = info 11 | state.uploadList.set(path, { percentCompleted, size }) 12 | if (percentCompleted === 100) { 13 | state.checkUploadInfo() 14 | } 15 | }, 16 | removeFileUploadInfo: ({ path }) => { 17 | state.uploadList.delete(path) 18 | }, 19 | checkUploadInfo: () => { 20 | let totalPercent = 0 21 | const uploadList = state.uploadList.entries() 22 | uploadList.forEach((item) => { 23 | totalPercent += item[1].percentCompleted 24 | }) 25 | if (totalPercent === 100 * uploadList.length) { 26 | setTimeout(() => state.uploadList = observable.map(), 3000) 27 | } 28 | } 29 | }) 30 | 31 | export default state 32 | -------------------------------------------------------------------------------- /app/components/Tab/index.js: -------------------------------------------------------------------------------- 1 | import TabContainer from './TabContainer' 2 | import * as actions from './actions' 3 | import * as state from './state' 4 | 5 | export default TabContainer 6 | export { actions, state } 7 | -------------------------------------------------------------------------------- /app/components/Tab/store.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions' 2 | import state, { Tab, TabGroup } from './state' 3 | 4 | class TabStore { 5 | constructor () { 6 | Object.assign(this, actions) 7 | } 8 | 9 | getState () { return state } 10 | 11 | getTab (key) { return state.tabs.get(key) } 12 | getTabGroup (key) { return state.tabGroups.get(key) } 13 | 14 | findTab (predicate) { 15 | const state = this.getState() 16 | return state.tabs.values().filter(predicate) 17 | } 18 | 19 | isValidTab (instance) { 20 | return (instance instanceof Tab && state.tabs.has(instance.id)) 21 | } 22 | 23 | isValidTabGroup (instance) { 24 | return (instance instanceof TabGroup && state.tabGroups.has(instance.id)) 25 | } 26 | 27 | } 28 | 29 | const store = new TabStore() 30 | export default store 31 | -------------------------------------------------------------------------------- /app/components/Terminal/index.js: -------------------------------------------------------------------------------- 1 | import TerminalContainer from './TerminalContainer' 2 | 3 | export default TerminalContainer 4 | -------------------------------------------------------------------------------- /app/components/Terminal/state.js: -------------------------------------------------------------------------------- 1 | import uniqueId from 'lodash/uniqueId' 2 | import is from 'utils/is' 3 | import { TabStateScope } from 'commons/Tab' 4 | import config from 'config' 5 | 6 | const { Tab: BaseTab, TabGroup: BaseTabGroup, state } = TabStateScope() 7 | state.keepOne = config.isLib 8 | 9 | class Tab extends BaseTab { 10 | constructor (props = {}) { 11 | super() 12 | this.id = is.undefined(props.id) ? uniqueId('tab_') : props.id 13 | state.tabs.set(this.id, this) 14 | } 15 | } 16 | 17 | class TabGroup extends BaseTabGroup { 18 | constructor (props = {}) { 19 | super() 20 | this.id = is.undefined(props.id) ? uniqueId('tab_group_') : props.id 21 | state.tabGroups.set(this.id, this) 22 | } 23 | } 24 | 25 | export default state 26 | export { Tab, TabGroup, state } 27 | -------------------------------------------------------------------------------- /app/components/Tooltip/TooltipTrigger.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import is from 'utils/is' 3 | import PropTypes from 'prop-types' 4 | import { addTooltip, removeTooltip } from './state' 5 | 6 | class TooltipTrigger extends Component { 7 | static defaultProps = { 8 | placement: 'top', 9 | } 10 | 11 | static propTypes = { 12 | placement: PropTypes.string, 13 | shouldShow: PropTypes.func, 14 | children: PropTypes.any, 15 | content: PropTypes.any, 16 | } 17 | 18 | onMouseOver = (e) => { 19 | const tooltipProps = this.getTooltipProps() 20 | if (!tooltipProps) return 21 | const tooltip = addTooltip(tooltipProps) 22 | e.stopPropagation() 23 | const dismissTooltip = (e) => { 24 | removeTooltip(tooltip) 25 | window.removeEventListener('mouseover', dismissTooltip) 26 | } 27 | window.addEventListener('mouseover', dismissTooltip) 28 | } 29 | 30 | getTooltipProps () { 31 | if (is.function(this.props.shouldShow) && this.props.shouldShow() === false) return null 32 | return { 33 | rect: this.dom.getBoundingClientRect(), 34 | placement: this.props.placement, 35 | content: this.props.content, 36 | } 37 | } 38 | 39 | render () { 40 | const childrenCount = React.Children.count(this.props.children) 41 | if (childrenCount === 1) { 42 | return React.cloneElement(this.props.children, { 43 | ref: r => this.dom = r, 44 | onMouseOver: this.onMouseOver, 45 | }) 46 | } 47 | return null 48 | } 49 | } 50 | 51 | export default TooltipTrigger 52 | -------------------------------------------------------------------------------- /app/components/Tooltip/Tooltips.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { observer } from 'mobx-react' 3 | import cx from 'classnames' 4 | import state from './state' 5 | import _ from 'lodash' 6 | 7 | const Tooltip = observer(({ placement, rect, content, show }) => { 8 | let x, y 9 | switch (placement) { 10 | case 'top': 11 | x = rect.left + rect.width / 2 12 | y = rect.top 13 | break 14 | case 'bottom': 15 | x = rect.left + rect.width / 2 16 | y = rect.bottom 17 | break 18 | case 'left': 19 | x = rect.left 20 | y = rect.top + rect.height / 2 21 | break 22 | case 'right': 23 | x = rect.right 24 | y = rect.top + rect.height / 2 25 | break 26 | } 27 | return ( 28 |
30 |
{content}
31 |
32 | ) 33 | }) 34 | 35 | 36 | const Tooltips = observer(() => ( 37 |
38 | {state.entities.map(tooltip => )} 39 |
40 | )) 41 | 42 | export default Tooltips 43 | -------------------------------------------------------------------------------- /app/components/Tooltip/index.js: -------------------------------------------------------------------------------- 1 | import TooltipTrigger from './TooltipTrigger' 2 | import Tooltips from './Tooltips' 3 | 4 | export default TooltipTrigger 5 | export { Tooltips } 6 | -------------------------------------------------------------------------------- /app/components/Tooltip/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | entities: [] 5 | }) 6 | 7 | export function addTooltip (tooltip, delay=500) { 8 | tooltip.show = false 9 | state.entities.push(tooltip) 10 | tooltip = state.entities[state.entities.length - 1] 11 | tooltip.timeoutId = window.setTimeout(() => tooltip.show = true, delay) 12 | return tooltip 13 | } 14 | 15 | export function removeTooltip (tooltip, delay=500) { 16 | window.clearTimeout(tooltip.timeoutId) 17 | tooltip.show = false 18 | window.setTimeout(() => state.entities.remove(tooltip), delay) 19 | } 20 | 21 | export default state 22 | -------------------------------------------------------------------------------- /app/components/TopBar/TopBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PluginArea from 'components/Plugins/component' 3 | import { TOPBAR } from 'components/Plugins/constants' 4 | import { observer } from 'mobx-react' 5 | import Breadcrumbs from './Breadcrumbs' 6 | 7 | const TopBar = observer(() => ( 8 |
9 | 10 | 11 |
12 | )) 13 | 14 | export default TopBar 15 | -------------------------------------------------------------------------------- /app/components/TopBar/index.js: -------------------------------------------------------------------------------- 1 | import TopBar from './TopBar' 2 | 3 | export default TopBar 4 | -------------------------------------------------------------------------------- /app/components/exports.js: -------------------------------------------------------------------------------- 1 | import * as Accordion from './Accordion' 2 | import * as TopBar from './TopBar' 3 | import * as ContextMenu from './ContextMenu' 4 | import * as Editor from './Editor' 5 | import * as Menu from './Menu' 6 | import * as MenuBar from './MenuBar' 7 | import * as Modal from './Modal' 8 | import * as Tab from './Tab' 9 | import * as Notification from './Notification' 10 | import * as FileTree from './FileTree' 11 | import * as Tooltip from './Tooltip' 12 | import * as Mask from './Mask' 13 | 14 | 15 | export { 16 | Accordion, 17 | TopBar, 18 | ContextMenu, 19 | Editor, 20 | Menu, 21 | Modal, 22 | Tab, 23 | MenuBar, 24 | Notification, 25 | FileTree, 26 | Tooltip, 27 | Mask 28 | } 29 | -------------------------------------------------------------------------------- /app/containers/GlobalPrompt.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import Prompt from 'components/Prompt/Prompt' 4 | 5 | class GlobalPrompt extends Component { 6 | constructor (props) { 7 | super(props) 8 | this.state = { 9 | prompts: [] 10 | } 11 | } 12 | 13 | componentDidMount () { 14 | const id = 'global-prompt-ide' 15 | const promptMessage = [] 16 | 17 | if (!localStorage.getItem('visited')) { 18 | promptMessage.push({ 19 | content: ( 20 |

21 | WebIDE 现已全面升级为 Cloud Studio,点击 22 | this.handleClosePrompt(id, 'update')} 27 | > 28 | 立即体验 29 | {' '} 30 |

31 | ), 32 | id: 'global-prompt-ide', 33 | type: 'update' 34 | }) 35 | this.setState({ prompts: promptMessage }) 36 | } 37 | } 38 | 39 | handleClosePrompt = (id, type) => { 40 | const { prompts } = this.state 41 | this.setState({ prompts: prompts.filter(prompt => prompt.id !== id) }) 42 | if (type === 'update') { 43 | localStorage.setItem('visited', true) 44 | } 45 | } 46 | 47 | render () { 48 | const { prompts } = this.state 49 | return 50 | } 51 | } 52 | 53 | export default GlobalPrompt 54 | -------------------------------------------------------------------------------- /app/containers/IDE.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { initializeFileTree } from '../components/FileTree/actions' 4 | import PanelsContainer from '../components/Panel' 5 | import Utilities from './Utilities' 6 | import hasVimium from 'utils/hasVimium' 7 | import { notify, NOTIFY_TYPE } from '../components/Notification/actions' 8 | import i18n from 'utils/createI18n' 9 | import GlobalPrompt from './GlobalPrompt' 10 | 11 | class IDE extends Component { 12 | constructor (props) { 13 | super(props) 14 | this.state = { isReady: false } 15 | } 16 | 17 | componentWillMount () { // initLifecycle_3: IDE specific init 18 | initializeFileTree() // @fixme: this is related to the quirk in filetree state 19 | this.setState({ isReady: true }) 20 | } 21 | 22 | componentDidMount () { 23 | if (hasVimium()) { 24 | notify({ 25 | notifyType: NOTIFY_TYPE.ERROR, 26 | message: i18n`global.hasVimium`, 27 | dismissAfter: 12000 28 | }) 29 | } 30 | } 31 | 32 | render () { 33 | if (!this.state.isReady) return null 34 | return ( 35 |
36 | 37 | 38 | 39 |
40 | ) 41 | } 42 | } 43 | 44 | export default connect()(IDE) 45 | -------------------------------------------------------------------------------- /app/containers/Initialize/state.js: -------------------------------------------------------------------------------- 1 | import { observable, computed } from 'mobx' 2 | 3 | const state = observable({ 4 | errorInfo: '', 5 | errorCode: null, 6 | status: '', 7 | }) 8 | export default state 9 | -------------------------------------------------------------------------------- /app/containers/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import i18n from 'utils/createI18n' 3 | 4 | class Login extends Component { 5 | render () { 6 | return ( 7 |
8 |
9 |
10 |
11 |

Coding WebIDE

12 |

Coding Anytime Anywhere

13 |
14 | 15 |
16 |
17 | ) 18 | } 19 | handleCodingLogin (e) { 20 | e.preventDefault() 21 | e.stopPropagation() 22 | // window.location.href = `/login?return_url=${window.location.href}` 23 | } 24 | } 25 | 26 | export default Login 27 | -------------------------------------------------------------------------------- /app/containers/Root/actions.js: -------------------------------------------------------------------------------- 1 | // import { createAction } from 'redux-actions' 2 | import config from '../../config' 3 | import ide from '../../IDE' 4 | import { qs } from 'utils' 5 | 6 | // initAppState 7 | export const INIT_STATE = 'INIT_STATE' 8 | export const initState = () => (dispatch) => { 9 | if (config.isPlatform) { 10 | const urlPath = window.location.pathname 11 | const queryEntryPathPattern = /^\/ws\/?$/ 12 | const wsPathPattern = /^\/ws\/([^\/]+)$/ 13 | const isFromQueryEntryPath = queryEntryPathPattern.test(urlPath) 14 | let spaceKey = null 15 | const match = wsPathPattern.exec(urlPath) 16 | if (match) { 17 | spaceKey = match[1] 18 | config.spaceKey = spaceKey 19 | } 20 | } else { 21 | const { spaceKey } = qs.parse(window.location.hash.slice(1)) 22 | if (spaceKey) config.spaceKey = spaceKey 23 | } 24 | window.ide = ide 25 | // dispatch({ type: INIT_STATE }) 26 | } 27 | -------------------------------------------------------------------------------- /app/containers/Root/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Provider, connect } from 'react-redux' 4 | import { Provider as MobxProvider } from 'mobx-react' 5 | 6 | import store from '../../store' // initLifecycle_1: gives the defaultState 7 | import mobxStore from '../../mobxStore' 8 | import IDE from '../IDE' 9 | import { initState } from './actions' 10 | 11 | class Root extends Component { 12 | static proptypes = { 13 | dispatch: PropTypes.func 14 | } 15 | componentWillMount () { 16 | // this.props.dispatch(initState()) // initLifecycle_2 17 | } 18 | render () { 19 | return 20 | } 21 | } 22 | 23 | Root = connect(null)(Root) 24 | 25 | export default () => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /app/containers/Root/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Provider, connect } from 'react-redux' 4 | import { Provider as MobxProvider } from 'mobx-react' 5 | 6 | import store from '../../store' // initLifecycle_1: gives the defaultState 7 | import mobxStore from '../../mobxStore' 8 | import Login from '../Login' 9 | import { initState } from './actions' 10 | 11 | class Root extends Component { 12 | static proptypes = { 13 | dispatch: PropTypes.func 14 | } 15 | componentWillMount () { 16 | // this.props.dispatch(initState()) // initLifecycle_2 17 | } 18 | render () { 19 | return 20 | } 21 | } 22 | 23 | Root = connect(null)(Root) 24 | 25 | export default () => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /app/containers/Root/reducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | import { qs } from 'utils' 3 | import config from '../../config' 4 | 5 | import { 6 | INIT_STATE, 7 | } from './actions' 8 | 9 | 10 | export default handleActions({ 11 | [INIT_STATE]: (state, action) => { 12 | const { spaceKey } = qs.parse(window.location.hash.slice(1)) 13 | if (spaceKey) config.spaceKey = spaceKey 14 | return ({ 15 | ...state, 16 | }) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /app/containers/Utilities.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ModalContainer from 'components/Modal' 3 | import Notification from 'components/Notification' 4 | import DragAndDrop from 'components/DragAndDrop' 5 | import { Tooltips } from 'components/Tooltip' 6 | import PluginArea from 'components/Plugins/component' 7 | import { CONTAINERS } from 'components/Plugins/constants' 8 | import { ContextMenuContainer } from 'components/ContextMenu' 9 | import Mask from 'components/Mask' 10 | 11 | 12 | const Utilities = () => ( 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | ) 23 | 24 | export default Utilities 25 | -------------------------------------------------------------------------------- /app/exports.js: -------------------------------------------------------------------------------- 1 | const app = { 2 | backendAPI: require('backendAPI'), 3 | commands: require('commands'), 4 | commons: require('commons/exports'), 5 | components: require('components/exports'), 6 | utils: require('utils/exports'), 7 | config: require('./config'), 8 | settings: require('./settings'), 9 | settingFormState: require('components/Setting/state') 10 | } 11 | 12 | const lib = { 13 | react: require('react'), 14 | mobx: require('mobx'), 15 | mobxReact: require('mobx-react'), 16 | classnames: require('classnames'), 17 | lodash: require('lodash'), 18 | eventemitter: require('eventemitter3'), 19 | sockjsClient: require('sockjs-client'), 20 | moment: require('moment'), 21 | codemirror: require('codemirror'), 22 | reactDom: require('react-dom'), 23 | axios: require('axios'), 24 | localforage: require('localforage'), 25 | styled: require('styled-components'), 26 | } 27 | 28 | export { app, lib } 29 | -------------------------------------------------------------------------------- /app/i18n/en_US/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileForceMove": "Do you want to overwrite?", 3 | "newFilePath": "Enter the path for the new file.", 4 | "posInfo": "Ln:{ln} Col:{col}", 5 | "goto": "Go to Line[:Column]", 6 | "newFileName": "Enter the new name (or new path) for this file.", 7 | "folderExist": "The folder already exists", 8 | "deleteNotifySuccess": "Delete success!", 9 | "newFileFolderPath": "Enter the path for the new folder.", 10 | "carets": "{length} carets", 11 | "deleteNotifyFailed": "Delete fail: {err}", 12 | "deleteHeader": "Are you sure you want to delete this file?", 13 | "fileExist": "The path already exists", 14 | "moveFolderFailed": "Failed to move folder", 15 | "deleteMessage": "You're trying to delete {file}", 16 | "deleteButton": "Delete", 17 | "overwriteButton": "Overwrite", 18 | "totalSelected": "{length} carets ({count} characters selected)", 19 | "fileToLarge": "Filesize is limit to {filesize} MB", 20 | "uploading": "Uploading", 21 | "uploadSucceed": "Upload succeed", 22 | "uploadFailed": "Upload failed" 23 | } -------------------------------------------------------------------------------- /app/i18n/en_US/fileTree.json: -------------------------------------------------------------------------------- 1 | { 2 | "contextMenu": { 3 | "newFile": "New File...", 4 | "newFolder": "New Folder...", 5 | "delete": "Delete...", 6 | "rename": "Rename...", 7 | "download": "Download", 8 | "upload": "Upload", 9 | "gitBlame": "Git Blame" 10 | } 11 | } -------------------------------------------------------------------------------- /app/i18n/en_US/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "loadingWorkspaceCloning": "Now cloning code...", 3 | "loadingWorkspaceCloneFailed": "Clone failed", 4 | "offline": "Offline", 5 | "recloneWorkspace": "Try clone again", 6 | "requestingCollaboration": "You have requested collaboration", 7 | "online": "Online", 8 | "loadingWorkspaceDenied": "Loading workspace denied", 9 | "requestCollaborationReject": "Your request has been rejected.", 10 | "loadingWorkspaceFailed": "Loading workspace is failed", 11 | "onSocketError": "file socket is error", 12 | "requestCollaboration": "Request collaboration", 13 | "connecting": "Connecting", 14 | "loadingWorkspace": "Loading workspace...", 15 | "reloadWorkspace": "Retry", 16 | "requestCollaborationExpires": "The demo has already expired.", 17 | "hasVimium": "Please disable Vimium plugin." 18 | } -------------------------------------------------------------------------------- /app/i18n/en_US/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const contents = ['menuBarItems', 'settings', 'file', 'panel', 'tab', 'git', 'fileTree', 'global', 'modal', 'login'] 4 | 5 | export default contents.reduce((p, v) => { 6 | p[v] = require(`./${v}.json`) 7 | return p 8 | }, {}) 9 | -------------------------------------------------------------------------------- /app/i18n/en_US/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "loginCoding": "Login with Coding" 3 | } -------------------------------------------------------------------------------- /app/i18n/en_US/menuBarItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "main": "Settings" 4 | }, 5 | "file": { 6 | "main": "File", 7 | "newFile": "New File", 8 | "newFolder": "New Folder", 9 | "save": "Save", 10 | "commit": "commit" 11 | }, 12 | "edit": { 13 | "main": "Edit", 14 | "format": "Format", 15 | "comment": "Toggle Comment" 16 | }, 17 | "git": { 18 | "rebase": "Rebase", 19 | "pull": "Pull", 20 | "unstashChanges": "Unstash Changes", 21 | "continueRebase": "Continue Rebasing", 22 | "stashChanges": "Stash Changes", 23 | "resetHead": "Reset HEAD", 24 | "branches": "Branches", 25 | "abortRebase": "Abort Rebasing", 26 | "resolveConflicts": "Resolve Conflicts", 27 | "skipCommit": "Skip Commit", 28 | "commit": "Commit", 29 | "main": "Git", 30 | "push": "Push", 31 | "tag": "Tag", 32 | "mergeBranch": "Merge Branch" 33 | }, 34 | "tools": { 35 | "main": "Tools", 36 | "terminal": "Terminal", 37 | "newTerminal": "New Terminal" 38 | }, 39 | "switch": "Switch to v1" 40 | } -------------------------------------------------------------------------------- /app/i18n/en_US/modal.json: -------------------------------------------------------------------------------- 1 | { 2 | "okButton": "OK", 3 | "cancelButton": "Cancel" 4 | } -------------------------------------------------------------------------------- /app/i18n/en_US/panel.json: -------------------------------------------------------------------------------- 1 | { 2 | "bottom": { 3 | "terminal": "Terminal", 4 | "gitGraph": "Git Logs", 5 | "history": "History" 6 | }, 7 | "left": { 8 | "project": "Project", 9 | "working": "Working" 10 | } 11 | } -------------------------------------------------------------------------------- /app/i18n/en_US/tab.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeDropdownMenuItems": { 3 | "close": "Close Pane", 4 | "untitledTab": "untitled", 5 | "noTabs": "" 6 | }, 7 | "contextMenu": { 8 | "close": "Close", 9 | "closeOthers": "Close Others", 10 | "closeAll": "Close All", 11 | "verticalSplit": "Vertical Split", 12 | "horizontalSplit": "Horizontal Split" 13 | } 14 | } -------------------------------------------------------------------------------- /app/i18n/index.json: -------------------------------------------------------------------------------- 1 | ["en_US", "zh_CN"] -------------------------------------------------------------------------------- /app/i18n/zh_CN/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileForceMove": "是否覆盖现有文件", 3 | "newFilePath": "请输入新文件的路径", 4 | "posInfo": "行:{ln} 列:{col}", 5 | "goto": "跳转到 行[:列]", 6 | "newFileName": "请输入新文件路径/文件名", 7 | "folderExist": "目录已经存在", 8 | "deleteNotifySuccess": "成功删除", 9 | "newFileFolderPath": "请输入新文件夹的路径", 10 | "carets": "{length} 光标", 11 | "deleteNotifyFailed": "删除失败:{err}", 12 | "deleteHeader": "你是否确认删除此文件", 13 | "fileExist": "文件已经存在", 14 | "moveFolderFailed": "目录移动失败", 15 | "deleteMessage": "你正在删除 {file}", 16 | "deleteButton": "删除", 17 | "overwriteButton": "覆盖", 18 | "totalSelected": "{length} 光标 (已选中 {count} 个字符)", 19 | "fileToLarge": "文件不能超过 {filesize} MB", 20 | "uploading": "正在上传", 21 | "uploadSucceed": "上传完成", 22 | "uploadFailed": "上传失败" 23 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/fileTree.json: -------------------------------------------------------------------------------- 1 | { 2 | "contextMenu": { 3 | "newFile": "新建文件...", 4 | "newFolder": "新建文件夹...", 5 | "delete": "删除...", 6 | "rename": "重命名...", 7 | "download": "下载", 8 | "upload": "上传", 9 | "gitBlame": "查看历史" 10 | } 11 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "loadingWorkspaceCloning": "正在 Clone 项目代码...", 3 | "loadingWorkspaceCloneFailed": "Clone 代码失败", 4 | "offline": "离线", 5 | "recloneWorkspace": "重新 Clone 代码", 6 | "requestingCollaboration": "协作申请已经提交", 7 | "online": "在线", 8 | "loadingWorkspaceDenied": "访问工作区未授权", 9 | "requestCollaborationReject": "您的申请被拒绝了", 10 | "loadingWorkspaceFailed": "加载工作区失败", 11 | "onSocketError": "已断线", 12 | "requestCollaboration": "申请协作", 13 | "connecting": "连接", 14 | "loadingWorkspace": "正在加载工作区", 15 | "reloadWorkspace": "重新加载", 16 | "requestCollaborationExpires": "试用已经结束", 17 | "hasVimium": "请禁用 Vimium 插件以避免按键冲突" 18 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/index.js: -------------------------------------------------------------------------------- 1 | const contents = ['menuBarItems', 'settings', 'file', 'panel', 'tab', 'git', 'fileTree', 'global', 'modal', 'login'] 2 | 3 | export default contents.reduce((p, v) => { 4 | p[v] = require(`./${v}.json`) 5 | return p 6 | }, {}) 7 | -------------------------------------------------------------------------------- /app/i18n/zh_CN/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "loginCoding": "使用 Coding 账号登录" 3 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/menuBarItems.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "main": "设置" 4 | }, 5 | "file": { 6 | "main": "文件", 7 | "newFile": "新建文件", 8 | "newFolder": "新建文件夹", 9 | "save": "保存", 10 | "commit": "commit" 11 | }, 12 | "edit": { 13 | "main": "编辑", 14 | "format": "格式化代码", 15 | "comment": "切换注释" 16 | }, 17 | "git": { 18 | "rebase": "变基", 19 | "pull": "拉取", 20 | "unstashChanges": "恢复代码", 21 | "continueRebase": "继续变基", 22 | "stashChanges": "储藏代码", 23 | "resetHead": "重置代码", 24 | "branches": "分支", 25 | "abortRebase": "终止变基", 26 | "resolveConflicts": "解决冲突", 27 | "skipCommit": "跳过提交", 28 | "commit": "提交", 29 | "main": "版本", 30 | "push": "推送", 31 | "tag": "标签", 32 | "mergeBranch": "合并分支" 33 | }, 34 | "tools": { 35 | "main": "工具", 36 | "terminal": "终端", 37 | "newTerminal": "新建" 38 | }, 39 | "switch": "切换到 v1 版本" 40 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/modal.json: -------------------------------------------------------------------------------- 1 | { 2 | "okButton": "确定", 3 | "cancelButton": "取消" 4 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/panel.json: -------------------------------------------------------------------------------- 1 | { 2 | "bottom": { 3 | "terminal": "终端", 4 | "gitGraph": "项目网络", 5 | "history": "版本历史" 6 | }, 7 | "left": { 8 | "project": "文件树", 9 | "working": "工作文件" 10 | } 11 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": "默认", 3 | "reset": "重置", 4 | "commit": "确认", 5 | "general": { 6 | "main": "通用", 7 | "language": "语言", 8 | "languageOption": { 9 | "english": "英语", 10 | "chinese": "中文" 11 | }, 12 | "hideFiles": "隐藏文件" 13 | }, 14 | "editor": { 15 | "main": "编辑器", 16 | "editorconfigNote": "如果你的项目根目录下有 .editorconfig 文件,那么本页面下的一些选项的控制会被它接管。", 17 | "editorconfigNoteLink": "打开 .editorconfig", 18 | "fontFamily": "字体", 19 | "charset": "编码", 20 | "indentStyle": "缩进风格", 21 | "indentSize": "缩进空格数", 22 | "tabWidth": "Tab 宽度", 23 | "trimTrailingWhitespace": "删除行尾空格", 24 | "insertFinalNewline": "文件结尾插入空白行", 25 | "autoSave": "自动保存", 26 | "autoWrap": "自动换行", 27 | "autoCompletion": "自动补全", 28 | "snippets": "代码片段" 29 | }, 30 | "keymap": { 31 | "main": "快捷键", 32 | "keyboardMode": "编辑器键盘模式" 33 | }, 34 | "appearance": { 35 | "main": "样式", 36 | "uiTheme": "UI 主题", 37 | "uiThemeOption": { 38 | "baseTheme": "默认", 39 | "dark": "黑色" 40 | }, 41 | "syntaxTheme": "代码着色", 42 | "fontSize": "编辑器字号" 43 | }, 44 | "extension": { 45 | "main": "插件中心", 46 | "searchPlaceholder": "根据插件名字搜索" 47 | }, 48 | "tabs": { 49 | "general": "通用", 50 | "appearance": "样式", 51 | "editor": "编辑器", 52 | "keymap": "快捷键", 53 | "extensions": "插件中心" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/i18n/zh_CN/tab.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeDropdownMenuItems": { 3 | "close": "关闭面板", 4 | "untitledTab": "未命名", 5 | "noTabs": "<没有标签>" 6 | }, 7 | "contextMenu": { 8 | "close": "关闭标签", 9 | "closeOthers": "关闭其他标签", 10 | "closeAll": "关闭全部标签", 11 | "verticalSplit": "垂直切分", 12 | "horizontalSplit": "水平切分" 13 | } 14 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /app/index.jsx: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import Root from './containers/Root' 5 | import './styles/main.styl' 6 | import initialize from './initialize' 7 | import InitializeContainer from './containers/Initialize' 8 | import SettingState from 'components/Setting/state' 9 | const uiTheme = SettingState.settings.appearance.ui_theme.value 10 | if (uiTheme === 'base-theme') { 11 | const baseTheme = require('!!style-loader/useable!css-loader!stylus-loader!./styles/base-theme/index.styl') 12 | baseTheme.use() 13 | window.themes = { '@current': baseTheme } 14 | } else { 15 | const darkTheme = require('!!style-loader/useable!css-loader!stylus-loader!./styles/dark/index.styl') 16 | darkTheme.use() 17 | window.themes = { '@current': darkTheme } 18 | } 19 | 20 | const rootElement = document.getElementById('root') 21 | render(, rootElement) 22 | 23 | async function startApp (module) { 24 | const step = await initialize() 25 | if (!step.allSuccess) { 26 | return 27 | } 28 | if (__DEV__) { 29 | const hotLoaderRender = () => 30 | render(, rootElement) 31 | 32 | hotLoaderRender() 33 | if (module.hot) module.hot.accept('./containers/Root', hotLoaderRender) 34 | } else { 35 | render(, rootElement) 36 | } 37 | } 38 | 39 | startApp(module) 40 | 41 | const log = (...args) => console.log(...args) || (x => x) 42 | if (__VERSION__) log(`[VERSION] ${__VERSION__}`) 43 | -------------------------------------------------------------------------------- /app/js.config.json: -------------------------------------------------------------------------------- 1 | { "moduleNameMapper": { 2 | "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.jestfilemock.js", 3 | "^.+\\.(css|less|scss)$": "identity-obj-proxy", 4 | "utils": "/app/utils/index.js", 5 | "config": "/app/config.js", 6 | "commons": "/app/commons", 7 | "components": "/app/components" 8 | }, 9 | "roots": [ 10 | "/app" 11 | ], 12 | "snapshotSerializers": ["/node_modules/enzyme-to-json/serializer"], 13 | "collectCoverageFrom": ["app/*.js"], 14 | "coveragePathIgnorePatterns": [], 15 | "coverageThreshold": { 16 | "global": { 17 | "branches": 46.17, 18 | "functions": 62.18, 19 | "lines": 58.18, 20 | "statements": 57.46 21 | } 22 | }, 23 | "globals": { 24 | "__DEV__": true, 25 | "__BACKEND_URL__": "", 26 | "__PACKAGE_SERVER__": "", 27 | "__WS_URL__": "", 28 | "__RUN_MODE__": "mode" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "allowSyntheticDefaultImports": true, 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "baseUrl": "./", 8 | "paths": { 9 | "*": ["*", "*/index", "*/index.js", "*/index.jsx"] 10 | } 11 | }, 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /app/localStoreCache.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | const localStoreCache = {} 3 | 4 | const stateDomainsToCache = [ 5 | // 'MarkdownEditorState', 6 | // 'FileTreeState', 7 | // 'PanelState', 8 | // 'PaneState', 9 | // 'TabState', 10 | // 'EditorState', 11 | // 'ModalState', 12 | // 'TerminalState', 13 | // 'GitState', 14 | // 'NotificationState', 15 | // 'WorkspaceState', 16 | // 'DragAndDrop', 17 | // 'SettingState', 18 | // 'PackageState', 19 | ] 20 | 21 | const stateFilter = state => stateDomainsToCache.reduce((stateToCache, domain) => { 22 | if (!state) return '' 23 | stateToCache[domain] = state[domain] 24 | return stateToCache 25 | }, {}) 26 | 27 | 28 | let cachedState 29 | localStoreCache.beforeReducer = (state, action) => { 30 | // if (!state) state = JSON.parse(window.localStorage.getItem('snapshot')) 31 | if (!state) return 32 | cachedState = JSON.stringify(stateFilter(state)) 33 | return state 34 | } 35 | 36 | localStoreCache.afterReducer = (state, action) => { 37 | const nextCachedState = JSON.stringify(stateFilter(state)) 38 | if (nextCachedState !== cachedState) { 39 | localStorage.setItem('snapshot', nextCachedState) 40 | cachedState = nextCachedState 41 | } 42 | return state 43 | } 44 | 45 | export default localStoreCache 46 | -------------------------------------------------------------------------------- /app/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /app/login.jsx: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import Login from './containers/Root/login' 5 | import './styles/main.styl' 6 | 7 | const rootElement = document.getElementById('root') 8 | async function startApp (module) { 9 | if (__DEV__) { 10 | const hotLoaderRender = () => 11 | render(, rootElement) 12 | 13 | hotLoaderRender() 14 | if (module.hot) module.hot.accept('./containers/Login', hotLoaderRender) 15 | } else { 16 | render(, rootElement) 17 | } 18 | } 19 | 20 | startApp(module) 21 | 22 | const log = (...args) => console.log(...args) || (x => x) 23 | if (__VERSION__) log(`[VERSION] ${__VERSION__}`) 24 | -------------------------------------------------------------------------------- /app/plugin/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | key: 'preload', 3 | Manager: require('./placeholder').default, 4 | } 5 | -------------------------------------------------------------------------------- /app/plugin/placeholder.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | pluginWillMount () { 3 | console.log('inner plugin') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/states/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux' 2 | import reducers from '../reducers' 3 | 4 | const store = createStore(reducers) 5 | 6 | export default store 7 | -------------------------------------------------------------------------------- /app/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux' 2 | import { composeReducers } from './utils' 3 | import { dispatch as emitterDispatch, emitterMiddleware } from 'utils/actions' 4 | import thunkMiddleware from 'redux-thunk' 5 | 6 | import GitReducer from './components/Git/reducer' 7 | import RootReducer from './containers/Root/reducer' 8 | 9 | import localStoreCache from './localStoreCache' 10 | 11 | const combinedReducers = combineReducers({ 12 | GitState: GitReducer, 13 | }) 14 | 15 | const crossReducers = composeReducers(RootReducer) 16 | const finalReducer = composeReducers( 17 | localStoreCache.afterReducer, 18 | crossReducers, 19 | combinedReducers, 20 | localStoreCache.beforeReducer 21 | ) 22 | 23 | const enhancer = compose( 24 | applyMiddleware(thunkMiddleware, emitterMiddleware), 25 | window.devToolsExtension ? window.devToolsExtension({ 26 | serialize: { 27 | replacer: (key, value) => { 28 | if (key === 'editor') return {} 29 | if (key === 'DOMNode') return {} 30 | return value 31 | } 32 | } 33 | }) : f => f 34 | ) 35 | // enhancer = applyMiddleware(thunkMiddleware) 36 | const store = createStore(finalReducer, enhancer) 37 | window.getState = store.getState 38 | window.dispatch = store.dispatch 39 | 40 | 41 | window.addEventListener('storage', (e) => { 42 | if (e.key && e.key.includes('CodingPackage')) { 43 | store.dispatch({ type: 'UPDATE_EXTENSION_CACHE' }) 44 | } 45 | }) 46 | 47 | export default store 48 | export const getState = store.getState 49 | export const dispatch = store.dispatch 50 | -------------------------------------------------------------------------------- /app/store.spec.js: -------------------------------------------------------------------------------- 1 | import { getState } from './store' 2 | 3 | describe('getState', () => { 4 | it('should getState', () => expect(getState()).toBe(false)) 5 | }) 6 | -------------------------------------------------------------------------------- /app/styles/base-theme/index.styl: -------------------------------------------------------------------------------- 1 | @import './styles/ui-variables'; 2 | @import './styles/tabs'; 3 | @import './styles/panes'; 4 | @import './styles/filetree'; 5 | @import './styles/menu'; 6 | @import './styles/bars'; 7 | @import './styles/git'; 8 | @import './styles/accordion'; 9 | @import './styles/filelist'; 10 | @import './styles/editor'; 11 | @import './styles/term'; 12 | @import './styles/mask'; -------------------------------------------------------------------------------- /app/styles/base-theme/styles/accordion.styl: -------------------------------------------------------------------------------- 1 | .accordion { 2 | .accordion-topbar { 3 | background-color: $tab-bar-background-color; 4 | border-color: $base-border-color; 5 | color: $text-color; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/bars.styl: -------------------------------------------------------------------------------- 1 | .menu-bar { 2 | background-color: $base-top-menu-bar-background; 3 | border-color: gray(80%); 4 | color: gray(15%); 5 | } 6 | 7 | .status-bar { 8 | border-color: $base-border-color; 9 | background-color: $base-top-menu-bar-background; 10 | } 11 | 12 | .breadcrumbs { 13 | background-color: $menu-background-color; 14 | border-color: $base-border-color; 15 | .crumb { 16 | background-color: $menu-background-color; 17 | &:before { 18 | border-left-color: $base-border-color; 19 | } 20 | &:after { 21 | border-left-color: $menu-background-color; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/editor.styl: -------------------------------------------------------------------------------- 1 | .status-bar-editor-widgets { 2 | display: flex; 3 | align-items: stretch; 4 | .editor-widget { 5 | display: flex; 6 | align-items: center; 7 | padding: 0 8px; 8 | &.enabled { 9 | background-color: #ccc; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/filelist.styl: -------------------------------------------------------------------------------- 1 | .file-list-container { 2 | .file-list-item { 3 | color: $text-color; 4 | &.focus { 5 | color: $text-color-highlight; 6 | background-color: $tree-view-background-color-highlight; 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /app/styles/base-theme/styles/filetree.styl: -------------------------------------------------------------------------------- 1 | .filetree-node.focus { 2 | color: $text-color-highlight; 3 | > .filetree-node-bg { 4 | background-color: $tree-view-background-color-highlight; 5 | } 6 | } 7 | 8 | .filetree-container { 9 | .filetree-node-container.highlight, 10 | .filetree-node.focus { 11 | background-color: $tree-view-background-color-highlight; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/git.styl: -------------------------------------------------------------------------------- 1 | // .status { color: @text-color; } 2 | // .status-added { color: @text-color-success; } // green 3 | // .status-ignored { color: @text-color-subtle; } // faded 4 | // .status-modified { color: @text-color-warning; } // orange 5 | // .status-removed { color: @text-color-error; } // red 6 | // .status-renamed { color: @text-color-info; } // blue 7 | 8 | .git-untracked { 9 | color: $untracked-color; 10 | } 11 | .git-confliction { 12 | color: $confliction-color; 13 | } 14 | .git-modified { 15 | color: $modified-color; 16 | } 17 | .git-modify { 18 | color: $modified-color; 19 | } 20 | .git-changed { 21 | color: $modified-color; 22 | } 23 | .git-rename { 24 | color: $modified-color; 25 | } 26 | .git-copy { 27 | color: $modified-color; 28 | } 29 | 30 | .git-added { 31 | color: $added-color; 32 | } 33 | .git-add { 34 | color: $added-color; 35 | } 36 | .git-missing { 37 | text-decoration: line-through; 38 | } 39 | 40 | .git-removed { 41 | text-decoration: line-through; 42 | } 43 | .git-delete { 44 | text-decoration: line-through; 45 | } 46 | 47 | .file-status-indicator { 48 | &.modified, &.changed, &.modify {color: hsl(47,100%,50%)} 49 | &.untracked, &.add {color: hsl(80,60%,40%)} 50 | &.missing {color: hsl(10,80%,45%)} 51 | &.confliction {color: hsl(10,80%,45%)} 52 | } 53 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/mask.styl: -------------------------------------------------------------------------------- 1 | .mask-container { 2 | background: rgba(0, 0, 0, 0.4); 3 | .progress { 4 | background-color: #fff; 5 | } 6 | } -------------------------------------------------------------------------------- /app/styles/base-theme/styles/menu.styl: -------------------------------------------------------------------------------- 1 | .menu { 2 | color: $text-color; 3 | background-color: $menu-background-color; 4 | border-color: $menu-border-color; 5 | opacity: 0.95; 6 | > li > hr { 7 | border-color: gray(80%); 8 | } 9 | } 10 | 11 | .menu-bar-container { 12 | background-color: $base-top-menu-bar-background; 13 | } 14 | 15 | .menu-bar-item-container, .menu-item-container { 16 | &.active { 17 | background-color: $menu-background-color-highlight; 18 | color: #fff; 19 | } 20 | &.disabled, &.active.disabled { 21 | background-color: transparent; 22 | color: $text-color-subtle; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/panes.styl: -------------------------------------------------------------------------------- 1 | .panel-container, .pane-container { 2 | background-color: $base-background-color; 3 | border: 1px solid $base-border-color; 4 | } 5 | -------------------------------------------------------------------------------- /app/styles/base-theme/styles/term.styl: -------------------------------------------------------------------------------- 1 | .ide-terminal { 2 | background: #FFF; 3 | } 4 | 5 | .terminal-panel { 6 | .terminal-toolbar { 7 | color: $text-color; 8 | } 9 | } -------------------------------------------------------------------------------- /app/styles/core-ui/Accordion.styl: -------------------------------------------------------------------------------- 1 | .resizeBar { 2 | $size = 6px 3 | z-index: z(pane-resize-bar); 4 | &.col-resize { 5 | cursor: col-resize; 6 | height: 100%; 7 | width: $size; 8 | margin-lr: $size * -0.5; 9 | } 10 | &.row-resize { 11 | cursor: row-resize; 12 | width: 100%; 13 | height: $size; 14 | margin-tb: $size * -0.5; 15 | } 16 | } 17 | 18 | .accordion-group { 19 | display: flex; 20 | height: 100%; 21 | width: 100%; 22 | } 23 | 24 | .accordion { 25 | flex-basis: 0; 26 | min-height: $tab-height; 27 | position: relative; 28 | // transition: flex-grow 1s ease; 29 | .accordion-topbar { 30 | display: flex; 31 | align-items: center; 32 | background-color: #f4f4f4; 33 | border-bottom: 1px solid $base-border-color; 34 | height: $tab-height; 35 | noSelect(); 36 | 37 | .indicator { 38 | margin-lr: 5px; 39 | width: 9px; 40 | text-align: center; 41 | } 42 | .accordion-header { 43 | cursor: pointer; 44 | flex: 1; 45 | white-space: nowrap; 46 | 47 | .icon { 48 | margin-right: 4px; 49 | } 50 | } 51 | .accordion-actions { 52 | cursor: pointer; 53 | margin-right: 6px; 54 | } 55 | } 56 | .accordion-body { 57 | position: absolute; 58 | top: 30px; 59 | bottom: 0; 60 | left: 0; 61 | right: 0; 62 | // overflow: auto; 63 | scrollBar(); 64 | &.hidden { 65 | display: none; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/styles/core-ui/Bar.styl: -------------------------------------------------------------------------------- 1 | $bar-size= 25px 2 | 3 | // the rotated label trick, see: https://gist.github.com/aiboy/7406727 4 | .side-bar { 5 | display: flex; 6 | noSelect(); 7 | 8 | &.left, &.right { 9 | width: $bar-size; 10 | flex-direction: column; 11 | .side-bar-label { 12 | padding: 5px 4px; 13 | text-align right; 14 | } 15 | } 16 | &.right { 17 | .side-bar-label { 18 | padding: 5px 2px; 19 | } 20 | } 21 | &.top, &.bottom { 22 | height: $bar-size; 23 | flex-direction: row; 24 | .side-bar-label { 25 | padding-lr: 5px; 26 | text-align center; 27 | } 28 | } 29 | 30 | .side-bar-label { 31 | cursor: pointer; 32 | overflow: hidden; 33 | &:hover { 34 | background-color: gray(95%); 35 | } 36 | &.active { 37 | background-color: gray(90%); 38 | } 39 | } 40 | 41 | &.top, &.bottom { 42 | .side-bar-label-container { 43 | display: flex; 44 | align-items: center; 45 | height: 100%; 46 | } 47 | } 48 | 49 | &.left, &.right { 50 | .side-bar-label-container { 51 | display: inline-block; 52 | white-space: nowrap; 53 | transform: translate(0.8em, -0.5em) rotate(90deg); 54 | transform-origin: 0 0.5em; 55 | &:before { 56 | // this is the key, it keeps the box from collapse 57 | content: ""; 58 | float: right; 59 | margin-bottom: 100%; 60 | } 61 | } 62 | } 63 | 64 | .side-bar-label-content { 65 | display: flex; 66 | line-height: 1; 67 | align-items: center; 68 | .icon {margin-right: 4px} 69 | } 70 | 71 | &.left .side-bar-label-content { 72 | transform: rotate(180deg); 73 | } 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /app/styles/core-ui/Breadcrumbs.styl: -------------------------------------------------------------------------------- 1 | .top-bar { 2 | display: flex; 3 | noSelect(); 4 | 5 | .widget { 6 | display: flex; 7 | margin-left: auto; 8 | white-space: nowrap; 9 | line-height: 12px; 10 | padding-right: 4px; 11 | align-items: center; 12 | } 13 | } 14 | 15 | .breadcrumbs { 16 | noSelect(); 17 | $breadcrumbs-height = 24px; 18 | 19 | display: flex; 20 | height: $breadcrumbs-height + 1px; 21 | 22 | .crumb-node-name { 23 | line-height: $breadcrumbs-height; 24 | } 25 | 26 | .crumb { 27 | display: flex; 28 | align-items: center; 29 | padding-left: 10px; 30 | padding-right: $breadcrumbs-height*0.25 + 10px; 31 | position: relative; 32 | 33 | i { 34 | font-style: normal; 35 | } 36 | 37 | &:before, &:after { 38 | display: block; 39 | content: ''; 40 | border-style: solid; 41 | border-color: transparent; 42 | border-right: none; 43 | } 44 | &:before { 45 | absolute(right 0 top -1px) 46 | border-top-width: $breadcrumbs-height*0.5 + 1px; 47 | border-bottom-width: $breadcrumbs-height*0.5 + 1px; 48 | border-left-width: $breadcrumbs-height*0.25 + 1px; 49 | } 50 | &:after { 51 | absolute(right 1px top 0px) 52 | border-top-width: $breadcrumbs-height*0.5 53 | border-bottom-width: $breadcrumbs-height*0.5 54 | border-left-width: $breadcrumbs-height*0.25; 55 | } 56 | &:last-child { 57 | &:before, &:after {display: none} 58 | } 59 | .fa-folder-o { 60 | margin-top: 2px; 61 | } 62 | .fa-folder { 63 | margin-top: 2px; 64 | color: $tree-view-folder-color; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/styles/core-ui/CommandPalette.styl: -------------------------------------------------------------------------------- 1 | .command-palette-input { 2 | margin-tb: 10px; 3 | } 4 | 5 | .command-palette-items { 6 | i, em {font-style: normal;} 7 | em { 8 | font-weight: bold; 9 | color: $text-color-highlight; 10 | } 11 | 12 | li { 13 | padding: 10px 10px; 14 | margin-lr: -10px; 15 | &.selected { 16 | background-color: $background-color-highlight; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/core-ui/DragAndDrop.styl: -------------------------------------------------------------------------------- 1 | .pane-layout-overlay { 2 | z-index: z(pane-layout-overlay); 3 | transition: all ease 0.2s; 4 | opacity: 0; 5 | background-color: hsl(210, 81%, 75%); 6 | } 7 | -------------------------------------------------------------------------------- /app/styles/core-ui/Editor.styl: -------------------------------------------------------------------------------- 1 | .CodeMirror-scroll { 2 | padding-bottom: 18px; 3 | } 4 | 5 | .CodeMirror pre { 6 | font-family: Consolas, "Source Code Pro", Consolas, "Courier New", Menlo, Monaco, "DejaVu Sans Mono", monospace; 7 | line-height: 1.3; 8 | } 9 | 10 | .CodeMirror .CodeMirror-linenumber { 11 | line-height: 1.3; 12 | } 13 | 14 | .CodeMirror .breakpoint-highlight { 15 | background: red; 16 | } 17 | 18 | .status-bar-editor-widgets { 19 | display: flex; 20 | align-items: stretch; 21 | .editor-widget { 22 | display: flex; 23 | align-items: center; 24 | padding: 0 8px; 25 | } 26 | } 27 | 28 | .mode-widget { 29 | display: flex; 30 | position: absolute; 31 | bottom: 24px; 32 | min-width: 200px; 33 | right: 1px; 34 | flex-direction: column; 35 | z-index: 201; 36 | 37 | ul { 38 | max-height: 200px; 39 | overflow: auto; 40 | } 41 | } 42 | 43 | .image-editor-loading { 44 | .fa-spinner { 45 | position: absolute; 46 | top: 50%; 47 | left: 50%; 48 | transform: translate(-50%,-50%); 49 | color: $text-color; 50 | } 51 | } 52 | 53 | .htmlPreview { 54 | overflow: hidden; 55 | position: relative; 56 | .preview-iframe-msk { 57 | position: absolute; 58 | overflow: auto; 59 | width: 100%; 60 | height: 100%; 61 | } 62 | iframe { 63 | position: absolute; 64 | overflow: auto; 65 | width: 100%; 66 | height: 100%; 67 | border: 0; 68 | background-color: white; 69 | } 70 | } -------------------------------------------------------------------------------- /app/styles/core-ui/FileList.styl: -------------------------------------------------------------------------------- 1 | .file-list-container { 2 | height: 100%; 3 | overflow: auto; 4 | scrollBar(); 5 | .file-list-item { 6 | padding: 4px 4px; 7 | cursor: pointer; 8 | .icon { 9 | width: 16px; 10 | 11 | } 12 | i { 13 | font-style: normal; 14 | width: 1em; 15 | text-align: center; 16 | margin-right: 5px; 17 | &:before { 18 | font-size: 14px; 19 | } 20 | } 21 | .fa-times { 22 | color: red; 23 | visibility: hidden; 24 | } 25 | &.focus { 26 | background-color: #e8e8e8; 27 | } 28 | &:hover .fa-times { 29 | visibility: visible; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/styles/core-ui/Mask.styl: -------------------------------------------------------------------------------- 1 | .mask-container { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | background: rgba(0, 0, 0, 0.4); 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | .progress { 12 | // position: absolute; 13 | // left: 25%; 14 | // top: 50%; 15 | // width: 50%; 16 | // margin-top: -15px; 17 | noSelect() 18 | padding: 10px 10px; 19 | border-radius: 5px; 20 | background-color: #fff; 21 | z-index: 10; 22 | > i { 23 | margin-right: 5px; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/styles/core-ui/Offline.styl: -------------------------------------------------------------------------------- 1 | .offline-container { 2 | $container-width = 60px; 3 | $toggle-handle-width = 10px; 4 | $button-text-width = $container-width - $toggle-handle-width - 2px; 5 | 6 | padding: 0; 7 | width: $container-width; 8 | height: 16px; 9 | line-height: 1.2; 10 | position: relative; 11 | overflow: hidden; 12 | noSelect() 13 | 14 | .toggle-group { 15 | position: absolute; 16 | display: flex; 17 | align-content: center; 18 | transition: all 0.35s ease; 19 | top: 0; 20 | bottom: 0; 21 | 22 | & > span { 23 | width: $button-text-width; 24 | display: inline-block; 25 | text-align: center; 26 | height: 100% 27 | } 28 | } 29 | 30 | &.on .toggle-group { 31 | left: 0px; 32 | } 33 | 34 | &.off .toggle-group { 35 | left: -1 * $button-text-width; 36 | } 37 | 38 | .toggle-handle { 39 | display: inline-block; 40 | width: $toggle-handle-width; 41 | background-color: #ffffff; 42 | height: 100%; 43 | border-radius: 2px; 44 | } 45 | } 46 | 47 | .blink { 48 | animation: blink 800ms infinite; 49 | } 50 | 51 | @keyframes blink { 52 | 0% { 53 | opacity: 1; 54 | } 55 | 50% { 56 | opacity: 0.2; 57 | } 58 | 100% { 59 | opacity: 1; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/styles/core-ui/StatusBar.styl: -------------------------------------------------------------------------------- 1 | $bar-height = 30px; 2 | 3 | .status-bar { 4 | // border-top: 1px solid transparent; 5 | display: flex; 6 | align-items: stretch; 7 | min-height: $bar-height; 8 | noSelect(); 9 | 10 | .status-widget-container { 11 | display: flex; 12 | &.right { 13 | margin-left: auto; 14 | } 15 | } 16 | 17 | .status-bar-menu-item { 18 | display: flex; 19 | align-items: center; 20 | padding: 0 8px; 21 | cursor: pointer; 22 | position: relative; 23 | > .menu { 24 | right: 6px; 25 | bottom: $bar-height; 26 | min-width: 200px; 27 | } 28 | } 29 | 30 | .toggle-layout { 31 | padding: 0 8px; 32 | } 33 | 34 | .status-messages { 35 | display: flex; 36 | align-items: center; 37 | .status-message { 38 | margin-right: 0.5em; 39 | } 40 | .status-message + .status-message { 41 | &:before { 42 | content: '|'; 43 | margin-right: 0.5em; 44 | } 45 | } 46 | } 47 | .status-bar-upload { 48 | padding: 0 8px; 49 | // line-height: $bar-height; 50 | display: flex; 51 | position: relative; 52 | .fa { 53 | line-height: $bar-height; 54 | margin-right: 4px; 55 | } 56 | .upload-messages { 57 | display: flex; 58 | align-items: center; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/styles/core-ui/Welcome.styl: -------------------------------------------------------------------------------- 1 | .welcome-page { 2 | color: $text-color; 3 | padding: 10px 40px; 4 | height: 100%; 5 | width: 100%; 6 | overflow: scroll; 7 | h1 { 8 | font-weight: normal; 9 | padding: 0; 10 | margin: 0; 11 | } 12 | .subtitle { 13 | font-size: 1.4em; 14 | padding-top: 10px; 15 | color: $text-color-subtle; 16 | padding-bottom: 40px; 17 | } 18 | h2 { 19 | font-weight: normal; 20 | padding: 0; 21 | margin: 0; 22 | font-size: 1.2em; 23 | padding-bottom: 6px; 24 | } 25 | .block { 26 | padding-bottom: 40px; 27 | } 28 | .link-item { 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /app/styles/core-ui/Workspace.styl: -------------------------------------------------------------------------------- 1 | .workspace-list { 2 | fixed(0); 3 | z-index: z(workspace-list); 4 | 5 | &:after { 6 | absolute(0); 7 | z-index: -1; 8 | content: ""; 9 | background-color: white; 10 | } 11 | 12 | overflow: auto; 13 | } 14 | 15 | .workspace { 16 | display: flex; 17 | align-items: center; 18 | height: 80px; 19 | border-bottom: 1px solid gray(85%); 20 | 21 | .workspace-name { 22 | font-size: 20px; 23 | } 24 | 25 | .workspace-action { 26 | margin-left: auto; 27 | } 28 | } 29 | 30 | 31 | .create-workspace-container, 32 | .workspace-list-container { 33 | width: 40vw; 34 | min-width: 600px; 35 | margin: auto; 36 | } 37 | 38 | .create-workspace-container { 39 | margin-bottom: 30px; 40 | .pre { 41 | font-family: monospace; 42 | font-size: 1em; 43 | overflow-wrap: break-word; 44 | word-wrap: break-word; 45 | } 46 | } 47 | 48 | .create-workspace-controls { 49 | display: flex; 50 | align-items: center; 51 | input {flex-grow: 1;} 52 | button {margin-left: 4px;} 53 | } 54 | 55 | @keyframes CREATING-WORKSPACE { 56 | 0% { content:'.'; } 57 | 33% { content:'..'; } 58 | 66% { content:'...'; } 59 | } 60 | 61 | .creating-workspace-indicator { 62 | opacity: 0.7; 63 | &:after { 64 | content: '.'; 65 | animation: CREATING-WORKSPACE 3s infinite; 66 | } 67 | } 68 | .creating-workspace-indicator-error { 69 | opacity: 0.7; 70 | color: red; 71 | } 72 | .creating-workspace-process { 73 | opacity: 0.7; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/styles/core-ui/index.styl: -------------------------------------------------------------------------------- 1 | @import "../ui-variables"; 2 | @import "./Menu"; 3 | @import "./MenuBar"; 4 | @import "./Breadcrumbs"; 5 | @import "./PanelAndPane"; 6 | @import "./Tab"; 7 | @import "./FileTree"; 8 | @import "./Modal"; 9 | @import "./Term"; 10 | @import "./Git"; 11 | @import "./GitMerge"; 12 | @import "./StatusBar"; 13 | @import "./Workspace"; 14 | @import "./CommandPalette"; 15 | @import "./DragAndDrop"; 16 | @import "./Settings"; 17 | @import "./Bar"; 18 | @import "./Markdown"; 19 | @import "./History"; 20 | @import "./Accordion"; 21 | @import "./Initialize"; 22 | @import "./Editor"; 23 | @import "./Offline"; 24 | @import "./FileList"; 25 | @import "./Tooltip"; 26 | @import "./Welcome"; 27 | @import "./Mask"; -------------------------------------------------------------------------------- /app/styles/dark/index.styl: -------------------------------------------------------------------------------- 1 | @import './styles/ui-variables'; 2 | @import './styles/tabs'; 3 | @import './styles/panes'; 4 | @import './styles/filetree'; 5 | @import './styles/menu'; 6 | @import './styles/bars'; 7 | @import './styles/git'; 8 | @import './styles/editor'; 9 | @import './styles/modal'; 10 | @import './styles/form'; 11 | @import './styles/history'; 12 | @import './styles/accordion'; 13 | @import './styles/markdown'; 14 | @import './styles/filelist'; 15 | @import './styles/base'; 16 | @import './styles/term'; 17 | @import './styles/mask'; 18 | @import './styles/git-merge'; 19 | @import './styles/diff'; 20 | 21 | // 插件用样式 22 | @import './styles/weapp'; 23 | @import './styles/collaborators'; 24 | @import './styles/env'; 25 | @import './styles/access'; 26 | @import './styles/java'; 27 | 28 | @import './styles/welcome'; -------------------------------------------------------------------------------- /app/styles/dark/styles/access.styl: -------------------------------------------------------------------------------- 1 | .panel { 2 | .access-url-container { 3 | color: $text-color; 4 | .access-url-panel { 5 | .panel-heading { 6 | background-color: $tab-bar-background-color; 7 | border-color: $tab-bar-border-color; 8 | 9 | input { 10 | background: $tab-bar-background-color; 11 | border: 1px solid $base-border-color; 12 | padding: 2px 10px; 13 | color: $text-color; 14 | } 15 | } 16 | .port-item { 17 | border-color: $tab-bar-border-color; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/accordion.styl: -------------------------------------------------------------------------------- 1 | .accordion { 2 | .accordion-topbar { 3 | background: $second-background-color; 4 | border-color: $base-border-color; 5 | color: $text-color; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/styles/dark/styles/bars.styl: -------------------------------------------------------------------------------- 1 | .menu-bar { 2 | background-color: $base-top-menu-bar-background; 3 | border-color: $base-border-color; 4 | color: $text-color; 5 | } 6 | 7 | .status-bar { 8 | border-color: $base-border-color; 9 | background-color: $base-top-menu-bar-background; 10 | color: $text-color; 11 | 12 | .git-branch-widget .widget-header { 13 | background-color: $base-background-color; 14 | color: $text-color; 15 | } 16 | } 17 | 18 | .breadcrumbs { 19 | background-color: $menu-background-color; 20 | border-color: $base-border-color; 21 | color: $text-color; 22 | .crumb { 23 | background-color: $menu-background-color; 24 | &:before { 25 | border-left-color: $base-border-color; 26 | } 27 | &:after { 28 | border-left-color: $menu-background-color; 29 | } 30 | } 31 | } 32 | 33 | .side-bar { 34 | color: $text-color; 35 | .side-bar-label:hover { 36 | background-color: $tab-background-color-hover; 37 | } 38 | .side-bar-label.active { 39 | background-color: $tab-background-color-active; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/styles/dark/styles/base.styl: -------------------------------------------------------------------------------- 1 | body { 2 | scrollbar-face-color: $tab-bar-background-color ; 3 | scrollbar-track-color: $base-border-color; 4 | scrollbar-highlight-color: $tab-bar-background-color; 5 | scrollbar-shadow-color: $tab-bar-background-color ; 6 | scrollbar-3dlight-color: $tab-bar-background-color; 7 | scrollbar-arrow-color: $text-color; 8 | scrollbar-darkshadow-color: $tab-bar-background-color; 9 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/collaborators.styl: -------------------------------------------------------------------------------- 1 | .panel { 2 | .loading { 3 | color: $text-color; 4 | } 5 | .collaborators { 6 | color: $text-color; 7 | } 8 | .collaboration-chat { 9 | color: $text-color; 10 | .chat-editor { 11 | border-color: $tab-bar-border-color; 12 | } 13 | .chat-item .chat-item-body { 14 | color: $text-color; 15 | } 16 | .chat-item .chat-item-header .chat-item-time { 17 | color: $text-color-subtle; 18 | } 19 | .emoji-mart { 20 | background: $base-background-color; 21 | border-color: $tab-bar-border-color; 22 | .emoji-mart-category-label span { 23 | background: $base-background-color; 24 | color: $text-color; 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/diff.styl: -------------------------------------------------------------------------------- 1 | .CodeMirror-merge-l-chunk-end { 2 | border-bottom: 1px solid #88e; 3 | } 4 | .CodeMirror-merge-l-chunk-start { 5 | border-top: 1px solid #88e; 6 | } 7 | .CodeMirror-merge-l-chunk { 8 | background: $app-background-color; 9 | } 10 | .CodeMirror-merge-l-connect { 11 | fill: $app-background-color; 12 | stroke: #88e; 13 | stroke-width: 1px; 14 | } 15 | .CodeMirror-merge-gap { 16 | background-color: $app-background-color; 17 | border-color: $base-border-color; 18 | } 19 | .CodeMirror-merge { 20 | border-color: $base-border-color; 21 | } 22 | 23 | .git-merge { 24 | .diffModal { 25 | .loading { 26 | background-color: $app-background-color; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/editor.styl: -------------------------------------------------------------------------------- 1 | .cm-s-monokai, .cm-s-material { 2 | &.CodeMirror { 3 | background: $base-background-color; 4 | } 5 | 6 | .CodeMirror-gutters { 7 | background: $base-background-color; 8 | } 9 | } 10 | 11 | .image-editor-loading { 12 | .fa-spinner { 13 | color: $text-color; 14 | } 15 | } 16 | 17 | .status-bar-editor-widgets { 18 | display: flex; 19 | align-items: stretch; 20 | .editor-widget { 21 | display: flex; 22 | align-items: center; 23 | padding: 0 8px; 24 | &.enabled { 25 | background-color: #444; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/styles/dark/styles/env.styl: -------------------------------------------------------------------------------- 1 | .panel { 2 | .env-list { 3 | .env-list-container .env-list-panel .panel-heading { 4 | color: $text-color; 5 | background-color: $tab-bar-background-color; 6 | border-color: $tab-bar-border-color; 7 | } 8 | .env-list-container .env-list-panel .env-item { 9 | border-color: $tab-bar-border-color; 10 | color: $text-color; 11 | &.current { 12 | border-left-color: #578ddf; 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/filelist.styl: -------------------------------------------------------------------------------- 1 | .file-list-container { 2 | color: $text-color; 3 | background-color: #252526; 4 | .file-list-item { 5 | color: $text-color; 6 | &.focus { 7 | color: $text-color-highlight; 8 | background-color: $tree-view-background-color-highlight; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/filetree.styl: -------------------------------------------------------------------------------- 1 | .filetree-node.focus { 2 | color: $text-color-highlight; 3 | > .filetree-node-bg { 4 | background-color: $tree-view-background-color-highlight; 5 | } 6 | } 7 | 8 | .filetree-container { 9 | color: $text-color; 10 | background-color: #252526; 11 | .filetree-node-container.highlight, 12 | .filetree-node.focus { 13 | background-color: $tree-view-background-color-highlight; 14 | } 15 | } 16 | .filetree-node-icon, .breadcrumbs .crumb, .menu-item-container { 17 | .fa-folder, .fa-folder-open { 18 | color: $tree-view-folder-color; 19 | } 20 | } 21 | 22 | .git-filetree-container { 23 | .filetree-node { 24 | border-color: $base-border-color; 25 | } 26 | } 27 | 28 | .git-commit-message-container > textarea { 29 | background-color: $base-background-color; 30 | border-color: $base-border-color; 31 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/form.styl: -------------------------------------------------------------------------------- 1 | .ide-container { 2 | .form-control { 3 | color: $text-color; 4 | background-color: $base-background-color; 5 | border-color: $base-border-color; 6 | } 7 | 8 | .form-control[disabled] { 9 | background-color: $base-background-color; 10 | border-color: $base-border-color; 11 | } 12 | 13 | .btn-default { 14 | background-color: $base-background-color; 15 | border-color: $base-border-color; 16 | color: $text-color; 17 | 18 | &:hover, &:active, &:active:hover { 19 | background-color: #000; 20 | border-color: $base-border-color; 21 | color: $text-color; 22 | } 23 | } 24 | 25 | hr { 26 | border-color: $base-border-color; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/styles/dark/styles/git-merge.styl: -------------------------------------------------------------------------------- 1 | .ide-container { 2 | .public_fixedDataTable_main { 3 | border-color: $base-border-color; 4 | } 5 | .public_fixedDataTableRow_main { 6 | background-color: $base-background-color; 7 | border-color: $base-border-color; 8 | color: $text-color; 9 | } 10 | .public_fixedDataTable_header, .public_fixedDataTable_header .public_fixedDataTableCell_main { 11 | background-color: $base-background-color; 12 | border-color: $base-border-color; 13 | color: $text-color; 14 | } 15 | .public_Scrollbar_main.public_Scrollbar_mainActive, .public_Scrollbar_main:hover { 16 | background-color: $base-background-color; 17 | } 18 | 19 | .public_Scrollbar_main:hover .public_Scrollbar_face:after, .public_Scrollbar_mainActive .public_Scrollbar_face:after, .public_Scrollbar_faceActive:after { 20 | background-color: $base-top-menu-bar-background; 21 | } 22 | 23 | .public_Scrollbar_face:after { 24 | background-color: $base-top-menu-bar-background; 25 | } 26 | 27 | .public_fixedDataTableRow_highlighted, .public_fixedDataTableRow_highlighted .public_fixedDataTableCell_main { 28 | background-color: $base-top-menu-bar-background; 29 | border-color: $base-border-color; 30 | color: $text-color; 31 | } 32 | 33 | .public_fixedDataTableCell_main { 34 | background-color: $second-background-color; 35 | border-color: $base-border-color; 36 | color: $text-color; 37 | } 38 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/git.styl: -------------------------------------------------------------------------------- 1 | // .status { color: @text-color; } 2 | // .status-added { color: @text-color-success; } // green 3 | // .status-ignored { color: @text-color-subtle; } // faded 4 | // .status-modified { color: @text-color-warning; } // orange 5 | // .status-removed { color: @text-color-error; } // red 6 | // .status-renamed { color: @text-color-info; } // blue 7 | 8 | .git-untracked { 9 | color: $untracked-color; 10 | } 11 | .git-confliction { 12 | color: $confliction-color; 13 | } 14 | .git-modified { 15 | color: $modified-color; 16 | } 17 | .git-modify { 18 | color: $modified-color; 19 | } 20 | .git-changed { 21 | color: $modified-color; 22 | } 23 | .git-rename { 24 | color: $modified-color; 25 | } 26 | .git-copy { 27 | color: $modified-color; 28 | } 29 | 30 | .git-added { 31 | color: $added-color; 32 | } 33 | .git-add { 34 | color: $added-color; 35 | } 36 | .git-missing { 37 | text-decoration: line-through; 38 | } 39 | 40 | .git-removed { 41 | text-decoration: line-through; 42 | } 43 | .git-delete { 44 | text-decoration: line-through; 45 | } 46 | 47 | .file-status-indicator { 48 | &.modified, &.changed, &.modify {color: hsl(47,100%,50%)} 49 | &.untracked, &.add {color: hsl(80,60%,40%)} 50 | &.missing {color: hsl(10,80%,45%)} 51 | &.confliction {color: hsl(10,80%,45%)} 52 | } 53 | -------------------------------------------------------------------------------- /app/styles/dark/styles/history.styl: -------------------------------------------------------------------------------- 1 | .ide-history .history-container { 2 | .history-title { 3 | background-color: $base-top-menu-bar-background; 4 | border-color: $base-border-color; 5 | color: $text-color; 6 | } 7 | .public_fixedDataTableRow_main { 8 | background-color: $base-background-color; 9 | border-color: $base-border-color; 10 | color: $text-color; 11 | } 12 | .public_fixedDataTable_header, .public_fixedDataTable_header .public_fixedDataTableCell_main { 13 | background-color: $base-background-color; 14 | border-color: $base-border-color; 15 | color: $text-color; 16 | } 17 | .public_Scrollbar_main.public_Scrollbar_mainActive, .public_Scrollbar_main:hover { 18 | background-color: $base-background-color; 19 | } 20 | 21 | .public_Scrollbar_main:hover .public_Scrollbar_face:after, .public_Scrollbar_mainActive .public_Scrollbar_face:after, .public_Scrollbar_faceActive:after { 22 | background-color: $base-top-menu-bar-background; 23 | } 24 | 25 | .public_Scrollbar_face:after { 26 | background-color: $base-top-menu-bar-background; 27 | } 28 | 29 | .public_fixedDataTableRow_highlighted, .public_fixedDataTableRow_highlighted .public_fixedDataTableCell_main { 30 | background-color: $base-top-menu-bar-background; 31 | border-color: $base-border-color; 32 | color: $text-color; 33 | } 34 | 35 | .public_fixedDataTableCell_main { 36 | background-color: $second-background-color; 37 | border-color: $base-border-color; 38 | color: $text-color; 39 | } 40 | 41 | } 42 | 43 | .git-logs-view { 44 | .flex-row { 45 | .message-text { 46 | color: $text-color; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/styles/dark/styles/java.styl: -------------------------------------------------------------------------------- 1 | .modals-container .project-config-container .form-line .fa-folder-o { 2 | border-color: $tab-bar-border-color; 3 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/mask.styl: -------------------------------------------------------------------------------- 1 | .mask-container { 2 | background: rgba(0, 0, 0, 0.4); 3 | .progress { 4 | background-color: $base-background-color; 5 | color: $text-color; 6 | border: 1px solid $base-border-color; 7 | } 8 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/menu.styl: -------------------------------------------------------------------------------- 1 | .menu { 2 | color: $text-color; 3 | background-color: $menu-background-color; 4 | border-color: $menu-border-color; 5 | opacity: 0.95; 6 | > li > hr { 7 | border-color: $base-border-color; 8 | } 9 | } 10 | 11 | .menu-bar-container { 12 | background-color: $base-top-menu-bar-background; 13 | } 14 | 15 | .menu-bar-item-container, .menu-item-container { 16 | &.active { 17 | background-color: $menu-background-color-highlight; 18 | color: #fff; 19 | } 20 | &.disabled, &.active.disabled { 21 | background-color: transparent; 22 | color: $text-color-subtle; 23 | } 24 | } 25 | 26 | .menu-bar-item-logo { 27 | filter: invert(100%) 28 | } 29 | -------------------------------------------------------------------------------- /app/styles/dark/styles/modal.styl: -------------------------------------------------------------------------------- 1 | .modal-container { 2 | .modal { 3 | background: $base-top-menu-bar-background; 4 | color: $text-color; 5 | .settings-header, .settings-header .tab-bar-header { 6 | border-color: $menu-border-color; 7 | } 8 | 9 | .settings-view { 10 | .modal-ops { 11 | border-color: $menu-border-color; 12 | } 13 | } 14 | } 15 | } 16 | 17 | .git-unstash-container .stash-list { 18 | border-color: $menu-border-color; 19 | background: $base-background-color; 20 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/panes.styl: -------------------------------------------------------------------------------- 1 | .panel-container, .pane-container { 2 | background-color: $base-background-color; 3 | border: 1px solid $base-border-color; 4 | .panel { 5 | background-color: $base-top-menu-bar-background; 6 | } 7 | } 8 | .resize-bar { 9 | &.col-resize { 10 | border-color: $base-border-color; 11 | } 12 | &.row-resize { 13 | border-color: $base-border-color; 14 | } 15 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/term.styl: -------------------------------------------------------------------------------- 1 | .ide-terminal { 2 | background: #000000; 3 | } 4 | 5 | .terminal-panel { 6 | .terminal-toolbar { 7 | color: #FFF; 8 | } 9 | } -------------------------------------------------------------------------------- /app/styles/dark/styles/welcome.styl: -------------------------------------------------------------------------------- 1 | .welcome-page { 2 | color: $text-color; 3 | .subtitle { 4 | color: $text-color-subtle; 5 | } 6 | } -------------------------------------------------------------------------------- /app/styles/lib.styl: -------------------------------------------------------------------------------- 1 | @import 'ui-variables'; 2 | @import "mixins"; 3 | @import 'scaffolding'; 4 | 5 | @import "core-ui" 6 | 7 | .hidden {display: none !important;} 8 | 9 | .workstation { 10 | @import 'bootstrap/normalize'; 11 | @import 'normalize'; 12 | width: 100%; 13 | height: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: stretch; 17 | } 18 | 19 | .utilities-container { 20 | isolation: isolate; 21 | z-index: z(utilities-container); 22 | } 23 | -------------------------------------------------------------------------------- /app/styles/main.styl: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/variables'; 2 | @import 'bootstrap/mixins'; 3 | // @import 'bootstrap/scaffolding'; 4 | 5 | @import 'bootstrap/grid'; 6 | @import 'bootstrap/buttons'; 7 | @import 'bootstrap/forms'; 8 | @import 'bootstrap/alerts'; 9 | 10 | @import 'ui-variables'; 11 | @import "mixins"; 12 | @import 'scaffolding'; 13 | 14 | @import "core-ui" 15 | 16 | @import "../../node_modules/fixed-data-table-2/dist/fixed-data-table.min.css" 17 | 18 | @import "../../node_modules/codemirror/lib/codemirror.css" 19 | @import "../../node_modules/codemirror/theme/monokai.css" 20 | @import "../../node_modules/codemirror/theme/material.css" 21 | @import "../../node_modules/codemirror/theme/neo.css" 22 | @import "../../node_modules/codemirror/theme/eclipse.css" 23 | @import "../../node_modules/file-icons-js/css/style.css" 24 | @import 'bootstrap/normalize'; 25 | @import 'normalize'; 26 | 27 | @import "../../node_modules/octicons/build/font/octicons.min.css" 28 | 29 | .hidden {display: none !important;} 30 | 31 | .ide-container { 32 | width: 100%; 33 | height: 100%; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: stretch; 37 | } 38 | 39 | .utilities-container { 40 | isolation: isolate; 41 | z-index: z(utilities-container); 42 | } 43 | -------------------------------------------------------------------------------- /app/styles/mixins/color.styl: -------------------------------------------------------------------------------- 1 | hsb($h-hsb, $s-hsb, $b-hsb, $a = 1) 2 | if $b-hsb == 0 3 | return rgba(hsla(0, 0, 0, $a)) 4 | else 5 | $l-hsl = ($b-hsb / 2) * (2 - ($s-hsb / 100)) 6 | $s-hsl = ($b-hsb * $s-hsb) / ($l-hsl < 50 ? $l-hsl * 2 : 200 - $l-hsl * 2) 7 | return rgba(hsla($h-hsb, $s-hsl, $l-hsl, $a)) 8 | 9 | gray($amount) { 10 | lighten(#000000, $amount); 11 | } -------------------------------------------------------------------------------- /app/styles/mixins/index.styl: -------------------------------------------------------------------------------- 1 | @import "z-index"; 2 | @import "position"; 3 | @import "color"; 4 | @import "padding-margin"; 5 | @import "misc"; 6 | @import "scrollbar"; 7 | -------------------------------------------------------------------------------- /app/styles/mixins/misc.styl: -------------------------------------------------------------------------------- 1 | noSelect() { 2 | -webkit-user-select: none; 3 | -moz-user-select: none; 4 | -ms-user-select: none; 5 | -o-user-select: none; 6 | user-select: none; 7 | cursor: default; 8 | } 9 | 10 | codingMonkeyBackground() { 11 | background-image: url("https://dn-coding-net-production-static.qbox.me/static/cb75498a0ffd36e2c694da62f1bdb86c.svg"); 12 | background-size: contain; 13 | background-repeat: no-repeat; 14 | background-position: center center; 15 | } 16 | 17 | // https://css-tricks.com/snippets/css/clear-fix/ 18 | clearfix() { 19 | &:after { 20 | content: ""; 21 | display: table; 22 | clear: both; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/styles/mixins/padding-margin.styl: -------------------------------------------------------------------------------- 1 | margin-lr($margin) { 2 | margin-left: $margin; 3 | margin-right: $margin; 4 | } 5 | 6 | margin-tb($margin) { 7 | margin-top: $margin; 8 | margin-bottom: $margin; 9 | } 10 | 11 | padding-lr($padding) { 12 | padding-left: $padding; 13 | padding-right: $padding; 14 | } 15 | 16 | padding-tb($padding) { 17 | padding-top: $padding; 18 | padding-bottom: $padding; 19 | } 20 | -------------------------------------------------------------------------------- /app/styles/mixins/scrollbar.styl: -------------------------------------------------------------------------------- 1 | scrollBar() { 2 | &::-webkit-scrollbar { 3 | width: 8px; 4 | height: 8px; 5 | -webkit-border-radius: 8px; 6 | display: block; 7 | } 8 | &::-webkit-scrollbar-button { 9 | width: 0; 10 | height: 0; 11 | display: none; 12 | } 13 | 14 | &::-webkit-scrollbar-corner { 15 | background-color: transparent; 16 | } 17 | &::-webkit-scrollbar-thumb { 18 | background-color: rgba(91, 91, 91, 0.5); 19 | } 20 | &::-webkit-scrollbar-thumb:hover { 21 | background-color: rgba(170, 170, 170, 0.8); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/styles/mixins/z-index.styl: -------------------------------------------------------------------------------- 1 | // Mind the "Stacking Contexts" problem: https://philipwalton.com/articles/what-no-one-told-you-about-z-index/ 2 | // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index 3 | // https://developer.mozilla.org/en-US/docs/Web/CSS/isolation 4 | z($e) { 5 | $z-index = null; 6 | for $sublist, $i in $z-index-list { 7 | if index($sublist, $e) != null { 8 | $z-index = index($sublist, $e)+$i*100; 9 | } 10 | } 11 | 12 | if ($z-index!=null) { 13 | return $z-index; 14 | } else { 15 | error('Missing element "'+$e+'" in $z-index-list'); 16 | } 17 | } 18 | 19 | $z-index-list = \ 20 | (placeholder), 21 | (main-pane-view modal-container), 22 | (pane-layout-overlay pane-resize-bar), 23 | (filetree-node-bg filetree-node-label), 24 | (modal-backdrop modal), 25 | (context-menu menu menu-bar-item-container), 26 | (utilities-container), 27 | (workspace-list); 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/styles/normalize.styl: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | *:before, 5 | *:after { 6 | box-sizing: border-box; 7 | } 8 | 9 | input, 10 | button, 11 | select, 12 | textarea { 13 | font-family: inherit; 14 | font-size: inherit; 15 | line-height: inherit; 16 | } 17 | 18 | ul, ol { 19 | list-style-type: none; 20 | list-style-image: none; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | 26 | hr { 27 | border: 0; 28 | border-top 1px solid grey; 29 | } 30 | -------------------------------------------------------------------------------- /app/styles/workstation.styl: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/variables'; 2 | @import 'bootstrap/mixins'; 3 | // @import 'bootstrap/scaffolding'; 4 | 5 | @import 'bootstrap/grid'; 6 | @import 'bootstrap/buttons'; 7 | @import 'bootstrap/forms'; 8 | @import 'bootstrap/alerts'; 9 | 10 | @import 'ui-variables'; 11 | @import "mixins"; 12 | @import 'scaffolding'; 13 | 14 | @import "core-ui" 15 | 16 | @import "../../node_modules/fixed-data-table-2/dist/fixed-data-table.min.css" 17 | 18 | @import "../../node_modules/codemirror/lib/codemirror.css" 19 | @import "../../node_modules/codemirror/theme/monokai.css" 20 | @import "../../node_modules/codemirror/theme/material.css" 21 | @import "../../node_modules/codemirror/theme/neo.css" 22 | @import "../../node_modules/codemirror/theme/eclipse.css" 23 | @import "../../node_modules/file-icons-js/css/style.css" 24 | 25 | 26 | 27 | .hidden {display: none !important;} 28 | 29 | .ide-container { 30 | @import 'bootstrap/normalize'; 31 | @import 'normalize'; 32 | width: 100%; 33 | height: 100%; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: stretch; 37 | } 38 | 39 | .utilities-container { 40 | isolation: isolate; 41 | z-index: z(utilities-container); 42 | } 43 | -------------------------------------------------------------------------------- /app/utils/RandColors.js: -------------------------------------------------------------------------------- 1 | const COLORS = [ 2 | // the git tower palettes [mod] 3 | '#58c6f3', // blue 4 | '#6de6e9', // aqua 5 | '#ace09e', // grean 6 | '#59b38a', // olive 7 | '#f7d56b', // yellow 8 | '#f5a95f', // orange 9 | '#fe7874', // red 10 | '#967acc', // purple 11 | '#ea75f3', // violet 12 | '#d3d7ed', // light purplish gray 13 | ] 14 | 15 | export default class RandColors { 16 | constructor () { 17 | this.colors = COLORS.slice() 18 | this._index = 0 19 | this._period = this.colors.length 20 | } 21 | 22 | get () { 23 | return this.colors[this._index++ % this._period] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/utils/actions/createAction.js: -------------------------------------------------------------------------------- 1 | import dispatch from './dispatch' 2 | 3 | const actionCreatorFactory = genActionData => function createAction (eventName, actionPayloadCreator = (x => x)) { 4 | function action (...args) { 5 | const payload = actionPayloadCreator(...args) 6 | const actionData = genActionData(eventName, payload) 7 | dispatch(actionData) 8 | return actionData 9 | } 10 | action.toString = () => eventName 11 | return action 12 | } 13 | 14 | const createAction = actionCreatorFactory( 15 | (eventName, payload) => ({ type: eventName, payload }) 16 | ) 17 | 18 | createAction.promise = actionCreatorFactory((eventName, payload) => { 19 | let resolve, reject 20 | const promise = new Promise((rs, rj) => { resolve = rs; reject = rj }) 21 | const meta = { promise, resolve, reject } 22 | 23 | promise.type = eventName 24 | promise.payload = payload 25 | promise.meta = meta 26 | promise.resolve = resolve 27 | promise.reject = reject 28 | 29 | return promise 30 | }) 31 | 32 | export default createAction 33 | -------------------------------------------------------------------------------- /app/utils/actions/dispatch.js: -------------------------------------------------------------------------------- 1 | import emitter from '../emitter' 2 | 3 | function isValidActionObj (actionObj) { 4 | if (!actionObj) return false 5 | if (typeof actionObj !== 'object') return false 6 | if (typeof actionObj.type !== 'string' || !actionObj.type) return false 7 | return true 8 | } 9 | 10 | function dispatch (actionObj) { 11 | if (isValidActionObj(actionObj)) { 12 | if (!actionObj.isDispatched) { 13 | emitter.emit(actionObj.type, actionObj) 14 | actionObj.isDispatched = true 15 | } 16 | } else if (__DEV__) { 17 | console.warn('Invalid action object is dispatched', actionObj) 18 | } 19 | return actionObj 20 | } 21 | 22 | export default dispatch 23 | -------------------------------------------------------------------------------- /app/utils/actions/emitterMiddleware.js: -------------------------------------------------------------------------------- 1 | import emitterDispatch from './dispatch' 2 | export default function emitterMiddleware ({ dispatch, getState }) { 3 | return next => (action) => { 4 | emitterDispatch(action) 5 | return next(action) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/utils/actions/handleAction.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { action as mobxAction } from 'mobx' 3 | import emitter from '../emitter' 4 | 5 | export default function handleAction (eventName, handler, state) { 6 | handler = mobxAction(eventName, handler) 7 | emitter.on(eventName, (actionMsg) => { 8 | // auto resolve/reject promisified actionMsg 9 | let result 10 | const resolve = actionMsg.resolve || (actionMsg.meta && actionMsg.meta.resolve) 11 | const reject = actionMsg.reject || (actionMsg.meta && actionMsg.meta.reject) 12 | // try { 13 | result = _.isUndefined(state) ? handler(actionMsg.payload, actionMsg) 14 | : handler(state, actionMsg.payload, actionMsg) 15 | if (_.isFunction(resolve)) resolve(result) 16 | // } catch (err) { 17 | // if (_.isFunction(reject)) reject(err) 18 | // else throw err 19 | // } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /app/utils/actions/handleActions.js: -------------------------------------------------------------------------------- 1 | import handleAction from './handleAction' 2 | 3 | export default function handleActions (handlers, state) { 4 | const eventNames = Object.keys(handlers) 5 | eventNames.forEach((eventName) => { 6 | handleAction(eventName, handlers[eventName], state) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /app/utils/actions/index.js: -------------------------------------------------------------------------------- 1 | import dispatch from './dispatch' 2 | import handleAction from './handleAction' 3 | import handleActions from './handleActions' 4 | import createAction from './createAction' 5 | import registerAction from './registerAction' 6 | import emitterMiddleware from './emitterMiddleware' 7 | 8 | export { 9 | dispatch, 10 | handleAction, 11 | handleActions, 12 | createAction, 13 | registerAction, 14 | emitterMiddleware 15 | } 16 | -------------------------------------------------------------------------------- /app/utils/actions/registerAction.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import createAction from './createAction' 3 | import handleAction from './handleAction' 4 | 5 | function _registerAction (_createAction, ...args) { 6 | let [eventName, actionPayloadCreator, handler] = args 7 | if (!_.isString(eventName) || !_.isFunction(actionPayloadCreator)) throw Error('registerAction syntax error') 8 | 9 | if (!_.isFunction(handler)) { 10 | handler = actionPayloadCreator 11 | actionPayloadCreator = payload => payload 12 | } 13 | 14 | handleAction(eventName, handler) 15 | 16 | // by default we promisify the actionMsg 17 | const actionCreator = _createAction(eventName, actionPayloadCreator) 18 | return actionCreator 19 | } 20 | 21 | function registerActionPromise (...args) { 22 | return _registerAction(createAction.promise, ...args) 23 | } 24 | 25 | function registerActionNormal (...args) { 26 | return _registerAction(createAction, ...args) 27 | } 28 | 29 | const registerAction = registerActionPromise 30 | registerAction.promise = registerActionPromise 31 | registerAction.normal = registerActionNormal 32 | 33 | export default registerAction 34 | -------------------------------------------------------------------------------- /app/utils/assignProps.js: -------------------------------------------------------------------------------- 1 | import is from './is' 2 | 3 | function assignProps (target, props, opts) { 4 | Object.keys(opts).forEach((key) => { 5 | const type = opts[key] 6 | const isType = typeof type === 'string' ? is[type] : is(type) 7 | if (type === 'any' || is.function(isType) && isType(props[key])) { 8 | target[key] = props[key] 9 | } 10 | }) 11 | return target 12 | } 13 | 14 | export default assignProps 15 | -------------------------------------------------------------------------------- /app/utils/codingPackageJsonp.js: -------------------------------------------------------------------------------- 1 | function codingPackageJsonp (exports) { 2 | codingPackageJsonp.data = exports 3 | } 4 | 5 | Object.defineProperty(codingPackageJsonp, 'data', { 6 | set (value) { 7 | if (this.current && this.current.startsWith('group')) { 8 | // const groupName = this.current.split('_')[1] 9 | const values = Array.isArray(value) ? value : [value] 10 | this.cache = (this.cache || []).concat(values) 11 | } else { 12 | this.cache = value 13 | } 14 | }, 15 | get () { return this.cache } 16 | }) 17 | 18 | codingPackageJsonp.groups = {} 19 | codingPackageJsonp.current = '' 20 | 21 | window.codingPackageJsonp = codingPackageJsonp 22 | -------------------------------------------------------------------------------- /app/utils/colors/hueFromString.js: -------------------------------------------------------------------------------- 1 | export default function hueFromString (name) { 2 | let a = 1 3 | for (let i = 0; i < name.length; i++) { 4 | a = 17 * (a + name.charCodeAt(i)) % 360 5 | } 6 | return Math.round(a) 7 | } 8 | -------------------------------------------------------------------------------- /app/utils/colors/index.js: -------------------------------------------------------------------------------- 1 | import chroma from './chroma' 2 | import hueFromString from './hueFromString' 3 | 4 | export { chroma, hueFromString } 5 | -------------------------------------------------------------------------------- /app/utils/composeReducers.js: -------------------------------------------------------------------------------- 1 | export default function composeReducers () { 2 | const reducers = [...arguments] 3 | if (reducers.length === 0) return arg => arg 4 | if (reducers.length === 1) return reducers[0] 5 | 6 | const last = reducers[reducers.length - 1] 7 | const rest = reducers.slice(0, -1) 8 | return function composedReducers (state, action) { 9 | return reducers.reduceRight((lastState, reducer) => reducer(lastState, action), state) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/utils/decorators/defaultProps.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function getDisplayName (WrappedComponent) { 4 | return WrappedComponent.displayName || WrappedComponent.name || 'Component' 5 | } 6 | 7 | export default function defaultProps (propsMapper) { 8 | return function decorator (WrappedComponent) { 9 | const displayName = getDisplayName(WrappedComponent) 10 | 11 | function WrapperComponent (props) { 12 | const mergedProps = { ...propsMapper(props), ...props } 13 | return React.createElement(WrappedComponent, mergedProps) 14 | } 15 | 16 | WrapperComponent.displayName = displayName 17 | WrapperComponent.WrappedComponent = WrappedComponent 18 | return WrapperComponent 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /app/utils/decorators/index.js: -------------------------------------------------------------------------------- 1 | import mapEntityFactory from './mapEntity' 2 | import defaultProps from './defaultProps' 3 | import protectedObservable from './protectedObservable' 4 | 5 | export { mapEntityFactory, defaultProps, protectedObservable } 6 | -------------------------------------------------------------------------------- /app/utils/decorators/mapEntity.js: -------------------------------------------------------------------------------- 1 | const get = (obj, prop) => { 2 | if (typeof obj.get === 'function') return obj.get(prop) 3 | return obj[prop] 4 | } 5 | 6 | function mapEntityFactory (entities) { 7 | // this decorator simply allows getting entity by id 8 | return function mapEntity (entityNames) { 9 | entityNames = Array.isArray(entityNames) ? entityNames : [...arguments] 10 | return function decorator (target, key, descriptor) { 11 | const fn = descriptor.value 12 | return { 13 | ...descriptor, 14 | value () { 15 | let args = [...arguments] 16 | args = args.map((entityId, i) => { 17 | const entityName = entityNames[i] 18 | if (entityName && typeof entityId === 'string') { 19 | return get(entities[entityName], entityId) 20 | } 21 | return entityId 22 | }) 23 | return fn.apply(this, args) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | export default mapEntityFactory 31 | -------------------------------------------------------------------------------- /app/utils/decorators/protectedObservable.js: -------------------------------------------------------------------------------- 1 | import { observable, computed } from 'mobx' 2 | 3 | /* 4 | * This decorator enforce a pattern that's widely used in this project, 5 | * 6 | * @protectedObservable _foo = 'bar' 7 | * 8 | * is a short hand for: 9 | * 10 | * @observable _foo = 'bar' 11 | * @computed 12 | * get foo () { return this._foo } 13 | * set foo (value) { return this._foo = value } 14 | * 15 | * you can specify publicKey explicitly by calling: 16 | * @protectedObservable('publicFoo') _foo = 'bar' 17 | */ 18 | function _protectedObservableDecorator (target, privateKey, descriptor, publicKey) { 19 | if (!publicKey) publicKey = privateKey.replace(/^_/, '') 20 | 21 | const computedDescriptor = computed(target, publicKey, { 22 | get () { return this[privateKey] }, 23 | set (v) { return this[privateKey] = v }, 24 | }) 25 | 26 | Object.defineProperty(target, publicKey, computedDescriptor) 27 | 28 | return observable(target, privateKey, descriptor) 29 | } 30 | 31 | function protectedObservable (optionalPublicKey) { 32 | if (typeof optionalPublicKey === 'string') { 33 | return function protectedObservableDecorator (target, key, descriptor) { 34 | return _protectedObservableDecorator(target, key, descriptor, optionalPublicKey) 35 | } 36 | } else { 37 | return _protectedObservableDecorator.apply(null, arguments) 38 | } 39 | } 40 | export default protectedObservable 41 | -------------------------------------------------------------------------------- /app/utils/dynamicStyle.js: -------------------------------------------------------------------------------- 1 | import { observable, autorun } from 'mobx' 2 | import mtln from './multiline' 3 | 4 | class DynamicStyle { 5 | constructor () { 6 | const style = document.createElement('style') 7 | document.body.appendChild(style) 8 | 9 | autorun(() => { 10 | style.innerHTML = this.styles.values().reduce((concatStyleText, styleText) => { 11 | return concatStyleText + '\n' + mtln(styleText) 12 | }, '') 13 | }) 14 | } 15 | 16 | @observable styles = observable.map() 17 | 18 | get (key) { 19 | return this.styles.get(key) 20 | } 21 | 22 | set (key, value) { 23 | this.styles.set(key, value) 24 | } 25 | 26 | } 27 | 28 | const dynamicStyle = new DynamicStyle() 29 | 30 | export default dynamicStyle 31 | -------------------------------------------------------------------------------- /app/utils/emitter/index.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'eventemitter3' 2 | 3 | export default new EventEmitter() 4 | 5 | export const PANEL_RESIZED = 'PANEL_RESIZED' 6 | export const PANEL_SHOW = 'PANEL_SHOW' 7 | export const PANEL_HIDE = 'PANEL_HIDE' 8 | export const THEME_CHANGED = 'THEME_CHANGED' 9 | export const SOCKET_TRIED_FAILED = 'SOCKET_TRIED_FAILED' 10 | export const SOCKET_RETRY = 'SOCKET_RETRY' 11 | export const FILE_CHANGE = 'FILE_CHANGE' 12 | export const FILE_HIGHLIGHT = 'FILE_HIGHLIGHT' 13 | export const TERM_ENV_HIDE = 'TERM_ENV_HIDE' 14 | export const TERM_ENV_SHOW = 'TERM_ENV_SHOW' 15 | export const TERMINAL_SHOW = 'TERMINAL_SHOW' 16 | export const GITGRAPH_SHOW = 'GITGRAPH_SHOW' 17 | -------------------------------------------------------------------------------- /app/utils/exports.js: -------------------------------------------------------------------------------- 1 | export * as actions from './actions' 2 | export * as assignProps from './assignProps' 3 | export * as codingPackageJsonp from './codingPackageJsonp' 4 | export * as colors from './colors' 5 | export * as decorators from './decorators' 6 | export * as dnd from './dnd' 7 | export * as emitter from './emitter' 8 | export * as extendObservableStrict from './extendObservableStrict' 9 | export * as getBackoff from './getBackoff' 10 | export * as getCookie from './getCookie' 11 | export * as getTabType from './getTabType' 12 | export * as handleActions from './handleActions' 13 | export * as hasVimium from './hasVimium' 14 | export * as immutableUpdate from './immutableUpdate' 15 | export * as is from './is' 16 | export * as loadStyle from './loadStyle' 17 | export * as multiline from './multiline' 18 | export * as path from './path' 19 | export * as RandColors from './RandColors' 20 | export * as request from './request' 21 | export * as setSelectionRange from './setSelectionRange' 22 | export * as stepFactory from './stepFactory' 23 | export * as qs from './qs' 24 | export { default as withTheme } from './withTheme' 25 | export * as maskActions from '../components/Mask/actions' 26 | -------------------------------------------------------------------------------- /app/utils/extendObservableStrict.js: -------------------------------------------------------------------------------- 1 | import { extendObservable } from 'mobx' 2 | 3 | // extendObservableStrict is like extendObservable, 4 | // except it only accept extension properties that appeared in defaults 5 | export default function extendObservableStrict (obj, defaults, extensions) { 6 | const acceptableKeys = Object.keys(defaults) 7 | const acceptables = Object.keys(extensions).reduce((acc, key) => { 8 | if (acceptableKeys.includes(key)) acc[key] = extensions[key] 9 | return acc 10 | }, {}) 11 | return extendObservable(obj, defaults, acceptables) 12 | } -------------------------------------------------------------------------------- /app/utils/getBackoff.js: -------------------------------------------------------------------------------- 1 | import Backoff from 'backo2' 2 | 3 | const defaultConfig = { 4 | delayMin: 1500, 5 | delayMax: 10000, 6 | } 7 | 8 | export default function getBackoff (config = {}) { 9 | config = { ...defaultConfig, ...config } 10 | const backoff = new Backoff({ 11 | min: config.delayMin, 12 | max: config.delayMax, 13 | jitter: 0.5, 14 | }) 15 | 16 | return backoff 17 | } 18 | -------------------------------------------------------------------------------- /app/utils/getCookie.js: -------------------------------------------------------------------------------- 1 | import memoize from 'lodash/memoize' 2 | 3 | function getCookie (name) { 4 | const value = `; ${document.cookie}` 5 | const parts = value.split(`; ${name}=`) 6 | if (parts.length == 2) return parts.pop().split(';').shift() 7 | } 8 | 9 | export default memoize(getCookie, name => `${name}@${document.cookie}`) 10 | -------------------------------------------------------------------------------- /app/utils/getTabType.js: -------------------------------------------------------------------------------- 1 | export default function getTabType (node) { 2 | if ( 3 | /^text\/[^/]+/.test(node.contentType) || ( 4 | node.contentType === 'application/xml' 5 | ) || ( 6 | node.contentType === 'application/x-sh' 7 | ) || ( 8 | node.contentType === 'application/xhtml+xml' 9 | )) { 10 | return 'TEXT' 11 | } else if (/^image\/[^/]+/.test(node.contentType)) { 12 | if (node.contentType === 'image/vnd.adobe.photoshop') { 13 | return 'UNKNOWN' 14 | } 15 | if (node.contentType === 'image/jpeg' || node.contentType === 'image/png' || node.contentType === 'image/bmp' || node.contentType === 'image/gif') { 16 | return 'IMAGE' 17 | } 18 | } 19 | // Unknown 20 | return 'UNKNOWN' 21 | } 22 | -------------------------------------------------------------------------------- /app/utils/handleActions.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | 3 | const wrapHandler = handler => function (state, action) { 4 | return handler(state, action.payload, action) 5 | } 6 | 7 | export default function newHandleActions (handlers, defaultState) { 8 | const wrappedHandlers = Object.keys(handlers).reduce( 9 | (h, key) => { 10 | h[key] = wrapHandler(handlers[key]) 11 | return h 12 | }, {}) 13 | 14 | return handleActions(wrappedHandlers, defaultState) 15 | } 16 | -------------------------------------------------------------------------------- /app/utils/hasVimium.js: -------------------------------------------------------------------------------- 1 | export default function hasVimium () { 2 | try { 3 | const shadowRoot = document.querySelector('html > div').shadowRoot 4 | return Boolean(shadowRoot.querySelector('style').textContent.match(/vimium/)) 5 | } catch (e) { 6 | return false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/utils/immutableUpdate.js: -------------------------------------------------------------------------------- 1 | import update from 'immutability-helper' 2 | import _ from 'lodash' 3 | 4 | // global update extends 5 | const removeValue = (valueToRemove, original) => { 6 | if (_.isArray(original)) { 7 | return _.without(original, valueToRemove) 8 | } 9 | if (_.isObject(original)) { 10 | return _.reduce(original, (result, value, key) => { 11 | if (value !== valueToRemove) result[key] = value 12 | return result 13 | }, {}) 14 | } 15 | return original 16 | } 17 | 18 | update.extend('$removeValue', removeValue) 19 | update.extend('$without', removeValue) 20 | 21 | const removeKey = (keysToRemove, original) => { 22 | if (!_.isArray(original) && !_.isObject(original)) return original 23 | if (!_.isArray(keysToRemove)) keysToRemove = [keysToRemove] 24 | return _.reduce(original, (result, value, key) => { 25 | if (!keysToRemove.includes(key)) result[key] = value 26 | return result 27 | }, _.isArray(original) ? [] : {}) 28 | } 29 | 30 | update.extend('$removeKey', removeKey) 31 | update.extend('$delete', removeKey) 32 | 33 | update.extend('$map', (fn, original) => { 34 | if (_.isArray(original)) return _.map(original, fn) 35 | if (_.isObject(original)) return _.mapValues(original, fn) 36 | return original 37 | }) 38 | 39 | export default update 40 | -------------------------------------------------------------------------------- /app/utils/index.js: -------------------------------------------------------------------------------- 1 | export path from './path' 2 | export request from './request' 3 | export setSelectionRange from './setSelectionRange' 4 | export composeReducers from './composeReducers' 5 | export update from './immutableUpdate' 6 | export qs from './qs' 7 | export { PluginRegistry } from './plugins.js' 8 | export stepFactory from './stepFactory' 9 | export loadStyle from './loadStyle' 10 | export codingPackageJsonp from './codingPackageJsonp' 11 | export emitter, * as E from './emitter' 12 | export handleActions from './handleActions' 13 | export getTabType from './getTabType' 14 | export dnd from './dnd' 15 | export getCookie from './getCookie' 16 | export getBackoff from './getBackoff' 17 | export multiline from './multiline' 18 | export is from './is' 19 | // export i18n from './createI18n' 20 | -------------------------------------------------------------------------------- /app/utils/is.js: -------------------------------------------------------------------------------- 1 | import isNull from 'lodash/isNull' 2 | import isUndefined from 'lodash/isUndefined' 3 | import isString from 'lodash/isString' 4 | import isBoolean from 'lodash/isBoolean' 5 | import isFunction from 'lodash/isFunction' 6 | import isArray from 'lodash/isArray' 7 | import isPlainObject from 'lodash/isPlainObject' 8 | import isNaN from 'lodash/isNaN' 9 | import _isNumber from 'lodash/isNumber' 10 | 11 | const isNumber = n => { 12 | if (isNaN(n)) return false 13 | return _isNumber(n) 14 | } 15 | 16 | function is (type) { 17 | if (isUndefined(type)) return isUndefined 18 | if (isNull(type)) return isNull 19 | switch (type) { 20 | case String: 21 | return isString 22 | case Number: 23 | return isNumber 24 | case Boolean: 25 | return isBoolean 26 | case Function: 27 | return isFunction 28 | case Array: 29 | return isArray 30 | case Object: 31 | return isPlainObject 32 | default: 33 | return undefined 34 | } 35 | } 36 | 37 | export default Object.assign(is, { 38 | null: isNull, 39 | undefined: isUndefined, 40 | string: isString, 41 | number: isNumber, 42 | boolean: isBoolean, 43 | function: isFunction, 44 | array: isArray, 45 | pojo: isPlainObject, 46 | plainObject: isPlainObject, 47 | }) 48 | 49 | export { 50 | isNull, 51 | isUndefined, 52 | isString, 53 | isNumber, 54 | isBoolean, 55 | isFunction, 56 | isArray, 57 | isPlainObject, 58 | } 59 | -------------------------------------------------------------------------------- /app/utils/loadStyle.js: -------------------------------------------------------------------------------- 1 | function loadStyle (cssUrl) { 2 | const styleElement = document.createElement('link') 3 | styleElement.rel = 'stylesheet' 4 | styleElement.type = 'text/css' 5 | styleElement.href = cssUrl 6 | 7 | const head = document.getElementsByTagName('head')[0] 8 | return { 9 | use () { 10 | head.appendChild(styleElement) 11 | }, 12 | 13 | unuse () { 14 | head.removeChild(styleElement) 15 | } 16 | } 17 | } 18 | 19 | export default loadStyle 20 | -------------------------------------------------------------------------------- /app/utils/multiline.js: -------------------------------------------------------------------------------- 1 | import flattenDeep from 'lodash/flattenDeep' 2 | import zip from 'lodash/zip' 3 | 4 | export default function multiline (strings, ...keys) { 5 | const oStr = Array.isArray(strings) ? flattenDeep(zip(strings, keys)).join('') : strings 6 | const reIndent = /^[\ \t]+/ 7 | let minIndentWidth = Infinity 8 | let minIndent = '' 9 | const oStrSplitted = oStr.split('\n') 10 | // discard first empty line 11 | if (oStrSplitted[0].replace(/\s*/g, '') === '') oStrSplitted.shift() 12 | return oStrSplitted.reduce((acc, str, idx) => { 13 | const matchOfIndent = reIndent.exec(str) 14 | const indent = matchOfIndent ? matchOfIndent[0] : '' 15 | if (indent.length !== str.length && indent.length < minIndentWidth) { 16 | minIndentWidth = indent.length 17 | minIndent = indent 18 | } 19 | acc.push(str) 20 | return acc 21 | }, []).map(str => str.replace(RegExp(`^${minIndent}`), '')).join('\n') 22 | } 23 | -------------------------------------------------------------------------------- /app/utils/path.js: -------------------------------------------------------------------------------- 1 | export default { 2 | join () { 3 | const path = Array.prototype.join.call(arguments, '/') 4 | return path.split(/\/+/).join('/') 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /app/utils/plugins.js: -------------------------------------------------------------------------------- 1 | export const PluginRegistry = { 2 | _plugins: {}, 3 | get packages () { 4 | return this._plugins 5 | }, 6 | delete (key) { 7 | delete this._plugins[key] 8 | }, 9 | get (key) { 10 | return this._plugins[key] 11 | }, 12 | set (key, plugin) { 13 | this._plugins[key] = plugin 14 | }, 15 | find (pkgId) { 16 | return Object.values(this._plugins).find(value => value.pkgId === pkgId) 17 | }, 18 | findAll (pkgId) { 19 | return Object.values(this._plugins).filter(value => value.pkgId === pkgId) 20 | }, 21 | findAllByType (loadType) { 22 | 23 | return Object.values(this._plugins).filter(value => value.loadType === loadType) 24 | } 25 | } 26 | // for test 27 | window.PluginRegistry = PluginRegistry -------------------------------------------------------------------------------- /app/utils/promise.prototype.finalCatch.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise !== 'function' && typeof Promise !== 'object') { 2 | throw `Cannot polyfill Promise when it is ${JSON.stringify(Promise)}` 3 | } 4 | 5 | const protoThen = Promise.prototype.then 6 | const protoCatch = Promise.prototype.catch 7 | 8 | const wrappedCatch = function (onRejected) { 9 | const original = this 10 | const newPromise = protoCatch.call(this, onRejected) 11 | 12 | if (original._finalCatchHandler) { 13 | original._isInherited = true 14 | newPromise.finalCatch(original._finalCatchHandler) 15 | } 16 | 17 | return newPromise 18 | } 19 | 20 | const wrappedThen = function () { 21 | const original = this 22 | const newPromise = protoThen.apply(this, arguments) 23 | 24 | if (original._finalCatchHandler) { 25 | original._isInherited = true 26 | newPromise.finalCatch(original._finalCatchHandler) 27 | } 28 | 29 | return newPromise 30 | } 31 | 32 | Promise.prototype.finalCatch = function (onRejected) { 33 | _this = this 34 | this._finalCatchHandler = function () { 35 | onRejected(...arguments) 36 | _this._finalCatchHandler = ((f) => {}) 37 | } 38 | protoThen.call(this, res => res, (err) => { 39 | if (!this._isInherited) { 40 | this._finalCatchHandler(err) 41 | } 42 | }) 43 | // this.then = wrappedThen 44 | // this.catch = wrappedCatch 45 | } 46 | 47 | Promise.prototype.then = wrappedThen 48 | Promise.prototype.catch = wrappedCatch 49 | -------------------------------------------------------------------------------- /app/utils/qs.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs' 2 | 3 | // source: https://github.com/jfromaniello/url-join 4 | function urlJoin () { 5 | let str = [].slice.call(arguments, 0).join('/') 6 | 7 | // make sure protocol is followed by two slashes 8 | str = str.replace(/:\//g, '://') 9 | 10 | // remove consecutive slashes 11 | str = str.replace(/([^:\s])\/+/g, '$1/') 12 | 13 | // remove trailing slash before parameters or hash 14 | str = str.replace(/\/(\?|&|#[^!])/g, '$1') 15 | 16 | // replace ? in parameters with & 17 | str = str.replace(/(\?.+)\?/g, '$1&') 18 | 19 | return str 20 | } 21 | 22 | const _stringify = qs.stringify.bind(qs) 23 | qs.stringify = function (queryObject) { 24 | // https://github.com/ljharb/qs#stringifying 25 | return _stringify(queryObject, { arrayFormat: 'brackets' }) 26 | } 27 | 28 | export default qs 29 | -------------------------------------------------------------------------------- /app/utils/setSelectionRange.js: -------------------------------------------------------------------------------- 1 | export default function setSelectionRange (input, start, end) { 2 | if (input.setSelectionRange) { 3 | input.focus() 4 | input.setSelectionRange(start, end) 5 | } else if (typeof input.selectionStart !== 'undefined') { 6 | input.selectionStart = start 7 | input.selectionEnd = end 8 | input.focus() 9 | } else if (input.createTextRange) { 10 | const selRange = input.createTextRange() 11 | selRange.collapse(true) 12 | selRange.moveStart('character', start) 13 | selRange.moveEnd('character', end) 14 | selRange.select() 15 | input.focus() 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/utils/toJS.js: -------------------------------------------------------------------------------- 1 | import { toJS } from 'mobx' 2 | 3 | export const mapToJS = map => map.entries() 4 | .reduce((p, v) => { 5 | const newValue = v[1].toJS ? v[1].toJS() : toJS(v[1]) 6 | if (newValue) { 7 | p[v[0]] = newValue 8 | } 9 | return p 10 | }, {}) 11 | -------------------------------------------------------------------------------- /app/utils/withTheme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ThemeProvider } from 'styled-components' 3 | import { observer } from 'mobx-react' 4 | 5 | export default Com => observer((props) => { 6 | const theme = window.themeVariables.toJS() 7 | return ( 8 | 9 | 10 | 11 | ) 12 | }) 13 | -------------------------------------------------------------------------------- /app/workspaces_standalone/backendAPI.js: -------------------------------------------------------------------------------- 1 | import { request } from '../utils' 2 | 3 | export function getWorkspaces () { 4 | return request.get('/workspaces') 5 | } 6 | 7 | export function createWorkspace (url) { 8 | return request.post('/workspaces', { url }) 9 | } 10 | 11 | export function deleteWorkspace (spaceKey) { 12 | return request.delete(`/workspaces/${spaceKey}`) 13 | } 14 | 15 | export function getPublicKey () { 16 | return request.get('/user?public_key') 17 | } 18 | -------------------------------------------------------------------------------- /app/workspaces_standalone/bootstrapping.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /app/workspaces_standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /app/workspaces_standalone/index.jsx: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | import WorkspaceList from './WorkspaceList' 6 | import bootstrapping from './bootstrapping' 7 | import '../styles/main.styl' 8 | import '../styles/base-theme/index.styl' 9 | import store from './store' 10 | 11 | // first bootstrap the state, load the store 12 | bootstrapping() 13 | 14 | // finally render the component 15 | const rootElement = document.getElementById('root') 16 | 17 | if (__DEV__) { 18 | const hotLoaderRender = () => 19 | render( 20 | 21 | 22 | 23 | , rootElement) 24 | 25 | hotLoaderRender() 26 | if (module.hot) module.hot.accept('./WorkspaceList', hotLoaderRender) 27 | } else { 28 | render(, rootElement) 29 | } 30 | -------------------------------------------------------------------------------- /app/workspaces_standalone/reducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | import { 3 | WORKSPACE_FETCH_PUBLIC_KEY, 4 | WORKSPACE_FETCH_LIST, 5 | WORKSPACE_OPEN, 6 | WORKSPACE_CREATING, 7 | WORKSPACE_CREATING_ERROR 8 | } from './actions' 9 | 10 | const defaultState = { 11 | route: 'WORKSPACES', 12 | workspaces: [], 13 | currentWorkspace: null, 14 | publicKey: null, 15 | fingerprint: null, 16 | isCreating: false, 17 | errMsg: null 18 | } 19 | 20 | export default handleActions({ 21 | [WORKSPACE_FETCH_PUBLIC_KEY]: (state, action) => { 22 | const { publicKey, fingerprint } = action.payload 23 | return { ...state, publicKey, fingerprint } 24 | }, 25 | 26 | [WORKSPACE_FETCH_LIST]: (state, action) => ({ ...state, workspaces: action.payload }), 27 | 28 | [WORKSPACE_OPEN]: (state, action) => ({ ...state, route: 'IDE', currentWorkspace: action.payload }), 29 | 30 | [WORKSPACE_CREATING]: (state, action) => ({ ...state, isCreating: action.payload }), 31 | 32 | [WORKSPACE_CREATING_ERROR]: (state, action) => ({ ...state, isCreating: false, errMsg: action.payload }) 33 | }, defaultState) 34 | -------------------------------------------------------------------------------- /app/workspaces_standalone/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunkMiddleware from 'redux-thunk' 3 | import WorkspaceReducer from './reducer' 4 | 5 | const enhancer = compose( 6 | applyMiddleware(thunkMiddleware), 7 | window.devToolsExtension ? window.devToolsExtension({ 8 | serialize: { 9 | replacer: (key, value) => { 10 | if (key === 'editor') return {} 11 | if (key === 'DOMNode') return {} 12 | return value 13 | } 14 | } 15 | }) : f => f 16 | ) 17 | const store = createStore(WorkspaceReducer, enhancer) 18 | 19 | export default store 20 | export const getState = store.getState 21 | export const dispatch = store.dispatch 22 | -------------------------------------------------------------------------------- /app/workstation.jsx: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import Root from './containers/Root' 5 | import WorkStation from './workstation/workstationFull' 6 | import './styles/workstation.styl' 7 | import initialize from './initialize' 8 | import InitializeContainer from './containers/Initialize' 9 | import SettingState from 'components/Setting/state' 10 | const uiTheme = SettingState.settings.appearance.ui_theme.value 11 | if (uiTheme === 'base-theme') { 12 | const baseTheme = require('!!style-loader/useable!css-loader!stylus-loader!./styles/base-theme/index.styl') 13 | baseTheme.use() 14 | window.themes = { '@current': baseTheme } 15 | } else { 16 | const darkTheme = require('!!style-loader/useable!css-loader!stylus-loader!./styles/dark/index.styl') 17 | darkTheme.use() 18 | window.themes = { '@current': darkTheme } 19 | } 20 | 21 | const rootElement = document.getElementById('root') 22 | render(, rootElement) 23 | 24 | async function startApp (module) { 25 | if (__DEV__) { 26 | const hotLoaderRender = () => 27 | render(, rootElement) 28 | 29 | hotLoaderRender() 30 | if (module.hot) module.hot.accept('./workstation/workstationFull', hotLoaderRender) 31 | } else { 32 | render(, rootElement) 33 | } 34 | } 35 | 36 | startApp(module) 37 | 38 | const log = (...args) => console.log(...args) || (x => x) 39 | if (__VERSION__) log(`[VERSION] ${__VERSION__}`) 40 | -------------------------------------------------------------------------------- /app/workstation/index.js: -------------------------------------------------------------------------------- 1 | import WorkStation from './workstationFull' 2 | 3 | export default WorkStation 4 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | root /usr/share/nginx/html; 6 | 7 | location / { 8 | index index.html index.htm; 9 | } 10 | 11 | location /ws { 12 | rewrite ^/ws/.*$ /workspace.html break; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/WebIDE-Frontend/fab3d991cd90be0c04ec7c57332bca32c6e9cddf/static/favicon.ico -------------------------------------------------------------------------------- /task.yaml.tpl: -------------------------------------------------------------------------------- 1 | apps: 2 | - script : npm 3 | name : 'main-project' 4 | args: start 5 | exec_mode: fork_mode 6 | exec_interpreter: node 7 | 8 | - script : npm 9 | name : 'main-packageList' 10 | args: run packageList 11 | exec_mode: fork_mode 12 | exec_interpreter: node 13 | 14 | - script : npm 15 | name : 'plugin-platform' 16 | cwd: /Users/zhengxinqi/mycodingjob/webide/WebIDE-Plugin-Platform 17 | args: start 18 | env: 19 | PORT: 4001 20 | exec_mode: fork_mode 21 | exec_interpreter: node 22 | 23 | - script : npm 24 | name : 'plugin-debugger' 25 | cwd: /Users/zhengxinqi/mycodingjob/webide/WebIDE-Plugin-Debugger 26 | args: start 27 | env: 28 | PORT: 4002 29 | exec_mode: fork_mode 30 | exec_interpreter: node -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | require('dotenv').config() 3 | var production = ['production', 'prod', 'qprod']; 4 | 5 | // module.exports = (process.env.NODE_ENV && production.indexOf(process.env.NODE_ENV) > -1) ? 6 | // require('./webpack_configs/webpack.prod.config.js') 7 | // : (process.env.NODE_ENV === 'staging' ? require('./webpack_configs/webpack.staging.config.js') : 8 | // require('./webpack_configs/webpack.dev.config.js')) 9 | if (process.env.RUN_MODE === 'lib') { 10 | if (process.env.NODE_ENV && process.env.NODE_ENV === 'dev') { 11 | module.exports = require('./webpack_configs/webpack.lib.dev.config.js') 12 | } else { 13 | module.exports = require('./webpack_configs/webpack.lib.config.js') 14 | } 15 | } else if (process.env.NODE_ENV && production.indexOf(process.env.NODE_ENV) > -1) { 16 | module.exports = require('./webpack_configs/webpack.prod.config.js') 17 | } else if (process.env.NODE_ENV === 'staging') { 18 | module.exports = require('./webpack_configs/webpack.staging.config.js') 19 | } else { 20 | module.exports = require('./webpack_configs/webpack.dev.config.js') 21 | } 22 | 23 | -------------------------------------------------------------------------------- /webpack_configs/devServer.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | // 调用平台版后台 4 | const isPlatform = Boolean(process.env.RUN_MODE); 5 | 6 | module.exports = function (options) { 7 | return { 8 | devServer: { 9 | hot: true, 10 | inline: true, 11 | host: options.host || '0.0.0.0', 12 | port: options.port || 8060, 13 | historyApiFallback: { 14 | rewrites: [ 15 | { from: /\/ws/, to: '/workspace.html' }, 16 | { from: /\/login/, to: '/login.html' } 17 | ] 18 | } 19 | }, 20 | plugins: [ 21 | new webpack.HotModuleReplacementPlugin({ multiStep: false }), 22 | new webpack.NamedModulesPlugin(), 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /webpack_configs/loaders/regexp-replace-loader.js: -------------------------------------------------------------------------------- 1 | module.exports = function (source) { 2 | var query = this.query; 3 | var re = new RegExp(query.match.pattern, query.match.flags); 4 | return source.replace(re, query.replaceWith); 5 | }; 6 | -------------------------------------------------------------------------------- /webpack_configs/stylesheet.config.js: -------------------------------------------------------------------------------- 1 | const bootstrap = require('bootstrap-styl') 2 | const stylusLoader = require('stylus-loader') 3 | 4 | module.exports = function (paths) { 5 | return { 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.woff2?\??([a-f\d]+)?(v=\d+\.\d+\.\d+)?$/, 10 | use: ['file-loader'] 11 | // loader: "url?limit=10000&mimetype=application/font-woff" 12 | }, { 13 | test: /\.ttf\??([a-f\d]+)?(v=\d+\.\d+\.\d+)?$/, 14 | use: ['file-loader'] 15 | // loader: "url?limit=10000&mimetype=application/octet-stream" 16 | }, { 17 | test: /\.eot\??([a-f\d]+)?(v=\d+\.\d+\.\d+)?$/, 18 | use: ['file-loader'] 19 | }, { 20 | test: /\.svg\??([a-f\d]+)?(v=\d+\.\d+\.\d+)?$/, 21 | use: ['file-loader'] 22 | // loader: "url?limit=10000&mimetype=image/svg+xml" 23 | }, { 24 | test: /\.styl$/, 25 | use: [ 26 | 'style-loader', 27 | 'css-loader', 28 | 'stylus-loader' 29 | ] 30 | }, { 31 | test: /\.css$/, 32 | use: ['style-loader', 'css-loader'] 33 | } 34 | ] 35 | }, 36 | // https://github.com/shama/stylus-loader/issues/149 37 | // https://github.com/shama/stylus-loader/pull/154/files#diff-0444c5b7c3bc2c340b3654c507443b06R35 38 | plugins: [ 39 | new (stylusLoader.OptionsPlugin)({ 40 | default: { use: [bootstrap()] } 41 | }) 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /webpack_configs/uglify.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | module.exports = function (options) { 4 | return { 5 | plugins: [ 6 | new webpack.optimize.UglifyJsPlugin({ 7 | beautify: false, 8 | comments: false, 9 | compress: { 10 | warnings: false, 11 | drop_console: true 12 | }, 13 | mangle: { 14 | except: ['$'], 15 | screw_ie8: true 16 | } 17 | }) 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webpack_configs/webpack.lib.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const merge = require('webpack-merge') 4 | const str = JSON.stringify 5 | const commonConfig = require('./workstation.config.js') 6 | 7 | const stylesheet = require('./stylesheet.lib.config') 8 | const uglify = require('./uglify.config') 9 | 10 | module.exports = merge( 11 | commonConfig({ 12 | staticDir: '/', 13 | }), 14 | stylesheet(), 15 | uglify(), 16 | { 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | __DEV__: false, 20 | __RUN_MODE__: str(process.env.RUN_MODE || ''), 21 | __BACKEND_URL__: str(process.env.BACKEND_URL || ''), 22 | __WS_URL__: str(process.env.WS_URL || ''), 23 | __STATIC_SERVING_URL__: str(process.env.STATIC_SERVING_URL || ''), 24 | __PACKAGE_DEV__: false, 25 | __PACKAGE_SERVER__: str(process.env.PACKAGE_SERVER || process.env.HTML_BASE_URL || ''), 26 | __NODE_ENV__: str(process.env.NODE_ENV || ''), 27 | }), 28 | ] 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /webpack_configs/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const merge = require('webpack-merge') 4 | const str = JSON.stringify 5 | const commonConfig = require('./common.config.js') 6 | 7 | const stylesheet = require('./stylesheet.config') 8 | const uglify = require('./uglify.config') 9 | 10 | module.exports = merge( 11 | commonConfig({ 12 | staticDir: process.env.RUN_MODE ? 'rs2' : 'rs', 13 | }), 14 | stylesheet(), 15 | uglify(), 16 | { 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | __DEV__: false, 20 | __RUN_MODE__: str(process.env.RUN_MODE || ''), 21 | __BACKEND_URL__: str(process.env.BACKEND_URL || ''), 22 | __WS_URL__: str(process.env.WS_URL || ''), 23 | __STATIC_SERVING_URL__: str(process.env.STATIC_SERVING_URL || ''), 24 | __PACKAGE_DEV__: false, 25 | __PACKAGE_SERVER__: str(process.env.PACKAGE_SERVER || process.env.HTML_BASE_URL || ''), 26 | __NODE_ENV__: str(process.env.NODE_ENV || ''), 27 | }), 28 | ] 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /webpack_configs/webpack.staging.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const merge = require('webpack-merge') 4 | 5 | const str = JSON.stringify 6 | const commonConfig = require('./common.config.js') 7 | const stylesheet = require('./stylesheet.config') 8 | // const uglify = require('./uglify.config') 9 | 10 | module.exports = merge( 11 | commonConfig({ 12 | staticDir: process.env.RUN_MODE ? 'rs2' : 'rs', 13 | }), 14 | stylesheet(), 15 | { devtool: 'cheap-module-eval-source-map' }, 16 | { 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | __DEV__: false, 20 | __RUN_MODE__: str(process.env.RUN_MODE || ''), 21 | __BACKEND_URL__: str(process.env.BACKEND_URL || ''), 22 | __WS_URL__: str(process.env.WS_URL || ''), 23 | __STATIC_SERVING_URL__: str(process.env.STATIC_SERVING_URL || ''), 24 | __PACKAGE_DEV__: false, 25 | __PACKAGE_SERVER__: str(process.env.PACKAGE_SERVER || process.env.HTML_BASE_URL || ''), 26 | __NODE_ENV__: str(process.env.NODE_ENV || ''), 27 | }), 28 | ] 29 | } 30 | ) 31 | --------------------------------------------------------------------------------