├── .gitignore ├── .openshift ├── action_hooks │ ├── build │ ├── deploy │ ├── post_deploy │ ├── pre_build │ └── pre_start_nodejs ├── cron │ ├── README.cron │ ├── daily │ │ └── .gitignore │ ├── hourly │ │ └── .gitignore │ ├── minutely │ │ └── .gitignore │ ├── monthly │ │ └── .gitignore │ └── weekly │ │ ├── README │ │ ├── chrono.dat │ │ ├── chronograph │ │ ├── jobs.allow │ │ └── jobs.deny ├── lib │ ├── init_settings │ ├── setup_custom_nodejs_env │ └── utils └── markers │ ├── NODEJS_VERSION │ └── README ├── .travis.yml ├── AUTHORS.md ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── Vagrantfile ├── app.js ├── docs ├── Makefile └── source │ ├── about.rst │ ├── conf.py │ ├── conv.rst │ ├── deploy.rst │ ├── design.rst │ ├── devel.rst │ ├── index.rst │ └── test.rst ├── lint-config.js ├── models ├── action.js ├── activity.js ├── attachment.js ├── board.js ├── boardMemberRelation.js ├── boardMemberStatus.js ├── card.js ├── cardLabelRelation.js ├── cardSourceRelation.js ├── checklist.js ├── checklistItem.js ├── comment.js ├── commentSourceRelation.js ├── configStatus.js ├── group.js ├── label.js ├── list.js ├── metadata.js ├── notification.js ├── notificationType.js ├── organization.js ├── permission.js ├── roles.js ├── syncConfig.js ├── user.js └── vote.js ├── nodejs-cantas.spec ├── npm-shrinkwrap.json ├── package.json ├── public ├── attachments │ └── .gitignore ├── images │ ├── 404-img.png │ ├── Calendar.png │ ├── Wrench.png │ ├── activetracking.png │ ├── add-2-icon.png │ ├── add.png │ ├── admin.png │ ├── bg-1.png │ ├── bg-grey.png │ ├── bg-white.png │ ├── black-heart.png │ ├── body-bg.png │ ├── btn-delete-hover.png │ ├── btn-delete.png │ ├── btn-edit-hover.png │ ├── btn-edit.png │ ├── cantas-agree-badge.png │ ├── cantas-agree.png │ ├── cantas-agree1.png │ ├── cantas-all.png │ ├── cantas-all1.png │ ├── cantas-assign.png │ ├── cantas-attachment.png │ ├── cantas-check-1.png │ ├── cantas-check.png │ ├── cantas-checklist.png │ ├── cantas-comment.png │ ├── cantas-disabled.png │ ├── cantas-disagree.png │ ├── cantas-disagree1.png │ ├── cantas-due-date.png │ ├── cantas-favicon.svg │ ├── cantas-help-activity.gif │ ├── cantas-help-archivecard.gif │ ├── cantas-help-archivelist.gif │ ├── cantas-help-assign.gif │ ├── cantas-help-board.gif │ ├── cantas-help-card.gif │ ├── cantas-help-checklist.gif │ ├── cantas-help-comment.gif │ ├── cantas-help-import-bugzilla.gif │ ├── cantas-help-import-trello.gif │ ├── cantas-help-invite.gif │ ├── cantas-help-list.gif │ ├── cantas-help-movecard.gif │ ├── cantas-help-movelist.gif │ ├── cantas-help-notification.gif │ ├── cantas-help-private.gif │ ├── cantas-login-header.svg │ ├── cantas-login.png │ ├── cantas-none.png │ ├── cantas-none1.png │ ├── cantas-option.png │ ├── cantas-part.png │ ├── cantas-part1.png │ ├── card-setting-1.png │ ├── delete.png │ ├── description.png │ ├── destroy.png │ ├── devel.png │ ├── eso-footer-logo.png │ ├── favicon.ico │ ├── favicon.png │ ├── footer-logo.png │ ├── footprint.png │ ├── header-logo-eso-developed.png │ ├── header-logo-eso-maintained.png │ ├── icon-archive.png │ ├── icon-invited.png │ ├── icon-owner.png │ ├── icon-time.png │ ├── infomation.png │ ├── information.png │ ├── irc.png │ ├── list-settings-hover.png │ ├── list-settings.png │ ├── loading.gif │ ├── lock.png │ ├── login-header.png │ ├── logo.png │ ├── logo.svg │ ├── man.png │ ├── member.png │ ├── notification.png │ ├── option.png │ ├── progressbar.gif │ ├── public-board.png │ ├── red-heart.png │ ├── search-bg.png │ ├── sent.png │ ├── set.png │ ├── setting.png │ ├── settings.png │ ├── side-bar.png │ ├── stage-server.png │ ├── ucd.png │ └── unlock.png ├── javascripts │ ├── application.js │ ├── constants.js │ ├── models │ │ ├── activity.js │ │ ├── attachment.js │ │ ├── base.js │ │ ├── board.js │ │ ├── boardMember.js │ │ ├── card.js │ │ ├── checklist.js │ │ ├── comment.js │ │ ├── label.js │ │ ├── list.js │ │ ├── notification.js │ │ ├── syncConfig.js │ │ ├── visitor.js │ │ └── vote.js │ ├── router.js │ ├── sortable.js │ ├── utils │ │ ├── safe_string.js │ │ └── utils.js │ ├── vendor │ │ ├── async.js │ │ ├── backbone.iobind.js │ │ ├── backbone.iosync.js │ │ ├── backbone.js │ │ ├── bootstrap.min.js │ │ ├── jade.js │ │ ├── jquery-2.1.1.js │ │ ├── jquery-ui-timepicker-addon.js │ │ ├── jquery-ui.js │ │ ├── jquery.ba-cond.js │ │ ├── jquery.fileupload-image.js │ │ ├── jquery.fileupload-process.js │ │ ├── jquery.fileupload-validate.js │ │ ├── jquery.fileupload.js │ │ ├── jquery.iframe-transport.js │ │ ├── jquery.slitslider.js │ │ ├── jquery.slug.js │ │ ├── load-image.min.js │ │ ├── markdown.js │ │ ├── modernizr.custom.79639.js │ │ ├── moment.js │ │ └── underscore.js │ └── views │ │ ├── accountSettings.js │ │ ├── activity.js │ │ ├── adminConfig.js │ │ ├── app.js │ │ ├── attachment.js │ │ ├── base.js │ │ ├── board.js │ │ ├── boardActiveUsers.js │ │ ├── boardMembers.js │ │ ├── boardlist.js │ │ ├── card.js │ │ ├── cardList.js │ │ ├── checklist.js │ │ ├── comment.js │ │ ├── confirmDialog.js │ │ ├── dashboard-navigation.js │ │ ├── dashboard.js │ │ ├── help.js │ │ ├── infiniteScroll.js │ │ ├── list.js │ │ ├── notification.js │ │ ├── quickSearch.js │ │ ├── search.js │ │ ├── sidebar.js │ │ ├── syncConfig.js │ │ └── welcome.js └── stylesheets │ ├── bootstrap-modal.css │ ├── custom.css │ ├── demo.css │ ├── eso-theme.css │ ├── fonts │ ├── droid-sans-bold.woff │ ├── droid-sans.woff │ ├── font-awesome │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ └── font │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── overpass-bold-wf.woff │ └── overpass-wf.woff │ ├── images │ ├── cantas-welcome-board.png │ ├── cantas-welcome-card-detail.png │ ├── cantas-welcome-card.png │ └── cantas-welcome-list.png │ ├── jquery-fileupload-ui.css │ ├── project.css │ └── style.css ├── routes └── index.js ├── scripts ├── addUser.js ├── cantas-init.sh ├── cantas-migration-to-0.3.sh ├── cantas.rc ├── lintcheck └── migrations │ ├── initCommentStatusForBoards.js │ ├── initLabelMetadata.js │ ├── initVoteStatusForBoards.js │ ├── migrateAddLabelsToBoards.js │ ├── migrateAddLabelsToCards.js │ ├── migrationCardAddBoardId.js │ ├── migrationCardAssigneesChanged.js │ ├── migrationChecklistItemAddCardId.js │ ├── v1.0.2-v1.1-down.js │ └── v1.0.2-v1.1-up.js ├── services ├── activity.js ├── auth │ ├── index.js │ ├── remoteUserStrategy.js │ ├── strategies.js │ └── utils.js ├── boardHandler.js ├── bugzilla.js ├── cardHandler.js ├── importTrello.js ├── infra.js ├── integration │ └── bz-API.js ├── mail.js ├── notification.js ├── signal.js ├── sites.js └── utils.js ├── settings.js ├── settings.json.example ├── sockets ├── boardMembership.js ├── boardMove.js ├── crud │ ├── activity.js │ ├── attachment.js │ ├── base.js │ ├── board.js │ ├── boardMemberRelation.js │ ├── card.js │ ├── cardLabelRelation.js │ ├── checklist.js │ ├── checklistItem.js │ ├── comment.js │ ├── index.js │ ├── list.js │ ├── notification.js │ ├── syncConfig.js │ └── vote.js ├── importTrello.js ├── index.js ├── patch_socket.js ├── room.js ├── signalHandlers.js ├── signals.js ├── signals_registration.js ├── stdlib.js └── syncBugzilla.js ├── sonar-project.properties ├── spec ├── helpers │ ├── jade.js │ └── jasmine-jquery.js ├── javascripts │ ├── boardView.spec.js │ ├── card.spec.js │ ├── cardVote.spec.js │ ├── checkEmailAddress.spec.js │ ├── checklistItem.spec.js │ ├── fixtures │ │ ├── adminConfig.html │ │ ├── boardView.html │ │ ├── cardBadges.html │ │ ├── cardDetail.html │ │ ├── cardVote.html │ │ ├── checklistItem.html │ │ ├── labelNeonlights.html │ │ ├── movelist.html │ │ ├── permissionWidget.html │ │ ├── timeout.html │ │ └── unselectLabel.html │ ├── labelNeonlights.spec.js │ ├── movelist.spec.js │ ├── permissionConfig.spec.js │ ├── sycnCardBadges.spec.js │ ├── timeout.spec.js │ └── unselectLabel.spec.js └── node │ ├── activity-test.js │ ├── boardHandler-test.js │ ├── boardMove-test.js │ ├── boardmemberrelation-test.js │ ├── checklistItem-test.js │ ├── comment-test.js │ ├── dbinit.js │ ├── invitation-test.js │ ├── mail-test.js │ ├── service-signal-test.js │ ├── service-strategy-test.js │ ├── service-utils-test.js │ ├── services-auth-test.js │ ├── socket-patch-test.js │ ├── user-test.js │ ├── utils.js │ └── vote-test.js ├── uploads └── .gitignore └── views ├── 404.jade ├── application.jade ├── backbone ├── accountSettings.jade ├── admin-config.jade ├── archived-item.jade ├── archived.jade ├── board-list.jade ├── board-title.jade ├── board.jade ├── card-add.jade ├── card-assign.jade ├── card-attachment-download.jade ├── card-attachment-upload.jade ├── card-attachment.jade ├── card-checkitem.jade ├── card-checklist.jade ├── card-detail.jade ├── card-due-date.jade ├── card-labels-neonlights.jade ├── card-list.jade ├── card-menu.jade ├── card-vote.jade ├── card-votes-total.jade ├── card.jade ├── comment-item.jade ├── comment.jade ├── dashboard-navigation.jade ├── dashboard.jade ├── help.jade ├── import-bugzilla.jade ├── label-assign.jade ├── list-menu.jade ├── list.jade ├── move-list-item.jade ├── move-list.jade ├── notification-item.jade ├── notification-menu.jade ├── permission-widget.jade ├── quick-search.jade ├── search.jade ├── sidebar.jade ├── syncconfig-item-edit.jade ├── syncconfig-item-input.jade ├── syncconfig-item.jade └── welcome.jade ├── email ├── assign.jade ├── comment.jade ├── editComment.jade ├── invitation.jade └── notification.jade ├── layout.jade ├── login.jade └── standalone-help.jade /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | *.rdb 5 | npm-debug.log 6 | _SpecRunner.html 7 | *~ 8 | *.swp 9 | public/javascripts/dist/*.js 10 | public/stylesheets/*.min.css 11 | .grunt 12 | redhat 13 | settings.json 14 | .tags 15 | tags 16 | .vagrant/ 17 | -------------------------------------------------------------------------------- /.openshift/action_hooks/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple build script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the deploy step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | # Setup path to include the custom Node[.js] version. 12 | _SHOW_SETUP_PATH_MESSAGES="true" setup_path_for_custom_node_version 13 | 14 | 15 | # we moved the package.json file out of the way in pre_build, 16 | # so that the OpenShift git post-receive hook doesn't try and use the 17 | # old npm version to install the dependencies. Move it back in. 18 | tmp_package_json="$(get_node_tmp_dir)/package.json" 19 | if [ -f "$tmp_package_json" ]; then 20 | # Only overlay it if there is no current package.json file. 21 | [ -f "${OPENSHIFT_REPO_DIR}/package.json" ] || \ 22 | mv "$tmp_package_json" "${OPENSHIFT_REPO_DIR}/package.json" 23 | fi 24 | 25 | 26 | # Do npm install with the new npm binary. 27 | if [ -f "${OPENSHIFT_REPO_DIR}"/package.json ]; then 28 | echo " - Installing dependencies w/ new version of npm ... " 29 | echo 30 | (cd "${OPENSHIFT_REPO_DIR}"; export TMPDIR="/tmp"; npm install -d) 31 | fi 32 | 33 | # Do grunt css and js minified. 34 | if [ -f "${OPENSHIFT_REPO_DIR}"/package.json ]; then 35 | echo " - Minified css js files ... " 36 | echo 37 | (cd "${OPENSHIFT_REPO_DIR}"; export TMPDIR="/tmp"; npm install -g grunt-cli; grunt) 38 | fi -------------------------------------------------------------------------------- /.openshift/action_hooks/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This deploy hook gets executed after dependencies are resolved and the 3 | # build hook has been run but before the application has been started back 4 | # up again. This script gets executed directly, so it could be python, php, 5 | # ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | 12 | # On slave/serving gears, need to do the install as part of deploy 13 | # so check if its needed. Just ensure the custom Node[.js] version is 14 | # installed. 15 | ensure_node_is_installed 16 | 17 | 18 | # Setup the settings.json file 19 | node "$OPENSHIFT_REPO_DIR/.openshift/lib/init_settings" 20 | 21 | 22 | # Seed the label metadata 23 | node "$OPENSHIFT_REPO_DIR/scripts/migrations/initLabelMetadata.js" 24 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple post deploy hook executed after your application 3 | # is deployed and started. This script gets executed directly, so 4 | # it could be python, php, ruby, etc. 5 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the build step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | 8 | # Source utility functions. 9 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 10 | 11 | # Ensure custom node version if not installed. 12 | echo "" 13 | ensure_node_is_installed 14 | 15 | 16 | # We need to move the package.json file out of the way in pre_build, so 17 | # that the OpenShift git post-receive hook doesn't try and use the old 18 | # npm version to install the dependencies. 19 | mv "${OPENSHIFT_REPO_DIR}/package.json" "$(get_node_tmp_dir)" -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_start_nodejs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | 17 | # Source utility functions. 18 | source "$OPENSHIFT_REPO_DIR/.openshift/lib/utils" 19 | 20 | # Setup path to include the custom Node[.js] version. 21 | ver=$(get_node_version) 22 | echo "" 23 | echo " - pre_start_nodejs: Adding Node.js version $ver binaries to path" 24 | _SHOW_SETUP_PATH_MESSAGES="true" setup_path_for_custom_node_version -------------------------------------------------------------------------------- /.openshift/cron/README.cron: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a periodic basis 2 | ======================================= 3 | Any scripts or jobs added to the minutely, hourly, daily, weekly or monthly 4 | directories will be run on a scheduled basis (frequency is as indicated by the 5 | name of the directory) using run-parts. 6 | 7 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 8 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} 9 | 10 | The presence of two specially named files jobs.deny and jobs.allow controls 11 | how run-parts executes your scripts/jobs. 12 | jobs.deny ===> Prevents specific scripts or jobs from being executed. 13 | jobs.allow ===> Only execute the named scripts or jobs (all other/non-named 14 | scripts that exist in this directory are ignored). 15 | 16 | The principles of jobs.deny and jobs.allow are the same as those of cron.deny 17 | and cron.allow and are described in detail at: 18 | http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html#s2-autotasks-cron-access 19 | 20 | See: man crontab or above link for more details and see the the weekly/ 21 | directory for an example. 22 | 23 | -------------------------------------------------------------------------------- /.openshift/cron/daily/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/.openshift/cron/daily/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/hourly/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/.openshift/cron/hourly/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/minutely/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/.openshift/cron/minutely/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/monthly/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/.openshift/cron/monthly/.gitignore -------------------------------------------------------------------------------- /.openshift/cron/weekly/README: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a weekly basis 2 | ===================================== 3 | Any scripts or jobs added to this directory will be run on a scheduled basis 4 | (weekly) using run-parts. 5 | 6 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 7 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} and handles 8 | the files named jobs.deny and jobs.allow specially. 9 | 10 | In this specific example, the chronograph script is the only script or job file 11 | executed on a weekly basis (due to white-listing it in jobs.allow). And the 12 | README and chrono.dat file are ignored either as a result of being black-listed 13 | in jobs.deny or because they are NOT white-listed in the jobs.allow file. 14 | 15 | For more details, please see ../README.cron file. 16 | 17 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/chrono.dat: -------------------------------------------------------------------------------- 1 | Time And Relative D...n In Execution (Open)Shift! 2 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/chronograph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "`date`: `cat $(dirname \"$0\")/chrono.dat`" 4 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.allow: -------------------------------------------------------------------------------- 1 | # 2 | # Script or job files listed in here (one entry per line) will be 3 | # executed on a weekly-basis. 4 | # 5 | # Example: The chronograph script will be executed weekly but the README 6 | # and chrono.dat files in this directory will be ignored. 7 | # 8 | # The README file is actually ignored due to the entry in the 9 | # jobs.deny which is checked before jobs.allow (this file). 10 | # 11 | chronograph 12 | 13 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.deny: -------------------------------------------------------------------------------- 1 | # 2 | # Any script or job files listed in here (one entry per line) will NOT be 3 | # executed (read as ignored by run-parts). 4 | # 5 | 6 | README 7 | 8 | -------------------------------------------------------------------------------- /.openshift/lib/setup_custom_nodejs_env: -------------------------------------------------------------------------------- 1 | # Utility functions for bash session - sourced in via the user's 2 | # bash profile ($OPENSHIFT_DATA_DIR/.bash_profile). 3 | 4 | # Source utility functions. 5 | source $OPENSHIFT_REPO_DIR/.openshift/lib/utils 6 | 7 | 8 | # Internal function to setup path and remove the wrappers. 9 | function _setup_path_and_remove_wrappers() { 10 | # First invocation of npm or node, so setup the custom path and 11 | # unset the wrappers. Add the custom node binaries to the PATH. 12 | [ -z "$ZDEBUG" ] || echo "Setting path to include custom Node version" 13 | setup_path_for_custom_node_version 14 | unset node 15 | unset npm 16 | unset _setup_path_and_remove_wrappers 17 | 18 | } # End of function _setup_path_and_remove_wrappers. 19 | 20 | 21 | # Temporary wrapper function to setup path before invoking npm. 22 | function npm() { 23 | # Setup path, remove wrappers and reinvoke npm. 24 | _setup_path_and_remove_wrappers 25 | npm "$@" 26 | 27 | } # End of function npm. 28 | 29 | 30 | # Temporary wrapper function to setup path before invoking node. 31 | function node() { 32 | # Setup path, remove wrappers and reinvoke node. 33 | _setup_path_and_remove_wrappers 34 | node "$@" 35 | 36 | } # End of function node. 37 | 38 | 39 | # 40 | # EOF 41 | -------------------------------------------------------------------------------- /.openshift/markers/NODEJS_VERSION: -------------------------------------------------------------------------------- 1 | # Uncomment one of the version lines to select the node version to use. 2 | # The last "non-blank" version line is the one picked up by the code in 3 | # .openshift/lib/utils 4 | # Default: 0.10.25 5 | # 6 | # 0.8.24 7 | # 0.9.1 8 | # 0.10.25 9 | # 0.11.11 10 | 0.10.28 11 | 12 | -------------------------------------------------------------------------------- /.openshift/markers/README: -------------------------------------------------------------------------------- 1 | Markers 2 | =========== 3 | 4 | Adding marker files to this directory will have the following effects: 5 | 6 | force_clean_build - Will remove any previously installed npm modules and 7 | re-install all the required modules from scratch 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | services: 5 | - mongodb 6 | before_install: npm install -g grunt-cli 7 | install: npm install 8 | before_script: 9 | - "export PHANTOMJS_EXECUTABLE='phantomjs --local-to-remote-url-access=yes --ignore-ssl-errors=yes'" 10 | - "export DISPLAY=:99.0" 11 | - "sh -e /etc/init.d/xvfb start" 12 | - "grunt default" 13 | - "cp settings.json.example settings.json" 14 | script: 15 | - "DISPLAY=:99.0 grunt test" 16 | - "/bin/bash ./scripts/lintcheck" 17 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Product Owner 2 | 3 | * Ren Yang 4 | 5 | # Developer 6 | 7 | * Deshi Xiao (Project Lead) 8 | * Chenxiong Qi 9 | * Chaobin Tang 10 | * YuGuang Wang 11 | * Zheng Liu 12 | * Xuebin Dong (front-end) 13 | * Haibo Lin 14 | * Jian Chen 15 | 16 | # Designer 17 | 18 | * Xiaoxue Zhang 19 | 20 | # QA 21 | 22 | * Chen Chen 23 | * Qiu Yang 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Devel 4 | 5 | - Authentication is refactored and email is used as the identify of user. 6 | - Local authentication strategy is removed and Cantas will rely on third party 7 | authentication services. 8 | - Make noreply email address and the email domain of invitation configurable 9 | - Fix SPEC errors 10 | - Add help script to add an user 11 | - Refactor app.js 12 | - #94 - Set unusable password in remote user and Kerberos strategy 13 | - Use Username as the placeholder instead 14 | - Refine flash message of Kerberos strategy 15 | - New local user strategy 16 | - Allow to specify default authentication strategry from settings 17 | - #69 - Google Authentication redirects back to login page, cannot log in 18 | - FIX: Oauth2 callback properties correction 19 | - FIX cantas service init file 20 | 21 | ## 1.0.1 (2014-09-02) 22 | 23 | + support login with google auth2 24 | + add mycards panels to home page 25 | + support cards filter 26 | + upload file to card, support image cover on card face 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2013 Cantas Team 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the “Software”), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | This application uses other third-party javascript components 23 | distributed under appropriate licenses. For more information, 24 | see the following: 25 | jquery.js http://jquery.org/ 26 | jquery-ui.js http://jquery.org/ 27 | backbone.js http://backbonejs.org/ 28 | socket.io.js http://socket.io/ 29 | underscore.js http://underscorejs.org/ 30 | backbone.iobind.js https://github.com/logicalparadox/backbone.iobind 31 | moment.js http://momentjs.com/ 32 | markdown.js https://github.com/evilstreak/markdown-js 33 | bootstrap-min.js http://getbootstrap.com/javascript/ 34 | async.js https://github.com/caolan/async 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DISTRO_ID=`lsb_release -i | cut -f2` 2 | 3 | <<<<<<< HEAD 4 | .PHONY: check tags 5 | 6 | tags: 7 | @ctags -R --languages=JavaScript \ 8 | --exclude=node_modules \ 9 | --exclude=public/javascripts/dist \ 10 | --exclude=public/javascripts/vendor \ 11 | --exclude=spec/helpers \ 12 | -f .tags 13 | 14 | check: 15 | ======= 16 | help: 17 | @echo "Utility used while development" 18 | @echo 19 | @echo "Targets:" 20 | @echo " check: run tests and check code style" 21 | @echo " run: run server" 22 | 23 | .PHONY: test 24 | test: 25 | >>>>>>> c2e192cba78f1c13193ae1bacf7d24e282eeee8c 26 | @echo "Running tests" 27 | @echo 28 | @grunt test 29 | 30 | .PHONY: lint 31 | lint: 32 | @if [ "`which nodelint &> /dev/null && echo 0 || echo 1`" != "0" ]; then \ 33 | echo "Cannot find command: nodeline"; \ 34 | echo ; \ 35 | exit 1; \ 36 | fi 37 | @echo "Checking code styles" 38 | @echo 39 | @sh ./scripts/lintcheck 40 | 41 | .PHONY: check 42 | check: test lint 43 | 44 | .PHONY: run 45 | run: 46 | @if [ ! -e settings.json ]; then \ 47 | cp settings.json.example settings.json; \ 48 | echo "settings.json is just copied from an example file. You need to configure it to meet your requirement."; \ 49 | fi 50 | @case "$(DISTRO_ID)" in \ 51 | Fedora) \ 52 | if [ x"`systemctl is-active mongod.service`" != "xactive" ]; then \ 53 | sudo systemctl start mongod.service; \ 54 | fi; \ 55 | if [ x"`systemctl is-active redis.service`" != "xactive" ]; then \ 56 | sudo systemctl start redis.service; \ 57 | fi; \ 58 | ;; \ 59 | *) \ 60 | echo "You have to start mongodb and redis manually."; \ 61 | esac 62 | @node app.js 63 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1-dev 2 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.box = "chef/fedora-21" 6 | config.ssh.pty = true 7 | config.vm.network "forwarded_port", guest: 3000, host: 3000 8 | config.vm.synced_folder ".", "/project/src" 9 | 10 | config.vm.provider "virtualbox" do |vb| 11 | vb.memory = "1024" 12 | end 13 | 14 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 15 | sudo yum install ack make gcc gcc-c++ git npm wget \ 16 | krb5-workstation krb5-devel \ 17 | mongodb mongodb-server redis tmux \ 18 | rpmlint rpm-build \ 19 | fontconfig-devel -y 20 | sudo systemctl enable mongod.service 21 | sudo systemctl enable redis.service 22 | sudo systemctl start mongod.service 23 | sudo systemctl start redis.service 24 | 25 | git config --global color.branch true 26 | git config --global color.diff true 27 | git config --global color.status true 28 | 29 | git config --global alias.br branch 30 | git config --global alias.cm commit 31 | git config --global alias.co checkout 32 | git config --global alias.fp format-patch 33 | git config --global alias.st status 34 | 35 | mkdir ~/npm-global 36 | npm config set prefix "~/npm-global" 37 | echo "export PATH=$HOME/npm-global/bin:$PATH" >> ~/.bash_profile 38 | 39 | npm install -g grunt-cli 40 | npm install -g nodelint 41 | 42 | cd /project/src 43 | git remote add upstream https://github.com/onepiecejs/nodejs-cantas 44 | npm install 45 | SHELL 46 | end 47 | -------------------------------------------------------------------------------- /docs/source/about.rst: -------------------------------------------------------------------------------- 1 | .. _about: 2 | 3 | About Cantas 4 | =============== 5 | 6 | See https://github.com/onepiecejs/nodejs-cantas/ 7 | 8 | Key features 9 | ------------ 10 | 11 | 12 | A brief history 13 | --------------- 14 | 15 | -------------------------------------------------------------------------------- /docs/source/conv.rst: -------------------------------------------------------------------------------- 1 | .. _conv: 2 | 3 | 4 | Code Convention 5 | =============== 6 | -------------------------------------------------------------------------------- /docs/source/deploy.rst: -------------------------------------------------------------------------------- 1 | .. _deploy: 2 | 3 | 4 | Package&Deployment 5 | ================== 6 | 7 | 8 | Getting to know Jekins 9 | ---------------------- 10 | 11 | 12 | Package 13 | ------- 14 | 15 | 16 | Deployment 17 | ---------- 18 | -------------------------------------------------------------------------------- /docs/source/design.rst: -------------------------------------------------------------------------------- 1 | .. _design: 2 | 3 | 4 | Dive Into Cantas 5 | ================ 6 | 7 | 8 | System architecture 9 | ------------------- 10 | 11 | 12 | Technical introduction 13 | ---------------------- 14 | 15 | 16 | Data schema 17 | ----------- 18 | 19 | 20 | Server 21 | ------ 22 | 23 | 24 | Client 25 | ------ 26 | 27 | 28 | System integration 29 | ------------------ 30 | -------------------------------------------------------------------------------- /docs/source/devel.rst: -------------------------------------------------------------------------------- 1 | .. _devel: 2 | 3 | 4 | Development 5 | =========== 6 | 7 | 8 | Code repos 9 | -------------------------- 10 | 11 | 12 | Dependencies 13 | ------------ 14 | 15 | 16 | Dev environment 17 | --------------------- 18 | 19 | 20 | Code structure 21 | -------------- 22 | 23 | 24 | Debug tools 25 | ----------- 26 | 27 | 28 | Workflow 29 | -------- 30 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. cantas documentation master file, created by 2 | sphinx-quickstart on Sun Jun 9 15:19:42 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | .. _index: 6 | 7 | Cantas Developer Guide 8 | ================================== 9 | 10 | Cantas is a flexible real-time collaboration tools. It can help u 11 | arrange&share your ideas with some aids(board, list, card). It provide a 12 | platform to share information and track their changes with each other. 13 | 14 | 15 | Support 16 | ------- 17 | 18 | mailing list: cantas-dev-list@redhat.com 19 | IRC: #cantas 20 | 21 | 22 | Contents 23 | -------- 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | 28 | about 29 | design 30 | devel 31 | test 32 | deploy 33 | conv 34 | 35 | 36 | Indices and tables 37 | ------------------ 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | 43 | -------------------------------------------------------------------------------- /docs/source/test.rst: -------------------------------------------------------------------------------- 1 | .. _test: 2 | 3 | 4 | Testing 5 | ======= 6 | -------------------------------------------------------------------------------- /models/action.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Action 3 | * 4 | * idMemberCreator 5 | * data 6 | * type 7 | * created 8 | * 9 | * REF: https://trello.com/docs/api/board/index.html#get-1-boards-board-id-actions 10 | * 11 | * [{ 12 | * "idMemberCreator": "4ee7deffe582acdec80000ac", 13 | * "data": { 14 | * "card": { 15 | * "id": "4eea522c91e31d174600027e", 16 | * "name": "Figure out how to read a user's board list" 17 | * }, 18 | * "board": { 19 | * "id": "4eea4ffc91e31d1746000046", 20 | * "name": "Example Board" 21 | * }, 22 | * "idMember": "4ee7df74e582acdec80000b6" 23 | * }, 24 | * "type": "addMemberToCard", 25 | * "created": "2011-12-15T20:01:59.688Z" 26 | * ] 27 | * 28 | */ 29 | 30 | (function(module) { 31 | 32 | "use strict"; 33 | 34 | var mongoose = require('mongoose'); 35 | var Schema = mongoose.Schema; 36 | var ObjectId = Schema.ObjectId; 37 | var ActionSchema; 38 | 39 | ActionSchema = new Schema({ 40 | idMemberCreator: ObjectId, 41 | data: { type: {}, default: {} }, 42 | type: { type: String }, 43 | created: { type: Date, default: Date.now } 44 | }); 45 | 46 | module.exports = mongoose.model('Action', ActionSchema); 47 | 48 | }(module)); 49 | -------------------------------------------------------------------------------- /models/activity.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var ActivitySchema = new Schema({ 10 | content: { type: String, required: true }, 11 | creatorId: { type: ObjectId, required: true, ref: 'User', index: true }, 12 | boardId: { type: ObjectId, required: true, ref: 'Board', index: true }, 13 | createdOn: { type: Date, default: Date.now } 14 | }); 15 | 16 | module.exports = mongoose.model('Activity', ActivitySchema); 17 | 18 | }(module)); 19 | -------------------------------------------------------------------------------- /models/attachment.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'), 6 | Schema = mongoose.Schema, 7 | ObjectId = Schema.ObjectId, 8 | AttachmentSchema; 9 | 10 | AttachmentSchema = new Schema({ 11 | cardId: {type: ObjectId, required: true, ref: 'Card', index: true}, 12 | uploaderId: {type: ObjectId, required: true, ref: 'User', index: true}, 13 | name: {type: String, required: true}, 14 | size: {type: Number, required: true}, 15 | // fileType field includes picture, video, audio and other 16 | fileType: {type: String, default: 'other'}, 17 | path: {type: String, required: true}, 18 | // boolean falg to indicate if this attachment is used as cover for the card 19 | isCover: {type: Boolean, default: false}, 20 | // path of the thumbnail which is used in the card view 21 | cardThumbPath: {type: String, default: ''}, 22 | // path of the thumbnail which is used in the card details view 23 | cardDetailThumbPath: {type: String, default: ''}, 24 | createdOn: {type: Date, default: Date.now} 25 | }); 26 | 27 | AttachmentSchema.statics.getById = function (id, callback) { 28 | this.findById({_id: id}).populate("cardId", "uploaderId").exec(callback); 29 | }; 30 | 31 | module.exports = mongoose.model('Attachment', AttachmentSchema); 32 | 33 | }(module)); 34 | -------------------------------------------------------------------------------- /models/boardMemberStatus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Board member status. 3 | */ 4 | 5 | (function (module) { 6 | 7 | "use strict"; 8 | 9 | module.exports = { 10 | unknown: "unknown", 11 | available: "available", 12 | inviting: "inviting", 13 | kickedOff: "kickedOff" 14 | }; 15 | 16 | }(module)); 17 | -------------------------------------------------------------------------------- /models/cardLabelRelation.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var CardLabelRelationSchema = new Schema({ 10 | boardId: { type: ObjectId, required: true, ref: 'Board', index: true }, 11 | cardId: { type: ObjectId, required: true, ref: 'Card', index: true }, 12 | labelId: { type: ObjectId, required: true, ref: 'Label', index: true }, 13 | selected: { type: Boolean, default: false}, 14 | createdOn: { type: Date, default: Date.now }, 15 | updatedOn: { type: Date, default: Date.now } 16 | }); 17 | 18 | module.exports = mongoose.model('CardLabelRelation', 19 | CardLabelRelationSchema); 20 | 21 | }(module)); 22 | -------------------------------------------------------------------------------- /models/cardSourceRelation.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | /* 10 | * CardSourceRelation schema is designed to record the relationship 11 | * between card and external source like bug id from bugzilla, 12 | * issue id from jira. 13 | */ 14 | 15 | var CardSourceRelationSchema = new Schema({ 16 | syncConfigId: { type: ObjectId, required: true, ref: 'SyncConfig', index: true }, 17 | cardId: { type: ObjectId, required: true, ref: 'Card', index: true }, 18 | // External source id like bug id from bugzilla, issue id from jira 19 | sourceId: { type: String, require: true }, 20 | 21 | // External system name 22 | sourceType: { type: String, require: true }, 23 | lastSyncTime: { type: Date, default: Date.now } 24 | }); 25 | 26 | module.exports = mongoose.model('CardSourceRelation', CardSourceRelationSchema); 27 | 28 | }(module)); 29 | -------------------------------------------------------------------------------- /models/checklist.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require("mongoose"); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var ChecklistSchema = new Schema({ 10 | title: {type: String, default: "New Checklist"}, 11 | cardId: {type: ObjectId, required: true, ref: "Card", index: true}, 12 | authorId: {type: ObjectId, required: true, ref: "User", index: true}, 13 | createdOn: {type: Date, default: Date.now}, 14 | updatedOn: {type: Date, default: Date.now} 15 | }); 16 | 17 | ChecklistSchema.statics.getById = function(id, callback) { 18 | this.findById({_id: id}).populate("cardId", "authorId").exec(callback); 19 | }; 20 | 21 | module.exports = mongoose.model("Checklist", ChecklistSchema); 22 | 23 | }(module)); 24 | -------------------------------------------------------------------------------- /models/checklistItem.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require("mongoose"); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var ChecklistItemSchema = new Schema({ 10 | content: {type: String, required: true}, 11 | checked: {type: Boolean, default: false}, 12 | order: {type: Number, default: 1}, 13 | checklistId: {type: ObjectId, required: true, ref: "Checklist", index: true}, 14 | cardId: {type: ObjectId, required: true, ref: "Card", index: true}, 15 | authorId: {type: ObjectId, required: true, ref: "User", index: true}, 16 | createdOn: {type: Date, default: Date.now}, 17 | updatedOn: {type: Date, default: Date.now} 18 | }); 19 | 20 | ChecklistItemSchema.static.getById = function(id, callback) { 21 | this.findById({_id: id}).populate("checklistId", "authorId").exec(callback); 22 | }; 23 | 24 | module.exports = mongoose.model("ChecklistItem", ChecklistItemSchema); 25 | 26 | }(module)); 27 | -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | var CommentSchema; 9 | 10 | CommentSchema = new Schema({ 11 | content: { type: String, required: true }, 12 | cardId: { type: ObjectId, required: true, ref: 'Card', index: true}, 13 | authorId: { type: ObjectId, required: true, ref: 'User', index: true }, 14 | createdOn: { type: Date, default: Date.now }, 15 | updatedOn: { type: Date, required: false } 16 | }); 17 | 18 | module.exports = mongoose.model('Comment', CommentSchema); 19 | 20 | }(module)); 21 | 22 | -------------------------------------------------------------------------------- /models/commentSourceRelation.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | /* 10 | * CommentSourceRelation schema is designed to record comment which 11 | * is from external system like bug comment from bugzilla. 12 | */ 13 | 14 | var CommentSourceRelationSchema = new Schema({ 15 | commentId: { type: ObjectId, required: true, ref: 'Comment', index: true }, 16 | cardId: { type: ObjectId, required: true, ref: 'Card', index: true }, 17 | sourceId: { type: String, require: true }, 18 | sourceType: { type: String, require: true }, 19 | lastSyncTime: { type: Date, default: Date.now } 20 | 21 | }); 22 | 23 | module.exports = mongoose.model('CommentSourceRelation', CommentSourceRelationSchema); 24 | 25 | }(module)); 26 | -------------------------------------------------------------------------------- /models/configStatus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Vote and Comment status. 3 | */ 4 | 5 | (function (module) { 6 | 7 | 'use strict'; 8 | 9 | module.exports = { 10 | // Vote/Comment is disabled, nobody can vote on a card. 11 | disabled: 'disabled', 12 | // Vote/Comment is enabled, and avialable to board members only. 13 | enabled: 'enabled', 14 | // Vote/Comment is opened for everyone in Cantas. 15 | opened: 'opened' 16 | }; 17 | 18 | module.exports.configDescription = { 19 | disabled: 'Disable', 20 | enabled: 'Public for Board Member', 21 | opened: 'Public for Every User' 22 | }; 23 | 24 | }(module)); 25 | -------------------------------------------------------------------------------- /models/group.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | var GroupSchema; 9 | 10 | GroupSchema = new Schema({ 11 | name: { type: String, required: true }, 12 | description: { type: String, default: '' }, 13 | created: { type: Date, default: Date.now } 14 | }); 15 | 16 | module.exports = mongoose.model('Group', GroupSchema); 17 | 18 | }(module)); 19 | 20 | -------------------------------------------------------------------------------- /models/label.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var LabelSchema = new Schema({ 10 | title: { type: String, default: '' }, 11 | order: { type: Number, required: true }, 12 | color: { type: String, required: true }, 13 | boardId: { type: ObjectId, required: true, ref: 'Board', index: true}, 14 | createdOn: { type: Date, default: Date.now }, 15 | updatedOn: { type: Date, default: Date.now } 16 | }); 17 | 18 | module.exports = mongoose.model('Label', LabelSchema); 19 | 20 | }(module)); 21 | -------------------------------------------------------------------------------- /models/list.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | var Card = require('./card'); 9 | var ListSchema; 10 | 11 | ListSchema = new Schema({ 12 | title: { type: String, required: true }, 13 | isArchived: { type: Boolean, default: false }, 14 | created: { type: Date, default: Date.now }, 15 | creatorId: { type: ObjectId, required: true, index: true }, 16 | order: { type: Number, default: -1}, 17 | boardId: { type: ObjectId, required: true, index: true }, 18 | perms: { 19 | delete: { 20 | users: [ ObjectId ], 21 | roles: [ ObjectId ] 22 | }, 23 | update: { 24 | users: [ ObjectId ], 25 | roles: [ ObjectId ] 26 | } 27 | } 28 | }); 29 | 30 | ListSchema.post('remove', function (list) { 31 | // Also remove its cards. 32 | Card.remove({listId: list._id}).exec(); 33 | }); 34 | 35 | module.exports = mongoose.model('List', ListSchema); 36 | 37 | }(module)); 38 | -------------------------------------------------------------------------------- /models/metadata.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Metadatas for arbitraty models 3 | * 4 | * Metadata model can be defined here, and export them by adding to the hash 5 | * assigned to module.exports . 6 | */ 7 | (function (module) { 8 | 9 | "use strict"; 10 | 11 | var mongoose = require('mongoose'); 12 | var Schema = mongoose.Schema; 13 | 14 | var LabelMetadataSchema = new Schema({ 15 | order: {type: Number, required: true, unique: true}, 16 | title: {type: String, default: '' }, 17 | color: {type: String, required: true, unique: true} 18 | }); 19 | 20 | module.exports = { 21 | LabelMetadata: mongoose.model('LabelMetadata', LabelMetadataSchema) 22 | }; 23 | 24 | }(module)); 25 | -------------------------------------------------------------------------------- /models/notification.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | var NotificationSchema; 9 | 10 | NotificationSchema = new Schema({ 11 | userId: { type: ObjectId, required: true, index: true}, 12 | message: { type: String, required: true }, 13 | type: { type: String, required: true },    // values: subscription, invitation, mentioned 14 | isUnread: {type: Boolean, default: true}, 15 | created: { type: Date, default: Date.now } 16 | }); 17 | 18 | module.exports = mongoose.model('Notification', NotificationSchema); 19 | 20 | }(module)); 21 | 22 | -------------------------------------------------------------------------------- /models/notificationType.js: -------------------------------------------------------------------------------- 1 | /* 2 | * definition of notification types. 3 | */ 4 | 5 | (function (module) { 6 | 7 | "use strict"; 8 | 9 | module.exports = { 10 | invitation: "invitation", 11 | subscription: "subscription", 12 | mentioned: "mentioned", 13 | information: "information" 14 | }; 15 | 16 | }(module)); 17 | -------------------------------------------------------------------------------- /models/organization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Model: Organization 3 | * 4 | * name String 5 | * description(Optional) String 6 | * 7 | */ 8 | 9 | (function (module) { 10 | 11 | "use strict"; 12 | 13 | var mongoose = require('mongoose'); 14 | var Schema = mongoose.Schema; 15 | var ObjectId = Schema.ObjectId; 16 | var OrganizationSchema; 17 | 18 | OrganizationSchema = new Schema({ 19 | name: { type: String }, 20 | description: { type: String } 21 | }); 22 | 23 | module.exports = mongoose.model('Organization', OrganizationSchema); 24 | 25 | }(module)); -------------------------------------------------------------------------------- /models/permission.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Model Permission 3 | * 4 | * name: String read,write,all 5 | */ 6 | 7 | (function(module) { 8 | 9 | "use strict"; 10 | 11 | var mongoose = require('mongoose'); 12 | var Schema = mongoose.Schema; 13 | var ObjectId = Schema.ObjectId; 14 | var PermissionSchema; 15 | 16 | PermissionSchema = new Schema({ 17 | idMember: ObjectId, 18 | /** 19 | * object logic view: such as: global,board,member,organization 20 | */ 21 | scope: { type: String }, 22 | /** 23 | * permission only have some types: read,write,all 24 | * Suppose Object(such as Board) trigger CloseBoard Action, 25 | * the permission will assign to the actual user. 26 | * we need design *token* logic here,and design backend service 27 | * to directly interact with mongo. 28 | * every Action will contains permission authorize and Optional data value. 29 | * REF: https://trello.com/docs/api/board/index.html#put-1-boards-board-id-closed 30 | * 31 | * "data": [{ 32 | * "idModel": "*", 33 | * "scope": "global", 34 | * "permission": "read" 35 | * }] 36 | * or 37 | * 38 | * "data": [{ 39 | * "idModel": "4eea4ffc91e31d1746000046", 40 | * "scope": "board", 41 | * "permission": "read" 42 | * },{ 43 | * "idModel": "4ee7deffe582acdec80000ac", 44 | * "scope": "board", 45 | * "permission": "write" 46 | * },{ 47 | * "idModel": "4ee7deffe582acdec80000ac", 48 | * "scope": "board", 49 | * "permission": "all" 50 | * } 51 | * ] 52 | * 53 | */ 54 | data: { type: [], default: [] }, 55 | created: { type: Date, default: Date.now } 56 | }); 57 | 58 | module.exports = mongoose.model('Permission', PermissionSchema); 59 | 60 | }(module)); 61 | -------------------------------------------------------------------------------- /models/roles.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | var RoleSchema; 9 | 10 | RoleSchema = new Schema({ 11 | name: { type: String, required: true }, 12 | perms: [ String ] 13 | }); 14 | 15 | module.exports = mongoose.model('Role', RoleSchema); 16 | 17 | }(module)); 18 | 19 | -------------------------------------------------------------------------------- /models/syncConfig.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | /* 10 | * SyncConfig schema is designed to record the mapping and 11 | * related settings between list and query url. 12 | * User can config several mappings in a board 13 | */ 14 | 15 | var SyncConfigSchema = new Schema({ 16 | boardId: { type: ObjectId, required: true, ref: 'Board', index: true }, 17 | listId: { type: ObjectId, ref: 'List', index: true }, 18 | 19 | // QueryUrl is used for querying bug/issue info 20 | // from external system like bugzilla or jira through API 21 | queryUrl: { type: String }, 22 | 23 | // External system name 24 | queryType: { type: String, required: true }, 25 | 26 | // If sync external systems based on sync config automatically 27 | isActive: { type: Boolean, default: true }, 28 | 29 | // Set the interval time when cantas sync external 30 | // systems automatically, default is 8 hours once 31 | intervalTime: { type: Number, default: 8 }, 32 | 33 | creatorId: { type: ObjectId, required: true, ref: 'User', index: true }, 34 | createdOn: { type: Date, default: Date.now }, 35 | updatedOn: { type: Date, default: Date.now } 36 | }); 37 | 38 | module.exports = mongoose.model('SyncConfig', SyncConfigSchema); 39 | 40 | }(module)); 41 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var authUtils = require('../services/auth/utils'); 6 | var servicesUtils = require('../services/utils'); 7 | var mongoose = require('mongoose'); 8 | var Schema = mongoose.Schema; 9 | var ObjectId = Schema.ObjectId; 10 | var UserSchema; 11 | 12 | var MAX_PASSWORD_LENGTH = 64; 13 | 14 | UserSchema = new Schema({ 15 | displayName: { type: String, required: true, match: /^[\w ]+$/ }, 16 | password: { type: String, default: '', select: false }, 17 | email: { type: String, required: true, lowercase: true, unique: true }, 18 | joined: { type: Date, default: Date.now }, 19 | roles: [ ObjectId ], 20 | isFirstLogin: { type: Boolean, default: true } 21 | }); 22 | 23 | // static method 24 | 25 | UserSchema.statics.exists = function(email, callback) { 26 | this.findOne({email: email}, '_id', function(err, user) { 27 | callback(user !== undefined && user !== null); 28 | }); 29 | }; 30 | 31 | // Instance methods 32 | 33 | UserSchema.methods.verifyUserPassword = function(password) { 34 | if (password === undefined || password === null || password.length === 0) { 35 | throw new Error('No password to set.'); 36 | } 37 | if (password.length > MAX_PASSWORD_LENGTH) { 38 | throw new Error('Password is too long.'); 39 | } 40 | }; 41 | 42 | /* 43 | * Set user's password. 44 | */ 45 | UserSchema.methods.setPassword = function(rawPassword, callback) { 46 | this.verifyUserPassword(rawPassword); 47 | var password = authUtils.makePassword(rawPassword); 48 | var model = this.model('User'); 49 | model.findByIdAndUpdate(this._id, {$set: {password: password}}, function(err, user) { 50 | callback(err === null); 51 | }); 52 | }; 53 | 54 | /* 55 | * Set an unusable password to user. 56 | */ 57 | UserSchema.methods.setUnusablePassword = function(callback) { 58 | var password = servicesUtils.randomString(); 59 | this.setPassword(password, callback); 60 | }; 61 | 62 | /* 63 | * Check whether user has a password. 64 | */ 65 | UserSchema.methods.checkPassword = function(rawPassword, callback) { 66 | this.verifyUserPassword(rawPassword); 67 | this.model('User').findById(this._id, 'password', function(err, user) { 68 | if (err) { 69 | callback(false); 70 | } else { 71 | var isValid = authUtils.checkPassword(rawPassword, user.password); 72 | callback(isValid); 73 | } 74 | }); 75 | }; 76 | 77 | module.exports = mongoose.model('User', UserSchema); 78 | 79 | }(module)); 80 | 81 | -------------------------------------------------------------------------------- /models/vote.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | "use strict"; 4 | 5 | var mongoose = require('mongoose'); 6 | var Schema = mongoose.Schema; 7 | var ObjectId = Schema.ObjectId; 8 | 9 | var VoteSchema = new Schema({ 10 | yesOrNo: { type: Boolean, default: true }, 11 | cardId: { type: ObjectId, required: true, ref: 'Card', index: true }, 12 | authorId: { type: ObjectId, required: true, ref: 'User', index: true }, 13 | createdOn: { type: Date, default: Date.now }, 14 | updatedOn: { type: Date, default: Date.now } 15 | }); 16 | 17 | module.exports = mongoose.model('Vote', VoteSchema); 18 | 19 | }(module)); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "HSS-IED-BJ", 3 | "description": "cantas is a real-time collaborative application.", 4 | "dependencies": { 5 | "async": "*", 6 | "connect-redis": "~1.4.x", 7 | "express": "~2.5.x", 8 | "jade": "*", 9 | "markdown": "*", 10 | "moment": "*", 11 | "mongoose": "3.6.13", 12 | "node-krb5": "*", 13 | "nodemailer": "~0.7.1", 14 | "passport": "*", 15 | "passport-local": "*", 16 | "passport-google-oauth": "~0.1.5", 17 | "redis": "*", 18 | "socket.io": "~0.9.x", 19 | "xmlrpc": "~1.1.0", 20 | "easyimage": "^1.0.2", 21 | "requestify": "*", 22 | "lodash": "*", 23 | "piwik-tracker": "^0.1.1" 24 | }, 25 | "devDependencies": { 26 | "expect.js": "*", 27 | "grunt": "~0.4.5", 28 | "grunt-contrib-concat": "*", 29 | "grunt-contrib-cssmin": "~0.6.1", 30 | "grunt-contrib-jasmine": "0.6.x", 31 | "grunt-contrib-uglify": "*", 32 | "grunt-contrib-watch": "~0.4.3", 33 | "grunt-simple-mocha": "~0.4.0", 34 | "mongoose-fakery": "*", 35 | "nodelint": "*", 36 | "optimist": "~0.6.0", 37 | "sinon": "*", 38 | "supertest": "*" 39 | }, 40 | "engines": { 41 | "node": "0.10.26", 42 | "npm": "1.4.7" 43 | }, 44 | "name": "cantas", 45 | "private": true, 46 | "scripts": { 47 | "lintcheck": "scripts/lintcheck", 48 | "start": "node app" 49 | }, 50 | "main": "app.js", 51 | "version": "1.0.0", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/onepiecejs/nodejs-cantas.git" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /public/attachments/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /public/images/404-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/404-img.png -------------------------------------------------------------------------------- /public/images/Calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/Calendar.png -------------------------------------------------------------------------------- /public/images/Wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/Wrench.png -------------------------------------------------------------------------------- /public/images/activetracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/activetracking.png -------------------------------------------------------------------------------- /public/images/add-2-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/add-2-icon.png -------------------------------------------------------------------------------- /public/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/add.png -------------------------------------------------------------------------------- /public/images/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/admin.png -------------------------------------------------------------------------------- /public/images/bg-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/bg-1.png -------------------------------------------------------------------------------- /public/images/bg-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/bg-grey.png -------------------------------------------------------------------------------- /public/images/bg-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/bg-white.png -------------------------------------------------------------------------------- /public/images/black-heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/black-heart.png -------------------------------------------------------------------------------- /public/images/body-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/body-bg.png -------------------------------------------------------------------------------- /public/images/btn-delete-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/btn-delete-hover.png -------------------------------------------------------------------------------- /public/images/btn-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/btn-delete.png -------------------------------------------------------------------------------- /public/images/btn-edit-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/btn-edit-hover.png -------------------------------------------------------------------------------- /public/images/btn-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/btn-edit.png -------------------------------------------------------------------------------- /public/images/cantas-agree-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-agree-badge.png -------------------------------------------------------------------------------- /public/images/cantas-agree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-agree.png -------------------------------------------------------------------------------- /public/images/cantas-agree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-agree1.png -------------------------------------------------------------------------------- /public/images/cantas-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-all.png -------------------------------------------------------------------------------- /public/images/cantas-all1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-all1.png -------------------------------------------------------------------------------- /public/images/cantas-assign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-assign.png -------------------------------------------------------------------------------- /public/images/cantas-attachment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-attachment.png -------------------------------------------------------------------------------- /public/images/cantas-check-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-check-1.png -------------------------------------------------------------------------------- /public/images/cantas-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-check.png -------------------------------------------------------------------------------- /public/images/cantas-checklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-checklist.png -------------------------------------------------------------------------------- /public/images/cantas-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-comment.png -------------------------------------------------------------------------------- /public/images/cantas-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-disabled.png -------------------------------------------------------------------------------- /public/images/cantas-disagree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-disagree.png -------------------------------------------------------------------------------- /public/images/cantas-disagree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-disagree1.png -------------------------------------------------------------------------------- /public/images/cantas-due-date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-due-date.png -------------------------------------------------------------------------------- /public/images/cantas-help-activity.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-activity.gif -------------------------------------------------------------------------------- /public/images/cantas-help-archivecard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-archivecard.gif -------------------------------------------------------------------------------- /public/images/cantas-help-archivelist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-archivelist.gif -------------------------------------------------------------------------------- /public/images/cantas-help-assign.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-assign.gif -------------------------------------------------------------------------------- /public/images/cantas-help-board.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-board.gif -------------------------------------------------------------------------------- /public/images/cantas-help-card.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-card.gif -------------------------------------------------------------------------------- /public/images/cantas-help-checklist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-checklist.gif -------------------------------------------------------------------------------- /public/images/cantas-help-comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-comment.gif -------------------------------------------------------------------------------- /public/images/cantas-help-import-bugzilla.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-import-bugzilla.gif -------------------------------------------------------------------------------- /public/images/cantas-help-import-trello.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-import-trello.gif -------------------------------------------------------------------------------- /public/images/cantas-help-invite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-invite.gif -------------------------------------------------------------------------------- /public/images/cantas-help-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-list.gif -------------------------------------------------------------------------------- /public/images/cantas-help-movecard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-movecard.gif -------------------------------------------------------------------------------- /public/images/cantas-help-movelist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-movelist.gif -------------------------------------------------------------------------------- /public/images/cantas-help-notification.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-notification.gif -------------------------------------------------------------------------------- /public/images/cantas-help-private.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-help-private.gif -------------------------------------------------------------------------------- /public/images/cantas-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-login.png -------------------------------------------------------------------------------- /public/images/cantas-none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-none.png -------------------------------------------------------------------------------- /public/images/cantas-none1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-none1.png -------------------------------------------------------------------------------- /public/images/cantas-option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-option.png -------------------------------------------------------------------------------- /public/images/cantas-part.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-part.png -------------------------------------------------------------------------------- /public/images/cantas-part1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/cantas-part1.png -------------------------------------------------------------------------------- /public/images/card-setting-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/card-setting-1.png -------------------------------------------------------------------------------- /public/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/delete.png -------------------------------------------------------------------------------- /public/images/description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/description.png -------------------------------------------------------------------------------- /public/images/destroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/destroy.png -------------------------------------------------------------------------------- /public/images/devel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/devel.png -------------------------------------------------------------------------------- /public/images/eso-footer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/eso-footer-logo.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/footer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/footer-logo.png -------------------------------------------------------------------------------- /public/images/footprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/footprint.png -------------------------------------------------------------------------------- /public/images/header-logo-eso-developed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/header-logo-eso-developed.png -------------------------------------------------------------------------------- /public/images/header-logo-eso-maintained.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/header-logo-eso-maintained.png -------------------------------------------------------------------------------- /public/images/icon-archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/icon-archive.png -------------------------------------------------------------------------------- /public/images/icon-invited.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/icon-invited.png -------------------------------------------------------------------------------- /public/images/icon-owner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/icon-owner.png -------------------------------------------------------------------------------- /public/images/icon-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/icon-time.png -------------------------------------------------------------------------------- /public/images/infomation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/infomation.png -------------------------------------------------------------------------------- /public/images/information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/information.png -------------------------------------------------------------------------------- /public/images/irc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/irc.png -------------------------------------------------------------------------------- /public/images/list-settings-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/list-settings-hover.png -------------------------------------------------------------------------------- /public/images/list-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/list-settings.png -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/loading.gif -------------------------------------------------------------------------------- /public/images/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/lock.png -------------------------------------------------------------------------------- /public/images/login-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/login-header.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/logo.png -------------------------------------------------------------------------------- /public/images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/man.png -------------------------------------------------------------------------------- /public/images/member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/member.png -------------------------------------------------------------------------------- /public/images/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/notification.png -------------------------------------------------------------------------------- /public/images/option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/option.png -------------------------------------------------------------------------------- /public/images/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/progressbar.gif -------------------------------------------------------------------------------- /public/images/public-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/public-board.png -------------------------------------------------------------------------------- /public/images/red-heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/red-heart.png -------------------------------------------------------------------------------- /public/images/search-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/search-bg.png -------------------------------------------------------------------------------- /public/images/sent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/sent.png -------------------------------------------------------------------------------- /public/images/set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/set.png -------------------------------------------------------------------------------- /public/images/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/setting.png -------------------------------------------------------------------------------- /public/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/settings.png -------------------------------------------------------------------------------- /public/images/side-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/side-bar.png -------------------------------------------------------------------------------- /public/images/stage-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/stage-server.png -------------------------------------------------------------------------------- /public/images/ucd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/ucd.png -------------------------------------------------------------------------------- /public/images/unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/images/unlock.png -------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Provide top-level namespaces for our javascript. 2 | $(function ($, _, Backbone) { 3 | 4 | "use strict"; 5 | 6 | window.cantas = window.cantas || {}; 7 | cantas.models = {}; 8 | var ioPort = (window.cantas.settings) ? window.cantas.settings.socketIO.port : 80; 9 | cantas.socket = io.connect("" + 10 | document.location.protocol + 11 | "//" + document.location.hostname + 12 | ":" + ioPort, { 13 | "reconnect": true, 14 | "max reconnection attempts": 100, 15 | "max reconnection delay": 32000, 16 | "reconnection delay": cantas.utils.randomWait(100,1000) 17 | }); 18 | cantas.views = {}; 19 | 20 | cantas.setTitle = function(title) { 21 | window.document.title = "Cantas | " + title; 22 | }; 23 | 24 | moment.lang('en', { 25 | calendar : { 26 | lastDay: '[Yesterday at] LT', 27 | sameDay: '[Today at] LT', 28 | nextDay: '[Tomorrow at] LT', 29 | lastWeek: '[last] dddd [at] LT', 30 | nextWeek: 'dddd [at] LT', 31 | sameElse: 'DD/MM/YYYY' 32 | } 33 | }); 34 | 35 | _.mixin({ 36 | compactObject : function(object) { 37 | var clone = _.clone(object); 38 | _.each(clone, function(value, key){ 39 | if(!value) { 40 | delete clone[key]; 41 | } 42 | }); 43 | return clone; 44 | } 45 | }); 46 | 47 | }(jQuery, _, Backbone)); 48 | -------------------------------------------------------------------------------- /public/javascripts/constants.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 4 | "use strict"; 5 | 6 | var cantas = window.cantas || {}; 7 | 8 | cantas.KEY_CODES = { 9 | SPACE: 32, 10 | COMMA: 188, 11 | ENTER: 13, 12 | BACKSPACE: 8, 13 | LEFT_ARROW: 37, 14 | RIGHT_ARROW: 39, 15 | }; 16 | 17 | window.cantas = cantas; 18 | 19 | }()); -------------------------------------------------------------------------------- /public/javascripts/models/activity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Models for displaying Activity logs. 3 | */ 4 | 5 | (function ($, _, Backbone) { 6 | 7 | "use strict"; 8 | 9 | cantas.models.Activity = cantas.models.BaseModel.extend({ 10 | idAttribute: "_id", 11 | socket: cantas.socket, 12 | urlRoot: "activity", 13 | 14 | url: function () { 15 | return "/activity/" + this.id; 16 | } 17 | 18 | }); 19 | 20 | cantas.models.ActivityCollection = cantas.models.BaseCollection.extend({ 21 | /* 22 | * With this url, collection responds all events whose name match 23 | * activities:[action] 24 | */ 25 | url: "/activity", 26 | model: cantas.models.Activity, 27 | 28 | comparator: function (activity) { 29 | return activity.get("createdOn"); 30 | } 31 | }); 32 | 33 | }(jQuery, _, Backbone)); 34 | -------------------------------------------------------------------------------- /public/javascripts/models/attachment.js: -------------------------------------------------------------------------------- 1 | //model attachment 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.models.Attachment = cantas.models.BaseModel.extend({ 7 | idAttribute: "_id", 8 | noIoBind: false, 9 | socket: cantas.socket, 10 | url: function () { 11 | return "/attachment" + ((this.id) ? '/' + this.id : ''); 12 | }, 13 | 14 | initialize: function () { 15 | if (!this.noIoBind) { 16 | this.ioBind('update', this.serverChange, this); 17 | this.ioBind('delete', this.serverDelete, this); 18 | } 19 | }, 20 | 21 | serverChange: function (data) { 22 | this.set(data); 23 | }, 24 | 25 | serverDelete: function (data) { 26 | if (typeof this.collection === 'object') { 27 | this.collection.remove(this); 28 | } else { 29 | this.trigger('remove', this); 30 | } 31 | }, 32 | 33 | modelCleanup: function () { 34 | this.ioUnbindAll(); 35 | return this; 36 | } 37 | 38 | }); 39 | 40 | 41 | //Collection 42 | cantas.models.AttachmentCollection = cantas.models.BaseCollection.extend({ 43 | model: cantas.models.Attachment, 44 | socket: cantas.socket, 45 | 46 | url: "/attachment", 47 | 48 | initialize: function () { 49 | this.socket.removeAllListeners("/attachment:create"); 50 | this.socket.on('/attachment:create', this.serverCreate, this); 51 | }, 52 | 53 | serverCreate: function (data) { 54 | if (data) { 55 | var card = cantas.utils.getCardModelById(data.cardId); 56 | if (card) { 57 | card.attachmentCollection.add(data); 58 | } 59 | } 60 | }, 61 | 62 | collectionCleanup: function (callback) { 63 | this.ioUnbindAll(); 64 | this.each(function (model) { 65 | model.modelCleanup(); 66 | }); 67 | return this; 68 | }, 69 | 70 | // sort attachments by 'createdOn' in ascending order 71 | comparator: function(a, b) { 72 | if (a.get('createdOn') > b.get('createdOn')) { 73 | return 1; 74 | } 75 | if (a.get('createdOn') < b.get('createdOn')) { 76 | return -1; 77 | } 78 | return 0; 79 | } 80 | 81 | }); 82 | 83 | }(jQuery, _, Backbone)); 84 | -------------------------------------------------------------------------------- /public/javascripts/models/boardMember.js: -------------------------------------------------------------------------------- 1 | // Board member Model 2 | 3 | (function ($, _, Backbone) { 4 | 5 | "use strict"; 6 | 7 | cantas.models.BoardMember = cantas.models.BaseModel.extend({ 8 | // FIXME: user is not correct. Should be boardmemberrelation. 9 | urlRoot: "user" 10 | }); 11 | 12 | /* 13 | * Container of member relations. 14 | */ 15 | cantas.models.BoardMemberCollection = cantas.models.BaseCollection.extend({ 16 | url: "/boardmemberrelation", 17 | model: cantas.models.BoardMember 18 | }); 19 | 20 | }(jQuery, _, Backbone)); 21 | -------------------------------------------------------------------------------- /public/javascripts/models/comment.js: -------------------------------------------------------------------------------- 1 | //model comment 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.models.Comment = cantas.models.BaseModel.extend({ 7 | idAttribute: "_id", 8 | noIoBind: false, 9 | socket: cantas.socket, 10 | url: function () { 11 | return "/comment" + ((this.id) ? '/' + this.id : ''); 12 | }, 13 | 14 | initialize: function () { 15 | this.on('modelCleanup', this.modelCleanup, this); 16 | if (!this.noIoBind) { 17 | this.ioBind('update', this.serverChange, this); 18 | this.ioBind('delete', this.serverDelete, this); 19 | } 20 | }, 21 | 22 | serverChange: function (data) { 23 | this.set(data); 24 | }, 25 | 26 | serverDelete: function (data) { 27 | if (typeof this.collection === 'object') { 28 | this.collection.remove(this); 29 | } else { 30 | this.trigger('remove', this); 31 | } 32 | }, 33 | 34 | modelCleanup: function () { 35 | this.ioUnbindAll(); 36 | return this; 37 | } 38 | 39 | }); 40 | 41 | 42 | //Collection 43 | cantas.models.CommentCollection = cantas.models.BaseCollection.extend({ 44 | model: cantas.models.Comment, 45 | socket: cantas.socket, 46 | url: "/comment", 47 | 48 | initialize: function () { 49 | this.socket.removeAllListeners("/comment:create"); 50 | this.socket.on('/comment:create', this.serverCreate, this); 51 | }, 52 | 53 | serverCreate: function (data) { 54 | if (data) { 55 | var card = cantas.utils.getCardModelById(data.cardId); 56 | if (card) { 57 | card.commentCollection.add(data); 58 | } 59 | } 60 | }, 61 | 62 | collectionCleanup: function (callback) { 63 | this.ioUnbindAll(); 64 | this.each(function (model) { 65 | model.modelCleanup(); 66 | }); 67 | return this; 68 | }, 69 | 70 | // sort comments by 'createdOn' in ascending order 71 | comparator: function(a, b) { 72 | if (a.get('createdOn') > b.get('createdOn')) { 73 | return 1; 74 | } 75 | if (a.get('createdOn') < b.get('createdOn')) { 76 | return -1; 77 | } 78 | return 0; 79 | } 80 | 81 | }); 82 | 83 | }(jQuery, _, Backbone)); 84 | -------------------------------------------------------------------------------- /public/javascripts/models/label.js: -------------------------------------------------------------------------------- 1 | (function ($, _, Backbone) { 2 | 3 | "use strict"; 4 | 5 | var BaseModel = cantas.models.BaseModel; 6 | var BaseCollection = cantas.models.BaseCollection; 7 | 8 | cantas.models.Label = BaseModel.extend({ 9 | urlRoot: 'label' 10 | }); 11 | 12 | cantas.models.LabelCollection = BaseCollection.extend({ 13 | model: cantas.models.Label, 14 | url: '/label', 15 | 16 | initialize: function(models, options) { 17 | _.bindAll(this, "serverCreate"); 18 | if (!this.noIoBind) { 19 | this.ioBind("create", this.socket, this.serverCreate, this); 20 | } 21 | } 22 | }); 23 | 24 | cantas.models.CardLabelRelation = BaseModel.extend({ 25 | urlRoot: 'cardlabelrelation' 26 | }); 27 | 28 | cantas.models.CardLabelRelationCollection = BaseCollection.extend({ 29 | model: cantas.models.CardLabelRelation, 30 | url: 'cardlabelrelation', 31 | 32 | /* 33 | * Due to the number of labels of a card is fixed, we don't need listen to 34 | * create event. So, override this function to disable default behavior, 35 | * and add other possible actions. 36 | */ 37 | initialize: function(models, options) { 38 | }, 39 | 40 | comparator: function(relation) { 41 | return relation.get('cardId').order; 42 | } 43 | }); 44 | 45 | }(jQuery, _, Backbone)); 46 | -------------------------------------------------------------------------------- /public/javascripts/models/notification.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Models for displaying Activity logs. 3 | */ 4 | 5 | (function ($, _, Backbone) { 6 | 7 | "use strict"; 8 | 9 | cantas.models.Notification = cantas.models.BaseModel.extend({ 10 | idAttribute: "_id", 11 | socket: cantas.socket, 12 | urlRoot: "notification", 13 | 14 | url: function() { 15 | return "/notification/" + this.id; 16 | } 17 | 18 | }); 19 | 20 | cantas.models.NotificationCollection = cantas.models.BaseCollection.extend({ 21 | /* 22 | * With this url, collection responds all events whose name match 23 | * notification:[action] 24 | */ 25 | url: "/notification", 26 | model: cantas.models.Notification, 27 | 28 | // sort notifications by 'created' in descending order 29 | comparator: function(a, b) { 30 | if (a.get('created') > b.get('created')) { 31 | return -1; 32 | } 33 | if (a.get('created') < b.get('created')) { 34 | return 1; 35 | } 36 | return 0; 37 | } 38 | }); 39 | 40 | }(jQuery, _, Backbone)); 41 | -------------------------------------------------------------------------------- /public/javascripts/models/syncConfig.js: -------------------------------------------------------------------------------- 1 | //model sync configuraion 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.models.SyncConfig = cantas.models.BaseModel.extend({ 7 | idAttribute: "_id", 8 | noIoBind: false, 9 | socket: cantas.socket, 10 | url: function () { 11 | return "/syncconfig" + ((this.id) ? '/' + this.id : ''); 12 | }, 13 | 14 | initialize: function () { 15 | if (!this.noIoBind) { 16 | this.ioBind('update', this.serverChange, this); 17 | this.ioBind('delete', this.serverDelete, this); 18 | } 19 | }, 20 | 21 | serverChange: function (data) { 22 | this.set(data); 23 | }, 24 | 25 | serverDelete: function (data) { 26 | if (typeof this.collection === 'object') { 27 | this.collection.remove(this); 28 | } else { 29 | this.trigger('remove', this); 30 | } 31 | }, 32 | 33 | modelCleanup: function () { 34 | this.ioUnbindAll(); 35 | return this; 36 | } 37 | 38 | }); 39 | 40 | 41 | //Collection 42 | cantas.models.SyncConfigCollection = cantas.models.BaseCollection.extend({ 43 | model: cantas.models.SyncConfig, 44 | socket: cantas.socket, 45 | 46 | url: "/syncconfig", 47 | 48 | initialize: function () { 49 | this.socket.removeAllListeners("/syncconfig:create"); 50 | this.socket.on('/syncconfig:create', this.serverCreate, this); 51 | }, 52 | 53 | serverCreate: function (data) { 54 | if (data) { 55 | var board = cantas.utils.getCurrentBoardModel(); 56 | if (board) { 57 | board.syncConfigCollection.add(data); 58 | } 59 | } 60 | }, 61 | 62 | collectionCleanup: function (callback) { 63 | this.ioUnbindAll(); 64 | this.each(function (model) { 65 | model.modelCleanup(); 66 | }); 67 | return this; 68 | }, 69 | 70 | // sort configurations by 'createdOn' in ascending order 71 | comparator: function(a, b) { 72 | if (a.get('createdOn') > b.get('createdOn')) { 73 | return 1; 74 | } 75 | if (a.get('createdOn') < b.get('createdOn')) { 76 | return -1; 77 | } 78 | return 0; 79 | } 80 | 81 | }); 82 | 83 | }(jQuery, _, Backbone)); 84 | -------------------------------------------------------------------------------- /public/javascripts/models/visitor.js: -------------------------------------------------------------------------------- 1 | // visitor model 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.models.BoardVisitor = cantas.models.BaseModel.extend({ 7 | urlRoot: "boardvisitor" 8 | }); 9 | 10 | cantas.models.BoardVisitorCollection = cantas.models.BaseCollection.extend({ 11 | url: "/boardvisitor", 12 | model: cantas.models.BoardVisitor, 13 | 14 | initialize: function() { 15 | 16 | if (!this.noIoBind) { 17 | this.ioBind("create", this.serverCreate, this); 18 | } 19 | } 20 | 21 | }); 22 | 23 | }(jQuery, _, Backbone)); 24 | -------------------------------------------------------------------------------- /public/javascripts/models/vote.js: -------------------------------------------------------------------------------- 1 | //model vote 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | var BaseModel = cantas.models.BaseModel; 6 | var BaseCollection = cantas.models.BaseCollection; 7 | 8 | cantas.models.Vote = BaseModel.extend({ 9 | urlRoot: 'vote', 10 | 11 | initialize: function () { 12 | this.on('modelCleanup', this.modelCleanup, this); 13 | if (!this.noIoBind) { 14 | this.ioBind('update', this.serverChange, this); 15 | this.ioBind('delete', this.serverDelete, this); 16 | } 17 | }, 18 | 19 | serverChange: function (data) { 20 | this.set(data); 21 | }, 22 | 23 | serverDelete: function (data) { 24 | if (typeof this.collection === 'object') { 25 | this.collection.remove(this); 26 | } else { 27 | this.trigger('remove', this); 28 | } 29 | }, 30 | 31 | modelCleanup: function () { 32 | this.ioUnbindAll(); 33 | return this; 34 | } 35 | 36 | }); 37 | 38 | cantas.models.VoteCollection = cantas.models.BaseCollection.extend({ 39 | model: cantas.models.Vote, 40 | url: '/vote', 41 | 42 | initialize: function(models, options) { 43 | _.bindAll(this, "serverCreate"); 44 | this.socket.removeAllListeners("/vote:create"); 45 | if (!this.noIoBind) { 46 | this.socket.on('/vote:create', this.serverCreate, this); 47 | } 48 | } 49 | 50 | }); 51 | 52 | }(jQuery, _, Backbone)); 53 | -------------------------------------------------------------------------------- /public/javascripts/utils/safe_string.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | (function ($, _, Backbone) { 4 | 5 | "use strict"; 6 | 7 | var tagsToReplace = { 8 | '&': "&", 9 | '<': "<", 10 | '>': ">" 11 | }; 12 | 13 | var replaceTag = function(tag) { 14 | return tagsToReplace[tag] || tag; 15 | }; 16 | 17 | var utils = cantas.utils || {}; 18 | 19 | // Ref: http://jsperf.com/encode-html-entities 20 | utils.safeString = function(str) { 21 | return str.replace(/[&<>]/g, replaceTag); 22 | }; 23 | 24 | utils.escapeString = function(str) { 25 | return str.replace(/\n/g, "
").replace(/ /g, " "); 26 | }; 27 | 28 | cantas.utils = utils; 29 | 30 | }(jQuery, _, Backbone)); 31 | -------------------------------------------------------------------------------- /public/javascripts/vendor/jquery.ba-cond.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * cond - v0.1 - 6/10/2009 3 | * http://benalman.com/projects/jquery-cond-plugin/ 4 | * 5 | * Copyright (c) 2009 "Cowboy" Ben Alman 6 | * Licensed under the MIT license 7 | * http://benalman.com/about/license/ 8 | * 9 | * Based on suggestions and sample code by Stephen Band and DBJDBJ in the 10 | * jquery-dev Google group: http://bit.ly/jqba1 11 | */ 12 | 13 | jQuery.fn.cond = function() { 14 | var undefined, 15 | args = arguments, 16 | i = 0, 17 | test, 18 | callback, 19 | result; 20 | 21 | while ( !test && i < args.length ) { 22 | test = args[ i++ ]; 23 | callback = args[ i++ ]; 24 | 25 | test = jQuery.isFunction( test ) ? test.call( this ) : test; 26 | 27 | result = !callback ? test 28 | : test ? callback.call( this, test ) 29 | : undefined; 30 | } 31 | 32 | return result !== undefined ? result : this; 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /public/javascripts/views/accountSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Page view for viewing / changing settings of current account 3 | */ 4 | 5 | (function ($, _, Backbone) { 6 | 7 | "use strict"; 8 | 9 | cantas.views.accountSettingsView = cantas.views.BaseView.extend({ 10 | events: { 11 | }, 12 | 13 | template: jade.compile($("#template-account-settings-view").text()), 14 | 15 | initialize: function () { 16 | }, 17 | 18 | render: function (context) { 19 | cantas.setTitle(this.options.title); 20 | 21 | this.$el.html(this.template({ 22 | header: this.options.title, 23 | displayName: cantas.user.displayName 24 | })); 25 | 26 | return this; 27 | }, 28 | 29 | close: function() { 30 | this.remove(); 31 | }, 32 | 33 | 34 | remove: function() { 35 | this.undelegateEvents(); 36 | this.$el.empty(); 37 | this.stopListening(); 38 | return this; 39 | } 40 | }); 41 | 42 | }(jQuery, _, Backbone)); 43 | -------------------------------------------------------------------------------- /public/javascripts/views/activity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Activity logs shown in right side of board page. It shows everyone's action 3 | * in the current opening board. 4 | */ 5 | 6 | (function ($, _, Backbone) { 7 | 8 | "use strict"; 9 | 10 | /* 11 | * View to render each section to show an Activity log 12 | */ 13 | cantas.views.ActivityView = Backbone.View.extend({ 14 | tagName: "li", 15 | 16 | initialize: function(options) { 17 | this.render(); 18 | }, 19 | 20 | render: function() { 21 | var html = _.template(this.getTemplate(), this.model.attributes); 22 | this.$el.html(html); 23 | return this; 24 | }, 25 | 26 | getTemplate: function() { 27 | return $("#activity-item-template").html(); 28 | } 29 | }); 30 | 31 | /* 32 | * View needs ActivityCollection to listen to event that an Activity is added 33 | * in server-side. When catch this event, view will render an area with the 34 | * newly added Activity object and display it in Activities area. 35 | */ 36 | cantas.views.ActivityCollectionView = Backbone.View.extend({ 37 | initialize: function(options) { 38 | this.collection.on("add", this.showOneActivity, this); 39 | this.activitiesContainer = $(".activity.side-bar").find("ul").first(); 40 | this.showActivitiesOnlyOnce(); 41 | 42 | // To show activities bar 43 | // Id is defined in an Anchor tag within board header section 44 | $("#toggleActivities").on("click", this, this.toggleActivities); 45 | // To hide activities bar 46 | $(".activity .collapse-bar").on("click", this, this.toggleActivities); 47 | $(".activity header a").on("click", this, this.toggleActivities); 48 | }, 49 | 50 | getCurrentBoardId: function() { 51 | return cantas.utils.getCurrentBoardModel().id; 52 | }, 53 | 54 | toggleActivities: function(event) { 55 | $(".activity").toggle("slide", { direction: "right" }, "fast"); 56 | }, 57 | 58 | showOneActivity: function(model) { 59 | var activityView = new cantas.views.ActivityView({ model: model }); 60 | this.activitiesContainer.prepend(activityView.el); 61 | }, 62 | 63 | /* 64 | * Load current board's activities only once. 65 | */ 66 | showActivitiesOnlyOnce: function() { 67 | if (this.activitiesLoaded) { 68 | return; 69 | } 70 | this.activitiesLoaded = true; 71 | this.collection.fetch({ 72 | data: {boardId: this.getCurrentBoardId()} 73 | }); 74 | }, 75 | 76 | remove: function() { 77 | this.collection.dispose(); 78 | this.undelegateEvents(); 79 | this.stopListening(); 80 | return this; 81 | } 82 | 83 | }); 84 | 85 | }(jQuery, _, Backbone)); 86 | -------------------------------------------------------------------------------- /public/javascripts/views/app.js: -------------------------------------------------------------------------------- 1 | (function ($, _, Backbone) { 2 | 3 | "use strict"; 4 | 5 | cantas.views.AppView = cantas.views.BaseView.extend({ 6 | 7 | el: 'body', 8 | 9 | events: { 10 | 'keyup .js-search-form': 'searchAction', 11 | 'submit .js-search-form': 'searchAction', 12 | 'focus .js-search-form': 'searchAction', 13 | 'click .quick-search': function(e) { 14 | e.stopPropagation(); 15 | } 16 | }, 17 | 18 | initialize: function() { 19 | this.initNofiticationView(); 20 | this.initQuickSearchView(); 21 | }, 22 | 23 | 24 | initNofiticationView: function() { 25 | this.notificationView = new cantas.views.NotificationView(); 26 | 27 | cantas.socket.on('/notification:create', function(data) { 28 | var obj = new cantas.models.Notification(data); 29 | this.notificationView.notificationCollection.add(data); 30 | }.bind(this)); 31 | }, 32 | 33 | 34 | initQuickSearchView: function() { 35 | this.quickSearchView = new cantas.views.QuickSearchView({ 36 | el: this.$('.quick-search-content') 37 | }); 38 | }, 39 | 40 | 41 | searchAction: function(e) { 42 | var code = e.keyCode || e.which; 43 | 44 | // Don't search if the user pressed esc or enter 45 | if (code === 27 || code === 13) { 46 | return; 47 | } 48 | 49 | e.preventDefault(); 50 | var q = this.$('.js-search-form [name="query"]').val(); 51 | this.quickSearchView.search(q); 52 | } 53 | 54 | 55 | }); 56 | 57 | 58 | }(jQuery, _, Backbone)); -------------------------------------------------------------------------------- /public/javascripts/views/base.js: -------------------------------------------------------------------------------- 1 | (function ($, _, Backbone) { 2 | 3 | "use strict"; 4 | 5 | /* 6 | * View to render each section to show an Activity log 7 | */ 8 | cantas.views.BaseView = Backbone.View.extend({ 9 | close: function() { 10 | if (this.model) { 11 | this.model.dispose(); 12 | } 13 | 14 | this.remove(); 15 | } 16 | }); 17 | 18 | 19 | }(jQuery, _, Backbone)); 20 | -------------------------------------------------------------------------------- /public/javascripts/views/boardlist.js: -------------------------------------------------------------------------------- 1 | // User Default View 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.views.BoardsView = cantas.views.BaseView.extend({ 7 | el: ".dashboard-content", 8 | 9 | events: { 10 | "click .js-close-board": "closeBoard", 11 | "click .js-open-board": "openBoard", 12 | "click .js-view-board": "viewBoard", 13 | }, 14 | 15 | template: jade.compile($("#template-board-list-view").text()), 16 | 17 | closeBoard: function(event) { 18 | var boardId = $(event.target).data('board'); 19 | var board = new cantas.models.Board({ _id: boardId}); 20 | board.patch({'isClosed': true}, {validate: false}); 21 | 22 | $(event.target).closest('li').fadeOut('slow', function() { 23 | $(event.target).closest('li').remove(); 24 | }); 25 | }, 26 | 27 | openBoard: function(event) { 28 | var boardId = $(event.target).data('board'); 29 | var board = new cantas.models.Board({ _id: boardId}); 30 | board.patch({'isClosed': false}, {validate: false}); 31 | 32 | $(event.target).closest('li').fadeOut('slow', function() { 33 | $(event.target).closest('li').remove(); 34 | }); 35 | }, 36 | 37 | viewBoard: function(e) { 38 | e.preventDefault(); 39 | cantas.navigateTo($(e.target).attr('href')); 40 | }, 41 | 42 | remove: function() { 43 | this.undelegateEvents(); 44 | this.$el.empty(); 45 | this.stopListening(); 46 | return this; 47 | }, 48 | 49 | render: function(context) { 50 | var h3Header; 51 | switch (context.title) { 52 | case 'mine': 53 | h3Header = 'My Boards'; 54 | break; 55 | case 'public': 56 | h3Header = 'Public Boards'; 57 | break; 58 | case 'closed': 59 | h3Header = 'Closed Boards'; 60 | break; 61 | } 62 | 63 | this.$el.html(this.template({ 64 | boards: context.boards, 65 | h3Header: h3Header, 66 | isCreator: this.isCreator, 67 | highlighted: context.highlighted 68 | })); 69 | 70 | cantas.setTitle(context.title); 71 | return this; 72 | }, 73 | 74 | isCreator: function(board) { 75 | return board.creatorId._id === cantas.user.id; 76 | } 77 | 78 | }); 79 | 80 | }(jQuery, _, Backbone)); 81 | 82 | -------------------------------------------------------------------------------- /public/javascripts/views/confirmDialog.js: -------------------------------------------------------------------------------- 1 | // Confirm Dialog View 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.views.ConfirmDialogView = Backbone.View.extend({ 7 | tagName: "div", 8 | id: "confirm-dialog", 9 | className: "alert-window clearfix", 10 | template: _.template('

