Compare commits

...
This repository has been archived on 2025-05-04. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

3 commits

Author SHA1 Message Date
Rob
a93aadf0fa Fix alert message 2016-07-15 13:39:28 -07:00
Rob
f7f20edb70 Adjust imports to match new structure. 2016-07-15 12:59:49 -07:00
Rob
d8733f0add Prerender jade templates to make web crawlers happier.
- Render html in pretty mode
- Fix spelling
- Don't show template errors in production.
- Remove Errorception
- Port some template changes forward
2016-07-15 11:21:27 -07:00
7 changed files with 300 additions and 25 deletions

87
app/templates/main.jade Normal file
View file

@ -0,0 +1,87 @@
doctype html
//[if lt IE 7]> <html class="lt-ie10 lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]
//[if IE 7]> <html class="lt-ie10 lt-ie9 lt-ie8" lang="en"> <![endif]
//[if IE 8]> <html class="lt-ie10 lt-ie9" lang="en"> <![endif]
//[if IE 9]> <html class="lt-ie10" lang="en"> <![endif]
//[if !IE] <!
html(lang='en')
// <![endif]
head
meta(charset='utf-8')
meta(name='viewport', content='width=1024')
title CodeCombat - Learn how to code by playing a game
meta(name='description', content='Learn programming with a multiplayer live coding strategy game for beginners. Learn Python or JavaScript as you defeat ogres, solve mazes, and level up. Open source HTML5 game!')
meta(property='og:title', content='CodeCombat: Learn to Code by Playing a Game')
meta(property='og:url', content='http://codecombat.com')
meta(property='og:type', content='game')
meta(property='og:image', content='http://codecombat.com/images/pages/home/play_img.png')
meta(property='og:site_name', content='CodeCombat')
meta(name='twitter:card', content='summary')
meta(name='twitter:title', content='CodeCombat: Learn to Code by Playing a Game')
meta(name='twitter:url', content='http://codecombat.com')
meta(name='twitter:site', content='CodeCombat')
meta(name='twitter:image:src', content='http://codecombat.com/images/pages/base/logo_square_250.png')
meta(name='twitter:description', content='Learn programming with a multiplayer live coding strategy game for beginners. Learn Python or JavaScript as you defeat ogres, solve mazes, and level up. Open source HTML5 game!')
link(href='https://plus.google.com/115285980638641924488', rel='publisher')
link(rel='shortcut icon', href='/images/favicon.ico')
link(rel='stylesheet', href='/stylesheets/app.css')
link(href='//fonts.googleapis.com/css?family=Merriweather', rel='stylesheet', type='text/css')
// Google Analytics
script.
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-39724129-1', 'auto');
// Mixpanel
script(type='text/javascript').
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
mixpanel.init("e71a4e60db7e1dc5e685be96776280f9");
script(src='https://checkout.stripe.com/checkout.js')
// IE9 doesn't support defer attribute: https://github.com/h5bp/lazyweb-requests/issues/42
//[if IE 9]>
script(src='/lib/ace/ace.js')
script(src='/javascripts/box2d.js')
script(src='/javascripts/vendor.js')
script(src='/javascripts/aether.js')
script(src='/javascripts/app.js')
<![endif]
// IE9 cors support breaks analytics logging: http://caniuse.com/#feat=cors
//[if IE 9]>
script(type='text/javascript', src='http://cdnjs.cloudflare.com/ajax/libs/jquery-ajaxtransport-xdomainrequest/1.0.2/jquery.xdomainrequest.min.js')
<![endif]
script(src='/lib/ace/ace.js', defer='')
script(src='/javascripts/vendor.js', defer='')
script(src='/javascripts/aether.js', defer='')
script(src='/javascripts/app.js', defer='')
script.
// IMPORTANT: If you edit here, make sure app/assets/javascripts/run-tests.js puts in placeholders for
// running client tests on Travis.
// Placeholder for iPad, which inspects the user object at the bottom of an injected page.
window.serverConfig = !{JSON.stringify(serverConfig)};
window.userObject = !{userObjectTag};
window.amActually = !{amActually ? JSON.stringify(amActually) : 'undefined'};
window.me = {
get: function(attribute) { return window.userObject[attribute]; }
}
onLoad = function() {
try {
// IE10 warning
var htmlElement = document.querySelector("html");
if (htmlElement) {
if ($.browser.msie && $.browser.versionNumber < 11) {
alert("CodeCombat does not run in Internet Explorer 11 or older. Sorry!");
}
}
// IE8 can't handle this
FastClick.attach(document.body);
require('core/initialize');
} catch (error) { }
}
body.clearfix(onload='onLoad();')
#fb-root
#page-container !{pageContent}
#modal-wrapper.modal-content.hide
#module-load-progress.progress
.progress-bar

