├── .plugins.json ├── app ├── commons │ ├── WorkSpace │ │ └── state.js │ ├── exports.js │ ├── Tree │ │ ├── index.js │ │ └── store.js │ ├── File │ │ ├── index.js │ │ └── store.js │ ├── Tab │ │ ├── index.js │ │ └── TabContent.jsx │ └── Pane │ │ └── state.js ├── components │ ├── MenuBar │ │ ├── actions.js │ │ ├── index.js │ │ ├── state.js │ │ └── store.js │ ├── Menu │ │ ├── index.js │ │ └── MenuContextTypes.js │ ├── TopBar │ │ ├── index.js │ │ └── TopBar.jsx │ ├── Git │ │ ├── GitGraph │ │ │ ├── index.js │ │ │ ├── helpers │ │ │ │ ├── index.js │ │ │ │ ├── RandColors.js │ │ │ │ └── roundVertices.js │ │ │ └── state.js │ │ └── index.js │ ├── Modal │ │ ├── FilePalette │ │ │ └── index.js │ │ ├── index.js │ │ ├── modals │ │ │ ├── Confirm.jsx │ │ │ ├── Alert.jsx │ │ │ ├── Form.jsx │ │ │ ├── index.js │ │ │ ├── modalCache.js │ │ │ └── Prompt.jsx │ │ └── state.js │ ├── Pane │ │ ├── index.js │ │ ├── PanesContainer.jsx │ │ ├── Pane.jsx │ │ └── PaneAxis.jsx │ ├── Panel │ │ ├── index.js │ │ ├── PanelsContainer.jsx │ │ ├── PanelAxis.jsx │ │ ├── actions.js │ │ ├── Panel.jsx │ │ └── SideBar │ │ │ └── SideBar.jsx │ ├── Terminal │ │ ├── index.js │ │ └── state.js │ ├── Accordion │ │ └── index.js │ ├── Editor │ │ ├── components │ │ │ ├── EditorWidgets │ │ │ │ ├── index.js │ │ │ │ ├── LinterWidget.jsx │ │ │ │ ├── EditorWidgets.jsx │ │ │ │ └── ModeWidget.jsx │ │ │ ├── CodeEditor │ │ │ │ ├── mixins │ │ │ │ │ ├── eslintMixin │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── eslintService.js │ │ │ │ │ │ ├── eslintMixin.js │ │ │ │ │ │ └── eslintServiceCommandTemplate.js │ │ │ │ │ └── gitBlameMixin.jsx │ │ │ │ ├── addons │ │ │ │ │ ├── mode │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ ├── CodeEditor.jsx │ │ │ │ └── TablessCodeEditor.jsx │ │ │ ├── WelcomeEditor.jsx │ │ │ ├── MarkdownEditor │ │ │ │ ├── state.js │ │ │ │ ├── actions.js │ │ │ │ └── reducer.js │ │ │ └── HtmlEditor │ │ │ │ ├── state.js │ │ │ │ ├── htmlMixin.js │ │ │ │ └── actions.js │ │ ├── index.js │ │ ├── store.js │ │ ├── actions.js │ │ └── EditorWrapper.jsx │ ├── Mask │ │ ├── index.js │ │ ├── state.js │ │ ├── actions.js │ │ └── Mask.jsx │ ├── Notification │ │ ├── state.js │ │ ├── actions.js │ │ └── index.jsx │ ├── Tooltip │ │ ├── index.js │ │ ├── state.js │ │ ├── Tooltips.jsx │ │ └── TooltipTrigger.jsx │ ├── FileTree │ │ ├── index.js │ │ ├── store.js │ │ └── contextMenuItems.js │ ├── Plugins │ │ ├── index.js │ │ ├── store.js │ │ ├── constants.js │ │ └── component.jsx │ ├── Tab │ │ ├── index.js │ │ └── store.js │ ├── StatusBar │ │ ├── index.js │ │ ├── actions.js │ │ ├── StatusBar.jsx │ │ └── state.js │ ├── ContextMenu │ │ ├── index.js │ │ ├── state.js │ │ ├── ContextMenuContainer.jsx │ │ └── ContextMenu.jsx │ ├── Setting │ │ ├── SettingForm.jsx │ │ ├── KeymapSetting.jsx │ │ └── EditorSetting.jsx │ ├── exports.js │ └── Prompt │ │ └── Prompt.jsx ├── i18n │ ├── index.json │ ├── en_US │ │ ├── login.json │ │ ├── modal.json │ │ ├── panel.json │ │ ├── index.js │ │ ├── fileTree.json │ │ ├── tab.json │ │ ├── global.json │ │ ├── file.json │ │ └── menuBarItems.json │ └── zh_CN │ │ ├── login.json │ │ ├── modal.json │ │ ├── panel.json │ │ ├── index.js │ │ ├── fileTree.json │ │ ├── tab.json │ │ ├── global.json │ │ ├── file.json │ │ ├── menuBarItems.json │ │ └── settings.json ├── workspaces_standalone │ ├── bootstrapping.js │ ├── backendAPI.js │ ├── index.html │ ├── store.js │ ├── index.jsx │ └── reducer.js ├── workstation │ └── index.js ├── plugin │ ├── index.js │ └── placeholder.js ├── IDE │ ├── IdeEnvironment.js │ └── index.js ├── styles │ ├── dark │ │ ├── styles │ │ │ ├── welcome.styl │ │ │ ├── java.styl │ │ │ ├── term.styl │ │ │ ├── accordion.styl │ │ │ ├── mask.styl │ │ │ ├── filelist.styl │ │ │ ├── panes.styl │ │ │ ├── base.styl │ │ │ ├── env.styl │ │ │ ├── modal.styl │ │ │ ├── access.styl │ │ │ ├── editor.styl │ │ │ ├── menu.styl │ │ │ ├── diff.styl │ │ │ ├── form.styl │ │ │ ├── collaborators.styl │ │ │ ├── filetree.styl │ │ │ ├── bars.styl │ │ │ ├── git.styl │ │ │ ├── git-merge.styl │ │ │ └── history.styl │ │ └── index.styl │ ├── base-theme │ │ ├── styles │ │ │ ├── mask.styl │ │ │ ├── panes.styl │ │ │ ├── term.styl │ │ │ ├── accordion.styl │ │ │ ├── filelist.styl │ │ │ ├── editor.styl │ │ │ ├── filetree.styl │ │ │ ├── menu.styl │ │ │ ├── bars.styl │ │ │ └── git.styl │ │ └── index.styl │ ├── mixins │ │ ├── index.styl │ │ ├── color.styl │ │ ├── padding-margin.styl │ │ ├── scrollbar.styl │ │ ├── misc.styl │ │ └── z-index.styl │ ├── core-ui │ │ ├── DragAndDrop.styl │ │ ├── CommandPalette.styl │ │ ├── Mask.styl │ │ ├── Welcome.styl │ │ ├── FileList.styl │ │ ├── index.styl │ │ ├── Offline.styl │ │ ├── StatusBar.styl │ │ ├── Editor.styl │ │ ├── Accordion.styl │ │ ├── Workspace.styl │ │ ├── Breadcrumbs.styl │ │ └── Bar.styl │ ├── normalize.styl │ ├── lib.styl │ ├── workstation.styl │ └── main.styl ├── utils │ ├── colors │ │ ├── index.js │ │ └── hueFromString.js │ ├── path.js │ ├── decorators │ │ ├── index.js │ │ ├── defaultProps.js │ │ ├── mapEntity.js │ │ └── protectedObservable.js │ ├── actions │ │ ├── emitterMiddleware.js │ │ ├── handleActions.js │ │ ├── index.js │ │ ├── dispatch.js │ │ ├── handleAction.js │ │ ├── createAction.js │ │ └── registerAction.js │ ├── toJS.js │ ├── hasVimium.js │ ├── getCookie.js │ ├── withTheme.js │ ├── getBackoff.js │ ├── assignProps.js │ ├── composeReducers.js │ ├── loadStyle.js │ ├── handleActions.js │ ├── extendObservableStrict.js │ ├── RandColors.js │ ├── setSelectionRange.js │ ├── codingPackageJsonp.js │ ├── emitter │ │ └── index.js │ ├── getTabType.js │ ├── plugins.js │ ├── dynamicStyle.js │ ├── index.js │ ├── qs.js │ ├── multiline.js │ ├── exports.js │ ├── immutableUpdate.js │ ├── is.js │ └── promise.prototype.finalCatch.js ├── store.spec.js ├── states │ └── index.js ├── containers │ ├── Initialize │ │ └── state.js │ ├── Root │ │ ├── reducer.js │ │ ├── actions.js │ │ ├── index.jsx │ │ └── login.jsx │ ├── Utilities.jsx │ ├── Login.jsx │ ├── IDE.jsx │ └── GlobalPrompt.jsx ├── commands │ ├── CommandPalette │ │ ├── items.js │ │ └── index.js │ ├── commandBindings │ │ ├── index.js │ │ ├── editor.js │ │ └── tab.js │ ├── index.js │ ├── lib │ │ ├── keymapper.js │ │ └── helpers.js │ └── dispatchCommand.js ├── backendAPI │ └── index.js ├── jsconfig.json ├── login.html ├── login.jsx ├── index.html ├── exports.js ├── js.config.json ├── localStoreCache.js ├── index.jsx ├── workstation.jsx └── store.js ├── .jestfilemock.js ├── .dockerignore ├── .yarnrc ├── .eslintignore ├── static └── favicon.ico ├── .env.tpl ├── .gitignore ├── Dockerfile ├── .flowconfig ├── webpack_configs ├── loaders │ └── regexp-replace-loader.js ├── uglify.config.js ├── devServer.config.js ├── webpack.lib.config.js ├── webpack.prod.config.js ├── webpack.staging.config.js └── stylesheet.config.js ├── .editorconfig ├── nginx.conf ├── task.yaml.tpl ├── webpack.config.js ├── .eslintrc └── LICENSE /.plugins.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /app/commons/WorkSpace/state.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/components/MenuBar/actions.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/i18n/index.json: -------------------------------------------------------------------------------- 1 | ["en_US", "zh_CN"] -------------------------------------------------------------------------------- /.jestfilemock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !build/ 3 | !nginx.conf 4 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org/" 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | static/* 3 | *.spec.js 4 | -------------------------------------------------------------------------------- /app/i18n/en_US/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "loginCoding": "Login with Coding" 3 | } -------------------------------------------------------------------------------- /app/i18n/zh_CN/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "loginCoding": "使用 Coding 账号登录" 3 | } -------------------------------------------------------------------------------- /app/workspaces_standalone/bootstrapping.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /app/components/Menu/index.js: -------------------------------------------------------------------------------- 1 | import Menu from './Menu' 2 | 3 | export default Menu 4 | -------------------------------------------------------------------------------- /app/i18n/zh_CN/modal.json: -------------------------------------------------------------------------------- 1 | { 2 | "okButton": "确定", 3 | "cancelButton": "取消" 4 | } -------------------------------------------------------------------------------- /app/commons/exports.js: -------------------------------------------------------------------------------- 1 | import * as File from './File' 2 | 3 | export { 4 | File, 5 | } 6 | -------------------------------------------------------------------------------- /app/i18n/en_US/modal.json: -------------------------------------------------------------------------------- 1 | { 2 | "okButton": "OK", 3 | "cancelButton": "Cancel" 4 | } -------------------------------------------------------------------------------- /app/components/TopBar/index.js: -------------------------------------------------------------------------------- 1 | import TopBar from './TopBar' 2 | 3 | export default TopBar 4 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding/WebIDE-Frontend/master/static/favicon.ico -------------------------------------------------------------------------------- /app/workstation/index.js: -------------------------------------------------------------------------------- 1 | import WorkStation from './workstationFull' 2 | 3 | export default WorkStation 4 | -------------------------------------------------------------------------------- /.env.tpl: -------------------------------------------------------------------------------- 1 | BACKEND_URL= 2 | WS_URL= 3 | STATIC_SERVING_URL= 4 | RUN_MODE=platform 5 | PACKAGE_SERVER= 6 | PACKAGE_DEV= -------------------------------------------------------------------------------- /app/components/Git/GitGraph/index.js: -------------------------------------------------------------------------------- 1 | import GitGraphTable from './GitGraphTable' 2 | export default GitGraphTable 3 | -------------------------------------------------------------------------------- /app/components/Modal/FilePalette/index.js: -------------------------------------------------------------------------------- 1 | import FilePalette from './component' 2 | 3 | export { FilePalette } 4 | -------------------------------------------------------------------------------- /app/components/Pane/index.js: -------------------------------------------------------------------------------- 1 | import PanesContainer from './PanesContainer' 2 | 3 | export default PanesContainer 4 | -------------------------------------------------------------------------------- /app/plugin/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | key: 'preload', 3 | Manager: require('./placeholder').default, 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .idea/ 4 | .vscode/ 5 | .env 6 | app/config.js 7 | *.bak 8 | coverage/ 9 | task.yaml -------------------------------------------------------------------------------- /app/components/Panel/index.js: -------------------------------------------------------------------------------- 1 | import PanelsContainer from './PanelsContainer' 2 | 3 | export default PanelsContainer 4 | -------------------------------------------------------------------------------- /app/IDE/IdeEnvironment.js: -------------------------------------------------------------------------------- 1 | 2 | const IdeEnvironment = () => ({ 3 | editors: {} 4 | }) 5 | export default IdeEnvironment 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/components/Terminal/index.js: -------------------------------------------------------------------------------- 1 | import TerminalContainer from './TerminalContainer' 2 | 3 | export default TerminalContainer 4 | -------------------------------------------------------------------------------- /app/plugin/placeholder.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | pluginWillMount () { 3 | console.log('inner plugin') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/commons/Tree/index.js: -------------------------------------------------------------------------------- 1 | import TreeNode from './TreeNode' 2 | import TreeNodeScope from './state' 3 | export { TreeNode, TreeNodeScope } 4 | -------------------------------------------------------------------------------- /app/components/Accordion/index.js: -------------------------------------------------------------------------------- 1 | import { Accordion, AccordionGroup } from './Accordion' 2 | 3 | export { Accordion, AccordionGroup } 4 | -------------------------------------------------------------------------------- /app/components/Editor/components/EditorWidgets/index.js: -------------------------------------------------------------------------------- 1 | import EditorWidgets from './EditorWidgets' 2 | 3 | export default EditorWidgets 4 | -------------------------------------------------------------------------------- /app/styles/dark/styles/welcome.styl: -------------------------------------------------------------------------------- 1 | .welcome-page { 2 | color: $text-color; 3 | .subtitle { 4 | color: $text-color-subtle; 5 | } 6 | } -------------------------------------------------------------------------------- /app/components/Editor/components/CodeEditor/mixins/eslintMixin/index.js: -------------------------------------------------------------------------------- 1 | import eslintMixin from './eslintMixin' 2 | export default eslintMixin 3 | -------------------------------------------------------------------------------- /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/utils/colors/index.js: -------------------------------------------------------------------------------- 1 | import chroma from './chroma' 2 | import hueFromString from './hueFromString' 3 | 4 | export { chroma, hueFromString } 5 | -------------------------------------------------------------------------------- /app/components/Editor/components/WelcomeEditor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function () { 4 | return
Welcome
5 | } 6 | -------------------------------------------------------------------------------- /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/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/components/Git/GitGraph/helpers/index.js: -------------------------------------------------------------------------------- 1 | import chroma from './chroma' 2 | import RandColors from './RandColors' 3 | 4 | export { chroma, RandColors } 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/store.spec.js: -------------------------------------------------------------------------------- 1 | import { getState } from './store' 2 | 3 | describe('getState', () => { 4 | it('should getState', () => expect(getState()).toBe(false)) 5 | }) 6 | -------------------------------------------------------------------------------- /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/components/Notification/state.js: -------------------------------------------------------------------------------- 1 | import { observable } from 'mobx' 2 | 3 | const state = observable({ 4 | notifications: [], 5 | }) 6 | 7 | export default state 8 | -------------------------------------------------------------------------------- /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/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/components/Git/index.js: -------------------------------------------------------------------------------- 1 | import GitBranchWidget from './GitBranchWidget' 2 | import GitCommitView from './GitCommitView' 3 | 4 | export { GitBranchWidget, GitCommitView } 5 | -------------------------------------------------------------------------------- /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/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/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/Tooltip/index.js: -------------------------------------------------------------------------------- 1 | import TooltipTrigger from './TooltipTrigger' 2 | import Tooltips from './Tooltips' 3 | 4 | export default TooltipTrigger 5 | export { Tooltips } 6 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/i18n/zh_CN/panel.json: -------------------------------------------------------------------------------- 1 | { 2 | "bottom": { 3 | "terminal": "终端", 4 | "gitGraph": "项目网络", 5 | "history": "版本历史" 6 | }, 7 | "left": { 8 | "project": "文件树", 9 | "working": "工作文件" 10 | } 11 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/i18n/zh_CN/fileTree.json: -------------------------------------------------------------------------------- 1 | { 2 | "contextMenu": { 3 | "newFile": "新建文件...", 4 | "newFolder": "新建文件夹...", 5 | "delete": "删除...", 6 | "rename": "重命名...", 7 | "download": "下载", 8 | "upload": "上传", 9 | "gitBlame": "查看历史" 10 | } 11 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/workspaces_standalone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coding WebIDE 开启云端开发模式! - Coding.net 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | --------------------------------------------------------------------------------