Delete Option

' + '

' + 11 | '
' + 12 | '' + 15 | '
' + 16 | '' + 17 | ''), 18 | 19 | initialize: function() { 20 | //when clicking outside area of the confirm dialog, it will disappear. 21 | $("body").on("click", function(event) { 22 | var e = event || window.event; 23 | var elem = e.srcElement || e.target; 24 | while (elem && elem !== $("body")[0]) { 25 | if (elem.id === "confirm-dialog") { 26 | return; 27 | } 28 | elem = elem.parentNode; 29 | } 30 | $("#confirm-dialog").hide(); 31 | }); 32 | }, 33 | 34 | render: function(context) { 35 | $("body").append(this.$el.html(this.template())); 36 | this.operationType = context.operationType; 37 | this.operationItem = context.operationItem; 38 | this.$el.find(".js-confirmInfo").text(context.confirmInfo); 39 | this.$el.find(".js-btn-yes").text(context.captionYes); 40 | this.$el.find(".js-btn-yes").on("click", context.yesCallback); 41 | this.$el.find(".js-btn-no").text(context.captionNo); 42 | this.$el.find(".js-btn-no").on("click", context.noCallback); 43 | if (!context.diplayNoAskOrNo) { 44 | this.$el.find('#js-cb-noask').parent().remove(); 45 | } 46 | 47 | var offsetX = cantas.utils.getOffsetX(context.pageX, "#confirm-dialog"); 48 | var offsetY = cantas.utils.getOffsetY(context.pageY, "#confirm-dialog"); 49 | var cssSettings = {"left": (context.pageX - offsetX), "top": (context.pageY - offsetY)}; 50 | if (context.width) { 51 | cssSettings.width = context.width; 52 | } else { 53 | cssSettings.width = 240; 54 | } 55 | this.$el.css(cssSettings); 56 | this.$el.toggle(); 57 | return this; 58 | } 59 | 60 | }); 61 | 62 | }(jQuery, _, Backbone)); 63 | -------------------------------------------------------------------------------- /public/javascripts/views/dashboard-navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout for the dashboard navigation 3 | */ 4 | (function ($, _, Backbone) { 5 | "use strict"; 6 | 7 | cantas.views.DashboardNavigationView = cantas.views.BaseView.extend({ 8 | 9 | events: { 10 | 'click a': 'openDashboard' 11 | }, 12 | 13 | template: jade.compile($("#template-dashboard-navigation-view").text()), 14 | 15 | initialize: function() {}, 16 | 17 | setActive: function(className) { 18 | this.$el.find('a.active').removeClass('active'); 19 | this.$el.find('a.' + className).parents('li').addClass("active"); 20 | return this; 21 | }, 22 | 23 | render: function(context) { 24 | this.$el.html(this.template()); 25 | return this; 26 | }, 27 | 28 | openDashboard: function(e) { 29 | e.preventDefault(); 30 | cantas.navigateTo($(e.target).attr('href')); 31 | }, 32 | 33 | remove: function() { 34 | this.undelegateEvents(); 35 | this.$el.empty(); 36 | this.stopListening(); 37 | return this; 38 | } 39 | 40 | }); 41 | 42 | }(jQuery, _, Backbone)); -------------------------------------------------------------------------------- /public/javascripts/views/dashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout for the dashboard 3 | */ 4 | (function ($, _, Backbone) { 5 | "use strict"; 6 | 7 | cantas.views.DashboardView = Backbone.View.extend({ 8 | el: '.content', 9 | 10 | events: {}, 11 | 12 | contentView: null, 13 | navigationView: null, 14 | 15 | template: jade.compile($("#template-dashboard-layout-view").text()), 16 | 17 | initialize: function() { 18 | _.bindAll(this, 'setContentView', 'setNavigationView'); 19 | }, 20 | 21 | render: function(context) { 22 | this.$el.html(this.template()); 23 | this.renderChildViews(); 24 | return this; 25 | }, 26 | 27 | /** 28 | * Set the active view for the main content area 29 | * 30 | * @param {View} view 31 | * @return {object} this 32 | */ 33 | setContentView: function(view) { 34 | this.contentView = view; 35 | return this; 36 | }, 37 | 38 | /** 39 | * Set the active view for the navigation 40 | * 41 | * @param {View} view 42 | * @return {object} this 43 | */ 44 | setNavigationView: function(view) { 45 | this.navigationView = view; 46 | return this; 47 | }, 48 | 49 | /** 50 | * Render the navigation and content if set 51 | * 52 | * @return {object} 53 | */ 54 | renderChildViews: function() { 55 | if (this.navigationView) { 56 | this.$el.find('.dashboard-navigation').append(this.navigationView.$el); 57 | } 58 | if (this.contentView) { 59 | this.$el.find('.dashboard-content').append(this.contentView.$el); 60 | } 61 | return this; 62 | }, 63 | 64 | 65 | close: function() { 66 | if (this.contentView) { 67 | this.contentView.close(); 68 | } 69 | 70 | if (this.navigationView) { 71 | this.navigationView.close(); 72 | } 73 | 74 | this.remove(); 75 | }, 76 | 77 | remove: function() { 78 | this.undelegateEvents(); 79 | this.$el.empty(); 80 | this.stopListening(); 81 | return this; 82 | } 83 | 84 | }); 85 | 86 | }(jQuery, _, Backbone)); 87 | 88 | -------------------------------------------------------------------------------- /public/javascripts/views/help.js: -------------------------------------------------------------------------------- 1 | // Help View 2 | 3 | (function ($, _, Backbone) { 4 | "use strict"; 5 | 6 | cantas.views.HelpView = Backbone.View.extend({ 7 | el: '.content', 8 | 9 | events: { 10 | }, 11 | 12 | template: jade.compile($("#template-help-view").text()), 13 | 14 | close: function() { 15 | this.remove(); 16 | }, 17 | 18 | remove: function() { 19 | this.undelegateEvents(); 20 | this.$el.empty(); 21 | this.stopListening(); 22 | return this; 23 | }, 24 | 25 | render: function(context) { 26 | this.$el.html(this.template()); 27 | cantas.setTitle(context.title); 28 | } 29 | }); 30 | 31 | }(jQuery, _, Backbone)); 32 | 33 | -------------------------------------------------------------------------------- /public/javascripts/views/welcome.js: -------------------------------------------------------------------------------- 1 | // Welcome View 2 | 3 | (function ($, _, Backbone) { 4 | 5 | "use strict"; 6 | 7 | cantas.views.WelcomeView = Backbone.View.extend({ 8 | 9 | el: '.content', 10 | 11 | events: { 12 | }, 13 | 14 | template: jade.compile($("#template-welcome-view").text()), 15 | 16 | close: function() { 17 | // this.$el.empty(); 18 | // this.undelegateEvents(); 19 | // this.stopListening(); 20 | 21 | return this; 22 | }, 23 | 24 | render: function(context) { 25 | this.$el.html(this.template()); 26 | cantas.setTitle(context.title); 27 | 28 | var $navArrows = $('#nav-arrows'); 29 | var $nav = $('#nav-dots > span'); 30 | 31 | var slitslider = $('#slider').slitslider({ 32 | onBeforeChange: function(slide, pos) { 33 | $nav.removeClass('nav-dot-current'); 34 | $nav.eq(pos).addClass('nav-dot-current'); 35 | } 36 | }); 37 | 38 | $navArrows.children(':last').on('click', function() { 39 | slitslider.next(); 40 | return false; 41 | }); 42 | 43 | $navArrows.children(':first').on('click', function() { 44 | slitslider.previous(); 45 | return false; 46 | }); 47 | 48 | $nav.each(function(i) { 49 | $(this).on('click', 50 | function(event) { 51 | var $dot = $(this); 52 | if (!slitslider.isActive()) { 53 | $nav.removeClass('nav-dot-current'); 54 | $dot.addClass('nav-dot-current'); 55 | } 56 | 57 | slitslider.jump(i + 1); 58 | return false; 59 | }); 60 | }); 61 | } 62 | 63 | }); 64 | 65 | }(jQuery, _, Backbone)); 66 | -------------------------------------------------------------------------------- /public/stylesheets/fonts/droid-sans-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/droid-sans-bold.woff -------------------------------------------------------------------------------- /public/stylesheets/fonts/droid-sans.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/droid-sans.woff -------------------------------------------------------------------------------- /public/stylesheets/fonts/font-awesome/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/font-awesome/font/FontAwesome.otf -------------------------------------------------------------------------------- /public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/font-awesome/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/stylesheets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/stylesheets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/stylesheets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/stylesheets/fonts/overpass-bold-wf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/overpass-bold-wf.woff -------------------------------------------------------------------------------- /public/stylesheets/fonts/overpass-wf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/fonts/overpass-wf.woff -------------------------------------------------------------------------------- /public/stylesheets/images/cantas-welcome-board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/images/cantas-welcome-board.png -------------------------------------------------------------------------------- /public/stylesheets/images/cantas-welcome-card-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/images/cantas-welcome-card-detail.png -------------------------------------------------------------------------------- /public/stylesheets/images/cantas-welcome-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/images/cantas-welcome-card.png -------------------------------------------------------------------------------- /public/stylesheets/images/cantas-welcome-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onepiecejs/nodejs-cantas/77ef66251a97dff98afc31294ed3ca73994891df/public/stylesheets/images/cantas-welcome-list.png -------------------------------------------------------------------------------- /public/stylesheets/jquery-fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 8.1 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | .fileinput-button { 13 | position: relative; 14 | overflow: hidden; 15 | } 16 | .fileinput-button input { 17 | position: absolute; 18 | top: 0; 19 | right: 0; 20 | margin: 0; 21 | opacity: 0; 22 | filter: alpha(opacity=0); 23 | transform: translate(-300px, 0) scale(4); 24 | font-size: 23px; 25 | direction: ltr; 26 | cursor: pointer; 27 | } 28 | .fileupload-buttonbar .btn, 29 | .fileupload-buttonbar .toggle { 30 | margin-bottom: 5px; 31 | margin-right: 9px; 32 | } 33 | 34 | .progress-animated .bar { 35 | background: url(../images/progressbar.gif) !important; 36 | filter: none; 37 | } 38 | .fileupload-loading { 39 | float: right; 40 | width: 32px; 41 | height: 32px; 42 | background: url(../images/loading.gif) center no-repeat; 43 | background-size: contain; 44 | display: none; 45 | } 46 | .fileupload-processing .fileupload-loading { 47 | display: block; 48 | } 49 | .files audio, 50 | .files video { 51 | max-width: 300px; 52 | } 53 | 54 | @media (max-width: 767px) { 55 | .fileupload-buttonbar .toggle, 56 | .files .toggle, 57 | .files .btn span { 58 | display: none; 59 | } 60 | .files .name { 61 | width: 80px; 62 | word-wrap: break-word; 63 | } 64 | .files audio, 65 | .files video { 66 | max-width: 80px; 67 | } 68 | } 69 | .template-upload td { 70 | text-align: center; 71 | width: 24%; 72 | } 73 | .template-download td { 74 | text-align: center; 75 | } -------------------------------------------------------------------------------- /scripts/addUser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Help script to add an user to database. 5 | * 6 | * Usage: 7 | * 8 | * ./scripts/addUser.js displayName password 9 | */ 10 | 11 | (function(module) { 12 | 13 | 'use strict'; 14 | 15 | var async = require('async'); 16 | var mongoose = require('mongoose'); 17 | var infra = require('../services/infra'); 18 | var settings = require('../settings'); 19 | var User = require('../models/user'); 20 | 21 | var displayName = null, password = null; 22 | var argv = process.argv; 23 | 24 | if (argv.length < 3) { 25 | console.log('You have to provide displayName and password.'); 26 | process.exit(1); 27 | } else { 28 | displayName = argv[2]; 29 | password = argv[3]; 30 | if (password === undefined) { 31 | console.log('Miss password'); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | infra.connectDB(settings.mongodb); 37 | 38 | process.on('exit', function() { 39 | mongoose.disconnect(function() { 40 | // Exit normally without the need to do any operation 41 | }); 42 | }); 43 | 44 | var user = new User({ 45 | displayName: displayName, 46 | email: displayName.replace(' ', '_') + '@' + settings.realm 47 | }); 48 | 49 | user.save(function(err, savedUser) { 50 | if (err) { 51 | console.log(err.message); 52 | process.exit(1); 53 | } 54 | 55 | savedUser.setPassword(password, function(result) { 56 | if (!result) { 57 | console.error('Failed to add user ', displayName); 58 | } 59 | process.exit(0); 60 | }); 61 | }); 62 | 63 | }(module)); 64 | -------------------------------------------------------------------------------- /scripts/cantas-init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Initial Script 4 | # Now initial collection of label 5 | 6 | NODE=`which node` 7 | NODE_MODULES_PATH=/usr/lib/node_modules/cantas/scripts/migrations 8 | 9 | ${NODE} ${NODE_MODULES_PATH}/initLabelMetadata.js 10 | -------------------------------------------------------------------------------- /scripts/cantas-migration-to-0.3.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Migration Main Script 4 | # Focus on integration with all cantas migrate scripts 5 | 6 | NODE=`which node` 7 | NODE_MODULES_PATH=/usr/lib/node_modules/cantas/scripts/migrations 8 | 9 | ${NODE} ${NODE_MODULES_PATH}/migrationCardAddBoardId.js 10 | ${NODE} ${NODE_MODULES_PATH}/migrationCardAssigneesChanged.js 11 | ${NODE} ${NODE_MODULES_PATH}/migrationChecklistItemAddCardId.js 12 | ${NODE} ${NODE_MODULES_PATH}/initLabelMetadata.js 13 | echo "Y" | ${NODE} ${NODE_MODULES_PATH}/migrateAddLabelsToBoards.js 14 | echo "Y" | ${NODE} ${NODE_MODULES_PATH}/migrateAddLabelsToCards.js 15 | -------------------------------------------------------------------------------- /scripts/cantas.rc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # cantas - Startup script for Cantas web service 4 | # 5 | # This is script should run after all dependent services are launched. 6 | # 7 | 8 | # chkconfig: 345 80 20 9 | # description: script to control Cantas web service. 10 | # 11 | # processname: cantas 12 | # pidfile: /var/run/cantas.pid 13 | # 14 | 15 | # Source function library. 16 | . /etc/rc.d/init.d/functions 17 | 18 | export NODE_ENV=${NODE_ENV:="production"} 19 | 20 | NAME=cantas 21 | INSTANCE_DIR=/usr/lib/node_modules/$NAME 22 | COMMAND=node 23 | SOURCE_NAME=app.js 24 | CANTAS_LOG_PATH=/var/log/$NAME 25 | 26 | user=cantas 27 | pidfile=/var/run/$NAME.pid 28 | forever_dir=/var/run/forever 29 | logfile=$CANTAS_LOG_PATH/$NAME.log 30 | errlogfile=$CANTAS_LOG_PATH/$NAME-err.log 31 | 32 | node=node 33 | forever=forever 34 | awk=awk 35 | sed=sed 36 | sudo=sudo 37 | 38 | start() 39 | { 40 | echo "Starting $NAME node instance: " 41 | 42 | if [ "$id" = "" ]; then 43 | touch $logfile 44 | chown $user $logfile 45 | touch $errlogfile 46 | chown $user $errlogfile 47 | 48 | touch $pidfile 49 | chown $user $pidfile 50 | 51 | [ -d $forever_dir ] || (mkdir -p $forever_dir ) 52 | 53 | # Launch the application 54 | daemon \ 55 | $forever start --minUptime 1000 --spinSleepTime 1000 -p $forever_dir --pidFile $pidfile -l $logfile -e $errlogfile -a --sourceDir $INSTANCE_DIR -c $COMMAND $SOURCE_NAME 56 | RETVAL=$? 57 | else 58 | echo "Instance already running" 59 | RETVAL=0 60 | fi 61 | } 62 | 63 | restart() 64 | { 65 | echo -n "Restarting $NAME node instance : " 66 | if [ "$id" != "" ]; then 67 | $forever restart -p $forever_dir $id 68 | RETVAL=$? 69 | else 70 | start 71 | fi 72 | } 73 | 74 | stop() 75 | { 76 | echo -n "Shutting down $NAME node instance : " 77 | if [ "$id" != "" ]; then 78 | $forever stop -p $forever_dir $id 79 | else 80 | echo "Instance is not running"; 81 | fi 82 | RETVAL=$? 83 | } 84 | 85 | getForeverId() { 86 | touch $pidfile 87 | chown $user $pidfile 88 | 89 | local pid=$(cat $pidfile) 90 | $forever list |$sed -e '1,2d' | $awk '{print $3}'; 91 | } 92 | id=$(getForeverId) 93 | 94 | case "$1" in 95 | start) 96 | start 97 | ;; 98 | stop) 99 | stop 100 | ;; 101 | status) 102 | $forever list 103 | ;; 104 | restart) 105 | restart 106 | ;; 107 | *) 108 | echo "Usage: {start|stop|status|restart}" 109 | exit 1 110 | ;; 111 | esac 112 | exit $RETVAL 113 | -------------------------------------------------------------------------------- /scripts/lintcheck: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run this script under the root directory of project. You can run this by 4 | # issuing `$ scripts/lintcheck` or `$ npm run-script lintcheck`. 5 | 6 | # Update this variable when add new directory in the root directory of project. 7 | dirs_to_check='models/ 8 | public/javascripts/models/ 9 | public/javascripts/utils/ 10 | public/javascripts/views/ 11 | routes/ scripts/ services/ sockets/ 12 | app.js settings.js' 13 | 14 | nodelint $dirs_to_check --config lint-config.js 15 | -------------------------------------------------------------------------------- /scripts/migrations/initLabelMetadata.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | 'use strict'; 4 | 5 | var async = require('async'); 6 | var mongoose = require('mongoose'); 7 | var settings = require(__dirname + '/../../settings'); 8 | var LabelMetadata = require(__dirname + '/../../models/metadata').LabelMetadata; 9 | 10 | 11 | if (settings.mongodb.url) { 12 | mongoose.connect(settings.mongodb.url); 13 | } else { 14 | mongoose.connect( 15 | settings.mongodb.host, 16 | settings.mongodb.name, 17 | settings.mongodb.port, 18 | { 19 | user: settings.mongodb.user, 20 | pass: settings.mongodb.pass 21 | } 22 | ); 23 | } 24 | 25 | var init_data = [ 26 | {order: 1, title: '', color: '#E7BAB6'}, 27 | {order: 2, title: '', color: '#A2D98C'}, 28 | {order: 3, title: '', color: '#F0F064'}, 29 | {order: 4, title: '', color: '#BDF2E6'}, 30 | {order: 5, title: '', color: '#7DC2FF'}, 31 | {order: 6, title: '', color: '#F5BE55'}, 32 | {order: 7, title: '', color: '#CEB3E6'}, 33 | {order: 8, title: '', color: '#EB9588'} 34 | ]; 35 | 36 | process.on('exit', function() { 37 | mongoose.disconnect(function() { 38 | console.log('Disconnected from mongodb.'); 39 | }); 40 | }); 41 | 42 | module.exports.init = function() { 43 | LabelMetadata.remove(function(err) { 44 | if (err) { 45 | console.error('Cannot remove all label metadata from mongodb.'); 46 | process.exit(1); 47 | } 48 | 49 | async.map(init_data, 50 | function(metadata, callback) { 51 | var lm = new LabelMetadata(metadata); 52 | lm.save(function(err, savedObject) { 53 | if (err) { 54 | callback(err, null); 55 | } else { 56 | callback(null, true); 57 | } 58 | }); 59 | }, 60 | function(err, results) { 61 | if (err) { 62 | console.error('Not all label metadata is stored.'); 63 | process.exit(1); 64 | } else { 65 | console.log('Label metadata is initialized successfully.'); 66 | process.exit(0); 67 | } 68 | }); 69 | }); 70 | }; 71 | 72 | }(module)); 73 | 74 | if (require.main === module) { 75 | module.exports.init(); 76 | } 77 | -------------------------------------------------------------------------------- /scripts/migrations/migrationCardAddBoardId.js: -------------------------------------------------------------------------------- 1 | // add boardId to card schema 2 | 3 | var async = require('async'); 4 | var util = require('util'); 5 | var mongoose = require('mongoose'); 6 | var settings = require('../../settings'); 7 | var List = require('../../models/list'); 8 | var Card = require('../../models/card'); 9 | 10 | mongoose.connect( 11 | settings.mongodb.host, 12 | settings.mongodb.name, 13 | settings.mongodb.port, 14 | { 15 | user: settings.mongodb.user, 16 | pass: settings.mongodb.pass 17 | } 18 | ); 19 | 20 | Card.find().populate('listId').exec(function(err, cards) { 21 | var processed = 0; 22 | var updated = 0; 23 | cards.forEach(function(card) { 24 | if (card.boardId && card.boardId.toString() === card.listId.boardId.toString()) { 25 | processed++; 26 | } else { 27 | var boardId = card.listId.boardId; 28 | card.update({$set: {boardId: boardId}}, function(err) { 29 | if (err) { 30 | console.log("Error: card %s : %s", card.id, err); 31 | } else { 32 | updated++; 33 | } 34 | processed++; 35 | }); 36 | } 37 | }); 38 | 39 | async.until( 40 | function() { 41 | return cards.length === processed; 42 | }, 43 | function(callback) { 44 | setTimeout(callback, 1000); 45 | }, 46 | function(err) { 47 | console.log(util.format("%d of %d cards proceessed.", processed, cards.length)); 48 | console.log(util.format("%d of %d cards updated.", updated, cards.length)); 49 | console.log("finished."); 50 | process.exit(); 51 | } 52 | ); 53 | }); 54 | -------------------------------------------------------------------------------- /scripts/migrations/migrationCardAssigneesChanged.js: -------------------------------------------------------------------------------- 1 | // change card.assignees from memberId to userId 2 | 3 | var async = require('async'); 4 | var util = require('util'); 5 | var mongoose = require('mongoose'); 6 | var settings = require('../../settings'); 7 | var Card = require('../../models/card'); 8 | var BoardMemberRelation = require('../../models/boardMemberRelation'); 9 | 10 | mongoose.connect( 11 | settings.mongodb.host, 12 | settings.mongodb.name, 13 | settings.mongodb.port, 14 | { 15 | user: settings.mongodb.user, 16 | pass: settings.mongodb.pass 17 | } 18 | ); 19 | 20 | // find cards with assignees 21 | Card.find({assignees: {$not: {$size: 0}}}).exec(function(err, cards) { 22 | var processed = 0; 23 | var updated = 0; 24 | cards.forEach(function(card) { 25 | BoardMemberRelation.find({_id: {$in: card.assignees}}, {userId: 1}). 26 | exec(function(err, members) { 27 | if (members.length) { 28 | var userIds = members.map(function(member) { 29 | return member.userId; 30 | }); 31 | card.assignees = userIds; 32 | card.save(function(err, updatedCard) { 33 | processed++; 34 | if (err) { 35 | console.log("Error ", card.id, " : ", err); 36 | } else { 37 | updated++; 38 | } 39 | }); 40 | } else { 41 | processed++; 42 | } 43 | }); 44 | }); 45 | 46 | async.until( 47 | function() { 48 | return cards.length === processed; 49 | }, 50 | function(callback) { 51 | setTimeout(callback, 1000); 52 | }, 53 | function(err) { 54 | console.log(util.format("%d of %d cards proceessed.", processed, cards.length)); 55 | console.log(util.format("%d of %d cards updated.", updated, cards.length)); 56 | console.log("finished."); 57 | process.exit(); 58 | } 59 | ); 60 | }); 61 | -------------------------------------------------------------------------------- /scripts/migrations/migrationChecklistItemAddCardId.js: -------------------------------------------------------------------------------- 1 | // add boardId to card schema 2 | 3 | var async = require('async'); 4 | var util = require('util'); 5 | var mongoose = require('mongoose'); 6 | var settings = require('../../settings'); 7 | var ChecklistItem = require('../../models/checklistItem'); 8 | var Checklist = require('../../models/checklist'); 9 | 10 | mongoose.connect( 11 | settings.mongodb.host, 12 | settings.mongodb.name, 13 | settings.mongodb.port, 14 | { 15 | user: settings.mongodb.user, 16 | pass: settings.mongodb.pass 17 | } 18 | ); 19 | 20 | ChecklistItem.find().populate('checklistId').exec(function(err, items) { 21 | var total = items.length; 22 | var processed = 0; 23 | var updated = 0; 24 | items.forEach(function(item) { 25 | if (item.cardId && item.cardId.toString() === item.checklistId.cardId.toString()) { 26 | processed++; 27 | } else { 28 | item.update({$set: {cardId: item.checklistId.cardId}}, function(err) { 29 | if (err) { 30 | console.log("Error: checklist item %s : %s", item.id, err); 31 | } else { 32 | updated++; 33 | } 34 | processed++; 35 | }); 36 | } 37 | }); 38 | 39 | async.until( 40 | function() { 41 | return total === processed; 42 | }, 43 | function(callback) { 44 | setTimeout(callback, 1000); 45 | }, 46 | function(err) { 47 | console.log(util.format("%d of %d checklist items proceessed.", processed, total)); 48 | console.log(util.format("%d of %d checklist items updated.", updated, total)); 49 | console.log("finished."); 50 | process.exit(); 51 | } 52 | ); 53 | }); 54 | -------------------------------------------------------------------------------- /scripts/migrations/v1.0.2-v1.1-down.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This migration script should run in mongo. 3 | * 4 | * Downgrade database from v1.1 to v1.0.2 5 | */ 6 | 7 | /*global 8 | db 9 | */ 10 | 11 | // 1. Remove displayName 12 | db.users.update({}, {$unset: {displayName: ""}}, {multi: true}); 13 | 14 | // 2. Restore fullname and username from backup 15 | db.users_backup_v1_0_2_v1_1.find().forEach(function(backupUser) { 16 | db.users.update({_id: backupUser.original_id}, {$set: { 17 | username: backupUser.username, 18 | fullname: backupUser.fullname 19 | }}); 20 | }); 21 | 22 | db.users.ensureIndex({username: 1}, 23 | {name: "idx_unique_username", unique: true, background: true}); 24 | -------------------------------------------------------------------------------- /scripts/migrations/v1.0.2-v1.1-up.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This migration script should run in mongo. 3 | * 4 | * Upgrade database from v1.0.2 to v1.1 5 | */ 6 | 7 | /*global 8 | db 9 | */ 10 | 11 | // 1. Backup users' original values in case something goes wrong. 12 | db.users.find().forEach(function(user) { 13 | db.users_backup_v1_0_2_v1_1.insert([ 14 | { 15 | original_id: user._id, 16 | username: user.username, 17 | fullname: user.fullname, 18 | email: user.email 19 | } 20 | ]); 21 | }); 22 | 23 | // 2. Insert new field displayName to db.users 24 | 25 | db.users.find().forEach(function(user) { 26 | var displayName = user.fullname; 27 | if (displayName === null) { 28 | displayName = user.email.split('@')[0]; 29 | } 30 | 31 | db.users.update({_id: user._id}, 32 | {$set: {displayName: displayName}}, 33 | {multi: true}); 34 | }); 35 | 36 | // 3. Drop unused fields 37 | db.users.dropIndex("username_1"); 38 | db.users.update({}, {$unset: {username: ""}}, {multi: true}); 39 | db.users.update({}, {$unset: {fullname: ""}}, {multi: true}); 40 | -------------------------------------------------------------------------------- /services/activity.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var ActivityModel = require("../models/activity"); 6 | 7 | /* 8 | * Base class for Activities. 9 | * 10 | * An Activity is an action happened in a specific board by users. 11 | * And all other users being in that board would receive the activity in 12 | * real-time. 13 | */ 14 | function Activity(options) { 15 | var _options = options || {}; 16 | 17 | this.socket = _options.socket; 18 | this.notTellMe = _options.exceptMe === undefined ? true : _options.exceptMe; 19 | 20 | // Used for send actvity back to all clients within the board. 21 | // This socket event name follows the specification talking with client. 22 | this.eventName = "/activity:create"; 23 | } 24 | 25 | /* 26 | * Log this activity and send it to others within the board. 27 | */ 28 | Activity.prototype.log = function(data) { 29 | if (!data) { 30 | return; 31 | } 32 | 33 | var content = data.content; 34 | if (!content) { 35 | throw new Error("Missing activity's content"); 36 | } 37 | 38 | var self = this; 39 | var currentUser = this.socket.getCurrentUser(); 40 | var currentBoardId = this.socket.getCurrentBoardId(); 41 | 42 | 43 | var creatorId = data.creatorId || currentUser._id; 44 | var boardId = data.boardId || currentBoardId; 45 | 46 | new ActivityModel({ 47 | 'content': content, 48 | 'creatorId': creatorId, 49 | 'boardId': boardId 50 | }).save(function(err, savedActivity) { 51 | if (err) { 52 | console.log(err); 53 | } else { 54 | if (boardId === currentBoardId) { 55 | self.socket.room.emit( 56 | self.eventName, 57 | savedActivity, 58 | {exceptMe: self.notTellMe} 59 | ); 60 | } else { 61 | var eventRoomName = "board:" + boardId; 62 | self.socket.broadcast.to(eventRoomName).emit( 63 | self.eventName, 64 | savedActivity, 65 | {exceptMe: self.notTellMe} 66 | ); 67 | } 68 | } 69 | }); 70 | 71 | }; 72 | 73 | module.exports.Activity = Activity; 74 | 75 | }(module)); 76 | -------------------------------------------------------------------------------- /services/auth/index.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | 'use strict'; 5 | 6 | var passport = require('passport'); 7 | var mongoose = require('mongoose'); 8 | var User = require('../../models/user'); 9 | var strategies = require('./strategies'); 10 | 11 | function findUserById(id, fn) { 12 | var ObjectId = mongoose.Types.ObjectId; 13 | User.findById({ _id: new ObjectId(id) }, function(err, user) { 14 | if (err || user === null) { 15 | fn(new Error('User ' + id + ' does not exist')); 16 | } 17 | 18 | fn(null, user); 19 | }); 20 | } 21 | 22 | // Passport session setup. 23 | // To support persistent login sessions, Passport needs to be able to 24 | // serialize users into and deserialize users out of the session. Typically, 25 | // this will be as simple as storing the user ID when serializing, and finding 26 | // the user by ID when deserializing. 27 | passport.serializeUser(function(user, done) { 28 | done(null, user.id); 29 | }); 30 | 31 | passport.deserializeUser(function(id, done) { 32 | findUserById(id, function (err, user) { 33 | done(err, user); 34 | }); 35 | }); 36 | 37 | // Set strategies for authentication. 38 | var key; 39 | for (key in strategies) { 40 | if (strategies.hasOwnProperty(key)) { 41 | passport.use(strategies[key]); 42 | } 43 | } 44 | 45 | module.exports = passport; 46 | 47 | }(module)); 48 | -------------------------------------------------------------------------------- /services/auth/utils.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | 'use strict'; 5 | 6 | var crypto = require('crypto'); 7 | var utils = require('../utils.js'); 8 | 9 | var ALGORIGTHM = 'pbkdf2'; 10 | var ITERATIONS = 24000; 11 | var MAXLEN = 64; 12 | var PASSWORD_SEPARATOR = '$'; 13 | 14 | /* 15 | * pbkdf2 is used for making and checking password. 16 | * 17 | * Because we have to support 0.10.x of nodejs, SHA1 is used. 18 | */ 19 | var makePassword = function(rawPassword) { 20 | var salt = utils.randomString(); 21 | var hash = crypto.pbkdf2Sync(rawPassword, salt, ITERATIONS, MAXLEN).toString('hex'); 22 | return [ALGORIGTHM, ITERATIONS, salt, hash].join(PASSWORD_SEPARATOR); 23 | }; 24 | 25 | var checkPassword = function(rawPassword, password) { 26 | var parts = password.split(PASSWORD_SEPARATOR); 27 | var iterations = parseInt(parts[1], 10); 28 | var salt = parts[2]; 29 | var originalHash = parts[3]; 30 | var hash = crypto.pbkdf2Sync(rawPassword, salt, iterations, MAXLEN); 31 | return hash.toString('hex') === originalHash; 32 | }; 33 | 34 | /* 35 | * Create user account on behalf of user if it does not exist yet 36 | * 37 | * The user account will be returned via the callback function. 38 | */ 39 | var createUserIfNotExist = function(displayName, email, callback) { 40 | var User = require('../../models/user'); 41 | User.findOne({email: email}, function(err, user) { 42 | if (err) { 43 | return callback(err, null); 44 | } 45 | if (user) { 46 | callback(null, user); 47 | } else { 48 | new User({displayName: displayName, email: email}).save(function(err, savedUser) { 49 | if (err) { 50 | callback(err, null); 51 | } else { 52 | savedUser.setUnusablePassword(function(result) { 53 | // Although this should not happen in most cases, if this really happens, 54 | // let's ignore it and leave warning message for further debug. 55 | if (!result) { 56 | console.warn('Cannot set unusable password to user ' + email); 57 | } 58 | callback(null, savedUser); 59 | }); 60 | } 61 | }); 62 | } 63 | }); 64 | }; 65 | 66 | module.exports = { 67 | 'checkPassword': checkPassword, 68 | 'createUserIfNotExist': createUserIfNotExist, 69 | 'makePassword': makePassword 70 | }; 71 | 72 | }(module)); 73 | -------------------------------------------------------------------------------- /services/cardHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Service for managing cards 3 | */ 4 | (function(exports) { 5 | 6 | var mongoose = require('mongoose'), 7 | Card = require('./../models/card'), 8 | async = require('async'); 9 | 10 | 11 | /** 12 | * Get all cards created by the provided user 13 | * 14 | * @param {User} user 15 | * @param {Function} callback 16 | * @return {void} 17 | */ 18 | exports.listMyCards = function(user, callback) { 19 | 20 | Card.find({ 21 | isArchived: false, 22 | creatorId: user._id 23 | }) 24 | .populate({ 25 | path: 'boardId', 26 | select: '_id title isPublic isClosed' 27 | }) 28 | .populate({ 29 | path: 'listId', 30 | select: '_id title isArchived' 31 | }) 32 | .sort('-created') 33 | .exec(function(err, cards) { 34 | callback(err, cards); 35 | }); 36 | 37 | }; 38 | 39 | 40 | }(exports)); -------------------------------------------------------------------------------- /services/infra.js: -------------------------------------------------------------------------------- 1 | (function (module) { 2 | 3 | 'use strict'; 4 | 5 | var connectRedis = require('connect-redis'); 6 | var mongoose = require('mongoose'); 7 | var redis = require('socket.io/node_modules/redis'); 8 | 9 | module.exports.connectDB = function(settings) { 10 | if (settings.url) { 11 | mongoose.connect(settings.url); 12 | } else { 13 | mongoose.connect( 14 | settings.host, 15 | settings.name, 16 | settings.port, 17 | { 18 | user: settings.user, 19 | pass: settings.pass 20 | } 21 | ); 22 | } 23 | }; 24 | 25 | module.exports.connectRedis = function(settings) { 26 | var redisClients = { 27 | redisPub: redis.createClient(settings.port, settings.host), 28 | redisSub: redis.createClient(settings.port, settings.host), 29 | redisClient: redis.createClient(settings.port, settings.host) 30 | }; 31 | 32 | var password = settings.password; 33 | if (password) { 34 | redisClients.redisPub.auth(password); 35 | redisClients.redisSub.auth(password); 36 | redisClients.redisClient.auth(password); 37 | } 38 | 39 | return redisClients; 40 | }; 41 | 42 | /* 43 | * Create a sesstion store that uses Redis as a storage. 44 | * 45 | * express: the express object loaded from express framework. 46 | * settings: an object containing necessary settings to connect Redis. 47 | */ 48 | module.exports.createRedisSessionStore = function(express, settings) { 49 | var RedisSessionStore = connectRedis(express); 50 | return new RedisSessionStore({ 51 | port: settings.port, 52 | host: settings.host, 53 | ttl: settings.ttl, 54 | pass: settings.password 55 | }); 56 | }; 57 | 58 | }(module)); -------------------------------------------------------------------------------- /services/integration/bz-API.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | "use strict"; 4 | 5 | var requestify = require('requestify'); 6 | 7 | var BugzillaAPIMethod = { 8 | BUG_GET: 'Bug.get', 9 | BUG_SEARCH: 'Bug.search', 10 | BUG_COMMENT: 'Bug.comments' 11 | }; 12 | 13 | var BugzillaClient = function (options) { 14 | options = options || {}; 15 | this.username = options.username; 16 | this.password = options.password; 17 | this.apiUrl = options.url || "https://bugzilla.mozilla.org/jsonrpc.cgi"; 18 | }; 19 | 20 | var handleResponse = function (response, field, callback) { 21 | var error = response.getBody().error; 22 | var json; 23 | 24 | if (response.getCode() >= 300 || response.getCode() < 200) { 25 | error = "HTTP status " + response.response.getCode(); 26 | } else { 27 | try { 28 | json = response.getBody(); 29 | } catch (e) { 30 | error = "Response wasn't valid json: '" + response.getBody() + "'"; 31 | } 32 | } 33 | if (json && json.error) { 34 | error = json.error.message; 35 | } 36 | 37 | var ret = null; 38 | if (!error) { 39 | ret = field ? json[field] : json; 40 | } 41 | callback(error, ret); 42 | }; 43 | 44 | var requestAPI = function(method, requestMethod, params, field, callback, _this) { 45 | var url = _this.apiUrl; 46 | if (_this.username && _this.password) { 47 | params.Bugzilla_login = _this.username; 48 | params.Bugzilla_password = _this.password; 49 | } 50 | if (params) { 51 | url += '?method=' + method + '¶ms=[' + JSON.stringify(params) + ']'; 52 | } 53 | requestify.request(url, { 54 | method: requestMethod, 55 | headers: {'Content-type': 'application/json' }, 56 | dataType: 'json' 57 | }) 58 | .then(function(response) { 59 | handleResponse(response, field, callback); 60 | }); 61 | }; 62 | 63 | BugzillaClient.prototype = { 64 | query: function (queryMethod, params, callback) { 65 | if (!callback) { 66 | callback = params; 67 | params = {}; 68 | } 69 | requestAPI(queryMethod, 'GET', params, 'result', callback, this); 70 | } 71 | }; 72 | 73 | exports.createClient = function (options) { 74 | return new BugzillaClient(options); 75 | }; 76 | 77 | exports.queryMethod = BugzillaAPIMethod; 78 | 79 | }(module)); 80 | -------------------------------------------------------------------------------- /services/mail.js: -------------------------------------------------------------------------------- 1 | var nodemailer = require("nodemailer"); 2 | var wellknown = require("nodemailer/lib/wellknown"); 3 | var settings = require("../settings"); 4 | 5 | /* 6 | * Add Zimbra to wellknown mail service so that Nodemailer recognizes it. 7 | */ 8 | wellknown.Zimbra = settings.mailServices.Zimbra; 9 | 10 | function mailer() {} 11 | 12 | /* 13 | * Send mail to recipients. 14 | * 15 | * All mail options, such as sender, recipients, subject and body, is contained 16 | * in options argument. 17 | * 18 | * Arguments: 19 | * - options.from: a string, address of sender 20 | * - options.to: a string or an array, addresses of recipients 21 | * - options.subject: a string, subject of the mail 22 | * - options.body: a string, body of mail that recipients can read 23 | * - options.html: a boolean, indicate whether the body is formatted in HTML 24 | */ 25 | mailer.prototype.sendmail = function(options, callback) { 26 | var recipients = options.to; 27 | if (typeof recipients === "object" && recipients.join !== undefined) { 28 | recipients = recipients.join(", "); 29 | } 30 | var mailOptions = { 31 | from: options.from, 32 | to: recipients, 33 | subject: options.subject 34 | }; 35 | mailOptions[options.html ? "html" : "text"] = options.body; 36 | var smtpTransport = nodemailer.createTransport("SMTP", { 37 | service: "Zimbra", 38 | }); 39 | smtpTransport.sendMail(mailOptions, function(error, response) { 40 | callback(error, response); 41 | }); 42 | }; 43 | 44 | mailer.prototype.sendmailFromNoReply = function(options, callback) { 45 | options.from = settings.mailServices.noreplyAddress; 46 | this.sendmail(options, callback); 47 | }; 48 | 49 | module.exports = mailer; 50 | -------------------------------------------------------------------------------- /services/sites.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 3 | var settings = require("../settings"); 4 | 5 | module.exports.currentSite = function() { 6 | var currentPhase = settings.sites.currentPhase; 7 | return settings.sites.phases[currentPhase]; 8 | }; 9 | 10 | }(module)); 11 | -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Settings module 3 | * 4 | * settings.json maintains all settings. All modules that require settings 5 | * just need load this module by calling require. 6 | * 7 | * Module exports a settings object to client. 8 | */ 9 | 10 | (function(module) { 11 | 12 | "use strict"; 13 | 14 | module.exports = require(__dirname + "/settings.json"); 15 | 16 | }(module)); 17 | -------------------------------------------------------------------------------- /settings.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "host": "0.0.0.0", 4 | "port": 3000 5 | }, 6 | "socketIO": { 7 | "port": 3000 8 | }, 9 | "auth": { 10 | "default": "kerberos", 11 | "strategy": "remote_user", 12 | "googleOAuth": { 13 | "clientID": "", 14 | "clientSecret": "" 15 | } 16 | }, 17 | "trustedDomains": ["*"], 18 | "links": { 19 | "bugzilla": "", 20 | "hssPortal": "" 21 | }, 22 | "mailServices": { 23 | "Zimbra": { 24 | "domains": [ 25 | "xxx.com" 26 | ], 27 | "host": "mail.xxx.com", 28 | "port": 25, 29 | "secureConnection": true 30 | }, 31 | "noreplyAddress": "noreply@example.com" 32 | }, 33 | "management": { 34 | "service": { 35 | "username": "cantas" 36 | } 37 | }, 38 | "mongodb": { 39 | "host": "127.0.0.1", 40 | "name": "cantas", 41 | "pass": "", 42 | "port": 27017, 43 | "user": "cantas" 44 | }, 45 | "notification": { 46 | "subjectPrefix": "[Cantas] - " 47 | }, 48 | "piwik": { 49 | "enable": false, 50 | "siteId": 0, 51 | "siteUrl": "analytics.xxx.com" 52 | }, 53 | "realm": "XXX.COM", 54 | "redis": { 55 | "host": "127.0.0.1", 56 | "port": 6379, 57 | "ttl": 86400 58 | }, 59 | "sites": { 60 | "currentPhase": "product", 61 | "phases": { 62 | "devel": "", 63 | "local": "http://localhost:3000", 64 | "product": "", 65 | "stage": "", 66 | "test": "" 67 | } 68 | }, 69 | "bugzilla": { 70 | "url": "https://bugzilla.xxx.com/jsonrpc.cgi", 71 | "username": "", 72 | "password": "", 73 | "bugBaseUrl": "https://bugzilla.xxx.com/show_bug.cgi?id=" 74 | }, 75 | "newBoardTitle": "Untitled board" 76 | } 77 | -------------------------------------------------------------------------------- /sockets/crud/activity.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var util = require("util"); 7 | var BaseCRUD = require("./base"); 8 | 9 | function ActivityCRUD(options) { 10 | BaseCRUD.call(this, options); 11 | 12 | this.updateEnabled = false; 13 | this.patchEnabled = false; 14 | 15 | this.modelClass = require("../../models/activity"); 16 | this.key = this.modelClass.modelName.toLowerCase(); 17 | } 18 | 19 | util.inherits(ActivityCRUD, BaseCRUD); 20 | 21 | module.exports = ActivityCRUD; 22 | 23 | }(module)); 24 | -------------------------------------------------------------------------------- /sockets/crud/boardMemberRelation.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var util = require("util"); 7 | var BaseCRUD = require("./base"); 8 | 9 | function BoardMemberRelationCRUD(options) { 10 | BaseCRUD.call(this, options); 11 | 12 | this.modelClass = require("../../models/boardMemberRelation"); 13 | this.key = this.modelClass.modelName.toLowerCase(); 14 | } 15 | 16 | util.inherits(BoardMemberRelationCRUD, BaseCRUD); 17 | 18 | BoardMemberRelationCRUD.prototype._read = function(data, callback) { 19 | this.modelClass.find(data).populate("userId").exec(callback); 20 | }; 21 | 22 | module.exports = BoardMemberRelationCRUD; 23 | 24 | }(module)); 25 | -------------------------------------------------------------------------------- /sockets/crud/cardLabelRelation.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var util = require("util"); 7 | var BaseCRUD = require("./base"); 8 | var signals = require("../signals"); 9 | 10 | function CardLabelRelationCRUD(options) { 11 | BaseCRUD.call(this, options); 12 | 13 | this.createEnabled = false; 14 | this.updateEnabled = false; 15 | 16 | this.modelClass = require("../../models/cardLabelRelation"); 17 | this.key = this.modelClass.modelName.toLowerCase(); 18 | } 19 | 20 | util.inherits(CardLabelRelationCRUD, BaseCRUD); 21 | 22 | CardLabelRelationCRUD.prototype._read = function(data, callback) { 23 | if (data) { 24 | this.modelClass 25 | .find(data) 26 | .populate('labelId') 27 | .exec(function (err, result) { 28 | callback(err, result); 29 | }); 30 | } else { 31 | this.modelClass.find({}, callback); 32 | } 33 | }; 34 | 35 | CardLabelRelationCRUD.prototype._patch = function(data, callback) { 36 | var self = this; 37 | var _id = data._id || data.id; 38 | var name = '/' + this.key + '/' + _id + ':update'; 39 | delete data._id; // _id is not modifiable 40 | 41 | this.modelClass.findByIdAndUpdate(_id, data) 42 | .populate("boardId cardId labelId") 43 | .exec(function (err, updatedData) { 44 | if (err) { 45 | callback(err, updatedData); 46 | } else { 47 | self.emitMessage(name, updatedData); 48 | 49 | signals.post_patch.send(updatedData, { 50 | instance: updatedData, 51 | socket: self.socket 52 | }, function(err, result) {}); 53 | 54 | } 55 | }); 56 | }; 57 | 58 | module.exports = CardLabelRelationCRUD; 59 | 60 | }(module)); 61 | -------------------------------------------------------------------------------- /sockets/crud/checklist.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var async = require("async"); 7 | var util = require("util"); 8 | var BaseCRUD = require("./base"); 9 | var signals = require("../signals"); 10 | 11 | function ChecklistCRUD(options) { 12 | BaseCRUD.call(this, options); 13 | 14 | this.deleteEnabled = true; 15 | 16 | this.modelClass = require("../../models/checklist"); 17 | this.key = this.modelClass.modelName.toLowerCase(); 18 | } 19 | 20 | util.inherits(ChecklistCRUD, BaseCRUD); 21 | 22 | ChecklistCRUD.prototype._create = function(data, callback) { 23 | var t = new this.modelClass(data), name = '/' + this.key + ':create'; 24 | var self = this; 25 | 26 | t.save(function (err, savedObject) { 27 | if (err) { 28 | callback(err, savedObject); 29 | } else { 30 | 31 | // TODO: log activity 32 | self.emitMessage(name, t); 33 | 34 | signals.post_create.send(savedObject, { 35 | instance: savedObject, 36 | socket: self.socket 37 | }, function(err, result) {}); 38 | } 39 | }); 40 | }; 41 | 42 | ChecklistCRUD.prototype._delete = function(data, callback) { 43 | var self = this; 44 | 45 | if (data && data._id) { 46 | var field = data._id; 47 | var name = '/' + this.key + '/' + field + ':delete'; 48 | 49 | async.waterfall([ 50 | function(callback) { 51 | self.isBoardMember(function(err, isMember) { 52 | callback(err, isMember); 53 | }); 54 | }, 55 | function(isMember, callback) { 56 | if (isMember) { 57 | self.modelClass.findByIdAndRemove(data._id, function(err, removedObject) { 58 | callback(err, removedObject); 59 | }); 60 | } else { 61 | callback(true, null); 62 | } 63 | } 64 | ], function(err, removedObject) { 65 | if (err) { 66 | callback(err, data); 67 | } else { 68 | self.emitMessage(name, removedObject); 69 | 70 | signals.post_delete.send(removedObject, { 71 | instance: removedObject, 72 | socket: self.socket 73 | }, function(err, result) { }); 74 | } 75 | }); 76 | } 77 | }; 78 | 79 | module.exports = ChecklistCRUD; 80 | 81 | }(module)); 82 | -------------------------------------------------------------------------------- /sockets/crud/checklistItem.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var async = require("async"); 7 | var util = require("util"); 8 | var BaseCRUD = require("./base"); 9 | var signals = require("../signals"); 10 | 11 | function ChecklistItemCRUD(options) { 12 | BaseCRUD.call(this, options); 13 | 14 | this.deleteEnabled = true; 15 | 16 | this.modelClass = require("../../models/checklistItem"); 17 | this.key = this.modelClass.modelName.toLowerCase(); 18 | } 19 | 20 | util.inherits(ChecklistItemCRUD, BaseCRUD); 21 | 22 | ChecklistItemCRUD.prototype._create = function(data, callback) { 23 | var t = new this.modelClass(data), name = '/' + this.key + ':create'; 24 | var self = this; 25 | 26 | t.save(function (err, savedObject) { 27 | if (err) { 28 | callback(err, savedObject); 29 | } else { 30 | 31 | // TODO: log activity 32 | self.emitMessage(name, t); 33 | 34 | signals.post_create.send(savedObject, { 35 | instance: savedObject, 36 | socket: self.socket 37 | }, function(err, result) {}); 38 | } 39 | }); 40 | }; 41 | 42 | ChecklistItemCRUD.prototype._patch = function(data, callback) { 43 | var self = this; 44 | var patchInfo = self._generatePatchInfo(data); 45 | var _id = patchInfo.id; 46 | var name = '/' + this.key + '/' + _id + ':update'; 47 | var originData = patchInfo.originData; 48 | var changeFields = patchInfo.changeFields; 49 | data = patchInfo.data; 50 | 51 | 52 | async.waterfall([ 53 | function(callback) { 54 | self.isBoardMember(function(err, isMember) { 55 | callback(err, isMember); 56 | }); 57 | }, 58 | function(isMember, callback) { 59 | 60 | // patch if isMember 61 | if (isMember) { 62 | data.updatedOn = Date.now(); 63 | self.modelClass 64 | .findByIdAndUpdate(_id, data) 65 | .exec(function(err, updatedData) { 66 | callback(err, updatedData); 67 | }); 68 | } else { 69 | callback(true, null); 70 | } 71 | } 72 | ], function(err, updatedData) { 73 | if (err) { 74 | callback(err, null); 75 | } else { 76 | self.emitMessage(name, updatedData); 77 | 78 | signals.post_patch.send(updatedData, { 79 | instance: updatedData, 80 | changeFields: changeFields, 81 | originData: originData, 82 | socket: self.socket 83 | }, function(err, result) {}); 84 | } 85 | }); 86 | }; 87 | 88 | module.exports = ChecklistItemCRUD; 89 | 90 | }(module)); 91 | -------------------------------------------------------------------------------- /sockets/crud/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Load all CRUD modules. 3 | */ 4 | 5 | (function(module) { 6 | 7 | "use strict"; 8 | 9 | var fs = require("fs"); 10 | var path = require("path"); 11 | 12 | var _crudObjects = {}; 13 | 14 | module.exports.crudObjects = _crudObjects; 15 | 16 | module.exports.setUp = function(socket, handshake) { 17 | var crudModules = fs.readdirSync(__dirname); 18 | // Do not import these modules, which are used internally. 19 | var excludeNames = ['index', 'base']; 20 | var i; 21 | for (i = 0; i < crudModules.length; i++) { 22 | if (path.extname(crudModules[i]) === ".js") { 23 | var cleanName = path.basename(crudModules[i], ".js"); 24 | if (excludeNames.indexOf(cleanName) < 0) { 25 | var Class = require("./" + cleanName); 26 | var obj = new Class({ socket: socket, handshake: handshake }); 27 | obj.listen(); 28 | _crudObjects[obj.modelClass.modelName] = obj; 29 | } 30 | } 31 | } 32 | }; 33 | 34 | }(module)); 35 | -------------------------------------------------------------------------------- /sockets/crud/notification.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var util = require("util"); 7 | var BaseCRUD = require("./base"); 8 | 9 | function NotificationCRUD(options) { 10 | BaseCRUD.call(this, options); 11 | 12 | this.modelClass = require("../../models/notification"); 13 | this.key = this.modelClass.modelName.toLowerCase(); 14 | } 15 | 16 | util.inherits(NotificationCRUD, BaseCRUD); 17 | 18 | NotificationCRUD.prototype._patch = function(data, callback) { 19 | var self = this; 20 | var _id = data._id || data.id; 21 | var name = '/' + this.key + '/' + _id + ':update'; 22 | delete data._id; // _id is not modifiable 23 | 24 | this.modelClass.findByIdAndUpdate(_id, data, function (err, updatedData) { 25 | if (err) { 26 | callback(err, updatedData); 27 | } else { 28 | self.emitMessage(name, updatedData); 29 | } 30 | }); 31 | }; 32 | 33 | module.exports = NotificationCRUD; 34 | 35 | }(module)); 36 | -------------------------------------------------------------------------------- /sockets/importTrello.js: -------------------------------------------------------------------------------- 1 | (function(exports) { 2 | "use strict"; 3 | 4 | // expose variable 5 | var async = require("async"); 6 | var util = require("util"); 7 | var stdlib = require("./stdlib"); 8 | var mongoose = require('mongoose'); 9 | var User = require("../models/user"); 10 | var Board = require("../models/board"); 11 | var List = require("../models/list"); 12 | var Card = require("../models/card"); 13 | var LogActivity = require("../services/activity").Activity; 14 | 15 | exports.init = function(socket) { 16 | 17 | socket.on('import-trello-complete', function(data) { 18 | var eventRoomName = "board:" + data.boardId; 19 | var eventName = 'alert-import-trello-complete'; 20 | var user = socket.getCurrentUser(); 21 | // record import action into activity log 22 | var content = user.displayName + ' has imported new content to this board.'; 23 | var activity = new LogActivity({socket: socket, exceptMe: false}); 24 | activity.log({ 25 | 'content': content, 26 | 'creatorId': user.id, 27 | 'boardId': data.boardId 28 | }); 29 | socket.broadcast.to(eventRoomName).emit(eventName, {}); 30 | }); 31 | }; 32 | 33 | }(exports)); 34 | -------------------------------------------------------------------------------- /sockets/patch_socket.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | module.exports.patch = function(socket) { 7 | 8 | socket.constructor.prototype.getCurrentUser = function() { 9 | return this.handshake.user; 10 | }; 11 | 12 | socket.constructor.prototype.getCurrentBoardId = function() { 13 | if (this.room.board && this.room.board.length >= 2) { 14 | return this.room.board.split(':')[1]; 15 | } 16 | return null; 17 | }; 18 | 19 | }; 20 | 21 | }(module)); 22 | -------------------------------------------------------------------------------- /sockets/signals.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Prdefined signals 3 | */ 4 | 5 | (function(module) { 6 | 7 | "use strict"; 8 | 9 | var Signal = require("../services/signal"); 10 | 11 | /* 12 | * Emit after CRUD function create executes successfully. 13 | * 14 | * Arguments: 15 | * - instance: the newly created object. 16 | * - socket: client socket. 17 | */ 18 | module.exports.post_create = new Signal(["instance", "socket"]); 19 | 20 | /* 21 | * Emit after CRUD function delete executes successfully. 22 | * 23 | * Arguments: 24 | * - instance: the deleted object. 25 | * - socket: client socket. 26 | */ 27 | module.exports.post_delete = new Signal(["instance", "socket"]); 28 | 29 | /* 30 | * Emit after CRUD function patch executes successfully. 31 | * 32 | * Arguments: 33 | * - instance: the just updated object. 34 | * - socket: client socket. 35 | */ 36 | module.exports.post_patch = new Signal(["instance", "socket"]); 37 | 38 | /* 39 | * Emit after CRUD function read executes successfully. 40 | * 41 | * Arguments: 42 | * - instances: objects returned back to client. 43 | * - socket: client socket. 44 | */ 45 | module.exports.post_read = new Signal(["instances", "socket"]); 46 | 47 | /* 48 | * Emit after CRUD function read executes successfully. 49 | * 50 | * Arguments: 51 | * - instance: the updated object. 52 | * - socket: client socket. 53 | */ 54 | module.exports.post_update = new Signal(["instance", "socket"]); 55 | 56 | }(module)); 57 | -------------------------------------------------------------------------------- /sockets/stdlib.js: -------------------------------------------------------------------------------- 1 | 2 | (function(module) { 3 | 4 | "use strict"; 5 | 6 | var definition = { 7 | RESP_SUCCESS: 0, 8 | RESP_FAILURE: 1 9 | }; 10 | 11 | module.exports = definition; 12 | 13 | }(module)); 14 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # required metadata 2 | sonar.projectKey=com.redhat.cantas 3 | sonar.projectName=Cantas 4 | sonar.projectVersion=0.5 5 | 6 | # optional description 7 | sonar.projectDescription=Cantas is realtime collaboration webapp 8 | 9 | # path to source directories (required) 10 | sonar.sources=./ 11 | 12 | sonar.exclusions=**/javascripts/dist/*.js,**/javascripts/vendor/*.js,**/stylesheets/*.css 13 | 14 | # The value of the property must be the key of the language. 15 | sonar.language=js 16 | 17 | sonar.profile=node 18 | sonar.tests=spec/node 19 | 20 | sonar.dynamicAnalysis=reuseReports 21 | 22 | 23 | # Encoding of the source code 24 | sonar.sourceEncoding=UTF-8 25 | -------------------------------------------------------------------------------- /spec/javascripts/checkEmailAddress.spec.js: -------------------------------------------------------------------------------- 1 | describe( "validate email address", function () { 2 | var checkEmail = cantas.utils.checkEmail; 3 | 4 | it("It's a valid email address", function () { 5 | expect(checkEmail("example@redhat.com")).toBe(true); 6 | }); 7 | 8 | it("It's an invalid email address", function () { 9 | expect(checkEmail("eaample@redhatcom")).toBe(false); 10 | }); 11 | }); -------------------------------------------------------------------------------- /spec/javascripts/fixtures/adminConfig.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/cardBadges.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/cardDetail.html: -------------------------------------------------------------------------------- 1 | 43 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/cardVote.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/checklistItem.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/labelNeonlights.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/movelist.html: -------------------------------------------------------------------------------- 1 | 5 | 94 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/permissionWidget.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/timeout.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /spec/javascripts/fixtures/unselectLabel.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/javascripts/labelNeonlights.spec.js: -------------------------------------------------------------------------------- 1 | describe('generateLabelCorrectly',function(){ 2 | var labelNeonLightsView; 3 | 4 | beforeEach(function(){ 5 | 6 | labelNeonLightsView = new cantas.views.LabelNeonLightsView({ 7 | className: 'card-filter clearfix', 8 | collection: new cantas.models.CardLabelRelationCollection([ 9 | new cantas.models.CardLabelRelation({ 10 | boardId: '51cb97495bffe9ba08000001', 11 | cardId: '51cb974c5bffe9ba0800000f', 12 | labelId: (new cantas.models.Label({ 13 | title: 'good luck', 14 | order: '1', 15 | color: '#E7BAB6', 16 | boardId: '51cb97495bffe9ba08000001' 17 | })).toJSON(), 18 | selected: true 19 | }), 20 | new cantas.models.CardLabelRelation({ 21 | boardId: '51cb97495bffe9ba08000001', 22 | cardId: '51cb974c5bffe9ba0800000f', 23 | labelId: (new cantas.models.Label({ 24 | title: 'bad luck', 25 | order: '1', 26 | color: '#E7BAB6', 27 | boardId: '51cb97495bffe9ba08000001' 28 | })).toJSON(), 29 | selected: false 30 | }) 31 | ]), 32 | card: null 33 | }); 34 | 35 | loadFixtures('labelNeonlights.html'); 36 | labelNeonLightsView.template = jade.compile($("#template-card-labels-neonlights").text()); 37 | labelNeonLightsView.render().$el.appendTo('body'); 38 | 39 | }); 40 | 41 | it('should generate correct title of label', function(){ 42 | expect(labelNeonLightsView.$('div.clabel')).toHaveProp('title', 'good luck'); 43 | }); 44 | 45 | it('should generate correct color of label', function(){ 46 | expect(labelNeonLightsView.$('div.clabel').css('background-color')).toEqual('rgb(231, 186, 182)'); 47 | }); 48 | 49 | it('should not generate the second label neon light', function(){ 50 | expect(labelNeonLightsView.$('div.clabel').eq(1).length).toEqual(0); 51 | }); 52 | 53 | }); -------------------------------------------------------------------------------- /spec/javascripts/movelist.spec.js: -------------------------------------------------------------------------------- 1 | describe('MoveListToView',function(){ 2 | var view; 3 | 4 | beforeEach(function(){ 5 | $('').appendTo('body'); 6 | loadFixtures('movelist.html'); 7 | view = new cantas.views.MoveListToView({ 8 | title: 'Move List', 9 | listId: "5195d6bc07eec66c36000014", 10 | boardId: "51b97050240934cf2f000005" 11 | }); 12 | 13 | view.template = jade.compile($("#template-move-to").text()); 14 | view.render(); 15 | }); 16 | 17 | afterEach(function() { 18 | $('.window-overlay').empty(); 19 | }); 20 | 21 | it('should showing move listTo view', function(){ 22 | expect($('body').find('.window-overlay')).not.toBeHidden(); 23 | expect($('body').find('.window-overlay').find('.modal-header > h3')).toHaveText("Move To"); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /spec/javascripts/permissionConfig.spec.js: -------------------------------------------------------------------------------- 1 | describe('Board permissions widget test', function() { 2 | var boardModel; 3 | var widget; 4 | 5 | beforeEach(function() { 6 | $('
').appendTo('body'); 7 | 8 | boardModel = new cantas.models.Board({ 9 | exampleStatus: 'enabled' // Should be able to work with any key on the model 10 | }); 11 | 12 | widget = new cantas.views.PermissionWidgetView({ 13 | el: '.widget-container', 14 | model: boardModel, 15 | key: 'exampleStatus' 16 | }); 17 | 18 | loadFixtures('permissionWidget.html'); 19 | widget.template = jade.compile($('#template-permission-widget-view').text()); 20 | widget.render(); 21 | 22 | spyOn(boardModel, 'patch').and.callFake(function() {}); 23 | }); 24 | 25 | afterEach(function() { 26 | widget.close(); 27 | }); 28 | 29 | it("Should default status to enabled", function() { 30 | expect($('[data-permission="enabled"]')).toHaveClass('checked'); 31 | }); 32 | 33 | it('Should call patch on the model when setting to disabled', function() { 34 | widget.$('[data-permission="disabled"]').trigger('click'); 35 | expect(boardModel.patch).toHaveBeenCalled; 36 | }); 37 | 38 | it("Should call patch on the model when setting to opened", function() { 39 | widget.$('[data-permission="opened"]').trigger('click'); 40 | expect(boardModel.patch).toHaveBeenCalled; 41 | }); 42 | 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /spec/javascripts/sycnCardBadges.spec.js: -------------------------------------------------------------------------------- 1 | describe('sycnCardBadges',function(){ 2 | var view; 3 | var badges = { 4 | comments: 1, 5 | checkitemsChecked: 2, 6 | checkitems: 3 7 | }; 8 | 9 | 10 | beforeEach(function(){ 11 | $('
').appendTo('body'); 12 | loadFixtures('cardBadges.html'); 13 | 14 | var card = new cantas.models.Card({ 15 | title: "test card badges", 16 | assignees: [], 17 | badges: badges 18 | }); 19 | 20 | view = new cantas.views.CardView({ 21 | model: card, 22 | attributes: {index: 1} 23 | }); 24 | 25 | view.template = jade.compile($("#template-card-view").text()); 26 | $(".list-content").append(view.render().el); 27 | }); 28 | 29 | afterEach(function() { 30 | $('.list-content').empty(); 31 | }); 32 | 33 | it('should show card view with badges', function(){ 34 | var expectedCommentBadge = badges.comments.toString(); 35 | var expectedChecklistBadge = badges.checkitemsChecked + '/' + badges.checkitems; 36 | 37 | // card view shown as expected 38 | expect($('body').find('.list-content').find('.list-card').length).toEqual(1); 39 | 40 | // comments badge shown as expected 41 | expect($('body').find('.list-content').find('.list-card') 42 | .find('.card-items').find('.card-comment').html() 43 | ).toEqual(expectedCommentBadge); 44 | 45 | // checklist items badge shown as expected 46 | expect($('body').find('.list-content').find('.list-card') 47 | .find('.card-items').find('.card-checklist').html() 48 | ).toEqual(expectedChecklistBadge); 49 | }); 50 | 51 | it('should update comments badge', function(){ 52 | badges.comments = 5; 53 | var expectedCommentBadge = badges.comments.toString(); 54 | 55 | view.model.set(badges); 56 | 57 | // wait 1 second to update comments badge 58 | setTimeout(function(){ 59 | expect($('body').find('.list-content').find('.list-card') 60 | .find('.card-items').find('.card-comment').html() 61 | ).toEqual(expectedCommentBadge); 62 | }, 1000); 63 | }); 64 | 65 | it('should update checklist items badge', function(){ 66 | badges.checkitems = 10; 67 | badges.checkitemsChecked = 3; 68 | var expectedChecklistBadge = badges.checkitemsChecked + '/' + badges.checkitems; 69 | 70 | view.model.set(badges); 71 | 72 | // wait 1 second to update checklist items badge 73 | setTimeout(function(){ 74 | expect($('body').find('.list-content').find('.list-card') 75 | .find('.card-items').find('.card-checklist').html() 76 | ).toEqual(expectedChecklistBadge); 77 | }, 1000); 78 | }); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /spec/javascripts/timeout.spec.js: -------------------------------------------------------------------------------- 1 | describe('user session timeout', function(){ 2 | 3 | it('#renderTimeoutBox window', function(){ 4 | loadFixtures('timeout.html'); 5 | expect($('body').find('.force-alert')).not.toBeVisible(); 6 | cantas.utils.renderTimeoutBox(); 7 | expect($('body').find('.force-alert')).toBeVisible(); 8 | }); 9 | 10 | it('#renderClearTimeoutBox window', function(){ 11 | loadFixtures('timeout.html'); 12 | cantas.utils.renderClearTimeoutBox() 13 | expect($('body').find('.force-alert')).not.toBeVisible(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /spec/javascripts/unselectLabel.spec.js: -------------------------------------------------------------------------------- 1 | describe('unselectLabel',function(){ 2 | var labelAssignView; 3 | var template_data; 4 | var relation; 5 | beforeEach(function(){ 6 | $('
').appendTo('body'); 7 | 8 | labelAssignView = new cantas.views.LabelAssignView({ 9 | collection: new cantas.models.CardLabelRelationCollection, 10 | card: new cantas.models.Card({ 11 | title: 'good luck', 12 | creatorId: '516f593cbd645e7306000001', 13 | listId: '51cb97495bffe9ba08000003', 14 | boardId: '51cb97495bffe9ba08000001' 15 | }) 16 | }); 17 | 18 | loadFixtures('unselectLabel.html'); 19 | labelAssignView.template = jade.compile($('#template-label-assign-view').text()); 20 | template_data = {relations: [{ 21 | _id: '51cb974c5bffe9ba08000011', 22 | color: '#E7BAB6', 23 | selected: true 24 | }]}; 25 | 26 | labelAssignView.$el.html(labelAssignView.template(template_data)).appendTo('body'); 27 | 28 | relation = new cantas.models.CardLabelRelation({ 29 | _id: '51cb974c5bffe9ba08000011', 30 | boardId: '51cb97495bffe9ba08000001', 31 | cardId: '51cb974c5bffe9ba0800000f', 32 | labelId: '51cb97495bffe9ba08000007', 33 | selected: true 34 | }); 35 | 36 | labelAssignView.collection = new cantas.models.CardLabelRelationCollection([relation]); 37 | 38 | spyOn(relation, "patch").and.callFake(function() {}); 39 | }); 40 | 41 | it('should unselect the label by click the lable with √', function(){ 42 | labelAssignView.$('ul.label-items').children().eq(0).trigger('click'); 43 | expect(relation.patch).toHaveBeenCalled(); 44 | }); 45 | 46 | }); -------------------------------------------------------------------------------- /spec/node/dbinit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var dbURI = 'mongodb://localhost/cantas_test'; 5 | var async = require('async'); 6 | 7 | beforeEach(function (done) { 8 | 9 | function clearDB() { 10 | async.series([ 11 | function(callback){ 12 | for (var i in mongoose.connection.collections) { 13 | mongoose.connection.collections[i].remove(function() {}); 14 | } 15 | callback(null); 16 | }, 17 | function(callback){ 18 | for (var i in mongoose.connection.collections) { 19 | mongoose.connection.collections[i].dropAllIndexes(function() {}); 20 | } 21 | callback(null); 22 | } 23 | ], 24 | function(err, results){ 25 | if(err || !results) throw err; 26 | done(); 27 | }); 28 | } 29 | 30 | if (mongoose.connection.readyState === 0) { 31 | mongoose.connect(dbURI, function (err) { 32 | if (err) { 33 | throw err; 34 | } 35 | return clearDB(); 36 | }); 37 | } else { 38 | return clearDB(); 39 | } 40 | }); 41 | 42 | afterEach(function (done) { 43 | setTimeout(function(){ 44 | mongoose.connection.close(); 45 | done(); 46 | },0); 47 | }); 48 | -------------------------------------------------------------------------------- /spec/node/invitation-test.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var async = require("async"); 5 | var assert = require("assert"); 6 | var User = require("../../models/user"); 7 | var Board = require("../../models/board"); 8 | var utils = require("./utils"); 9 | 10 | var mongoose = require('mongoose'); 11 | var dbinit = require('./dbinit'); 12 | 13 | describe("Test board member invitation", function() { 14 | 15 | var invitee = null; 16 | var inviter = null; 17 | var user3 = null; 18 | var board = null; 19 | 20 | beforeEach(function(done) { 21 | async.series([ 22 | function(callback){ 23 | invitee = new User({ 24 | displayName: "test_user1", 25 | email: "test_user1@example.com" 26 | }); 27 | invitee.save(function(err){ 28 | callback(err,invitee); 29 | }); 30 | }, 31 | function(callback){ 32 | inviter = new User({ 33 | displayName: "test_user2", 34 | email: "test_user2@example.com" 35 | }); 36 | inviter.save(function(err){ 37 | callback(err,inviter); 38 | }); 39 | }, 40 | function(callback){ 41 | user3 = new User({ 42 | displayName: "test_user3", 43 | email: "test_user3@example.com" 44 | }); 45 | user3.save(function(err){ 46 | callback(err,user3); 47 | }); 48 | }, 49 | function(callback){ 50 | board = new Board({ title: "Test board", creatorId: inviter.id }); 51 | board.save(function(err){ 52 | callback(err,board); 53 | }); 54 | } 55 | ], 56 | function(err,results){ 57 | if(err || !results){ 58 | throw Error("invitation test:"+err); 59 | } 60 | 61 | if(results.length) { 62 | return done(); 63 | } 64 | }); 65 | }); 66 | 67 | afterEach(function(done) { 68 | utils.removeEachSeries([board, inviter, invitee, user3], done); 69 | }); 70 | 71 | // it("Test invite a board member"); 72 | }); 73 | -------------------------------------------------------------------------------- /spec/node/mail-test.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var assert = require("assert"); 5 | var Mailer = require("../../services/mail"); 6 | var SMTPTransport = require("nodemailer/lib/engines/smtp"); 7 | 8 | describe("Test Mailer interface", function() { 9 | 10 | var mailer = null; 11 | var response = null; 12 | var comingEmailMessage = null; 13 | 14 | before(function() { 15 | mailer = new Mailer(); 16 | response = {}; 17 | comingEmailMessage = null; 18 | 19 | /* 20 | * Fake method to send mail, its behavior is always correct. 21 | * 22 | * I replace the SMTPTransport's sendMail method, if you want its original 23 | * behavior not changed, SMTPConnectionPool's sendMail method should be 24 | * replaced. 25 | */ 26 | SMTPTransport.prototype.sendMail = function(emailMessage, callback) { 27 | // Instead of sending mail message actually, store it to variable to 28 | // stimulate the succeed 29 | comingEmailMessage = emailMessage; 30 | callback(null, response); 31 | }; 32 | }); 33 | 34 | it("Sending mail to single recipient.", function(done) { 35 | mailer.sendmail({ 36 | from: "cqi@redhat.com", 37 | to: "cqi@redhat.com", 38 | subject: "hello from", 39 | body: "Hello world." 40 | }, function(error, response) { 41 | assert.equal(comingEmailMessage._message.from, "cqi@redhat.com"); 42 | }); 43 | done(); 44 | }); 45 | 46 | it("Sending mail to multiple recipients.", function(done) { 47 | var mailOptions = { 48 | from: "cqi@redhat.com", 49 | to: ["cqi@redhat.com", "other@redhat.com"], 50 | subject: "hello world", 51 | body: "Hello world." 52 | }; 53 | mailer.sendmail(mailOptions, function(error, response) { 54 | assert.equal(comingEmailMessage._message.to, mailOptions.to.join(", ")); 55 | }) 56 | done(); 57 | }); 58 | 59 | it("Sending mail from noreply.", function(done) { 60 | var mailOptions = { 61 | to: "cqi@redhat.com", 62 | subject: "hello world", 63 | body: "Hello world." 64 | }; 65 | mailer.sendmailFromNoReply(mailOptions, function(error, response) { 66 | assert.equal(comingEmailMessage._message.from, "noreply@example.com"); 67 | assert.equal(comingEmailMessage._message.to, "cqi@redhat.com"); 68 | }) 69 | done(); 70 | }); 71 | }) 72 | -------------------------------------------------------------------------------- /spec/node/service-utils-test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var assert = require('assert'); 5 | var utils = require('../../services/utils'); 6 | 7 | 8 | describe('Test generate random string', function() { 9 | 10 | it('get random string', function() { 11 | var string = utils.randomString(); 12 | assert(string.length > 0); 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /spec/node/services-auth-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var utils = require('../../services/auth/utils'); 5 | 6 | 7 | describe('Test password operations', function() { 8 | 9 | it('make password', function() { 10 | var password = utils.makePassword('admin'); 11 | assert(password.length > 0); 12 | }); 13 | 14 | it('check a password', function() { 15 | var rawPassword = 'do not tell u'; 16 | var password = utils.makePassword(rawPassword); 17 | assert(utils.checkPassword('what?', password) === false); 18 | assert(utils.checkPassword(rawPassword, password) === true); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /spec/node/socket-patch-test.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict"; 3 | 4 | var assert = require("assert"); 5 | var SocketPatch = require("../../sockets/patch_socket"); 6 | var EventEmitter = process.EventEmitter; 7 | 8 | // Mock the real Socket class 9 | var Socket = function() {}; 10 | 11 | Socket.prototype.__proto__ = EventEmitter.prototype; 12 | 13 | Socket.prototype.__defineGetter__('handshake', function () { 14 | return {user: {displayName: "socket user"}}; 15 | }); 16 | 17 | 18 | describe("Test socket patch", function() { 19 | 20 | var socket = null; 21 | 22 | beforeEach(function() { 23 | socket = new Socket(); 24 | }); 25 | 26 | afterEach(function() { 27 | socket = null; 28 | }); 29 | 30 | it("Test patch getCurrentUser", function() { 31 | SocketPatch.patch(socket); 32 | 33 | assert.notEqual(socket.getCurrentUser, undefined, 34 | "getCurrentUser method should exist."); 35 | assert.equal(socket.handshake.user.displayName, 36 | socket.getCurrentUser().displayName, 37 | "User's display name does not equal to the one within socket."); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /spec/node/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var async = require("async"); 4 | 5 | 6 | var removeEachSeries = function(objs, done) { 7 | async.eachSeries(objs, 8 | function(obj, callback) { 9 | obj.remove(function(err) { 10 | if (err) { 11 | callback(err); 12 | } else { 13 | callback(null); 14 | } 15 | }); 16 | }, 17 | function(err) { 18 | if (err) { throw err; } 19 | done(); 20 | }); 21 | } 22 | 23 | module.exports.removeEachSeries = removeEachSeries; -------------------------------------------------------------------------------- /uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /views/backbone/accountSettings.jade: -------------------------------------------------------------------------------- 1 | script#template-account-settings-view(type="text/template") 2 | |div.board-ul-title 3 | | h3= header 4 | |div.settings-container 5 | | form(role="form") 6 | | div.form-group 7 | | label.control-label Display Name 8 | | input#displayName(type="text", value=displayName) 9 | -------------------------------------------------------------------------------- /views/backbone/admin-config.jade: -------------------------------------------------------------------------------- 1 | script#template-admin-config-view(type="text/template") 2 | |div.modal-config.fade.in 3 | | div.modal.fade.in 4 | | div.modal-header 5 | | button.close.btn(data-dismiss="modal") × 6 | | h3 Permission Control 7 | | div.modal-body 8 | | article.admin-config 9 | | section.row-fluid 10 | | dl.span6 11 | | dt 12 | | h3 Vote 13 | | dd.js-vote 14 | | dl.span6 15 | | dt 16 | | h3 Comment 17 | | dd.js-comment 18 | | section.row-fluid 19 | | dl.span6 20 | | dt 21 | | h3 Manage Lists 22 | | dd.js-list 23 | | dl.span6 24 | | dt 25 | | h3 Manage Cards 26 | | dd.js-card 27 | | footer.modal-footer 28 | | button.btn(data-dismiss="modal") Close 29 | -------------------------------------------------------------------------------- /views/backbone/archived-item.jade: -------------------------------------------------------------------------------- 1 | script#template-archived-items-view(type="text/template") 2 | |- if (section == 'lists') 3 | | div.ar-list 4 | | p.item-name= item.title 5 | | a(href="#",class="js-reopen sent",data-listid="\#{item._id}",title="Send to board") 6 | |- else if (section == 'cards') 7 | | div(class=(highlighted === item._id) ? 'card highlighted' : 'card') 8 | | p= item.title 9 | | a(href="#",class="js-cards-reopen sent",data-cardid="\#{item._id}",title="Send to board") -------------------------------------------------------------------------------- /views/backbone/archived.jade: -------------------------------------------------------------------------------- 1 | script#template-archived-view(type="text/template") 2 | |div 3 | | div.modal-header.clearfix 4 | | button(type="button",class="close",data-dismiss="modal",aria-hidden="true")× 5 | | h2.window-title-text Archived  6 | | span.js-current-section \#{section} 7 | | ul.nav.nav-tabs 8 | | li.lists-tab 9 | | a.js-switch-section List 10 | | li.cards-tab 11 | | a.js-switch-section Card 12 | | div.modal-body 13 | | p.loading Loading.... 14 | | div.window_module 15 | | ul.archive-list.js-archive-items 16 | -------------------------------------------------------------------------------- /views/backbone/board-list.jade: -------------------------------------------------------------------------------- 1 | script#template-board-list-view(type="text/template") 2 | |div.board-ul-title 3 | | h3= h3Header 4 | | a.btn.btn-warning.new-board(type="text", onclick="cantas.navigateTo('boards/new')") New Board 5 | |div.board-ul-wapper 6 | | div.clearfix.board-ul-header 7 | | span.owner.board-name Board Name 8 | | span Creation Time 9 | | span Owner 10 | |ul.board-ul 11 | | each board in boards 12 | | if board.isClosed == true 13 | | li(class=(highlighted === board._id) ? 'board-close highlighted' : 'board-close') 14 | | div.owner.board-name= board.title 15 | | div.board-meta 16 | | span= cantas.utils.formatDate(board.created) 17 | | span= board.creatorId.displayName 18 | | if isCreator(board) 19 | | a.js-open-board.reopen.board-list-status(data-board= board._id, title="Open this board") 20 | | else 21 | | li(class=(highlighted === board._id) ? 'highlighted' : '') 22 | | a.owner.board-name.js-view-board(href="/board/\#{board._id}")= board.title 23 | | div.board-meta 24 | | span= cantas.utils.formatDate(board.created) 25 | | span= board.creatorId.displayName 26 | | if isCreator(board) 27 | | a.js-close-board.delete.board-list-status(data-board= board._id, title="Close this board") 28 | -------------------------------------------------------------------------------- /views/backbone/board-title.jade: -------------------------------------------------------------------------------- 1 | script#template-board-title-view(type="text/template") 2 | |div#board-edit-title.edit-title 3 | | input#board-title-input(type="text") 4 | | button#board-title-save.btn(href="javascript:void(0)") Save 5 | -------------------------------------------------------------------------------- /views/backbone/card-add.jade: -------------------------------------------------------------------------------- 1 | script#template-card-add-view(type="text/template") 2 | |div.hidden-option.ui-state-disabled.js-add-card-area 3 | | form.form-horizontal.option-form 4 | | div.control-group 5 | | div.controls 6 | | textarea#card-form.input-xlarge.xx(placeholder="Untitled Card") 7 | | footer.add-card-button.clearfix 8 | | button.btn.btn-primary.finish-add-checklist.js-btn-submit Add 9 | | button.btn.option-cancel.js-cancel Cancel 10 | -------------------------------------------------------------------------------- /views/backbone/card-assign.jade: -------------------------------------------------------------------------------- 1 | script#template-card-assign-view(type="text/template") 2 | |header.modal-header 3 | | button#close-board-edit.close.js-close-assign-window × 4 | | h3 Assign 5 | |div.modal-body 6 | | ul.assignee 7 | | each member in members 8 | | if member.checked 9 | | li.js-select-assignee.checked(data-uid= member.userId._id)= member.userId.username 10 | | span 11 | | else 12 | | li.js-select-assignee(data-uid= member.userId._id)= member.userId.username 13 | | span 14 | |footer.modal-footer 15 | | button#save-board-edit.btn.btn-primary.js-save-assignee Save 16 | | button#cancel-board-edit.btn.js-cancel-assign-window Cancel 17 | -------------------------------------------------------------------------------- /views/backbone/card-attachment-download.jade: -------------------------------------------------------------------------------- 1 | script#template-attachment-download-view(type="text/template"). 2 | td.download-cover 3 | if fileType === 'picture' 4 | div.checkbox.js-download-cover 5 | span 6 | td.download-preview 7 | if thumbnail 8 | img(src= thumbnail) 9 | if isBoardMember 10 | td.download-name 11 | a(href=url, target='_blank')= fileName 12 | else 13 | td.download-name= fileName 14 | td.download-size= size 15 | td.download-uploader= uploaderId.username 16 | td.download-createdOn= createdOn 17 | td.download-control 18 | if isBoardAdmin || (isBoardMember && isUploader) 19 | button.btn.btn-danger.download-delete.js-download-delete Delete -------------------------------------------------------------------------------- /views/backbone/card-attachment-upload.jade: -------------------------------------------------------------------------------- 1 | script#template-attachment-upload-view(type="text/template"). 2 | td.upload-preview 3 | td.upload-name= name 4 | td.upload-size= fileSize 5 | div.progress.progress-success.progress-striped.active.js-upload-progress(role='progressbar', aria-valuemin="0", aria-valuemax='100', aria-valuenow='0') 6 | div.bar(style='width:0%') 7 | td.upload-control 8 | button.btn.btn-primary.start.js-upload-start(disabled='disabled') Processing... 9 | button.btn.btn-danger.upload-delete.js-upload-delete Delete -------------------------------------------------------------------------------- /views/backbone/card-attachment.jade: -------------------------------------------------------------------------------- 1 | script#template-attachment-view(type="text/template"). 2 | h4 Attachment 3 | table.fileupload-table.table-striped.attachment-download-table.js-attachment-download-table(role='presentation') 4 | tbody 5 | if isBoardMember 6 | span.btn.btn-success.fileinput-button 7 | span Select  files... 8 | input#attachmentUpload(type="file", multiple="", name="attachment") 9 | table.fileupload-table.table-striped.attachment-upload-table.js-attachment-upload-table(role='presentation') 10 | tbody 11 | -------------------------------------------------------------------------------- /views/backbone/card-checkitem.jade: -------------------------------------------------------------------------------- 1 | script#template-card-checkitem(type="text/template") 2 | |p.js-check-item-text(title="Edit Content")= cantas.utils.safeString(content) 3 | |div.checkbox.js-item-checkbox 4 | | span 5 | |a.delete.delete-item.js-item-delete 6 | -------------------------------------------------------------------------------- /views/backbone/card-checklist.jade: -------------------------------------------------------------------------------- 1 | script#template-card-checklist(type="text/template") 2 | |a.delete 3 | |div.checklist-title 4 | | h4= cantas.utils.safeString(title) 5 | | a.js-fold-items.extend 6 | | a.delete.delete-checklist.js-checklist-delete 7 | | div.progress 8 | | div.bar.bar-success.bar-self(style="width: 0%;") 9 | | a 0% 10 | | ul.js-items 11 | | ul.js-item-entryview 12 | | li.js-add-item 13 | | a.new-item Add item 14 | -------------------------------------------------------------------------------- /views/backbone/card-detail.jade: -------------------------------------------------------------------------------- 1 | script#template-card-detail-view(type="text/template") 2 | |div(id= card._id, class="modal card-detail") 3 | | div.modal-header 4 | | button.close(data-dismiss="modal")× 5 | | h3.card-detail-title.card-title.js-edit-title.js-title(title="Click to edit...")= card.title 6 | | div.card-option-edit.hidden.js-edit-title-area 7 | | textarea.js-title-input 8 | | button.btn.btn-primary.js-save-title Save 9 | | button.btn.js-cancel-title Cancel 10 | | section 11 | | a.assigned.js-edit-assign.js-assignees= card.assignees 12 | | a.card-label.js-edit-label 13 | | span Label 14 | | div.card-filter.clearfix 15 | | a.card-vote.js-edit-vote 16 | | a.js-subscribe 17 | | a.card-due-date.js-edit-due-date 18 | | if card.dueDateDisplay 19 | | span= card.dueDateDisplay 20 | | else 21 | | span Due Date 22 | | div.modal.window-label.window-detail.hide 23 | | div.modal.window-assign.window-detail.hide 24 | | div.modal.window-vote.window-detail.hide 25 | | div.modal.window-due-date.window-detail.hide 26 | | div.modal.window-attachment.window-detail.hide 27 | | div.modal-body 28 | | div.card-option-panel 29 | | section 30 | | a.js-edit-assign 31 | | span Assign 32 | | section(style="display: none;") 33 | | a.js-add-duedate 34 | | span Due Date 35 | | section(title="Add a checklist") 36 | | a.js-add-checklist 37 | | span Checklist 38 | | section 39 | | a.js-add-attachment 40 | | span Attachment 41 | | section 42 | | a.js-add-comment 43 | | span Comment 44 | | section.card-option.card-description 45 | | h4.desc.card-detail-desc.js-edit-desc.js-desc(title="Click to edit...")!= card.description 46 | | a: span.new-item.js-edit-desc Edit Card Description 47 | | div.card-option-edit.js-edit-desc-area 48 | | textarea.js-desc-input(placeholder="Add description") 49 | | button.btn.btn-primary.js-save-desc Save 50 | | button.btn.js-cancel-desc Cancel 51 | | section.card-option.js-checklist-section 52 | | section.card-option.attachment 53 | | section.card-option.comment 54 | | div.modal-footer 55 | | a.new-item.markdown-help.footer-left(href="http://daringfireball.net/projects/markdown/syntax", target="_blank") Markdown Syntax Help 56 | | i.fa.icon-external-link 57 | | a.btn.js-close-card-detail(data-dismiss="modal", href="javascript: void(0)") Close 58 | -------------------------------------------------------------------------------- /views/backbone/card-due-date.jade: -------------------------------------------------------------------------------- 1 | script#template-card-due-date-view(type="text/template") 2 | |header.modal-header 3 | | button#close-board-edit.close.js-close × 4 | | h3 Due Date 5 | |div.modal-body 6 | | form(id="form-due-date") 7 | | div.due-date-inputs 8 | | div.field-date 9 | | label Date 10 | | input(type="text", value=dueDate, class="input-due-date js-due-date", name="due-date") 11 | | div.field-time 12 | | label Time 13 | | input(type="text", value=dueTime, class="input-due-time js-due-time", name="due-time") 14 | | div.js-datepicker 15 | | div.js-timepicker 16 | |footer.modal-footer 17 | | button.btn.btn-primary.js-save Save 18 | | button.btn.btn-danger.js-delete Remove 19 | | button.btn.js-close Cancel -------------------------------------------------------------------------------- /views/backbone/card-labels-neonlights.jade: -------------------------------------------------------------------------------- 1 | script#template-card-labels-neonlights(type="text/template") 2 | |each relation in relations 3 | | if relation.selected 4 | | div(title=relation.labelId.title, style="background-color: " + relation.labelId.color).clabel 5 | -------------------------------------------------------------------------------- /views/backbone/card-list.jade: -------------------------------------------------------------------------------- 1 | script#template-card-list-view(type="text/template") 2 | |div.board-ul-title 3 | | h3= h3Header 4 | |div.card-scroll-view 5 | | div.card-archive 6 | 7 | script#template-card-list-view-no-results(type="text/template") 8 | |p.alert.alert-info 9 | | i.icon-info-sign 10 | | span= message 11 | 12 | script#template-card-list-load-more(type="text/template") 13 | |div.load-more 14 | | button(type="button").btn.btn-warning.btn-load-more.js-load-more 15 | | i.icon-spin.icon-spinner 16 | | i.icon-chevron-down 17 | | span Load More -------------------------------------------------------------------------------- /views/backbone/card-menu.jade: -------------------------------------------------------------------------------- 1 | div#card-menu.modal.card-menu.menu.hide(style="display:none") 2 | header.modal-header 3 | h4 Card Options 4 | div.modal-body 5 | ul 6 | li.js-archive-card 7 | a Archive this card 8 | li.js-move-card 9 | a Move Card 10 | -------------------------------------------------------------------------------- /views/backbone/card-vote.jade: -------------------------------------------------------------------------------- 1 | script#template-card-vote-view(type="text/template") 2 | |header.modal-header 3 | | button#close-board-edit.close.js-close-vote-window × 4 | | h3 Vote 5 | |div.modal-body 6 | | if voteControl === 'opened' 7 | | a#card-agree.js-vote-agree.burst-12.agree.vote 8 | | span 9 | | span 10 | | span 11 | | a#card-disagree.js-vote-disagree.burst-12.disagree.vote 12 | | span 13 | | span 14 | | span 15 | | else if voteControl === 'disabled' 16 | | span This vote is only for members. 17 | | else if voteControl === 'closed' 18 | | span This vote is closed. 19 | -------------------------------------------------------------------------------- /views/backbone/card-votes-total.jade: -------------------------------------------------------------------------------- 1 | script#template-card-votes-total-view(type="text/template") 2 | |span.vote-agree-num= voteYes 3 | |span.agree 4 | |span.vote-disagree-num= voteNo 5 | |span.disagree 6 | -------------------------------------------------------------------------------- /views/backbone/card.jade: -------------------------------------------------------------------------------- 1 | script#template-card-view(type="text/template") 2 | |div.card-container.ui-state-default.card-title 3 | | div.card.clearfix 4 | | a.setting.card-setting 5 | | p= title 6 | | if typeof cover === 'string' 7 | | if cover 8 | | if coverURL.indexOf('thumb-card') > -1 9 | | div.card-cover(style='background-image:\#{coverURL};') 10 | | else 11 | | div.card-cover.original-size(style='background-image:\#{coverURL};') 12 | | else 13 | | div.card-cover.hide 14 | | section.card-items 15 | | each assignee in assignees 16 | | span.card-assignee(title="Assignee: "+assignee.username + "")= assignee.username 17 | | if typeof badges === "object" 18 | | if badges.comments !== 0 19 | | span.card-comment(title="\#{badges.comments} comment(s).")= badges.comments 20 | | if badges.checkitems !== 0 21 | | span.card-checklist(title=" \#{checkitemsProgress} checklist items completed.")= checkitemsProgress 22 | | if (badges.votesYes + badges.votesNo) !== 0 23 | | span.card-vote(title=" \#{badges.votesYes} agree, \#{badges.votesNo} disagree.")= badgesVotes 24 | | if badges.attachments !== 0 25 | | span.card-attachment(title=" \#{badges.attachments} attachment(s).")= badges.attachments 26 | | if dueDateDisplay 27 | | span.card-due-date= "Due " + dueDateDisplay 28 | | if typeof index !== "undefined" 29 | | div.card-index(title="Card Position") 30 | | span.order-number= index 31 | | div.card-filter.clearfix 32 | 33 | script#template-card-meta-view(type="text/template") 34 | |div.card-meta!= meta -------------------------------------------------------------------------------- /views/backbone/comment-item.jade: -------------------------------------------------------------------------------- 1 | script#template-comment-item-view(type="text/template") 2 | |dt 3 | | span.speanker= authorId.username 4 | |dd 5 | | span.speaker-detail!= content 6 | | div.comment-option 7 | | div.item-time= "Creation: " + cantas.utils.formatDate(createdOn) 8 | | if typeof updatedOn !== 'undefined' 9 | | div.item-time= " Last Modified: " + cantas.utils.formatDate(updatedOn) 10 | | if authorId._id === cantas.utils.getCurrentUser().id 11 | | a.edit-comment.js-edit-comment(data-cid= _id) Edit 12 | 13 | script#template-comment-item-edit-view(type="text/template") 14 | |textarea.js-comment-input 15 | |button.btn.btn-primary.js-save-comment Save 16 | |button.btn.js-cancel-comment Cancel -------------------------------------------------------------------------------- /views/backbone/comment.jade: -------------------------------------------------------------------------------- 1 | script#template-comment-view(type="text/template") 2 | |h4 Comment 3 | |textarea.js-add-comment-input(placeholder="Add comment") 4 | |div.js-comment-controls.hide.comment-button 5 | | button.btn.btn-primary.js-save-comment Save 6 | | button.btn.js-cancel-comment Cancel 7 | |div.comment-list 8 | -------------------------------------------------------------------------------- /views/backbone/dashboard-navigation.jade: -------------------------------------------------------------------------------- 1 | script#template-dashboard-navigation-view(type="text/template") 2 | |ul 3 | | li 4 | | li 5 | | span 6 | | a.nav-boards-public(href="/boards/public") Public Boards 7 | | li 8 | | span 9 | | a.nav-boards-mine(href="/boards/mine") My Boards 10 | | li 11 | | span 12 | | a.nav-boards-closed(href="/boards/closed") Closed Board 13 | | li 14 | | span 15 | | a.nav-cards-mine(href="/cards/mine") My Cards 16 | | li 17 | | span 18 | | a.nav-account-settings(href="/account") Account Settings 19 | | li -------------------------------------------------------------------------------- /views/backbone/dashboard.jade: -------------------------------------------------------------------------------- 1 | script#template-dashboard-layout-view(type="text/template") 2 | |div.row-fluid.dashboard-layout 3 | | div.span2.dashboard-navigation 4 | | div.span10.dashboard-content -------------------------------------------------------------------------------- /views/backbone/import-bugzilla.jade: -------------------------------------------------------------------------------- 1 | script#template-import-bugzilla-view(type="text/template"). 2 | div 3 | div.modal-header 4 | button(type="button",class="close",data-dismiss="modal",aria-hidden="true")× 5 | h2.window-title-text Import Bugs From Bugzilla 6 | div.mapping-config 7 | div.row-fluid 8 | div.span2 9 | button.btn.btn-primary.js-add-mapping Add Mapping 10 | div.span2 11 | button.btn.btn-primary.js-sync-all Sync All 12 | div.modal-body.mapping-body 13 | div.window_module 14 | div.mapping-list.hide.mapping-add 15 | footer.modal-footer 16 | button.btn(data-dismiss="modal") Close 17 | -------------------------------------------------------------------------------- /views/backbone/label-assign.jade: -------------------------------------------------------------------------------- 1 | script#template-label-assign-view(type="text/template") 2 | |header.modal-header 3 | | button#close-board-edit.close.js-close-label-window × 4 | | h3 Label 5 | |div.modal-body 6 | | ul.label-items 7 | | each relation in relations 8 | | if relation.selected 9 | | li.checked(id=relation._id, style='background-color:' + relation.labelId.color) 10 | | span 11 | | else 12 | | li(id=relation._id, style='background-color:' + relation.labelId.color) 13 | | span 14 | -------------------------------------------------------------------------------- /views/backbone/list-menu.jade: -------------------------------------------------------------------------------- 1 | div#list-menu.modal.list-menu.menu(style="display:none") 2 | header.modal-header 3 | h4 List Options 4 | div.modal-body 5 | ul 6 | li.js-add-card 7 | a Add Card 8 | li.js-archive-list 9 | a Archive This List 10 | li.js-archive-all-cards 11 | a Archive All Cards in This List 12 | li.js-move-list 13 | a Move List 14 | -------------------------------------------------------------------------------- /views/backbone/list.jade: -------------------------------------------------------------------------------- 1 | script#template-list-view(type="text/template") 2 | |header.list-header.handle 3 | | div.list-header-title 4 | | h4.js-list-title 5 | | strong.js-list-title-text(title= title)= title 6 | | span.js-card-quantity(title="Total") 7 | | a.list-setting.setting.js-list-setting 8 | |section.list-content.droptrue.move.ui-state-default.connectedSortable.js-list-content 9 | |footer.add-plugin.js-add-card Add a card 10 | 11 | -------------------------------------------------------------------------------- /views/backbone/move-list-item.jade: -------------------------------------------------------------------------------- 1 | script#template-move-list-item(type="text/template") 2 | |a(data-itemid= data._id, data-label= data.title)= data.title 3 | |span › 4 | -------------------------------------------------------------------------------- /views/backbone/move-list.jade: -------------------------------------------------------------------------------- 1 | script#template-move-list(type="text/template") 2 | |div.modal-backdrop.fade.in 3 | | div#movement.modal.fade.in 4 | | div.modal-header 5 | | button.close(data-dismiss="modal") × 6 | | h3= data.title 7 | | div.modal-body 8 | | div.choose-position 9 | | ul 10 | | li 11 | | h4 Choose Board 12 | | li 13 | | input(class="js-move-search", type="text", placeholder="Search") 14 | | li 15 | | ul.js-move-items 16 | | ul.hide 17 | | li 18 | | h4 Choose List 19 | | li 20 | | input(class="js-move-search",type="text", placeholder="Search") 21 | | li 22 | | ul.js-move-items 23 | | ul.hide 24 | | li 25 | | h4 Set Position 26 | | li.specific 27 | | a.js-set-position(data-position="top") Top 28 | | a.js-set-position(data-position="middle") Middle 29 | | a.js-set-position(data-position="bottom") Bottom 30 | | li 31 | | ul.js-move-position 32 | | footer.modal-footer 33 | | button.js-move.btn.btn-primary Move 34 | | button.btn(data-dismiss="modal") Cancel 35 | -------------------------------------------------------------------------------- /views/backbone/notification-item.jade: -------------------------------------------------------------------------------- 1 | script#template-notification-item-view(type="text/template") 2 | |span(href="javascript: void(0)") \!{message} [\#{created}] 3 | -------------------------------------------------------------------------------- /views/backbone/notification-menu.jade: -------------------------------------------------------------------------------- 1 | script#template-notification-menu-view(type="text/template"). 2 | a(class="dropdown-toggle js-dropdown-toggle",data-toggle="dropdown") Notification 3 | a(class="reminder") 1 4 | ul(class="dropdown-menu dropdown-notification") 5 | li.all-read 6 | a.js-all-read Mark All as Read 7 | ul.dropdown-notification-content.js-dropdown-content 8 | li.show-more 9 | a.js-show-more Show More -------------------------------------------------------------------------------- /views/backbone/permission-widget.jade: -------------------------------------------------------------------------------- 1 | script#template-permission-widget-view(type="text/template") 2 | |if selected == 'opened' 3 | | a.checked.js-option.all(data-permission="opened") 4 | | span 5 | | span 6 | | span Public for Every User 7 | |else 8 | | a.js-option.all(data-permission="opened") 9 | | span 10 | | span 11 | | span Public for Every User 12 | |if selected == 'enabled' 13 | | a.checked.js-option.part(data-permission="enabled") 14 | | span 15 | | span 16 | | span Public for Board Members 17 | |else 18 | | a.js-option.part(data-permission="enabled") 19 | | span 20 | | span 21 | | span Public for Board Members 22 | |if selected == 'disabled' 23 | | a.checked.js-option.none(data-permission="disabled") 24 | | span 25 | | span 26 | | span Disable 27 | |else 28 | | a.js-option.none(data-permission="disabled") 29 | | span 30 | | span 31 | | span Disable -------------------------------------------------------------------------------- /views/backbone/quick-search.jade: -------------------------------------------------------------------------------- 1 | script#template-quick-search-view(type="text/template") 2 | |div.board-ul-title.clearfix 3 | | h3.pull-left Search Results 4 | |div.results-container 5 | | .row-fluid 6 | | .span4 7 | | h4.boards-title Boards 8 | | span.count 9 | | .board-archive 10 | | .span8 11 | | h4.cards-title Cards 12 | | span.count 13 | | .card-archive 14 | | a.btn.btn-warning.js-full-results View all results 15 | | i.icon-arrow-right 16 | |div.no-results 17 | | span.message Please enter a search query -------------------------------------------------------------------------------- /views/backbone/search.jade: -------------------------------------------------------------------------------- 1 | script#template-search-view(type="text/template") 2 | |div.search-results-container 3 | | h4.boards-title Boards 4 | | .board-archive 5 | | h4.cards-title Cards 6 | | .card-archive 7 | 8 | script#template-search-board-list-view(type="text/template") 9 | |ul.board-ul 10 | | each board in boards 11 | | if board.isClosed === true 12 | | li.board-close 13 | | a(href="/boards/closed?highlighted=" + board._id, class="board-name js-view-board")= board.title 14 | | else 15 | | li 16 | | a(href="/board/" + board._id, class="board-name js-view-board")= board.title -------------------------------------------------------------------------------- /views/backbone/syncconfig-item-edit.jade: -------------------------------------------------------------------------------- 1 | script#template-syncconfig-item-edit-view(type="text/template"). 2 | div.span1 3 | div.span6 4 | label Input bugzilla link 5 | input(value=queryUrl).js-query-url 6 | div.js-alert.alert.hide Loading... 7 | div.span5 8 | label Put bugs in List 9 | a.js-specify-list New List 10 | select.js-list-name 11 | each list, i in lists 12 | if list.id === listId._id 13 | option(value=list.id, selected="selected")= list.name 14 | else 15 | option(value=list.id)= list.name 16 | input(placeholder="Input New List Name").hide.js-list-name 17 | div.mapping-submit 18 | button.btn.btn-primary.js-syncconfig-save Save 19 | button.btn.js-syncconfig-cancel Cancel 20 | -------------------------------------------------------------------------------- /views/backbone/syncconfig-item-input.jade: -------------------------------------------------------------------------------- 1 | script#template-syncconfig-item-input-view(type="text/template"). 2 | div.row-fluid 3 | div.span1 4 | div.span6 5 | label Input bugzilla link 6 | input(placeholder="Input a Bugzilla link").js-query-url 7 | div.js-alert.alert.hide Loading... 8 | div.span5 9 | label Put bugs in List 10 | a.js-specify-list New List 11 | select.js-list-name 12 | option(value="") ---- 13 | each list, i in lists 14 | option(value=list.id)= list.name 15 | input(placeholder="Input New List Name").hide.js-list-name 16 | div.mapping-submit 17 | button.btn.btn-primary.js-syncconfig-add Save 18 | button.btn.js-syncconfig-cancel Cancel 19 | -------------------------------------------------------------------------------- /views/backbone/syncconfig-item.jade: -------------------------------------------------------------------------------- 1 | script#template-syncconfig-item-view(type="text/template"). 2 | a.delete.mapping-delete.js-syncconfig-delete 3 | div.row-fluid 4 | div.span1 5 | if isActive 6 | span.enabled.js-is-active(title="Click to disable this mapping") 7 | else 8 | span.disabled.js-is-active(title="Click to enable this mapping") 9 | div.span5 10 | label Input bugzilla link 11 | div.js-queryurl-wrapper(title="Click to Eidt Query URL") 12 | span.glyphicon.glyphicon-link 13 | p= queryUrl 14 | div.span5 15 | label Put bugs in List 16 | div.js-listname-wrapper(title="Click to Change List") 17 | span.glyphicon.glyphicon-list-alt 18 | p= listName 19 | div.mapping-loading.js-sync-status 20 | div.process-loading 21 | div.loader 22 | div.top 23 | div.bar 24 | div.bar 25 | div.bar 26 | div.bar 27 | div.bar 28 | div.bottom 29 | div.bar 30 | div.bar 31 | div.bar 32 | div.bar 33 | div.bar 34 | div.span1 35 | if isBoardMember && isActive 36 | button.btn.btn-primary.js-mapping-sync Sync 37 | -------------------------------------------------------------------------------- /views/email/assign.jade: -------------------------------------------------------------------------------- 1 | extends notification 2 | 3 | block content 4 | h4 Hi #{assignee}, 5 | p 6 | | #{assigner} assign card #{cardTitle} to you. 7 | p Regards, 8 | p Cantas 9 | 10 | -------------------------------------------------------------------------------- /views/email/comment.jade: -------------------------------------------------------------------------------- 1 | extends notification 2 | 3 | block content 4 | h4 Hi #{receiver}, 5 | p 6 | | #{sender} added a comment in card #{cardTitle}. 7 | |

