├── .babelrc ├── .gitignore ├── .jshintrc ├── .nvmrc ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── bower.json ├── circle.yml ├── deploy.js ├── deploy ├── heroku.js └── version.js ├── karma.config.js ├── package.json ├── server.js ├── src ├── css │ ├── aboutpage.less │ ├── admin │ │ └── index.less │ ├── angular │ │ ├── angular-csp.less │ │ ├── angular-growl.less │ │ ├── angular-wizard.less │ │ └── charLimit.less │ ├── app2.less │ ├── community.less │ ├── community │ │ ├── create.less │ │ ├── invite.less │ │ ├── posts.less │ │ └── settings.less │ ├── footer.less │ ├── homepage.less │ ├── index.less │ ├── ment.io │ │ └── userMentions.less │ ├── mixins.less │ ├── mixins │ │ ├── full-screen-modal.less │ │ ├── has-banner.less │ │ ├── inputs.less │ │ ├── settings-page.less │ │ ├── simple-page.less │ │ ├── type.less │ │ └── widgets.less │ ├── nav.less │ ├── network.less │ ├── onboarding.less │ ├── post │ │ ├── comment.less │ │ ├── edit.less │ │ ├── followers.less │ │ ├── fulfill.less │ │ └── post.less │ ├── profile │ │ ├── about.less │ │ ├── profile-edit.less │ │ ├── profile-only.less │ │ └── profile-shared.less │ ├── project.less │ ├── project │ │ ├── cards.less │ │ └── edit.less │ ├── search.less │ ├── shared │ │ ├── colors.less │ │ ├── dropdown.less │ │ ├── icons.less │ │ ├── social-media.less │ │ ├── tags.less │ │ └── toolbar.less │ ├── styleguide.less │ ├── subscribe.less │ ├── support.less │ └── user │ │ ├── entrance.less │ │ ├── home.less │ │ ├── notifications.less │ │ └── settings.less ├── html │ ├── admin │ │ ├── communities.tpl.html │ │ └── metrics.tpl.html │ ├── pages │ │ ├── _clientEnv.ejs │ │ ├── _favicon.ejs │ │ ├── _footer.ejs │ │ ├── _header.ejs │ │ ├── _menu.ejs │ │ ├── _newrelic.ejs │ │ ├── _rollbar.ejs │ │ ├── _segment.ejs │ │ ├── _twitter.ejs │ │ ├── about │ │ │ ├── _header.ejs │ │ │ ├── _linksBar.ejs │ │ │ ├── careers │ │ │ │ └── index.ejs │ │ │ ├── contact │ │ │ │ └── index.ejs │ │ │ ├── index.ejs │ │ │ └── team │ │ │ │ └── index.ejs │ │ ├── admin │ │ │ └── index.ejs │ │ ├── app │ │ │ └── index.ejs │ │ ├── help │ │ │ ├── _whatIsHylo.ejs │ │ │ ├── index.ejs │ │ │ └── markdown │ │ │ │ └── index.ejs │ │ ├── index.ejs │ │ ├── newapp │ │ │ └── index.ejs │ │ ├── styleguide │ │ │ └── index.ejs │ │ ├── subscribe │ │ │ └── index.ejs │ │ └── terms │ │ │ ├── index.ejs │ │ │ └── privacy │ │ │ └── index.ejs │ ├── ui-partials │ │ ├── README │ │ ├── announcer.ejs │ │ └── nav.ejs │ └── ui │ │ ├── app │ │ ├── 404.tpl.ejs │ │ ├── comments.tpl.html │ │ ├── fulfillModal.tpl.html │ │ ├── network.tpl.html │ │ ├── search.tpl.html │ │ ├── socialMedia.tpl.html │ │ └── typeahead-tag-user.tpl.html │ │ ├── community │ │ ├── about.tpl.html │ │ ├── base.tpl.html │ │ ├── create.tpl.html │ │ ├── events.tpl.html │ │ ├── invite.tpl.html │ │ ├── join.tpl.html │ │ ├── members.tpl.html │ │ ├── posts.tpl.html │ │ ├── projects.tpl.html │ │ └── settings.tpl.html │ │ ├── entrance │ │ ├── base.tpl.html │ │ ├── forgot-password.tpl.html │ │ ├── login.tpl.html │ │ ├── presignup.tpl.html │ │ ├── signup-with-code.tpl.html │ │ ├── signup.tpl.html │ │ └── waitlist.tpl.html │ │ ├── home │ │ ├── all-posts.tpl.html │ │ ├── following-posts.tpl.html │ │ ├── my-posts.tpl.html │ │ ├── projects.tpl.html │ │ ├── show.tpl.html │ │ └── simple.tpl.html │ │ ├── network │ │ ├── about.tpl.html │ │ ├── communities.tpl.html │ │ ├── members.tpl.html │ │ └── posts.tpl.html │ │ ├── onboarding │ │ ├── community-modal.tpl.html │ │ ├── profile-modal.tpl.html │ │ ├── seeds.tpl.html │ │ └── start.tpl.ejs │ │ ├── post │ │ ├── card.tpl.html │ │ ├── edit-page.tpl.html │ │ ├── edit.tpl.html │ │ ├── infinite-event-scroll.tpl.html │ │ ├── infinite-scroll.tpl.html │ │ ├── list.tpl.html │ │ ├── show.tpl.html │ │ ├── toolbar.tpl.html │ │ └── welcome.tpl.html │ │ ├── profile │ │ ├── about.tpl.html │ │ ├── base.tpl.html │ │ ├── contributions.tpl.html │ │ ├── edit.tpl.html │ │ └── thanks.tpl.html │ │ ├── project │ │ ├── edit.tpl.html │ │ ├── invite.tpl.html │ │ ├── posts.tpl.html │ │ ├── settings.tpl.html │ │ ├── show.tpl.ejs │ │ └── users.tpl.html │ │ ├── shared │ │ ├── confirm.tpl.html │ │ ├── join-community.tpl.html │ │ ├── main.tpl.ejs │ │ ├── ngTagsInput │ │ │ ├── autocomplete-user.tpl.html │ │ │ └── tag-user.tpl.html │ │ ├── people-mentions.tpl.html │ │ ├── project-cards.tpl.html │ │ ├── typeaheadUser.tpl.html │ │ └── user-list-item.tpl.html │ │ ├── support │ │ └── base.tpl.ejs │ │ └── user │ │ ├── notifications.tpl.html │ │ ├── settings.tpl.html │ │ └── use-invitation.tpl.html ├── img │ ├── about │ │ ├── careerBanner.jpg │ │ ├── companyBanner.jpg │ │ ├── contactBanner.jpg │ │ ├── headshots │ │ │ ├── ArtBrock.jpg │ │ │ ├── DanielGoldman.jpg │ │ │ ├── DavidHodgson.jpg │ │ │ ├── DavidMartin.jpg │ │ │ ├── Edward.jpg │ │ │ ├── EricBerlow.jpg │ │ │ ├── FeranandaIbarra.jpg │ │ │ ├── JohnKatovich.jpg │ │ │ ├── Lawrence.jpg │ │ │ ├── MihaelaUlieru.jpg │ │ │ ├── Minda.jpg │ │ │ └── Ray.jpg │ │ └── teamBanner.jpg │ ├── appicon.png │ ├── facebook.png │ ├── favicon.png │ ├── faviconDev.png │ ├── faviconStaging.png │ ├── google.png │ ├── homepage │ │ ├── birdsBg.png │ │ ├── bolt.png │ │ ├── coworkingBg-faint.jpg │ │ ├── coworkingBg.jpg │ │ ├── graph.png │ │ ├── hexagon.png │ │ ├── magGlass.png │ │ ├── network.jpg │ │ ├── swirlTexture.jpg │ │ └── wrench.png │ ├── largeh.png │ ├── linkedin.png │ ├── logotype-teal-transparent.png │ ├── logotype-white-transparent.png │ ├── merkaba-black.png │ ├── merkaba.png │ ├── onboarding │ │ └── iho │ │ │ ├── intention1.jpg │ │ │ ├── intention1_m.jpg │ │ │ ├── intention2.jpg │ │ │ ├── intention2_m.jpg │ │ │ ├── offer1.jpg │ │ │ ├── offer1_m.jpg │ │ │ ├── offer2.jpg │ │ │ ├── offer2_m.jpg │ │ │ ├── request1.jpg │ │ │ ├── request1_m.jpg │ │ │ ├── request2.jpg │ │ │ └── request2_m.jpg │ ├── projects │ │ ├── community.svg │ │ └── public.svg │ ├── smallh.png │ └── testimonials │ │ └── logos │ │ ├── Afrilabs.png │ │ ├── Embassy.png │ │ ├── Enspiral.png │ │ ├── OaklandHub.png │ │ └── SfunCube.png └── js │ ├── admin │ ├── index.js │ └── services │ │ ├── Admin.js │ │ └── Chart.js │ ├── angular │ └── angulartics-segmentio.js │ ├── app │ ├── animations.js │ ├── controllers.js │ ├── controllers │ │ ├── AnnouncerCtrl.js │ │ ├── FulfillmentCtrl.js │ │ ├── NavCtrl.js │ │ ├── SearchCtrl.js │ │ ├── community │ │ │ ├── CommunityCtrl.js │ │ │ ├── CommunityEventsCtrl.js │ │ │ ├── CommunityInviteCtrl.js │ │ │ ├── CommunityMembersCtrl.js │ │ │ ├── CommunityPostsCtrl.js │ │ │ ├── CommunitySettingsCtrl.js │ │ │ ├── JoinCommunityByUrlCtrl.js │ │ │ ├── JoinCommunityCtrl.js │ │ │ └── NewCommunityCtrl.js │ │ ├── home │ │ │ ├── AllPostsCtrl.js │ │ │ └── FollowedPostsCtrl.js │ │ ├── post │ │ │ ├── CommentsCtrl.js │ │ │ ├── PostCardCtrl.js │ │ │ ├── PostCtrl.js │ │ │ ├── PostEditCtrl.js │ │ │ ├── PostEditPageCtrl.js │ │ │ ├── PostListCtrl.js │ │ │ └── WelcomePostCtrl.js │ │ ├── profile.js │ │ ├── profile │ │ │ ├── ProfileEditCtrl.js │ │ │ ├── contributions.js │ │ │ └── thanks.js │ │ ├── project │ │ │ ├── ProjectCtrl.js │ │ │ ├── ProjectEditCtrl.js │ │ │ ├── ProjectInviteCtrl.js │ │ │ ├── ProjectPostsCtrl.js │ │ │ └── ProjectUsersCtrl.js │ │ └── user │ │ │ ├── ForgotPasswordCtrl.js │ │ │ ├── LoginCtrl.js │ │ │ ├── NotificationsCtrl.js │ │ │ ├── SignupCtrl.js │ │ │ └── UserSettingsCtrl.js │ ├── directives.js │ ├── directives │ │ ├── anguvideo.js │ │ ├── contenteditable.js │ │ ├── embeddedComments.js │ │ ├── inlinePostInput.js │ │ ├── masonry.js │ │ ├── postCard.js │ │ ├── postEditor.js │ │ ├── postsToolbar.js │ │ ├── seeMore.js │ │ ├── socialMedia.js │ │ ├── touchClass.js │ │ └── welcomePost.js │ ├── filters.js │ ├── index.js │ ├── routes.js │ ├── routes │ │ ├── community.js │ │ ├── entrance.js │ │ ├── home.js │ │ ├── network.js │ │ ├── onboarding.js │ │ ├── profile.js │ │ └── project.js │ ├── services.js │ └── services │ │ ├── Activity.js │ │ ├── Cache.js │ │ ├── Comment.js │ │ ├── Community.js │ │ ├── CurrentUser.js │ │ ├── GooglePicker.js │ │ ├── Invitation.js │ │ ├── ModalLoginSignup.js │ │ ├── Network.js │ │ ├── Post.js │ │ ├── PostManager.js │ │ ├── Project.js │ │ ├── RichText.js │ │ ├── Search.js │ │ ├── ThirdPartyAuth.js │ │ ├── TimeText.js │ │ ├── User.js │ │ ├── UserCache.js │ │ ├── UserMentions.js │ │ ├── bodyClass.js │ │ ├── clickthroughTracker.js │ │ ├── defaultUserBanner.js │ │ ├── dialog.js │ │ ├── filepickerUpload.js │ │ ├── hideNavIOS.js │ │ ├── history.js │ │ ├── isAndroidApp.js │ │ ├── isIOSApp.js │ │ ├── joinCommunity.js │ │ ├── myHttpInterceptor.js │ │ ├── onboarding.js │ │ ├── popupDone.js │ │ ├── removeTrailingSlash.js │ │ └── webViewJavascriptBridge.js │ ├── index.js │ ├── onload.js │ ├── sails.io.js │ └── subscribe │ └── index.js ├── templateEnv.js └── test ├── bundle.tests.js ├── features ├── CommunityCtrl.tests.js └── routes.tests.js ├── services ├── RichText.tests.js └── removeTrailingSlash.tests.js └── setup.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # IntelliJ Idea 17 | .idea/ 18 | *.iml 19 | *.iws 20 | 21 | # Eclipse 22 | .classpath 23 | .project 24 | .settings/ 25 | 26 | # Mac 27 | .DS_Store 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directory 36 | # Commenting this out is preferred by some people, see 37 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 38 | node_modules 39 | 40 | # Users Environment Variables 41 | .lock-wscript 42 | 43 | bower_components/ 44 | dist/ 45 | .env 46 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "browser": true, 4 | "esnext": true, 5 | "globals": { 6 | "_": true, 7 | "alert": false, 8 | "angular": false, 9 | "describe": false, 10 | "expect": false, 11 | "filepicker": true, 12 | "format": true, 13 | "hyloEnv": true, 14 | "it": false, 15 | "module": false, 16 | "require": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.12.7 -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hylo", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "bootstrap": "~3.3.2", 6 | "angular": "1.3.17", 7 | "angular-animate": "1.3.17", 8 | "angular-growl": "0.4.0", 9 | "angular-mocks": "1.3.17", 10 | "angular-resource": "1.3.17", 11 | "angular-sanitize": "1.3.17", 12 | "angular-touch": "1.3.17", 13 | "angular-ui-router": "0.2.14", 14 | "angulartics": "1.0.3", 15 | "ngInfiniteScroll": "1.2.0", 16 | "ment.io": "0.9.22", 17 | "gsap": "~1.15.1", 18 | "ng-tags-input": "igorprado/ngTagsInput#68a6c42a", 19 | "angular-ui-bootstrap-bower": "0.13.0", 20 | "afkl-lazy-image": "~0.1.4", 21 | "stellar": "~0.6.2", 22 | "bootstrap-ui-datetime-picker": "~1.2.5" 23 | }, 24 | "private": true, 25 | "resolutions": { 26 | "angular": "1.3.17" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | deployment: 2 | production: 3 | branch: master 4 | commands: 5 | - "grunt deploy --to=production" 6 | machine: 7 | node: 8 | version: 0.12.7 9 | -------------------------------------------------------------------------------- /deploy/heroku.js: -------------------------------------------------------------------------------- 1 | 2 | if (!process.env.HEROKU_API_TOKEN) { 3 | throw new Error("HEROKU_API_TOKEN is not set"); 4 | } 5 | 6 | var instance; 7 | 8 | var client = function() { 9 | if (!instance) { 10 | instance = new (require('heroku-client'))({token: process.env.HEROKU_API_TOKEN}); 11 | } 12 | return instance; 13 | }; 14 | 15 | module.exports = { 16 | config: function(app) { 17 | return client().apps(app).configVars(); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /deploy/version.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | var format = require('util').format; 3 | 4 | var _command = function(cmd) { 5 | return exec(cmd, {silent: true}).stdout.replace(/\n/, ''); 6 | }; 7 | 8 | module.exports = function(maxlength) { 9 | if (!maxlength) maxlength = 40; 10 | return _command(format("git show|head -n1|awk '{print $2}'|cut -c -%s", maxlength)); 11 | }; 12 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var babelify = require('babelify') 4 | 5 | // Karma configuration 6 | // Generated on Wed Oct 22 2014 16:54:59 GMT-0700 (PDT) 7 | 8 | module.exports = function(config) { 9 | config.set({ 10 | 11 | // base path that will be used to resolve all patterns (eg. files, exclude) 12 | basePath: '', 13 | 14 | browserify: { 15 | debug: true, 16 | transform: [ 17 | 'debowerify', 18 | 'browserify-ngannotate', 19 | babelify.configure({ 20 | ignore: /\/bower_components\// 21 | }) 22 | ] 23 | }, 24 | 25 | // frameworks to use 26 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 27 | frameworks: ['browserify', 'mocha', 'chai'], 28 | 29 | // list of files / patterns to load in the browser 30 | files: [ 31 | 'test/setup.js', 32 | 'test/**/*.tests.js' 33 | ], 34 | 35 | // list of files to exclude 36 | exclude: [ 37 | ], 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | 'test/**/*.js': ['browserify'] 43 | }, 44 | 45 | // test results reporter to use 46 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 47 | reporters: ['spec'], 48 | 49 | // web server port 50 | port: 9876, 51 | 52 | // enable / disable colors in the output (reporters and logs) 53 | colors: true, 54 | 55 | // level of logging 56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 57 | logLevel: config.LOG_WARN, 58 | 59 | // enable / disable watching file and executing tests whenever any file changes 60 | autoWatch: true, 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS2'], 65 | 66 | // browserify takes a long time 67 | browserNoActivityTimeout: 30000, 68 | 69 | // Continuous Integration mode 70 | // if true, Karma captures browsers, runs the tests and exits 71 | singleRun: true 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | static = require('node-static'), 3 | fileServer = new static.Server('./dist'), 4 | _ = require('lodash'), 5 | url = require('url'), 6 | util = require('util'); 7 | 8 | module.exports = function(opts) { 9 | 10 | http.createServer(function(req, res) { 11 | 12 | var u = url.parse(req.url), 13 | originalUrl = req.url, 14 | changePathname = function(value) { 15 | u.pathname = value; 16 | req.url = url.format(u); 17 | }, 18 | log = function(resolution) { 19 | opts.log.writeln('%s %s %s', req.method, originalUrl, resolution); 20 | }; 21 | 22 | // remove trailing slash 23 | u.pathname = u.pathname.replace(/\/$/, ''); 24 | 25 | if (_.startsWith(u.pathname, '/assets')) { 26 | changePathname(u.pathname.replace(/^\/assets/, '')); 27 | } 28 | 29 | // all local assets 30 | fileServer.serve(req, res, function(err, result) { 31 | if (err && err.status === 404) { 32 | res.statusCode = 404; 33 | res.end('404 Not Found'); 34 | log('→ Not Found'); 35 | } else { 36 | log('→ OK'); 37 | } 38 | }); 39 | 40 | }).listen(opts.port); 41 | 42 | opts.log.writeln('listening on port ' + opts.port); 43 | }; 44 | -------------------------------------------------------------------------------- /src/css/admin/index.less: -------------------------------------------------------------------------------- 1 | @import 'bower_components/bootstrap/less/bootstrap'; 2 | @import '../shared/colors'; 3 | @import '../shared/icons'; 4 | 5 | ul.horizontal-menu { 6 | list-style: none; 7 | padding-left: 0; 8 | li { 9 | display: inline-block; 10 | margin-right: 10px; 11 | &:before { 12 | content: '☺'; 13 | padding-right: 10px; 14 | } 15 | } 16 | } 17 | 18 | .community-card { 19 | display: inline-block; 20 | margin-right: 5px; 21 | margin-bottom: 5px; 22 | border-radius: 4px; 23 | border: 1px solid @border-light; 24 | background-color: @faint-gray; 25 | padding: 10px; 26 | img { 27 | float: left; 28 | width: 40px; 29 | height: 40px; 30 | margin-right: 5px; 31 | margin-bottom: 5px; 32 | } 33 | .text { 34 | overflow: hidden; 35 | } 36 | width: 16vw; 37 | } 38 | 39 | .sort-controls { 40 | label { 41 | margin-left: 8px; 42 | } 43 | } 44 | 45 | .icon-members-new { 46 | position: relative; 47 | top: 1px; 48 | } 49 | 50 | .chart { 51 | svg { 52 | min-height: 600px; 53 | } 54 | } 55 | 56 | // remove bars for zeroes 57 | // http://stackoverflow.com/questions/22378785/nvd3-charts-remove-0-value-bars 58 | .nvd3 rect[height="1"] { 59 | display: none !important; 60 | } 61 | -------------------------------------------------------------------------------- /src/css/angular/angular-csp.less: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-block-transitions { 16 | transition:0s all!important; 17 | -webkit-transition:0s all!important; 18 | } 19 | 20 | /* show the element during a show/hide animation when the 21 | * animation is ongoing, but the .ng-hide class is active */ 22 | .ng-hide-add-active, .ng-hide-remove { 23 | display: block!important; 24 | } 25 | -------------------------------------------------------------------------------- /src/css/angular/angular-growl.less: -------------------------------------------------------------------------------- 1 | .growl { 2 | position: fixed; 3 | bottom: 0; 4 | left: 10px; 5 | float: left; 6 | width: 250px; 7 | z-index: 200000; 8 | } 9 | 10 | .growl-item.ng-enter, 11 | .growl-item.ng-leave { 12 | -webkit-transition:0.5s linear all; 13 | -moz-transition:0.5s linear all; 14 | -o-transition:0.5s linear all; 15 | transition:0.5s linear all; 16 | } 17 | 18 | .growl-item.ng-enter, 19 | .growl-item.ng-leave.ng-leave-active { 20 | opacity:0; 21 | } 22 | .growl-item.ng-leave, 23 | .growl-item.ng-enter.ng-enter-active { 24 | opacity:1; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/css/angular/charLimit.less: -------------------------------------------------------------------------------- 1 | // Char-limit Directive 2 | 3 | .right-inner-addon { 4 | position: relative; 5 | 6 | input { 7 | padding-right: 30px; 8 | } 9 | 10 | & > i { 11 | display: block; 12 | position: absolute; 13 | right: 0px; 14 | padding: 9px 12px; 15 | cursor: pointer; 16 | } 17 | 18 | .icon-alert { 19 | font-size: 16px; 20 | } 21 | 22 | .chars-left { 23 | .proxima-nova-light; 24 | font-style: normal; 25 | font-size: 20px; 26 | color: #CCC; 27 | } 28 | 29 | &.char-limit-warning { 30 | .chars-left, i { 31 | color: @alert-warning-text; 32 | } 33 | } 34 | 35 | &.char-limit-error { 36 | .chars-left, i { 37 | color: @alert-danger-text; 38 | } 39 | } 40 | 41 | &.char-limit-error, &.char-limit-warning { 42 | .chars-left { 43 | background-color: @white; 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/css/community/create.less: -------------------------------------------------------------------------------- 1 | .createCommunity { 2 | .settings-page; 3 | .full-screen-modal; 4 | .body-text; 5 | 6 | form { 7 | padding-bottom: @std-padding * 2; 8 | 9 | button { 10 | .rounded-button; 11 | } 12 | } 13 | 14 | .setting-item { 15 | .side-column label { 16 | padding-top: 6px; 17 | } 18 | p.help.error { 19 | margin-bottom: 0; 20 | } 21 | } 22 | 23 | .banner { 24 | width: 100%; 25 | .fluid-height(38%); 26 | background-size: cover; 27 | background-position: center; 28 | } 29 | 30 | .community-icon { 31 | width: 60px; 32 | height: 60px; 33 | background-size: cover; 34 | background-position: center; 35 | } 36 | 37 | p.help { 38 | margin-top: 5px; 39 | 40 | &.error { 41 | color: @error-color; 42 | } 43 | } 44 | 45 | .footer { 46 | text-align: right; 47 | } 48 | } -------------------------------------------------------------------------------- /src/css/community/invite.less: -------------------------------------------------------------------------------- 1 | .community-invite { 2 | .settings-page; 3 | .full-screen-modal; 4 | 5 | .section { 6 | label[for="inviteAsModerator"] { 7 | font-weight: normal; 8 | font-size: 14px; 9 | line-height: 32px; 10 | margin: 0 0 0 5px; 11 | display: inline-block; 12 | vertical-align: top; 13 | } 14 | .right { 15 | text-align: right; 16 | top: 12px; 17 | } 18 | button { 19 | .rounded-button; 20 | } 21 | input[type="text"] { 22 | .body-text; 23 | line-height: 30px; 24 | &.placeholding { font-style: italic; } 25 | display: block; 26 | width: 100%; 27 | margin-bottom: @std-padding; 28 | } 29 | textarea { 30 | margin-bottom: @std-padding; 31 | height: 180px; 32 | @media (max-width: @screen-xs-max) { 33 | height: 40vh; 34 | } 35 | } 36 | .full-column.help { 37 | margin-top: @std-padding; 38 | text-align: center; 39 | a { 40 | text-decoration: underline; 41 | } 42 | } 43 | .results { 44 | tr { 45 | background-color: @state-success-bg; 46 | 47 | &.error { 48 | background-color: @state-danger-bg; 49 | } 50 | 51 | td { 52 | padding: 5px 10px; 53 | .body-text; 54 | } 55 | } 56 | h3 { 57 | .title-text; 58 | } 59 | } 60 | } 61 | } 62 | 63 | .subheader { 64 | margin-top: @std-padding; 65 | } 66 | 67 | .invite-section-label { 68 | .medium-header; 69 | } 70 | .invite-section-label.code { 71 | margin-top: @std-padding; 72 | padding-top: @std-padding; 73 | border-top: solid 1px @border-lightest; 74 | } 75 | 76 | p.help.error { 77 | margin-top: @std-padding; 78 | color: @error-color !important; 79 | } 80 | -------------------------------------------------------------------------------- /src/css/footer.less: -------------------------------------------------------------------------------- 1 | footer.text-center { 2 | color: #999; 3 | .footer-links { 4 | padding-left: 0; 5 | margin-top: 50px; 6 | 7 | li { 8 | display: inline; 9 | padding: 0 2px; 10 | } 11 | 12 | li:first-child { 13 | padding-left: 0; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/css/index.less: -------------------------------------------------------------------------------- 1 | 2 | // 3rd-party libraries 3 | @import 'bower_components/bootstrap/less/bootstrap'; 4 | @import (inline) 'node_modules/medium.js/medium.css'; 5 | @import (inline) 'bower_components/ng-tags-input/ngTagsInput-bower/ng-tags-input.css'; 6 | @import 'ment.io/userMentions'; 7 | 8 | // Bootstrap overrides 9 | .btn { 10 | .proxima-nova-regular; 11 | } 12 | @headings-font-family: "proxima-nova", sans-serif; 13 | 14 | // other useful global variables 15 | @std-padding: floor(@grid-gutter-width / 2); 16 | 17 | // for static pages 18 | @import 'aboutpage'; 19 | @import 'homepage'; 20 | 21 | // for angular app (mostly) 22 | @import 'angular/angular-csp'; 23 | @import 'angular/angular-growl'; 24 | @import 'angular/charLimit'; 25 | @import 'app2'; 26 | @import 'community'; 27 | @import 'community/create'; 28 | @import 'community/invite'; 29 | @import 'community/posts'; 30 | @import 'community/settings'; 31 | @import 'footer'; 32 | @import 'mixins'; 33 | @import 'nav'; 34 | @import 'network'; 35 | @import 'onboarding'; 36 | @import 'profile/about'; 37 | @import 'profile/profile-edit'; 38 | @import 'profile/profile-only'; 39 | @import 'profile/profile-shared'; 40 | @import 'project'; 41 | @import 'project/cards'; 42 | @import 'project/edit'; 43 | @import 'search'; 44 | @import 'post/comment'; 45 | @import 'post/edit'; 46 | @import 'post/followers'; 47 | @import 'post/fulfill'; 48 | @import 'post/post'; 49 | @import 'shared/colors'; 50 | @import 'shared/tags'; 51 | @import 'shared/dropdown'; 52 | @import 'shared/icons'; 53 | @import 'shared/social-media'; 54 | @import 'shared/toolbar'; 55 | @import 'support'; 56 | @import 'user/home'; 57 | @import 'user/entrance'; 58 | @import 'user/notifications'; 59 | @import 'user/settings'; 60 | -------------------------------------------------------------------------------- /src/css/ment.io/userMentions.less: -------------------------------------------------------------------------------- 1 | .mentio-editor.elastic{ 2 | overflow-y: auto; 3 | overflow-x:hidden; 4 | } 5 | 6 | .menu-highlighted { 7 | font-weight: bold; 8 | } 9 | 10 | .user-search { 11 | .list-group-item.active { 12 | color: #fff; 13 | background-color: #428bca; 14 | border-color: #428bca; 15 | 16 | .text-muted { 17 | color: #ccc; 18 | } 19 | 20 | .text-primary { 21 | color: #fff; 22 | } 23 | } 24 | 25 | .list-group-item { 26 | padding: 5px; 27 | } 28 | 29 | .user-photo { 30 | width: 30px; 31 | height: 30px; 32 | max-width: 30px; 33 | max-height: 30px; 34 | border-radius: 15px; 35 | } 36 | } 37 | 38 | .unpad-last-p p:last-child { 39 | margin-bottom: 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/css/mixins/full-screen-modal.less: -------------------------------------------------------------------------------- 1 | .full-screen-modal() { 2 | .simple-page; 3 | 4 | #content { 5 | position: relative; 6 | overflow: visible; 7 | } 8 | 9 | #nav, #nav-pad { 10 | display: none; 11 | } 12 | 13 | #main-container { 14 | background-color: @white; 15 | margin-top: @nav-height + @std-padding; 16 | @media (max-width: @screen-xs-max) { 17 | padding: 0; 18 | overflow-x: hidden; 19 | } 20 | } 21 | 22 | nav { 23 | border: none; 24 | border-bottom: 2px solid #DCDCDC; 25 | background-color: @white; 26 | border-radius: 0; 27 | padding: 0 @std-padding; 28 | margin: 0 !important; 29 | height: @nav-height; 30 | line-height: @nav-height; 31 | position: fixed; 32 | width: 100%; 33 | top: 0; 34 | left: 0; 35 | z-index: @nav-z-index; 36 | 37 | .close { 38 | .big-close; 39 | position: relative; 40 | z-index: 2; 41 | 42 | @media (min-width: @screen-sm) { 43 | padding-right: @std-padding*2; 44 | } 45 | } 46 | 47 | .icon-hylo-script { 48 | font-size: 45px; 49 | display: block; 50 | margin: 5px 0 5px; 51 | @media (max-width: @screen-xs-max) { 52 | display: none; 53 | } 54 | } 55 | 56 | .navbar-center { 57 | .section-title; 58 | position: absolute; 59 | width: 100%; 60 | left: 0; 61 | top: 0; 62 | text-align: center; 63 | margin: auto; 64 | @media (max-width: @screen-xs-max) { 65 | text-align: left; 66 | padding-left: @std-padding; 67 | padding-top: 0; 68 | } 69 | } 70 | } 71 | 72 | .all-sections { 73 | @media (max-width: @screen-xs-max) { 74 | padding-left: @std-padding * 2; 75 | padding-right: @std-padding * 2; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/css/mixins/inputs.less: -------------------------------------------------------------------------------- 1 | .borderless-text-input() { 2 | border: none; 3 | box-shadow: none; 4 | outline: none; 5 | background-color: transparent; 6 | margin-left: 0; 7 | } 8 | 9 | @default-button-height: 32px; 10 | 11 | .rounded-button(@size: @default-button-height, @background-color: @white) { 12 | .button-text; 13 | &, &:hover { 14 | text-decoration: none; 15 | color: @text-dark; 16 | } 17 | background-color: @background-color; 18 | cursor: pointer; 19 | border: 1px solid @light-gray; 20 | border-radius: @size; 21 | display: inline-block; 22 | height: @size; 23 | line-height: @size - 2px; 24 | padding: 0px 15px; 25 | outline: none; 26 | > i { 27 | position: relative; 28 | top: 1px; 29 | } 30 | :active, &.active { 31 | background-color: darken(@background-color, 20%); 32 | } 33 | 34 | &.disabled, &[disabled] { 35 | opacity: 0.4; 36 | } 37 | } 38 | 39 | .rounded-button-dark(@size: @default-button-height, @background-color: @dark-gray) { 40 | .rounded-button(@size, @background-color); 41 | &, &:hover { 42 | color: @white; 43 | } 44 | } 45 | 46 | .clear-button { 47 | &, &:hover { 48 | color: @white; 49 | } 50 | background-color: transparent; 51 | border: 1px solid white; 52 | &:active { 53 | background-color: rgba(255, 255, 255, 0.3); 54 | } 55 | } 56 | 57 | .rounded-button-clear { 58 | .rounded-button; 59 | .clear-button; 60 | } 61 | 62 | .circular-button(@size: @default-button-height) { 63 | .rounded-button(@size); 64 | width: @size; 65 | text-align: center !important; 66 | padding: 0; 67 | } 68 | 69 | .round-input { 70 | -webkit-appearance: none; 71 | border: 1px solid @button-border; 72 | border-radius: 25px; 73 | outline: none; 74 | margin: 0; 75 | display: inline-block; 76 | position: relative; 77 | .proxima-nova(100); 78 | } 79 | 80 | .round-radio() { 81 | .round-input; 82 | padding: 10px; 83 | } 84 | 85 | .round-checkbox() { 86 | .round-input; 87 | padding: 15px; 88 | 89 | &:checked { 90 | 91 | &:after { 92 | font-family: @icon-font-family; 93 | content: '\e62e'; 94 | font-size: 14px; 95 | position: absolute; 96 | top: 8px; 97 | left: 7px; 98 | color: @default-text; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/css/mixins/settings-page.less: -------------------------------------------------------------------------------- 1 | .settings-page() { 2 | 3 | .section-label { 4 | .medium-header; 5 | padding: @std-padding 0; 6 | border-bottom: 1px solid @border-light; 7 | color: @default-text; 8 | 9 | .icon-right, .icon-down { 10 | float: right; 11 | margin-top: 13px; 12 | font-size: 12px; 13 | } 14 | 15 | @media (max-width: @screen-xs-max) { 16 | margin: 0 -@std-padding; 17 | padding: @std-padding; 18 | } 19 | } 20 | 21 | label { 22 | .title-text; 23 | display: block; 24 | } 25 | 26 | .setting-item { 27 | .make-row; 28 | padding-top: @std-padding; 29 | padding-bottom: @std-padding; 30 | 31 | 32 | input[type=checkbox] { 33 | .round-checkbox(); 34 | } 35 | 36 | button { 37 | .rounded-button; 38 | 39 | &.main-action { 40 | .rounded-button-dark; 41 | } 42 | } 43 | 44 | p { 45 | .body-text; 46 | 47 | &.help { 48 | .meta-text; 49 | } 50 | } 51 | 52 | .main-column { 53 | .make-sm-column(8); 54 | button { 55 | margin-top: @std-padding/2; 56 | } 57 | } 58 | .side-column { 59 | .make-sm-column(4); 60 | 61 | @media (min-width: @screen-sm) { 62 | text-align: right; 63 | } 64 | } 65 | .value { 66 | @media (min-width: @screen-sm) { 67 | text-align: right; 68 | p { 69 | text-align: left; 70 | } 71 | } 72 | } 73 | .full-column { 74 | .make-sm-column(12); 75 | } 76 | .half-column { 77 | .make-sm-column(6); 78 | } 79 | .right { 80 | @media (min-width: @screen-sm) { 81 | text-align: right; 82 | } 83 | } 84 | .summary { 85 | .meta-text; 86 | display: block; 87 | } 88 | } 89 | 90 | .help { 91 | .meta-text; 92 | a { 93 | color: @link-color !important; 94 | } 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/css/mixins/simple-page.less: -------------------------------------------------------------------------------- 1 | .simple-page(@columns: 6) { 2 | .all-sections > h1 { 3 | .section-title; 4 | margin-top: 0; 5 | margin-bottom: @std-padding * 2; 6 | line-height: 18px; 7 | } 8 | 9 | .main-content { 10 | .make-row; 11 | } 12 | 13 | .all-sections { 14 | .make-sm-column(@columns + 2); 15 | .make-sm-column-offset((10 - @columns) / 2); 16 | .make-md-column(@columns); 17 | .make-md-column-offset((12 - @columns) / 2); 18 | 19 | > p.section { 20 | padding: @std-padding 0; 21 | } 22 | 23 | // avoid overlapping the intercom button 24 | @media (max-width: @screen-xs-max) { 25 | margin-bottom: 70px; 26 | } 27 | 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/css/network.less: -------------------------------------------------------------------------------- 1 | .centered-column(@size) { 2 | .make-sm-column(@size + 2); 3 | .make-md-column-offset((10-@size)/2); 4 | .make-md-column(@size); 5 | .make-md-column-offset((12-@size)/2); 6 | 7 | @media (max-width: @screen-xs-max) { 8 | padding-left: 0; 9 | padding-right: 0; 10 | } 11 | } 12 | 13 | body.network { 14 | .has-banner(4); 15 | } 16 | 17 | body.network-posts { 18 | .posts { 19 | .centered-column(6); 20 | } 21 | } 22 | 23 | body.network-communities { 24 | 25 | .tab-content .communities { 26 | .centered-column(10); 27 | padding-top: @std-padding*2; 28 | } 29 | 30 | .community-card-wrapper { 31 | .make-sm-column(4); 32 | } 33 | 34 | .community-card { 35 | width: 100%; 36 | position: relative; 37 | border: 1px solid @border-light; 38 | border-radius: @border-radius-base; 39 | overflow: hidden; 40 | margin-bottom: @std-padding*2; 41 | 42 | .background-wrapper { 43 | font-size: 0; // inline-block spacing fix 44 | } 45 | 46 | .background { 47 | .fluid-height(38.2%); 48 | > div { 49 | .banner-background; 50 | } 51 | } 52 | 53 | .middle-content { 54 | position: relative; 55 | padding: @std-padding; 56 | background: @faint-gray; 57 | } 58 | 59 | .logo { 60 | width: @avatar-size-default; 61 | height: @avatar-size-default; 62 | margin-top: -@avatar-size-default/2 - @std-padding; 63 | margin-bottom: 10px; 64 | background-size: cover; 65 | } 66 | 67 | .name { 68 | .small-header; 69 | } 70 | } 71 | } 72 | 73 | body.network-members { 74 | .tab-content { 75 | .centered-column(6); 76 | padding-top: @std-padding*2; 77 | @media (max-width: @screen-xs-max) { 78 | padding-left: @std-padding; 79 | padding-right: @std-padding; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/css/post/followers.less: -------------------------------------------------------------------------------- 1 | .followers { 2 | .meta-text(); 3 | margin-bottom: 10px; 4 | 5 | @media (max-width: @screen-xs-max) { 6 | margin-top: 4px; 7 | } 8 | 9 | a, a:hover { 10 | color: @link-color; 11 | } 12 | } 13 | 14 | .add-followers { 15 | .meta-text; 16 | width: 100%; 17 | min-height: 44px; 18 | display: table; 19 | border: 1px solid @border-light; 20 | border-top: 0px; 21 | 22 | .add-label, .add-tags { 23 | display: table-cell; 24 | vertical-align: top; 25 | } 26 | 27 | .add-label { 28 | .body-text; 29 | color: @text-dark; 30 | width: 120px; 31 | background-color: @visible-gray-bg; 32 | text-align: center; 33 | vertical-align: top; 34 | padding-top: 16px; 35 | } 36 | 37 | .add-tags { 38 | padding: 0; 39 | 40 | .tags { 41 | border: none; 42 | box-shadow: none; 43 | } 44 | } 45 | } 46 | 47 | .add-followers-complete { 48 | height: 30px; 49 | margin: 5px 5px 0 0; 50 | 51 | .add-followers-btn { 52 | .rounded-button(); 53 | background-color: initial; 54 | float: right; 55 | background-color: @dark-blue; 56 | color: @white; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/css/post/fulfill.less: -------------------------------------------------------------------------------- 1 | .fulfill-modal { 2 | h4 { 3 | .large-header; 4 | color: @intention-color; 5 | } 6 | 7 | p { 8 | .body-text; 9 | } 10 | 11 | .top-contributor-item { 12 | padding: 3px; 13 | border: 1px solid rgba(255,255,255, 0); 14 | 15 | &.selected { 16 | background-color: #F1F2F2; 17 | border: 1px solid #00EA83; 18 | border-radius: 5px; 19 | } 20 | 21 | .avatar { 22 | height: 30px; 23 | width: 30px; 24 | border-radius: 15px; 25 | } 26 | 27 | div { 28 | .proxima-nova-regular; 29 | font-size: 12px; 30 | vertical-align: middle; 31 | height: 30px; 32 | overflow: hidden; 33 | display: table-cell; 34 | padding-left: 5px; 35 | } 36 | } 37 | 38 | a.add { 39 | margin-top: @std-padding; 40 | .rounded-button-dark(32px, @intention-color); 41 | } 42 | } -------------------------------------------------------------------------------- /src/css/profile/about.less: -------------------------------------------------------------------------------- 1 | /* Styles for the about section, shared across main profile view & edit profile view */ 2 | .profile .about, .editProfile .about { 3 | 4 | section { 5 | margin-bottom: 30px; 6 | 7 | h2 { 8 | .small-header; 9 | line-height: 1.8em; 10 | color: @readable-text; 11 | margin: 0; 12 | 13 | .help { 14 | .meta-text; 15 | } 16 | } 17 | } 18 | 19 | ul { 20 | padding-left: 0; 21 | margin-bottom: @std-padding; 22 | } 23 | 24 | li { 25 | list-style-type: none; 26 | position: relative; 27 | padding: 5px 0; 28 | color: @default-text; 29 | .close { 30 | color: @readable-text; 31 | @media (min-width: @screen-sm) { 32 | position: absolute; 33 | top: 4px; 34 | left: -20px; 35 | } 36 | } 37 | } 38 | 39 | .styled-placeholders(14px); 40 | } 41 | 42 | .profile .about { 43 | section { 44 | ul { 45 | li { 46 | padding: 0; 47 | &, a, a:hover { 48 | .body-text; 49 | } 50 | } 51 | } 52 | .entry { 53 | .body-text; 54 | } 55 | } 56 | } 57 | 58 | .editProfile .about { 59 | 60 | input[type="text"] { 61 | .borderless-text-input; 62 | width: 100%; 63 | margin-bottom: @std-padding; 64 | border-bottom: 1px solid @border-light; 65 | line-height: 28px; 66 | } 67 | 68 | textarea { 69 | .body-text; 70 | @media (max-width: @screen-xs-max) { 71 | min-height: 12vh; 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/css/project/cards.less: -------------------------------------------------------------------------------- 1 | .project-cards { 2 | // all the margin weirdness below 3 | // is so we play nice with Masonry 4 | 5 | margin-left: -@std-padding/2; 6 | margin-right: -@std-padding/2; 7 | 8 | // hide until Masonry is done 9 | opacity: 0; 10 | &.layout-complete { 11 | opacity: 1; 12 | } 13 | 14 | @media (max-width: @screen-xs-max) { 15 | margin-left: -@std-padding/2; 16 | margin-right: -@std-padding/2; 17 | } 18 | 19 | .project-wrapper { 20 | .make-sm-column(6); 21 | padding-left: @std-padding/2; 22 | padding-right: @std-padding/2; 23 | margin-bottom: @std-padding; 24 | 25 | @media (max-width: @screen-xs-max) { 26 | position: static !important; 27 | } 28 | } 29 | } 30 | 31 | .project-card { 32 | border-radius: @border-radius-base; 33 | border: 1px solid @border-light; 34 | cursor: pointer; 35 | 36 | .image { 37 | border-top-left-radius: @border-radius-base; 38 | border-top-right-radius: @border-radius-base; 39 | width: 100%; 40 | .fluid-height(61.8%); 41 | background-size: cover; 42 | background-position: center; 43 | } 44 | 45 | .content { 46 | padding: @std-padding; 47 | } 48 | 49 | h3 { 50 | margin-top: 0; 51 | } 52 | 53 | .avatar { 54 | .user-avatar-sm; 55 | display: inline-block; 56 | vertical-align: middle; 57 | margin-right: 5px; 58 | } 59 | 60 | .contributors { 61 | float: right; 62 | } 63 | 64 | .draft-label { 65 | margin-left: @std-padding*2; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/css/project/edit.less: -------------------------------------------------------------------------------- 1 | .newProject, .editProject { 2 | margin-bottom: @std-padding*4; 3 | 4 | .simple-page; 5 | .settings-page; 6 | 7 | .main-content { 8 | .has-rounded-dropdowns; 9 | } 10 | 11 | .all-sections > div { 12 | padding-top: @std-padding; 13 | padding-bottom: @std-padding; 14 | } 15 | 16 | .button { 17 | .rounded-button; 18 | &.save { 19 | .rounded-button-dark; 20 | } 21 | } 22 | 23 | .header { 24 | p { 25 | font-style: italic; 26 | } 27 | .save { 28 | float: right; 29 | margin-left: @std-padding; 30 | } 31 | } 32 | 33 | .footer { 34 | text-align: right; 35 | } 36 | 37 | .details { 38 | height: 12em; 39 | overflow-y: auto; 40 | } 41 | 42 | .add-media input { 43 | width: 40%; 44 | display: inline-block; 45 | } 46 | 47 | .image-preview { 48 | margin-top: @std-padding; 49 | } 50 | 51 | .counter { 52 | float: right; 53 | .meta-text; 54 | } 55 | 56 | .visibility { 57 | .btn-group { 58 | margin-bottom: 5px; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/css/search.less: -------------------------------------------------------------------------------- 1 | body.search { 2 | .simple-page(8); 3 | 4 | .search-input { 5 | .proxima-nova-light; 6 | .borderless-text-input; 7 | padding: 0; 8 | font-size: 40px; 9 | width: 100%; 10 | margin-bottom: @std-padding; 11 | @media (min-width: @screen-sm) { 12 | margin-top: @std-padding; 13 | } 14 | } 15 | 16 | h3 { 17 | .section-title; 18 | padding-bottom: @std-padding; 19 | margin-bottom: @std-padding; 20 | border-bottom: 1px solid @border-lightest; 21 | 22 | @media (max-width: @screen-xs-max) { 23 | padding-left: @std-padding; 24 | padding-right: @std-padding; 25 | } 26 | } 27 | 28 | .people { 29 | .make-sm-column(4); 30 | .make-sm-column-push(8); 31 | 32 | .avatar { 33 | .user-avatar-sm; 34 | float: left; 35 | margin-right: @std-padding; 36 | } 37 | .info { 38 | overflow: hidden; 39 | } 40 | .name { 41 | .title-text; 42 | } 43 | .blurb { 44 | .meta-text; 45 | } 46 | 47 | } 48 | 49 | .post-results { 50 | .make-sm-column(8); 51 | .make-sm-column-pull(4); 52 | } 53 | 54 | .people, .post-results { 55 | @media (max-width: @screen-xs-max) { 56 | padding: 0; 57 | } 58 | } 59 | 60 | .person { 61 | margin-bottom: @std-padding; 62 | 63 | @media (max-width: @screen-xs-max) { 64 | padding: 0 15px; 65 | } 66 | } 67 | 68 | em.highlight { 69 | font-weight: bold; 70 | font-style: normal; 71 | } 72 | 73 | .load-more { 74 | display: block; 75 | text-align: center; 76 | margin-bottom: @std-padding * 2; 77 | } 78 | 79 | .no-results { 80 | @media (max-width: @screen-xs-max) { 81 | padding-left: @std-padding; 82 | padding-right: @std-padding; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/css/shared/colors.less: -------------------------------------------------------------------------------- 1 | // New Colors 2 | @default-border: #d9d9d9; 3 | @primary-border: #8c8c8c; 4 | @button-border: @light-gray; 5 | 6 | @secondary-text: #4d4d4d; 7 | @inactive-text: #bfbfbf; 8 | @readable-text: @default-text; 9 | @detail-text: #a6a6a6; 10 | @white: #fff; 11 | @light-gray: #B3B3B3; 12 | 13 | //Old Colors 14 | @hylo-teal: #4FB19F; 15 | @hylo-orange: #E79336; 16 | @hylo-green: #52B065; 17 | @dark-gray: #444; 18 | @visible-gray-bg: #E6E6E6; 19 | @lighter-gray: #D7D6D6; 20 | @faint-gray: #f6f6f6; 21 | @text-dark: #333; 22 | @text-medium-dark: #5b5b5b; 23 | @text-gray: #808080; 24 | @text-light-gray: #999; 25 | @dark-blue: #388ECB; 26 | 27 | @link-color: @dark-blue; 28 | @default-text: @text-dark; 29 | @intention-color: @hylo-teal; 30 | @offer-color: @hylo-green; 31 | @request-color: @hylo-orange; 32 | @error-color: @alert-danger-text; // from bootstrap 33 | 34 | @border-lightest: #E0E0E0; 35 | @border-light: #ccc; 36 | @border-medium: @text-light-gray; 37 | @border-dark: @text-gray; 38 | 39 | @dark-accent: @visible-gray-bg; 40 | 41 | // these are overrides to variables defined in bootstrap/variables.less. 42 | // changes will take effect even if declared after they are used 43 | @brand-success: @hylo-teal; 44 | @brand-info: @faint-gray; 45 | @btn-info-color: @text-gray; 46 | -------------------------------------------------------------------------------- /src/css/shared/dropdown.less: -------------------------------------------------------------------------------- 1 | .dropdown-menu { 2 | li { 3 | min-width: initial; 4 | 5 | &.active { 6 | .dropdown-list-user { 7 | .name { 8 | color: @white; 9 | } 10 | } 11 | } 12 | } 13 | 14 | border: 1px solid @border-light; 15 | 16 | &[responsive-dropdown-menu] { 17 | a { 18 | overflow: hidden; 19 | text-overflow: ellipsis; 20 | } 21 | } 22 | } 23 | 24 | a.dropdown-toggle { 25 | cursor: pointer; 26 | } 27 | 28 | .dropdown-list-user { 29 | padding: 5px 15px !important; 30 | 31 | .avatar { 32 | .user-avatar-sm; 33 | display: inline-block; 34 | vertical-align: middle; 35 | } 36 | .name { 37 | .body-text; 38 | margin-left: 6px; 39 | line-height: 33px; 40 | display: inline-block; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/css/shared/social-media.less: -------------------------------------------------------------------------------- 1 | .user-links { 2 | display: inline-block; 3 | position: relative; 4 | top: -1px; 5 | @media (max-width: @screen-xs-max) { 6 | display: block; 7 | } 8 | } 9 | 10 | .social-media { 11 | display: inline-block; 12 | 13 | @media (max-width: @screen-xs-max) { 14 | padding-top: 6px; 15 | } 16 | 17 | > a, > i { 18 | display: inline-block; 19 | position: relative; 20 | font-size: @social-media-icon-size; 21 | color: @white; 22 | text-decoration: none; 23 | text-align: center; 24 | width: 24px; 25 | margin: 0 6px; 26 | 27 | @media (max-width: @screen-xs-max) { 28 | } 29 | } 30 | 31 | .send-message { 32 | margin-left: 13px; 33 | &.lone { 34 | margin-left: 6px; 35 | } 36 | i { 37 | position: relative; 38 | top: 4px; 39 | } 40 | } 41 | 42 | .status { 43 | position: absolute; 44 | display: block; 45 | top: -27px; 46 | left: 2px; 47 | font-size: 10px; 48 | border-radius: 11px; 49 | border: 1px solid @border-lightest; 50 | color: @text-medium-dark; 51 | background-color: @white; 52 | width: 21px; 53 | height: 21px; 54 | line-height: 21px; 55 | text-align: center; 56 | &.icon-big-check { 57 | color: @hylo-teal; 58 | } 59 | } 60 | } 61 | 62 | .community-members { 63 | .social-media { 64 | padding-bottom: 4px; 65 | 66 | > a { 67 | margin-left: 0; 68 | margin-right: 12px; 69 | text-align: left; 70 | font-size: 16px; 71 | 72 | &.send-message { 73 | margin-left: 7px; 74 | top: -1px; 75 | 76 | &.lone { 77 | margin-left: 0; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/css/shared/tags.less: -------------------------------------------------------------------------------- 1 | tags-input { 2 | 3 | .tags { 4 | .form-control; 5 | height: auto; 6 | padding: 3px; 7 | 8 | .tag-item, .input { 9 | line-height: @avatar-size-xs + 4px; 10 | height: @avatar-size-xs + 8px; 11 | } 12 | 13 | .tag-item { 14 | background: white !important; 15 | 16 | i, span { 17 | vertical-align: middle; 18 | } 19 | 20 | .avatar { 21 | .user-avatar-xs; 22 | display: inline-block; 23 | vertical-align: middle; 24 | } 25 | 26 | .name { 27 | .body-text; 28 | } 29 | 30 | &.selected { 31 | background: @link-color !important; 32 | .name { 33 | color: @white; 34 | } 35 | } 36 | } 37 | } 38 | 39 | .suggestion-item { 40 | .avatar { 41 | .user-avatar-xs; 42 | display: inline-block; 43 | vertical-align: middle; 44 | } 45 | 46 | .name { 47 | .body-text; 48 | margin-left: 6px; 49 | line-height: 33px; 50 | display: inline-block; 51 | } 52 | 53 | &.selected .name { 54 | color: @white; 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/css/shared/toolbar.less: -------------------------------------------------------------------------------- 1 | // it's entirely possible that this could be redundant with bootstrap's 2 | // built-in navbar component -- but i needed something simple and didn't 3 | // want to have to deal with potential edge cases arising from the bootstrap 4 | // navbar's extra functionality 5 | // 6 | .toolbar { 7 | .button-text; 8 | text-align: center; 9 | padding: @std-padding 0; 10 | border-bottom: 1px solid @border-light; 11 | min-height: 64px; 12 | position: relative; 13 | 14 | @media (max-width: @screen-xs-max) { 15 | padding: 5px 0; 16 | min-height: 44px; 17 | } 18 | 19 | .toolbar-left { 20 | float: left; 21 | left: 0; 22 | text-align: left; 23 | } 24 | .toolbar-right { 25 | float: right; 26 | text-align: left; 27 | } 28 | p { 29 | line-height: @default-button-height; 30 | } 31 | label { 32 | font-weight: normal; 33 | input { 34 | margin-right: 2px; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/css/subscribe.less: -------------------------------------------------------------------------------- 1 | @import 'bower_components/bootstrap/less/bootstrap'; 2 | @import 'shared/colors'; 3 | @import 'mixins'; 4 | 5 | @std-padding: floor(@grid-gutter-width / 2); 6 | 7 | body { 8 | background: url(img/homepage/coworkingBg-faint.jpg); 9 | background-position: center; 10 | background-size: cover; 11 | min-height: 100vh; 12 | 13 | padding-top: 10vh; 14 | .body-text; 15 | } 16 | 17 | div.success { 18 | border-radius: @border-radius-base; 19 | padding: @std-padding; 20 | margin-top: @std-padding; 21 | margin-bottom: @std-padding; 22 | background-color: @state-success-bg; 23 | color: @alert-success-text; 24 | } 25 | -------------------------------------------------------------------------------- /src/css/support.less: -------------------------------------------------------------------------------- 1 | .faqs-page { 2 | .section { 3 | .make-sm-column(8); 4 | .make-sm-column-push(2); 5 | .body-text; 6 | color: @default-text; 7 | 8 | h3 { 9 | font-size: 20px; 10 | line-height: 2; 11 | margin-top: @std-padding; 12 | font-weight: 500; 13 | } 14 | } 15 | } 16 | 17 | .support { 18 | #nav { 19 | padding-bottom: 30px; 20 | } 21 | 22 | .support-content { 23 | .make-sm-column(6); 24 | .make-sm-column-push(3); 25 | padding-bottom: @std-padding*3; 26 | .body-text; 27 | color: @default-text; 28 | 29 | h1 { 30 | .section-title; 31 | } 32 | h2 { 33 | .medium-header; 34 | margin-top: @std-padding * 3; 35 | } 36 | h3 { 37 | .small-header; 38 | margin-top: @std-padding * 2; 39 | } 40 | } 41 | 42 | .community-icon { 43 | display: inline-block; 44 | height: 24px; 45 | width: 24px; 46 | vertical-align: middle; 47 | svg { 48 | width: 100%; 49 | height: 100%; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/css/user/home.less: -------------------------------------------------------------------------------- 1 | body.home { 2 | .tabs { 3 | .make-row; 4 | border-bottom: 1px solid @border-light; 5 | line-height: 53px; 6 | 7 | > a { 8 | display: block; 9 | .make-xs-column(3); 10 | .make-md-column(2); 11 | .strong-title-text; 12 | text-align: center; 13 | color: @text-light-gray; 14 | 15 | &:first-child { 16 | .make-md-column-offset(2); 17 | } 18 | 19 | .triangle { 20 | display: none; 21 | } 22 | 23 | &, &:hover { 24 | text-decoration: none; 25 | } 26 | 27 | @media (max-width: @screen-xs-max) { 28 | .small-title-text; 29 | padding: @std-padding 0; 30 | line-height: initial; 31 | padding-left: 0; 32 | padding-right: 0; 33 | } 34 | 35 | &.active { 36 | position: relative; 37 | color: @default-text; 38 | 39 | .has-bottom-triangle; 40 | } 41 | } 42 | } 43 | 44 | .tab-content-wrapper { 45 | .simple-page; 46 | position: relative; 47 | padding-top: @std-padding * 2; 48 | } 49 | 50 | .add-new { 51 | position: relative; 52 | top: -@std-padding; 53 | } 54 | 55 | .post, .welcome-post { 56 | @media (max-width: @screen-xs-max) { 57 | margin-left: -@std-padding; 58 | margin-right: -@std-padding; 59 | } 60 | } 61 | } 62 | 63 | body.home-allPosts { 64 | .tab-content-wrapper { 65 | padding-top: 0; 66 | } 67 | 68 | .add-new { 69 | top: 0; 70 | } 71 | 72 | .posts-toolbar { 73 | margin-bottom: @std-padding; 74 | @media (max-width: @screen-xs-max) { 75 | margin-bottom: 0; 76 | } 77 | } 78 | } 79 | 80 | .home-myPosts { 81 | .tab-content { 82 | .post { 83 | .post-without-avatar; 84 | } 85 | } 86 | } 87 | 88 | body.home-simple { 89 | .tab-content-wrapper { 90 | .make-sm-column(6); 91 | .make-sm-column-offset(3); 92 | padding-top: 0; 93 | } 94 | 95 | .add-new { 96 | display: none; 97 | } 98 | 99 | textarea { 100 | margin-bottom: 10px; 101 | min-height: 8em; 102 | } 103 | 104 | .button { 105 | .rounded-button; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/css/user/notifications.less: -------------------------------------------------------------------------------- 1 | .notifications { 2 | .simple-page; 3 | 4 | .buttons { 5 | @media (min-width: @screen-sm) { 6 | margin-top: @std-padding; 7 | text-align: right; 8 | width: 100%; 9 | } 10 | a { 11 | .rounded-button; 12 | } 13 | margin-bottom: @std-padding; 14 | } 15 | 16 | .notification { 17 | .make-row; 18 | margin-bottom: @std-padding; 19 | padding: @std-padding; 20 | border-bottom: 1px solid @border-lightest; 21 | 22 | &.unread { 23 | background-color: fadeout(@link-color, 85%); 24 | } 25 | 26 | .avatar { 27 | .user-avatar-sm; 28 | float: left; 29 | margin-right: @std-padding; 30 | } 31 | 32 | .content { 33 | overflow: hidden; 34 | } 35 | 36 | .title { 37 | overflow: hidden; 38 | padding: 7px 0 @std-padding; 39 | } 40 | 41 | .body-text { 42 | .body-text; 43 | padding: 0 @std-padding 5px; 44 | } 45 | 46 | .controls { 47 | color: @text-gray; 48 | .meta-text; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/css/user/settings.less: -------------------------------------------------------------------------------- 1 | .userSettings { 2 | .simple-page; 3 | .settings-page; 4 | 5 | .change-password { 6 | .rounded-button; 7 | } 8 | 9 | .email { 10 | .buttons { 11 | @media (min-width: @screen-sm) { 12 | text-align: right; 13 | } 14 | margin-top: 5px; 15 | p { 16 | margin-bottom: 5px; 17 | } 18 | } 19 | } 20 | 21 | .communities { 22 | a { 23 | font-size: 15px; 24 | } 25 | } 26 | 27 | select { 28 | font-size: 16px; 29 | } 30 | 31 | @media (max-width: @screen-xs-max) { 32 | .communities label { 33 | margin-bottom: 0; 34 | } 35 | .setting-item .summary { 36 | margin-bottom: 5px; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/html/admin/communities.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | Sort by 3 | 4 | 5 | 6 | 7 | 8 |
9 | Sort 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 | {{community.name}}
19 | {{community.beta_access_code}}
20 | {{community.slug}}
21 | ID: {{community.id}}
22 | {{community.memberships}}
23 | {{community.created_at | shortFromNow}} old 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /src/html/admin/metrics.tpl.html: -------------------------------------------------------------------------------- 1 |

New users

2 | 3 | 4 |

New posts

5 | 6 | 7 |

New comments

8 | 9 | 10 |

New user activity

11 | 12 | -------------------------------------------------------------------------------- /src/html/pages/_clientEnv.ejs: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/html/pages/_favicon.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var path; 3 | switch (environment) { 4 | case 'production': 5 | path = 'favicon'; break; 6 | case 'staging': 7 | path = 'faviconStaging'; break; 8 | default: 9 | path = 'faviconDev'; 10 | } 11 | %> 12 | 13 | -------------------------------------------------------------------------------- /src/html/pages/_footer.ejs: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 16 | 17 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/html/pages/_header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <%- include('_newrelic') %> 15 | <%- include('_clientEnv') %> 16 | <%- include('_segment') %> 17 | <%- include('_favicon') %> 18 | 19 | 20 | 21 | 22 | 23 | Hylo 24 | 25 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/html/pages/_menu.ejs: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /src/html/pages/_segment.ejs: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/html/pages/_twitter.ejs: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/html/pages/about/_header.ejs: -------------------------------------------------------------------------------- 1 | <% global.bodyClass = 'about-page'; %> 2 | <%- include('../_header'); %> 3 | <%- include('../_menu'); %> 4 | -------------------------------------------------------------------------------- /src/html/pages/about/_linksBar.ejs: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /src/html/pages/about/contact/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../_header'); %> 2 | 3 | 4 | 5 | 6 | 33 | 34 |
35 |

Contact Us.

36 |
37 | 38 | <%- include('../_linksBar'); %> 39 | 40 | 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 | 50 |
51 |

Contact Us

52 |
53 | 54 |
55 |

56 | Hylo Inc. 57 |
58 | 2323 Broadway 59 |
60 | Oakland, CA 94612 61 |
62 | hello@hylo.com 63 |
64 | +1 415 225 7335 65 |

66 |
67 |
68 |
69 | 70 | <%- include('../../_footer'); %> -------------------------------------------------------------------------------- /src/html/pages/admin/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%- include('../_favicon') %> 10 | 11 | 12 | 13 | Hylo Admin 14 | 15 | 16 | 17 |
18 |

Hylo Admin UI

19 | 20 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/html/pages/app/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%- environment == 'development' ? '' : include('../_newrelic') %> 14 | <%- include('../_clientEnv') %> 15 | <%- include('../_rollbar') %> 16 | <%- include('../_segment') %> 17 | <%- include('../_favicon') %> 18 | <%- include('../_twitter') %> 19 | 20 | 21 | 22 | Hylo 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/html/pages/help/index.ejs: -------------------------------------------------------------------------------- 1 | <% global.bodyClass = 'faqs-page'; %> 2 | <%- include('../_header'); %> 3 | <%- include('../_menu'); %> 4 | 5 |
6 |
7 |
8 | <%- include('_whatIsHylo'); %> 9 |
10 |
11 |
12 | 13 | 14 | <%- include('../_footer'); %> -------------------------------------------------------------------------------- /src/html/pages/help/markdown/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 82 |
14 |
15 | What is markdown?
16 | =================
17 | 
18 | 
19 | Markdown is a way of writing plain text (left),
20 | so that it can be converted to rich text (right).
21 | 
22 | This page is written in markdown to show how it works.
23 | 
24 | 
25 | Headings: just underline with equals
26 | ====================================
27 | 
28 | Or underline with dash
29 | ----------------------
30 | 
31 | 
32 | Paragraphs
33 | --------
34 | 
35 | 
36 | You need
37 | to put a
38 | blank line
39 | in between lines.
40 | 
41 | Or it will end up as one big paragraph!
42 | 
43 | 
44 | Emphasis
45 | --------
46 | 
47 | 
48 | You can italicize a word with _underscore_ or *star*.
49 | 
50 | __Bold__ a word by doubling the **emphasis**.
51 | 
52 | 
53 | Lists
54 | -----
55 | 
56 | 
57 | Here's how to make a list:
58 | 
59 | * leave a blank line before your list
60 | * use stars (*)
61 | * leave a space after the star
62 | 
63 | 
64 | Links and Images
65 | ----------------
66 | 
67 | 
68 | Repeat out loud:
69 | 
70 | Square brackets for name then round brackets for URL.
71 | 
72 | [Hylo Homepage](https://www.hylo.com/)
73 | 
74 | Images are the same, but prefixed with an exclamation mark.
75 | 
76 | ![Hylo logo](https://hylo-app.s3.amazonaws.com/misc/hylo-logo-teal-on-transparent.png)
77 | 
78 | Thanks to our friends at [Loomio](https://www.loomio.org/)!
79 | 
80 | 
81 |
83 |
84 | 85 | 86 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/html/pages/newapp/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 |

New Hylo App Available!

13 |

14 | We just released a new version of the Hylo site, and a new version of the iOS app to go with it. 15 |

16 |

17 | Please click here to update Hylo in the App Store. 18 |

19 |

20 | If you have any questions, you can reach us at hello@hylo.com. We'd love to hear from you. 21 |

22 | 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/html/pages/subscribe/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include('../_clientEnv') %> 8 | <%- include('../_segment') %> 9 | <%- include('../_favicon') %> 10 | 11 | 12 | 13 | 14 | 17 | Hylo 18 | 19 | 20 | 21 |
22 |
23 |

Subscribe to Hylo

24 |

Click the button below to enter your billing details.

25 | 26 | 27 | 28 |
29 | Your subscription was successfully created! If you have any questions, please email hello@hylo.com. 30 |
31 | 32 |

33 |

Return to Hylo

34 |
35 |
36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/html/ui-partials/README: -------------------------------------------------------------------------------- 1 | This folder is for partials intended to be inserted into Angular templates via 2 | EJS. Using EJS performs better, since it happens once during the build step, 3 | rather than at runtime in the browser. -------------------------------------------------------------------------------- /src/html/ui-partials/announcer.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{text}} 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/html/ui/app/404.tpl.ejs: -------------------------------------------------------------------------------- 1 |
2 |

404 Not Found

3 | 4 |

5 | Sorry, we can't find the page you're looking for. 6 | Start over? 7 |

8 | 9 |
10 | 11 | 12 |

Short for hylozoism (from the Greek hylē, “matter” and zōē, “life”):

13 |

The philosophical doctrine holding that all matter (including the universe as a whole) is alive and conscious.

14 |
15 | 16 |
-------------------------------------------------------------------------------- /src/html/ui/app/fulfillModal.tpl.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/html/ui/app/network.tpl.html: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /src/html/ui/app/search.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | 8 | 9 | 10 |
11 |
12 |

People

13 | 14 |
15 | 16 |
17 | 18 |

19 |
20 |
21 | 22 | Load {{pageSize('people')}} more (out of {{remaining('people')}}) 23 | 24 | 25 |
26 | No people matched your search. 27 |
28 | 29 |
30 | 31 |
32 |

Posts

33 | 34 |
35 | 36 |
37 | 38 | Show {{pageSize('posts')}} more (out of {{remaining('posts')}}) 39 | 40 | 41 |
42 | No posts matched your search. 43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | -------------------------------------------------------------------------------- /src/html/ui/app/socialMedia.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/html/ui/app/typeahead-tag-user.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /src/html/ui/community/about.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |   7 | {{community.location}} 8 |
9 | 10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /src/html/ui/community/base.tpl.html: -------------------------------------------------------------------------------- 1 | 48 | 49 |
50 | -------------------------------------------------------------------------------- /src/html/ui/community/events.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 9 |

10 |
11 |
12 |
13 | 14 |
15 | 16 |
17 | No events yet. You can create one by going to the Posts tab. 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/html/ui/community/join.tpl.html: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 | {{ code }} is not the right code. Try again. 19 |
20 | -------------------------------------------------------------------------------- /src/html/ui/community/members.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{userCount}} members

5 |
6 | 7 | 8 | Search 9 | 10 |
11 |
12 | 13 |
18 | 19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /src/html/ui/community/posts.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | Make your first post! 6 |
7 | 8 | Learn more 9 | 10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 |

18 | Loading... 19 | There are no posts to display. 20 |

21 |
22 | -------------------------------------------------------------------------------- /src/html/ui/community/projects.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |

No projects yet. Be the first to start one!

8 |
9 | -------------------------------------------------------------------------------- /src/html/ui/entrance/base.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
-------------------------------------------------------------------------------- /src/html/ui/entrance/forgot-password.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Set your password

3 | 4 |
{{error}}
5 |
{{success}}
6 | 7 |
8 | 9 | 10 |
11 | 18 |

Please enter a valid email address.

19 |

Please fill in this field.

20 |
21 | 22 |
23 | Submit 24 |
25 |
26 | 27 | 34 | 35 |
36 | -------------------------------------------------------------------------------- /src/html/ui/entrance/login.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Log in to Hylo

4 | 5 |
6 | 7 |

Log in with one of these services:

8 | 9 |
10 | Google 11 | 12 | LinkedIn 13 |
14 | 15 |
16 | 17 | 18 |
19 | 26 |

Please enter a valid email address.

27 |

Please fill in this field.

28 |
29 | 30 |
31 | 38 |

Please fill in this field.

39 |
40 | 41 |
42 | Log in 43 |
44 |
45 | 46 | 53 |
54 | -------------------------------------------------------------------------------- /src/html/ui/entrance/presignup.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Sign up for Hylo

3 | 4 |

We are currently in private beta with a number of pilot communities.

5 |

I have an invitation code

6 |

I don't have an invitation code

7 |
8 | 9 | -------------------------------------------------------------------------------- /src/html/ui/entrance/waitlist.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Contact us

3 | 4 |
{{success}}
5 | 6 |

Thanks for your interest in Hylo!

7 | 8 |

If you are a leader, manager, or member of a community that could benefit from using Hylo, please leave your information in the form below and we'll get back to you soon.

9 | 10 |
11 |
12 |
13 | 14 |

Please fill in this field.

15 |
16 | 17 |
18 | 19 |

Please fill in this field.

20 |

Please enter a valid email address.

21 |
22 |
23 | 24 |
25 | 26 |

Please fill in this field.

27 |
28 | 29 |
30 | Submit 31 |
32 |
33 | 34 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /src/html/ui/home/all-posts.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
7 |

Your communities are completely devoid of posts! You could be the first to unite people around a common vision. Just click the button above to start.

8 |
9 |
10 | -------------------------------------------------------------------------------- /src/html/ui/home/following-posts.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | You aren't following any posts yet. 6 | Click FOLLOW on any post to receive notifications when it is updated. 7 |

8 |
9 |
10 | -------------------------------------------------------------------------------- /src/html/ui/home/my-posts.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |

You haven't posted anything yet. Why not right now? There's no time like the present! Just click the button above to start.

6 |
7 |
8 | -------------------------------------------------------------------------------- /src/html/ui/home/projects.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /src/html/ui/home/show.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | All posts 4 | 5 | 6 | My posts 7 | 8 | 9 | Follow­ing 10 | 11 | 12 | My pro­jects 13 | 14 |
15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/html/ui/home/simple.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Join a community

4 | 5 | 6 | 7 |
8 |

Projects

9 | 10 |
11 | 12 |

Create your own community

13 | 14 | Get started 15 | -------------------------------------------------------------------------------- /src/html/ui/network/about.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /src/html/ui/network/communities.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 | 21 |
{{community.name}}
22 | {{community.memberCount}} member{{community.memberCount == 1 ? '' : 's'}} 23 |
24 |
25 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /src/html/ui/network/members.tpl.html: -------------------------------------------------------------------------------- 1 |
6 | 7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/html/ui/network/posts.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /src/html/ui/onboarding/community-modal.tpl.html: -------------------------------------------------------------------------------- 1 |

Welcome!

2 | 3 |
4 |
5 |

Let's get started! Tell us a little about yourself.

6 |

What groups or organizations are you a part of?

7 | 8 |

Type names, separated by commas or line breaks. This information will be searchable and also shown on your profile, helping other members discover opportunities to collaborate with you.

9 |
10 |
11 | Next 12 |
13 |
14 | 15 |
16 |
17 |

Thanks!

18 |

Now, are there any skills, passions, or hobbies you'd like to be known for in your community?

19 | 20 |

Type words or short phrases, separated by commas or line breaks. This information will be searchable and also shown on your profile, helping other members discover opportunities to collaborate with you.

21 |
22 |
23 | Back 24 | Next 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/html/ui/onboarding/profile-modal.tpl.html: -------------------------------------------------------------------------------- 1 |

This is your profile page.

2 | 3 |
4 |

You can see everything you've posted here as well. Other members of this community can view this page to learn more about you.

5 | 6 |

You can customize your personal information, your profile picture, and more. Click the "Edit Profile" button in the upper right.

7 | 8 |
9 | 10 |
Got it!
11 | -------------------------------------------------------------------------------- /src/html/ui/onboarding/seeds.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

We share our skills, resources, and ideas on Hylo by
"planting seeds".

4 | 5 |
6 |

Seeds are visible to everyone in the community. There are three kinds of seeds:

7 | 8 |

9 | 10 | Offer 11 |

12 |

Anything you have that you want to share. It could be any skill, item, information, or opportunity, from your professional or personal life.

13 | 14 |
    15 |
  • "I'd like to offer a class in nonviolent communication."
  • 16 |
  • "I'd like to share my graphic design skills."
  • 17 |
  • "I'd like to share free tickets to tonight's game."
  • 18 |
19 | 20 |

21 | 22 | Request 23 |

24 |

Something you're looking for--again, any skill, item, information, or opportunity, whether personal or professional.

25 | 26 |
    27 |
  • "I'm looking for a good accountant to help me with my taxes."
  • 28 |
  • "I'm looking for a jigsaw that I can borrow for a weekend."
  • 29 |
  • "I'm looking for feedback on my latest blog post."
  • 30 |
31 | 32 |

33 | 34 | Intention 35 |

36 |

A vision of something you'd like to create, with the support of your community.

37 | 38 |
    39 |
  • "I'd like to create a community garden."
  • 40 |
  • "I'd like to start a Toastmasters group."
  • 41 |
  • "I'd like to create a children's book about endangered jungle wildlife."
  • 42 |
43 |
44 | 45 | 48 | Back 49 |
50 |
51 | -------------------------------------------------------------------------------- /src/html/ui/onboarding/start.tpl.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

Welcome to {{onboarding.community.name}}

6 | 7 | 8 |
9 | 10 |
11 |

Discover the people, skills, and resources all around you with Hylo. Together, we can create anything we can imagine.

12 |
13 | 14 |
15 |

Thank you for joining {{onboarding.community.name}} on Hylo.

16 |
17 |
18 |
{{onboarding.community.leader.name}}
19 |
20 |

21 |
22 | 23 |
24 | Continue 25 |
26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /src/html/ui/post/edit-page.tpl.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/html/ui/post/infinite-event-scroll.tpl.html: -------------------------------------------------------------------------------- 1 |
5 | 6 |
7 |
8 | {{header}} 9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/html/ui/post/infinite-scroll.tpl.html: -------------------------------------------------------------------------------- 1 |
5 |
6 | 7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /src/html/ui/post/list.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | You haven't made any posts yet -- go to your community to do so! 5 |

6 |

7 | {{user.firstName()}} has not posted in any of your communities yet. 8 |

9 |
10 | -------------------------------------------------------------------------------- /src/html/ui/post/show.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/html/ui/post/toolbar.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | {{selected.sort.label}} 7 | 8 | 9 | 14 |
15 | 16 |
17 | 18 | {{selected.filter.label}} 19 | 20 | 21 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /src/html/ui/post/welcome.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 14 | 15 |
16 | You 17 | 18 | {{user.name}} 19 | 20 | joined 21 | {{post.communities[0].name}} 22 | a community that is no longer active 23 | {{post.created_at | fromNow}}. 24 | Say hello! 25 |
26 |
27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /src/html/ui/profile/base.tpl.html: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 | 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /src/html/ui/profile/contributions.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Contributed to 4 | a 5 | an 6 | {{contribution.post.type}} 7 | by {{contribution.post.user.name}}
8 |
{{contribution.post.name}}
9 |
10 |
{{user.firstName()}} has not made any contributions in your communities yet.
11 | -------------------------------------------------------------------------------- /src/html/ui/profile/thanks.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
Thanked in {{thank.comment.post.user.name}}'s {{thank.comment.post.type}}
3 |
{{thank.comment.post.name}}
4 |
5 |
{{user.firstName()}} has not been thanked in your communities yet.
6 | -------------------------------------------------------------------------------- /src/html/ui/project/invite.tpl.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |
12 |
    13 |
  • {{error}}
  • 14 |
15 |
16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | The message will also contain a link to the project. 24 |
25 | 26 | 27 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /src/html/ui/project/posts.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
7 | 8 |
9 | 10 |
No requests or offers yet.
11 | -------------------------------------------------------------------------------- /src/html/ui/project/settings.tpl.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/html/ui/project/users.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | Invite friends and community members to help out. 3 | 4 | 5 | Invite contributors 6 | 7 |
8 | 9 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /src/html/ui/shared/confirm.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 8 | -------------------------------------------------------------------------------- /src/html/ui/shared/join-community.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |

Enter the code that was given to you by your community manager.

3 | 4 |
5 | 6 | 7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/html/ui/shared/main.tpl.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../../ui-partials/announcer') %> 2 | <%- include('../../ui-partials/nav') %> 3 |
4 | -------------------------------------------------------------------------------- /src/html/ui/shared/ngTagsInput/autocomplete-user.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /src/html/ui/shared/ngTagsInput/tag-user.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | {{$getDisplayText()}} 3 | 4 | -------------------------------------------------------------------------------- /src/html/ui/shared/people-mentions.tpl.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/html/ui/shared/project-cards.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
8 |
9 |

{{project.title}}

10 |

{{project.intention}}

11 |

12 | 13 | {{project.user.name}} 14 |

15 | 16 | {{project.contributor_count}} 17 | contributor{{project.contributor_count == 1 ? '' : 's'}} 18 | 19 | Draft 20 | 21 | 22 | 23 | {{project.open_request_count}} 24 | request{{project.open_request_count == 1 ? '' : 's'}} 25 | 26 |
27 |
28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/html/ui/shared/typeaheadUser.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /src/html/ui/shared/user-list-item.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | moderator 4 |
5 | 6 | 7 | 16 | 17 | 18 | {{::user.name}} 19 | 22 |
23 |
{{::user.bio}}
24 |
25 |
26 |
Skills:
27 | 32 |
33 |
34 |
Groups:
35 | 40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /src/html/ui/user/notifications.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | 24 | 25 |
26 |
27 |
28 | 29 |
30 | {{::event.created_at | fromNow}} 31 | 32 |  •  33 | 34 | Say "Thank you" 35 | 36 | 37 | 38 | You thanked {{::event.actor.name | firstName}} 39 | 40 | 41 |  •  42 | Reply 43 | 44 |
45 |
46 | 47 |
48 | 49 |
50 | Activity on posts you follow, as well as comments or posts that mention you by name, will appear here. 51 |
52 | 53 |
54 |
55 | -------------------------------------------------------------------------------- /src/html/ui/user/use-invitation.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Hmm.

4 | 5 |
6 | Please wait... 7 |
8 | 9 |
10 | The invitation link you used is not valid. Please contact us at support@hylo.com for assistance. 11 |
12 | 13 |
14 |

That invitation link has already been used.

15 |

Would you like to log in?

16 |

Please contact us at support@hylo.com if you need assistance.

17 |
18 | 19 |
20 |

You're already a member of that community.

21 |

Continue

22 |
23 | 24 |
25 | 26 | 27 |

Short for hylozoism (from the Greek hylē, “matter” and zōē, “life”):

28 |

The philosophical doctrine holding that all matter (including the universe as a whole) is alive and conscious.

29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /src/img/about/careerBanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/careerBanner.jpg -------------------------------------------------------------------------------- /src/img/about/companyBanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/companyBanner.jpg -------------------------------------------------------------------------------- /src/img/about/contactBanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/contactBanner.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/ArtBrock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/ArtBrock.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/DanielGoldman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/DanielGoldman.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/DavidHodgson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/DavidHodgson.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/DavidMartin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/DavidMartin.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/Edward.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/Edward.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/EricBerlow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/EricBerlow.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/FeranandaIbarra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/FeranandaIbarra.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/JohnKatovich.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/JohnKatovich.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/Lawrence.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/Lawrence.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/MihaelaUlieru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/MihaelaUlieru.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/Minda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/Minda.jpg -------------------------------------------------------------------------------- /src/img/about/headshots/Ray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/headshots/Ray.jpg -------------------------------------------------------------------------------- /src/img/about/teamBanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/about/teamBanner.jpg -------------------------------------------------------------------------------- /src/img/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/appicon.png -------------------------------------------------------------------------------- /src/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/facebook.png -------------------------------------------------------------------------------- /src/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/favicon.png -------------------------------------------------------------------------------- /src/img/faviconDev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/faviconDev.png -------------------------------------------------------------------------------- /src/img/faviconStaging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/faviconStaging.png -------------------------------------------------------------------------------- /src/img/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/google.png -------------------------------------------------------------------------------- /src/img/homepage/birdsBg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/birdsBg.png -------------------------------------------------------------------------------- /src/img/homepage/bolt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/bolt.png -------------------------------------------------------------------------------- /src/img/homepage/coworkingBg-faint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/coworkingBg-faint.jpg -------------------------------------------------------------------------------- /src/img/homepage/coworkingBg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/coworkingBg.jpg -------------------------------------------------------------------------------- /src/img/homepage/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/graph.png -------------------------------------------------------------------------------- /src/img/homepage/hexagon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/hexagon.png -------------------------------------------------------------------------------- /src/img/homepage/magGlass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/magGlass.png -------------------------------------------------------------------------------- /src/img/homepage/network.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/network.jpg -------------------------------------------------------------------------------- /src/img/homepage/swirlTexture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/swirlTexture.jpg -------------------------------------------------------------------------------- /src/img/homepage/wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/homepage/wrench.png -------------------------------------------------------------------------------- /src/img/largeh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/largeh.png -------------------------------------------------------------------------------- /src/img/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/linkedin.png -------------------------------------------------------------------------------- /src/img/logotype-teal-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/logotype-teal-transparent.png -------------------------------------------------------------------------------- /src/img/logotype-white-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/logotype-white-transparent.png -------------------------------------------------------------------------------- /src/img/merkaba-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/merkaba-black.png -------------------------------------------------------------------------------- /src/img/merkaba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/merkaba.png -------------------------------------------------------------------------------- /src/img/onboarding/iho/intention1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/intention1.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/intention1_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/intention1_m.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/intention2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/intention2.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/intention2_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/intention2_m.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/offer1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/offer1.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/offer1_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/offer1_m.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/offer2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/offer2.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/offer2_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/offer2_m.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/request1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/request1.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/request1_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/request1_m.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/request2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/request2.jpg -------------------------------------------------------------------------------- /src/img/onboarding/iho/request2_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/onboarding/iho/request2_m.jpg -------------------------------------------------------------------------------- /src/img/smallh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/smallh.png -------------------------------------------------------------------------------- /src/img/testimonials/logos/Afrilabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/testimonials/logos/Afrilabs.png -------------------------------------------------------------------------------- /src/img/testimonials/logos/Embassy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/testimonials/logos/Embassy.png -------------------------------------------------------------------------------- /src/img/testimonials/logos/Enspiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/testimonials/logos/Enspiral.png -------------------------------------------------------------------------------- /src/img/testimonials/logos/OaklandHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/testimonials/logos/OaklandHub.png -------------------------------------------------------------------------------- /src/img/testimonials/logos/SfunCube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hylozoic/hylo-frontend/2ac0974fb662be7960f5c96d24717dbde232ef94/src/img/testimonials/logos/SfunCube.png -------------------------------------------------------------------------------- /src/js/admin/services/Admin.js: -------------------------------------------------------------------------------- 1 | module.exports = function($resource) { 2 | 'ngInject'; 3 | return $resource('/noo/admin/:id', { 4 | id: '@id' 5 | }, { 6 | getMetrics: { 7 | url: '/noo/admin/metrics' 8 | } 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/js/admin/services/Chart.js: -------------------------------------------------------------------------------- 1 | var d3 = window.d3, 2 | nv = window.nv; 3 | 4 | module.exports = { 5 | 6 | render: function(opts) { 7 | 8 | nv.addGraph(function() { 9 | var chart; 10 | 11 | switch (opts.type) { 12 | case 'line': 13 | chart = nv.models.lineChart() 14 | .x(d => d[0]) 15 | .y(d => d[1]); 16 | break; 17 | case 'bar': 18 | chart = nv.models.multiBarChart() 19 | .x(d => d[0]) 20 | .y(d => d[1]) 21 | .clipEdge(true) 22 | .stacked(true) 23 | .showControls(false); 24 | } 25 | 26 | chart.xAxis 27 | .showMaxMin(false) 28 | .tickFormat(d => d3.time.format('%x')(new Date(d))); 29 | 30 | chart.yAxis 31 | .tickFormat(d3.format(',.2f')); 32 | 33 | if (opts.setup) opts.setup(chart); 34 | 35 | d3.select(opts.to) 36 | .datum(opts.data) 37 | .transition().duration(500).call(chart); 38 | 39 | nv.utils.windowResize(chart.update); 40 | 41 | return chart; 42 | }); 43 | 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/js/angular/angulartics-segmentio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Angulartics v0.15.20 3 | * (c) 2013 Luis Farzati http://luisfarzati.github.io/angulartics 4 | * License: MIT 5 | */ 6 | (function(angular) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angulartics.segment.io 12 | * Enables analytics support for Segment.io (http://segment.io) 13 | */ 14 | angular.module('angulartics.segment.io', ['angulartics']) 15 | .config(['$analyticsProvider', function ($analyticsProvider) { 16 | $analyticsProvider.registerPageTrack(function (path) { 17 | try { 18 | analytics.page(path); 19 | } catch (e) { 20 | if (!(e instanceof ReferenceError)) { 21 | throw e; 22 | } 23 | } 24 | }); 25 | 26 | $analyticsProvider.registerEventTrack(function (action, properties) { 27 | try { 28 | analytics.track(action, properties); 29 | } catch (e) { 30 | if (!(e instanceof ReferenceError)) { 31 | throw e; 32 | } 33 | } 34 | }); 35 | }]); 36 | })(angular); 37 | -------------------------------------------------------------------------------- /src/js/app/animations.js: -------------------------------------------------------------------------------- 1 | var heightAndTopMargin = function(element) { 2 | return parseInt(element.clientHeight) + parseInt(getComputedStyle(element).marginTop); 3 | } 4 | 5 | module.exports = function(angularModule) { 6 | angularModule.animation('.fade-in', function() { 7 | return { 8 | addClass: function(element, className) { 9 | element.css({opacity: 1, display: 'block'}); 10 | // TweenLite.to(element, 0.5, {opacity: 1, display: 'block'}); 11 | }, 12 | removeClass: function(element, className) { 13 | TweenLite.to(element, 0.5, {opacity: 0, display: 'none'}); 14 | } 15 | }; 16 | }) 17 | .animation('.drop-in', function($timeout) { 18 | return { 19 | addClass: function(element, className) { 20 | element.css('display', 'block'); 21 | element.css('top', -heightAndTopMargin(element[0]) + 'px'); 22 | $timeout(function() { TweenLite.to(element, 0.5, {top: 0}) }, 0); 23 | }, 24 | removeClass: function(element, className) { 25 | element.css('top', -heightAndTopMargin(element[0]) + 'px'); 26 | // TweenLite.to(element, 0.5, {top: -element.outerHeight()}); 27 | } 28 | }; 29 | }); 30 | } -------------------------------------------------------------------------------- /src/js/app/controllers/AnnouncerCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, $rootScope, Onboarding) { 2 | 3 | var onclick; 4 | 5 | $rootScope.$on('announcer:show', function(event, data) { 6 | onclick = data.onclick; 7 | $scope.text = data.text; 8 | $scope.className = data.className; 9 | $scope.active = true; 10 | }); 11 | 12 | $rootScope.$on('announcer:hide', function() { 13 | $scope.className = null; 14 | $scope.active = false; 15 | }) 16 | 17 | $scope.go = function() { 18 | $scope.active = false; 19 | if (typeof(onclick) === 'function') 20 | onclick(); 21 | }; 22 | }; 23 | 24 | module.exports = function(angularModule) { 25 | angularModule.controller('AnnouncerCtrl', controller); 26 | }; -------------------------------------------------------------------------------- /src/js/app/controllers/FulfillmentCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, $modalInstance, Post, User) { 2 | 3 | var post = $scope.post, 4 | contributors = $scope.contributors = []; 5 | 6 | $scope.save = function () { 7 | Post.fulfill({id: post.id, contributors: _.pluck(contributors, 'id')}, function() { 8 | post.contributors = contributors; 9 | post.fulfilled_at = new Date(); 10 | }); 11 | $modalInstance.close(); 12 | }; 13 | 14 | $scope.findMembers = function(search) { 15 | return User.autocomplete({q: search}).$promise; 16 | }; 17 | 18 | }; 19 | 20 | module.exports = function(angularModule) { 21 | angularModule.controller('FulfillmentCtrl', controller); 22 | }; 23 | -------------------------------------------------------------------------------- /src/js/app/controllers/NavCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function($scope, $modal, CurrentUser, joinCommunity) { 2 | 'ngInject'; 3 | 4 | $scope.currentUser = CurrentUser.get(); 5 | 6 | $scope.joinCommunity = joinCommunity; 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/CommunityCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, $analytics, community, currentUser) { 2 | 3 | $scope.community = community; 4 | $scope.canModerate = currentUser && currentUser.canModerate(community); 5 | $scope.canInvite = $scope.canModerate || community.settings.all_can_invite; 6 | 7 | $analytics.eventTrack('Community: Load Community', { 8 | community_id: community.id, 9 | community_name: community.name, 10 | community_slug: community.slug 11 | }); 12 | 13 | }; 14 | 15 | module.exports = function(angularModule) { 16 | angularModule.controller('CommunityCtrl', controller); 17 | }; 18 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/CommunityEventsCtrl.js: -------------------------------------------------------------------------------- 1 | var moment = require('moment-timezone') 2 | 3 | module.exports = function ($scope, community, currentUser, firstPostQuery, Post, PostManager) { 4 | 'ngInject' 5 | 6 | $scope.currentUser = currentUser 7 | 8 | var postManager = new PostManager({ 9 | firstPage: firstPostQuery, 10 | scope: $scope, 11 | attr: 'posts', 12 | query: function () { 13 | return Post.queryForCommunity({ 14 | communityId: community.id, 15 | limit: 10, 16 | offset: $scope.posts.length, 17 | type: 'event', 18 | sort: 'start_time', 19 | filter: $scope.showPastEvents ? null : 'future' 20 | }).$promise 21 | } 22 | }) 23 | 24 | postManager.setup() 25 | 26 | $scope.updateView = function () { 27 | postManager.reload() 28 | } 29 | 30 | var group = function (event) { 31 | var now = moment() 32 | var eventTime = moment(event.start_time) 33 | var difference = eventTime.diff(now, 'days') 34 | if (difference < 0) { 35 | return 'Past' 36 | } else if (difference === 0) { 37 | return 'Today' 38 | } else if (difference === 1) { 39 | return 'Tomorrow' 40 | } else if (difference < 8 && eventTime.isoWeek() === now.isoWeek()) { 41 | return 'This Week' 42 | } else if (difference < 15 && eventTime.isoWeek() === now.isoWeek() + 1) { 43 | return 'Next Week' 44 | } else if (difference < 31 && eventTime.month() === now.month()) { 45 | return 'Month' 46 | } else { 47 | return 'Future' 48 | } 49 | } 50 | 51 | $scope.eventHeader = function (previous, current) { 52 | var currentGroup = group(current) 53 | 54 | if (previous) { 55 | var previousGroup = group(previous) 56 | if (previousGroup === currentGroup) return 57 | } 58 | 59 | return currentGroup 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/CommunityMembersCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, community, usersQuery, $dialog, Cache, currentUser, $timeout) { 2 | $scope.users = usersQuery.people; 3 | $scope.userCount = usersQuery.people_total; 4 | 5 | $scope.loadMore = _.debounce(function() { 6 | if ($scope.loadMoreDisabled) return; 7 | $scope.loadMoreDisabled = true; 8 | 9 | community.members({offset: $scope.users.length}, function(resp) { 10 | $scope.users = _.uniq($scope.users.concat(resp.people), u => u.id); 11 | 12 | Cache.set('community.members:' + community.id, { 13 | people: $scope.users, 14 | people_total: resp.people_total 15 | }, {maxAge: 10 * 60}); 16 | 17 | $scope.userCount = resp.people_total; 18 | 19 | if (resp.people.length > 0 && $scope.users.length < resp.people_total) 20 | $timeout(function() { $scope.loadMoreDisabled = false; }); 21 | }); 22 | 23 | }, 200); 24 | 25 | $scope.search = function(term) { 26 | $scope.$state.go('search', {c: community.id, q: term}); 27 | }; 28 | 29 | $scope.remove = function(user, index) { 30 | $dialog.confirm({ 31 | message: 'Are you sure you want to remove ' + user.name + ' from this community?', 32 | }).then(function() { 33 | community.removeMember({userId: user.id}, function() { 34 | $scope.users.splice(index, 1); 35 | }); 36 | }); 37 | }; 38 | 39 | }; 40 | 41 | module.exports = function(angularModule) { 42 | angularModule.controller('CommunityMembersCtrl', controller); 43 | }; 44 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/CommunityPostsCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, Cache, Post, community, onboarding, firstPostQuery, PostManager, currentUser) { 2 | 'ngInject' 3 | 4 | $scope.onboarding = onboarding 5 | $scope.currentUser = currentUser 6 | 7 | var postManager = new PostManager({ 8 | firstPage: firstPostQuery, 9 | scope: $scope, 10 | attr: 'posts', 11 | cache: function (posts, total) { 12 | Cache.set('community.posts:' + community.id, { 13 | posts: posts, 14 | posts_total: total 15 | }, {maxAge: 10 * 60}) 16 | }, 17 | query: function () { 18 | return Post.queryForCommunity({ 19 | communityId: community.id, 20 | limit: 10, 21 | offset: $scope.posts.length, 22 | type: $scope.selected.filter.value, 23 | sort: $scope.selected.sort.value 24 | }).$promise 25 | } 26 | }) 27 | 28 | postManager.setup() 29 | 30 | $scope.updateView = function (data) { 31 | $scope.selected = data 32 | postManager.reload() 33 | } 34 | 35 | $scope.$on('post-editor-done', function (event, payload) { 36 | if (payload.action === 'create') { 37 | postManager.reload() 38 | } 39 | }) 40 | 41 | $scope.startPosting = function () { 42 | $scope.$broadcast('open-post-editor') 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/JoinCommunityByUrlCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, $controller, community, code, currentUser, 2 | $modal, growl, $timeout) { 3 | 'ngInject' 4 | 5 | $scope.community = community 6 | $scope.code = code 7 | 8 | var membership = _.find(currentUser.memberships, m => m.community_id === community.id) 9 | if (membership) { 10 | $timeout(() => { 11 | $scope.$state.go('community.posts', {community: membership.community.slug}) 12 | growl.addSuccessMessage('You are already a member of this community.') 13 | }) 14 | } else { 15 | // Put functions from JoinCommunityCtrl onto $scope. 16 | $controller('JoinCommunityCtrl', {$scope: $scope}) 17 | 18 | // Validate the community join code from last part of the URL. 19 | $scope.validateCode() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/js/app/controllers/community/JoinCommunityCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function($scope, Community, CurrentUser, $timeout, $analytics) { 2 | 'ngInject'; 3 | 4 | $scope.validateCode = _.debounce(function() { 5 | if (_.isEmpty($scope.code)) { 6 | $scope.isCodeValid = false; 7 | return; 8 | } 9 | 10 | Community.validate({ 11 | column: 'beta_access_code', 12 | constraint: 'exists', 13 | value: $scope.code 14 | }, function(resp) { 15 | if (resp.exists) { 16 | $scope.isCodeValid = true; 17 | } else { 18 | $scope.isCodeValid = false; 19 | } 20 | }); 21 | }, 250); 22 | 23 | $scope.submit = function() { 24 | if (!$scope.isCodeValid) return; 25 | 26 | Community.join({code: $scope.code}, function(membership) { 27 | var community = membership.community; 28 | 29 | if ($scope.$close) $scope.$close(); 30 | 31 | var user = CurrentUser.get(); 32 | if (!_.find(user.memberships, m => m.community_id === community.id)) 33 | user.memberships.push(membership); 34 | 35 | $scope.$state.go('appEntry'); 36 | }); 37 | }; 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /src/js/app/controllers/home/AllPostsCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, currentUser, firstPostQuery, UserCache, PostManager) { 2 | 'ngInject' 3 | 4 | var postManager = new PostManager({ 5 | firstPage: firstPostQuery, 6 | scope: $scope, 7 | attr: 'posts', 8 | query: function () { 9 | return currentUser.allPosts({ 10 | limit: 10, 11 | offset: $scope.posts.length, 12 | type: $scope.selected.filter.value, 13 | sort: $scope.selected.sort.value 14 | }).$promise 15 | }, 16 | cache: function (posts, total) { 17 | UserCache.allPosts.set({ 18 | posts: posts, 19 | posts_total: total 20 | }) 21 | } 22 | }) 23 | 24 | postManager.setup() 25 | 26 | $scope.hasPosts = $scope.posts.length > 0 27 | 28 | $scope.updateView = function (data) { 29 | $scope.selected = data 30 | postManager.reload() 31 | } 32 | 33 | $scope.$on('post-editor-done', function (event, payload) { 34 | if (payload.action === 'create') { 35 | postManager.reload() 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/js/app/controllers/home/FollowedPostsCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, currentUser, firstPostQuery, UserCache, PostManager) { 2 | $scope.posts = firstPostQuery.posts; 3 | $scope.hasPosts = $scope.posts.length > 0; 4 | 5 | var postManager = new PostManager({ 6 | firstPage: firstPostQuery, 7 | scope: $scope, 8 | attr: 'posts', 9 | query: function() { 10 | return currentUser.followedPosts({ 11 | limit: 10, 12 | offset: $scope.posts.length 13 | }).$promise; 14 | }, 15 | cache: function(posts, total) { 16 | UserCache.followedPosts.set(currentUser.id, { 17 | posts: posts, 18 | posts_total: total 19 | }); 20 | } 21 | }); 22 | 23 | postManager.setup(); 24 | 25 | }; 26 | 27 | module.exports = function(angularModule) { 28 | angularModule.controller('FollowedPostsCtrl', controller); 29 | }; -------------------------------------------------------------------------------- /src/js/app/controllers/post/PostCtrl.js: -------------------------------------------------------------------------------- 1 | var striptags = require('striptags') 2 | var truncate = require('html-truncate') 3 | 4 | var controller = function ($scope, Post, growl, post, currentUser, $stateParams, $history) { 5 | $scope.post = post 6 | 7 | $scope.postdeleted = function (deletedPost) { 8 | growl.addSuccessMessage('Post has been removed: ' + deletedPost.name, {ttl: 5000}) 9 | if ($history.isEmpty()) { 10 | $scope.$state.go('community.posts', {community: deletedPost.communities[0].slug}) 11 | } else { 12 | $history.go(-1); 13 | } 14 | } 15 | 16 | if ($stateParams.action === 'unfollow') { 17 | post.unfollow({}, function () { 18 | post.followers = _.without(post.followers, _.findWhere(post.followers, {id: currentUser.id})) 19 | growl.addSuccessMessage('You are no longer following this post.', {ttl: 8000}) 20 | }) 21 | } 22 | } 23 | 24 | module.exports = function (angularModule) { 25 | angularModule.controller('PostCtrl', controller) 26 | } 27 | -------------------------------------------------------------------------------- /src/js/app/controllers/post/PostEditPageCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, $history, $state, post, communities) { 2 | 'ngInject' 3 | 4 | $scope.post = post 5 | $scope.editing = !!post 6 | $scope.communities = communities 7 | 8 | $scope.close = function () { 9 | $scope.$broadcast('post-editor-closing') 10 | 11 | if ($history.isEmpty()) { 12 | if (_.isEmpty(communities)) { 13 | $state.go('home.allPosts') 14 | } else { 15 | $state.go('community.posts', {community: communities[0].slug}) 16 | } 17 | } else { 18 | $history.go(-1) 19 | } 20 | } 21 | 22 | $scope.$on('post-editor-done', $scope.close) 23 | } 24 | -------------------------------------------------------------------------------- /src/js/app/controllers/post/PostListCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, Post, firstPostQuery, user, isSelf, UserCache, PostManager) { 2 | 'ngInject' 3 | 4 | var postManager = new PostManager({ 5 | firstPage: firstPostQuery, 6 | scope: $scope, 7 | attr: 'posts', 8 | query: function () { 9 | return Post.queryForUser({ 10 | userId: user.id, 11 | limit: 10, 12 | offset: $scope.posts.length 13 | }).$promise 14 | }, 15 | cache: function (posts, total) { 16 | UserCache.posts.set(user.id, { 17 | posts: posts, 18 | posts_total: total 19 | }) 20 | } 21 | }) 22 | 23 | postManager.setup() 24 | 25 | $scope.hasPosts = $scope.posts.length > 0 26 | $scope.isSelf = isSelf 27 | 28 | $scope.$on('post-editor-done', function (event, payload) { 29 | if (payload.action === 'create') { 30 | postManager.reload() 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/js/app/controllers/post/WelcomePostCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function($scope, Post, $analytics, CurrentUser) { 2 | 'ngInject'; 3 | 4 | var post = $scope.post; 5 | $scope.user = post.relatedUsers[0]; 6 | $scope.isFollowing = () => _.any(post.followers, CurrentUser.is); 7 | $scope.isSelf = CurrentUser.is($scope.user); 8 | 9 | $scope.unfollow = () => { 10 | Post.follow({id: post.id}); 11 | post.followers = _.without(post.followers, _.find(post.followers, CurrentUser.is)); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/js/app/controllers/profile.js: -------------------------------------------------------------------------------- 1 | var controller = function($scope, $analytics, user, isSelf, growl, $anchorScroll) { 2 | $scope.user = user; 3 | $scope.isSelf = isSelf; 4 | 5 | $analytics.eventTrack('Member Profiles: Loaded a profile', {user_id: $scope.user.id}); 6 | if (isSelf) $analytics.eventTrack('Member Profiles: Loaded Own Profile'); 7 | 8 | if (!user.banner_url) { 9 | user.banner_url = require('../services/defaultUserBanner'); 10 | } 11 | 12 | $scope.normalizeUrl = function(url) { 13 | if (url.substring(0, 4) === 'http') 14 | return url; 15 | 16 | return 'http://' + url; 17 | }; 18 | 19 | $anchorScroll(); 20 | }; 21 | 22 | module.exports = function(angularModule) { 23 | angularModule.controller('ProfileCtrl', controller); 24 | }; 25 | -------------------------------------------------------------------------------- /src/js/app/controllers/profile/contributions.js: -------------------------------------------------------------------------------- 1 | var dependencies = ['$scope', '$analytics', 'contributions']; 2 | dependencies.push(function($scope, $analytics, contributions ) { 3 | $scope.contributions = contributions; 4 | }); 5 | 6 | module.exports = function(angularModule) { 7 | angularModule.controller('ProfileContributionsCtrl', dependencies); 8 | }; 9 | -------------------------------------------------------------------------------- /src/js/app/controllers/profile/thanks.js: -------------------------------------------------------------------------------- 1 | var dependencies = ['$scope', '$analytics', 'thanks']; 2 | dependencies.push(function($scope, $analytics, thanks) { 3 | $scope.thanks = thanks; 4 | }); 5 | 6 | module.exports = function(angularModule) { 7 | angularModule.controller('ProfileThanksCtrl', dependencies); 8 | }; 9 | -------------------------------------------------------------------------------- /src/js/app/controllers/project/ProjectEditCtrl.js: -------------------------------------------------------------------------------- 1 | var filepickerUpload = require('../../services/filepickerUpload'); 2 | 3 | var controller = function($scope, currentUser, Project, project, $stateParams) { 4 | 5 | $scope.communities = _.map(currentUser.memberships, function(membership) { 6 | return membership.community; 7 | }); 8 | 9 | if (!project) { 10 | var community = $scope.communities[0] 11 | var community_slug = $stateParams.community 12 | if (community_slug) { 13 | community = _.find($scope.communities, c => c.slug === community_slug) 14 | } 15 | project = { 16 | community: community, 17 | visibility: 0 18 | }; 19 | } else { 20 | project.community = _.find($scope.communities, function(c) { return c.id == project.community_id }); 21 | } 22 | 23 | $scope.project = project; 24 | 25 | $scope.visibilityOptions = [ 26 | {label: 'Only the community', value: 0}, 27 | {label: 'Anyone', value: 1} 28 | ]; 29 | 30 | $scope.pickedVisibility = function() { 31 | return _.find($scope.visibilityOptions, function(opt) { 32 | return project.visibility == opt.value; 33 | }); 34 | }; 35 | 36 | $scope.save = function() { 37 | var attrs = _.merge({}, _.omit(project, 'community'), { 38 | community_id: project.community.id 39 | }); 40 | Project.save(attrs, function(project) { 41 | $scope.$state.go('project.posts', {id: project.id, slug: project.slug}); 42 | }); 43 | }; 44 | 45 | $scope.addImage = function() { 46 | if ($scope.addingImage) return; 47 | $scope.addingImage = true; 48 | 49 | var finish = function() { 50 | $scope.addingImage = false; 51 | $scope.$apply(); 52 | }; 53 | 54 | filepickerUpload({ 55 | path: format('user/%s/projects', currentUser.id), 56 | success: function(url) { 57 | project.image_url = url; 58 | finish(); 59 | }, 60 | failure: finish 61 | }); 62 | }; 63 | 64 | $scope.removeImage = function() { 65 | project.image_url = null; 66 | }; 67 | 68 | $scope.unpublish = function() { 69 | project.unpublish(function() { 70 | project.published_at = null; 71 | $scope.$state.go('project.posts', {id: project.slug}); 72 | }) 73 | }; 74 | 75 | }; 76 | 77 | module.exports = function(angularModule) { 78 | angularModule.controller('ProjectEditCtrl', controller); 79 | }; 80 | -------------------------------------------------------------------------------- /src/js/app/controllers/project/ProjectInviteCtrl.js: -------------------------------------------------------------------------------- 1 | var addrs = require('email-addresses'); 2 | 3 | module.exports = function($scope, $history, growl, project, User) { 4 | "ngInject"; 5 | 6 | $scope.close = function() { 7 | if ($history.isEmpty()) { 8 | $scope.$state.go('project.contributors', {slug: project.slug}); 9 | } else { 10 | $history.go(-1); 11 | } 12 | }; 13 | 14 | $scope.findMembers = function(search) { 15 | return User.autocomplete({q: search}).$promise; 16 | }; 17 | 18 | $scope.subject = format('Join my project "%s" on Hylo', project.title); 19 | $scope.message = format("I would like your help on a project I'm starting:\n\n" + 20 | "%s\n%s\n\nYou can help make it happen!", 21 | project.title, project.intention); 22 | 23 | var parsedEmails = function() { 24 | var emailString = ($scope.emails || '').trim(); 25 | if (emailString === '') return []; 26 | 27 | return _.map(emailString.split(','), function(email) { 28 | return email.trim(); 29 | }); 30 | }; 31 | 32 | var validate = function() { 33 | var errors = []; 34 | _.map(parsedEmails(), function(email) { 35 | if (!addrs(email)) { 36 | errors.push(format('"%s" is not a valid email address.', email)); 37 | } 38 | }); 39 | 40 | if (errors.length > 0) { 41 | $scope.errors = errors; 42 | return false; 43 | } 44 | 45 | $scope.errors = null; 46 | return true; 47 | }; 48 | 49 | $scope.send = function() { 50 | if (!validate()) return; 51 | $scope.sending = true; 52 | 53 | project.invite({ 54 | subject: $scope.subject, 55 | message: $scope.message, 56 | users: _.map($scope.users, function(u) { return u.id }), 57 | emails: parsedEmails() 58 | }, function(resp) { 59 | $scope.sending = false; 60 | $scope.close(); 61 | var count = $scope.users.length + parsedEmails().length; 62 | growl.addSuccessMessage(format('Sent %s invitation%s.', count, count === 1 ? '' : 's')); 63 | }, function() { 64 | $scope.sending = false; 65 | }); 66 | }; 67 | 68 | } -------------------------------------------------------------------------------- /src/js/app/controllers/project/ProjectPostsCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, project, Post, Cache, UserCache, growl, 2 | $analytics, currentUser, postQuery, $stateParams, UserMentions, PostManager) { 3 | 'ngInject' 4 | 5 | $scope.currentUser = currentUser 6 | 7 | var postManager = new PostManager({ 8 | firstPage: postQuery, 9 | scope: $scope, 10 | attr: 'posts', 11 | query: function () { 12 | return project.posts({ 13 | limit: 10, 14 | offset: $scope.posts.length, 15 | token: $stateParams.token 16 | }).$promise 17 | } 18 | }) 19 | 20 | postManager.setup() 21 | 22 | $scope.$on('post-editor-done', function (event, payload) { 23 | if (payload.action === 'create') { 24 | postManager.reload() 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/js/app/controllers/project/ProjectUsersCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, $stateParams, project, users, $dialog) { 2 | "ngInject"; 3 | $scope.users = users; 4 | 5 | $scope.$on('joinProject', function() { 6 | $scope.users = []; 7 | $scope.loadMoreDisabled = false; 8 | $scope.loadMore(); 9 | }); 10 | 11 | $scope.loadMore = _.debounce(function() { 12 | if ($scope.loadMoreDisabled) return; 13 | $scope.loadMoreDisabled = true; 14 | 15 | project.users({ 16 | limit: 20, 17 | offset: $scope.users.length, 18 | token: $stateParams.token 19 | }, function(users) { 20 | Array.prototype.push.apply($scope.users, users); 21 | 22 | if (users.length > 0 && $scope.users.length < users[0].total) 23 | $scope.loadMoreDisabled = false; 24 | }); 25 | 26 | }, 200); 27 | 28 | $scope.remove = function(user, index) { 29 | $dialog.confirm({ 30 | message: 'Are you sure you want to remove ' + user.name + ' from this project?', 31 | }).then(function() { 32 | project.removeUser({userId: user.id}, function() { 33 | $scope.users.splice(index, 1); 34 | }); 35 | }); 36 | }; 37 | 38 | $scope.toggleModerator = function(user, index) { 39 | var newRole = user.membership.role === 1 ? 0 : 1; 40 | user.membership.role = newRole; 41 | project.toggleModeratorRole({userId: user.id, role: newRole}); 42 | }; 43 | 44 | $scope.isModerator = function(user) { 45 | return user.membership.role === 1; 46 | } 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /src/js/app/controllers/user/ForgotPasswordCtrl.js: -------------------------------------------------------------------------------- 1 | var controller = function ($scope, $analytics, User, context) { 2 | $analytics.eventTrack('Password reset start') 3 | var requestPending = false 4 | 5 | $scope.submit = function (form) { 6 | form.submitted = true 7 | $scope.error = null 8 | if (form.$invalid || requestPending) return 9 | 10 | requestPending = true 11 | 12 | User.requestPasswordChange({email: $scope.email}, function (resp) { 13 | if (!resp.error) { 14 | $analytics.eventTrack('Password reset success', {email: $scope.email}) 15 | $scope.success = 'Please click the link in the email that we just sent you.' 16 | } else if (resp.error === 'no user') { 17 | $analytics.eventTrack('Password reset failure', {email: $scope.email}) 18 | $scope.error = 'The email address you entered was not recognized.' 19 | } 20 | requestPending = false 21 | }) 22 | } 23 | 24 | $scope.go = function (state) { 25 | if (context === 'modal') { 26 | $scope.$close({action: 'go', state: state}) 27 | } else { 28 | $scope.$state.go(state) 29 | } 30 | } 31 | } 32 | 33 | module.exports = function (angularModule) { 34 | angularModule.controller('ForgotPasswordCtrl', controller) 35 | } 36 | -------------------------------------------------------------------------------- /src/js/app/controllers/user/UserSettingsCtrl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($scope, growl, $stateParams, $analytics, currentUser, Community, $history, $dialog, UserCache, joinCommunity) { 2 | 'ngInject' 3 | var user = $scope.user = currentUser 4 | var editing = $scope.editing = {} 5 | var edited = $scope.edited = {} 6 | 7 | if (!user.settings.digest_frequency) { 8 | user.settings.digest_frequency = 'daily' 9 | } 10 | 11 | $analytics.eventTrack('User Settings: Viewed') 12 | 13 | if ($stateParams.expand === 'password') { 14 | $scope.expand1 = true 15 | editing.password = true 16 | } 17 | 18 | if ($stateParams.expand === 'prompts') { 19 | $scope.expand1 = true 20 | } 21 | 22 | $scope.close = function () { 23 | if ($history.isEmpty()) { 24 | $scope.$state.go('profile.posts', {id: user.id}) 25 | } else { 26 | $history.go(-1) 27 | } 28 | } 29 | 30 | $scope.edit = function (field) { 31 | edited[field] = user[field] 32 | editing[field] = true 33 | } 34 | 35 | $scope.cancelEdit = function (field) { 36 | editing[field] = false 37 | } 38 | 39 | $scope.saveEdit = function (field, form) { 40 | if (form && form.$invalid) return 41 | 42 | editing[field] = false 43 | var data = {} 44 | data[field] = edited[field] 45 | 46 | user.update(data, function () { 47 | user[field] = edited[field] 48 | $analytics.eventTrack('User Settings: Changed ' + field, {user_id: user.id}) 49 | growl.addSuccessMessage('Saved change.') 50 | }, function (err) { 51 | growl.addErrorMessage(err.data) 52 | }) 53 | } 54 | 55 | $scope.toggle = function (field, isInSettings) { 56 | var data = {} 57 | if (isInSettings) { 58 | data.settings = {} 59 | data.settings[field] = user.settings[field] 60 | } else { 61 | data[field] = user[field] 62 | } 63 | user.update(data, function () { 64 | growl.addSuccessMessage('Saved change.') 65 | }, function (err) { 66 | growl.addErrorMessage(err.data) 67 | }) 68 | } 69 | 70 | $scope.leaveCommunity = function (communityId, index) { 71 | $dialog.confirm({ 72 | message: 'Are you sure you want to leave this community?' 73 | }).then(function () { 74 | Community.leave({id: communityId}, function () { 75 | user.memberships.splice(index, 1) 76 | UserCache.allPosts.clear(currentUser.id) 77 | }) 78 | }) 79 | } 80 | 81 | $scope.joinCommunity = joinCommunity 82 | } 83 | -------------------------------------------------------------------------------- /src/js/app/directives/contenteditable.js: -------------------------------------------------------------------------------- 1 | window.rangy = require('rangy'); 2 | window.Undo = require('medium.js/node_modules/undo.js/undo'); 3 | var Medium = require('medium.js/medium'); 4 | 5 | var directive = function($sce, $timeout) { 6 | return { 7 | restrict: 'A', // only activate on element attribute 8 | require: '?ngModel', // get a hold of NgModelController 9 | link: function(scope, element, attrs, ngModel) { 10 | if(!ngModel) return; // do nothing if no ng-model 11 | 12 | function read() { 13 | var html = element.html(); 14 | if (html === '
' || html === '

 

') { 15 | html = ''; 16 | } 17 | 18 | ngModel.$setViewValue(html); 19 | } 20 | 21 | // update mediumEditor when ngModel updates 22 | // so that we can start out the contenteditable element 23 | // with pre-filled content, e.g. when editing an existing post. 24 | // this is the same as calling mediumEditor.value(ngModel.$viewValue), 25 | // except that it skips a call to makeUndoable(), which would trigger 26 | // a change event and cause a "digest in progress" error. 27 | ngModel.$render = function() { 28 | if (mediumEditor && typeof(ngModel.$viewValue) !== 'undefined') { 29 | mediumEditor.element.innerHTML = ngModel.$viewValue; 30 | mediumEditor.clean(); 31 | mediumEditor.placeholders(); 32 | } 33 | }; 34 | 35 | var mediumEditor = new Medium({ 36 | element: angular.element(element)[0], 37 | mode: Medium.partialMode, 38 | // placeholder: attrs.placeholder, 39 | autoHR: false, 40 | pasteAsText: true, 41 | tags: { 42 | innerLevel: ['a', 'br'] 43 | } 44 | }); 45 | 46 | scope.$on("$destroy", function handleDestroyEvent() { 47 | mediumEditor.destroy(); 48 | }); 49 | 50 | // Listen for change events to enable binding 51 | element.on('blur keyup change', function(e) { 52 | scope.$apply(read); 53 | }); 54 | 55 | // commenting this out because it prevents setting an initial value with ng-model 56 | // read(); 57 | } 58 | }; 59 | }; 60 | 61 | module.exports = function(angularModule) { 62 | angularModule.directive('contenteditable', directive); 63 | }; 64 | -------------------------------------------------------------------------------- /src/js/app/directives/embeddedComments.js: -------------------------------------------------------------------------------- 1 | var directive = function() { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | post: '=' 6 | }, 7 | controller: 'CommentsCtrl', 8 | templateUrl: "/ui/app/comments.tpl.html", 9 | replace: true 10 | }; 11 | }; 12 | 13 | module.exports = function(angularModule) { 14 | angularModule.directive('embeddedComments', directive); 15 | }; 16 | -------------------------------------------------------------------------------- /src/js/app/directives/inlinePostInput.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | community: '=', 6 | project: '=' 7 | }, 8 | controller: function ($scope) { 9 | 'ngInject' 10 | 11 | $scope.communities = $scope.community 12 | ? [$scope.community] 13 | : $scope.project 14 | ? [_.cloneDeep($scope.project.community)] 15 | : [] 16 | 17 | $scope.expand = function () { 18 | $scope.expanded = true 19 | } 20 | 21 | $scope.$on('post-editor-done', () => { 22 | $scope.expanded = false 23 | }) 24 | }, 25 | template: '
', 26 | replace: true, 27 | link: function (scope, element, attrs) { 28 | element.on('click', function () { 29 | if (!scope.expanded) { 30 | scope.$apply(() => scope.expanded = true) 31 | } 32 | }) 33 | 34 | scope.$on('open-post-editor', () => { 35 | scope.expand() 36 | element.find('input')[0].focus() 37 | }) 38 | 39 | scope.$watch('expanded', val => { 40 | if (val) { 41 | element.addClass('expanded') 42 | } else { 43 | element.removeClass('expanded') 44 | } 45 | }) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/js/app/directives/postCard.js: -------------------------------------------------------------------------------- 1 | var directive = function() { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | post: '=', // the post to generate markup for as a bi-directional model. See http://docs.angularjs.org/api/ng.$compile 6 | removeFn: '&', 7 | startExpanded: '=' 8 | }, 9 | controller: 'PostCardCtrl', 10 | templateUrl: "/ui/post/card.tpl.html", 11 | replace: true, 12 | link: function(scope, element, attrs) { 13 | element.addClass(scope.post.type); 14 | if (scope.post.fulfilled_at) element.addClass('fulfilled'); 15 | } 16 | }; 17 | }; 18 | 19 | module.exports = function(angularModule) { 20 | angularModule.directive('postCard', directive); 21 | }; 22 | -------------------------------------------------------------------------------- /src/js/app/directives/postEditor.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | post: '=', // the post to generate markup for as a bi-directional model. See http://docs.angularjs.org/api/ng.$compile 6 | communities: '=', 7 | project: '=' 8 | }, 9 | controller: 'PostEditCtrl', 10 | templateUrl: '/ui/post/edit.tpl.html', 11 | replace: true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/js/app/directives/postsToolbar.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | community: '=', 6 | update: '&', 7 | hidePostButton: '@', 8 | hideWelcomePosts: '=' 9 | }, 10 | controller: function ($scope, CurrentUser) { 11 | 'ngInject' 12 | 13 | $scope.currentUser = CurrentUser.get() 14 | 15 | $scope.selectOptions = { 16 | sort: [ 17 | {label: 'Recent', value: 'recent'}, 18 | {label: 'Top', value: 'top'} 19 | ], 20 | filter: [ 21 | {label: 'All Posts', value: ($scope.hideWelcomePosts ? 'all' : 'all+welcome')}, 22 | {label: 'Intentions', value: 'intention'}, 23 | {label: 'Offers', value: 'offer'}, 24 | {label: 'Requests', value: 'request'}, 25 | {label: 'Chats', value: 'chat'} 26 | ] 27 | } 28 | 29 | $scope.selected = { 30 | sort: $scope.selectOptions.sort[0], 31 | filter: $scope.selectOptions.filter[0] 32 | } 33 | 34 | $scope.reload = function () { 35 | $scope.update({data: $scope.selected}) 36 | } 37 | 38 | $scope.select = function (type, value) { 39 | $scope.selected[type] = _.find( 40 | $scope.selectOptions[type], 41 | x => x.value === value 42 | ) 43 | 44 | $scope.reload() 45 | } 46 | 47 | $scope.reload() 48 | }, 49 | templateUrl: '/ui/post/toolbar.tpl.html', 50 | replace: true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/js/app/directives/seeMore.js: -------------------------------------------------------------------------------- 1 | var directive = function($compile) { 2 | return { 3 | replace: true, 4 | template: '
', 5 | link: function(scope, element, attrs) { 6 | scope.$watch('contents', function(contents) { 7 | element.empty(); 8 | if (!contents) return; 9 | 10 | if (scope.truncated) { 11 | var el = angular.element(contents); 12 | angular.element(el[el.length - 1]).append(' See More'); 13 | $compile(el)(scope); 14 | element.append(el); 15 | } else { 16 | element.append(contents); 17 | } 18 | }); 19 | }, 20 | scope: { 21 | contents: '=', 22 | truncated: '=', 23 | expand: '&' 24 | } 25 | } 26 | }; 27 | 28 | module.exports = function(angularModule) { 29 | angularModule.directive('seeMore', directive); 30 | }; -------------------------------------------------------------------------------- /src/js/app/directives/socialMedia.js: -------------------------------------------------------------------------------- 1 | var directive = function($analytics) { 2 | 3 | var controller = function($scope, $element) { 4 | $scope.twitterUrl = function() { 5 | return 'https://twitter.com/' + $scope.user.twitter_name; 6 | }; 7 | 8 | $scope.hasSocialMediaLink = function() { 9 | return $scope.user.twitter_name || $scope.user.linkedin_url || $scope.user.facebook_url; 10 | }; 11 | 12 | $scope.clickedSocialLink = function(network, url) { 13 | $analytics.eventTrack('Clicked a Social Media link', {network: network, url: url, page: $scope.page}); 14 | }; 15 | 16 | $scope.trackEmail = function() { 17 | $analytics.eventTrack('Clicked Email Button', {user_id: $scope.user.id, page: $scope.page}); 18 | }; 19 | }; 20 | 21 | return { 22 | restrict: 'E', 23 | replace: true, 24 | scope: { 25 | user: '=', 26 | page: '=', 27 | isSelf: '=' 28 | }, 29 | templateUrl: "/ui/app/socialMedia.tpl.html", 30 | controller: controller 31 | }; 32 | }; 33 | 34 | module.exports = function(angularModule) { 35 | angularModule.directive('socialMedia', directive); 36 | }; -------------------------------------------------------------------------------- /src/js/app/directives/touchClass.js: -------------------------------------------------------------------------------- 1 | module.exports = function(angularModule) { 2 | 3 | angularModule.directive('touchClass', function() { 4 | return function(scope, element, attrs) { 5 | element.on('touchstart', function() { 6 | element.addClass(attrs.touchClass); 7 | }); 8 | element.on('touchend', function() { 9 | element.removeClass(attrs.touchClass); 10 | }); 11 | }; 12 | }); 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /src/js/app/directives/welcomePost.js: -------------------------------------------------------------------------------- 1 | module.exports = app => app.directive('welcomePost', () => { 2 | return { 3 | restrict: 'E', 4 | replace: true, 5 | scope: {post: '=', showComments: '=?'}, 6 | controller: 'WelcomePostCtrl', 7 | templateUrl: '/ui/post/welcome.tpl.html' 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /src/js/app/filters.js: -------------------------------------------------------------------------------- 1 | var RichText = require('./services/RichText') 2 | var prettydate = require('pretty-date') 3 | 4 | module.exports = function (angularModule) { 5 | angularModule 6 | .filter('markdown', function ($filter) { 7 | return function (text) { 8 | return $filter('unsafe')(RichText.markdown(text)) 9 | } 10 | }) 11 | .filter('richText', function () { 12 | return function (text) { 13 | return RichText.present(text, {skipWrap: true}) 14 | } 15 | }) 16 | .filter('firstName', function () { 17 | return function (name) { 18 | return name.split(' ')[0] 19 | } 20 | }) 21 | .filter('fromNow', function () { 22 | return function (dateStr) { 23 | return prettydate.format(new Date(dateStr)) 24 | } 25 | }) 26 | .filter('shortFromNow', function () { 27 | return function (dateStr) { 28 | return prettydate.format(new Date(dateStr)).replace(' ago', '') 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/js/app/routes/onboarding.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($stateProvider) { 2 | $stateProvider 3 | .state('onboarding', { 4 | abstract: true, 5 | parent: 'main', 6 | views: { 7 | main: { 8 | template: "
" 9 | }, 10 | }, 11 | }) 12 | .state('onboarding.start', { 13 | url: '/h/onboarding/start', 14 | resolve: { 15 | onboardingInit: function(onboarding) { 16 | 'ngInject'; 17 | return onboarding.init(); 18 | } 19 | }, 20 | views: { 21 | onboarding: { 22 | templateUrl: '/ui/onboarding/start.tpl.html', 23 | controller: /*@ngInject*/ function(onboarding, $scope) { 24 | $scope.onboarding = onboarding; 25 | onboarding.setStep('start'); 26 | } 27 | } 28 | } 29 | }) 30 | .state('onboarding.seeds', { 31 | url: '/h/onboarding/seeds', 32 | views: { 33 | onboarding: { 34 | templateUrl: '/ui/onboarding/seeds.tpl.html', 35 | controller: /*@ngInject*/ function(onboarding, $scope) { 36 | $scope.onboarding = onboarding; 37 | } 38 | } 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/js/app/services.js: -------------------------------------------------------------------------------- 1 | module.exports = function (angularModule) { 2 | // FIXME don't do modules like this anymore; 3 | // instead just export the factory function 4 | // as in the second section below 5 | require('./services/Community')(angularModule) 6 | require('./services/Activity')(angularModule) 7 | require('./services/Comment')(angularModule) 8 | require('./services/Search')(angularModule) 9 | require('./services/Post')(angularModule) 10 | require('./services/Invitation')(angularModule) 11 | require('./services/Project')(angularModule) 12 | require('./services/Network')(angularModule) 13 | require('./services/bodyClass')(angularModule) 14 | require('./services/onboarding')(angularModule) 15 | require('./services/clickthroughTracker')(angularModule) 16 | require('./services/history')(angularModule) 17 | require('./services/dialog')(angularModule) 18 | require('./services/Cache')(angularModule) 19 | require('./services/UserCache')(angularModule) 20 | require('./services/ThirdPartyAuth')(angularModule) 21 | require('./services/removeTrailingSlash')(angularModule) 22 | require('./services/myHttpInterceptor')(angularModule) 23 | require('./services/GooglePicker')(angularModule) 24 | require('./services/joinCommunity')(angularModule) 25 | require('./services/popupDone') 26 | 27 | angularModule 28 | .factory('CurrentUser', require('./services/CurrentUser')) 29 | .factory('User', require('./services/User')) 30 | .factory('UserMentions', require('./services/UserMentions')) 31 | .factory('PostManager', require('./services/PostManager')) 32 | .factory('ModalLoginSignup', require('./services/ModalLoginSignup')) 33 | } 34 | -------------------------------------------------------------------------------- /src/js/app/services/Activity.js: -------------------------------------------------------------------------------- 1 | var service = function($resource) { 2 | return $resource('/noo/activity/:id', {id: '@id'}, { 3 | markAllRead: { 4 | method: 'POST', 5 | url: '/noo/activity/mark-all-read' 6 | } 7 | }); 8 | }; 9 | 10 | module.exports = function(angularModule) { 11 | angularModule.factory('Activity', service); 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/app/services/Cache.js: -------------------------------------------------------------------------------- 1 | var factory = function() { 2 | 3 | var Cache = function() { 4 | this.store = {}; 5 | }; 6 | 7 | _.extend(Cache.prototype, { 8 | 9 | set: function(key, value, options) { 10 | var options = _.merge({}, options, { 11 | createdAt: new Date().getTime() 12 | }); 13 | 14 | // expire if maxAge (in seconds) has passed 15 | if (options.maxAge) { 16 | options.expireAt = options.createdAt + options.maxAge * 1000; 17 | } 18 | 19 | this.store[key] = {value: value, options: options}; 20 | }, 21 | 22 | get: function(key) { 23 | if (!_.has(this.store, key)) 24 | return; 25 | 26 | var entry = this.store[key], 27 | expireAt = entry.options.expireAt; 28 | 29 | if (expireAt && expireAt < new Date().getTime()) 30 | return; 31 | 32 | return entry.value; 33 | }, 34 | 35 | drop: function(key) { 36 | delete this.store[key]; 37 | } 38 | 39 | }); 40 | 41 | return new Cache(); 42 | }; 43 | 44 | module.exports = function(angularModule) { 45 | angularModule.factory('Cache', factory); 46 | } -------------------------------------------------------------------------------- /src/js/app/services/Comment.js: -------------------------------------------------------------------------------- 1 | var service = function($resource) { 2 | return $resource('/noo/comment/:id', {id: '@id'}, { 3 | thank: { 4 | method: 'POST', 5 | url: '/noo/comment/:id/thank' 6 | } 7 | }); 8 | }; 9 | 10 | module.exports = function(angularModule) { 11 | angularModule.factory('Comment', service); 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/app/services/CurrentUser.js: -------------------------------------------------------------------------------- 1 | // Use this only if you can't get the currentUser resolve dependency from ui-router. 2 | 3 | module.exports = function(User) { 4 | 'ngInject'; 5 | var user; 6 | var api = { 7 | set: u => user = u, 8 | get: () => user, 9 | isLoggedIn: () => !!user, 10 | 11 | is: userOrId => user && (user.id === userOrId || user.id === userOrId.id), 12 | 13 | load: () => 14 | User.current().$promise.then(resp => 15 | _.tap(resp.id ? resp : null, user => { 16 | api.set(user); 17 | window.hyloEnv.provideUser(user); 18 | })) 19 | }; 20 | return api; 21 | }; 22 | -------------------------------------------------------------------------------- /src/js/app/services/Invitation.js: -------------------------------------------------------------------------------- 1 | var factory = function($resource) { 2 | var Invitation = $resource('/noo/invitation/:token', { 3 | token: '@token' 4 | }, { 5 | use: { 6 | url: '/noo/invitation/:token', 7 | method: 'POST' 8 | } 9 | }); 10 | 11 | var storedInvitation; 12 | 13 | Invitation.store = function(data) { 14 | storedInvitation = data; 15 | }; 16 | 17 | Invitation.storedData = function() { 18 | return storedInvitation; 19 | }; 20 | 21 | return Invitation; 22 | }; 23 | 24 | module.exports = function(angularModule) { 25 | angularModule.factory('Invitation', factory); 26 | }; -------------------------------------------------------------------------------- /src/js/app/services/ModalLoginSignup.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | usage: 4 | 5 | ModalLoginSignup.start({ 6 | resolve: { 7 | // any extra resolve dependencies to pass to the signup/login controllers 8 | }, 9 | finish: function () { 10 | // what to do after login/signup is successful 11 | } 12 | }) 13 | 14 | */ 15 | 16 | module.exports = function ($modal) { 17 | 'ngInject' 18 | return { 19 | start: function (options) { 20 | start($modal, options) 21 | } 22 | } 23 | } 24 | 25 | function start ($modal, options) { 26 | var modalDefaults = { 27 | backdrop: true, 28 | keyboard: false, 29 | windowClass: 'login-signup-modal', 30 | resolve: _.merge({ 31 | context: () => 'modal', 32 | projectInvitation: () => null 33 | }, options.resolve) 34 | } 35 | 36 | var open = function (state) { 37 | var modalOptions = {} 38 | 39 | if (state === 'signup') { 40 | _.merge(modalOptions, { 41 | templateUrl: '/ui/entrance/signup.tpl.html', 42 | controller: 'SignupCtrl' 43 | }, modalDefaults) 44 | } else if (state === 'login') { 45 | _.merge(modalOptions, { 46 | templateUrl: '/ui/entrance/login.tpl.html', 47 | controller: 'LoginCtrl' 48 | }, modalDefaults) 49 | } else if (state === 'forgotPassword') { 50 | _.merge(modalOptions, { 51 | templateUrl: '/ui/entrance/forgot-password.tpl.html', 52 | controller: 'ForgotPasswordCtrl' 53 | }, modalDefaults) 54 | } 55 | 56 | $modal.open(modalOptions).result.then(handle) 57 | } 58 | 59 | var handle = function (result) { 60 | if (result.action === 'go') { 61 | // toggling between login, signup, forgot password within the modal 62 | open(result.state) 63 | } else if (result.action === 'finish') { 64 | options.finish() 65 | } 66 | } 67 | 68 | open('signup') 69 | } 70 | -------------------------------------------------------------------------------- /src/js/app/services/Network.js: -------------------------------------------------------------------------------- 1 | var factory = function($resource, Community, User) { 2 | var Network = $resource('/noo/network/:id', { 3 | id: '@id' 4 | }); 5 | 6 | _.extend(Network.prototype, { 7 | communities: function(params, success, error) { 8 | return Community.queryForNetwork(_.extend({id: this.id}, params), success, error); 9 | }, 10 | members: function(params, success, error) { 11 | return User.queryForNetwork(_.extend({id: this.id}, params), success, error); 12 | } 13 | }); 14 | 15 | return Network; 16 | }; 17 | 18 | module.exports = function(angularModule) { 19 | angularModule.factory('Network', factory); 20 | }; 21 | -------------------------------------------------------------------------------- /src/js/app/services/Post.js: -------------------------------------------------------------------------------- 1 | var factory = function ($resource) { 2 | var Post = $resource('/noo/post/:id/:action', { 3 | id: '@id', 4 | projectId: '@projectId' 5 | }, { 6 | comment: { 7 | method: 'POST', 8 | params: { 9 | action: 'comment' 10 | } 11 | }, 12 | follow: { 13 | method: 'POST', 14 | params: { 15 | action: 'follow' 16 | } 17 | }, 18 | unfollow: { 19 | method: 'POST', 20 | params: { 21 | action: 'follow', 22 | force: 'unfollow' 23 | } 24 | }, 25 | respond: { 26 | method: 'POST', 27 | params: { 28 | action: 'respond' 29 | } 30 | }, 31 | queryForCommunity: { 32 | url: '/noo/community/:communityId/posts' 33 | }, 34 | queryForUser: { 35 | url: '/noo/user/:userId/posts' 36 | }, 37 | queryForNetwork: { 38 | url: '/noo/network/:id/posts' 39 | }, 40 | fulfill: { 41 | method: 'POST', 42 | url: '/noo/post/:id/fulfill' 43 | }, 44 | vote: { 45 | method: 'POST', 46 | url: '/noo/post/:id/vote' 47 | }, 48 | findComments: { 49 | url: '/noo/post/:id/comments', 50 | isArray: true 51 | }, 52 | complain: { 53 | method: 'POST', 54 | url: '/noo/post/:id/complain' 55 | } 56 | }) 57 | 58 | Post.relevantCommunity = function (post, user) { 59 | if (!user) return post.communities[0] 60 | var ids = _.pluck(user.memberships, 'community_id') 61 | return _.find(post.communities, c => _.contains(ids, c.id)) || post.communities[0] 62 | } 63 | 64 | // let's make things a bit more OO around here 65 | _.extend(Post.prototype, { 66 | update: function (params, success, error) { 67 | return Post.save(_.extend({id: this.id}, params), success, error) 68 | }, 69 | fulfill: function (params, success, error) { 70 | return Post.fulfill(_.extend({id: this.id}, params), success, error) 71 | }, 72 | vote: function (params, success, error) { 73 | return Post.vote(_.extend({id: this.id}, params), success, error) 74 | }, 75 | findComments: function (params, success, error) { 76 | return Post.findComments(_.extend({id: this.id}, params), success, error) 77 | }, 78 | unfollow: function (params, success, error) { 79 | return Post.unfollow(_.extend({id: this.id}, params), success, error) 80 | } 81 | }) 82 | 83 | return Post 84 | } 85 | 86 | module.exports = function (angularModule) { 87 | angularModule.factory('Post', factory) 88 | } 89 | -------------------------------------------------------------------------------- /src/js/app/services/PostManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | encapsulate the pagination logic for posts. 3 | 4 | this hardcodes the names "loadMoreDisabled", "loadMore", and "removePost", 5 | which couples it to the templates that use the infinite-scroll and post-card directives. 6 | 7 | it also assumes the server response will contain "posts" and "posts_total" keys, 8 | which couples it to our particular server implementation. 9 | */ 10 | 11 | module.exports = function (growl, $analytics, $timeout) { 12 | 'ngInject' 13 | 14 | var PostManager = function (opts) { 15 | var firstPage = opts.firstPage 16 | this.opts = opts 17 | this.attr = opts.attr 18 | this.scope = opts.scope 19 | this.scope[this.attr] = firstPage.posts 20 | this.scope.loadMoreDisabled = this.scope.posts.length >= firstPage.posts_total 21 | if (opts.hideWelcomePosts) this.scope.hideWelcomePosts = true 22 | } 23 | 24 | _.extend(PostManager.prototype, { 25 | setup: function () { 26 | this.scope.loadMore = _.debounce(function () { 27 | if (this.scope.loadMoreDisabled) return 28 | this.scope.loadMoreDisabled = true 29 | this.scope.loadingPosts = true 30 | 31 | this.opts.query().then(this._append.bind(this)) 32 | }.bind(this), 200) 33 | 34 | this.scope.removePost = function (postToRemove) { 35 | growl.addSuccessMessage('Post has been removed: ' + postToRemove.name, {ttl: 5000}) 36 | $analytics.eventTrack('Post: Remove', {post_name: postToRemove.name, post_id: postToRemove.id}) 37 | var index = this.scope[this.attr].indexOf(postToRemove) 38 | this.scope[this.attr].splice(index, 1) 39 | }.bind(this) 40 | }, 41 | 42 | reload: function () { 43 | this.scope.loadingPosts = true 44 | this.scope[this.attr] = [] 45 | this.scope.loadMoreDisabled = false 46 | this.scope.loadMore() 47 | }, 48 | 49 | _append: function (resp) { 50 | this.scope.loadingPosts = false 51 | 52 | this.scope[this.attr] = _.uniq( 53 | this.scope[this.attr].concat(resp.posts), 54 | post => post.id 55 | ) 56 | 57 | if (this.opts.cache) { 58 | this.opts.cache(this.scope[this.attr], resp.posts_total) 59 | } 60 | 61 | if (resp.posts.length > 0 && this.scope[this.attr].length < resp.posts_total) { 62 | $timeout(() => this.scope.loadMoreDisabled = false) 63 | } 64 | } 65 | 66 | }) 67 | 68 | return PostManager 69 | } 70 | -------------------------------------------------------------------------------- /src/js/app/services/RichText.js: -------------------------------------------------------------------------------- 1 | var linkify = require('html-linkify'), 2 | marked = require('marked'), 3 | truncate = require('html-truncate'); 4 | 5 | marked.setOptions({ 6 | gfm: true, 7 | breaks: true, 8 | sanitize: true 9 | }); 10 | 11 | module.exports = { 12 | present: function(text, opts) { 13 | if (!opts) opts = {}; 14 | if (!text) return '

'; 15 | 16 | // wrap in a

tag 17 | if (!opts.skipWrap && text.substring(0, 3) != '

') 18 | text = format('

%s

', text); 19 | 20 | // make links 21 | text = linkify(text, {escape: false, attributes: {target: '_blank'}}); 22 | 23 | // link hashtags 24 | // this is not ideal because it hardcodes the search path 25 | text = text.replace(/( |^|>)#(\w+)/g, '$1#$2'); 26 | 27 | if (opts.maxlength) 28 | text = truncate(text, opts.maxlength); 29 | 30 | return text; 31 | }, 32 | 33 | markdown: function(text, opts) { 34 | var text = marked(text); 35 | if (opts && opts.maxlength) { 36 | text = truncate(text, opts.maxlength); 37 | } 38 | return text; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/js/app/services/Search.js: -------------------------------------------------------------------------------- 1 | var factory = function($resource) { 2 | var Search = $resource('/noo/search'); 3 | 4 | return Search; 5 | }; 6 | 7 | module.exports = function(angularModule) { 8 | angularModule.factory('Search', factory); 9 | }; -------------------------------------------------------------------------------- /src/js/app/services/ThirdPartyAuth.js: -------------------------------------------------------------------------------- 1 | var factory = function() { 2 | 3 | return { 4 | openPopup: function(service) { 5 | var width, height; 6 | 7 | if (service === 'google') { 8 | width = 420; 9 | height = 480; 10 | } else if (service === 'facebook') { 11 | width = 560; 12 | height = 520; 13 | } else if (service === 'linkedin') { 14 | width = 400; 15 | height = 584; 16 | } 17 | 18 | // n.b. positioning of the popup is off on Chrome with multiple displays 19 | return window.open( 20 | '/noo/login/' + service, 21 | service + 'Auth', 22 | format('width=%s, height=%s, left=%s, top=%s, titlebar=no, toolbar=no, menubar=no', 23 | width, 24 | height, 25 | document.documentElement.clientWidth/2 - width/2, 26 | document.documentElement.clientHeight/2 - height/2 27 | ) 28 | ); 29 | } 30 | }; 31 | }; 32 | 33 | module.exports = function(angularModule) { 34 | angularModule.factory('ThirdPartyAuth', factory); 35 | }; 36 | -------------------------------------------------------------------------------- /src/js/app/services/TimeText.js: -------------------------------------------------------------------------------- 1 | var moment = require('moment-timezone') 2 | 3 | var sameDay = function (t1, t2) { 4 | return t1.getFullYear() === t2.getFullYear() && 5 | t1.getMonth() === t2.getMonth() && 6 | t1.getDate() === t2.getDate() 7 | } 8 | 9 | module.exports = { 10 | range: function (start, end) { 11 | var startText = moment(start).calendar(null, { 12 | sameElse: 'dddd, MMM D, YYYY [at] h:mm A' 13 | }) 14 | if (!end) { 15 | return startText 16 | } else if (sameDay(start, end)) { 17 | startText = startText.replace(' at ', ' from ') 18 | var endText = moment(end).format('h:mm A') 19 | return format('%s to %s', startText, endText) 20 | } else { 21 | return format('%s to %s', startText, moment(end).calendar()) 22 | } 23 | }, 24 | 25 | rangeFullText: function (start, end) { 26 | if (!end) { 27 | return moment(start).format('LLLL') 28 | } else { 29 | return moment(start).format('LLLL') + ' to ' + moment(end).format('LLLL') 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/js/app/services/UserMentions.js: -------------------------------------------------------------------------------- 1 | module.exports = function (Community, User, $rootScope) { 2 | 'ngInject' 3 | return { 4 | userTextRaw: function (user) { 5 | return format( 6 | '@%s', 7 | user.id, user.id, user.name) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/js/app/services/bodyClass.js: -------------------------------------------------------------------------------- 1 | // generate a list of classes to add to the body tag, 2 | // based on the current ui-router state 3 | // 4 | // add the state name, but also the parent's name if 5 | // the state is a sub-view 6 | // 7 | 8 | var extractClassName = function(stateName) { 9 | if (stateName.match(/\./)) { 10 | return [stateName.split('.', 1)[0], stateName.replace(/\./g, '-')]; 11 | } 12 | 13 | return stateName; 14 | }; 15 | 16 | module.exports = function(angularModule) { 17 | angularModule.factory('$bodyClass', function($state) { 18 | return function() { 19 | return extractClassName($state.current.name); 20 | }; 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/js/app/services/clickthroughTracker.js: -------------------------------------------------------------------------------- 1 | var qs = require('querystring'); 2 | 3 | var service = function ($analytics) { 4 | return { 5 | track: function(location) { 6 | var params = qs.parse(location.search.replace(/^\?/, '')); 7 | if (!params.ctt) return; 8 | 9 | $analytics.eventTrack('Clickthrough', { 10 | path: location.pathname, 11 | type: params.ctt, 12 | id: params.cti 13 | }); 14 | 15 | // remove the params to prevent double-counting events on page reload 16 | var newUrl = location.pathname, 17 | otherParams = _.omit(params, 'ctt', 'cti'); 18 | 19 | if (_.any(otherParams)) { 20 | newUrl += '?' + qs.stringify(otherParams); 21 | } 22 | window.history.replaceState({}, 'Hylo', newUrl); 23 | } 24 | }; 25 | }; 26 | 27 | module.exports = function (angularModule) { 28 | angularModule.factory('clickthroughTracker', service); 29 | } -------------------------------------------------------------------------------- /src/js/app/services/defaultUserBanner.js: -------------------------------------------------------------------------------- 1 | module.exports = 'https://d3ngex8q79bk55.cloudfront.net/misc/default_user_banner.jpg'; 2 | -------------------------------------------------------------------------------- /src/js/app/services/dialog.js: -------------------------------------------------------------------------------- 1 | 2 | // show bootstrap modals for dialogs instead of browser-native UI 3 | 4 | var factory = function($modal) { 5 | return { 6 | confirm: function(opts) { 7 | return $modal.open({ 8 | backdrop: true, 9 | templateUrl: '/ui/shared/confirm.tpl.html', 10 | controller: function($scope) { 11 | $scope.message = opts.message; 12 | } 13 | }).result; 14 | } 15 | }; 16 | }; 17 | 18 | module.exports = function(angularModule) { 19 | angularModule.factory('$dialog', factory); 20 | }; -------------------------------------------------------------------------------- /src/js/app/services/hideNavIOS.js: -------------------------------------------------------------------------------- 1 | var isIOSApp = require('./isIOSApp') 2 | 3 | module.exports = function (hide) { 4 | if (isIOSApp()) { 5 | if (hide) { 6 | document.getElementById('nav').style.display = 'none' 7 | } else { 8 | document.getElementById('nav').style.display = 'block' 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/js/app/services/history.js: -------------------------------------------------------------------------------- 1 | // adapted from: 2 | // http://plnkr.co/edit/DJH6mQUCbTFfSbdCBYUo?p=preview 3 | // https://github.com/angular-ui/ui-router/issues/92 4 | 5 | var service = function($state, $rootScope, $window) { 6 | 7 | var history = [], going = false; 8 | 9 | angular.extend(this, { 10 | push: function(state, params) { 11 | if (going) { 12 | going = false; 13 | } else { 14 | history.push({ state: state, params: params }); 15 | } 16 | }, 17 | all: function() { 18 | return history; 19 | }, 20 | isEmpty: function() { 21 | return history.length == 0; 22 | }, 23 | go: function(step) { 24 | var index = history.length + (step || -1), prev = history[index]; 25 | 26 | history = history.slice(0, index); 27 | going = true; 28 | 29 | return $state.go(prev.state, prev.params); 30 | }, 31 | back: function() { 32 | return this.go(-1); 33 | } 34 | }); 35 | 36 | }; 37 | 38 | var run = function($history, $state, $rootScope) { 39 | $rootScope.$on("$stateChangeSuccess", function(event, to, toParams, from, fromParams) { 40 | if (!from.abstract) { 41 | $history.push(from, fromParams); 42 | } 43 | }); 44 | 45 | if (!$state.current.abstract) { 46 | $history.push($state.current, $state.params); 47 | } 48 | }; 49 | 50 | module.exports = function(angularModule) { 51 | angularModule.service('$history', service).run(run); 52 | }; -------------------------------------------------------------------------------- /src/js/app/services/isAndroidApp.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return (navigator.userAgent.indexOf('Hylo-Android-App') > -1) 3 | } 4 | -------------------------------------------------------------------------------- /src/js/app/services/isIOSApp.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return (navigator.userAgent.indexOf('Hylo-App') > -1) 3 | } 4 | -------------------------------------------------------------------------------- /src/js/app/services/joinCommunity.js: -------------------------------------------------------------------------------- 1 | var service = function($modal) { 2 | return function() { 3 | $modal.open({ 4 | templateUrl: '/ui/shared/join-community.tpl.html', 5 | controller: 'JoinCommunityCtrl' 6 | }); 7 | }; 8 | }; 9 | 10 | module.exports = function(angularModule) { 11 | angularModule.factory('joinCommunity', service) 12 | } 13 | -------------------------------------------------------------------------------- /src/js/app/services/myHttpInterceptor.js: -------------------------------------------------------------------------------- 1 | var config = function ($httpProvider) { 2 | $httpProvider.interceptors.push(function ($q, growl) { 3 | return { 4 | responseError: function(rejection) { 5 | if (!_.include([401, 403, 422, 0], rejection.status)) { 6 | Rollbar.error(format('%s: %s', rejection.status, rejection.config.url)); 7 | var message = format('Oops! An error occurred. The Hylo team has been notified. (%s)', rejection.status); 8 | growl.addErrorMessage(message, {ttl: 5000}); 9 | } 10 | 11 | if (rejection.status === 0) { 12 | Rollbar.error('Timeout', {url: rejection.config.url}); 13 | growl.addErrorMessage("Oops! Your request timed out. Please check your network connection and try again.", {ttl: 5000}); 14 | } 15 | 16 | return $q.reject(rejection); 17 | } 18 | }; 19 | }); 20 | }; 21 | 22 | module.exports = function (angularModule) { 23 | angularModule.config(config); 24 | }; 25 | -------------------------------------------------------------------------------- /src/js/app/services/popupDone.js: -------------------------------------------------------------------------------- 1 | 2 | // this is put into the global namespace for use by iframes and popups. 3 | // 4 | // iframe: `parent.popupDone(options)` 5 | // popup: `opener.popupDone(options)` 6 | // 7 | // since this couples the DOM with controller methods, it is being isolated 8 | // in this file. 9 | // 10 | // if you want to add more use cases, define a new value for the 'context' 11 | // key of the options argument, and create a new if-clause. also mention 12 | // in a comment which controller is being called. 13 | // 14 | // don't forget to use $scope.$apply() as necessary in the controller method! 15 | // 16 | window.popupDone = function(opts) { 17 | 18 | if (opts.context == 'linkedin-profile') { 19 | // controllers/profile/edit.js 20 | var node = document.querySelector('[ui-view="main"]'); 21 | angular.element(node).scope().finishLinkedinChange(opts.url); 22 | 23 | } else if (opts.context === 'oauth') { 24 | // controllers/user/{Login,Signup}Ctrl.js 25 | var node = document.querySelector('.login-signup-dialog'); 26 | angular.element(node).scope().finishThirdPartyAuth(opts.error); 27 | } 28 | 29 | }; -------------------------------------------------------------------------------- /src/js/app/services/removeTrailingSlash.js: -------------------------------------------------------------------------------- 1 | // remove trailing slashes from paths 2 | var rule = function ($injector, $location) { 3 | var path = $location.url(); 4 | 5 | if (path[path.length - 1] === '/') { 6 | return path.substring(0, path.length - 1); 7 | } 8 | 9 | if (path.indexOf('/?') > -1) { 10 | return path.replace('/?', '?'); 11 | } 12 | }; 13 | 14 | module.exports = function (angularModule) { 15 | angularModule.config(function ($urlRouterProvider) { 16 | $urlRouterProvider.rule(rule); 17 | }); 18 | }; -------------------------------------------------------------------------------- /src/js/app/services/webViewJavascriptBridge.js: -------------------------------------------------------------------------------- 1 | /* usage: 2 | * var connectWebViewJavascriptBridge = require('webViewJavascriptBridge') 3 | * connectWebViewJavascriptBridge(function(bridge) { 4 | * bridge.send(...) 5 | * } 6 | */ 7 | 8 | module.exports = function (callback) { 9 | if (window.WebViewJavascriptBridge) { 10 | callback(window.WebViewJavascriptBridge) 11 | } else { 12 | document.addEventListener('WebViewJavascriptBridgeReady', function () { 13 | callback(window.WebViewJavascriptBridge) 14 | }, false) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | 2 | // because i use this so damn often, let's just make it a global. 3 | // at least until we get ES6 template strings up in here 4 | window.format = require('util').format 5 | 6 | require('./onload') 7 | 8 | // Bower components 9 | require('afkl-lazy-image') 10 | require('angular-animate') 11 | require('angular-growl') 12 | require('angular-resource') 13 | require('angular-sanitize') 14 | require('angular-touch') 15 | require('angular-ui-bootstrap-bower/ui-bootstrap') 16 | require('angular-ui-bootstrap-bower/ui-bootstrap-tpls') 17 | require('angular-ui-router') 18 | require('angulartics') 19 | require('bootstrap-ui-datetime-picker/dist/datetime-picker') 20 | require('gsap/src/uncompressed/TweenLite') 21 | require('gsap/src/uncompressed/plugins/CSSPlugin') 22 | // // require('gsap/src/uncompressed/easing/EasePack') 23 | require('ment.io') 24 | require('ng-tags-input') 25 | require('ngInfiniteScroll') 26 | 27 | // Manually-installed components 28 | require('./angular/angulartics-segmentio') 29 | 30 | require('./app/index') 31 | 32 | if (window.hyloEnv.environment !== 'test') { 33 | angular.element(document).ready(function () { 34 | angular.bootstrap(document.getElementsByTagName('html')[0], ['hyloApp'], {strictDi: true}) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/js/subscribe/index.js: -------------------------------------------------------------------------------- 1 | require('angular-resource') 2 | 3 | var app = angular.module('subscribeApp', ['ngResource']) 4 | 5 | app.factory('Subscription', function ($resource) { 6 | return $resource('/noo/subscription/:id') 7 | }) 8 | 9 | app.controller('BaseCtrl', function ($scope, Subscription) { 10 | var params = require('querystring').parse(window.location.search.replace(/^\?/, '')) 11 | var panelLabel = 'Start 30-Day Free Trial' 12 | var planId, amount 13 | 14 | switch (params.plan) { 15 | case '50x': 16 | planId = 'basic-no-trial' 17 | panelLabel = 'Subscribe' 18 | break 19 | case '50': 20 | planId = 'basic' 21 | break 22 | case '100': 23 | planId = 'hylo-basic-100' 24 | break 25 | case '150': 26 | planId = 'hylo-basic-150' 27 | break 28 | case '200': 29 | planId = 'hylo-basic-200' 30 | break 31 | case '250': 32 | planId = 'hylo-basic-250' 33 | break 34 | case '300': 35 | planId = 'hylo-basic-300' 36 | break 37 | } 38 | 39 | amount = parseInt(params.plan, 10) 40 | 41 | var handler = window.StripeCheckout.configure({ 42 | key: window.stripePublishableKey, 43 | image: '//d3ngex8q79bk55.cloudfront.net/misc/hylo-logo-white-on-teal-circle.png', 44 | token: function (token) { 45 | Subscription.save({planId: planId, token: token}, function (resp) { 46 | $scope.success = true 47 | }) 48 | } 49 | }) 50 | 51 | $scope.startStripe = function () { 52 | handler.open({ 53 | name: 'Hylo', 54 | description: 'Basic Plan ($' + amount + '/month)', 55 | panelLabel: panelLabel, 56 | allowRememberMe: false 57 | }) 58 | } 59 | }) 60 | -------------------------------------------------------------------------------- /templateEnv.js: -------------------------------------------------------------------------------- 1 | var format = require('util').format; 2 | 3 | module.exports = function(env) { 4 | 5 | if (env === 'development') { 6 | process.env.BUNDLE_VERSION = 'dev'; 7 | } 8 | 9 | var rootPath = format('%s/assets/%s', 10 | process.env.ASSET_HOST_URL, 11 | process.env.BUNDLE_VERSION); 12 | 13 | return { 14 | environment: env, 15 | rootPath: rootPath, 16 | imageUrl: function(path) { 17 | return format('%s/img/%s', rootPath, path); 18 | }, 19 | assetUrl: function(path) { 20 | if (env !== 'development' && path.match(/bundle\.(js|css)/)) { 21 | path = path.replace(/\.(js|css)$/, '.min.$1'); 22 | } 23 | return format('%s/%s', rootPath, path); 24 | } 25 | }; 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /test/bundle.tests.js: -------------------------------------------------------------------------------- 1 | describe('bundle', function() { 2 | 3 | it('loads angular', function() { 4 | expect(typeof(angular)).to.equal('object'); 5 | }); 6 | 7 | }); -------------------------------------------------------------------------------- /test/features/CommunityCtrl.tests.js: -------------------------------------------------------------------------------- 1 | var ctrlFn = require('../../src/js/app/controllers/community/CommunityCtrl'); 2 | 3 | describe('CommunityCtrl', function() { 4 | var scope, community, $controller; 5 | 6 | beforeEach(function() { 7 | var mod = angular.module('test', []); 8 | ctrlFn(mod); 9 | 10 | angular.mock.module('test'); 11 | 12 | angular.mock.inject(function($rootScope, _$controller_) { 13 | scope = $rootScope.$new(); 14 | $controller = _$controller_; 15 | community = {id: 1, name: 'hello', slug: 'hello'}; 16 | }); 17 | }); 18 | 19 | it('sets the community in its scope', function() { 20 | var ctrl = $controller('CommunityCtrl', { 21 | $scope: scope, 22 | $analytics: {eventTrack: function() {}}, 23 | community: community, 24 | currentUser: {canModerate: function() { return true; }} 25 | }); 26 | expect(scope.community).to.equal(community); 27 | }); 28 | 29 | it('sets canModerate', function() { 30 | var ctrl = $controller('CommunityCtrl', { 31 | $scope: scope, 32 | $analytics: {eventTrack: function() {}}, 33 | community: community, 34 | currentUser: {canModerate: function() { return true; }} 35 | }); 36 | expect(scope.canModerate).to.be.true; 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/features/routes.tests.js: -------------------------------------------------------------------------------- 1 | var routesFn = require('../../src/js/app/routes'); 2 | var onboarding 3 | 4 | describe('routes', function() { 5 | 6 | var $rootScope, $state, $injector, $q; 7 | 8 | describe('for a community', function() { 9 | 10 | var community, deferred; 11 | 12 | beforeEach(function() { 13 | 14 | community = {id: 1, name: 'hello', slug: 'hello'}; 15 | 16 | var mod = angular.module('test', ['ui.router']); 17 | mod.config(function($locationProvider) { 18 | $locationProvider.html5Mode(true); 19 | }); 20 | 21 | routesFn(mod); 22 | angular.mock.module('test', function($provide) { 23 | $provide.value('Community', { 24 | get: function() { 25 | deferred = $q.defer(); 26 | return {$promise: deferred.promise}; 27 | } 28 | }); 29 | 30 | $provide.value('CurrentUser', { 31 | load: function() {} 32 | }); 33 | 34 | $provide.value('Onboarding', function() {}); 35 | }); 36 | 37 | inject(function(_$rootScope_, _$state_, _$injector_, $templateCache, _$q_) { 38 | $rootScope = _$rootScope_; 39 | $state = _$state_; 40 | $injector = _$injector_; 41 | $q = _$q_; 42 | 43 | $templateCache.put('/ui/shared/main.tpl.html', ''); 44 | $templateCache.put('/ui/community/base.tpl.html', ''); 45 | $templateCache.put('/ui/community/about.tpl.html', ''); 46 | }); 47 | }); 48 | 49 | it('matches a community', function() { 50 | expect($state.href('community.about', {community: 'foo'})).to.equal('/c/foo/about'); 51 | }); 52 | 53 | it('resolves the community object', function() { 54 | $state.go('community.about', {community: 'foo'}); 55 | deferred.resolve(community); 56 | $rootScope.$apply(); 57 | expect($state.$current.name).to.equal('community.about'); 58 | 59 | // Call invoke to inject dependencies and run function 60 | var promise = $injector.invoke($state.$current.parent.parent.resolve.community); 61 | expect(promise).to.equal(deferred.promise); 62 | }); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/services/RichText.tests.js: -------------------------------------------------------------------------------- 1 | var RichText = require('../../src/js/app/services/RichText'); 2 | 3 | describe('RichText', function() { 4 | 5 | it("linkifies a link with a '#' correctly", function() { 6 | var input = '

http://www.foo.com/bar/#baz

', 7 | expected = '

http://www.foo.com/bar/#baz

', 8 | output = RichText.present(input); 9 | 10 | expect(output).to.equal(expected); 11 | }); 12 | 13 | it('links hashtags', function() { 14 | var input = '

and #foo

', 15 | expected = '

and #foo

', 16 | output = RichText.present(input); 17 | 18 | expect(output).to.equal(expected); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /test/services/removeTrailingSlash.tests.js: -------------------------------------------------------------------------------- 1 | var removeTrailingSlash; 2 | 3 | var mockModule = { 4 | config: function(args) { 5 | // args[0] is annotation, args[1] is function 6 | args[1]({ 7 | rule: function(method) { 8 | removeTrailingSlash = method; 9 | } 10 | }) 11 | } 12 | }; 13 | 14 | require('../../src/js/app/services/removeTrailingSlash')(mockModule); 15 | 16 | // mock the $location service 17 | var location = function(path) { 18 | return { 19 | url: function() { return path; } 20 | }; 21 | }; 22 | 23 | describe('removeTrailingSlash', function() { 24 | 25 | it('removes the slash from a simple path', function() { 26 | var output = removeTrailingSlash(null, location('/foo/bar/')) 27 | expect(output).to.equal('/foo/bar'); 28 | }); 29 | 30 | it('removes the slash from a path with query parameters', function() { 31 | var output = removeTrailingSlash(null, location('/foo/bar/?baz=bonk')); 32 | expect(output).to.equal('/foo/bar?baz=bonk'); 33 | }); 34 | 35 | it('returns null if no change is necessary', function() { 36 | var output = removeTrailingSlash(null, location('/foo/bar')); 37 | expect(output).to.be.undefined; 38 | }); 39 | 40 | }); -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | require('angular'); 3 | require('angular-mocks'); 4 | 5 | window.hyloEnv = { 6 | isProd: false, 7 | environment: 'test', 8 | version: 'test', 9 | s3: { 10 | bucket: 'hylo-testing', 11 | cloudfrontHost: 'hylo-testing.s3.amazonaws.com' 12 | }, 13 | fb: {appId: 'fakeId'}, 14 | rollbar: {token: 'fakeToken'}, 15 | segment: {key: 'fakeKey'}, 16 | filepicker: {key: 'fakeKey'}, 17 | onUser: function(fn) { 18 | hyloEnv.onUserCallbacks.push(fn); 19 | }, 20 | provideUser: function(user) { 21 | _.each(hyloEnv.onUserCallbacks, function(fn, i) { 22 | fn.call(user, user); 23 | }); 24 | }, 25 | onUserCallbacks: [] 26 | }; 27 | 28 | require('../src/js/index'); 29 | --------------------------------------------------------------------------------