View file

@ -26,7 +26,7 @@ if view.showAds()
img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat") img(src="/images/pages/base/logo.png", title="Powered by CodeCombat - Learn how to code by playing a game ", alt="Powered by CodeCombat")
if campaign if campaign
.map .map(style="width: 800px; height: 600px")
.gradient.horizontal-gradient.top-gradient .gradient.horizontal-gradient.top-gradient
.gradient.vertical-gradient.right-gradient .gradient.vertical-gradient.right-gradient
.gradient.horizontal-gradient.bottom-gradient .gradient.horizontal-gradient.bottom-gradient
@ -118,7 +118,7 @@ if view.showAds()
if campaign && campaign.locked && !godmode if campaign && campaign.locked && !godmode
h3.campaign-locked(data-i18n="play.locked") Locked h3.campaign-locked(data-i18n="play.locked") Locked
else if campaign else if campaign
btn(data-i18n="common.play").btn.btn-illustrated.btn-lg.btn-success.play-button a(data-i18n="common.play", href="/play/#{campaignSlug}").btn.btn-illustrated.btn-lg.btn-success.play-button
if campaign && campaign.get('description') if campaign && campaign.get('description')
p.campaign-description p.campaign-description
span= i18n(campaign.attributes, 'description') span= i18n(campaign.attributes, 'description')

View file

@ -663,6 +663,8 @@ module.exports = class CampaignView extends RootView
console.error "CampaignView hero update couldn't find hero slug for original:", hero console.error "CampaignView hero update couldn't find hero slug for original:", hero
onClickPortalCampaign: (e) -> onClickPortalCampaign: (e) ->
e.preventDefault()
e.stopPropagation()
campaign = $(e.target).closest('.campaign') campaign = $(e.target).closest('.campaign')
return if campaign.is('.locked') or campaign.is('.silhouette') return if campaign.is('.locked') or campaign.is('.silhouette')
campaignSlug = campaign.data('campaign-slug') campaignSlug = campaign.data('campaign-slug')

View file

@ -60,6 +60,7 @@
"bluebird": "^3.2.1", "bluebird": "^3.2.1",
"chalk": "^1.1.3", "chalk": "^1.1.3",
"co": "^4.6.0", "co": "^4.6.0",
"cheerio": "^0.20.0",
"co-express": "^1.2.1", "co-express": "^1.2.1",
"coffee-script": "1.9.x", "coffee-script": "1.9.x",
"connect": "2.7.x", "connect": "2.7.x",
@ -69,6 +70,7 @@
"geoip-lite": "^1.1.6", "geoip-lite": "^1.1.6",
"graceful-fs": "~2.0.1", "graceful-fs": "~2.0.1",
"gridfs-stream": "~1.1.1", "gridfs-stream": "~1.1.1",
"jade": "^1.11.0",
"jsondiffpatch": "0.1.17", "jsondiffpatch": "0.1.17",
"lodash": "~2.4.1", "lodash": "~2.4.1",
"lz-string": "^1.3.3", "lz-string": "^1.3.3",

View file

@ -56,7 +56,8 @@ module.exports.routes =
'routes/sprites' 'routes/sprites'
'routes/queue' 'routes/queue'
'routes/stacklead' 'routes/stacklead'
'routes/stripe' 'routes/stripe',
'routes/html'
] ]
mongoose = require 'mongoose' mongoose = require 'mongoose'

199
server/routes/html.coffee Normal file
View file

@ -0,0 +1,199 @@
User = require '../models/User'
UserHandler = require '../handlers/user_handler'
LevelSession = require '../models/LevelSession'
Campaign = require '../models/Campaign'
Level = require '../models/Level'
config = require '../../server_config'
log = require 'winston'
Mandate = require '../models/Mandate'
utils = require '../lib/utils'
cheerio = require 'cheerio'
en = require '../../app/locale/en'
_ = require 'lodash'
translate = (key) ->
html = /^\[html\]/.test(key)
key = key.substring(6) if html
t = en.translation
#TODO: Replace with _.property when we get modern lodash
path = key.split(/[.]/)
while path.length > 0
k = path.shift()
t = t[k]
return key unless t?
return out =
text: t
html: html
makeContext = (req) ->
view =
isIPadBrowser: -> false
isMobile: -> false
isOldBrowser: -> false
forumLink: -> 'http://discourse.codecombat.com/'
showAds: -> true
me =
getPhotoURL: -> ''
isAnonymous: -> true
get: (what) -> ''
level: -> ''
displayName: -> ''
isTeacher: -> false
isOnPremiumServer: -> false
isAdmin: -> false
gems: -> 0
isPremium: -> false
opts =
view: view
me: me
i18n: (a, b) ->
return a.i18n.en[a] if 'i18n' in a
a[b]
injectView = (res, view, opts, next) ->
res.render (view + '.jade'), opts, (err, data) ->
if err
if config.isProduction
res.locals.pageContent = ''
else
res.locals.pageContent = ('<pre>' + err + '</pre>')
return next()
c = cheerio.load(data)
name = view.split(/\//).pop()
name += '-view' unless /-view$/.test(name)
roots = c.root().children()
elms = c('[data-i18n]')
elms.each (i, e) ->
i = c(this)
t = translate(i.data('i18n'))
if t.html
i.html(t.text)
else
i.text(t.text);
o = cheerio.load("<div>")
container = o('div')
container.attr('id', name)
container.html(c.html())
unless o('.style-flat').length > 0
container.addClass('site-chrome')
container.addClass('show-background') unless opts.showBackground is false
container.attr('style', 'display: none') if opts.dontActuallyShow
res.locals.pageContent = o.html()
next()
exports.setup = (app) ->
handle = (route, view, next) ->
app.get route, (req, res) ->
try
next req, res, (opts) ->
injectView res, view, opts, ->
renderMain req, res
catch e
renderMain req, res
handleSimply = (route, view, extra) ->
handle route, view, (req, res, next) ->
opts = makeContext req
_.extend opts.view, extra(req, res) if extra
next(opts)
handleSimply '/', 'new-home-view', ->
justPlaysCourses: -> false
courses: {models: []}
handleSimply '/about', 'about'
handleSimply '/community', 'community-view'
handleSimply '/contribute', 'contribute/contribute'
handleSimply '/teachers/quote', 'request-quote-view', ->
trialRequest:
isNew: -> true
handleSimply '/courses/teachers', 'courses/teacher-courses-view', ->
classrooms:
models: []
courses:
models: []
handleSimply '/legal', 'legal'
handleSimply '/privacy', 'privacy'
handle '/play', 'play/campaign-view', (req, res, next) ->
opts = makeConext req
Campaign.find({type: 'hero'}).exec (err, docs) ->
levels = {}
docs.forEach (m,k) ->
v = m.toObject()
levels[v.slug] =
attributes:
fullName: v.name
name: v.name
slug: v.slug
description: v.description
i18n: v.i18n
get: (name) -> v[name]
res.locals.campaigns = levels
res.locals.adjacentCampaigns = []
res.locals.levels = []
opts.showBackground = false
next opts
handle '/play/:campaign', 'play/campaign-view', (req, res, next) ->
opts = makeContext req
Campaign.findOne({type: 'hero', slug: req.params.campaign}).exec (err, doc) ->
levels = {}
v = doc.toObject()
res.locals.campaign =
attributes:
fullName: v.name
name: v.name
slug: v.slug
description: v.description
i18n: v.i18n
get: (name) -> v[name]
res.locals.adjacentCampaigns = []
res.locals.levelStatusMap = {}
res.locals.levelDifficultyMap = {}
res.locals.levelPlayCountMap = {}
res.locals.marked = (o) -> o
res.locals.levels = v.levels
opts.dontActuallyShow = true
opts.showBackground = false
next opts
handle '/play/level/:level', 'play/level', (req, res, next) ->
opts = makeContext req
Level.findOne({slug: req.params.level}).exec (err, doc) ->
opts.showBackground = false
opts.dontActuallyShow = true
next opts
exports.renderMain = renderMain = (req,res) ->
user = if req.user then JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/') else '{}'
res.locals.pageContent = res.locals.pageContent || '!'
Mandate.findOne({}).cache(5 * 60 * 1000).exec (err, mandate) ->
if err
log.error "Error getting mandate config: #{err}"
configData = {}
else
configData = _.omit mandate?.toObject() or {}, '_id'
configData.picoCTF = config.picoCTF
configData.production = config.isProduction
res.locals.serverConfig = configData
res.locals.userObjectTag = user
res.locals.amActually = req.session.amActually
res.header 'Cache-Control', 'no-cache, no-store, must-revalidate'
res.header 'Pragma', 'no-cache'
res.header 'Expires', 0
res.render 'main.jade'

View file

@ -16,6 +16,7 @@ config = require './server_config'
auth = require './server/commons/auth' auth = require './server/commons/auth'
routes = require './server/routes' routes = require './server/routes'
UserHandler = require './server/handlers/user_handler' UserHandler = require './server/handlers/user_handler'
html = require './server/routes/html'
slack = require './server/slack' slack = require './server/slack'
Mandate = require './server/models/Mandate' Mandate = require './server/models/Mandate'
global.tv4 = require 'tv4' # required for TreemaUtils to work global.tv4 = require 'tv4' # required for TreemaUtils to work
@ -193,26 +194,7 @@ setupJavascript404s = (app) ->
setupFallbackRouteToIndex = (app) -> setupFallbackRouteToIndex = (app) ->
app.all '*', (req, res) -> app.all '*', (req, res) ->
fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) -> html.renderMain(req, res)
log.error "Error modifying main.html: #{err}" if err
# insert the user object directly into the html so the application can have it immediately. Sanitize </script>
user = if req.user then JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/') else '{}'
Mandate.findOne({}).cache(5 * 60 * 1000).exec (err, mandate) ->
if err
log.error "Error getting mandate config: #{err}"
configData = {}
else
configData = _.omit mandate?.toObject() or {}, '_id'
configData.picoCTF = config.picoCTF
configData.production = config.isProduction
data = data.replace '"serverConfigTag"', JSON.stringify configData
data = data.replace('"userObjectTag"', user)
data = data.replace('"amActuallyTag"', JSON.stringify(req.session.amActually))
res.header 'Cache-Control', 'no-cache, no-store, must-revalidate'
res.header 'Pragma', 'no-cache'
res.header 'Expires', 0
res.send 200, data
setupFacebookCrossDomainCommunicationRoute = (app) -> setupFacebookCrossDomainCommunicationRoute = (app) ->
app.get '/channel.html', (req, res) -> app.get '/channel.html', (req, res) ->
@ -242,9 +224,11 @@ exports.setupMailchimp = ->
exports.setExpressConfigurationOptions = (app) -> exports.setExpressConfigurationOptions = (app) ->
app.set('port', config.port) app.set('port', config.port)
app.set('views', __dirname + '/app/views') app.set('views', __dirname + '/app/templates')
app.set('view engine', 'jade') app.set('view engine', 'jade')
app.set('view options', { layout: false }) app.set('view options', { layout: false, pretty: true })
app.locals.basedir = __dirname + '/app'
app.locals.pretty = true
app.set('env', if config.isProduction then 'production' else 'development') app.set('env', if config.isProduction then 'production' else 'development')
app.set('json spaces', 0) if config.isProduction app.set('json spaces', 0) if config.isProduction