├── data ├── logs │ └── .gitkeep └── tmp │ └── .gitkeep ├── server ├── chat │ ├── chatModel.js │ ├── chatRoutes.js │ └── chatController.js ├── editor │ ├── editorModel.js │ ├── editorController.js │ └── editorRoutes.js ├── tests │ ├── test-files │ │ ├── zipExampleProject │ │ │ ├── example.md │ │ │ ├── superExample.js │ │ │ └── exampleFolder │ │ │ │ ├── superExample.js │ │ │ │ └── subExampleFolder │ │ │ │ └── subSuperExampleFolder.js │ │ ├── fileExample.zip │ │ ├── zipExampleProject.zip │ │ └── dummyForTest.js │ ├── integration │ │ ├── index.js │ │ ├── login.js │ │ ├── template.js │ │ └── user.js │ ├── index.js │ └── db.tests.js ├── file │ ├── isGitUrl.js │ ├── fileRouter.js │ ├── writeFileAndDirectory.js │ ├── downloadController.js │ ├── getDocumentHash.js │ └── cloneGitRepositoryToProject.js ├── project │ ├── getUser.js │ ├── getProject.js │ ├── projectRoutes.js │ └── getProjectZip.js ├── clientConfigParser.js ├── liveDbClient.js ├── user │ ├── userRoutes.js │ └── userController.js ├── sharejs │ ├── shareJSServer.js │ └── socketHandler.js ├── rethinkdb.js ├── template │ └── templateRouter.js ├── auth │ ├── authRouter.js │ ├── authController.js │ └── index.js ├── api.js ├── index.js ├── deleteAllDatabases.js ├── models.js └── db.js ├── .bowerrc ├── compilebox.yml ├── client ├── assets │ ├── img │ │ ├── move.png │ │ ├── friends.png │ │ ├── login.png │ │ ├── team │ │ │ ├── doug.jpg │ │ │ ├── chase.jpg │ │ │ ├── jorge.jpg │ │ │ ├── catherine.jpg │ │ │ └── dougphung.jpg │ │ ├── add-project.png │ │ ├── landingScreenshots │ │ │ ├── downloadProject.png │ │ │ ├── gitCloningShot.png │ │ │ ├── videoScreenShot.png │ │ │ ├── compileScreenShot.png │ │ │ └── realTimeEditingShot.png │ │ ├── puff.svg │ │ └── circles.svg │ └── scss │ │ ├── _project.scss │ │ ├── codemirror-themes │ │ ├── _index.scss │ │ ├── _neat.scss │ │ ├── _elegant.scss │ │ ├── _neo.scss │ │ ├── _eclipse.scss │ │ ├── _cobalt.scss │ │ ├── _monokai.scss │ │ ├── _night.scss │ │ ├── _blackboard.scss │ │ ├── _3024-day.scss │ │ ├── _paraiso-dark.scss │ │ ├── _3024-night.scss │ │ ├── _paraiso-light.scss │ │ ├── _base16-dark.scss │ │ ├── _vibrant-ink.scss │ │ ├── _base16-light.scss │ │ ├── _twilight.scss │ │ ├── _midnight.scss │ │ ├── _pastel-on-dark.scss │ │ ├── _xq-light.scss │ │ ├── _lesser-dark.scss │ │ └── _xq-dark.scss │ │ ├── _base.scss │ │ ├── _globals.scss │ │ ├── main.scss │ │ ├── _project-file-structure.scss │ │ ├── _login.scss │ │ ├── _home.scss │ │ └── _header.scss ├── app │ ├── project │ │ ├── document │ │ │ ├── document.html │ │ │ └── document.js │ │ ├── chat │ │ │ ├── video │ │ │ │ ├── video.html │ │ │ │ └── video.js │ │ │ ├── chat.html │ │ │ └── chat.js │ │ ├── project.html │ │ ├── uploads │ │ │ └── uploads.js │ │ ├── fileStructure │ │ │ ├── fileStructure.html │ │ │ └── fileStructure.js │ │ └── toolbar │ │ │ ├── toolbar.html │ │ │ └── toolbar.js │ ├── home │ │ ├── home.html │ │ └── projects │ │ │ ├── projects.html │ │ │ └── projects.js │ ├── templates │ │ ├── mainHeaderDirective.js │ │ ├── modalAddUser.html │ │ ├── modalMoveFile.html │ │ ├── modalAddFolder.html │ │ ├── mainHeader.html │ │ ├── modalAddFile.html │ │ ├── modifyFileStructureModal.js │ │ ├── modifyProjectModal.js │ │ ├── modalCreateProject.html │ │ └── modalCreateProject.js │ ├── userBox.js │ ├── services │ │ ├── toolbarFactory.js │ │ ├── templatesFactory.js │ │ ├── videoFactory.js │ │ ├── authFactory.js │ │ ├── projectFactory.js │ │ ├── filesFactory.js │ │ ├── documentFactory.js │ │ ├── projectListFactory.js │ │ └── socketFactory.js │ ├── landing │ │ └── landing.js │ ├── login │ │ └── login.html │ └── app.js ├── node-webkit.html └── index.html ├── .editorconfig ├── prod.yml ├── config ├── test.js └── default.js ├── staging.yml ├── run.sh ├── .jshintrc ├── dev.yml ├── RETHINKDB_TODO.md ├── .gitignore ├── .travis.yml ├── knexfile.js ├── Dockerfile ├── .jscsrc ├── LICENSE ├── contributing.md ├── bower.json ├── karma.conf.js ├── README.md ├── package.json ├── gulpfile.js └── press_release.md /data/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/chat/chatModel.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/chat/chatRoutes.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/editor/editorModel.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/chat/chatController.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/editor/editorController.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/editor/editorRoutes.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/lib/" 3 | } -------------------------------------------------------------------------------- /server/tests/test-files/zipExampleProject/example.md: -------------------------------------------------------------------------------- 1 | This is my resume.md -------------------------------------------------------------------------------- /server/tests/test-files/zipExampleProject/superExample.js: -------------------------------------------------------------------------------- 1 | This is another example. -------------------------------------------------------------------------------- /server/tests/test-files/zipExampleProject/exampleFolder/superExample.js: -------------------------------------------------------------------------------- 1 | This is another example. -------------------------------------------------------------------------------- /compilebox.yml: -------------------------------------------------------------------------------- 1 | compilebox: 2 | build: ./compilebox/ 3 | ports: 4 | - "9002:80" 5 | privileged: true -------------------------------------------------------------------------------- /client/assets/img/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/move.png -------------------------------------------------------------------------------- /client/assets/img/friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/friends.png -------------------------------------------------------------------------------- /client/assets/img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/login.png -------------------------------------------------------------------------------- /client/assets/img/team/doug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/team/doug.jpg -------------------------------------------------------------------------------- /client/assets/img/add-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/add-project.png -------------------------------------------------------------------------------- /client/assets/img/team/chase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/team/chase.jpg -------------------------------------------------------------------------------- /client/assets/img/team/jorge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/team/jorge.jpg -------------------------------------------------------------------------------- /client/assets/img/team/catherine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/team/catherine.jpg -------------------------------------------------------------------------------- /client/assets/img/team/dougphung.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/team/dougphung.jpg -------------------------------------------------------------------------------- /server/tests/test-files/fileExample.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/server/tests/test-files/fileExample.zip -------------------------------------------------------------------------------- /server/tests/test-files/zipExampleProject.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/server/tests/test-files/zipExampleProject.zip -------------------------------------------------------------------------------- /server/tests/test-files/zipExampleProject/exampleFolder/subExampleFolder/subSuperExampleFolder.js: -------------------------------------------------------------------------------- 1 | // This is yet another example 2 | window.hello = 'hello'; -------------------------------------------------------------------------------- /client/assets/img/landingScreenshots/downloadProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/landingScreenshots/downloadProject.png -------------------------------------------------------------------------------- /client/assets/img/landingScreenshots/gitCloningShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/landingScreenshots/gitCloningShot.png -------------------------------------------------------------------------------- /client/assets/img/landingScreenshots/videoScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/landingScreenshots/videoScreenShot.png -------------------------------------------------------------------------------- /client/assets/img/landingScreenshots/compileScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/landingScreenshots/compileScreenShot.png -------------------------------------------------------------------------------- /client/assets/img/landingScreenshots/realTimeEditingShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-friends/CodeFriends/HEAD/client/assets/img/landingScreenshots/realTimeEditingShot.png -------------------------------------------------------------------------------- /client/app/project/document/document.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
{{ documentPath }}
5 | 6 |
-------------------------------------------------------------------------------- /server/file/isGitUrl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isGitUrl = function (str) { 4 | var re = /(?:git|ssh|https?|git@[\w\.]+):(?:\/\/)?[\w\.@:\/-~\-]+(\.git\/?)/g; 5 | return re.test(str); 6 | }; 7 | 8 | module.exports = isGitUrl; -------------------------------------------------------------------------------- /client/app/home/home.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 |
8 |
-------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /server/tests/test-files/dummyForTest.js: -------------------------------------------------------------------------------- 1 | 'blah blah blah' 2 | 'blah blah blah' 3 | 'blah blah blah' 4 | 'blah blah blah' 5 | 'blah blah blah' 6 | 'blah blah blah' 7 | 'blah blah blah' 8 | 'blah blah blah' 9 | 'blah blah blah' 10 | 'blah blah blah' 11 | 'blah blah blah' 12 | 'blah blah blah' 13 | 'blah blah blah' -------------------------------------------------------------------------------- /prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | build: . 3 | ports: 4 | - "8005:80" 5 | expose: 6 | - "80" 7 | links: 8 | - "db" 9 | environment: 10 | NODE_ENV: production 11 | PORT: 80 12 | db: 13 | image: tutum/mysql:5.5 14 | ports: 15 | - "3306:3306" 16 | environment: 17 | MYSQL_PASS: "6gdP7R3adDYNehj" 18 | -------------------------------------------------------------------------------- /server/project/getUser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var models = require('../models.js').models; 4 | 5 | var getUser = function (newUserName) { 6 | return models.User 7 | .query({ 8 | where: { 9 | username: newUserName 10 | } 11 | }) 12 | .fetch({ 13 | withRelated: ['project'] 14 | }); 15 | }; 16 | 17 | module.exports = getUser; -------------------------------------------------------------------------------- /client/app/project/chat/video/video.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /client/assets/scss/_project.scss: -------------------------------------------------------------------------------- 1 | // Project view 2 | .project-view-container { 3 | height: 100vh; 4 | overflow: hidden; 5 | color: rgb(36, 46, 50); 6 | background-color: $silver-blue; 7 | width: 100%; 8 | } 9 | 10 | .project-container { 11 | height: calc(100% - #{$toolbar-height}); 12 | } 13 | 14 | // Editor 15 | .padContainer { 16 | font-size: 1.3em; 17 | height: 100%; 18 | } -------------------------------------------------------------------------------- /config/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Inherits from `default.js` 4 | */ 5 | var config = { 6 | 'mysql': { 7 | 'database' : 'code_friends_test' 8 | }, 9 | 'mongo': 'mongodb://localhost:27017/codeFriendsTest?auto_reconnect', 10 | 'rethinkdb': { 11 | 'host': 'localhost', 12 | 'port': 28015, 13 | 'db': 'code_friends_test', 14 | }, 15 | }; 16 | module.exports = config; 17 | -------------------------------------------------------------------------------- /staging.yml: -------------------------------------------------------------------------------- 1 | server: 2 | build: . 3 | ports: 4 | - "8005:80" 5 | - "9000:9000" 6 | - "9001:9001" 7 | expose: 8 | - "80" 9 | - "9000" 10 | - "9001" 11 | links: 12 | - "db" 13 | environment: 14 | NODE_ENV: staging 15 | PORT: 80 16 | db: 17 | image: tutum/mysql:5.5 18 | ports: 19 | - "3306:3306" 20 | environment: 21 | MYSQL_PASS: "6gdP7R3adDYNehj" 22 | -------------------------------------------------------------------------------- /server/clientConfigParser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('config'); 3 | 4 | var clientConfigParser = function (req, res) { 5 | var _config = { 6 | 'ports': config.get('ports'), 7 | 'url': config.get('url') 8 | }; 9 | var str = 'window.config = ' + JSON.stringify(_config) + ';'; 10 | res 11 | .type('text/javascript') 12 | .send(str); 13 | }; 14 | 15 | module.exports = clientConfigParser; -------------------------------------------------------------------------------- /server/liveDbClient.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('bluebird'); 4 | var config = require('config'); 5 | var liveDBRethinkDBClient = require('livedb-rethinkdb'); 6 | // var liveDBMongoClient = require('livedb-mongo'); 7 | var livedb = require('livedb'); 8 | var db = liveDBRethinkDBClient(config.get('rethinkdb'), { 9 | safe: true 10 | }); 11 | var backend = Promise.promisifyAll(livedb.client(db)); 12 | 13 | module.exports = backend; 14 | -------------------------------------------------------------------------------- /server/user/userRoutes.js: -------------------------------------------------------------------------------- 1 | var userController = require('./userController.js'); 2 | var express = require('express'); 3 | 4 | var userRouter = express.Router(); 5 | 6 | userRouter.post('/', userController.post); 7 | userRouter.get('/', userController.getAllUsers); 8 | userRouter.get('/:username', userController.getSpecificUser); 9 | userRouter.put('/', userController.put); 10 | userRouter.delete('/', userController.delete); 11 | 12 | module.exports = userRouter; -------------------------------------------------------------------------------- /client/app/templates/mainHeaderDirective.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | angular.module('codeFriends.mainHeader', []) 5 | .directive('cfMainHeader', ['AuthFactory', function (AuthFactory) { 6 | var $scope = { 7 | 'hello': [1, 2, 3, 4, ] 8 | }; 9 | return { 10 | restrict: 'E', 11 | templateUrl: '/app/templates/mainHeader.html', 12 | $scope: $scope 13 | }; 14 | }]); 15 | })(); -------------------------------------------------------------------------------- /server/sharejs/shareJSServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var connect = require('connect'), 4 | http = require('http'), 5 | shareJSapp = connect(), 6 | shareJSServer = http.createServer(shareJSapp), 7 | WebSocketServer = require('ws').Server, 8 | wss = new WebSocketServer({ 9 | server: shareJSServer 10 | }); 11 | var socketConnectionHandler = require('./socketHandler'); 12 | 13 | wss.on('connection', socketConnectionHandler); 14 | 15 | module.exports = shareJSServer; -------------------------------------------------------------------------------- /client/app/userBox.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.userBox', []) 6 | .controller('UserBoxController', UserBox); 7 | 8 | UserBox.$inject = ['AuthFactory']; 9 | 10 | function UserBox(AuthFactory) { 11 | var vm = this; 12 | vm.userLoggedIn = (AuthFactory.userId !== null); 13 | vm.userName = AuthFactory.userName; 14 | vm.githubAvatarUrl = AuthFactory.githubAvatarUrl; 15 | } 16 | 17 | })(); -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | params=" -u ${DB_ENV_MYSQL_USER} -p${DB_ENV_MYSQL_PASS} -h ${DB_PORT_3306_TCP_ADDR} " 4 | echo $params; 5 | 6 | # Create Database 7 | mysql $params < 2 |

Invite a collaborator to this project

3 | 12 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "forin": true, 6 | "freeze": true, 7 | "immed": true, 8 | "indent": 2, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "nonbsp": true, 14 | "nonew": false, 15 | "quotmark": "single", 16 | "undef": true, 17 | 18 | "maxcomplexity": 10, 19 | "maxdepth": 4, 20 | "maxlen": 120, 21 | "maxparams": 5, 22 | "maxstatements": 25, 23 | 24 | "eqnull": true, 25 | "esnext": true, 26 | 27 | "node": true 28 | } -------------------------------------------------------------------------------- /dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | build: . 3 | ports: 4 | - "80:80" 5 | - "9000:9000" 6 | - "9001:9001" 7 | links: 8 | - "db" 9 | - "mongo" 10 | volumes: 11 | - ./data:/app/data 12 | - ./config:/app/config 13 | - ./server:/app/server 14 | - ./client:/app/client 15 | environment: 16 | NODE_ENV: docker-local 17 | PORT: 80 18 | db: 19 | image: tutum/mysql:5.5 20 | ports: 21 | - "3306:3306" 22 | environment: 23 | MYSQL_PASS: "6gdP7R3adDYNehj" 24 | mongo: 25 | image: dockerfile/mongodb 26 | ports: 27 | - "27017:27017" -------------------------------------------------------------------------------- /RETHINKDB_TODO.md: -------------------------------------------------------------------------------- 1 | ## Mongo to RethinkDB Migration 2 | 3 | - File uses a `submitAsync` function which is part of LiveDB 4 | 1. DONE server/file/fileController.js 5 | - File uses Mongo in 2 places to get/set file structure of a project 6 | 1. DONE server/file/uploadController.js 7 | - File uses a `submitAsync` function which is part of LiveDB 8 | 1. DONE server/chatServer.js 9 | - Chat room table names are note being created. Will throw an error. 10 | 1. DONE server/deleteAllDatabases.js 11 | 1. DONE server/liveDbClient.js 12 | - Uses the RethinkDB/MongoDB Live DB client module 13 | -------------------------------------------------------------------------------- /client/app/project/chat/video/video.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.video', ['ngSanitize']) 6 | .controller('VideoController', VideoController); 7 | 8 | VideoController.$inject = ['$scope', '$state', '$stateParams', 'VideoFactory']; 9 | 10 | function VideoController($scope, $state, $stateParams, VideoFactory) { 11 | var roomID = $stateParams.projectName; 12 | 13 | $scope.$on('STARTVIDEO', function () { 14 | VideoFactory.connect(roomID); 15 | window.isVideoOn = true; 16 | }); 17 | }; 18 | })(); -------------------------------------------------------------------------------- /server/project/getProject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var models = require('../models.js').models; 4 | 5 | var getProject = function (projectNameOrId) { 6 | return models.Project 7 | .query({ 8 | where: { 9 | project_name: projectNameOrId 10 | }, 11 | orWhere: { 12 | id: projectNameOrId 13 | } 14 | }) 15 | .fetch({ 16 | withRelated: ['user'] 17 | }) 18 | .then(function (project) { 19 | if (!project) throw new Error('No Model Could Be Found'); 20 | return project; 21 | }); 22 | }; 23 | 24 | module.exports = getProject; -------------------------------------------------------------------------------- /server/template/templateRouter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var templateController = require('./templateController.js'); 5 | 6 | var templateRouter = express.Router(); 7 | 8 | templateRouter.get('/', templateController.getAllTemplates); 9 | templateRouter.post('/', templateController.createNewTemplate); 10 | templateRouter.put('/newName', templateController.updateTemplateName); 11 | templateRouter.put('/newGitRepoUrl', templateController.updateGitRepoUrl); 12 | templateRouter.delete('/', templateController.deleteTemplate); 13 | 14 | module.exports = templateRouter; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !* 2 | !*.* 3 | 4 | # Dependency directory 5 | node_modules 6 | client/lib 7 | client/dist 8 | client/dist/* 9 | client/dist/main.js 10 | client/dist/main.css 11 | 12 | # Ingore Config 13 | config/* 14 | !config/default.json 15 | !config/test.json 16 | 17 | ### OSX ### 18 | .DS_Store 19 | 20 | # Ignore .nws and zips 21 | *.nw 22 | *.zip 23 | 24 | data/* 25 | data/**/* 26 | data/logs/* 27 | data/logs/**/* 28 | data/tmp/* 29 | data/tmp/**/* 30 | !data/logs/.gitkeep 31 | !data/tmp/.gitkeep 32 | !data/git-repositories/.gitkeep 33 | 34 | # Ignore Fabfile 35 | fabfile.py 36 | fabfile.pyc 37 | **/*.pyc -------------------------------------------------------------------------------- /client/node-webkit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Friends 6 | 7 | 8 | 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/file/fileRouter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var fileController = require('./fileController'); 5 | var uploadController = require('./uploadController.js'); 6 | var downloadController = require('./downloadController.js'); 7 | 8 | var fileRouter = express.Router(); 9 | 10 | fileRouter.post('/', fileController.createNewFileOrFolder); 11 | fileRouter.get('/', fileController.get); 12 | fileRouter.get('/download/*', downloadController.downloadFile); 13 | fileRouter.post('/upload/', uploadController.uploadNewFile); 14 | fileRouter.put('/move', fileController.moveFileInProject); 15 | 16 | module.exports = fileRouter; -------------------------------------------------------------------------------- /client/app/project/project.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /client/app/services/toolbarFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('ToolbarFactory', ToolbarFactory); 7 | 8 | ToolbarFactory.$inject = ['$rootScope']; 9 | 10 | function ToolbarFactory($rootScope) { 11 | 12 | var factory = { 13 | theme: 'default', 14 | changeTheme: changeTheme 15 | }; 16 | return factory; 17 | 18 | function changeTheme(theme) { 19 | ToolbarFactory.theme = theme; 20 | $rootScope.$broadcast('theme:changed', theme); 21 | $rootScope.$emit('theme:changed', theme); 22 | } 23 | 24 | } 25 | 26 | })(); -------------------------------------------------------------------------------- /server/auth/authRouter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var authControllers = require('./authController'); 5 | 6 | var auth = require('./index'); 7 | var authRouter = express.Router(); 8 | 9 | authRouter.use('/login/callback', auth.authenticate('github'), function (req, res) { 10 | res.redirect('/#/home'); 11 | }); 12 | authRouter.post('/login', auth.authenticate('local'), authControllers.login); 13 | authRouter.get('/login', auth.authenticate('github')); 14 | authRouter.post('/signup', authControllers.signup); 15 | authRouter.use('/user', authControllers.getUser); 16 | authRouter.use('/logout', authControllers.logout); 17 | 18 | module.exports = authRouter; -------------------------------------------------------------------------------- /client/app/landing/landing.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.landing', ['ui.router']) 6 | .controller('LandingController', LandingController); 7 | 8 | LandingController.$inject = ['$window']; 9 | 10 | function LandingController($window) { 11 | var vm = this; 12 | vm.init = init; 13 | 14 | function init() { 15 | var wistiaEmbed = window.Wistia.embed('j63zsovroj', { 16 | version: 'v1', 17 | autoPlay: true, 18 | videoFoam: true 19 | }); 20 | wistiaEmbed.bind('end', function () { 21 | wistiaEmbed.play(); 22 | }); 23 | } 24 | init(); 25 | } 26 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_index.scss: -------------------------------------------------------------------------------- 1 | @import '_3024-day.scss'; 2 | @import '_3024-night.scss'; 3 | @import '_ambiance.scss'; 4 | @import '_base16-dark.scss'; 5 | @import '_base16-light.scss'; 6 | @import '_blackboard.scss'; 7 | @import '_cobalt.scss'; 8 | @import '_eclipse.scss'; 9 | @import '_elegant.scss'; 10 | @import '_lesser-dark.scss'; 11 | @import '_midnight.scss'; 12 | @import '_monokai.scss'; 13 | @import '_neat.scss'; 14 | @import '_neo.scss'; 15 | @import '_night.scss'; 16 | @import '_paraiso-dark.scss'; 17 | @import '_paraiso-light.scss'; 18 | @import '_pastel-on-dark.scss'; 19 | @import '_solarized.scss'; 20 | @import '_twilight.scss'; 21 | @import '_vibrant-ink.scss'; 22 | @import '_xq-dark.scss'; 23 | @import '_xq-light.scss'; -------------------------------------------------------------------------------- /server/tests/index.js: -------------------------------------------------------------------------------- 1 | /*global describe:true, before: true, after: true */ 2 | 'use strict'; 3 | 4 | var db = require('../db'); 5 | var deleteAllDatabases = require('../deleteAllDatabases'); 6 | 7 | describe('Code Friends', function () { 8 | 9 | before(function (done) { 10 | // createAllTables is promise 11 | db.createAllTables.then(function () { 12 | done(); 13 | }); 14 | }); 15 | 16 | require('./db.tests.js'); 17 | require('./integration'); 18 | 19 | // Delete All Test Tables 20 | after(function (done) { 21 | deleteAllDatabases() 22 | .then(function () { 23 | done(); 24 | }) 25 | .catch(function (err) { 26 | console.log('Didn\'t delete tables:', err); 27 | }); 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /server/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var projectRouter = require('./project/projectRoutes'); 4 | var userRouter = require('./user/userRoutes'); 5 | var fileRouter = require('./file/fileRouter'); 6 | var templateRouter = require('./template/templateRouter'); 7 | // var uploadRouter = require('./upload/uploadRoutes'); 8 | // var downloadRouter = require('./download/downloadRoutes'); 9 | var express = require('express'); 10 | 11 | var apiRouter = express.Router(); 12 | 13 | apiRouter.use('/file', fileRouter); 14 | apiRouter.use('/project', projectRouter); 15 | apiRouter.use('/template', templateRouter); 16 | apiRouter.use('/user', userRouter); 17 | // apiRouter.use('/upload', uploadRouter); 18 | // apiRouter.use('/download', downloadRouter); 19 | 20 | module.exports = apiRouter; -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_neat.scss: -------------------------------------------------------------------------------- 1 | .cm-s-neat span.cm-comment { color: #a86; } 2 | .cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } 3 | .cm-s-neat span.cm-string { color: #a22; } 4 | .cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } 5 | .cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } 6 | .cm-s-neat span.cm-variable { color: black; } 7 | .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } 8 | .cm-s-neat span.cm-meta {color: #555;} 9 | .cm-s-neat span.cm-link { color: #3a3; } 10 | 11 | .cm-s-neat .CodeMirror-activeline-background {background: #e8f2ff !important;} 12 | .cm-s-neat .CodeMirror-matchingbracket {outline:1px solid grey; color:black !important;} 13 | -------------------------------------------------------------------------------- /server/project/projectRoutes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var projectController = require('./projectController.js'); 5 | 6 | var projectRouter = express.Router(); 7 | 8 | projectRouter.post('/', projectController.post); 9 | projectRouter.get('/', projectController.getAllProjects); 10 | projectRouter.get('/download/:projectNameOrId', projectController.downloadSpecificProject); 11 | projectRouter.get('/:projectNameOrId', projectController.getSpecificProject); 12 | projectRouter.put('/addUser', projectController.addUser); 13 | // projectRouter.post('/add', projectController.addToRepository); 14 | // projectRouter.post('/commit', projectController.commitToRepository); 15 | projectRouter.delete('/:projectNameOrId', projectController.delete); 16 | 17 | module.exports = projectRouter; -------------------------------------------------------------------------------- /server/file/writeFileAndDirectory.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 'use strict'; 3 | 4 | var Promise = require('bluebird'); 5 | var fs = Promise.promisifyAll(require('fs')); 6 | var mkdirp = Promise.promisify(require('mkdirp')); 7 | var path = require('path'); 8 | 9 | var writeFileAndDirecotry = function (filePath, fileContents) { 10 | return fs.exists(path.dirname(filePath)) 11 | .then(function (exists) { 12 | if (!exists) { 13 | // Create directory if it doesn't exist 14 | return mkdirp(filePath); 15 | } 16 | return true; 17 | }) 18 | .then(function () { 19 | return fs.writeFileAsync(filePath, fileContents); 20 | }) 21 | .catch(function (err) { 22 | console.log('Error Writing File', filePath, err); 23 | }); 24 | }; 25 | 26 | module.exports = writeFileAndDirecotry; 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: node_js 3 | services: 4 | - mongodb 5 | before_install: 6 | - npm update -g npm 7 | - sudo add-apt-repository ppa:rethinkdb/ppa -y 8 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 9 | - sudo apt-get -qq update 10 | - sudo apt-get -qq install g++-4.8 11 | - sudo apt-get -y --force-yes install rethinkdb 12 | - export CXX='g++-4.8' 13 | before_script: 14 | - npm install -g gulp 15 | - sudo cp /etc/rethinkdb/default.conf.sample /etc/rethinkdb/instances.d/instance1.conf 16 | - sudo /etc/init.d/rethinkdb start 17 | - mysql -e 'create database code_friends;' 18 | - mysql -e 'create database code_friends_test;' 19 | node_js: 20 | - '0.10' 21 | branches: 22 | only: 23 | - master 24 | notifications: 25 | email: false 26 | git: 27 | depth: 1 28 | script: npm --expose-gc test 29 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_elegant.scss: -------------------------------------------------------------------------------- 1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;} 2 | .cm-s-elegant span.cm-comment {color: #262; font-style: italic; line-height: 1em;} 3 | .cm-s-elegant span.cm-meta {color: #555; font-style: italic; line-height: 1em;} 4 | .cm-s-elegant span.cm-variable {color: black;} 5 | .cm-s-elegant span.cm-variable-2 {color: #b11;} 6 | .cm-s-elegant span.cm-qualifier {color: #555;} 7 | .cm-s-elegant span.cm-keyword {color: #730;} 8 | .cm-s-elegant span.cm-builtin {color: #30a;} 9 | .cm-s-elegant span.cm-link {color: #762;} 10 | .cm-s-elegant span.cm-error {background-color: #fdd;} 11 | 12 | .cm-s-elegant .CodeMirror-activeline-background {background: #e8f2ff !important;} 13 | .cm-s-elegant .CodeMirror-matchingbracket {outline:1px solid grey; color:black !important;} 14 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | // Update with your config settings. 2 | 3 | module.exports = { 4 | 5 | development: { 6 | client: 'sqlite3', 7 | connection: { 8 | filename: './dev.sqlite3' 9 | } 10 | }, 11 | 12 | staging: { 13 | client: 'postgresql', 14 | connection: { 15 | database: 'my_db', 16 | user: 'username', 17 | password: 'password' 18 | }, 19 | pool: { 20 | min: 2, 21 | max: 10 22 | }, 23 | migrations: { 24 | tableName: 'knex_migrations' 25 | } 26 | }, 27 | 28 | production: { 29 | client: 'postgresql', 30 | connection: { 31 | database: 'my_db', 32 | user: 'username', 33 | password: 'password' 34 | }, 35 | pool: { 36 | min: 2, 37 | max: 10 38 | }, 39 | migrations: { 40 | tableName: 'knex_migrations' 41 | } 42 | } 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /client/app/templates/modalMoveFile.html: -------------------------------------------------------------------------------- 1 |
2 |

