Merge branch 'master' into production

This commit is contained in:
Matt Lott 2016-08-23 05:42:46 -07:00
commit 2728691739
19 changed files with 144 additions and 114 deletions

View file

@ -18,6 +18,17 @@ module.exports.combineAncestralObject = (obj, propertyName) ->
obj = Object.getPrototypeOf(obj)
combined
module.exports.courseIDs = courseIDs =
INTRODUCTION_TO_COMPUTER_SCIENCE: '560f1a9f22961295f9427742'
COMPUTER_SCIENCE_2: '5632661322961295f9428638'
GAME_DEVELOPMENT_1: '5789587aad86a6efb573701e'
WEB_DEVELOPMENT_1: '5789587aad86a6efb573701f'
COMPUTER_SCIENCE_3: '56462f935afde0c6fd30fc8c'
GAME_DEVELOPMENT_2: '57b621e7ad86a6efb5737e64'
WEB_DEVELOPMENT_2: '5789587aad86a6efb5737020'
COMPUTER_SCIENCE_4: '56462f935afde0c6fd30fc8d'
COMPUTER_SCIENCE_5: '569ed916efa72b0ced971447'
module.exports.normalizeFunc = (func_thing, object) ->
# func could be a string to a function in this class
# or a function in its own right
@ -382,6 +393,24 @@ module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
module.exports.needsPractice = (playtime=0, threshold=2) ->
playtime / 60 > threshold
module.exports.sortCourses = (courses) ->
orderedIDs = [
courseIDs.INTRODUCTION_TO_COMPUTER_SCIENCE
courseIDs.COMPUTER_SCIENCE_2
courseIDs.GAME_DEVELOPMENT_1
courseIDs.WEB_DEVELOPMENT_1
courseIDs.COMPUTER_SCIENCE_3
courseIDs.GAME_DEVELOPMENT_2
courseIDs.WEB_DEVELOPMENT_2
courseIDs.COMPUTER_SCIENCE_4
courseIDs.COMPUTER_SCIENCE_5
]
_.sortBy courses, (course) ->
# ._id can be from classroom.courses, otherwise it's probably .id
index = orderedIDs.indexOf(course.id ? course._id)
index = 9001 if index is -1
index
module.exports.usStateCodes =
# https://github.com/mdzhang/us-state-codes
# generated by js2coffee 2.2.0
@ -481,3 +510,4 @@ module.exports.usStateCodes =
getStateCodeByStateName: getStateCodeByStateName
}
)()

View file

@ -358,6 +358,9 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
prerenderedSpriteSheet = thangType.getPrerenderedSpriteSheet(colorConfig, 'segmented')
if prerenderedSpriteSheet and not prerenderedSpriteSheet.loadedImage
return
else if prerenderedSpriteSheet
animations = prerenderedSpriteSheet.spriteSheet._animations
renderedActions = _.zipObject(animations, _.times(animations.length, -> true))
containersToRender = thangType.getContainersForActions(actionNames)
#console.log 'render segmented', thangType.get('name'), actionNames, colorConfig, 'because we do not have prerendered sprite sheet?', prerenderedSpriteSheet
spriteBuilder = new SpriteBuilder(thangType, {colorConfig: colorConfig})
@ -367,7 +370,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
container = new createjs.Sprite(@spriteSheet)
container.gotoAndStop(containerKey)
frame = spriteSheetBuilder.addFrame(container)
else if prerenderedSpriteSheet
else if prerenderedSpriteSheet and renderedActions[containerGlobalName]
container = new createjs.Sprite(prerenderedSpriteSheet.spriteSheet)
container.gotoAndStop(containerGlobalName)
scale = @resolutionFactor / (prerenderedSpriteSheet.get('resolutionFactor') or 1)

View file

