2014-01-03 13:32:13 -05:00
module.exports.clone = (obj) ->
2014-06-30 22:16:26 -04:00
return obj if obj is null or typeof ( obj ) isnt ' object '
2014-01-03 13:32:13 -05:00
temp = obj . constructor ( )
for key of obj
temp [ key ] = module . exports . clone ( obj [ key ] )
temp
module.exports.combineAncestralObject = (obj, propertyName) ->
combined = { }
while obj ? [ propertyName ]
for key , value of obj [ propertyName ]
continue if combined [ key ]
combined [ key ] = value
if obj . __proto__
obj = obj . __proto__
else
# IE has no __proto__. TODO: does this even work? At most it doesn't crash.
obj = Object . getPrototypeOf ( obj )
combined
module.exports.normalizeFunc = (func_thing, object) ->
# func could be a string to a function in this class
# or a function in its own right
object ? = { }
if _ . isString ( func_thing )
func = object [ func_thing ]
if not func
2014-08-14 13:28:50 -04:00
console . error " Could not find method #{ func_thing } in object " , object
2014-01-03 13:32:13 -05:00
return => null # always return a func, or Mediator will go boom
func_thing = func
2014-03-13 11:39:53 -04:00
return func_thing
2016-02-06 17:02:44 -05:00
module.exports.objectIdToDate = (objectID) ->
new Date ( parseInt ( objectID . toString ( ) . slice ( 0 , 8 ) , 16 ) * 1000 )
2014-01-10 19:48:28 -05:00
module.exports.hexToHSL = (hex) ->
rgbToHsl ( hexToR ( hex ) , hexToG ( hex ) , hexToB ( hex ) )
hexToR = (h) -> parseInt ( cutHex ( h ) ) . substring ( 0 , 2 ) , 16
hexToG = (h) -> parseInt ( cutHex ( h ) ) . substring ( 2 , 4 ) , 16
hexToB = (h) -> parseInt ( cutHex ( h ) ) . substring ( 4 , 6 ) , 16
2014-06-30 22:16:26 -04:00
cutHex = (h) -> ( if ( h . charAt ( 0 ) is ' # ' ) then h . substring ( 1 , 7 ) else h )
2014-03-13 11:39:53 -04:00
2014-01-10 19:48:28 -05:00
module.exports.hslToHex = (hsl) ->
' # ' + ( toHex ( n ) for n in hslToRgb ( hsl . . . ) ) . join ( ' ' )
2014-03-13 11:39:53 -04:00
2014-01-10 19:48:28 -05:00
toHex = (n) ->
h = Math . floor ( n ) . toString ( 16 )
h = ' 0 ' + h if h . length is 1
2014-03-12 15:30:43 -04:00
h
2014-08-23 18:51:59 -04:00
module.exports.i18n = ( say , target , language = me . get ( ' preferredLanguage ' , true ) , fallback = ' en ' ) ->
2014-03-12 15:30:43 -04:00
generalResult = null
2016-03-07 11:13:49 -05:00
fallBackResult = null
fallForwardResult = null # If a general language isn't available, the first specific one will do.
fallSidewaysResult = null # If a specific language isn't available, its sibling specific language will do.
2014-03-12 15:30:43 -04:00
matches = ( /\w+/gi ) . exec ( language )
generalName = matches [ 0 ] if matches
2014-03-12 19:47:08 -04:00
for localeName , locale of say . i18n
2014-07-13 13:05:58 -04:00
continue if localeName is ' - '
2014-03-12 19:47:08 -04:00
if target of locale
result = locale [ target ]
2014-03-12 15:30:43 -04:00
else continue
2014-07-04 10:23:47 -04:00
return result if localeName is language
generalResult = result if localeName is generalName
2016-03-07 11:13:49 -05:00
fallBackResult = result if localeName is fallback
fallForwardResult = result if localeName . indexOf ( language ) is 0 and not fallForwardResult ?
fallSidewaysResult = result if localeName . indexOf ( generalName ) is 0 and not fallSidewaysResult ?
2014-03-12 15:30:43 -04:00
return generalResult if generalResult ?
2016-03-07 11:13:49 -05:00
return fallForwardResult if fallForwardResult ?
return fallSidewaysResult if fallSidewaysResult ?
return fallBackResult if fallBackResult ?
2014-03-12 20:07:36 -04:00
return say [ target ] if target of say
2014-03-13 11:39:53 -04:00
null
2014-05-18 15:14:22 -04:00
module.exports.getByPath = (target, path) ->
2014-06-14 14:12:17 -04:00
throw new Error ' Expected an object to match a query against, instead got null ' unless target
2014-05-18 15:14:22 -04:00
pieces = path . split ( ' . ' )
obj = target
for piece in pieces
return undefined unless piece of obj
obj = obj [ piece ]
obj
2014-06-03 06:40:47 -04:00
2014-07-09 14:23:05 -04:00
module.exports.isID = (id) -> _ . isString ( id ) and id . length is 24 and id . match ( /[a-f0-9]/gi ) ? . length is 24
2014-06-03 06:40:47 -04:00
module.exports.round = _ . curry (digits, n) ->
n = + n . toFixed ( digits )
2014-06-14 14:12:17 -04:00
positify = (func) -> (params) -> (x) -> if x > 0 then func ( params ) ( x ) else 0
2014-06-12 13:39:45 -04:00
# f(x) = ax + b
createLinearFunc = (params) ->
2014-06-03 06:40:47 -04:00
(x) -> ( params . a or 1 ) * x + ( params . b or 0 )
2014-06-12 13:39:45 -04:00
# f(x) = ax² + bx + c
createQuadraticFunc = (params) ->
(x) -> ( params . a or 1 ) * x * x + ( params . b or 1 ) * x + ( params . c or 0 )
# f(x) = a log(b (x + c)) + d
createLogFunc = (params) ->
(x) -> if x > 0 then ( params . a or 1 ) * Math . log ( ( params . b or 1 ) * ( x + ( params . c or 0 ) ) ) + ( params . d or 0 ) else 0
2015-02-18 12:06:32 -05:00
# f(x) = ax^b + c
createPowFunc = (params) ->
(x) -> ( params . a or 1 ) * Math . pow ( x , params . b or 1 ) + ( params . c or 0 )
2014-06-12 13:39:45 -04:00
module.exports.functionCreators =
linear: positify ( createLinearFunc )
quadratic: positify ( createQuadraticFunc )
logarithmic: positify ( createLogFunc )
2015-02-18 12:06:32 -05:00
pow: positify ( createPowFunc )
2014-06-17 15:42:27 -04:00
# Call done with true to satisfy the 'until' goal and stop repeating func
module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) ->
waitSoFar = 0
( done = (success) ->
2014-07-07 13:56:30 -04:00
if ( waitSoFar += wait ) <= totalWait and not success
2014-06-17 15:42:27 -04:00
_ . delay ( -> func done ) , wait ) false
2014-08-04 09:26:21 -04:00
module.exports.grayscale = (imageData) ->
d = imageData . data
for i in [ 0 . . d . length ] by 4
r = d [ i ]
g = d [ i + 1 ]
b = d [ i + 2 ]
v = 0.2126 * r + 0.7152 * g + 0.0722 * b
d [ i ] = d [ i + 1 ] = d [ i + 2 ] = v
imageData
2014-08-08 07:08:13 -04:00
# Deep compares l with r, with the exception that undefined values are considered equal to missing values
# Very practical for comparing Mongoose documents where undefined is not allowed, instead fields get deleted
module.exports.kindaEqual = compare = (l, r) ->
if _ . isObject ( l ) and _ . isObject ( r )
for key in _ . union Object . keys ( l ) , Object . keys ( r )
return false unless compare l [ key ] , r [ key ]
return true
else if l is r
return true
else
return false
2014-11-29 16:09:38 -05:00
2015-01-14 14:09:01 -05:00
# Return UTC string "YYYYMMDD" for today + offset
2015-01-07 17:49:21 -05:00
module.exports.getUTCDay = (offset=0) ->
day = new Date ( )
day . setDate ( day . getUTCDate ( ) + offset )
partYear = day . getUTCFullYear ( )
partMonth = ( day . getUTCMonth ( ) + 1 )
partMonth = " 0 " + partMonth if partMonth < 10
partDay = day . getUTCDate ( )
partDay = " 0 " + partDay if partDay < 10
2015-01-14 14:09:01 -05:00
" #{ partYear } #{ partMonth } #{ partDay } "
2015-01-07 17:49:21 -05:00
2014-11-29 16:09:38 -05:00
# Fast, basic way to replace text in an element when you don't need much.
# http://stackoverflow.com/a/4962398/540620
2015-02-12 19:06:27 -05:00
if document ? . createElement
2014-11-29 16:09:38 -05:00
dummy = document . createElement ' div '
dummy.innerHTML = ' text '
TEXT = if dummy . textContent is ' text ' then ' textContent ' else ' innerText '
module.exports.replaceText = (elems, text) ->
elem [ TEXT ] = text for elem in elems
null
2014-12-28 16:25:20 -05:00
# Add a stylesheet rule
# http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript/26230472#26230472
# Don't use wantonly, or we'll have to implement a simple mechanism for clearing out old rules.
2015-02-12 19:06:27 -05:00
if document ? . createElement
2014-12-28 16:25:20 -05:00
module.exports.injectCSS = ( (doc) ->
# wrapper for all injected styles and temp el to create them
wrap = doc . createElement ( " div " )
temp = doc . createElement ( " div " )
# rules like "a {color: red}" etc.
return (cssRules) ->
# append wrapper to the body on the first call
unless wrap . id
wrap.id = " injected-css "
wrap.style.display = " none "
doc . body . appendChild wrap
# <br> for IE: http://goo.gl/vLY4x7
temp.innerHTML = " <br><style> " + cssRules + " </style> "
wrap . appendChild temp . children [ 1 ]
return
) ( document )
2015-03-03 12:04:53 -05:00
module.exports.getQueryVariable = getQueryVariable = (param, defaultValue) ->
query = document . location . search . substring 1
pairs = ( pair . split ( ' = ' ) for pair in query . split ' & ' )
for pair in pairs when pair [ 0 ] is param
return { ' true ' : true , ' false ' : false } [ pair [ 1 ] ] ? decodeURIComponent ( pair [ 1 ] )
defaultValue
2015-03-13 18:19:20 -04:00
module.exports.getSponsoredSubsAmount = getSponsoredSubsAmount = (price=999, subCount=0, personalSub=false) ->
# 1 100%
# 2-11 80%
# 12+ 60%
# TODO: make this less confusing
return 0 unless subCount > 0
offset = if personalSub then 1 else 0
if subCount <= 1 - offset
price
else if subCount <= 11 - offset
Math . round ( ( 1 - offset ) * price + ( subCount - 1 + offset ) * price * 0.8 )
else
Math . round ( ( 1 - offset ) * price + 10 * price * 0.8 + ( subCount - 11 + offset ) * price * 0.6 )
2015-09-03 14:04:40 -04:00
2015-10-01 18:23:20 -04:00
module.exports.getCourseBundlePrice = getCourseBundlePrice = (coursePrices, seats=20) ->
totalPricePerSeat = coursePrices . reduce ( (a, b) -> a + b ) , 0
if coursePrices . length > 2
2015-09-03 14:04:40 -04:00
pricePerSeat = Math . round ( totalPricePerSeat / 2.0 )
else
pricePerSeat = parseInt ( totalPricePerSeat )
seats * pricePerSeat
2015-09-10 13:37:32 -04:00
module.exports.getCoursePraise = getCoursePraise = ->
praise = [
{
quote: " The kids love it. "
source: " Leo Joseph Tran, Athlos Leadership Academy "
} ,
{
quote: " My students have been using the site for a couple of weeks and they love it. "
source: " Scott Hatfield, Computer Applications Teacher, School Technology Coordinator, Eastside Middle School "
} ,
{
quote: " Thanks for the captivating site. My eighth graders love it. "
source: " Janet Cook, Ansbach Middle/High School "
} ,
{
quote: " My students have started working on CodeCombat and love it! I love that they are learning coding and problem solving skills without them even knowing it!! "
source: " Kristin Huff, Special Education Teacher, Webb City School District "
} ,
{
quote: " I recently introduced Code Combat to a few of my fifth graders and they are loving it! "
source: " Shauna Hamman, Fifth Grade Teacher, Four Peaks Elementary School "
} ,
{
quote: " Overall I think it ' s a fantastic service. Variables, arrays, loops, all covered in very fun and imaginative ways. Every kid who has tried it is a fan. "
source: " Aibinder Andrew, Technology Teacher "
} ,
{
quote: " I love what you have created. The kids are so engaged. "
source: " Desmond Smith, 4KS Academy "
} ,
{
quote: " My students love the website and I hope on having content structured around it in the near future. "
source: " Michael Leonard, Science Teacher, Clearwater Central Catholic High School "
}
]
praise [ _ . random ( 0 , praise . length - 1 ) ]
2015-09-25 13:03:44 -04:00
2015-12-14 14:10:37 -05:00
module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=0, users=0, months=0) ->
2015-09-25 13:03:44 -04:00
return 0 unless users > 0 and months > 0
total = price * users * months
total
2015-11-10 18:22:09 -05:00
2016-06-24 12:36:18 -04:00
startsWithVowel = (s) -> s [ 0 ] in ' aeiouAEIOU '
2016-02-19 13:55:42 -05:00
module.exports.filterMarkdownCodeLanguages = (text, language) ->
2015-12-10 12:05:34 -05:00
return ' ' unless text
2016-02-19 13:55:42 -05:00
currentLanguage = language or me . get ( ' aceConfig ' ) ? . language or ' python '
2015-12-23 13:38:00 -05:00
excludedLanguages = _ . without [ ' javascript ' , ' python ' , ' coffeescript ' , ' clojure ' , ' lua ' , ' java ' , ' io ' ] , currentLanguage
2016-05-31 13:48:16 -04:00
# Exclude language-specific code blocks like ```python (... code ...)``` for each non-target language.
codeBlockExclusionRegex = new RegExp " ```( #{ excludedLanguages . join ( ' | ' ) } ) \n [^`]+``` \n ? " , ' gm '
# Exclude language-specific images like ![python - image description](image url) for each non-target language.
imageExclusionRegex = new RegExp " ! \\ [( #{ excludedLanguages . join ( ' | ' ) } ) - .+? \\ ] \\ (.+? \\ ) \n ? " , ' gm '
2016-06-24 12:36:18 -04:00
text = text . replace ( codeBlockExclusionRegex , ' ' ) . replace ( imageExclusionRegex , ' ' )
commonLanguageReplacements =
python: [
[ ' true ' , ' True ' ] , [ ' false ' , ' False ' ] , [ ' null ' , ' None ' ] ,
[ ' object ' , ' dictionary ' ] , [ ' Object ' , ' Dictionary ' ] ,
[ ' array ' , ' list ' ] , [ ' Array ' , ' List ' ] ,
]
lua: [
[ ' null ' , ' nil ' ] ,
[ ' object ' , ' table ' ] , [ ' Object ' , ' Table ' ] ,
[ ' array ' , ' table ' ] , [ ' Array ' , ' Table ' ] ,
]
for [ from , to ] in commonLanguageReplacements [ currentLanguage ] ? [ ]
# Convert JS-specific keywords and types to Python ones, if in simple `code` tags.
# This won't cover it when it's not in an inline code tag by itself or when it's not in English.
text = text . replace / / /`#{from}`/ / / g , " ` #{ to } ` "
# Now change "An `dictionary`" to "A `dictionary`", etc.
if startsWithVowel ( from ) and not startsWithVowel ( to )
text = text . replace / / /(\ a|A)n(\ `#{to}`)/ / / g , " $1$2 "
if not startsWithVowel ( from ) and startsWithVowel ( to )
text = text . replace / / /(\ a|A)(\ `#{to}`)/ / / g , " $1n$2 "
return text
2015-11-10 18:22:09 -05:00
module.exports.aceEditModes = aceEditModes =
' javascript ' : ' ace/mode/javascript '
' coffeescript ' : ' ace/mode/coffee '
' python ' : ' ace/mode/python '
2015-12-23 13:38:00 -05:00
' java ' : ' ace/mode/java '
2015-11-10 18:22:09 -05:00
' lua ' : ' ace/mode/lua '
2016-01-04 19:41:24 -05:00
' java ' : ' ace/mode/java '
2015-11-10 18:22:09 -05:00
module.exports.initializeACE = (el, codeLanguage) ->
contents = $ ( el ) . text ( ) . trim ( )
editor = ace . edit el
editor . setOptions maxLines: Infinity
editor . setReadOnly true
editor . setTheme ' ace/theme/textmate '
editor . setShowPrintMargin false
editor . setShowFoldWidgets false
editor . setHighlightActiveLine false
editor . setHighlightActiveLine false
editor . setBehavioursEnabled false
editor . renderer . setShowGutter false
editor . setValue contents
editor . clearSelection ( )
session = editor . getSession ( )
session . setUseWorker false
session . setMode aceEditModes [ codeLanguage ]
session . setWrapLimitRange null
session . setUseWrapMode true
session . setNewLineMode ' unix '
return editor
2016-03-30 16:57:19 -04:00
2016-05-24 15:00:04 -04:00
module.exports.capitalLanguages = capitalLanguages =
2016-03-30 16:57:19 -04:00
' javascript ' : ' JavaScript '
' coffeescript ' : ' CoffeeScript '
' python ' : ' Python '
' java ' : ' Java '
' lua ' : ' Lua '
2016-06-26 16:51:14 -04:00
module.exports.createLevelNumberMap = (levels) ->
levelNumberMap = { }
practiceLevelTotalCount = 0
practiceLevelCurrentCount = 0
for level , i in levels
levelNumber = i - practiceLevelTotalCount + 1
if level . practice
levelNumber = i - practiceLevelTotalCount + String . fromCharCode ( ' a ' . charCodeAt ( 0 ) + practiceLevelCurrentCount )
practiceLevelTotalCount ++
practiceLevelCurrentCount ++
else
practiceLevelCurrentCount = 0
levelNumberMap [ level . key ] = levelNumber
levelNumberMap
module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
2016-06-29 15:50:23 -04:00
# levels = [{practice: true/false, complete: true/false}]
index = currentIndex
index ++
if needsPractice
if levels [ currentIndex ] . practice or index < levels . length and levels [ index ] . practice
# Needs practice, on practice or next practice, choose next incomplete level
# May leave earlier practice levels incomplete and reach end of course
index ++ while index < levels . length and levels [ index ] . complete
2016-06-26 16:51:14 -04:00
else
2016-06-29 15:50:23 -04:00
# Needs practice, on required, next required, choose first incomplete level of previous practice chain
index - -
index - - while index >= 0 and not levels [ index ] . practice
if index >= 0
index - - while index >= 0 and levels [ index ] . practice
if index >= 0
index ++
index ++ while index < levels . length and levels [ index ] . practice and levels [ index ] . complete
if levels [ index ] . practice and not levels [ index ] . complete
return index
index = currentIndex + 1
index ++ while index < levels . length and levels [ index ] . complete
else
# No practice needed, next required incomplete level
index ++ while index < levels . length and ( levels [ index ] . practice or levels [ index ] . complete )
index
2016-06-26 16:51:14 -04:00
module.exports.needsPractice = (playtime=0, threshold=2) ->
playtime / 60 > threshold
2016-06-28 19:41:33 -04:00
module.exports.usStateCodes =
# https://github.com/mdzhang/us-state-codes
# generated by js2coffee 2.2.0
( ->
stateNamesByCode =
' AL ' : ' Alabama '
' AK ' : ' Alaska '
' AZ ' : ' Arizona '
' AR ' : ' Arkansas '
' CA ' : ' California '
' CO ' : ' Colorado '
' CT ' : ' Connecticut '
' DE ' : ' Delaware '
' DC ' : ' District of Columbia '
' FL ' : ' Florida '
' GA ' : ' Georgia '
' HI ' : ' Hawaii '
' ID ' : ' Idaho '
' IL ' : ' Illinois '
' IN ' : ' Indiana '
' IA ' : ' Iowa '
' KS ' : ' Kansas '
' KY ' : ' Kentucky '
' LA ' : ' Louisiana '
' ME ' : ' Maine '
' MD ' : ' Maryland '
' MA ' : ' Massachusetts '
' MI ' : ' Michigan '
' MN ' : ' Minnesota '
' MS ' : ' Mississippi '
' MO ' : ' Missouri '
' MT ' : ' Montana '
' NE ' : ' Nebraska '
' NV ' : ' Nevada '
' NH ' : ' New Hampshire '
' NJ ' : ' New Jersey '
' NM ' : ' New Mexico '
' NY ' : ' New York '
' NC ' : ' North Carolina '
' ND ' : ' North Dakota '
' OH ' : ' Ohio '
' OK ' : ' Oklahoma '
' OR ' : ' Oregon '
' PA ' : ' Pennsylvania '
' RI ' : ' Rhode Island '
' SC ' : ' South Carolina '
' SD ' : ' South Dakota '
' TN ' : ' Tennessee '
' TX ' : ' Texas '
' UT ' : ' Utah '
' VT ' : ' Vermont '
' VA ' : ' Virginia '
' WA ' : ' Washington '
' WV ' : ' West Virginia '
' WI ' : ' Wisconsin '
' WY ' : ' Wyoming '
stateCodesByName = _ . invert ( stateNamesByCode )
# normalizes case and removes invalid characters
# returns null if can't find sanitized code in the state map
sanitizeStateCode = (code) ->
code = if _ . isString ( code ) then code . trim ( ) . toUpperCase ( ) . replace ( /[^A-Z]/g , ' ' ) else null
if stateNamesByCode [ code ] then code else null
# returns a valid state name else null
getStateNameByStateCode = (code) ->
stateNamesByCode [ sanitizeStateCode ( code ) ] or null
# normalizes case and removes invalid characters
# returns null if can't find sanitized name in the state map
sanitizeStateName = (name) ->
if ! _ . isString ( name )
return null
# bad whitespace remains bad whitespace e.g. "O hi o" is not valid
name = name . trim ( ) . toLowerCase ( ) . replace ( /[^a-z\s]/g , ' ' ) . replace ( /\s+/g , ' ' )
tokens = name . split ( /\s+/ )
tokens = _ . map ( tokens , (token) ->
token . charAt ( 0 ) . toUpperCase ( ) + token . slice ( 1 )
)
# account for District of Columbia
if tokens . length > 2
tokens [ 1 ] = tokens [ 1 ] . toLowerCase ( )
name = tokens . join ( ' ' )
if stateCodesByName [ name ] then name else null
# returns a valid state code else null
getStateCodeByStateName = (name) ->
stateCodesByName [ sanitizeStateName ( name ) ] or null
return {
sanitizeStateCode: sanitizeStateCode
getStateNameByStateCode: getStateNameByStateCode
sanitizeStateName: sanitizeStateName
getStateCodeByStateName: getStateCodeByStateName
}
) ( )