Move File/Folder

3 | 19 |
-------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Friends 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/app/services/templatesFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('TemplatesFactory', TemplatesFactory); 7 | 8 | TemplatesFactory.$inject = ['$http']; 9 | 10 | function TemplatesFactory($http) { 11 | 12 | var factory = { 13 | getTemplates: getTemplates, 14 | postTemplate: postTemplate 15 | }; 16 | 17 | return factory; 18 | 19 | function getTemplates() { 20 | return $http.get('api/template/') 21 | .then(function (res) { 22 | return res.data; 23 | }); 24 | } 25 | 26 | function postTemplate(templateName, templateUrl) { 27 | return $http.post('api/template/', { 28 | templateName: templateName, 29 | gitRepoUrl: templateUrl 30 | }) 31 | .catch(function (err) { 32 | console.log('Error posting template from TemplatesFactory', err); 33 | }); 34 | } 35 | } 36 | 37 | })(); -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Configuration Structure 4 | * 5 | * default.js 6 | * - test.js 7 | * - development.js 8 | * - - staging.js 9 | * - - - production.js 10 | */ 11 | var config = { 12 | 'mysql': { 13 | 'host': 'localhost', 14 | 'user': 'root', 15 | 'password': '', 16 | 'database': 'code_friends' 17 | }, 18 | 'mongo': 'mongodb://127.0.0.1:27017/codeFriends?auto_reconnect', 19 | 'rethinkdb': { 20 | 'host': 'localhost', 21 | 'port': 28015, 22 | 'db': 'code_friends', 23 | }, 24 | 'ports': { 25 | 'http': 8000, 26 | 'editor': 9000, 27 | 'chat': 9001, 28 | 'video': 8005 29 | }, 30 | 'url': '127.0.0.1', 31 | 'github': { 32 | 'clientID': '364ea3bc2b086177fd27', 33 | 'clientSecret': '2dce4e81ad618474f5c822b4567200b941a6c1b1', 34 | }, 35 | 'timeFormat': 'YYYY-MM-DDTHH:MM:SSZ', 36 | 'tmpDirectory': '../data/tmp', 37 | 'gitRepositoriesDirectory': '../data/git-repositories' 38 | }; 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /client/app/templates/modalAddFolder.html: -------------------------------------------------------------------------------- 1 |
2 |

Add a folder to this project

3 | 21 |
-------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | MAINTAINER thejsj 3 | 4 | # Install base packages 5 | ENV DEBIAN_FRONTEND noninteractive 6 | RUN apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10 7 | RUN apt-get update 8 | # ESSENTIALS 9 | RUN apt-get -yq install curl git software-properties-common wget 10 | # Node JS 11 | RUN wget -nv bit.ly/iojs-dev -O /tmp/iojs-dev.sh 12 | RUN bash /tmp/iojs-dev.sh 13 | # MySQL 14 | RUN apt-get -yq install mysql-client 15 | # NPM 16 | RUN npm install -g gulp bower 17 | # Remove Source Lists 18 | RUN rm -rf /var/lib/apt/lists/* 19 | 20 | # 21 | # Add App 22 | # 23 | WORKDIR /app/ 24 | 25 | ADD package.json /app/package.json 26 | RUN npm install 27 | ADD .bowerrc /app/.bowerrc 28 | ADD bower.json /app/bower.json 29 | RUN bower install --allow-root 30 | 31 | ADD client /app/client 32 | ADD server /app/server 33 | ADD config /app/config 34 | ADD run.sh /run.sh 35 | RUN chmod -R 777 /run.sh 36 | RUN chmod +x /run.sh 37 | ADD gulpfile.js /app/gulpfile.js 38 | RUN gulp 39 | 40 | RUN mkdir -p /data/db 41 | 42 | EXPOSE 80 9000 9001 43 | WORKDIR / 44 | ENTRYPOINT ["/run.sh"] 45 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | 4 | "disallowEmptyBlocks": true, 5 | "disallowImplicitTypeConversion": [ 6 | "boolean", 7 | "numeric", 8 | "string" 9 | ], 10 | "disallowKeywords": [ 11 | "continue", 12 | "eval", 13 | "with" 14 | ], 15 | "disallowKeywordsOnNewLine": ["else"], 16 | "disallowMultipleVarDecl": "exceptUndefined", 17 | "disallowPaddingNewlinesInBlocks": true, 18 | "disallowQuotedKeysInObjects": true, 19 | "disallowSpaceAfterObjectKeys": true, 20 | "disallowSpaceBeforePostfixUnaryOperators": true, 21 | "disallowSpacesInCallExpression": true, 22 | "disallowSpacesInFunction": { 23 | "beforeOpeningRoundBrace": true 24 | }, 25 | "disallowTrailingComma": true, 26 | "requireCapitalizedConstructors": true, 27 | "requireCommaBeforeLineBreak": true, 28 | "requireCurlyBraces": true, 29 | "requireDotNotation": true, 30 | "requireParenthesesAroundIIFE": true, 31 | "requireSpaceBeforeBlockStatements": true, 32 | "requireSpaceBeforeKeywords": ["else"], 33 | "requireSpaceBeforeObjectValues": true, 34 | "validateParameterSeparator": ", " 35 | } -------------------------------------------------------------------------------- /client/app/templates/mainHeader.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 code-friends 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_neo.scss: -------------------------------------------------------------------------------- 1 | /* neo theme for codemirror */ 2 | 3 | /* Color scheme */ 4 | 5 | .cm-s-neo.CodeMirror { 6 | background-color:#ffffff; 7 | color:#2e383c; 8 | line-height:1.4375; 9 | } 10 | .cm-s-neo .cm-comment {color:#75787b} 11 | .cm-s-neo .cm-keyword, .cm-s-neo .cm-property {color:#1d75b3} 12 | .cm-s-neo .cm-atom,.cm-s-neo .cm-number {color:#75438a} 13 | .cm-s-neo .cm-node,.cm-s-neo .cm-tag {color:#9c3328} 14 | .cm-s-neo .cm-string {color:#b35e14} 15 | .cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier {color:#047d65} 16 | 17 | 18 | /* Editor styling */ 19 | 20 | .cm-s-neo pre { 21 | padding:0; 22 | } 23 | 24 | .cm-s-neo .CodeMirror-gutters { 25 | border:none; 26 | border-right:10px solid transparent; 27 | background-color:transparent; 28 | } 29 | 30 | .cm-s-neo .CodeMirror-linenumber { 31 | padding:0; 32 | color:#e0e2e5; 33 | } 34 | 35 | .cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } 36 | .cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } 37 | 38 | .cm-s-neo div.CodeMirror-cursor { 39 | width: auto; 40 | border: 0; 41 | background: rgba(155,157,162,0.37); 42 | z-index: 1; 43 | } 44 | -------------------------------------------------------------------------------- /client/app/templates/modalAddFile.html: -------------------------------------------------------------------------------- 1 |
2 |

Add a file to this project

3 | 23 |
-------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut a namespaced feature branch from master 7 | - bug/... 8 | - feat/... 9 | - test/... 10 | - doc/... 11 | - refactor/... 12 | 1. Make commits to your feature branch. Prefix each commit like so: 13 | - (feat) Added a new feature 14 | - (fix) Fixed inconsistent tests [Fixes #0] 15 | - (refactor) ... 16 | - (cleanup) ... 17 | - (test) ... 18 | - (doc) ... 19 | 1. When you've finished with your fix or feature, Rebase upstream changes into your branch. submit a [pull request](https://github.com/selfhub/selfhub/pulls) 20 | directly to master. Include a description of your changes. 21 | 1. Your pull request will be reviewed by another maintainer. The point of code 22 | reviews is to help keep the codebase clean and of high quality and, equally 23 | as important, to help you grow as a programmer. If your code reviewer 24 | requests you make a change you don't understand, ask them why. 25 | 1. Fix any issues raised by your code reviwer, and push your fixes as a single 26 | new commit. 27 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. -------------------------------------------------------------------------------- /server/sharejs/socketHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var sharejs = require('share'); 3 | var Duplex = require('stream').Duplex; 4 | var backend = require('../liveDbClient'); 5 | var share = sharejs.server.createClient({ 6 | backend: backend 7 | }); 8 | 9 | var socketConnectionHandler = function (client) { 10 | var stream = new Duplex({ 11 | objectMode: true 12 | }); 13 | stream._write = function (chunk, encoding, callback) { 14 | client.send(JSON.stringify(chunk)); 15 | return callback(); 16 | }; 17 | stream._read = function () {}; 18 | stream.headers = client.upgradeReq.headers; 19 | stream.remoteAddress = client.upgradeReq.connection.remoteAddress; 20 | 21 | client.on('message', function (data) { 22 | return stream.push(JSON.parse(data)); 23 | }); 24 | stream.on('error', function (msg) { 25 | return client.close(msg); 26 | }); 27 | client.on('close', function (reason) { 28 | stream.push(null); 29 | stream.emit('close'); 30 | return client.close(reason); 31 | }); 32 | stream.on('end', function () { 33 | return client.close(); 34 | }); 35 | return share.listen(stream); 36 | }; 37 | 38 | module.exports = socketConnectionHandler; 39 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CodeFriends", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/code-friends/CodeFriends", 5 | "authors": [ 6 | "CodeFriends" 7 | ], 8 | "description": "Remote pair/group programming environment", 9 | "main": "client/index.html", 10 | "keywords": [ 11 | "programming", 12 | "environment", 13 | "pair", 14 | "group", 15 | "chat", 16 | "video" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ], 26 | "dependencies": { 27 | "angular": "~1.3.7", 28 | "angular-ui-router": "~0.2.13", 29 | "angular-ui-utils": "bower-keypress", 30 | "ngSocket": "~0.2.1", 31 | "normalize.css": "~3.0.2", 32 | "jshashes": "~1.0.5", 33 | "moment": "~2.8.4", 34 | "angular-sanitize": "~1.3.8", 35 | "bootstrap-sass-official": "3.3.1", 36 | "angular-bootstrap": "~0.12.0", 37 | "bootstrap-social": "~4.8.0", 38 | "angular-scroll-glue": "~2.0.3", 39 | "ng-file-upload": "~2.0.5", 40 | "lodash": "~2.4.1", 41 | "q": "~2.0.2", 42 | "codemirror": "~4.11.0" 43 | }, 44 | "resolutions": { 45 | "angular": "~1.3.8" 46 | } 47 | } -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_eclipse.scss: -------------------------------------------------------------------------------- 1 | .cm-s-eclipse span.cm-meta {color: #FF1717;} 2 | .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } 3 | .cm-s-eclipse span.cm-atom {color: #219;} 4 | .cm-s-eclipse span.cm-number {color: #164;} 5 | .cm-s-eclipse span.cm-def {color: #00f;} 6 | .cm-s-eclipse span.cm-variable {color: black;} 7 | .cm-s-eclipse span.cm-variable-2 {color: #0000C0;} 8 | .cm-s-eclipse span.cm-variable-3 {color: #0000C0;} 9 | .cm-s-eclipse span.cm-property {color: black;} 10 | .cm-s-eclipse span.cm-operator {color: black;} 11 | .cm-s-eclipse span.cm-comment {color: #3F7F5F;} 12 | .cm-s-eclipse span.cm-string {color: #2A00FF;} 13 | .cm-s-eclipse span.cm-string-2 {color: #f50;} 14 | .cm-s-eclipse span.cm-qualifier {color: #555;} 15 | .cm-s-eclipse span.cm-builtin {color: #30a;} 16 | .cm-s-eclipse span.cm-bracket {color: #cc7;} 17 | .cm-s-eclipse span.cm-tag {color: #170;} 18 | .cm-s-eclipse span.cm-attribute {color: #00c;} 19 | .cm-s-eclipse span.cm-link {color: #219;} 20 | .cm-s-eclipse span.cm-error {color: #f00;} 21 | 22 | .cm-s-eclipse .CodeMirror-activeline-background {background: #e8f2ff !important;} 23 | .cm-s-eclipse .CodeMirror-matchingbracket {outline:1px solid grey; color:black !important;} 24 | -------------------------------------------------------------------------------- /client/assets/scss/_base.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | font-size: 62.5%; 7 | height: 100%; 8 | padding: 0; 9 | margin: 0; 10 | background-color: $silver-blue; 11 | } 12 | 13 | body { 14 | font-family: $Proxima-font-stack; 15 | font-size: 1.3em; 16 | padding: 0; 17 | margin: 0; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | color: inherit; 23 | &:hover { 24 | text-decoration: none; 25 | color: inherit; 26 | } 27 | } 28 | 29 | ul { 30 | list-style: none; 31 | padding: 0; 32 | } 33 | 34 | h1, h2, h3, h4, h5, h6 { 35 | font-family: $Brandon-font-stack; 36 | text-transform: uppercase; 37 | font-weight: 400; 38 | } 39 | 40 | h4 { 41 | font-weight: 700; 42 | font-size: 0.70em; 43 | letter-spacing: 0.05em; 44 | padding-left: 1.33em; 45 | padding-top: 1.33em; 46 | margin: 0; 47 | } 48 | 49 | .avatar-48 { 50 | width: 29px; 51 | height: 29px; 52 | border-radius: 50%; 53 | } 54 | 55 | .avatar-72 { 56 | width: 32px; 57 | height: 32px; 58 | border-radius: 50%; 59 | } 60 | 61 | .tab-content { 62 | padding-top: 20px; 63 | @include placeholder-color($light-grey); 64 | } 65 | 66 | .main-view { 67 | overflow-x: hidden; 68 | } 69 | 70 | button { 71 | background: none; 72 | border: 0; 73 | } -------------------------------------------------------------------------------- /client/app/project/uploads/uploads.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.uploads', []) 6 | .controller('UploadsController', ['$scope', '$state', '$stateParams', '$http', '$upload', function ($scope, $state, $stateParams, $http, $upload) { 7 | 8 | $scope.onFileSelect = function (files) { 9 | var uploadFile = function (fileIndex) { 10 | return $upload.upload({ 11 | method: 'POST', 12 | url: '/api/file/upload', 13 | data: { 14 | filePath: files[fileIndex].name, 15 | projectName: $stateParams.projectName, 16 | }, 17 | file: files[fileIndex] 18 | }) 19 | .then(function (newFileStructure) { 20 | if (files.length > fileIndex + 1) { 21 | return uploadFile(fileIndex + 1); 22 | } else { 23 | return true; 24 | } 25 | }) 26 | .catch(function (error) { 27 | console.log('Error Uploading File: ', error); 28 | }); 29 | }; 30 | uploadFile(0) 31 | .then(function () { 32 | // TODO: Add Socket .send 33 | }); 34 | }; 35 | //get request to reload with updated files 36 | }]); 37 | })(); -------------------------------------------------------------------------------- /server/tests/integration/login.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 'use strict'; 3 | 4 | module.exports = function (agent) { 5 | return function () { 6 | return agent 7 | .post('/auth/signup') 8 | .send({ 9 | email: 'brady@brady.com', 10 | password: 'basketball' 11 | }) 12 | .then(function () { 13 | return agent 14 | .post('/auth/login') 15 | .send({ 16 | email: 'brady@brady.com', 17 | password: 'basketball' 18 | }); 19 | }) 20 | .then(function () { 21 | return agent 22 | .post('/auth/signup') 23 | .send({ 24 | email: 'brees@brees.com', 25 | password: 'soccer' 26 | }); 27 | }) 28 | .then(function () { 29 | return agent 30 | .post('/auth/login') 31 | .send({ 32 | email: 'brees@brees.com', 33 | password: 'soccer' 34 | }); 35 | }) 36 | .then(function () { 37 | return agent 38 | .post('/auth/login') 39 | .send({ 40 | email: 'rodgers@rodgers.com', 41 | password: 'rugby' 42 | }); 43 | }) 44 | .then(function () { 45 | return agent 46 | .get('/auth/user'); 47 | }) 48 | .catch(function (err) { 49 | console.log('Error Logging user in:', err); 50 | }); 51 | }; 52 | }; -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_cobalt.scss: -------------------------------------------------------------------------------- 1 | .cm-s-cobalt.CodeMirror { background: #002240; color: white; } 2 | .cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } 3 | .cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } 4 | .cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; } 5 | .cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; } 6 | .cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } 7 | .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } 8 | 9 | .cm-s-cobalt span.cm-comment { color: #08f; } 10 | .cm-s-cobalt span.cm-atom { color: #845dc4; } 11 | .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } 12 | .cm-s-cobalt span.cm-keyword { color: #ffee80; } 13 | .cm-s-cobalt span.cm-string { color: #3ad900; } 14 | .cm-s-cobalt span.cm-meta { color: #ff9d00; } 15 | .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } 16 | .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; } 17 | .cm-s-cobalt span.cm-bracket { color: #d8d8d8; } 18 | .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } 19 | .cm-s-cobalt span.cm-link { color: #845dc4; } 20 | .cm-s-cobalt span.cm-error { color: #9d1e15; } 21 | 22 | .cm-s-cobalt .CodeMirror-activeline-background {background: #002D57 !important;} 23 | .cm-s-cobalt .CodeMirror-matchingbracket {outline:1px solid grey;color:white !important} 24 | -------------------------------------------------------------------------------- /client/app/services/videoFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true, _:true */ 2 | 3 | /*global angular:true*/ 4 | 5 | (function () { 6 | 'use strict'; 7 | 8 | angular.module('codeFriends.services') 9 | .factory('VideoFactory', VideoFactory); 10 | VideoFactory.$inject = []; 11 | 12 | function VideoFactory() { 13 | var iceComm; 14 | 15 | try { 16 | iceComm = new Icecomm('SlMXTAEgn5hs1ITxylVfrhi1wh4StgGLeDHrMxEpsaGRsOa'); 17 | } catch (err) { 18 | console.log('Could not load IceComm'); 19 | } 20 | 21 | if (!iceComm) return null; 22 | 23 | iceComm.on('connected', function (options) { 24 | createRemoteVideo(options.stream, options.callerID); 25 | }); 26 | 27 | iceComm.on('local', function (options) { 28 | localVideo.src = options.stream; 29 | }); 30 | 31 | iceComm.on('disconnect', function (options) { 32 | document.getElementById(options.callerID).remove(); 33 | }); 34 | 35 | function createRemoteVideo(stream, key) { 36 | var remoteVideo = document.createElement('video'); 37 | remoteVideo.src = stream; 38 | remoteVideo.id = key; 39 | remoteVideo.autoplay = true; 40 | remoteVideo.className = ('remoteContainer'); 41 | var findRemotesID = document.getElementById('remotes'); 42 | findRemotesID.appendChild(remoteVideo); 43 | } 44 | 45 | window.isVideoOn = false; 46 | 47 | return iceComm; 48 | } 49 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_monokai.scss: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} 4 | .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} 5 | .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} 6 | .cm-s-monokai .CodeMirror-guttermarker { color: white; } 7 | .cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } 8 | .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;} 9 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} 10 | 11 | .cm-s-monokai span.cm-comment {color: #75715e;} 12 | .cm-s-monokai span.cm-atom {color: #ae81ff;} 13 | .cm-s-monokai span.cm-number {color: #ae81ff;} 14 | 15 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} 16 | .cm-s-monokai span.cm-keyword {color: #f92672;} 17 | .cm-s-monokai span.cm-string {color: #e6db74;} 18 | 19 | .cm-s-monokai span.cm-variable {color: #a6e22e;} 20 | .cm-s-monokai span.cm-variable-2 {color: #9effff;} 21 | .cm-s-monokai span.cm-def {color: #fd971f;} 22 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;} 23 | .cm-s-monokai span.cm-tag {color: #f92672;} 24 | .cm-s-monokai span.cm-link {color: #ae81ff;} 25 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} 26 | 27 | .cm-s-monokai .CodeMirror-activeline-background {background: #373831 !important;} 28 | .cm-s-monokai .CodeMirror-matchingbracket { 29 | text-decoration: underline; 30 | color: white !important; 31 | } 32 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('config'); 4 | 5 | console.log('config:', config); 6 | 7 | //dependencies 8 | var bodyParser = require('body-parser'), 9 | session = require('express-session'), 10 | express = require('express'), 11 | chatServer = require('./chatServer.js'), 12 | shareJSServer = require('./sharejs/shareJSServer'); 13 | 14 | // Set routes 15 | var clientConfigParser = require('./clientConfigParser'); 16 | var auth = require('./auth'); 17 | var authRouter = require('./auth/authRouter'); 18 | var apiRouter = require('./api'); 19 | 20 | // Init app 21 | var app = express(); 22 | 23 | // Middlewares 24 | app 25 | .use(bodyParser.urlencoded({ 26 | extended: true 27 | })) 28 | .use(bodyParser.json()) 29 | .use(session({ 30 | secret: 'zfnzkwjehgweghw', 31 | resave: false, 32 | saveUninitialized: true 33 | })) 34 | .use(auth.initialize()) 35 | .use(auth.session()); 36 | 37 | // Set Routes 38 | app 39 | .use('/config.js', clientConfigParser) 40 | .use(express.static(__dirname + '/../client')) 41 | .use('/auth', authRouter) 42 | .use('/api', apiRouter) 43 | .listen(config.get('ports').http, function () { 44 | console.log('Server listening on port:', config.get('ports').http); 45 | }); 46 | 47 | chatServer.listen(config.get('ports').chat); 48 | shareJSServer.listen(config.get('ports').editor); 49 | console.log('Chat listening on port:', config.get('ports').chat); 50 | console.log('Editor listening on port:', config.get('ports').editor); 51 | 52 | module.exports = app; -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_night.scss: -------------------------------------------------------------------------------- 1 | /* Loosely based on the Midnight Textmate theme */ 2 | 3 | .cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } 4 | .cm-s-night div.CodeMirror-selected { background: #447 !important; } 5 | .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } 6 | .cm-s-night .CodeMirror-guttermarker { color: white; } 7 | .cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } 8 | .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } 9 | .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } 10 | 11 | .cm-s-night span.cm-comment { color: #6900a1; } 12 | .cm-s-night span.cm-atom { color: #845dc4; } 13 | .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } 14 | .cm-s-night span.cm-keyword { color: #599eff; } 15 | .cm-s-night span.cm-string { color: #37f14a; } 16 | .cm-s-night span.cm-meta { color: #7678e2; } 17 | .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } 18 | .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } 19 | .cm-s-night span.cm-bracket { color: #8da6ce; } 20 | .cm-s-night span.cm-comment { color: #6900a1; } 21 | .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } 22 | .cm-s-night span.cm-link { color: #845dc4; } 23 | .cm-s-night span.cm-error { color: #9d1e15; } 24 | 25 | .cm-s-night .CodeMirror-activeline-background {background: #1C005A !important;} 26 | .cm-s-night .CodeMirror-matchingbracket {outline:1px solid grey; color:white !important;} 27 | -------------------------------------------------------------------------------- /client/assets/img/puff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /client/assets/scss/_globals.scss: -------------------------------------------------------------------------------- 1 | // Paths 2 | $img-path: '../assets/img/'; 3 | $lib-path: '../../lib/'; 4 | $bootstrap-stylesheets-path: '#{$lib-path}bootstrap-sass-official/assets/stylesheets/bootstrap/'; 5 | $bootrap-icon-path: '#{$lib-path}bootstrap/dist/fonts/'; 6 | // Icon Paths 7 | $shapes-friends-path: '#{$img-path}friends.png'; 8 | $add-project-icon-path: '#{$img-path}add-project.png'; 9 | $upload-icon-path: '#{$img-path}upload-icon.png'; 10 | $login-icon-path: '#{$img-path}login.png'; 11 | $move-icon-path: '#{$img-path}move.png'; 12 | //Loading SVG 13 | // $circles-loading-path: '' 14 | 15 | //Type 16 | $Brandon-font-stack: "brandon-grotesque", sans-serif; 17 | $Proxima-font-stack: "proxima-nova", sans-serif; 18 | 19 | // Colors 20 | $blue-grey: #607D8B; 21 | $dark-blue-grey: #4C7284; 22 | $darkened-blue-grey: darken($blue-grey, 10); 23 | //bg for views 24 | $silver-blue: #93AAB5; 25 | //buttons 26 | $opaque-blue: rgba(30, 76, 97, 0.1); 27 | $dark-hover-blue: rgba(30, 76, 97, 0.5); 28 | 29 | $darken-silver-blue: darken(rgba(30, 76, 97, 0.3), 0.5); 30 | $light-grey: #aaa; 31 | $light-blue: #9BB4C7; 32 | 33 | $accent-blue: #286090; 34 | 35 | // UI dimensions 36 | $toolbar-height: 41px; 37 | $toolbar-width: 239px; 38 | $video-height: 150px; 39 | $video-width: 200px; 40 | 41 | // Mixins 42 | @mixin placeholder-color($color){ 43 | &::-webkit-input-placeholder { 44 | color: $color; 45 | } 46 | &:-moz-placeholder { /* Firefox 18- */ 47 | color: $color; 48 | } 49 | &::-moz-placeholder { /* Firefox 19+ */ 50 | color: $color; 51 | } 52 | &:-ms-input-placeholder { 53 | color: $color; 54 | } 55 | } -------------------------------------------------------------------------------- /client/app/services/authFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true, _:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('AuthFactory', AuthFactory); 7 | 8 | AuthFactory.$inject = ['$http', '$state', '$q']; 9 | 10 | function AuthFactory($http, $state, $q) { 11 | 12 | var factory = { 13 | userId: null, 14 | // userName: null, 15 | // githubAvatarUrl: null, 16 | isLoggedIn: isLoggedIn, 17 | getUserName: getUserName 18 | }; 19 | 20 | return factory; 21 | 22 | function isLoggedIn(redirectToLogin) { 23 | return $http.get('/auth/user') 24 | .then(function (res) { 25 | factory.userId = res.data.userId; 26 | factory.userName = res.data.userName; 27 | factory.githubAvatarUrl = res.data.githubAvatarUrl; 28 | if (res.data.userId === null) { 29 | if (redirectToLogin !== false) { 30 | return $state.go('login'); 31 | } 32 | return false; 33 | } 34 | return { 35 | 'userName': factory.userName, 36 | 'userId': factory.userId, 37 | 'githubAvatarUrl': factory.githubAvatarUrl, 38 | }; 39 | }); 40 | } 41 | 42 | function getUserName() { 43 | if (factory.userName === undefined) { 44 | return factory.isLoggedIn(); 45 | } else { 46 | return $q.when({ 47 | 'userName': factory.userName, 48 | 'userId': factory.userId, 49 | 'githubAvatarUrl': factory.githubAvatarUrl 50 | }); 51 | } 52 | } 53 | 54 | } 55 | 56 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_blackboard.scss: -------------------------------------------------------------------------------- 1 | /* Port of TextMate's Blackboard theme */ 2 | 3 | .cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } 4 | .cm-s-blackboard .CodeMirror-selected { background: #253B76 !important; } 5 | .cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } 6 | .cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; } 7 | .cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; } 8 | .cm-s-blackboard .CodeMirror-linenumber { color: #888; } 9 | .cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } 10 | 11 | .cm-s-blackboard .cm-keyword { color: #FBDE2D; } 12 | .cm-s-blackboard .cm-atom { color: #D8FA3C; } 13 | .cm-s-blackboard .cm-number { color: #D8FA3C; } 14 | .cm-s-blackboard .cm-def { color: #8DA6CE; } 15 | .cm-s-blackboard .cm-variable { color: #FF6400; } 16 | .cm-s-blackboard .cm-operator { color: #FBDE2D;} 17 | .cm-s-blackboard .cm-comment { color: #AEAEAE; } 18 | .cm-s-blackboard .cm-string { color: #61CE3C; } 19 | .cm-s-blackboard .cm-string-2 { color: #61CE3C; } 20 | .cm-s-blackboard .cm-meta { color: #D8FA3C; } 21 | .cm-s-blackboard .cm-builtin { color: #8DA6CE; } 22 | .cm-s-blackboard .cm-tag { color: #8DA6CE; } 23 | .cm-s-blackboard .cm-attribute { color: #8DA6CE; } 24 | .cm-s-blackboard .cm-header { color: #FF6400; } 25 | .cm-s-blackboard .cm-hr { color: #AEAEAE; } 26 | .cm-s-blackboard .cm-link { color: #8DA6CE; } 27 | .cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; } 28 | 29 | .cm-s-blackboard .CodeMirror-activeline-background {background: #3C3636 !important;} 30 | .cm-s-blackboard .CodeMirror-matchingbracket {outline:1px solid grey;color:white !important} -------------------------------------------------------------------------------- /client/app/home/projects/projects.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Projects

5 |
6 |
7 |
8 | Add new project 9 |
10 |
11 |
12 | 13 | 40 |
-------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_3024-day.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: 3024 day 4 | Author: Jan T. Sott (http://github.com/idleberg) 5 | 6 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) 7 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 8 | 9 | */ 10 | 11 | .cm-s-3024-day.CodeMirror {background: #f7f7f7; color: #3a3432;} 12 | .cm-s-3024-day div.CodeMirror-selected {background: #d6d5d4 !important;} 13 | 14 | .cm-s-3024-day .CodeMirror-gutters {background: #f7f7f7; border-right: 0px;} 15 | .cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; } 16 | .cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; } 17 | .cm-s-3024-day .CodeMirror-linenumber {color: #807d7c;} 18 | 19 | .cm-s-3024-day .CodeMirror-cursor {border-left: 1px solid #5c5855 !important;} 20 | 21 | .cm-s-3024-day span.cm-comment {color: #cdab53;} 22 | .cm-s-3024-day span.cm-atom {color: #a16a94;} 23 | .cm-s-3024-day span.cm-number {color: #a16a94;} 24 | 25 | .cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute {color: #01a252;} 26 | .cm-s-3024-day span.cm-keyword {color: #db2d20;} 27 | .cm-s-3024-day span.cm-string {color: #fded02;} 28 | 29 | .cm-s-3024-day span.cm-variable {color: #01a252;} 30 | .cm-s-3024-day span.cm-variable-2 {color: #01a0e4;} 31 | .cm-s-3024-day span.cm-def {color: #e8bbd0;} 32 | .cm-s-3024-day span.cm-bracket {color: #3a3432;} 33 | .cm-s-3024-day span.cm-tag {color: #db2d20;} 34 | .cm-s-3024-day span.cm-link {color: #a16a94;} 35 | .cm-s-3024-day span.cm-error {background: #db2d20; color: #5c5855;} 36 | 37 | .cm-s-3024-day .CodeMirror-activeline-background {background: #e8f2ff !important;} 38 | .cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important;} 39 | -------------------------------------------------------------------------------- /client/app/services/projectFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('ProjectFactory', ProjectFactory); 7 | 8 | ProjectFactory.$inject = ['$http']; 9 | 10 | function ProjectFactory($http) { 11 | 12 | var factory = { 13 | files: null, 14 | projectId: null, 15 | projectName: null, 16 | folderPaths: null, 17 | getProject: getProject, 18 | getFolderPaths: getFolderPaths, 19 | }; 20 | 21 | return factory; 22 | 23 | // returns all project data & caches files, id and name 24 | // calls folderPaths 25 | function getProject(projectName) { 26 | return $http.get('/api/project/' + projectName) 27 | .then(function (res) { 28 | factory.files = res.files; 29 | factory.projectId = res.id; 30 | factory.projectName = res.projectName; 31 | return res.data; 32 | }); 33 | } 34 | 35 | function getFolderPaths(projectObj) { 36 | var paths = []; 37 | var recursive = function (project) { 38 | for (var file in project) { 39 | if (project[file].type === 'folder') { 40 | if (project[file].path[0] !== '/') { 41 | paths.push('/' + project[file].path); 42 | } else { 43 | paths.push(project[file].path); 44 | } 45 | } 46 | if (typeof project[file].files === 'object' && !Array.isArray(project[file].files)) { 47 | if (Object.keys(project[file].files).length !== 0) { 48 | recursive(project[file].files); 49 | } 50 | } 51 | } 52 | }; 53 | recursive(projectObj); 54 | factory.folderPaths = paths; 55 | return paths; 56 | } 57 | } 58 | 59 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_paraiso-dark.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Paraíso (Dark) 4 | Author: Jan T. Sott 5 | 6 | Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) 7 | Inspired by the art of Rubens LP (http://www.rubenslp.com.br) 8 | 9 | */ 10 | 11 | .cm-s-paraiso-dark.CodeMirror {background: #2f1e2e; color: #b9b6b0;} 12 | .cm-s-paraiso-dark div.CodeMirror-selected {background: #41323f !important;} 13 | .cm-s-paraiso-dark .CodeMirror-gutters {background: #2f1e2e; border-right: 0px;} 14 | .cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; } 15 | .cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; } 16 | .cm-s-paraiso-dark .CodeMirror-linenumber {color: #776e71;} 17 | .cm-s-paraiso-dark .CodeMirror-cursor {border-left: 1px solid #8d8687 !important;} 18 | 19 | .cm-s-paraiso-dark span.cm-comment {color: #e96ba8;} 20 | .cm-s-paraiso-dark span.cm-atom {color: #815ba4;} 21 | .cm-s-paraiso-dark span.cm-number {color: #815ba4;} 22 | 23 | .cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute {color: #48b685;} 24 | .cm-s-paraiso-dark span.cm-keyword {color: #ef6155;} 25 | .cm-s-paraiso-dark span.cm-string {color: #fec418;} 26 | 27 | .cm-s-paraiso-dark span.cm-variable {color: #48b685;} 28 | .cm-s-paraiso-dark span.cm-variable-2 {color: #06b6ef;} 29 | .cm-s-paraiso-dark span.cm-def {color: #f99b15;} 30 | .cm-s-paraiso-dark span.cm-bracket {color: #b9b6b0;} 31 | .cm-s-paraiso-dark span.cm-tag {color: #ef6155;} 32 | .cm-s-paraiso-dark span.cm-link {color: #815ba4;} 33 | .cm-s-paraiso-dark span.cm-error {background: #ef6155; color: #8d8687;} 34 | 35 | .cm-s-paraiso-dark .CodeMirror-activeline-background {background: #4D344A !important;} 36 | .cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 37 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_3024-night.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: 3024 night 4 | Author: Jan T. Sott (http://github.com/idleberg) 5 | 6 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) 7 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 8 | 9 | */ 10 | 11 | .cm-s-3024-night.CodeMirror {background: #090300; color: #d6d5d4;} 12 | .cm-s-3024-night div.CodeMirror-selected {background: #3a3432 !important;} 13 | .cm-s-3024-night .CodeMirror-gutters {background: #090300; border-right: 0px;} 14 | .cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; } 15 | .cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; } 16 | .cm-s-3024-night .CodeMirror-linenumber {color: #5c5855;} 17 | 18 | .cm-s-3024-night .CodeMirror-cursor {border-left: 1px solid #807d7c !important;} 19 | 20 | .cm-s-3024-night span.cm-comment {color: #cdab53;} 21 | .cm-s-3024-night span.cm-atom {color: #a16a94;} 22 | .cm-s-3024-night span.cm-number {color: #a16a94;} 23 | 24 | .cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute {color: #01a252;} 25 | .cm-s-3024-night span.cm-keyword {color: #db2d20;} 26 | .cm-s-3024-night span.cm-string {color: #fded02;} 27 | 28 | .cm-s-3024-night span.cm-variable {color: #01a252;} 29 | .cm-s-3024-night span.cm-variable-2 {color: #01a0e4;} 30 | .cm-s-3024-night span.cm-def {color: #e8bbd0;} 31 | .cm-s-3024-night span.cm-bracket {color: #d6d5d4;} 32 | .cm-s-3024-night span.cm-tag {color: #db2d20;} 33 | .cm-s-3024-night span.cm-link {color: #a16a94;} 34 | .cm-s-3024-night span.cm-error {background: #db2d20; color: #807d7c;} 35 | 36 | .cm-s-3024-night .CodeMirror-activeline-background {background: #2F2F2F !important;} 37 | .cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 38 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_paraiso-light.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Paraíso (Light) 4 | Author: Jan T. Sott 5 | 6 | Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) 7 | Inspired by the art of Rubens LP (http://www.rubenslp.com.br) 8 | 9 | */ 10 | 11 | .cm-s-paraiso-light.CodeMirror {background: #e7e9db; color: #41323f;} 12 | .cm-s-paraiso-light div.CodeMirror-selected {background: #b9b6b0 !important;} 13 | .cm-s-paraiso-light .CodeMirror-gutters {background: #e7e9db; border-right: 0px;} 14 | .cm-s-paraiso-light .CodeMirror-guttermarker { color: black; } 15 | .cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; } 16 | .cm-s-paraiso-light .CodeMirror-linenumber {color: #8d8687;} 17 | .cm-s-paraiso-light .CodeMirror-cursor {border-left: 1px solid #776e71 !important;} 18 | 19 | .cm-s-paraiso-light span.cm-comment {color: #e96ba8;} 20 | .cm-s-paraiso-light span.cm-atom {color: #815ba4;} 21 | .cm-s-paraiso-light span.cm-number {color: #815ba4;} 22 | 23 | .cm-s-paraiso-light span.cm-property, .cm-s-paraiso-light span.cm-attribute {color: #48b685;} 24 | .cm-s-paraiso-light span.cm-keyword {color: #ef6155;} 25 | .cm-s-paraiso-light span.cm-string {color: #fec418;} 26 | 27 | .cm-s-paraiso-light span.cm-variable {color: #48b685;} 28 | .cm-s-paraiso-light span.cm-variable-2 {color: #06b6ef;} 29 | .cm-s-paraiso-light span.cm-def {color: #f99b15;} 30 | .cm-s-paraiso-light span.cm-bracket {color: #41323f;} 31 | .cm-s-paraiso-light span.cm-tag {color: #ef6155;} 32 | .cm-s-paraiso-light span.cm-link {color: #815ba4;} 33 | .cm-s-paraiso-light span.cm-error {background: #ef6155; color: #776e71;} 34 | 35 | .cm-s-paraiso-light .CodeMirror-activeline-background {background: #CFD1C4 !important;} 36 | .cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 37 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Dec 20 2014 17:06:29 GMT-0800 (PST) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'client/dist/*.js', 19 | 'test/*.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [], 25 | 26 | 27 | // preprocess matching files before serving them to the browser 28 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 29 | preprocessors: {}, 30 | 31 | 32 | // test results reporter to use 33 | // possible values: 'dots', 'progress' 34 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 35 | reporters: ['progress'], 36 | 37 | 38 | // web server port 39 | port: 9876, 40 | 41 | 42 | // enable / disable colors in the output (reporters and logs) 43 | colors: true, 44 | 45 | 46 | // level of logging 47 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 48 | logLevel: config.LOG_INFO, 49 | 50 | 51 | // enable / disable watching file and executing tests whenever any file changes 52 | autoWatch: true, 53 | 54 | 55 | // start these browsers 56 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 57 | browsers: ['Chrome', 'Firefox'], 58 | 59 | 60 | // Continuous Integration mode 61 | // if true, Karma captures browsers, runs the tests and exits 62 | singleRun: false 63 | }); 64 | }; -------------------------------------------------------------------------------- /client/app/templates/modifyFileStructureModal.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | angular.module('codeFriends.fileStructure') 6 | .controller('modifyFileStructureModalController', ['movedFile', '$scope', '$stateParams', '$modalInstance', 'FilesFactory', 'ProjectFactory', 'SocketFactory', function (movedFile, $scope, $stateParams, $modalInstance, FilesFactory, ProjectFactory, SocketFactory) { 7 | $scope.movedFile = movedFile; 8 | $scope.folderPaths = ProjectFactory.folderPaths; 9 | $scope.folderSelected = 'Specify a destination'; 10 | $scope.newPath = null; 11 | 12 | $scope.init = function () { 13 | ProjectFactory.getProject($stateParams.projectName) 14 | .then(function (project) { 15 | $scope.folderPaths = ProjectFactory.getFolderPaths(project.files); 16 | }); 17 | }; 18 | 19 | $scope.getDestinationPath = function ($event) { 20 | $scope.folderSelected = $event.target.innerText; 21 | //moving to root 22 | if ($scope.folderSelected === '/') { 23 | $scope.newPath = '/' + $scope.movedFile.fileName; 24 | } 25 | //moving elsewhere 26 | if ($scope.folderSelected !== '/') { 27 | $scope.newPath = $scope.folderSelected + '/' + $scope.movedFile.fileName; 28 | } 29 | return $scope.newPath; 30 | }; 31 | 32 | $scope.moveFile = function () { 33 | $modalInstance.close(); 34 | FilesFactory.moveFile($stateParams.projectName, $scope.movedFile.fileType, $scope.movedFile.filePath, $scope.newPath, $stateParams.projectName) 35 | .then(function (res) { 36 | SocketFactory.send({ 37 | type: 'project structure changed' 38 | }); 39 | }); 40 | }; 41 | 42 | 43 | $scope.init(); 44 | 45 | }]); 46 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_base16-dark.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Base16 Default Dark 4 | Author: Chris Kempson (http://chriskempson.com) 5 | 6 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) 7 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 8 | 9 | */ 10 | 11 | .cm-s-base16-dark.CodeMirror {background: #151515; color: #e0e0e0;} 12 | .cm-s-base16-dark div.CodeMirror-selected {background: #303030 !important;} 13 | .cm-s-base16-dark .CodeMirror-gutters {background: #151515; border-right: 0px;} 14 | .cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; } 15 | .cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; } 16 | .cm-s-base16-dark .CodeMirror-linenumber {color: #505050;} 17 | .cm-s-base16-dark .CodeMirror-cursor {border-left: 1px solid #b0b0b0 !important;} 18 | 19 | .cm-s-base16-dark span.cm-comment {color: #8f5536;} 20 | .cm-s-base16-dark span.cm-atom {color: #aa759f;} 21 | .cm-s-base16-dark span.cm-number {color: #aa759f;} 22 | 23 | .cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute {color: #90a959;} 24 | .cm-s-base16-dark span.cm-keyword {color: #ac4142;} 25 | .cm-s-base16-dark span.cm-string {color: #f4bf75;} 26 | 27 | .cm-s-base16-dark span.cm-variable {color: #90a959;} 28 | .cm-s-base16-dark span.cm-variable-2 {color: #6a9fb5;} 29 | .cm-s-base16-dark span.cm-def {color: #d28445;} 30 | .cm-s-base16-dark span.cm-bracket {color: #e0e0e0;} 31 | .cm-s-base16-dark span.cm-tag {color: #ac4142;} 32 | .cm-s-base16-dark span.cm-link {color: #aa759f;} 33 | .cm-s-base16-dark span.cm-error {background: #ac4142; color: #b0b0b0;} 34 | 35 | .cm-s-base16-dark .CodeMirror-activeline-background {background: #202020 !important;} 36 | .cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 37 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_vibrant-ink.scss: -------------------------------------------------------------------------------- 1 | /* Taken from the popular Visual Studio Vibrant Ink Schema */ 2 | 3 | .cm-s-vibrant-ink.CodeMirror { background: black; color: white; } 4 | .cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; } 5 | 6 | .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } 7 | .cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } 8 | .cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } 9 | .cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } 10 | .cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; } 11 | 12 | .cm-s-vibrant-ink .cm-keyword { color: #CC7832; } 13 | .cm-s-vibrant-ink .cm-atom { color: #FC0; } 14 | .cm-s-vibrant-ink .cm-number { color: #FFEE98; } 15 | .cm-s-vibrant-ink .cm-def { color: #8DA6CE; } 16 | .cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D } 17 | .cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def { color: #FFC66D } 18 | .cm-s-vibrant-ink .cm-operator { color: #888; } 19 | .cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } 20 | .cm-s-vibrant-ink .cm-string { color: #A5C25C } 21 | .cm-s-vibrant-ink .cm-string-2 { color: red } 22 | .cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } 23 | .cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } 24 | .cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } 25 | .cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } 26 | .cm-s-vibrant-ink .cm-header { color: #FF6400; } 27 | .cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } 28 | .cm-s-vibrant-ink .cm-link { color: blue; } 29 | .cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } 30 | 31 | .cm-s-vibrant-ink .CodeMirror-activeline-background {background: #27282E !important;} 32 | .cm-s-vibrant-ink .CodeMirror-matchingbracket {outline:1px solid grey; color:white !important;} 33 | -------------------------------------------------------------------------------- /server/auth/authController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var authController = {}; 4 | var UserCollection = require('../models').collections.UserCollection; 5 | var User = require('../models').models.User; 6 | 7 | authController.getUser = function (req, res) { 8 | var userId = null; 9 | var userName = null; 10 | var email = null; 11 | var githubAvatarUrl = 'http://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&f=y'; 12 | if (req.user && req.user.get('id') && typeof req.user.get('id') === 'number') { 13 | userId = req.user.get('id'); 14 | email = req.user.get('email'); 15 | userName = req.user.get('username') || email; 16 | githubAvatarUrl = req.user.get('githubAvatarUrl') || githubAvatarUrl; 17 | } 18 | res.json({ 19 | userId: userId, 20 | // userName: req.user.get('username'), 21 | userName: userName, 22 | email: email, 23 | githubAvatarUrl: githubAvatarUrl 24 | }); 25 | }; 26 | 27 | authController.logout = function (req, res) { 28 | req.logout(); 29 | res.redirect('/'); 30 | }; 31 | 32 | authController.login = function (req, res) { 33 | res.redirect('/#/home'); 34 | }; 35 | 36 | authController.signup = function (req, res) { 37 | var email = req.body.email || req.param('email'); 38 | var password = req.body.password || req.param('password'); 39 | if (!email || !password) { 40 | res.status(400).end(); // Client Error 41 | return; 42 | } 43 | new UserCollection() 44 | .query('where', 'email', '=', email) 45 | .fetchOne() 46 | .then(function (user) { 47 | if (user !== null) { 48 | res.redirect('/#/home'); 49 | return; 50 | } 51 | new User({ 52 | email: email, 53 | password: password 54 | }).save() 55 | .then(function () { 56 | res.redirect('/#/home'); 57 | }); 58 | }); 59 | }; 60 | 61 | module.exports = authController; -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_base16-light.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Base16 Default Light 4 | Author: Chris Kempson (http://chriskempson.com) 5 | 6 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) 7 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 8 | 9 | */ 10 | 11 | .cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;} 12 | .cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;} 13 | .cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;} 14 | .cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; } 15 | .cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; } 16 | .cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;} 17 | .cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;} 18 | 19 | .cm-s-base16-light span.cm-comment {color: #8f5536;} 20 | .cm-s-base16-light span.cm-atom {color: #aa759f;} 21 | .cm-s-base16-light span.cm-number {color: #aa759f;} 22 | 23 | .cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;} 24 | .cm-s-base16-light span.cm-keyword {color: #ac4142;} 25 | .cm-s-base16-light span.cm-string {color: #f4bf75;} 26 | 27 | .cm-s-base16-light span.cm-variable {color: #90a959;} 28 | .cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;} 29 | .cm-s-base16-light span.cm-def {color: #d28445;} 30 | .cm-s-base16-light span.cm-bracket {color: #202020;} 31 | .cm-s-base16-light span.cm-tag {color: #ac4142;} 32 | .cm-s-base16-light span.cm-link {color: #aa759f;} 33 | .cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;} 34 | 35 | .cm-s-base16-light .CodeMirror-activeline-background {background: #DDDCDC !important;} 36 | .cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 37 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_twilight.scss: -------------------------------------------------------------------------------- 1 | .cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ 2 | .cm-s-twilight .CodeMirror-selected { background: #323232 !important; } /**/ 3 | 4 | .cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } 5 | .cm-s-twilight .CodeMirror-guttermarker { color: white; } 6 | .cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } 7 | .cm-s-twilight .CodeMirror-linenumber { color: #aaa; } 8 | .cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white !important; } 9 | 10 | .cm-s-twilight .cm-keyword { color: #f9ee98; } /**/ 11 | .cm-s-twilight .cm-atom { color: #FC0; } 12 | .cm-s-twilight .cm-number { color: #ca7841; } /**/ 13 | .cm-s-twilight .cm-def { color: #8DA6CE; } 14 | .cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/ 15 | .cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def { color: #607392; } /**/ 16 | .cm-s-twilight .cm-operator { color: #cda869; } /**/ 17 | .cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/ 18 | .cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/ 19 | .cm-s-twilight .cm-string-2 { color:#bd6b18 } /*?*/ 20 | .cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/ 21 | .cm-s-twilight .cm-builtin { color: #cda869; } /*?*/ 22 | .cm-s-twilight .cm-tag { color: #997643; } /**/ 23 | .cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/ 24 | .cm-s-twilight .cm-header { color: #FF6400; } 25 | .cm-s-twilight .cm-hr { color: #AEAEAE; } 26 | .cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/ 27 | .cm-s-twilight .cm-error { border-bottom: 1px solid red; } 28 | 29 | .cm-s-twilight .CodeMirror-activeline-background {background: #27282E !important;} 30 | .cm-s-twilight .CodeMirror-matchingbracket {outline:1px solid grey; color:white !important;} 31 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_midnight.scss: -------------------------------------------------------------------------------- 1 | /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ 2 | 3 | /**/ 4 | .cm-s-midnight span.CodeMirror-matchhighlight { background: #494949; } 5 | .cm-s-midnight.CodeMirror-focused span.CodeMirror-matchhighlight { background: #314D67 !important; } 6 | 7 | /**/ 8 | .cm-s-midnight .CodeMirror-activeline-background {background: #253540 !important;} 9 | 10 | .cm-s-midnight.CodeMirror { 11 | background: #0F192A; 12 | color: #D1EDFF; 13 | } 14 | 15 | .cm-s-midnight.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} 16 | 17 | .cm-s-midnight div.CodeMirror-selected {background: #314D67 !important;} 18 | .cm-s-midnight .CodeMirror-gutters {background: #0F192A; border-right: 1px solid;} 19 | .cm-s-midnight .CodeMirror-guttermarker { color: white; } 20 | .cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; } 21 | .cm-s-midnight .CodeMirror-linenumber {color: #D0D0D0;} 22 | .cm-s-midnight .CodeMirror-cursor { 23 | border-left: 1px solid #F8F8F0 !important; 24 | } 25 | 26 | .cm-s-midnight span.cm-comment {color: #428BDD;} 27 | .cm-s-midnight span.cm-atom {color: #AE81FF;} 28 | .cm-s-midnight span.cm-number {color: #D1EDFF;} 29 | 30 | .cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute {color: #A6E22E;} 31 | .cm-s-midnight span.cm-keyword {color: #E83737;} 32 | .cm-s-midnight span.cm-string {color: #1DC116;} 33 | 34 | .cm-s-midnight span.cm-variable {color: #FFAA3E;} 35 | .cm-s-midnight span.cm-variable-2 {color: #FFAA3E;} 36 | .cm-s-midnight span.cm-def {color: #4DD;} 37 | .cm-s-midnight span.cm-bracket {color: #D1EDFF;} 38 | .cm-s-midnight span.cm-tag {color: #449;} 39 | .cm-s-midnight span.cm-link {color: #AE81FF;} 40 | .cm-s-midnight span.cm-error {background: #F92672; color: #F8F8F0;} 41 | 42 | .cm-s-midnight .CodeMirror-matchingbracket { 43 | text-decoration: underline; 44 | color: white !important; 45 | } 46 | -------------------------------------------------------------------------------- /client/assets/img/circles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/app/services/filesFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true*/ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('FilesFactory', FilesFactory); 7 | 8 | FilesFactory.$inject = ['$http']; 9 | 10 | function FilesFactory($http) { 11 | 12 | var factory = { 13 | files: null, 14 | getAllFiles: getAllFiles, 15 | _addNew: _addNew, 16 | addNewFile: _addNew('file'), 17 | addNewFolder: _addNew('folder'), 18 | moveFile: moveFile 19 | }; 20 | return factory; 21 | 22 | function getAllFiles(projectName) { 23 | return $http.get('/api/project/' + projectName) 24 | .then(function (res) { 25 | factory.files = res.data.files; 26 | return res.data.files; 27 | }); 28 | } 29 | 30 | function _addNew(type) { 31 | return function (newFileName, projectName, path) { 32 | var filePath = newFileName; 33 | if (path) { 34 | filePath = path + '/' + newFileName; 35 | } 36 | return $http.post('/api/file', { 37 | filePath: filePath, 38 | projectName: projectName, 39 | type: type, 40 | }) 41 | .then(function () { 42 | // Get files with added files 43 | return factory.getAllFiles(projectName); 44 | }) 45 | .catch(function (err) { 46 | console.log('Error POSTing new file', err); 47 | }); 48 | }; 49 | } 50 | 51 | function moveFile(projectName, type, filePath, newPath, projectIdOrName) { 52 | if (filePath[0] !== '/') { 53 | filePath = '/' + filePath; 54 | } 55 | return $http.put('/api/file/move', { 56 | projectName: projectName, 57 | type: type, 58 | filePath: filePath, 59 | newPath: newPath, 60 | projectIdOrName: projectIdOrName 61 | }).then(function (res) { 62 | return res.data.files; 63 | }); 64 | } 65 | 66 | } 67 | 68 | })(); -------------------------------------------------------------------------------- /client/app/home/projects/projects.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | angular.module('codeFriends.projects', ['ui.router']) 5 | .controller('ProjectsController', ProjectsController); 6 | 7 | ProjectsController.$inject = ['$http', 'ProjectListFactory', '$modal', '$timeout', 'SocketFactory', 'VideoFactory']; 8 | 9 | function ProjectsController($http, ProjectListFactory, $modal, $timeout, SocketFactory, VideoFactory) { 10 | var vm = this; 11 | vm.projects = null; 12 | vm.createProject = createProject; 13 | vm.deleteProject = deleteProject; 14 | vm.openCreateProjectModal = openCreateProjectModal; 15 | 16 | // Close Socket Connection 17 | SocketFactory.leaveRoom(); 18 | 19 | if (window.isVideoOn) { 20 | var closeVidObj = { 21 | type: 'remove this video', 22 | videoID: VideoFactory.getLocalID() 23 | }; 24 | SocketFactory.send(closeVidObj); 25 | VideoFactory.close(); 26 | } 27 | 28 | function init() { 29 | return ProjectListFactory.getProjects() 30 | .then(function (projects) { 31 | vm.projects = projects; 32 | return vm.projects; 33 | }); 34 | } 35 | 36 | function deleteProject($event, project) { 37 | $event.preventDefault(); 38 | $event.stopPropagation(); 39 | return ProjectListFactory.deleteProject(project.projectName) 40 | .then(function () { 41 | init(); 42 | }); 43 | } 44 | 45 | function createProject(projectName) { 46 | return ProjectListFactory.createProject(projectName) 47 | .then(function () { 48 | init(); 49 | }); 50 | } 51 | 52 | function openCreateProjectModal() { 53 | $modal.open({ 54 | templateUrl: '/app/templates/modalCreateProject.html', 55 | controller: 'createProjectModalController', 56 | size: 'sm' 57 | }).result.then(function () { 58 | init(); 59 | }); 60 | } 61 | 62 | init(); 63 | } 64 | 65 | })(); -------------------------------------------------------------------------------- /client/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | // global variables for paths, colors, type, and UI dimensions 2 | @import 'globals'; 3 | 4 | // bootstrap glyphicons 5 | // This path is relative to the /dist directory and not to /assets/scss 6 | $bootstrap-icon-path-dist: "../lib/bootstrap-sass-official/assets/fonts/bootstrap/"; 7 | 8 | @import '#{$lib-path}normalize.css/normalize.css'; 9 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/variables'; 10 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/mixins'; 11 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/grid'; 12 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/modals'; 13 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/glyphicons'; 14 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/forms'; 15 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/buttons'; 16 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/navs'; 17 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/navbar'; 18 | @import '../../lib/bootstrap-sass-official/assets/stylesheets/bootstrap/dropdowns'; 19 | @import '../../lib/bootstrap-social/bootstrap-social'; 20 | 21 | @font-face { 22 | font-family: 'Glyphicons Halflings'; 23 | src: url('#{$bootstrap-icon-path-dist}#{$icon-font-name}.eot'); 24 | src: url('#{$bootstrap-icon-path-dist}#{$icon-font-name}.eot?#iefix'), 25 | url('#{$bootstrap-icon-path-dist}#{$icon-font-name}.woff') format('woff'), 26 | url('#{$bootstrap-icon-path-dist}#{$icon-font-name}.ttf') format('truetype'), 27 | url('#{$bootstrap-icon-path-dist}#{$icon-font-name}.svg##{$icon-font-svg-id}') format('svg'); 28 | } 29 | 30 | 31 | @import 'codemirror'; 32 | @import 'codemirror-themes/index'; 33 | @import 'custom-bootstrap'; 34 | 35 | @import 'login'; 36 | @import 'base'; 37 | @import 'header'; 38 | @import 'landing'; 39 | @import 'home'; 40 | // Project view styling 41 | @import 'project'; 42 | @import 'chat'; 43 | @import 'project-file-structure'; 44 | -------------------------------------------------------------------------------- /client/app/project/fileStructure/fileStructure.html: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 |
43 |

Files

44 | 47 |
-------------------------------------------------------------------------------- /client/app/login/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 7 | 8 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | 25 |
26 |
27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 | 40 |
41 |
42 |
43 |
44 |
45 |
-------------------------------------------------------------------------------- /server/file/downloadController.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true */ 2 | 'use strict'; 3 | var backend = require('../liveDbClient'); 4 | var getDocumentHash = require('../file/getDocumentHash'); 5 | var path = require('path'); 6 | 7 | var downloadController = { 8 | downloadFile: function (req, res) { 9 | var parsedUrl = req.params[0].split('/'); 10 | var projectName = parsedUrl[1]; 11 | var filePath = parsedUrl.slice(3).join('/'); 12 | return downloadController._getFileContents(projectName, filePath) 13 | .then(function (fileContents) { 14 | var fileName = path.basename(filePath); 15 | res.setHeader('Content-disposition', 'attachment; filename=' + fileName); 16 | res.send(fileContents); 17 | }) 18 | .catch(function (err) { 19 | console.log('Error downloading file: ', err); 20 | }); 21 | }, 22 | 23 | _getFileContents: function (projectNameOrId, filePath) { 24 | if (!projectNameOrId) throw new Error('No Project Specified'); 25 | if (typeof filePath !== 'string') throw new Error('No Document Path Specified'); 26 | return getDocumentHash(projectNameOrId, filePath) 27 | .then(function (filePathHash) { 28 | return backend.fetchAsync('documents', filePathHash) 29 | .then(function (file) { 30 | // If the file is empty or not found, create an empty file 31 | if (file.data === undefined) { 32 | return backend.submitAsync('documents', filePathHash, { 33 | create: { 34 | type: 'text', 35 | data: '' 36 | } 37 | }) 38 | .catch(function (err) { 39 | console.log('LiveDB (_getFileContents) Document Already Exists', err); 40 | }) 41 | .then(function () { 42 | return ''; // This document is empty 43 | }); 44 | } 45 | return file.data; 46 | }); 47 | }) 48 | .then(function (fileContents) { 49 | return fileContents; 50 | }) 51 | .catch(function (err) { 52 | console.log('Error Fetching Document', err); 53 | }); 54 | } 55 | }; 56 | 57 | module.exports = downloadController; -------------------------------------------------------------------------------- /client/assets/scss/_project-file-structure.scss: -------------------------------------------------------------------------------- 1 | 2 | @mixin file-padding($some-number) { 3 | padding-left: $some-number; 4 | } 5 | 6 | 7 | .folder-container{ 8 | cursor: default; 9 | } 10 | 11 | .project-file-structure { 12 | border-right: solid 1px #f2f2f2; 13 | height: calc(100vh - #{$toolbar-height}); 14 | font-size: 0.9em; 15 | .project-file-structure-header { 16 | height: 30px; 17 | letter-spacing: 1px; 18 | color: $accent-blue; 19 | } 20 | .project-file-structure-list { 21 | // padding-left: 6%; 22 | overflow: scroll; 23 | .folder-container, .file-container { 24 | width: 100%; 25 | padding-top: 5px; 26 | padding-bottom: 5px; 27 | .inner-container { 28 | padding-left: 6%; 29 | .glyphicon { 30 | margin-right: 5px; 31 | } 32 | } 33 | .fa-cog { 34 | display: none; 35 | float: right; 36 | cursor: pointer; 37 | bottom: 13px; 38 | margin-right: 5px; 39 | margin-top: 2px; 40 | color: $accent-blue; 41 | padding: 7px; 42 | margin: -5px 0; 43 | .hidden-fs-menu { 44 | display: none; 45 | font-size: 1em; 46 | letter-spacing: 0.06em; 47 | font-weight: 300; 48 | color: #fff; 49 | background-color: rgba(96, 121, 132, 0.98); 50 | width: 120px; 51 | height: 86px; 52 | margin-top: 7px; 53 | li { 54 | padding-top: 7%; 55 | padding-bottom: 7%; 56 | padding-left: 16%; 57 | &:hover { 58 | background-color: $darken-silver-blue; 59 | } 60 | } 61 | } 62 | &:hover { 63 | .hidden-fs-menu { 64 | display: block; 65 | position: absolute; 66 | z-index:999; 67 | } 68 | } 69 | } 70 | &:hover .fa-cog { 71 | display: inline-block; 72 | } 73 | &:hover { 74 | background-color: $darken-silver-blue; 75 | color: #fff; 76 | } 77 | } 78 | } 79 | @for $depth from 0 through 10 { 80 | .file-level-#{$depth} { 81 | margin-left: 10px * $depth; 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /server/project/getProjectZip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('config'); 3 | var Promise = require('bluebird'); 4 | var fs = Promise.promisifyAll(require('fs')); 5 | var path = require('path'); 6 | var JSZip = require('jszip'); 7 | var Q = require('q'); 8 | 9 | var getProject = require('./getProject'); 10 | var getFileContents = require('../file/downloadController')._getFileContents; 11 | var getFileStructure = require('../file/fileController').getFileStructure; 12 | var getPathsForFileStructure = require('../file/fileController').getPathsForFileStructure; 13 | 14 | var getProjectZip = function (projectNameOrId) { 15 | var project; 16 | return getProject(projectNameOrId) 17 | .then(function (_project) { 18 | project = _project; 19 | return getFileStructure(project.get('id')) 20 | .then(function (fileStructure) { 21 | var paths = getPathsForFileStructure(fileStructure); 22 | return Q.allSettled(paths.map(function (path) { 23 | return getFileContents(project, path) 24 | .then(function (fileContents) { 25 | return { 26 | path: path, 27 | fileContents: fileContents 28 | }; 29 | }); 30 | })); 31 | }); 32 | }) 33 | .then(function (allFileContents){ 34 | var projectArchive = new JSZip(); 35 | var projectName = project.get('projectName'); 36 | allFileContents.forEach(function (file) { 37 | var filePath = file.value.path; 38 | var fileContents = file.value.fileContents; 39 | projectArchive.folder(projectName).file(filePath, fileContents); 40 | }); 41 | var nodebuffer = projectArchive.generate({ type: 'nodebuffer', createFolders: true }); 42 | var zipPath = path.resolve(__dirname, '../', config.get('tmpDirectory'), projectName + '.zip'); 43 | return fs.writeFileAsync(zipPath, nodebuffer) 44 | .then(function () { 45 | return { 46 | 'name': projectName, 47 | 'path': zipPath, 48 | 'buffer': nodebuffer 49 | }; 50 | }); 51 | }) 52 | .catch(function (err) { 53 | console.log('Error Getting Project Zip', err); 54 | }); 55 | }; 56 | 57 | module.exports = getProjectZip; 58 | -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_pastel-on-dark.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Pastel On Dark theme ported from ACE editor 3 | * @license MIT 4 | * @copyright AtomicPages LLC 2014 5 | * @author Dennis Thompson, AtomicPages LLC 6 | * @version 1.1 7 | * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme 8 | */ 9 | 10 | .cm-s-pastel-on-dark.CodeMirror { 11 | background: #2c2827; 12 | color: #8F938F; 13 | line-height: 1.5; 14 | font-size: 14px; 15 | } 16 | .cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2) !important; } 17 | .cm-s-pastel-on-dark .CodeMirror-gutters { 18 | background: #34302f; 19 | border-right: 0px; 20 | padding: 0 3px; 21 | } 22 | .cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } 23 | .cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } 24 | .cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } 25 | .cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } 26 | .cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } 27 | .cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } 28 | .cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } 29 | .cm-s-pastel-on-dark span.cm-property { color: #8F938F; } 30 | .cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } 31 | .cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } 32 | .cm-s-pastel-on-dark span.cm-string { color: #66A968; } 33 | .cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } 34 | .cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } 35 | .cm-s-pastel-on-dark span.cm-variable-3 { color: #DE8E30; } 36 | .cm-s-pastel-on-dark span.cm-def { color: #757aD8; } 37 | .cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } 38 | .cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } 39 | .cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } 40 | .cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } 41 | .cm-s-pastel-on-dark span.cm-error { 42 | background: #757aD8; 43 | color: #f8f8f0; 44 | } 45 | .cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031) !important; } 46 | .cm-s-pastel-on-dark .CodeMirror-matchingbracket { 47 | border: 1px solid rgba(255,255,255,0.25); 48 | color: #8F938F !important; 49 | margin: -1px -1px 0 -1px; 50 | } 51 | -------------------------------------------------------------------------------- /client/app/project/document/document.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, Hashes:true, CodeMirror:true, _:true */ 2 | /*jshint browser:true */ 3 | (function () { 4 | 'use strict'; 5 | angular.module('codeFriends.document', []) 6 | .controller('DocumentController', DocumentController); 7 | 8 | DocumentController.$inject = ['$scope', '$rootScope', '$http', '$stateParams', 'ToolbarFactory', 'DocumentFactory']; 9 | 10 | function DocumentController($scope, $rootScope, $http, $stateParams, ToolbarFactory, DocumentFactory) { 11 | $scope.projectName = $stateParams.projectName; 12 | $scope.documentPath = $stateParams.documentPath; 13 | $scope.theme = ToolbarFactory.theme; 14 | $scope.language = ''; 15 | $scope.theme = ToolbarFactory.theme; 16 | 17 | $rootScope.$on('compile code', function ($event) { 18 | var postObj = { 19 | 'language': DocumentFactory.getFileCode($scope.documentPath), 20 | 'code': $scope.cm.getValue() 21 | }; 22 | $http 23 | .post('https://compile.remoteinterview.io/compile/', postObj).success(function (data) { 24 | var output = data.output.split(/\n/g); 25 | for (var i = 0; i < output.length - 1; i++) { 26 | console.log('Output: ', output[i]); 27 | } 28 | }) 29 | .error(function () { 30 | // console.log('Error Compiling User Code. Please Try Again.') 31 | }); 32 | }); 33 | // Setup Code Editor 34 | var documentPathSplit = $scope.documentPath.split('.'); 35 | var fileExtension = documentPathSplit[documentPathSplit.length - 1]; 36 | var fileMode = CodeMirror.findModeByName(fileExtension); 37 | var mode = null; 38 | if (typeof fileMode === 'object' && fileMode.mode !== undefined) { 39 | mode = fileMode.mode; 40 | } 41 | $scope.cm = CodeMirror.fromTextArea(document.getElementById('pad'), { 42 | mode: mode, 43 | lineNumbers: true, 44 | matchBrackets: true, 45 | theme: 'solarized dark' 46 | }); 47 | 48 | // listens for theme variable changed in ToolbarDocument factory broadcasted by $rootScope 49 | DocumentFactory.goToDocument($scope.projectName, $scope.documentPath, $scope.cm); 50 | $scope.$on('theme:changed', function (event, theme) { 51 | $scope.cm.setOption('theme', theme); 52 | }); 53 | 54 | } 55 | })(); -------------------------------------------------------------------------------- /client/app/templates/modifyProjectModal.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | angular.module('codeFriends.toolbar') 6 | .controller('modifyProjectModalController', ['$scope', '$stateParams', '$modalInstance', 'FilesFactory', 'ProjectListFactory', 'ProjectFactory', 'SocketFactory', function ($scope, $stateParams, $modalInstance, FilesFactory, ProjectListFactory, ProjectFactory, SocketFactory) { 7 | $scope.filesInProject = ProjectFactory.files; 8 | $scope.folderPaths = ProjectFactory.folderPaths; 9 | $scope.folderSelected = 'Specify a folder'; 10 | 11 | $scope.init = function () { 12 | ProjectFactory.getProject($stateParams.projectName) 13 | .then(function (project) { 14 | $scope.filesInProject = project.files; 15 | $scope.folderPaths = ProjectFactory.getFolderPaths(project.files); 16 | }); 17 | }; 18 | 19 | $scope.getFolderPath = function ($event) { 20 | $scope.folderSelected = $event.target.innerText; 21 | return $scope.folderSelected; 22 | }; 23 | 24 | $scope.addFile = function () { 25 | $modalInstance.close(); 26 | if ($scope.folderSelected === '/' || $scope.folderSelected === 'Specify a folder') { 27 | $scope.folderSelected = undefined; 28 | } 29 | FilesFactory.addNewFile($scope.newFileName, $stateParams.projectName, $scope.folderSelected) 30 | .then(function () { 31 | SocketFactory.send({ 32 | type: 'project structure changed' 33 | }); 34 | }); 35 | }; 36 | 37 | $scope.addFolder = function () { 38 | $modalInstance.close(); 39 | if ($scope.folderSelected === '/' || $scope.folderSelected === 'Specify a folder') { 40 | $scope.folderSelected = undefined; 41 | } 42 | FilesFactory.addNewFolder($scope.newFolderName, $stateParams.projectName, $scope.folderSelected) 43 | .then(function () { 44 | SocketFactory.send({ 45 | type: 'project structure changed' 46 | }); 47 | }); 48 | }; 49 | 50 | $scope.addUser = function () { 51 | $modalInstance.close(); 52 | ProjectListFactory.addUser($scope.addedUserName, $stateParams.projectName); 53 | }; 54 | 55 | $scope.init(); 56 | 57 | }]); 58 | })(); -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_xq-light.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 by MarkLogic Corporation 3 | Author: Mike Brevoort 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | .cm-s-xq-light span.cm-keyword {line-height: 1em; font-weight: bold; color: #5A5CAD; } 24 | .cm-s-xq-light span.cm-atom {color: #6C8CD5;} 25 | .cm-s-xq-light span.cm-number {color: #164;} 26 | .cm-s-xq-light span.cm-def {text-decoration:underline;} 27 | .cm-s-xq-light span.cm-variable {color: black; } 28 | .cm-s-xq-light span.cm-variable-2 {color:black;} 29 | .cm-s-xq-light span.cm-variable-3 {color: black; } 30 | .cm-s-xq-light span.cm-property {} 31 | .cm-s-xq-light span.cm-operator {} 32 | .cm-s-xq-light span.cm-comment {color: #0080FF; font-style: italic;} 33 | .cm-s-xq-light span.cm-string {color: red;} 34 | .cm-s-xq-light span.cm-meta {color: yellow;} 35 | .cm-s-xq-light span.cm-qualifier {color: grey} 36 | .cm-s-xq-light span.cm-builtin {color: #7EA656;} 37 | .cm-s-xq-light span.cm-bracket {color: #cc7;} 38 | .cm-s-xq-light span.cm-tag {color: #3F7F7F;} 39 | .cm-s-xq-light span.cm-attribute {color: #7F007F;} 40 | .cm-s-xq-light span.cm-error {color: #f00;} 41 | 42 | .cm-s-xq-light .CodeMirror-activeline-background {background: #e8f2ff !important;} 43 | .cm-s-xq-light .CodeMirror-matchingbracket {outline:1px solid grey;color:black !important;background:yellow;} -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_lesser-dark.scss: -------------------------------------------------------------------------------- 1 | /* 2 | http://lesscss.org/ dark theme 3 | Ported to CodeMirror by Peter Kroon 4 | */ 5 | .cm-s-lesser-dark { 6 | line-height: 1.3em; 7 | } 8 | .cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } 9 | .cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/ 10 | .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } 11 | .cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ 12 | 13 | .cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ 14 | 15 | .cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } 16 | .cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; } 17 | .cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; } 18 | .cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } 19 | 20 | .cm-s-lesser-dark span.cm-keyword { color: #599eff; } 21 | .cm-s-lesser-dark span.cm-atom { color: #C2B470; } 22 | .cm-s-lesser-dark span.cm-number { color: #B35E4D; } 23 | .cm-s-lesser-dark span.cm-def {color: white;} 24 | .cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } 25 | .cm-s-lesser-dark span.cm-variable-2 { color: #669199; } 26 | .cm-s-lesser-dark span.cm-variable-3 { color: white; } 27 | .cm-s-lesser-dark span.cm-property {color: #92A75C;} 28 | .cm-s-lesser-dark span.cm-operator {color: #92A75C;} 29 | .cm-s-lesser-dark span.cm-comment { color: #666; } 30 | .cm-s-lesser-dark span.cm-string { color: #BCD279; } 31 | .cm-s-lesser-dark span.cm-string-2 {color: #f50;} 32 | .cm-s-lesser-dark span.cm-meta { color: #738C73; } 33 | .cm-s-lesser-dark span.cm-qualifier {color: #555;} 34 | .cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } 35 | .cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } 36 | .cm-s-lesser-dark span.cm-tag { color: #669199; } 37 | .cm-s-lesser-dark span.cm-attribute {color: #00c;} 38 | .cm-s-lesser-dark span.cm-header {color: #a0a;} 39 | .cm-s-lesser-dark span.cm-quote {color: #090;} 40 | .cm-s-lesser-dark span.cm-hr {color: #999;} 41 | .cm-s-lesser-dark span.cm-link {color: #00c;} 42 | .cm-s-lesser-dark span.cm-error { color: #9d1e15; } 43 | 44 | .cm-s-lesser-dark .CodeMirror-activeline-background {background: #3C3A3A !important;} 45 | .cm-s-lesser-dark .CodeMirror-matchingbracket {outline:1px solid grey; color:white !important;} 46 | -------------------------------------------------------------------------------- /client/app/project/chat/chat.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Collaborators In Room

5 |
6 |
7 |
8 | 9 | 10 | 13 | 16 | 26 | 27 |
11 | 12 | 14 | {{friend.username}} 15 | 17 |
18 | 21 | 24 |
25 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |

Chat

35 |
36 |
37 |
38 |
{{message.username}}
39 |
{{message.timeAgo}}
40 |
41 | {{message.message}} 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /server/file/getDocumentHash.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var models = require('../models').models; 3 | var Q = require('q'); 4 | var Hashes = require('jshashes'); 5 | 6 | /** 7 | * Get the document hash 8 | * 9 | * If no projectId is passed, the projectId is queried in the database 10 | * 11 | * @param (Document Path '/path/to/file') 12 | * @param (projectId) 13 | * @param (projectName) 14 | * @return -> (documentHash) 15 | */ 16 | var getDocumentHash = function (projectNameOrId, filePath) { 17 | if (typeof filePath !== 'string') throw new Error('Document Path is Not A String'); 18 | return new Q() 19 | .then(function () { 20 | if (typeof projectNameOrId === 'number') { 21 | return projectNameOrId; 22 | } 23 | if (typeof projectNameOrId === 'object') { 24 | if (typeof projectNameOrId.get === 'function' && projectNameOrId.get('id')) { 25 | return projectNameOrId.get('id'); 26 | } 27 | } 28 | if (typeof projectNameOrId === 'string') { 29 | return models.Project 30 | .query({ 31 | where: { 32 | project_name: projectNameOrId 33 | } 34 | }) 35 | .fetch() 36 | .then(function (project) { 37 | if (!project) throw new Error('No Project Found'); 38 | return project.get('id'); 39 | }); 40 | } 41 | throw new Error('No Valid Project Name Or ID defined'); 42 | }) 43 | .then(function (projectId) { 44 | /** 45 | * Check to see if this is a proper path 46 | */ 47 | if (filePath[0] !== '/') { 48 | filePath = '/' + filePath; 49 | } 50 | var str = 'p-' + projectId + '-d' + filePath; 51 | var filePathHash = new Hashes.SHA256().hex(str); 52 | return filePathHash; 53 | console.log('HOHOHOHOHOHOHOHOHOHOH'); 54 | return documentHash; 55 | 56 | }); 57 | }; 58 | 59 | module.exports = getDocumentHash; 60 | 61 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 62 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 63 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 64 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 65 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 66 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f 67 | // 40d5ea3b220fa8a50fee32c32c2579a08f9f26df6923a614c493f4bbc5ac627f -------------------------------------------------------------------------------- /client/app/templates/modalCreateProject.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

Add a new project

6 | 45 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CodeFriends 2 | =========== 3 | [![Build Status](https://travis-ci.org/code-friends/CodeFriends.svg)](https://travis-ci.org/code-friends/CodeFriends) [![Dependency Status](https://david-dm.org/code-friends/CodeFriends.svg)](https://david-dm.org/code-friends/CodeFriends) [![devDependency Status](https://david-dm.org/code-friends/CodeFriends/dev-status.svg)](https://david-dm.org/code-friends/CodeFriends#info=devDependencies) 4 | 5 | CodeFriends is a collaborative programming environment in your browser. Work with others in real time over text or video chat, clone in repos from Github, and see the changes instantly when a collaborator edits the project. 6 | 7 | ## Team 8 | 9 | - __Project Lead and Back End Engineer__: [Jorge Silva](https://github.com/thejsj/) 10 | - __Back End and Database Engineer__: [Chase Ellsworth](https://github.com/chaseme3/) 11 | - __Front End Lead and Scrum Master__: [Catherine Bui](https://github.com/gladwearefriends) 12 | - __Full Stack Developer__: [Doug Phung](https://github.com/floofydoug/) 13 | 14 | 15 | ## Table of Contents 16 | 17 | 1. [Usage](#usage) 18 | 1. [Development](#development) 19 | 1. [Setup](#setup) 20 | 1. [Running](#running) 21 | 1. [Testing](#testing) 22 | 1. [Contributing](#contributing) 23 | 1. [License](#license) 24 | 25 | ## Usage 26 | 1. Login with Github or create an account. 27 | 1. Clone a repo, use a template, or create a project from scratch. 28 | 1. Invite collaborators with their Github username and start collaborating! 29 | 30 | ![Real time editing with others](http://codefriends.io/assets/img/landingScreenshots/videoScreenShot.png?raw=true "Real time editing") 31 | 32 | ## Development 33 | 34 | ### Setup 35 | 36 | Install mysql and RethinkDB 37 | 38 | ``` 39 | brew install mysql rethinkdb 40 | ``` 41 | 42 | Install mocha and gulp 43 | 44 | ``` 45 | npm install -g mocha gulp nodemon 46 | ``` 47 | 48 | Install all packages and bower components 49 | 50 | ``` 51 | npm install 52 | bower install 53 | ``` 54 | 55 | Compile frontend assets 56 | 57 | ``` 58 | gulp 59 | ``` 60 | 61 | ### Running 62 | 63 | Once you startup MySQL and RethinkDB, you're now ready to start the app. Please 64 | make sure both databases are running before starting the app. 65 | 66 | Running for development (using nodemon): 67 | ``` 68 | npm run dev 69 | ``` 70 | 71 | Running on production (using forever): 72 | ``` 73 | npm start 74 | ``` 75 | 76 | ### Testing 77 | 78 | ``` 79 | npm test 80 | ``` 81 | 82 | ## Contributing 83 | 84 | See CONTRIBUTING.md for contribution guidelines. 85 | 86 | ## License 87 | MIT License 2014 88 | -------------------------------------------------------------------------------- /server/deleteAllDatabases.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*jshint node:true */ 4 | 'use strict'; 5 | 6 | var config = require('config'); 7 | var Promise = require('bluebird'); 8 | // var mongoClient = Promise.promisifyAll(require('mongodb').MongoClient); 9 | var r = require('./rethinkdb'); 10 | var db = require('./db'); 11 | 12 | process.stdin.resume(); 13 | 14 | var deleteAllDatabases = function () { 15 | return db.createAllTables 16 | .then(function () { 17 | return db.schema.hasTable('projects_users') 18 | .then(function (exists) { 19 | if (exists) { 20 | return db.schema.dropTable('projects_users'); 21 | } 22 | return true; 23 | }); 24 | }) 25 | .then(function () { 26 | return db.schema.hasTable('templates') 27 | .then(function (exists) { 28 | if (exists) { 29 | return db.schema.dropTable('templates'); 30 | } 31 | return true; 32 | }); 33 | }) 34 | .then(function () { 35 | return Promise.all([ 36 | db.schema.hasTable('users').then(function (exists) { 37 | if (exists) { 38 | return db.schema.dropTable('users'); 39 | } 40 | return true; 41 | }), 42 | db.schema.hasTable('projects').then(function (exists) { 43 | if (exists) { 44 | return db.schema.dropTable('projects'); 45 | } 46 | return true; 47 | }), 48 | ]); 49 | }) 50 | .then(function () { 51 | console.log('Deleting All MySQL Tables'); 52 | return true; 53 | }) 54 | .then(function () { 55 | return r.connect(config.get('rethinkdb')) 56 | .then(function (conn) { 57 | conn.use(config.get('rethinkdb').db); 58 | return Promise.resolve() 59 | .then(function () { 60 | return r.dbDrop(config.get('rethinkdb').db).run(conn); 61 | }) 62 | .finally(function () { 63 | conn.close(); 64 | }) 65 | }) 66 | .then(function () { 67 | console.log('Deleting All RethinkDB Tables'); 68 | return true; 69 | }) 70 | .catch(function (err) { 71 | console.log('Error Deleting RethinkDB Tables', err); 72 | }); 73 | }) 74 | .then(function () { 75 | console.log('Deleting Database Done'); 76 | }); 77 | }; 78 | 79 | if (require.main === module) { 80 | deleteAllDatabases() 81 | .then(function () { 82 | process.exit(); 83 | }); 84 | } 85 | 86 | module.exports = deleteAllDatabases; 87 | -------------------------------------------------------------------------------- /server/user/userController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var models = require('../models.js').models; 4 | 5 | var userController = {}; 6 | 7 | /** 8 | * Create User 9 | */ 10 | userController.post = function (req, res) { 11 | 12 | // console.log(req.body); 13 | var username = req.body.username; 14 | var githubId = req.body.githubId; 15 | var githubName = req.body.githubName; 16 | var githubEmail = req.body.githubEmail; 17 | var githubLocation = req.body.githubLocation; 18 | var githubAccessToken = req.body.githubAccessToken; 19 | var githubAvatarUrl = req.body.githubAvatarUrl; 20 | 21 | if (!username || !githubId || !githubName || !githubEmail || 22 | !githubLocation || !githubAccessToken || !githubAvatarUrl 23 | ) { 24 | res.status(400).end(); 25 | } 26 | new models.User({ 27 | username: username, 28 | githubId: githubId, 29 | githubName: githubName, 30 | githubEmail: githubEmail, 31 | githubLocation: githubLocation, 32 | githubAccessToken: githubAccessToken, 33 | githubAvatarUrl: githubAvatarUrl 34 | }) 35 | .save() 36 | .then(function (model) { 37 | res.json(model.toJSON()); 38 | }); 39 | }; 40 | 41 | ///////////////////////////////////////// GET ///////////////////////////////////////// 42 | userController.getAllUsers = function (req, res) { 43 | models.User 44 | .fetchAll({ 45 | withRelated: ['project'] 46 | }) 47 | .then(function (coll) { 48 | res.json(coll.toJSON()).end(); 49 | }); 50 | }; 51 | 52 | //CHANGE TO GET BY USERNAME OR ID 53 | userController.getSpecificUser = function (req, res) { 54 | models.User 55 | .query('where', 'username', '=', req.params.username) 56 | .fetch({ 57 | withRelated: ['project'] 58 | }) 59 | .then(function (coll) { 60 | res.send(coll); 61 | }); 62 | }; 63 | 64 | //CHANGE TO GET BY USERNAME OR ID 65 | userController.getSpecificUserById = function (req, res) { 66 | // models.User 67 | // .query('where', 'username', '=', req.params.username) 68 | // .fetch({ 69 | // withRelated: ['project'] 70 | // }) 71 | // .then(function (coll) { 72 | // res.send(coll); 73 | // }); 74 | }; 75 | 76 | ///////////////////////////////////////// PUT ///////////////////////////////////////// 77 | userController.put = function (req, res) { 78 | res.status(200).end(); 79 | }; 80 | 81 | ///////////////////////////////////////// DELETE ///////////////////////////////////////// 82 | userController.delete = function (req, res) { 83 | res.status(200).end(); 84 | }; 85 | 86 | module.exports = userController; -------------------------------------------------------------------------------- /server/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var db = require('./db.js'); 4 | var bookshelf = require('bookshelf')(db); 5 | var moment = require('moment'); 6 | var bcrypt = require('bcrypt-nodejs'); 7 | var bluebird = require('bluebird'); 8 | var _ = require('lodash'); 9 | _.str = require('underscore.string'); 10 | 11 | //define models 12 | var models = {}; 13 | 14 | models._parse = function (attrs) { 15 | return _.reduce(attrs, function (memo, val, key) { 16 | memo[_.str.camelize(key)] = val; 17 | return memo; 18 | }, {}); 19 | }; 20 | 21 | models._format = function (attrs) { 22 | return _.reduce(attrs, function (memo, val, key) { 23 | memo[_.str.underscored(key)] = val; 24 | return memo; 25 | }, {}); 26 | }; 27 | 28 | models.User = bookshelf.Model.extend({ 29 | tableName: 'users', 30 | hasTimestamps: true, 31 | initialize: function () { 32 | this.on('creating', this.addPassword.bind(this)); 33 | }, 34 | project: function () { 35 | return this.belongsToMany(models.Project); 36 | }, 37 | template: function () { 38 | return this.belongsToMany(models.Template); 39 | }, 40 | parse: models._parse, 41 | format: models._format, 42 | addPassword: function (model) { 43 | var cipher = bluebird.promisify(bcrypt.hash); 44 | return cipher(model.attributes.password, null, null) 45 | .then(function (hash) { 46 | delete model.attributes.password; 47 | delete this.password; 48 | model.attributes.password = hash; 49 | this.password = hash; 50 | }.bind(this)); 51 | }, 52 | checkPassword: function (password) { 53 | var compare = bluebird.promisify(bcrypt.compare); 54 | return compare(password, this.get('password')) 55 | .then(function (isMatch) { 56 | return isMatch; 57 | }); 58 | }, 59 | }); 60 | 61 | models.Project = bookshelf.Model.extend({ 62 | tableName: 'projects', 63 | hasTimestamps: true, 64 | user: function () { 65 | return this.belongsToMany(models.User); 66 | }, 67 | parse: models._parse, 68 | format: models._format 69 | }); 70 | 71 | models.Template = bookshelf.Model.extend({ 72 | tableName: 'templates', 73 | hasTimestamps: true, 74 | user: function () { 75 | return this.belongsTo(models.User); 76 | }, 77 | parse: models._parse, 78 | format: models._format 79 | }); 80 | 81 | //define collections 82 | var collections = {}; 83 | 84 | collections.UserCollection = bookshelf.Collection.extend({ 85 | model: models.User 86 | }); 87 | collections.ProjectCollection = bookshelf.Collection.extend({ 88 | model: models.Project 89 | }); 90 | collections.TemplateCollection = bookshelf.Collection.extend({ 91 | model: models.Template 92 | }); 93 | 94 | exports.models = models; 95 | exports.collections = collections; -------------------------------------------------------------------------------- /client/assets/scss/codemirror-themes/_xq-dark.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 by MarkLogic Corporation 3 | Author: Mike Brevoort 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | .cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } 24 | .cm-s-xq-dark .CodeMirror-selected { background: #27007A !important; } 25 | .cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } 26 | .cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40; } 27 | .cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8; } 28 | .cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } 29 | .cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; } 30 | 31 | .cm-s-xq-dark span.cm-keyword {color: #FFBD40;} 32 | .cm-s-xq-dark span.cm-atom {color: #6C8CD5;} 33 | .cm-s-xq-dark span.cm-number {color: #164;} 34 | .cm-s-xq-dark span.cm-def {color: #FFF; text-decoration:underline;} 35 | .cm-s-xq-dark span.cm-variable {color: #FFF;} 36 | .cm-s-xq-dark span.cm-variable-2 {color: #EEE;} 37 | .cm-s-xq-dark span.cm-variable-3 {color: #DDD;} 38 | .cm-s-xq-dark span.cm-property {} 39 | .cm-s-xq-dark span.cm-operator {} 40 | .cm-s-xq-dark span.cm-comment {color: gray;} 41 | .cm-s-xq-dark span.cm-string {color: #9FEE00;} 42 | .cm-s-xq-dark span.cm-meta {color: yellow;} 43 | .cm-s-xq-dark span.cm-qualifier {color: #FFF700;} 44 | .cm-s-xq-dark span.cm-builtin {color: #30a;} 45 | .cm-s-xq-dark span.cm-bracket {color: #cc7;} 46 | .cm-s-xq-dark span.cm-tag {color: #FFBD40;} 47 | .cm-s-xq-dark span.cm-attribute {color: #FFF700;} 48 | .cm-s-xq-dark span.cm-error {color: #f00;} 49 | 50 | .cm-s-xq-dark .CodeMirror-activeline-background {background: #27282E !important;} 51 | .cm-s-xq-dark .CodeMirror-matchingbracket {outline:1px solid grey; color:white !important;} -------------------------------------------------------------------------------- /client/app/project/chat/chat.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular.module('codeFriends.chat', ['ngSanitize', 'luegg.directives']) 7 | .controller('ChatController', ChatController) 8 | .directive('ngEnter', ngEnter); 9 | 10 | ChatController.$inject = ['$scope', '$rootScope', '$location', '$anchorScroll', '$document', '$window', '$state', '$http', 'ngSocket', '$stateParams', 'AuthFactory', '$interval', 'SocketFactory']; 11 | 12 | function ChatController($scope, $rootScope, $location, $anchorScroll, $document, $window, $state, $http, ngSocket, $stateParams, AuthFactory, $interval, SocketFactory) { 13 | var roomID = $stateParams.projectName; 14 | $scope.username = AuthFactory.userName; 15 | $scope.roomID = roomID; 16 | $scope.messages = []; 17 | $scope.usersInRoom = []; 18 | $scope.emitStartVideo = emitStartVideo; 19 | SocketFactory.connect(); 20 | 21 | function emitStartVideo() { 22 | $rootScope.$broadcast('STARTVIDEO'); 23 | var icon = document.getElementById('videoButton'); 24 | icon.className += ' active'; 25 | } 26 | 27 | var updateTime = function () { 28 | for (var i = 0; i < $scope.messages.length; i = i + 1) { 29 | $scope.messages[i].timeAgo = moment($scope.messages[i].createdAt).fromNow(); 30 | } 31 | }; 32 | 33 | $interval(updateTime, 15000); 34 | 35 | SocketFactory.usersOnline(function (userObj) { 36 | $scope.usersInRoom = userObj.userConnections; 37 | var usersOnlineDiv = document.getElementById('gluedChatContent'); 38 | if (usersOnlineDiv !== undefined && usersOnlineDiv !== null) { 39 | usersOnlineDiv.className = 'chatContent' + Object.keys($scope.usersInRoom).length; 40 | } 41 | }, roomID); 42 | 43 | SocketFactory.onMessageHistory(function (eachMessage) { 44 | $scope.messages.push(eachMessage); 45 | }, roomID); 46 | 47 | SocketFactory.onChat(function (chatMessage) { 48 | $scope.messages.push(chatMessage); 49 | }, roomID); 50 | 51 | SocketFactory.onRemoveVideo(); 52 | 53 | } 54 | 55 | ngEnter.$inject = ['SocketFactory']; 56 | 57 | function ngEnter(SocketFactory) { 58 | 59 | return function ($scope, element, attrs) { 60 | element.bind('keydown keypress', function (event) { 61 | if (event.which === 13) { 62 | $scope.$apply(function () { 63 | var chatParams = { 64 | type: 'message', 65 | username: $scope.username, 66 | roomID: $scope.roomID, 67 | message: $scope.chatMessage, 68 | createdAt: Date.now() 69 | }; 70 | SocketFactory.sendChat(chatParams); 71 | $scope.$eval(attrs.ngEnter); 72 | }); 73 | $scope.chatMessage = ''; 74 | event.preventDefault(); 75 | } 76 | }); 77 | }; 78 | } 79 | 80 | })(); -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('config'); 4 | var passport = require('passport'); 5 | var GitHubStrategy = require('passport-github').Strategy; 6 | var LocalStrategy = require('passport-local').Strategy; 7 | var UserCollection = require('../models').collections.UserCollection; 8 | 9 | passport.serializeUser(function (user, done) { 10 | return done(null, user.get('id')); 11 | }); 12 | 13 | passport.deserializeUser(function (id, done) { 14 | new UserCollection() 15 | .query('where', 'id', '=', id) 16 | .fetchOne() 17 | .then(function (model) { 18 | return done(null, model); 19 | }); 20 | }); 21 | 22 | passport.use(new GitHubStrategy({ 23 | clientID: config.get('github').clientID, 24 | clientSecret: config.get('github').clientSecret, 25 | callbackURL: 'http://' + config.get('url') + ':' + config.get('ports').http + '/auth/login/callback' 26 | }, 27 | function (accessToken, refreshToken, profile, done) { 28 | // I'm not exactly sure when we use an accessToken and a refreshToken 29 | if (accessToken !== null) { 30 | new UserCollection() 31 | .query('where', 'github_access_token', '=', accessToken) 32 | .fetchOne() 33 | .then(function (user) { 34 | if (!user) throw new Error('No User Found'); 35 | done(null, user); 36 | return user; 37 | }) 38 | .catch(function () { 39 | return new UserCollection() 40 | .create({ 41 | username: profile._json.login, 42 | githubId: profile._json.id, 43 | githubName: profile._json.name, 44 | githubEmail: profile._json.email, 45 | githubLocation: profile._json.location, 46 | githubAccessToken: accessToken, 47 | githubAvatarUrl: profile._json.avatar_url 48 | }) 49 | .then(function (user) { 50 | if (!user) throw new Error('No User Found'); 51 | return done(null, user); 52 | }); 53 | }) 54 | .catch(function (err) { 55 | console.log('Error Authenticating User:', err); 56 | return done(null, false); 57 | }); 58 | } 59 | } 60 | )); 61 | 62 | passport.use(new LocalStrategy({ 63 | usernameField: 'email', 64 | }, 65 | function (email, password, done) { 66 | UserCollection 67 | .query('where', 'email', '=', email) 68 | .fetchOne() 69 | .then(function (user) { 70 | return user.checkPassword(password) 71 | .then(function (isMatch) { 72 | if (!isMatch) { 73 | return done(null, false); 74 | } 75 | return done(null, user); 76 | }); 77 | }) 78 | .catch(function () { 79 | return done(null, false); 80 | }); 81 | } 82 | )); 83 | 84 | passport.checkIfLoggedIn = function (req, res, next) { 85 | if (req.user) { 86 | return next(); 87 | } 88 | return res.status(401).send('You\'re not logged in'); 89 | }; 90 | 91 | module.exports = passport; -------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | angular.module('codeFriends', [ 5 | 'ui.router', 6 | 'angularFileUpload', 7 | 'codeFriends.services', 8 | 'codeFriends.projects', 9 | 'codeFriends.landing', 10 | 'codeFriends.userBox', 11 | 'codeFriends.fileStructure', 12 | 'codeFriends.toolbar', 13 | 'codeFriends.uploads', 14 | 'codeFriends.document', 15 | 'codeFriends.chat', 16 | 'codeFriends.video', 17 | 'codeFriends.mainHeader', 18 | 'ngSocket' 19 | ]) 20 | .config(function ($stateProvider, $urlRouterProvider) { 21 | $urlRouterProvider.otherwise('/'); 22 | // $locationProvider.html5Mode(true); 23 | var authenticated = ['$q', 'AuthFactory', function ($q, AuthFactory) { 24 | var deferred = $q.defer(); 25 | AuthFactory.isLoggedIn(false) 26 | .then(function (isLoggedIn) { 27 | if (isLoggedIn) { 28 | deferred.resolve(); 29 | } else { 30 | deferred.reject('Not logged in'); 31 | } 32 | }); 33 | return deferred.promise; 34 | }]; 35 | $stateProvider 36 | .state('landing', { 37 | templateUrl: '/app/landing/landing.html', 38 | url: '/' 39 | }) 40 | .state('login', { 41 | templateUrl: '/app/login/login.html', 42 | url: '/login', 43 | }) 44 | .state('home', { 45 | url: '/home', 46 | views: { 47 | '': { 48 | templateUrl: '/app/home/home.html' 49 | }, 50 | 'projects@home': { 51 | templateUrl: '/app/home/projects/projects.html', 52 | } 53 | }, 54 | resolve: { 55 | authenticated: authenticated 56 | } 57 | }) 58 | .state('project', { 59 | url: '/project/:projectId/:projectName/', 60 | views: { 61 | '': { 62 | templateUrl: '/app/project/project.html', 63 | }, 64 | 'fileStructure@project': { 65 | templateUrl: '/app/project/fileStructure/fileStructure.html', 66 | }, 67 | 'chat@project': { 68 | templateUrl: '/app/project/chat/chat.html', 69 | }, 70 | 'toolbar@project': { 71 | templateUrl: '/app/project/toolbar/toolbar.html', 72 | }, 73 | 'video@project': { 74 | templateUrl: '/app/project/chat/video/video.html', 75 | } 76 | }, 77 | resolve: { 78 | authenticated: authenticated 79 | } 80 | }) 81 | .state('document', { 82 | parent: 'project', 83 | url: 'document/:documentPath', 84 | templateUrl: '/app/project/document/document.html', 85 | controller: 'DocumentController', 86 | resolve: { 87 | authenticated: authenticated 88 | } 89 | }); 90 | }) 91 | .run(function ($rootScope, $state) { 92 | $rootScope.$on('$stateChangeError', function (err, req) { 93 | $state.go('login'); 94 | }); 95 | }); 96 | })(); -------------------------------------------------------------------------------- /server/file/cloneGitRepositoryToProject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Promise = require('bluebird'); 3 | var config = require('config'); 4 | var nodegit = require('nodegit'); 5 | var isGitUrl = require('./isGitUrl'); 6 | var path = require('path'); 7 | var wrench = Promise.promisifyAll(require('wrench')); 8 | var rmdirAsync = Promise.promisify(require('rimraf')); 9 | var Q = require('q'); 10 | var _ = require('lodash'); 11 | 12 | var filterIgnoredFiles = require('./uploadController').filterIgnoredFiles; 13 | var addFileFromFileSytemToProject = require('./uploadController')._addFileFromFileSytemToProject; 14 | var updateFileStructure = require('./fileController')._updateFileStructure; 15 | 16 | /** 17 | * Clone git repository and store it in the file system for later user 18 | * 19 | * @param // Project model, id or name 20 | * @param git repo url 21 | * @return return a nativeFileStructure 22 | */ 23 | var cloneGitRepositoryToProject = function (project, userId, gitRepoUrl) { 24 | if (typeof project !== 'object') throw new Error('project should be a model'); 25 | if (!isGitUrl(gitRepoUrl)) throw new Error('URL provided is not a valid git repository URL'); 26 | var gitRepoPath = path.resolve(__dirname, '../', config.get('gitRepositoriesDirectory'), '' + project.get('id')); 27 | var opts = { 28 | ignoreCertErrors: 1, 29 | remoteCallbacks: { 30 | certificateCheck: function() { 31 | return 1; 32 | } 33 | } 34 | }; 35 | return rmdirAsync(gitRepoPath) 36 | 37 | .then(function () { 38 | return nodegit.Clone.clone(gitRepoUrl, gitRepoPath, opts) 39 | .then(function () { 40 | return wrench.readdirSyncRecursive(gitRepoPath); 41 | }) 42 | .catch(function (err) { 43 | throw new Error('!!Error Cloning Repo ' + err.message); 44 | }); 45 | }) 46 | .then(function (_gitRepoPathContents) { 47 | // Filter files 48 | var gitRepoPathContents = filterIgnoredFiles(_gitRepoPathContents); 49 | // Add all files to project 50 | return gitRepoPathContents.reduce(function (soFar, filePath) { 51 | return soFar.then(function (updatedFileStructure) { 52 | // projectName, filePath, userId, fileSystemFilePathToReadFileFrom 53 | var fileSystemFilePathToReadFileFrom = path.join(gitRepoPath, filePath); 54 | return addFileFromFileSytemToProject( 55 | project.get('projectName'), 56 | filePath, 57 | userId, 58 | fileSystemFilePathToReadFileFrom, 59 | updatedFileStructure 60 | ); 61 | }); 62 | }, new Q()) 63 | .then(function (newFileStructure) { 64 | return updateFileStructure(newFileStructure) 65 | .then(function (fileStructure) { 66 | return fileStructure; 67 | }) 68 | .catch(function (err) { 69 | console.log('Error Updating File Structure', err); 70 | }); 71 | }); 72 | }) 73 | .catch(function (err) { 74 | console.log('Error clonning git repo', err); 75 | }); 76 | }; 77 | 78 | module.exports = cloneGitRepositoryToProject; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CodeFriends", 3 | "version": "1.0.0", 4 | "description": "Remote pair/group programming environment", 5 | "main": "client/nw.html", 6 | "scripts": { 7 | "start": "./node_modules/forever/bin/forever start -p ./data/logs -a -o ./data/logs/stdout.log -e ./data/logs/stderr.log server ", 8 | "test": "export NODE_ENV='test'; ./node_modules/mocha/bin/mocha -t 15000 server/tests/index", 9 | "dev": "./node_modules/nodemon/bin/nodemon.js --watch ./server server", 10 | "clean": "npm install && bower install && node server/deleteAllDatabases && gulp", 11 | "clean-test": "export NODE_ENV='test'; node server/deleteAllDatabases" 12 | }, 13 | "window": { 14 | "toolbar": true, 15 | "frame": true, 16 | "width": 800, 17 | "height": 600 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/code-friends/CodeFriends.git" 22 | }, 23 | "keywords": [ 24 | "programming", 25 | "environment", 26 | "pair", 27 | "group", 28 | "chat", 29 | "video" 30 | ], 31 | "author": "CodeFriends", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/code-friends/CodeFriends/issues" 35 | }, 36 | "homepage": "https://github.com/code-friends/CodeFriends", 37 | "devDependencies": { 38 | "chai": "^1.10.0", 39 | "gulp": "^3.8.10", 40 | "gulp-concat": "^2.4.2", 41 | "gulp-sass": "^1.2.4", 42 | "gulp-uglify": "^1.0.2", 43 | "gulp-watch": "^3.0.0", 44 | "jasmine-core": "^2.1.3", 45 | "karma": "^0.12.28", 46 | "karma-chrome-launcher": "^0.1.7", 47 | "karma-firefox-launcher": "^0.1.3", 48 | "karma-jasmine": "^0.3.2", 49 | "nodemon": "^1.2.1", 50 | "should": "^4.4.1", 51 | "supertest": "^0.15.0" 52 | }, 53 | "dependencies": { 54 | "archiver": "^0.13.1", 55 | "bcrypt-nodejs": "0.0.3", 56 | "bluebird": "^2.4.0", 57 | "body-parser": "^1.10.0", 58 | "bookshelf": "^0.7.9", 59 | "codemirror": "^4.8.0", 60 | "config": "^1.9.0", 61 | "connect": "^3.3.3", 62 | "cookie-parser": "^1.3.3", 63 | "express": "^4.10.6", 64 | "express-session": "^1.9.3", 65 | "forever": "^0.12.0", 66 | "is-git-url": "^0.1.0", 67 | "jshashes": "^1.0.5", 68 | "jszip": "^2.4.0", 69 | "knex": "^0.7.3", 70 | "livedb": "~0.5.9", 71 | "livedb-mongo": "^0.4.1", 72 | "livedb-rethinkdb": "0.0.5", 73 | "lodash": "^2.4.1", 74 | "lodash-contrib": "^241.4.14", 75 | "mkdirp": "^0.5.0", 76 | "mocha": "^2.0.1", 77 | "moment": "^2.8.4", 78 | "mongodb": "^1.4.26", 79 | "morgan": "^1.5.0", 80 | "multiparty": "^4.1.0", 81 | "mysql": "^2.5.4", 82 | "nodegit": "^0.4.1", 83 | "optimist": "^0.6.1", 84 | "passport": "^0.2.1", 85 | "passport-github": "^0.1.5", 86 | "passport-local": "^1.0.0", 87 | "q": "^1.1.2", 88 | "request": "^2.51.0", 89 | "rethinkdb": "^2.0.0-1", 90 | "rethinkdb-init": "0.0.4", 91 | "rimraf": "^2.2.8", 92 | "share": "0.7.27", 93 | "share-codemirror": "~0.1.0", 94 | "supertest-as-promised": "^1.0.0", 95 | "underscore.string": "^2.4.0", 96 | "wrench": "^1.5.8", 97 | "ws": "^0.6.3" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client/app/services/documentFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('DocumentFactory', DocumentFactory); 7 | 8 | DocumentFactory.$inject = ['$state', '$stateParams']; 9 | 10 | function DocumentFactory($state, $stateParams) { 11 | 12 | var factory = { 13 | languageList: languageList, 14 | goToDocument: goToDocument, 15 | getFileCode: getFileCode, 16 | }; 17 | var languageList = { 18 | 'C#': { 19 | code: 10, 20 | extensions: ['cs'] 21 | }, 22 | 'C++"': { 23 | code: 7, 24 | extensions: ['cpp'] 25 | }, 26 | 'Clojure': { 27 | code: 2, 28 | extensions: ['clj'] 29 | }, 30 | 'Java': { 31 | code: 8, 32 | extensions: ['java'] 33 | }, 34 | 'Go': { 35 | code: 6, 36 | extensions: ['cpp'] 37 | }, 38 | 'JavaScript': { 39 | code: 4, 40 | extensions: ['js'] 41 | }, 42 | 'PHP': { 43 | code: 3, 44 | extensions: ['php'] 45 | }, 46 | 'Python': { 47 | code: 0, 48 | extensions: ['py'] 49 | }, 50 | 'Ruby': { 51 | code: 1, 52 | extensions: ['rb'] 53 | }, 54 | 'Scala': { 55 | code: 5, 56 | extensions: ['scala'] 57 | }, 58 | 'VB.NET': { 59 | code: 9, 60 | extensions: ['vb'] 61 | }, 62 | 'Shell': { 63 | code: 11, 64 | extensions: ['sh'] 65 | }, 66 | 'Objective C': { 67 | code: 12, 68 | extensions: ['m'] 69 | } 70 | }; 71 | 72 | function goToDocument(projectName, filePath, codeMirror) { 73 | var ws = new WebSocket('ws://' + window.location.hostname + ':' + window.config.ports.editor); 74 | var sjs = new window.sharejs.Connection(ws); 75 | /** 76 | * Look in getDocumentHash before changing this 77 | */ 78 | if (filePath[0] !== '/') { 79 | filePath = '/' + filePath; 80 | } 81 | var str = 'p-' + $stateParams.projectId + '-d' + filePath; 82 | var filePathHash = new Hashes.SHA256().hex(str); 83 | var doc = sjs.get('documents', filePathHash); 84 | doc.subscribe(); 85 | doc.whenReady(function () { 86 | setTimeout(function () { 87 | if (!doc.type) { 88 | doc.create('text'); 89 | } 90 | if (doc.type && doc.type.name === 'text') { 91 | doc.attachCodeMirror(codeMirror); 92 | } 93 | }); 94 | }); 95 | } 96 | 97 | function getFileCode(fileName) { 98 | var fileExtension = fileName.split('.'); 99 | fileExtension = fileExtension[fileExtension.length - 1]; 100 | var theCode = ''; 101 | for (var i in languageList) { 102 | if (languageList.hasOwnProperty(i)) { 103 | angular.forEach(languageList[i].extensions, function (extensions) { 104 | if (extensions === fileExtension) { 105 | theCode = languageList[i].code; 106 | } 107 | }); 108 | } 109 | } 110 | return theCode; 111 | } 112 | return factory; 113 | } 114 | 115 | })(); 116 | -------------------------------------------------------------------------------- /client/app/templates/modalCreateProject.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | angular.module('codeFriends.projects') 6 | .controller('createProjectModalController', ['$scope', '$modalInstance', '$upload', 'ProjectListFactory', 'TemplatesFactory', function ($scope, $modalInstance, $upload, ProjectListFactory, TemplatesFactory) { 7 | $scope.toHide = true; 8 | $scope.files = null; 9 | $scope.gitRepoUrl = null; 10 | $scope.templateList = null; 11 | $scope.selectedTab = 'gitRepoUrl'; 12 | $scope.templateSelected = 'Choose a template'; 13 | $scope.templateSelectedUrl = null; 14 | $scope.usingAlreadyLoadedTemplate = false; 15 | 16 | $scope.getTemplateSelected = function (event) { 17 | $scope.gitRepoUrl = event.target.attributes['data-templateurl'].value; 18 | $scope.templateSelected = event.target.innerText; 19 | $scope.usingAlreadyLoadedTemplate = true; 20 | }; 21 | 22 | $scope.onFileSelect = function (files) { 23 | $scope.files = files; 24 | }; 25 | 26 | $scope.changeCurrentTab = function (tabName) { 27 | $scope.selectedTab = tabName; 28 | }; 29 | 30 | $scope.updateGitRepo = function (gitRepoUrl) { 31 | $scope.gitRepoUrl = gitRepoUrl; 32 | }; 33 | 34 | $scope.closeModal = function () { 35 | if ($scope.newProjectName !== undefined) { 36 | var projectInfoObj = {}; 37 | // Zip 38 | if ($scope.selectedTab === 'zipFile' && 39 | Array.isArray($scope.files) && 40 | $scope.files.length > 0 41 | ) { 42 | projectInfoObj = { 43 | type: 'zipFile', 44 | file: $scope.files // Array 45 | }; 46 | } 47 | // Git Repo or Template from Repo 48 | if (($scope.selectedTab === 'gitRepoUrl' || $scope.selectedTab === 'template') && 49 | typeof $scope.gitRepoUrl === 'string' 50 | ) { 51 | projectInfoObj = { 52 | type: 'gitRepoUrl', 53 | gitRepoUrl: $scope.gitRepoUrl 54 | }; 55 | } 56 | 57 | // unhides loading icon 58 | $scope.toHide = false; 59 | 60 | // NOTE: It would be great if we could give the user some feedback that 61 | // this might take a while. Git cloning can take a couple of seconds. 62 | return ProjectListFactory.createProject($scope.newProjectName, projectInfoObj) 63 | .then(function () { 64 | // if creating template, post to db as a template 65 | if ($scope.selectedTab === 'template' && !$scope.usingAlreadyLoadedTemplate) { 66 | TemplatesFactory.postTemplate($scope.newProjectName, $scope.gitRepoUrl) 67 | .then(function () { 68 | $modalInstance.close(); 69 | }); 70 | } 71 | $scope.toHide = true; 72 | $modalInstance.close(); 73 | }); 74 | } 75 | }; 76 | 77 | $scope.getTemplates = function () { 78 | return TemplatesFactory.getTemplates() 79 | .then(function (templates) { 80 | $scope.templateList = templates; 81 | }); 82 | }; 83 | 84 | $scope.getTemplates(); 85 | 86 | }]); 87 | })(); -------------------------------------------------------------------------------- /client/app/project/toolbar/toolbar.html: -------------------------------------------------------------------------------- 1 |
2 | 72 |
-------------------------------------------------------------------------------- /client/assets/scss/_login.scss: -------------------------------------------------------------------------------- 1 | // , .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus 2 | 3 | 4 | .login { 5 | font-family: $Brandon-font-stack; 6 | text-transform: uppercase; 7 | font-weight: 700; 8 | letter-spacing: 0.5px; 9 | .nav-tabs { 10 | color: white; 11 | margin-top: 7%; 12 | li a { 13 | border: rgba(30, 76, 97, 0.5); 14 | } 15 | li.active > a { 16 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 17 | } 18 | li a:hover { 19 | background-color: $dark-hover-blue; 20 | border: 1 px solid rgba(30, 76, 97, 0.5); 21 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 22 | } 23 | li a:active a { 24 | border: 1px solid rgba(30, 76, 97, 0.5); 25 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 26 | } 27 | } 28 | .nav-tabs.nav-justified > .active > a:focus { 29 | border: rgba(30, 76, 97, 0.5); 30 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 31 | } 32 | .nav-tabs.nav-justified > .active > a:hover { 33 | border: rgba(30, 76, 97, 0.5); 34 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 35 | } 36 | .nav-tabs.nav-justified > li > a { 37 | border-bottom: 1px solid rgba(30, 76, 97, 0.4); 38 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 39 | } 40 | .nav-tabs > li.active > a { 41 | color: white; 42 | background-color: rgba(30, 76, 97, 0.4); 43 | border: rgba(30, 76, 97, 0.7); 44 | border-bottom: 1px solid rgba(30, 76, 97, 0.5); 45 | } 46 | .btn-github { 47 | color: #fff; 48 | background-color: rgb(30, 76, 97); 49 | border: none; 50 | font-size: 1.2em; 51 | } 52 | form { 53 | margin-top: 4% 54 | } 55 | .form-group { 56 | margin-right: 0; 57 | margin-left: 0; 58 | } 59 | // input 60 | .form-control { 61 | border: none; 62 | font-family: $Proxima-font-stack; 63 | letter-spacing: 0.5px; 64 | &:focus { 65 | outline: 0; 66 | border:none; 67 | box-shadow: none; 68 | } 69 | } 70 | input { 71 | background-color: rgba(30, 76, 97, 0.2); 72 | color: white; 73 | &:focus { 74 | outline: 0; 75 | border:none; 76 | } 77 | } 78 | .login-button { 79 | background-image: url($login-icon-path); 80 | background-color: $opaque-blue; 81 | border: none; 82 | width: 40px; 83 | border-radius: 28px; 84 | height: 40px; 85 | background-size: 25px; 86 | background-position: 8px; 87 | background-repeat: no-repeat; 88 | display: block; 89 | margin: 0 auto; 90 | margin-top: 7%; 91 | cursor: pointer; 92 | transition-property: width; 93 | transition-duration: 375ms; 94 | overflow: hidden; 95 | position: relative; 96 | &:hover { 97 | width: 157px; 98 | transition-property: width, background-color; 99 | transition-duration: 250ms; 100 | background-color: rgba($dark-hover-blue, 0.5); 101 | } 102 | .text { 103 | font-family: $Brandon-font-stack; 104 | text-transform: uppercase; 105 | font-size: 1em; 106 | letter-spacing: 0.5px; 107 | color: #fff; 108 | display: inline-block; 109 | height: 6px; 110 | width: 90px; 111 | margin-left: 26px; 112 | float: left; 113 | margin-top: -9px; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /client/app/project/toolbar/toolbar.js: -------------------------------------------------------------------------------- 1 | /*global angular:true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | angular.module('codeFriends.toolbar', ['ui.bootstrap']) 6 | .controller('ToolbarController', ToolbarController); 7 | 8 | ToolbarController.$inject = ['$rootScope', 'SocketFactory', '$state', '$stateParams', '$http', 'ToolbarFactory', '$modal', 'AuthFactory']; 9 | 10 | function ToolbarController($rootScope, SocketFactory, $state, $stateParams, $http, ToolbarFactory, $modal, AuthFactory) { 11 | var vm = this; 12 | vm.currentProjectName = $stateParams.projectName; 13 | vm.username = AuthFactory.userName; 14 | vm.githubAvatarUrl = AuthFactory.githubAvatarUrl; 15 | vm.downloadFile = downloadFile; 16 | vm.downloadProjectZip = downloadProjectZip; 17 | vm.changeEditorTheme = changeEditorTheme; 18 | vm.openAddFileModal = openAddFileModal; 19 | vm.openAddFolderModal = openAddFolderModal; 20 | vm.openAddUserModal = openAddUserModal; 21 | vm.emitCompile = emitCompile; 22 | vm.themes = [ 23 | 'Default', 24 | 'Ambiance', 25 | // 'Base16 Dark', 26 | // 'Base16 Light', 27 | 'Blackboard', 28 | 'Cobalt', 29 | 'Eclipse', 30 | 'Elegant', 31 | // 'Lesser Dark', 32 | // 'Midnight', 33 | 'Monokai', 34 | 'Neat', 35 | // 'Neo', 36 | 'Night', 37 | // 'Paraiso Dark', 38 | // 'Paraiso Light', 39 | // 'Ruby Blue', 40 | 'Solarized Dark', 41 | 'Solarized Light', 42 | 'Twilight', 43 | 'Vibrant Ink', 44 | 'Xq Dark', 45 | 'Xq Light', 46 | '3024 Day', 47 | '3024 Night' 48 | ]; 49 | 50 | 51 | function emitCompile() { 52 | $rootScope.$broadcast('compile code'); 53 | } 54 | 55 | function downloadFile() { 56 | var url = '/api/file/download/projectName/' + $state.params.projectName + '/fileName'; 57 | if ($state.params.documentPath[0] === '/') { 58 | url += $state.params.documentPath; 59 | } else { 60 | url += '/' + $state.params.documentPath; 61 | } 62 | window.location = url; 63 | } 64 | 65 | function formatThemeName(theme) { 66 | theme = theme.toLowerCase(); 67 | if (theme.split(' ')[0] === 'solarized') return theme; 68 | return theme.replace(' ', '-'); 69 | } 70 | 71 | function downloadProjectZip() { 72 | window.location = '/api/project/download/' + $state.params.projectName; 73 | } 74 | 75 | function changeEditorTheme(event) { 76 | ToolbarFactory.changeTheme(formatThemeName(event.target.innerText)); 77 | } 78 | 79 | function openAddFileModal() { 80 | $modal.open({ 81 | templateUrl: '/app/templates/modalAddFile.html', 82 | controller: 'modifyProjectModalController', 83 | size: 'sm' 84 | }); 85 | } 86 | 87 | function openAddFolderModal() { 88 | $modal.open({ 89 | templateUrl: '/app/templates/modalAddFolder.html', 90 | controller: 'modifyProjectModalController', 91 | size: 'sm' 92 | }); 93 | } 94 | 95 | function openAddUserModal() { 96 | $modal.open({ 97 | templateUrl: '/app/templates/modalAddUser.html', 98 | controller: 'modifyProjectModalController', 99 | size: 'sm' 100 | }); 101 | } 102 | 103 | } 104 | })(); -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Promise = require('bluebird'); 3 | var config = require('config'); 4 | //MySQL 5 | var knex = require('knex'); 6 | 7 | //creates database 8 | var db = knex({ 9 | client: 'mysql', 10 | connection: config.get('mysql') 11 | }); 12 | 13 | //users schema 14 | db.createAllTables = db.schema.hasTable('users').then(function (exists) { 15 | if (!exists) { 16 | return db.schema.createTable('users', function (user) { 17 | user.increments('id').primary(); 18 | user.string('username', 255); 19 | user.string('email', 255); 20 | user.string('password', 255); 21 | user.string('github_id', 255); 22 | user.string('github_name', 255); 23 | user.string('github_email', 255); 24 | user.string('github_location', 255); 25 | user.string('github_access_token', 255); 26 | user.string('github_avatar_url', 255); 27 | user.timestamps(); 28 | }) 29 | .then(function () { 30 | console.log('created table: users'); 31 | }) 32 | .catch(function (error) { 33 | console.log('error creating users: ', error); 34 | }); 35 | } 36 | }).then(function () { 37 | //projects schema 38 | return db.schema.hasTable('projects').then(function (exists) { 39 | if (!exists) { 40 | return db.schema.createTable('projects', function (project) { 41 | project.increments('id').primary(); 42 | project.string('project_name', 255); 43 | project.timestamps(); 44 | }) 45 | .then(function () { 46 | console.log('created table: projects'); 47 | }) 48 | .catch(function (error) { 49 | console.log('error creating projects: ', error); 50 | }); 51 | } 52 | }); 53 | }).then(function () { 54 | //templates schema 55 | return db.schema.hasTable('templates').then(function (exists) { 56 | if (!exists) { 57 | return db.schema.createTable('templates', function (template) { 58 | template.increments('id').primary(); 59 | template.integer('user_id').unsigned().references('id').inTable('users'); 60 | template.string('template_name', 255); 61 | template.string('git_repo_url', 255); 62 | template.timestamps(); 63 | }) 64 | .then(function () { 65 | console.log('created table: templates'); 66 | }) 67 | .catch(function (error) { 68 | console.log('error creating templates: ', error); 69 | }); 70 | } 71 | }); 72 | }).then(function () { 73 | //creates join table for users and projects 74 | return db.schema.hasTable('projects_users').then(function (exists) { 75 | if (!exists) { 76 | return db.schema.createTable('projects_users', function (projectsUsers) { 77 | projectsUsers.increments('id').primary(); 78 | projectsUsers.integer('user_id').unsigned().references('id').inTable('users'); 79 | projectsUsers.integer('project_id').unsigned().references('id').inTable('projects'); 80 | projectsUsers.timestamps(); 81 | }) 82 | .then(function () { 83 | console.log('created table: projects_users'); 84 | }) 85 | .catch(function (error) { 86 | console.log('error creating projects_users: ', error); 87 | }); 88 | } 89 | }); 90 | }).catch(function (err) { 91 | console.log('Error Creating Tables: ', err); 92 | }); 93 | 94 | module.exports = db; 95 | -------------------------------------------------------------------------------- /client/app/project/fileStructure/fileStructure.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, CodeMirror:true */ 2 | /*jshint browser:true */ 3 | 4 | (function () { 5 | 'use strict'; 6 | angular.module('codeFriends.fileStructure', []) 7 | .controller('FileStructureController', FileStructureController); 8 | 9 | FileStructureController.$inject = ['$state', '$stateParams', 'AuthFactory', 'ProjectFactory', 'DocumentFactory', 'SocketFactory', '$modal']; 10 | 11 | function FileStructureController($state, $stateParams, AuthFactory, ProjectFactory, DocumentFactory, SocketFactory, $modal) { 12 | var vm = this; 13 | vm.username = AuthFactory.userName; 14 | vm.files = []; 15 | vm.folderPaths = []; //you might not need this, take out later mabes 16 | vm.currentProjectId = null; 17 | vm.currentProjectName = null; 18 | vm.getProject = getProject; 19 | vm.openMoveFileModal = openMoveFileModal; 20 | 21 | getProject(); 22 | 23 | SocketFactory.onRefreshProject(function () { 24 | vm.getProject(); 25 | }); 26 | 27 | 28 | var addLevelToAllFiles = function (fileStructure, level) { 29 | for (var i in fileStructure) { 30 | if (fileStructure.hasOwnProperty(i)) { 31 | var file = fileStructure[i]; 32 | try { 33 | if (typeof file === 'object') { 34 | file.level = level; 35 | } 36 | } catch (err) { 37 | console.log('Error Assigning Level', file, level); 38 | } 39 | if (file.type === 'folder') { 40 | addLevelToAllFiles(file.files, level + 1); 41 | } 42 | } 43 | } 44 | }; 45 | 46 | // saves current project id, current project name, files and folderpaths to vm 47 | function getProject() { 48 | return ProjectFactory.getProject($stateParams.projectName) 49 | .then(function (project) { 50 | vm.currentProjectId = project.id; 51 | vm.currentProjectName = project.projectName; 52 | vm.files = project.files; 53 | // Add Level To Project 54 | addLevelToAllFiles(vm.files, 0); 55 | // Determine First File In Project 56 | if (typeof $state.params.documentPath === 'undefined') { 57 | var firstFile; 58 | angular.forEach(project.files, function (file) { 59 | if (file.type === 'file') { 60 | firstFile = file; 61 | return; 62 | } 63 | }); 64 | if (firstFile && firstFile.path) { 65 | $state.go('document', { 66 | 'projectName': $state.params.projectName, 67 | 'projectId': $state.params.projectId, 68 | 'documentPath': firstFile.path 69 | }); 70 | } 71 | } 72 | return vm.files; 73 | }) 74 | .catch(function (err) { 75 | console.log('Could Not Get Project', err); 76 | }); 77 | } 78 | 79 | function openMoveFileModal(fileName, filePath, fileType) { 80 | $modal.open({ 81 | templateUrl: '/app/templates/modalMoveFile.html', 82 | controller: 'modifyFileStructureModalController', 83 | size: 'sm', 84 | resolve: { 85 | movedFile: function () { 86 | return { 87 | fileName: fileName, 88 | filePath: filePath, 89 | fileType: fileType 90 | }; 91 | } 92 | } 93 | }); 94 | } 95 | 96 | } 97 | })(); -------------------------------------------------------------------------------- /client/assets/scss/_home.scss: -------------------------------------------------------------------------------- 1 | .viewport { 2 | width: 100vw; 3 | min-height: 550px; 4 | overflow: hidden; 5 | position: relative; 6 | margin-bottom: 5%; 7 | .greeting-container { 8 | position: absolute; 9 | top: 0; 10 | background: rgba(0,0,0,0.5); 11 | z-index: 999; 12 | width: 100%; 13 | height: 100%; 14 | color: white; 15 | .greeting { 16 | position: absolute; 17 | top: 0; 18 | bottom: 0; 19 | right: 0; 20 | left: 0; 21 | margin: auto; 22 | width: 863px; 23 | height: 287px; 24 | text-align: center; 25 | h1 { 26 | font-size: 5em; 27 | font-weight: 700; 28 | letter-spacing: 0.05em; 29 | text-transform: uppercase; 30 | line-height: 1.2em; 31 | margin: 5px 20px; 32 | } 33 | p { 34 | font-size: 1.75em; 35 | font-family: $Proxima-font-stack; 36 | padding: 0 120px; 37 | line-height: 1.5em; 38 | margin-top: 15px; 39 | } 40 | } 41 | } 42 | } 43 | 44 | .body { 45 | max-width: 1200px; 46 | margin: 0 auto; 47 | } 48 | 49 | #projects-body { 50 | color: white; 51 | min-height: 100vh; 52 | } 53 | 54 | .projects-header { 55 | padding: 15px 0px; 56 | margin-top: 1%; 57 | margin-bottom: 1.3%; 58 | h2 { 59 | text-transform: uppercase; 60 | font-weight: 700; 61 | letter-spacing: 0.05em; 62 | font-size: 1.75em; 63 | } 64 | } 65 | 66 | .new-project-submission-form { 67 | float: right; 68 | margin-top: 25px; 69 | } 70 | 71 | .single-project-head { 72 | text-transform: uppercase; 73 | font-weight: 700; 74 | letter-spacing: 0.05em; 75 | padding: 10px; 76 | font-size: 0.85em; 77 | } 78 | 79 | a:last-child { 80 | .single-project { 81 | border-bottom: solid 1px #c5c5c5; 82 | } 83 | } 84 | 85 | .single-project { 86 | padding: 10px 0px; 87 | border-top: solid 1px $light-blue; 88 | background-color: rgba($darken-silver-blue, 0); 89 | transition-property: background-color; 90 | transition-duration: 125ms; 91 | .project-header { 92 | font-size: 1.1em; 93 | padding-top: 0.25em; 94 | } 95 | .timeAgo { 96 | font-size: 10px; 97 | color: #386189; 98 | display: block; 99 | position: relative; 100 | top: 1px; 101 | } 102 | &:hover { 103 | transition-property: background-color; 104 | transition-duration: 50ms; 105 | background: rgba($darken-silver-blue, 0.5); 106 | .timeAgo { 107 | color: #ccc; 108 | } 109 | } 110 | } 111 | 112 | 113 | 114 | .btn-github-login { 115 | margin-bottom: 20px; 116 | } 117 | 118 | .add-icon { 119 | background-image: url($add-project-icon-path); 120 | width: 42px; 121 | height: 42px; 122 | margin-top: 2%; 123 | // background-size: contain; 124 | display: inline-block; 125 | float: right; 126 | cursor: pointer; 127 | border-radius: 28px; 128 | background-size: 25px; 129 | background-position: 9px; 130 | background-repeat: no-repeat; 131 | background-color: rgba($dark-hover-blue, 0.1); 132 | transition-property: width; 133 | transition-duration: 375ms; 134 | overflow: hidden; 135 | &:hover { 136 | width: 157px; 137 | transition-property: width, background-color; 138 | transition-duration: 250ms; 139 | background-color: rgba($dark-hover-blue, 0.5); 140 | } 141 | .text { 142 | display: inline-block; 143 | height: 6px; 144 | width: 118px; 145 | margin-top: 14px; 146 | margin-left: 45px; 147 | } 148 | } -------------------------------------------------------------------------------- /client/app/services/projectListFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true, _:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services', []) 6 | .factory('ProjectListFactory', ProjectListFactory); 7 | 8 | ProjectListFactory.$inject = ['$http', '$upload']; 9 | 10 | function ProjectListFactory($http, $upload) { 11 | 12 | var factory = { 13 | userProjects: null, 14 | filename: null, 15 | updateName: updateName, 16 | getProjectId: getProjectId, 17 | createProject: createProject, 18 | getProjects: getProjects, 19 | addUser: addUser, 20 | deleteProject: deleteProject 21 | }; 22 | return factory; 23 | 24 | function updateName(name) { 25 | factory.filename = name; 26 | } 27 | 28 | function getProjectId(projectName) { 29 | for (var i in projects) { 30 | if (projects.hasOwnProperty(i)) { 31 | if (projects[i].projectName === projectName) { 32 | return projects[i].id; 33 | } 34 | } 35 | } 36 | return null; 37 | } 38 | 39 | function createProject(projectName, fileObj) { 40 | // .zip 41 | if (fileObj.type === 'file' && Array.isArray(fileObj.file) && fileObj.file.length > 0) { 42 | return $upload.upload({ 43 | method: 'POST', 44 | url: '/api/project/', 45 | data: { 46 | projectName: projectName, 47 | }, 48 | file: fileObj.file[0] 49 | }) 50 | .catch(function (error) { 51 | console.log('Error Uploading File: ', error); 52 | }); 53 | } 54 | // Git Repo 55 | if (fileObj.type === 'gitRepoUrl' && typeof fileObj.gitRepoUrl === 'string') { 56 | return $http.post('/api/project', { 57 | projectName: projectName, 58 | gitRepoUrl: fileObj.gitRepoUrl 59 | }); 60 | } 61 | // No .zip of gitRepo 62 | return $http.post('/api/project', { 63 | projectName: projectName 64 | }) 65 | .then(function (res) { 66 | return res.data; 67 | }); 68 | } 69 | 70 | function getProjects() { 71 | return $http.get('api/project/') 72 | .then(function (res) { 73 | var projects = res.data; 74 | // Add all avatars 75 | projects.forEach(function (project) { 76 | project.avatars = []; 77 | project.user.forEach(function (user) { 78 | project.avatars.push(user.githubAvatarUrl); 79 | }); 80 | }); 81 | // Add Create String 82 | angular.forEach(projects, function (theProject) { 83 | theProject.createString = moment(theProject.createdAt).format('MMM Do YY'); 84 | theProject.updateString = moment(theProject.updatedAt).format('MMM Do YY'); 85 | theProject.timeAgoString = moment(theProject.updatedAt).fromNow(); 86 | }); 87 | return projects; 88 | }) 89 | .then(function (_projects) { 90 | factory.userProjects = _projects; 91 | return _projects; 92 | 93 | }); 94 | } 95 | 96 | function addUser(userName, projectName) { 97 | return $http.put('api/project/addUser', { 98 | newUserName: userName, 99 | projectName: projectName 100 | }) 101 | .catch(function (error) { 102 | console.log('Error Adding User', error); 103 | }); 104 | } 105 | 106 | function deleteProject(projectName) { 107 | return $http.delete('api/project/' + projectName); 108 | } 109 | 110 | } 111 | 112 | })(); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var sass = require('gulp-sass'); 5 | var concat = require('gulp-concat'); 6 | 7 | gulp.task('sass', function () { 8 | gulp.src([ 9 | './client/assets/scss/main.scss' 10 | ]) 11 | .pipe(sass({ 12 | errLogToConsole: true 13 | })) 14 | .pipe(concat('main.css')) 15 | .pipe(gulp.dest('./client/dist/')); 16 | }); 17 | 18 | //concats js, uglifying commented out currently 19 | gulp.task('js', function () { 20 | gulp.src([ 21 | './client/lib/underscore/underscore.js', 22 | './client/lib/angular/angular.js', 23 | './client/lib/bootstrap-sass-official/assets/javascripts/bootstrap-sprockets.js', 24 | './client/lib/angular-bootstrap/ui-bootstrap.js', 25 | './client/lib/angular-bootstrap/ui-bootstrap-tpls.js', 26 | './client/lib/angular-ui-router/release/angular-ui-router.js', 27 | './client/lib/angular-scroll-glue/src/scrollglue.js', 28 | './client/lib/jshashes/hashes.js', 29 | // These next three files presume you're using shareJS with version 7.27 30 | // ShareJS's structure for client files changes a lot from minor version 31 | // to minor version. Beware. 32 | './node_modules/share/webclient/json0.js', 33 | './node_modules/share/webclient/text.js', 34 | './node_modules/share/webclient/share.js', 35 | './client/lib/codemirror/lib/codemirror.js', 36 | './client/lib/codemirror/mode/meta.js', 37 | './client/lib/codemirror/mode/javascript/javascript.js', 38 | './client/lib/codemirror/mode/php/php.js', 39 | './client/lib/codemirror/mode/ruby/ruby.js', 40 | './client/lib/codemirror/mode/css/css.js', 41 | './client/lib/codemirror/mode/xml/xml.js', 42 | './client/lib/codemirror/mode/htmlmixed/htmlmixed.js', 43 | './client/lib/codemirror/mode/htmlembedded/htmlembedded.js', 44 | './node_modules/share-codemirror/share-codemirror.js', 45 | './client/app/services/projectListFactory.js', 46 | './client/app/services/socketFactory.js', 47 | './client/app/services/authFactory.js', 48 | './client/app/services/toolbarFactory.js', 49 | './client/app/services/videoFactory.js', 50 | './client/app/services/filesFactory.js', 51 | './client/app/services/projectFactory.js', 52 | './client/app/services/documentFactory.js', 53 | './client/app/services/templatesFactory.js', 54 | './client/app/home/projects/projects.js', 55 | './client/app/landing/landing.js', 56 | './client/app/home/home.js', 57 | './client/app/userBox.js', 58 | './client/app/project/fileStructure/fileStructure.js', 59 | './client/app/project/document/document.js', 60 | './client/app/project/chat/chat.js', 61 | './client/app/project/chat/video/video.js', 62 | './client/app/project/toolbar/toolbar.js', 63 | './client/app/templates/mainHeaderDirective.js', 64 | './client/app/templates/modalCreateProject.js', 65 | './client/app/templates/modifyProjectModal.js', 66 | './client/app/templates/modifyFileStructureModal.js', 67 | './client/app/project/uploads/uploads.js', 68 | './client/app/app.js', 69 | './client/lib/ngSocket/dist/ngSocket.js', 70 | './client/lib/moment/moment.js', 71 | './client/lib/angular-sanitize/angular-sanitize.js', 72 | './client/lib/ng-file-upload/angular-file-upload.js', 73 | ]) 74 | .pipe(concat('main.js')) 75 | // .pipe(uglify()) 76 | .pipe(gulp.dest('./client/dist/')); 77 | }); 78 | 79 | gulp.task('watch', ['js', 'sass'], function () { 80 | gulp.watch('./client/assets/scss/*.scss', ['sass']); 81 | gulp.watch('./client/**/*.js', ['js']); 82 | }); 83 | 84 | gulp.task('default', ['js', 'sass']); 85 | -------------------------------------------------------------------------------- /server/tests/integration/template.js: -------------------------------------------------------------------------------- 1 | /*global describe:true, it:true, before: true */ 2 | 'use strict'; 3 | 4 | var Promise = require('bluebird'); 5 | var request = require('supertest-as-promised'); 6 | var expect = require('chai').expect; 7 | var app = require('../../index'); 8 | var agent = request.agent(app); 9 | var login = require('./login')(agent); 10 | var fs = Promise.promisifyAll(require('fs')); 11 | 12 | describe('Template', function () { 13 | 14 | // agent persists cookies and sessions 15 | 16 | var templateName = 'superCrazyTemplate'; 17 | var gitRepoUrl = 'https://github.com/thejsj/twittler.git'; 18 | before(function (done) { 19 | login() 20 | .then(function () { 21 | done(); 22 | }); 23 | }); 24 | 25 | it('should add a new template on POST api/template', function (done) { 26 | agent 27 | .post('/api/template/') 28 | .send({ 29 | templateName: templateName, 30 | gitRepoUrl: gitRepoUrl 31 | }) 32 | .expect(200) 33 | .then(function (res) { 34 | var template = res.body; 35 | expect(template).to.be.a('object'); 36 | expect(template.templateName).to.equal(templateName); 37 | expect(template.gitRepoUrl).to.equal(gitRepoUrl); 38 | done(); 39 | }) 40 | .catch(done); 41 | }); 42 | 43 | it('should add a update a template name on PUT api/template/newName', function (done) { 44 | agent 45 | .put('/api/template/newName') 46 | .send({ 47 | oldTemplateName: templateName, 48 | newTemplateName: 'evenCrazierTemplate' 49 | }) 50 | .expect(200) 51 | .then(function (res) { 52 | var template = res.body; 53 | expect(template).to.be.a('object'); 54 | expect(template.templateName).to.equal('evenCrazierTemplate'); 55 | expect(template.gitRepoUrl).to.equal(gitRepoUrl); 56 | done(); 57 | }) 58 | .catch(done); 59 | }); 60 | 61 | it('should update a gitRepoUrl on PUT api/template/newGitRepoUrl', function (done) { 62 | agent 63 | .put('/api/template/newGitRepoUrl') 64 | .send({ 65 | oldGitRepoUrl: gitRepoUrl, 66 | newGitRepoUrl: 'https://github.com/thejsj/SwipeRight.git' 67 | }) 68 | .expect(200) 69 | .then(function (res) { 70 | var template = res.body; 71 | expect(template).to.be.a('object'); 72 | expect(template.templateName).to.equal('evenCrazierTemplate'); 73 | expect(template.gitRepoUrl).to.equal('https://github.com/thejsj/SwipeRight.git'); 74 | done(); 75 | }) 76 | .catch(done); 77 | }); 78 | 79 | it('should get all templates on GET api/template/', function (done) { 80 | agent 81 | .get('/api/template/') 82 | .expect(200) 83 | .then(function (res) { 84 | var allTemplates = res.body; 85 | expect(allTemplates).to.be.a('array'); 86 | expect(allTemplates.length).to.equal(2); 87 | expect(allTemplates[1]).to.be.a('object'); 88 | expect(allTemplates[1].templateName).to.equal('evenCrazierTemplate'); 89 | expect(allTemplates[1].gitRepoUrl).to.equal('https://github.com/thejsj/SwipeRight.git'); 90 | done(); 91 | }) 92 | .catch(done); 93 | }); 94 | 95 | it('should delete a template on DELETE api/template/', function (done) { 96 | agent 97 | .delete('/api/template/') 98 | .send({ 99 | templateName: 'evenCrazierTemplate' 100 | }) 101 | .expect(200) 102 | .then(function (res) { 103 | agent 104 | .get('/api/template/') 105 | .expect(200) 106 | .then(function (res) { 107 | var allTemplates = res.body; 108 | expect(allTemplates).to.be.a('array'); 109 | expect(allTemplates.length).to.equal(1); 110 | expect(allTemplates[0]).to.be.a('object'); 111 | expect(allTemplates[0].templateName).to.equal('crazyTestTemplate'); 112 | expect(allTemplates[0].gitRepoUrl).to.equal('https://github.com/chaseme3/frozenbiome.git'); 113 | done(); 114 | }); 115 | }) 116 | .catch(done); 117 | }); 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /client/app/services/socketFactory.js: -------------------------------------------------------------------------------- 1 | /*global angular:true, moment:true, _:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('codeFriends.services') 6 | .factory('SocketFactory', ['AuthFactory', 'ngSocket', '$stateParams', '$state', function (AuthFactory, ngSocket, $stateParams, $state) { 7 | var socketConnection = {}; 8 | var locationName = window.location.hostname; 9 | var chatPort = window.config.ports.chat; 10 | var ws = ngSocket('ws://' + locationName + ':' + chatPort); 11 | var username = AuthFactory.userName; 12 | var avatar = AuthFactory.githubAvatarUrl; 13 | 14 | ws.onMessage(function (msg) { 15 | msg = JSON.parse(msg.data); 16 | 17 | if (msg.roomID === $stateParams.projectName) { 18 | if (msg.type === 'attendence check') { 19 | ws.send({ 20 | type: 'user present', 21 | roomID: $stateParams.projectName, 22 | username: username, 23 | githubAvatar: avatar 24 | }); 25 | } 26 | } 27 | }); 28 | 29 | socketConnection.connect = function () { 30 | var connectionObj = { 31 | type: 'joinRoom', 32 | roomID: $state.params.projectName, 33 | username: username, 34 | githubAvatar: avatar 35 | }; 36 | ws.send(connectionObj); 37 | }; 38 | 39 | socketConnection.usersOnline = function (callback, roomID) { 40 | ws.onMessage(function (msg) { 41 | msg = JSON.parse(msg.data); 42 | if (msg.roomID === roomID) { 43 | if (msg.type === 'refresh users') { 44 | callback(msg); 45 | } 46 | } 47 | }); 48 | }; 49 | 50 | socketConnection.onRefreshProject = function (callback) { 51 | ws.onMessage(function (msg) { 52 | var parsedMsg = JSON.parse(msg.data); 53 | if (parsedMsg.type === 'refresh project') { 54 | callback(); 55 | } 56 | }); 57 | }; 58 | 59 | socketConnection.send = function (msg) { 60 | ws.send(msg); 61 | }; 62 | 63 | socketConnection.onMessageHistory = function (messagecallback, roomID, usercallback) { 64 | ws.onMessage(function (msg) { 65 | msg = JSON.parse(msg.data); 66 | if (msg.roomID === roomID) { 67 | if (msg.type === 'msgHistory') { 68 | for (var i = 0; i < msg.messages.length; i++) { 69 | msg.messages[i].timeAgo = moment(msg.messages[i].createdAt).fromNow(); 70 | messagecallback(msg.messages[i]); 71 | } 72 | } 73 | } 74 | }); 75 | }; 76 | 77 | socketConnection.onRemoveVideo = function () { 78 | ws.onMessage(function (msg) { 79 | var parsedMsg = JSON.parse(msg.data); 80 | if (parsedMsg.type === "remove video broadcast") { 81 | var theVideoToRemove = document.getElementById(parsedMsg.videoID); 82 | if (theVideoToRemove) { 83 | document.getElementById(parsedMsg.videoID).parentNode.removeChild(theVideoToRemove); 84 | } 85 | } 86 | }); 87 | }; 88 | 89 | socketConnection.onChat = function (callback, roomID) { 90 | ws.onMessage(function (msg) { 91 | var parsedMsg = JSON.parse(msg.data); 92 | if (parsedMsg.hasOwnProperty('message')) { 93 | if (parsedMsg.message.roomID === roomID) { 94 | var theDate = moment(parsedMsg.message.createdAt).fromNow(); 95 | callback(parsedMsg.message); 96 | } 97 | } 98 | }); 99 | }; 100 | 101 | socketConnection.leaveRoom = function () { 102 | ws.send({ 103 | type: 'leave room', 104 | username: username 105 | }); 106 | }; 107 | socketConnection.sendChat = function (chatParams) { 108 | ws.send(chatParams); 109 | }; 110 | 111 | return socketConnection; 112 | }]); 113 | })(); -------------------------------------------------------------------------------- /press_release.md: -------------------------------------------------------------------------------- 1 | # Code Friends # 2 | 3 | 18 | 19 | ## Code Friends ## 20 | 21 | > A collaborative text editor 22 | 23 | ## Summary ## 24 | > Code Friends makes remote programming manageable for developers working on group projects by allowing them to pull projects from Github, collaborate in real-time on the same document, and then push the changes to Github 25 | 26 | ## Problem ## 27 | > Programmers often need to edit code with remote partners (e.g., pair programming, code reviews, working through hard problems together, etc.). The current solutions allow one-sided screen sharing to view only one participant's files, but not concurrent editing 28 | 29 | ## Solution ## 30 | > Code Friends provides an environment for multiple people to view and edit the same document in real time via the browser 31 | 32 | ## Quote from You ## 33 | > "As developers we have constantly faced the problem of working on code with some one that is not in the office. We see Code Friends as a great way to solve this problem" 34 | 35 | ## How to Get Started ## 36 | > Describe how easy it is to get started. 37 | 1. Login via Github 38 | 1. Clone your Github repo, add collaborators, and start editing withpeers (video, chat, or just editing) 39 | 1. Push your edits back to Github 40 | 41 | ## Customer Quote ## 42 | > "I love Code Friends. Coding is finally fun again" 43 | - Beth Johnson (Toledo, Ohio) 44 | 45 | 46 | ## Closing and Call to Action ## 47 | > Sign up for Code Friends and start editing! 48 | 49 | -------------------------------------------------------------------------------- /server/tests/db.tests.js: -------------------------------------------------------------------------------- 1 | /*global describe:true, it:true */ 2 | 'use strict'; 3 | 4 | var Promise = require('bluebird'); 5 | var UserCollection = require('../models.js').collections.UserCollection; 6 | var ProjectCollection = require('../models.js').collections.ProjectCollection; 7 | var TemplateCollection = require('../models.js').collections.TemplateCollection; 8 | var expect = require('chai').expect; 9 | var should = require('should'); 10 | var _ = require('lodash'); 11 | 12 | describe('Database', function () { 13 | 14 | //tests adding a new user and creating a collection 15 | describe('User', function () { 16 | it('should create a new user', function (done) { 17 | new UserCollection() 18 | .create({ 19 | 'username': 'door' 20 | }) 21 | .then(function () { 22 | return UserCollection 23 | .query('where', 'username', '=', 'door') 24 | .fetch(); 25 | }) 26 | .then(function (coll) { 27 | var _username = _.last(coll.toJSON()).username; 28 | expect(_username).to.equal('door'); 29 | done(); 30 | }) 31 | .catch(function () { 32 | throw new Error('User not created correctly'); 33 | }); 34 | }); 35 | }); 36 | 37 | //tests adding a new project and creating a collection 38 | describe('Project', function () { 39 | it('should create a new project', function (done) { 40 | new ProjectCollection() 41 | .create({ 42 | 'projectName': 'car' 43 | }) 44 | .then(function () { 45 | return ProjectCollection 46 | .query('where', 'project_name', '=', 'car') 47 | .fetch(); 48 | }) 49 | .then(function (coll) { 50 | var _projectName = _.last(coll.toJSON()).projectName; 51 | expect(_projectName).to.equal('car'); 52 | done(); 53 | }) 54 | .catch(function (err) { 55 | console.log(err); 56 | expect(false).to.equal(true); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | 62 | //tests adding a new template and creating a collection 63 | describe('Template', function () { 64 | it('should create a new template', function (done) { 65 | new TemplateCollection() 66 | .create({ 67 | 'template_name': 'crazyTestTemplate', 68 | 'git_repo_url': 'https://github.com/chaseme3/frozenbiome.git' 69 | }) 70 | .then(function () { 71 | return TemplateCollection 72 | .query('where', 'template_name', '=', 'crazyTestTemplate') 73 | .fetch(); 74 | }) 75 | .then(function (coll) { 76 | var _templateName = _.last(coll.toJSON()).templateName; 77 | expect(_templateName).to.equal('crazyTestTemplate'); 78 | done(); 79 | }) 80 | .catch(function (err) { 81 | console.log(err); 82 | expect(false).to.equal(true); 83 | done(); 84 | }); 85 | }); 86 | }); 87 | 88 | //create model for user with tied project 89 | //tests adding a new project and creating a collection 90 | describe('User/Project', function () { 91 | it('should attach user to a project', function (done) { 92 | var project, user; 93 | new ProjectCollection() 94 | .query('where', 'project_name', '=', 'car') 95 | .fetchOne() 96 | .then(function (_project) { 97 | project = _project; 98 | return new UserCollection().query('where', 'username', '=', 'door').fetchOne(); 99 | }) 100 | .then(function (_user) { 101 | user = _user; 102 | return Promise.all([ 103 | project.related('user').attach(user), 104 | user.related('project').attach(project) 105 | ]); 106 | }) 107 | .then(function () { 108 | return UserCollection.query('where', 'username', '=', 'door').fetchOne({ 109 | withRelated: ['project'] 110 | }); 111 | }) 112 | .then(function (_user) { 113 | expect(_user.toJSON().project[0].projectName).to.equal('car'); 114 | done(); 115 | }) 116 | .catch(function () { 117 | expect(false).to.equal(true); 118 | }); 119 | }); 120 | }); 121 | 122 | }); -------------------------------------------------------------------------------- /server/tests/integration/user.js: -------------------------------------------------------------------------------- 1 | /*global describe:true, it:true, before: true */ 2 | 'use strict'; 3 | 4 | var request = require('supertest-as-promised'); 5 | var UserCollection = require('../../models').collections.UserCollection; 6 | var app = require('../../index'); 7 | var agent = request.agent(app); 8 | var login = require('./login')(agent); 9 | 10 | describe('User', function () { 11 | 12 | before(function (done) { 13 | new UserCollection() 14 | .create({ 15 | 'username': 'Catherine' 16 | }) 17 | .then(function (_user) { 18 | global.user = _user; 19 | return login(); 20 | }) 21 | .then(function () { 22 | return new UserCollection() 23 | .create({ 24 | 'username': 'Chase' 25 | }); 26 | }) 27 | .then(function () { 28 | done(); 29 | }) 30 | .catch(function (err) { 31 | console.log('Problem Creating Test Users:', err); 32 | }); 33 | }); 34 | 35 | it('should get all of the users and their projects on GET /user', function (done) { 36 | agent 37 | .get('/api/user') 38 | .expect(200) 39 | .end(function (err, res) { 40 | var users = res.body; 41 | users.should.be.instanceof(Array); 42 | users[0].should.have.property('id'); 43 | users[0].should.have.property('username'); 44 | users[0].should.have.property('githubId'); 45 | users[0].should.have.property('githubName'); 46 | users[0].should.have.property('githubEmail'); 47 | users[0].should.have.property('githubLocation'); 48 | users[0].should.have.property('githubAccessToken'); 49 | users[0].should.have.property('githubAvatarUrl'); 50 | users[0].should.have.property('createdAt'); 51 | users[0].should.have.property('updatedAt'); 52 | users[0].should.have.property('project'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should get a specific user on GET /user/:username', function (done) { 58 | agent 59 | .get('/api/user/' + global.user.get('username')) 60 | .expect(200) 61 | .end(function (err, res) { 62 | var user = res.body; 63 | user.should.be.instanceof(Object); 64 | user.should.have.property('id'); 65 | user.should.have.property('username'); 66 | user.should.have.property('githubId'); 67 | user.should.have.property('githubName'); 68 | user.should.have.property('githubEmail'); 69 | user.should.have.property('githubLocation'); 70 | user.should.have.property('githubAccessToken'); 71 | user.should.have.property('githubAvatarUrl'); 72 | user.should.have.property('createdAt'); 73 | user.should.have.property('updatedAt'); 74 | user.should.have.property('project'); 75 | user.project.should.be.instanceof(Array); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should create a new user on POST /user', function (done) { 81 | agent 82 | .post('/api/user') 83 | .send({ 84 | username: 'chaseme3', 85 | githubId: 'thisIsMyGithubId', 86 | githubName: 'thisIsMyGithubName', 87 | githubEmail: 'thisIsMyGithubEmail', 88 | githubLocation: 'thisIsMyGithubLocation', 89 | githubAccessToken: 'thisIsMyGithubAccessToken', 90 | githubAvatarUrl: 'thisIsMyGithubAvatarUrl' 91 | }) 92 | .end(function (err, res) { 93 | var _user = res.body; 94 | agent 95 | .get('/api/user/' + _user.username) 96 | .expect(200) 97 | .end(function (err, res) { 98 | var user = res.body; 99 | user.should.have.property('id'); 100 | user.should.have.property('username'); 101 | user.username.should.equal(_user.username); 102 | user.should.have.property('githubId'); 103 | user.should.have.property('githubName'); 104 | user.should.have.property('githubEmail'); 105 | user.should.have.property('githubLocation'); 106 | user.should.have.property('githubAccessToken'); 107 | user.should.have.property('githubAvatarUrl'); 108 | user.should.have.property('createdAt'); 109 | user.should.have.property('updatedAt'); 110 | user.should.have.property('project'); 111 | user.project.should.be.instanceof(Array); 112 | done(); 113 | }); 114 | }); 115 | }); 116 | 117 | }); -------------------------------------------------------------------------------- /client/assets/scss/_header.scss: -------------------------------------------------------------------------------- 1 | .header, #toolbar { 2 | font-family: $Brandon-font-stack; 3 | } 4 | 5 | .header, #toolbar { 6 | $li-padding: 10px; 7 | font-size: 0.8em; 8 | letter-spacing: 0.13em; 9 | font-weight: 400; 10 | border: 0; 11 | color: #fff; 12 | font-size: 1.6em; 13 | cursor: default; 14 | background-color: $blue-grey; 15 | color: #fff; 16 | z-index: 95; 17 | height: $toolbar-height; 18 | border-radius: 0; 19 | margin-bottom: 0; 20 | .dropdown-menu { 21 | background: rgba(96, 121, 132, 0.98); 22 | padding: 0; 23 | } 24 | .navbar-brand { 25 | &:hover, &:focus { 26 | background: $darkened-blue-grey; 27 | } 28 | } 29 | &.navbar { 30 | border-radius: 0; 31 | border: 0; 32 | } 33 | nav .open > a, 34 | .nav .open > a:hover, 35 | .nav .open > a:focus { 36 | color: #fff; 37 | background: $darkened-blue-grey; 38 | } 39 | .navbar-nav > .open > a, 40 | .navbar-nav > .open > a:hover, 41 | .navbar-nav > .open > a:focus, { 42 | background: $darkened-blue-grey; 43 | } 44 | .navbar-nav > li > a, 45 | .navbar-nav > a { 46 | font-size: 11px; 47 | text-transform: uppercase; 48 | color: #fff; 49 | } 50 | .navbar-nav > li > a:hover, 51 | .navbar-nav > li > a:focus { 52 | color: #fff; 53 | } 54 | &.navbar-default { 55 | .dropdown-menu { 56 | border-radius: 0; 57 | li { 58 | font-family: $Proxima-font-stack; 59 | padding-top: 8px; 60 | padding-bottom: 8px; 61 | padding-left: 10px; 62 | &.divider { 63 | background: none; 64 | height: 0; 65 | padding-top: 0; 66 | padding-bottom: 0; 67 | border-bottom: 1px solid $darkened-blue-grey; 68 | } 69 | &.dropdown-header { 70 | color: #d7d7d7; 71 | text-transform: uppercase; 72 | letter-spacing: 0.1em; 73 | &:hover { 74 | background: none; 75 | } 76 | } 77 | &.form-container { 78 | padding: 0; 79 | input { 80 | background: none; 81 | border: none; 82 | width: 100%; 83 | text-align: left; 84 | height: 100%; 85 | padding: 7px 10px; 86 | color: #fff; 87 | font-family: $Proxima-font-stack; 88 | font-size: 1em; 89 | letter-spacing: 0.06em; 90 | font-weight: 300; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | &.navbar-default{ 97 | #logo-text { 98 | color: #fff; 99 | } 100 | a.navbar-brand.dropdown-toggle { 101 | color: #fff; 102 | } 103 | .dropdown-menu { 104 | background: $blue-grey; 105 | li { 106 | &:hover { 107 | background: $darkened-blue-grey; 108 | } 109 | } 110 | } 111 | } 112 | .dropdown-menu > li > a { 113 | color: #fff; 114 | background: none; 115 | font-weight: 300; 116 | display: inline-block; 117 | } 118 | .userName { 119 | color: #fff; 120 | cursor: pointer; 121 | &:hover { 122 | color: #fff; 123 | background: $darkened-blue-grey; 124 | } 125 | } 126 | .profile-image { 127 | float: left; 128 | border-radius: 18px; 129 | margin-right: 5px; 130 | width: 24px; 131 | height: 24px; 132 | margin-top: -2px; 133 | } 134 | } 135 | 136 | .navbar-brand, .navbar-default .navbar-brand { 137 | color: #fff; 138 | #logo-image { 139 | background: url($shapes-friends-path) no-repeat center center; 140 | background-size: contain; 141 | width: 60px; 142 | height: 20px; 143 | display: inline-block; 144 | float: left; 145 | margin-top: 2px; 146 | } 147 | #logo-text { 148 | font-size: 1em; 149 | font-weight: 700; 150 | color: #fff; 151 | letter-spacing: 0.1em; 152 | display: inline-block; 153 | margin-left: 5px; 154 | text-transform: uppercase; 155 | position: relative; 156 | top: 3px; 157 | } 158 | } 159 | 160 | 161 | // Toolbar styling 162 | #toolbar { 163 | ul { 164 | li { 165 | cursor: pointer; 166 | ul { 167 | font-family: $Proxima-font-stack; 168 | font-size: 0.6em; 169 | letter-spacing: 0.06em; 170 | font-weight: 300; 171 | } 172 | } 173 | } 174 | // ul li ul 175 | .first-dropDown, .second-dropDown { 176 | li { 177 | background: $blue-grey; 178 | } 179 | } 180 | } 181 | 182 | #toolbar-user { 183 | float: right; 184 | ul { 185 | float: right; 186 | } 187 | } 188 | 189 | #toolbar.navbar-default .dropdown-menu { 190 | background: rgba(96, 121, 132, 0.98) 191 | } 192 | --------------------------------------------------------------------------------