mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-28 10:06:08 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
ee07d53671
58 changed files with 837 additions and 249 deletions
|
@ -72,6 +72,12 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
|
||||
'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')
|
||||
|
||||
'multiplayer': go('MultiplayerView')
|
||||
|
|
|
@ -175,3 +175,7 @@ prunePath = (delta, path) ->
|
|||
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')
|
||||
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']
|
|
@ -28,7 +28,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
about: "Sobre Nosaltres"
|
||||
contact: "Contacta"
|
||||
twitter_follow: "Segueix-nos"
|
||||
# teachers: "Teachers"
|
||||
teachers: "Profesors"
|
||||
|
||||
modal:
|
||||
close: "Tancar"
|
||||
|
@ -56,7 +56,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
account: "Conta" # Tooltip on account 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
|
||||
# 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"
|
||||
older_campaigns: "Campanyes antigues"
|
||||
anonymous: "Jugador anònim"
|
||||
|
@ -132,7 +132,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
body: "Cos"
|
||||
version: "Versió"
|
||||
# commit_msg: "Commit Message"
|
||||
# version_history: "Version History"
|
||||
version_history: "Historial de versions"
|
||||
# version_history_for: "Version History for: "
|
||||
result: "Resultat"
|
||||
results: "Resultats"
|
||||
|
@ -148,8 +148,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
# opponent: "Opponent"
|
||||
# rank: "Rank"
|
||||
score: "Puntuació"
|
||||
# win: "Win"
|
||||
# loss: "Loss"
|
||||
win: "Guanyats"
|
||||
loss: "Perduts"
|
||||
# tie: "Tie"
|
||||
easy: "Fàcil"
|
||||
medium: "Intermedi"
|
||||
|
@ -175,7 +175,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
play_level:
|
||||
done: "Fet"
|
||||
home: "Inici"
|
||||
# skip: "Skip"
|
||||
skip: "Ometre"
|
||||
game_menu: "Menu de joc"
|
||||
guide: "Guia"
|
||||
# restart: "Restart"
|
||||
|
@ -200,10 +200,10 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
# victory_play_skip: "Skip Ahead"
|
||||
victory_play_next_level: "Jugar el següent nivell"
|
||||
# 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_too_hard: "Too Hard"
|
||||
# victory_saving_progress: "Saving Progress"
|
||||
victory_play_too_hard: "Massa difícil"
|
||||
victory_saving_progress: "Desa progrés"
|
||||
victory_go_home: "Tornar a l'inici" # Only in old-style levels.
|
||||
victory_review: "Diguens més!" # Only in old-style levels.
|
||||
# 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"
|
||||
health: "Salut"
|
||||
speed: "Velocitat"
|
||||
# skills: "Skills"
|
||||
skills: "Habilitats"
|
||||
|
||||
save_load:
|
||||
granularity_saved_games: "Desats"
|
||||
|
@ -482,7 +482,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
# revert: "Revert"
|
||||
# revert_models: "Revert Models"
|
||||
# pick_a_terrain: "Pick A Terrain"
|
||||
# small: "Small"
|
||||
small: "Petit"
|
||||
# grassy: "Grassy"
|
||||
# fork_title: "Fork New Version"
|
||||
# fork_creating: "Creating Fork..."
|
||||
|
@ -492,11 +492,11 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
live_chat: "Xat en directe"
|
||||
# level_some_options: "Some Options?"
|
||||
# level_tab_thangs: "Thangs"
|
||||
# level_tab_scripts: "Scripts"
|
||||
level_tab_scripts: "Scripts"
|
||||
level_tab_settings: "Configuració"
|
||||
level_tab_components: "Components"
|
||||
level_tab_systems: "Sistemes"
|
||||
# level_tab_docs: "Documentation"
|
||||
level_tab_docs: "Documentació"
|
||||
# level_tab_thangs_title: "Current Thangs"
|
||||
level_tab_thangs_all: "Tot"
|
||||
# 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_btn_new: "Create New Component"
|
||||
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_components_title: "Back to All Thangs"
|
||||
# level_components_type: "Type"
|
||||
# level_component_edit_title: "Edit Component"
|
||||
# level_component_config_schema: "Config Schema"
|
||||
# level_component_settings: "Settings"
|
||||
level_component_settings: "Configuració"
|
||||
# level_system_edit_title: "Edit System"
|
||||
create_system_title: "Crea un nou sistema"
|
||||
# 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"
|
||||
# level_completion: "Level Completion"
|
||||
|
||||
# article:
|
||||
# edit_btn_preview: "Preview"
|
||||
# edit_article_title: "Edit Article"
|
||||
article:
|
||||
edit_btn_preview: "Vista previa"
|
||||
edit_article_title: "Editar l'article"
|
||||
|
||||
contribute:
|
||||
# page_title: "Contributing"
|
||||
|
@ -783,8 +783,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
|
|||
# merge_conflict_with: "MERGE CONFLICT WITH"
|
||||
no_changes: "Sense canvis"
|
||||
|
||||
# guide:
|
||||
# temp: "Temp"
|
||||
guide:
|
||||
temp: "Temp"
|
||||
|
||||
multiplayer:
|
||||
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."
|
||||
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."
|
||||
# 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."
|
||||
# email_title: "Email"
|
||||
# email_description_prefix: "We will not inundate you with spam. Through"
|
||||
# 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."
|
||||
# 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:"
|
||||
# recruitment_title: "Recruitment"
|
||||
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not 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_suffix: ""
|
||||
featured: "Destacat"
|
||||
# not_featured: "Not Featured"
|
||||
not_featured: "Sense destacar"
|
||||
looking_for: "Buscant:"
|
||||
last_updated: "Ultima actualització:"
|
||||
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_other_sub_title: "Altres"
|
||||
# av_other_debug_base_url: "Base (for debugging base.jade)"
|
||||
# u_title: "User List"
|
||||
u_title: "Llista d'usuaris"
|
||||
# ucp_title: "User Code Problems"
|
||||
# lg_title: "Latest Games"
|
||||
# clas: "CLAs"
|
||||
|
|
|
@ -536,6 +536,7 @@
|
|||
achievement_query_misc: "Key achievement off of miscellanea"
|
||||
achievement_query_goals: "Key achievement off of level goals"
|
||||
level_completion: "Level Completion"
|
||||
pop_i18n: "Populate I18N"
|
||||
|
||||
article:
|
||||
edit_btn_preview: "Preview"
|
||||
|
|
|
@ -12,6 +12,10 @@ module.exports = class Achievement extends CocoModel
|
|||
getExpFunction: ->
|
||||
func = @get('function', true)
|
||||
return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators
|
||||
|
||||
save: ->
|
||||
@populateI18N()
|
||||
super(arguments...)
|
||||
|
||||
@styleMapping:
|
||||
1: 'achievement-wood'
|
||||
|
|
|
@ -233,7 +233,7 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
getDelta: ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @_revertAttributes, @attributes
|
||||
differ.diff(_.omit(@_revertAttributes, deltasLib.DOC_SKIP_PATHS), _.omit(@attributes, deltasLib.DOC_SKIP_PATHS))
|
||||
|
||||
getDeltaWith: (otherModel) ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
|
@ -272,9 +272,11 @@ class CocoModel extends Backbone.Model
|
|||
sum = 0
|
||||
data ?= $.extend true, {}, @attributes
|
||||
schema ?= @schema() or {}
|
||||
addedI18N = false
|
||||
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
|
||||
addedI18N = true
|
||||
|
||||
if _.isPlainObject data
|
||||
for key, value of data
|
||||
|
@ -287,6 +289,7 @@ class CocoModel extends Backbone.Model
|
|||
if schema.items and _.isArray 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()
|
||||
sum
|
||||
|
||||
|
@ -343,10 +346,8 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
updateI18NCoverage: ->
|
||||
i18nObjects = @findI18NObjects()
|
||||
console.log 'i18n objects', i18nObjects
|
||||
return unless i18nObjects.length
|
||||
langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
|
||||
console.log 'lang code arrays', langCodeArrays
|
||||
window.codes = langCodeArrays
|
||||
@set('i18nCoverage', _.intersection(langCodeArrays...))
|
||||
|
||||
findI18NObjects: (data, results) ->
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = class LevelComponent extends CocoModel
|
|||
@LandID: '524b7aff7fc0f6d519000006'
|
||||
@CollidesID: '524b7b857fc0f6d519000012'
|
||||
@PlansID: '524b7b517fc0f6d51900000d'
|
||||
@ProgrammableID: '524b7b5a7fc0f6d51900000e'
|
||||
urlRoot: '/db/level.component'
|
||||
|
||||
set: (key, val, options) ->
|
||||
|
|
|
@ -78,10 +78,7 @@ _.extend AchievementSchema.properties,
|
|||
default: {kind: 'linear', parameters: {}}
|
||||
required: ['kind', 'parameters']
|
||||
additionalProperties: false
|
||||
i18n: c.object
|
||||
format: 'i18n'
|
||||
props: ['name', 'description']
|
||||
description: 'Help translate this achievement'
|
||||
i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate 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['mongoQueryOperator'] = MongoQueryOperatorSchema
|
||||
AchievementSchema.definitions['mongoFindQuery'] = MongoFindQuerySchema
|
||||
c.extendTranslationCoverageProperties AchievementSchema
|
||||
|
||||
module.exports = AchievementSchema
|
||||
|
|
|
@ -23,6 +23,12 @@ PropertyDocumentationSchema = c.object {
|
|||
required: ['name', 'type', 'description']
|
||||
},
|
||||
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')
|
||||
# not actual JS types, just whatever they describe...
|
||||
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}
|
||||
]
|
||||
i18n: { type: 'object', format: 'i18n', props: ['description'], description: 'Help translate this return value'}
|
||||
|
||||
DependencySchema = c.object {
|
||||
title: 'Component Dependency'
|
||||
|
@ -155,5 +162,6 @@ c.extendSearchableProperties LevelComponentSchema
|
|||
c.extendVersionedProperties LevelComponentSchema, 'level.component'
|
||||
c.extendPermissionsProperties LevelComponentSchema, 'level.component'
|
||||
c.extendPatchableProperties LevelComponentSchema
|
||||
c.extendTranslationCoverageProperties LevelComponentSchema
|
||||
|
||||
module.exports = LevelComponentSchema
|
||||
|
|
|
@ -116,6 +116,7 @@ _.extend ThangTypeSchema.properties,
|
|||
rotationType: {title: 'Rotation', type: 'string', enum: ['isometric', 'fixed', 'free']}
|
||||
matchWorldDimensions: {title: 'Match World Dimensions', type: 'boolean'}
|
||||
shadow: {title: 'Shadow Diameter', type: 'number', format: 'meters', description: 'Shadow diameter in meters'}
|
||||
description: { type:'string', format: 'markdown', title: 'Description' }
|
||||
layerPriority:
|
||||
title: 'Layer Priority'
|
||||
type: 'integer'
|
||||
|
@ -144,6 +145,7 @@ _.extend ThangTypeSchema.properties,
|
|||
type: 'number'
|
||||
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
|
||||
i18n: {type: 'object', format: 'i18n', props: ['name', 'description'], description: 'Help translate this ThangType\'s name and description.'}
|
||||
|
||||
ThangTypeSchema.required = []
|
||||
|
||||
|
@ -158,5 +160,6 @@ c.extendBasicProperties ThangTypeSchema, 'thang.type'
|
|||
c.extendSearchableProperties ThangTypeSchema
|
||||
c.extendVersionedProperties ThangTypeSchema, 'thang.type'
|
||||
c.extendPatchableProperties ThangTypeSchema
|
||||
c.extendTranslationCoverageProperties ThangTypeSchema
|
||||
|
||||
module.exports = ThangTypeSchema
|
||||
|
|
|
@ -166,6 +166,7 @@ me.FunctionArgumentSchema = me.object {
|
|||
required: ['name', 'type', 'example', 'description']
|
||||
},
|
||||
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...
|
||||
type: me.shortString(title: 'Type', description: 'Intended type of the argument.')
|
||||
example:
|
||||
|
|
|
@ -1,3 +1,27 @@
|
|||
#guide-view
|
||||
h3
|
||||
text-decoration: underline
|
||||
#guide-view, #settings-treema .treema-markdown
|
||||
.nav-tabs
|
||||
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%
|
12
app/styles/i18n/i18n-edit-model-view.sass
Normal file
12
app/styles/i18n/i18n-edit-model-view.sass
Normal 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
|
|
@ -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%
|
|
@ -34,7 +34,9 @@ nav.navbar.navbar-default(role='navigation')
|
|||
if !me.get('anonymous')
|
||||
li#create-new-component-button
|
||||
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.dropdown-header Info
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ block header
|
|||
a(data-i18n="common.fork")#fork-start-button Fork
|
||||
li(class=anonymous ? "disabled": "")
|
||||
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.dropdown-header Info
|
||||
li#history-button
|
||||
|
|
|
@ -6,13 +6,15 @@ block modal-body-content
|
|||
.button.close(type="button", data-dismiss="modal", aria-hidden="true") ×
|
||||
.tabbable.tabs-left
|
||||
- var submenus = ["inventory", "choose-hero", "save-load", "options", "guide", "multiplayer"]
|
||||
- if (!showDevBits) { // Not done yet.
|
||||
- if (!showsGuide) {
|
||||
- submenus.splice(4, 1);
|
||||
- }
|
||||
- if (!showDevBits) { // Not done yet.
|
||||
- submenus.splice(2, 1);
|
||||
- }
|
||||
- if (!showInventory)
|
||||
- submenus.splice(0, 1);
|
||||
ul.nav.nav-tabs
|
||||
ul.nav.nav-tabs#game-menu-nav
|
||||
for submenu, index in submenus
|
||||
li(class=index ? "" : "active")
|
||||
a(href='#' + submenu + '-view', data-toggle='tab')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
h3 Guide, and stuff
|
||||
|
||||
div(data-i18n="guide.temp") Temp
|
||||
|
||||
p ... and more stuff
|
||||
|
||||
p Just put the existing guide in there pretty much as it is. Also add tab for keyboard shortcuts.
|
||||
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
|
||||
|
|
71
app/templates/i18n/i18n-edit-model-view.jade
Normal file
71
app/templates/i18n/i18n-edit-model-view.jade
Normal 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
|
23
app/templates/i18n/i18n-home-view.jade
Normal file
23
app/templates/i18n/i18n-home-view.jade
Normal 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'
|
||||
|
|
@ -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
|
||||
|
||||
if showsGuide
|
||||
button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
|
||||
|
||||
if spectateGame
|
||||
button.btn.btn-xs.btn-inverse.banner#next-game-button(title="Next Game", data-i18n="play_level.next-game") Next game!
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1 +1 @@
|
|||
span.doc-title= doc.title
|
||||
span.doc-title(data-property-name=doc.name)= doc.title
|
|
@ -267,6 +267,11 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
|
|||
res = (r for r in res when r[0] isnt '-')
|
||||
res
|
||||
|
||||
populateData: ->
|
||||
super()
|
||||
if Object.keys(@data).length is 0
|
||||
@data['-'] = {'-':'-'} # also to get around mongoose bug
|
||||
|
||||
getChildSchema: (key) ->
|
||||
#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')
|
||||
|
||||
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
|
||||
#this must be filled out in order for the i18n node to work
|
||||
|
||||
|
|
|
@ -93,14 +93,14 @@ module.exports = class DeltaView extends CocoView
|
|||
treemaOptions = { schema: deltaData.schema or {}, readOnly: true }
|
||||
|
||||
if _.isObject(deltaData.left) and leftEl = deltaEl.find('.old-value')
|
||||
options = _.defaults {data: deltaData.left}, treemaOptions
|
||||
options = _.defaults {data: _.merge({}, deltaData.left)}, treemaOptions
|
||||
try
|
||||
TreemaNode.make(leftEl, options).build()
|
||||
catch error
|
||||
console.error "Couldn't show left details Treema for", deltaData.left, treemaOptions
|
||||
|
||||
if _.isObject(deltaData.right) and rightEl = deltaEl.find('.new-value')
|
||||
options = _.defaults {data: deltaData.right}, treemaOptions
|
||||
options = _.defaults {data: _.merge({}, deltaData.right)}, treemaOptions
|
||||
try
|
||||
TreemaNode.make(rightEl, options).build()
|
||||
catch error
|
||||
|
|
|
@ -2,15 +2,13 @@ ModalView = require 'views/kinds/ModalView'
|
|||
template = require 'templates/editor/patch_modal'
|
||||
DeltaView = require 'views/editor/DeltaView'
|
||||
auth = require 'lib/auth'
|
||||
deltasLib = require 'lib/deltas'
|
||||
|
||||
module.exports = class PatchModal extends ModalView
|
||||
id: 'patch-modal'
|
||||
template: template
|
||||
plain: true
|
||||
modalWidthPercent: 60
|
||||
@DOC_SKIP_PATHS = [
|
||||
'_id','version', 'commitMessage', 'parent', 'created',
|
||||
'slug', 'index', '__v', 'patches', 'creator', 'js', 'watchers']
|
||||
|
||||
events:
|
||||
'click #withdraw-button': 'withdrawPatch'
|
||||
|
@ -54,7 +52,7 @@ module.exports = class PatchModal extends ModalView
|
|||
|
||||
afterRender: ->
|
||||
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')
|
||||
@insertSubView(@deltaView, changeEl)
|
||||
super()
|
||||
|
|
|
@ -16,7 +16,7 @@ module.exports = class AchievementEditView extends RootView
|
|||
'click #recalculate-button': 'confirmRecalculation'
|
||||
'click #recalculate-all-button': 'confirmAllRecalculation'
|
||||
'click #delete-button': 'confirmDeletion'
|
||||
|
||||
|
||||
constructor: (options, @achievementID) ->
|
||||
super options
|
||||
@achievement = new Achievement(_id: @achievementID)
|
||||
|
|
|
@ -43,7 +43,7 @@ module.exports = class LevelEditView extends RootView
|
|||
'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs')
|
||||
'click #level-patch-button': 'startPatchingLevel'
|
||||
'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'
|
||||
'mouseup .nav-tabs > li a': 'toggleTab'
|
||||
|
||||
|
@ -163,6 +163,11 @@ module.exports = class LevelEditView extends RootView
|
|||
button = @$el.find('#level-watch-button')
|
||||
@level.watch(button.find('.watch').is(':visible'))
|
||||
button.find('> span').toggleClass('secret')
|
||||
|
||||
onPopulateI18N: ->
|
||||
@level.populateI18N()
|
||||
f = -> document.location.reload()
|
||||
setTimeout(f, 200)
|
||||
|
||||
toggleTab: (e) ->
|
||||
@renderScrollbar()
|
||||
|
|
|
@ -20,6 +20,7 @@ module.exports = class LevelComponentEditView extends CocoView
|
|||
'click #component-history-button': 'showVersionHistory'
|
||||
'click #patch-component-button': 'startPatchingComponent'
|
||||
'click #component-watch-button': 'toggleWatchComponent'
|
||||
'click #pop-component-i18n-button': 'onPopulateI18N'
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
|
@ -133,6 +134,10 @@ module.exports = class LevelComponentEditView extends CocoView
|
|||
@levelComponent.watch(button.find('.watch').is(':visible'))
|
||||
button.find('> span').toggleClass('secret')
|
||||
|
||||
onPopulateI18N: ->
|
||||
@levelComponent.populateI18N()
|
||||
@render()
|
||||
|
||||
destroy: ->
|
||||
@destroyAceEditor(@editor)
|
||||
@componentSettingsTreema?.destroy()
|
||||
|
|
|
@ -5,6 +5,7 @@ LevelComponent = require 'models/LevelComponent'
|
|||
LevelSystem = require 'models/LevelSystem'
|
||||
DeltaView = require 'views/editor/DeltaView'
|
||||
PatchModal = require 'views/editor/PatchModal'
|
||||
deltasLib = require 'lib/deltas'
|
||||
|
||||
module.exports = class SaveLevelModal extends SaveVersionModal
|
||||
template: template
|
||||
|
@ -40,7 +41,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal
|
|||
for changeEl, i in changeEls
|
||||
model = models[i]
|
||||
try
|
||||
deltaView = new DeltaView({model: model, skipPaths: PatchModal.DOC_SKIP_PATHS})
|
||||
deltaView = new DeltaView({model: model, skipPaths: deltasLib.DOC_SKIP_PATHS})
|
||||
@insertSubView(deltaView, $(changeEl))
|
||||
catch e
|
||||
console.error 'Couldn\'t create delta view:', e
|
||||
|
@ -95,6 +96,7 @@ module.exports = class SaveLevelModal extends SaveVersionModal
|
|||
@showLoading()
|
||||
tuples = _.zip(modelsToSave, formsToSave)
|
||||
for [newModel, form] in tuples
|
||||
newModel.updateI18NCoverage() if newModel.get('i18nCoverage')
|
||||
res = newModel.save()
|
||||
do (newModel, form) =>
|
||||
res.error =>
|
||||
|
|
|
@ -45,6 +45,7 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
'click .play-with-level-button': 'onPlayLevel'
|
||||
'click .play-with-level-parent': 'onPlayLevelSelect'
|
||||
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
|
||||
'click #pop-level-i18n-button': 'onPopulateLevelI18N'
|
||||
|
||||
subscriptions:
|
||||
'editor:thang-type-color-groups-changed': 'onColorGroupsChanged'
|
||||
|
@ -352,7 +353,8 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
saveNewThangType: (e) ->
|
||||
newThangType = if e.major then @thangType.cloneNewMajorVersion() else @thangType.cloneNewMinorVersion()
|
||||
newThangType.set('commitMessage', e.commitMessage)
|
||||
|
||||
newThangType.updateI18NCoverage() if newThangType.get('i18nCoverage')
|
||||
|
||||
res = newThangType.save()
|
||||
return unless res
|
||||
modal = $('#save-version-modal')
|
||||
|
@ -404,9 +406,9 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
|
||||
pushChangesToPreview: =>
|
||||
return if @temporarilyIgnoringChanges
|
||||
# TODO: This doesn't delete old Treema keys you deleted
|
||||
for key, value of @treema.data
|
||||
@thangType.set(key, value)
|
||||
for key of @thangType.attributes
|
||||
continue if key is 'components'
|
||||
@thangType.set(key, @treema.data[key])
|
||||
@updateSelectBox()
|
||||
@refreshAnimation()
|
||||
@updateDots()
|
||||
|
@ -455,6 +457,10 @@ module.exports = class ThangTypeEditView extends RootView
|
|||
showVersionHistory: (e) ->
|
||||
@openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
|
||||
|
||||
onPopulateLevelI18N: ->
|
||||
@thangType.populateI18N()
|
||||
_.delay((-> document.location.reload()), 500)
|
||||
|
||||
openSaveModal: ->
|
||||
@openModalView new SaveVersionModal model: @thangType
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ module.exports = class GameMenuModal extends ModalView
|
|||
|
||||
events:
|
||||
'change input.select': 'onSelectionChanged'
|
||||
'shown.bs.tab .nav-tabs a': 'onTabShown'
|
||||
'shown.bs.tab #game-menu-nav a': 'onTabShown'
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
|
@ -30,6 +30,8 @@ module.exports = class GameMenuModal extends ModalView
|
|||
context = super(context)
|
||||
context.showDevBits = @options.showDevBits
|
||||
context.showInventory = @options.showInventory
|
||||
docs = @options.level.get('documentation') ? {}
|
||||
context.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -1,17 +1,58 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
template = require 'templates/game-menu/guide-view'
|
||||
{me} = require 'lib/auth'
|
||||
ThangType = require 'models/ThangType'
|
||||
Article = require 'models/Article'
|
||||
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'
|
||||
className: 'tab-pane'
|
||||
template: template
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super(context)
|
||||
context
|
||||
constructor: (options) ->
|
||||
@firstOnly = options.firstOnly
|
||||
@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: ->
|
||||
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', {}
|
||||
|
|
|
@ -336,6 +336,7 @@ module.exports = class InventoryView extends CocoView
|
|||
gearByLevel =
|
||||
'dungeons-of-kithgard': {feet: 'simple-boots'}
|
||||
'gems-in-the-deep': {feet: 'simple-boots'}
|
||||
'forgetful-gemsmith': {feet: 'simple-boots'}
|
||||
'shadow-guard': {feet: 'simple-boots'}
|
||||
'true-names': {feet: 'simple-boots', 'right-hand': 'longsword'}
|
||||
'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'}
|
||||
'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'}
|
||||
'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'}
|
||||
# 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]
|
||||
if @inserted
|
||||
if @supermodel.finished()
|
||||
|
|
16
app/views/i18n/I18NEditAchievementView.coffee
Normal file
16
app/views/i18n/I18NEditAchievementView.coffee
Normal 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, []
|
47
app/views/i18n/I18NEditComponentView.coffee
Normal file
47
app/views/i18n/I18NEditComponentView.coffee
Normal 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'
|
||||
|
58
app/views/i18n/I18NEditLevelView.coffee
Normal file
58
app/views/i18n/I18NEditLevelView.coffee
Normal 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
|
159
app/views/i18n/I18NEditModelView.coffee
Normal file
159
app/views/i18n/I18NEditModelView.coffee
Normal 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')
|
15
app/views/i18n/I18NEditThangTypeView.coffee
Normal file
15
app/views/i18n/I18NEditThangTypeView.coffee
Normal 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')
|
98
app/views/i18n/I18NHomeView.coffee
Normal file
98
app/views/i18n/I18NHomeView.coffee
Normal 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()
|
|
@ -106,15 +106,20 @@ module.exports = class RootView extends CocoView
|
|||
$select.parent().find('.options, .trigger').remove()
|
||||
$select.unwrap().removeClass('fancified')
|
||||
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)
|
||||
genericCodes = _.filter codes, (code) ->
|
||||
_.find(codes, (code2) ->
|
||||
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(
|
||||
$('<option></option>').val(code).text(localeInfo.nativeDescription))
|
||||
$select.val(preferred).fancySelect().parent().find('.trigger').addClass('header-font')
|
||||
$('body').attr('lang', preferred)
|
||||
$select.val(initialVal)
|
||||
|
||||
onLanguageChanged: ->
|
||||
newLang = $('.language-dropdown').val()
|
||||
|
|
|
@ -4,6 +4,7 @@ DeltaView = require 'views/editor/DeltaView'
|
|||
PatchModal = require 'views/editor/PatchModal'
|
||||
nameLoader = require 'lib/NameLoader'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
deltasLib = require 'lib/deltas'
|
||||
|
||||
class VersionsViewCollection extends CocoCollection
|
||||
url: ''
|
||||
|
@ -55,7 +56,7 @@ module.exports = class VersionsModal extends ModalView
|
|||
@deltaView = new DeltaView({
|
||||
model: earlierVersion
|
||||
comparisonModel: laterVersion
|
||||
skipPaths: PatchModal.DOC_SKIP_PATHS
|
||||
skipPaths: deltasLib.DOC_SKIP_PATHS
|
||||
loadModels: true
|
||||
})
|
||||
@insertSubView(@deltaView, deltaEl)
|
||||
|
|
|
@ -2,7 +2,6 @@ CocoView = require 'views/kinds/CocoView'
|
|||
template = require 'templates/play/level/control_bar'
|
||||
{me} = require 'lib/auth'
|
||||
|
||||
LevelGuideModal = require './modal/LevelGuideModal'
|
||||
GameMenuModal = require 'views/game-menu/GameMenuModal'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
|
||||
|
@ -16,16 +15,9 @@ module.exports = class ControlBarView extends CocoView
|
|||
'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame'
|
||||
|
||||
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 #game-menu-button': 'showGameMenuModal'
|
||||
|
||||
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
|
||||
|
||||
'click .home a': 'onClickHome'
|
||||
|
||||
constructor: (options) ->
|
||||
|
@ -69,30 +61,10 @@ module.exports = class ControlBarView extends CocoView
|
|||
c.multiplayerSession = @multiplayerSession if @multiplayerSession
|
||||
c.multiplayerPlayers = @multiplayerPlayers if @multiplayerPlayers
|
||||
c.meID = me.id
|
||||
docs = @level.get('documentation') ? {}
|
||||
c.showsGuide = docs.specificArticles?.length or docs.generalArticles?.length
|
||||
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: ->
|
||||
@openModalView new GameMenuModal level: @level, session: @session
|
||||
@openModalView new GameMenuModal level: @level, session: @session, supermodel: @supermodel
|
||||
|
||||
onClickHome: (e) ->
|
||||
e.preventDefault()
|
||||
|
|
|
@ -142,37 +142,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
# 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: ->
|
||||
c = super()
|
||||
c.world = @world
|
||||
|
|
|
@ -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', {}
|
|
@ -1,6 +1,7 @@
|
|||
popoverTemplate = require 'templates/play/level/tome/spell_palette_entry_popover'
|
||||
{downTheChain} = require 'lib/world/world_utils'
|
||||
window.Vector = require 'lib/world/vector' # So we can document it
|
||||
utils = require 'lib/utils'
|
||||
|
||||
safeJSONStringify = (input, maxDepth) ->
|
||||
recursion = (input, path, depth) ->
|
||||
|
@ -89,11 +90,26 @@ module.exports = class DocFormatter
|
|||
if @doc.returns
|
||||
toTranslate.push {obj: @doc.returns, prop: 'example'}, {obj: @doc.returns, prop: 'description'}
|
||||
for {obj, prop} in toTranslate
|
||||
# Translate into chosen code language.
|
||||
if val = obj[prop]?[@options.language]
|
||||
obj[prop] = val
|
||||
else unless _.isString obj[prop]
|
||||
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: ->
|
||||
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]
|
||||
|
|
|
@ -22,6 +22,8 @@ module.exports = class Spell
|
|||
@levelType = options.level.get('type', true)
|
||||
|
||||
p = options.programmableMethod
|
||||
@commentI18N = p.i18n
|
||||
@commentContext = p.context
|
||||
@languages = p.languages ? {}
|
||||
@languages.javascript ?= p.source
|
||||
@name = p.name
|
||||
|
@ -61,6 +63,18 @@ module.exports = class Spell
|
|||
setLanguage: (@language) ->
|
||||
#console.log 'setting language to', @language, 'so using original source', @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) ->
|
||||
if @thangs[thang.id]
|
||||
|
|
|
@ -68,7 +68,8 @@
|
|||
"node-gyp": "~0.13.0",
|
||||
"aether": "~0.2.39",
|
||||
"JASON": "~0.1.3",
|
||||
"JQDeferred": "~2.1.0"
|
||||
"JQDeferred": "~2.1.0",
|
||||
"jsondiffpatch": "0.1.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jade": "0.33.x",
|
||||
|
|
|
@ -64,6 +64,7 @@ AchievementSchema.post 'save', -> @constructor.loadAchievements()
|
|||
|
||||
AchievementSchema.plugin(plugins.NamedPlugin)
|
||||
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
|
||||
AchievementSchema.plugin plugins.TranslationCoveragePlugin
|
||||
|
||||
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema, 'achievements')
|
||||
|
||||
|
|
|
@ -5,13 +5,37 @@ class AchievementHandler extends Handler
|
|||
modelClass: Achievement
|
||||
|
||||
# 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']
|
||||
jsonSchema = require '../../app/schemas/models/achievement.coffee'
|
||||
|
||||
|
||||
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) ->
|
||||
# /db/achievement?related=<ID>
|
||||
|
|
|
@ -7,6 +7,7 @@ Patch = require '../patches/Patch'
|
|||
User = require '../users/User'
|
||||
sendwithus = require '../sendwithus'
|
||||
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}
|
||||
FETCH_LIMIT = 300
|
||||
|
@ -32,10 +33,23 @@ module.exports = class Handler
|
|||
hasAccess: (req) -> true
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
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
|
||||
return document.hasPermissionsForMethod?(req.user, method or req.method)
|
||||
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()
|
||||
getEditableProperties: (req, document) ->
|
||||
props = @editableProperties.slice()
|
||||
|
@ -89,8 +103,29 @@ module.exports = class Handler
|
|||
|
||||
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 @modelClass.schema.uses_coco_search
|
||||
else if @modelClass.schema.uses_coco_search
|
||||
term = req.query.term
|
||||
matchedObjects = []
|
||||
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
|
||||
|
|
|
@ -12,6 +12,7 @@ LevelComponentSchema.plugin plugins.PermissionsPlugin
|
|||
LevelComponentSchema.plugin plugins.VersionedPlugin
|
||||
LevelComponentSchema.plugin plugins.SearchablePlugin, {searchable: ['name', 'searchStrings', 'description']}
|
||||
LevelComponentSchema.plugin plugins.PatchablePlugin
|
||||
LevelComponentSchema.plugin plugins.TranslationCoveragePlugin
|
||||
LevelComponentSchema.pre('save', (next) ->
|
||||
name = @get('name')
|
||||
strings = _.str.humanize(name).toLowerCase().split(' ')
|
||||
|
|
|
@ -14,6 +14,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
|
|||
'propertyDocumentation'
|
||||
'configSchema'
|
||||
'name'
|
||||
'i18nCoverage'
|
||||
]
|
||||
|
||||
getEditableProperties: (req, document) ->
|
||||
|
|
|
@ -28,6 +28,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'banner'
|
||||
'employerDescription'
|
||||
'terrain'
|
||||
'i18nCoverage'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
|
|
@ -36,13 +36,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
|
|||
'featureImage'
|
||||
'spriteType'
|
||||
'i18nCoverage'
|
||||
'i18n'
|
||||
'description'
|
||||
]
|
||||
|
||||
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) ->
|
||||
if req.query.view in ['items', 'heroes']
|
||||
if req.query.view in ['items', 'heroes', 'i18n-coverage']
|
||||
projection = {}
|
||||
if req.query.project
|
||||
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'
|
||||
query.kind = 'Unit'
|
||||
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
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
|
|
|
@ -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.')
|
||||
humansGameID = req.body.humansGameID
|
||||
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
|
||||
unless ogresGameID and humansGameID
|
||||
#fetch random games here
|
||||
|
|
|
@ -14,6 +14,7 @@ config = require './server_config'
|
|||
auth = require './server/routes/auth'
|
||||
UserHandler = require './server/users/user_handler'
|
||||
global.tv4 = require 'tv4' # required for TreemaUtils to work
|
||||
global.jsondiffpatch = require 'jsondiffpatch'
|
||||
|
||||
productionLogging = (tokens, req, res) ->
|
||||
status = res.statusCode
|
||||
|
|
|
@ -76,5 +76,6 @@ describe 'lib/FacebookHandler.coffee', ->
|
|||
expect(params.gender).toBe(mockMe.gender)
|
||||
expect(params.email).toBe(mockMe.email)
|
||||
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()
|
||||
|
|
Loading…
Reference in a new issue