content: 8 | | #{comment} 9 | p Regards, 10 | p Cantas 11 | 12 | -------------------------------------------------------------------------------- /views/email/editComment.jade: -------------------------------------------------------------------------------- 1 | extends notification 2 | 3 | block content 4 | h4 Hi #{receiver}, 5 | p 6 | | #{sender} edited a comment in card #{cardTitle}. 7 | |

original content: 8 | | #{originComment} 9 | |

current content: 10 | | #{currentComment} 11 | p Regards, 12 | p Cantas 13 | 14 | -------------------------------------------------------------------------------- /views/email/invitation.jade: -------------------------------------------------------------------------------- 1 | extends notification 2 | 3 | block content 4 | h4 Hi #{inviteeName}, 5 | p 6 | | You are invited by #{inviterName} to join board #{boardTitle}. 7 | | Click #{boardUrl} to join. 8 | p Regards, 9 | p Cantas 10 | 11 | -------------------------------------------------------------------------------- /views/email/notification.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | style. 5 | .mail{ 6 | background: #333; 7 | padding: 50px; 8 | } 9 | .mail-label{ 10 | background: blue; 11 | } 12 | .mail-card{ 13 | margin: 0 auto; 14 | color: #000000; 15 | font-size: 13px; 16 | min-width: 80%; 17 | padding: 9px 18px; 18 | z-index: 0; 19 | border-radius: 3px; 20 | border-bottom-right-radius: 95px 15px; 21 | background: #FCDF71; 22 | } 23 | span{ 24 | font-style: italic; 25 | } 26 | 27 | body 28 | div.mail 29 | div.mail-label 30 | div.mail-card 31 | block content 32 | h4 Hi #{displayName}, 33 | p !{message} 34 | p Regards, 35 | p Cantas 36 | 37 | --------------------------------------------------------------------------------