mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
07bf0238b4
31 changed files with 606 additions and 564 deletions
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
BIN
app/assets/images/pages/about/new_languages.png
Normal file
BIN
app/assets/images/pages/about/new_languages.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
app/assets/images/pages/about/new_languages_xs.png
Normal file
BIN
app/assets/images/pages/about/new_languages_xs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -52,6 +52,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'artisans/solution-problems': go('artisans/SolutionProblemsView')
|
||||
'artisans/thang-tasks': go('artisans/ThangTasksView')
|
||||
'artisans/level-concepts': go('artisans/LevelConceptMap')
|
||||
'artisans/level-guides': go('artisans/LevelGuidesView')
|
||||
|
||||
'beta': go('HomeView')
|
||||
|
||||
|
|
|
@ -579,17 +579,17 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
tip_programming_not_about_computers: "天文学が望遠鏡に関する学問でないのと同様に、計算機科学はコンピュータに関する学問ではない。 - エドガー・ダイクストラ"
|
||||
tip_mulan: "できると信じていれば、できる。 - ムーラン"
|
||||
|
||||
# play_game_dev_level:
|
||||
# created_by: "Created by {{name}}"
|
||||
# how_to_play_title: "How to play:"
|
||||
# how_to_play_1: "Use the mouse to control the hero!"
|
||||
# how_to_play_2: "Click anywhere on the map to move to that location."
|
||||
# how_to_play_3: "Click on the ogres to attack them."
|
||||
# restart: "Restart Level"
|
||||
# play: "Play Level"
|
||||
# play_more_codecombat: "Play More CodeCombat"
|
||||
# default_student_instructions: "Click to control your hero and win your game!"
|
||||
# back_to_coding: "Back to Coding"
|
||||
play_game_dev_level:
|
||||
created_by: "作成者:{{name}}"
|
||||
how_to_play_title: "遊び方:"
|
||||
how_to_play_1: "マウスでヒーローを操作しましょう!"
|
||||
how_to_play_2: "マップの動きたい場所をどこでもクリックしましょう."
|
||||
how_to_play_3: "オーガをクリックして攻撃しましょう."
|
||||
restart: "レベルをリセット"
|
||||
play: "プレイレベル"
|
||||
play_more_codecombat: "もっとCodeCombatで遊ぶ"
|
||||
default_student_instructions: "ヒーローをクリックしてゲームに勝ちましょう!"
|
||||
back_to_coding: "コーディングに戻る"
|
||||
|
||||
game_menu:
|
||||
inventory_tab: "インベントリー"
|
||||
|
@ -767,8 +767,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
current_value: "現在値"
|
||||
default_value: "デフォルト値"
|
||||
parameters: "パラメータ"
|
||||
# required_parameters: "Required Parameters"
|
||||
# optional_parameters: "Optional Parameters"
|
||||
required_parameters: "必須パラメーター"
|
||||
optional_parameters: "任意パラメーター"
|
||||
returns: "リターン"
|
||||
granted_by: "スキルを与えてくれるアイテム:"
|
||||
|
||||
|
@ -823,9 +823,9 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
phoenix_title: "ソフトウェアエンジニア"
|
||||
nolan_title: "地区担当マネージャー"
|
||||
elliot_title: "パートナーシップマネージャー"
|
||||
# elliot_blurb: "Mindreader"
|
||||
# lisa_title: "Market Development Rep"
|
||||
# sean_title: "Territory Manager"
|
||||
elliot_blurb: "読心術者"
|
||||
lisa_title: "市場開発代表"
|
||||
sean_title: "地域部長"
|
||||
retrostyle_title: "イラスト"
|
||||
retrostyle_blurb: "レトロスタイルのゲーム"
|
||||
jose_title: "ミュージック"
|
||||
|
@ -1564,7 +1564,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
article_title: "アーティクル エディター"
|
||||
thang_title: "サングエディター"
|
||||
level_title: "レベルエディター"
|
||||
# course_title: "Course Editor"
|
||||
course_title: "コースエディター"
|
||||
achievement_title: "実績エディター"
|
||||
poll_title: "投票エディター"
|
||||
back: "バック"
|
||||
|
@ -1750,7 +1750,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
rank_failed: "ランキングに送信できませんでした。"
|
||||
rank_being_ranked: "ランキングにのっています"
|
||||
rank_last_submitted: "送信"
|
||||
# help_simulate: "Help simulate games?"
|
||||
help_simulate: "試合のシミュレートのヘルプ?"
|
||||
# code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
|
||||
# no_ranked_matches_pre: "No ranked matches for the "
|
||||
# no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
|
||||
|
@ -2039,19 +2039,19 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
# license: "license"
|
||||
# oreilly: "ebook of your choice"
|
||||
|
||||
# calendar:
|
||||
# year: "Year"
|
||||
# day: "Day"
|
||||
# month: "Month"
|
||||
# january: "January"
|
||||
# february: "February"
|
||||
# march: "March"
|
||||
# april: "April"
|
||||
# may: "May"
|
||||
# june: "June"
|
||||
# july: "July"
|
||||
# august: "August"
|
||||
# september: "September"
|
||||
# october: "October"
|
||||
# november: "November"
|
||||
# december: "December"
|
||||
calendar:
|
||||
year: "年"
|
||||
day: "日"
|
||||
month: "月"
|
||||
january: "1月"
|
||||
february: "2月"
|
||||
march: "3月"
|
||||
april: "4月"
|
||||
may: "5月"
|
||||
june: "6月"
|
||||
july: "7月"
|
||||
august: "8月"
|
||||
september: "9月"
|
||||
october: "10月"
|
||||
november: "11月"
|
||||
december: "12月"
|
||||
|
|
|
@ -305,22 +305,22 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
enter_class_code: "Coloque seu código de classe"
|
||||
enter_birthdate: "Coloque sua data de aniversário:"
|
||||
parent_use_birthdate: "Responsáveis, usem o seu propio dia de nascimento."
|
||||
# ask_teacher_1: "Ask your teacher for your Class Code."
|
||||
# ask_teacher_2: "Not part of a class? Create an "
|
||||
# ask_teacher_3: "Individual Account"
|
||||
# ask_teacher_4: " instead."
|
||||
# about_to_join: "You're about to join:"
|
||||
# enter_parent_email: "Enter your parent’s email address:"
|
||||
# parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again."
|
||||
# parent_email_sent: "We’ve sent an email with further instructions on how to create an account. Ask your parent to check their inbox."
|
||||
# account_created: "Account Created!"
|
||||
# confirm_student_blurb: "Write down your information so that you don't forget it. Your teacher can also help you reset your password at any time."
|
||||
# confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!"
|
||||
# write_this_down: "Write this down:"
|
||||
# start_playing: "Start Playing!"
|
||||
# sso_connected: "Successfully connected with:"
|
||||
# select_your_starting_hero: "Select Your Starting Hero:"
|
||||
# you_can_always_change_your_hero_later: "You can always change your hero later."
|
||||
ask_teacher_1: "Pergunte ao seu professor qual o código da sua turma."
|
||||
ask_teacher_2: "Não faz parte da turma? Crie uma "
|
||||
ask_teacher_3: "Conta Pessoal"
|
||||
ask_teacher_4: " como alternativa."
|
||||
about_to_join: "Sobre juntar-se:"
|
||||
enter_parent_email: "Informe o endereço de e-mail de seus pais:"
|
||||
parent_email_error: "Algo de errado aconteceu ao tentar enviar o email. Verifique o endereço de email e tente novamente."
|
||||
parent_email_sent: "Nós enviamos um email com mais instruções de como criar uma conta. Solicite aos seus pais para que verifiquem suas caixas de emails."
|
||||
account_created: "Conta criada!"
|
||||
confirm_student_blurb: "Anote suas informações para que você não esqueça. Seu professor também pode ajudá-lo reiniciando sua senha a qualquer momento."
|
||||
confirm_individual_blurb: "Anote suas informações de acesso no caso de você dela depois. Verifique seu e-mail para que você possa recuperar sua senha caso a tenha esquecid. Verifique sua caixa de entrada de emails!"
|
||||
write_this_down: "Escreva isso:"
|
||||
start_playing: "Comece jogando!"
|
||||
sso_connected: "Conectado com sucesso como:"
|
||||
select_your_starting_hero: "Selecione um herói para começar:"
|
||||
you_can_always_change_your_hero_later: "Você poderá mudar seu herói depois."
|
||||
|
||||
recover:
|
||||
recover_account_title: "Recuperar conta"
|
||||
|
@ -337,18 +337,18 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
|
||||
common:
|
||||
back: "Voltar" # When used as an action verb, like "Navigate backward"
|
||||
# coming_soon: "Coming soon!"
|
||||
coming_soon: "Em breve!"
|
||||
continue: "Continuar" # When used as an action verb, like "Continue forward"
|
||||
# default_code: "Default Code"
|
||||
default_code: "Código padrão"
|
||||
loading: "Carregando..."
|
||||
# overview: "Overview"
|
||||
# solution: "Solution"
|
||||
# intro: "Intro"
|
||||
overview: "Visão geral"
|
||||
solution: "Solução"
|
||||
intro: "Introdução"
|
||||
saving: "Salvando..."
|
||||
sending: "Enviando..."
|
||||
send: "Enviar"
|
||||
# sent: "Sent"
|
||||
# type: "Type"
|
||||
sent: "Enviado"
|
||||
type: "Tipo"
|
||||
cancel: "Cancelar"
|
||||
save: "Salvar"
|
||||
publish: "Publicar"
|
||||
|
@ -364,7 +364,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
submit_patch: "Enviar arranjo"
|
||||
submit_changes: "Enviar mudanças"
|
||||
save_changes: "Salvar mudanças"
|
||||
# required_field: "required"
|
||||
required_field: "obrigatório"
|
||||
|
||||
general:
|
||||
and: "e"
|
||||
|
@ -418,7 +418,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
wizard: "Feiticeiro"
|
||||
first_name: "Primeiro Nome"
|
||||
last_name: "Último Nome"
|
||||
# last_initial: "Last Initial"
|
||||
last_initial: "Última Inicial"
|
||||
username: "Nome de Usuário"
|
||||
|
||||
units:
|
||||
|
@ -438,15 +438,15 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
years: "anos"
|
||||
|
||||
play_level:
|
||||
# level_complete: "Level Complete"
|
||||
level_complete: "Nível Completo"
|
||||
completed_level: "Nivel Completo:"
|
||||
course: "Curso:"
|
||||
done: "Pronto"
|
||||
next_level: "Proximo Nivel"
|
||||
next_game: "Próximo jogo"
|
||||
# language: "Language"
|
||||
# languages: "Languages"
|
||||
# programming_language: "Programming language"
|
||||
language: "Linguagem"
|
||||
languages: "Linguagens"
|
||||
programming_language: "Linguagem de programação"
|
||||
show_menu: "Mostrar menu do jogo"
|
||||
home: "Início" # Not used any more, will be removed soon.
|
||||
level: "Fase" # Like "Level: Dungeons of Kithgard"
|
||||
|
@ -481,10 +481,10 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
victory_experience_gained: "XP ganho"
|
||||
victory_gems_gained: "Gemas ganhas"
|
||||
victory_new_item: "Novo item"
|
||||
# victory_new_hero: "New Hero"
|
||||
victory_new_hero: "Novo herói"
|
||||
victory_viking_code_school: "Pelas barbas do profeta, esse foi um nível difícil! Se você ainda não é um desenvolvedor de software, você deveria ser. Você acaba de ser priorizado para aceitação na Viking Code School, onde você pode aprender mais e se tornar um desenvolvedor web profissional em 14 semanas."
|
||||
victory_become_a_viking: "Torne-se um viking"
|
||||
# victory_no_progress_for_teachers: "Progress is not saved for teachers. But, you can add a student account to your classroom for yourself."
|
||||
victory_no_progress_for_teachers: "O progresso não é salvo para o professores. Mas, você mesmo pode adicionar um conta de aluno na sua turma."
|
||||
guide_title: "Guia"
|
||||
tome_cast_button_run: "Rodar"
|
||||
tome_cast_button_running: "Rodando"
|
||||
|
@ -494,8 +494,8 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
|||
tome_available_spells: "Feitiços Disponíveis"
|
||||
tome_your_skills: "Suas habilidades"
|
||||
tome_current_method: "Método Atual"
|
||||
# hints: "Hints"
|
||||
# hints_title: "Hint {{number}}"
|
||||
hints: "Sugestões"
|
||||
hints_title: "Sugestão {{number}}"
|
||||
code_saved: "Código Salvo"
|
||||
skip_tutorial: "Pular (esc)"
|
||||
keyboard_shortcuts: "Teclas de atalho"
|
||||
|
|
5
app/styles/artisans/level-guides-view.sass
Normal file
5
app/styles/artisans/level-guides-view.sass
Normal file
|
@ -0,0 +1,5 @@
|
|||
#level-guides-view
|
||||
.problem
|
||||
color: red
|
||||
.level-details
|
||||
width: 15%
|
|
@ -247,10 +247,9 @@ block content
|
|||
#story-languages
|
||||
.text-center
|
||||
.text-h5(data-i18n="about.story_statistic_3c")
|
||||
#language-icons.text-center(title="CoffeeScript, JavaScript, Python, Java, Lua")
|
||||
img.hidden-xs(src="/images/pages/about/languages.png")
|
||||
img.hidden-sm.hidden-md.hidden-lg(src="/images/pages/about/languages_group1.png")
|
||||
img.hidden-sm.hidden-md.hidden-lg(src="/images/pages/about/languages_group2.png")
|
||||
#language-icons.text-center(title="JavaScript, Python, HTML, CSS, jQuery, Bootstrap")
|
||||
img.hidden-xs(src="/images/pages/about/new_languages.png")
|
||||
img.hidden-sm.hidden-md.hidden-lg(src="/images/pages/about/new_languages_xs.png")
|
||||
|
||||
#story-graphic-4.text-center
|
||||
p
|
||||
|
|
|
@ -6,11 +6,14 @@ block content
|
|||
a(href='/artisans/thang-tasks')
|
||||
|Thang Tasks
|
||||
div
|
||||
a(href="/artisans/level-tasks")
|
||||
a(href='/artisans/level-tasks')
|
||||
|Level Tasks
|
||||
div
|
||||
a(href="/artisans/solution-problems")
|
||||
a(href='/artisans/solution-problems')
|
||||
|Solution Problems
|
||||
div
|
||||
a(href="/artisans/level-concepts")
|
||||
a(href='/artisans/level-concepts')
|
||||
|Level Concept Map
|
||||
div
|
||||
a(href='/artisans/level-guides')
|
||||
|Level Guides Overview
|
||||
|
|
36
app/templates/artisans/level-guides-view.jade
Normal file
36
app/templates/artisans/level-guides-view.jade
Normal file
|
@ -0,0 +1,36 @@
|
|||
// DNT
|
||||
extends /templates/base
|
||||
|
||||
block content
|
||||
div
|
||||
a(href='/artisans')
|
||||
span.glyphicon.glyphicon-chevron-left
|
||||
span Artisans Home
|
||||
button#overview-button Show Overviews
|
||||
br
|
||||
button#intro-button Show Intros
|
||||
table.table#level-table
|
||||
for levelObj in (view.levels || [])
|
||||
- var level = levelObj.level
|
||||
tr
|
||||
td.level-details
|
||||
a(href='/editor/level/'+level.get('slug') target="_blank")=level.get('name')
|
||||
div
|
||||
ul
|
||||
for problem in levelObj.problems
|
||||
li.problem=problem
|
||||
td(style='width:90%')
|
||||
if levelObj.overview
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
h2.panel-title
|
||||
a(data-toggle='collapse' href='#'+level.get('slug')+'-overview-collapse') Overview
|
||||
.panel-collapse.collapse.overview(id=level.get('slug')+'-overview-collapse')
|
||||
pre=levelObj.overview.body
|
||||
if levelObj.intro
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
h2.panel-title
|
||||
a(data-toggle='collapse' href='#'+level.get('slug')+'-intro-collapse') Intro
|
||||
.panel-collapse.collapse.intro(id=level.get('slug')+'-intro-collapse')
|
||||
pre=levelObj.intro.body
|
|
@ -41,7 +41,7 @@ if view.showAds()
|
|||
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
if level.slug == 'lost-viking'
|
||||
img.star(src="/file/db/thang.type/5441c3144e9aeb727cc97111/portrait.png")
|
||||
else if level.requiresSubscription
|
||||
else if level.requiresSubscription && !level.adventurer
|
||||
img.star(src="/images/pages/play/star.png")
|
||||
if levelStatusMap[level.slug] === 'complete'
|
||||
img.banner(src="/images/pages/play/level-banner-complete.png")
|
||||
|
|
98
app/views/artisans/LevelGuidesView.coffee
Normal file
98
app/views/artisans/LevelGuidesView.coffee
Normal file
|
@ -0,0 +1,98 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
template = require 'templates/artisans/level-guides-view'
|
||||
|
||||
Campaigns = require 'collections/Campaigns'
|
||||
Campaign = require 'models/Campaign'
|
||||
|
||||
Levels = require 'collections/Levels'
|
||||
Level = require 'models/Level'
|
||||
|
||||
module.exports = class LevelGuidesView extends RootView
|
||||
template: template
|
||||
id: 'level-guides-view'
|
||||
events:
|
||||
'click #overview-button': 'onOverviewButtonClicked'
|
||||
'click #intro-button': 'onIntroButtonClicked'
|
||||
|
||||
excludedCampaigns = [
|
||||
'pico-ctf', 'auditions'
|
||||
]
|
||||
includedCampaigns = [
|
||||
'intro', 'course-2', 'course-3', 'course-4', 'course-5', 'course-6',
|
||||
'web-dev-1', 'web-dev-2',
|
||||
'game-dev-1', 'game-dev-2'
|
||||
]
|
||||
levels: []
|
||||
|
||||
onOverviewButtonClicked: (e) ->
|
||||
@$('.overview').toggleClass('in')
|
||||
onIntroButtonClicked: (e) ->
|
||||
@$('.intro').toggleClass('in')
|
||||
|
||||
initialize: () ->
|
||||
|
||||
@campaigns = new Campaigns()
|
||||
|
||||
@listenTo(@campaigns, 'sync', @onCampaignsLoaded)
|
||||
@supermodel.trackRequest(@campaigns.fetch(
|
||||
data:
|
||||
project: 'name,slug,levels'
|
||||
))
|
||||
onCampaignsLoaded: (campCollection) ->
|
||||
for camp in campCollection.models
|
||||
campaignSlug = camp.get 'slug'
|
||||
continue if campaignSlug in excludedCampaigns
|
||||
continue unless campaignSlug in includedCampaigns
|
||||
levels = camp.get 'levels'
|
||||
|
||||
levels = new Levels()
|
||||
@listenTo(levels, 'sync', @onLevelsLoaded)
|
||||
levels.fetchForCampaign(campaignSlug)
|
||||
#for key, level of levels
|
||||
|
||||
onLevelsLoaded: (lvlCollection) ->
|
||||
lvlCollection.models.reverse()
|
||||
#console.log lvlCollection
|
||||
for level in lvlCollection.models
|
||||
#console.log level
|
||||
levelSlug = level.get 'slug'
|
||||
overview = _.find(level.get('documentation').specificArticles, name:'Overview')
|
||||
intro = _.find(level.get('documentation').specificArticles, name:'Intro')
|
||||
#if intro and overview
|
||||
problems = []
|
||||
if not overview
|
||||
problems.push 'No Overview'
|
||||
else
|
||||
if not overview.i18n
|
||||
problems.push 'Overview doesn\'t have i18n field'
|
||||
if not overview.body
|
||||
problems.push 'Overview doesn\'t have a body'
|
||||
else
|
||||
if level.get('campaign')?.indexOf('web') is -1
|
||||
jsIndex = overview.body.indexOf('```javascript')
|
||||
pyIndex = overview.body.indexOf('```python')
|
||||
if jsIndex is -1 and pyIndex isnt -1 or jsIndex isnt -1 and pyIndex is -1
|
||||
problems.push 'Overview is missing a language example.'
|
||||
if not intro
|
||||
problems.push 'No Intro'
|
||||
else
|
||||
if not intro.i18n
|
||||
problems.push 'Intro doesn\'t have i18n field'
|
||||
if not intro.body
|
||||
problems.push 'Intro doesn\'t have a body'
|
||||
else
|
||||
if intro.body.indexOf('file/db') is -1
|
||||
problems.push 'Intro is missing image'
|
||||
if level.get('campaign')?.indexOf('web') is -1
|
||||
jsIndex = intro.body.indexOf('```javascript')
|
||||
pyIndex = intro.body.indexOf('```python')
|
||||
if jsIndex is -1 and pyIndex isnt -1 or jsIndex isnt -1 and pyIndex is -1
|
||||
problems.push 'Intro is missing a language example.'
|
||||
@levels.push
|
||||
level: level
|
||||
overview: overview
|
||||
intro: intro
|
||||
problems: problems
|
||||
@levels.sort (a, b) ->
|
||||
return b.problems.length - a.problems.length
|
||||
@renderSelectors '#level-table'
|
|
@ -178,7 +178,7 @@ module.exports = class CampaignView extends RootView
|
|||
context.levels = _.reject context.levels, slug: reject
|
||||
if me.isOnFreeOnlyServer()
|
||||
context.levels = _.reject context.levels, 'requiresSubscription'
|
||||
@annotateLevel level for level in context.levels
|
||||
@annotateLevels(context.levels)
|
||||
count = @countLevels context.levels
|
||||
context.levelsCompleted = count.completed
|
||||
context.levelsTotal = count.total
|
||||
|
@ -198,7 +198,7 @@ module.exports = class CampaignView extends RootView
|
|||
context.adjacentCampaigns = _.filter _.values(_.cloneDeep(@campaign?.get('adjacentCampaigns') or {})), (ac) =>
|
||||
if ac.showIfUnlocked and not @editorMode
|
||||
return false if _.isString(ac.showIfUnlocked) and ac.showIfUnlocked not in me.levels()
|
||||
return false if _.isArray(ac.showIfUnlocked) and _.intersection(ac.showIfUnlocked, me.levels()).length < 0
|
||||
return false if _.isArray(ac.showIfUnlocked) and _.intersection(ac.showIfUnlocked, me.levels()).length <= 0
|
||||
ac.name = utils.i18n ac, 'name'
|
||||
styles = []
|
||||
styles.push "color: #{ac.color}" if ac.color
|
||||
|
@ -231,7 +231,7 @@ module.exports = class CampaignView extends RootView
|
|||
if _.isString(ac.showIfUnlocked)
|
||||
_.find(@campaigns.models, id: acID)?.locked = false if ac.showIfUnlocked in me.levels()
|
||||
else if _.isArray(ac.showIfUnlocked)
|
||||
_.find(@campaigns.models, id: acID)?.locked = false if _.intersection(ac.showIfUnlocked, me.levels()).length
|
||||
_.find(@campaigns.models, id: acID)?.locked = false if _.intersection(ac.showIfUnlocked, me.levels()).length > 0
|
||||
|
||||
context
|
||||
|
||||
|
@ -278,9 +278,11 @@ module.exports = class CampaignView extends RootView
|
|||
return me.getCampaignAdsGroup() is 'leaderboard-ads'
|
||||
false
|
||||
|
||||
annotateLevel: (level) ->
|
||||
annotateLevels: (orderedLevels) ->
|
||||
previousIncompletePracticeLevel = false # Lock owned levels if there's a earlier incomplete practice level to play
|
||||
for level in orderedLevels
|
||||
level.position ?= { x: 10, y: 10 }
|
||||
level.locked = not me.ownsLevel level.original
|
||||
level.locked = not me.ownsLevel(level.original) or previousIncompletePracticeLevel
|
||||
level.locked = true if level.slug is 'kithgard-mastery' and @calculateExperienceScore() is 0
|
||||
level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
|
||||
level.locked = false if @editorMode
|
||||
|
@ -290,8 +292,8 @@ module.exports = class CampaignView extends RootView
|
|||
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
||||
level.disabled = false if me.isInGodMode()
|
||||
level.color = 'rgb(255, 80, 60)'
|
||||
if level.requiresSubscription
|
||||
level.color = 'rgb(80, 130, 200)'
|
||||
level.color = 'rgb(80, 130, 200)' if level.requiresSubscription
|
||||
level.color = 'rgb(200, 80, 200)' if level.adventurer
|
||||
if unlocksHero = _.find(level.rewards, 'hero')?.hero
|
||||
level.unlocksHero = unlocksHero
|
||||
if level.unlocksHero
|
||||
|
@ -309,18 +311,22 @@ module.exports = class CampaignView extends RootView
|
|||
"""
|
||||
level.color = 'rgb(80, 130, 200)' if problem.solved
|
||||
|
||||
if level.practice and not level.locked and @levelStatusMap[level.slug] isnt 'complete' and
|
||||
(not level.requiresSubscription or level.adventurer or not @requiresSubscription)
|
||||
previousIncompletePracticeLevel = true
|
||||
|
||||
level.hidden = level.locked
|
||||
if level.concepts?.length
|
||||
level.displayConcepts = level.concepts
|
||||
maxConcepts = 6
|
||||
if level.displayConcepts.length > maxConcepts
|
||||
level.displayConcepts = level.displayConcepts.slice -maxConcepts
|
||||
level
|
||||
|
||||
countLevels: (levels) ->
|
||||
count = total: 0, completed: 0
|
||||
for level, levelIndex in levels
|
||||
@annotateLevel level unless level.locked? # Annotate if we haven't already.
|
||||
continue if level.practice
|
||||
@annotateLevels(levels) unless level.locked? # Annotate if we haven't already.
|
||||
unless level.disabled
|
||||
unlockedInSameCampaign = levelIndex < 5 # First few are always counted (probably unlocked in previous campaign)
|
||||
for otherLevel in levels when not unlockedInSameCampaign and otherLevel isnt level
|
||||
|
@ -334,34 +340,42 @@ module.exports = class CampaignView extends RootView
|
|||
leaderboardModal = new LeaderboardModal supermodel: @supermodel, levelSlug: levelSlug
|
||||
@openModalView leaderboardModal
|
||||
|
||||
determineNextLevel: (levels) ->
|
||||
foundNext = false
|
||||
determineNextLevel: (orderedLevels) ->
|
||||
dontPointTo = ['lost-viking', 'kithgard-mastery'] # Challenge levels we don't want most players bashing heads against
|
||||
subscriptionPrompts = [{slug: 'boom-and-bust', unless: 'defense-of-plainswood'}]
|
||||
for level in levels
|
||||
# Iterate through all levels in order and look to find the first unlocked one that meets all our criteria for being pointed out as the next level.
|
||||
level.nextLevels = (reward.level for reward in level.rewards ? [] when reward.level)
|
||||
unless foundNext
|
||||
for nextLevelOriginal in level.nextLevels
|
||||
nextLevel = _.find levels, original: nextLevelOriginal
|
||||
|
||||
findNextLevel = (nextLevels, practiceOnly) =>
|
||||
for nextLevelOriginal in nextLevels
|
||||
nextLevel = _.find orderedLevels, original: nextLevelOriginal
|
||||
continue if not nextLevel or nextLevel.locked
|
||||
continue if practiceOnly and not nextLevel.practice
|
||||
|
||||
# If it's a challenge level, we efficiently determine whether we actually do want to point it out.
|
||||
if nextLevel and nextLevel.slug is 'kithgard-mastery' and not nextLevel.locked and not @levelStatusMap[nextLevel.slug] and @calculateExperienceScore() >= 3
|
||||
if nextLevel.slug is 'kithgard-mastery' and not @levelStatusMap[nextLevel.slug] and @calculateExperienceScore() >= 3
|
||||
unless (timesPointedOut = storage.load("pointed-out-#{nextLevel.slug}") or 0) > 3
|
||||
# We may determineNextLevel more than once per render, so we can't just do this once. But we do give up after a couple highlights.
|
||||
dontPointTo = _.without dontPointTo, nextLevel.slug
|
||||
storage.save "pointed-out-#{nextLevel.slug}", timesPointedOut + 1
|
||||
|
||||
# Should we point this level out?
|
||||
if nextLevel and not nextLevel.locked and not nextLevel.disabled and @levelStatusMap[nextLevel.slug] isnt 'complete' and nextLevel.slug not in dontPointTo and not nextLevel.replayable and (
|
||||
me.isPremium() or not nextLevel.requiresSubscription or
|
||||
if not nextLevel.disabled and @levelStatusMap[nextLevel.slug] isnt 'complete' and nextLevel.slug not in dontPointTo and
|
||||
not nextLevel.replayable and (
|
||||
me.isPremium() or not nextLevel.requiresSubscription or nextLevel.adventurer or
|
||||
_.any(subscriptionPrompts, (prompt) => nextLevel.slug is prompt.slug and not @levelStatusMap[prompt.unless])
|
||||
)
|
||||
nextLevel.next = true
|
||||
foundNext = true
|
||||
break
|
||||
if not foundNext and levels[0] and not levels[0].locked and @levelStatusMap[levels[0].slug] isnt 'complete'
|
||||
levels[0].next = true
|
||||
return true
|
||||
false
|
||||
|
||||
foundNext = false
|
||||
for level in orderedLevels
|
||||
# Iterate through all levels in order and look to find the first unlocked one that meets all our criteria for being pointed out as the next level.
|
||||
level.nextLevels = (reward.level for reward in level.rewards ? [] when reward.level)
|
||||
break if foundNext = findNextLevel(level.nextLevels, true) # Check practice levels first
|
||||
break if foundNext = findNextLevel(level.nextLevels, false)
|
||||
|
||||
if not foundNext and orderedLevels[0] and not orderedLevels[0].locked and @levelStatusMap[orderedLevels[0].slug] isnt 'complete'
|
||||
orderedLevels[0].next = true
|
||||
|
||||
calculateExperienceScore: ->
|
||||
adultPoint = me.get('ageRange') in ['18-24', '25-34', '35-44', '45-100'] # They have to have answered the poll for this, likely after Shadow Guard.
|
||||
|
@ -409,6 +423,7 @@ module.exports = class CampaignView extends RootView
|
|||
@particleMan.removeEmitters()
|
||||
@particleMan.attach @$el.find('.map')
|
||||
for level in @campaign.renderedLevels ? {}
|
||||
continue if level.practice
|
||||
terrain = @terrain.replace('-branching-test', '').replace(/(campaign-)?(game|web)-dev-\d/, 'forest').replace('intro', 'dungeon')
|
||||
particleKey = ['level', terrain]
|
||||
particleKey.push level.type if level.type and not (level.type in ['hero', 'course']) # Would use isType, but it's not a Level model
|
||||
|
|
|
@ -321,14 +321,12 @@ module.exports = class Autocomplete
|
|||
attackEntry.content = attackEntry.content.replace '${1:enemy}', '"${1:Enemy Name}"'
|
||||
snippetEntries.push attackEntry
|
||||
|
||||
# Add copied hero. entries for most important ones that start with hero.
|
||||
sortedEntries = _.sortBy snippetEntries, (entry) -> -1 * parseInt(entry.importance ? 0)
|
||||
for entry in sortedEntries
|
||||
if entry.content?.indexOf('hero.') is 0
|
||||
newEntry = _.cloneDeep(entry)
|
||||
entry.name = "hero.#{newEntry.name}"
|
||||
snippetEntries.push(newEntry)
|
||||
break if snippetEntries.length - sortedEntries.length >= 10
|
||||
# Update 'hero.' and 'game.' entries to include their prefixes
|
||||
for entry in snippetEntries
|
||||
if entry.content?.indexOf('hero.') is 0 and entry.name?.indexOf('hero.') < 0
|
||||
entry.name = "hero.#{entry.name}"
|
||||
else if entry.content?.indexOf('game.') is 0 and entry.name?.indexOf('game.') < 0
|
||||
entry.name = "game.#{entry.name}"
|
||||
|
||||
if haveFindNearest and not haveFindNearestEnemy
|
||||
spellView.translateFindNearest()
|
||||
|
|
|
@ -136,7 +136,7 @@ module.exports = (SnippetManager, autoLineEndings) ->
|
|||
beginningOfLine = session.getLine(pos.row).substring(0,pos.column - prefix.length)
|
||||
|
||||
unless (fullPrefixParts.length < 3 and /^(hero|self|this|@)$/.test(fullPrefixParts[0]) ) or /^\s*$/.test(beginningOfLine)
|
||||
console.log "Bailing", fullPrefixParts, '|', prefix, '|', beginningOfLine, '|', pos.column - prefix.length
|
||||
# console.log "DEBUG: autocomplete bailing", fullPrefixParts, '|', prefix, '|', beginningOfLine, '|', pos.column - prefix.length
|
||||
@completions = completions
|
||||
return callback null, completions
|
||||
|
||||
|
@ -195,10 +195,10 @@ getFullIdentifier = (doc, pos) ->
|
|||
scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captureReturn) ->
|
||||
# console.log "Snippets snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}"
|
||||
fuzzScore = 0.1
|
||||
snippetLineBreaks = (snippet.match(lineBreak) || []).length
|
||||
# input will be replaced by snippet
|
||||
# trim snippet prefix and suffix if already in the document (line)
|
||||
if prefixStart = snippet.toLowerCase().indexOf(input.toLowerCase()) > -1
|
||||
snippetLines = (snippet.match(lineBreak) || []).length
|
||||
captionStart = snippet.indexOf caption
|
||||
|
||||
# Calculate snippet prefixes and suffixes. E.g. full snippet might be: "self." + "moveLeft" + "()"
|
||||
|
@ -243,14 +243,18 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu
|
|||
# console.log 'Snippets atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line
|
||||
toLinePrefix = line.substring 0, linePrefixIndex
|
||||
if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(toLinePrefix) and not /^[ \t]*(?:if\b|elif\b)/.test(toLinePrefix)
|
||||
snippet += autoLineEndings[lang] if snippetLines is 0 and autoLineEndings[lang]
|
||||
snippet += "\n" if snippetLines is 0 and not /\$\{/.test(snippet)
|
||||
snippet += autoLineEndings[lang] if snippetLineBreaks is 0 and autoLineEndings[lang]
|
||||
snippet += "\n" if snippetLineBreaks is 0 and not /\$\{/.test(snippet)
|
||||
|
||||
if captureReturn and /^\s*$/.test(toLinePrefix)
|
||||
snippet = captureReturn + linePrefix + snippet
|
||||
|
||||
# console.log "Snippets snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}"
|
||||
else
|
||||
# Append automatic line ending and newline for simple scenario
|
||||
if line.trim() is input
|
||||
snippet += autoLineEndings[lang] if snippetLineBreaks is 0 and autoLineEndings[lang]
|
||||
snippet += "\n" if snippetLineBreaks is 0 and not /\$\{/.test(snippet)
|
||||
fuzzScore += score snippet, input
|
||||
|
||||
startsWith = (string, searchString, position) ->
|
||||
|
|
|
@ -23,6 +23,8 @@ let zpMinActivityDate = new Date();
|
|||
zpMinActivityDate.setUTCDate(zpMinActivityDate.getUTCDate() - 30);
|
||||
zpMinActivityDate = zpMinActivityDate.toISOString().substring(0, 10);
|
||||
|
||||
const closeParallelLimit = 100;
|
||||
|
||||
getZPContacts((err, emailContactMap) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
|
@ -33,7 +35,7 @@ getZPContacts((err, emailContactMap) => {
|
|||
const contact = emailContactMap[email];
|
||||
tasks.push(createUpsertCloseLeadFn(contact));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
if (err) console.log(err);
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
});
|
||||
|
@ -127,26 +129,39 @@ function updateCloseLead(zpContact, existingLead, done) {
|
|||
}
|
||||
|
||||
function createUpsertCloseLeadFn(zpContact) {
|
||||
// New contact lead matching algorithm:
|
||||
// 1. New contact email exists
|
||||
// 2. New contact NCES school id exists
|
||||
// 3. New contact NCES district id and no NCES school id
|
||||
// 4. New contact school name and no NCES data
|
||||
// 5. New contact district name and no NCES data
|
||||
return (done) => {
|
||||
// console.log(`DEBUG: createUpsertCloseLeadFn ${zpContact.organization} ${zpContact.email}`);
|
||||
const query = `email:${zpContact.email}`;
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
let query = `email:${zpContact.email}`;
|
||||
let url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
const data = JSON.parse(body);
|
||||
if (data.total_results != 0) return done();
|
||||
const query = `name:${zpContact.organization}`;
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
|
||||
query = `name:${zpContact.organization}`;
|
||||
if (zpContact.nces_school_id) {
|
||||
query = `custom.demo_nces_id:"${zpContact.nces_school_id}"`;
|
||||
}
|
||||
else if (zpContact.nces_district_id) {
|
||||
query = `custom.demo_nces_district_id:"${zpContact.nces_district_id}" custom.demo_nces_id:""`;
|
||||
}
|
||||
url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
const data = JSON.parse(body);
|
||||
if (data.total_results === 0) {
|
||||
console.log(`DEBUG: Creating lead for ${zpContact.organization} ${zpContact.email}`);
|
||||
console.log(`DEBUG: Creating lead for ${zpContact.organization} ${zpContact.email} nces_district_id=${zpContact.nces_district_id} nces_school_id=${zpContact.nces_school_id}`);
|
||||
return createCloseLead(zpContact, done);
|
||||
}
|
||||
else {
|
||||
const existingLead = data.data[0];
|
||||
console.log(`DEBUG: Adding ${zpContact.organization} ${zpContact.email} to ${existingLead.id}`);
|
||||
console.log(`DEBUG: Adding to ${existingLead.id} ${zpContact.organization} ${zpContact.email} nces_district_id=${zpContact.nces_district_id} nces_school_id=${zpContact.nces_school_id}`);
|
||||
return updateCloseLead(zpContact, existingLead, done);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ if (process.argv.length !== 10) {
|
|||
// TODO: Reduce response data via _fields param
|
||||
// TODO: Assumes 1:1 contact:email relationship (Close.io supports multiple emails for a single contact)
|
||||
// TODO: Cleanup country/status lookup code
|
||||
// TODO: parallelize update leads
|
||||
// TODO: Handle trial requests as individual contacts to be imported, instead of batching them into leads immediately via CocoLead objects
|
||||
|
||||
// Save as custom fields instead of user-specific lead notes (also saving nces_ props)
|
||||
const commonTrialProperties = ['organization', 'district', 'city', 'state', 'country'];
|
||||
|
@ -59,6 +59,8 @@ const usSchoolStatuses = ['Auto Attempt 1', 'New US Schools Auto Attempt 1', 'Ne
|
|||
|
||||
const emailDelayMinutes = 27;
|
||||
|
||||
const closeParallelLimit = 100;
|
||||
|
||||
const scriptStartTime = new Date();
|
||||
const closeIoApiKey = process.argv[2];
|
||||
// Automatic mails sent as API owners, first key assumed to be primary and gets 50% of the leads
|
||||
|
@ -677,7 +679,7 @@ function updateExistingLead(lead, existingLead, userApiKeyMap, done) {
|
|||
newContact.lead_id = existingLead.id;
|
||||
tasks.push(createAddContactFn(newContact, lead, existingLead, userApiKeyMap));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
if (err) return done(err);
|
||||
|
||||
// Add notes
|
||||
|
@ -690,7 +692,7 @@ function updateExistingLead(lead, existingLead, userApiKeyMap, done) {
|
|||
for (const newNote of newNotes) {
|
||||
tasks.push(createAddNoteFn(existingLead.id, newNote));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
|
@ -721,7 +723,7 @@ function saveNewLead(lead, done) {
|
|||
for (const newNote of newNotes) {
|
||||
tasks.push(createAddNoteFn(existingLead.id, newNote));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
if (err) return done(err);
|
||||
|
||||
// Send emails to new contacts
|
||||
|
@ -733,7 +735,7 @@ function saveNewLead(lead, done) {
|
|||
tasks.push(createSendEmailFn(email.email, existingLead.id, contact.id, emailTemplate, postData.status));
|
||||
}
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
|
@ -767,15 +769,15 @@ function createFindExistingLeadFn(email, name, existingLeads) {
|
|||
}
|
||||
|
||||
function createUpdateLeadFn(lead, existingLeads, userApiKeyMap) {
|
||||
// New contact lead matching algorithm:
|
||||
// 1. New contact email exists
|
||||
// 2. New contact NCES school id exists
|
||||
// 3. New contact NCES district id and no NCES school id
|
||||
// 4. New contact school name and no NCES data
|
||||
// 5. New contact district name and no NCES data
|
||||
return (done) => {
|
||||
// console.log('DEBUG: updateLead', lead.name);
|
||||
const query = `name:"${lead.name}"`;
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
if (data.total_results === 0) {
|
||||
|
||||
if (existingLeads[lead.name.toLowerCase()]) {
|
||||
if (existingLeads[lead.name.toLowerCase()].length === 1) {
|
||||
// console.log(`DEBUG: Using lead from email lookup: ${lead.name}`);
|
||||
|
@ -784,17 +786,44 @@ function createUpdateLeadFn(lead, existingLeads, userApiKeyMap) {
|
|||
console.error(`ERROR: ${existingLeads[lead.name.toLowerCase()].length} email leads found for ${lead.name}`);
|
||||
return done();
|
||||
}
|
||||
return saveNewLead(lead, done);
|
||||
|
||||
let nces_district_id;
|
||||
let nces_school_id;
|
||||
for (const trial of lead.trialRequests) {
|
||||
if (!trial.properties) continue;
|
||||
if (trial.properties.nces_district_id) {
|
||||
nces_district_id = trial.properties.nces_district_id;
|
||||
if (trial.properties.nces_id) {
|
||||
nces_district_id = trial.properties.nces_district_id;
|
||||
nces_school_id = trial.properties.nces_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(`DEBUG: updateLead district ${nces_district_id} school ${nces_school_id}`);
|
||||
|
||||
let query = `name:"${lead.name}"`;
|
||||
if (nces_school_id) {
|
||||
query = `custom.demo_nces_id:"${nces_school_id}"`;
|
||||
}
|
||||
else if (nces_district_id) {
|
||||
query = `custom.demo_nces_district_id:"${nces_district_id}" custom.demo_nces_id:""`;
|
||||
}
|
||||
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
|
||||
request.get(url, (error, response, body) => {
|
||||
if (error) return done(error);
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
if (data.total_results > 1) {
|
||||
console.error(`ERROR: ${data.total_results} leads found for ${lead.name}`);
|
||||
console.error(`ERROR: ${data.total_results} leads found for ${lead.name} nces_district_id=${nces_district_id} nces_school_id=${nces_school_id}`);
|
||||
return done();
|
||||
}
|
||||
if (data.total_results === 1) {
|
||||
return updateExistingLead(lead, data.data[0], userApiKeyMap, done);
|
||||
}
|
||||
return saveNewLead(lead, done);
|
||||
} catch (error) {
|
||||
// console.log(url);
|
||||
console.log(`ERROR: updateLead ${error}`);
|
||||
// console.log(body);
|
||||
return done();
|
||||
}
|
||||
});
|
||||
|
@ -939,7 +968,7 @@ function updateLeads(leads, done) {
|
|||
for (const closeIoMailApiKey of closeIoMailApiKeys) {
|
||||
tasks.push(createGetUserFn(closeIoMailApiKey.apiKey));
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
if (err) console.log(err);
|
||||
// Lookup existing leads via email to protect against direct lead name querying later
|
||||
// Querying via lead name is unreliable
|
||||
|
@ -951,14 +980,14 @@ function updateLeads(leads, done) {
|
|||
tasks.push(createFindExistingLeadFn(email.toLowerCase(), name.toLowerCase(), existingLeads));
|
||||
}
|
||||
}
|
||||
async.parallel(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
if (err) return done(err);
|
||||
const tasks = [];
|
||||
for (const name in leads) {
|
||||
if (leadsToSkip.indexOf(name) >= 0) continue;
|
||||
tasks.push(createUpdateLeadFn(leads[name], existingLeads, userApiKeyMap));
|
||||
}
|
||||
async.series(tasks, (err, results) => {
|
||||
async.parallelLimit(tasks, closeParallelLimit, (err, results) => {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -123,10 +123,14 @@ module.exports =
|
|||
if _.isEmpty(req.body)
|
||||
throw new errors.UnprocessableEntity('No input')
|
||||
|
||||
props = doc.schema.statics.editableProperties.slice()
|
||||
if not doc.schema.statics.editableProperties
|
||||
console.warn 'No editableProperties set for', doc.constructor.modelName
|
||||
props = (doc.schema.statics.editableProperties or []).slice()
|
||||
|
||||
if doc.isNew
|
||||
props = props.concat doc.schema.statics.postEditableProperties
|
||||
props = props.concat(doc.schema.statics.postEditableProperties or [])
|
||||
if not doc.schema.statics.postEditableProperties
|
||||
console.warn 'No postEditableProperties set for', doc.constructor.modelName
|
||||
|
||||
if doc.schema.uses_coco_permissions and req.user
|
||||
isOwner = doc.getAccessForUserObjectId(req.user._id) is 'owner'
|
||||
|
|
|
@ -16,64 +16,8 @@ Classroom = require '../models/Classroom'
|
|||
LevelHandler = class LevelHandler extends Handler
|
||||
modelClass: Level
|
||||
jsonSchema: require '../../app/schemas/models/level'
|
||||
editableProperties: [
|
||||
'description'
|
||||
'documentation'
|
||||
'background'
|
||||
'nextLevel'
|
||||
'scripts'
|
||||
'thangs'
|
||||
'systems'
|
||||
'victory'
|
||||
'name'
|
||||
'i18n'
|
||||
'icon'
|
||||
'goals'
|
||||
'type'
|
||||
'showsGuide'
|
||||
'banner'
|
||||
'employerDescription'
|
||||
'terrain'
|
||||
'i18nCoverage'
|
||||
'loadingTip'
|
||||
'requiresSubscription'
|
||||
'adventurer'
|
||||
'practice'
|
||||
'shareable'
|
||||
'adminOnly'
|
||||
'disableSpaces'
|
||||
'hidesSubmitUntilRun'
|
||||
'hidesPlayButton'
|
||||
'hidesRunShortcut'
|
||||
'hidesHUD'
|
||||
'hidesSay'
|
||||
'hidesCodeToolbar'
|
||||
'hidesRealTimePlayback'
|
||||
'backspaceThrottle'
|
||||
'lockDefaultCode'
|
||||
'moveRightLoopSnippet'
|
||||
'realTimeSpeedFactor'
|
||||
'autocompleteFontSizePx'
|
||||
'requiredCode'
|
||||
'suspectCode'
|
||||
'requiredGear'
|
||||
'restrictedGear'
|
||||
'allowedHeroes'
|
||||
'tasks'
|
||||
'helpVideos'
|
||||
'campaign'
|
||||
'campaignIndex'
|
||||
'replayable'
|
||||
'buildTime'
|
||||
'scoreTypes'
|
||||
'concepts'
|
||||
'picoCTFProblem'
|
||||
'practiceThresholdMinutes',
|
||||
'primerLanguage'
|
||||
'studentPlayInstructions'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
editableProperties: Level.editableProperties
|
||||
postEditableProperties: Level.postEditableProperties
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
return @getSession(req, res, args[0]) if args[1] is 'session'
|
||||
|
|
|
@ -9,62 +9,66 @@ mongoose = require 'mongoose'
|
|||
database = require '../commons/database'
|
||||
parse = require '../commons/parse'
|
||||
|
||||
# More info on database versioning: https://github.com/codecombat/codecombat/wiki/Versioning
|
||||
|
||||
module.exports =
|
||||
postNewVersion: (Model, options={}) -> wrap (req, res) ->
|
||||
# Find the document which is getting a new version
|
||||
parent = yield database.getDocFromHandle(req, Model)
|
||||
if not parent
|
||||
throw new errors.NotFound('Parent not found.')
|
||||
|
||||
# TODO: Figure out a better way to do this
|
||||
# Check permissions
|
||||
# TODO: Figure out an encapsulated way to do this; it's more permissions than versioning
|
||||
if options.hasPermissionsOrTranslations
|
||||
permissions = options.hasPermissionsOrTranslations
|
||||
permissions = [permissions] if _.isString(permissions)
|
||||
permissions = ['admin'] if not _.isArray(permissions)
|
||||
hasPermission = _.any(req.user?.hasPermission(permission) for permission in permissions)
|
||||
if Model.schema.uses_coco_permissions and not hasPermission
|
||||
hasPermission = parent.hasPermissionsForMethod(req.user, req.method)
|
||||
if not (hasPermission or database.isJustFillingTranslations(req, parent))
|
||||
throw new errors.Forbidden()
|
||||
|
||||
# Create the new version, a clone of the parent with POST data applied
|
||||
doc = database.initDoc(req, Model)
|
||||
ATTRIBUTES_NOT_INHERITED = ['_id', 'version', 'created', 'creator']
|
||||
doc.set(_.omit(parent.toObject(), ATTRIBUTES_NOT_INHERITED))
|
||||
|
||||
database.assignBody(req, doc, { unsetMissing: true })
|
||||
|
||||
# Get latest version
|
||||
# Get latest (minor or major) version. This may not be the same document (or same major version) as parent.
|
||||
latestSelect = 'version index slug'
|
||||
major = req.body.version?.major
|
||||
original = parent.get('original')
|
||||
if _.isNumber(major)
|
||||
q1 = Model.findOne({original: original, 'version.isLatestMinor': true, 'version.major': major})
|
||||
else
|
||||
q1 = Model.findOne({original: original, 'version.isLatestMajor': true})
|
||||
q1.select 'version'
|
||||
q1.select latestSelect
|
||||
latest = yield q1.exec()
|
||||
|
||||
if not latest
|
||||
# handle the case where no version is marked as latest, since making new
|
||||
# Handle the case where no version is marked as latest, since making new
|
||||
# versions is not atomic
|
||||
if not latest
|
||||
if _.isNumber(major)
|
||||
q2 = Model.findOne({original: original, 'version.major': major})
|
||||
q2.sort({'version.minor': -1})
|
||||
else
|
||||
q2 = Model.findOne()
|
||||
q2.sort({'version.major': -1, 'version.minor': -1})
|
||||
q2.select 'version'
|
||||
q2.select(latestSelect)
|
||||
latest = yield q2.exec()
|
||||
if not latest
|
||||
throw new errors.NotFound('Previous version not found.')
|
||||
|
||||
# Transfer latest version
|
||||
# Update the latest version, making it no longer the latest. This includes
|
||||
major = req.body.version?.major
|
||||
version = _.clone(latest.get('version'))
|
||||
wasLatestMajor = version.isLatestMajor
|
||||
version.isLatestMajor = false
|
||||
if _.isNumber(major)
|
||||
version.isLatestMinor = false
|
||||
|
||||
conditions = {_id: latest._id}
|
||||
|
||||
raw = yield Model.update(conditions, {version: version, $unset: {index: 1, slug: 1}})
|
||||
raw = yield latest.update({$set: {version: version}, $unset: {index: 1, slug: 1}})
|
||||
if not raw.nModified
|
||||
console.error('Conditions', conditions)
|
||||
console.error('Doc', doc)
|
||||
|
@ -89,7 +93,12 @@ module.exports =
|
|||
|
||||
doc.set('parent', latest._id)
|
||||
|
||||
try
|
||||
doc = yield doc.save()
|
||||
catch e
|
||||
# Revert changes to latest doc made earlier, should set everything back to normal
|
||||
yield latest.update({$set: _.pick(latest.toObject(), 'version', 'index', 'slug')})
|
||||
throw e
|
||||
|
||||
editPath = req.headers['x-current-path']
|
||||
docLink = "http://codecombat.com#{editPath}"
|
||||
|
|
|
@ -93,6 +93,7 @@ AchievementSchema.statics.editableProperties = [
|
|||
'i18nCoverage'
|
||||
'hidden'
|
||||
]
|
||||
AchievementSchema.statics.postEditableProperties = []
|
||||
|
||||
AchievementSchema.statics.jsonSchema = require '../../app/schemas/models/achievement'
|
||||
|
||||
|
|
|
@ -56,5 +56,6 @@ CampaignSchema.statics.editableProperties = [
|
|||
'adjacentCampaigns'
|
||||
'levels'
|
||||
]
|
||||
CampaignSchema.statics.postEditableProperties = []
|
||||
|
||||
module.exports = mongoose.model('campaign', CampaignSchema)
|
||||
|
|
|
@ -23,6 +23,7 @@ ClassroomSchema.statics.editableProperties = [
|
|||
'ageRangeMax'
|
||||
'archived'
|
||||
]
|
||||
ClassroomSchema.statics.postEditableProperties = []
|
||||
|
||||
ClassroomSchema.statics.generateNewCode = (done) ->
|
||||
tryCode = ->
|
||||
|
|
|
@ -28,6 +28,7 @@ CodeLogSchema.statics.editableProperties = [
|
|||
'log'
|
||||
'created'
|
||||
]
|
||||
CodeLogSchema.statics.postEditableProperties = []
|
||||
|
||||
CodeLogSchema.statics.jsonSchema = require '../../app/schemas/models/codelog.schema'
|
||||
|
||||
|
|
|
@ -46,4 +46,64 @@ LevelSchema.post 'init', (doc) ->
|
|||
if _.isString(doc.get('nextLevel'))
|
||||
doc.set('nextLevel', undefined)
|
||||
|
||||
LevelSchema.statics.postEditableProperties = ['name']
|
||||
|
||||
LevelSchema.statics.editableProperties = [
|
||||
'description'
|
||||
'documentation'
|
||||
'background'
|
||||
'nextLevel'
|
||||
'scripts'
|
||||
'thangs'
|
||||
'systems'
|
||||
'victory'
|
||||
'name'
|
||||
'i18n'
|
||||
'icon'
|
||||
'goals'
|
||||
'type'
|
||||
'showsGuide'
|
||||
'banner'
|
||||
'employerDescription'
|
||||
'terrain'
|
||||
'i18nCoverage'
|
||||
'loadingTip'
|
||||
'requiresSubscription'
|
||||
'adventurer'
|
||||
'practice'
|
||||
'shareable'
|
||||
'adminOnly'
|
||||
'disableSpaces'
|
||||
'hidesSubmitUntilRun'
|
||||
'hidesPlayButton'
|
||||
'hidesRunShortcut'
|
||||
'hidesHUD'
|
||||
'hidesSay'
|
||||
'hidesCodeToolbar'
|
||||
'hidesRealTimePlayback'
|
||||
'backspaceThrottle'
|
||||
'lockDefaultCode'
|
||||
'moveRightLoopSnippet'
|
||||
'realTimeSpeedFactor'
|
||||
'autocompleteFontSizePx'
|
||||
'requiredCode'
|
||||
'suspectCode'
|
||||
'requiredGear'
|
||||
'restrictedGear'
|
||||
'allowedHeroes'
|
||||
'tasks'
|
||||
'helpVideos'
|
||||
'campaign'
|
||||
'campaignIndex'
|
||||
'replayable'
|
||||
'buildTime'
|
||||
'scoreTypes'
|
||||
'concepts'
|
||||
'picoCTFProblem'
|
||||
'practiceThresholdMinutes',
|
||||
'primerLanguage'
|
||||
'studentPlayInstructions'
|
||||
]
|
||||
|
||||
|
||||
module.exports = Level = mongoose.model('level', LevelSchema)
|
||||
|
|
|
@ -97,6 +97,10 @@ module.exports.setup = (app) ->
|
|||
app.get('/db/course_instance/:handle/classroom', mw.auth.checkLoggedIn(), mw.courseInstances.fetchClassroom)
|
||||
app.get('/db/course_instance/:handle/course', mw.auth.checkLoggedIn(), mw.courseInstances.fetchCourse)
|
||||
|
||||
Level = require '../models/Level'
|
||||
app.post('/db/level/:handle', mw.auth.checkLoggedIn(), mw.versions.postNewVersion(Level, { hasPermissionsOrTranslations: 'artisan' })) # TODO: add /new-version to route like Article has
|
||||
app.get('/db/level/:handle/session', mw.auth.checkHasUser(), mw.levels.upsertSession)
|
||||
|
||||
app.put('/db/user/:handle', mw.users.resetEmailVerifiedFlag)
|
||||
app.delete('/db/user/:handle', mw.users.removeFromClassrooms)
|
||||
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)
|
||||
|
@ -104,7 +108,6 @@ module.exports.setup = (app) ->
|
|||
app.put('/db/user/-/remain-teacher', mw.users.remainTeacher)
|
||||
app.post('/db/user/:userID/request-verify-email', mw.users.sendVerificationEmail)
|
||||
app.post('/db/user/:userID/verify/:verificationCode', mw.users.verifyEmailAddress) # TODO: Finalize URL scheme
|
||||
app.get('/db/level/:handle/session', mw.auth.checkHasUser(), mw.levels.upsertSession)
|
||||
app.get('/db/user/-/students', mw.auth.checkHasPermission(['admin']), mw.users.getStudents)
|
||||
app.get('/db/user/-/teachers', mw.auth.checkHasPermission(['admin']), mw.users.getTeachers)
|
||||
app.post('/db/user/:handle/signup-with-facebook', mw.users.signupWithFacebook)
|
||||
|
|
|
@ -52,9 +52,40 @@ describe 'POST /db/level/:handle', ->
|
|||
|
||||
url = getURL("/db/level/#{@level.id}")
|
||||
[res, body] = yield request.postAsync({url: url, json: levelJSON})
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.statusCode).toBe(201)
|
||||
done()
|
||||
|
||||
it 'does not break the target level if a name change would conflict with another level', utils.wrap (done) ->
|
||||
yield utils.clearModels([Level, User])
|
||||
user = yield utils.initUser()
|
||||
yield utils.loginUser(user)
|
||||
yield utils.makeLevel({name: 'Taken Name'})
|
||||
level = yield utils.makeLevel({name: 'Another Level'})
|
||||
json = _.extend({}, level.toObject(), {name: 'Taken Name'})
|
||||
[res, body] = yield request.postAsync({url: utils.getURL("/db/level/#{level.id}"), json})
|
||||
expect(res.statusCode).toBe(409)
|
||||
level = yield Level.findById(level.id)
|
||||
# should be unchanged
|
||||
expect(level.get('slug')).toBe('another-level')
|
||||
expect(level.get('version').isLatestMinor).toBe(true)
|
||||
expect(level.get('version').isLatestMajor).toBe(true)
|
||||
expect(level.get('index')).toBeDefined()
|
||||
done()
|
||||
|
||||
it 'enforces permissions', ->
|
||||
yield utils.clearModels([Level, User])
|
||||
user = yield utils.initUser()
|
||||
yield utils.loginUser(user)
|
||||
level = yield utils.makeLevel({description:'Original desc'})
|
||||
|
||||
otherUser = yield utils.initUser()
|
||||
yield utils.loginUser(otherUser)
|
||||
json = _.extend({}, level.toObject(), {description: 'Trollin'})
|
||||
[res, body] = yield request.postAsync({url: utils.getURL("/db/level/#{level.id}"), json})
|
||||
expect(res.statusCode).toBe(403)
|
||||
level = yield Level.findById(level.id)
|
||||
expect(level.get('description')).toBe('Original desc')
|
||||
done()
|
||||
|
||||
describe 'GET /db/level/:handle/session', ->
|
||||
|
||||
|
|
37
test/app/views/play/CampaignView.spec.coffee
Normal file
37
test/app/views/play/CampaignView.spec.coffee
Normal file
|
@ -0,0 +1,37 @@
|
|||
factories = require 'test/app/factories'
|
||||
CampaignView = require 'views/play/CampaignView'
|
||||
Levels = require 'collections/Levels'
|
||||
|
||||
describe 'CampaignView', ->
|
||||
|
||||
describe 'when 4 earned levels', ->
|
||||
beforeEach ->
|
||||
@campaignView = new CampaignView()
|
||||
@campaignView.levelStatusMap = {}
|
||||
levels = new Levels(_.times(4, -> factories.makeLevel()))
|
||||
@campaignView.campaign = factories.makeCampaign({}, {levels})
|
||||
@levels = (level.toJSON() for level in levels.models)
|
||||
earned = me.get('earned') or {}
|
||||
earned.levels ?= []
|
||||
earned.levels.push(level.original) for level in @levels
|
||||
me.set('earned', earned)
|
||||
|
||||
describe 'and 3rd one is practice', ->
|
||||
beforeEach ->
|
||||
@levels[2].practice = true
|
||||
@campaignView.annotateLevels(@levels)
|
||||
it 'hides next levels if there are practice levels to do', ->
|
||||
expect(@levels[2].hidden).toEqual(false)
|
||||
expect(@levels[3].hidden).toEqual(true)
|
||||
|
||||
describe 'and 2nd rewards a practice a non-practice level', ->
|
||||
beforeEach ->
|
||||
@campaignView.levelStatusMap[@levels[0].slug] = 'complete'
|
||||
@campaignView.levelStatusMap[@levels[1].slug] = 'complete'
|
||||
@levels[1].rewards = [{level: @levels[2].original}, {level: @levels[3].original}]
|
||||
@levels[2].practice = true
|
||||
@campaignView.annotateLevels(@levels)
|
||||
@campaignView.determineNextLevel(@levels)
|
||||
it 'points at practice level first', ->
|
||||
expect(@levels[2].next).toEqual(true)
|
||||
expect(@levels[3].next).not.toBeDefined(true)
|
8
vendor/scripts/jquery.minicolors.min.js
vendored
8
vendor/scripts/jquery.minicolors.min.js
vendored
File diff suppressed because one or more lines are too long
245
vendor/styles/jquery.minicolors.css
vendored
245
vendor/styles/jquery.minicolors.css
vendored
|
@ -1,245 +0,0 @@
|
|||
.minicolors {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.minicolors-swatch {
|
||||
position: absolute;
|
||||
vertical-align: middle;
|
||||
background: url(/images/jquery.minicolors.png) -80px 0;
|
||||
border: solid 1px #ccc;
|
||||
cursor: text;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.minicolors-swatch-color {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.minicolors input[type=hidden] + .minicolors-swatch {
|
||||
width: 28px;
|
||||
position: static;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Panel */
|
||||
.minicolors-panel {
|
||||
position: absolute;
|
||||
width: 173px;
|
||||
height: 152px;
|
||||
background: white;
|
||||
border: solid 1px #CCC;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, .2);
|
||||
z-index: 99999;
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.minicolors-panel.minicolors-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Panel positioning */
|
||||
.minicolors-position-top .minicolors-panel {
|
||||
top: -154px;
|
||||
}
|
||||
|
||||
.minicolors-position-right .minicolors-panel {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.minicolors-position-bottom .minicolors-panel {
|
||||
top: auto;
|
||||
}
|
||||
|
||||
.minicolors-position-left .minicolors-panel {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.minicolors-with-opacity .minicolors-panel {
|
||||
width: 194px;
|
||||
}
|
||||
|
||||
.minicolors .minicolors-grid {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: url(/images/jquery.minicolors.png) -120px 0;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.minicolors .minicolors-grid-inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.minicolors-slider-saturation .minicolors-grid {
|
||||
background-position: -420px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider-saturation .minicolors-grid-inner {
|
||||
background: url(/images/jquery.minicolors.png) -270px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider-brightness .minicolors-grid {
|
||||
background-position: -570px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider-brightness .minicolors-grid-inner {
|
||||
background: black;
|
||||
}
|
||||
|
||||
.minicolors-slider-wheel .minicolors-grid {
|
||||
background-position: -720px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider,
|
||||
.minicolors-opacity-slider {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 152px;
|
||||
width: 20px;
|
||||
height: 150px;
|
||||
background: white url(/images/jquery.minicolors.png) 0 0;
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.minicolors-slider-saturation .minicolors-slider {
|
||||
background-position: -60px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider-brightness .minicolors-slider {
|
||||
background-position: -20px 0;
|
||||
}
|
||||
|
||||
.minicolors-slider-wheel .minicolors-slider {
|
||||
background-position: -20px 0;
|
||||
}
|
||||
|
||||
.minicolors-opacity-slider {
|
||||
left: 173px;
|
||||
background-position: -40px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.minicolors-with-opacity .minicolors-opacity-slider {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Pickers */
|
||||
.minicolors-grid .minicolors-picker {
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
left: 70px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: solid 1px black;
|
||||
border-radius: 10px;
|
||||
margin-top: -6px;
|
||||
margin-left: -6px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.minicolors-grid .minicolors-picker > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 8px;
|
||||
border: solid 2px white;
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.minicolors-picker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
background: white;
|
||||
border: solid 1px black;
|
||||
margin-top: -2px;
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* Inline controls */
|
||||
.minicolors-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.minicolors-inline .minicolors-input {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.minicolors-inline .minicolors-panel {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
box-shadow: none;
|
||||
z-index: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Default theme */
|
||||
.minicolors-theme-default .minicolors-swatch {
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.minicolors-theme-default.minicolors-position-right .minicolors-swatch {
|
||||
left: auto;
|
||||
right: 5px;
|
||||
}
|
||||
.minicolors-theme-default.minicolors {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
.minicolors-theme-default .minicolors-input {
|
||||
height: 20px;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
padding-left: 26px;
|
||||
}
|
||||
.minicolors-theme-default.minicolors-position-right .minicolors-input {
|
||||
padding-right: 26px;
|
||||
padding-left: inherit;
|
||||
}
|
||||
|
||||
/* Bootstrap theme */
|
||||
.minicolors-theme-bootstrap .minicolors-swatch {
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.minicolors-theme-bootstrap.minicolors-position-right .minicolors-swatch {
|
||||
left: auto;
|
||||
right: 3px;
|
||||
}
|
||||
.minicolors-theme-bootstrap .minicolors-input {
|
||||
padding-left: 44px;
|
||||
}
|
||||
.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input {
|
||||
padding-right: 44px;
|
||||
padding-left: 12px;
|
||||
}
|
Loading…
Reference in a new issue