├── Gruntfile.js ├── Profile ├── README.md ├── api ├── controllers │ ├── .gitkeep │ ├── ChatController.js │ ├── MessageController.js │ └── UserController.js ├── models │ ├── .gitkeep │ ├── Message.js │ ├── Room.js │ └── User.js ├── policies │ └── sessionAuth.js ├── responses │ ├── badRequest.js │ ├── created.js │ ├── forbidden.js │ ├── notFound.js │ ├── ok.js │ └── serverError.js └── services │ ├── .gitkeep │ ├── ModelService.js │ └── TranslatorService.js ├── app.js ├── application.txt ├── assets ├── favicon.ico ├── google1684ab573d7f7395.html ├── images │ ├── .gitkeep │ ├── active-icon.svg │ ├── inactive-icon.svg │ ├── light-loading-ring.svg │ └── loading-ring.svg ├── js │ ├── app.js │ ├── chat.js │ ├── dependencies │ │ ├── jquery-3.1.1.min.js │ │ ├── linkify-jquery.min.js │ │ ├── linkify.min.js │ │ ├── mustache.js │ │ └── sails.io.js │ ├── translate.js │ └── user.js ├── robots.txt ├── styles │ ├── bootstrap.min.css │ ├── icons.less │ ├── importer.less │ └── pages │ │ └── chat-room.less └── templates │ └── .gitkeep ├── bower.json ├── config ├── blueprints.js ├── bootstrap.js ├── connections.js ├── cors.js ├── csrf.js ├── env │ ├── development.js │ ├── development │ │ └── connections.js │ ├── production.js │ └── production │ │ └── connections.js ├── globals.js ├── http.js ├── i18n.js ├── locales │ ├── _README.md │ ├── de.json │ ├── en.json │ ├── es.json │ └── fr.json ├── log.js ├── models.js ├── policies.js ├── routes.js ├── session.js ├── sockets.js └── views.js ├── package.json ├── tasks ├── README.md ├── config │ ├── bower.js │ ├── clean.js │ ├── coffee.js │ ├── concat.js │ ├── copy.js │ ├── cssmin.js │ ├── jst.js │ ├── less.js │ ├── sails-linker.js │ ├── sync.js │ ├── uglify.js │ └── watch.js ├── pipeline.js └── register │ ├── build.js │ ├── buildProd.js │ ├── compileAssets.js │ ├── default.js │ ├── linkAssets.js │ ├── linkAssetsBuild.js │ ├── linkAssetsBuildProd.js │ ├── prod.js │ └── syncAssets.js └── views ├── 403.ejs ├── 404.ejs ├── 500.ejs ├── index.ejs └── layout.ejs /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gruntfile 3 | * 4 | * This Node script is executed when you run `grunt` or `sails lift`. 5 | * It's purpose is to load the Grunt tasks in your project's `tasks` 6 | * folder, and allow you to add and remove tasks as you see fit. 7 | * For more information on how this works, check out the `README.md` 8 | * file that was generated in your `tasks` folder. 9 | * 10 | * WARNING: 11 | * Unless you know what you're doing, you shouldn't change this file. 12 | * Check out the `tasks` directory instead. 13 | */ 14 | 15 | module.exports = function(grunt) { 16 | 17 | 18 | // Load the include-all library in order to require all of our grunt 19 | // configurations and task registrations dynamically. 20 | var includeAll; 21 | try { 22 | includeAll = require('include-all'); 23 | } catch (e0) { 24 | try { 25 | includeAll = require('sails/node_modules/include-all'); 26 | } catch (e1) { 27 | console.error('Could not find `include-all` module.'); 28 | console.error('Skipping grunt tasks...'); 29 | console.error('To fix this, please run:'); 30 | console.error('npm install include-all --save`'); 31 | console.error(); 32 | 33 | grunt.registerTask('default', []); 34 | return; 35 | } 36 | } 37 | 38 | 39 | /** 40 | * Loads Grunt configuration modules from the specified 41 | * relative path. These modules should export a function 42 | * that, when run, should either load/configure or register 43 | * a Grunt task. 44 | */ 45 | function loadTasks(relPath) { 46 | return includeAll({ 47 | dirname: require('path').resolve(__dirname, relPath), 48 | filter: /(.+)\.js$/, 49 | excludeDirs: /^\.(git|svn)$/ 50 | }) || {}; 51 | } 52 | 53 | /** 54 | * Invokes the function from a Grunt configuration module with 55 | * a single argument - the `grunt` object. 56 | */ 57 | function invokeConfigFn(tasks) { 58 | for (var taskName in tasks) { 59 | if (tasks.hasOwnProperty(taskName)) { 60 | tasks[taskName](grunt); 61 | } 62 | } 63 | } 64 | 65 | 66 | 67 | // Load task functions 68 | var taskConfigurations = loadTasks('./tasks/config'), 69 | registerDefinitions = loadTasks('./tasks/register'); 70 | 71 | // (ensure that a default task exists) 72 | if (!registerDefinitions.default) { 73 | registerDefinitions.default = function(grunt) { 74 | grunt.registerTask('default', []); 75 | }; 76 | } 77 | 78 | // Run task functions to configure Grunt. 79 | invokeConfigFn(taskConfigurations); 80 | invokeConfigFn(registerDefinitions); 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /Profile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![banner](https://cloud.githubusercontent.com/assets/1388393/24481214/9a02dede-1534-11e7-9dc3-a8cf54034930.png)](http://poly.chat) 2 | 3 | Follow us on Twitter [@poly_chat](https://twitter.com/poly_chat) or you can DM me directly at [@willports](https://twitter.com/willports). Would love to hear your feedback! 4 | 5 | ## What is Poly? 6 | [Poly](http://poly.chat) is a universal chat room where regardless of what language you speak can communicate with people of all languages from around the world in real time. 7 | 8 | ## Why I built Poly? 9 | [Poly](http://poly.chat) is a side-project of mine I’ve been working on in my free time the past couple months. It all started when a podcast I was listening to raised the idea of 2 billion extra internet users in the next 10 - 15 years, due to advancements in low earth satellite technology. I thought amazing! But how are all these people going to be able to communicate with each other. Oceans used to separate us, now with the internet all that’s left is language. 10 | 11 | So I thought surely Translation APIs by now would be strong enough for universal chat to be possible so I built Poly to test my hypothesis. 12 | 13 | ## Community Guidelines 14 | - Be friendly and respectful of others 15 | - Your message could be translated into over 60 languages so try make it more interesting that just 16 | > testing 123 17 | - Please no spam 18 | - More coming soon! 19 | 20 | ## What is Poly built in? 21 | For those interested in the technical side. 22 | - Node.js + Sails.js 23 | - Socket.io 24 | 25 | API: 26 | - Microsoft’s Text Translation API 27 | 28 | ## Find any bugs or have any feature ideas? 29 | Please feel free to open up an issue https://github.com/dawilster/poly/issues if someone already hasn't 30 | 31 | Follow us on Twitter [@poly_chat](https://twitter.com/poly_chat) or you can DM me directly at [@willports](https://twitter.com/willports). Would love to hear your feedback! 32 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/ChatController.js: -------------------------------------------------------------------------------- 1 | var MsTranslator = require('mstranslator'); 2 | 3 | module.exports = { 4 | reset: function(req, res) { 5 | 6 | //set all users to inactive 7 | User.update({active: true}, {active: false}).exec(function afterwards(err, updated){ 8 | if (err) { 9 | return res.serverError(err); 10 | } 11 | 12 | Message.destroy({}).exec(function (err){ 13 | if (err) { 14 | return res.serverError(err); 15 | } 16 | 17 | return res.ok(); 18 | }); 19 | }); 20 | }, 21 | welcome: function(req, res) { 22 | return res.json({ 23 | welcome_message: "Welcome to Poly Chat :)" 24 | }); 25 | }, 26 | 27 | languages: function(req, res) { 28 | var client = new MsTranslator({ 29 | api_key: process.env.MS_TRANSLATOR_API_KEY 30 | }, true); 31 | 32 | var languages = []; 33 | var languageMapping = {}; 34 | 35 | client.getLanguagesForTranslate(function(err, languageCodes) { 36 | if (err) { return res.serverError(err); } 37 | 38 | var params = {locale: 'en', languageCodes: languageCodes}; 39 | 40 | client.getLanguageNames(params, function (err, languageNames) { 41 | if (err) { return res.serverError(err); } 42 | 43 | //assign language names to lang codes 44 | for (var i = 0; i < languageCodes.length; i++) { 45 | languages[i] = { name: languageNames[i], code: languageCodes[i] }; 46 | languageMapping[languageCodes[i]] = languageNames[i]; 47 | } 48 | 49 | return res.json({ 50 | languages: languages, 51 | languageMapping: languageMapping 52 | }); 53 | }); 54 | 55 | }); 56 | }, 57 | 58 | // Post a message in a public chat room 59 | message: function(req, res) { 60 | // Get the ID of the currently connected socket 61 | var userId = req.session.userId; 62 | // Use that ID to look up the user in the session 63 | // We need to do this because we can have more than one user 64 | // per session, since we're creating one user per socket 65 | User.findOne(userId).exec(function(err, user) { 66 | if (err) { return res.serverError(err); } 67 | if (user == undefined) { return res.send(404); } 68 | 69 | Room.message(req.param('roomId'), { 70 | msg: req.param('msg'), 71 | user: user 72 | }); 73 | }); 74 | }, 75 | 76 | translate: function(req, res) { 77 | 78 | var client = new MsTranslator({ 79 | api_key: process.env.MS_TRANSLATOR_API_KEY 80 | }, true); 81 | 82 | client.detect({ text: req.param('msg')}, function(err, detectedLang) { 83 | // Don't worry about access token, it will be auto-generated if needed. 84 | client.translate({ 85 | text: req.param('msg'), 86 | to: req.param('lang') 87 | }, function(err, translatedText) { 88 | res.json({ 89 | targetLang: req.param('lang'), 90 | translatedText: translatedText, 91 | originalText: req.param('msg'), 92 | detectedSourceLanguage: detectedLang 93 | }); 94 | }); 95 | }); 96 | } 97 | 98 | }; 99 | -------------------------------------------------------------------------------- /api/controllers/MessageController.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | create: function(req, res) { 3 | var params = req.params.all(); 4 | 5 | // attach requesting user 6 | params['user'] = req.session.userId; 7 | 8 | Message.create(params).exec(function(err, message) { 9 | if (err) { return res.serverError(err); } 10 | 11 | User.findOne(params['user']).exec(function(err, user) { 12 | //can populate association on create so we need to inject them here 13 | message['user'] = { id: user.id, name: user.name }; 14 | 15 | Message.publishCreate(message); 16 | 17 | res.status(201); 18 | res.json(message); 19 | }); 20 | }); 21 | }, 22 | find: function(req, res) { 23 | var lang = req.param('lang'); 24 | 25 | Message.find({original: true}).sort('createdAt ASC').populate('user').exec((err, messages) => { 26 | if(lang) { 27 | // translate messages and return 28 | var translations = messages.map((message) => { 29 | return TranslatorService.translateMessage(message, req.param('lang')); 30 | }); 31 | 32 | Promise.all(translations).then((data) => { 33 | Message.find({translatedLanguage: req.param('lang')}).sort('createdAt ASC').populate('user').exec(function(err, messages) { 34 | return res.json(messages); 35 | }); 36 | }); 37 | } else { 38 | return res.json(messages); 39 | } 40 | }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /api/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | set_language: function(req, res) { 3 | var userId = req.session.userId; 4 | 5 | User.findOne(userId).exec(function(err, user) { 6 | if (err) { return res.serverError(err); } 7 | if (user == undefined) { return res.send(404); } 8 | 9 | user.languages = []; 10 | user.languages.push(req.param('lang_code')); 11 | 12 | user.save(function(err) { 13 | if (err) { return res.serverError(err); } 14 | 15 | User.stats(function(err, stats) { 16 | if (err) { return res.serverError(err); } 17 | 18 | Room.message('random', { type: 'stat', stats: stats }); 19 | }); 20 | }); 21 | }); 22 | }, 23 | login: function(req, res) { 24 | Room.findOrCreate({ name: 'random'}).exec(function(err, room) { 25 | User.findOne({accessToken: req.param('access_token')}).exec(function(err, user) { 26 | if (err) { return res.serverError(err); } 27 | if (user == undefined) { return res.send(404); } 28 | 29 | // Set session userId will add a userId session to browser 30 | req.session.userId = user.id 31 | 32 | User.subscribe(req, user, 'message'); 33 | 34 | // Subscribe this socket to the random chat room 35 | Room.subscribe(req, room, 'message'); 36 | 37 | // Get updates about users being created 38 | User.watch(req); 39 | 40 | Message.watch(req); 41 | 42 | // Get updates about rooms being created 43 | Room.watch(req); 44 | 45 | user.active = true; 46 | 47 | user.save(function(err) { 48 | if (err) { return res.serverError(err); } 49 | 50 | User.broadcaseCount(); 51 | 52 | user['accessToken'] = user.accessToken; 53 | 54 | res.json(user.toJSON(true)); 55 | }); 56 | 57 | }); 58 | }); 59 | }, 60 | signup: function(req, res) { 61 | //Create 'random' channel 62 | Room.findOrCreate({ name: 'random'}).exec(function(err, room) { 63 | User.create({ 64 | name: req.param('name'), 65 | active: true, 66 | }).exec(function(err, user) { 67 | if (err) { return res.serverError(err); } 68 | 69 | // Save this user in the session, indexed by their socket ID. 70 | // This way we can look the user up by socket ID later. 71 | req.session.userId = user.id 72 | 73 | // Subscribe the connected socket to custom messages regarding the user. 74 | // While any socket subscribed to the user will receive messages about the 75 | // user changing their name or being destroyed, ONLY this particular socket 76 | // will receive "message" events. This allows us to send private messages 77 | // between users. 78 | User.subscribe(req, user, 'message'); 79 | 80 | // Subscribe this socket to the random chat room 81 | Room.subscribe(req, room, 'message'); 82 | 83 | Message.watch(req); 84 | 85 | // Get updates about users being created 86 | User.watch(req); 87 | 88 | // Get updates about rooms being created 89 | Room.watch(req); 90 | 91 | // Publish this user creation event to every socket watching the User model via User.watch() 92 | User.publishCreate(user, req); 93 | 94 | // Updates the user count 95 | User.broadcaseCount(); 96 | 97 | user['accessToken'] = user.accessToken; 98 | 99 | res.json(user.toJSON(true)); 100 | }); 101 | }); 102 | 103 | } 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/Message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Message 3 | * 4 | * @module :: Model 5 | * @description :: The main ledger of messages 6 | * 7 | */ 8 | 9 | module.exports = { 10 | autosubscribe: ['destroy', 'update', 'create'], 11 | attributes: { 12 | body: { 13 | type: 'string' 14 | }, 15 | originalBody: { 16 | type: 'string' 17 | }, 18 | sourceLanguage: { 19 | type: 'string' 20 | }, 21 | translatedLanguage: { 22 | type: 'string' 23 | }, 24 | parentId: { 25 | type: 'string' 26 | }, 27 | original: { 28 | type: 'boolean', 29 | defaultsTo: function() { 30 | return true; 31 | } 32 | }, 33 | user: { 34 | model: 'user', 35 | required: true 36 | } 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /api/models/Room.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Room 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * 7 | */ 8 | 9 | module.exports = { 10 | 11 | // Subscribers only get to hear about update and destroy events, 12 | // and about users joining or leaving the room. 13 | // This lets us keep our count of users in the room accurate, without 14 | // sending chat message for a room to anyone but people who actually 15 | // want to get them. To get chat messages for a room, you subscribe 16 | // to the 'message' context explicitly. 17 | autosubscribe: ['destroy', 'update', 'add:users', 'remove:users'], 18 | attributes: { 19 | name: { 20 | type: 'string', 21 | primaryKey: true 22 | }, 23 | users: { 24 | collection: 'user', 25 | via: 'rooms' 26 | } 27 | 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User 3 | * 4 | * @module :: Model 5 | * @description :: A short summary of how this model works and what it represents. 6 | * 7 | */ 8 | 9 | 10 | module.exports = { 11 | autosubscribe: ['destroy', 'update'], 12 | attributes: { 13 | accessToken: { 14 | type: 'string', 15 | defaultsTo: function() { 16 | return ModelService.generateUUID(); 17 | } 18 | }, 19 | name: 'string', 20 | active: { 21 | type: 'boolean', 22 | defaultsTo: function() { 23 | return false; 24 | } 25 | }, 26 | languages: { 27 | type: 'array', 28 | defaultsTo: function() { 29 | return []; 30 | } 31 | }, 32 | rooms: { 33 | collection: 'room', 34 | via: 'users', 35 | dominant: true 36 | }, 37 | // Override toJSON instance method 38 | toJSON: function(auth) { 39 | var obj = this.toObject(); 40 | 41 | if(auth != true) { 42 | delete obj.accessToken; 43 | } 44 | 45 | return obj; 46 | } 47 | }, 48 | broadcaseCount: function() { 49 | User.active(function(err, count) { 50 | //update user count 51 | Room.message('random', { 52 | type: 'joined', 53 | count: count, 54 | }); 55 | 56 | User.stats(function(err, stats) { 57 | Room.message('random', { 58 | type: 'stat', 59 | stats: stats, 60 | }); 61 | }); 62 | 63 | }); 64 | }, 65 | active: function(cb) { 66 | User.count({ active: true }).exec(function(err, count) { 67 | if (err) return cb(err); 68 | 69 | return cb(null, count); 70 | }); 71 | }, 72 | stats: function(cb) { 73 | User.find({ active: true }).exec(function(err, users) { 74 | if (err) return cb(err); 75 | 76 | var stats = { languages: {} }; 77 | 78 | users.map(function(user) { 79 | user.languages.map(function(lang) { 80 | stats['languages'][lang] = ++stats['languages'][lang] || 1 81 | }); 82 | }); 83 | 84 | return cb(null, stats); 85 | }); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /api/policies/sessionAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sessionAuth 3 | * 4 | * @module :: Policy 5 | * @description :: Simple policy to allow any authenticated user 6 | * Assumes that your login action in one of your controllers sets `req.session.authenticated = true;` 7 | * @docs :: http://sailsjs.org/#!/documentation/concepts/Policies 8 | * 9 | */ 10 | module.exports = function(req, res, next) { 11 | 12 | // User is allowed, proceed to the next policy, 13 | // or if this is the last policy, the controller 14 | if (req.session.authenticated) { 15 | return next(); 16 | } 17 | 18 | // User is not allowed 19 | // (default res.forbidden() behavior can be overridden in `config/403.js`) 20 | return res.forbidden('You are not permitted to perform this action.'); 21 | }; 22 | -------------------------------------------------------------------------------- /api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 400 (Bad Request) Handler 3 | * 4 | * Usage: 5 | * return res.badRequest(); 6 | * return res.badRequest(data); 7 | * return res.badRequest(data, 'some/specific/badRequest/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.badRequest( 12 | * 'Please choose a valid `password` (6-12 characters)', 13 | * 'trial/signup' 14 | * ); 15 | * ``` 16 | */ 17 | 18 | module.exports = function badRequest(data, options) { 19 | 20 | // Get access to `req`, `res`, & `sails` 21 | var req = this.req; 22 | var res = this.res; 23 | var sails = req._sails; 24 | 25 | // Set status code 26 | res.status(400); 27 | 28 | // Log error to console 29 | if (data !== undefined) { 30 | sails.log.verbose('Sending 400 ("Bad Request") response: \n',data); 31 | } 32 | else sails.log.verbose('Sending 400 ("Bad Request") response'); 33 | 34 | // Only include errors in response if application environment 35 | // is not set to 'production'. In production, we shouldn't 36 | // send back any identifying information about errors. 37 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 38 | data = undefined; 39 | } 40 | 41 | // If the user-agent wants JSON, always respond with JSON 42 | // If views are disabled, revert to json 43 | if (req.wantsJSON || sails.config.hooks.views === false) { 44 | return res.jsonx(data); 45 | } 46 | 47 | // If second argument is a string, we take that to mean it refers to a view. 48 | // If it was omitted, use an empty object (`{}`) 49 | options = (typeof options === 'string') ? { view: options } : options || {}; 50 | 51 | // Attempt to prettify data for views, if it's a non-error object 52 | var viewData = data; 53 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 54 | try { 55 | viewData = require('util').inspect(data, {depth: null}); 56 | } 57 | catch(e) { 58 | viewData = undefined; 59 | } 60 | } 61 | 62 | // If a view was provided in options, serve it. 63 | // Otherwise try to guess an appropriate view, or if that doesn't 64 | // work, just send JSON. 65 | if (options.view) { 66 | return res.view(options.view, { data: viewData, title: 'Bad Request' }); 67 | } 68 | 69 | // If no second argument provided, try to serve the implied view, 70 | // but fall back to sending JSON(P) if no view can be inferred. 71 | else return res.guessView({ data: viewData, title: 'Bad Request' }, function couldNotGuessView () { 72 | return res.jsonx(data); 73 | }); 74 | 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /api/responses/created.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 201 (CREATED) Response 3 | * 4 | * Usage: 5 | * return res.created(); 6 | * return res.created(data); 7 | * return res.created(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function created (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.created() :: Sending 201 ("CREATED") response'); 22 | 23 | // Set status code 24 | res.status(201); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false) { 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string') ? { view: options } : options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 39 | try { 40 | viewData = require('util').inspect(data, {depth: null}); 41 | } 42 | catch(e) { 43 | viewData = undefined; 44 | } 45 | } 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: viewData, title: 'Created' }); 52 | } 53 | 54 | // If no second argument provided, try to serve the implied view, 55 | // but fall back to sending JSON(P) if no view can be inferred. 56 | else return res.guessView({ data: viewData, title: 'Created' }, function couldNotGuessView () { 57 | return res.jsonx(data); 58 | }); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 403 (Forbidden) Handler 3 | * 4 | * Usage: 5 | * return res.forbidden(); 6 | * return res.forbidden(err); 7 | * return res.forbidden(err, 'some/specific/forbidden/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.forbidden('Access denied.'); 12 | * ``` 13 | */ 14 | 15 | module.exports = function forbidden (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(403); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.verbose('Sending 403 ("Forbidden") response: \n',data); 28 | } 29 | else sails.log.verbose('Sending 403 ("Forbidden") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Forbidden' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('403', { data: viewData, title: 'Forbidden' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 404 (Not Found) Handler 3 | * 4 | * Usage: 5 | * return res.notFound(); 6 | * return res.notFound(err); 7 | * return res.notFound(err, 'some/specific/notfound/view'); 8 | * 9 | * e.g.: 10 | * ``` 11 | * return res.notFound(); 12 | * ``` 13 | * 14 | * NOTE: 15 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 16 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 17 | * automatically. 18 | */ 19 | 20 | module.exports = function notFound (data, options) { 21 | 22 | // Get access to `req`, `res`, & `sails` 23 | var req = this.req; 24 | var res = this.res; 25 | var sails = req._sails; 26 | 27 | // Set status code 28 | res.status(404); 29 | 30 | // Log error to console 31 | if (data !== undefined) { 32 | sails.log.verbose('Sending 404 ("Not Found") response: \n',data); 33 | } 34 | else sails.log.verbose('Sending 404 ("Not Found") response'); 35 | 36 | // Only include errors in response if application environment 37 | // is not set to 'production'. In production, we shouldn't 38 | // send back any identifying information about errors. 39 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 40 | data = undefined; 41 | } 42 | 43 | // If the user-agent wants JSON, always respond with JSON 44 | // If views are disabled, revert to json 45 | if (req.wantsJSON || sails.config.hooks.views === false) { 46 | return res.jsonx(data); 47 | } 48 | 49 | // If second argument is a string, we take that to mean it refers to a view. 50 | // If it was omitted, use an empty object (`{}`) 51 | options = (typeof options === 'string') ? { view: options } : options || {}; 52 | 53 | // Attempt to prettify data for views, if it's a non-error object 54 | var viewData = data; 55 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 56 | try { 57 | viewData = require('util').inspect(data, {depth: null}); 58 | } 59 | catch(e) { 60 | viewData = undefined; 61 | } 62 | } 63 | 64 | // If a view was provided in options, serve it. 65 | // Otherwise try to guess an appropriate view, or if that doesn't 66 | // work, just send JSON. 67 | if (options.view) { 68 | return res.view(options.view, { data: viewData, title: 'Not Found' }); 69 | } 70 | 71 | // If no second argument provided, try to serve the default view, 72 | // but fall back to sending JSON(P) if any errors occur. 73 | else return res.view('404', { data: viewData, title: 'Not Found' }, function (err, html) { 74 | 75 | // If a view error occured, fall back to JSON(P). 76 | if (err) { 77 | // 78 | // Additionally: 79 | // • If the view was missing, ignore the error but provide a verbose log. 80 | if (err.code === 'E_VIEW_FAILED') { 81 | sails.log.verbose('res.notFound() :: Could not locate view for error page (sending JSON instead). Details: ',err); 82 | } 83 | // Otherwise, if this was a more serious error, log to the console with the details. 84 | else { 85 | sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 86 | } 87 | return res.jsonx(data); 88 | } 89 | 90 | return res.send(html); 91 | }); 92 | 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /api/responses/ok.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 200 (OK) Response 3 | * 4 | * Usage: 5 | * return res.ok(); 6 | * return res.ok(data); 7 | * return res.ok(data, 'auth/login'); 8 | * 9 | * @param {Object} data 10 | * @param {String|Object} options 11 | * - pass string to render specified view 12 | */ 13 | 14 | module.exports = function sendOK (data, options) { 15 | 16 | // Get access to `req`, `res`, & `sails` 17 | var req = this.req; 18 | var res = this.res; 19 | var sails = req._sails; 20 | 21 | sails.log.silly('res.ok() :: Sending 200 ("OK") response'); 22 | 23 | // Set status code 24 | res.status(200); 25 | 26 | // If appropriate, serve data as JSON(P) 27 | // If views are disabled, revert to json 28 | if (req.wantsJSON || sails.config.hooks.views === false) { 29 | return res.jsonx(data); 30 | } 31 | 32 | // If second argument is a string, we take that to mean it refers to a view. 33 | // If it was omitted, use an empty object (`{}`) 34 | options = (typeof options === 'string') ? { view: options } : options || {}; 35 | 36 | // Attempt to prettify data for views, if it's a non-error object 37 | var viewData = data; 38 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 39 | try { 40 | viewData = require('util').inspect(data, {depth: null}); 41 | } 42 | catch(e) { 43 | viewData = undefined; 44 | } 45 | } 46 | 47 | // If a view was provided in options, serve it. 48 | // Otherwise try to guess an appropriate view, or if that doesn't 49 | // work, just send JSON. 50 | if (options.view) { 51 | return res.view(options.view, { data: viewData, title: 'OK' }); 52 | } 53 | 54 | // If no second argument provided, try to serve the implied view, 55 | // but fall back to sending JSON(P) if no view can be inferred. 56 | else return res.guessView({ data: viewData, title: 'OK' }, function couldNotGuessView () { 57 | return res.jsonx(data); 58 | }); 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 500 (Server Error) Response 3 | * 4 | * Usage: 5 | * return res.serverError(); 6 | * return res.serverError(err); 7 | * return res.serverError(err, 'some/specific/error/view'); 8 | * 9 | * NOTE: 10 | * If something throws in a policy or controller, or an internal 11 | * error is encountered, Sails will call `res.serverError()` 12 | * automatically. 13 | */ 14 | 15 | module.exports = function serverError (data, options) { 16 | 17 | // Get access to `req`, `res`, & `sails` 18 | var req = this.req; 19 | var res = this.res; 20 | var sails = req._sails; 21 | 22 | // Set status code 23 | res.status(500); 24 | 25 | // Log error to console 26 | if (data !== undefined) { 27 | sails.log.error('Sending 500 ("Server Error") response: \n',data); 28 | } 29 | else sails.log.error('Sending empty 500 ("Server Error") response'); 30 | 31 | // Only include errors in response if application environment 32 | // is not set to 'production'. In production, we shouldn't 33 | // send back any identifying information about errors. 34 | if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { 35 | data = undefined; 36 | } 37 | 38 | // If the user-agent wants JSON, always respond with JSON 39 | // If views are disabled, revert to json 40 | if (req.wantsJSON || sails.config.hooks.views === false) { 41 | return res.jsonx(data); 42 | } 43 | 44 | // If second argument is a string, we take that to mean it refers to a view. 45 | // If it was omitted, use an empty object (`{}`) 46 | options = (typeof options === 'string') ? { view: options } : options || {}; 47 | 48 | // Attempt to prettify data for views, if it's a non-error object 49 | var viewData = data; 50 | if (!(viewData instanceof Error) && 'object' == typeof viewData) { 51 | try { 52 | viewData = require('util').inspect(data, {depth: null}); 53 | } 54 | catch(e) { 55 | viewData = undefined; 56 | } 57 | } 58 | 59 | // If a view was provided in options, serve it. 60 | // Otherwise try to guess an appropriate view, or if that doesn't 61 | // work, just send JSON. 62 | if (options.view) { 63 | return res.view(options.view, { data: viewData, title: 'Server Error' }); 64 | } 65 | 66 | // If no second argument provided, try to serve the default view, 67 | // but fall back to sending JSON(P) if any errors occur. 68 | else return res.view('500', { data: viewData, title: 'Server Error' }, function (err, html) { 69 | 70 | // If a view error occured, fall back to JSON(P). 71 | if (err) { 72 | // 73 | // Additionally: 74 | // • If the view was missing, ignore the error but provide a verbose log. 75 | if (err.code === 'E_VIEW_FAILED') { 76 | sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ',err); 77 | } 78 | // Otherwise, if this was a more serious error, log to the console with the details. 79 | else { 80 | sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); 81 | } 82 | return res.jsonx(data); 83 | } 84 | 85 | return res.send(html); 86 | }); 87 | 88 | }; 89 | 90 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/api/services/.gitkeep -------------------------------------------------------------------------------- /api/services/ModelService.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | generateUUID: function b(a) { 3 | return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /api/services/TranslatorService.js: -------------------------------------------------------------------------------- 1 | var MsTranslator = require('mstranslator'); 2 | 3 | module.exports = { 4 | translateMessage(original, targetLang) { 5 | return new Promise((resolve, reject) => { 6 | Message.findOne({parentId: original.id, 7 | translatedLanguage: targetLang}).exec((err, message) => { 8 | if(message == undefined) { 9 | this.translateText(original.body, targetLang, function(translatedText, detectedLang) { 10 | var newMessage = { 11 | user: original.user, 12 | createdAt: original.createdAt, 13 | updatedAt: original.updatedAt, 14 | original: false, 15 | translatedLanguage: targetLang, 16 | sourceLanguage: detectedLang, 17 | body: translatedText, 18 | originalBody: original.body, 19 | parentId: original.id, 20 | }; 21 | 22 | Message.create(newMessage).exec(function(err, message) { 23 | resolve(message); 24 | }); 25 | }); 26 | } else { 27 | resolve(); 28 | } 29 | }); 30 | }); 31 | }, 32 | translateText(body, targetLang, cb) { 33 | var client = new MsTranslator({ 34 | api_key: process.env.MS_TRANSLATOR_API_KEY 35 | }, true); 36 | 37 | client.detect({ text: body}, function(err, detectedLang) { 38 | client.translate({ 39 | text: body, 40 | to: targetLang 41 | }, function(err, translatedText) { 42 | return cb(translatedText, detectedLang); 43 | }); 44 | }); 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * Use `app.js` to run your app without `sails lift`. 5 | * To start the server, run: `node app.js`. 6 | * 7 | * This is handy in situations where the sails CLI is not relevant or useful. 8 | * 9 | * For example: 10 | * => `node app.js` 11 | * => `forever start app.js` 12 | * => `node debug app.js` 13 | * => `modulus deploy` 14 | * => `heroku scale` 15 | * 16 | * 17 | * The same command-line arguments are supported, e.g.: 18 | * `node app.js --silent --port=80 --prod` 19 | */ 20 | 21 | 22 | // Ensure we're in the project directory, so cwd-relative paths work as expected 23 | // no matter where we actually lift from. 24 | // > Note: This is not required in order to lift, but it is a convenient default. 25 | process.chdir(__dirname); 26 | 27 | // Attempt to import `sails`. 28 | var sails; 29 | try { 30 | sails = require('sails'); 31 | } catch (e) { 32 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.'); 33 | console.error('To do that, run `npm install sails`'); 34 | console.error(''); 35 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.'); 36 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,'); 37 | console.error('but if it doesn\'t, the app will run with the global sails instead!'); 38 | return; 39 | } 40 | 41 | // --• 42 | // Try to get `rc` dependency (for loading `.sailsrc` files). 43 | var rc; 44 | try { 45 | rc = require('rc'); 46 | } catch (e0) { 47 | try { 48 | rc = require('sails/node_modules/rc'); 49 | } catch (e1) { 50 | console.error('Could not find dependency: `rc`.'); 51 | console.error('Your `.sailsrc` file(s) will be ignored.'); 52 | console.error('To resolve this, run:'); 53 | console.error('npm install rc --save'); 54 | rc = function () { return {}; }; 55 | } 56 | } 57 | 58 | 59 | // Start server 60 | sails.lift(rc('sails')); 61 | -------------------------------------------------------------------------------- /application.txt: -------------------------------------------------------------------------------- 1 | 2 | Universal chat app. 'Poly' (like polyglot) seamlessly translates text messages in real time between people each speaking different languages. We want to enable individuals and groups to be able to communicate and collaborate regardless of their language and proficiency. 3 | 4 | Inefficiency to collaborate when some or all members aren't proficient in the same language. 5 | 6 | A feeling of connectedness to the world. There's a level of isolation when the only people you can properly communicate with have to speak the same language as you. 7 | 8 | Existing technology just doesn't cut it. Apps like Messenger, WhatsApp and Slack chat don't offer translation driven experiences. Rather it's an after thought. Users have to leave the app or copy/paste words into Google. 9 | 10 | As a broad scope we'd like to see everyone using Poly in some form. 11 | Whether it's with in the co-workers, customers, exchange students on projects or even your grandparents who can't quite speak English we want to be there driving that experience. 12 | 13 | 14 | We want to share our software with the world. Poly will be available to all developers and online communities. With a few clicks you'll be able to embed chat capabilities powered by Poly to your website or community. We can can then charge monthly subscriptions for our services. 15 | 16 | At the moment costs are $50 per month. With my development experience and design savviness I'll be able to deploy a working prototype for next to nothing. 17 | Obviously that's going to change. I envision hiring people to fulfill and accelerate future development but I believe minimalism to keep costs low and investing sweat equity where I can. 18 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/assets/favicon.ico -------------------------------------------------------------------------------- /assets/google1684ab573d7f7395.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google1684ab573d7f7395.html -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/assets/images/.gitkeep -------------------------------------------------------------------------------- /assets/images/active-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | active 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/images/inactive-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | active copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/images/light-loading-ring.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/loading-ring.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | //basically the ability to remember a persons username with cookies 2 | //so subsequent logins won't prompt for username 3 | var allLanguages = []; 4 | var _firstLaunch = true; 5 | var currentUser = null; 6 | var languageMapping = {}; 7 | 8 | $(function(){ 9 | 10 | $('.languages-select').on('change', function() { 11 | $('#current-language').val(this.value); 12 | 13 | io.socket.post('/user/set_language', { lang_code: this.value}, function() {}); 14 | loadMessageHistory(); 15 | }); 16 | 17 | //sending a message 18 | $('form').submit(function(){ 19 | submitMessage($('#m').val()); 20 | 21 | //reset form 22 | $('#m').val(''); 23 | 24 | return false; 25 | }); 26 | 27 | $(document).on('mouseenter', '.message-body', function() { 28 | $(this).find('.see-original').text('Hover a word to learn more'); 29 | 30 | $(this).find('.rate-btn').removeClass('hide'); 31 | $(this).find('.translated-text').addClass('hide'); 32 | $(this).find('.original-text').removeClass('hide'); 33 | }); 34 | 35 | $(document).on('mouseleave', '.message-body', function() { 36 | $(this).find('.see-original').text('Hover for original'); 37 | 38 | $(this).find('.rate-btn').addClass('hide'); 39 | $(this).find('.original-text').addClass('hide'); 40 | $(this).find('.translated-text').removeClass('hide'); 41 | }); 42 | 43 | }); 44 | 45 | io.socket.on('connect', function socketConnected() { 46 | //check cookies if exists login with ID otherwise ask for username 47 | var access_token = getCookie('access_token'); 48 | 49 | 50 | 51 | fetchLanguages(function() { 52 | //cookie exists 53 | if(access_token === undefined) { 54 | promptUsername(function usernameEntered(name) { 55 | signUp(name); 56 | }); 57 | } else { 58 | loginUser(access_token); 59 | } 60 | }); 61 | 62 | }); 63 | 64 | io.socket.on('user', function messageReceived(message) { 65 | switch (message.verb) { 66 | // Handle user creation 67 | case 'created': 68 | renderUserJoinedMessage(message); 69 | break; 70 | case 'destroyed': 71 | renderLeftRoomMessage(message); 72 | break 73 | 74 | default: 75 | break; 76 | } 77 | }); 78 | 79 | io.socket.on('message', function messageReceived(message) { 80 | switch (message.verb) { 81 | // Handle user creation 82 | case 'created': 83 | renderMessage(message.data) 84 | break; 85 | default: 86 | break; 87 | } 88 | }); 89 | 90 | io.socket.on('room', function messageReceived(message) { 91 | switch (message.verb) { 92 | case 'messaged': 93 | switch(message.data.type) { 94 | case 'stat': 95 | updateLanguageStats(message.data.stats); 96 | break; 97 | case 'joined': 98 | updateUserCount(message.data); 99 | break; 100 | default: 101 | renderMessage(message.data) 102 | break; 103 | } 104 | break; 105 | default: 106 | break; 107 | } 108 | }); 109 | 110 | function loadMessageHistory() { 111 | $('.chat-messages').empty(); 112 | 113 | $('.messages.loading-indicator').removeClass('hide'); 114 | 115 | var lang = $('#current-language').val(); 116 | 117 | console.log('fetch messages: /message?lang=' + lang); 118 | 119 | io.socket.get('/message', { lang: lang }, function(data) { 120 | renderMessages(data); 121 | }); 122 | } 123 | 124 | function loginUser(access_token) { 125 | io.socket.get('/user/login', {access_token: access_token}, function(data) { 126 | currentUser = data; 127 | 128 | $('#username').val(data.name); 129 | 130 | autoDetectLanguage(); 131 | }); 132 | } 133 | 134 | function signUp(name) { 135 | io.socket.get("/user/signup", { name: name }, function(data) { 136 | currentUser = data; 137 | 138 | var expires = "; expires=Fri, 31 Dec 2999 23:59:59 GMT"; 139 | 140 | document.cookie = "access_token=" + data.accessToken + expires; 141 | 142 | autoDetectLanguage(); 143 | }); 144 | } 145 | 146 | function updateLanguageStats(stats) { 147 | $('.live-stats').empty(); 148 | 149 | Object.keys(stats.languages).forEach(function (lang) { 150 | var count = stats.languages[lang]; 151 | var noun = count > 1 ? 'speakers' : 'speaker'; 152 | 153 | $('.live-stats').append('
  • '+ count + ' '+ languageMapping[lang] +' ' + noun + '
  • '); 154 | }); 155 | } 156 | 157 | function fetchLanguages(callBack) { 158 | //fetch supported languages 159 | io.socket.get('/chat/languages', {}, function(data) { 160 | languageMapping = data.languageMapping; 161 | 162 | var rendered = Mustache.render($('#language-select').html(), 163 | { languages: data.languages } 164 | ); 165 | 166 | $('.languages-select').html(rendered); 167 | 168 | callBack(); 169 | }); 170 | } 171 | 172 | function autoDetectLanguage() { 173 | var code = window.navigator.userLanguage || window.navigator.language; 174 | 175 | var option = $(".languages-select option[data-code='" + code +"']"); 176 | 177 | if(option.length === 0) { 178 | console.log('Language code '+ code +' was not found. Defaulting to English'); 179 | $('.languages-select').val('en').change(); 180 | } else { 181 | $('.languages-select').val(code).change(); 182 | } 183 | } 184 | 185 | function getCookie(name) { 186 | var value = "; " + document.cookie; 187 | var parts = value.split("; " + name + "="); 188 | if (parts.length == 2) return parts.pop().split(";").shift(); 189 | } 190 | -------------------------------------------------------------------------------- /assets/js/chat.js: -------------------------------------------------------------------------------- 1 | function renderMessage(message) { 2 | var lang = $('#current-language').val(); 3 | 4 | if(message.user.id === currentUser.id) { 5 | var rendered = Mustache.render($('#chat-message-yourself').html(), message); 6 | 7 | $('.chat-messages').append(rendered); 8 | 9 | updateScroll(); 10 | 11 | $('.chat-messages').linkify({ 12 | target: "_blank" 13 | }); 14 | } else { 15 | translateText(message.body, lang).done(function(res) { 16 | var rendered = Mustache.render($('#chat-message').html(), { 17 | shortName: message.user.name.substr(0, 1), 18 | body: res, 19 | originalLanguage: languageMapping[res.detectedSourceLanguage], 20 | translate: (lang != res.detectedSourceLanguage) 21 | }); 22 | 23 | $('.chat-messages').append(rendered); 24 | 25 | $('.chat-messages').linkify({ 26 | target: "_blank" 27 | }); 28 | }); 29 | } 30 | } 31 | 32 | function renderMessages(messages) { 33 | var lang = $('#current-language').val(); 34 | 35 | messages.forEach(function(message) { 36 | if(message.user.id === currentUser.id) { 37 | var params = { body: message.originalBody } 38 | 39 | var rendered = Mustache.render($('#chat-message-yourself').html(), params); 40 | 41 | $('.chat-messages').append(rendered); 42 | } else { 43 | var rendered = Mustache.render($('#chat-message').html(), { 44 | shortName: message.user.name.substr(0, 1), 45 | body: {translatedText: message.body, originalText: message.originalBody}, 46 | originalLanguage: languageMapping[message.sourceLanguage], 47 | translate: (lang != message.sourceLanguage) 48 | }); 49 | 50 | $('.chat-messages').append(rendered); 51 | } 52 | 53 | $('.chat-messages').linkify({ 54 | target: "_blank" 55 | }); 56 | }); 57 | 58 | $('.messages.loading-indicator').addClass('hide'); 59 | 60 | updateScroll(); 61 | } 62 | 63 | // Render User has joined message 64 | function renderUserJoinedMessage(message) { 65 | var template = Mustache.render($('#joined-message').html(), { 66 | shortName: message.data.name.substr(0, 1), 67 | name: message.data.name 68 | }); 69 | 70 | $('.chat-messages').append(template); 71 | } 72 | 73 | function renderLeftRoomMessage(message){ 74 | var template = Mustache.render($('#left-message').html(), message.previous); 75 | 76 | $('.chat-messages').append(template); 77 | } 78 | 79 | function renderWelcomeMessage(username, force) { 80 | var lang = $('#current-language').val(); 81 | 82 | if($('#received-welcome').val() === 'false' || force) { 83 | return $.ajax({ 84 | url: "chat/welcome", 85 | data: { username: $('#username').val()} 86 | }).done(function(res) { 87 | translateText(res.welcome_message, $('#current-language').val()).done(function(res) { 88 | var template = Mustache.render($('#welcome-message').html(), { 89 | username: username, 90 | translatedText: res.translatedText, 91 | originalText: res.originalText, 92 | translate: (lang != res.detectedSourceLanguage) 93 | }); 94 | 95 | $('.chat-messages').append(template); 96 | $('#received-welcome').val('true'); 97 | }); 98 | }); 99 | 100 | } 101 | } 102 | 103 | function updateUserCount(data) { 104 | $('#random-active').text(data.count); 105 | } 106 | 107 | function submitMessage(message) { 108 | io.socket.post('/message', { body: message, sourceLanguage: $('#current-language').val() }, function(err, data) { 109 | if(err) { return console.log(err); } 110 | }); 111 | } 112 | 113 | //used to update chat window scroll when new messages are added 114 | function updateScroll(){ 115 | var element = document.getElementById("messages-scrollable"); 116 | element.scrollTop = element.scrollHeight; 117 | } 118 | 119 | function promptUsername(callBack) { 120 | var username = prompt("Please enter your name", ""); 121 | 122 | if (username == null) { 123 | promptUsername(callBack); 124 | } else { 125 | $('#username').val(username); 126 | 127 | callBack(username); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /assets/js/dependencies/linkify-jquery.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(e,t,n){var i=function(t,n){function i(e,t,n){var i=n[n.length-1];e.replaceChild(i,t);for(var r=n.length-2;r>=0;r--)e.insertBefore(n[r],i),i=n[r]}function r(e,t,n){for(var i=[],r=e,a=Array.isArray(r),o=0,r=a?r:r[Symbol.iterator]();;){var l;if(a){if(o>=r.length)break;l=r[o++]}else{if(o=r.next(),o.done)break;l=o.value}var f=l;if("nl"===f.type&&t.nl2br)i.push(n.createElement("br"));else if(f.isLink&&t.check(f)){var s=t.resolve(f),c=s.formatted,u=s.formattedHref,d=s.tagName,m=s.className,y=s.target,h=s.events,k=s.attributes,v=n.createElement(d);if(v.setAttribute("href",u),m&&v.setAttribute("class",m),y&&v.setAttribute("target",y),k)for(var g in k)v.setAttribute(g,k[g]);if(h)for(var b in h)v.addEventListener?v.addEventListener(b,h[b]):v.attachEvent&&v.attachEvent("on"+b,h[b]);v.appendChild(n.createTextNode(c)),i.push(v)}else i.push(n.createTextNode(f.toString()))}return i}function a(e,t,n){if(!e||e.nodeType!==d)throw new Error("Cannot linkify "+e+" - Invalid DOM Node type");var o=t.ignoreTags;if("A"===e.tagName||s.contains(o,e.tagName))return e;for(var l=e.firstChild;l;){switch(l.nodeType){case d:a(l,t,n);break;case m:var c=l.nodeValue,y=f(c);if(0===y.length||1===y.length&&y[0]instanceof u)break;var h=r(y,t,n);i(e,l,h),l=h[h.length-1]}l=l.nextSibling}return e}function o(t,n){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];try{i=i||document||e&&e.document||global&&global.document}catch(r){}if(!i)throw new Error("Cannot find document implementation. If you are in a non-browser environment like Node.js, pass the document implementation as the third argument to linkifyElement.");return n=new c(n),a(t,n,i)}function l(t){function n(e){return e=o.normalize(e),this.each(function(){o.helper(this,e,i)})}var i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t.fn=t.fn||{};try{i=i||document||e&&e.document||global&&global.document}catch(r){}if(!i)throw new Error("Cannot find document implementation. If you are in a non-browser environment like Node.js, pass the document implementation as the second argument to linkify/jquery");"function"!=typeof t.fn.linkify&&(t.fn.linkify=n,t(i).ready(function(){t("[data-linkify]").each(function(){var e=t(this),n=e.data(),i=n.linkify,r=n.linkifyNlbr,a={attributes:n.linkifyAttributes,defaultProtocol:n.linkifyDefaultProtocol,events:n.linkifyEvents,format:n.linkifyFormat,formatHref:n.linkifyFormatHref,nl2br:!!r&&0!==r&&"false"!==r,tagName:n.linkifyTagname,target:n.linkifyTarget,className:n.linkifyClassName||n.linkifyLinkclass,validate:n.linkifyValidate,ignoreTags:n.linkifyIgnoreTags},o="this"===i?e:e.find(i);o.linkify(a)})}))}t="default"in t?t["default"]:t;var f=n.tokenize,s=n.options,c=s.Options,u=n.parser.TOKENS.TEXT,d=1,m=3;o.helper=a,o.normalize=function(e){return new c(e)};try{!define&&(e.linkifyElement=o)}catch(y){}return l}(n,t);"function"!=typeof n.fn.linkify&&i(n)}(window,linkify,jQuery); -------------------------------------------------------------------------------- /assets/js/dependencies/linkify.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};!function(e){function n(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=Object.create(t.prototype);for(var a in n)o[a]=n[a];return o.constructor=e,e.prototype=o,e}function o(t){t=t||{},this.defaultProtocol=t.defaultProtocol||h.defaultProtocol,this.events=t.events||h.events,this.format=t.format||h.format,this.formatHref=t.formatHref||h.formatHref,this.nl2br=t.nl2br||h.nl2br,this.tagName=t.tagName||h.tagName,this.target=t.target||h.target,this.validate=t.validate||h.validate,this.ignoreTags=[],this.attributes=t.attributes||t.linkAttributes||h.attributes,this.className=t.className||t.linkClass||h.className;for(var e=t.ignoreTags||h.ignoreTags,n=0;n=r)return[];for(;a1&&void 0!==arguments[1]?arguments[1]:null,n=this.next(new t(""));return n===this.defaultTransition?(n=new this.constructor(e),this.on(t,n)):e&&(n.T=e),n},test:function(t,e){return t instanceof e}}),b=l();b.prototype={toString:function(){return this.v+""}};var v=u(),y=u("@"),k=u(":"),w=u("."),j=u(),x=u(),z=u("\n"),O=u(),S=u("+"),N=u("#"),T=u(),A=u("mailto:"),L=u("?"),E=u("/"),C=u("_"),P=u(),R=u(),q=u(),H=u("{"),B=u("["),U=u("<"),M=u("("),D=u("}"),I=u("]"),K=u(">"),_=u(")"),G=u("&"),Y=Object.freeze({Base:b,DOMAIN:v,AT:y,COLON:k,DOT:w,PUNCTUATION:j,LOCALHOST:x,NL:z,NUM:O,PLUS:S,POUND:N,QUERY:L,PROTOCOL:T,MAILTO:A,SLASH:E,UNDERSCORE:C,SYM:P,TLD:R,WS:q,OPENBRACE:H,OPENBRACKET:B,OPENANGLEBRACKET:U,OPENPAREN:M,CLOSEBRACE:D,CLOSEBRACKET:I,CLOSEANGLEBRACKET:K,CLOSEPAREN:_,AMPERSAND:G}),Q="aaa|aarp|abb|abbott|abogado|ac|academy|accenture|accountant|accountants|aco|active|actor|ad|adac|ads|adult|ae|aeg|aero|af|afl|ag|agency|ai|aig|airforce|airtel|al|alibaba|alipay|allfinanz|alsace|am|amica|amsterdam|an|analytics|android|ao|apartments|app|apple|aq|aquarelle|ar|aramco|archi|army|arpa|arte|as|asia|associates|at|attorney|au|auction|audi|audio|author|auto|autos|avianca|aw|ax|axa|az|azure|ba|baidu|band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bb|bbc|bbva|bcg|bcn|bd|be|beats|beer|bentley|berlin|best|bet|bf|bg|bh|bharti|bi|bible|bid|bike|bing|bingo|bio|biz|bj|black|blackfriday|bloomberg|blue|bm|bms|bmw|bn|bnl|bnpparibas|bo|boats|boehringer|bom|bond|boo|book|boots|bosch|bostik|bot|boutique|br|bradesco|bridgestone|broadway|broker|brother|brussels|bs|bt|budapest|bugatti|build|builders|business|buy|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|call|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|cc|cd|ceb|center|ceo|cern|cf|cfa|cfd|cg|ch|chanel|channel|chase|chat|cheap|chloe|christmas|chrome|church|ci|cipriani|circle|cisco|citic|city|cityeats|ck|cl|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|cm|cn|co|coach|codes|coffee|college|cologne|com|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses|cr|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|de|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|download|drive|dubai|durban|dvag|dz|earth|eat|ec|edeka|edu|education|ee|eg|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|fast|feedback|ferrero|fi|film|final|finance|financial|firestone|firmdale|fish|fishing|fit|fitness|fj|fk|flickr|flights|florist|flowers|flsmidth|fly|fm|fo|foo|football|ford|forex|forsale|forum|foundation|fox|fr|fresenius|frl|frogans|frontier|fund|furniture|futbol|fyi|ga|gal|gallery|gallup|game|garden|gb|gbiz|gd|gdn|ge|gea|gent|genting|gf|gg|ggee|gh|gi|gift|gifts|gives|giving|gl|glass|gle|global|globo|gm|gmail|gmbh|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|got|gov|gp|gq|gr|grainger|graphics|gratis|green|gripe|group|gs|gt|gu|gucci|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|hdfcbank|health|healthcare|help|helsinki|here|hermes|hiphop|hitachi|hiv|hk|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hr|hsbc|ht|hu|hyundai|ibm|icbc|ice|icu|id|ie|ifm|iinet|il|im|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insurance|insure|int|international|investments|io|ipiranga|iq|ir|irish|is|iselect|ist|istanbul|it|itau|iwc|jaguar|java|jcb|je|jetzt|jewelry|jlc|jll|jm|jmp|jo|jobs|joburg|jot|joy|jp|jpmorgan|jprs|juegos|kaufen|kddi|ke|kerryhotels|kerrylogistics|kerryproperties|kfh|kg|kh|ki|kia|kim|kinder|kitchen|kiwi|km|kn|koeln|komatsu|kp|kpn|kr|krd|kred|kuokgroup|kw|ky|kyoto|kz|la|lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latrobe|law|lawyer|lb|lc|lds|lease|leclerc|legal|lexus|lgbt|li|liaison|lidl|life|lifeinsurance|lifestyle|lighting|like|limited|limo|lincoln|linde|link|live|living|lixil|lk|loan|loans|local|locus|lol|london|lotte|lotto|love|lr|ls|lt|ltd|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|makeup|man|management|mango|market|marketing|markets|marriott|mba|mc|md|me|med|media|meet|melbourne|meme|memorial|men|menu|meo|mg|mh|miami|microsoft|mil|mini|mk|ml|mm|mma|mn|mo|mobi|mobily|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mp|mq|mr|ms|mt|mtn|mtpc|mtr|mu|museum|mutuelle|mv|mw|mx|my|mz|na|nadex|nagoya|name|natura|navy|nc|ne|nec|net|netbank|network|neustar|new|news|nexus|nf|ng|ngo|nhk|ni|nico|nikon|ninja|nissan|nl|no|nokia|norton|nowruz|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|office|okinawa|om|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|origins|osaka|otsuka|ovh|pa|page|pamperedchef|panerai|paris|pars|partners|parts|party|passagens|pe|pet|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pid|pin|ping|pink|pizza|pk|pl|place|play|playstation|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|promo|properties|property|protection|ps|pt|pub|pw|pwc|py|qa|qpon|quebec|quest|racing|re|read|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|ricoh|rio|rip|ro|rocher|rocks|rodeo|room|rs|rsvp|ru|ruhr|run|rw|rwe|ryukyu|sa|saarland|safe|safety|sakura|sale|salon|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|sas|saxo|sb|sbs|sc|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scor|scot|sd|se|seat|security|seek|select|sener|services|seven|sew|sex|sexy|sfr|sg|sh|sharp|shell|shia|shiksha|shoes|show|shriram|si|singles|site|sj|sk|ski|skin|sky|skype|sl|sm|smile|sn|sncf|so|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|spiegel|spot|spreadbetting|sr|srl|st|stada|star|starhub|statefarm|statoil|stc|stcgroup|stockholm|storage|store|studio|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swatch|swiss|sx|sy|sydney|symantec|systems|sz|tab|taipei|taobao|tatamotors|tatar|tattoo|tax|taxi|tc|tci|td|team|tech|technology|tel|telecity|telefonica|temasek|tennis|tf|tg|th|thd|theater|theatre|tickets|tienda|tiffany|tips|tires|tirol|tj|tk|tl|tm|tmall|tn|to|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|tp|tr|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tt|tube|tui|tunes|tushu|tv|tvs|tw|tz|ua|ubs|ug|uk|unicom|university|uno|uol|us|uy|uz|va|vacations|vana|vc|ve|vegas|ventures|verisign|versicherung|vet|vg|vi|viajes|video|viking|villas|vin|vip|virgin|vision|vista|vistaprint|viva|vlaanderen|vn|vodka|volkswagen|vote|voting|voto|voyage|vu|vuelos|wales|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weir|wf|whoswho|wien|wiki|williamhill|win|windows|wine|wme|wolterskluwer|work|works|world|ws|wtc|wtf|xbox|xerox|xin|xperia|xxx|xyz|yachts|yahoo|yamaxun|yandex|ye|yodobashi|yoga|yokohama|youtube|yt|za|zara|zero|zip|zm|zone|zuerich|zw".split("|"),W="0123456789".split(""),X="0123456789abcdefghijklmnopqrstuvwxyz".split(""),Z=[" ","\f","\r","\t","\x0B"," "," ","᠎"],F=[],J=function(t){return new m(t)},V=J(),$=J(O),tt=J(v),et=J(),nt=J(q);V.on("@",J(y)).on(".",J(w)).on("+",J(S)).on("#",J(N)).on("?",J(L)).on("/",J(E)).on("_",J(C)).on(":",J(k)).on("{",J(H)).on("[",J(B)).on("<",J(U)).on("(",J(M)).on("}",J(D)).on("]",J(I)).on(">",J(K)).on(")",J(_)).on("&",J(G)).on([",",";","!",'"',"'"],J(j)),V.on("\n",J(z)).on(Z,nt),nt.on(Z,nt);for(var ot=0;ot=0&&u++,c++,a++;if(!(u<0)){a-=u,c-=u;var p=l.emit();o.push(new p(t.substr(a-c,c)))}}return o},yt=V,kt=Object.freeze({State:m,TOKENS:Y,run:vt,start:yt}),wt=l();wt.prototype={type:"token",isLink:!1,toString:function(){for(var t=[],e=0;e0&&void 0!==arguments[0]?arguments[0]:"http";return{type:this.type,value:this.toString(),href:this.toHref(t)}}};var jt=n(wt,l(),{type:"email",isLink:!0}),xt=n(wt,l(),{type:"email",isLink:!0,toHref:function(){this.v;return"mailto:"+this.toString()}}),zt=n(wt,l(),{type:"text"}),Ot=n(wt,l(),{type:"nl"}),St=n(wt,l(),{type:"url",isLink:!0,toHref:function(){for(var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"http",e=!1,n=!1,o=this.v,a=[],r=0;o[r]instanceof T;)e=!0,a.push(o[r].toString().toLowerCase()),r++;for(;o[r]instanceof E;)n=!0,a.push(o[r].toString()),r++;for(;p(o[r]);)a.push(o[r].toString().toLowerCase()),r++;for(;r=0&&u++,n++,c++;if(u<0)for(var p=n-c;p0&&(o.push(new zt(a)),a=[]),n-=u,c-=u;var h=l.emit();o.push(new h(t.slice(n-c,n)))}}return a.length>0&&o.push(new zt(a)),o},fe=Object.freeze({State:d,TOKENS:Nt,run:ge,start:At});Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var me=function(t){return ge(vt(t))},de=function(t){for(var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=me(t),o=[],a=0;a1&&void 0!==arguments[1]?arguments[1]:null,n=me(t);return 1===n.length&&n[0].isLink&&(!e||n[0].type===e)};e.find=de,e.inherits=n,e.options=g,e.parser=fe,e.scanner=kt,e.test=be,e.tokenize=me}(self.linkify=self.linkify||{})}(); -------------------------------------------------------------------------------- /assets/js/translate.js: -------------------------------------------------------------------------------- 1 | function translateText(string, targetLang) { 2 | return $.ajax({ 3 | url: "/translate", 4 | data: { msg: string, lang: targetLang } 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /assets/js/user.js: -------------------------------------------------------------------------------- 1 | function postMessage() { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | # The robots.txt file is used to control how search engines index your live URLs. 2 | # See http://sailsjs.org/documentation/anatomy/my-app/assets/robots-txt for more information. 3 | 4 | 5 | 6 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 7 | # User-Agent: * 8 | # Disallow: / 9 | -------------------------------------------------------------------------------- /assets/styles/icons.less: -------------------------------------------------------------------------------- 1 | .active-icon { 2 | background-image: url('../images/active-icon.svg'); 3 | height: 9px; 4 | width: 9px; 5 | display: inline-block; 6 | margin-right: 2px; 7 | } 8 | 9 | .inactive-icon { 10 | background-image: url('../images/inactive-icon.svg'); 11 | height: 9px; 12 | width: 9px; 13 | display: inline-block; 14 | margin-right: 2px; 15 | } 16 | -------------------------------------------------------------------------------- /assets/styles/importer.less: -------------------------------------------------------------------------------- 1 | @import 'bootstrap.min.css'; 2 | @import 'pages/chat-room.less'; 3 | @import 'icons.less'; 4 | -------------------------------------------------------------------------------- /assets/styles/pages/chat-room.less: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100vw; 3 | } 4 | .master-container { 5 | overflow-x: hidden; 6 | } 7 | .chat-window { 8 | overflow: hidden; 9 | } 10 | .chat-sidebar { 11 | .card-header { 12 | text-align: center; 13 | } 14 | } 15 | .chat-window, .chat-sidebar { 16 | height: 100vh; 17 | border: 0; 18 | border-radius: 0; 19 | 20 | .card-header { 21 | border-radius: 0; 22 | } 23 | } 24 | .loading-indicator { 25 | position: absolute; 26 | left: 50%; 27 | top: 50%; 28 | 29 | margin-left: -25px; 30 | margin-top: -25px; 31 | } 32 | .sidebar-list { 33 | margin: 0; 34 | padding: 0; 35 | padding-left: 14px; 36 | list-style: none; 37 | 38 | li { 39 | padding: 10px 15px; 40 | border-top: 1px solid rgba(201,200,238,0.15); 41 | border-bottom: 1px solid rgba(201,200,238,0.15); 42 | } 43 | } 44 | .live-stats { 45 | li { 46 | padding-left: 50px; 47 | } 48 | } 49 | .channel-list { 50 | .channel-active { 51 | float: right; 52 | font-size: 11px; 53 | line-height: 25px; 54 | } 55 | } 56 | .chat-messages { 57 | width: 100%; 58 | } 59 | 60 | //helpers 61 | .scrollable { 62 | overflow: scroll; 63 | height: inherit; 64 | } 65 | 66 | .hide { 67 | display: none; 68 | } 69 | .no-padding { 70 | padding: 0; 71 | } 72 | //end helpers 73 | 74 | .chat-message { 75 | margin-bottom: 5px; 76 | min-height: 40px; 77 | 78 | .message-body { 79 | width: auto; 80 | max-width: 85%; 81 | font-size: 12px; 82 | line-height: 14px; 83 | padding: 10px 15px; 84 | background: #FFFFFF; 85 | border-radius: 6px; 86 | min-width: 250px; 87 | cursor: pointer; 88 | display: inline-block; 89 | position: relative; 90 | .info { 91 | font-size: 10px; 92 | } 93 | } 94 | .rate-btn { 95 | position: absolute; 96 | right: 10px; 97 | } 98 | .rate-btn:hover { 99 | text-decoration: underline; 100 | } 101 | .action-buttons { 102 | margin-top: 5px; 103 | } 104 | .original-language { 105 | text-align: left; 106 | } 107 | .see-original { 108 | float: right; 109 | text-align: right; 110 | font-style: italic; 111 | margin-left: 15px; 112 | } 113 | } 114 | .profile-picture { 115 | display: inline-block; 116 | text-transform: uppercase; 117 | border-radius: 50%; 118 | width: 30px; 119 | height: 30px; 120 | text-align: center; 121 | padding-top: 3px; 122 | 123 | .short-name { 124 | // visibility: hidden; 125 | text-transform: uppercase; 126 | display: inline-block; 127 | width: 15px; 128 | } 129 | .short-name:first-letter { 130 | // visibility:visible; 131 | } 132 | } 133 | .compose-message { 134 | padding: 15px 20px 0; 135 | background-color: #fff; 136 | 137 | position: relative; 138 | bottom: 10px; 139 | .form-control { 140 | border: none; 141 | } 142 | } 143 | 144 | .chat-message.yourself { 145 | .message-body { 146 | float: right; 147 | min-width: inherit; 148 | } 149 | text-align: right; 150 | } 151 | .purple.chat-sidebar { 152 | background-color: #3F3F5F; 153 | color: #CAC8EE; 154 | 155 | .card-header { 156 | background-color: #2D2D4E; 157 | } 158 | } 159 | .purple.chat-window { 160 | background-color: #DCDDF5; 161 | 162 | .card-header { 163 | background-color: #585886; 164 | color: #CAC8EE; 165 | } 166 | .profile-picture { 167 | background-color: #5B5B89; 168 | color: #fff; 169 | } 170 | .message-body { 171 | color: #8785AB; 172 | 173 | .info { 174 | color: rgba(135,133,171,0.50); 175 | } 176 | } 177 | .chat-message.yourself { 178 | .message-body { 179 | background-color: #89A1FC; 180 | color: #fff; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /assets/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawilster/poly/eb9051d49e406751e0bad3418b1e5eb5e7de201b/assets/templates/.gitkeep -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "**/.*", 4 | "node_modules", 5 | "bower_components", 6 | "test", 7 | "tests" 8 | ], 9 | "dependencies": { 10 | "bootstrap": "v4.0.0-alpha.6", 11 | "mustache.js": "mustache#^2.3.0" 12 | }, 13 | "name": "chat-app", 14 | "version": "0.0.0", 15 | "author": "william", 16 | "private": true 17 | } 18 | -------------------------------------------------------------------------------- /config/blueprints.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blueprint API Configuration 3 | * (sails.config.blueprints) 4 | * 5 | * These settings are for the global configuration of blueprint routes and 6 | * request options (which impact the behavior of blueprint actions). 7 | * 8 | * You may also override any of these settings on a per-controller basis 9 | * by defining a '_config' key in your controller definition, and assigning it 10 | * a configuration object with overrides for the settings in this file. 11 | * A lot of the configuration options below affect so-called "CRUD methods", 12 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 13 | * 14 | * It's important to realize that, even if you haven't defined these yourself, as long as 15 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 16 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 17 | * 18 | * For more information on the blueprint API, check out: 19 | * http://sailsjs.org/#!/documentation/reference/blueprint-api 20 | * 21 | * For more information on the settings in this file, see: 22 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.blueprints.html 23 | * 24 | */ 25 | 26 | module.exports.blueprints = { 27 | 28 | /*************************************************************************** 29 | * * 30 | * Action routes speed up the backend development workflow by * 31 | * eliminating the need to manually bind routes. When enabled, GET, POST, * 32 | * PUT, and DELETE routes will be generated for every one of a controller's * 33 | * actions. * 34 | * * 35 | * If an `index` action exists, additional naked routes will be created for * 36 | * it. Finally, all `actions` blueprints support an optional path * 37 | * parameter, `id`, for convenience. * 38 | * * 39 | * `actions` are enabled by default, and can be OK for production-- * 40 | * however, if you'd like to continue to use controller/action autorouting * 41 | * in a production deployment, you must take great care not to * 42 | * inadvertently expose unsafe/unintentional controller logic to GET * 43 | * requests. * 44 | * * 45 | ***************************************************************************/ 46 | 47 | actions: true, 48 | 49 | /*************************************************************************** 50 | * * 51 | * RESTful routes (`sails.config.blueprints.rest`) * 52 | * * 53 | * REST blueprints are the automatically generated routes Sails uses to * 54 | * expose a conventional REST API on top of a controller's `find`, * 55 | * `create`, `update`, and `destroy` actions. * 56 | * * 57 | * For example, a BoatController with `rest` enabled generates the * 58 | * following routes: * 59 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: * 60 | * GET /boat -> BoatController.find * 61 | * GET /boat/:id -> BoatController.findOne * 62 | * POST /boat -> BoatController.create * 63 | * PUT /boat/:id -> BoatController.update * 64 | * DELETE /boat/:id -> BoatController.destroy * 65 | * * 66 | * `rest` blueprint routes are enabled by default, and are suitable for use * 67 | * in a production scenario, as long you take standard security precautions * 68 | * (combine w/ policies, etc.) * 69 | * * 70 | ***************************************************************************/ 71 | 72 | rest: true, 73 | 74 | /*************************************************************************** 75 | * * 76 | * Shortcut routes are simple helpers to provide access to a * 77 | * controller's CRUD methods from your browser's URL bar. When enabled, * 78 | * GET, POST, PUT, and DELETE routes will be generated for the * 79 | * controller's`find`, `create`, `update`, and `destroy` actions. * 80 | * * 81 | * `shortcuts` are enabled by default, but should be disabled in * 82 | * production. * 83 | * * 84 | ***************************************************************************/ 85 | 86 | shortcuts: true, 87 | 88 | /*************************************************************************** 89 | * * 90 | * An optional mount path for all blueprint routes on a controller, * 91 | * including `rest`, `actions`, and `shortcuts`. This allows you to take * 92 | * advantage of blueprint routing, even if you need to namespace your API * 93 | * methods. * 94 | * * 95 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 96 | * `sails.config.routes`) * 97 | * * 98 | ***************************************************************************/ 99 | 100 | prefix: '', 101 | 102 | /*************************************************************************** 103 | * * 104 | * An optional mount path for all REST blueprint routes on a controller. * 105 | * And it do not include `actions` and `shortcuts` routes. * 106 | * This allows you to take advantage of REST blueprint routing, * 107 | * even if you need to namespace your RESTful API methods * 108 | * * 109 | ***************************************************************************/ 110 | 111 | // restPrefix: '', 112 | 113 | /*************************************************************************** 114 | * * 115 | * Whether to pluralize controller names in blueprint routes. * 116 | * * 117 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 118 | * `sails.config.routes`) * 119 | * * 120 | * For example, REST blueprints for `FooController` with `pluralize` * 121 | * enabled: * 122 | * GET /foos/:id? * 123 | * POST /foos * 124 | * PUT /foos/:id? * 125 | * DELETE /foos/:id? * 126 | * * 127 | ***************************************************************************/ 128 | 129 | pluralize: false, 130 | 131 | /*************************************************************************** 132 | * * 133 | * Whether the blueprint controllers should populate model fetches with * 134 | * data from other models which are linked by associations * 135 | * * 136 | * If you have a lot of data in one-to-many associations, leaving this on * 137 | * may result in very heavy api calls * 138 | * * 139 | ***************************************************************************/ 140 | 141 | // populate: true, 142 | 143 | /**************************************************************************** 144 | * * 145 | * Whether to run Model.watch() in the find and findOne blueprint actions. * 146 | * Can be overridden on a per-model basis. * 147 | * * 148 | ****************************************************************************/ 149 | 150 | // autoWatch: true, 151 | 152 | /**************************************************************************** 153 | * * 154 | * The default number of records to show in the response from a "find" * 155 | * action. Doubles as the default size of populated arrays if populate is * 156 | * true. * 157 | * * 158 | ****************************************************************************/ 159 | 160 | // defaultLimit: 30 161 | 162 | }; 163 | -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap 3 | * (sails.config.bootstrap) 4 | * 5 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 6 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 7 | * 8 | * For more information on bootstrapping your app, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.bootstrap.html 10 | */ 11 | 12 | module.exports.bootstrap = function(cb) { 13 | 14 | // It's very important to trigger this callback method when you are finished 15 | // with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap) 16 | cb(); 17 | }; 18 | -------------------------------------------------------------------------------- /config/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.connections.html 20 | */ 21 | 22 | module.exports.connections = { 23 | 24 | /*************************************************************************** 25 | * * 26 | * Local disk storage for DEVELOPMENT ONLY * 27 | * * 28 | * Installed by default. * 29 | * * 30 | ***************************************************************************/ 31 | localDiskDb: { 32 | adapter: 'sails-disk' 33 | }, 34 | 35 | 36 | /*************************************************************************** 37 | * * 38 | * MySQL is the world's most popular relational database. * 39 | * http://en.wikipedia.org/wiki/MySQL * 40 | * * 41 | * Run: npm install sails-mysql * 42 | * * 43 | ***************************************************************************/ 44 | // someMysqlServer: { 45 | // adapter: 'sails-mysql', 46 | // host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 47 | // user: 'YOUR_MYSQL_USER', //optional 48 | // password: 'YOUR_MYSQL_PASSWORD', //optional 49 | // database: 'YOUR_MYSQL_DB' //optional 50 | // }, 51 | 52 | /*************************************************************************** 53 | * * 54 | * MongoDB is the leading NoSQL database. * 55 | * http://en.wikipedia.org/wiki/MongoDB * 56 | * * 57 | * Run: npm install sails-mongo * 58 | * * 59 | ***************************************************************************/ 60 | // mongodb: { 61 | // adapter: 'sails-mongo', 62 | // host: 'ds157439.mlab.com', 63 | // port: 57439, 64 | // user: 'poly', //optional 65 | // password: 'qTZbxAt8eJfCgkKKYhbJGevJLQRNewHQ', //optional 66 | // database: 'heroku_vxmclkvk' //optional 67 | // }, 68 | // mongodb: { 69 | // adapter: 'sails-mongo', 70 | // host: 'localhost', 71 | // port: 27017, 72 | // user: '', //optional 73 | // password: '', //optional 74 | // database: 'poly_db_dev' //optional 75 | // }, 76 | /*************************************************************************** 77 | * * 78 | * PostgreSQL is another officially supported relational database. * 79 | * http://en.wikipedia.org/wiki/PostgreSQL * 80 | * * 81 | * Run: npm install sails-postgresql * 82 | * * 83 | * * 84 | ***************************************************************************/ 85 | // somePostgresqlServer: { 86 | // adapter: 'sails-postgresql', 87 | // host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', 88 | // user: 'YOUR_POSTGRES_USER', // optional 89 | // password: 'YOUR_POSTGRES_PASSWORD', // optional 90 | // database: 'YOUR_POSTGRES_DB' //optional 91 | // } 92 | 93 | 94 | /*************************************************************************** 95 | * * 96 | * More adapters: https://github.com/balderdashy/sails * 97 | * * 98 | ***************************************************************************/ 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Origin Resource Sharing (CORS) Settings 3 | * (sails.config.cors) 4 | * 5 | * CORS is like a more modern version of JSONP-- it allows your server/API 6 | * to successfully respond to requests from client-side JavaScript code 7 | * running on some other domain (e.g. google.com) 8 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 9 | * 10 | * For more information on CORS, check out: 11 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 12 | * 13 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 14 | * by adding a "cors" object to the route configuration: 15 | * 16 | * '/get foo': { 17 | * controller: 'foo', 18 | * action: 'bar', 19 | * cors: { 20 | * origin: 'http://foobar.com,https://owlhoot.com' 21 | * } 22 | * } 23 | * 24 | * For more information on this configuration file, see: 25 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.cors.html 26 | * 27 | */ 28 | 29 | module.exports.cors = { 30 | 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | 40 | // allRoutes: false, 41 | 42 | /*************************************************************************** 43 | * * 44 | * Which domains which are allowed CORS access? This can be a * 45 | * comma-delimited list of hosts (beginning with http:// or https://) or * 46 | * "*" to allow all domains CORS access. * 47 | * * 48 | ***************************************************************************/ 49 | 50 | // origin: '*', 51 | 52 | /*************************************************************************** 53 | * * 54 | * Allow cookies to be shared for CORS requests? * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // credentials: true, 59 | 60 | /*************************************************************************** 61 | * * 62 | * Which methods should be allowed for CORS requests? This is only used in * 63 | * response to preflight requests (see article linked above for more info) * 64 | * * 65 | ***************************************************************************/ 66 | 67 | // methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 68 | 69 | /*************************************************************************** 70 | * * 71 | * Which headers should be allowed for CORS requests? This is only used in * 72 | * response to preflight requests. * 73 | * * 74 | ***************************************************************************/ 75 | 76 | // headers: 'content-type' 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /config/csrf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cross-Site Request Forgery Protection Settings 3 | * (sails.config.csrf) 4 | * 5 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 6 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 7 | * 8 | * When enabled, all non-GET requests to the Sails server must be accompanied by 9 | * a special token, identified as the '_csrf' parameter. 10 | * 11 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 12 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 13 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 14 | * 15 | * This allows us to have certainty that our users' requests haven't been hijacked, 16 | * and that the requests they're making are intentional and legitimate. 17 | * 18 | * This token has a short-lived expiration timeline, and must be acquired by either: 19 | * 20 | * (a) For traditional view-driven web apps: 21 | * Fetching it from one of your views, where it may be accessed as 22 | * a local variable, e.g.: 23 | *
    24 | * 25 | *
    26 | * 27 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 28 | * Sending a GET request to the `/csrfToken` route, where it will be returned 29 | * as JSON, e.g.: 30 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 31 | * 32 | * 33 | * Enabling this option requires managing the token in your front-end app. 34 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 35 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 36 | * 37 | * For more information on CSRF, check out: 38 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 39 | * 40 | * For more information on this configuration file, including info on CSRF + CORS, see: 41 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.csrf.html 42 | * 43 | */ 44 | 45 | /**************************************************************************** 46 | * * 47 | * Enabled CSRF protection for your site? * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // module.exports.csrf = false; 52 | 53 | /**************************************************************************** 54 | * * 55 | * You may also specify more fine-grained settings for CSRF, including the * 56 | * domains which are allowed to request the CSRF token via AJAX. These * 57 | * settings override the general CORS settings in your config/cors.js file. * 58 | * * 59 | ****************************************************************************/ 60 | 61 | // module.exports.csrf = { 62 | // grantTokenViaAjax: true, 63 | // origin: '' 64 | // } 65 | -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Development environment settings 3 | * 4 | * This file can include shared settings for a development team, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the development * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /config/env/development/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.connections.html 20 | */ 21 | 22 | module.exports.connections = { 23 | mongodb: { 24 | adapter: 'sails-mongo', 25 | host: 'localhost', 26 | port: 27017, 27 | user: '', //optional 28 | password: '', //optional 29 | database: 'poly_db_dev' //optional 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Production environment settings 3 | * 4 | * This file can include shared settings for a production environment, 5 | * such as API keys or remote database passwords. If you're using 6 | * a version control solution for your Sails app, this file will 7 | * be committed to your repository unless you add it to your .gitignore 8 | * file. If your repository will be publicly viewable, don't add 9 | * any private information to this file! 10 | * 11 | */ 12 | 13 | module.exports = { 14 | 15 | /*************************************************************************** 16 | * Set the default database connection for models in the production * 17 | * environment (see config/connections.js and config/models.js ) * 18 | ***************************************************************************/ 19 | 20 | // models: { 21 | // connection: 'mongodb', 22 | // }, 23 | 24 | session: { 25 | url: process.env.REDIS_URL 26 | }, 27 | 28 | /*************************************************************************** 29 | * Set the port in the production environment to 80 * 30 | ***************************************************************************/ 31 | 32 | port: 80, 33 | 34 | /*************************************************************************** 35 | * Set the log level in production environment to "silent" * 36 | ***************************************************************************/ 37 | 38 | log: { 39 | level: "silent" 40 | } 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /config/env/production/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connections 3 | * (sails.config.connections) 4 | * 5 | * `Connections` are like "saved settings" for your adapters. What's the difference between 6 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 7 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 8 | * A `connection` is that additional information. 9 | * 10 | * Each model must have a `connection` property (a string) which is references the name of one 11 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 12 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 13 | * . 14 | * Note: If you're using version control, you should put your passwords/api keys 15 | * in `config/local.js`, environment variables, or use another strategy. 16 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 17 | * 18 | * For more information on configuration, check out: 19 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.connections.html 20 | */ 21 | 22 | module.exports.connections = { 23 | mongodb: { 24 | adapter: 'sails-mongo', 25 | host: process.env.MONGO_HOST, 26 | port: 57439, 27 | user: process.env.MONGO_USER, 28 | password: process.env.MONGO_PASSWORD, 29 | database: process.env.MONGO_DB 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /config/globals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Variable Configuration 3 | * (sails.config.globals) 4 | * 5 | * Configure which global variables which will be exposed 6 | * automatically by Sails. 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.globals.html 10 | */ 11 | module.exports.globals = { 12 | 13 | /**************************************************************************** 14 | * * 15 | * Expose the lodash installed in Sails core as a global variable. If this * 16 | * is disabled, like any other node module you can always run npm install * 17 | * lodash --save, then var _ = require('lodash') at the top of any file. * 18 | * * 19 | ****************************************************************************/ 20 | 21 | // _: true, 22 | 23 | /**************************************************************************** 24 | * * 25 | * Expose the async installed in Sails core as a global variable. If this is * 26 | * disabled, like any other node module you can always run npm install async * 27 | * --save, then var async = require('async') at the top of any file. * 28 | * * 29 | ****************************************************************************/ 30 | 31 | // async: true, 32 | 33 | /**************************************************************************** 34 | * * 35 | * Expose the sails instance representing your app. If this is disabled, you * 36 | * can still get access via req._sails. * 37 | * * 38 | ****************************************************************************/ 39 | 40 | // sails: true, 41 | 42 | /**************************************************************************** 43 | * * 44 | * Expose each of your app's services as global variables (using their * 45 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js * 46 | * would have a globalId of NaturalLanguage by default. If this is disabled, * 47 | * you can still access your services via sails.services.* * 48 | * * 49 | ****************************************************************************/ 50 | 51 | // services: true, 52 | 53 | /**************************************************************************** 54 | * * 55 | * Expose each of your app's models as global variables (using their * 56 | * "globalId"). E.g. a model defined in api/models/User.js would have a * 57 | * globalId of User by default. If this is disabled, you can still access * 58 | * your models via sails.models.*. * 59 | * * 60 | ****************************************************************************/ 61 | 62 | // models: true 63 | }; 64 | -------------------------------------------------------------------------------- /config/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP Server Settings 3 | * (sails.config.http) 4 | * 5 | * Configuration for the underlying HTTP server in Sails. 6 | * Only applies to HTTP requests (not WebSockets) 7 | * 8 | * For more information on configuration, check out: 9 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.http.html 10 | */ 11 | 12 | module.exports.http = { 13 | 14 | /**************************************************************************** 15 | * * 16 | * Express middleware to use for every Sails request. To add custom * 17 | * middleware to the mix, add a function to the middleware config object and * 18 | * add its key to the "order" array. The $custom key is reserved for * 19 | * backwards-compatibility with Sails v0.9.x apps that use the * 20 | * `customMiddleware` config option. * 21 | * * 22 | ****************************************************************************/ 23 | 24 | middleware: { 25 | 26 | /*************************************************************************** 27 | * * 28 | * The order in which middleware should be run for HTTP request. (the Sails * 29 | * router is invoked by the "router" middleware below.) * 30 | * * 31 | ***************************************************************************/ 32 | 33 | // order: [ 34 | // 'startRequestTimer', 35 | // 'cookieParser', 36 | // 'session', 37 | // 'myRequestLogger', 38 | // 'bodyParser', 39 | // 'handleBodyParserError', 40 | // 'compress', 41 | // 'methodOverride', 42 | // 'poweredBy', 43 | // '$custom', 44 | // 'router', 45 | // 'www', 46 | // 'favicon', 47 | // '404', 48 | // '500' 49 | // ], 50 | 51 | /**************************************************************************** 52 | * * 53 | * Example custom middleware; logs each request to the console. * 54 | * * 55 | ****************************************************************************/ 56 | 57 | // myRequestLogger: function (req, res, next) { 58 | // console.log("Requested :: ", req.method, req.url); 59 | // return next(); 60 | // } 61 | 62 | 63 | /*************************************************************************** 64 | * * 65 | * The body parser that will handle incoming multipart HTTP requests. By * 66 | * default as of v0.10, Sails uses * 67 | * [skipper](http://github.com/balderdashy/skipper). See * 68 | * http://www.senchalabs.org/connect/multipart.html for other options. * 69 | * * 70 | * Note that Sails uses an internal instance of Skipper by default; to * 71 | * override it and specify more options, make sure to "npm install skipper" * 72 | * in your project first. You can also specify a different body parser or * 73 | * a custom function with req, res and next parameters (just like any other * 74 | * middleware function). * 75 | * * 76 | ***************************************************************************/ 77 | 78 | // bodyParser: require('skipper')({strict: true}) 79 | 80 | }, 81 | 82 | /*************************************************************************** 83 | * * 84 | * The number of seconds to cache flat files on disk being served by * 85 | * Express static middleware (by default, these files are in `.tmp/public`) * 86 | * * 87 | * The HTTP static cache is only active in a 'production' environment, * 88 | * since that's the only time Express will cache flat-files. * 89 | * * 90 | ***************************************************************************/ 91 | 92 | // cache: 31557600000 93 | }; 94 | -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internationalization / Localization Settings 3 | * (sails.config.i18n) 4 | * 5 | * If your app will touch people from all over the world, i18n (or internationalization) 6 | * may be an important part of your international strategy. 7 | * 8 | * 9 | * For more informationom i18n in Sails, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Internationalization 11 | * 12 | * For a complete list of i18n options, see: 13 | * https://github.com/mashpie/i18n-node#list-of-configuration-options 14 | * 15 | * 16 | */ 17 | 18 | module.exports.i18n = { 19 | 20 | /*************************************************************************** 21 | * * 22 | * Which locales are supported? * 23 | * * 24 | ***************************************************************************/ 25 | 26 | // locales: ['en', 'es', 'fr', 'de'], 27 | 28 | /**************************************************************************** 29 | * * 30 | * What is the default locale for the site? Note that this setting will be * 31 | * overridden for any request that sends an "Accept-Language" header (i.e. * 32 | * most browsers), but it's still useful if you need to localize the * 33 | * response for requests made by non-browser clients (e.g. cURL). * 34 | * * 35 | ****************************************************************************/ 36 | 37 | // defaultLocale: 'en', 38 | 39 | /**************************************************************************** 40 | * * 41 | * Automatically add new keys to locale (translation) files when they are * 42 | * encountered during a request? * 43 | * * 44 | ****************************************************************************/ 45 | 46 | // updateFiles: false, 47 | 48 | /**************************************************************************** 49 | * * 50 | * Path (relative to app root) of directory to store locale (translation) * 51 | * files in. * 52 | * * 53 | ****************************************************************************/ 54 | 55 | // localesDirectory: '/config/locales' 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

    <%= __('Welcome to PencilPals!') %>

    23 |

    <%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

    24 |

    <%= i18n('That\'s right-- you can use either i18n() or __()') %>

    25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /config/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Willkommen", 3 | "A brand new app.": "Eine neue App." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Welcome", 3 | "A brand new app.": "A brand new app." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenido", 3 | "A brand new app.": "Una nueva aplicación." 4 | } 5 | -------------------------------------------------------------------------------- /config/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Welcome": "Bienvenue", 3 | "A brand new app.": "Une toute nouvelle application." 4 | } 5 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Built-in Log Configuration 3 | * (sails.config.log) 4 | * 5 | * Configure the log level for your app, as well as the transport 6 | * (Underneath the covers, Sails uses Winston for logging, which 7 | * allows for some pretty neat custom transports/adapters for log messages) 8 | * 9 | * For more information on the Sails logger, check out: 10 | * http://sailsjs.org/#!/documentation/concepts/Logging 11 | */ 12 | 13 | module.exports.log = { 14 | 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | 27 | // level: 'info' 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default model configuration 3 | * (sails.config.models) 4 | * 5 | * Unless you override them, the following properties will be included 6 | * in each of your models. 7 | * 8 | * For more info on Sails models, see: 9 | * http://sailsjs.org/#!/documentation/concepts/ORM 10 | */ 11 | 12 | module.exports.models = { 13 | 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | connection: 'mongodb', 21 | 22 | /*************************************************************************** 23 | * * 24 | * How and whether Sails will attempt to automatically rebuild the * 25 | * tables/collections/etc. in your schema. * 26 | * * 27 | * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html * 28 | * * 29 | ***************************************************************************/ 30 | migrate: 'alter' 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /config/policies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Policy Mappings 3 | * (sails.config.policies) 4 | * 5 | * Policies are simple functions which run **before** your controllers. 6 | * You can apply one or more policies to a given controller, or protect 7 | * its actions individually. 8 | * 9 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 10 | * below by its filename, minus the extension, (e.g. "authenticated") 11 | * 12 | * For more information on how policies work, see: 13 | * http://sailsjs.org/#!/documentation/concepts/Policies 14 | * 15 | * For more information on configuring policies, check out: 16 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.policies.html 17 | */ 18 | 19 | 20 | module.exports.policies = { 21 | 22 | /*************************************************************************** 23 | * * 24 | * Default policy for all controllers and actions (`true` allows public * 25 | * access) * 26 | * * 27 | ***************************************************************************/ 28 | 29 | // '*': true, 30 | 31 | /*************************************************************************** 32 | * * 33 | * Here's an example of mapping some policies to run before a controller * 34 | * and its actions * 35 | * * 36 | ***************************************************************************/ 37 | // RabbitController: { 38 | 39 | // Apply the `false` policy as the default for all of RabbitController's actions 40 | // (`false` prevents all access, which ensures that nothing bad happens to our rabbits) 41 | // '*': false, 42 | 43 | // For the action `nurture`, apply the 'isRabbitMother' policy 44 | // (this overrides `false` above) 45 | // nurture : 'isRabbitMother', 46 | 47 | // Apply the `isNiceToAnimals` AND `hasRabbitFood` policies 48 | // before letting any users feed our rabbits 49 | // feed : ['isNiceToAnimals', 'hasRabbitFood'] 50 | // } 51 | }; 52 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Mappings 3 | * (sails.config.routes) 4 | * 5 | * Your routes map URLs to views and controllers. 6 | * 7 | * If Sails receives a URL that doesn't match any of the routes below, 8 | * it will check for matching files (images, scripts, stylesheets, etc.) 9 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 10 | * might match an image file: `/assets/images/foo.jpg` 11 | * 12 | * Finally, if those don't match either, the default 404 handler is triggered. 13 | * See `api/responses/notFound.js` to adjust your app's 404 logic. 14 | * 15 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 16 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 17 | * CoffeeScript for the front-end. 18 | * 19 | * For more information on configuring custom routes, check out: 20 | * http://sailsjs.org/#!/documentation/concepts/Routes/RouteTargetSyntax.html 21 | */ 22 | 23 | module.exports.routes = { 24 | 25 | /*************************************************************************** 26 | * * 27 | * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, * 28 | * etc. depending on your default view engine) your home page. * 29 | * * 30 | * (Alternatively, remove this and add an `index.html` file in your * 31 | * `assets` directory) * 32 | * * 33 | ***************************************************************************/ 34 | 35 | '/': { 36 | view: 'index' 37 | }, 38 | 39 | 'get /translate': 'ChatController.translate', 40 | 'get /welcome': 'ChatController.welcome', 41 | 'get /reset' : 'ChatController.reset' 42 | 43 | 44 | /*************************************************************************** 45 | * * 46 | * Custom routes here... * 47 | * * 48 | * If a request to a URL doesn't match any of the custom routes above, it * 49 | * is matched against Sails route blueprints. See `config/blueprints.js` * 50 | * for configuration options and examples. * 51 | * * 52 | ***************************************************************************/ 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /config/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Session Configuration 3 | * (sails.config.session) 4 | * 5 | * Sails session integration leans heavily on the great work already done by 6 | * Express, but also unifies Socket.io with the Connect session store. It uses 7 | * Connect's cookie parser to normalize configuration differences between Express 8 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access 9 | * and auto-save to `req.session` with Socket.io the same way you would with Express. 10 | * 11 | * For more information on configuring the session, check out: 12 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.session.html 13 | */ 14 | 15 | module.exports.session = { 16 | 17 | /*************************************************************************** 18 | * * 19 | * Session secret is automatically generated when your new app is created * 20 | * Replace at your own risk in production-- you will invalidate the cookies * 21 | * of your users, forcing them to log in again. * 22 | * * 23 | ***************************************************************************/ 24 | secret: 'd0c55bdc5312a787dec0c9f730c11660', 25 | 26 | 27 | /*************************************************************************** 28 | * * 29 | * Set the session cookie expire time The maxAge is set by milliseconds, * 30 | * the example below is for 24 hours * 31 | * * 32 | ***************************************************************************/ 33 | 34 | // cookie: { 35 | // maxAge: 24 * 60 * 60 * 1000 36 | // }, 37 | 38 | /*************************************************************************** 39 | * * 40 | * Uncomment the following lines to set up a Redis session store that can * 41 | * be shared across multiple Sails.js servers. * 42 | * * 43 | * Requires connect-redis (https://www.npmjs.com/package/connect-redis) * 44 | * * 45 | ***************************************************************************/ 46 | 47 | adapter: 'redis', 48 | 49 | /*************************************************************************** 50 | * * 51 | * The following values are optional, if no options are set a redis * 52 | * instance running on localhost is expected. Read more about options at: * 53 | * * 54 | * https://github.com/visionmedia/connect-redis * 55 | * * 56 | ***************************************************************************/ 57 | 58 | // host: 'localhost', 59 | // port: 6379, 60 | // ttl: , 61 | // db: 0, 62 | // pass: , 63 | // prefix: 'sess:', 64 | 65 | 66 | /*************************************************************************** 67 | * * 68 | * Uncomment the following lines to set up a MongoDB session store that can * 69 | * be shared across multiple Sails.js servers. * 70 | * * 71 | * Requires connect-mongo (https://www.npmjs.com/package/connect-mongo) * 72 | * Use version 0.8.2 with Node version <= 0.12 * 73 | * Use the latest version with Node >= 4.0 * 74 | * * 75 | ***************************************************************************/ 76 | 77 | // adapter: 'mongo', 78 | // url: 'mongodb://user:password@localhost:27017/dbname', // user, password and port optional 79 | 80 | /*************************************************************************** 81 | * * 82 | * Optional Values: * 83 | * * 84 | * See https://github.com/kcbanner/connect-mongo for more * 85 | * information about connect-mongo options. * 86 | * * 87 | * See http://bit.ly/mongooptions for more information about options * 88 | * available in `mongoOptions` * 89 | * * 90 | ***************************************************************************/ 91 | 92 | // collection: 'sessions', 93 | // stringify: true, 94 | // mongoOptions: { 95 | // server: { 96 | // ssl: true 97 | // } 98 | // } 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#!/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | 13 | module.exports.sockets = { 14 | // This custom onDisconnect function will be run each time a socket disconnects 15 | afterDisconnect: function(session, socket, cb) { 16 | console.log("DISCONNECT SESSION", session); 17 | try { 18 | // Look up the user ID using the connected socket 19 | var userId = session.userId; 20 | 21 | // Get the user instance 22 | User.findOne(userId).exec(function(err, user) { 23 | 24 | if (err) { return cb(); } 25 | 26 | //deactivate the user 27 | User.update(userId, {active: false}).exec(function(err) { 28 | if (err) { return cb(); } 29 | User.stats(function(err, stats) { 30 | if (err) { return res.serverError(err); } 31 | 32 | User.broadcaseCount(); 33 | }); 34 | }); 35 | }); 36 | } catch (e) { 37 | console.log("Error in onDisconnect: ", e); 38 | return cb(); 39 | } 40 | 41 | } 42 | 43 | 44 | 45 | /*************************************************************************** 46 | * * 47 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 48 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 49 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 50 | * servers and throw them behind a load balancer. * 51 | * * 52 | * One of the big challenges of scaling an application is that these sorts * 53 | * of clustered deployments cannot share memory, since they are on * 54 | * physically different machines. On top of that, there is no guarantee * 55 | * that a user will "stick" with the same server between requests (whether * 56 | * HTTP or sockets), since the load balancer will route each request to the * 57 | * Sails server with the most available resources. However that means that * 58 | * all room/pubsub/socket processing and shared memory has to be offloaded * 59 | * to a shared, remote messaging queue (usually Redis) * 60 | * * 61 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 62 | * sockets by default. To enable a remote redis pubsub server, uncomment * 63 | * the config below. * 64 | * * 65 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 66 | * is left unset, Sails will try to connect to redis running on localhost * 67 | * via port 6379 * 68 | * * 69 | ***************************************************************************/ 70 | // adapter: 'memory', 71 | 72 | // 73 | // -OR- 74 | // 75 | 76 | // adapter: 'socket.io-redis', 77 | // host: '127.0.0.1', 78 | // port: 6379, 79 | // db: 0, 80 | // pass: '', 81 | 82 | 83 | 84 | /*************************************************************************** 85 | * * 86 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 87 | * a cookie (this is used by the sails.io.js socket client to get access to * 88 | * a 3rd party cookie and to enable sessions). * 89 | * * 90 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 91 | * requests sent via a socket.io connection that used this cookie to * 92 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 93 | * unit tests) * 94 | * * 95 | ***************************************************************************/ 96 | 97 | // grant3rdPartyCookie: true, 98 | 99 | 100 | 101 | /*************************************************************************** 102 | * * 103 | * `beforeConnect` * 104 | * * 105 | * This custom beforeConnect function will be run each time BEFORE a new * 106 | * socket is allowed to connect, when the initial socket.io handshake is * 107 | * performed with the server. * 108 | * * 109 | * By default, when a socket tries to connect, Sails allows it, every time. * 110 | * (much in the same way any HTTP request is allowed to reach your routes. * 111 | * If no valid cookie was sent, a temporary session will be created for the * 112 | * connecting socket. * 113 | * * 114 | * If the cookie sent as part of the connection request doesn't match any * 115 | * known user session, a new user session is created for it. * 116 | * * 117 | * In most cases, the user would already have a cookie since they loaded * 118 | * the socket.io client and the initial HTML page you're building. * 119 | * * 120 | * However, in the case of cross-domain requests, it is possible to receive * 121 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 122 | * In this case, there is no way to keep track of the requesting user * 123 | * between requests, since there is no identifying information to link * 124 | * him/her with a session. The sails.io.js client solves this by connecting * 125 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 126 | * works, even in Safari), then opening the connection. * 127 | * * 128 | * You can also pass along a ?cookie query parameter to the upgrade url, * 129 | * which Sails will use in the absence of a proper cookie e.g. (when * 130 | * connecting from the client): * 131 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 132 | * * 133 | * Finally note that the user's cookie is NOT (and will never be) accessible* 134 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 135 | * app's security. * 136 | * * 137 | ***************************************************************************/ 138 | // beforeConnect: function(handshake, cb) { 139 | // // `true` allows the connection 140 | // return cb(null, true); 141 | // 142 | // // (`false` would reject the connection) 143 | // }, 144 | 145 | 146 | /*************************************************************************** 147 | * * 148 | * `afterDisconnect` * 149 | * * 150 | * This custom afterDisconnect function will be run each time a socket * 151 | * disconnects * 152 | * * 153 | ***************************************************************************/ 154 | // afterDisconnect: function(session, socket, cb) { 155 | // // By default: do nothing. 156 | // return cb(); 157 | // }, 158 | 159 | /*************************************************************************** 160 | * * 161 | * `transports` * 162 | * * 163 | * A array of allowed transport methods which the clients will try to use. * 164 | * On server environments that don't support sticky sessions, the "polling" * 165 | * transport should be disabled. * 166 | * * 167 | ***************************************************************************/ 168 | // transports: ["polling", "websocket"] 169 | 170 | }; 171 | -------------------------------------------------------------------------------- /config/views.js: -------------------------------------------------------------------------------- 1 | /** 2 | * View Engine Configuration 3 | * (sails.config.views) 4 | * 5 | * Server-sent views are a classic and effective way to get your app up 6 | * and running. Views are normally served from controllers. Below, you can 7 | * configure your templating language/framework of choice and configure 8 | * Sails' layout support. 9 | * 10 | * For more information on views and layouts, check out: 11 | * http://sailsjs.org/#!/documentation/concepts/Views 12 | */ 13 | 14 | module.exports.views = { 15 | 16 | /**************************************************************************** 17 | * * 18 | * View engine (aka template language) to use for your app's *server-side* * 19 | * views * 20 | * * 21 | * Sails+Express supports all view engines which implement TJ Holowaychuk's * 22 | * `consolidate.js`, including, but not limited to: * 23 | * * 24 | * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, * 25 | * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, * 26 | * toffee, walrus, & whiskers * 27 | * * 28 | * For more options, check out the docs: * 29 | * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine * 30 | * * 31 | ****************************************************************************/ 32 | 33 | engine: 'ejs', 34 | 35 | 36 | /**************************************************************************** 37 | * * 38 | * Layouts are simply top-level HTML templates you can use as wrappers for * 39 | * your server-side views. If you're using ejs or jade, you can take * 40 | * advantage of Sails' built-in `layout` support. * 41 | * * 42 | * When using a layout, when one of your views is served, it is injected * 43 | * into the `body` partial defined in the layout. This lets you reuse header * 44 | * and footer logic between views. * 45 | * * 46 | * NOTE: Layout support is only implemented for the `ejs` view engine! * 47 | * For most other engines, it is not necessary, since they implement * 48 | * partials/layouts themselves. In those cases, this config will be * 49 | * silently ignored. * 50 | * * 51 | * The `layout` setting may be set to one of the following: * 52 | * * 53 | * If `false`, layouts will be disabled. Otherwise, if a string is * 54 | * specified, it will be interpreted as the relative path to your layout * 55 | * file from `views/` folder. (the file extension, ".ejs", should be * 56 | * omitted) * 57 | * * 58 | ****************************************************************************/ 59 | 60 | /**************************************************************************** 61 | * * 62 | * Using Multiple Layouts * 63 | * * 64 | * If you're using the default `ejs` or `handlebars` Sails supports the use * 65 | * of multiple `layout` files. To take advantage of this, before rendering a * 66 | * view, override the `layout` local in your controller by setting * 67 | * `res.locals.layout`. (this is handy if you parts of your app's UI look * 68 | * completely different from each other) * 69 | * * 70 | * e.g. your default might be * 71 | * layout: 'layouts/public' * 72 | * * 73 | * But you might override that in some of your controllers with: * 74 | * layout: 'layouts/internal' * 75 | * * 76 | ****************************************************************************/ 77 | 78 | layout: 'layout', 79 | 80 | /**************************************************************************** 81 | * * 82 | * Partials are simply top-level snippets you can leverage to reuse template * 83 | * for your server-side views. If you're using handlebars, you can take * 84 | * advantage of Sails' built-in `partials` support. * 85 | * * 86 | * If `false` or empty partials will be located in the same folder as views. * 87 | * Otherwise, if a string is specified, it will be interpreted as the * 88 | * relative path to your partial files from `views/` folder. * 89 | * * 90 | ****************************************************************************/ 91 | 92 | partials: false 93 | 94 | 95 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "connect-redis": "^3.2.0", 4 | "ejs": "2.3.4", 5 | "foreman": "2.0.0", 6 | "google-translate": "^1.0.9", 7 | "grunt": "1.0.1", 8 | "grunt-bower-task": "^0.3.4", 9 | "grunt-contrib-clean": "1.0.0", 10 | "grunt-contrib-coffee": "1.0.0", 11 | "grunt-contrib-concat": "1.0.1", 12 | "grunt-contrib-copy": "1.0.0", 13 | "grunt-contrib-cssmin": "1.0.1", 14 | "grunt-contrib-jst": "1.0.0", 15 | "grunt-contrib-less": "1.3.0", 16 | "grunt-contrib-uglify": "2.1.0", 17 | "grunt-contrib-watch": "1.0.0", 18 | "grunt-sails-linker": "~0.10.1", 19 | "grunt-sync": "0.5.2", 20 | "include-all": "^1.0.0", 21 | "jquery": "^3.1.1", 22 | "mongodb": "^2.2.24", 23 | "mstranslator": "^2.1.1", 24 | "rc": "1.0.1", 25 | "redis": "^2.6.5", 26 | "sails": "~0.12.11", 27 | "sails-disk": "~0.10.9", 28 | "sails-generate-bower": "0.0.2", 29 | "sails-hook-babel": "^6.0.3", 30 | "sails-mongo": "^0.12.2", 31 | "socket.io-client": "^1.7.2", 32 | "socket.io-redis": "^4.0.0" 33 | }, 34 | "name": "chat-app", 35 | "private": true, 36 | "version": "0.0.0", 37 | "description": "a Sails application", 38 | "keywords": [], 39 | "scripts": { 40 | "start": "sails lift", 41 | "debug": "node debug app.js" 42 | }, 43 | "main": "app.js", 44 | "repository": { 45 | "type": "git", 46 | "url": "git://github.com/william/chat-app.git" 47 | }, 48 | "author": "william", 49 | "license": "" 50 | } 51 | -------------------------------------------------------------------------------- /tasks/README.md: -------------------------------------------------------------------------------- 1 | # About the `tasks` folder 2 | 3 | The `tasks` directory is a suite of Grunt tasks and their configurations, bundled for your convenience. The Grunt integration is mainly useful for bundling front-end assets, (like stylesheets, scripts, & markup templates) but it can also be used to run all kinds of development tasks, from browserify compilation to database migrations. 4 | 5 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, read on! 6 | 7 | 8 | ### How does this work? 9 | 10 | The asset pipeline bundled in Sails is a set of Grunt tasks configured with conventional defaults designed to make your project more consistent and productive. 11 | 12 | The entire front-end asset workflow in Sails is completely customizable-- while it provides some suggestions out of the box, Sails makes no pretense that it can anticipate all of the needs you'll encounter building the browser-based/front-end portion of your application. Who's to say you're even building an app for a browser? 13 | 14 | 15 | 16 | ### What tasks does Sails run automatically? 17 | 18 | Sails runs some of these tasks (the ones in the `tasks/register` folder) automatically when you run certain commands. 19 | 20 | ###### `sails lift` 21 | 22 | Runs the `default` task (`tasks/register/default.js`). 23 | 24 | ###### `sails lift --prod` 25 | 26 | Runs the `prod` task (`tasks/register/prod.js`). 27 | 28 | ###### `sails www` 29 | 30 | Runs the `build` task (`tasks/register/build.js`). 31 | 32 | ###### `sails www --prod` (production) 33 | 34 | Runs the `buildProd` task (`tasks/register/buildProd.js`). 35 | 36 | 37 | ### Can I customize this for SASS, Angular, client-side Jade templates, etc? 38 | 39 | You can modify, omit, or replace any of these Grunt tasks to fit your requirements. You can also add your own Grunt tasks- just add a `someTask.js` file in the `grunt/config` directory to configure the new task, then register it with the appropriate parent task(s) (see files in `grunt/register/*.js`). 40 | 41 | 42 | ### Do I have to use Grunt? 43 | 44 | Nope! To disable Grunt integration in Sails, just delete your Gruntfile or disable the Grunt hook. 45 | 46 | 47 | ### What if I'm not building a web frontend? 48 | 49 | That's ok! A core tenant of Sails is client-agnosticism-- it's especially designed for building APIs used by all sorts of clients; native Android/iOS/Cordova, serverside SDKs, etc. 50 | 51 | You can completely disable Grunt by following the instructions above. 52 | 53 | If you still want to use Grunt for other purposes, but don't want any of the default web front-end stuff, just delete your project's `assets` folder and remove the front-end oriented tasks from the `grunt/register` and `grunt/config` folders. You can also run `sails new myCoolApi --no-frontend` to omit the `assets` folder and front-end-oriented Grunt tasks for future projects. You can also replace your `sails-generate-frontend` module with alternative community generators, or create your own. This allows `sails new` to create the boilerplate for native iOS apps, Android apps, Cordova apps, SteroidsJS apps, etc. 54 | 55 | -------------------------------------------------------------------------------- /tasks/config/bower.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Install bower components. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Installs bower components and copies the required files into the assets folder structure. 7 | * 8 | */ 9 | 10 | module.exports = function(grunt) { 11 | 12 | grunt.config.set('bower', { 13 | install: { 14 | options: { 15 | targetDir: './assets/vendor', 16 | layout: 'byType', 17 | install: true, 18 | verbose: false, 19 | cleanTargetDir: true, 20 | cleanBowerDir: true, 21 | bowerOptions: {} 22 | } 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-bower-task'); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/config/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `clean` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Remove the files and folders in your Sails app's web root 7 | * (conventionally a hidden directory called `.tmp/public`). 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-clean 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('clean', { 16 | dev: ['.tmp/public/**'], 17 | build: ['www'] 18 | }); 19 | 20 | grunt.loadNpmTasks('grunt-contrib-clean'); 21 | }; 22 | -------------------------------------------------------------------------------- /tasks/config/coffee.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `coffee` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Compile CoffeeScript files located in `assets/js` into Javascript 7 | * and generate new `.js` files in `.tmp/public/js`. 8 | * 9 | * For usage docs see: 10 | * https://github.com/gruntjs/grunt-contrib-coffee 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | 15 | grunt.config.set('coffee', { 16 | dev: { 17 | options: { 18 | bare: true, 19 | sourceMap: true, 20 | sourceRoot: './' 21 | }, 22 | files: [{ 23 | expand: true, 24 | cwd: 'assets/js/', 25 | src: ['**/*.coffee'], 26 | dest: '.tmp/public/js/', 27 | ext: '.js' 28 | }] 29 | } 30 | }); 31 | 32 | grunt.loadNpmTasks('grunt-contrib-coffee'); 33 | }; 34 | -------------------------------------------------------------------------------- /tasks/config/concat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `concat` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Concatenates the contents of multiple JavaScript and/or CSS files 7 | * into two new files, each located at `concat/production.js` and 8 | * `concat/production.css` respectively in `.tmp/public/concat`. 9 | * 10 | * This is used as an intermediate step to generate monolithic files 11 | * that can then be passed in to `uglify` and/or `cssmin` for minification. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-concat 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('concat', { 20 | js: { 21 | src: require('../pipeline').jsFilesToInject, 22 | dest: '.tmp/public/concat/production.js' 23 | }, 24 | css: { 25 | src: require('../pipeline').cssFilesToInject, 26 | dest: '.tmp/public/concat/production.css' 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-concat'); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/config/copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `copy` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Copy files and/or folders from your `assets/` directory into 7 | * the web root (`.tmp/public`) so they can be served via HTTP, 8 | * and also for further pre-processing by other Grunt tasks. 9 | * 10 | * #### Normal usage (`sails lift`) 11 | * Copies all directories and files (except CoffeeScript and LESS) 12 | * from the `assets/` folder into the web root -- conventionally a 13 | * hidden directory located `.tmp/public`. 14 | * 15 | * #### Via the `build` tasklist (`sails www`) 16 | * Copies all directories and files from the .tmp/public directory into a www directory. 17 | * 18 | * For usage docs see: 19 | * https://github.com/gruntjs/grunt-contrib-copy 20 | * 21 | */ 22 | module.exports = function(grunt) { 23 | 24 | grunt.config.set('copy', { 25 | dev: { 26 | files: [{ 27 | expand: true, 28 | cwd: './assets', 29 | src: ['**/*.!(coffee|less)'], 30 | dest: '.tmp/public' 31 | }] 32 | }, 33 | build: { 34 | files: [{ 35 | expand: true, 36 | cwd: '.tmp/public', 37 | src: ['**/*'], 38 | dest: 'www' 39 | }] 40 | } 41 | }); 42 | 43 | grunt.loadNpmTasks('grunt-contrib-copy'); 44 | }; 45 | -------------------------------------------------------------------------------- /tasks/config/cssmin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compress CSS files. 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minify the intermediate concatenated CSS stylesheet which was 7 | * prepared by the `concat` task at `.tmp/public/concat/production.css`. 8 | * 9 | * Together with the `concat` task, this is the final step that minifies 10 | * all CSS files from `assets/styles/` (and potentially your LESS importer 11 | * file from `assets/styles/importer.less`) 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-cssmin 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('cssmin', { 20 | dist: { 21 | src: ['.tmp/public/concat/production.css'], 22 | dest: '.tmp/public/min/production.min.css' 23 | } 24 | }); 25 | 26 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/config/jst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `jst` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Precompile HTML templates using Underscore/Lodash notation into 7 | * functions, creating a `.jst` file. This can be brought into your HTML 8 | * via a ', 38 | appRoot: '.tmp/public' 39 | }, 40 | files: { 41 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 42 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 43 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 44 | } 45 | }, 46 | 47 | devJsRelative: { 48 | options: { 49 | startTag: '', 50 | endTag: '', 51 | fileTmpl: '', 52 | appRoot: '.tmp/public', 53 | relative: true 54 | }, 55 | files: { 56 | '.tmp/public/**/*.html': require('../pipeline').jsFilesToInject, 57 | 'views/**/*.html': require('../pipeline').jsFilesToInject, 58 | 'views/**/*.ejs': require('../pipeline').jsFilesToInject 59 | } 60 | }, 61 | 62 | prodJs: { 63 | options: { 64 | startTag: '', 65 | endTag: '', 66 | fileTmpl: '', 67 | appRoot: '.tmp/public' 68 | }, 69 | files: { 70 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 71 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 72 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 73 | } 74 | }, 75 | 76 | prodJsRelative: { 77 | options: { 78 | startTag: '', 79 | endTag: '', 80 | fileTmpl: '', 81 | appRoot: '.tmp/public', 82 | relative: true 83 | }, 84 | files: { 85 | '.tmp/public/**/*.html': ['.tmp/public/min/production.min.js'], 86 | 'views/**/*.html': ['.tmp/public/min/production.min.js'], 87 | 'views/**/*.ejs': ['.tmp/public/min/production.min.js'] 88 | } 89 | }, 90 | 91 | devStyles: { 92 | options: { 93 | startTag: '', 94 | endTag: '', 95 | fileTmpl: '', 96 | appRoot: '.tmp/public' 97 | }, 98 | 99 | files: { 100 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 101 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 102 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 103 | } 104 | }, 105 | 106 | devStylesRelative: { 107 | options: { 108 | startTag: '', 109 | endTag: '', 110 | fileTmpl: '', 111 | appRoot: '.tmp/public', 112 | relative: true 113 | }, 114 | 115 | files: { 116 | '.tmp/public/**/*.html': require('../pipeline').cssFilesToInject, 117 | 'views/**/*.html': require('../pipeline').cssFilesToInject, 118 | 'views/**/*.ejs': require('../pipeline').cssFilesToInject 119 | } 120 | }, 121 | 122 | prodStyles: { 123 | options: { 124 | startTag: '', 125 | endTag: '', 126 | fileTmpl: '', 127 | appRoot: '.tmp/public' 128 | }, 129 | files: { 130 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 131 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 132 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 133 | } 134 | }, 135 | 136 | prodStylesRelative: { 137 | options: { 138 | startTag: '', 139 | endTag: '', 140 | fileTmpl: '', 141 | appRoot: '.tmp/public', 142 | relative: true 143 | }, 144 | files: { 145 | '.tmp/public/index.html': ['.tmp/public/min/production.min.css'], 146 | 'views/**/*.html': ['.tmp/public/min/production.min.css'], 147 | 'views/**/*.ejs': ['.tmp/public/min/production.min.css'] 148 | } 149 | }, 150 | 151 | // Bring in JST template object 152 | devTpl: { 153 | options: { 154 | startTag: '', 155 | endTag: '', 156 | fileTmpl: '', 157 | appRoot: '.tmp/public' 158 | }, 159 | files: { 160 | '.tmp/public/index.html': ['.tmp/public/jst.js'], 161 | 'views/**/*.html': ['.tmp/public/jst.js'], 162 | 'views/**/*.ejs': ['.tmp/public/jst.js'] 163 | } 164 | }, 165 | 166 | devJsJade: { 167 | options: { 168 | startTag: '// SCRIPTS', 169 | endTag: '// SCRIPTS END', 170 | fileTmpl: 'script(src="%s")', 171 | appRoot: '.tmp/public' 172 | }, 173 | files: { 174 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 175 | } 176 | }, 177 | 178 | devJsRelativeJade: { 179 | options: { 180 | startTag: '// SCRIPTS', 181 | endTag: '// SCRIPTS END', 182 | fileTmpl: 'script(src="%s")', 183 | appRoot: '.tmp/public', 184 | relative: true 185 | }, 186 | files: { 187 | 'views/**/*.jade': require('../pipeline').jsFilesToInject 188 | } 189 | }, 190 | 191 | prodJsJade: { 192 | options: { 193 | startTag: '// SCRIPTS', 194 | endTag: '// SCRIPTS END', 195 | fileTmpl: 'script(src="%s")', 196 | appRoot: '.tmp/public' 197 | }, 198 | files: { 199 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 200 | } 201 | }, 202 | 203 | prodJsRelativeJade: { 204 | options: { 205 | startTag: '// SCRIPTS', 206 | endTag: '// SCRIPTS END', 207 | fileTmpl: 'script(src="%s")', 208 | appRoot: '.tmp/public', 209 | relative: true 210 | }, 211 | files: { 212 | 'views/**/*.jade': ['.tmp/public/min/production.min.js'] 213 | } 214 | }, 215 | 216 | devStylesJade: { 217 | options: { 218 | startTag: '// STYLES', 219 | endTag: '// STYLES END', 220 | fileTmpl: 'link(rel="stylesheet", href="%s")', 221 | appRoot: '.tmp/public' 222 | }, 223 | 224 | files: { 225 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 226 | } 227 | }, 228 | 229 | devStylesRelativeJade: { 230 | options: { 231 | startTag: '// STYLES', 232 | endTag: '// STYLES END', 233 | fileTmpl: 'link(rel="stylesheet", href="%s")', 234 | appRoot: '.tmp/public', 235 | relative: true 236 | }, 237 | 238 | files: { 239 | 'views/**/*.jade': require('../pipeline').cssFilesToInject 240 | } 241 | }, 242 | 243 | prodStylesJade: { 244 | options: { 245 | startTag: '// STYLES', 246 | endTag: '// STYLES END', 247 | fileTmpl: 'link(rel="stylesheet", href="%s")', 248 | appRoot: '.tmp/public' 249 | }, 250 | files: { 251 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 252 | } 253 | }, 254 | 255 | prodStylesRelativeJade: { 256 | options: { 257 | startTag: '// STYLES', 258 | endTag: '// STYLES END', 259 | fileTmpl: 'link(rel="stylesheet", href="%s")', 260 | appRoot: '.tmp/public', 261 | relative: true 262 | }, 263 | files: { 264 | 'views/**/*.jade': ['.tmp/public/min/production.min.css'] 265 | } 266 | }, 267 | 268 | // Bring in JST template object 269 | devTplJade: { 270 | options: { 271 | startTag: '// TEMPLATES', 272 | endTag: '// TEMPLATES END', 273 | fileTmpl: 'script(type="text/javascript", src="%s")', 274 | appRoot: '.tmp/public' 275 | }, 276 | files: { 277 | 'views/**/*.jade': ['.tmp/public/jst.js'] 278 | } 279 | } 280 | }); 281 | 282 | grunt.loadNpmTasks('grunt-sails-linker'); 283 | }; 284 | -------------------------------------------------------------------------------- /tasks/config/sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `sync` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Synchronize files from the `assets` folder to `.tmp/public`, 7 | * smashing anything that's already there. 8 | * 9 | * This task synchronizes one directory with another (like rsync). 10 | * In the default Sails asset pipeline, it plays very similar role 11 | * to `grunt-contrib-copy`, but copies only those files that have 12 | * actually changed since the last time the task was run. 13 | * 14 | * For usage docs see: 15 | * https://github.com/tomusdrw/grunt-sync 16 | * 17 | */ 18 | module.exports = function(grunt) { 19 | 20 | grunt.config.set('sync', { 21 | dev: { 22 | files: [{ 23 | cwd: './assets', 24 | src: ['**/*.!(coffee|less)'], 25 | dest: '.tmp/public' 26 | }] 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-sync'); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/config/uglify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `uglify` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Minify client-side JavaScript files using UglifyJS. 7 | * 8 | * For usage docs see: 9 | * https://github.com/gruntjs/grunt-contrib-uglify 10 | * 11 | */ 12 | module.exports = function(grunt) { 13 | 14 | grunt.config.set('uglify', { 15 | dist: { 16 | src: ['.tmp/public/concat/production.js'], 17 | dest: '.tmp/public/min/production.min.js' 18 | } 19 | }); 20 | 21 | grunt.loadNpmTasks('grunt-contrib-uglify'); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/config/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `watch` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * Run predefined tasks whenever watched file patterns are added, changed or deleted. 7 | * 8 | * Watch for changes on: 9 | * - files in the `assets` folder 10 | * - the `tasks/pipeline.js` file 11 | * and re-run the appropriate tasks. 12 | * 13 | * For usage docs see: 14 | * https://github.com/gruntjs/grunt-contrib-watch 15 | * 16 | */ 17 | module.exports = function(grunt) { 18 | 19 | grunt.config.set('watch', { 20 | assets: { 21 | 22 | // Assets to watch: 23 | files: ['assets/**/*', 'tasks/pipeline.js', '!**/node_modules/**'], 24 | 25 | // When assets are changed: 26 | tasks: ['syncAssets' , 'linkAssets' ] 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-contrib-watch'); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/pipeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * grunt/pipeline.js 3 | * 4 | * The order in which your css, javascript, and template files should be 5 | * compiled and linked from your views and static HTML files. 6 | * 7 | * (Note that you can take advantage of Grunt-style wildcard/glob/splat expressions 8 | * for matching multiple files, and ! in front of an expression to ignore files.) 9 | * 10 | * For more information see: 11 | * https://github.com/balderdashy/sails-docs/blob/master/anatomy/myApp/tasks/pipeline.js.md 12 | */ 13 | 14 | 15 | // CSS files to inject in order 16 | // 17 | // (if you're using LESS with the built-in default config, you'll want 18 | // to change `assets/styles/importer.less` instead.) 19 | var cssFilesToInject = [ 20 | 'styles/**/*.css' 21 | ]; 22 | 23 | 24 | // Client-side javascript files to inject in order 25 | // (uses Grunt-style wildcard/glob/splat expressions) 26 | var jsFilesToInject = [ 27 | 28 | // Load sails.io before everything else 29 | 'js/dependencies/sails.io.js', 30 | 31 | // Dependencies like jQuery, or Angular are brought in here 32 | 'js/dependencies/linkify.min.js', 33 | 'js/dependencies/**/*.js', 34 | 35 | // All of the rest of your client-side js files 36 | // will be injected here in no particular order. 37 | 'js/**/*.js' 38 | ]; 39 | 40 | 41 | // Client-side HTML templates are injected using the sources below 42 | // The ordering of these templates shouldn't matter. 43 | // (uses Grunt-style wildcard/glob/splat expressions) 44 | // 45 | // By default, Sails uses JST templates and precompiles them into 46 | // functions for you. If you want to use jade, handlebars, dust, etc., 47 | // with the linker, no problem-- you'll just want to make sure the precompiled 48 | // templates get spit out to the same file. Be sure and check out `tasks/README.md` 49 | // for information on customizing and installing new tasks. 50 | var templateFilesToInject = [ 51 | 'templates/**/*.html' 52 | ]; 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | // Default path for public folder (see documentation for more information) 61 | var tmpPath = '.tmp/public/'; 62 | 63 | // Prefix relative paths to source files so they point to the proper locations 64 | // (i.e. where the other Grunt tasks spit them out, or in some cases, where 65 | // they reside in the first place) 66 | module.exports.cssFilesToInject = cssFilesToInject.map(function(cssPath) { 67 | // If we're ignoring the file, make sure the ! is at the beginning of the path 68 | if (cssPath[0] === '!') { 69 | return require('path').join('!.tmp/public/', cssPath.substr(1)); 70 | } 71 | return require('path').join('.tmp/public/', cssPath); 72 | }); 73 | module.exports.jsFilesToInject = jsFilesToInject.map(function(jsPath) { 74 | // If we're ignoring the file, make sure the ! is at the beginning of the path 75 | if (jsPath[0] === '!') { 76 | return require('path').join('!.tmp/public/', jsPath.substr(1)); 77 | } 78 | return require('path').join('.tmp/public/', jsPath); 79 | }); 80 | module.exports.templateFilesToInject = templateFilesToInject.map(function(tplPath) { 81 | // If we're ignoring the file, make sure the ! is at the beginning of the path 82 | if (tplPath[0] === '!') { 83 | return require('path').join('!assets/', tplPath.substr(1)); 84 | } 85 | return require('path').join('assets/',tplPath); 86 | }); 87 | 88 | 89 | -------------------------------------------------------------------------------- /tasks/register/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `build` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed if you run `sails www` or 7 | * `grunt build` in a development environment. It generates a 8 | * folder containing your compiled assets, e.g. for troubleshooting 9 | * issues with other Grunt plugins, bundling assets for an Electron 10 | * or PhoneGap app, or deploying your app's flat files to a CDN. 11 | * 12 | * Note that when running `sails www` in a production environment (with the 13 | * `NODE_ENV` environment variable set to 'production') the `buildProd` task 14 | * will be run instead of this one. 15 | * 16 | * For more information see: 17 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/build-js 18 | * 19 | */ 20 | module.exports = function(grunt) { 21 | grunt.registerTask('build', [ 22 | 'compileAssets', 23 | 'linkAssetsBuild', 24 | 'clean:build', 25 | 'copy:build' 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/register/buildProd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `buildProd` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed instead of `build` if you 7 | * run `sails www` in a production environment, e.g.: 8 | * `NODE_ENV=production sails www` 9 | * 10 | * This generates a folder containing your compiled (and usually minified) 11 | * assets. The most common use case for this is bundling up files to 12 | * deploy to a CDN. 13 | * 14 | * For more information see: 15 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/build-prod-js 16 | * 17 | */ 18 | module.exports = function(grunt) { 19 | grunt.registerTask('buildProd', [ 20 | 'compileAssets', 21 | 'concat', 22 | 'uglify', 23 | 'cssmin', 24 | 'linkAssetsBuildProd', 25 | 'clean:build', 26 | 'copy:build' 27 | ]); 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /tasks/register/compileAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `compileAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `default`, `prod`, `build`, and 8 | * `buildProd` tasklists. 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/compile-assets-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('compileAssets', [ 16 | 'clean:dev', 17 | 'jst:dev', 18 | 'less:dev', 19 | 'copy:dev', 20 | 'coffee:dev' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/register/default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `default` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This is the default Grunt tasklist that will be executed if you 7 | * run `grunt` in the top level directory of your app. It is also 8 | * called automatically when you start Sails in development mode using 9 | * `sails lift` or `node app`. 10 | * 11 | * Note that when lifting your app with a custom environment setting 12 | * (i.e. `sails.config.environment`), Sails will look for a tasklist file 13 | * with the same name and run that instead of this one. 14 | * 15 | * > Note that as a special case for compatibility/historial reasons, if 16 | * > your environment is "production", and Sails cannot find a tasklist named 17 | * > `production.js`, it will attempt to run the `prod.js` tasklist as well 18 | * > before defaulting to `default.js`. 19 | * 20 | * For more information see: 21 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/default-js 22 | * 23 | */ 24 | module.exports = function (grunt) { 25 | grunt.registerTask('default', ['compileAssets', 'linkAssets', 'watch']); 26 | }; 27 | -------------------------------------------------------------------------------- /tasks/register/linkAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `default` tasklist and the `watch` task 8 | * (but only if the `grunt-sails-linker` package is in use). 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('linkAssets', [ 16 | 'sails-linker:devJs', 17 | 'sails-linker:devStyles', 18 | 'sails-linker:devTpl', 19 | 'sails-linker:devJsJade', 20 | 'sails-linker:devStylesJade', 21 | 'sails-linker:devTplJade' 22 | ]); 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuild.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssetsBuild` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `build` tasklist. 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-build-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('linkAssetsBuild', [ 15 | 'sails-linker:devJsRelative', 16 | 'sails-linker:devStylesRelative', 17 | 'sails-linker:devTpl', 18 | 'sails-linker:devJsRelativeJade', 19 | 'sails-linker:devStylesRelativeJade', 20 | 'sails-linker:devTplJade' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/register/linkAssetsBuildProd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `linkAssetsBuildProd` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `buildProd` tasklist. 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/link-assets-build-prod-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('linkAssetsBuildProd', [ 15 | 'sails-linker:prodJsRelative', 16 | 'sails-linker:prodStylesRelative', 17 | 'sails-linker:devTpl', 18 | 'sails-linker:prodJsRelativeJade', 19 | 'sails-linker:prodStylesRelativeJade', 20 | 'sails-linker:devTplJade' 21 | ]); 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/register/prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `prod` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist will be executed instead of `default` when 7 | * your Sails app is lifted in a production environment (e.g. using 8 | * `NODE_ENV=production node app`). 9 | * 10 | * For more information see: 11 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/prod-js 12 | * 13 | */ 14 | module.exports = function(grunt) { 15 | grunt.registerTask('prod', [ 16 | 'compileAssets', 17 | 'concat', 18 | 'uglify', 19 | 'cssmin', 20 | 'sails-linker:prodJs', 21 | 'sails-linker:prodStyles', 22 | 'sails-linker:devTpl', 23 | 'sails-linker:prodJsJade', 24 | 'sails-linker:prodStylesJade', 25 | 'sails-linker:devTplJade' 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/register/syncAssets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `syncAssets` 3 | * 4 | * --------------------------------------------------------------- 5 | * 6 | * This Grunt tasklist is not designed to be used directly-- rather 7 | * it is a helper called by the `watch` task (`tasks/config/watch.js`). 8 | * 9 | * For more information see: 10 | * http://sailsjs.org/documentation/anatomy/my-app/tasks/register/sync-assets-js 11 | * 12 | */ 13 | module.exports = function(grunt) { 14 | grunt.registerTask('syncAssets', [ 15 | 'jst:dev', 16 | 'less:dev', 17 | 'sync:dev', 18 | 'coffee:dev' 19 | ]); 20 | }; 21 | -------------------------------------------------------------------------------- /views/403.ejs: -------------------------------------------------------------------------------- 1 | 35 | 36 | 40 | 41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |

    48 | Forbidden 49 |

    50 |

    51 | <% if (typeof data !== 'undefined') { %> 52 | <%= data %> 53 | <% } else { %> 54 | You don't have permission to see the page you're trying to reach. 55 | <% } %> 56 |

    57 |

    58 | Why might this be happening? 59 |

    60 |
    61 | 62 | 67 |
    68 | 69 | -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 35 | 36 | 40 | 41 |
    42 |
    43 | 44 |
    45 | 46 |
    47 |

    48 | Something's fishy here. 49 |

    50 |

    51 | <% if (typeof data!== 'undefined') { %> 52 | <%= data %> 53 | <% } else { %> 54 | The page you were trying to reach doesn't exist. 55 | <% } %> 56 |

    57 |

    58 | Why might this be happening? 59 |

    60 |
    61 | 62 | 67 |
    68 | 69 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 |
    8 | 9 |
    10 |
    11 | Poly 12 |
    13 |
    14 |
    15 |
    16 | 31 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    Random
    44 |
    v0.0.1
    45 |
    46 |
    47 | 48 |
    49 |
    50 |
    51 |
    52 | 53 |
    54 | 55 |
    56 |
    57 |
    58 |
    59 | 60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 | 67 | 68 |
    69 |
    70 | 71 |
    72 |
    73 |
    74 |
    75 | 76 |
    77 |
    78 |
    79 |
    80 | 81 | 82 | 83 | 84 | 85 | 86 | 118 | 119 | 147 | 148 | 166 | 167 | 185 | 186 | 191 | 192 | 201 | -------------------------------------------------------------------------------- /views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%=typeof title == 'undefined' ? 'Poly Chat' : title%> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | <%- body %> 26 | 27 | 28 | 29 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | --------------------------------------------------------------------------------