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'
'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')

View file

@ -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']

View file

@ -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 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_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"

View file

@ -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"

View file

@ -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'

View file

@ -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) ->

View file

@ -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) ->

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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%

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,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

View file

@ -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

View file

@ -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')

View file

@ -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

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
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!

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
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

View file

@ -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

View file

@ -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()

View file

@ -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)

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 #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()

View file

@ -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()

View file

@ -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 =>

View file

@ -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

View file

@ -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: ->

View file

@ -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', {}

View file

@ -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()

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.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()

View file

@ -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)

View file

@ -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()

View file

@ -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

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'
{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]

View file

@ -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]

View file

@ -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",

View file

@ -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')

View file

@ -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>

View file

@ -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: {}]

View file

@ -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(' ')

View file

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

View file

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

View file

@ -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)

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.')
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

View file

@ -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

View file

@ -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()