@ -652,7 +652,6 @@
feature2: "__heroesCount__ powerful <strong>new heroes</strong> with unique skills!"
feature3: "__bonusLevelsCount__+ bonus levels"
feature4: "<strong>{{gems}} bonus gems</strong> every month!"
feature5: "Video tutorials"
feature6: "Premium email support"
feature7: "Private <strong>Clans</strong>"
feature8: "<strong>No ads!</strong>"

View file

@ -186,6 +186,9 @@ module.exports = class Classroom extends CocoModel
options.type = 'POST'
@fetch(options)
getSortedCourses: ->
utils.sortCourses(@get('courses') ? [])
updateCourses: (options={}) ->
options.url = @url() + '/update-courses'
options.type = 'POST'

View file

@ -42,7 +42,14 @@ _.extend CampaignSchema.properties, {
position: c.point2d()
rotation: { type: 'number', format: 'degrees' }
color: { type: 'string' }
showIfUnlocked: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
showIfUnlocked:
oneOf: [
{ type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
{
type: 'array',
items: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
}
]
}
}}
levelsUpdated: c.date()

View file

@ -103,12 +103,12 @@ module.exports =
'tome:change-config': c.object {title: 'Change Config', description: 'Published when you change your tome settings'}
'tome:update-snippets': c.object {title: 'Update Snippets', description: 'Published when we need to add Zatanna snippets', required: ['propGroups', 'allDocs']},
'tome:update-snippets': c.object {title: 'Update Snippets', description: 'Published when we need to add autocomplete snippets', required: ['propGroups', 'allDocs']},
propGroups: {type: 'object'}
allDocs: {type: 'object'}
language: {type: 'string'}
'tome:insert-snippet': c.object {title: 'Insert Snippet', description: 'Published when we need to insert a Zatanna snippet', required: ['doc', 'language', 'formatted']},
'tome:insert-snippet': c.object {title: 'Insert Snippet', description: 'Published when we need to insert a autocomplete snippet', required: ['doc', 'language', 'formatted']},
doc: {type: 'object'}
language: {type: 'string'}
formatted: {type: 'object'}

View file

@ -289,8 +289,11 @@ $level-resize-transition-time: 0.5s
.game-container, .level-content, #game-area, #canvas-wrapper
height: 100%
#canvas-wrapper
height: calc(100% - 50px)
#canvas-wrapper canvas
display: none
display: none
#web-surface-view
position: absolute

View file

@ -59,13 +59,6 @@
td.free-cell
td.center-ok
span.glyphicon.glyphicon-ok
tr
td.feature-description
span(data-i18n="subscribe.feature5")
if !me.isOnPremiumServer()
td.free-cell
td.center-ok
span.glyphicon.glyphicon-ok
tr
td.feature-description
span(data-i18n="[html]subscribe.feature7")

View file

@ -220,9 +220,9 @@ mixin studentRow(student)
+longLevelName(student.latestCompleteLevel)
td
if state.get('progressData')
- var courses = view.classroom.get('courses').map(function(c) { return view.courses.get(c._id); });
- var courses = view.sortedCourses.map(function(c) { return view.courses.get(c._id); });
- var courseLabelsArray = view.helper.courseLabelsArray(courses);
each trimCourse, index in view.classroom.get('courses')
each trimCourse, index in view.sortedCourses
- var course = view.courses.get(trimCourse._id);
- var instance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
if instance && instance.hasMember(student)
@ -255,7 +255,7 @@ mixin courseProgressTab
span(data-i18n='teacher.select_course')
span.spr :
select.course-select
each trimCourse in view.classroom.get('courses')
each trimCourse in view.sortedCourses
- var course = view.courses.get(trimCourse._id);
option(value=course.id selected=(course===state.get('selectedCourse')))
= i18n(course.attributes, 'name')
@ -423,7 +423,7 @@ mixin bulkAssignControls
span(data-i18n='teacher.bulk_assign')
span :
select.bulk-course-select.form-control
each trimCourse in _.rest(view.classroom.get('courses'))
each trimCourse in _.rest(view.sortedCourses)
- var course = view.courses.get(trimCourse._id)
option(value=course.id selected=(course===state.get('selectedCourse')))
= i18n(course.attributes, 'name')

View file

@ -77,7 +77,7 @@ mixin classRow(classroom)
if classroom.get('members').length == 0
+addStudentsButton(classroom)
else
- var courses = classroom.get('courses').map(function(c) { return view.courses.get(c._id); });
- var courses = classroom.getSortedCourses().map(function(c) { return view.courses.get(c._id); });
- var courseLabelsArray = view.helper.courseLabelsArray(courses);
each trimCourse, index in classroom.get('courses') || []
- var course = view.courses.get(trimCourse._id);

View file

@ -83,6 +83,7 @@ module.exports = class TeacherClassView extends RootView
@classroom = new Classroom({ _id: classroomID })
@supermodel.trackRequest @classroom.fetch()
@onKeyPressStudentSearch = _.debounce(@onKeyPressStudentSearch, 200)
@sortedCourses = []
@students = new Users()
@listenTo @classroom, 'sync', ->
@ -164,6 +165,7 @@ module.exports = class TeacherClassView extends RootView
null
onLoaded: ->
@sortedCourses = @classroom.getSortedCourses()
@removeDeletedStudents() # TODO: Move this to mediator listeners? For both classroom and students?
@calculateProgressAndLevels()

View file

@ -184,7 +184,8 @@ module.exports = class LevelLoadingView extends CocoView
#{problem.category} - #{problem.score} points
""", sanitize: false
else
html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body'))
language = @session?.get('codeLanguage')
html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body'), language)
@$el.find('.intro-doc').removeClass('hidden').find('.intro-doc-content').html html
@resize()

View file

@ -47,22 +47,30 @@ module.exports = class SpellPaletteEntryView extends CocoView
Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.name, entry: @
soundIndex = Math.floor(Math.random() * 4)
@playSound "spell-palette-entry-open-#{soundIndex}", 0.75
popover = @$el.data('bs.popover')
popover?.$tip?.i18n()
codeLanguage = @options.language
oldEditor.destroy() for oldEditor in @aceEditors
@aceEditors = []
aceEditors = @aceEditors
# Initialize Ace for each popover code snippet
popover?.$tip?.find('.docs-ace').each ->
aceEditor = utils.initializeACE @, codeLanguage
aceEditors.push aceEditor
@afterRenderPopover()
onMouseEnter: (e) ->
# Make sure the doc has the updated Thang so it can regenerate its prop value
# NOTE: This can't be run twice without resetting the popover content HTML
# in between. If you do, Ace will break.
afterRenderPopover: ->
popover = @$el.data('bs.popover')
popover?.$tip?.i18n()
codeLanguage = @options.language
oldEditor.destroy() for oldEditor in @aceEditors
@aceEditors = []
aceEditors = @aceEditors
# Initialize Ace for each popover code snippet that still needs it
popover?.$tip?.find('.docs-ace').each ->
aceEditor = utils.initializeACE @, codeLanguage
aceEditors.push aceEditor
resetPopoverContent: ->
@$el.data('bs.popover').options.content = @docFormatter.formatPopover()
@$el.popover('setContent')
@$el.popover 'show' unless @popoverPinned or @otherPopoverPinned
onMouseEnter: (e) ->
return if @popoverPinned or @otherPopoverPinned
@resetPopoverContent()
@$el.popover 'show'
onMouseLeave: (e) ->
@$el.popover 'hide' unless @popoverPinned or @otherPopoverPinned
@ -76,8 +84,9 @@ module.exports = class SpellPaletteEntryView extends CocoView
@playSound 'spell-palette-entry-unpin'
else
@popoverPinned = true
@$el.popover 'show'
@resetPopoverContent()
@$el.add('.spell-palette-popover.popover').addClass 'pinned'
@$el.popover 'show'
x = $('<button type="button" data-dismiss="modal" aria-hidden="true" class="close">×</button>')
$('.spell-palette-popover.popover').append x
x.on 'click', @onClick

View file

@ -12,7 +12,7 @@ LevelComponent = require 'models/LevelComponent'
UserCodeProblem = require 'models/UserCodeProblem'
utils = require 'core/utils'
CodeLog = require 'models/CodeLog'
Zatanna = require './editor/zatanna'
Autocomplete = require './editor/autocomplete'
module.exports = class SpellView extends CocoView
id: 'spell-view'
@ -45,7 +45,7 @@ module.exports = class SpellView extends CocoView
'tome:spell-statement-index-updated': 'onStatementIndexUpdated'
'tome:change-language': 'onChangeLanguage'
'tome:change-config': 'onChangeEditorConfig'
'tome:update-snippets': 'addZatannaSnippets'
'tome:update-snippets': 'addAutocompleteSnippets'
'tome:insert-snippet': 'onInsertSnippet'
'tome:spell-beautify': 'onSpellBeautify'
'tome:maximize-toggled': 'onMaximizeToggled'
@ -459,7 +459,7 @@ module.exports = class SpellView extends CocoView
if (e.command.name is 'insertstring' and intersects()) or
(e.command.name in ['Backspace', 'throttle-backspaces'] and intersectsLeft()) or
(e.command.name is 'del' and intersectsRight())
@zatanna?.off?()
@autocomplete?.off?()
pulseLockedCode()
return false
else if e.command.name in ['enter-skip-delimiters', 'Enter', 'Return']
@ -468,41 +468,41 @@ module.exports = class SpellView extends CocoView
e.editor.navigateLineStart()
return false
else if e.command.name in ['Enter', 'Return'] and not e.editor?.completer?.popup?.isOpen
@zatanna?.on?()
@autocomplete?.on?()
return e.editor.execCommand 'enter-skip-delimiters'
@zatanna?.on?()
@autocomplete?.on?()
e.command.exec e.editor, e.args or {}
initAutocomplete: (@autocomplete) ->
initAutocomplete: (@autocompleteOn) ->
# TODO: Turn on more autocompletion based on level sophistication
# TODO: E.g. using the language default snippets yields a bunch of crazy non-beginner suggestions
# TODO: Options logic shouldn't exist both here and in updateAutocomplete()
return if @spell.language is 'html'
popupFontSizePx = @options.level.get('autocompleteFontSizePx') ? 16
@zatanna = new Zatanna @ace,
@autocomplete = new Autocomplete @ace,
basic: false
liveCompletion: false
snippetsLangDefaults: false
completers:
keywords: false
snippets: @autocomplete
snippets: @autocompleteOn
autoLineEndings:
javascript: ';'
popupFontSizePx: popupFontSizePx
popupLineHeightPx: 1.5 * popupFontSizePx
popupWidthPx: 380
updateAutocomplete: (@autocomplete) ->
@zatanna?.set 'snippets', @autocomplete
updateAutocomplete: (@autocompleteOn) ->
@autocomplete?.set 'snippets', @autocompleteOn
addZatannaSnippets: (e) ->
addAutocompleteSnippets: (e) ->
# Snippet entry format:
# content: code inserted into document
# meta: displayed right-justfied in popup
# name: displayed left-justified in popup, and what's being matched
# tabTrigger: fallback for name field
return unless @zatanna and @autocomplete
@zatanna.addCodeCombatSnippets @options.level, @, e
return unless @autocomplete and @autocompleteOn
@autocomplete.addCodeCombatSnippets @options.level, @, e
translateFindNearest: ->
# If they have advanced glasses but are playing a level which assumes earlier glasses, we'll adjust the sample code to use the more advanced APIs instead.
@ -582,7 +582,7 @@ module.exports = class SpellView extends CocoView
@createTranslationView() unless @translationView
@toolbarView?.toggleFlow false
@updateAether false, false
# @addZatannaSnippets()
# @addAutocompleteSnippets()
@highlightCurrentLine()
cast: (preload=false, realTime=false, justBegin=false) ->
@ -1219,7 +1219,7 @@ module.exports = class SpellView extends CocoView
onChangeLanguage: (e) ->
return unless @spell.canWrite()
@aceSession.setMode utils.aceEditModes[e.language]
@zatanna?.set 'language', utils.aceEditModes[e.language].substr('ace/mode/')
@autocomplete?.set 'language', utils.aceEditModes[e.language].substr('ace/mode/')
wasDefault = @getSource() is @spell.originalSource
@spell.setLanguage e.language
@reloadCode true if wasDefault
@ -1287,7 +1287,7 @@ module.exports = class SpellView extends CocoView
@debugView?.destroy()
@translationView?.destroy()
@toolbarView?.destroy()
@zatanna?.addSnippets [], @editorLang if @editorLang?
@autocomplete?.addSnippets [], @editorLang if @editorLang?
$(window).off 'resize', @onWindowResize
window.clearTimeout @saveSpadeTimeout
@saveSpadeTimeout = null

View file

@ -23,7 +23,7 @@ defaults =
# TODO: Create list of manual test cases
module.exports = class Zatanna
module.exports = class Autocomplete
Tokenizer = ''
BackgroundTokenizer = ''
@ -43,7 +43,7 @@ module.exports = class Zatanna
#TODO: Renable option validation if we care
#validationResult = optionsValidator @options
#unless validationResult.valid
# throw new Error "Invalid Zatanna options: " + JSON.stringify(validationResult.errors, null, 4)
# throw new Error "Invalid Autocomplete options: " + JSON.stringify(validationResult.errors, null, 4)
ace.config.loadModule 'ace/ext/language_tools', () =>
@snippetManager = ace.require('ace/snippets').snippetManager
@ -154,7 +154,7 @@ module.exports = class Zatanna
off: -> @paused = true
doLiveCompletion: (e) =>
# console.log 'Zatanna doLiveCompletion', e
# console.log 'Autocomplete doLiveCompletion', e
return unless @options.basic or @options.liveCompletion or @options.completers.snippets
return if @paused
@ -172,6 +172,9 @@ module.exports = class Zatanna
# Bake a fresh autocomplete every keystroke
editor.completer?.detach() if hasCompleter
# Skip common single letter variable names
return if /^x$|^y$/i.test(prefix)
# Only autocomplete if there's a prefix that can be matched
if (prefix)
unless (editor.completer)
@ -330,7 +333,7 @@ module.exports = class Zatanna
if haveFindNearest and not haveFindNearestEnemy
spellView.translateFindNearest()
# window.zatannaInstance = @zatanna # For debugging. Make sure to not leave active when committing.
# window.AutocompleteInstance = @Autocomplete # For debugging. Make sure to not leave active when committing.
# window.snippetEntries = snippetEntries
lang = utils.aceEditModes[e.language].substr 'ace/mode/'.length
@addSnippets snippetEntries, lang

View file

@ -37,8 +37,8 @@ module.exports = (SnippetManager, autoLineEndings) ->
range = new Range cursor.row, cursor.column - 1 - prevWord.length, cursor.row, cursor.column
editor.session.remove range
else
# console.log "Zatanna cursor.column=#{cursor.column} snippet='#{snippet}' line='#{line}' prevWord='#{prevWord}'"
# console.log "Zatanna prevWordIndex=#{prevWordIndex}"
# console.log "Snippets cursor.column=#{cursor.column} snippet='#{snippet}' line='#{line}' prevWord='#{prevWord}'"
# console.log "Snippets prevWordIndex=#{prevWordIndex}"
# Lookup original completion
# TODO: Can we identify correct completer somehow?
@ -51,10 +51,10 @@ module.exports = (SnippetManager, autoLineEndings) ->
break if originalCompletion
if originalCompletion?
# console.log 'Zatanna original completion', originalCompletion
# console.log 'Snippets original completion', originalCompletion
# Get original snippet prefix (accounting for extra '\n' and possibly autoLineEndings at end)
lang = editor.session.getMode()?.$id?.substr 'ace/mode/'.length
# console.log 'Zatanna lang', lang, autoLineEndings[lang]?.length
# console.log 'Snippets lang', lang, autoLineEndings[lang]?.length
extraEndLength = 1
extraEndLength += autoLineEndings[lang].length if autoLineEndings[lang]?
if snippetIndex = originalCompletion.content.indexOf snippet.substr(0, snippet.length - extraEndLength)
@ -62,21 +62,21 @@ module.exports = (SnippetManager, autoLineEndings) ->
else
originalPrefix = ''
snippetStart = cursor.column - originalPrefix.length
# console.log "Zatanna originalPrefix='#{originalPrefix}' snippetStart=#{snippetStart}"
# console.log "Snippets originalPrefix='#{originalPrefix}' snippetStart=#{snippetStart}"
if snippetStart > 0 and snippetStart <= line.length
extraIndex = snippetStart - 1
# console.log "Zatanna prev char='#{line[extraIndex]}'"
# console.log "Snippets prev char='#{line[extraIndex]}'"
if line[extraIndex] is '.'
# Fuzzy string match previous word before '.', and remove if a match to beginning of snippet
originalObject = originalCompletion.content.substring(0, originalCompletion.content.indexOf('.'))
prevObjectIndex = extraIndex - 1
# console.log "Zatanna prevObjectIndex=#{prevObjectIndex}"
# console.log "Snippets prevObjectIndex=#{prevObjectIndex}"
if prevObjectIndex >= 0 and /\w/.test(line[prevObjectIndex])
prevObjectIndex-- while prevObjectIndex >= 0 and /\w/.test(line[prevObjectIndex])
prevObjectIndex++ if prevObjectIndex < 0 or not /\w/.test(line[prevObjectIndex])
# console.log "Zatanna prevObjectIndex=#{prevObjectIndex} extraIndex=#{extraIndex}"
# console.log "Snippets prevObjectIndex=#{prevObjectIndex} extraIndex=#{extraIndex}"
prevObject = line.substring prevObjectIndex, extraIndex
#TODO: We use to use fuzziac here, but we forgot why. Using
@ -87,7 +87,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
if fuzzer
finalScore = fuzzer.score prevObject
# console.log "Zatanna originalObject='#{originalObject}' prevObject='#{prevObject}'", finalScore
# console.log "Snippets originalObject='#{originalObject}' prevObject='#{prevObject}'", finalScore
if finalScore > 0.5
range = new Range cursor.row, prevObjectIndex, cursor.row, snippetStart
editor.session.remove range
@ -113,7 +113,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
baseInsertSnippet.call @, editor, snippet
getCompletions: (editor, session, pos, prefix, callback) ->
# console.log "Zatanna getCompletions pos.column=#{pos.column} prefix=#{prefix}"
# console.log "Snippets getCompletions pos.column=#{pos.column} prefix=#{prefix}"
# Completion format:
# prefix: text that will be replaced by snippet
# caption: displayed left-justified in popup, and what's being matched
@ -149,7 +149,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
continue unless caption
[snippet, fuzzScore] = scrubSnippet s.content, caption, line, prefix, pos, lang, autoLineEndings, s.captureReturn
completions.push
content: s.content # Used internally by Zatanna, not by ace autocomplete
content: s.content # Used internally by Snippets, not by ace autocomplete
caption: caption
snippet: snippet
score: fuzzScore * s.importance ? 1.0
@ -162,7 +162,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
@completions = _.filter(completions, (x) -> x.caption.indexOf prefix is 0)
return callback null, @completions
# console.log 'Zatanna snippet completions', completions
# console.log 'Snippets snippet completions', completions
@completions = completions
callback null, completions
@ -170,7 +170,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
# TODO: https://github.com/ajaxorg/ace/commit/7b01a4273e91985c9177f53d238d6b83fe99dc56
# TODO: But, if it was we could use this and pass a 'completer: @' property for each completion
# insertMatch: (editor, data) ->
# console.log 'Zatanna snippets insertMatch', editor, data
# console.log 'Snippets snippets insertMatch', editor, data
# if data.snippet
# SnippetManager.insertSnippet editor, data.snippet
# else
@ -193,7 +193,7 @@ getFullIdentifier = (doc, pos) ->
text.substring start, end
scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captureReturn) ->
# console.log "Zatanna snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}"
# console.log "Snippets snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}"
fuzzScore = 0.1
# input will be replaced by snippet
# trim snippet prefix and suffix if already in the document (line)
@ -223,7 +223,7 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu
# TODO: This is broken for attack(find in Python, but seems ok in JavaScript.
# Don't eat existing matched parentheses
# console.log "Zatanna checking parentheses lineSuffix=#{lineSuffix} pos.column=#{pos.column} input.length=#{input.length}, prevChar=#{line[pos.column - input.length - 1]} line.length=#{line.length} nextChar=#{line[pos.column]}"
# console.log "Snippets checking parentheses lineSuffix=#{lineSuffix} pos.column=#{pos.column} input.length=#{input.length}, prevChar=#{line[pos.column - input.length - 1]} line.length=#{line.length} nextChar=#{line[pos.column]}"
if pos.column - input.length >= 0 and line[pos.column - input.length - 1] is '(' and pos.column < line.length and line[pos.column] is ')' and lineSuffix is ')'
lineSuffix = ''
@ -238,9 +238,9 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu
# If at end of line
# And, no parentheses are before snippet. E.g. 'if ('
# And, line doesn't start with whitespace followed by 'if ' or 'elif '
# console.log "Zatanna autoLineEndings linePrefixIndex='#{linePrefixIndex}'"
# console.log "Snippets autoLineEndings linePrefixIndex='#{linePrefixIndex}'"
if lineSuffix.length is 0 and /^\s*$/.test line.slice pos.column
# console.log 'Zatanna atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line
# console.log 'Snippets atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line
toLinePrefix = line.substring 0, linePrefixIndex
if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(toLinePrefix) and not /^[ \t]*(?:if\b|elif\b)/.test(toLinePrefix)
snippet += autoLineEndings[lang] if snippetLines is 0 and autoLineEndings[lang]
@ -249,7 +249,7 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu
if captureReturn and /^\s*$/.test(toLinePrefix)
snippet = captureReturn + linePrefix + snippet
# console.log "Zatanna snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}"
# console.log "Snippets snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}"
else
fuzzScore += score snippet, input

View file

@ -1,15 +1,19 @@
// Update course data
// Update courses
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// eg: mongo localhost:27017/coco scripts/mongodb/updateCourses.js
// NOTE: uses slug as unique identifier, so changing the slug will insert a new course
// NOTE: Do not use this to create new courses
// TODO: Replace this script with the course editor
load('bower_components/lodash/dist/lodash.js');
var courses =
[
{
_id: ObjectId('560f1a9f22961295f9427742'),
name: "Introduction to Computer Science",
slug: "introduction-to-computer-science",
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
@ -20,6 +24,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('5632661322961295f9428638'),
name: "Computer Science 2",
slug: "computer-science-2",
campaignID: ObjectId("562f88e84df18473073c74e2"),
@ -30,6 +35,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('5789587aad86a6efb573701e'),
name: "Game Development 1",
slug: "game-development-1",
campaignID: ObjectId("5789236960deed1f00ec2ab8"),
@ -39,6 +45,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('5789587aad86a6efb573701f'),
name: "Web Development 1",
slug: "web-development-1",
campaignID: ObjectId("578913f2c8871ac2326fa3e4"),
@ -48,6 +55,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('56462f935afde0c6fd30fc8c'),
name: "Computer Science 3",
slug: "computer-science-3",
campaignID: ObjectId("56462ac4410c528505e1160a"),
@ -58,6 +66,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('57b621e7ad86a6efb5737e64'),
name: "Game Development 2",
slug: "game-development-2",
campaignID: ObjectId("57b49c866430272000100c4d"),
@ -67,6 +76,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('5789587aad86a6efb5737020'),
name: "Web Development 2",
slug: "web-development-2",
campaignID: ObjectId("57891570c8871ac2326fa3f8"),
@ -76,6 +86,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('56462f935afde0c6fd30fc8d'),
name: "Computer Science 4",
slug: "computer-science-4",
campaignID: ObjectId("56462c1133f1478605ebd018"),
@ -86,6 +97,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('569ed916efa72b0ced971447'),
name: "Computer Science 5",
slug: "computer-science-5",
campaignID: ObjectId("568ad069a6584820004437f2"),
@ -96,6 +108,7 @@ var courses =
releasePhase: 'released'
},
{
_id: ObjectId('57a0dea5ad86a6efb57375c2'),
name: "JS Primer",
slug: "js-primer",
campaignID: ObjectId("579a5f37843ad12000e6d4c7"),
@ -123,7 +136,7 @@ _.forEach(courses, function(course) {
print("Updating courses..");
for (var i = 0; i < courses.length; i++) {
var result = db.courses.update({slug: courses[i].slug}, {$set: courses[i]});
var result = db.courses.update({_id: courses[i]._id}, {$set: courses[i]});
if (result.nMatched !== 1) {
print("Failed to update " + courses[i].slug);
print(JSON.stringify(result, null, 2));

View file

@ -2,6 +2,7 @@ mongoose = require 'mongoose'
config = require '../../server_config'
plugins = require '../plugins/plugins'
jsonSchema = require '../../app/schemas/models/course.schema'
{sortCourses} = require '../../app/core/utils'
CourseSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
@ -18,45 +19,8 @@ CourseSchema.statics.editableProperties = [
CourseSchema.statics.jsonSchema = jsonSchema
CourseSchema.statics.sortCourses = (courses) ->
ordering = [
'introduction-to-computer-science'
'computer-science-2'
'game-dev-1'
'game-development-1'
'web-dev-1'
'web-development-1'
'computer-science-3'
'game-dev-2'
'game-development-2'
'web-dev-2'
'web-development-2'
'computer-science-4'
'game-dev-3'
'game-development-3'
'web-dev-3'
'web-development-3'
'computer-science-5'
'game-dev-4'
'game-development-4'
'web-dev-4'
'web-development-4'
'computer-science-6'
'game-dev-5'
'game-development-5'
'web-dev-5'
'web-development-5'
'computer-science-7'
'game-dev-6'
'game-development-6'
'web-dev-6'
'web-development-6'
'computer-science-8'
]
_.sortBy courses, (course) ->
index = ordering.indexOf(course.get?('slug') or course.slug)
index = 9001 if index is -1
index
sortCourses(courses)
CourseSchema.post 'init', (doc) ->
if !doc.get('i18nCoverage')
doc.set('i18nCoverage', [])

View file

@ -376,7 +376,7 @@ UserSchema.pre('save', (next) ->
if not @allowEmailNames # for testing
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
if filter.test(name)
return next(new errors.UnprocessableEntity('Name may not be an email'))
return next(new errors.UnprocessableEntity('Username may not be an email'))
@set('nameLower', name.toLowerCase())
else