Merge branch 'master' into production
BIN
app/assets/images/pages/contribute/class_detail_adventurer.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
app/assets/images/pages/contribute/class_detail_ambassador.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
app/assets/images/pages/contribute/class_detail_archmage.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
app/assets/images/pages/contribute/class_detail_artisan.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
app/assets/images/pages/contribute/class_detail_diplomat.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
app/assets/images/pages/contribute/class_detail_scribe.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
app/assets/images/pages/contribute/tile_adventurer.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
app/assets/images/pages/contribute/tile_ambassador.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
app/assets/images/pages/contribute/tile_archmage.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
app/assets/images/pages/contribute/tile_artisan.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
app/assets/images/pages/contribute/tile_diplomat.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
app/assets/images/pages/contribute/tile_scribe.png
Normal file
After Width: | Height: | Size: 75 KiB |
|
@ -477,12 +477,13 @@
|
||||||
contact:
|
contact:
|
||||||
contact_us: "Contact CodeCombat"
|
contact_us: "Contact CodeCombat"
|
||||||
welcome: "Good to hear from you! Use this form to send us email. "
|
welcome: "Good to hear from you! Use this form to send us email. "
|
||||||
contribute_prefix: "If you're interested in contributing, check out our "
|
|
||||||
contribute_page: "contribute page"
|
|
||||||
contribute_suffix: "!"
|
|
||||||
forum_prefix: "For anything public, please try "
|
forum_prefix: "For anything public, please try "
|
||||||
forum_page: "our forum"
|
forum_page: "our forum"
|
||||||
forum_suffix: " instead."
|
forum_suffix: " instead."
|
||||||
|
subscribe_prefix: "If you need help figuring out a level, please"
|
||||||
|
subscribe: "buy a CodeCombat subscription"
|
||||||
|
subscribe_suffix: "and we'll be happy to help you with your code."
|
||||||
|
subscriber_support: "Since you're a CodeCombat subscriber, your email will get our priority support."
|
||||||
where_reply: "Where should we reply?"
|
where_reply: "Where should we reply?"
|
||||||
send: "Send Feedback"
|
send: "Send Feedback"
|
||||||
contact_candidate: "Contact Candidate" # Deprecated
|
contact_candidate: "Contact Candidate" # Deprecated
|
||||||
|
|
25
app/styles/contribute.sass
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#contribute-view
|
||||||
|
.class_tile
|
||||||
|
position: relative
|
||||||
|
width: 330px
|
||||||
|
padding: 5px
|
||||||
|
float: left
|
||||||
|
|
||||||
|
&:hover img
|
||||||
|
outline: 3px solid #161a9e
|
||||||
|
|
||||||
|
.class_text
|
||||||
|
position: absolute
|
||||||
|
bottom: 5px
|
||||||
|
width: 300px
|
||||||
|
padding: 12px
|
||||||
|
z-index: 1
|
||||||
|
background-color: rgba(255,255,255,.5)
|
||||||
|
|
||||||
|
p
|
||||||
|
color: black
|
||||||
|
|
||||||
|
h3
|
||||||
|
color: black
|
||||||
|
padding-top: 0px
|
||||||
|
margin-top: 0px
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
#homepage_screenshot
|
#homepage_screenshot
|
||||||
margin: 20px 0px
|
margin: 20px 0px
|
||||||
|
|
||||||
|
.class_detail
|
||||||
|
float: left
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 360px
|
||||||
|
|
||||||
.signature
|
.signature
|
||||||
text-align: right
|
text-align: right
|
||||||
|
|
||||||
|
@ -13,21 +20,9 @@
|
||||||
width: 150px
|
width: 150px
|
||||||
margin: 10px 10px 20px 20px
|
margin: 10px 10px 20px 20px
|
||||||
|
|
||||||
#contribute-nav
|
|
||||||
float: left
|
|
||||||
max-width: 20%
|
|
||||||
width: 250px
|
|
||||||
box-sizing: border-box
|
|
||||||
margin-left: 20px
|
|
||||||
padding-top: 40px
|
|
||||||
|
|
||||||
li
|
|
||||||
float: none
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
.class-main
|
.class-main
|
||||||
margin-left: 25%
|
margin-left: 33%
|
||||||
padding: 40px
|
padding: 0px 40px 40px 40px
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
|
|
||||||
.header-scrolling-fix
|
.header-scrolling-fix
|
||||||
|
|
|
@ -206,16 +206,8 @@ $level-resize-transition-time: 0.5s
|
||||||
@include opacity(1)
|
@include opacity(1)
|
||||||
|
|
||||||
@media screen and (min-aspect-ratio: 17/10)
|
@media screen and (min-aspect-ratio: 17/10)
|
||||||
display: none
|
&:not(.premium)
|
||||||
|
display: none
|
||||||
.hour-of-code-explanation
|
|
||||||
margin-top: 5px
|
|
||||||
color: white
|
|
||||||
font-size: 12px
|
|
||||||
|
|
||||||
a
|
|
||||||
color: white
|
|
||||||
text-decoration: underline
|
|
||||||
|
|
||||||
#fullscreen-editor-background-screen
|
#fullscreen-editor-background-screen
|
||||||
background-color: black
|
background-color: black
|
||||||
|
|
|
@ -104,13 +104,6 @@
|
||||||
&.btn-#{nth($tuple, 1)}
|
&.btn-#{nth($tuple, 1)}
|
||||||
@include banner-button(nth($tuple, 2), #FFF)
|
@include banner-button(nth($tuple, 2), #FFF)
|
||||||
|
|
||||||
.footer .footer-link-text a
|
|
||||||
@include opacity(0.75)
|
|
||||||
@include transition(opacity .10s linear)
|
|
||||||
|
|
||||||
&:hover, &:active
|
|
||||||
@include opacity(1)
|
|
||||||
|
|
||||||
$GI: 0.5 // gradient intensity; can tweak this 0-1
|
$GI: 0.5 // gradient intensity; can tweak this 0-1
|
||||||
|
|
||||||
.gradient
|
.gradient
|
||||||
|
@ -152,22 +145,3 @@
|
||||||
top: 0
|
top: 0
|
||||||
height: 100%
|
height: 100%
|
||||||
width: 2%
|
width: 2%
|
||||||
|
|
||||||
.footer
|
|
||||||
@media screen and (min-aspect-ratio: 17/10)
|
|
||||||
display: none
|
|
||||||
|
|
||||||
&:not(:hover)
|
|
||||||
@include opacity(0.6)
|
|
||||||
|
|
||||||
.hour-of-code-explanation
|
|
||||||
margin-top: 5px
|
|
||||||
color: white
|
|
||||||
font-size: 12px
|
|
||||||
|
|
||||||
&:not(:hover)
|
|
||||||
@include opacity(0.75)
|
|
||||||
|
|
||||||
a
|
|
||||||
color: white
|
|
||||||
text-decoration: underline
|
|
||||||
|
|
|
@ -4,12 +4,11 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_adventurer.png", alt="")
|
||||||
|
|
||||||
div.class-main#adventurer-main
|
div.class-main#adventurer-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/adventurer.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.adventurer_title") Adventurer
|
span(data-i18n="classes.adventurer_title") Adventurer
|
||||||
|
|
|
@ -4,12 +4,11 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_ambassador.png", alt="")
|
||||||
|
|
||||||
div.class-main#ambassador-main
|
div.class-main#ambassador-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/ambassador.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.ambassador_title") Ambassador
|
span(data-i18n="classes.ambassador_title") Ambassador
|
||||||
|
|
|
@ -4,13 +4,12 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_archmage.png", alt="")
|
||||||
|
|
||||||
div.class-main#archmage-main
|
div.class-main#archmage-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/archmage.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.archmage_title") Archmage
|
span(data-i18n="classes.archmage_title") Archmage
|
||||||
span
|
span
|
||||||
|
|
|
@ -4,12 +4,11 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_artisan.png", alt="")
|
||||||
|
|
||||||
div.class-main#artisan-main
|
div.class-main#artisan-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/artisan.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.artisan_title") Artisan
|
span(data-i18n="classes.artisan_title") Artisan
|
||||||
|
|
|
@ -2,181 +2,77 @@ extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
div.contribute_class
|
h2 Contributing
|
||||||
|
p CodeCombat is 100% open source and hundreds of dedicated players have helped us build the games
|
||||||
|
| into what it is today. Join us and write the next chapter in CodeCombat's quest to teach the
|
||||||
|
| world to code!
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
a(href="/contribute/archmage")
|
||||||
|
div.class_tile
|
||||||
|
img(src="/images/pages/contribute/tile_archmage.png", alt="")
|
||||||
|
|
||||||
div#contribute-main.class-main
|
div.class_text
|
||||||
div#intro
|
h3 Archmage
|
||||||
|
|
||||||
h2(data-i18n="contribute.page_title") Contributing
|
p(data-i18n="contribute.short_archmage")
|
||||||
|
| If you are a developer interested in coding educational games, become an archmage
|
||||||
|
| to help us build CodeCombat!
|
||||||
|
|
||||||
#homepage_screenshot
|
a(href="/contribute/artisan")
|
||||||
img.img-responsive(src="/images/pages/contribute/contribute_header.png", alt="")
|
div.class_tile
|
||||||
|
img.tile-img(src="/images/pages/contribute/tile_artisan.png", alt="")
|
||||||
|
|
||||||
p
|
div.class_text
|
||||||
strong(data-i18n="contribute.introduction_desc_intro")
|
h3 Artisan
|
||||||
| We have high hopes for CodeCombat.
|
|
||||||
|
|
|
||||||
span(data-i18n="contribute.introduction_desc_pref")
|
|
||||||
| We want to be where programmers of all stripes come to learn and play together,
|
|
||||||
| introduce others to the wonderful world of coding,
|
|
||||||
| and reflect the best parts of the community.
|
|
||||||
| We can't and don't want to do that alone;
|
|
||||||
| what makes projects like GitHub, Stack Overflow and Linux great are the people who
|
|
||||||
| use them and build on them.
|
|
||||||
| To that end,
|
|
||||||
a(href="https://github.com/codecombat/codecombat", data-i18n="contribute.introduction_desc_github_url")
|
|
||||||
| CodeCombat is totally open source
|
|
||||||
span(data-i18n="contribute.introduction_desc_suf")
|
|
||||||
| , and we aim to provide as many ways as possible for you to take part and
|
|
||||||
| make this project as much yours as ours.
|
|
||||||
p(data-i18n="contribute.introduction_desc_ending")
|
|
||||||
| We hope you'll join our party!
|
|
||||||
p(data-i18n="contribute.introduction_desc_signature").signature
|
|
||||||
| - Nick, George, Scott, Michael, and Matt
|
|
||||||
hr
|
|
||||||
|
|
||||||
.contributor-signup-anonymous
|
p(data-i18n="contribute.short_artisan")
|
||||||
|
| Build and share levels for you and your friends to play. Become an Artisan to learn
|
||||||
|
| the art of teaching others to program.
|
||||||
|
|
||||||
#archmage.header-scrolling-fix
|
a(href="/contribute/adventurer")
|
||||||
.class_image
|
div.class_tile
|
||||||
img.img-responsive(src="/images/pages/contribute/archmage.png", alt="")
|
img.tile-img(src="/images/pages/contribute/tile_adventurer.png", alt="")
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
div.class_text
|
||||||
span(data-i18n="classes.archmage_title") Archmage
|
h3 Adventurer
|
||||||
span
|
|
||||||
span(data-i18n="classes.archmage_title_description") (Coder)
|
|
||||||
p(data-i18n="contribute.archmage_summary")
|
|
||||||
| Interested in working on game graphics, user interface design, database and server organization,
|
|
||||||
| multiplayer networking, physics, sound, or game engine performance? Want to help build a game to
|
|
||||||
| help other people learn what you are good at? We have a lot to do and if you are an experienced
|
|
||||||
| programmer and want to develop for CodeCombat, this class is for you. We would love your help
|
|
||||||
| building the best programming game ever.
|
|
||||||
|
|
||||||
a(href="/contribute/archmage")
|
p(data-i18n="contribute.short_adventurer")
|
||||||
p.lead(data-i18n="contribute.more_about_archmage")
|
| Get our new levels (even our subscriber content) for free one week early and help us
|
||||||
| Learn More About Becoming an Archmage
|
| work out bugs before our public release.
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="developer", data-contributor-class-name="archmage")
|
a(href="/contribute/scribe")
|
||||||
|
div.class_tile
|
||||||
|
img.tile-img(src="/images/pages/contribute/tile_scribe.png", alt="")
|
||||||
|
|
||||||
#artisan.header-scrolling-fix
|
div.class_text
|
||||||
|
h3 Scribe
|
||||||
|
|
||||||
.class_image
|
p(data-i18n="contribute.short_scribe")
|
||||||
img.img-responsive(src="/images/pages/contribute/artisan.png", alt="")
|
| Good code needs good documentation. Write,
|
||||||
|
| edit, and improve the docs read by millions of players across the globe.
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
|
||||||
span(data-i18n="classes.artisan_title") Artisan
|
|
||||||
span
|
|
||||||
span(data-i18n="classes.artisan_title_description") (Level Builder)
|
|
||||||
p
|
|
||||||
span(data-i18n="contribute.artisan_summary_pref")
|
|
||||||
| Want to design levels and expand CodeCombat's arsenal? People are playing through our
|
|
||||||
| content at a pace faster than we can build! Right now, our level editor is barebone,
|
|
||||||
| so be wary. Making levels will be a little challenging and buggy. If you have visions
|
|
||||||
| of campaigns spanning for-loops to
|
|
||||||
span
|
|
||||||
a(href="http://stackoverflow.com/questions/758088/seeking-contrived-example-code-continuations/758105#758105")
|
|
||||||
| Mondo Bizzaro
|
|
||||||
span(data-i18n="contribute.artisan_summary_suf")
|
|
||||||
| , then this class is for you.
|
|
||||||
|
|
||||||
a(href="/contribute/artisan")
|
a(href="/contribute/diplomat")
|
||||||
p.lead(data-i18n="contribute.more_about_artisan")
|
|
||||||
| Learn More About Becoming An Artisan
|
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="level_creator", data-contributor-class-name="artisan")
|
div.class_tile
|
||||||
|
img.tile-img(src="/images/pages/contribute/tile_diplomat.png", alt="")
|
||||||
|
|
||||||
#adventurer.header-scrolling-fix
|
div.class_text
|
||||||
|
h3 Diplomat
|
||||||
|
|
||||||
.class_image
|
p(data-i18n="contribute.short_diplomat")
|
||||||
img.img-responsive(src="/images/pages/contribute/adventurer.png", alt="")
|
| CodeCombat is localized in 39 languages by our Diplomats. Help them
|
||||||
|
| out and contribute translations.
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
a(href="/contribute/ambassador")
|
||||||
span(data-i18n="classes.adventurer_title") Adventurer
|
div.class_tile
|
||||||
span
|
img.tile-img(src="/images/pages/contribute/tile_ambassador.png", alt="")
|
||||||
span(data-i18n="classes.adventurer_title_description") (Level Playtester)
|
|
||||||
p(data-i18n="contribute.adventurer_summary")
|
|
||||||
| Let us be clear about your role: you are the tank. You are going to take heavy damage.
|
|
||||||
| We need people to try out brand-new levels and help identify how to make things better.
|
|
||||||
| The pain will be enormous; making good games is a long process and no one gets
|
|
||||||
| it right the first time.
|
|
||||||
| If you can endure and have a high constitution score, then this class is for you.
|
|
||||||
|
|
||||||
a(href="/contribute/adventurer")
|
|
||||||
p.lead(data-i18n="contribute.more_about_adventurer")
|
|
||||||
| Learn More About Becoming an Adventurer
|
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="tester", data-contributor-class-name="adventurer")
|
div.class_text
|
||||||
|
h3 Ambassador
|
||||||
#scribe.header-scrolling-fix
|
|
||||||
|
|
||||||
.class_image
|
p(data-i18n="contribute.short_ambassador")
|
||||||
img.img-responsive(src="/images/pages/contribute/scribe.png", alt="")
|
| Tame our forum users and provide direction for those with questions. Our ambassadors
|
||||||
|
| represent CodeCombat to the world.
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
div.clearfix
|
||||||
span(data-i18n="classes.scribe_title") Scribe
|
|
||||||
span
|
|
||||||
span(data-i18n="classes.scribe_title_description") (Article Editor)
|
|
||||||
p
|
|
||||||
span(data-i18n="contribute.scribe_summary_pref")
|
|
||||||
| CodeCombat is not just going to be a bunch of levels. It will also be a resource of
|
|
||||||
| programming knowledge that players can hook into. That way, each Artisan can link
|
|
||||||
| to a detailed article that for the player's edification:
|
|
||||||
| documentation akin to what the
|
|
||||||
a(href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide", data-i18n="contribute.scribe_introduction_url_mozilla")
|
|
||||||
| Mozilla Developer Network
|
|
||||||
span(data-i18n="contribute.scribe_summary_suf")
|
|
||||||
| has built. If you enjoy explaining programming concepts, then this class is for you.
|
|
||||||
|
|
||||||
a(href="/contribute/scribe")
|
|
||||||
p.lead(data-i18n="contribute.more_about_scribe")
|
|
||||||
| Learn More About Becoming a Scribe
|
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="article_editor", data-contributor-class-name="scribe")
|
|
||||||
|
|
||||||
#diplomat.header-scrolling-fix
|
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/diplomat.png", alt="")
|
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
|
||||||
span(data-i18n="classes.diplomat_title") Diplomat
|
|
||||||
span
|
|
||||||
span(data-i18n="classes.diplomat_title_description") (Translator)
|
|
||||||
p
|
|
||||||
span(data-i18n="contribute.diplomat_summary")
|
|
||||||
| There is a large interest in CodeCombat in other countries that do not speak English!
|
|
||||||
| We are looking for translators who are willing to spend their time translating the
|
|
||||||
| site's corpus of words so that CodeCombat is accessible across the world as soon as
|
|
||||||
| possible. If you'd like to help getting CodeCombat international, then this class is
|
|
||||||
| for you.
|
|
||||||
|
|
||||||
a(href="/contribute/diplomat")
|
|
||||||
p.lead(data-i18n="contribute.more_about_diplomat")
|
|
||||||
| Learn More About Becoming a Diplomat
|
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="translator", data-contributor-class-name="diplomat")
|
|
||||||
|
|
||||||
#ambassador.header-scrolling-fix
|
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/ambassador.png", alt="")
|
|
||||||
|
|
||||||
h3.header-scrolling-fix
|
|
||||||
span(data-i18n="classes.ambassador_title") Ambassador
|
|
||||||
span
|
|
||||||
span(data-i18n="classes.ambassador_title_description") (Support)
|
|
||||||
p(data-i18n="contribute.ambassador_summary")
|
|
||||||
| We are trying to build a community, and every community needs a support team when
|
|
||||||
| there are troubles. We have got chats, emails, and social networks so that our users
|
|
||||||
| can get acquainted with the game. If you want to help people get involved, have fun,
|
|
||||||
| and learn some programming, then this c lass is for you.
|
|
||||||
|
|
||||||
a(href="/contribute/ambassador")
|
|
||||||
p.lead(data-i18n="contribute.more_about_ambassador")
|
|
||||||
| Learn More About Becoming an Ambassador
|
|
||||||
|
|
||||||
.contributor-signup(data-contributor-class-id="support", data-contributor-class-name="ambassador")
|
|
||||||
|
|
||||||
div.clearfix
|
|
|
@ -4,12 +4,11 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_diplomat.png", alt="")
|
||||||
|
|
||||||
div.class-main#diplomat-main
|
div.class-main#diplomat-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/diplomat.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.diplomat_title") Diplomat
|
span(data-i18n="classes.diplomat_title") Diplomat
|
||||||
|
|
|
@ -4,13 +4,12 @@ block content
|
||||||
|
|
||||||
div.contribute_class
|
div.contribute_class
|
||||||
|
|
||||||
include /templates/contribute/contribute_nav
|
.class_detail
|
||||||
|
|
||||||
|
img(src="/images/pages/contribute/class_detail_scribe.png", alt="")
|
||||||
|
|
||||||
div.class-main#scribe-main
|
div.class-main#scribe-main
|
||||||
|
|
||||||
.class_image
|
|
||||||
img.img-responsive(src="/images/pages/contribute/scribe.png", alt="")
|
|
||||||
|
|
||||||
h2
|
h2
|
||||||
span(data-i18n="classes.scribe_title") Scribe
|
span(data-i18n="classes.scribe_title") Scribe
|
||||||
span
|
span
|
||||||
|
|
|
@ -6,13 +6,16 @@ block modal-header-content
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
p
|
p
|
||||||
span(data-i18n="contact.welcome") Good to hear from you! Use this form to send us email.
|
span(data-i18n="contact.welcome") Good to hear from you! Use this form to send us email.
|
||||||
span(data-i18n="contact.contribute_prefix") If you're interested in contributing, check out our
|
span.spl(data-i18n="contact.forum_prefix") For anything public, please try
|
||||||
a(href="/contribute", data-dismiss="modal", data-i18n="contact.contribute_page") contribute page
|
|
||||||
span(data-i18n="contact.contribute_suffix") !
|
|
||||||
p
|
|
||||||
span(data-i18n="contact.forum_prefix") For anything public, please try
|
|
||||||
a(href="http://discourse.codecombat.com/", data-i18n="contact.forum_page") our forum
|
a(href="http://discourse.codecombat.com/", data-i18n="contact.forum_page") our forum
|
||||||
span(data-i18n="contact.forum_suffix") instead.
|
span(data-i18n="contact.forum_suffix") instead.
|
||||||
|
if me.isPremium()
|
||||||
|
p(data-i18n="contact.subscriber_support") Since you're a CodeCombat subscriber, your email will get our priority support.
|
||||||
|
else
|
||||||
|
p
|
||||||
|
span(data-i18n="contact.subscribe_prefix") If you need help figuring out a level, please
|
||||||
|
a.spl.spr(data-toggle="coco-modal", data-target="core/SubscribeModal", data-i18n="contact.subscribe") buy a CodeCombat subscription
|
||||||
|
span(data-i18n="contact.subscribe_suffix") and we'll be happy to help you with your code.
|
||||||
.form
|
.form
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="contact-email", data-i18n="general.email") Email
|
label.control-label(for="contact-email", data-i18n="general.email") Email
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
|
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
|
||||||
|
|
||||||
#play-footer
|
if !me.get('anonymous')
|
||||||
p(class='footer-link-text')
|
#play-footer(class=me.isPremium() ? "premium" : "")
|
||||||
a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Contact
|
p(class='footer-link-text')
|
||||||
|
a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Contact
|
||||||
|
|
|
@ -11,7 +11,3 @@
|
||||||
#level-chat-view
|
#level-chat-view
|
||||||
#playback-view
|
#playback-view
|
||||||
#thang-hud
|
#thang-hud
|
||||||
.footer
|
|
||||||
.content
|
|
||||||
p(class='footer-link-text')
|
|
||||||
a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") Contact
|
|
|
@ -83,6 +83,9 @@
|
||||||
else
|
else
|
||||||
span.player-name.spr= me.get('name')
|
span.player-name.spr= me.get('name')
|
||||||
button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out
|
button#logout-button.btn.btn-illustrated.btn-warning(data-i18n="login.log_out") Log Out
|
||||||
|
if me.isPremium()
|
||||||
|
button.btn.btn-illustrated.btn-primary(data-i18n="nav.contact", data-toggle="coco-modal", data-target="core/ContactModal") Contact
|
||||||
|
|
||||||
|
|
||||||
button.btn.btn-lg.btn-inverse#volume-button(title="Adjust volume")
|
button.btn.btn-lg.btn-inverse#volume-button(title="Adjust volume")
|
||||||
.glyphicon.glyphicon-volume-off
|
.glyphicon.glyphicon-volume-off
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
RootView = require 'views/core/RootView'
|
RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/account/subscription-view'
|
template = require 'templates/account/subscription-view'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
SubscribeModal = require 'views/play/modal/SubscribeModal'
|
SubscribeModal = require 'views/core/SubscribeModal'
|
||||||
|
|
||||||
module.exports = class SubscriptionView extends RootView
|
module.exports = class SubscriptionView extends RootView
|
||||||
id: "subscription-view"
|
id: "subscription-view"
|
||||||
|
@ -33,7 +33,7 @@ module.exports = class SubscriptionView extends RootView
|
||||||
c.cost = "$#{(subscription.plan.amount/100).toFixed(2)}"
|
c.cost = "$#{(subscription.plan.amount/100).toFixed(2)}"
|
||||||
if card = @stripeInfo.cards?.data?[0]
|
if card = @stripeInfo.cards?.data?[0]
|
||||||
c.card = "#{card.brand}: x#{card.last4}"
|
c.card = "#{card.brand}: x#{card.last4}"
|
||||||
|
|
||||||
c.stripeInfo = @stripeInfo
|
c.stripeInfo = @stripeInfo
|
||||||
c.subscribed = me.get('stripe')?.planID
|
c.subscribed = me.get('stripe')?.planID
|
||||||
c.active = me.isPremium()
|
c.active = me.isPremium()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
ModalView = require 'views/core/ModalView'
|
ModalView = require 'views/core/ModalView'
|
||||||
template = require 'templates/play/modal/subscribe-modal'
|
template = require 'templates/core/subscribe-modal'
|
||||||
stripeHandler = require 'core/services/stripe'
|
stripeHandler = require 'core/services/stripe'
|
||||||
utils = require 'core/utils'
|
utils = require 'core/utils'
|
||||||
AuthModal = require 'views/core/AuthModal'
|
AuthModal = require 'views/core/AuthModal'
|
|
@ -9,7 +9,7 @@ ThangType = require 'models/ThangType'
|
||||||
MusicPlayer = require 'lib/surface/MusicPlayer'
|
MusicPlayer = require 'lib/surface/MusicPlayer'
|
||||||
storage = require 'core/storage'
|
storage = require 'core/storage'
|
||||||
AuthModal = require 'views/core/AuthModal'
|
AuthModal = require 'views/core/AuthModal'
|
||||||
SubscribeModal = require 'views/play/modal/SubscribeModal'
|
SubscribeModal = require 'views/core/SubscribeModal'
|
||||||
Level = require 'models/Level'
|
Level = require 'models/Level'
|
||||||
|
|
||||||
trackedHourOfCode = false
|
trackedHourOfCode = false
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
CocoView = require 'views/core/CocoView'
|
CocoView = require 'views/core/CocoView'
|
||||||
template = require 'templates/play/level/level_loading'
|
template = require 'templates/play/level/level_loading'
|
||||||
utils = require 'core/utils'
|
utils = require 'core/utils'
|
||||||
SubscribeModal = require 'views/play/modal/SubscribeModal'
|
SubscribeModal = require 'views/core/SubscribeModal'
|
||||||
|
|
||||||
module.exports = class LevelLoadingView extends CocoView
|
module.exports = class LevelLoadingView extends CocoView
|
||||||
id: 'level-loading-view'
|
id: 'level-loading-view'
|
||||||
|
|
|
@ -7,30 +7,33 @@
|
||||||
// For a given style:
|
// For a given style:
|
||||||
// - Video completion rates (Not too interesting unless each level has all styles available)
|
// - Video completion rates (Not too interesting unless each level has all styles available)
|
||||||
// - Video completion rates, per-level too
|
// - Video completion rates, per-level too
|
||||||
// TODO: The rest of these.
|
|
||||||
// - Watched another video
|
// - Watched another video
|
||||||
// - Level completion rates
|
// - Level completion rates
|
||||||
// - Subscription coversion rates
|
// - Subscription coversion totals
|
||||||
|
// TODO: The rest
|
||||||
// - How many people who start a level click the help button, and which one?
|
// - How many people who start a level click the help button, and which one?
|
||||||
// - Need a hard start date when the help button presented
|
// - Need a hard start date when the help button presented
|
||||||
|
|
||||||
|
|
||||||
// Intial production deploy completed at 12:42am 12/18/14 PST
|
// 12:42am 12/18/14 PST - Intial production deploy completed
|
||||||
var testStartDate='2014-12-14T08:42:00.000Z';
|
var testStartDate = '2014-12-18T08:42:00.000Z';
|
||||||
|
// 12:29pm 12/18/14 PST - 2nd deploy w/ originals for dungeons-of-kithgard and second-kithmaze
|
||||||
|
// TODO: move this date up to avoid prod deploy transitional data messing with us.
|
||||||
|
// testStartDate = '2014-12-18T20:29:00.000Z';
|
||||||
|
testStartDate = '2014-12-18T22:29:00.000Z';
|
||||||
|
|
||||||
function printVideoCompletionRates() {
|
function printVideoCompletionRates() {
|
||||||
print("Querying for help video events...");
|
print("Querying for help video events...");
|
||||||
var videosCursor = db['analytics.log.events'].find({
|
var videosCursor = db['analytics.log.events'].find({
|
||||||
$and: [
|
$and: [
|
||||||
{"created": { $gte: ISODate(testStartDate)}},
|
{"created": { $gte: ISODate(testStartDate)}},
|
||||||
{$or : [
|
{$or : [
|
||||||
{"event": "Start help video"},
|
{"event": "Start help video"},
|
||||||
{"event": "Finish help video"}
|
{"event": "Finish help video"}
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
print("Building video progression data...");
|
print("Building video progression data...");
|
||||||
// Build: <style><level><userID><event> counts
|
// Build: <style><level><userID><event> counts
|
||||||
var videoProgression = {};
|
var videoProgression = {};
|
||||||
|
@ -131,14 +134,242 @@ function printVideoCompletionRates() {
|
||||||
styleLevelCompletionRates.push(data);
|
styleLevelCompletionRates.push(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styleLevelCompletionRates.sort(function(a,b) {return b['rate'] && a['rate'] ? b.rate - a.rate : 0;});
|
styleLevelCompletionRates.sort(function(a,b) {
|
||||||
|
if (a.level !== b.level) {
|
||||||
|
if (a.level < b.level) return -1;
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
return b['rate'] && a['rate'] ? b.rate - a.rate : 0;
|
||||||
|
});
|
||||||
|
|
||||||
print("Per-level style completion rates:");
|
print("Per-level style completion rates:");
|
||||||
for (var i = 0; i < styleLevelCompletionRates.length; i++) {
|
for (var i = 0; i < styleLevelCompletionRates.length; i++) {
|
||||||
var item = styleLevelCompletionRates[i];
|
var item = styleLevelCompletionRates[i];
|
||||||
var msg = item.level + "\t" + item.style + "\t" + item.started + "\t" + item.finished;
|
var msg = item.level + "\t" + item.style + (item.style === 'edited' ? "\t\t" : "\t") + item.started + "\t" + item.finished;
|
||||||
if (item['rate']) msg += "\t" + item.rate + "%";
|
if (item['rate']) msg += "\t" + item.rate + "%";
|
||||||
print(msg);
|
print(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printVideoCompletionRates();
|
|
||||||
|
function printWatchedAnotherVideoRates() {
|
||||||
|
// How useful is a style/level in yielding more video starts
|
||||||
|
// Algorithm:
|
||||||
|
// 1. Fetch all start/finish video events after test start date
|
||||||
|
// 2. Create a per-userID dictionary of user event history arrays
|
||||||
|
// 3. Sort each user event history array in ascending order. Now we have a video watching history, per-user.
|
||||||
|
// 4. Walk through each user's history
|
||||||
|
// a. Increment global count for level/style/event, for each level/style event in past history.
|
||||||
|
// b. Save current entry in the past history.
|
||||||
|
// 5. Sort by ascending level name, descending started count
|
||||||
|
|
||||||
|
// TODO: only attribute one start/finish per level to a user?
|
||||||
|
|
||||||
|
print("Querying for help video events...");
|
||||||
|
var videosCursor = db['analytics.log.events'].find({
|
||||||
|
$and: [
|
||||||
|
{"created": { $gte: ISODate(testStartDate)}},
|
||||||
|
{$or : [
|
||||||
|
{"event": "Start help video"},
|
||||||
|
{"event": "Finish help video"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Building per-user video progression data...");
|
||||||
|
// Find video progression per-user
|
||||||
|
// Build: <userID>[sorted style/event/level/date events]
|
||||||
|
var videoProgression = {};
|
||||||
|
while (videosCursor.hasNext()) {
|
||||||
|
var doc = videosCursor.next();
|
||||||
|
var event = doc.event;
|
||||||
|
var userID = doc.user.valueOf();
|
||||||
|
var created = doc.created
|
||||||
|
var levelID = doc.properties.level;
|
||||||
|
var style = doc.properties.style;
|
||||||
|
|
||||||
|
if (!videoProgression[userID]) videoProgression[userID] = [];
|
||||||
|
videoProgression[userID].push({
|
||||||
|
style: style,
|
||||||
|
level: levelID,
|
||||||
|
event: event,
|
||||||
|
created: created.toISOString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// printjson(videoProgression);
|
||||||
|
|
||||||
|
print("Sorting per-user video progression data...");
|
||||||
|
for (userID in videoProgression) videoProgression[userID].sort(function (a,b) {return a.created < b.created ? -1 : 1});
|
||||||
|
|
||||||
|
print("Building per-level/style additional watched videos..");
|
||||||
|
var additionalWatchedVideos = {};
|
||||||
|
for (userID in videoProgression) {
|
||||||
|
|
||||||
|
// Walk user's history, and tally what preceded each historical entry
|
||||||
|
var userHistory = videoProgression[userID];
|
||||||
|
// printjson(userHistory);
|
||||||
|
var previouslyWatched = {};
|
||||||
|
for (var i = 0; i < userHistory.length; i++) {
|
||||||
|
|
||||||
|
// Walk previously watched events, and attribute to correct additionally watched entry
|
||||||
|
var item = userHistory[i];
|
||||||
|
var level = item.level;
|
||||||
|
var style = item.style;
|
||||||
|
var event = item.event;
|
||||||
|
var created = item.created;
|
||||||
|
for (previousLevel in previouslyWatched) {
|
||||||
|
for (previousStyle in previouslyWatched[previousLevel]) {
|
||||||
|
if (previousLevel === level) continue;
|
||||||
|
var previous = previouslyWatched[previousLevel];
|
||||||
|
// For previous level and style, 'event' followed it
|
||||||
|
if (!additionalWatchedVideos[previousLevel]) additionalWatchedVideos[previousLevel] = {};
|
||||||
|
if (!additionalWatchedVideos[previousLevel][previousStyle]) {
|
||||||
|
additionalWatchedVideos[previousLevel][previousStyle] = {};
|
||||||
|
}
|
||||||
|
// TODO: care which video watched next?
|
||||||
|
if (!additionalWatchedVideos[previousLevel][previousStyle][event]) {
|
||||||
|
additionalWatchedVideos[previousLevel][previousStyle][event] = 0;
|
||||||
|
}
|
||||||
|
additionalWatchedVideos[previousLevel][previousStyle][event]++;
|
||||||
|
|
||||||
|
if (previousLevel === 'the-second-kithmaze') {
|
||||||
|
print("Followed the-second-kithmaze " + userID + " " + level + " " + event + " " + created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add level/style to previouslyWatched for this user
|
||||||
|
if (!previouslyWatched[level]) previouslyWatched[level] = {};
|
||||||
|
if (!previouslyWatched[level][style]) previouslyWatched[level][style] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Sorting additional watched videos by started event counts...");
|
||||||
|
var additionalWatchedVideoByStarted = [];
|
||||||
|
for (levelID in additionalWatchedVideos) {
|
||||||
|
for (style in additionalWatchedVideos[levelID]) {
|
||||||
|
var started = 0;
|
||||||
|
var finished = 0;
|
||||||
|
for (event in additionalWatchedVideos[levelID][style]) {
|
||||||
|
if (event === "Start help video") started += additionalWatchedVideos[levelID][style][event];
|
||||||
|
else if (event === "Finish help video") finished += additionalWatchedVideos[levelID][style][event];
|
||||||
|
else throw new Error("Unknown event " + event);
|
||||||
|
}
|
||||||
|
var data = {
|
||||||
|
level: levelID,
|
||||||
|
style: style,
|
||||||
|
started: started,
|
||||||
|
finished: finished
|
||||||
|
};
|
||||||
|
if (finished > 0) data['rate'] = finished / started * 100;
|
||||||
|
additionalWatchedVideoByStarted.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
additionalWatchedVideoByStarted.sort(function(a,b) {
|
||||||
|
if (a.level !== b.level) {
|
||||||
|
if (a.level < b.level) return -1;
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
return b.started - a.started;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Per-level additional videos watched:");
|
||||||
|
print("For a given level and style, this is how many more videos were started and finished.");
|
||||||
|
print("Columns: level, style, started, finished, additionals completion rate");
|
||||||
|
for (var i = 0; i < additionalWatchedVideoByStarted.length; i++) {
|
||||||
|
var item = additionalWatchedVideoByStarted[i];
|
||||||
|
var msg = item.level + "\t" + item.style + (item.style === 'edited' ? "\t\t" : "\t") + item.started + "\t" + item.finished;
|
||||||
|
if (item['rate']) msg += "\t" + item.rate + "%";
|
||||||
|
print(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printSubConversionTotals() {
|
||||||
|
// For a user, who started a video, did they subscribe afterwards?
|
||||||
|
|
||||||
|
// Find each started event, per user
|
||||||
|
print("Querying for help video start events...");
|
||||||
|
var eventsCursor = db['analytics.log.events'].find({
|
||||||
|
$and: [
|
||||||
|
{"created": { $gte: ISODate(testStartDate)}},
|
||||||
|
{$or : [
|
||||||
|
{"event": "Start help video"},
|
||||||
|
{"event": "Finished subscription purchase"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Building per-user events progression data...");
|
||||||
|
// Find event progression per-user
|
||||||
|
var eventsProgression = {};
|
||||||
|
while (eventsCursor.hasNext()) {
|
||||||
|
var doc = eventsCursor.next();
|
||||||
|
var event = doc.event;
|
||||||
|
var userID = doc.user.valueOf();
|
||||||
|
var created = doc.created
|
||||||
|
var levelID = doc.properties.level;
|
||||||
|
var style = doc.properties.style;
|
||||||
|
|
||||||
|
if (!eventsProgression[userID]) eventsProgression[userID] = [];
|
||||||
|
eventsProgression[userID].push({
|
||||||
|
style: style,
|
||||||
|
level: levelID,
|
||||||
|
event: event,
|
||||||
|
created: created.toISOString()
|
||||||
|
})
|
||||||
|
// if (event === 'Finished subscription purchase')
|
||||||
|
// printjson(eventsProgression[userID]);
|
||||||
|
}
|
||||||
|
// printjson(eventsProgression);
|
||||||
|
|
||||||
|
print("Sorting per-user events progression data...");
|
||||||
|
for (userID in eventsProgression) eventsProgression[userID].sort(function (a,b) {return a.created < b.created ? -1 : 1});
|
||||||
|
|
||||||
|
|
||||||
|
print("Building per-level/style sub purchases..");
|
||||||
|
// Build: <level><style><count>
|
||||||
|
var subPurchaseCounts = {};
|
||||||
|
for (userID in eventsProgression) {
|
||||||
|
var history = eventsProgression[userID];
|
||||||
|
for (var i = 0; i < history.length; i++) {
|
||||||
|
if (history[i].event === 'Finished subscription purchase') {
|
||||||
|
var item = i > 0 ? history[i - 1] : {level: 'unknown', style: 'unknown'};
|
||||||
|
// if (i === 0) {
|
||||||
|
// print(userID);
|
||||||
|
// printjson(history[i]);
|
||||||
|
// }
|
||||||
|
if (!subPurchaseCounts[item.level]) subPurchaseCounts[item.level] = {};
|
||||||
|
if (!subPurchaseCounts[item.level][item.style]) subPurchaseCounts[item.level][item.style] = 0;
|
||||||
|
subPurchaseCounts[item.level][item.style]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// printjson(subPurchaseCounts);
|
||||||
|
|
||||||
|
print("Sorting per-level/style sub purchase counts...");
|
||||||
|
var subPurchasesByTotal = [];
|
||||||
|
for (levelID in subPurchaseCounts) {
|
||||||
|
for (style in subPurchaseCounts[levelID]) {
|
||||||
|
subPurchasesByTotal.push({
|
||||||
|
level: levelID,
|
||||||
|
style: style,
|
||||||
|
total: subPurchaseCounts[levelID][style]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subPurchasesByTotal.sort(function (a,b) {
|
||||||
|
if (a.level !== b.level) return a.level < b.level ? -1 : 1;
|
||||||
|
return b.total - a.total;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Per-level/style following sub purchases:");
|
||||||
|
print("Columns: level, style, following sub purchases.");
|
||||||
|
print("'unknown' means no preceding start help video event.");
|
||||||
|
for (var i = 0; i < subPurchasesByTotal.length; i++) {
|
||||||
|
var item = subPurchasesByTotal[i];
|
||||||
|
print(item.level + "\t" + item.style + (item.style === 'edited' ? "\t\t" : "\t") + item.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printVideoCompletionRates();
|
||||||
|
printWatchedAnotherVideoRates();
|
||||||
|
printSubConversionTotals();
|
|
@ -14,17 +14,25 @@ module.exports.setup = (app) ->
|
||||||
return res.end()
|
return res.end()
|
||||||
|
|
||||||
createMailContext = (sender, message, user, recipientID, subject, done) ->
|
createMailContext = (sender, message, user, recipientID, subject, done) ->
|
||||||
|
level = if user?.get('points') > 0 then Math.floor(5 * Math.log((1 / 100) * (xp + 100))) + 1 else 0
|
||||||
|
premium = user?.isPremium()
|
||||||
|
content = """
|
||||||
|
#{message}
|
||||||
|
|
||||||
|
#{user.get('name') or 'Anonymous'} - Level #{level}#{if premium then ' - Subscriber' else ''} - #{user._id}
|
||||||
|
"""
|
||||||
|
|
||||||
context =
|
context =
|
||||||
email_id: sendwithus.templates.plain_text_email
|
email_id: sendwithus.templates.plain_text_email
|
||||||
recipient:
|
recipient:
|
||||||
address: config.mail.username
|
address: if premium then config.mail.supportPremium else config.mail.supportPrimary
|
||||||
sender:
|
sender:
|
||||||
address: config.mail.username
|
address: config.mail.username
|
||||||
reply_to: sender
|
reply_to: sender
|
||||||
name: user.get('name')
|
name: user.get('name')
|
||||||
email_data:
|
email_data:
|
||||||
subject: "[CodeCombat] #{subject ? ('Feedback - ' + sender)}"
|
subject: "[CodeCombat] #{subject ? ('Feedback - ' + sender)}"
|
||||||
content: "#{message}\n\nUsername: #{user.get('name') or 'Anonymous'}\nID: #{user._id}"
|
content: content
|
||||||
|
|
||||||
if recipientID and (user.isAdmin() or ('employer' in (user.get('permissions') ? [])))
|
if recipientID and (user.isAdmin() or ('employer' in (user.get('permissions') ? [])))
|
||||||
User.findById(recipientID, 'email').exec (err, document) ->
|
User.findById(recipientID, 'email').exec (err, document) ->
|
||||||
|
|
|
@ -36,6 +36,9 @@ else
|
||||||
config.mongo.password = process.env.COCO_MONGO_PASSWORD or ''
|
config.mongo.password = process.env.COCO_MONGO_PASSWORD or ''
|
||||||
|
|
||||||
config.mail =
|
config.mail =
|
||||||
|
username: process.env.COCO_MAIL_SERVICE_USERNAME or ''
|
||||||
|
supportPrimary: process.env.COCO_MAIL_SUPPORT_PRIMARY or ''
|
||||||
|
supportPremium: process.env.COCO_MAIL_SUPPORT_PREMIUM or ''
|
||||||
username: process.env.COCO_MAIL_SERVICE_USERNAME or ''
|
username: process.env.COCO_MAIL_SERVICE_USERNAME or ''
|
||||||
mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or ''
|
mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or ''
|
||||||
mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or '/mail/webhook'
|
mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or '/mail/webhook'
|
||||||
|
|
|
@ -58,11 +58,12 @@ setupExpressMiddleware = (app) ->
|
||||||
express.logger.format('prod', productionLogging)
|
express.logger.format('prod', productionLogging)
|
||||||
app.use(express.logger('prod'))
|
app.use(express.logger('prod'))
|
||||||
app.use express.compress filter: (req, res) ->
|
app.use express.compress filter: (req, res) ->
|
||||||
|
return false if req.headers.host is 'codecombat.com' # CloudFlare will gzip it for us on codecombat.com
|
||||||
compressible res.getHeader('Content-Type')
|
compressible res.getHeader('Content-Type')
|
||||||
else
|
else
|
||||||
express.logger.format('dev', developmentLogging)
|
express.logger.format('dev', developmentLogging)
|
||||||
app.use(express.logger('dev'))
|
app.use(express.logger('dev'))
|
||||||
app.use(express.static(path.join(__dirname, 'public')))
|
app.use(express.static(path.join(__dirname, 'public'), maxAge: 30 * 60 * 1000))
|
||||||
app.use(useragent.express())
|
app.use(useragent.express())
|
||||||
|
|
||||||
app.use(express.favicon())
|
app.use(express.favicon())
|
||||||
|
|