├── .dir-locals.el ├── .gitignore ├── .jshintrc ├── .lein-failures ├── Dockerfile ├── LIBRARIES.md ├── LICENSE.md ├── Procfile ├── README.md ├── REFACTORING.md ├── assets ├── images │ ├── lightbulb.png │ ├── lightbulb.svg │ ├── people.png │ ├── people.svg │ ├── policy.png │ ├── policy.svg │ ├── speechbubbles.png │ └── speechbubbles.svg ├── jade │ ├── _routes.jade │ ├── add-draft.jade │ ├── add-question.jade │ ├── admin-activity.jade │ ├── admin-removal-confirmation.jade │ ├── annotations-dashboard.jade │ ├── authorisation-page.jade │ ├── comments-dashboard.jade │ ├── cookie-page.jade │ ├── create-objective.jade │ ├── create-profile.jade │ ├── draft-comments.jade │ ├── draft-diff.jade │ ├── draft-list.jade │ ├── draft-section.jade │ ├── draft.jade │ ├── edit-profile.jade │ ├── error-404.jade │ ├── error-configuration.jade │ ├── error-default.jade │ ├── error-log-in.jade │ ├── import-draft.jade │ ├── index.jade │ ├── invite-writer.jade │ ├── layouts │ │ └── base.jade │ ├── learn-more.jade │ ├── library.jade │ ├── mixins │ │ ├── _buttons.jade │ │ ├── _comments.jade │ │ ├── _dashboard.jade │ │ ├── _draft.jade │ │ ├── _helpers.jade │ │ ├── _masthead.jade │ │ ├── _objectives.jade │ │ ├── _policy.jade │ │ ├── _profile.jade │ │ ├── _progress_indicator.jade │ │ ├── _questions.jade │ │ ├── _secondary_navigation.jade │ │ ├── _sharing_meta.jade │ │ ├── _view_helpers.jade │ │ └── _writers.jade │ ├── modals │ │ ├── share-objective-modal.jade │ │ └── share-question-modal.jade │ ├── objective-comments.jade │ ├── objective-invitation-response.jade │ ├── objective-list.jade │ ├── objective.jade │ ├── playground │ │ ├── editor.jade │ │ └── policy.jade │ ├── profile.jade │ ├── project-status.jade │ ├── question.jade │ ├── questions-dashboard.jade │ ├── sign-in.jade │ └── sign-up.jade ├── js │ ├── custom │ │ └── main.js │ └── vendor │ │ ├── cookieconsent.js │ │ ├── jquery-1.11.2.js │ │ ├── jquery.scrollto.js │ │ ├── modernizr.js │ │ ├── moment-timezone-with-data-2010-2020.min.js │ │ ├── moment-with-locales.min.js │ │ └── webfont.js └── scss │ ├── _manifest.scss │ ├── basic.scss │ ├── font_awesome │ ├── _animated.scss │ ├── _bordered-pulled.scss │ ├── _core.scss │ ├── _fixed-width.scss │ ├── _icons.scss │ ├── _larger.scss │ ├── _list.scss │ ├── _mixins.scss │ ├── _path.scss │ ├── _rotated-flipped.scss │ ├── _stacked.scss │ └── _variables.scss │ ├── ie8.scss │ ├── mixins │ ├── _dimensions.scss │ ├── _document.scss │ ├── _functions.scss │ ├── _positioning.scss │ └── _star.scss │ ├── modern.scss │ ├── modules │ ├── _actions_vote.scss │ ├── _admin_activity.scss │ ├── _admin_removal_confirmation.scss │ ├── _answer_list.scss │ ├── _approval_box.scss │ ├── _bootstrap_social.scss │ ├── _browsehappy.scss │ ├── _comments.scss │ ├── _cookie_message.scss │ ├── _create_profile_form.scss │ ├── _draft.scss │ ├── _draft_list.scss │ ├── _drafting_message.scss │ ├── _edit_draft.scss │ ├── _edit_profile_form.scss │ ├── _errors.scss │ ├── _footer.scss │ ├── _get_involved.scss │ ├── _guidance_text.scss │ ├── _info_box.scss │ ├── _intro_middle_container_wrapper.scss │ ├── _invitation_response.scss │ ├── _list_items.scss │ ├── _main.scss │ ├── _masthead.scss │ ├── _messages.scss │ ├── _modal.scss │ ├── _navigation.scss │ ├── _objective_list_item.scss │ ├── _objective_navigation.scss │ ├── _policy.scss │ ├── _policy_contributors_list.scss │ ├── _profile.scss │ ├── _profile_objective_list_item.scss │ ├── _progress_indicator.scss │ ├── _question_list.scss │ ├── _response.scss │ ├── _secondary_navigation.scss │ ├── _share_widget.scss │ ├── _sign_up_form.scss │ ├── _status_bar.scss │ ├── _writer_dashboard.scss │ ├── _writer_list.scss │ ├── _writer_profile.scss │ ├── dashboard │ │ ├── _dashboard_annotation_item.scss │ │ ├── _dashboard_answer_item.scss │ │ ├── _dashboard_comment_item.scss │ │ ├── _dashboard_stat.scss │ │ ├── _dashboard_writer_note.scss │ │ ├── _writer_dashboard_filter_list.scss │ │ └── _writer_dashboard_navigation.scss │ └── draft │ │ ├── _draft_add_inline_comment.scss │ │ ├── _draft_editor.scss │ │ ├── _draft_preview_document.scss │ │ ├── _draft_tabs.scss │ │ └── _draft_version_writer.scss │ ├── pages │ ├── _draft_preview.scss │ ├── _index.scss │ ├── _objective.scss │ ├── _objective_list.scss │ └── _sign_in.scss │ └── root │ ├── _buttons.scss │ ├── _colours.scss │ ├── _forms.scss │ ├── _grid.scss │ ├── _helpers.scss │ ├── _links.scss │ ├── _lists.scss │ ├── _mixins.scss │ ├── _normalize.scss │ ├── _print.scss │ ├── _root.scss │ ├── _theme.scss │ ├── _typography.scss │ └── _variables.scss ├── deploy_prod.sh ├── deploy_vagrant.sh ├── dev-app.js ├── dev ├── dev_helpers │ ├── launch.clj │ ├── profiling.clj │ ├── stub_twitter_and_stonecutter.clj │ └── translation.clj ├── pgres └── user.clj ├── develop.sh ├── docs ├── CONFIG.md ├── DOCKER.md ├── HEROKU.md └── UBUNTU.md ├── go.sh ├── gruntfile.js ├── heroku.env.sh ├── init-script └── remote_start_objective8_docker.sh ├── load_testing └── load_tests.clj ├── migrations ├── 000_setup_postgres.down.sql ├── 000_setup_postgres.up.sql ├── 001_add_questions.down.sql ├── 001_add_questions.up.sql ├── 002_add_bearer_tokens.down.sql ├── 002_add_bearer_tokens.up.sql ├── 003_add_answers.down.sql ├── 003_add_answers.up.sql ├── 004_add_invitations.down.sql ├── 004_add_invitations.up.sql ├── 005_add_invitation_status.down.sql ├── 005_add_invitation_status.up.sql ├── 006_add_candidates.down.sql ├── 006_add_candidates.up.sql ├── 007_change_rejected_invitation_status_to_declined.down.sql ├── 007_change_rejected_invitation_status_to_declined.up.sql ├── 008_add_drafts.down.sql ├── 008_add_drafts.up.sql ├── 009_add_global_identifiers.down.sql ├── 009_add_global_identifiers.up.sql ├── 010_add_global_identifier_reference_to_answers.down.sql ├── 010_add_global_identifier_reference_to_answers.up.sql ├── 011_add_up_down_votes.down.sql ├── 011_add_up_down_votes.up.sql ├── 012_Remove_objective_id_from_global_identifiers.down.sql ├── 012_Remove_objective_id_from_global_identifiers.up.sql ├── 013_add_global_identifier_to_objectives.down.sql ├── 013_add_global_identifier_to_objectives.up.sql ├── 014_add_comment_on_id_to_comments.down.sql ├── 014_add_comment_on_id_to_comments.up.sql ├── 015_add_global_id_to_drafts.down.sql ├── 015_add_global_id_to_drafts.up.sql ├── 016_add_uniqueness_and_not_null_constraint_to_answers_global_id.down.sql ├── 016_add_uniqueness_and_not_null_constraint_to_answers_global_id.up.sql ├── 017_add_global_id_to_comments.down.sql ├── 017_add_global_id_to_comments.up.sql ├── 018_add_status_to_objectives.down.sql ├── 018_add_status_to_objectives.up.sql ├── 019_add_stars.down.sql ├── 019_add_stars.up.sql ├── 020_add_marks.down.sql ├── 020_add_marks.up.sql ├── 021_rename_candidates_to_writers.down.sql ├── 021_rename_candidates_to_writers.up.sql ├── 022_add_writer_notes.down.sql ├── 022_add_writer_notes.up.sql ├── 023_add_sections.down.sql ├── 023_add_sections.up.sql ├── 024_add_admins.down.sql ├── 024_add_admins.up.sql ├── 025_add_removed_by_admin_to_objectives.down.sql ├── 025_add_removed_by_admin_to_objectives.up.sql ├── 026_add_admin_removals.down.sql ├── 026_add_admin_removals.up.sql ├── 027_add_reference_to_note_on_id_writer_notes.down.sql ├── 027_add_reference_to_note_on_id_writer_notes.up.sql ├── 028_add_objective_id_column_to_sections_table.down.sql ├── 028_add_objective_id_column_to_sections_table.up.sql ├── 029_add_reasons.down.sql ├── 029_add_reasons.up.sql ├── 030_remove_status_from_objectives.down.sql ├── 030_remove_status_from_objectives.up.sql ├── 031_remove_end_date_from_objectives.down.sql ├── 031_remove_end_date_from_objectives.up.sql ├── 032_rename_twitter_id_to_auth_provider_user_id_in_users.down.sql ├── 032_rename_twitter_id_to_auth_provider_user_id_in_users.up.sql ├── 033_rename_twitter_id_to_auth_provider_user_id_in_admins.down.sql ├── 033_rename_twitter_id_to_auth_provider_user_id_in_admins.up.sql ├── 034_add_activities.down.sql ├── 034_add_activities.up.sql ├── 035_remove_activities.down.sql └── 035_remove_activities.up.sql ├── npm-postinstall.sh ├── ops ├── Vagrantfile ├── development.inventory ├── development_playbook.yml ├── digital_ocean_box.inventory ├── digital_ocean_box_playbook.yml ├── digital_ocean_box_staging.inventory ├── objective8_config_template ├── objective8_docker_config_template ├── provision-objective8-staging.sh ├── provision-objective8.sh ├── roles │ ├── common │ │ └── tasks │ │ │ └── main.yml │ ├── coracle │ │ └── tasks │ │ │ └── main.yml │ ├── dev │ │ └── tasks │ │ │ └── main.yml │ ├── docker │ │ └── tasks │ │ │ └── main.yml │ ├── ferm │ │ ├── files │ │ │ └── ferm.conf │ │ └── tasks │ │ │ └── main.yml │ ├── leiningen │ │ ├── files │ │ │ └── lein │ │ └── tasks │ │ │ └── main.yml │ ├── nginx │ │ ├── files │ │ │ ├── testing_objective8.crt │ │ │ └── testing_objective8.key │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ └── objective8.j2 │ ├── objective8_application_config │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ └── objective8_config.j2 │ ├── objective8_postgres_backups │ │ ├── files │ │ │ └── postgres_backup.sh │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ └── .s3cfg.j2 │ ├── objective8_postgres_config │ │ ├── files │ │ │ ├── pg_hba.conf │ │ │ └── postgresql.conf │ │ └── tasks │ │ │ └── main.yml │ └── postgres │ │ └── tasks │ │ └── main.yml ├── staging_vm.inventory └── staging_vm_playbook.yml ├── package.json ├── prod └── main.clj ├── project.clj ├── resources ├── log4j.debug ├── log4j.dev ├── log4j.heroku ├── log4j.prod ├── log4j.snap ├── log4j.test ├── public │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── gapi │ │ ├── gapi.js │ │ └── google_docs_import.js │ ├── images │ │ ├── lightbulb.png │ │ ├── people.png │ │ ├── policy.png │ │ └── speechbubbles.png │ ├── stonecutter-sign-in-icon.png │ └── stonecutter.ico └── translations │ ├── en.csv │ ├── es.csv │ └── languages.txt ├── snap.env.sh ├── src └── objective8 │ ├── back_end │ ├── actions.clj │ ├── domain │ │ ├── activities.clj │ │ ├── admin_removals.clj │ │ ├── answers.clj │ │ ├── bearer_tokens.clj │ │ ├── comments.clj │ │ ├── drafts.clj │ │ ├── invitations.clj │ │ ├── marks.clj │ │ ├── objectives.clj │ │ ├── questions.clj │ │ ├── stars.clj │ │ ├── up_down_votes.clj │ │ ├── users.clj │ │ ├── writer_notes.clj │ │ └── writers.clj │ ├── handlers.clj │ ├── requests.clj │ └── storage │ │ ├── database.clj │ │ ├── mappings.clj │ │ ├── storage.clj │ │ └── uris.clj │ ├── config.clj │ ├── core.clj │ ├── front_end │ ├── api │ │ ├── domain.clj │ │ └── http.clj │ ├── config.clj │ ├── draft_diffs.clj │ ├── front_end_requests.clj │ ├── handlers.clj │ ├── permissions.clj │ ├── sanitiser.clj │ ├── templates │ │ ├── add_draft.clj │ │ ├── add_question.clj │ │ ├── admin_activity.clj │ │ ├── admin_removal_confirmation.clj │ │ ├── cookies.clj │ │ ├── create_objective.clj │ │ ├── create_profile.clj │ │ ├── dashboard_annotations.clj │ │ ├── dashboard_comments.clj │ │ ├── dashboard_questions.clj │ │ ├── draft.clj │ │ ├── draft_comments.clj │ │ ├── draft_diff.clj │ │ ├── draft_list.clj │ │ ├── draft_section.clj │ │ ├── edit_profile.clj │ │ ├── error_404.clj │ │ ├── error_pages.clj │ │ ├── import_draft.clj │ │ ├── index.clj │ │ ├── invite_writer.clj │ │ ├── learn_more.clj │ │ ├── objective.clj │ │ ├── objective_comments.clj │ │ ├── objective_list.clj │ │ ├── page_furniture.clj │ │ ├── profile.clj │ │ ├── project_status.clj │ │ ├── question.clj │ │ ├── sign_in.clj │ │ ├── sign_up.clj │ │ └── template_functions.clj │ ├── translation.clj │ ├── views.clj │ └── workflows │ │ ├── facebook.clj │ │ ├── okta.clj │ │ ├── sign_up.clj │ │ ├── stonecutter.clj │ │ ├── stub_twitter.clj │ │ └── twitter.clj │ ├── middleware.clj │ ├── routes.clj │ └── utils.clj ├── start_app_vm.sh └── test ├── autotest_functional_tests.sh ├── autotest_unit_and_integration_tests.sh ├── cloverage_entrypoint.clj ├── objective8 ├── functional │ ├── functional_tests.clj │ └── screenshots │ │ └── .gitkeep ├── integration │ ├── back_end │ │ ├── admin_actions.clj │ │ ├── answers.clj │ │ ├── comments.clj │ │ ├── drafting.clj │ │ ├── invitations.clj │ │ ├── objectives.clj │ │ ├── questions.clj │ │ ├── stars.clj │ │ ├── tokens.clj │ │ ├── up_down_votes.clj │ │ ├── users.clj │ │ ├── writer_notes.clj │ │ └── writers.clj │ ├── db │ │ ├── activities.clj │ │ ├── answers.clj │ │ ├── bearer_tokens.clj │ │ ├── comments.clj │ │ ├── drafts.clj │ │ ├── marks.clj │ │ ├── objectives.clj │ │ ├── questions.clj │ │ ├── stars.clj │ │ ├── storage.clj │ │ ├── up_down_votes.clj │ │ ├── users.clj │ │ └── writer_notes.clj │ ├── fixtures │ │ ├── error--lookup-path-too-long.csv │ │ ├── error--lookup-path-too-short.csv │ │ ├── l1.csv │ │ ├── l2.csv │ │ └── language.csv │ ├── front_end │ │ ├── admin_actions.clj │ │ ├── annotations.clj │ │ ├── answers.clj │ │ ├── comments.clj │ │ ├── dashboard.clj │ │ ├── drafting.clj │ │ ├── front_end.clj │ │ ├── objectives.clj │ │ ├── questions.clj │ │ ├── sign_in.clj │ │ ├── signup.clj │ │ ├── stars.clj │ │ ├── up_down_votes.clj │ │ ├── users.clj │ │ ├── validations.clj │ │ └── writers.clj │ ├── integration_helpers.clj │ ├── rendering │ │ └── translations.clj │ ├── storage_helpers.clj │ └── translation.clj └── unit │ ├── actions_test.clj │ ├── activities_test.clj │ ├── answers_test.clj │ ├── auth_views.clj │ ├── back_end_requests_test.clj │ ├── draft_diffs_test.clj │ ├── drafts_test.clj │ ├── front_end_requests_test.clj │ ├── http_api_test.clj │ ├── invitations_test.clj │ ├── middleware_test.clj │ ├── objectives_test.clj │ ├── permissions_test.clj │ ├── questions_test.clj │ ├── sanitiser_test.clj │ ├── storage │ ├── mappings_test.clj │ └── storage_test.clj │ ├── template_functions_test.clj │ ├── translation_test.clj │ ├── users_test.clj │ ├── utils_test.clj │ ├── views_test.clj │ ├── workflows │ ├── facebook_test.clj │ ├── okta_test.clj │ ├── sign_up_test.clj │ ├── stonecutter_test.clj │ ├── test-stonecutter-key.json │ └── twitter_test.clj │ └── writers_test.clj ├── run_all_tests.sh └── run_browser_tests.sh /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((mode . objective8)))) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tar 2 | *.log 3 | target 4 | .nrepl-port 5 | .lein-repl-history 6 | node_modules/ 7 | *.swp 8 | *.swo 9 | .#* 10 | \#* 11 | resources/public/*.css 12 | resources/public/*.css.map 13 | resources/public/*.js 14 | resources/public/images/*.svg 15 | resources/templates/jade 16 | test/objective8/functional/screenshots/*.png 17 | ops/.vagrant 18 | ops/*_config 19 | ops/roles/nginx/files/secure/ 20 | **/secure/** 21 | .lein-deps-sum 22 | lib/ 23 | .DS_Store 24 | start_with_credentials.sh 25 | start_with_twitter_credentials.sh 26 | start_locally.sh 27 | hs_err*.log 28 | .idea/ 29 | profiles.clj 30 | latest.dump 31 | tmp/ 32 | objective8_config 33 | *.iml 34 | *.tar.gz 35 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": { 3 | "document": true, 4 | "window": true, 5 | "location": true, 6 | "$": true, 7 | "setTimeout": true, 8 | "console": true, 9 | "alert": true, 10 | "Handlebars": true, 11 | "localStorage": true 12 | }, 13 | "node" : false, 14 | "browser" : false, 15 | "boss" : true, 16 | "curly": false, 17 | "debug": false, 18 | "devel": false, 19 | "eqeqeq": true, 20 | "evil": true, 21 | "forin": false, 22 | "immed": false, 23 | "laxbreak": false, 24 | "newcap": true, 25 | "noarg": true, 26 | "noempty": false, 27 | "nonew": false, 28 | "nomen": false, 29 | "onevar": false, 30 | "plusplus": false, 31 | "regexp": false, 32 | "undef": true, 33 | "sub": true, 34 | "strict": false, 35 | "white": false, 36 | "eqnull": true, 37 | "esnext": true 38 | } -------------------------------------------------------------------------------- /.lein-failures: -------------------------------------------------------------------------------- 1 | #{} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dcent/clojure-npm-grunt-gulp 2 | 3 | COPY . ./ 4 | RUN lein with-profile production deps && \ 5 | npm install && \ 6 | npm rebuild node-sass && \ 7 | grunt build 8 | 9 | CMD grunt load-custom-theme && \ 10 | lein uberjar && \ 11 | cp target/objective8-0.0.1-SNAPSHOT-standalone.jar ./ && \ 12 | java -jar objective8-0.0.1-SNAPSHOT-standalone.jar 13 | -------------------------------------------------------------------------------- /LIBRARIES.md: -------------------------------------------------------------------------------- 1 | # Clojure libraries used 2 | 3 | - http-kit 4 | 5 | - bidi 6 | 7 | - ring 8 | - json 9 | - params 10 | - keyword-params 11 | - session 12 | - flash 13 | - anti-forgery 14 | - friend 15 | 16 | - enlive 17 | 18 | - tower 19 | 20 | - clj-oauth 21 | 22 | - clj-time 23 | 24 | - korma 25 | 26 | - postgresql 27 | 28 | - ragtime 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ThoughtWorks Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: source heroku.env.sh && lein with-profile heroku do ragtime migrate, trampoline run 2 | -------------------------------------------------------------------------------- /REFACTORING.md: -------------------------------------------------------------------------------- 1 | #Objective8 Refactoring and Style Guide 2 | 3 | ##Style 4 | 5 | ###Use `->` in function names to show transformation 6 | 7 | ####Prefer 8 | 9 | ```Clojure 10 | 11 | (defn km->miles [km] (* 0.621371192 km)) 12 | 13 | ``` 14 | 15 | ####To 16 | 17 | ```Clojure 18 | 19 | (defn km-to-miles [km] (* 0.621371192 km)) 20 | (defn convert-km-to-miles [km] (* 0.621371192 km)) 21 | 22 | ``` 23 | 24 | ###Keep the left hand side of let statements simple 25 | 26 | ####Prefer 27 | 28 | ```Clojure 29 | 30 | (let [username (:username (get-user-info 123)) 31 | last-log-in (:last-online (get-user-stats 123))] 32 | (welcome-message username last-log-in)) 33 | 34 | ``` 35 | 36 | ####To 37 | 38 | ```Clojure 39 | 40 | (let [{username :username} (get-user-info 123) 41 | {last-log-in :last-online} (get-user-stats 123)] 42 | (welcome-message username last-log-in)) 43 | 44 | ``` 45 | 46 | ##Refactoring 47 | 48 | ###Replace state building in let statements with threading 49 | 50 | Use the `->` or `some->` macros when the desired effect is transformation. 51 | 52 | ####Replace 53 | 54 | ```Clojure 55 | 56 | (defn perform-action [a b c] 57 | (let [one (create-one a) 58 | two (create-two-from-one one) 59 | three (create-three-from-two two)] 60 | (make-result three b c))) 61 | 62 | ``` 63 | 64 | ####With 65 | 66 | ```Clojure 67 | 68 | (defn perform-action [a b c] 69 | (-> (create-one a) 70 | create-two-from-one 71 | create-three-from-two 72 | (make-result b c))) 73 | 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /assets/images/lightbulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/assets/images/lightbulb.png -------------------------------------------------------------------------------- /assets/images/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/assets/images/people.png -------------------------------------------------------------------------------- /assets/images/policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/assets/images/policy.png -------------------------------------------------------------------------------- /assets/images/policy.svg: -------------------------------------------------------------------------------- 1 | 3 | Policy 4 | 5 | 6 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/images/speechbubbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/assets/images/speechbubbles.png -------------------------------------------------------------------------------- /assets/images/speechbubbles.svg: -------------------------------------------------------------------------------- 1 | 3 | Speech bubbles 4 | 10 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /assets/jade/add-question.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "objective" 6 | - pageTitle = "!Objective" 7 | 8 | block content 9 | .middle-container 10 | .objective-navigation 11 | .objective-navigation-list-wrapper 12 | ul.objective-navigation-list 13 | li 14 | a.clj-objective-navigation-item-objective(href="/objective") 15 | i.fa.fa-angle-left 16 | = " " 17 | span(data-l8n="content:objective-nav/back-to-objective") !Back to objective 18 | 19 | .objective-outline#outline 20 | h1.objective-title.clj-objective-title !Build safer cycling networks in the streets of Madrid. 21 | 22 | .objective-write-a-question 23 | h2(data-l8n="content:question-create/add-a-question") !Add a question to this objective 24 | //- can this be moved inside the mixin? (comment-create / clj ...) 25 | .question-create.clj-question-create 26 | +addQuestionForm(true) 27 | 28 | block footerScripts 29 | script. 30 | // temp 31 | $(document).ready(function () { 32 | var pageNav = $('.objective-navigation-list-wrapper'); 33 | var pageNavStickHeight = pageNav.offset().top; 34 | $(window).scroll(function () { 35 | if ($(this).scrollTop() > pageNavStickHeight) { 36 | pageNav.addClass("sticky"); 37 | } 38 | else { 39 | pageNav.removeClass("sticky"); 40 | } 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /assets/jade/admin-activity.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - pageTitle = "!Admin activity | Objective[8]" 6 | 7 | block content 8 | .middle-container 9 | 10 | h1(data-l8n="content:admin-activity/page-title") !Admin activity 11 | 12 | .admin-activity 13 | h2(data-l8n="content:admin-activity/subtitle") !The following content has been removed 14 | ol.unstyled-list.admin-removal-list.clj-admin-removals-list 15 | li.admin-removal-list-item.clj-admin-removals-list-item 16 | p.admin-removal-list-item-uri.clj-admin-removal-uri !objectives/id 17 | p.clj-admin-removal-time !2015-05-01 18 | li.admin-removal-list-item.clj-admin-removals-list-item 19 | p.admin-removal-list-item-uri.clj-admin-removal-uri !objectives/id 20 | p.clj-admin-removal-time !2015-05-01 21 | -------------------------------------------------------------------------------- /assets/jade/admin-removal-confirmation.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "" 6 | - pageTitle = "!Admin removal confirmation" 7 | 8 | block content 9 | .middle-container 10 | article 11 | h1(data-l8n="content:admin-removal-confirmation/page-title") !Please confirm 12 | p.lede 13 | span(data-l8n="content:admin-removal-confirmation/are-you-sure") !Are you sure you want to remove this objective: 14 | = " " 15 | span.clj-objective-title !More bikes in Soho 16 | 17 | .admin-removal-actions 18 | a.pink-button(href="/objectives", data-l8n="content:admin-removal-confirmation/no") !No 19 | form.admin-removal-confirmation-form.clj-removal-confirmation-form(method="POST", action="/meta/admin-removal-confirmation") 20 | input.clj-removal-uri(type="hidden", value="!removal-uri", name="removal-uri") 21 | button.button.func--confirm-removal(type="submit", value="yes", data-l8n="content:admin-removal-confirmation/yes") !Yes 22 | -------------------------------------------------------------------------------- /assets/jade/authorisation-page.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | - pageTitle = "!Authorisation error | Objective[8]" 5 | 6 | block content 7 | 8 | .middle-container.clj-error-404 9 | h1(data-l8n="content:authorisation-page/page-title") !Authorisation required 10 | p(data-l8n="html:authorisation-page/page-intro").lede !Sorry, you are currently not authorised to access this application. To gain access, please contact the site administrator at example.com -------------------------------------------------------------------------------- /assets/jade/cookie-page.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "" 6 | - pageTitle = "! Cookies" 7 | 8 | block content 9 | 10 | .middle-container#clj--cookie-page 11 | h1(data-l8n="content:cookie-page/page-title") !Cookies 12 | p(data-l8n="html:cookie-page/page-intro") !To make this site work properly, we sometimes place small data files called cookies on your device. 13 | 14 | h2(data-l8n="content:cookie-page/sub-title-1") !What are cookies? 15 | p(data-l8n="html:cookie-page/cookie-definition") !A cookie is a small text file... 16 | 17 | h2(data-l8n="content:cookie-page/sub-title-2") !How do we use cookies? 18 | div(data-l8n="html:cookie-page/our-cookies") !We use Google Analytics, a popular web analytics service provided by Google, Inc. ... 19 | 20 | h2(data-l8n="content:cookie-page/sub-title-3") !How to control cookies 21 | p(data-l8n="html:cookie-page/control-cookies") !You can control and/or delete cookies as you wish - for details, see... 22 | -------------------------------------------------------------------------------- /assets/jade/create-profile.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "" 6 | - pageTitle = "!Create profile | Objective[8]" 7 | 8 | block content 9 | 10 | .middle-container 11 | h1.create-profile-title(data-l8n="content:create-profile/page-title") !Create your writer profile 12 | p.lede(data-l8n="content:create-profile/profile-intro") !Your profile is open for everyone to read. You should talk about your writing experience. Include your areas of policy expertise. 13 | form.create-profile-form.clj-create-profile-form(action="/create-profile" method="POST") 14 | fieldset.create-profile-fields 15 | p.form-field 16 | label.form-field-label(for="name") 17 | span.form-field-label-title(data-l8n="content:create-profile/name-label") !Name 18 | input.clj-create-profile-name.func--name(id="name", name="name", type="text") 19 | span.user-input-error-message.clj-name-length-error(data-l8n="content:create-profile/name-length-validation-message") !The name can have at most 50 characters 20 | span.user-input-error-message.clj-name-empty-error(data-l8n="content:create-profile/name-empty-validation-message") !The name cannot be empty 21 | p.form-field 22 | label.form-field-label(for="biography") 23 | span.form-field-label-title(data-l8n="content:create-profile/biography") !Biography 24 | textarea.clj-create-profile-biog.func--biog(id="biog", name="biog", type="text") 25 | span.user-input-error-message.clj-biog-length-error(data-l8n="content:create-profile/biog-length-validation-message") !The biography can have at most 5000 characters 26 | span.user-input-error-message.clj-biog-empty-error(data-l8n="content:create-profile/biog-empty-validation-message") !The biography cannot be empty 27 | button.button(type="submit", data-l8n="content:create-profile/button") !Create profile 28 | -------------------------------------------------------------------------------- /assets/jade/draft-comments.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | //- bodyClass = "objective" 6 | - pageTitle = "!Comments on draft" 7 | 8 | block content 9 | 10 | .middle-container 11 | +secondaryNavigation 12 | 13 | .draft-comments#comments 14 | ol.comment-list.clj-comment-list 15 | +commentItem('Phil Smith', '2 September 2015 07:41') 16 | p !Oooh Paseo del Prado. 17 | +commentItem('Jen Bloggs', '1 September 2015 07:41') 18 | p !Paseo del Prado. 19 | +commentItem('Jen Bloggs', '1 September 2015 07:41') 20 | p !Paseo del Prado. 21 | 22 | block footerScripts 23 | +secondaryNavigationStickyScript 24 | -------------------------------------------------------------------------------- /assets/jade/edit-profile.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "" 6 | - pageTitle = "!Edit profile | Objective[8]" 7 | 8 | block content 9 | 10 | .middle-container 11 | h1.edit-profile-title(data-l8n="content:edit-profile/page-title") !Edit your writer profile 12 | p.lede(data-l8n="content:edit-profile/profile-intro") !Your profile is open for everyone to read. You should talk about your writing experience. Include your areas of policy expertise. 13 | form.edit-profile-form.clj-edit-profile-form(action="/edit-profile" method="POST") 14 | fieldset.edit-profile-fields 15 | p.form-field 16 | label.form-field-label(for="name") 17 | span.form-field-label-title(data-l8n="content:edit-profile/name-label") !Name 18 | input.clj-edit-profile-name.func--name(id="name", name="name", type="text") 19 | span.user-input-error-message.clj-name-length-error(data-l8n="content:edit-profile/name-length-validation-message") !The name can have at most 50 characters 20 | span.user-input-error-message.clj-name-empty-error(data-l8n="content:edit-profile/name-empty-validation-message") !The name cannot be empty 21 | p.form-field 22 | label.form-field-label(for="biography") 23 | span.form-field-label-title(data-l8n="content:edit-profile/biography") !Biography 24 | textarea.clj-edit-profile-biog.func--biog(id="biog", name="biog", type="text") 25 | span.user-input-error-message.clj-biog-length-error(data-l8n="content:edit-profile/biog-length-validation-message") !The biography can have at most 5000 characters 26 | span.user-input-error-message.clj-biog-empty-error(data-l8n="content:edit-profile/biog-empty-validation-message") !The biography cannot be empty 27 | button.button(type="submit", data-l8n="content:edit-profile/button") !Update profile 28 | -------------------------------------------------------------------------------- /assets/jade/error-404.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | - pageTitle = "!404" 5 | 6 | block content 7 | 8 | .middle-container.clj-error-404 9 | h1(data-l8n="content:error-404/page-title") !Uh oh 10 | p(data-l8n="content:error-404/page-intro").lede !The page doesn't exist 11 | div(data-l8n="html:error-404/page-content") 12 | p !Content 13 | -------------------------------------------------------------------------------- /assets/jade/error-configuration.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | - pageTitle = "!Configuration error | Objective[8]" 5 | 6 | block content 7 | 8 | .middle-container.clj-error-404 9 | h1(data-l8n="content:error-configuration/page-title") !Application configuration error 10 | p(data-l8n="content:error-configuration/page-intro").lede !There is a problem with the way this instance of Objective8 has been set up. Please contact the site administrator! -------------------------------------------------------------------------------- /assets/jade/error-default.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | - pageTitle = "!Error | Objective[8]" 5 | 6 | block content 7 | 8 | .middle-container.clj-error-404 9 | h1(data-l8n="content:error-default/page-title") !An error has occurred 10 | p(data-l8n="content:error-default/page-intro").lede !Sorry, something went wrong! Please try again later, or contact the site administrator if the problem persists. -------------------------------------------------------------------------------- /assets/jade/error-log-in.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | - pageTitle = "!Error | Objective[8]" 5 | 6 | block content 7 | 8 | .middle-container.clj-error-404 9 | h1(data-l8n="content:error-log-in/page-title") !An error has occurred 10 | p(data-l8n="content:error-log-in/page-intro").lede !Sorry, something went wrong! Please try again later, or contact the site administrator if the problem persists. 11 | .error-sign-in-button 12 | a.button(href="/sign-in", data-l8n="content:error-log-in/sign-in-button") !Sign in -------------------------------------------------------------------------------- /assets/jade/invite-writer.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "objective" 6 | - pageTitle = "!Objective" 7 | 8 | block content 9 | .middle-container 10 | 11 | .objective-navigation 12 | .objective-navigation-list-wrapper 13 | ul.objective-navigation-list 14 | li 15 | a.clj-objective-navigation-item-objective(href="/objective") 16 | i.fa.fa-angle-left 17 | = " " 18 | span(data-l8n="content:objective-nav/back-to-objective") !Back to objective 19 | 20 | 21 | h1.objective-title 22 | span.objective-title-label(data-l8n="content:invite-writer/section-title") !Invite a policy writer for: 23 | span.clj-objective-title !Build safer cycling networks in the streets of Madrid. 24 | 25 | +inviteWriterForm() 26 | 27 | 28 | block footerScripts 29 | script. 30 | // temp 31 | $(document).ready(function () { 32 | var pageNav = $('.objective-navigation-list-wrapper'); 33 | var pageNavStickHeight = pageNav.offset().top; 34 | $(window).scroll(function () { 35 | if ($(this).scrollTop() > pageNavStickHeight) { 36 | pageNav.addClass("sticky"); 37 | } 38 | else { 39 | pageNav.removeClass("sticky"); 40 | } 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /assets/jade/learn-more.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | 3 | block vars 4 | // - pageTitle = "Settings" 5 | 6 | block content 7 | 8 | .middle-container.clj-learn-more 9 | h1.clj-learn-more-heading(data-l8n="content:learn-more/page-title") !Objective[8] 101 10 | h2.clj-learn-more-sub-heading(data-l8n="content:learn-more/sub-title") !What are the basics? 11 | p.lede.clj-learn-more-lede(data-l8n="content:learn-more/page-intro") !Objective8 is a platform to enable collaborative policy making for democratic organisations. 12 | 13 | div(data-l8n="html:learn-more/page-content") 14 | p !Policy is used by governments and organisations to make consistent and fair decisions in order to achieve desired outcomes. 15 | p An objective is a change that could be achieved by introducing new policy. For example: improving access to public housing, or increasing the safety of our roads.

16 | p Writers are experts responsible for drafting policy. They use the crowdsourced questions, answers and comments to produce policy drafts. These are further refined through cycles of feedback and redrafting. 17 | p You can get involved and have your say by creating and sharing objectives, offering your opinion through comments, asking and answering questions, suggesting policy writers*, or even providing feedback on the policy drafts themselves. The whole process is open and transparent. 18 | 19 | a.button.index-get-started(href="/objectives", title="!Get started", data-l8n="attr/title:learn-more/get-started-button-title content:learn-more/get-started-button-text") !Get started 20 | -------------------------------------------------------------------------------- /assets/jade/mixins/_buttons.jade: -------------------------------------------------------------------------------- 1 | mixin anchorButton(href, title) 2 | - title = title || null 3 | a.button.clj-anchor-button(href=href,title=title) 4 | block 5 | 6 | mixin submitButton(value, title) 7 | - title = title || null 8 | button.button.clj-form-button(type="submit",title=title,value) 9 | =value 10 | 11 | -------------------------------------------------------------------------------- /assets/jade/mixins/_draft.jade: -------------------------------------------------------------------------------- 1 | mixin addCommentButton(count) 2 | .draft-add-inline-comment.clj-draft-add-inline-comment 3 | a.clj-annotation-link.func--annotation-link(href="/draft-annotation", title="!Annotations", data-l8n="attr/title:draft/annotation-link-title") 4 | if count > 0 5 | i.fa.fa-comment-o.fa-lg 6 | span.visuallyhidden(data-l8n="content:draft/annotation") !Add a comment 7 | span.draft-add-inline-comment-count.clj-draft-add-inline-comment-count.func--annotation-count #{count} 8 | else 9 | i.fa.fa-comment-o.fa-lg 10 | span.visuallyhidden(data-l8n="content:draft/annotation") !Add a comment 11 | block 12 | -------------------------------------------------------------------------------- /assets/jade/mixins/_helpers.jade: -------------------------------------------------------------------------------- 1 | mixin javascriptIncludeTag(path, type) 2 | - type = type || 'text/javascript' 3 | script(src="#{javascriptsBase}/#{path}.js", type="#{type}") 4 | 5 | mixin stylesheetLinkTag(path, media) 6 | link(href="#{stylesheetsBase}/#{path}.css", media=media, rel="stylesheet", type="text/css", id="stylesheet") 7 | 8 | mixin imageIncludeTag(path, alt, cssClasses, title) 9 | - alt = alt || '' 10 | - cssClasses = cssClasses || '' 11 | - title = title || '' 12 | img(src="#{imagesBase}/#{path}", alt="#{alt}", class="#{cssClasses}", title="#{title}") 13 | -------------------------------------------------------------------------------- /assets/jade/mixins/_policy.jade: -------------------------------------------------------------------------------- 1 | mixin policyContributorItem(contributor) 2 | li.policy-contributors-list-item #{contributor} -------------------------------------------------------------------------------- /assets/jade/mixins/_profile.jade: -------------------------------------------------------------------------------- 1 | mixin profileObjectiveListItem(objectiveTitle) 2 | li.profile-objective-list-item.clj-profile-objective-list-item 3 | .profile-objective-list-item-removal-container.clj-profile-objective-list-item-removal-container 4 | form.profile-objective-list-item-removal.clj-profile-objective-removal-form(action="/meta/admin-removals", method="post") 5 | input.clj-removal-uri(type="hidden", value="!removal-uri", name="removal-uri") 6 | input.clj-removal-sample(type="hidden", value="!removal-sample", name="removal-sample") 7 | button.objective-list-item-removal-button.func--remove-objective(type="submit") 8 | i.fa.fa-trash-o.fa-lg 9 | .profile-objective-list-item-star-container.clj-star-container 10 | .profile-objective-list-item-star.clj-profile-objective-list-item-star 11 | i.fa.fa-star 12 | a.profile-objective-list-item-link.clj-profile-objective-list-item-link.func--objective-list-item-link(href="/objectives/[id]") 13 | h3.profile-objective-list-item-title.clj-profile-objective-list-item-title.func--objective-title #{objectiveTitle} 14 | .profile-objective-list-dashboard-container.clj-profile-objective-list-dashboard-container 15 | a.button.clj-profile-objective-list-dashboard-link.func--dashboard-link(data-l8n="content:profile/dashboard-link") !Dashboard 16 | -------------------------------------------------------------------------------- /assets/jade/mixins/_progress_indicator.jade: -------------------------------------------------------------------------------- 1 | mixin progressIndicator(section,numberOfDrafts) 2 | 3 | .clj-objective-progress-indicator 4 | ul.progress-indicator 5 | li.clj-progress-objective-item(class="#{section == 'objective'? 'on' : ''}") 6 | a.clj-progress-objective-link(href="/objective", data-l8n="content:objective-nav/objective") !Objective 7 | li.clj-progress-drafts-item(class="#{section == 'draft'? 'on' : ''}") 8 | a.clj-progress-drafts-link.func--drafting-message-link(href="/draft") 9 | span(data-l8n="content:objective-nav/drafts") !Drafts 10 | =" " 11 | span.clj-progress-draft-count (#{numberOfDrafts}) 12 | //- li 13 | //- a(href="#") Policy 14 | -------------------------------------------------------------------------------- /assets/jade/mixins/_secondary_navigation.jade: -------------------------------------------------------------------------------- 1 | mixin secondaryNavigation 2 | .secondary-navigation.clj-secondary-navigation 3 | .secondary-navigation-content-wrapper 4 | .secondary-navigation-content 5 | ul.secondary-parent-navigation-controls 6 | li.secondary-parent-navigation 7 | a.clj-parent-link.func--parent-link(href="#") 8 | i.fa.fa-angle-up 9 | span.secondary-parent-navigation-text.clj-parent-text !Parent 10 | 11 | ul.secondary-navigation-content-controls 12 | li.secondary-navigation-previous.clj-secondary-navigation-previous 13 | a.clj-secondary-navigation-previous-link.func--previous-link(href="#") 14 | i.fa.fa-angle-left 15 | span.secondary-navigation-text(data-l8n="content:secondary-navigation/previous") !Previous 16 | li.secondary-navigation-next.clj-secondary-navigation-next 17 | a.clj-secondary-navigation-next-link.func--next-link(href="#") 18 | span.secondary-navigation-text(data-l8n="content:secondary-navigation/next") !Next 19 | i.fa.fa-angle-right 20 | 21 | mixin secondaryNavigationStickyScript 22 | script. 23 | // temp 24 | $(document).ready(function () { 25 | var pageNav = $('.secondary-navigation-content-wrapper'); 26 | var pageNavStickHeight = pageNav.offset().top; 27 | $(window).scroll(function () { 28 | if ($(this).scrollTop() > pageNavStickHeight) { 29 | pageNav.addClass("sticky"); 30 | } 31 | else { 32 | pageNav.removeClass("sticky"); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /assets/jade/mixins/_sharing_meta.jade: -------------------------------------------------------------------------------- 1 | mixin facebookMetaTags() 2 | meta.clj-meta-sharing-facebook-site-name(property="og:site_name", content="Objective8") 3 | meta.clj-meta-sharing-facebook-type(property="og:type", content="article") 4 | meta.clj-meta-sharing-facebook-title(property="og:title", content="!title") 5 | meta.clj-meta-sharing-facebook-url(property="og:url", content="!url") 6 | meta.clj-meta-sharing-facebook-description(property="og:description", content="!description") 7 | meta.clj-meta-sharing-facebook-image(property="og:image", content="!image url") 8 | // meta.clj-sharing-meta-facebook-admins(property="fb:admins", content="!facebook id for site administrator - for tracking likes/shares") -------------------------------------------------------------------------------- /assets/jade/modals/share-objective-modal.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/base 2 | include ../mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "objective" 6 | - pageTitle = "!Share objective modal" 7 | 8 | block content 9 | .middle-container.clj-share-objective-modal 10 | article 11 | h1.share-objective-modal-heading(data-l8n="content:share-objective-modal/heading") !Objective created 12 | p.modal-share-objective-text 13 | em.clj-objective-text.func--share-objective-modal-text !Improve the cycling experience in Soho 14 | p.lede 15 | span(data-l8n="content:share-objective-modal/helper-text") !Build community engagement around your objective by sharing it 16 | p.share-box 17 | ul.modal-share-list 18 | +shareIcon("fa-reddit-square", "reddit") 19 | +shareIcon("fa-facebook-square", "facebook") 20 | +shareIcon("fa-google-plus-square", "google-plus") 21 | +shareIcon("fa-twitter-square", "twitter") 22 | +shareIcon("fa-linkedin-square", "linked-in") 23 | .share-by-url 24 | label.share-by-url-label(for="copy-url", data-l8n="content:share-objective-modal/share-by-url-label") !Share this link: 25 | input.share-by-url-text-input.clj-share-by-url-text-input(type="text", value="!url-for-objective") -------------------------------------------------------------------------------- /assets/jade/modals/share-question-modal.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/base 2 | include ../mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "objective" 6 | - pageTitle = "!Share question modal" 7 | 8 | block content 9 | .middle-container.clj-share-question-modal 10 | article 11 | h1(data-l8n="content:share-question-modal/heading") !Question created 12 | p.lede 13 | span(data-l8n="content:share-question-modal/helper-text") !Share your question to increase the number of answers. 14 | p.modal-share-question-text 15 | em.clj-question-text.func--share-question-modal-text !How can we increase the number of people cycling in Soho? 16 | p.share-box 17 | ul.modal-share-list 18 | +shareIcon("fa-reddit-square", "reddit") 19 | +shareIcon("fa-facebook-square", "facebook") 20 | +shareIcon("fa-google-plus-square", "google-plus") 21 | +shareIcon("fa-twitter-square", "twitter") 22 | +shareIcon("fa-linkedin-square", "linked-in") 23 | .share-by-url 24 | label.share-by-url-label(for="copy-url", data-l8n="content:share-question-modal/share-by-url-label") !Share this link: 25 | input.share-by-url-text-input.clj-share-by-url-text-input(type="text", value="!url-for-question") -------------------------------------------------------------------------------- /assets/jade/objective-comments.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | //- bodyClass = "objective" 6 | - pageTitle = "!Comments on objective" 7 | 8 | block content 9 | 10 | .middle-container 11 | +secondaryNavigation 12 | 13 | .objective-comments#comments 14 | ol.comment-list.clj-comment-list 15 | +commentItem('Phil Smith', '2 September 2015 07:41') 16 | p !Oooh Paseo del Prado. 17 | +commentItem('Jen Bloggs', '1 September 2015 07:41') 18 | p !Paseo del Prado. 19 | 20 | block footerScripts 21 | +secondaryNavigationStickyScript 22 | -------------------------------------------------------------------------------- /assets/jade/objective-invitation-response.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "objective" 6 | - pageTitle = "!Objective writer invitation" 7 | 8 | block content 9 | .middle-container.clj-invitation-response 10 | article 11 | h1(data-l8n="content:invitation-response/page-title") !You've been invited to help draft a policy 12 | p.lede 13 | span(data-l8n="content:invitation-response/help-achieve") !Can you help write policy to achieve this objective: 14 | = " " 15 | span.modal-objective-title.clj-objective-title !More bikes in Soho 16 | p(data-l8n="content:invitation-response/rsvp-text") !If you're interested, you can sign in and accept the invitation below. 17 | 18 | .invitation-response-actions 19 | form.invitation-response-form.clj-invitation-response-decline(method="POST", action="{{/objectives/:o-id/writer-invitations/:i-id/decline}}") 20 | button.pink-button(data-l8n="content:invitation-response/decline", type="submit", value="decline") !Decline 21 | 22 | form.invitation-response-form.clj-invitation-response-accept(method="POST", action="{{/objectives/:o-id/writer-invitations/:i-id/accept}}") 23 | button.button.func--invitation-accept(type="submit", value="accept", data-l8n="content:invitation-response/accept") !Accept 24 | -------------------------------------------------------------------------------- /assets/jade/objective-list.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - pageTitle = "!Objectives | Objective[8]" 6 | 7 | block content 8 | 9 | .middle-container 10 | 11 | h1(data-l8n="content:objective-list/page-title") !Objectives 12 | 13 | .objectives-list-actions 14 | a.button.create-objective-link(href="/objectives/create", data-l8n="content:objective-list/create-objective-link") !Create an objective 15 | 16 | .func--promoted-objectives-container.clj-promoted-objectives-container 17 | h2(data-l8n="content:objective-list/promoted-subtitle") !Promoted objectives 18 | span.promoted-objective-info.clj-promoted-objective-information(data-l8n="content:objective-list/promoted-objective-information") 19 | | !There is a limit of three promoted objectives. 20 | ol.unstyled-list.clj-promoted-objective-list 21 | +objectiveListItemWithDemoteForm('!Having more fun in the streets of Berlin.', 29) 22 | | !London and Pairs are two prime examples of places where people have fun. 23 | 24 | .recent-objectives 25 | h2(data-l8n="content:objective-list/subtitle") !Recently created objectives 26 | ol.unstyled-list.clj-objective-list 27 | +objectiveListItemWithPromoteForm('!Build safer cycling networks in the streets of Madrid.', 30) 28 | | !London and Pairs are two prime examples of cycle super highway heaven. 29 | -------------------------------------------------------------------------------- /assets/jade/question.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_sharing_meta 3 | include mixins/_view_helpers 4 | 5 | block headerMeta 6 | +facebookMetaTags() 7 | 8 | block content 9 | .middle-container 10 | 11 | +secondaryNavigation 12 | 13 | .question-outline#outline 14 | h1.question-leader.clj-question !How many roads must a man walk down before you can call him a man? 15 | 16 | h2(data-l8n="content:question-page/suggested-answers") !Suggested answers 17 | ul.answer-list#answers 18 | +answerItemBlank() 19 | +answerItem() 20 | 21 | .answer-new.clj-answer-new 22 | form(action="!add-answer-action", method="POST").clj-answer-form 23 | p.form-field 24 | label.form-field-label 25 | span.answer-new-title.clj-question(for="add-an-answer") !How many roads must a man walk down before you can call him a man? 26 | textarea#add-an-answer.answer-reply-box.clj-input-answer.func--add-answer(rows=4, name="answer") 27 | span.user-input-error-message.clj-answer-empty-error(data-l8n="content:question-page/answer-empty-validation-message") !Please provide an answer 28 | span.user-input-error-message.clj-answer-length-error(data-l8n="content:question-page/answer-length-validation-message") !Your answer can have at most 500 characters 29 | button(type="submit", data-l8n="content:question-page/submit-answer-button").button.answer-reply !Add my answer 30 | 31 | block footerScripts 32 | +secondaryNavigationStickyScript 33 | -------------------------------------------------------------------------------- /assets/jade/sign-in.jade: -------------------------------------------------------------------------------- 1 | extends layouts/base 2 | include mixins/_view_helpers 3 | 4 | block vars 5 | - bodyClass = "" 6 | - pageTitle = "! Sign in" 7 | 8 | block content 9 | 10 | .sign-in-container 11 | h1.sign-in-title(data-l8n="content:sign-in/page-title") !Sign in or Sign up 12 | p.helper-text(data-l8n="content:sign-in/twitter-helper-text") !We will never post to Twitter without your permission. 13 | form.sign-in-form.clj-sign-in-twitter(method="post", action="twitter-sign-in") 14 | button.btn-social.btn-twitter.btn-lg.func--sign-in-with-twitter(type="submit") 15 | i.fa.fa-twitter 16 | span(data-l8n="content:sign-in/sign-in-with-twitter") !Sign in with Twitter 17 | form.sign-in-form.clj-sign-in-facebook(method="post", action="facebook-sign-in") 18 | button.btn-social.btn-facebook.btn-lg.func--sign-in-with-facebook(type="submit") 19 | i.fa.fa-facebook 20 | span(data-l8n="content:sign-in/sign-in-with-facebook") !Sign in with Facebook 21 | form.sign-in-form.clj-sign-in-d-cent(method="get", action="d-cent-sign-in") 22 | button.btn-social.btn-d-cent.btn-lg.func--sign-in-with-d-cent(type="submit") 23 | span.icon-d-cent 24 | span(data-l8n="content:sign-in/sign-in-with-d-cent") !Sign in with D-Cent 25 | -------------------------------------------------------------------------------- /assets/js/vendor/jquery.scrollto.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.scrollto.js 0.0.1 - https://github.com/yckart/jquery.scrollto.js 3 | * Scroll smooth to any element in your DOM. 4 | * 5 | * Copyright (c) 2012 Yannick Albert (http://yckart.com) 6 | * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). 7 | * 2013/02/17 8 | **/ 9 | $.scrollTo = $.fn.scrollTo = function(x, y, options){ 10 | if (!(this instanceof $)) return $.fn.scrollTo.apply($('html, body'), arguments); 11 | 12 | options = $.extend({}, { 13 | gap: { 14 | x: 0, 15 | y: 0 16 | }, 17 | animation: { 18 | easing: 'swing', 19 | duration: 600, 20 | complete: $.noop, 21 | step: $.noop 22 | } 23 | }, options); 24 | 25 | return this.each(function(){ 26 | var elem = $(this); 27 | elem.stop().animate({ 28 | scrollLeft: !isNaN(Number(x)) ? x : $(y).offset().left + options.gap.x, 29 | scrollTop: !isNaN(Number(y)) ? y : $(y).offset().top + options.gap.y 30 | }, options.animation); 31 | }); 32 | }; -------------------------------------------------------------------------------- /assets/scss/basic.scss: -------------------------------------------------------------------------------- 1 | @import "root/normalize"; 2 | 3 | body { 4 | padding: 1em; 5 | width: 40em; 6 | margin: 0 auto; 7 | } 8 | 9 | label { 10 | display: block; 11 | } 12 | 13 | input { 14 | margin-bottom: 2em; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | 15 | @mixin fa-icon-rotate($degrees, $rotation) { 16 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 17 | -webkit-transform: rotate($degrees); 18 | -ms-transform: rotate($degrees); 19 | transform: rotate($degrees); 20 | } 21 | 22 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 23 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 24 | -webkit-transform: scale($horiz, $vert); 25 | -ms-transform: scale($horiz, $vert); 26 | transform: scale($horiz, $vert); 27 | } 28 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /assets/scss/font_awesome/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /assets/scss/ie8.scss: -------------------------------------------------------------------------------- 1 | @import 'root/normalize'; 2 | @import 'root/variables'; 3 | @import 'root/colours'; 4 | @import 'root/mixins'; 5 | 6 | @mixin mq($point) { 7 | @content; 8 | } 9 | 10 | @import "manifest"; 11 | 12 | body { 13 | min-width: 960px; 14 | } -------------------------------------------------------------------------------- /assets/scss/mixins/_dimensions.scss: -------------------------------------------------------------------------------- 1 | @mixin size($w,$h:$w) { 2 | width: $w; 3 | height: $h; 4 | } 5 | @mixin circle($w,$h:$w,$r:50%) { 6 | width: $w; 7 | height: $h; 8 | border-radius: $r; 9 | } -------------------------------------------------------------------------------- /assets/scss/mixins/_document.scss: -------------------------------------------------------------------------------- 1 | @mixin previewDocument() { 2 | position: relative; 3 | margin-bottom: 2em; 4 | padding-bottom: 2em; 5 | font-family: $serif; 6 | font-size: 110%; 7 | color: $default_text_color; 8 | border-bottom: 1px solid $border_color; 9 | @include mq($small_device) { 10 | margin-bottom: 4em; 11 | } 12 | 13 | .document-title { 14 | font-family: $sans-serif; 15 | letter-spacing: -0.025em; 16 | @include mq($large_device) { 17 | font-size: 2.6em; 18 | } 19 | } 20 | .lede { 21 | line-height: 1.4; 22 | } 23 | p, li, th, td { 24 | line-height: 1.6; 25 | } 26 | > :last-child { 27 | margin-bottom: 0; 28 | } 29 | } -------------------------------------------------------------------------------- /assets/scss/mixins/_functions.scss: -------------------------------------------------------------------------------- 1 | @function image-url($src) { 2 | @return url('/static/images/' + $src); 3 | } 4 | -------------------------------------------------------------------------------- /assets/scss/mixins/_positioning.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-middle-me() { 2 | position: absolute; 3 | top: 50%; 4 | transform: translateY(-50%); 5 | } 6 | 7 | @mixin middle-me() { 8 | position: absolute; 9 | top: 50%; 10 | left: 50%; 11 | transform: translate(-50%, -50%); 12 | } -------------------------------------------------------------------------------- /assets/scss/mixins/_star.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | @mixin star($width, $font-size, $allow_hover: true) { 4 | transition: transform 1s; 5 | transform-style: preserve-3d; 6 | width: $width; 7 | font-size: $font-size; 8 | color: $default_text_color; 9 | outline: none; 10 | &:focus { 11 | color: $color3; 12 | } 13 | @if($allow_hover) { 14 | &:hover { 15 | color: $color3; 16 | transform: translateZ(200px) rotate3d(0,1,0,180deg); 17 | } 18 | } 19 | &.starred { 20 | color: $color3; 21 | .fa-star-o { 22 | display: none; 23 | } 24 | .fa-star { 25 | display: inline-block; 26 | } 27 | } 28 | .fa-star { 29 | display: none; 30 | } 31 | } 32 | 33 | .star-container { 34 | perspective: 800px; 35 | } 36 | 37 | .star { 38 | @include star(45px, 2em); 39 | text-align: center; 40 | margin-left: auto; 41 | margin-right: auto; 42 | } 43 | -------------------------------------------------------------------------------- /assets/scss/modern.scss: -------------------------------------------------------------------------------- 1 | @import 'root/normalize'; 2 | @import 'root/variables'; 3 | @import 'root/colours'; 4 | @import 'root/mixins'; 5 | 6 | @import "manifest"; -------------------------------------------------------------------------------- /assets/scss/modules/_actions_vote.scss: -------------------------------------------------------------------------------- 1 | $vote_button_spacing: 0.75em; 2 | 3 | .actions-up-down-vote-form { 4 | display: inline-block; 5 | margin-right: 0.5em; 6 | } 7 | .actions-vote-up, 8 | .actions-vote-down { 9 | -webkit-appearance: none; 10 | display: inline-block; 11 | padding: 2px 0 3px 0; 12 | overflow: hidden; 13 | cursor: pointer; 14 | text-align: center; 15 | background-color: transparent; 16 | border: 0; 17 | &:visited, & { 18 | color: $default_text_color; 19 | } 20 | &:hover, &:visited, & { 21 | text-decoration: none; 22 | } 23 | &:hover { 24 | color: rgba($default_text_color,0.5); 25 | } 26 | &:focus { 27 | outline: $color3 solid 2px; 28 | } 29 | &[disabled=disabled] { 30 | color: inherit; 31 | } 32 | } 33 | .actions-vote-down:last-child { 34 | &:after { 35 | display: none; 36 | } 37 | } 38 | .actions-vote-count { 39 | min-width: 1.5em; 40 | padding-right: 0.5em; 41 | text-align: left; 42 | } 43 | .vote-direction-text { 44 | display: inline-block; 45 | width: 0.00001em; 46 | height: 0.00001em; 47 | overflow: hidden; 48 | } 49 | 50 | .actions-vote-spacer { 51 | display: inline-block; 52 | margin-right: 0.5em; 53 | &:before { 54 | content: '|'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/scss/modules/_admin_activity.scss: -------------------------------------------------------------------------------- 1 | .admin-removal-list { 2 | border-top: solid 1px; 3 | } 4 | .admin-removal-list-item { 5 | border-bottom: solid 1px; 6 | } 7 | .admin-removal-list-item-uri { 8 | margin-top: 1em; 9 | margin-bottom: 0.2em; 10 | } 11 | -------------------------------------------------------------------------------- /assets/scss/modules/_admin_removal_confirmation.scss: -------------------------------------------------------------------------------- 1 | .admin-removal-confirmation-form { 2 | display: inline-block; 3 | } 4 | -------------------------------------------------------------------------------- /assets/scss/modules/_approval_box.scss: -------------------------------------------------------------------------------- 1 | 2 | .approval-vote-option { 3 | margin-bottom: 0.6em; 4 | } 5 | 6 | .approval-score { 7 | font-size: 1.4em; 8 | } 9 | 10 | .approval-content { 11 | padding-right: 2em; 12 | } 13 | 14 | .approval-options { 15 | position: absolute; 16 | width: 6em; 17 | top: 2em; 18 | right: 0; 19 | } 20 | 21 | .approval-item { 22 | padding-right: 5em; 23 | position: relative; 24 | } 25 | 26 | .approval-button { 27 | border: none; 28 | background: none; 29 | padding: 0; 30 | margin: 0; 31 | display: block; 32 | width: 100%; 33 | text-align: left; 34 | font-size: 1em; 35 | color: $default_text_color; 36 | & i { 37 | width: 27px; 38 | margin-right: 0.1em; 39 | } 40 | } 41 | 42 | .approval-up { 43 | &:hover { 44 | color: $color3; 45 | background:none; 46 | } 47 | &[disabled="disabled"] { 48 | color: inherit; 49 | } 50 | } 51 | 52 | .approval-down { 53 | &:hover { 54 | color: $color2_light; 55 | background:none; 56 | } 57 | &[disabled="disabled"] { 58 | color: inherit; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /assets/scss/modules/_browsehappy.scss: -------------------------------------------------------------------------------- 1 | .browserupgrade { 2 | position: fixed; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0.4em; 6 | background: $color3; 7 | text-align: center; 8 | font-size: 0.9em; 9 | color: $black; 10 | } -------------------------------------------------------------------------------- /assets/scss/modules/_comments.scss: -------------------------------------------------------------------------------- 1 | .add-comment-form { 2 | margin-bottom: 1em; 3 | } 4 | .comment-list { 5 | @extend %list-item-container; 6 | .comment-list { 7 | margin-top: 1em; 8 | padding-left: 1.5em; 9 | padding-top: 1em; 10 | border-left: 4px solid $grey-4; 11 | } 12 | &:hover { 13 | border-left-color: $grey-3; 14 | } 15 | } 16 | .comment-item { 17 | @extend %list-item; 18 | .comment-item { 19 | padding-top: 0; 20 | border-bottom: none; 21 | } 22 | } 23 | .comment-meta { 24 | @extend %list-item-meta; 25 | } 26 | .comment-author { 27 | @extend %list-item-author; 28 | } 29 | .comment-date { 30 | @extend %list-item-date; 31 | } 32 | .comment-text { 33 | @include word_wrap; 34 | @extend %list-item-text; 35 | } 36 | 37 | .comment-actions { 38 | @extend .list-item-actions; 39 | } 40 | .comment-reply { 41 | @extend %list-item-reply-button; 42 | } 43 | 44 | .comment-history-item { 45 | padding-top: 1em; 46 | padding-bottom: 1em; 47 | } 48 | .comment-history-item-link { 49 | &:visited, & { 50 | color: $color1; 51 | text-decoration: none; 52 | } 53 | &:hover { 54 | text-decoration: underline; 55 | } 56 | } 57 | 58 | .comment-reason-text { 59 | @extend %list-item-date; 60 | margin-bottom: .6em; 61 | &:not(:empty) { 62 | &:before { 63 | content: ', '; 64 | } 65 | } 66 | &:empty { 67 | display: none; 68 | } 69 | } 70 | 71 | .annotation-reason { 72 | fieldset { 73 | @include cf; 74 | border: none; 75 | padding: 0 0 1em 0; 76 | } 77 | label { 78 | float: left; 79 | width: 50%; 80 | margin-bottom: 0.5em; 81 | } 82 | span { 83 | padding-left: 0.4em; 84 | font-size: 0.9em; 85 | } 86 | } -------------------------------------------------------------------------------- /assets/scss/modules/_cookie_message.scss: -------------------------------------------------------------------------------- 1 | .cc_container { 2 | font-family: $sans-serif; 3 | 4 | a { 5 | color: $link-colour; 6 | &:visited { 7 | color: $link-visited-colour; 8 | } 9 | &:hover { 10 | color: $link-hover-colour; 11 | } 12 | &:active { 13 | color: $link-active-colour; 14 | } 15 | &:focus { 16 | outline: $color2_light solid 2px; 17 | } 18 | } 19 | 20 | .cc_btn { 21 | background-color: $color3; 22 | } 23 | } -------------------------------------------------------------------------------- /assets/scss/modules/_create_profile_form.scss: -------------------------------------------------------------------------------- 1 | .create-profile-form { 2 | .form-field { 3 | margin-bottom: 0.5em; 4 | } 5 | } 6 | 7 | .create-profile-fields { 8 | padding: 0 0 1em 0; 9 | border: 0; 10 | } 11 | -------------------------------------------------------------------------------- /assets/scss/modules/_draft.scss: -------------------------------------------------------------------------------- 1 | .add-a-draft-wrapper { 2 | text-align: center; 3 | margin-bottom: 2em; 4 | } 5 | 6 | 7 | %draft-section { 8 | margin-bottom: 2em; 9 | @include mq($small_device) { 10 | margin-bottom: 4em; 11 | } 12 | } 13 | 14 | .latest-draft-wrapper { 15 | @extend %draft-section; 16 | } 17 | .previous-drafts { 18 | @extend %draft-section; 19 | } 20 | .draft-writers { 21 | @extend %draft-section; 22 | } 23 | .draft-comments { 24 | @extend %draft-section; 25 | } 26 | 27 | .draft-info-wrapper { 28 | @include cf 29 | } 30 | 31 | .draft-info-links { 32 | @include mq($large_device) { 33 | float: right; 34 | } 35 | } 36 | 37 | .draft-info-link { 38 | font-size: 1.1em; 39 | display: block; 40 | padding-top: 1em; 41 | padding-bottom: 1em; 42 | } 43 | 44 | .draft-writer-container { 45 | @include mq($large_device) { 46 | float: left; 47 | width: 50%; 48 | } 49 | } 50 | 51 | .import-draft-error { 52 | h2 { 53 | margin-top: 0; 54 | line-height: 1.2; 55 | color: $color2_light; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /assets/scss/modules/_drafting_message.scss: -------------------------------------------------------------------------------- 1 | .drafting-message { 2 | border-bottom: 1px solid $default_text_color; 3 | margin-bottom: 2em; 4 | padding-top: 1em; 5 | padding-bottom: 2em; 6 | @include mq($small_device) { 7 | margin-bottom: 4em; 8 | padding-top: 2em; 9 | padding-bottom: 4em; 10 | } 11 | } 12 | .drafting-message-title { 13 | padding-top: 2em; 14 | border-top: 1px solid $default_text_color; 15 | } 16 | .drafting-message-link { 17 | @include default-button; 18 | margin-bottom: 0; 19 | } -------------------------------------------------------------------------------- /assets/scss/modules/_edit_draft.scss: -------------------------------------------------------------------------------- 1 | #clj-edit-draft { 2 | article { 3 | padding: 0.5em; 4 | border: 1px solid black; 5 | margin-bottom: 1em; 6 | } 7 | textarea { 8 | height: 500px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /assets/scss/modules/_edit_profile_form.scss: -------------------------------------------------------------------------------- 1 | .edit-profile-form { 2 | .form-field { 3 | margin-bottom: 0.5em; 4 | } 5 | } 6 | 7 | .edit-profile-fields { 8 | padding: 0 0 1em 0; 9 | border: 0; 10 | } 11 | -------------------------------------------------------------------------------- /assets/scss/modules/_errors.scss: -------------------------------------------------------------------------------- 1 | .error-sign-in-button { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /assets/scss/modules/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer-container { 2 | position: absolute; 3 | left: 0; 4 | bottom: 0; 5 | width: 100%; 6 | height: $footer_height; 7 | color: $offwhite; 8 | background-color: $black; 9 | } 10 | 11 | .footer { 12 | position: relative; 13 | margin: 0 1em 0 1em; 14 | padding: 1em 0 1em 0; 15 | @include mq($large_device) { 16 | margin: 0 auto 0 auto; 17 | padding: 1em; 18 | } 19 | } 20 | 21 | .footer-row { 22 | @include cf; 23 | margin-bottom: 1em; 24 | } 25 | 26 | .footer-content-links { 27 | float: left; 28 | list-style: none; 29 | margin: 0; 30 | padding: 0; 31 | width: 50%; 32 | li { 33 | margin-bottom: 0.25em; 34 | &:last-child { 35 | margin-bottom: 0; 36 | } 37 | } 38 | } 39 | .footer-link { 40 | display: inline-block; 41 | line-height: 1.4; 42 | color: $offwhite; 43 | text-decoration: none; 44 | &:visited { 45 | color: $offwhite; 46 | } 47 | &:hover { 48 | color: #2e8aca; 49 | } 50 | } 51 | 52 | .footer-content-text { 53 | float: right; 54 | width: 50%; 55 | } 56 | 57 | .footer-alpha-warning { 58 | font-style: italic; 59 | } 60 | 61 | .footer-footer { 62 | padding-top: 1em; 63 | border-top: 1px solid rgba($offwhite,0.25); 64 | text-align: right; 65 | } 66 | 67 | -------------------------------------------------------------------------------- /assets/scss/modules/_get_involved.scss: -------------------------------------------------------------------------------- 1 | .get-involved-widget { 2 | background: $color1; 3 | color: white; 4 | padding: 1em 1em 2em; 5 | margin: 2em 0 0; 6 | @include cf; 7 | } 8 | 9 | .get-involved-widget-title { 10 | margin: 0 0 0.5em; 11 | } 12 | 13 | .get-invloved-widget-item { 14 | border-bottom: 1px solid $color1_dark; 15 | padding-bottom: 1em; 16 | margin-bottom: 1em; 17 | @include mq($extra_large_device) { 18 | float: left; 19 | width: 29.3333%; 20 | border-bottom: none; 21 | padding-bottom: 0; 22 | margin: 0 2%; 23 | } 24 | i { 25 | font-size: 2em; 26 | float: left; 27 | position: relative; 28 | top: 2px; 29 | } 30 | .get-invloved-widget-item-text { 31 | padding-left: 2.7em; 32 | a { 33 | display: block; 34 | color: white; 35 | &:visited { 36 | color: white; 37 | } 38 | &:hover { 39 | color: white; 40 | text-decoration: none; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /assets/scss/modules/_guidance_text.scss: -------------------------------------------------------------------------------- 1 | $guidance_text_side_gutter: 1.5em; 2 | $guidance_text_top_gutter: 1.45em; 3 | 4 | .guidance-text-wrapper { 5 | background-color: $color1; 6 | margin: -1em -1em 2em -1em; 7 | padding: 1em; 8 | } 9 | 10 | .guidance-text { 11 | position: relative; 12 | max-width: $max_content_width; 13 | margin: -2px auto 0 auto; 14 | padding: $guidance_text_top_gutter $guidance_text_side_gutter 1em $guidance_text_side_gutter; 15 | background: $white; 16 | border: 1px solid $border_color; 17 | box-shadow: 0 4px 4px rgba(black,0.1); 18 | ul { 19 | padding-left: 1em; 20 | @include mq($small_device) { 21 | padding-left: 2em; 22 | } 23 | } 24 | } 25 | .guidance-text-heading { 26 | line-height: 1.1; 27 | margin-top: 0; 28 | margin-bottom: 0.4em; 29 | color: $color1; 30 | @include mq($medium_device) { 31 | padding-right: 100px; 32 | } 33 | } 34 | 35 | .guidance-buttons { 36 | text-align: right; 37 | margin-bottom: 1em; 38 | @include mq($medium_device) { 39 | margin-bottom: 0; 40 | } 41 | } 42 | 43 | .button-hide-guidance-text { 44 | text-decoration: none; 45 | display: block; 46 | z-index: 10; 47 | @include mq($medium_device) { 48 | position: absolute; 49 | top: 27px; 50 | right: $guidance_text_side_gutter; 51 | } 52 | span { 53 | margin-right: 2px; 54 | } 55 | &:hover { 56 | text-decoration: none; 57 | span { 58 | text-decoration: underline; 59 | } 60 | } 61 | &:visited, & { 62 | color: $default_text_color; 63 | } 64 | } 65 | 66 | .inline-guidance { 67 | margin-bottom: 1.5em; 68 | } 69 | .inline-guidance-list { 70 | padding: 0.5em 0 0 0; 71 | list-style: none; 72 | } 73 | .inline-guidance-list-item { 74 | margin-bottom: 0.75em; 75 | &:last-child { 76 | margin-bottom: 0; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /assets/scss/modules/_info_box.scss: -------------------------------------------------------------------------------- 1 | .info-box-text { 2 | font-size: 1em; 3 | text-align: center; 4 | padding: 0 3em; 5 | margin-bottom: 1em; 6 | @include mq($smallish_device) { 7 | padding-left: 1.75em; 8 | padding-right: 1.75em; 9 | } 10 | @include mq($large_device) { 11 | font-size: 1.1em; 12 | line-height: 1.3; 13 | } 14 | } 15 | .info-box-icon { 16 | color: $color3; 17 | overflow: hidden; 18 | text-align: center; 19 | font-size: 6em; 20 | margin-bottom: 0.15em; 21 | } 22 | .info-box-emphasis { 23 | color: $color2_light; 24 | } 25 | -------------------------------------------------------------------------------- /assets/scss/modules/_intro_middle_container_wrapper.scss: -------------------------------------------------------------------------------- 1 | .intro-middle-container-wrapper { 2 | border-top: 1px solid rgba(white,0.1); 3 | background: $intro_grey; 4 | margin: -2em -1em 1em -1em; 5 | padding: 1.5em 1em 1em 1em; 6 | color: $white; 7 | @include mq($medium_device) { 8 | margin-bottom: 3em; 9 | padding-top: 3em; 10 | padding-bottom: 2em; 11 | } 12 | @include mq($large_device) { 13 | margin-top: -3.5em; 14 | } 15 | } -------------------------------------------------------------------------------- /assets/scss/modules/_invitation_response.scss: -------------------------------------------------------------------------------- 1 | .invitation-response-form { 2 | display: inline-block; 3 | } 4 | .invitation-response-actions { 5 | 6 | } -------------------------------------------------------------------------------- /assets/scss/modules/_list_items.scss: -------------------------------------------------------------------------------- 1 | %list-item-container { 2 | margin: 0 0 $default_block_spacing 0; 3 | padding: 0; 4 | list-style: none; 5 | >:last-child { 6 | margin-bottom: 0; 7 | } 8 | } 9 | %list-item { 10 | border-bottom: 1px solid $border_color; 11 | padding: 1em 0; 12 | 13 | } 14 | %list-item-meta { 15 | margin-bottom: 0.2em; 16 | } 17 | %list-item-author { 18 | padding-right: 0.75em; 19 | color: $color2_light; 20 | font-size: 0.9em; 21 | } 22 | %list-item-date { 23 | font-size: 0.75em; 24 | color: $grey-1; 25 | } 26 | %list-item-text { 27 | color: $grey-15; 28 | margin-bottom: 0.3em; 29 | p { 30 | margin-bottom: 0.6em; 31 | &:last-of-type { 32 | margin-bottom: 0; 33 | } 34 | } 35 | &:empty { 36 | display: none; 37 | } 38 | } 39 | .list-item-actions { 40 | font-size: 87.5%; 41 | } 42 | %list-item-reply-button { 43 | display: inline-block; 44 | padding: 2px 0 3px 0; 45 | font-weight: 700; 46 | &:visited, & { 47 | color: $grey-15; 48 | text-decoration: none; 49 | } 50 | &:hover { 51 | text-decoration: underline; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/scss/modules/_main.scss: -------------------------------------------------------------------------------- 1 | .main-content { 2 | min-height: 100%; 3 | padding: $alpha_body_vertical_spacing 1em #{$footer_height + 16px} 1em; 4 | @include mq($medium_device) { 5 | padding-bottom: $footer_height + 136; 6 | } 7 | } -------------------------------------------------------------------------------- /assets/scss/modules/_messages.scss: -------------------------------------------------------------------------------- 1 | .flash-message-success, 2 | .invitation-banner { 3 | padding: 0.3em 1em 0.5em; 4 | position: relative; 5 | z-index: 5; 6 | width: 100%; 7 | background: $color1_dark; 8 | color: $white; 9 | p { 10 | margin-top: 0; 11 | margin-bottom: 0; 12 | } 13 | a { 14 | color: #E1FFE1; 15 | &:hover { 16 | color: #E1FFE1; 17 | text-decoration: none; 18 | } 19 | &:visited { 20 | color: #E1FFE1; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /assets/scss/modules/_policy.scss: -------------------------------------------------------------------------------- 1 | .policy-document { 2 | @include previewDocument(); 3 | padding-top: 1em; 4 | @include mq($medium_device) { 5 | padding-top: 3em; 6 | } 7 | } -------------------------------------------------------------------------------- /assets/scss/modules/_policy_contributors_list.scss: -------------------------------------------------------------------------------- 1 | .policy-contributors-list { 2 | @include cf(); 3 | @include unstyledList(); 4 | } 5 | .policy-contributors-list-item { 6 | margin-bottom: 0.4em; 7 | @include mq($medium_device) { 8 | width: 50%; 9 | float: left; 10 | } 11 | } -------------------------------------------------------------------------------- /assets/scss/modules/_profile.scss: -------------------------------------------------------------------------------- 1 | .profile-edit-button { 2 | margin-bottom: 1.6em; 3 | } 4 | -------------------------------------------------------------------------------- /assets/scss/modules/_progress_indicator.scss: -------------------------------------------------------------------------------- 1 | $progress_indicator_height: 40px; 2 | .progress-indicator { 3 | @include cf; 4 | margin: 0 auto $default_block_spacing auto; 5 | padding: 0 1em; 6 | list-style: none; 7 | text-align: center; 8 | @include mq($small_device) { 9 | padding-left: 0; 10 | padding-right: 0; 11 | } 12 | li { 13 | display: inline-block; 14 | margin: 0 -2px; 15 | width: 50%; 16 | padding: 0; 17 | background-color: $grey-4; 18 | color: $grey-15; 19 | @include mq($small_device) { 20 | width: 120px; 21 | } 22 | &:first-child { 23 | border-radius: 3px 0 0 3px; 24 | } 25 | &:last-child { 26 | border-radius: 0 3px 3px 0; 27 | } 28 | } 29 | .complete { 30 | background-color: $color1; 31 | color: $white; 32 | } 33 | .on { 34 | background-color: $color3; 35 | pointer-events: none; 36 | } 37 | a { 38 | display: block; 39 | height: $progress_indicator_height; 40 | line-height: $progress_indicator_height - 2; 41 | color: inherit; 42 | text-decoration: none; 43 | font-size: 90%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assets/scss/modules/_response.scss: -------------------------------------------------------------------------------- 1 | .response-action { 2 | background: $offwhite; 3 | // margin-top: 2em; 4 | padding-bottom: 2em; 5 | } 6 | .response-wrapper { 7 | //TODO use @extend .main-wrapper , node-sass needs upgrading 8 | padding: 1em; 9 | @include mq($large_device) { 10 | padding-left: 280px; 11 | max-width: 1100px; 12 | margin: 0 auto; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/scss/modules/_share_widget.scss: -------------------------------------------------------------------------------- 1 | .share-widget-title { 2 | margin: 1em 0 0.5em; 3 | font-size: 1em; 4 | text-align: center; 5 | @include mq($large_device) { 6 | text-align: left; 7 | } 8 | } 9 | 10 | .share-icon-list { 11 | list-style: none; 12 | margin: 0 0 0.5em -15px; 13 | padding: 0; 14 | text-align: center; 15 | li { 16 | display: inline-block; 17 | margin: 0 .25em 0; 18 | } 19 | } 20 | 21 | .share-this-url-input { 22 | width: 100%; 23 | padding: 0.2em 0.5em; 24 | font-size: 0.8em; 25 | } 26 | -------------------------------------------------------------------------------- /assets/scss/modules/_sign_up_form.scss: -------------------------------------------------------------------------------- 1 | .sign-up-form { 2 | margin: 0.67em 0; 3 | .form-field { 4 | margin-bottom: 0.5em; 5 | } 6 | } 7 | 8 | .sign-up-fields { 9 | padding: 0 0 1em 0; 10 | border: 0; 11 | } -------------------------------------------------------------------------------- /assets/scss/modules/_status_bar.scss: -------------------------------------------------------------------------------- 1 | .status-bar { 2 | position: relative; 3 | z-index: 3; 4 | min-height: $status_bar_height; 5 | padding: 0.3em 1em 0.5em; 6 | // margin-bottom: $alpha_block_spacing; 7 | background: $color2; 8 | color: white; 9 | @include mq($large_device) { 10 | // margin-bottom: $gamma_block_spacing; 11 | } 12 | p { 13 | margin-top: 0; 14 | margin-bottom: 0; 15 | } 16 | a { 17 | color: white; 18 | &:visited { 19 | color: white; 20 | } 21 | &:hover { 22 | opacity: 0.9; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/scss/modules/_writer_list.scss: -------------------------------------------------------------------------------- 1 | $writer_avatar_size_alpha: 40px; 2 | $writer_avatar_size_beta: 60px; 3 | 4 | .writer-item-list { 5 | @extend %list-item-container; 6 | } 7 | 8 | .writer-item { 9 | @include cf; 10 | margin: 0 1em 1em 0; 11 | padding: 0; 12 | } 13 | 14 | .writer-item-link { 15 | display: block; 16 | text-decoration: none; 17 | &:visited { 18 | color: inherit; 19 | } 20 | &:hover { 21 | text-decoration: none; 22 | } 23 | } 24 | 25 | .writer-item-photo { 26 | @include circle($writer_avatar_size_alpha); 27 | background: $grey-4; 28 | overflow: hidden; 29 | float: left; 30 | @include mq($smallish_device) { 31 | @include circle($writer_avatar_size_beta); 32 | } 33 | img { 34 | display: block; 35 | width: 100%; 36 | height: auto; 37 | } 38 | } 39 | .writer-item-no-image-icon { 40 | display: block; 41 | height: $writer_avatar_size_alpha; 42 | line-height: ($writer_avatar_size_alpha - 3px); 43 | text-align: center; 44 | color: rgba($grey-15,0.2); 45 | font-size: 1em; 46 | @include mq($smallish_device) { 47 | height: $writer_avatar_size_beta; 48 | line-height: ($writer_avatar_size_beta - 3px); 49 | font-size: 2em; 50 | } 51 | } 52 | 53 | .writer-item-author { 54 | display: inline-block; 55 | line-height: 1.1; 56 | color: $default_text_color; 57 | } 58 | 59 | .writer-item-text { 60 | padding-top: 5px; 61 | padding-left: ($writer_avatar_size_alpha + 15px); 62 | @include mq($smallish_device) { 63 | padding-left: ($writer_avatar_size_beta + 15px); 64 | } 65 | @include mq($large_device) { 66 | padding-top: 10px; 67 | } 68 | p:last-child { 69 | margin-bottom: 0; 70 | } 71 | } 72 | 73 | .writer-item-descripton { 74 | color: $grey-1; 75 | font-style: italic; 76 | @include word_wrap; 77 | } 78 | -------------------------------------------------------------------------------- /assets/scss/modules/_writer_profile.scss: -------------------------------------------------------------------------------- 1 | $writer_profile_avatar_size_alpha: 100px; 2 | $writer_profile_avatar_size_beta: 200px; 3 | 4 | .writer-profile { 5 | position: relative; 6 | } 7 | .writer-profile-head { 8 | margin-bottom: 1em; 9 | padding-right: $writer_profile_avatar_size_alpha; 10 | @include mq($medium_device) { 11 | padding-right: $writer_profile_avatar_size_beta; 12 | } 13 | } 14 | .writer-profile-title { 15 | margin-bottom: 0; 16 | color: $color2_light; 17 | } 18 | .writer-profile-joined { 19 | display: block; 20 | font-size: 0.75em; 21 | color: $grey-1; 22 | margin-bottom: 0; 23 | } 24 | 25 | .writer-profile-photo { 26 | @include circle($writer_profile_avatar_size_alpha); 27 | margin: 1em 0 1em 1em; 28 | background: $grey-4; 29 | overflow: hidden; 30 | float: right; 31 | @include mq($medium_device) { 32 | @include circle($writer_profile_avatar_size_beta); 33 | } 34 | img { 35 | display: block; 36 | width: 100%; 37 | height: auto; 38 | } 39 | } 40 | .writer-profile-no-image-icon { 41 | display: block; 42 | height: $writer_profile_avatar_size_alpha; 43 | line-height: ($writer_profile_avatar_size_alpha - 3px); 44 | text-align: center; 45 | color: rgba($grey-15,0.2); 46 | @include mq($medium_device) { 47 | height: $writer_profile_avatar_size_beta; 48 | line-height: ($writer_profile_avatar_size_beta - 3px); 49 | font-size: 6em; 50 | } 51 | } 52 | 53 | .writer-profile-biog { 54 | @include word_wrap; 55 | } 56 | .writer-profile-links { 57 | 58 | } 59 | .writer-profile-link { 60 | display: inline-block; 61 | text-decoration: none; 62 | &:hover { 63 | text-decoration: underline; 64 | } 65 | &:hover, &:visited { 66 | color: $link-hover-colour; 67 | } 68 | } 69 | .writer-profile-link-icon { 70 | margin-right: 0.5em; 71 | } 72 | .writer-profile-link-text { 73 | 74 | } 75 | 76 | .writer-profile-objective-list { 77 | padding-top: 3.5em; 78 | } 79 | -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_dashboard_annotation_item.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Dashboard annotation item 4 | 5 | */ 6 | 7 | .dashboard-annotation-section-list { 8 | @extend %list-item-container; 9 | padding-left: 1.5em; 10 | margin-left: 1em; 11 | border-left: solid 3px $color1; 12 | } 13 | 14 | .dashboard-annotation-list { 15 | @extend %list-item-container; 16 | } 17 | 18 | .dashboard-annotation-item { 19 | padding: 0.75em 0; 20 | &:first-child { 21 | padding-top: 0; 22 | } 23 | &:last-child { 24 | border-bottom: 1px solid $border_color; 25 | } 26 | } 27 | 28 | .dashboard-annotation-meta { 29 | @extend %list-item-meta; 30 | } 31 | 32 | .dashboard-annotation-author { 33 | @extend %list-item-author; 34 | } 35 | 36 | .dashboard-annotation-date { 37 | @extend %list-item-date; 38 | } 39 | 40 | .dashboard-annotation-text { 41 | @include word_wrap; 42 | @extend %list-item-text; 43 | color: $default_text_color; 44 | } 45 | 46 | .dashboard-annotation-actions { 47 | @extend .list-item-actions; 48 | margin-bottom: 0.5em; 49 | font-size: 1em; 50 | } 51 | 52 | .dashboard-annotation-section { 53 | padding-left: 1em; 54 | margin-bottom: 2em; 55 | } 56 | 57 | .dashboard-annotation-section-item { 58 | &:not(:first-child) { 59 | padding-top: 2em; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_dashboard_answer_item.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Answer item 4 | 5 | */ 6 | 7 | .dashboard-answer-item { 8 | padding: 2em 0; 9 | border-top: 1px solid $border_color; 10 | border-bottom: 1px solid $border_color; 11 | } 12 | .dashboard-answer-item-text { 13 | font-size: 1.1em; 14 | margin-bottom: 0.5em; 15 | @include word_wrap; 16 | } 17 | .dashboard-answer-item-stats { 18 | font-size: 1.3em; 19 | margin-bottom: 0.15em; 20 | } 21 | .dashboard-answer-item-up-score, 22 | .dashboard-answer-item-down-score { 23 | display: inline-block; 24 | } 25 | .dashboard-answer-item-up-score { 26 | margin-right: 0.5em; 27 | } 28 | .dashboard-answer-item-down-score { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_dashboard_comment_item.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Dashboard comment item 4 | 5 | */ 6 | 7 | .dashboard-comment-list { 8 | @extend %list-item-container; 9 | } 10 | 11 | .dashboard-comment-item { 12 | padding: 2em 0; 13 | border-top: 1px solid $border_color; 14 | border-bottom: 1px solid $border_color; 15 | } 16 | 17 | .dashboard-comment-meta { 18 | @extend %list-item-meta; 19 | } 20 | 21 | .dashboard-comment-author { 22 | @extend %list-item-author; 23 | } 24 | 25 | .dashboard-comment-date { 26 | @extend %list-item-date; 27 | } 28 | 29 | .dashboard-comment-text { 30 | @include word_wrap; 31 | @extend %list-item-text; 32 | color: $default_text_color; 33 | font-size: 1.1em; 34 | } 35 | 36 | .dashboard-vote-view { 37 | padding-right: 0.5em; 38 | } 39 | 40 | .dashboard-vote-count { 41 | min-width: 1.5em; 42 | padding-right: 0; 43 | text-align: left; 44 | } 45 | 46 | .dashboard-comment-actions { 47 | @extend .list-item-actions; 48 | margin-bottom: 0.5em; 49 | font-size: 1em; 50 | } 51 | -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_dashboard_stat.scss: -------------------------------------------------------------------------------- 1 | .dashboard-stat { 2 | padding-left: 1em; 3 | padding-right: 1em; 4 | display: inline-block; 5 | text-align: center; 6 | @include mq($medium_device) { 7 | padding-left: 0; 8 | padding-right: 2em; 9 | margin-right: 2em; 10 | text-align: left; 11 | } 12 | border-right: 1px solid $grey-3; 13 | &:last-of-type { 14 | margin-right: 0; 15 | border-right: none; 16 | } 17 | } 18 | .dashboard-stat-title { 19 | display: block; 20 | font-size: 1em; 21 | line-height: 1; 22 | } 23 | .dashboard-stat-amount { 24 | display: block; 25 | line-height: 1.2; 26 | font-size: 1.5em; 27 | font-weight: bold; 28 | color: $color2_light; 29 | @include mq($medium_device) { 30 | font-size: 2em; 31 | } 32 | } -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_dashboard_writer_note.scss: -------------------------------------------------------------------------------- 1 | $dashboard_save_writer_note_button_width: 100px; 2 | .dashboard-writer-note-item-form { 3 | padding-top: 0.25em; 4 | } 5 | 6 | .dashboard-writer-note-item-field { 7 | padding-right: $dashboard_save_writer_note_button_width; 8 | } 9 | .dashboard-writer-note-item-save-button { 10 | transition: background-color 250ms ease, color 250ms ease; 11 | position: absolute; 12 | top: 1px; 13 | right: 1px; 14 | z-index: 1; 15 | width: $dashboard_save_writer_note_button_width; 16 | height: 36px; 17 | background: transparent; 18 | color: $color1; 19 | border: 0; 20 | border-left: 1px solid $grey-3; 21 | &:hover { 22 | background-color: $color1; 23 | color: $white; 24 | } 25 | // outline 26 | } 27 | 28 | .dashboard-writer-note-item-container { 29 | padding-top: 0.25em; 30 | color: $color1; 31 | } -------------------------------------------------------------------------------- /assets/scss/modules/dashboard/_writer_dashboard_filter_list.scss: -------------------------------------------------------------------------------- 1 | .writer-dashboard-filter-list { 2 | display: table; 3 | width: 100%; 4 | border: 1px solid $grey-3; 5 | list-style: none; 6 | margin: 0; 7 | padding: 0; 8 | @include mq($medium_device) { 9 | border: none; 10 | } 11 | } 12 | .writer-dashboard-filter-list-item { 13 | position: relative; 14 | display: table-cell; 15 | &:after { 16 | content: ''; 17 | display: block; 18 | position: absolute; 19 | bottom: 0; 20 | right: 0; 21 | width: 1px; 22 | height: 100%; 23 | background: $grey-3; 24 | @include mq($medium_device) { 25 | height: 75%; 26 | } 27 | } 28 | &:last-child:after { 29 | display: none; 30 | } 31 | } 32 | .writer-dashboard-filter-list-item-button { 33 | display: block; 34 | height: 60px; 35 | padding: 1em; 36 | text-align: center; 37 | border-bottom: 5px solid $grey-3; 38 | &:visited, & { 39 | color: $default_text_color; 40 | } 41 | &:hover { 42 | text-decoration: none; 43 | color: $color2_light; 44 | border-color: rgba($color2_light,0.1); 45 | } 46 | &:focus { 47 | outline: none; 48 | border-bottom-color: $color3; 49 | } 50 | &.on, &:active { 51 | cursor: pointer; 52 | border-bottom-color: $color2_light; 53 | } 54 | &.on { 55 | pointer-events: none; 56 | } 57 | } -------------------------------------------------------------------------------- /assets/scss/modules/draft/_draft_add_inline_comment.scss: -------------------------------------------------------------------------------- 1 | .draft-add-inline-comment { 2 | $comment_icon_size: 24px; 3 | $hit_area_spacer: 10px; 4 | position: absolute; 5 | right: 0; 6 | a { 7 | @include mq($large_device) { 8 | width: $comment_icon_size; 9 | } 10 | position: relative; 11 | display: inline-block; 12 | width: 2.2em; 13 | height: $comment_icon_size; 14 | font-size: 16px; 15 | opacity: 0.35; 16 | color: currentColor; 17 | transition: opacity 250ms ease; 18 | text-decoration: none; 19 | &:hover { 20 | opacity: 1; 21 | } 22 | &:after { 23 | $hit_area: $comment_icon_size + $hit_area_spacer; 24 | content: ''; 25 | display: block; 26 | @include size($hit_area); 27 | position: absolute; 28 | top: -($hit_area_spacer/2); 29 | left: -($hit_area_spacer/2); 30 | } 31 | &:focus { 32 | outline: none; 33 | } 34 | i { 35 | position: absolute; 36 | top: $hit_area_spacer/2; 37 | left: 4px; 38 | } 39 | } 40 | } 41 | 42 | .draft-add-inline-comment-count { 43 | margin-left: 2em; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /assets/scss/modules/draft/_draft_editor.scss: -------------------------------------------------------------------------------- 1 | $editor_min_device_width: 321; 2 | 3 | .html-draft-editor { 4 | @include mq($editor_min_device_width) { 5 | height: 100%; 6 | } 7 | body { 8 | @include mq($editor_min_device_width) { 9 | height: 100%; 10 | } 11 | } 12 | .main-content { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | padding: $header_height 0 0 0; 19 | } 20 | } 21 | 22 | .draft-editor { 23 | @include mq($editor_min_device_width) { 24 | display: flex; 25 | flex-direction: row; 26 | align-items: stretch; 27 | align-self: stretch; 28 | height: 100%; 29 | } 30 | .draft-preview-document { 31 | font-size: 50%; 32 | @include mq($medium_device) { 33 | font-size: 100%; 34 | } 35 | } 36 | } 37 | .draft-editor-console { 38 | padding-left: 1em; 39 | padding-right: 1em; 40 | @include mq($editor_min_device_width) { 41 | flex: 1; 42 | overflow-y: auto; 43 | } 44 | @include mq($medium_device) { 45 | padding-left: 2em; 46 | padding-right: 2em; 47 | } 48 | } 49 | .draft-editor-document { 50 | padding: 1em; 51 | @include mq($editor_min_device_width) { 52 | flex: 1; 53 | max-height: 100%; 54 | max-width: 50%; 55 | overflow-y: auto; 56 | } 57 | @include mq($medium_device) { 58 | padding: 2em; 59 | } 60 | } 61 | 62 | 63 | .removed-from-version, 64 | del, 65 | .added-to-version, 66 | ins { 67 | text-decoration: none; 68 | white-space: pre-wrap; 69 | border-bottom: 1px dotted transparent; 70 | } 71 | 72 | .removed-from-version, 73 | del { 74 | background-color: rgba($color2_light,0.1); 75 | &:hover { 76 | border-bottom-color: $color2_light; 77 | } 78 | } 79 | .added-to-version, 80 | ins { 81 | background-color: rgba($color1_dark,0.1); 82 | &:hover { 83 | border-bottom-color: $color1_dark; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /assets/scss/modules/draft/_draft_preview_document.scss: -------------------------------------------------------------------------------- 1 | .draft-preview-document { 2 | @include previewDocument(); 3 | } 4 | .draft-preview-document-with-comments { 5 | @include previewDocument(); 6 | padding-right: 40px; 7 | } 8 | 9 | .annotate-draft-preview-document { 10 | @include previewDocument(); 11 | padding-bottom: 0; 12 | border-bottom: none; 13 | @include mq($small_device) { 14 | margin-bottom: 2em; 15 | } 16 | } 17 | 18 | .annotate-text { 19 | border-left: 5px solid $color1; 20 | padding-left: 1em; 21 | @include mq($large_device) { 22 | padding-left: 2.5em; 23 | } 24 | } -------------------------------------------------------------------------------- /assets/scss/modules/draft/_draft_tabs.scss: -------------------------------------------------------------------------------- 1 | .draft-tabs { 2 | $draft_tab_color_off: $grey-4; 3 | $draft_tab_color_hover: lighten($color1, 5%); 4 | $draft_tab_color_active: $color1; 5 | margin: 0 auto 2em auto; 6 | padding: 0; 7 | list-style: none; 8 | text-align: center; 9 | li { 10 | min-width: 6em; 11 | margin: 0 -2px; 12 | padding: 0; 13 | display: inline-block; 14 | background: $draft_tab_color_off; 15 | &:first-child { 16 | border-radius: 3em 0 0 3em; 17 | } 18 | &:last-child { 19 | border-radius: 0 3em 3em 0; 20 | } 21 | &.on a { 22 | background-color: $draft_tab_color_active; 23 | color: $white; 24 | } 25 | } 26 | a { 27 | display: block; 28 | font-size: 90%; 29 | line-height: 1.8; 30 | text-decoration: none; 31 | background-color: $draft_tab_color_off; 32 | color: $grey-15; 33 | border-radius: inherit; 34 | transition: background 200ms ease, color 200ms ease; 35 | &:hover { 36 | background-color: $draft_tab_color_hover; 37 | color: $white; 38 | } 39 | &:focus { 40 | 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /assets/scss/modules/draft/_draft_version_writer.scss: -------------------------------------------------------------------------------- 1 | $draft_version_writer_avatar_size_alpha: 40px; 2 | $draft_version_writer_avatar_size_beta: 60px; 3 | 4 | .draft-version-writer { 5 | width: 70%; 6 | margin-bottom: 1em; 7 | @include cf 8 | } 9 | 10 | .draft-version-writer-photo { 11 | @include circle($draft_version_writer_avatar_size_alpha); 12 | background: $grey-4; 13 | overflow: hidden; 14 | float: left; 15 | @include mq($smallish_device) { 16 | @include circle($draft_version_writer_avatar_size_beta); 17 | } 18 | img { 19 | display: block; 20 | width: 100%; 21 | height: auto; 22 | } 23 | } 24 | .draft-version-writer-no-image-icon { 25 | display: block; 26 | height: $draft_version_writer_avatar_size_alpha; 27 | line-height: ($draft_version_writer_avatar_size_alpha - 3px); 28 | text-align: center; 29 | color: rgba($grey-15,0.2); 30 | @include mq($smallish_device) { 31 | height: $draft_version_writer_avatar_size_beta; 32 | line-height: ($draft_version_writer_avatar_size_beta - 3px); 33 | } 34 | } 35 | 36 | .draft-version-writer-author { 37 | display: inline-block; 38 | line-height: 1.1; 39 | color: $default_text_color; 40 | } 41 | 42 | .draft-version-writer-text { 43 | padding-top: 0; 44 | padding-left: ($draft_version_writer_avatar_size_alpha + 8px); 45 | @include mq($smallish_device) { 46 | padding-top: 5px; 47 | padding-left: ($draft_version_writer_avatar_size_beta + 8px); 48 | } 49 | p:last-child { 50 | margin-bottom: 0; 51 | } 52 | } 53 | 54 | .draft-version-writer-descripton { 55 | color: $grey-1; 56 | font-style: italic; 57 | } 58 | -------------------------------------------------------------------------------- /assets/scss/pages/_draft_preview.scss: -------------------------------------------------------------------------------- 1 | .no-draft-version { 2 | text-align: center; 3 | margin-bottom: 2em; 4 | min-height: 200px; 5 | background: $grey-3; 6 | line-height: 200px; 7 | } 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/scss/pages/_objective_list.scss: -------------------------------------------------------------------------------- 1 | .objectives-list-actions { 2 | margin-bottom: 2em; 3 | @include mq($small_device) { 4 | margin-bottom: 3em; 5 | } 6 | } -------------------------------------------------------------------------------- /assets/scss/pages/_sign_in.scss: -------------------------------------------------------------------------------- 1 | .icon-d-cent { 2 | background-image: url('/static/stonecutter-sign-in-icon.png'); 3 | background-size: cover; 4 | background-repeat: no-repeat; 5 | } 6 | 7 | .sign-in-title { 8 | margin: 0; 9 | font-size: 1.5em; 10 | @include mq($large_device) { 11 | font-size: 2em; 12 | } 13 | } 14 | .sign-in-container { 15 | max-width: 30em; 16 | border: 1px solid $grey-3; 17 | margin: 0 auto 0.5em; 18 | padding: 1em; 19 | text-align: center; 20 | border-radius: 6px; 21 | @include mq($large_device) { 22 | padding: 2em; 23 | } 24 | } 25 | 26 | .sign-in-form { 27 | display: inline-block; 28 | max-width: 100%; 29 | margin-top: 0.5em; 30 | } 31 | 32 | .sign-up-container { 33 | max-width: 30em; 34 | border: 1px solid $grey-3; 35 | margin: 0 auto 0.5em; 36 | padding: 1em; 37 | border-radius: 4px; 38 | @include mq($large_device) { 39 | padding: 2em; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/scss/root/_buttons.scss: -------------------------------------------------------------------------------- 1 | @mixin default-button($text_color: $color1, $bg_color: $white, $hover_color: $color1, $text_hover_color: $white) { 2 | position: relative; 3 | display: inline-block; 4 | max-width: 18em; 5 | margin-right: 0.5em; 6 | margin-bottom: 0.8em; 7 | padding: 0.55em 1.35em 0.65em 1.35em; 8 | border: 1px solid $text_color; 9 | border-radius: 2em; 10 | transition: background 0.2s ease-in-out, color 0.2s ease-in-out, border 0.2s ease-in-out; 11 | outline-color: transparent; 12 | 13 | &:after { // fake the outline 14 | content: ''; 15 | opacity: 0; 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | margin: -2px; 20 | width: 100%; 21 | height: 100%; 22 | border: 2px solid $color3; 23 | border-radius: inherit; 24 | } 25 | &:visited, &:focus, & { 26 | background-color: $bg_color; 27 | text-decoration: none; 28 | color: $text_color; 29 | outline-width: 0; 30 | } 31 | &:hover { 32 | background-color: $hover_color; 33 | text-decoration: none; 34 | color: $text_hover_color; 35 | cursor: pointer; 36 | } 37 | &:focus { 38 | border-color: $color3; 39 | &:after { 40 | opacity: 1; 41 | } 42 | &:hover { 43 | border-color: transparent; 44 | &:after { 45 | opacity: 0; 46 | } 47 | } 48 | } 49 | } 50 | button { 51 | -webkit-appearance: none; 52 | -moz-appearance: none; 53 | margin: 0; 54 | padding: 0; 55 | } 56 | 57 | .button { 58 | @include default-button(); 59 | } 60 | .button-white { 61 | @include default-button($white, transparent, $white, $color1); 62 | } 63 | 64 | .pink-button { 65 | @include default-button($color2_light); 66 | } 67 | 68 | .normalise-button { 69 | padding: 0; 70 | margin: 0; 71 | border: none; 72 | background-color: transparent; 73 | } -------------------------------------------------------------------------------- /assets/scss/root/_colours.scss: -------------------------------------------------------------------------------- 1 | @import "_theme.scss"; 2 | 3 | //Colours 4 | $color1: #007E84 !default; 5 | $color1_dark: darken($color1, 5%); 6 | $color2: #9C0F83 !default; 7 | $color2_light: lighten($color2, 5%); 8 | $color3: #ffbf47 !default; 9 | 10 | // Standard palette, greys 11 | $black: #0b0c0c; 12 | $grey-1: #6f777b; 13 | $grey-15: #666666; 14 | $grey-2: #bfc1c3; 15 | $grey-3: #dee0e2; 16 | $grey-4: #f8f8f8; 17 | $grey-5: #E5E5E5; 18 | $offwhite: #F9F9F9; 19 | $white: #fff; 20 | 21 | $intro_grey: #5a6673; 22 | 23 | $default_text_color: rgba($black,0.8); 24 | $border_color: $grey-4; 25 | $grey-out: rgba($grey-1,0.7); 26 | 27 | // Links 28 | $link-colour: #005ea5; 29 | $link-active-colour: #2e8aca; 30 | $link-hover-colour: #2e8aca; 31 | $link-visited-colour: #005ea5; 32 | 33 | -------------------------------------------------------------------------------- /assets/scss/root/_forms.scss: -------------------------------------------------------------------------------- 1 | label { 2 | display: block; 3 | margin: 0 0 0.2em 0; 4 | } 5 | 6 | .form-field { 7 | position: relative; 8 | margin-top: 0; 9 | margin-bottom: 1em; 10 | } 11 | .form-field-label { 12 | display: block; 13 | margin-bottom: 0; 14 | } 15 | 16 | .form-field-label-title { 17 | display: inline-block; 18 | line-height: 1.2; 19 | margin-bottom: 0.2em; 20 | padding-top: 0.2em; 21 | font-weight: bold; 22 | } 23 | 24 | .label-helper { 25 | display: block; 26 | color: $grey-1; 27 | font-size: 0.9em; 28 | margin: 0 0 0.5em 0; 29 | } 30 | 31 | .not-yet-implemented-comment { 32 | font-size: smaller; 33 | } 34 | 35 | input, 36 | textarea { 37 | resize: none; 38 | width: 100%; 39 | // margin-bottom: 1em; 40 | padding: 0.5em; 41 | border: 1px solid #dcdcdc; 42 | &:focus { 43 | outline: $color2_light solid 2px; 44 | } 45 | } 46 | 47 | input[type='radio'] { 48 | width: initial; 49 | outline: none; 50 | } 51 | 52 | .user-input-error-message { 53 | font-size: 0.85em; 54 | color: $color2; 55 | } 56 | 57 | .required { 58 | color: $color2_light; 59 | } 60 | -------------------------------------------------------------------------------- /assets/scss/root/_grid.scss: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | @include cf; 3 | padding: 1em 0; 4 | margin-bottom: 2em; 5 | } 6 | 7 | .grid-container:after { 8 | content: " "; 9 | visibility: hidden; 10 | display: block; 11 | height: 0; 12 | clear: both; 13 | } 14 | 15 | .grid-column { 16 | @include mq($smallish_device) { 17 | min-height: 240px; 18 | float: left; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /assets/scss/root/_helpers.scss: -------------------------------------------------------------------------------- 1 | .no-margin-bottom { 2 | margin-bottom: 0; 3 | } 4 | 5 | .helper-text { 6 | color: $grey-1; 7 | font-size: 0.9em; 8 | margin-top: 0.5em; 9 | margin-bottom: 0.5em; 10 | } 11 | 12 | .not-yet-implemented-footnote-marker { 13 | color: $color2; 14 | font-weight: bold; 15 | } -------------------------------------------------------------------------------- /assets/scss/root/_links.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: $link-colour; 3 | text-decoration: underline; 4 | &:visited { 5 | color: $link-visited-colour; 6 | text-decoration: underline; 7 | } 8 | &:hover { 9 | color: $link-hover-colour; 10 | text-decoration: underline; 11 | } 12 | &:active { 13 | color: $link-active-colour; 14 | text-decoration: underline; 15 | } 16 | &:focus { 17 | outline: $color2_light solid 2px; 18 | } 19 | } 20 | 21 | .hidden-until-focus { 22 | @include visuallyhidden; 23 | &:focus { 24 | @include revert-visuallyhidden; 25 | position: absolute; 26 | background-color: white; 27 | } 28 | 29 | @include mq-max(768) { 30 | display: none; 31 | visibility: hidden; 32 | } 33 | } -------------------------------------------------------------------------------- /assets/scss/root/_lists.scss: -------------------------------------------------------------------------------- 1 | @mixin unstyledList() { 2 | list-style: none; 3 | margin: 0 0 1em 0; 4 | padding: 0; 5 | } 6 | 7 | .list-large { 8 | font-size: 1.2em; 9 | } 10 | .unstyled-list { 11 | @include unstyledList(); 12 | margin-bottom: 2em; 13 | } 14 | -------------------------------------------------------------------------------- /assets/scss/root/_print.scss: -------------------------------------------------------------------------------- 1 | @media print { 2 | * { 3 | background: transparent !important; 4 | box-shadow:none !important; 5 | color: black !important; 6 | text-shadow: none !important; 7 | -ms-filter: none !important; 8 | filter:none !important; 9 | } 10 | a, 11 | a:visited { 12 | text-decoration: underline; 13 | } 14 | a[href]:after { 15 | content: " (" attr(href) ")"; 16 | } 17 | abbr[title]:after { 18 | content: " (" attr(title) ")"; 19 | } 20 | .ir a:after, 21 | a[href^="javascript:"]:after, 22 | a[href^="#"]:after { 23 | content: ""; 24 | } 25 | pre, 26 | blockquote { 27 | border: 1px solid #999; 28 | page-break-inside: avoid; 29 | } 30 | thead { 31 | display: table-header-group; 32 | } 33 | tr, 34 | img { 35 | page-break-inside: avoid; 36 | } 37 | img { 38 | max-width: 100% !important; 39 | } 40 | @page { 41 | margin: 0.5cm; 42 | } 43 | p, 44 | h2, 45 | h3 { 46 | orphans: 3; 47 | widows: 3; 48 | } 49 | h2, 50 | h3 { 51 | page-break-after: avoid; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/scss/root/_root.scss: -------------------------------------------------------------------------------- 1 | * { 2 | @include box-sizing(border-box); 3 | } 4 | 5 | @-ms-viewport { 6 | width: device-width; 7 | } 8 | @-o-viewport { 9 | width: device-width; 10 | } 11 | 12 | html, 13 | body { 14 | min-height: 100%; 15 | overflow-x: hidden; 16 | } 17 | 18 | html { 19 | font-size: #{$doc-font-size + px}; 20 | position: relative; 21 | -webkit-tap-highlight-color: rgba(0,0,0,0); 22 | } 23 | 24 | body { 25 | background: $black; 26 | color: $default_text_color; 27 | font-family: $sans-serif; 28 | line-height: $baselineheight; 29 | -webkit-font-smoothing: antialiased; 30 | } 31 | 32 | .middle-container { 33 | max-width: $max_content_width; 34 | margin: 0 auto; 35 | } 36 | 37 | .app-wrapper { 38 | position: relative; 39 | top: 0; 40 | bottom: 100%; 41 | left: 0; 42 | z-index: 2; 43 | min-height: 100%; 44 | min-width: 100%; 45 | background-color: white; 46 | transition: transform 250ms ease-in-out; 47 | } -------------------------------------------------------------------------------- /assets/scss/root/_theme.scss: -------------------------------------------------------------------------------- 1 | $color1: #007E84; 2 | $color2: #9C0F83; 3 | $color3: #ffbf47; -------------------------------------------------------------------------------- /assets/scss/root/_typography.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin-top: 0; 3 | line-height: 1.2; 4 | font-family: $sans-serif; 5 | letter-spacing: -0.025em; 6 | @include mq($large_device) { 7 | font-size: 2.6em; 8 | } 9 | } 10 | h2 { 11 | margin-top: 0; 12 | line-height: 1.2; 13 | } 14 | p { 15 | margin-top: 0; 16 | &.lede { 17 | font-size: 1.3em; 18 | } 19 | } 20 | 21 | table { 22 | margin-bottom: 1em; 23 | } 24 | th, td { 25 | padding-right: 2em; 26 | text-align: left; 27 | &:last-child { 28 | padding-right: 0; 29 | } 30 | } 31 | th { 32 | padding-bottom: 0.5em; 33 | border-bottom: 1px solid currentColor; 34 | } 35 | td { 36 | padding-top: 0.5em; 37 | padding-bottom: 0.5em; 38 | } 39 | -------------------------------------------------------------------------------- /assets/scss/root/_variables.scss: -------------------------------------------------------------------------------- 1 | // Required for mixins, functions and helpers 2 | $doc-font-size : 16; //used for REM and Breakpoint conversion 3 | $baselineheight : 1.4; //used for typography and global lineheight 4 | 5 | 6 | //Font stacks 7 | $sans-serif : 'Source Sans Pro',Arial,sans-serif; 8 | $serif : "Georgia","Times New Roman",Times,serif; 9 | 10 | // $sans-serif : Arial,sans-serif; 11 | // $serif : 'Merriweather', "Georgia","Times New Roman",Times,serif; 12 | $monospace : Courier, monospace; 13 | 14 | 15 | //Stop FOUT on webfont loading 16 | .wf-loading h1 { 17 | visibility: hidden; 18 | } 19 | .wf-active h1, 20 | .wf-inactive h1 { 21 | visibility: visible; 22 | font-family: 'Source Sans Pro'; 23 | } 24 | 25 | $max_content_width: 40em; 26 | 27 | // alpha: mobile 28 | // beta: tabletish 29 | // gamma: desktopish 30 | // delta: superdesktopish 31 | 32 | $alpha_body_vertical_spacing: 1em; 33 | $gamma_body_vertical_spacing: 8.5em; 34 | $gamma_body_top_spacing: 3em; 35 | 36 | $default_block_spacing: 2em; 37 | $alpha_block_spacing: 1em; 38 | $gamma_block_spacing: 2em; 39 | 40 | $masthead_height: 52px; 41 | $status_bar_height: 35px; 42 | $header_height: $masthead_height + $status_bar_height; 43 | 44 | $small_device: 320; 45 | $smallish_device: 480; 46 | $medium_device: 640; 47 | $large_device: 768; 48 | $extra_large_device: 960; 49 | $monster_large_device: 1220; 50 | 51 | $footer_height: 200px; -------------------------------------------------------------------------------- /deploy_prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ssh $REMOTE_USER@$SERVER_IP <= -1) 8 | ); 9 | -------------------------------------------------------------------------------- /migrations/012_Remove_objective_id_from_global_identifiers.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | ALTER TABLE global_identifiers 6 | ADD COLUMN objective_id integer; 7 | 8 | UPDATE global_identifiers 9 | SET objective_id = answers.objective_id 10 | FROM answers 11 | WHERE answers.global_id = global_identifiers._id; 12 | 13 | ALTER TABLE global_identifiers 14 | ADD CONSTRAINT global_identifiers_objective_id_not_null CHECK(objective_id IS NOT NULL); 15 | 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /migrations/012_Remove_objective_id_from_global_identifiers.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.global_identifiers 2 | DROP COLUMN objective_id; 3 | -------------------------------------------------------------------------------- /migrations/013_add_global_identifier_to_objectives.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Remove foreign key constraint from objectives table */ 6 | ALTER TABLE objectives 7 | DROP CONSTRAINT objectives_global_id_fkey; 8 | 9 | /* Remove global identifiers for objectives */ 10 | DELETE FROM global_identifiers USING objectives 11 | WHERE global_identifiers._id = objectives.global_id; 12 | 13 | /* Drop global_id column from objectives */ 14 | ALTER TABLE objectives 15 | DROP COLUMN global_id; 16 | 17 | COMMIT; 18 | -------------------------------------------------------------------------------- /migrations/013_add_global_identifier_to_objectives.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Link objectives to global identifier */ 6 | ALTER TABLE objectives 7 | ADD COLUMN global_id integer; 8 | 9 | ALTER TABLE global_identifiers 10 | ADD COLUMN temp_objective_id integer; 11 | 12 | INSERT INTO global_identifiers (temp_objective_id) 13 | SELECT _id FROM objectives; 14 | 15 | UPDATE objectives 16 | SET global_id = global_identifiers._id 17 | FROM global_identifiers 18 | WHERE global_identifiers.temp_objective_id = objectives._id; 19 | 20 | ALTER TABLE objectives 21 | ALTER COLUMN global_id SET NOT NULL, 22 | ADD CONSTRAINT objectives_global_id_fkey 23 | FOREIGN KEY (global_id) REFERENCES global_identifiers (_id), 24 | ADD CONSTRAINT objectives_global_id_unique 25 | UNIQUE (global_id); 26 | 27 | ALTER TABLE global_identifiers 28 | DROP COLUMN temp_objective_id; 29 | 30 | COMMIT; 31 | -------------------------------------------------------------------------------- /migrations/014_add_comment_on_id_to_comments.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.comments 2 | DROP COLUMN comment_on_id; 3 | -------------------------------------------------------------------------------- /migrations/014_add_comment_on_id_to_comments.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | ALTER TABLE comments 6 | ADD COLUMN comment_on_id integer REFERENCES global_identifiers (_id); 7 | 8 | UPDATE comments 9 | SET comment_on_id = objectives.global_id 10 | FROM objectives 11 | WHERE objectives._id = comments.objective_id; 12 | 13 | ALTER TABLE comments 14 | ALTER COLUMN comment_on_id SET NOT NULL; 15 | 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /migrations/015_add_global_id_to_drafts.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Remove foreign key constraint from drafts table */ 6 | ALTER TABLE drafts 7 | DROP CONSTRAINT drafts_global_id_fkey; 8 | 9 | /* Remove global identifiers for objectives */ 10 | DELETE FROM global_identifiers USING drafts 11 | WHERE global_identifiers._id = drafts.global_id; 12 | 13 | /* Drop global_id column from objectives */ 14 | ALTER TABLE drafts 15 | DROP COLUMN global_id; 16 | 17 | COMMIT; 18 | -------------------------------------------------------------------------------- /migrations/015_add_global_id_to_drafts.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Link each draft to a global identifier */ 6 | ALTER TABLE drafts 7 | ADD COLUMN global_id integer; 8 | 9 | ALTER TABLE global_identifiers 10 | ADD COLUMN temp_draft_id integer; 11 | 12 | INSERT INTO global_identifiers (temp_draft_id) 13 | SELECT _id FROM drafts; 14 | 15 | UPDATE drafts 16 | SET global_id = global_identifiers._id 17 | FROM global_identifiers 18 | WHERE global_identifiers.temp_draft_id = drafts._id; 19 | 20 | ALTER TABLE drafts 21 | ALTER COLUMN global_id SET NOT NULL, 22 | ADD CONSTRAINT drafts_global_id_fkey 23 | FOREIGN KEY (global_id) REFERENCES global_identifiers (_id), 24 | ADD CONSTRAINT drafts_global_id_unique 25 | UNIQUE (global_id); 26 | 27 | ALTER TABLE global_identifiers 28 | DROP COLUMN temp_draft_id; 29 | 30 | COMMIT; 31 | -------------------------------------------------------------------------------- /migrations/016_add_uniqueness_and_not_null_constraint_to_answers_global_id.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.answers 2 | ALTER COLUMN global_id DROP NOT NULL, 3 | DROP CONSTRAINT answers_global_id_unique; 4 | -------------------------------------------------------------------------------- /migrations/016_add_uniqueness_and_not_null_constraint_to_answers_global_id.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.answers 2 | ALTER COLUMN global_id SET NOT NULL, 3 | ADD CONSTRAINT answers_global_id_unique 4 | UNIQUE (global_id); 5 | -------------------------------------------------------------------------------- /migrations/017_add_global_id_to_comments.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Remove foreign key constraint from comments table */ 6 | ALTER TABLE comments 7 | DROP CONSTRAINT comments_global_id_fkey; 8 | 9 | /* Remove global identifiers for comments */ 10 | DELETE FROM global_identifiers USING comments 11 | WHERE global_identifiers._id = comments.global_id; 12 | 13 | /* Drop global_id column from comments */ 14 | ALTER TABLE comments 15 | DROP COLUMN global_id; 16 | 17 | COMMIT; 18 | -------------------------------------------------------------------------------- /migrations/017_add_global_id_to_comments.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | /* Link each comment to a global identifier */ 6 | ALTER TABLE comments 7 | ADD COLUMN global_id integer; 8 | 9 | ALTER TABLE global_identifiers 10 | ADD COLUMN temp_comment_id integer; 11 | 12 | INSERT INTO global_identifiers (temp_comment_id) 13 | SELECT _id FROM comments; 14 | 15 | UPDATE comments 16 | SET global_id = global_identifiers._id 17 | FROM global_identifiers 18 | WHERE global_identifiers.temp_comment_id = comments._id; 19 | 20 | ALTER TABLE comments 21 | ALTER COLUMN global_id SET NOT NULL, 22 | ADD CONSTRAINT comments_global_id_fkey 23 | FOREIGN KEY (global_id) REFERENCES global_identifiers (_id), 24 | ADD CONSTRAINT comments_global_id_unique 25 | UNIQUE (global_id); 26 | 27 | ALTER TABLE global_identifiers 28 | DROP COLUMN temp_comment_id; 29 | 30 | COMMIT; 31 | -------------------------------------------------------------------------------- /migrations/018_add_status_to_objectives.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | ALTER TABLE objectives 6 | DROP COLUMN status; 7 | 8 | DROP TYPE objective_status; 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /migrations/018_add_status_to_objectives.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | CREATE TYPE objective_status AS ENUM ('open', 'drafting', 'closed'); 6 | 7 | ALTER TABLE objectives 8 | ADD COLUMN status objective_status NOT NULL DEFAULT 'open'; 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /migrations/019_add_stars.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.stars; 2 | -------------------------------------------------------------------------------- /migrations/019_add_stars.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.stars ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | objective_id integer REFERENCES objective8.objectives (_id) NOT NULL, 5 | created_by_id integer REFERENCES objective8.users (_id) NOT NULL, 6 | active boolean NOT NULL DEFAULT true); 7 | -------------------------------------------------------------------------------- /migrations/020_add_marks.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.marks; 2 | -------------------------------------------------------------------------------- /migrations/020_add_marks.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.marks ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | question_id integer REFERENCES objective8.questions (_id) NOT NULL, 5 | created_by_id integer REFERENCES objective8.users (_id) NOT NULL, 6 | active boolean NOT NULL DEFAULT true); 7 | -------------------------------------------------------------------------------- /migrations/021_rename_candidates_to_writers.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.writers RENAME TO candidates; 2 | ALTER TABLE objective8.candidates RENAME COLUMN writer TO candidate; 3 | -------------------------------------------------------------------------------- /migrations/021_rename_candidates_to_writers.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.candidates RENAME TO writers; 2 | ALTER TABLE objective8.writers RENAME COLUMN candidate TO writer; 3 | -------------------------------------------------------------------------------- /migrations/022_add_writer_notes.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.writer_notes; 2 | -------------------------------------------------------------------------------- /migrations/022_add_writer_notes.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.writer_notes ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | created_by_id integer REFERENCES objective8.users (_id) NOT NULL, 5 | objective_id integer REFERENCES objective8.objectives (_id) NOT NULL, 6 | note_on_id integer NOT NULL, 7 | global_id integer REFERENCES objective8.global_identifiers (_id) UNIQUE NOT NULL, 8 | note json NOT NULL 9 | ); 10 | -------------------------------------------------------------------------------- /migrations/023_add_sections.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.sections; 2 | -------------------------------------------------------------------------------- /migrations/023_add_sections.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.sections ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | draft_id integer REFERENCES objective8.drafts (_id) NOT NULL, 5 | global_id integer REFERENCES objective8.global_identifiers (_id) UNIQUE NOT NULL, 6 | section_label char(8) NOT NULL 7 | ); 8 | -------------------------------------------------------------------------------- /migrations/024_add_admins.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.admins; 2 | -------------------------------------------------------------------------------- /migrations/024_add_admins.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.admins ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | twitter_id varchar NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/025_add_removed_by_admin_to_objectives.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.objectives 2 | DROP COLUMN removed_by_admin; 3 | -------------------------------------------------------------------------------- /migrations/025_add_removed_by_admin_to_objectives.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.objectives 2 | ADD COLUMN removed_by_admin boolean DEFAULT FALSE; 3 | -------------------------------------------------------------------------------- /migrations/026_add_admin_removals.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.admin_removals; 2 | -------------------------------------------------------------------------------- /migrations/026_add_admin_removals.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.admin_removals ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | removal_uri varchar NOT NULL, 5 | removed_by_id integer REFERENCES objective8.users (_id) NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /migrations/027_add_reference_to_note_on_id_writer_notes.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.writer_notes 2 | DROP CONSTRAINT note_on_id_fkey; 3 | -------------------------------------------------------------------------------- /migrations/027_add_reference_to_note_on_id_writer_notes.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.writer_notes 2 | ADD CONSTRAINT note_on_id_fkey 3 | FOREIGN KEY (note_on_id) REFERENCES objective8.global_identifiers (_id); 4 | -------------------------------------------------------------------------------- /migrations/028_add_objective_id_column_to_sections_table.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.sections 2 | DROP COLUMN objective_id; 3 | -------------------------------------------------------------------------------- /migrations/028_add_objective_id_column_to_sections_table.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | ALTER TABLE sections 6 | ADD COLUMN objective_id integer; 7 | 8 | UPDATE sections 9 | SET objective_id = drafts.objective_id 10 | FROM drafts 11 | WHERE drafts._id = sections.draft_id; 12 | 13 | ALTER TABLE sections 14 | ALTER COLUMN objective_id SET NOT NULL, 15 | ADD CONSTRAINT sections_objective_id_fkey 16 | FOREIGN KEY (objective_id) REFERENCES objectives (_id); 17 | 18 | COMMIT; 19 | -------------------------------------------------------------------------------- /migrations/029_add_reasons.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.reasons; 2 | -------------------------------------------------------------------------------- /migrations/029_add_reasons.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | CREATE TYPE reason_type AS ENUM ('unclear', 'expand', 'suggestion', 'language', 'general'); 6 | 7 | CREATE TABLE reasons ( 8 | _id SERIAL PRIMARY KEY, 9 | _created_at timestamp DEFAULT current_timestamp, 10 | comment_id integer REFERENCES comments (_id) NOT NULL, 11 | reason reason_type NOT NULL DEFAULT 'general' 12 | ); 13 | 14 | INSERT INTO reasons (comment_id) 15 | SELECT comments._id 16 | FROM comments 17 | JOIN sections 18 | ON comments.comment_on_id = sections.global_id; 19 | 20 | COMMIT; 21 | 22 | -------------------------------------------------------------------------------- /migrations/030_remove_status_from_objectives.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | CREATE TYPE objective_status AS ENUM ('open', 'drafting', 'closed'); 6 | 7 | ALTER TABLE objectives 8 | ADD COLUMN status objective_status NOT NULL DEFAULT 'open'; 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /migrations/030_remove_status_from_objectives.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | SET LOCAL SCHEMA 'objective8'; 4 | 5 | ALTER TABLE objectives 6 | DROP COLUMN status; 7 | 8 | DROP TYPE objective_status; 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /migrations/031_remove_end_date_from_objectives.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.objectives 2 | ADD COLUMN end_date timestamp NOT NULL; 3 | -------------------------------------------------------------------------------- /migrations/031_remove_end_date_from_objectives.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.objectives 2 | DROP COLUMN end_date; 3 | -------------------------------------------------------------------------------- /migrations/032_rename_twitter_id_to_auth_provider_user_id_in_users.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.users RENAME COLUMN auth_provider_user_id TO twitter_id; 2 | -------------------------------------------------------------------------------- /migrations/032_rename_twitter_id_to_auth_provider_user_id_in_users.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.users RENAME COLUMN twitter_id TO auth_provider_user_id; 2 | -------------------------------------------------------------------------------- /migrations/033_rename_twitter_id_to_auth_provider_user_id_in_admins.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.admins RENAME COLUMN auth_provider_user_id TO twitter_id; 2 | -------------------------------------------------------------------------------- /migrations/033_rename_twitter_id_to_auth_provider_user_id_in_admins.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE objective8.admins RENAME COLUMN twitter_id TO auth_provider_user_id; 2 | -------------------------------------------------------------------------------- /migrations/034_add_activities.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.activities; 2 | -------------------------------------------------------------------------------- /migrations/034_add_activities.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.activities ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | activity json NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/035_remove_activities.down.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE objective8.activities ( 2 | _id SERIAL PRIMARY KEY, 3 | _created_at timestamp DEFAULT current_timestamp, 4 | activity json NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /migrations/035_remove_activities.up.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE objective8.activities; 2 | -------------------------------------------------------------------------------- /npm-postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm rebuild node-sass 4 | grunt build -------------------------------------------------------------------------------- /ops/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | digital_ocean_memory = 1024 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | 9 | config.vm.box = "ubuntu/trusty64" 10 | 11 | config.vm.define "development", primary: true do |dev| 12 | dev.vm.network "private_network", ip: "192.168.50.50" 13 | dev.vm.network :forwarded_port, guest: 22, host: 2282, id: "ssh", auto_correct: false 14 | dev.vm.network :forwarded_port, guest: 8080, host: 8080, id: "app", auto_correct: false 15 | 16 | dev.vm.synced_folder "../", "/var/objective8" 17 | 18 | dev.vm.provider :virtualbox do |vbox| 19 | vbox.customize ["modifyvm", :id, "--memory", 2048] 20 | end 21 | 22 | dev.vm.provision "ansible" do |ansible| 23 | ansible.playbook = "development_playbook.yml" 24 | ansible.inventory_path = "development.inventory" 25 | ansible.sudo = true 26 | end 27 | end 28 | 29 | config.vm.define "staging_vm" do |staging| 30 | staging.vm.network "private_network", ip: "192.168.50.52" 31 | staging.vm.network :forwarded_port, guest: 22, host: 2224, id: "ssh", auto_correct: false 32 | 33 | staging.vm.synced_folder "../", "/var/objective8" 34 | 35 | staging.vm.provider "virtualbox" do |vbox| 36 | vbox.customize ["modifyvm", :id, "--memory", digital_ocean_memory, "--cpus", 1] 37 | end 38 | 39 | staging.vm.provision "ansible" do |ansible| 40 | ansible.playbook = "staging_vm_playbook.yml" 41 | ansible.inventory_path = "staging_vm.inventory" 42 | ansible.extra_vars = {CONFIG_FILE_PATH: "objective8_vagrant_docker_config"} 43 | ansible.sudo = true 44 | end 45 | end 46 | 47 | end 48 | 49 | -------------------------------------------------------------------------------- /ops/development.inventory: -------------------------------------------------------------------------------- 1 | default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2282 2 | 3 | [development] 4 | default CORACLE_BEARER_TOKEN="test-bearer-token" 5 | 6 | [development:vars] 7 | external_jwk_set_url="" -------------------------------------------------------------------------------- /ops/development_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: development 3 | 4 | vars: 5 | DB_PASSWORD: "development" 6 | 7 | roles: 8 | - common 9 | - dev 10 | - leiningen 11 | - postgres 12 | - docker 13 | - coracle 14 | - objective8_postgres_config 15 | 16 | -------------------------------------------------------------------------------- /ops/digital_ocean_box.inventory: -------------------------------------------------------------------------------- 1 | default ansible_ssh_host=objective8.dcentproject.eu 2 | 3 | [digital_ocean_box] 4 | default 5 | 6 | [digital_ocean_box:vars] 7 | site_address="https://objective8.dcentproject.eu" 8 | cert_location="secure/objective8.crt" 9 | cert_key_location="secure/objective8.key" 10 | external_jwk_set_url="https://objective8.dcentproject.eu/as2/jwk-set" 11 | -------------------------------------------------------------------------------- /ops/digital_ocean_box_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: digital_ocean_box 3 | sudo: true 4 | remote_user: root 5 | 6 | vars_files: 7 | - "{{ CONFIG_FILE_PATH }}" 8 | 9 | roles: 10 | - common 11 | - postgres 12 | - nginx 13 | - ferm 14 | - docker 15 | - coracle 16 | - objective8_postgres_config 17 | - objective8_postgres_backups 18 | - objective8_application_config 19 | -------------------------------------------------------------------------------- /ops/digital_ocean_box_staging.inventory: -------------------------------------------------------------------------------- 1 | default ansible_ssh_host=objective8-staging.dcentproject.eu 2 | 3 | [digital_ocean_box] 4 | default 5 | 6 | [digital_ocean_box:vars] 7 | site_address="https://objective8-staging.dcentproject.eu" 8 | cert_location="secure/objective8.crt" 9 | cert_key_location="secure/objective8.key" 10 | external_jwk_set_url="https://objective8-staging.dcentproject.eu/as2/jwk-set" 11 | -------------------------------------------------------------------------------- /ops/objective8_config_template: -------------------------------------------------------------------------------- 1 | --- 2 | API_BEARER_NAME: "" 3 | API_BEARER_TOKEN: "" 4 | 5 | BASE_URI: "" 6 | 7 | DB_USER: "" 8 | DB_PASSWORD: "" 9 | DB_HOST: "" 10 | DB_NAME: "" 11 | 12 | ADMINS: "" 13 | 14 | AWS_ACCESS_KEY: "" 15 | AWS_SECRET_KEY: "" 16 | AWS_GPG_PASSPHRASE: "" 17 | 18 | JAVA_OPTS: "" 19 | 20 | APP_NAME: "" 21 | 22 | SHOW_ALPHA_WARNINGS: "" 23 | 24 | GA_TRACKING_ID: "" 25 | COOKIE_MESSAGE_ENABLED: "" 26 | 27 | STONECUTTER_AUTH_URL: "" 28 | STONECUTTER_CLIENT_ID: "" 29 | STONECUTTER_CLIENT_SECRET: "" 30 | STONECUTTER_NAME: "" 31 | 32 | TWITTER_CONSUMER_TOKEN: "" 33 | TWITTER_CONSUMER_SECRET_TOKEN: "" 34 | 35 | FB_CLIENT_ID: "" 36 | FB_CLIENT_SECRET: "" 37 | 38 | CORACLE_URI: "" 39 | CORACLE_BEARER_TOKEN: "" 40 | -------------------------------------------------------------------------------- /ops/objective8_docker_config_template: -------------------------------------------------------------------------------- 1 | API_BEARER_NAME= 2 | API_BEARER_TOKEN= 3 | 4 | BASE_URI= 5 | APP_PORT= 6 | 7 | POSTGRES_DB= 8 | POSTGRES_USER= 9 | POSTGRES_PASSWORD= 10 | 11 | ADMINS= 12 | 13 | AWS_ACCESS_KEY= 14 | AWS_SECRET_KEY= 15 | AWS_GPG_PASSPHRASE= 16 | 17 | APP_NAME= 18 | 19 | SHOW_ALPHA_WARNINGS= 20 | 21 | GA_TRACKING_ID= 22 | COOKIE_MESSAGE_ENABLED= 23 | 24 | STONECUTTER_AUTH_URL= 25 | STONECUTTER_CLIENT_ID= 26 | STONECUTTER_CLIENT_SECRET= 27 | STONECUTTER_NAME= 28 | 29 | OKTA_CLIENT_ID= 30 | OKTA_CLIENT_SECRET= 31 | OKTA_AUTH_URL= 32 | 33 | TWITTER_CONSUMER_TOKEN= 34 | TWITTER_CONSUMER_SECRET_TOKEN= 35 | 36 | FB_CLIENT_ID= 37 | FB_CLIENT_SECRET= 38 | 39 | CORACLE_URI= 40 | CORACLE_BEARER_TOKEN= 41 | -------------------------------------------------------------------------------- /ops/provision-objective8-staging.sh: -------------------------------------------------------------------------------- 1 | ansible-playbook digital_ocean_box_playbook.yml -i digital_ocean_box_staging.inventory --extra-vars="CONFIG_FILE_PATH=objective8_staging_docker_config" 2 | -------------------------------------------------------------------------------- /ops/provision-objective8.sh: -------------------------------------------------------------------------------- 1 | ansible-playbook digital_ocean_box_playbook.yml -i digital_ocean_box.inventory --extra-vars="CONFIG_FILE_PATH=objective8_docker_config" 2 | -------------------------------------------------------------------------------- /ops/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: update apt 3 | command: sudo apt-get update 4 | 5 | -------------------------------------------------------------------------------- /ops/roles/coracle/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: make db directory 4 | file: path=/data/db state=directory 5 | 6 | - name: docker mongo 7 | docker_container: 8 | name: mongo 9 | image: mongo:3 10 | pull: true 11 | volumes: 12 | - /data/db:/data/db 13 | api_version: 1.21 14 | 15 | - name: docker coracle 16 | docker_container: 17 | name: coracle 18 | image: dcent/coracle:latest 19 | pull: true 20 | links: 21 | - "mongo:mongo" 22 | expose: 7000 23 | ports: 24 | - "7000:7000/tcp" 25 | env: 26 | BEARER_TOKEN: "{{ CORACLE_BEARER_TOKEN }}" 27 | EXTERNAL_JWK_SET_URL: "{{ external_jwk_set_url }}" 28 | api_version: 1.21 -------------------------------------------------------------------------------- /ops/roles/dev/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install git 3 | apt: name=git state=present 4 | 5 | - name: add nodesource apt-key 6 | apt_key: url=https://deb.nodesource.com/gpgkey/nodesource.gpg.key state=present 7 | 8 | - name: add nodesource deb repo 9 | apt_repository: repo='deb https://deb.nodesource.com/node_0.10 trusty main' state=present 10 | 11 | - name: add nodesource deb-src repo 12 | apt_repository: repo='deb-src https://deb.nodesource.com/node_0.10 trusty main' state=present 13 | 14 | - name: install node.js 15 | apt: name=nodejs state=latest 16 | 17 | - name: symlink node->nodejs 18 | file: src=/usr/bin/nodejs dest=/usr/bin/node state=link 19 | 20 | - name: Node.js | Install packages 21 | npm: name={{item}} global=yes 22 | with_items: 23 | - npm 24 | - grunt-cli 25 | 26 | - name: install docker.io 27 | apt: name="docker.io" state=present 28 | 29 | - name: install firefox 30 | get_url: url=https://ftp.mozilla.org/pub/firefox/releases/38.0/linux-x86_64/en-US/firefox-38.0.tar.bz2 dest=/opt/ 31 | 32 | - name: extract firefox 33 | unarchive: src=/opt/firefox-38.0.tar.bz2 dest=/opt/ copy=no 34 | 35 | - name: link firefox binary to path 36 | file: src=/opt/firefox/firefox dest=/usr/bin/firefox state=link 37 | 38 | - name: install xvfb 39 | apt: name=xvfb state=present 40 | 41 | - name: Install node dependencies 42 | npm: path=/var/objective8 43 | 44 | - name: Force post-install 45 | command: ./npm-postinstall.sh 46 | args: 47 | chdir: /var/objective8/ -------------------------------------------------------------------------------- /ops/roles/docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # - script: get_docker.sh 3 | 4 | - name: install pip 5 | apt: name=python-pip state=present 6 | 7 | - name: install docker-py 8 | pip: name=docker-py state=latest 9 | 10 | - name: add docker apt key 11 | apt_key: keyserver=hkp://p80.pool.sks-keyservers.net:80 id=36A1D7869245C8950F966E92D8576A8BA88D21E9 12 | 13 | - name: add docker apt repo 14 | apt_repository: repo='deb https://get.docker.com/ubuntu docker main' state=present 15 | 16 | - name: install lxc-docker 17 | apt: name=lxc-docker state=latest 18 | 19 | - name: restart docker service 20 | service: name=docker state=restarted 21 | -------------------------------------------------------------------------------- /ops/roles/ferm/files/ferm.conf: -------------------------------------------------------------------------------- 1 | # Ferm script for configuring iptables. 2 | 3 | table filter { 4 | 5 | chain INPUT { 6 | # Set the default policy to ACCEPT to avoid getting 7 | # accidentally locked out. 8 | policy ACCEPT; 9 | 10 | # Connection tracking. 11 | mod state state INVALID DROP; 12 | mod state state (ESTABLISHED RELATED) ACCEPT; 13 | 14 | # Allow local connections. 15 | interface lo ACCEPT; 16 | 17 | # Allow ssh connections. 18 | proto tcp dport ssh ACCEPT; 19 | 20 | # Allow http(s) connections. 21 | proto tcp dport (https http) ACCEPT; 22 | 23 | # Allow postgres connections 24 | proto tcp dport 5432 ACCEPT; 25 | 26 | # Ansible specified rules. 27 | 28 | # Because the default policy is to ACCEPT we DROP 29 | # everything that comes through to this stage. 30 | DROP; 31 | } 32 | 33 | chain fail2ban-ssh; 34 | 35 | # Outgoing connections are not limited. 36 | chain OUTPUT policy ACCEPT; 37 | 38 | # This is not a router. 39 | chain FORWARD policy DROP; 40 | } 41 | -------------------------------------------------------------------------------- /ops/roles/ferm/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install ferm 3 | apt: name=ferm state=present 4 | 5 | - name: Add ferm config directory 6 | file: path=/etc/ferm state=directory 7 | owner=root group=root mode=0700 8 | 9 | - name: add the ferm.conf file to /etc/ferm 10 | copy: src=ferm.conf dest=/etc/ferm/ferm.conf 11 | owner=root group=root mode=0700 12 | 13 | - name: run ferm 14 | command: ferm /etc/ferm/ferm.conf 15 | -------------------------------------------------------------------------------- /ops/roles/leiningen/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install jdk7 3 | apt: name="openjdk-7-jdk" state=present 4 | 5 | - name: create /home/vagrant/bin 6 | command: "mkdir -p /home/vagrant/bin" 7 | 8 | - name: copy lein script 9 | copy: src="lein" dest="/home/vagrant/bin/lein" mode=0755 10 | 11 | -------------------------------------------------------------------------------- /ops/roles/nginx/files/testing_objective8.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE3DCCA8SgAwIBAgIJAKgZaeGnsuPVMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD 3 | VQQGEwJFTjEVMBMGA1UECBMMVGhvdWdodFdvcmtzMRUwEwYDVQQHEwxUaG91Z2h0 4 | V29ya3MxFTATBgNVBAoTDFRob3VnaHRXb3JrczEVMBMGA1UECxMMVGhvdWdodFdv 5 | cmtzMREwDwYDVQQDEwhURVNUIEtFWTEmMCQGCSqGSIb3DQEJARYXZC1jZW50QHRo 6 | b3VnaHR3b3Jrcy5jb20wHhcNMTUwNjE4MTQyNTQ0WhcNMTYwNjE3MTQyNTQ0WjCB 7 | pDELMAkGA1UEBhMCRU4xFTATBgNVBAgTDFRob3VnaHRXb3JrczEVMBMGA1UEBxMM 8 | VGhvdWdodFdvcmtzMRUwEwYDVQQKEwxUaG91Z2h0V29ya3MxFTATBgNVBAsTDFRo 9 | b3VnaHRXb3JrczERMA8GA1UEAxMIVEVTVCBLRVkxJjAkBgkqhkiG9w0BCQEWF2Qt 10 | Y2VudEB0aG91Z2h0d29ya3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 11 | CgKCAQEA6UHomRFXnkfZyuly3GlBFWLZg53P6z3CPt2KE7Pu42vLXNBbQDBkY8LN 12 | ko1yFLqsHTIcgqk05mVPRqar9rycUUCHJx364hc65KMDJxElTax/QeDBPFWqSZfQ 13 | zIHlNasctCpd2SUzihpaCUkCUMAKEUmnQQq29Ck/7870+55l7Xo7wrW8p9Qlu7+r 14 | WFvW5wZ96fk+U6PuFiIYAJ40Hi3N32Wvea1R466fOLAzBrnIsGzHzYD1TZkVp5BY 15 | as3lVhopENXTlssv2ZoyxHE/uGdffdLxXXSUffaXMQF8hYXkexzK+g19uCo1LLOU 16 | gIH2Xzu2nghjEupepEqkTdNY4gxTQQIDAQABo4IBDTCCAQkwHQYDVR0OBBYEFO6h 17 | N3IzD1aP9uRyZ7cgWp6zXweRMIHZBgNVHSMEgdEwgc6AFO6hN3IzD1aP9uRyZ7cg 18 | Wp6zXweRoYGqpIGnMIGkMQswCQYDVQQGEwJFTjEVMBMGA1UECBMMVGhvdWdodFdv 19 | cmtzMRUwEwYDVQQHEwxUaG91Z2h0V29ya3MxFTATBgNVBAoTDFRob3VnaHRXb3Jr 20 | czEVMBMGA1UECxMMVGhvdWdodFdvcmtzMREwDwYDVQQDEwhURVNUIEtFWTEmMCQG 21 | CSqGSIb3DQEJARYXZC1jZW50QHRob3VnaHR3b3Jrcy5jb22CCQCoGWnhp7Lj1TAM 22 | BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCs+H9WeFqhUvltOz2JfqiR 23 | 29EenyyYRRz469HGRoOasmude4YwAyldDryOK0/D4EN6YceJVUdLxEpilhip3RbT 24 | fKJKmDkluCVx+JaFijkTbekAFMIGJS+YJk2XLvemwaNo5XUez3/QtCIYE3a88iEe 25 | W8xLeCMQwgxp2qwU7XG5nerI6lwSeqqDup0VMtsq1gbAYquFF6LzHjrj8aZdxbkW 26 | npKNMk35Qybgum8hUObxGKUGs5kFYwXxWyknXfqktNNTb7CU8ED4khstpJmouMqT 27 | QkWj1LRFJUdvZ4QOSC77X0DsO6FLRW1uNv8BG6l75EC/iRvU0+pSCtFQ/0hOqOmL 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /ops/roles/nginx/files/testing_objective8.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA6UHomRFXnkfZyuly3GlBFWLZg53P6z3CPt2KE7Pu42vLXNBb 3 | QDBkY8LNko1yFLqsHTIcgqk05mVPRqar9rycUUCHJx364hc65KMDJxElTax/QeDB 4 | PFWqSZfQzIHlNasctCpd2SUzihpaCUkCUMAKEUmnQQq29Ck/7870+55l7Xo7wrW8 5 | p9Qlu7+rWFvW5wZ96fk+U6PuFiIYAJ40Hi3N32Wvea1R466fOLAzBrnIsGzHzYD1 6 | TZkVp5BYas3lVhopENXTlssv2ZoyxHE/uGdffdLxXXSUffaXMQF8hYXkexzK+g19 7 | uCo1LLOUgIH2Xzu2nghjEupepEqkTdNY4gxTQQIDAQABAoIBAQDd5m9qPo9F6kCM 8 | wm0ctZzOxYz7osPLnKMPvx2+BKy7+S4ri6NjeyXlqcp7IshmY/echrGMs3+5tqMm 9 | KSTqwx9KRMLOOb0UHNlP7KvxHGSKchFWEISD61LmU+C0zNXKqo/R7YP+MV/If60r 10 | rCLhwu9Q5uqP+6t0t1E1x4JTZKq+sHrXLRWZF7MW7uKl1MS19KvUSWhEVhC7MvQo 11 | lXPvsajUMHIMKewc1bAMe91assZZN/wXbd5QT7BBDtguYt9QA+mzikZ1KkopD1rL 12 | GT++79zq4n1UeawG0BE4wK3qbLnnweFcOZ5HiLQirOyftPmwAw+84qrAiwKBvYO1 13 | T5Pmu6bhAoGBAPqbhn7+FmyJg2BnBkANJLi/63GQeM4mwGf6PJfzxfWuDZ7r26ga 14 | 3yVhVFEc2a0TP4d70bwNezgo95LmgqEF/wOy9DFMkolAjc7DYqxZVc2fdHOeWjbr 15 | dexuUT0/5Lr8wCxRt9Wy/GUWLMRZDdyAGWMMCbNCjsnEGfTg5/PuSeF1AoGBAO5G 16 | z2aiFxaVSqZCg8GXwnD+PqkOf3fd0zuLY2PTM5q1V5ytRaCGr7UXgUo05uDwBTpG 17 | ++1Nj2Fv+5FfSycyZ9Mjzfc07ISV30xAoslfzzDe8CzkoPhe9Dq+BB/KOtgww+id 18 | 076ym+6bcgwXtloFg9bs2nbOzGOCSH6qgYid3IUdAoGAcps7Z8olUR+WIDkdR597 19 | Iq5KKxZJ2OUp6qMqoMcPyen+OqZcPsWDNSIMoEeUWK1LyVbbtKdpqWY6ykh2htMq 20 | K3PkbsM45GHMODlsX6s/LRj19YX+dc06kZRlvKACp2y9Kcd5TdZbzJLWiDi9uRAw 21 | C/bOXNdcW3M995n/GbPov50CgYEAgsgk4B1JdOC+V2Ecti5Yz/IMbHgDqMP9Q6pv 22 | BASRR3IPQ3SrSb+DQ29CTHua2Y2EIQeDES3H6+AuAQ1/z2TQLchyLSRESUWFiXHU 23 | p5jJvPYwd0OJwqlDfdZ7pwM1pyGk9dFivkGEasOxJkgBk4mBYn8gLaO19Uw2BCgL 24 | 7vNObVUCgYBFlLGfUBZ0H5V7fomwpHa4cDjbb/eyUSfBUf/tGyY8gD1D7DXWphBH 25 | vQw+sBQxWO+JTT0gj2qf6+ZajfdwDYwJJ5D9u0yy+fR0AR6rngymiCouvf7JO2In 26 | nemATzx0OA+w2LZHcD7yHYUAYHIaJTnyy1hnFvpOKD5JfmAwSyUkUw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /ops/roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install nginx 3 | apt: name=nginx state=present 4 | 5 | - name: install fail2ban 6 | apt: name=fail2ban state=present 7 | 8 | - name: create SSL folder 9 | file: path="/etc/nginx/ssl" state=directory 10 | 11 | - name: copy SSL cert 12 | copy: src={{ cert_location }} dest="/etc/nginx/ssl/objective8.crt" 13 | 14 | - name: copy SSL key 15 | copy: src={{ cert_key_location }} dest="/etc/nginx/ssl/objective8.key" 16 | 17 | - name: Create directory for DH parameter file 18 | file: path=/etc/nginx/cert state=directory mode=0755 19 | 20 | - name: Create DH parameters file 21 | command: openssl dhparam 2048 -out /etc/nginx/cert/dhparam.pem 22 | args: 23 | creates: /etc/nginx/cert/dhparam.pem 24 | 25 | - name: copy over objective8 nginx config 26 | template: src="objective8.j2" dest="/etc/nginx/sites-available/objective8" mode=0644 27 | 28 | - name: create symbolic link to nginx objective8 config 29 | file: src="/etc/nginx/sites-available/objective8" dest="/etc/nginx/sites-enabled/objective8" state=link 30 | 31 | - name: remove default nginx symbolic link from sites-enabled 32 | file: path="/etc/nginx/sites-enabled/default" state=absent 33 | 34 | - name: restart nginx 35 | service: name=nginx state=restarted 36 | -------------------------------------------------------------------------------- /ops/roles/nginx/templates/objective8.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | return 301 {{ site_address }}$request_uri; 4 | } 5 | 6 | 7 | server { 8 | listen 443 ssl; 9 | 10 | ssl_certificate /etc/nginx/ssl/objective8.crt; 11 | ssl_certificate_key /etc/nginx/ssl/objective8.key; 12 | 13 | ssl_session_cache shared:SSL:32m; 14 | ssl_session_timeout 10m; 15 | 16 | ssl_dhparam /etc/nginx/cert/dhparam.pem; 17 | ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 18 | 19 | # trailing '/' after port means app doesn't know about '/as2/' 20 | location /as2/ { 21 | proxy_pass http://localhost:7000/; 22 | } 23 | # no trailing '/' after port means app knows about '/api/v1/' 24 | location /api/v1/ { 25 | proxy_pass http://localhost:8081; 26 | } 27 | location / { 28 | proxy_pass http://localhost:8080; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ops/roles/objective8_application_config/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: copy application config file 3 | template: src=objective8_config.j2 dest=/usr/local/objective8/objective8_config 4 | -------------------------------------------------------------------------------- /ops/roles/objective8_application_config/templates/objective8_config.j2: -------------------------------------------------------------------------------- 1 | API_BEARER_NAME={{ API_BEARER_NAME }} 2 | API_BEARER_TOKEN={{ API_BEARER_TOKEN }} 3 | BASE_URI={{ BASE_URI }} 4 | DB_USER={{ DB_USER }} 5 | DB_PASSWORD={{ DB_PASSWORD }} 6 | DB_HOST={{ DB_HOST }} 7 | DB_NAME={{ DB_NAME }} 8 | ADMINS={{ ADMINS }} 9 | AWS_ACCESS_KEY={{ AWS_ACCESS_KEY }} 10 | AWS_SECRET_KEY={{ AWS_SECRET_KEY }} 11 | AWS_GPG_PASSPHRASE={{ AWS_GPG_PASSPHRASE }} 12 | JAVA_OPTS={{ JAVA_OPTS }} 13 | APP_NAME={{ APP_NAME }} 14 | SHOW_ALPHA_WARNINGS={{ SHOW_ALPHA_WARNINGS }} 15 | GA_TRACKING_ID={{ GA_TRACKING_ID }} 16 | COOKIE_MESSAGE_ENABLED={{ COOKIE_MESSAGE_ENABLED }} 17 | STONECUTTER_AUTH_URL={{ STONECUTTER_AUTH_URL }} 18 | STONECUTTER_CLIENT_ID={{ STONECUTTER_CLIENT_ID }} 19 | STONECUTTER_CLIENT_SECRET={{ STONECUTTER_CLIENT_SECRET }} 20 | STONECUTTER_NAME={{ STONECUTTER_NAME }} 21 | TWITTER_CONSUMER_TOKEN={{ TWITTER_CONSUMER_TOKEN }} 22 | TWITTER_CONSUMER_SECRET_TOKEN={{ TWITTER_CONSUMER_SECRET_TOKEN }} 23 | FB_CLIENT_ID={{ FB_CLIENT_ID }} 24 | FB_CLIENT_SECRET={{ FB_CLIENT_SECRET }} 25 | CORACLE_BEARER_TOKEN={{ CORACLE_BEARER_TOKEN }} 26 | CORACLE_URI={{ CORACLE_URI }} 27 | -------------------------------------------------------------------------------- /ops/roles/objective8_postgres_backups/files/postgres_backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir=/usr/local/objective8 4 | cd $dir 5 | mkdir -p $dir/backups 6 | backup_path=$dir/backups/backup_$(date +"%Y%m%d%H%M").bak 7 | PGPASSFILE=$dir/.pgpass pg_dump -U objective8 -h localhost > $backup_path 8 | 9 | bucket_name=objective8_pg_db_backups 10 | s3cmd mb s3://$bucket_name -c $dir/.s3cfg 11 | s3cmd put $backup_path s3://$bucket_name -c $dir/.s3cfg 12 | -------------------------------------------------------------------------------- /ops/roles/objective8_postgres_backups/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: copy postgres_backup.sh 3 | copy: src=postgres_backup.sh dest=/usr/local/objective8 4 | owner=root group=root mode=0700 5 | 6 | - name: install S3cmd (command line backup tool for Amazon S3) 7 | apt: name="s3cmd" state=present 8 | 9 | - name: copy S3cmd configuration file 10 | template: src=.s3cfg.j2 dest=/usr/local/objective8/.s3cfg 11 | 12 | - cron: name="create postgres backups" special_time=daily job="/usr/local/objective8/postgres_backup.sh" 13 | -------------------------------------------------------------------------------- /ops/roles/objective8_postgres_backups/templates/.s3cfg.j2: -------------------------------------------------------------------------------- 1 | [default] 2 | access_key = {{ AWS_ACCESS_KEY }} 3 | bucket_location = US 4 | cloudfront_host = cloudfront.amazonaws.com 5 | default_mime_type = binary/octet-stream 6 | delete_removed = False 7 | dry_run = False 8 | enable_multipart = True 9 | encoding = UTF-8 10 | encrypt = True 11 | follow_symlinks = False 12 | force = False 13 | get_continue = False 14 | gpg_command = /usr/bin/gpg 15 | gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s 16 | gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s 17 | gpg_passphrase = {{ AWS_GPG_PASSPHRASE }} 18 | guess_mime_type = True 19 | host_base = s3.amazonaws.com 20 | host_bucket = %(bucket)s.s3.amazonaws.com 21 | human_readable_sizes = False 22 | invalidate_on_cf = False 23 | list_md5 = False 24 | log_target_prefix = 25 | mime_type = 26 | multipart_chunk_size_mb = 15 27 | preserve_attrs = True 28 | progress_meter = True 29 | proxy_host = 30 | proxy_port = 0 31 | recursive = False 32 | recv_chunk = 4096 33 | reduced_redundancy = False 34 | secret_key = {{ AWS_SECRET_KEY }} 35 | send_chunk = 4096 36 | simpledb_host = sdb.amazonaws.com 37 | skip_existing = False 38 | socket_timeout = 300 39 | urlencoding_mode = normal 40 | use_https = False 41 | verbosity = WARNING 42 | website_endpoint = http://%(bucket)s.s3-website-%(location)s.amazonaws.com/ 43 | website_error = 44 | website_index = index.html 45 | -------------------------------------------------------------------------------- /ops/roles/objective8_postgres_config/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create objective8 database 3 | postgresql_db: name=objective8 login_user=postgres 4 | sudo_user: postgres 5 | 6 | - name: add objective8 role 7 | #TODO - password management 8 | postgresql_user: name=objective8 login_user=postgres password={{ DB_PASSWORD }} db=objective8 9 | sudo_user: postgres 10 | 11 | - name: give objective8 user CREATE on database 12 | postgresql_privs: role=objective8 database=objective8 state=present privs=CREATE type=database 13 | sudo_user: postgres 14 | 15 | - name: create .pgpass file 16 | lineinfile: dest=/usr/local/objective8/.pgpass state=present create=yes line="localhost:5432:objective8:objective8:{{ DB_PASSWORD }}" mode=0600 17 | 18 | - name: copy postgres hba config file to /etc/postgresql 19 | copy: src=pg_hba.conf dest=/etc/postgresql/9.3/main/pg_hba.conf 20 | owner=postgres group=postgres mode=0700 21 | 22 | - name: copy postgres config file to /etc/postgresql 23 | copy: src=postgresql.conf dest=/etc/postgresql/9.3/main/postgresql.conf 24 | owner=postgres group=postgres mode=0700 25 | 26 | - name: restart postgres after updating configuration files 27 | service: name=postgresql state=restarted 28 | -------------------------------------------------------------------------------- /ops/roles/postgres/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install postgresql 3 | apt: name=postgresql state=present 4 | 5 | - name: install postgresql-contrib 6 | apt: name=postgresql-contrib state=present 7 | 8 | - name: install libpq-dev (for ansible postgres config) 9 | apt: name="libpq-dev" state=present 10 | 11 | - name: install python-psycopg2 (for ansible postgres config) 12 | apt: name="python-psycopg2" state=present 13 | -------------------------------------------------------------------------------- /ops/staging_vm.inventory: -------------------------------------------------------------------------------- 1 | default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2224 2 | 3 | [staging_vm] 4 | default site_address="https://192.168.50.52" cert_location="testing_objective8.crt" cert_key_location="testing_objective8.key" 5 | -------------------------------------------------------------------------------- /ops/staging_vm_playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: staging_vm 3 | 4 | vars_files: 5 | - "{{ CONFIG_FILE_PATH }}" 6 | 7 | roles: 8 | - common 9 | - postgres 10 | - nginx 11 | - ferm 12 | - docker 13 | - objective8_postgres_config 14 | - objective8_postgres_backups 15 | - objective8_application_config 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "objective8", 3 | "version": "0.0.2", 4 | "description": "", 5 | "engines": { 6 | "node": "0.10.28" 7 | }, 8 | "dependencies": { 9 | "grunt": "^0.4.5", 10 | "grunt-autoprefixer": "^3.0.0", 11 | "grunt-cli": "^0.1.13", 12 | "grunt-contrib-clean": "^0.6.0", 13 | "grunt-contrib-concat": "~0.5.0", 14 | "grunt-contrib-copy": "^0.8.0", 15 | "grunt-contrib-jade": "^0.14.1", 16 | "grunt-contrib-jshint": "~0.11.0", 17 | "grunt-contrib-uglify": "~0.7.0", 18 | "grunt-contrib-watch": "^0.6.1", 19 | "grunt-sass": "0.18.1", 20 | "jade": "^1.9.2", 21 | "jshint-stylish": "~1.0.0", 22 | "load-grunt-tasks": "~0.4.0", 23 | "node-sass": "2.1.1", 24 | "grunt-browser-sync": "2.2.0", 25 | "grunt-express-server": "0.5.1" 26 | }, 27 | "devDependencies": { 28 | "express": "^4.12.1", 29 | "grunt-browser-sync": "^2.0.0", 30 | "grunt-express-server": "^0.4.19", 31 | "serve-favicon": "^2.1.7" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/d-cent/objective8" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/d-cent/objective8/issues" 39 | }, 40 | "scripts": { 41 | "postinstall": "./npm-postinstall.sh" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /prod/main.clj: -------------------------------------------------------------------------------- 1 | (ns main 2 | (:require [objective8.core :as core] 3 | [objective8.back-end.storage.database :as db] 4 | [ragtime.main :as rm] 5 | [ragtime.sql.database]) 6 | (:gen-class)) 7 | 8 | (def db-string (str "jdbc:postgresql://" (:host db/db-config) 9 | ":" (:port db/db-config) 10 | "/" (:db db/db-config) 11 | "?user=" (:user db/db-config) 12 | "&&password=" (:password db/db-config))) 13 | 14 | (defn -main [] 15 | (rm/migrate {:database db-string 16 | :migrations "ragtime.sql.files/migrations"}) 17 | (core/start-server)) 18 | -------------------------------------------------------------------------------- /resources/log4j.debug: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=DEBUG, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.Target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 9 | -------------------------------------------------------------------------------- /resources/log4j.dev: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | log4j.logger.objective8.config=WARN, stdout 4 | log4j.logger.objective8.back-end.storage=INFO, stdout 5 | 6 | # Direct log messages to stdout 7 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 8 | log4j.appender.stdout.Target=System.out 9 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 11 | -------------------------------------------------------------------------------- /resources/log4j.heroku: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=WARN, stdout 3 | 4 | # Direct log messages to stdout 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.Target=System.out 7 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 9 | -------------------------------------------------------------------------------- /resources/log4j.prod: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=WARN, file 3 | 4 | # Direct log messages to file 5 | log4j.appender.file=org.apache.log4j.RollingFileAppender 6 | 7 | log4j.appender.file.File=${LOG_PATH}/objective8-app.log 8 | log4j.appender.file.MaxFileSize=10MB 9 | log4j.appender.file.MaxBackupIndex=10 10 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 11 | log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 12 | -------------------------------------------------------------------------------- /resources/log4j.snap: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | log4j.logger.objective8.config=OFF, stdout 4 | log4j.logger.objective8.front-end.front-end-requests=OFF, stdout 5 | log4j.logger.clj-webdriver.core=OFF, stdout 6 | log4j.logger.objective8.back-end.storage=WARN, stdout 7 | 8 | # Direct log messages to stdout 9 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 10 | log4j.appender.stdout.Target=System.out 11 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 13 | -------------------------------------------------------------------------------- /resources/log4j.test: -------------------------------------------------------------------------------- 1 | # Root logger option 2 | log4j.rootLogger=INFO, stdout 3 | log4j.logger.objective8.config=OFF, stdout 4 | log4j.logger.objective8.front-end.front-end-requests=OFF, stdout 5 | log4j.logger.clj-webdriver.core=OFF, stdout 6 | log4j.logger.objective8.back-end.storage=WARN, stdout 7 | 8 | # Direct log messages to stdout 9 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 10 | log4j.appender.stdout.Target=System.out 11 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%L - %m%n 13 | -------------------------------------------------------------------------------- /resources/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/apple-touch-icon.png -------------------------------------------------------------------------------- /resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/favicon.ico -------------------------------------------------------------------------------- /resources/public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /resources/public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /resources/public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /resources/public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /resources/public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /resources/public/images/lightbulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/images/lightbulb.png -------------------------------------------------------------------------------- /resources/public/images/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/images/people.png -------------------------------------------------------------------------------- /resources/public/images/policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/images/policy.png -------------------------------------------------------------------------------- /resources/public/images/speechbubbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/images/speechbubbles.png -------------------------------------------------------------------------------- /resources/public/stonecutter-sign-in-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/stonecutter-sign-in-icon.png -------------------------------------------------------------------------------- /resources/public/stonecutter.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/resources/public/stonecutter.ico -------------------------------------------------------------------------------- /resources/translations/languages.txt: -------------------------------------------------------------------------------- 1 | en.csv 2 | es.csv 3 | -------------------------------------------------------------------------------- /snap.env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export BASE_URI="localhost:8080" 4 | export APP_PORT="8080" 5 | export API_BEARER_NAME=functionalTests 6 | export API_BEARER_TOKEN=functionalTestsToken 7 | export DB_USER=$SNAP_DB_PG_USER 8 | export DB_PASSWORD=$SNAP_DB_PG_PASSWORD 9 | export DB_HOST=$SNAP_DB_PG_HOST 10 | export DB_PORT=$SNAP_DB_PG_PORT 11 | export DB_NAME=$SNAP_DB_PG_NAME 12 | export DB_JDBC_URL=$SNAP_DB_PG_JDBC_URL 13 | export JAVA_OPTS="-Dlog4j.configuration=log4j.snap" 14 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/admin_removals.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.admin-removals 2 | (:require [objective8.back-end.storage.uris :as uris] 3 | [objective8.back-end.storage.storage :as storage] 4 | [objective8.utils :as utils])) 5 | 6 | (defn uri-for-admin-removal [admin-removal] 7 | (str "/admin-removals/" (:_id admin-removal))) 8 | 9 | (defn store-admin-removal! [{:keys [removed-by-uri] :as admin-removal-data}] 10 | (when-let [{removed-by-id :_id entity :entity} (uris/uri->query removed-by-uri)] 11 | (when (= :user entity) 12 | (some-> admin-removal-data 13 | (utils/select-all-or-nothing [:removal-uri]) 14 | (assoc :entity :admin-removal 15 | :removed-by-id removed-by-id) 16 | (storage/pg-store!) 17 | (assoc :removed-by-uri removed-by-uri) 18 | (utils/update-in-self [:uri] uri-for-admin-removal) 19 | (dissoc :_id :removed-by-id))))) 20 | 21 | (defn- ids->uris [{:keys [removed-by-id] :as admin-removal}] 22 | (-> admin-removal 23 | (utils/update-in-self [:uri] uri-for-admin-removal) 24 | (assoc :removed-by-uri (uris/user-id->uri removed-by-id)) 25 | (dissoc :_id :removed-by-id))) 26 | 27 | (defn get-admin-removals [] 28 | (->> (storage/pg-retrieve {:entity :admin-removal} 29 | {:limit 50 30 | :sort {:field :_created_at 31 | :ordering :DESC}}) 32 | :result 33 | (map ids->uris))) 34 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/answers.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.answers 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.back-end.storage.uris :as uris] 4 | [objective8.utils :as utils])) 5 | 6 | (defn uri-for-answer [answer] 7 | (str "/objectives/" (:objective-id answer) 8 | "/questions/" (:question-id answer) 9 | "/answers/" (:_id answer))) 10 | 11 | (defn store-answer! [answer-data] 12 | (some-> answer-data 13 | (assoc :entity :answer) 14 | storage/pg-store! 15 | (dissoc :global-id) 16 | (utils/update-in-self [:uri] uri-for-answer))) 17 | 18 | (defn get-answers [question-uri query-params] 19 | (let [query (-> (uris/uri->query question-uri) 20 | (utils/ressoc :_id :question-id) 21 | (assoc :sorted-by (:sorted-by query-params)) 22 | (assoc :filter-type (:filter-type query-params)) 23 | (assoc :offset (get query-params :offset 0)))] 24 | (->> (storage/pg-retrieve-answers query) 25 | (map #(dissoc % :global-id)) 26 | (map #(utils/update-in-self % [:uri] uri-for-answer))))) 27 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/bearer_tokens.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.bearer-tokens 2 | (:require [objective8.back-end.storage.storage :as storage])) 3 | 4 | (defn get-token [name] 5 | (-> (storage/pg-retrieve {:entity :bearer-token :bearer-name name}) 6 | :result 7 | first)) 8 | 9 | (defn update-token! [token-details] 10 | (storage/pg-update-bearer-token! (assoc token-details :entity :bearer-token))) 11 | 12 | (defn store-token! [token-details] 13 | (storage/pg-store! (assoc token-details :entity :bearer-token))) 14 | 15 | (defn token-provider [bearer-name] 16 | (:bearer-token (get-token bearer-name))) 17 | 18 | (defn stub-token-provider [bearer-name] 19 | (when (= bearer-name "bearer") "token")) 20 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/invitations.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.invitations 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.back-end.storage.mappings :as mappings] 4 | [objective8.back-end.domain.objectives :as objectives] 5 | [objective8.utils :as utils])) 6 | 7 | (defn store-invitation! [invitation] 8 | (storage/pg-store! (assoc invitation 9 | :entity :invitation 10 | :status "active" 11 | :uuid (utils/generate-random-uuid)))) 12 | 13 | (defn get-active-invitation 14 | "Returns the invitation with the given uuid if it is active, otherwise returns nil" 15 | [uuid] 16 | (-> (storage/pg-retrieve {:entity :invitation 17 | :uuid uuid 18 | :status (mappings/string->postgres-type "invitation_status" "active")}) 19 | :result 20 | first)) 21 | 22 | (defn get-invitation [uuid] 23 | (-> (storage/pg-retrieve {:entity :invitation 24 | :uuid uuid}) 25 | :result 26 | first)) 27 | 28 | (defn accept-invitation! [{objective-id :objective-id :as invitation}] 29 | (storage/pg-update-invitation-status! invitation "accepted")) 30 | 31 | (defn decline-invitation! [{objective-id :objective-id :as invitation}] 32 | (storage/pg-update-invitation-status! invitation "declined")) 33 | 34 | (defn decline-invitation-by-uuid [uuid] 35 | (some-> (get-active-invitation uuid) 36 | decline-invitation!)) 37 | 38 | (defn retrieve-active-invitations [objective-id] 39 | (-> (storage/pg-retrieve {:entity :invitation 40 | :objective-id objective-id 41 | :status (mappings/string->postgres-type "invitation_status" "active")}) 42 | :result)) 43 | 44 | (defn expire-invitation! [invitation] 45 | (storage/pg-update-invitation-status! invitation "expired")) 46 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/marks.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.marks 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.back-end.storage.uris :as uris] 4 | [objective8.utils :as utils])) 5 | 6 | (defn uri-for-mark [mark] 7 | (str "/meta/marks/" (:_id mark))) 8 | 9 | (defn created-by-uri-for-mark [mark] 10 | (str "/users/" (:created-by-id mark))) 11 | 12 | (defn mark-data->mark [{:keys [question-uri created-by-uri active] :as mark-data}] 13 | {:entity :mark 14 | :question-id (:_id (uris/uri->query question-uri)) 15 | :created-by-id (:_id (uris/uri->query created-by-uri)) 16 | :active active}) 17 | 18 | (defn store-mark! [{:keys [question-uri created-by-uri] :as mark-data}] 19 | (some-> mark-data 20 | mark-data->mark 21 | storage/pg-store! 22 | (assoc :question-uri question-uri :created-by-uri created-by-uri) 23 | (utils/update-in-self [:uri] uri-for-mark) 24 | (dissoc :_id :question-id :created-by-id))) 25 | 26 | (defn get-mark-for-question [question-uri] 27 | (let [question-id (:_id (uris/uri->query question-uri))] 28 | (some-> (storage/pg-retrieve {:entity :mark :question-id question-id} 29 | {:sort {:field :_created_at 30 | :ordering :DESC}}) 31 | :result 32 | first 33 | (assoc :question-uri question-uri) 34 | (utils/update-in-self [:created-by-uri] created-by-uri-for-mark) 35 | (utils/update-in-self [:uri] uri-for-mark) 36 | (dissoc :_id :question-id :created-by-id)))) 37 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/questions.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.questions 2 | (:require [objective8.back-end.storage.uris :as uris] 3 | [objective8.utils :as utils] 4 | [objective8.back-end.storage.storage :as storage])) 5 | 6 | (defn store-question! [question] 7 | (some-> (storage/pg-store! (assoc question :entity :question)) 8 | (utils/update-in-self [:uri] uris/question->uri))) 9 | 10 | (defn get-questions-for-objective [objective-uri] 11 | (let [{entity :entity objective-id :_id :as query} (uris/uri->query objective-uri)] 12 | (when (= :objective entity) 13 | (->> (storage/pg-retrieve-questions-for-objective objective-id) 14 | (map #(utils/update-in-self % [:uri] uris/question->uri)))))) 15 | 16 | (defn get-questions-for-objective-by-most-answered [objective-uri] 17 | (let [{entity :entity objective-id :_id :as query} (uris/uri->query objective-uri)] 18 | (when (= :objective entity) 19 | (->> (storage/pg-retrieve-questions-for-objective-by-most-answered {:entity entity :objective_id objective-id}) 20 | (map #(utils/update-in-self % [:uri] uris/question->uri)))))) 21 | 22 | (defn get-question [question-uri] 23 | (let [query (uris/uri->query question-uri)] 24 | (some-> (storage/pg-retrieve-question-by-query-map query) 25 | (utils/update-in-self [:uri] uris/question->uri)))) 26 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/stars.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.stars 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.back-end.storage.uris :as uris] 4 | [objective8.utils :as utils])) 5 | 6 | (defn store-star! [data] 7 | (some-> data 8 | (utils/select-all-or-nothing [:objective-id :created-by-id]) 9 | (assoc :entity :star 10 | :active true) 11 | storage/pg-store!)) 12 | 13 | (defn toggle-star! [star] 14 | (storage/pg-toggle-star! star)) 15 | 16 | (defn get-star [objective-uri created-by-uri] 17 | (let [objective-id (:_id (uris/uri->query objective-uri)) 18 | created-by-id (:_id (uris/uri->query created-by-uri))] 19 | (-> (storage/pg-retrieve {:entity :star :objective-id objective-id :created-by-id created-by-id}) 20 | :result 21 | first))) 22 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/up_down_votes.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.up-down-votes 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.utils :as utils])) 4 | 5 | (defn store-vote! [{:keys [global-id] :as entity-to-vote-on} 6 | {:keys [vote-on-uri] :as vote-data}] 7 | (some-> vote-data 8 | (utils/select-all-or-nothing [:vote-type :created-by-id]) 9 | (assoc :entity :up-down-vote 10 | :global-id global-id) 11 | storage/pg-store! 12 | (dissoc :global-id) 13 | (assoc :vote-on-uri vote-on-uri))) 14 | 15 | (defn get-vote [global-id created-by-id] 16 | (first (:result (storage/pg-retrieve {:entity :up-down-vote 17 | :global-id global-id 18 | :created-by-id created-by-id})))) 19 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/users.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.users 2 | (:require [objective8.back-end.storage.storage :as storage])) 3 | 4 | (defn retrieve-user [user-uri] 5 | (let [result (storage/pg-retrieve-entity-by-uri user-uri)] 6 | (when (get result :_id) result))) 7 | 8 | (defn find-user-by-auth-provider-user-id [auth-provider-user-id] 9 | (-> (storage/pg-retrieve {:entity :user :auth-provider-user-id auth-provider-user-id}) 10 | :result 11 | first)) 12 | 13 | (defn find-user-by-username [username] 14 | (-> (storage/pg-retrieve {:entity :user :username username}) 15 | :result 16 | first)) 17 | 18 | (defn store-user! [user] 19 | (-> (assoc user :entity :user) 20 | storage/pg-store!)) 21 | 22 | (defn update-user! [user] 23 | (storage/pg-update-user! user)) 24 | 25 | (defn store-admin! [admin-data] 26 | (storage/pg-store! (assoc admin-data :entity :admin))) 27 | 28 | (defn get-admin-by-auth-provider-user-id [auth-provider-user-id] 29 | (-> (storage/pg-retrieve {:entity :admin :auth-provider-user-id auth-provider-user-id}) 30 | :result 31 | first)) 32 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/writer_notes.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.writer-notes 2 | (:require [objective8.back-end.storage.storage :as storage] 3 | [objective8.utils :as utils])) 4 | 5 | (defn replace-note-on-id [note note-on-uri] 6 | (-> note 7 | (assoc :note-on-uri note-on-uri) 8 | (dissoc :note-on-id))) 9 | 10 | (defn uri-for-note [note] 11 | (str "/notes/" (:_id note))) 12 | 13 | (defn store-note-for! [entity-to-note-on {:keys [note-on-uri] :as note-data}] 14 | (when-let [{:keys [objective-id _id global-id]} entity-to-note-on] 15 | (some-> note-data 16 | (utils/select-all-or-nothing [:note :created-by-id]) 17 | (assoc :entity :writer-note 18 | :note-on-id global-id 19 | :objective-id (or objective-id _id)) 20 | (dissoc :note-on-uri) 21 | storage/pg-store! 22 | (dissoc :global-id) 23 | (utils/update-in-self [:uri] uri-for-note) 24 | (replace-note-on-id note-on-uri)))) 25 | 26 | (defn retrieve-note [entity-uri] 27 | (when-let [{note-on-id :global-id} (storage/pg-retrieve-entity-by-uri entity-uri :with-global-id)] 28 | (some-> (storage/pg-retrieve {:entity :writer-note :note-on-id note-on-id}) 29 | :result 30 | first 31 | (dissoc :global-id) 32 | (utils/update-in-self [:uri] uri-for-note) 33 | (replace-note-on-id entity-uri)))) 34 | -------------------------------------------------------------------------------- /src/objective8/back_end/domain/writers.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.domain.writers 2 | (:require [clojure.tools.logging :as log] 3 | [objective8.utils :as utils] 4 | [objective8.back-end.domain.invitations :as i] 5 | [objective8.back-end.storage.storage :as storage])) 6 | 7 | 8 | (defn create-writer [{:keys [invitation-uuid invitee-id] :as writer-data}] 9 | (try 10 | (some-> (i/get-active-invitation invitation-uuid) 11 | i/accept-invitation! 12 | (utils/select-all-or-nothing [:writer-name :reason :objective-id :invited-by-id :_id]) 13 | (utils/ressoc :_id :invitation-id) 14 | (utils/ressoc :reason :invitation-reason) 15 | (assoc :entity :writer :user-id invitee-id) 16 | storage/pg-store!) 17 | 18 | (catch org.postgresql.util.PSQLException e 19 | (throw (Exception. "Failed to create writer"))))) 20 | 21 | (defn retrieve-writers [objective-id] 22 | (:result (storage/pg-retrieve {:entity :writer 23 | :objective-id objective-id} 24 | {:limit 50}))) 25 | 26 | (defn retrieve-writers-by-user-id [user-id] 27 | (:result (storage/pg-retrieve {:entity :writer 28 | :user-id user-id} 29 | {:limit 50}))) 30 | 31 | (defn retrieve-writer-for-objective [user-id objective-id] 32 | (first (:result (storage/pg-retrieve {:entity :writer 33 | :user-id user-id 34 | :objective-id objective-id})))) 35 | -------------------------------------------------------------------------------- /src/objective8/back_end/storage/database.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.back-end.storage.database 2 | (:require [korma.db :as db] 3 | [clojure.tools.logging :as log] 4 | [objective8.config :as config])) 5 | 6 | (def db-config (:db-config config/environment)) 7 | 8 | (def postgres-spec (db/postgres db-config)) 9 | 10 | (def spec db/postgres) 11 | 12 | (defn connect! 13 | "Connect to the database described by the DB spec" 14 | ([] 15 | (connect! postgres-spec)) 16 | 17 | ([spec] 18 | (log/info (str "Attempting to connect to the database" (dissoc spec :password))) 19 | (db/defdb objective8-db spec))) 20 | -------------------------------------------------------------------------------- /src/objective8/front_end/api/domain.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.api.domain 2 | (:require [objective8.config :as config])) 3 | 4 | (defn starred? [objective] 5 | (get-in objective [:meta :starred])) 6 | 7 | (defn marked? [question] 8 | (get-in question [:meta :marked])) 9 | -------------------------------------------------------------------------------- /src/objective8/front_end/config.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.config) 2 | 3 | (def comments-pagination 50) 4 | (def answers-pagination 50) 5 | -------------------------------------------------------------------------------- /src/objective8/front_end/sanitiser.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.sanitiser 2 | (:require [clojure.string :as string]) 3 | (:import [org.owasp.html 4 | HtmlSanitizer AttributePolicy ElementPolicy 5 | HtmlPolicyBuilder HtmlSanitizer$Policy PolicyFactory Sanitizers])) 6 | 7 | (defn sanitise-html [html] 8 | (let [policy 9 | ;(-> (HtmlPolicyBuilder.) (.allowCommonBlockElements) (.allowCommonInlineFormattingElements) (.allowStyling) (.toFactory)) 10 | (-> Sanitizers/BLOCKS (.and Sanitizers/FORMATTING) (.and Sanitizers/LINKS) (.and Sanitizers/STYLES))] 11 | (.sanitize policy html))) 12 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/admin_removal_confirmation.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.admin-removal-confirmation 2 | (:require [net.cgrand.enlive-html :as html] 3 | [net.cgrand.jsoup :as jsoup] 4 | [objective8.front-end.templates.page-furniture :as pf] 5 | [objective8.front-end.templates.template-functions :as tf])) 6 | 7 | (def admin-removal-confirmation-template (html/html-resource "templates/jade/admin-removal-confirmation.html" {:parser jsoup/parser})) 8 | 9 | (defn admin-removal-confirmation-page [{:keys [anti-forgery-snippet doc data] :as context}] 10 | (let [{:keys [removal-uri removal-sample] :as removal-data} (:removal-data data)] 11 | (html/at admin-removal-confirmation-template 12 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr :content (:description doc)) 13 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)) 14 | [:.clj-objective-title] (html/content removal-sample) 15 | [:.clj-removal-confirmation-form] (html/prepend anti-forgery-snippet) 16 | [:.clj-removal-uri] (html/set-attr :value removal-uri)))) 17 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/cookies.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.cookies 2 | (:require [net.cgrand.enlive-html :as html] 3 | [objective8.front-end.templates.page-furniture :as pf])) 4 | 5 | (def cookie-template (html/html-resource "templates/jade/cookie-page.html")) 6 | 7 | (defn cookie-page [{:keys [doc] :as context}] 8 | (html/at cookie-template 9 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 10 | [:.clj-masthead-signed-out] (html/substitute (pf/masthead context)) 11 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)))) -------------------------------------------------------------------------------- /src/objective8/front_end/templates/create_objective.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.create-objective 2 | (:require [net.cgrand.enlive-html :as html] 3 | [net.cgrand.jsoup :as jsoup] 4 | [objective8.front-end.templates.page-furniture :as pf] 5 | [objective8.front-end.templates.template-functions :as tf])) 6 | 7 | (def create-objective-template (html/html-resource "templates/jade/create-objective.html" {:parser jsoup/parser})) 8 | 9 | (defn apply-validations [{:keys [doc] :as context} nodes] 10 | (let [validation-data (get-in doc [:flash :validation]) 11 | validation-report (:report validation-data) 12 | previous-inputs (:data validation-data)] 13 | (html/at nodes 14 | [:.clj-title-length-error] (when (contains? (:title validation-report) :length) identity) 15 | [:.clj-input-objective-title] (html/set-attr :value (:title previous-inputs)) 16 | 17 | [:.clj-description-empty-error] (when (contains? (:description validation-report) :empty) identity) 18 | [:.clj-description-length-error] (when (contains? (:description validation-report) :length) identity) 19 | [:.clj-input-objective-background] (html/content (:description previous-inputs))))) 20 | 21 | (defn create-objective-page [{:keys [anti-forgery-snippet doc] :as context}] 22 | (->> 23 | (html/at create-objective-template 24 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 25 | [:.clj-masthead-signed-out] (html/substitute (pf/masthead context)) 26 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)) 27 | 28 | [:.clj-guidance-buttons] nil 29 | [:.clj-create-objective-form] (html/prepend anti-forgery-snippet)) 30 | (apply-validations context))) 31 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/draft_diff.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.draft-diff 2 | (:require [net.cgrand.enlive-html :as html] 3 | [net.cgrand.jsoup :as jsoup] 4 | [objective8.utils :as utils] 5 | [objective8.front-end.templates.page-furniture :as pf] 6 | [objective8.front-end.templates.template-functions :as tf])) 7 | 8 | (def draft-diff-template (html/html-resource "templates/jade/draft-diff.html" {:parser jsoup/parser})) 9 | 10 | (defn draft-diff-page [{:keys [data doc] :as context}] 11 | (let [{:keys [current-draft previous-draft-diffs current-draft-diffs]} data] 12 | (html/at draft-diff-template 13 | [:.clj-masthead-signed-out] nil 14 | [:.clj-status-bar] nil 15 | [:.clj-close-link] (html/set-attr :href (utils/path-for :fe/draft :id (:objective-id current-draft) :d-id (:_id current-draft))) 16 | [:.clj-add-previous-draft] (html/html-content previous-draft-diffs) 17 | [:.clj-add-current-draft] (html/html-content current-draft-diffs)))) 18 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/draft_section.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.draft-section 2 | (:require [net.cgrand.enlive-html :as html] 3 | [net.cgrand.jsoup :as jsoup] 4 | [objective8.utils :as utils] 5 | [objective8.front-end.templates.page-furniture :as pf] 6 | [objective8.front-end.templates.template-functions :as tf])) 7 | 8 | (def draft-section-template (html/html-resource "templates/jade/draft-section.html" {:parser jsoup/parser})) 9 | 10 | (defn draft-section-page [{:keys [data doc] :as context}] 11 | (let [section (:section data) 12 | section-uri (:uri section) 13 | draft-uri (first (clojure.string/split section-uri #"/sections"))] 14 | (html/at draft-section-template 15 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 16 | [:.clj-masthead-signed-out] (html/substitute (pf/masthead context)) 17 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)) 18 | [:.clj-back-to-draft-link] (html/set-attr :href draft-uri) 19 | [:.clj-draft-section] (html/html-content (:section section)) 20 | [:.clj-comment-list] (html/content (pf/comment-list context)) 21 | [:.clj-comment-create] (html/content (pf/comment-create context :section))))) 22 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/error_404.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.error-404 2 | (:require [net.cgrand.enlive-html :as html] 3 | [objective8.front-end.templates.page-furniture :as f] 4 | [objective8.front-end.templates.template-functions :as tf])) 5 | 6 | (def error-404-template (html/html-resource "templates/jade/error-404.html")) 7 | 8 | (defn error-404-page [{:keys [doc] :as context}] 9 | (html/at error-404-template 10 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 11 | [:.clj-masthead-signed-out] (html/substitute (f/masthead context)) 12 | [:.clj-status-bar] (html/substitute (f/status-flash-bar context)))) 13 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/index.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.index 2 | (:require [net.cgrand.enlive-html :as html] 3 | [objective8.front-end.templates.page-furniture :as pf] 4 | [objective8.front-end.templates.template-functions :as tf])) 5 | 6 | (def index-template (html/html-resource "templates/jade/index.html")) 7 | 8 | (defn index-page [{:keys [doc] :as context}] 9 | (html/at index-template 10 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 11 | [:.clj-masthead-signed-out] (html/substitute (pf/masthead context)) 12 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)))) 13 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/learn_more.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.learn-more 2 | (:require [net.cgrand.enlive-html :as html] 3 | [objective8.front-end.templates.page-furniture :as pf] 4 | [objective8.front-end.templates.template-functions :as tf])) 5 | 6 | (def learn-more (html/html-resource "templates/jade/learn-more.html")) 7 | 8 | (defn learn-more-page [{:keys [doc] :as context}] 9 | (html/at learn-more 10 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 11 | [:.clj-masthead-signed-out] (html/substitute (pf/masthead context)) 12 | [:.clj-status-bar] (html/substitute (pf/status-flash-bar context)))) 13 | -------------------------------------------------------------------------------- /src/objective8/front_end/templates/project_status.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.templates.project-status 2 | (:require [net.cgrand.enlive-html :as html] 3 | [objective8.front-end.templates.template-functions :as tf] 4 | [objective8.front-end.templates.page-furniture :as f])) 5 | 6 | (def project-status-template (html/html-resource "templates/jade/project-status.html")) 7 | 8 | (defn project-status-page [{:keys [doc] :as context}] 9 | (html/at project-status-template 10 | [(and (html/has :meta) (html/attr= :name "description"))] (html/set-attr "content" (:description doc)) 11 | [:.clj-masthead-signed-out] (html/substitute (f/masthead context)) 12 | [:.clj-status-bar] (html/substitute (f/status-flash-bar context)))) 13 | -------------------------------------------------------------------------------- /src/objective8/front_end/workflows/stub_twitter.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.front-end.workflows.stub-twitter 2 | (:require [bidi.ring :refer [make-handler]] 3 | [clojure.tools.logging :as log] 4 | [ring.util.response :as response] 5 | [objective8.front-end.workflows.sign-up :refer [sign-up-workflow]] 6 | [objective8.utils :as utils])) 7 | 8 | ;; Stub out twitter authentication workflow 9 | 10 | (def twitter-id (atom "twitter-FAKE_ID")) 11 | 12 | (defn update-stubbed-twitter-id [{:keys [params] :as request}] 13 | (if-let [new-id (:twitter-id params)] 14 | (do 15 | (reset! twitter-id (str "twitter-" new-id)) 16 | {:headers {"Content-Type" "application/json"} 17 | :status 200 18 | :body {:twitter-id new-id}}) 19 | {:status 500 20 | :body "Resetting stubbed twitter id failed"})) 21 | 22 | (defn stub-twitter-handler [{:keys [params session] :as request}] 23 | (let [twitter-id (get params :twitter-id @twitter-id)] 24 | (log/info "Stubbing twitter with fake twitter id: " twitter-id) 25 | (-> (response/redirect (str utils/host-url "/sign-up")) 26 | (assoc-in [:session :auth-provider-user-id] twitter-id)))) 27 | 28 | (def stub-twitter-routes 29 | ["/" {"twitter-sign-in" :stub-twitter 30 | "set-stubbed-twitter-id" :update-stub-twitter}]) 31 | 32 | (def stub-twitter-handlers 33 | {:stub-twitter stub-twitter-handler 34 | :update-stub-twitter update-stubbed-twitter-id}) 35 | 36 | (def stub-twitter-workflow 37 | (make-handler stub-twitter-routes stub-twitter-handlers)) 38 | 39 | -------------------------------------------------------------------------------- /start_app_vm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | grunt build 4 | API_BEARER_NAME=abc \ 5 | API_BEARER_TOKEN=def \ 6 | BASE_URI=localhost:8080 \ 7 | TWITTER_CONSUMER_TOKEN="stub-token" \ 8 | TWITTER_CONSUMER_SECRET_TOKEN="stub-secret" \ 9 | GA_TRACKING_ID=234234234 \ 10 | lein repl -------------------------------------------------------------------------------- /test/autotest_functional_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | start-stop-daemon --start -b -x /usr/bin/Xvfb -- :1 -screen 0 1280x1024x16 3 | JAVA_OPTS="-Dlog4j.configuration=log4j.test" \ 4 | API_BEARER_NAME=functionalTests \ 5 | API_BEARER_TOKEN=functionalTestsToken \ 6 | GA_TRACKING_ID="" \ 7 | DISPLAY=:1 lein midje :autotest test/objective8/functional/ src/ 8 | start-stop-daemon --stop -x /usr/bin/Xvfb 9 | -------------------------------------------------------------------------------- /test/autotest_unit_and_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | JAVA_OPTS="-Dlog4j.configuration=log4j.test" \ 3 | lein midje :autotest test/objective8/unit/ test/objective8/integration/ src/ 4 | -------------------------------------------------------------------------------- /test/cloverage_entrypoint.clj: -------------------------------------------------------------------------------- 1 | (ns cloverage-entrypoint 2 | (:require [clojure.test :refer :all])) 3 | 4 | (deftest empty-test) 5 | -------------------------------------------------------------------------------- /test/objective8/functional/screenshots/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/objective8/8c36ee2d4049fe1e40e5e12f93ccd27914f0fd7d/test/objective8/functional/screenshots/.gitkeep -------------------------------------------------------------------------------- /test/objective8/integration/db/bearer_tokens.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.integration.db.bearer_tokens 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.bearer-tokens :as bt] 4 | [objective8.back-end.storage.storage :as s] 5 | [objective8.back-end.storage.database :as db] 6 | [objective8.integration.integration-helpers :as helpers])) 7 | 8 | (def bearer-name "mr-api") 9 | (def bearer-token "12345") 10 | (def bearer-token-map 11 | {:entity :bearer-token 12 | :bearer-name bearer-name 13 | :bearer-token bearer-token}) 14 | 15 | (facts "Bearer tokens" 16 | 17 | (against-background 18 | [(before :contents (do (helpers/db-connection) 19 | (s/pg-store! bearer-token-map))) 20 | (after :facts (helpers/truncate-tables))] 21 | 22 | (fact "Gets a bearer token from a bearer-name" 23 | (bt/token-provider bearer-name) => bearer-token) 24 | 25 | (fact "Returns nil if the token does not exist for a given name" 26 | (bt/token-provider "some other guy") => nil))) 27 | -------------------------------------------------------------------------------- /test/objective8/integration/db/up_down_votes.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.integration.db.up-down-votes 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.up-down-votes :as votes] 4 | [objective8.integration.integration-helpers :as ih] 5 | [objective8.integration.storage-helpers :as sh])) 6 | 7 | (facts "about storing votes" 8 | (against-background 9 | [(before :contents (do (ih/db-connection) 10 | (ih/truncate-tables))) 11 | (after :facts (ih/truncate-tables))] 12 | 13 | (fact "votes can be stored against an answer" 14 | (let [answer-to-vote-on (sh/store-an-answer) 15 | {voting-user-id :_id} (sh/store-a-user) 16 | uri "answer-uri" 17 | vote-data {:vote-on-uri uri :created-by-id voting-user-id :vote-type :up} 18 | stored-vote (votes/store-vote! answer-to-vote-on vote-data)] 19 | stored-vote => (contains {:_id integer? 20 | :vote-type :up 21 | :created-by-id voting-user-id 22 | :entity :up-down-vote 23 | :vote-on-uri "answer-uri"}) 24 | stored-vote =not=> (contains {:global-id anything}))))) 25 | -------------------------------------------------------------------------------- /test/objective8/integration/db/users.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.integration.db.users 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.users :as users] 4 | [objective8.integration.integration-helpers :as ih] 5 | [objective8.integration.storage-helpers :as sh])) 6 | 7 | (def profile-data {:name "name" :biog "biography"}) 8 | 9 | (against-background 10 | [(before :contents (do (ih/db-connection) 11 | (ih/truncate-tables))) 12 | (after :facts (ih/truncate-tables))] 13 | 14 | (facts "about updating users" 15 | (fact "a user can be updated with a writer profile" 16 | (let [user (sh/store-a-user) 17 | user-with-profile (assoc user :profile profile-data)] 18 | (users/update-user! user-with-profile) => user-with-profile))) 19 | 20 | (facts "about admin roles" 21 | (fact "admin roles are stored" 22 | (let [admin-data {:auth-provider-user-id "192734"}] 23 | (users/store-admin! admin-data) => (contains admin-data))))) 24 | -------------------------------------------------------------------------------- /test/objective8/integration/fixtures/error--lookup-path-too-long.csv: -------------------------------------------------------------------------------- 1 | template-1,tag-1,content with a comma, that wasn't quoted. -------------------------------------------------------------------------------- /test/objective8/integration/fixtures/error--lookup-path-too-short.csv: -------------------------------------------------------------------------------- 1 | template-1,missing tag -------------------------------------------------------------------------------- /test/objective8/integration/fixtures/l1.csv: -------------------------------------------------------------------------------- 1 | template-1,tag-1,"a" -------------------------------------------------------------------------------- /test/objective8/integration/fixtures/l2.csv: -------------------------------------------------------------------------------- 1 | template-1,tag-1,"b" -------------------------------------------------------------------------------- /test/objective8/integration/fixtures/language.csv: -------------------------------------------------------------------------------- 1 | template-1,tag-1,"template 1 tag 1 content" 2 | template-1,tag-2,"template 1 tag 2 content" 3 | template-2,tag-1,"template 2 tag 1 content" 4 | template-2,tag-2,"template 2 tag 2 %app-name%" -------------------------------------------------------------------------------- /test/objective8/unit/answers_test.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.unit.answers-test 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.answers :as answers] 4 | [objective8.back-end.domain.objectives :as objectives] 5 | [objective8.back-end.storage.storage :as storage] 6 | [objective8.config :as config])) 7 | 8 | (fact "Postgresql exceptions are not caught" 9 | (answers/store-answer! {:answer "something"}) => (throws org.postgresql.util.PSQLException) 10 | (provided 11 | (storage/pg-store! {:entity :answer :answer "something"}) =throws=> (org.postgresql.util.PSQLException. 12 | (org.postgresql.util.ServerErrorMessage. "" 0)))) 13 | -------------------------------------------------------------------------------- /test/objective8/unit/objectives_test.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.unit.objectives-test 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.objectives :as objectives] 4 | [objective8.back-end.storage.storage :as storage])) 5 | 6 | (def USER_ID 1) 7 | (def OBJECTIVE_ID 234) 8 | 9 | (fact "Postgresql exceptions are not caught" 10 | (objectives/store-objective! {:objective "something"}) => (throws org.postgresql.util.PSQLException) 11 | (provided 12 | (storage/pg-store! {:entity :objective 13 | :removed-by-admin false 14 | :objective "something"}) =throws=> (org.postgresql.util.PSQLException. (org.postgresql.util.ServerErrorMessage. "" 0)))) 15 | -------------------------------------------------------------------------------- /test/objective8/unit/questions_test.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.unit.questions-test 2 | (:require [midje.sweet :refer :all] 3 | [objective8.back-end.domain.questions :as questions] 4 | [objective8.back-end.domain.objectives :as objectives] 5 | [objective8.back-end.storage.storage :as storage] 6 | [objective8.config :as config])) 7 | 8 | (def USER_ID 1) 9 | (def OBJECTIVE_ID 234) 10 | 11 | (fact "Postgresql exceptions are not caught" 12 | (questions/store-question! {:question "something"}) => (throws org.postgresql.util.PSQLException) 13 | (provided 14 | (storage/pg-store! {:entity :question :question "something"}) =throws=> (org.postgresql.util.PSQLException. 15 | (org.postgresql.util.ServerErrorMessage. "" 0)))) 16 | -------------------------------------------------------------------------------- /test/objective8/unit/sanitiser_test.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.unit.sanitiser-test 2 | (:require [midje.sweet :refer :all] 3 | [objective8.front-end.sanitiser :refer :all])) 4 | 5 | (fact "sanitiser gets rid of scripts in html" 6 | (sanitise-html "

Title

") => "

Title

") 7 | 8 | (fact "sanitiser strips html and body tags" 9 | (sanitise-html "

BODY

") => "

BODY

") 10 | -------------------------------------------------------------------------------- /test/objective8/unit/translation_test.clj: -------------------------------------------------------------------------------- 1 | (ns objective8.unit.translation-test 2 | (:require [midje.sweet :refer :all] 3 | [objective8.front-end.translation :as tr] 4 | [clojure.algo.generic.functor :as algs])) 5 | 6 | (def translations-directory "translations") 7 | 8 | (defn map->key-set [map] 9 | (set (keys map))) 10 | 11 | (defn map-of-maps->map-of-sets [lang-map] 12 | (algs/fmap map->key-set lang-map)) 13 | 14 | (defn check-diff [map-primary map-secondary] 15 | (let [diff (clojure.data/diff map-primary map-secondary)] 16 | (concat (first diff) (second diff)))) 17 | 18 | (defn test-languages [en-map other-languages-map] 19 | (algs/fmap (partial check-diff en-map) other-languages-map)) 20 | 21 | (facts "the translations have the same keys in all languages" 22 | (let [languages (tr/load-translations (tr/find-translation-resources translations-directory)) 23 | en-map (:en languages) 24 | other-languages (dissoc languages :en) 25 | valueless-en-map (map-of-maps->map-of-sets en-map) 26 | valueless-other-lang-map (algs/fmap map-of-maps->map-of-sets other-languages) 27 | differences (test-languages valueless-en-map valueless-other-lang-map)] 28 | differences => (has every? empty?))) 29 | 30 | -------------------------------------------------------------------------------- /test/objective8/unit/workflows/test-stonecutter-key.json: -------------------------------------------------------------------------------- 1 | {"kty":"RSA","kid":"test-key","n":"jwbc0t66Ik9T9JEhcmaanmd_zk21_I4WGHFS2B7UEIIXQYtzenr83qBiebGnYkiwIyAEX-_XE0Hqnof3EWKqGNjZUm9U0r3vLiXAi1OKgnr-_C2-Q3MebmfczfYvmenhyVUErd4HL_ILSg4iOOiN2pkRNBN7deYUpCr0swuW8zSIjejNTwDS_xXInBTiFsl26iT6Hc1DKX6QD1rJUr6ZC7mQYXTK_Dk0tmgkmn2-9k5pkKVo9xRfOi4l72jy_Imf1BeoAMRn-fzOdApNglgWS6RxJRszXjumOGY72OscyF22WHmbwQI9lovF3q8J3326sJH7fSVNVc0I_rzqUL8k0w","e":"AQAB","d":"U91WRl9LDSsus00WK5p2N0PA8RsoBrrZweNRDGCnQDbHpCs8vyi2dWPd2jWNTFgKz83KQubDWgtgoyxedtc_neopI-kb96ZfRNPmHswRf4jXUs8PrUUnJt3H3wznxHwbZI5xe_GgjUCD1hyLfIsAApmWOM7jqbILkGePrByzmk7mO2QoTDu48B3HL7RV5p9JPSq8cjK77Fb0gcZ1dkVMA3sX5kN9TIUC0Na_sjo2BsXe-HD9oF3rgEnMaVMedOhCXrRBagHTedMoijzfoAYtsob-_Xt1vHzelNE5LVpVlFHQH0-12wHazVZroQyMlEb_FOXFdmXvmD2EwooGJ8P-QQ","p":"0GEl5uCQk30jGlPOmyr-e8-IypC0g-zON8DANYLiRxrfOh1XcQn-HQnMMVjksembMakmnWh2CWj3stpMBKByYOs6LTtH-mpPgS853KA--XIQ7_75-6wLpt5KwAOb9Uh68hvsNuJC6R8pThcVP8ciq33s3QbTw37llOTlQtQonpk","q":"r7Zg3fR5U7UQ4HL-Q_hcy6RnM0aKTaw3CoTWfEcP3gUp_VyiWcJ2rhy69ss31xUYOfekzpX55WC53noHe0Un9Vfwo13i-7oAeo9Pe4k-g5H_QXxKZIsvatzCk0QBTFXCtPygcGPn0lpwBl9h2JXya4ErMTGt4m1SAlfwRYvQ3ks","dp":"oOYiif9kIz5A2JdVtOKh1aAOE7tgZ212Xf5ra-ZAKn9JVmpJlnMM0Ac8r-3fyLCEsPfXZTu_yMxQVr3QbNIm_0ciiMJ5dZaZBTseBomFlr7I7UeIZxgVdye1uEYRKnho1oFRB5_14mO5VR7lvXVaA-kb-B7JbO6S_0Eu9Uc0hiE","dq":"aDyLDmCfMPczAdN13yCQ_AWVayenmBhXtpfDHYqScSUjGbhAX3srKHLGvu0jPSa4bnroe90gl-BfowlFMu19nOAEUwW5R5e5_PrTLffm6-pKQLDY-PhQstYvX2lyU7R0gyVyj_nUZkdcOYuP4gph-0BvPQm5m586jUVZtggRai0","qi":"RGyusuU5JY3mpESLP_KGcUM0aG93pohX-2o9c-fRGceCVWvwC8nhmTjLJl0m-KzVpNlQmhcl5AViORXZRgvSW8HjC2kAQx9kMeDISXSnOUn3X96eMTZ1vYAECMqMBvIJRqMqdaJFw8hMY_2bQlTNjMLM02993fO4lWZX-ccJc4g"} 2 | -------------------------------------------------------------------------------- /test/run_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | start-stop-daemon --start -b -x /usr/bin/Xvfb -- :1 -screen 0 1280x1024x16 3 | JAVA_OPTS="-Dlog4j.configuration=log4j.test" \ 4 | API_BEARER_NAME=functionalTests \ 5 | API_BEARER_TOKEN=functionalTestsToken \ 6 | GA_TRACKING_ID="" \ 7 | DISPLAY=:1 lein do clean, midje $* 8 | start-stop-daemon --stop -x /usr/bin/Xvfb 9 | -------------------------------------------------------------------------------- /test/run_browser_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ -z "$DISPLAY" ] 3 | then 4 | start-stop-daemon --start -b -x /usr/bin/Xvfb -- :1 -screen 0 1280x1024x16 5 | JAVA_OPTS="-Dlog4j.configuration=log4j.test" \ 6 | API_BEARER_NAME=functionalTests \ 7 | API_BEARER_TOKEN=functionalTestsToken \ 8 | GA_TRACKING_ID="" \ 9 | DISPLAY=:1 lein midje objective8.functional.* $* 10 | start-stop-daemon --stop -x /usr/bin/Xvfb 11 | else 12 | JAVA_OPTS="-Dlog4j.configuration=log4j.test" \ 13 | API_BEARER_NAME=functionalTests \ 14 | API_BEARER_TOKEN=functionalTestsToken \ 15 | GA_TRACKING_ID="" \ 16 | DISPLAY=:1 lein midje objective8.functional.* $* 17 | fi 18 | --------------------------------------------------------------------------------