Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-28 22:18:35 -07:00
commit ee07d53671
58 changed files with 837 additions and 249 deletions

View file

@ -72,6 +72,12 @@ module.exports = class CocoRouter extends Backbone.Router
'github/*path': 'routeToServer' 'github/*path': 'routeToServer'
'i18n': go('i18n/I18NHomeView')
'i18n/thang/:handle': go('i18n/I18NEditThangTypeView')
'i18n/component/:handle': go('i18n/I18NEditComponentView')
'i18n/level/:handle': go('i18n/I18NEditLevelView')
'i18n/achievement/:handle': go('i18n/I18NEditAchievementView')
'legal': go('LegalView') 'legal': go('LegalView')
'multiplayer': go('MultiplayerView') 'multiplayer': go('MultiplayerView')

View file

@ -175,3 +175,7 @@ prunePath = (delta, path) ->
prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined
keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t') keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t')
delete delta[path[0]] if keys.length is 0 delete delta[path[0]] if keys.length is 0
module.exports.DOC_SKIP_PATHS = [
'_id','version', 'commitMessage', 'parent', 'created',
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers']

View file

@ -28,7 +28,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
about: "Sobre Nosaltres" about: "Sobre Nosaltres"
contact: "Contacta" contact: "Contacta"
twitter_follow: "Segueix-nos" twitter_follow: "Segueix-nos"
# teachers: "Teachers" teachers: "Profesors"
modal: modal:
close: "Tancar" close: "Tancar"
@ -56,7 +56,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
account: "Conta" # Tooltip on account button from /play account: "Conta" # Tooltip on account button from /play
settings: "Configuració" # Tooltip on settings button from /play settings: "Configuració" # Tooltip on settings button from /play
next: "Següent" # Go from choose hero to choose inventory before playing a level next: "Següent" # Go from choose hero to choose inventory before playing a level
# change_hero: "Change Hero" # Go back from choose inventory to choose hero change_hero: "Canviar heroi" # Go back from choose inventory to choose hero
choose_inventory: "Equipar objectes" choose_inventory: "Equipar objectes"
older_campaigns: "Campanyes antigues" older_campaigns: "Campanyes antigues"
anonymous: "Jugador anònim" anonymous: "Jugador anònim"
@ -132,7 +132,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
body: "Cos" body: "Cos"
version: "Versió" version: "Versió"
# commit_msg: "Commit Message" # commit_msg: "Commit Message"
# version_history: "Version History" version_history: "Historial de versions"
# version_history_for: "Version History for: " # version_history_for: "Version History for: "
result: "Resultat" result: "Resultat"
results: "Resultats" results: "Resultats"
@ -148,8 +148,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# opponent: "Opponent" # opponent: "Opponent"
# rank: "Rank" # rank: "Rank"
score: "Puntuació" score: "Puntuació"
# win: "Win" win: "Guanyats"
# loss: "Loss" loss: "Perduts"
# tie: "Tie" # tie: "Tie"
easy: "Fàcil" easy: "Fàcil"
medium: "Intermedi" medium: "Intermedi"
@ -175,7 +175,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
play_level: play_level:
done: "Fet" done: "Fet"
home: "Inici" home: "Inici"
# skip: "Skip" skip: "Ometre"
game_menu: "Menu de joc" game_menu: "Menu de joc"
guide: "Guia" guide: "Guia"
# restart: "Restart" # restart: "Restart"
@ -200,10 +200,10 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# victory_play_skip: "Skip Ahead" # victory_play_skip: "Skip Ahead"
victory_play_next_level: "Jugar el següent nivell" victory_play_next_level: "Jugar el següent nivell"
# victory_play_more_practice: "More Practice" # victory_play_more_practice: "More Practice"
# victory_play_too_easy: "Too Easy" victory_play_too_easy: "Massa fàcil"
# victory_play_just_right: "Just Right" # victory_play_just_right: "Just Right"
# victory_play_too_hard: "Too Hard" victory_play_too_hard: "Massa difícil"
# victory_saving_progress: "Saving Progress" victory_saving_progress: "Desa progrés"
victory_go_home: "Tornar a l'inici" # Only in old-style levels. victory_go_home: "Tornar a l'inici" # Only in old-style levels.
victory_review: "Diguens més!" # Only in old-style levels. victory_review: "Diguens més!" # Only in old-style levels.
# victory_hour_of_code_done: "Are You Done?" # victory_hour_of_code_done: "Are You Done?"
@ -302,7 +302,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# attack: "Damage" # Can also translate as "Attack" # attack: "Damage" # Can also translate as "Attack"
health: "Salut" health: "Salut"
speed: "Velocitat" speed: "Velocitat"
# skills: "Skills" skills: "Habilitats"
save_load: save_load:
granularity_saved_games: "Desats" granularity_saved_games: "Desats"
@ -482,7 +482,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# revert: "Revert" # revert: "Revert"
# revert_models: "Revert Models" # revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain" # pick_a_terrain: "Pick A Terrain"
# small: "Small" small: "Petit"
# grassy: "Grassy" # grassy: "Grassy"
# fork_title: "Fork New Version" # fork_title: "Fork New Version"
# fork_creating: "Creating Fork..." # fork_creating: "Creating Fork..."
@ -492,11 +492,11 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
live_chat: "Xat en directe" live_chat: "Xat en directe"
# level_some_options: "Some Options?" # level_some_options: "Some Options?"
# level_tab_thangs: "Thangs" # level_tab_thangs: "Thangs"
# level_tab_scripts: "Scripts" level_tab_scripts: "Scripts"
level_tab_settings: "Configuració" level_tab_settings: "Configuració"
level_tab_components: "Components" level_tab_components: "Components"
level_tab_systems: "Sistemes" level_tab_systems: "Sistemes"
# level_tab_docs: "Documentation" level_tab_docs: "Documentació"
# level_tab_thangs_title: "Current Thangs" # level_tab_thangs_title: "Current Thangs"
level_tab_thangs_all: "Tot" level_tab_thangs_all: "Tot"
# level_tab_thangs_conditions: "Starting Conditions" # level_tab_thangs_conditions: "Starting Conditions"
@ -508,13 +508,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
level_component_tab_title: "Components actuals" level_component_tab_title: "Components actuals"
# level_component_btn_new: "Create New Component" # level_component_btn_new: "Create New Component"
level_systems_tab_title: "Sistemes actuals" level_systems_tab_title: "Sistemes actuals"
# level_systems_btn_new: "Create New System" level_systems_btn_new: "Crea un nou sistema"
level_systems_btn_add: "Afegir sistema" level_systems_btn_add: "Afegir sistema"
# level_components_title: "Back to All Thangs" # level_components_title: "Back to All Thangs"
# level_components_type: "Type" # level_components_type: "Type"
# level_component_edit_title: "Edit Component" # level_component_edit_title: "Edit Component"
# level_component_config_schema: "Config Schema" # level_component_config_schema: "Config Schema"
# level_component_settings: "Settings" level_component_settings: "Configuració"
# level_system_edit_title: "Edit System" # level_system_edit_title: "Edit System"
create_system_title: "Crea un nou sistema" create_system_title: "Crea un nou sistema"
# new_component_title: "Create New Component" # new_component_title: "Create New Component"
@ -537,9 +537,9 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# achievement_query_goals: "Key achievement off of level goals" # achievement_query_goals: "Key achievement off of level goals"
# level_completion: "Level Completion" # level_completion: "Level Completion"
# article: article:
# edit_btn_preview: "Preview" edit_btn_preview: "Vista previa"
# edit_article_title: "Edit Article" edit_article_title: "Editar l'article"
contribute: contribute:
# page_title: "Contributing" # page_title: "Contributing"
@ -783,8 +783,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# merge_conflict_with: "MERGE CONFLICT WITH" # merge_conflict_with: "MERGE CONFLICT WITH"
no_changes: "Sense canvis" no_changes: "Sense canvis"
# guide: guide:
# temp: "Temp" temp: "Temp"
multiplayer: multiplayer:
multiplayer_title: "Configuració multijugador" # We'll be changing this around significantly soon. Until then, it's not important to translate. multiplayer_title: "Configuració multijugador" # We'll be changing this around significantly soon. Until then, it's not important to translate.
@ -808,13 +808,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# practices_description: "These are our promises to you, the player, in slightly less legalese." # practices_description: "These are our promises to you, the player, in slightly less legalese."
privacy_title: "Privacitat" privacy_title: "Privacitat"
# privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent." # privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
# security_title: "Security" security_title: "Seguretat"
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems." # security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
# email_title: "Email" # email_title: "Email"
# email_description_prefix: "We will not inundate you with spam. Through" # email_description_prefix: "We will not inundate you with spam. Through"
# email_settings_url: "your email settings" # email_settings_url: "your email settings"
# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time." # email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
# cost_title: "Cost" cost_title: "Cost"
# cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:" # cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
# recruitment_title: "Recruitment" # recruitment_title: "Recruitment"
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizardnot just in the game, but also in real life." # recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizardnot just in the game, but also in real life."
@ -900,7 +900,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
profile_for_prefix: "Perfil de " profile_for_prefix: "Perfil de "
profile_for_suffix: "" profile_for_suffix: ""
featured: "Destacat" featured: "Destacat"
# not_featured: "Not Featured" not_featured: "Sense destacar"
looking_for: "Buscant:" looking_for: "Buscant:"
last_updated: "Ultima actualització:" last_updated: "Ultima actualització:"
contact: "Contacta" contact: "Contacta"
@ -1059,7 +1059,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# av_entities_user_code_problems_list_url: "User Code Problems List" # av_entities_user_code_problems_list_url: "User Code Problems List"
av_other_sub_title: "Altres" av_other_sub_title: "Altres"
# av_other_debug_base_url: "Base (for debugging base.jade)" # av_other_debug_base_url: "Base (for debugging base.jade)"
# u_title: "User List" u_title: "Llista d'usuaris"
# ucp_title: "User Code Problems" # ucp_title: "User Code Problems"
# lg_title: "Latest Games" # lg_title: "Latest Games"
# clas: "CLAs" # clas: "CLAs"

View file

@ -536,6 +536,7 @@
achievement_query_misc: "Key achievement off of miscellanea" achievement_query_misc: "Key achievement off of miscellanea"
achievement_query_goals: "Key achievement off of level goals" achievement_query_goals: "Key achievement off of level goals"
level_completion: "Level Completion" level_completion: "Level Completion"
pop_i18n: "Populate I18N"
article: article:
edit_btn_preview: "Preview" edit_btn_preview: "Preview"

View file

@ -13,6 +13,10 @@ module.exports = class Achievement extends CocoModel
func = @get('function', true) func = @get('function', true)
return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators
save: ->
@populateI18N()
super(arguments...)
@styleMapping: @styleMapping:
1: 'achievement-wood' 1: 'achievement-wood'
2: 'achievement-stone' 2: 'achievement-stone'

View file

@ -233,7 +233,7 @@ class CocoModel extends Backbone.Model
getDelta: -> getDelta: ->
differ = deltasLib.makeJSONDiffer() differ = deltasLib.makeJSONDiffer()
differ.diff @_revertAttributes, @attributes differ.diff(_.omit(@_revertAttributes, deltasLib.DOC_SKIP_PATHS), _.omit(@attributes, deltasLib.DOC_SKIP_PATHS))
getDeltaWith: (otherModel) -> getDeltaWith: (otherModel) ->
differ = deltasLib.makeJSONDiffer() differ = deltasLib.makeJSONDiffer()
@ -272,9 +272,11 @@ class CocoModel extends Backbone.Model
sum = 0 sum = 0
data ?= $.extend true, {}, @attributes data ?= $.extend true, {}, @attributes
schema ?= @schema() or {} schema ?= @schema() or {}
addedI18N = false
if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n? if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
data.i18n = {'-':'-'} # mongoose doesn't work with empty objects data.i18n = {'-':{'-':'-'}} # mongoose doesn't work with empty objects
sum += 1 sum += 1
addedI18N = true
if _.isPlainObject data if _.isPlainObject data
for key, value of data for key, value of data
@ -287,6 +289,7 @@ class CocoModel extends Backbone.Model
if schema.items and _.isArray data if schema.items and _.isArray data
sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data
@set('i18n', data.i18n) if addedI18N and not path # need special case for root i18n
@updateI18NCoverage() @updateI18NCoverage()
sum sum
@ -343,10 +346,8 @@ class CocoModel extends Backbone.Model
updateI18NCoverage: -> updateI18NCoverage: ->
i18nObjects = @findI18NObjects() i18nObjects = @findI18NObjects()
console.log 'i18n objects', i18nObjects return unless i18nObjects.length
langCodeArrays = (_.keys(i18n) for i18n in i18nObjects) langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
console.log 'lang code arrays', langCodeArrays
window.codes = langCodeArrays
@set('i18nCoverage', _.intersection(langCodeArrays...)) @set('i18nCoverage', _.intersection(langCodeArrays...))
findI18NObjects: (data, results) -> findI18NObjects: (data, results) ->

View file

@ -12,6 +12,7 @@ module.exports = class LevelComponent extends CocoModel
@LandID: '524b7aff7fc0f6d519000006' @LandID: '524b7aff7fc0f6d519000006'
@CollidesID: '524b7b857fc0f6d519000012' @CollidesID: '524b7b857fc0f6d519000012'
@PlansID: '524b7b517fc0f6d51900000d' @PlansID: '524b7b517fc0f6d51900000d'
@ProgrammableID: '524b7b5a7fc0f6d51900000e'
urlRoot: '/db/level.component' urlRoot: '/db/level.component'
set: (key, val, options) -> set: (key, val, options) ->

View file

@ -78,10 +78,7 @@ _.extend AchievementSchema.properties,
default: {kind: 'linear', parameters: {}} default: {kind: 'linear', parameters: {}}
required: ['kind', 'parameters'] required: ['kind', 'parameters']
additionalProperties: false additionalProperties: false
i18n: c.object i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this achievement'}
format: 'i18n'
props: ['name', 'description']
description: 'Help translate this achievement'
rewards: c.RewardSchema 'awarded by this achievement' rewards: c.RewardSchema 'awarded by this achievement'
@ -93,5 +90,6 @@ _.extend AchievementSchema, # Let's have these on the bottom
AchievementSchema.definitions = {} AchievementSchema.definitions = {}
AchievementSchema.definitions['mongoQueryOperator'] = MongoQueryOperatorSchema AchievementSchema.definitions['mongoQueryOperator'] = MongoQueryOperatorSchema
AchievementSchema.definitions['mongoFindQuery'] = MongoFindQuerySchema AchievementSchema.definitions['mongoFindQuery'] = MongoFindQuerySchema
c.extendTranslationCoverageProperties AchievementSchema
module.exports = AchievementSchema module.exports = AchievementSchema

View file

@ -23,6 +23,12 @@ PropertyDocumentationSchema = c.object {
required: ['name', 'type', 'description'] required: ['name', 'type', 'description']
}, },
name: {type: 'string', title: 'Name', description: 'Name of the property.'} name: {type: 'string', title: 'Name', description: 'Name of the property.'}
i18n: { type: 'object', format: 'i18n', props: ['description', 'context'], description: 'Help translate this property'}
context: {
type: 'object'
title: 'Example template context'
additionalProperties: { type: 'string' }
}
codeLanguages: c.array {title: 'Specific Code Languages', description: 'If present, then only the languages specified will show this documentation. Leave unset for language-independent documentation.', format: 'code-languages-array'}, c.shortString(title: 'Code Language', description: 'A specific code language to show this documentation for.', format: 'code-language') codeLanguages: c.array {title: 'Specific Code Languages', description: 'If present, then only the languages specified will show this documentation. Leave unset for language-independent documentation.', format: 'code-languages-array'}, c.shortString(title: 'Code Language', description: 'A specific code language to show this documentation for.', format: 'code-language')
# not actual JS types, just whatever they describe... # not actual JS types, just whatever they describe...
type: c.shortString(title: 'Type', description: 'Intended type of the property.') type: c.shortString(title: 'Type', description: 'Intended type of the property.')
@ -84,6 +90,7 @@ PropertyDocumentationSchema = c.object {
} }
{title: 'Description', type: 'string', description: 'Description of the return value.', maxLength: 1000} {title: 'Description', type: 'string', description: 'Description of the return value.', maxLength: 1000}
] ]
i18n: { type: 'object', format: 'i18n', props: ['description'], description: 'Help translate this return value'}
DependencySchema = c.object { DependencySchema = c.object {
title: 'Component Dependency' title: 'Component Dependency'
@ -155,5 +162,6 @@ c.extendSearchableProperties LevelComponentSchema
c.extendVersionedProperties LevelComponentSchema, 'level.component' c.extendVersionedProperties LevelComponentSchema, 'level.component'
c.extendPermissionsProperties LevelComponentSchema, 'level.component' c.extendPermissionsProperties LevelComponentSchema, 'level.component'
c.extendPatchableProperties LevelComponentSchema c.extendPatchableProperties LevelComponentSchema
c.extendTranslationCoverageProperties LevelComponentSchema
module.exports = LevelComponentSchema module.exports = LevelComponentSchema

View file

@ -116,6 +116,7 @@ _.extend ThangTypeSchema.properties,
rotationType: {title: 'Rotation', type: 'string', enum: ['isometric', 'fixed', 'free']} rotationType: {title: 'Rotation', type: 'string', enum: ['isometric', 'fixed', 'free']}
matchWorldDimensions: {title: 'Match World Dimensions', type: 'boolean'} matchWorldDimensions: {title: 'Match World Dimensions', type: 'boolean'}
shadow: {title: 'Shadow Diameter', type: 'number', format: 'meters', description: 'Shadow diameter in meters'} shadow: {title: 'Shadow Diameter', type: 'number', format: 'meters', description: 'Shadow diameter in meters'}
description: { type:'string', format: 'markdown', title: 'Description' }
layerPriority: layerPriority:
title: 'Layer Priority' title: 'Layer Priority'
type: 'integer' type: 'integer'
@ -144,6 +145,7 @@ _.extend ThangTypeSchema.properties,
type: 'number' type: 'number'
description: 'Snap to this many meters in the y-direction.' description: 'Snap to this many meters in the y-direction.'
components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on 'original', not whole thing components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on 'original', not whole thing
i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this ThangType\'s name and description.'}
ThangTypeSchema.required = [] ThangTypeSchema.required = []
@ -158,5 +160,6 @@ c.extendBasicProperties ThangTypeSchema, 'thang.type'
c.extendSearchableProperties ThangTypeSchema c.extendSearchableProperties ThangTypeSchema
c.extendVersionedProperties ThangTypeSchema, 'thang.type' c.extendVersionedProperties ThangTypeSchema, 'thang.type'
c.extendPatchableProperties ThangTypeSchema c.extendPatchableProperties ThangTypeSchema
c.extendTranslationCoverageProperties ThangTypeSchema
module.exports = ThangTypeSchema module.exports = ThangTypeSchema

View file

@ -166,6 +166,7 @@ me.FunctionArgumentSchema = me.object {
required: ['name', 'type', 'example', 'description'] required: ['name', 'type', 'example', 'description']
}, },
name: {type: 'string', pattern: me.identifierPattern, title: 'Name', description: 'Name of the function argument.'} name: {type: 'string', pattern: me.identifierPattern, title: 'Name', description: 'Name of the function argument.'}
i18n: { type: 'object', format: 'i18n', props: ['description'], description: 'Help translate this argument'}
# not actual JS types, just whatever they describe... # not actual JS types, just whatever they describe...
type: me.shortString(title: 'Type', description: 'Intended type of the argument.') type: me.shortString(title: 'Type', description: 'Intended type of the argument.')
example: example:

View file

@ -1,3 +1,27 @@
#guide-view #guide-view, #settings-treema .treema-markdown
h3 .nav-tabs
text-decoration: underline height: 41px
.tab-content
padding-top: 20px
margin-bottom: 50px
li:not(.active) a[data-toggle="tab"]
cursor: pointer
img
display: block
margin: 0 auto
img + em
display: block
margin: 0 auto
text-align: center
hr
border-color: #5c5c5c
width: 80%
table
width: 80%
margin: 20px 10%

View file

@ -0,0 +1,12 @@
.i18n-edit-model-view
#patch-submit
margin-top: 5px
td
width: 40%
.outer-content
padding: 10px
select
margin-bottom: 10px

View file

@ -1,25 +0,0 @@
#docs-modal .modal-dialog, #settings-treema .treema-markdown
width: 800px
.tab-content
padding-top: 20px
li:not(.active) a[data-toggle="tab"]
cursor: pointer
img
display: block
margin: 0 auto
img + em
display: block
margin: 0 auto
text-align: center
hr
border-color: #5c5c5c
width: 80%
table
width: 80%
margin: 20px 10%

View file

@ -34,6 +34,8 @@ nav.navbar.navbar-default(role='navigation')
if !me.get('anonymous') if !me.get('anonymous')
li#create-new-component-button li#create-new-component-button
a(data-i18n="editor.level_component_b_new") Create New Component a(data-i18n="editor.level_component_b_new") Create New Component
li
a(data-i18n="editor.pop_i18n")#pop-component-i18n-button Populate i18n
li.divider li.divider
li.dropdown-header Info li.dropdown-header Info

View file

@ -56,6 +56,8 @@ block header
a(data-i18n="common.fork")#fork-start-button Fork a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
li.divider li.divider
li.dropdown-header Info li.dropdown-header Info
li#history-button li#history-button

View file

@ -6,13 +6,15 @@ block modal-body-content
.button.close(type="button", data-dismiss="modal", aria-hidden="true") × .button.close(type="button", data-dismiss="modal", aria-hidden="true") ×
.tabbable.tabs-left .tabbable.tabs-left
- var submenus = ["inventory", "choose-hero", "save-load", "options", "guide", "multiplayer"] - var submenus = ["inventory", "choose-hero", "save-load", "options", "guide", "multiplayer"]
- if (!showDevBits) { // Not done yet. - if (!showsGuide) {
- submenus.splice(4, 1); - submenus.splice(4, 1);
- }
- if (!showDevBits) { // Not done yet.
- submenus.splice(2, 1); - submenus.splice(2, 1);
- } - }
- if (!showInventory) - if (!showInventory)
- submenus.splice(0, 1); - submenus.splice(0, 1);
ul.nav.nav-tabs ul.nav.nav-tabs#game-menu-nav
for submenu, index in submenus for submenu, index in submenus
li(class=index ? "" : "active") li(class=index ? "" : "active")
a(href='#' + submenu + '-view', data-toggle='tab') a(href='#' + submenu + '-view', data-toggle='tab')

View file

@ -1,7 +1,7 @@
h3 Guide, and stuff ul.nav.nav-tabs
for doc in docs
div(data-i18n="guide.temp") Temp li
a(data-target="#docs_tab_#{doc.slug}", data-toggle="tab") #{doc.name}
p ... and more stuff div.tab-content
for doc in docs
p Just put the existing guide in there pretty much as it is. Also add tab for keyboard shortcuts. div.tab-pane(id="docs_tab_#{doc.slug}")!= doc.html

View file

@ -0,0 +1,71 @@
extends /templates/base
block header
if model.loading
nav.navbar.navbar-default(role='navigation')
.container-fluid
ul.nav.navbar-nav
li
a(href="/i18n")
span.glyphicon-home.glyphicon
else
nav.navbar.navbar-default(role='navigation')
ul.nav.navbar-nav
li
a(href="/i18n")
span.glyphicon-home.glyphicon
.navbar-header
span.navbar-brand #{model.get('name')}
ul.nav.navbar-nav.navbar-right
li
button.btn.btn-info.btn-sm.pull-right#patch-submit(disabled=model.hasLocalChanges() ? null : 'disabled', value=model.id) Submit Changes
li.dropdown
a(data-toggle='dropdown')
span.glyphicon-chevron-down.glyphicon
ul.dropdown-menu
li.dropdown-header Actions
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li.divider
li.dropdown-header Info
li#history-button
a(href='#', data-i18n="general.version_history") Version History
li.divider
li.dropdown-header Help
li
a(href='https://github.com/codecombat/codecombat/wiki', data-i18n="editor.wiki", target="_blank") Wiki
li
a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat
li
a(href='http://discourse.codecombat.com/category/diplomat', data-i18n="nav.forum", target="_blank") Forum
li
a(data-toggle="coco-modal", data-target="modal/ContactModal", data-i18n="nav.contact") Email
block outer_content
.outer-content
select.form-control#language-select
table.table
for row in translationList
tr(data-format=row.format || '')
th= row.title
td.english-value-row
div= row.enValue
td.to-value-row
if row.format === 'markdown'
div(data-index=row.index.toString())= row.toValue
else
input.input-sm.form-control.translation-input(data-index=row.index.toString(), value=row.toValue)
div#error-view
.clearfix
block footer

View file

@ -0,0 +1,23 @@
extends /templates/base
block content
.progress
.progress-bar.progress-bar-info(role="progressbar" aria-valuenow=progress aria-valuemin="0" aria-valuemax="100" style="width: "+progress+"%")= progress+"%"
table.table.table-condensed
tr
th
select#language-select.form-control.input-sm
th Type
th Specifically Covered
th Generally Covered
for model in collection.models
tr
td
a(href=model.i18nURLBase+model.get('slug'))= model.get('name')
td= model.constructor.className
td(class=model.specificallyCovered ? 'success' : 'danger')= model.specificallyCovered ? 'Yes' : 'No'
td(class=model.generallyCovered ? 'success' : 'danger')= model.generallyCovered ? 'Yes' : 'No'

View file

@ -21,9 +21,6 @@ h4.title
button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu
if showsGuide
button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
if spectateGame if spectateGame
button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game! button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game!

View file

@ -1,16 +0,0 @@
extends /templates/modal/modal_base
block modal-header-content
h3(data-i18n="play_level.guide_title") Guide
block modal-body-content
ul.nav.nav-tabs
for doc in docs
li
a(data-target="#docs_tab_#{doc.slug}", data-toggle="tab") #{doc.name}
div.tab-content
for doc in docs
div.tab-pane(id="docs_tab_#{doc.slug}")!= doc.html
block modal-footer-content
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close

View file

@ -1 +1 @@
span.doc-title= doc.title span.doc-title(data-property-name=doc.name)= doc.title

View file

@ -267,6 +267,11 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
res = (r for r in res when r[0] isnt '-') res = (r for r in res when r[0] isnt '-')
res res
populateData: ->
super()
if Object.keys(@data).length is 0
@data['-'] = {'-':'-'} # also to get around mongoose bug
getChildSchema: (key) -> getChildSchema: (key) ->
#construct the child schema here #construct the child schema here
@ -281,7 +286,12 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
@workingSchema.props = (prop for prop,_ of @parent.schema.properties when prop isnt 'i18n') @workingSchema.props = (prop for prop,_ of @parent.schema.properties when prop isnt 'i18n')
for i18nProperty in @workingSchema.props for i18nProperty in @workingSchema.props
i18nChildSchema.properties[i18nProperty] = @parent.schema.properties[i18nProperty] parentSchemaProperties = @parent.schema.properties ? {}
for extraSchemas in [@parent.schema.oneOf, @parent.schema.anyOf]
for extraSchema in extraSchemas ? []
for prop, schema of extraSchema?.properties ? {}
parentSchemaProperties[prop] ?= schema
i18nChildSchema.properties[i18nProperty] = parentSchemaProperties[i18nProperty]
return i18nChildSchema return i18nChildSchema
#this must be filled out in order for the i18n node to work #this must be filled out in order for the i18n node to work

View file

@ -93,14 +93,14 @@ module.exports = class DeltaView extends CocoView
treemaOptions = { schema: deltaData.schema or {}, readOnly: true } treemaOptions = { schema: deltaData.schema or {}, readOnly: true }
if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value') if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value')
options = _.defaults {data: deltaData.left}, treemaOptions options = _.defaults {data: _.merge({}, deltaData.left)}, treemaOptions
try try
TreemaNode.make(leftEl, options).build() TreemaNode.make(leftEl, options).build()
catch error catch error
console.error "Couldn't show left details Treema for", deltaData.left, treemaOptions console.error "Couldn't show left details Treema for", deltaData.left, treemaOptions
if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value') if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value')
options = _.defaults {data: deltaData.right}, treemaOptions options = _.defaults {data: _.merge({}, deltaData.right)}, treemaOptions
try try
TreemaNode.make(rightEl, options).build() TreemaNode.make(rightEl, options).build()
catch error catch error

View file

@ -2,15 +2,13 @@ ModalView = require 'views/kinds/ModalView'
template = require 'templates/editor/patch_modal' template = require 'templates/editor/patch_modal'
DeltaView = require 'views/editor/DeltaView' DeltaView = require 'views/editor/DeltaView'
auth = require 'lib/auth' auth = require 'lib/auth'
deltasLib = require 'lib/deltas'
module.exports = class PatchModal extends ModalView module.exports = class PatchModal extends ModalView
id: 'patch-modal' id: 'patch-modal'
template: template template: template
plain: true plain: true
modalWidthPercent: 60 modalWidthPercent: 60
@DOC_SKIP_PATHS = [
'_id','version', 'commitMessage', 'parent', 'created',
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers']
events: events:
'click #withdraw-button': 'withdrawPatch' 'click #withdraw-button': 'withdrawPatch'
@ -54,7 +52,7 @@ module.exports = class PatchModal extends ModalView
afterRender: -> afterRender: ->
return super() unless @supermodel.finished() and @deltaWorked return super() unless @supermodel.finished() and @deltaWorked
@deltaView = new DeltaView({model:@pendingModel, headModel:@headModel, skipPaths: PatchModal.DOC_SKIP_PATHS}) @deltaView = new DeltaView({model:@pendingModel, headModel:@headModel, skipPaths: deltasLib.DOC_SKIP_PATHS})
changeEl = @$el.find('.changes-stub') changeEl = @$el.find('.changes-stub')
@insertSubView(@deltaView, changeEl) @insertSubView(@deltaView, changeEl)
super() super()

View file

@ -43,7 +43,7 @@ module.exports = class LevelEditView extends RootView
'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs') 'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs')
'click #level-patch-button': 'startPatchingLevel' 'click #level-patch-button': 'startPatchingLevel'
'click #level-watch-button': 'toggleWatchLevel' 'click #level-watch-button': 'toggleWatchLevel'
'click #pop-level-i18n-button': -> @level.populateI18N() 'click #pop-level-i18n-button': 'onPopulateI18N'
'click a[href="#editor-level-documentation"]': 'onClickDocumentationTab' 'click a[href="#editor-level-documentation"]': 'onClickDocumentationTab'
'mouseup .nav-tabs > li a': 'toggleTab' 'mouseup .nav-tabs > li a': 'toggleTab'
@ -164,6 +164,11 @@ module.exports = class LevelEditView extends RootView
@level.watch(button.find('.watch').is(':visible')) @level.watch(button.find('.watch').is(':visible'))
button.find('> span').toggleClass('secret') button.find('> span').toggleClass('secret')
onPopulateI18N: ->
@level.populateI18N()
f = -> document.location.reload()
setTimeout(f, 200)
toggleTab: (e) -> toggleTab: (e) ->
@renderScrollbar() @renderScrollbar()
return unless $(document).width() <= 800 return unless $(document).width() <= 800

View file

@ -20,6 +20,7 @@ module.exports = class LevelComponentEditView extends CocoView
'click #component-history-button': 'showVersionHistory' 'click #component-history-button': 'showVersionHistory'
'click #patch-component-button': 'startPatchingComponent' 'click #patch-component-button': 'startPatchingComponent'
'click #component-watch-button': 'toggleWatchComponent' 'click #component-watch-button': 'toggleWatchComponent'
'click #pop-component-i18n-button': 'onPopulateI18N'
constructor: (options) -> constructor: (options) ->
super options super options
@ -133,6 +134,10 @@ module.exports = class LevelComponentEditView extends CocoView
@levelComponent.watch(button.find('.watch').is(':visible')) @levelComponent.watch(button.find('.watch').is(':visible'))
button.find('> span').toggleClass('secret') button.find('> span').toggleClass('secret')
onPopulateI18N: ->
@levelComponent.populateI18N()
@render()
destroy: -> destroy: ->
@destroyAceEditor(@editor) @destroyAceEditor(@editor)
@componentSettingsTreema?.destroy() @componentSettingsTreema?.destroy()

View file

@ -5,6 +5,7 @@ LevelComponent = require 'models/LevelComponent'
LevelSystem = require 'models/LevelSystem' LevelSystem = require 'models/LevelSystem'
DeltaView = require 'views/editor/DeltaView' DeltaView = require 'views/editor/DeltaView'
PatchModal = require 'views/editor/PatchModal' PatchModal = require 'views/editor/PatchModal'
deltasLib = require 'lib/deltas'
module.exports = class SaveLevelModal extends SaveVersionModal module.exports = class SaveLevelModal extends SaveVersionModal
template: template template: template
@ -40,7 +41,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal
for changeEl, i in changeEls for changeEl, i in changeEls
model = models[i] model = models[i]
try try
deltaView = new DeltaView({model: model, skipPaths: PatchModal.DOC_SKIP_PATHS}) deltaView = new DeltaView({model: model, skipPaths: deltasLib.DOC_SKIP_PATHS})
@insertSubView(deltaView, $(changeEl)) @insertSubView(deltaView, $(changeEl))
catch e catch e
console.error 'Couldn\'t create delta view:', e console.error 'Couldn\'t create delta view:', e
@ -95,6 +96,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal
@showLoading() @showLoading()
tuples = _.zip(modelsToSave, formsToSave) tuples = _.zip(modelsToSave, formsToSave)
for [newModel, form] in tuples for [newModel, form] in tuples
newModel.updateI18NCoverage() if newModel.get('i18nCoverage')
res = newModel.save() res = newModel.save()
do (newModel, form) => do (newModel, form) =>
res.error => res.error =>

View file

@ -45,6 +45,7 @@ module.exports = class ThangTypeEditView extends RootView
'click .play-with-level-button': 'onPlayLevel' 'click .play-with-level-button': 'onPlayLevel'
'click .play-with-level-parent': 'onPlayLevelSelect' 'click .play-with-level-parent': 'onPlayLevelSelect'
'keyup .play-with-level-input': 'onPlayLevelKeyUp' 'keyup .play-with-level-input': 'onPlayLevelKeyUp'
'click #pop-level-i18n-button': 'onPopulateLevelI18N'
subscriptions: subscriptions:
'editor:thang-type-color-groups-changed': 'onColorGroupsChanged' 'editor:thang-type-color-groups-changed': 'onColorGroupsChanged'
@ -352,6 +353,7 @@ module.exports = class ThangTypeEditView extends RootView
saveNewThangType: (e) -> saveNewThangType: (e) ->
newThangType = if e.major then @thangType.cloneNewMajorVersion() else @thangType.cloneNewMinorVersion() newThangType = if e.major then @thangType.cloneNewMajorVersion() else @thangType.cloneNewMinorVersion()
newThangType.set('commitMessage', e.commitMessage) newThangType.set('commitMessage', e.commitMessage)
newThangType.updateI18NCoverage() if newThangType.get('i18nCoverage')
res = newThangType.save() res = newThangType.save()
return unless res return unless res
@ -404,9 +406,9 @@ module.exports = class ThangTypeEditView extends RootView
pushChangesToPreview: => pushChangesToPreview: =>
return if @temporarilyIgnoringChanges return if @temporarilyIgnoringChanges
# TODO: This doesn't delete old Treema keys you deleted for key of @thangType.attributes
for key, value of @treema.data continue if key is 'components'
@thangType.set(key, value) @thangType.set(key, @treema.data[key])
@updateSelectBox() @updateSelectBox()
@refreshAnimation() @refreshAnimation()
@updateDots() @updateDots()
@ -455,6 +457,10 @@ module.exports = class ThangTypeEditView extends RootView
showVersionHistory: (e) -> showVersionHistory: (e) ->
@openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID @openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
onPopulateLevelI18N: ->
@thangType.populateI18N()
_.delay((-> document.location.reload()), 500)
openSaveModal: -> openSaveModal: ->
@openModalView new SaveVersionModal model: @thangType @openModalView new SaveVersionModal model: @thangType

View file

@ -16,7 +16,7 @@ module.exports = class GameMenuModal extends ModalView
events: events:
'change input.select': 'onSelectionChanged' 'change input.select': 'onSelectionChanged'
'shown.bs.tab .nav-tabs a': 'onTabShown' 'shown.bs.tab #game-menu-nav a': 'onTabShown'
constructor: (options) -> constructor: (options) ->
super options super options
@ -30,6 +30,8 @@ module.exports = class GameMenuModal extends ModalView
context = super(context) context = super(context)
context.showDevBits = @options.showDevBits context.showDevBits = @options.showDevBits
context.showInventory = @options.showInventory context.showInventory = @options.showInventory
docs = @options.level.get('documentation') ? {}
context.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length
context context
afterRender: -> afterRender: ->

View file

@ -1,17 +1,58 @@
CocoView = require 'views/kinds/CocoView' CocoView = require 'views/kinds/CocoView'
template = require 'templates/game-menu/guide-view' template = require 'templates/game-menu/guide-view'
{me} = require 'lib/auth' Article = require 'models/Article'
ThangType = require 'models/ThangType' utils = require 'lib/utils'
module.exports = class GuideView extends CocoView # let's implement this once we have the docs database schema set up
module.exports = class LevelGuideView extends CocoView
template: template
id: 'guide-view' id: 'guide-view'
className: 'tab-pane' className: 'tab-pane'
template: template
getRenderData: (context={}) -> constructor: (options) ->
context = super(context) @firstOnly = options.firstOnly
context @docs = options?.docs ? options.level.get('documentation') ? {}
general = @docs.generalArticles or []
specific = @docs.specificArticles or []
articles = options.supermodel.getModels(Article)
articleMap = {}
articleMap[article.get('original')] = article for article in articles
general = (articleMap[ref.original] for ref in general)
general = (article.attributes for article in general when article)
@docs = specific.concat(general)
@docs = $.extend(true, [], @docs)
@docs = [@docs[0]] if @firstOnly and @docs[0]
doc.html = marked(utils.i18n doc, 'body') for doc in @docs
doc.name = (utils.i18n doc, 'name') for doc in @docs
doc.slug = _.string.slugify(doc.name) for doc in @docs
super()
getRenderData: ->
c = super()
c.docs = @docs
c
afterRender: -> afterRender: ->
super() super()
#Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1 # no, only when the tab is activated if @docs.length is 1
@$el.html(@docs[0].html)
else
# incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex
@$el.find('.nav-tabs li:first').addClass('active')
@$el.find('.tab-content .tab-pane:first').addClass('active')
@$el.find('.nav-tabs a').click(@clickTab)
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1
clickTab: (e) =>
@$el.find('li.active').removeClass('active')
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-tab-switch', volume: 1
afterInsert: ->
super()
Backbone.Mediator.publish 'level:docs-shown', {}
onHidden: ->
Backbone.Mediator.publish 'level:docs-hidden', {}

View file

@ -336,6 +336,7 @@ module.exports = class InventoryView extends CocoView
gearByLevel = gearByLevel =
'dungeons-of-kithgard': {feet: 'simple-boots'} 'dungeons-of-kithgard': {feet: 'simple-boots'}
'gems-in-the-deep': {feet: 'simple-boots'} 'gems-in-the-deep': {feet: 'simple-boots'}
'forgetful-gemsmith': {feet: 'simple-boots'}
'shadow-guard': {feet: 'simple-boots'} 'shadow-guard': {feet: 'simple-boots'}
'true-names': {feet: 'simple-boots', 'right-hand': 'longsword'} 'true-names': {feet: 'simple-boots', 'right-hand': 'longsword'}
'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic'} 'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic'}
@ -345,9 +346,11 @@ module.exports = class InventoryView extends CocoView
'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'longsword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'longsword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', eyes: 'crude-glasses'} 'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', eyes: 'crude-glasses'}
'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer'} 'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer', torso: 'leather-tunic'}
'defense-of-plainswood': {feet: 'simple-boots', 'right-hand': 'builders-hammer'} 'defense-of-plainswood': {feet: 'simple-boots', 'right-hand': 'builders-hammer'}
# TODO: figure out leather boots for plainswood (or next one?) 'winding-trail': {feet: 'leather-boots', 'right-hand': 'builders-hammer'}
'thornbush-farm': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'}
'a-fiery-trap': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'}
return unless necessaryGear = gearByLevel[@options.levelID] return unless necessaryGear = gearByLevel[@options.levelID]
if @inserted if @inserted
if @supermodel.finished() if @supermodel.finished()

View file

@ -0,0 +1,16 @@
I18NEditModelView = require './I18NEditModelView'
Achievement = require 'models/Achievement'
module.exports = class I18NEditAchievementView extends I18NEditModelView
id: "i18n-edit-component-view"
modelClass: Achievement
buildTranslationList: ->
lang = @selectedLanguage
# name, description
if i18n = @model.get('i18n')
if name = @model.get('name')
@wrapRow "Achievement name", ['name'], name, i18n[lang]?.name, []
if description = @model.get('description')
@wrapRow "Achievement description", ['description'], description, i18n[lang]?.description, []

View file

@ -0,0 +1,47 @@
I18NEditModelView = require './I18NEditModelView'
LevelComponent = require 'models/LevelComponent'
module.exports = class I18NEditComponentView extends I18NEditModelView
id: "i18n-edit-component-view"
modelClass: LevelComponent
buildTranslationList: ->
lang = @selectedLanguage
propDocs = @model.get('propertyDocumentation')
for propDoc, propDocIndex in propDocs
#- Component property descriptions
if i18n = propDoc.i18n
path = ["propertyDocumentation", propDocIndex]
if _.isObject propDoc.description
for progLang, description of propDoc
@wrapRow "#{propDoc.name} description (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown'
else if _.isString propDoc.description
@wrapRow "#{propDoc.name} description", ['description'], propDoc.description, i18n[lang]?.description, path, 'markdown'
if context = propDoc.context
for key, value of context
@wrapRow "#{propDoc.name} context value", ["context", key], value, i18n[lang]?.context[key], path
#- Component return value descriptions
if i18n = propDoc.returns?.i18n
path = ["propertyDocumentation", propDocIndex, "returns"]
d = propDoc.returns.description
if _.isObject d
for progLang, description of d
@wrapRow "#{propDoc.name} return val (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown'
else if _.isString d
@wrapRow "#{propDoc.name} return val", ['description'], d, i18n[lang]?.description, path, 'markdown'
#- Component argument descriptions
if propDoc.args
for argDoc, argIndex in propDoc.args
if i18n = argDoc.i18n
path = ["propertyDocumentation", propDocIndex, 'args', argIndex]
if _.isObject argDoc.description
for progLang, description of argDoc
@wrapRow "#{propDoc.name} arg description #{argDoc.name} (#{progLang})", [progLang, 'description'], description, i18n[lang]?[progLang]?.description, path, 'markdown'
else if _.isString argDoc.description
@wrapRow "#{propDoc.name} arg description #{argDoc.name}", ['description'], argDoc.description, i18n[lang]?.description, path, 'markdown'

View file

@ -0,0 +1,58 @@
I18NEditModelView = require './I18NEditModelView'
Level = require 'models/Level'
LevelComponent = require 'models/LevelComponent'
module.exports = class I18NEditLevelView extends I18NEditModelView
id: "i18n-edit-level-view"
modelClass: Level
buildTranslationList: ->
lang = @selectedLanguage
# name, description
if i18n = @model.get('i18n')
if name = @model.get('name')
@wrapRow "Level name", ['name'], name, i18n[lang]?.name, []
if description = @model.get('description')
@wrapRow "Level description", ['description'], description, i18n[lang]?.description, []
# goals
for goal, index in @model.get('goals') ? []
if i18n = goal.i18n
@wrapRow "Goal name", ['name'], goal.name, i18n[lang]?.name, ['goals', index]
# documentation
for doc, index in @model.get('documentation')?.specificArticles ? []
if i18n = doc.i18n
@wrapRow "Guide article name", ['name'], doc.name, i18n[lang]?.name, ['documentation', 'specificArticles', index]
@wrapRow "'#{doc.name}' description", ['description'], doc.description, i18n[lang]?.description, ['documentation', 'specificArticles', index], 'markdown'
# sprite dialogues
for script, scriptIndex in @model.get('scripts') ? []
for noteGroup, noteGroupIndex in script.noteChain ? []
for spriteCommand, spriteCommandIndex in noteGroup.sprites ? []
pathPrefix = ['scripts', scriptIndex, 'noteChain', noteGroupIndex, 'sprites', spriteCommandIndex, 'say']
if i18n = spriteCommand.say?.i18n
if spriteCommand.say.text
@wrapRow "Sprite text", ['text'], spriteCommand.say.text, i18n[lang]?.text, pathPrefix, 'markdown'
if spriteCommand.say.blurb
@wrapRow "Sprite blurb", ['blurb'], spriteCommand.say.blurb, i18n[lang]?.blurb, pathPrefix
for response, responseIndex in spriteCommand.say?.responses ? []
if i18n = response.i18n
@wrapRow "Response button", ['text'], response.text, i18n[lang]?.text, pathPrefix.concat(['responses', responseIndex])
# victory modal
if i18n = @model.get('victory')?.i18n
@wrapRow "Victory text", ['body'], @model.get('victory').body, i18n[lang]?.body, ['victory'], 'markdown'
# code comments
for thang, thangIndex in @model.get('thangs') ? []
for component, componentIndex in thang.components ? []
continue unless component.original is LevelComponent.ProgrammableID
for methodName, method of component.config?.programmableMethods ? {}
if (i18n = method.i18n) and (context = method.context)
for key, value of context
path = ['thangs', thangIndex, 'components', componentIndex, 'config', 'programmableMethods', methodName]
@wrapRow "Code comment", ["context", key], value, i18n[lang]?.context[key], path

View file

@ -0,0 +1,159 @@
RootView = require 'views/kinds/RootView'
locale = require 'locale/locale'
Patch = require 'models/Patch'
template = require 'templates/i18n/i18n-edit-model-view'
deltasLib = require 'lib/deltas'
module.exports = class I18NEditModelView extends RootView
className: 'editor i18n-edit-model-view'
template: template
events:
'change .translation-input': 'onInputChanged'
'change #language-select': 'onLanguageSelectChanged'
'click #patch-submit': 'onSubmitPatch'
constructor: (options, @modelHandle) ->
super(options)
@model = new @modelClass(_id: @modelHandle)
@model = @supermodel.loadModel(@model, 'model').model
@model.saveBackups = true
@selectedLanguage = me.get('preferredLanguage', true)
showLoading: ($el) ->
$el ?= @$el.find('.outer-content')
super($el)
onLoaded: ->
super()
@model.markToRevert() unless @model.hasLocalChanges()
getRenderData: ->
c = super()
c.model = @model
c.selectedLanguage = @selectedLanguage
@translationList = []
if @supermodel.finished() then @buildTranslationList() else []
result.index = index for result, index in @translationList
c.translationList = @translationList
c
afterRender: ->
super()
@hush = true
$select = @$el.find('#language-select').empty()
@addLanguagesToSelect($select, @selectedLanguage)
@hush = false
editors = []
@$el.find('tr[data-format="markdown"]').each((index, el) =>
englishEditor = ace.edit(enEl=$(el).find('.english-value-row div')[0])
englishEditor.el = enEl
englishEditor.setReadOnly(true)
toEditor = ace.edit(toEl=$(el).find('.to-value-row div')[0])
toEditor.el = toEl
toEditor.on 'change', @onEditorChange
editors = editors.concat([englishEditor, toEditor])
)
for editor in editors
session = editor.getSession()
session.setTabSize 2
session.setMode 'ace/mode/markdown'
session.setNewLineMode = 'unix'
session.setUseSoftTabs true
editor.setOptions({ maxLines: Infinity })
onEditorChange: (event, editor) =>
return if @destroyed
index = $(editor.el).data('index')
rowInfo = @translationList[index]
value = editor.getValue()
@onTranslationChanged(rowInfo, value)
wrapRow: (title, key, enValue, toValue, path, format) ->
@translationList.push {
title: title,
key: key,
enValue: enValue,
toValue: toValue or '',
path: path
format: format
}
buildTranslationList: -> [] # overwrite
onInputChanged: (e) ->
index = $(e.target).data('index')
rowInfo = @translationList[index]
value = $(e.target).val()
@onTranslationChanged(rowInfo, value)
onTranslationChanged: (rowInfo, value) ->
#- Navigate down to where the translation will live
base = @model.attributes
for seg in rowInfo.path
base = base[seg]
base = base.i18n
base[@selectedLanguage] ?= {}
base = base[@selectedLanguage]
if rowInfo.key.length > 1
for seg in rowInfo.key[..-2]
base[seg] ?= {}
base = base[seg]
#- Set the data in a non-kosher way
base[rowInfo.key[rowInfo.key.length-1]] = value
@model.saveBackup()
#- Enable patch submit button
@$el.find('#patch-submit').attr('disabled', null)
onLanguageSelectChanged: (e) ->
return if @hush
@selectedLanguage = $(e.target).val()
@render()
onSubmitPatch: (e) ->
delta = @model.getDelta()
flattened = deltasLib.flattenDelta(delta)
save = _.all(flattened, (delta) ->
return _.isArray(delta.o) and delta.o.length is 1 and 'i18n' in delta.dataPath
)
if save
modelToSave = @model.cloneNewMinorVersion()
modelToSave.updateI18NCoverage() if modelToSave.get('i18nCoverage')
else
modelToSave = new Patch()
modelToSave.set 'delta', @model.getDelta()
modelToSave.set 'target', {
'collection': _.string.underscored @model.constructor.className
'id': @model.id
}
if @modelClass.schema.properties.commitMessage
commitMessage = "Diplomat submission for lang #{@selectedLanguage}: #{flattened.length} change(s)."
modelToSave.set 'commitMessage', commitMessage
errors = modelToSave.validate()
button = $(e.target)
button.attr('disabled', 'disabled')
return button.text('Failed to Submit Changes') if errors
res = modelToSave.save()
return button.text('Failed to Submit Changes') unless res
res.error => button.text('Error Submitting Changes')
res.success => button.text('Submit Changes')

View file

@ -0,0 +1,15 @@
I18NEditModelView = require './I18NEditModelView'
ThangType = require 'models/ThangType'
module.exports = class ThangTypeI18NView extends I18NEditModelView
id: "thang-type-i18n-view"
modelClass: ThangType
buildTranslationList: ->
lang = @selectedLanguage
@model.markToRevert() unless @model.hasLocalChanges()
i18n = @model.get('i18n')
if i18n
name = @model.get('name')
@wrapRow('Name', ['name'], name, i18n[lang]?.name, [])
@wrapRow('Description', ['description'], @model.get('description'), i18n[lang]?.description, [], 'markdown')

View file

@ -0,0 +1,98 @@
RootView = require 'views/kinds/RootView'
template = require 'templates/i18n/i18n-home-view'
CocoCollection = require 'collections/CocoCollection'
LevelComponent = require 'models/LevelComponent'
ThangType = require 'models/ThangType'
Level = require 'models/Level'
Achievement = require 'models/Achievement'
languages = _.keys(require 'locale/locale').sort()
PAGE_SIZE = 100
module.exports = class I18NHomeView extends RootView
id: "i18n-home-view"
template: template
events:
'change #language-select': 'onLanguageSelectChanged'
constructor: (options) ->
super(options)
@selectedLanguage = me.get('preferredLanguage', true)
#-
@aggregateModels = new Backbone.Collection()
@aggregateModels.comparator = (m) ->
return 2 if m.specificallyCovered
return 1 if m.generallyCovered
return 0
project = ['name', 'components.original', 'i18nCoverage', 'slug']
@thangTypes = new CocoCollection([], { url: '/db/thang.type?view=i18n-coverage', project: project, model: ThangType })
@components = new CocoCollection([], { url: '/db/level.component?view=i18n-coverage', project: project, model: LevelComponent })
@levels = new CocoCollection([], { url: '/db/level?view=i18n-coverage', project: project, model: Level })
@achievements = new CocoCollection([], { url: '/db/achievement?view=i18n-coverage', project: project, model: Achievement })
for c in [@thangTypes, @components, @levels, @achievements]
c.skip = 0
c.fetch({data: {skip: 0, limit: PAGE_SIZE}, cache:false})
@supermodel.loadCollection(c, 'documents')
@listenTo c, 'sync', @onCollectionSynced
onCollectionSynced: (collection) ->
for model in collection.models
model.i18nURLBase = switch model.constructor.className
when "ThangType" then "/i18n/thang/"
when "LevelComponent" then "/i18n/component/"
when "Achievement" then "/i18n/achievement/"
when "Level" then "/i18n/level/"
getMore = collection.models.length is PAGE_SIZE
@aggregateModels.add(collection.models)
@render()
if getMore
collection.skip += PAGE_SIZE
collection.fetch({data: {skip: collection.skip, limit: PAGE_SIZE}})
getRenderData: ->
c = super()
@updateCoverage()
c.languages = languages
c.selectedLanguage = @selectedLanguage
c.collection = @aggregateModels
covered = (m for m in @aggregateModels.models when m.specificallyCovered).length
total = @aggregateModels.models.length
c.progress = if total then parseInt(100 * covered / total) else 100
c
updateCoverage: ->
selectedBase = @selectedLanguage[..2]
relatedLanguages = (l for l in languages when l.startsWith(selectedBase) and l isnt @selectedLanguage)
for model in @aggregateModels.models
@updateCoverageForModel(model, relatedLanguages)
model.generallyCovered = true if @selectedLanguage.startsWith 'en'
@aggregateModels.sort()
updateCoverageForModel: (model, relatedLanguages) ->
model.specificallyCovered = true
model.generallyCovered = true
coverage = model.get('i18nCoverage')
if @selectedLanguage not in coverage
model.specificallyCovered = false
if not _.any((l in coverage for l in relatedLanguages))
model.generallyCovered = false
return
afterRender: ->
super()
@addLanguagesToSelect(@$el.find('#language-select'), @selectedLanguage)
onLanguageSelectChanged: (e) ->
@selectedLanguage = $(e.target).val()
@render()

View file

@ -106,15 +106,20 @@ module.exports = class RootView extends CocoView
$select.parent().find('.options, .trigger').remove() $select.parent().find('.options, .trigger').remove()
$select.unwrap().removeClass('fancified') $select.unwrap().removeClass('fancified')
preferred = me.get('preferredLanguage', true) preferred = me.get('preferredLanguage', true)
@addLanguagesToSelect($select, preferred)
$select.fancySelect().parent().find('.trigger').addClass('header-font')
$('body').attr('lang', preferred)
addLanguagesToSelect: ($select, initialVal) ->
initialVal ?= me.get('preferredLanguage', true)
codes = _.keys(locale) codes = _.keys(locale)
genericCodes = _.filter codes, (code) -> genericCodes = _.filter codes, (code) ->
_.find(codes, (code2) -> _.find(codes, (code2) ->
code2 isnt code and code2.split('-')[0] is code) code2 isnt code and code2.split('-')[0] is code)
for code, localeInfo of locale when not (code in genericCodes) or code is preferred for code, localeInfo of locale when not (code in genericCodes) or code is initialVal
$select.append( $select.append(
$('<option></option>').val(code).text(localeInfo.nativeDescription)) $('<option></option>').val(code).text(localeInfo.nativeDescription))
$select.val(preferred).fancySelect().parent().find('.trigger').addClass('header-font') $select.val(initialVal)
$('body').attr('lang', preferred)
onLanguageChanged: -> onLanguageChanged: ->
newLang = $('.language-dropdown').val() newLang = $('.language-dropdown').val()

View file

@ -4,6 +4,7 @@ DeltaView = require 'views/editor/DeltaView'
PatchModal = require 'views/editor/PatchModal' PatchModal = require 'views/editor/PatchModal'
nameLoader = require 'lib/NameLoader' nameLoader = require 'lib/NameLoader'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
deltasLib = require 'lib/deltas'
class VersionsViewCollection extends CocoCollection class VersionsViewCollection extends CocoCollection
url: '' url: ''
@ -55,7 +56,7 @@ module.exports = class VersionsModal extends ModalView
@deltaView = new DeltaView({ @deltaView = new DeltaView({
model: earlierVersion model: earlierVersion
comparisonModel: laterVersion comparisonModel: laterVersion
skipPaths: PatchModal.DOC_SKIP_PATHS skipPaths: deltasLib.DOC_SKIP_PATHS
loadModels: true loadModels: true
}) })
@insertSubView(@deltaView, deltaEl) @insertSubView(@deltaView, deltaEl)

View file

@ -2,7 +2,6 @@ CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/control_bar' template = require 'templates/play/level/control_bar'
{me} = require 'lib/auth' {me} = require 'lib/auth'
LevelGuideModal = require './modal/LevelGuideModal'
GameMenuModal = require 'views/game-menu/GameMenuModal' GameMenuModal = require 'views/game-menu/GameMenuModal'
RealTimeCollection = require 'collections/RealTimeCollection' RealTimeCollection = require 'collections/RealTimeCollection'
@ -16,16 +15,9 @@ module.exports = class ControlBarView extends CocoView
'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame' 'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame'
events: events:
'click #docs-button': ->
window.tracker?.trackEvent 'Clicked Docs', level: @level.get('name'), label: @level.get('name')
@showGuideModal()
'click #next-game-button': -> Backbone.Mediator.publish 'level:next-game-pressed', {} 'click #next-game-button': -> Backbone.Mediator.publish 'level:next-game-pressed', {}
'click #game-menu-button': 'showGameMenuModal' 'click #game-menu-button': 'showGameMenuModal'
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} 'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
'click .home a': 'onClickHome' 'click .home a': 'onClickHome'
constructor: (options) -> constructor: (options) ->
@ -69,30 +61,10 @@ module.exports = class ControlBarView extends CocoView
c.multiplayerSession = @multiplayerSession if @multiplayerSession c.multiplayerSession = @multiplayerSession if @multiplayerSession
c.multiplayerPlayers = @multiplayerPlayers if @multiplayerPlayers c.multiplayerPlayers = @multiplayerPlayers if @multiplayerPlayers
c.meID = me.id c.meID = me.id
docs = @level.get('documentation') ? {}
c.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length
c c
afterRender: ->
super()
@guideHighlightInterval ?= setInterval @onGuideHighlight, 5 * 60 * 1000
destroy: ->
clearInterval @guideHighlightInterval if @guideHighlightInterval
super()
onGuideHighlight: =>
return if @destroyed or @guideShownOnce
@$el.find('#docs-button').hide().show('highlight', 4000)
showGuideModal: ->
options = {docs: @level.get('documentation'), supermodel: @supermodel}
@openModalView(new LevelGuideModal(options))
clearInterval @guideHighlightInterval
@guideHighlightInterval = null
showGameMenuModal: -> showGameMenuModal: ->
@openModalView new GameMenuModal level: @level, session: @session @openModalView new GameMenuModal level: @level, session: @session, supermodel: @supermodel
onClickHome: (e) -> onClickHome: (e) ->
e.preventDefault() e.preventDefault()

View file

@ -142,37 +142,6 @@ module.exports = class PlayLevelView extends RootView
# CocoView overridden methods ############################################### # CocoView overridden methods ###############################################
updateProgress: (progress) ->
super(progress)
return if @seenDocs
return if @isIPadApp()
return unless @levelLoader.session.loaded and @levelLoader.level.loaded
return unless showFrequency = @levelLoader.level.get('showsGuide')
session = @levelLoader.session
diff = new Date().getTime() - new Date(session.get('created')).getTime()
return if showFrequency is 'first-time' and diff > (5 * 60 * 1000)
articles = @levelLoader.supermodel.getModels Article
for article in articles
return unless article.loaded
@showGuide()
showGuide: ->
@seenDocs = true
LevelGuideModal = require './modal/LevelGuideModal'
options =
docs: @levelLoader.level.get('documentation')
supermodel: @supermodel
firstOnly: true
@openModalView(new LevelGuideModal(options), true)
onGuideOpened = (e) ->
@guideOpenTime = new Date()
onGuideClosed = (e) ->
application.tracker?.trackTiming new Date() - @guideOpenTime, 'Intro Guide Time', @levelID, @levelID, 100
@onLevelStarted()
Backbone.Mediator.subscribeOnce 'modal:opened', onGuideOpened, @
Backbone.Mediator.subscribeOnce 'modal:closed', onGuideClosed, @
return true
getRenderData: -> getRenderData: ->
c = super() c = super()
c.world = @world c.world = @world

View file

@ -1,60 +0,0 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/play/level/modal/docs'
Article = require 'models/Article'
utils = require 'lib/utils'
# let's implement this once we have the docs database schema set up
module.exports = class LevelGuideModal extends ModalView
template: template
id: 'docs-modal'
shortcuts:
'enter': 'hide'
constructor: (options) ->
@firstOnly = options.firstOnly
@docs = options?.docs ? {}
general = @docs.generalArticles or []
specific = @docs.specificArticles or []
articles = options.supermodel.getModels(Article)
articleMap = {}
articleMap[article.get('original')] = article for article in articles
general = (articleMap[ref.original] for ref in general)
general = (article.attributes for article in general when article)
@docs = specific.concat(general)
@docs = $.extend(true, [], @docs)
@docs = [@docs[0]] if @firstOnly and @docs[0]
doc.html = marked(utils.i18n doc, 'body') for doc in @docs
doc.name = (utils.i18n doc, 'name') for doc in @docs
doc.slug = _.string.slugify(doc.name) for doc in @docs
super()
getRenderData: ->
c = super()
c.docs = @docs
c
afterRender: ->
super()
if @docs.length is 1
@$el.find('.modal-body').html(@docs[0].html)
else
# incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex
@$el.find('.nav-tabs li:first').addClass('active')
@$el.find('.tab-content .tab-pane:first').addClass('active')
@$el.find('.nav-tabs a').click(@clickTab)
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-open', volume: 1
clickTab: (e) =>
@$el.find('li.active').removeClass('active')
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'guide-tab-switch', volume: 1
afterInsert: ->
super()
Backbone.Mediator.publish 'level:docs-shown', {}
onHidden: ->
Backbone.Mediator.publish 'level:docs-hidden', {}

View file

@ -1,6 +1,7 @@
popoverTemplate = require 'templates/play/level/tome/spell_palette_entry_popover' popoverTemplate = require 'templates/play/level/tome/spell_palette_entry_popover'
{downTheChain} = require 'lib/world/world_utils' {downTheChain} = require 'lib/world/world_utils'
window.Vector = require 'lib/world/vector' # So we can document it window.Vector = require 'lib/world/vector' # So we can document it
utils = require 'lib/utils'
safeJSONStringify = (input, maxDepth) -> safeJSONStringify = (input, maxDepth) ->
recursion = (input, path, depth) -> recursion = (input, path, depth) ->
@ -89,11 +90,26 @@ module.exports = class DocFormatter
if @doc.returns if @doc.returns
toTranslate.push {obj: @doc.returns, prop: 'example'}, {obj: @doc.returns, prop: 'description'} toTranslate.push {obj: @doc.returns, prop: 'example'}, {obj: @doc.returns, prop: 'description'}
for {obj, prop} in toTranslate for {obj, prop} in toTranslate
# Translate into chosen code language.
if val = obj[prop]?[@options.language] if val = obj[prop]?[@options.language]
obj[prop] = val obj[prop] = val
else unless _.isString obj[prop] else unless _.isString obj[prop]
obj[prop] = null obj[prop] = null
# Translate into chosen spoken language.
if val = obj[prop]
context = @doc.context
obj[prop] = val = utils.i18n obj, prop
if @doc.i18n
spokenLanguage = me.get 'preferredLanguage'
while spokenLanguage
spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack?
if spokenLanguageContext = @doc.i18n[spokenLanguage]?.context
context = _.merge context, spokenLanguageContext
break
fallingBack = true
obj[prop] = _.template val, context if context
formatPopover: -> formatPopover: ->
content = popoverTemplate doc: @doc, language: @options.language, value: @formatValue(), marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in @doc.args ? []), writable: @options.writable, selectedMethod: @options.selectedMethod, cooldowns: @inferCooldowns(), item: @options.item content = popoverTemplate doc: @doc, language: @options.language, value: @formatValue(), marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in @doc.args ? []), writable: @options.writable, selectedMethod: @options.selectedMethod, cooldowns: @inferCooldowns(), item: @options.item
owner = if @doc.owner is 'this' then @options.thang else window[@doc.owner] owner = if @doc.owner is 'this' then @options.thang else window[@doc.owner]

View file

@ -22,6 +22,8 @@ module.exports = class Spell
@levelType = options.level.get('type', true) @levelType = options.level.get('type', true)
p = options.programmableMethod p = options.programmableMethod
@commentI18N = p.i18n
@commentContext = p.context
@languages = p.languages ? {} @languages = p.languages ? {}
@languages.javascript ?= p.source @languages.javascript ?= p.source
@name = p.name @name = p.name
@ -61,6 +63,18 @@ module.exports = class Spell
setLanguage: (@language) -> setLanguage: (@language) ->
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript #console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
@originalSource = @languages[language] ? @languages.javascript @originalSource = @languages[language] ? @languages.javascript
# Translate comments chosen spoken language.
return unless @commentContext
context = $.extend true, {}, @commentContext
if @commentI18N
spokenLanguage = me.get 'preferredLanguage'
while spokenLanguage
spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack?
if spokenLanguageContext = @commentI18N[spokenLanguage]?.context
context = _.merge context, spokenLanguageContext
break
fallingBack = true
@originalSource = _.template @originalSource, context
addThang: (thang) -> addThang: (thang) ->
if @thangs[thang.id] if @thangs[thang.id]

View file

@ -68,7 +68,8 @@
"node-gyp": "~0.13.0", "node-gyp": "~0.13.0",
"aether": "~0.2.39", "aether": "~0.2.39",
"JASON": "~0.1.3", "JASON": "~0.1.3",
"JQDeferred": "~2.1.0" "JQDeferred": "~2.1.0",
"jsondiffpatch": "0.1.17"
}, },
"devDependencies": { "devDependencies": {
"jade": "0.33.x", "jade": "0.33.x",

View file

@ -64,6 +64,7 @@ AchievementSchema.post 'save', -> @constructor.loadAchievements()
AchievementSchema.plugin(plugins.NamedPlugin) AchievementSchema.plugin(plugins.NamedPlugin)
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
AchievementSchema.plugin plugins.TranslationCoveragePlugin
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements') module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements')

View file

@ -5,13 +5,37 @@ class AchievementHandler extends Handler
modelClass: Achievement modelClass: Achievement
# Used to determine which properties requests may edit # Used to determine which properties requests may edit
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category', 'rewards'] editableProperties: [
'name'
'query'
'worth'
'collection'
'description'
'userField'
'proportionalTo'
'icon'
'function'
'related'
'difficulty'
'category'
'rewards'
'i18n'
'i18nCoverage'
]
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
jsonSchema = require '../../app/schemas/models/achievement.coffee' jsonSchema = require '../../app/schemas/models/achievement.coffee'
hasAccess: (req) -> hasAccess: (req) ->
req.method is 'GET' or req.user?.isAdmin() req.method in ['GET', 'PUT'] or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
method = (method or req.method).toLowerCase()
return true if method is 'get'
return true if req.user?.isAdmin()
return true if method is 'put' and @isJustFillingTranslations(req, document)
return
get: (req, res) -> get: (req, res) ->
# /db/achievement?related=<ID> # /db/achievement?related=<ID>

View file

@ -7,6 +7,7 @@ Patch = require '../patches/Patch'
User = require '../users/User' User = require '../users/User'
sendwithus = require '../sendwithus' sendwithus = require '../sendwithus'
hipchat = require '../hipchat' hipchat = require '../hipchat'
deltasLib = require '../../app/lib/deltas'
PROJECT = {original: 1, name: 1, version: 1, description: 1, slug: 1, kind: 1, created: 1, permissions: 1} PROJECT = {original: 1, name: 1, version: 1, description: 1, slug: 1, kind: 1, created: 1, permissions: 1}
FETCH_LIMIT = 300 FETCH_LIMIT = 300
@ -32,10 +33,23 @@ module.exports = class Handler
hasAccess: (req) -> true hasAccess: (req) -> true
hasAccessToDocument: (req, document, method=null) -> hasAccessToDocument: (req, document, method=null) ->
return true if req.user?.isAdmin() return true if req.user?.isAdmin()
if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() is 'put'
return true if @isJustFillingTranslations(req, document)
if @modelClass.schema.uses_coco_permissions if @modelClass.schema.uses_coco_permissions
return document.hasPermissionsForMethod?(req.user, method or req.method) return document.hasPermissionsForMethod?(req.user, method or req.method)
return true return true
isJustFillingTranslations: (req, document) ->
differ = deltasLib.makeJSONDiffer()
omissions = ['original'].concat(deltasLib.DOC_SKIP_PATHS)
delta = differ.diff(_.omit(document.toObject(), omissions), _.omit(req.body, omissions))
flattened = deltasLib.flattenDelta(delta)
_.all(flattened, (delta) ->
# sometimes coverage gets moved around... allow other changes to happen to i18nCoverage
return _.isArray(delta.o) and (('i18n' in delta.dataPath and delta.o.length is 1) or 'i18nCoverage' in delta.dataPath))
formatEntity: (req, document) -> document?.toObject() formatEntity: (req, document) -> document?.toObject()
getEditableProperties: (req, document) -> getEditableProperties: (req, document) ->
props = @editableProperties.slice() props = @editableProperties.slice()
@ -89,8 +103,29 @@ module.exports = class Handler
specialParameters = ['term', 'project', 'conditions'] specialParameters = ['term', 'project', 'conditions']
if @modelClass.schema.uses_coco_translation_coverage and req.query.view is 'i18n-coverage'
# TODO: generalize view, project, limit and skip query parameters
projection = {}
if req.query.project
projection[field] = 1 for field in req.query.project.split(',')
query = {slug: {$exists: true}, i18nCoverage: {$exists: true}}
q = @modelClass.find(query, projection)
skip = parseInt(req.query.skip)
if skip? and skip < 1000000
q.skip(skip)
limit = parseInt(req.query.limit)
if limit? and limit < 1000
q.limit(limit)
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
# If the model uses coco search it's probably a text search # If the model uses coco search it's probably a text search
if @modelClass.schema.uses_coco_search else if @modelClass.schema.uses_coco_search
term = req.query.term term = req.query.term
matchedObjects = [] matchedObjects = []
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}] filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]

View file

@ -12,6 +12,7 @@ LevelComponentSchema.plugin plugins.PermissionsPlugin
LevelComponentSchema.plugin plugins.VersionedPlugin LevelComponentSchema.plugin plugins.VersionedPlugin
LevelComponentSchema.plugin plugins.SearchablePlugin, {searchable: ['name', 'searchStrings', 'description']} LevelComponentSchema.plugin plugins.SearchablePlugin, {searchable: ['name', 'searchStrings', 'description']}
LevelComponentSchema.plugin plugins.PatchablePlugin LevelComponentSchema.plugin plugins.PatchablePlugin
LevelComponentSchema.plugin plugins.TranslationCoveragePlugin
LevelComponentSchema.pre('save', (next) -> LevelComponentSchema.pre('save', (next) ->
name = @get('name') name = @get('name')
strings = _.str.humanize(name).toLowerCase().split(' ') strings = _.str.humanize(name).toLowerCase().split(' ')

View file

@ -14,6 +14,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
'propertyDocumentation' 'propertyDocumentation'
'configSchema' 'configSchema'
'name' 'name'
'i18nCoverage'
] ]
getEditableProperties: (req, document) -> getEditableProperties: (req, document) ->

View file

@ -28,6 +28,7 @@ LevelHandler = class LevelHandler extends Handler
'banner' 'banner'
'employerDescription' 'employerDescription'
'terrain' 'terrain'
'i18nCoverage'
] ]
postEditableProperties: ['name'] postEditableProperties: ['name']

View file

@ -36,13 +36,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'featureImage' 'featureImage'
'spriteType' 'spriteType'
'i18nCoverage' 'i18nCoverage'
'i18n'
'description'
] ]
hasAccess: (req) -> hasAccess: (req) ->
req.method is 'GET' or req.user?.isAdmin() req.method in ['GET', 'PUT'] or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
method = (method or req.method).toLowerCase()
return true if method is 'get'
return true if req.user?.isAdmin()
return true if method is 'put' and @isJustFillingTranslations(req, document)
return
get: (req, res) -> get: (req, res) ->
if req.query.view in ['items', 'heroes'] if req.query.view in ['items', 'heroes', 'i18n-coverage']
projection = {} projection = {}
if req.query.project if req.query.project
projection[field] = 1 for field in req.query.project.split(',') projection[field] = 1 for field in req.query.project.split(',')
@ -52,7 +61,19 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
else if req.query.view is 'heroes' else if req.query.view is 'heroes'
query.kind = 'Unit' query.kind = 'Unit'
query.original = {$in: _.values heroes} # TODO: replace with some sort of ThangType property later query.original = {$in: _.values heroes} # TODO: replace with some sort of ThangType property later
ThangType.find(query, projection).exec (err, documents) => else if req.query.view is 'i18n-coverage'
query.i18nCoverage = {$exists: true}
q = ThangType.find(query, projection)
skip = parseInt(req.query.skip)
if skip? and skip < 1000000
q.skip(skip)
limit = parseInt(req.query.limit)
if limit? and limit < 1000
q.limit(limit)
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents) documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents) @sendSuccess(res, documents)

View file

@ -124,7 +124,7 @@ module.exports.getTwoGames = (req, res) ->
#if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.') #if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.')
humansGameID = req.body.humansGameID humansGameID = req.body.humansGameID
ogresGameID = req.body.ogresGameID ogresGameID = req.body.ogresGameID
ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival'] ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span', 'dueling-grounds', 'cavern-survival', 'intuit-tf2014']
levelID = _.sample ladderGameIDs levelID = _.sample ladderGameIDs
unless ogresGameID and humansGameID unless ogresGameID and humansGameID
#fetch random games here #fetch random games here

View file

@ -14,6 +14,7 @@ config = require './server_config'
auth = require './server/routes/auth' auth = require './server/routes/auth'
UserHandler = require './server/users/user_handler' UserHandler = require './server/users/user_handler'
global.tv4 = require 'tv4' # required for TreemaUtils to work global.tv4 = require 'tv4' # required for TreemaUtils to work
global.jsondiffpatch = require 'jsondiffpatch'
productionLogging = (tokens, req, res) -> productionLogging = (tokens, req, res) ->
status = res.statusCode status = res.statusCode

View file

@ -76,5 +76,6 @@ describe 'lib/FacebookHandler.coffee', ->
expect(params.gender).toBe(mockMe.gender) expect(params.gender).toBe(mockMe.gender)
expect(params.email).toBe(mockMe.email) expect(params.email).toBe(mockMe.email)
expect(params.facebookID).toBe(mockMe.id) expect(params.facebookID).toBe(mockMe.id)
expect(request.method).toBe('PATCH') #expect(request.method).toBe('PATCH') # PATCHes are PUTs until more proxy servers recognize PATCH
expect(request.method).toBe('PUT')
expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy() expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy()