├── .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
"
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------