mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
2728691739
19 changed files with 144 additions and 114 deletions
|
@ -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
|
||||
}
|
||||
)()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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', [])
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue