From 5c8c7fff1d9b93d24e6a29b05e3d2d1080866c58 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Sat, 5 Apr 2014 17:05:03 -0700 Subject: [PATCH] Starting work on simple recruitment listings. Moved Treema and the JS parts of Bootstrap to bower. --- app/locale/en.coffee | 5 +- app/styles/account/settings.sass | 5 +- app/styles/employers.sass | 15 + app/templates/account/job_profile.jade | 8 + app/templates/account/settings.jade | 6 + app/templates/employers.jade | 26 + app/views/account/job_profile_view.coffee | 53 + app/views/account/settings_view.coffee | 21 +- app/views/editor/level/home.coffee | 4 +- app/views/employers_view.coffee | 46 + app/views/kinds/SearchView.coffee | 2 +- bower.json | 24 +- config.coffee | 14 +- package.json | 1 - server/commons/schemas.coffee | 3 + server/users/user_handler.coffee | 4 +- server/users/user_schema.coffee | 35 + server_setup.coffee | 4 +- vendor/scripts/bootstrap/affix.js | 126 - vendor/scripts/bootstrap/alert.js | 98 - vendor/scripts/bootstrap/bootstrap.js | 12 - vendor/scripts/bootstrap/button.js | 115 - vendor/scripts/bootstrap/carousel.js | 217 -- vendor/scripts/bootstrap/collapse.js | 179 -- vendor/scripts/bootstrap/dropdown.js | 154 - vendor/scripts/bootstrap/modal.js | 246 -- vendor/scripts/bootstrap/popover.js | 117 - vendor/scripts/bootstrap/scrollspy.js | 158 - vendor/scripts/bootstrap/tab.js | 135 - vendor/scripts/bootstrap/tooltip.js | 386 --- vendor/scripts/bootstrap/transition.js | 56 - vendor/scripts/treema.js | 3430 --------------------- vendor/styles/treema.css | 299 -- 33 files changed, 250 insertions(+), 5754 deletions(-) create mode 100644 app/styles/employers.sass create mode 100644 app/templates/account/job_profile.jade create mode 100644 app/views/account/job_profile_view.coffee delete mode 100644 vendor/scripts/bootstrap/affix.js delete mode 100644 vendor/scripts/bootstrap/alert.js delete mode 100644 vendor/scripts/bootstrap/bootstrap.js delete mode 100644 vendor/scripts/bootstrap/button.js delete mode 100644 vendor/scripts/bootstrap/carousel.js delete mode 100644 vendor/scripts/bootstrap/collapse.js delete mode 100644 vendor/scripts/bootstrap/dropdown.js delete mode 100644 vendor/scripts/bootstrap/modal.js delete mode 100644 vendor/scripts/bootstrap/popover.js delete mode 100644 vendor/scripts/bootstrap/scrollspy.js delete mode 100644 vendor/scripts/bootstrap/tab.js delete mode 100644 vendor/scripts/bootstrap/tooltip.js delete mode 100644 vendor/scripts/bootstrap/transition.js delete mode 100644 vendor/scripts/treema.js delete mode 100644 vendor/styles/treema.css diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 7d35d42aa..cd6367464 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -616,7 +616,7 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr bad_input: "Bad input." server_error: "Server error." unknown: "Unknown error." - + resources: your_sessions: "Your Sessions" level: "Level" @@ -626,4 +626,5 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr facebook_friend_sessions: "Facebook Friend Sessions" gplus_friends: "G+ Friends" gplus_friend_sessions: "G+ Friend Sessions" - leaderboard: 'leaderboard' \ No newline at end of file + leaderboard: "Leaderboard" + user_schema: "User Schema" diff --git a/app/styles/account/settings.sass b/app/styles/account/settings.sass index 8751e59ef..7c41cf895 100644 --- a/app/styles/account/settings.sass +++ b/app/styles/account/settings.sass @@ -37,4 +37,7 @@ font-size: 12px .form - max-width: 600px \ No newline at end of file + max-width: 600px + + #job-profile-treema + background-color: white diff --git a/app/styles/employers.sass b/app/styles/employers.sass new file mode 100644 index 000000000..a95b126dc --- /dev/null +++ b/app/styles/employers.sass @@ -0,0 +1,15 @@ +#employers-view + .tablesorter + //img + // display: none + + .tablesorter-header + cursor: pointer + &:hover + color: black + + .tablesorter-headerAsc + background-color: #cfc + + .tablesorter-headerDesc + background-color: #ccf diff --git a/app/templates/account/job_profile.jade b/app/templates/account/job_profile.jade new file mode 100644 index 000000000..74b9f61c4 --- /dev/null +++ b/app/templates/account/job_profile.jade @@ -0,0 +1,8 @@ +h3(data-i18n="account_settings.job_profile") Job Profile + +if me.get('jobProfileApproved') + p.lead(data-i18n="account_settings.job_profile_approved") Your job profile has been approved by CodeCombat. +else + p.lead(data-i18n="account_settings.job_profile_explanation") Fill this out, and we will try to find you a job, and stuff. + +#job-profile-treema \ No newline at end of file diff --git a/app/templates/account/settings.jade b/app/templates/account/settings.jade index 91b533b1b..435e140dc 100644 --- a/app/templates/account/settings.jade +++ b/app/templates/account/settings.jade @@ -21,6 +21,9 @@ block content a(href="#password-pane", data-toggle="tab", data-i18n="account_settings.password_tab") Password li a(href="#email-pane", data-toggle="tab", data-i18n="account_settings.emails_tab") Emails + if me.isAdmin() + li + a(href="#job-profile-pane", data-toggle="tab", data-i18n="account_settings.job_profile_tab") Job Profile .tab-content#settings-panes #general-pane.tab-pane @@ -153,3 +156,6 @@ block content span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments. button.btn#toggle-all-button(data-i18n="account_settings.email_toggle") Toggle All + + #job-profile-pane.tab-pane + #job-profile-view diff --git a/app/templates/employers.jade b/app/templates/employers.jade index 485d21622..e22a05cfb 100644 --- a/app/templates/employers.jade +++ b/app/templates/employers.jade @@ -32,3 +32,29 @@ block content h4 Skill: from interns and entry level to senior developers and management h4 Technologies: just about everything h4 Countries: USA, Canada, Australia, and many more + + p If you click on a profile, you can see: + ul + li extra links + li extra skill tags + li self description + li work experience description: list of company name, job title, dates + li educational experience: list of school name, degree & major, dates + li our notes + li links to CodeCombat code in action + li contact modal link + li visa needs + li more location + li projects: list of pic, name, description, link (like http://www.folyo.me/designers/stanislav-udotov) + + table.table.table-condensed.table-hover.table-responsive.tablesorter + thead + tr + th Name + th Location + th Looking For + // Remote, Full-time, Part-time, Contracting, Internship + th Top 5 Skills + th Yrs Exp + th Last Updated + th Current Job diff --git a/app/views/account/job_profile_view.coffee b/app/views/account/job_profile_view.coffee new file mode 100644 index 000000000..0658f58fc --- /dev/null +++ b/app/views/account/job_profile_view.coffee @@ -0,0 +1,53 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/account/job_profile' +{me} = require('lib/auth') + +module.exports = class JobProfileView extends CocoView + id: 'job-profile-view' + template: template + + editableSettings: [ + 'lookingFor', 'active', 'name', 'city', 'country', 'skills', 'experience', 'shortDescription', 'longDescription', + 'work', 'education', 'visa', 'projects', 'links' + ] + readOnlySettings: [ + 'updated' + ] + + constructor: (options) -> + super options + unless me.schema().loaded + @addSomethingToLoad("user_schema") + @listenToOnce me, 'schema-loaded', => @somethingLoaded 'user_schema' + + afterRender: -> + super() + return if @loading() + @buildJobProfileTreema() + + buildJobProfileTreema: -> + visibleSettings = @editableSettings.concat @readOnlySettings + data = _.pick (me.get('jobProfile') ? {}), (value, key) => key in visibleSettings + data.name ?= (me.get('firstName') + ' ' + me.get('lastName')).trim() if me.get('firstName') + schema = _.cloneDeep me.schema().get('properties').jobProfile + schema.properties = _.pick schema.properties, (value, key) => key in visibleSettings + schema.required = _.intersection schema.required, visibleSettings + for prop in @readOnlySettings + schema.properties[prop].readOnly = true + treemaOptions = + filePath: "db/user/#{me.id}" + schema: schema + data: data + callbacks: {change: @onJobProfileChanged} + + @jobProfileTreema = @$el.find('#job-profile-treema').treema treemaOptions + @jobProfileTreema.build() + @jobProfileTreema.open() + + onJobProfileChanged: (e) => + @hasEditedProfile = true + @trigger 'change' + + getData: -> + return {} unless me.get('jobProfile') or @hasEditedProfile + _.pick @jobProfileTreema.data, (value, key) => key in @editableSettings diff --git a/app/views/account/settings_view.coffee b/app/views/account/settings_view.coffee index df815b3cb..274773ed2 100644 --- a/app/views/account/settings_view.coffee +++ b/app/views/account/settings_view.coffee @@ -5,6 +5,7 @@ forms = require('lib/forms') User = require('models/User') WizardSettingsView = require './wizard_settings_view' +JobProfileView = require './job_profile_view' module.exports = class SettingsView extends View id: 'account-settings-view' @@ -45,9 +46,14 @@ module.exports = class SettingsView extends View ) @chooseTab(location.hash.replace('#','')) - WizardSettingsView = new WizardSettingsView() - @listenTo(WizardSettingsView, 'change', @save) - @insertSubView WizardSettingsView + + wizardSettingsView = new WizardSettingsView() + @listenTo wizardSettingsView, 'change', @save + @insertSubView wizardSettingsView + + @jobProfileView = new JobProfileView() + @listenTo @jobProfileView, 'change', @save + @insertSubView @jobProfileView chooseTab: (category) -> id = "##{category}-pane" @@ -129,3 +135,12 @@ module.exports = class SettingsView extends View permissions = [] permissions.push 'admin' if adminCheckbox.prop('checked') me.set('permissions', permissions) + + jobProfile = me.get('jobProfile') ? {} + updated = false + for key, val of @jobProfileView.getData() + updated = updated or jobProfile[key] isnt val + jobProfile[key] = val + if updated + jobProfile.updated = new Date() # doesn't work + me.set 'jobProfile', jobProfile diff --git a/app/views/editor/level/home.coffee b/app/views/editor/level/home.coffee index ffb1a5ac9..247c9d3ff 100644 --- a/app/views/editor/level/home.coffee +++ b/app/views/editor/level/home.coffee @@ -1,8 +1,8 @@ SearchView = require 'views/kinds/SearchView' -module.exports = class ThangTypeHomeView extends SearchView +module.exports = class EditorSearchView extends SearchView id: "editor-level-home-view" modelLabel: 'Level' model: require 'models/Level' modelURL: '/db/level' - tableTemplate: require 'templates/editor/level/table' \ No newline at end of file + tableTemplate: require 'templates/editor/level/table' diff --git a/app/views/employers_view.coffee b/app/views/employers_view.coffee index d5e2eeb2a..331e0f36e 100644 --- a/app/views/employers_view.coffee +++ b/app/views/employers_view.coffee @@ -4,3 +4,49 @@ template = require 'templates/employers' module.exports = class EmployersView extends View id: "employers-view" template: template + + afterRender: -> + @sortTable() + super() + + sortTable: -> + # http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html + $.extend $.tablesorter.themes.bootstrap, + # these classes are added to the table. To see other table classes available, + # look here: http://twitter.github.com/bootstrap/base-css.html#tables + table: "table table-bordered" + caption: "caption" + header: "bootstrap-header" # give the header a gradient background + footerRow: "" + footerCells: "" + icons: "" # add "icon-white" to make them white; this icon class is added to the in the header + sortNone: "bootstrap-icon-unsorted" + sortAsc: "icon-chevron-up" # glyphicon glyphicon-chevron-up" # we are still using v2 icons + sortDesc: "icon-chevron-down" # glyphicon-chevron-down" # we are still using v2 icons + active: "" # applied when column is sorted + hover: "" # use custom css here - bootstrap class may not override it + filterRow: "" # filter row class + even: "" # odd row zebra striping + odd: "" # even row zebra striping + + # call the tablesorter plugin and apply the uitheme widget + @$el.find(".tablesorter").tablesorter( + theme: "bootstrap" + widthFixed: true + headerTemplate: "{content} {icon}" + # widget code contained in the jquery.tablesorter.widgets.js file + # use the zebra stripe widget if you plan on hiding any rows (filter widget) + widgets: [ + "uitheme" + "zebra" + ] + widgetOptions: + # using the default zebra striping class name, so it actually isn't included in the theme variable above + # this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden + zebra: [ + "even" + "odd" + ] + # reset filters button + filter_reset: ".reset" + ) diff --git a/app/views/kinds/SearchView.coffee b/app/views/kinds/SearchView.coffee index f49eb4994..5f93924c3 100644 --- a/app/views/kinds/SearchView.coffee +++ b/app/views/kinds/SearchView.coffee @@ -8,7 +8,7 @@ class SearchCollection extends Backbone.Collection @url = "#{modelURL}/search?project=true" @url += "&term=#{term}" if @term -module.exports = class ThangTypeHomeView extends View +module.exports = class SearchView extends View template: template className: 'search-view' diff --git a/bower.json b/bower.json index e73834bc5..c0a2fa27d 100644 --- a/bower.json +++ b/bower.json @@ -36,7 +36,10 @@ "underscore.string": "~2.3.3", "firebase": "~1.0.2", "catiline": "~2.9.3", - "d3": "~3.4.4" + "d3": "~3.4.4", + "jquery.tablesorter": "~2.15.13", + "treema": "~0.0.1", + "bootstrap": "~3.1.1" }, "overrides": { "backbone": { @@ -50,6 +53,25 @@ }, "underscore.string": { "main": "lib/underscore.string.js" + }, + "jquery.tablesorter": { + "main": [ + "js/jquery.tablesorter.js", + "js/jquery.tablesorter.widgets.js", + "css/theme.bootstrap.css" + ] + }, + "bootstrap": { + "main": [ + "./dist/js/bootstrap.js", + "./dist/fonts/glyphicons-halflings-regular.eot", + "./dist/fonts/glyphicons-halflings-regular.svg", + "./dist/fonts/glyphicons-halflings-regular.ttf", + "./dist/fonts/glyphicons-halflings-regular.woff" + ] } + }, + "resolutions": { + "jquery": "~2.0.3" } } diff --git a/config.coffee b/config.coffee index 1a860cb76..7e600735e 100644 --- a/config.coffee +++ b/config.coffee @@ -45,17 +45,7 @@ exports.config = 'bower_components/lodash/dist/lodash.js' 'bower_components/backbone/backbone.js' # Twitter Bootstrap jquery plugins - 'vendor/scripts/bootstrap/transition.js' - 'vendor/scripts/bootstrap/affix.js' - 'vendor/scripts/bootstrap/alert.js' - 'vendor/scripts/bootstrap/button.js' - 'vendor/scripts/bootstrap/carousel.js' - 'vendor/scripts/bootstrap/collapse.js' - 'vendor/scripts/bootstrap/dropdown.js' - 'vendor/scripts/bootstrap/modal.js' - 'vendor/scripts/bootstrap/scrollspy.js' - 'vendor/scripts/bootstrap/tab.js' - 'vendor/scripts/bootstrap/tooltip.js' + 'bower_components/bootstrap/dist/bootstrap.js' # CreateJS dependencies 'vendor/scripts/easeljs-NEXT.combined.js' 'vendor/scripts/preloadjs-NEXT.combined.js' @@ -70,7 +60,7 @@ exports.config = stylesheets: defaultExtension: 'sass' joinTo: - 'stylesheets/app.css': /^(app|vendor)/ + 'stylesheets/app.css': /^(app|vendor|bower_components)/ order: before: ['app/styles/bootstrap.scss'] templates: diff --git a/package.json b/package.json index 665ac0131..fc229cd02 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "css-brunch": "> 1.0 < 1.8", "jade-brunch": "> 1.0 < 1.8", "uglify-js-brunch": "~1.7.4", - "clean-css-brunch": "> 1.0 < 1.8", "auto-reload-brunch": "> 1.0 < 1.8", "brunch": "~1.7.4", "jasmine-node": "1.13.x", diff --git a/server/commons/schemas.coffee b/server/commons/schemas.coffee index 060ff8348..e249ef757 100644 --- a/server/commons/schemas.coffee +++ b/server/commons/schemas.coffee @@ -8,6 +8,8 @@ combine = (base, ext) -> return base unless ext? return _.extend(base, ext) +urlPattern = '^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-‌​\.\?\,\'\/\\\+&%\$#_]*)?$' + # Common schema properties me.object = (ext, props) -> combine {type: 'object', additionalProperties: false, properties: props or {}}, ext me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext @@ -16,6 +18,7 @@ me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext) me.date = (ext) -> combine({type: 'string', format: 'date-time'}, ext) # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext) +me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext) PointSchema = me.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]}, x: {title: "x", description: "The x coordinate.", type: "number", "default": 15} diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 168f10d91..c654ce71f 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -13,7 +13,7 @@ LevelSession = require('../levels/sessions/LevelSession') serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset'] privateProperties = [ 'permissions', 'email', 'firstName', 'lastName', 'gender', 'facebookID', - 'gplusID', 'music', 'volume', 'aceConfig' + 'gplusID', 'music', 'volume', 'aceConfig', 'jobProfile', 'jobProfileApproved' ] UserHandler = class UserHandler extends Handler @@ -23,7 +23,7 @@ UserHandler = class UserHandler extends Handler 'name', 'photoURL', 'password', 'anonymous', 'wizardColor1', 'volume', 'firstName', 'lastName', 'gender', 'facebookID', 'gplusID', 'emailSubscriptions', 'testGroupNumber', 'music', 'hourOfCode', 'hourOfCodeComplete', 'preferredLanguage', - 'wizard', 'aceConfig', 'autocastDelay', 'lastLevel' + 'wizard', 'aceConfig', 'autocastDelay', 'lastLevel', 'jobProfile' ] jsonSchema: schema diff --git a/server/users/user_schema.coffee b/server/users/user_schema.coffee index 18d526de5..52259cfc9 100644 --- a/server/users/user_schema.coffee +++ b/server/users/user_schema.coffee @@ -56,6 +56,41 @@ UserSchema = c.object {}, simulatedBy: {type: 'integer', minimum: 0, default: 0} simulatedFor: {type: 'integer', minimum: 0, default: 0} + jobProfile: c.object {title: 'Job Profile', required: ['lookingFor', 'active', 'name', 'city', 'country', 'skills', 'experience', 'shortDescription', 'longDescription', 'visa', 'work', 'education', 'projects', 'links']}, + lookingFor: {title: 'Looking For', type: 'string', enum: ['Full-time', 'Part-time', 'Remote', 'Contracting', 'Internship'], default: 'Full-time', description: 'What kind of developer position do you want?'} + active: {title: 'Active', type: 'boolean', description: 'Want interview offers right now?'} + updated: c.date {title: 'Last Updated', description: 'How fresh your profile appears to employers. The fresher, the better. Profiles go inactive after 30 days.'} + name: c.shortString {title: 'Name', description: 'Name you want employers to see, like "Nick Winter".'} + city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', default: 'Defaultsville, CA'} + country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', default: 'USA'} + skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency. Employers will see the first five at a glance.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true}, + {type: 'string', minLength: 1, maxLength: 20, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"'} + experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'} + shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.', default: 'Programmer seeking to build great software.'} + longDescription: {type: 'string', maxLength: 2000, title: 'Long Description', description: 'Give employeers more details. Highlight your stunning personality. Markdown okay. 2000 characters max.', format: 'markdown', default: '* I write great code.\n* You need great code?\n* Great!'} + visa: c.shortString {title: 'US Work Status', description: 'Are you authorized to work in the US, or do you need visa sponsorship?', enum: ['Authorized to work in the US', 'Need visa sponsorship'], default: 'Authorized to work in the US'} + work: c.array {title: 'Work Experience', description: 'List your relevant work experience.'}, + c.object {title: 'Job', description: 'Some work experience you had.', required: ['employer', 'role', 'duration']}, + employer: c.shortString {title: 'Employer', description: 'Name of your employer.'} + role: c.shortString {title: 'Job Title', description: 'What was your job title or role?'} + duration: c.shortString {title: 'Duration', description: 'When did you hold this gig? Ex.: "Feb 2013 - present".'} + education: c.array {title: 'Education', description: 'List your academic ordeals.'}, + c.object {title: 'Ordeal', description: 'Some education that befell you.', required: ['school', 'degree', 'duration']}, + school: c.shortString {title: 'School', description: 'Name of your school.'} + degree: c.shortString {title: 'Degree', description: 'What was your degree and field of study? Ex. Ph.D. Human-Computer Interaction (incomplete)'} + duration: c.shortString {title: 'Duration', description: 'When? Ex.: "Aug 2004 - May 2008".'} + projects: c.array {title: 'Projects', description: 'Highlight your projects to amaze employers.'}, + c.object {title: 'Project', description: 'A project you created.', required: ['name', 'description', 'picture', 'link']}, + name: c.shortString {title: 'Project Name', description: 'What was the project called?'} + description: {type: 'string', title: 'Description', description: 'Briefly describe the project.', maxLength: 400, format: 'markdown'} + picture: {type: 'string', title: 'Picture', format: 'image-file', description: 'Upload a 300x225px or larger image showing off the project.'} + link: c.url {title: 'Link', description: 'Link to the project.', default: 'http://codecombat.com'} + links: c.array {title: 'Links', description: 'Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.'}, + c.object {title: 'Link', description: 'A link to another site you want to highlight, like your GitHub, your LinkedIn, or your blog.', required: ['name', 'link']}, + name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "Twitter"'} + link: c.url {title: 'Link', description: 'The URL.', default: 'http://codecombat.com'} + jobProfileApproved: {title: 'Job Profile Approved', type: 'boolean', description: 'Whether your profile has been approved by CodeCombat.'} + c.extendBasicProperties UserSchema, 'user' module.exports = UserSchema diff --git a/server_setup.coffee b/server_setup.coffee index c06482a85..d8dcd1c03 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -3,6 +3,7 @@ path = require 'path' authentication = require 'passport' useragent = require 'express-useragent' fs = require 'graceful-fs' +log = require('winston') database = require './server/commons/database' baseRoute = require './server/routes/base' @@ -96,7 +97,8 @@ setupFallbackRouteToIndex = (app) -> auth.loginUser(req, res, user, false, next) sendMain = (req, res) -> - fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err,data) -> + fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) -> + log.error "Error modifying main.html: #{err}" if err # insert the user object directly into the html so the application can have it immediately data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user))) res.send data diff --git a/vendor/scripts/bootstrap/affix.js b/vendor/scripts/bootstrap/affix.js deleted file mode 100644 index 552bffa3f..000000000 --- a/vendor/scripts/bootstrap/affix.js +++ /dev/null @@ -1,126 +0,0 @@ -/* ======================================================================== - * Bootstrap: affix.js v3.0.3 - * http://getbootstrap.com/javascript/#affix - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // AFFIX CLASS DEFINITION - // ====================== - - var Affix = function (element, options) { - this.options = $.extend({}, Affix.DEFAULTS, options) - this.$window = $(window) - .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) - .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) - - this.$element = $(element) - this.affixed = - this.unpin = null - - this.checkPosition() - } - - Affix.RESET = 'affix affix-top affix-bottom' - - Affix.DEFAULTS = { - offset: 0 - } - - Affix.prototype.checkPositionWithEventLoop = function () { - setTimeout($.proxy(this.checkPosition, this), 1) - } - - Affix.prototype.checkPosition = function () { - if (!this.$element.is(':visible')) return - - var scrollHeight = $(document).height() - var scrollTop = this.$window.scrollTop() - var position = this.$element.offset() - var offset = this.options.offset - var offsetTop = offset.top - var offsetBottom = offset.bottom - - if (typeof offset != 'object') offsetBottom = offsetTop = offset - if (typeof offsetTop == 'function') offsetTop = offset.top() - if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() - - var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false : - offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' : - offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false - - if (this.affixed === affix) return - if (this.unpin) this.$element.css('top', '') - - this.affixed = affix - this.unpin = affix == 'bottom' ? position.top - scrollTop : null - - this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : '')) - - if (affix == 'bottom') { - this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() }) - } - } - - - // AFFIX PLUGIN DEFINITION - // ======================= - - var old = $.fn.affix - - $.fn.affix = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.affix') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.affix', (data = new Affix(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.affix.Constructor = Affix - - - // AFFIX NO CONFLICT - // ================= - - $.fn.affix.noConflict = function () { - $.fn.affix = old - return this - } - - - // AFFIX DATA-API - // ============== - - $(window).on('load', function () { - $('[data-spy="affix"]').each(function () { - var $spy = $(this) - var data = $spy.data() - - data.offset = data.offset || {} - - if (data.offsetBottom) data.offset.bottom = data.offsetBottom - if (data.offsetTop) data.offset.top = data.offsetTop - - $spy.affix(data) - }) - }) - -}(jQuery); diff --git a/vendor/scripts/bootstrap/alert.js b/vendor/scripts/bootstrap/alert.js deleted file mode 100644 index 695ad74d0..000000000 --- a/vendor/scripts/bootstrap/alert.js +++ /dev/null @@ -1,98 +0,0 @@ -/* ======================================================================== - * Bootstrap: alert.js v3.0.3 - * http://getbootstrap.com/javascript/#alerts - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // ALERT CLASS DEFINITION - // ====================== - - var dismiss = '[data-dismiss="alert"]' - var Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = $(selector) - - if (e) e.preventDefault() - - if (!$parent.length) { - $parent = $this.hasClass('alert') ? $this : $this.parent() - } - - $parent.trigger(e = $.Event('close.bs.alert')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent.trigger('closed.bs.alert').remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent - .one($.support.transition.end, removeElement) - .emulateTransitionEnd(150) : - removeElement() - } - - - // ALERT PLUGIN DEFINITION - // ======================= - - var old = $.fn.alert - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.alert') - - if (!data) $this.data('bs.alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - // ALERT NO CONFLICT - // ================= - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - // ALERT DATA-API - // ============== - - $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) - -}(jQuery); diff --git a/vendor/scripts/bootstrap/bootstrap.js b/vendor/scripts/bootstrap/bootstrap.js deleted file mode 100644 index fee1bda9d..000000000 --- a/vendor/scripts/bootstrap/bootstrap.js +++ /dev/null @@ -1,12 +0,0 @@ -//= require affix -//= require alert -//= require button -//= require carousel -//= require collapse -//= require dropdown -//= require tab -//= require transition -//= require scrollspy -//= require modal -//= require tooltip -//= require popover diff --git a/vendor/scripts/bootstrap/button.js b/vendor/scripts/bootstrap/button.js deleted file mode 100644 index c9fdde5e4..000000000 --- a/vendor/scripts/bootstrap/button.js +++ /dev/null @@ -1,115 +0,0 @@ -/* ======================================================================== - * Bootstrap: button.js v3.0.3 - * http://getbootstrap.com/javascript/#buttons - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // BUTTON PUBLIC CLASS DEFINITION - // ============================== - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Button.DEFAULTS, options) - } - - Button.DEFAULTS = { - loadingText: 'loading...' - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - var $el = this.$element - var val = $el.is('input') ? 'val' : 'html' - var data = $el.data() - - state = state + 'Text' - - if (!data.resetText) $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d); - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.closest('[data-toggle="buttons"]') - var changed = true - - if ($parent.length) { - var $input = this.$element.find('input') - if ($input.prop('type') === 'radio') { - // see if clicking on current one - if ($input.prop('checked') && this.$element.hasClass('active')) - changed = false - else - $parent.find('.active').removeClass('active') - } - if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') - } - - if (changed) this.$element.toggleClass('active') - } - - - // BUTTON PLUGIN DEFINITION - // ======================== - - var old = $.fn.button - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.button') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.button', (data = new Button(this, options))) - - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.Constructor = Button - - - // BUTTON NO CONFLICT - // ================== - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - // BUTTON DATA-API - // =============== - - $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - e.preventDefault() - }) - -}(jQuery); diff --git a/vendor/scripts/bootstrap/carousel.js b/vendor/scripts/bootstrap/carousel.js deleted file mode 100644 index 6391a36df..000000000 --- a/vendor/scripts/bootstrap/carousel.js +++ /dev/null @@ -1,217 +0,0 @@ -/* ======================================================================== - * Bootstrap: carousel.js v3.0.3 - * http://getbootstrap.com/javascript/#carousel - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // CAROUSEL CLASS DEFINITION - // ========================= - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.paused = - this.sliding = - this.interval = - this.$active = - this.$items = null - - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.DEFAULTS = { - interval: 5000 - , pause: 'hover' - , wrap: true - } - - Carousel.prototype.cycle = function (e) { - e || (this.paused = false) - - this.interval && clearInterval(this.interval) - - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - - return this - } - - Carousel.prototype.getActiveIndex = function () { - this.$active = this.$element.find('.item.active') - this.$items = this.$active.parent().children() - - return this.$items.index(this.$active) - } - - Carousel.prototype.to = function (pos) { - var that = this - var activeIndex = this.getActiveIndex() - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) - if (activeIndex == pos) return this.pause().cycle() - - return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) - } - - Carousel.prototype.pause = function (e) { - e || (this.paused = true) - - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - - this.interval = clearInterval(this.interval) - - return this - } - - Carousel.prototype.next = function () { - if (this.sliding) return - return this.slide('next') - } - - Carousel.prototype.prev = function () { - if (this.sliding) return - return this.slide('prev') - } - - Carousel.prototype.slide = function (type, next) { - var $active = this.$element.find('.item.active') - var $next = next || $active[type]() - var isCycling = this.interval - var direction = type == 'next' ? 'left' : 'right' - var fallback = type == 'next' ? 'first' : 'last' - var that = this - - if (!$next.length) { - if (!this.options.wrap) return - $next = this.$element.find('.item')[fallback]() - } - - this.sliding = true - - isCycling && this.pause() - - var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) - - if ($next.hasClass('active')) return - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - this.$element.one('slid.bs.carousel', function () { - var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) - $nextIndicator && $nextIndicator.addClass('active') - }) - } - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - $active - .one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid.bs.carousel') }, 0) - }) - .emulateTransitionEnd(600) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid.bs.carousel') - } - - isCycling && this.cycle() - - return this - } - - - // CAROUSEL PLUGIN DEFINITION - // ========================== - - var old = $.fn.carousel - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.carousel') - var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) - var action = typeof option == 'string' ? option : options.slide - - if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - $.fn.carousel.Constructor = Carousel - - - // CAROUSEL NO CONFLICT - // ==================== - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - - // CAROUSEL DATA-API - // ================= - - $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { - var $this = $(this), href - var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - var options = $.extend({}, $target.data(), $this.data()) - var slideIndex = $this.attr('data-slide-to') - if (slideIndex) options.interval = false - - $target.carousel(options) - - if (slideIndex = $this.attr('data-slide-to')) { - $target.data('bs.carousel').to(slideIndex) - } - - e.preventDefault() - }) - - $(window).on('load', function () { - $('[data-ride="carousel"]').each(function () { - var $carousel = $(this) - $carousel.carousel($carousel.data()) - }) - }) - -}(jQuery); diff --git a/vendor/scripts/bootstrap/collapse.js b/vendor/scripts/bootstrap/collapse.js deleted file mode 100644 index 1a079938e..000000000 --- a/vendor/scripts/bootstrap/collapse.js +++ /dev/null @@ -1,179 +0,0 @@ -/* ======================================================================== - * Bootstrap: collapse.js v3.0.3 - * http://getbootstrap.com/javascript/#collapse - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // COLLAPSE PUBLIC CLASS DEFINITION - // ================================ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Collapse.DEFAULTS, options) - this.transitioning = null - - if (this.options.parent) this.$parent = $(this.options.parent) - if (this.options.toggle) this.toggle() - } - - Collapse.DEFAULTS = { - toggle: true - } - - Collapse.prototype.dimension = function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - Collapse.prototype.show = function () { - if (this.transitioning || this.$element.hasClass('in')) return - - var startEvent = $.Event('show.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var actives = this.$parent && this.$parent.find('> .panel > .in') - - if (actives && actives.length) { - var hasData = actives.data('bs.collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('bs.collapse', null) - } - - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - .addClass('collapsing') - [dimension](0) - - this.transitioning = 1 - - var complete = function () { - this.$element - .removeClass('collapsing') - .addClass('in') - [dimension]('auto') - this.transitioning = 0 - this.$element.trigger('shown.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - var scrollSize = $.camelCase(['scroll', dimension].join('-')) - - this.$element - .one($.support.transition.end, $.proxy(complete, this)) - .emulateTransitionEnd(350) - [dimension](this.$element[0][scrollSize]) - } - - Collapse.prototype.hide = function () { - if (this.transitioning || !this.$element.hasClass('in')) return - - var startEvent = $.Event('hide.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var dimension = this.dimension() - - this.$element - [dimension](this.$element[dimension]()) - [0].offsetHeight - - this.$element - .addClass('collapsing') - .removeClass('collapse') - .removeClass('in') - - this.transitioning = 1 - - var complete = function () { - this.transitioning = 0 - this.$element - .trigger('hidden.bs.collapse') - .removeClass('collapsing') - .addClass('collapse') - } - - if (!$.support.transition) return complete.call(this) - - this.$element - [dimension](0) - .one($.support.transition.end, $.proxy(complete, this)) - .emulateTransitionEnd(350) - } - - Collapse.prototype.toggle = function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - - // COLLAPSE PLUGIN DEFINITION - // ========================== - - var old = $.fn.collapse - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.collapse') - var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.Constructor = Collapse - - - // COLLAPSE NO CONFLICT - // ==================== - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - // COLLAPSE DATA-API - // ================= - - $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - var target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - var $target = $(target) - var data = $target.data('bs.collapse') - var option = data ? 'toggle' : $this.data() - var parent = $this.attr('data-parent') - var $parent = parent && $(parent) - - if (!data || !data.transitioning) { - if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') - $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - } - - $target.collapse(option) - }) - -}(jQuery); diff --git a/vendor/scripts/bootstrap/dropdown.js b/vendor/scripts/bootstrap/dropdown.js deleted file mode 100644 index 13352ef7c..000000000 --- a/vendor/scripts/bootstrap/dropdown.js +++ /dev/null @@ -1,154 +0,0 @@ -/* ======================================================================== - * Bootstrap: dropdown.js v3.0.3 - * http://getbootstrap.com/javascript/#dropdowns - * ======================================================================== - * Copyright 2013 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ======================================================================== */ - - -+function ($) { "use strict"; - - // DROPDOWN CLASS DEFINITION - // ========================= - - var backdrop = '.dropdown-backdrop' - var toggle = '[data-toggle=dropdown]' - var Dropdown = function (element) { - $(element).on('click.bs.dropdown', this.toggle) - } - - Dropdown.prototype.toggle = function (e) { - var $this = $(this) - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { - // if mobile we use a backdrop because click events don't delegate - $('