Updated signature test

Updated signature test

--- test/signature.coffee
+++ test/signature.coffee
@@ -7,5 +7,12 @@
   it 'is a function', ->
     expect(bodyguard).to.be.a('function')

+  it 'throws an exception when no name is defined', ->
+    expect(-> bodyguard()).to.throw(Error)
+
+  it 'returns undefined if no definition or object is defined', ->
+    expect(bodyguard('test')).to.be.a('undefined')
+    expect(bodyguard('test')).to.equal(undefined)
+
   it 'has silence property', ->
     expect(bodyguard).to.have.a.property('silent')


Improve SQL Beautifier to support missing config options.

Fixes #67.

--- lib/langs/sql-beautify.coffee
+++ lib/langs/sql-beautify.coffee
@@ -3,10 +3,15 @@
 ###
 getCmd = (inputPath, outputPath, options) ->
   path = options.sqlformat_path
-  optionsStr = " --indent_width={0} --keywords={1} --identifiers={2} --reindent"
-  optionsStr = optionsStr.replace("{0}", options.indent_size)
-		                     .replace("{1}", options.keywords)
-      					         .replace("{2}", options.identifiers)
+
+  optionsStr = "--reindent"
+  if options.indent_size?
+    optionsStr += " --indent_width=#{options.indent_size}"
+  if options.keywords?
+    optionsStr += " --keywords=#{options.keywords}"
+  if options.identifiers
+    optionsStr += " --identifiers=#{options.identifiers}"
+
   if path
     # Use absolute path
     "python \"" + path + "\" \"" + inputPath + "\" " + optionsStr


remove text.html.basic from linter scope

--- lib/linter-js-standard.coffee
+++ lib/linter-js-standard.coffee
@@ -7,7 +7,7 @@

   # The syntax that the linter handles. May be a string or
   # list/tuple of strings. Names should be all lowercase.
-  @syntax: ['source.js', 'source.js.jsx', 'source.js.jquery', 'text.html.basic']
+  @syntax: ['source.js', 'source.js.jsx', 'source.js.jquery']

   # A string, list, tuple or callable that returns a string, list or tuple,
   # containing the command line (with arguments) used to lint.


318 more strings translated

318 more strings translated

--- app/locale/el.coffee
+++ app/locale/el.coffee
@@ -363,8 +363,8 @@
     auth_caption: "Αποθήκευσε την πρόοδό σου."

   leaderboard:
-#    leaderboard: "Leaderboard"
-#    view_other_solutions: "View Leaderboards"
+    leaderboard: "Πίνακας Πρωτοπόρων"
+    view_other_solutions: "Προβολή Πίνακα Πρωτοπόρων"
     scores: "Σκορ"
     top_players: "Κορυφαίοι Παίκτες Κατά"
     day: "Σήμερα"
@@ -630,57 +630,57 @@
     monitor_progress_4: "σελίδα."
     monitor_progress_5: "Αφού γίνουν μέλη, θα δεις μια περίληψη της προόδου του μαθητή στη σελίδα της φατρίας σου."
     private_clans_1: "Οι ιδιωτικές φατρίες παρέχουν αυξημένη ιδιωτικήτητα και λεπτομερίς πληροφορίες προόδου για κάθε ένα μαθητή."
-#    private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
+    private_clans_2: "Για να φτιάξεις μια ιδωιτκή φατρία, τσέκαρε το 'Κάνε τη φατρία ιδιωτική' όταν φτιάχνεις μια"
     private_clans_3: "."
-#    who_for_title: "Who is CodeCombat for?"
-#    who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
-#    who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
-#    material_title: "How much material is there?"
-#    material_premium_server: "Approximately 50 hours of gameplay spread over 190+ subscriber-only levels so far."
-#    material_1: "Approximately 25 hours of free content and an additional 15 hours of subscriber content."
-#    concepts_title: "What concepts are covered?"
-#    how_much_title: "How much does a monthly subscription cost?"
-#    how_much_1: "A"
-#    how_much_2: "monthly subscription"
-#    how_much_3: "costs $9.99, and can be cancelled anytime."
-#    how_much_4: "Additionally, we provide discounts for larger groups:"
-#    how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school.  Please contact"
-#    how_much_6: "for more details."
-#    more_info_title: "Where can I find more information?"
-#    more_info_1: "Our"
-#    more_info_2: "teachers forum"
-#    more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
-#    sys_requirements_title: "System Requirements"
-#    sys_requirements_1: "A modern web browser. Newer versions of Chrome, Firefox, or Safari. Internet Explorer 9 or later."
-#    sys_requirements_2: "CodeCombat is not supported on iPad yet."
-
-#  teachers_survey:
-#    title: "Teacher Survey"
-#    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
-#    retrieving: "Retrieving information..."
-#    being_reviewed_1: "Your application for a free trial subscription is being"
-#    being_reviewed_2: "reviewed."
-#    approved_1: "Your application for a free trial subscription was"
-#    approved_2: "approved."
-#    approved_3: "Further instructions have been sent to"
-#    denied_1: "Your application for a free trial subscription has been"
-#    denied_2: "denied."
-#    contact_1: "Please contact"
-#    contact_2: "if you have further questions."
-#    description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our"
-#    description_2: "teachers"
-#    description_3: "page."
-#    description_4: "Please fill out this quick survey and we’ll email you setup instructions."
-#    email: "Email Address"
-#    school: "Name of School"
-#    location: "Name of City"
-#    age_students: "How old are your students?"
-#    under: "Under"
-#    other: "Other:"
-#    amount_students: "How many students do you teach?"
-#    hear_about: "How did you hear about CodeCombat?"
-#    fill_fields: "Please fill out all fields."
-#    thanks: "Thanks! We'll send you setup instructions shortly."
+    who_for_title: "Για ποιον είναι το CodeCombat;"
+    who_for_1: "Προτείνουμε το CodeCombat για μαθητές από 9 χρονών και πάνω. Δεν απαιτείται προηγούμενη πείρα στον προγραμματισμό."
+    who_for_2: "Σχεδιάσαμε το CodeCombat και για αγόρια και για κορίτσια."
+    material_title: "Πόσο υλικό υπάρχει;"
+    material_premium_server: "Περίπου 50 ώρες παιχνιδιού, με πάνω από 190 επίπεδα μόνο για συνδρομητές, μέχρι τώρα."
+    material_1: "Περίπου 25 ώρες δψρεάν περιεχομένου και επιπλέον 15 ώρες συνδρομητικού περιεχομένου."
+    concepts_title: "Ποια θέματα καλύπτονται;"
+    how_much_title: "Πόσο κοστίζει μια μηνιαία συνδρομή;"
+    how_much_1: "Μια"
+    how_much_2: "μηνιαία συνδρομή"
+    how_much_3: "κοστίζει $9.99 δολλάρια ΗΠΑ, και μπορεί να ακυρωθεί οποιαδήποτε στιγμή."
+    how_much_4: "Επιπλεέον, προσφέρουμε εκπτώσεις για μεγαλύτερες ομάδες:"
+    how_much_5: "Δεχόμαστε αγορές με έκπτωση, μόνο για μια φορά, και ετήσιες συνδρομέςγια ομάδες, όπως μια τάξη ή ένα σχολείο. Παρακαλούμε επικοινωνείστε"
+    how_much_6: "για περισσότερες πληροφορίες."
+    more_info_title: "Που μπορώ να βρω περισσότερες πληροφορίες"
+    more_info_1: "Το"
+    more_info_2: "φόρουμ των εκπαιδευτικών"
+    more_info_3: "είναι ένα καλό σημείο για να συνδεθείτε με άλλους εκπαιδευτικούς που χρησιμοποιούν το CodeCombat."
+    sys_requirements_title: "Απαιτήσεις Συστήματος"
+    sys_requirements_1: "Ένας σύγχρονος φυλλομετρητής. Οι νεώτερες εκδόσεις των Chrome, Firefox, ή Safari. Ο Internet Explorer από την έκδοση 9 και μετά."
+    sys_requirements_2: "Το CodeCombat δεν υποστηρίζεται ακόμα στο iPad."
+
+  teachers_survey:
+    title: "Έρευνα Εκπαιδευτικών"
+    must_be_logged: "Πρέπει πρώτα να συνδεθείται. Παρακαλώ δημιουργήστε ένα λογαριασμό ή συνδεθείται από το μενού παραπάνω."
+    retrieving: "Ανάκληση πληροφοριών..."
+    being_reviewed_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή είναι"
+    being_reviewed_2: "υπό θεώρηση."
+    approved_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή"
+    approved_2: "εγκρίθηκε."
+    approved_3: "Σας στείλαμε περισσότερες οδηγίες στο"
+    denied_1: "Η αίτησή σας για δωρεάν δοκιμαστική συνδρομή"
+    denied_2: "απορίφθηκε."
+    contact_1: "Παρακαλούμε επικοινωνείστε"
+    contact_2: "αν έχετε περαιτέρω ερωτήσεις."
+    description_1: "Δίνουμε δωρεάν συνδρομές σε εκπαιδευτικούς για να αξιολογήσουν το παιχνίδι. Μπορείτε να βρείτε περισσότερες πληροφορίες στις"
+    description_2: "εκπαιδευτική"
+    description_3: "σελίδα."
+    description_4: "Παρακαλούμε, συμπληρώστε αυτή τη φόρμα και θα σας στείλουμε οδηγίες."
+    email: "Διεύθυνση Ηλεκτρονικού Ταχυδρομείου"
+    school: "Όνομα Σχολείου"
+    location: "Πόλη"
+    age_students: "Πόσο χρονών είναι οι μαθητές σας;"
+    under: "Κάτω από"
+    other: "Άλλο:"
+    amount_students: "Πόσους μαθητές διδάσκεται;"
+    hear_about: "Πως μάθατε για το CodeCombat;"
+    fill_fields: "Παρακαλώ συμπληρώστε όλα τα πεδία."
+    thanks: "Ευχαριστούμε! Θα σας στείλουμε οδηγίες για τις ρυθμίσεις σας σύντομα."

   versions:
     save_version_title: "Αποθήκευση Νέας Έκδοσης"
@@ -734,9 +734,9 @@
     email_announcements: "Ανακοινώσεις"
     email_announcements_description: "Λάβετε emails για τα τελευταία νέα και τις εξελίξεις του CodeCombat."
     email_notifications: "Ειδοποιήσεις"
-#    email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
-#    email_any_notes: "Any Notifications"
-#    email_any_notes_description: "Disable to stop all activity notification emails."
+    email_notifications_summary: "Έλεγχοι για προσωποποιημένη, αυτόματη ειδοποίηση μέσω ηλεκτρονικού ταχυδρομείου σχετικά με τη δραστηριότητά σου στο CodeCombat."
+    email_any_notes: "Ειδοποιήσεις"
+    email_any_notes_description: "Απενεργοποίησέ το για να σταματήσεις να δέχεσαι μηνύματα για όλες τις δραστηριότητες."
     email_news: "Ειδήσεις"
     email_recruit_notes: "Θέσεις εργασίας"
     email_recruit_notes_description: "Αν παίζεις πραγματικά καλά, μπορεί να επικοινωνήσουμε μαζί σου, για να σου δώσουμε μια ()καλύτερη) δουλειά."
@@ -755,211 +755,211 @@
     sample_profile: "Δες ένα δοκιμαστικό προφίλ"
     view_profile: "Προβολή του προφίλ σου"

-#  keyboard_shortcuts:
-#    keyboard_shortcuts: "Keyboard Shortcuts"
-#    space: "Space"
-#    enter: "Enter"
-#    press_enter: "press enter"
-#    escape: "Escape"
-#    shift: "Shift"
-#    run_code: "Run current code."
-#    run_real_time: "Run in real time."
-#    continue_script: "Continue past current script."
+  keyboard_shortcuts:
+    keyboard_shortcuts: "Συντομεύσει πληκτρολογίοθ"
+    space: "Κενό"
+    enter: "Enter"
+    press_enter: "πατείστε enter"
+    escape: "Escape"
+    shift: "Shift"
+    run_code: "Εκτέλεση κώδικα."
+    run_real_time: "Εκτέλεση σε πραγματικό χρόνο."
+    continue_script: "Συνέχεια μετά το τρέχων σενάριο εντολών."
 #    skip_scripts: "Skip past all skippable scripts."
-#    toggle_playback: "Toggle play/pause."
+    toggle_playback: "Εναλλαγή εκτέλεσης/παύσης."
 #    scrub_playback: "Scrub back and forward through time."
 #    single_scrub_playback: "Scrub back and forward through time by a single frame."
 #    scrub_execution: "Scrub through current spell execution."
-#    toggle_debug: "Toggle debug display."
+    toggle_debug: "Εναλλαγή προβολής εκσφαλμάτωσης."
 #    toggle_grid: "Toggle grid overlay."
 #    toggle_pathfinding: "Toggle pathfinding overlay."
 #    beautify: "Beautify your code by standardizing its formatting."
-#    maximize_editor: "Maximize/minimize code editor."
-
-#  community:
-#    main_title: "CodeCombat Community"
-#    introduction: "Check out the ways you can get involved below and decide what sounds the most fun. We look forward to working with you!"
-#    level_editor_prefix: "Use the CodeCombat"
-#    level_editor_suffix: "to create and edit levels. Users have created levels for their classes, friends, hackathons, students, and siblings. If create a new level sounds intimidating you can start by forking one of ours!"
-#    thang_editor_prefix: "We call units within the game 'thangs'. Use the"
-#    thang_editor_suffix: "to modify the CodeCombat source artwork. Allow units to throw projectiles, alter the direction of an animation, change a unit's hit points, or upload your own vector sprites."
-#    article_editor_prefix: "See a mistake in some of our docs? Want to make some instructions for your own creations? Check out the"
-#    article_editor_suffix: "and help CodeCombat players get the most out of their playtime."
-#    find_us: "Find us on these sites"
-#    social_github: "Check out all our code on GitHub"
-#    social_blog: "Read the CodeCombat blog on Sett"
-#    social_discource: "Join the discussion on our Discourse forum"
-#    social_facebook: "Like CodeCombat on Facebook"
-#    social_twitter: "Follow CodeCombat on Twitter"
-#    social_gplus: "Join CodeCombat on Google+"
-#    social_hipchat: "Chat with us in the public CodeCombat Slack channel"
-#    contribute_to_the_project: "Contribute to the project"
-
-#  clans:
-#    clan: "Clan"
-#    clans: "Clans"
-#    new_name: "New clan name"
-#    new_description: "New clan description"
-#    make_private: "Make clan private"
-#    subs_only: "subscribers only"
-#    create_clan: "Create New Clan"
-#    private_preview: "Preview"
-#    public_clans: "Public Clans"
-#    my_clans: "My Clans"
-#    clan_name: "Clan Name"
-#    name: "Name"
-#    chieftain: "Chieftain"
-#    type: "Type"
-#    edit_clan_name: "Edit Clan Name"
-#    edit_clan_description: "Edit Clan Description"
-#    edit_name: "edit name"
-#    edit_description: "edit description"
-#    private: "(private)"
-#    summary: "Summary"
-#    average_level: "Average Level"
-#    average_achievements: "Average Achievements"
-#    delete_clan: "Delete Clan"
-#    leave_clan: "Leave Clan"
-#    join_clan: "Join Clan"
-#    invite_1: "Invite:"
-#    invite_2: "*Invite players to this Clan by sending them this link."
-#    members: "Members"
-#    progress: "Progress"
-#    not_started_1: "not started"
-#    started_1: "started"
-#    complete_1: "complete"
-#    exp_levels: "Expand levels"
-#    rem_hero: "Remove Hero"
-#    status: "Status"
-#    complete_2: "Complete"
-#    started_2: "Started"
-#    not_started_2: "Not Started"
-#    view_solution: "Click to view solution."
-#    latest_achievement: "Latest Achievement"
-#    playtime: "Playtime"
-#    last_played: "Last played"
-#    leagues_explanation: "Play in a league against other clan members in these multiplayer arena instances."
-#    track_concepts1: "Track concepts"
-#    track_concepts2a: "learned by each student"
-#    track_concepts2b: "learned by each member"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
-#    track_concepts6a: "Sort students by name or progress"
-#    track_concepts6b: "Sort members by name or progress"
-#    track_concepts7: "Requires invitation"
-#    track_concepts8: "to join"
-#    private_require_sub: "Private clans require a subscription to create or join."
-
-#  courses:
-#    course: "Course"
-#    courses: "courses"
-#    not_enrolled: "You are not enrolled in this course."
-#    visit_pref: "Please visit the"
-#    visit_suf: "page to enroll."
-#    select_class: "Select one of your classes"
-#    unnamed: "*unnamed*"
-#    select: "Select"
-#    unnamed_class: "Unnamed Class"
-#    edit_settings: "edit class settings"
-#    edit_settings1: "Edit Class Settings"
-#    progress: "Class Progress"
-#    add_students: "Add Students"
-#    stats: "Statistics"
-#    total_students: "Total students:"
-#    average_time: "Average level play time:"
-#    total_time: "Total play time:"
-#    average_levels: "Average levels completed:"
-#    total_levels: "Total levels completed:"
-#    furthest_level: "Furthest level completed:"
-#    concepts_covered: "Concepts Covered"
-#    students: "Students"
-#    students1: "students"
-#    expand_details: "Expand details"
-#    concepts: "Concepts"
-#    levels: "levels"
-#    played: "Played"
-#    play_time: "Play time:"
-#    completed: "Completed:"
-#    invite_students: "Invite students to join this class."
-#    invite_link_header: "Link to join course"
-#    invite_link_p_1: "Give this link to students you would like to have join the course."
-#    invite_link_p_2: "Or have us email them directly:"
-#    capacity_used: "Course slots used:"
-#    enter_emails: "Enter student emails to invite, one per line"
-#    send_invites: "Send Invites"
-#    title: "Title"
-#    description: "Description"
-#    languages_available: "Select programming languages available to the class:"
-#    all_lang: "All Languages"
-#    show_progress: "Show student progress to everyone in the class"
-#    creating_class: "Creating class..."
-#    purchasing_course: "Purchasing course..."
-#    buy_course: "Buy Course"
-#    buy_course1: "Buy this course"
-#    create_class: "Create Class"
-#    select_all_courses: "Select 'All Courses' for a 50% discount!"
-#    all_courses: "All Courses"
-#    number_students: "Number of students"
-#    enter_number_students: "Enter the number of students you need for this class."
-#    name_class: "Name your class"
-#    displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
-#    buy: "Buy"
-#    purchasing_for: "You are purchasing a license for"
-#    creating_for: "You are creating a class for"
-#    for: "for" # Like in 'for 30 students'
-#    receive_code: "Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class."
-#    free_trial: "Free trial for teachers!"
-#    get_access: "to get individual access to all courses for evalutaion purposes."
-#    questions: "Questions?"
-#    faq: "Courses FAQ"
-#    question: "Q:" # Like in 'Question'
-#    question1: "What's the difference between these courses and the single player game?"
-#    answer: "A:" # Like in 'Answer'
-#    answer1: "The single player game is designed for individuals, while the courses are designed for classes."
-#    answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
-#    teachers_click: "Teachers Click Here"
-#    students_click: "Students Click Here"
-#    courses_on_coco: "Courses on CodeCombat"
+    maximize_editor: "Μεγιστοποίηση/ελαχιστοποίηση διορθωτή κώδικα."
+
+  community:
+    main_title: "Κοινότητα του CodeCombat"
+    introduction: "Δες τι τρόποι υπάρχουν για να συνεισφέρεις και αποφάσισε ποιος ακούγεται πιο διασκεδαστικός. Ανυπομονούμε να δουλέψουμε μαζί σου!"
+    level_editor_prefix: "Χρησιμοποίησε το CodeCombat"
+    level_editor_suffix: "για να δημιουργήσεις και επεξεργαστείς επίπεδα. Οι χρήστες έχουν δημιουργήσει επίπεδα για τις τάξεις τους, τους φίλους τους, για ημέρες δημιουργίας (hackathons), για μαθητές, και για τα παιδιά τους. Αν το να δημιουργήσεις ένα νέο επίπεδο ακούγεται τρομακτικό μπορείς να ξεκινήσεις αλλάζοντας ένα από τα δικά μας!"
+    thang_editor_prefix: "Τι μονάδες μέσα στο παιχνίδι τις ονομάζουμε 'thangs'. Χρησιμοποίησε το"
+    thang_editor_suffix: "για να αλλάξεις τα γραφικά του CodeCombat. Επέτρεψε στις μονάδες να ρίχνουν βλήματα, άλλαξε την κατεύθυνση του animation, άλλαξε τους πόντους κτυπήματος μιας μονάδα, ή ανέβασε τα δικά σου γραφικά."
+    article_editor_prefix: "Είδες ένα λάθος στην τεκμηρίωσή μας; Θέλεις να γράψεις οδηγίες για τις δημιουργίες σου; Κοίταξε το"
+    article_editor_suffix: "και βοήθησε άλλους παίκτες του CodeCombat να εκμεταλευτούν το χρόνο τους όσο το δυνατόν καλύτερα."
+    find_us: "Μπορείς να μας βρεις στους ακόλουθους ιστοχώρους"
+    social_github: "Δες όλον τον κώδικά μας στο GitHub"
+    social_blog: "Διάβασε το ιστολόγιο του CodeCombat στο Sett"
+    social_discource: "Μπείτε στη συζήτηση στο φόρουμ μας που είναι βασισμένο στο Discourse"
+    social_facebook: "Κάντε Like στο CodeCombat στο Facebook"
+    social_twitter: "Ακολουθείστε το CodeCombat στο Twitter"
+    social_gplus: "Συνδεθείτε με το CodeCombat στο Google+"
+    social_hipchat: "Κουβεντιάστε μαζί μας στο δημόσιο κανάλι του CodeCombat στο Slack"
+    contribute_to_the_project: "Contribute to the project"
+
+  clans:
+    clan: "Φατρία"
+    clans: "Φατρίες"
+    new_name: "Όνομα νέας φατρίας"
+    new_description: "Περιγραφή νέας φατρίας"
+    make_private: "Ιδιωτική φατρία"
+    subs_only: "μόνο για συνδρομητές"
+    create_clan: "Δημιουργία Νέας Φατρίας"
+    private_preview: "Προεπισκόπηση"
+    public_clans: "Δημόσιες Φατρίες"
+    my_clans: "Οι Φατρίες Μου"
+    clan_name: "Όνομα Φατρίας"
+    name: "Όνομα"
+    chieftain: "Αρχηγός της Φατρίας"
+    type: "Τύπος"
+    edit_clan_name: "Επεξεργασία Ονόματος Φατρίας"
+    edit_clan_description: "Επεξεργασία Περιγραφής Φατρίας"
+    edit_name: "επεξεργασία ονόματος"
+    edit_description: "επεξεργασία περιγραφής"
+    private: "(ιδιωτική)"
+    summary: "Περίληψη"
+    average_level: "Μέσο Επίπεδο"
+    average_achievements: "Μέσα Κατορθώματα"
+    delete_clan: "Διαγραφή Φατρίας"
+    leave_clan: "Εγκατέλειψε τη Φατρία"
+    join_clan: "Μπες στη Φατρία"
+    invite_1: "Προσκάλεσε:"
+    invite_2: "*Προσκάλεσ παίκτες στη Φατρία αυτή στέλνοντας τους συτό το σύνδεσμο."
+    members: "Μέλη"
+    progress: "Πρόοδος"
+    not_started_1: "δεν ξεκίνησε"
+    started_1: "ξεκίνησε"
+    complete_1: "ολοκλήρωση"
+    exp_levels: "Επέκταση επιπέδων"
+    rem_hero: "Αφαίρεση Ήρωα"
+    status: "Κατάσταση"
+    complete_2: "Ολοκληρώθηκε"
+    started_2: "Ξεκίνησε"
+    not_started_2: "Δεν ξεκίνησε"
+    view_solution: "Πάτα για να δεις τη λύση."
+    latest_achievement: "Τελευταίο Κατόρθωμα"
+    playtime: "Χρόνος παιχνιδιού"
+    last_played: "Έπαιξε τελευταία φορά"
+    leagues_explanation: "Παίξε πρωτάθλημα εναντίων άλλων μελών της Φατρίας σε αυτές τις αρένες πολλών παικτών."
+    track_concepts1: "Καταγραφή θεμάτων"
+    track_concepts2a: "που έμαθε κάθε μαθητής"
+    track_concepts2b: "που έμαθε κάθε μέλος"
+    track_concepts3a: "Καταγραφή επιπέδων που ολοκλήρωσε κάθε μαθητής"
+    track_concepts3b: "Καταγραφή επιπέδων που ολοκλήρωσε κάθε μέλος"
+    track_concepts4a: "Δες των μαθητών σου τις"
+    track_concepts4b: "Δες των μελών σου τις"
+    track_concepts5: "λύσεις"
+    track_concepts6a: "Ταξινόμηση μαθητών κατά όνομα ή πρόοδο"
+    track_concepts6b: "Ταξινόμηση μελών κατά όνομα ή πρόοδο"
+    track_concepts7: "Απαιτεί πρόσκληση"
+    track_concepts8: "για να συνδεθείς"
+    private_require_sub: "Οι ιδιωτικές φατρίες απιατούν να είσαι συνδρομητής για τις δημιουργήσεις ή να συνδεθείς με αυτές."
+
+  courses:
+    course: "Μάθημα"
+    courses: "μαθήματα"
+    not_enrolled: "Δεν έχετε εγγραφεί στο μάθημα αυτό."
+    visit_pref: "Παρακαλώ επισκεφτείτε τη "
+    visit_suf: "σελίδα για να εγγραφείτε."
+    select_class: "Επιλέξτε μια από τις τάξεις σας"
+    unnamed: "*χωρίς όνομα*"
+    select: "Επιλογή"
+    unnamed_class: "Τάξη Χωρίς Όνομα"
+    edit_settings: "επεξεργασία ρυθμίσεων τάξης"
+    edit_settings1: "Επεξεργασία Ρυθμίσεων Τάξης"
+    progress: "Πρόοδος Τάξης"
+    add_students: "Προσθήκη Μαθητών"
+    stats: "Στατιστικά"
+    total_students: "Σύνολο μαθητών:"
+    average_time: "Μέσος όρος παιξίματος επιπέδου:"
+    total_time: "Συνολικός χρόνος παιξίματος:"
+    average_levels: "Μέσος όρος ολοκληρωμένων επιπέδων:"
+    total_levels: "Συνολικά ολοκληρωμένα επίπεδα:"
+    furthest_level: "Επιπλέον ολοκληρωμένα επίπεδα:"
+    concepts_covered: "Θέματα που καλύφθηκαν"
+    students: "Μαθητές"
+    students1: "μαθητές"
+    expand_details: "Ανάπτυξη λεπτομερειών"
+    concepts: "Θέματα"
+    levels: "επίπεδα"
+    played: "Παίχτηκαν"
+    play_time: "Χρόνος παιχνιδιού:"
+    completed: "Ολοκληρώθηκαν:"
+    invite_students: "Πρόσκληση σε μαθητές να συμμετάσχουν στην τάξη αυτή."
+    invite_link_header: "Σϋνδεσμος για εγγραφή στο μαθήμα"
+    invite_link_p_1: "Δώσε αυτό το σύνδεσμο στους μαθητές που θέλεις να εγγραφούν στο μάθημα."
+    invite_link_p_2: "Ή μπορούμε να τους στείλουμε με μήνυμα ηλεκτρονικού ταχυδρομείου άμεσα:"
+    capacity_used: "Θέσεις του μαθήματος που χρησιμοποιούνται:"
+    enter_emails: "Δώσε τις διευθύνσεις ηλεκτρονικού ταχυδρομείου των μαθητών για να τους προσκαλέσουμε, μια ανά γραμμή"
+    send_invites: "Αποστολή Προσκλήσεων"
+    title: "Τίτλος"
+    description: "Περιγραφή"
+    languages_available: "Διάλεξε ποιες γλώσσες προγραμματισμού  θα είναι διαθέσιμες σε αυτήν την τάξη:"
+    all_lang: "Όλες οι Γλώσσες"
+    show_progress: "Δείξε την πρόοδο του μαθητή σε όλους στην τάξη"
+    creating_class: "Δημιουργία τάξης..."
+    purchasing_course: "Αγορά μαθήματος..."
+    buy_course: "Αγορά Μαθήματος"
+    buy_course1: "Αγόρασε αυτό το μάθημα"
+    create_class: "Δημιουργία Τάξης"
+    select_all_courses: "Διάλεξε  'Όλα τα μαθήματα' για έκπτωση 50%!"
+    all_courses: "Όλα τα μαθήματα"
+    number_students: "Αριθμός Μαθητών"
+    enter_number_students: "Δώσε τον αριθμό μαθητών που χρειάζεσαι γι αυτήν την τάξη."
+    name_class: "Όνομα Τάξης"
+    displayed_course_page: "Θα εμφανίζεται στη σελίδα του μαθήματοςγι αεσένα και του μαθητές σου. Μπορεί να αλλαχθεί αργότερα."
+    buy: "Αγορά"
+    purchasing_for: "Αγοράζεις μια άδεια για"
+    creating_for: "Δημιουργείς μια τάξη για"
+    for: "για" # Like in 'for 30 students'
+    receive_code: "Θα λάβεις μετά έναν κωδικό ξεκλειδώματος για να τον δώσεις στους μαθητές σου για να εγγραφούν στο μάθημα."
+    free_trial: "Δωρεάν δοκιμή για εκπαιδευτικούς!"
+    get_access: "για να έχει πρόσβαση σε όλα τα μαθήματα για να μπορέσεις να τα αξιολογήσεις."
+    questions: "Ερωτήσεις;"
+    faq: "Συχνές Ερωτήσεις και Απαντήσεις για τα Μαθήματα"
+    question: "Ε:" # Like in 'Question'
+    question1: "Ποιες είναι οι διαφορές μεταξύ αυτών των μαθημάτων και του παιχνιδιού ενός παίκτη;"
+    answer: "Α:" # Like in 'Answer'
+    answer1: "Τα παιχνίδια ενός παίκτη είναι σχεδισαμένα για ξεχωριστά άτομα, ενώ τα μαθήματα είναι σχεδιασμένα για τάξεις."
+    answer2: "Το παιχνίδι ενός παίκτη έχει αντικείμενα, πετράδια, επιλογή ήρωα, αλλαγή επιπέδων, και αγορές μέσα από την εφαρμογή. Τα μαθήματα παρέχουν χαρακτηριστικά διαχείρησης τάξης και τα επίπεδα έχουν βελτιστοποιηθεί για τους ρυθμούς των μαθητών."
+    teachers_click: "Εκπαιδευτικοί Πατήστε Εδώ"
+    students_click: "Μαθητές Πατήστε Εδώ"
+    courses_on_coco: "Μαθήματα στο CodeCombat"
 #    designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
-#    more_in_less: "Learn more in less time"
-#    no_experience: "No coding experience necesssary"
-#    easy_monitor: "Easily monitor student progress"
-#    purchase_for_class: "Purchase a course for your entire class. It's easy to sign up your students!"
-#    see_the: "See the"
-#    more_info: "for more information."
-#    choose_course: "Choose Your Course:"
-#    enter_code: "Enter an unlock code to join an existing class"
-#    enter_code1: "Enter unlock code"
-#    enroll: "Enroll"
-#    pick_from_classes: "Pick from your current classes"
+    more_in_less: "Μάθε περισσότερα σε λιγότερο χρόνο"
+    no_experience: "Δεν απαιτούνται γνώσεις προγραμματισμού"
+    easy_monitor: "Παρακολούθησε εύκολα την πρόοδο των μαθητών"
+    purchase_for_class: "Αγόρασε ένα μάθημα για όλη την τάξη σου. Είναι εύκολο να εγγράψεις τους μαθητές σου!"
+    see_the: "Δες το"
+    more_info: "για περισσότερες πληροφορίες."
+    choose_course: "Διάλεξε Το Μάθημα Σου:"
+    enter_code: "Γράψε έναν κωδικό ξεκλειδώματος για να συμμετάσχεις σε μια υπάρχουσα τάξη"
+    enter_code1: "Γράψε τον κωδικό ξεκλειδώματος"
+    enroll: "Εγγραφή"
+    pick_from_classes: "Διάλεξε από τις υπάρχουσες τάξεις σου"
 #    enter: "Enter"
-#    or: "Or"
-#    topics: "Topics"
-#    hours_content: "Hours of content:"
-#    get_free: "Get FREE course"
+    or: "Ή"
+    topics: "Θέματα"
+    hours_content: "Ώρες περιεχομένου:"
+    get_free: "Πάρε ΔΩΡΕΑΝ μάθημα"

   classes:
     archmage_title: "Αρχιμάγος"
     archmage_title_description: "(Προγραμματιστής)"
-#    archmage_summary: "If you are a developer interested in coding educational games, become an archmage to help us build CodeCombat!"
+    archmage_summary: "Αν είσαι προγραμματιστής που σε ενδιαφέρουν τα εκπαιδευτικά παιχνίδια, γίνε Αρχιμάγος για να μας βοηθήσεις να φτιάξουμε το CodeCombat!"
     artisan_title: "Τεχνίτης"
     artisan_title_description: "(Δημιουργός επιπέδων)"
-#    artisan_summary: "Build and share levels for you and your friends to play. Become an Artisan to learn the art of teaching others to program."
+    artisan_summary: "Φτιάξε και μοιράσου επίπεδα για να παίζεις εσύ και οι φίλοι σου. Γίνε Τεχνίτης για να μάθεις την τέχνη του να διδάσκεις τους άλλους προγραμματισμό."
     adventurer_title: "Εξερευνητής"
     adventurer_title_description: "(Δοκιμαστής επιπέδων)"
-#    adventurer_summary: "Get our new levels (even our subscriber content) for free one week early and help us work out bugs before our public release."
+    adventurer_summary: "Παίξε τα νέα μας επίπεδα (ακόμα και τα συνδρομητικά) δωρεάν για μια εβδομάδα πιο νωρίς και βοήθησέ μας να βρούμε τα λάθη πριν δημοσιεύσουμε τα επίπεδα."
     scribe_title: "Γραφέας"
     scribe_title_description: "(Συντάκτης άρθρων)"
-#    scribe_summary: "Good code needs good documentation. Write, edit, and improve the docs read by millions of players across the globe."
+    scribe_summary: "Ο καλός κώδικας χρειάζεται και καλή τεκμηρίωση. Γράψε, επεξεργάσου και βελτίωσε τα κείμενα που διαβάζονται από εκατομύρια παίκτες σε όλη την υφήλιο."
     diplomat_title: "Διπλωμάτης"
     diplomat_title_description: "(Μεταφραστής)"
-#    diplomat_summary: "CodeCombat is localized in 45+ languages by our Diplomats. Help us out and contribute translations."
+    diplomat_summary: "Το CodeCombat έχει μεταφραστεί σε πάνω από 45 γλώσσες από τους Διπλωμάτες μας. Βοήθησέ μας συνεισφέροντας μεταφράσεις."
     ambassador_title: "Πρεσβευτής"
     ambassador_title_description: "(Υποστήριξη)"
-#    ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world."
+    ambassador_summary: "Βοήθησε τους χρήστες στο φόρουμ μας και δώσε οδηγίες σε αυτούς που έχουν ερωτήσεις. Οι Πρεσβευτές μας αντιπροσωπεύουν το CodeCombat στον κόσμο."

 #  editor:
 #    main_title: "CodeCombat Editors"
@@ -1049,33 +1049,33 @@
 #    add_system_title: "Add Systems to Level"
 #    done_adding: "Done Adding"

-#  article:
-#    edit_btn_preview: "Preview"
-#    edit_article_title: "Edit Article"
-
-#  polls:
-#    priority: "Priority"
-
-#  contribute:
-#    page_title: "Contributing"
-#    intro_blurb: "CodeCombat is 100% open source! Hundreds of dedicated players have helped us build the game into what it is today. Join us and write the next chapter in CodeCombat's quest to teach the world to code!"
-#    alert_account_message_intro: "Hey there!"
-#    alert_account_message: "To subscribe for class emails, you'll need to be logged in first."
-#    archmage_introduction: "One of the best parts about building games is they synthesize so many different things. Graphics, sound, real-time networking, social networking, and of course many of the more common aspects of programming, from low-level database management, and server administration to user facing design and interface building. There's a lot to do, and if you're an experienced programmer with a hankering to really dive into the nitty-gritty of CodeCombat, this class might be for you. We would love to have your help building the best programming game ever."
-#    class_attributes: "Class Attributes"
-#    archmage_attribute_1_pref: "Knowledge in "
-#    archmage_attribute_1_suf: ", or a desire to learn. Most of our code is in this language. If you're a fan of Ruby or Python, you'll feel right at home. It's JavaScript, but with a nicer syntax."
-#    archmage_attribute_2: "Some experience in programming and personal initiative. We'll help you get oriented, but we can't spend much time training you."
-#    how_to_join: "How To Join"
-#    join_desc_1: "Anyone can help out! Just check out our "
-#    join_desc_2: "to get started, and check the box below to mark yourself as a brave Archmage and get the latest news by email. Want to chat about what to do or how to get more deeply involved? "
-#    join_desc_3: ", or find us in our "
-#    join_desc_4: "and we'll go from there!"
-#    join_url_email: "Email us"
-#    join_url_hipchat: "public Slack channel"
-#    archmage_subscribe_desc: "Get emails on new coding opportunities and announcements."
-#    artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
-#    artisan_introduction_suf: ", then this class might be for you."
+  article:
+    edit_btn_preview: "Προεπισκόπηση"
+    edit_article_title: "Επεξεργασία Άρθρου"
+
+  polls:
+    priority: "Προτεραιότητα"
+
+  contribute:
+    page_title: "Συνεισφορά"
+    intro_blurb: "Το CodeCombat είναι 100% ανοικτού κώδικα! Εκατοντάδες αφοσιωμένοι παίκτες μας βοήθησαν να κτίσουμε το παιχνίδι σε αυτό που είναι σήμερα. Ελάτε μαζί μας και γράψτε το επόμενο κεφάλαιο στην αναζήτηση του CodeCombat να μάθει τον κόσμο να προγραμματίζει!"
+    alert_account_message_intro: "Ε, εσύ!"
+    alert_account_message: "Για να γραφτείς στα μηνύματα ηλεκτρονικού ταχυδρομείου της τάξης πρέπει πρώτα να συνδεθείς."
+    archmage_introduction: "Ένα από τα καλύτερα μέρη της δημιουργίας παιχνιδιών είναι ότι περιέχουν πολλά πράγματα. Γραφικά, ήχο, δικτύωση σε πραγματικό χρόνο, κοινωνική μηχανική, και φυσικά πολλές συνηθισμένες απόψεις προγραμματισμού, από χαμηλού επιπέδου διαχείρηση βάσεων δεδομένων και διαχείρηση συστήματος μέχρι σχεδιασμό και υλοποίηση διεπαφής χρήστη. Υπάρχουν πολλά πράγματα να γίνου, και αν είσαι έμπειρος προγραμματιστής με λαχτάρα να κολυπμήσεις στα βαθιά του κώδικα του CodeCombat, αυτή η τάξη μπορεί να είναι για σένα. Θα χαρούμε να βοηθήσεις να χτίσουμετο καλύτερο παιχνίδι που έγινε ποτέ."
+    class_attributes: "Ιδιότητες της Τάξης"
+    archmage_attribute_1_pref: "Γνώση "
+    archmage_attribute_1_suf: ", ή επιθυμία να μάθεις. Ο περισσότερος κώδικας μας είναι σε αυτή τη γλώσσα. Αν είσαι λάτρης της Ruby ή της Python, θα νοιώσεις άνετα αμέσως. Είναι JavaScript, αλλά με πιο ωραίο συντακτικό."
+    archmage_attribute_2: "Θα χρειαστεί να έχεις κάποια εμπειρία στον προγραμματισμό και να μπορείς να παίρνεις πρωτοβουλίες. Θα σε βοηθήσουμε να μπεις στο σωστό δρόμο, αλλά δεν μπορούμε να σε εκπαιδεύσουμε."
+    how_to_join: "Πως να Συμμετάσχεις"
+    join_desc_1: "Όλοι μπορούν να βοηθήσουμε! Απλά δες το "
+    join_desc_2: "για να ξεκινήσεις, και πάτα το κουμπί παρακάτω για να μας δείξεις ότι είσαι ένας γενναίος Αρχιμάγος και να παίρνεις τα τελευταία νέα μέσω ηλεκτρονικού ταχυδρομείου. Θέλεις να συζητήσεις για το τι να κάνεις ή πως να εμπλακείς πιο βαθειά "
+    join_desc_3: ", ή να μας βρεις στο "
+    join_desc_4: "και εμείς θα το πάρουμε από εκεί και μετά!"
+    join_url_email: "Στείλε μας μήνυμα"
+    join_url_hipchat: "δημόσιο κανάλι στο Slack"
+    archmage_subscribe_desc: "Παίρνε μηνύματα ηλεκτρονικού ταχυδρομείου για νέες ευκαιρείες προγραμματισμού και ανακοινώσεις."
+    artisan_introduction_pref: "Πρέπει να δημιουργήσουμε επιπλέον επίπεδα! Οι άνθρωποι φωνάζουν για καινούριο περιεχόμενο, and we can only build so many ourselves. Προς το παρόν ο σταθμός εργασίας σου είναι στο πρώτο επίπεδο. Ο διορθωτής επιπέδων μας καλά καλά δεν μπορεί να χρησιμοποιηθεί ούτε από τους δημιουργούς του, γιαυτό πρόσεχε. Αν οραματίζεσε καμπάνιες που να περιέχουν επαναλήψεις Για μέχρι"
+    artisan_introduction_suf: ", τότε αυτή η τάξη ίσως να είναι για σένα."
 #    artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"
 #    artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
 #    artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"
@@ -1189,21 +1189,21 @@
 #    humans: "Red" # Ladder page display team name
 #    ogres: "Blue"

-#  user:
-#    stats: "Stats"
-#    singleplayer_title: "Singleplayer Levels"
-#    multiplayer_title: "Multiplayer Levels"
-#    achievements_title: "Achievements"
-#    last_played: "Last Played"
-#    status: "Status"
-#    status_completed: "Completed"
-#    status_unfinished: "Unfinished"
-#    no_singleplayer: "No Singleplayer games played yet."
-#    no_multiplayer: "No Multiplayer games played yet."
-#    no_achievements: "No Achievements earned yet."
-#    favorite_prefix: "Favorite language is "
-#    favorite_postfix: "."
-#    not_member_of_clans: "Not a member of any clans yet."
+  user:
+    stats: "Στατιστικά"
+    singleplayer_title: "Επίπεδα Ενός Παίκτη"
+    multiplayer_title: "Επίπεδα Πολλών Παικτών"
+    achievements_title: "Κατορθώματα"
+    last_played: "Έπαιξες Τελευταία"
+    status: "Κατάσταση"
+    status_completed: "Ολοκληρώθηκε"
+    status_unfinished: "Δεν ολοκληρώθηκε"
+    no_singleplayer: "Δεν έχεις παίξει ακόμα παιχνίδια ενός παίκτη."
+    no_multiplayer: "Δεν έχεις παίξει ακόμα παιχνίδια πολλών παικτών."
+    no_achievements: "Δεν έχεις κανένα κατόρθωμα ακόμα."
+    favorite_prefix: "Η αγαπημένη σου γλώσσα είναι "
+    favorite_postfix: "."
+    not_member_of_clans: "Δεν είσαι μέλος σε καμμιά φατρία ακόμα."

 #  achievements:
 #    last_earned: "Last Earned"
@@ -1277,19 +1277,19 @@
 #    redeemed: "Redeemed"
 #    no_codes: "No codes yet!"

-#  loading_error:
-#    could_not_load: "Error loading from server"
-#    connection_failure: "Connection failed."
-#    unauthorized: "You need to be signed in. Do you have cookies disabled?"
-#    forbidden: "You do not have the permissions."
-#    not_found: "Not found."
-#    not_allowed: "Method not allowed."
-#    timeout: "Server timeout."
-#    conflict: "Resource conflict."
-#    bad_input: "Bad input."
-#    server_error: "Server error."
-#    unknown: "Unknown error."
-#    error: "ERROR"
+  loading_error:
+    could_not_load: "Σφάλμα φόρτωσης από τον εξυπηρετητή"
+    connection_failure: "Η σύνδεση απότυχε."
+    unauthorized: "Πρέπει να είσαι συνδεμένος. Μήπως έχεις απεργοποιήσει τα cookies;"
+    forbidden: "Δεν σου επιτρέπεται."
+    not_found: "Δε βρέθηκε."
+    not_allowed: "Η μέθοδος δεν επιτρέπεται."
+    timeout: "Ο εξυπηρετητής άργησε πολύ να απαντήσει."
+    conflict: "Σύγκρουση πόρων."
+    bad_input: "Λάθος δεδομένα εισόδου."
+    server_error: "Σφάλμα εξυπηρετητή."
+    unknown: "Άγνωστο σφάλμα."
+    error: "Σφάλμα"

 #  resources:
 #    sessions: "Sessions"
@@ -1342,44 +1342,44 @@
 #    poll: "Poll"
 #    user_polls_record: "Poll Voting History"

-#  concepts:
-#    advanced_strings: "Advanced Strings"
-#    algorithms: "Algorithms"
-#    arguments: "Arguments"
-#    arithmetic: "Arithmetic"
-#    arrays: "Arrays"
-#    basic_syntax: "Basic Syntax"
-#    boolean_logic: "Boolean Logic"
-#    break_statements: "Break Statements"
-#    classes: "Classes"
-#    continue_statements: "Continue Statements"
-#    for_loops: "For Loops"
-#    functions: "Functions"
-#    graphics: "Graphics"
-#    if_statements: "If Statements"
-#    input_handling: "Input Handling"
-#    math_operations: "Math Operations"
-#    object_literals: "Object Literals"
-#    parameters: "Parameters"
-#    strings: "Strings"
-#    variables: "Variables"
-#    vectors: "Vectors"
-#    while_loops: "Loops"
-#    recursion: "Recursion"
-
-#  delta:
-#    added: "Added"
-#    modified: "Modified"
-#    not_modified: "Not Modified"
-#    deleted: "Deleted"
+  concepts:
+    advanced_strings: "Προχωρημένες Συμβολοσειρές"
+    algorithms: "Αλγόριθμοι"
+    arguments: "Ορίσματα"
+    arithmetic: "Αριθμητικά"
+    arrays: "Πίνακες"
+    basic_syntax: "Βασικό συντακτικό"
+    boolean_logic: "Μπουλιανή Λογική"
+    break_statements: "Εντολές Break"
+    classes: "Κλάσεις"
+    continue_statements: "Εντολές Continue"
+    for_loops: "Επαναλήχεις με Για "
+    functions: "Συναρτήσεις"
+    graphics: "Γραφικά"
+    if_statements: "Εντολές Αν"
+    input_handling: "Χειρισμού Εισόδου"
+    math_operations: "Μαθηματικές Λειτουργίες"
+    object_literals: "Αντικείμενα"
+    parameters: "Παράμετροι"
+    strings: "Συμβολοσειρές"
+    variables: "Μεταβλητές"
+    vectors: "Δυναμικοί Πίνακες"
+    while_loops: "Επαναλήψεις"
+    recursion: "Αναδρομή"
+
+  delta:
+    added: "Προστέθηκε"
+    modified: "Τροποποίηθηκε"
+    not_modified: "Δεν Τροποποίηθηκε"
+    deleted: "Διαγράφηκε"
 #    moved_index: "Moved Index"
-#    text_diff: "Text Diff"
+    text_diff: "Διαφορά Κειμένου"
 #    merge_conflict_with: "MERGE CONFLICT WITH"
-#    no_changes: "No Changes"
+    no_changes: "Δεν υπάρχουν αλλαγές"

   multiplayer:
     multiplayer_title: "Ρυθμίσεις multiplayer" # We'll be changing this around significantly soon. Until then, it's not important to translate.
-#    multiplayer_toggle: "Enable multiplayer"
+    multiplayer_toggle: "Enable multiplayer"
 #    multiplayer_toggle_description: "Allow others to join your game."
     multiplayer_link_description: "Δώστε αυτόν το σύνδεσμο σε οποιονδήποτε για να ενταχθούν στο παιχνίδι σου."
     multiplayer_hint_label: "Συμβουλή:"


Avoid skimmers thinking we are teaching Microsoft Excel

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -11,7 +11,7 @@
     learn_more: "Learn more"
     classroom_in_a_box: "A classroom in-a-box for teaching computer science."
     codecombat_is: "CodeCombat is a platform for students to learn computer science while playing through a real game."
-    our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
+    our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
     top_screenshots_hint: "Students write code and see their changes update in real-time"
     designed_with: "Designed with teachers in mind"
     real_code: "Real, typed code"


minor text change

minor text change


Update nl.coffee

Update nl.coffee

--- app/locale/nl.coffee
+++ app/locale/nl.coffee
@@ -944,7 +944,7 @@
     wallOfDarkness_name: "Duistere Muur"
     wallOfDarkness_description: "Verberg je achter een muur van schaduwen om nieuwsgierige blikken te vermijden."

-#  avatar_selection:
+  avatar_selection:
     pick_an_avatar: "Kies een avatar die jou moet voorstellen als speler"

   premium_features:
@@ -1006,7 +1006,7 @@
     level_to_unlock: "Beschikbaar na level:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
     restricted_to_certain_heroes: "Alleen bepaalde helden kunnen dit level spelen."

-#  char_customization_modal:
+  char_customization_modal:
     heading: "Personaliseer je Held"
     body: "Lichaam"
     name_label: "Naam van de Held"
@@ -1356,7 +1356,7 @@
     beautify: "Maak je code netter door de opmaak overal gelijk te maken."
     maximize_editor: "Maximaliseer/minimaliseer code editor."

-#  cinematic:
+  cinematic:
     click_anywhere_continue: "klik ergens om door te gaan"

   community:


:art: fix corner case bug and precise count handling in Overrwrite mode

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -301,68 +301,97 @@
 # Duplicate
 # -------------------------
 class DuplicateSelectedText extends MoveSelectedText
+  getCount: ->
+    count = super
+    return count unless @isOverwrite()
+    switch @direction
+      when 'up'
+        if @isLinewise()
+          counts = @editor.getSelections().map (selection) ->
+            {start} = selection.getBufferRange()
+            Math.floor(start.row / swrap(selection).getRowCount())
+          count = Math.min([count, counts...]...)
+        else if @vimState.isMode('visual', 'blockwise')
+          counts = @vimState.getBlockwiseSelections().map (bs) ->
+            {start} = bs.getTop().getBufferRange()
+            Math.floor(start.row / bs.getHeight())
+          count = Math.min([count, counts...]...)
+      when 'left'
+        if @isLinewise()
+          count = 0
+        else
+          counts = @editor.getSelections().map (selection) ->
+            {start, end} = selection.getBufferRange()
+            Math.floor(start.column / (end.column - start.column))
+          count = Math.min([count, counts...]...)
+    count
+
   execute: ->
     selections = @editor.getSelectionsOrderedByBufferPosition()
-    selections.reverse() if @direction in ['down', 'up']
-
+    selections.reverse() if @direction is 'down'
+    return if (count = @getCount()) is 0
     @editor.transact =>
       if @isLinewise()
         for selection in selections
           if @vimState.submode is 'linewise'
-            @duplicateLinewise(selection)
+            @duplicateLinewise(selection, count)
           else
             swrap(selection).switchToLinewise =>
-              @duplicateLinewise(selection)
+              @duplicateLinewise(selection, count)
       else
         switch @direction
           when 'right', 'left'
             for selection in selections
-              @duplicateCharacterwise(selection)
+              @duplicateCharacterwise(selection, count)
           when 'up', 'down'
-            for blockwiseSelection in @vimState.getBlockwiseSelections()
-              @duplicateCharacterwise(blockwiseSelection)
-
-  # Can move to util
-  duplicateLinewiseText: (rows, direction, count) ->
-    switch direction
-      when 'up', 'down' then _.flatten([1..count].map -> rows)
-      when 'left', 'right' then rows.map (text) -> text.repeat(count)
-
-  duplicateLinewise: (selection) ->
-    count = @getCount()
-    count += 1 if @direction in ['left', 'right']
+            if wasCharacterwise = @vimState.isMode('visual', 'characterwise')
+              @vimState.activate('visual', 'blockwise')
+
+            bss = @vimState.getBlockwiseSelectionsOrderedByBufferPosition()
+            console.log bss.length
+            bss.reverse() if @direction is 'down'
+            # console.log count
+            for bs in bss
+              @duplicateBlockwiseSelection(bs, count)
+
+            if wasCharacterwise
+              @vimState.activate('visual', 'characterwise')
+
+  duplicateLinewise: (selection, count) ->
     {start, end} = selection.getBufferRange()
     rows = swrap(selection).lineTextForBufferRows()
-    if @direction is 'up' and @isOverwrite()
-      count = Math.min(count, Math.floor(start.row / rows.length))
-      return unless count > 0
-    lineTexts = @duplicateLinewiseText(rows, @direction, count)
+    lineTexts = switch @direction
+      when 'up', 'down' then _.flatten([1..count].map -> rows)
+      when 'left', 'right' then rows.map (text) -> text.repeat(count+1)
+
     height = lineTexts.length
     newText = lineTexts.join("\n") + "\n"

     range = switch @direction
       when 'up'
-        start = end = start
-        start = start.translate([-height, 0]) if @isOverwrite()
-        new Range(start, end)
+        if @isOverwrite()
+          [start.translate([-height, 0]), start]
+        else
+          [start, start]
       when 'down'
-        start = end = end
-        end = end.translate([+height, 0]) if @isOverwrite()
-        new Range(start, end)
+        if @isOverwrite()
+          [end, end.translate([+height, 0])]
+        else
+          [end, end]
       when 'left', 'right'
         selection.getBufferRange()
     @setTextInRangeAndSelect(range, newText, selection)

 class DuplicateSelectedTextUp extends DuplicateSelectedText
   direction: 'up'
-  duplicateBlockwiseSelection: (blockwiseSelection, direction) ->
+
+  duplicateBlockwiseSelection: (blockwiseSelection, count) ->
     {selections} = blockwiseSelection
-    height = selections.length
-
-    count = @getCount()
+    height = blockwiseSelection.getHeight()
+
     unless @isOverwrite() # Insert Blank row
       [startRow, endRow] = blockwiseSelection.getBufferRowRange()
-      point = switch direction
+      point = switch @direction
         when 'up' then [startRow - 1, Infinity]
         when 'down' then [endRow + 1, 0]
       @insertTextAtPoint(point, "\n".repeat(height * count))
@@ -373,9 +402,9 @@
         when 'down' then [+height * count, 0]

     ranges = []
-    @countTimes (num) =>
+    for num in [1..count]
       selections.forEach (selection) =>
-        translation = getTranslation(direction, num)
+        translation = getTranslation(@direction, num)
         range = selection.getBufferRange().translate(translation)
         @complementSpacesToPoint(range.start)
         ranges.push @editor.setTextInBufferRange(range, selection.getText())
@@ -386,19 +415,15 @@
       new Range(first.start, last.end)
     else
       new Range(first.end, last.start).translate([0, -1], [0, +1])
-    #   [first.end, last.start]
     blockwiseSelection.setBufferRange(range)
-
-  duplicateCharacterwise: (blockwiseSelection) ->
-    @duplicateBlockwiseSelection(blockwiseSelection, @direction)

 class DuplicateSelectedTextDown extends DuplicateSelectedTextUp
   direction: 'down'

 class DuplicateSelectedTextRight extends DuplicateSelectedText
   direction: 'right'
-  duplicateCharacterwise: (selection) ->
-    newText = selection.getText().repeat(@getCount())
+  duplicateCharacterwise: (selection, count) ->
+    newText = selection.getText().repeat(count)
     width = newText.length
     {start, end} = selection.getBufferRange()
     range = switch @direction


Update th.coffee

Update th.coffee

I've made some improvements. Thanks for letting me participate in the edit.

--- app/locale/th.coffee
+++ app/locale/th.coffee
@@ -453,10 +453,10 @@
 #    combo_all_concepts_used: "You used all concepts possible to solve the challenge. Great job!"
 #    combo_not_all_concepts_used: "You used __complete__ out of the __total__ concepts possible to solve the challenge. Try to get all __total__ concepts next time!"
 #    start_challenge: "Start Challenge"
-#    next_game: "Next game"
-#    languages: "Languages"
+    next_game: "เกมส์ต่อไป"
+    languages: "ภาษา"
 #    programming_language: "Programming language"
-#    show_menu: "Show game menu"
+    show_menu: "แสดงเมนูเกมส์"
     home: "หน้าแรก" # Not used any more, will be removed soon.
 #    level: "Level" # Like "Level: Dungeons of Kithgard"
     skip: "ข้าม"
@@ -470,7 +470,7 @@
 #    combo_challenge_goals: "Challenge Level Goals"
 #    concept_challenge_goal: "Concept Challenge Goal"
 #    combo_challenge_goal: "Challenge Level Goal"
-#    running: "Running..."
+#    running: "Running....!!!"
     success: "สำเร็จ!"
     incomplete: "ไม่สมบูรณ์"
     timed_out: "หมดเวลา"
@@ -551,7 +551,7 @@
 #    tip_patience: "Patience you must have, young Padawan. - Yoda"
 #    tip_documented_bug: "A documented bug is not a bug; it is a feature."
 #    tip_impossible: "It always seems impossible until it's done. - Nelson Mandela"
-    tip_talk_is_cheap: "อย่าเสียเวลาพูดเลย ให้ผมดูโมค้ดของคุณดีกว่า - Linus Torvalds"
+    tip_talk_is_cheap: "อย่าเสียเวลาพูดเลย ให้ผมดูโค้ดของคุณดีกว่า - Linus Torvalds"
 #    tip_first_language: "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay"
 #    tip_hardware_problem: "Q: How many programmers does it take to change a light bulb?  A: None, it's a hardware problem."
 #    tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
@@ -568,7 +568,7 @@
     tip_source_code: "เราอยากจะเปลี่ยนโลกนี้ แต่เขาไม่ให้ Source Code ผม"
 #    tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
 #    tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
-#    tip_google: "Have a problem you can't solve? Google it!"
+    tip_google: "เมื่อคุณมีปัญหาแล้วแก้ไขไม่ได้ เข้า Google สิ!!!"
 #    tip_adding_evil: "Adding a pinch of evil."
 #    tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
 #    tip_open_source_contribute: "You can help CodeCombat improve!"
@@ -576,7 +576,7 @@
 #    tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
     tip_strong_opponents: "ศัตรูที่แข็งแกร่งที่สุด ก็ยังมีจุดอ่อน - อุจิวะ อิทาจิ"
 #    tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen."
-#    tip_solve_then_write: "First, solve the problem. Then, write the code. - John Johnson"
+    tip_solve_then_write: "อย่างแรก ต้องแก้ไขปัญหาก่อน จากนั้นก็เขียนโค๊ด. - John Johnson"
 #    tip_compiler_ignores_comments: "Sometimes I think that the compiler ignores my comments."
 #    tip_understand_recursion: "The only way to understand recursion is to understand recursion."
 #    tip_life_and_polymorphism: "Open Source is like a totally polymorphic heterogeneous structure: All types are welcome."
@@ -602,16 +602,16 @@
 #    project_published_noty: "Your level has been published!"
 #    keep_editing: "Keep Editing"

-#  apis:
-#    methods: "Methods"
-#    events: "Events"
+  apis:
+    methods: "วิธีการ"
+#    events: "events"
 #    spawnable: "Spawnable"
 #    html: "HTML"
 #    math: "Math"
 #    array: "Array"
 #    object: "Object"
 #    string: "String"
-#    function: "Function"
+    function: "ฟังก์ชั่น"
 #    vector: "Vector"
 #    date: "Date"
 #    jquery: "jQuery"
@@ -646,25 +646,25 @@
 #    goal_collect_amount: "Collect __amount__ items."

   game_menu:
-#    inventory_tab: "Inventory"
+    inventory_tab: "คลัง"
     save_load_tab: "เซฟ/โหลด"
-#    options_tab: "Options"
+    options_tab: "ตัวเลือก"
 #    guide_tab: "Guide"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
+    guide_video_tutorial: "วีดีโอสอน"
+#    guide_tips: "tips"
     multiplayer_tab: "ผู้เล่นหลายคน"
-#    auth_tab: "Sign Up"
-#    inventory_caption: "Equip your hero"
+    auth_tab: "สมัครสมาชิก"
+    inventory_caption: "เลือกฮีโร่ของคุณ!!"
 #    choose_hero_caption: "Choose hero, language"
 #    options_caption: "Configure settings"
 #    guide_caption: "Docs and tips"
-#    multiplayer_caption: "Play with friends!"
-#    auth_caption: "Save your progress."
+    multiplayer_caption: "เล่นกับเพื่อนๆ!!!"
+    auth_caption: "บันทึกความก้าวหน้าของคุณ"

   leaderboard:
-#    view_other_solutions: "View Leaderboards"
-#    scores: "Scores"
-#    top_players: "Top Players by"
+    view_other_solutions: "ดู Leaderboards"
+    scores: "สกอร์"
+    top_players: "Top Players คือ"
     day: "วันนี้"
     week: "สัปดาห์นี้"
     all: "ตลอดกาล"
@@ -673,23 +673,23 @@
 #    damage_taken: "Damage Taken"
 #    damage_dealt: "Damage Dealt"
     difficulty: "ระดับความยาก"
-#    gold_collected: "Gold Collected"
+    gold_collected: "Gold ที่เก็บได้"
 #    survival_time: "Survived"
 #    defeated: "Enemies Defeated"
 #    code_length: "Lines of Code"
 #    score_display: "__scoreType__: __score__"

-#  inventory:
-#    equipped_item: "Equipped"
+  inventory:
+    equipped_item: "ใช้งานแล้ว"
 #    required_purchase_title: "Required"
 #    available_item: "Available"
 #    restricted_title: "Restricted"
-#    should_equip: "(double-click to equip)"
+    should_equip: "(ดับเบิลคลิ๊ก..เพื่อใช้งาน)"
 #    equipped: "(equipped)"
 #    locked: "(locked)"
 #    restricted: "(restricted in this level)"
-#    equip: "Equip"
-#    unequip: "Unequip"
+    equip: "ใช้งาน"
+    unequip: "ถอดออก"
 #    warrior_only: "Warrior Only"
 #    ranger_only: "Ranger Only"
 #    wizard_only: "Wizard Only"


* [test] minor changes

* [test] minor changes

--- test/util-test.coffee
+++ test/util-test.coffee
@@ -9,302 +9,301 @@

 chai.use(sinonChai)

-describe "util functions", ->
-  describe "isEmpty", ->
-    isEmpty = util.isEmpty
-    isEmptyFunction = util.isEmptyFunction
-
-    it "should be empty", ->
-      assert.ok isEmpty(null)
-      assert.ok isEmpty("")
-      assert.ok isEmpty({})
-      assert.ok isEmpty([])
-      (->
-        assert.ok isEmpty(arguments)
-      )()
-      assert.notOk isEmpty("hi")
-      assert.notOk isEmpty(length: 0)
-      assert.notOk isEmpty([1])
-      (->
-        assert.notOk isEmpty(arguments)
-      ) 1
-
-    it "should be empty function", ->
-      emptyFunc = ->
-      isEmpty(emptyFunc).should.be.true "emptyFunc"
-      emptyFunc = (abc, ase)->
-      isEmpty(emptyFunc).should.be.true "emptyFunc2"
-      isEmptyFunction("function(arg1, arg2, arg3){\n}").should.be.true
-      isEmptyFunction("function(arg1, arg2, arg3){\n;}").should.be.true
-      isEmptyFunction("function   asFn  (arg1, arg2, arg3){\n\n;}").should.be.true
-      isEmptyFunction("function(arg1, arg2, arg3){abs;}").should.not.be.true
-
-  describe "inject", ->
-    inject = util.inject
-    it "should inject a function before execution", ->
-      run = (a,b,c)->[a,b,c]
-      onBefore = sinon.spy()
-      run = inject(run, onBefore)
-      run(1,"b",3).should.be.deep.equal [1,"b",3]
-      onBefore.should.have.been.calledWith(1,"b",3)
-      onBefore.should.have.been.calledOnce
-
-    it "should inject a function before execution and change the arguments", ->
-      runOrg = sinon.spy (a,b,c)->[a,b,c]
-      onBefore = sinon.spy (a,b,c)->a=2;b="B";c=4;arguments
-      run = inject(runOrg, onBefore)
-      run(1,"b",3).should.be.deep.equal [2,"B", 4]
-      onBefore.should.have.been.calledWith(1,"b",3)
-      onBefore.should.have.been.calledOnce
-      runOrg.should.have.been.calledOnce
-
-    it "should inject a function before execution and deny the original function execution", ->
-      runOrg = sinon.spy (a,b,c)->[a,b,c]
-      onBefore = sinon.spy ->false
-      run = inject(runOrg, onBefore)
-      run(1,"b",3).should.be.false
-      onBefore.should.have.been.calledWith(1,"b",3)
-      onBefore.should.have.been.calledOnce
-      runOrg.should.have.not.been.called
-
-    it "should inject a function after execution", ->
-      runOrg = sinon.spy (a,b,c)->[a,b,c]
-      onAfter = sinon.spy (a,b,c, result, isDenied)->
-        a.should.be.equal 1
-        b.should.be.equal "b"
-        c.should.be.equal 3
-        result.should.be.deep.equal [1,"b",3]
-        isDenied.should.be.false
-        return
-      run = inject(runOrg, null, onAfter)
-      run(1,"b",3).should.be.deep.equal [1,"b",3]
-      onAfter.should.have.been.calledWith(1,"b",3)
-      onAfter.should.have.been.calledOnce
-      runOrg.should.have.been.calledOnce
-
-    it "should inject a function after execution and change result", ->
-      runOrg = sinon.spy (a,b,c)->[a,b,c]
-      onAfter = sinon.spy (a,b,c, result, isDenied)->
-        a.should.be.equal 1
-        b.should.be.equal "b"
-        c.should.be.equal 3
-        result.should.be.deep.equal [1,"b",3]
-        isDenied.should.be.false
-        [1,2,3]
-      run = inject(runOrg, null, onAfter)
-      run(1,"b",3).should.be.deep.equal [1,2,3]
-      onAfter.should.have.been.calledWith(1,"b",3)
-      onAfter.should.have.been.calledOnce
-      runOrg.should.have.been.calledOnce
-
-    it "should inject a function before and after execution", ->
-      runOrg = sinon.spy (a,b,c)->[a,b,c]
-      onBefore = sinon.spy()
-      onAfter = sinon.spy (a,b,c, result, isDenied)->
-        a.should.be.equal 1
-        b.should.be.equal "b"
-        c.should.be.equal 3
-        result.should.be.deep.equal [1,"b",3]
-        isDenied.should.be.false
-        return
-      run = inject(runOrg, onBefore, onAfter)
-      run(1,"b",3).should.be.deep.equal [1,"b",3]
-      onBefore.should.have.been.calledWith(1,"b",3)
-      onBefore.should.have.been.calledOnce
-      onAfter.should.have.been.calledWith(1,"b",3)
-      onAfter.should.have.been.calledOnce
-      runOrg.should.have.been.calledOnce
-
-  describe "isRegExp", ->
-    isRegExp = util.isRegExp
-    it "should check a RegExp instance correct", ->
-      isRegExp(/ahi/).should.be.true
-      isRegExp(new RegExp()).should.be.true
-    it "should check an illegal RegExp argument correct", ->
-      isRegExp().should.be.false
-      isRegExp(RegExp).should.be.false
-      isRegExp("/sdd/g").should.be.false
-
-  describe "isDate", ->
-    isDate = util.isDate
-    it "should check a Date instance correct", ->
-      isDate(new Date()).should.be.true
-      isDate(new Date(2015,1,1)).should.be.true
-    it "should check an illegal date argument correct", ->
-      isDate().should.be.false
-      isDate(Date).should.be.false
-      isDate("2015-01-01").should.be.false
-
-  describe "isUndefined", ->
-    isUndefined = util.isUndefined
-    it "should check undefined type correct", ->
-      isUndefined(undefined).should.be.true
-      isUndefined(`undefined`).should.be.true
-    it "should check an other type to false", ->
-      isUndefined(null).should.be.false
-      isUndefined(Date).should.be.false
-      isUndefined(false).should.be.false
-      isUndefined(0).should.be.false
-      isUndefined('undefined').should.be.false
-
-  describe "isNullOrUndefined", ->
-    isNullOrUndefined = util.isNullOrUndefined
-    it "should check undefined type correct", ->
-      isNullOrUndefined(undefined).should.be.true
-    it "should check null type correct", ->
-      isNullOrUndefined(null).should.be.true
-    it "should check an other type to false", ->
-      isNullOrUndefined(Date).should.be.false
-      isNullOrUndefined(false).should.be.false
-      isNullOrUndefined(0).should.be.false
-      isNullOrUndefined('undefined').should.be.false
-
-  describe "isObject", ->
-    isObject = util.isObject
-    it "should check object type correct", ->
-      Obj = ->
-      obj = Object.create(null)
-      isObject({}).should.be.true
-      isObject(obj).should.be.true
-      isObject(new Obj()).should.be.true
-      isObject(new Date()).should.be.true
-      isObject(/dd/).should.be.true
-    it "should check an other type to false", ->
-      isObject(null).should.be.false
-      isObject("object").should.be.false
-      isObject(false).should.be.false
-      isObject(true).should.be.false
-      isObject(0).should.be.false
-      isObject(->).should.be.false
-
-  describe "isFunction", ->
-    isFunction = util.isFunction
-    it "should check function type correct", ->
-      isFunction(->).should.be.true
-      isFunction(Date).should.be.true
-      isFunction(RegExp).should.be.true
-    it "should check an other type to false", ->
-      isFunction(new RegExp()).should.be.false
-      isFunction(new ->).should.be.false
-      isFunction(false).should.be.false
-      isFunction(true).should.be.false
-      isFunction(0).should.be.false
-      isFunction(null).should.be.false
-      isFunction(undefined).should.be.false
-      isFunction("").should.be.false
-
-  describe "isString", ->
-    isString = util.isString
-    it "should check string type correct", ->
-      isString("").should.be.true
-      isString("hello").should.be.true
-    it "should check an other type to false", ->
-      isString(new RegExp()).should.be.false
-      isString(new ->).should.be.false
-      isString(false).should.be.false
-      isString(true).should.be.false
-      isString(0).should.be.false
-      isString(null).should.be.false
-      isString(undefined).should.be.false
-  describe "_extend", ->
-    extend = util._extend
-    it "should extend an object", ->
-      org = {a: 1, b:2}
-      add = {a: 3}
-      extend(org, add).should.be.equal org
-      org.should.be.deep.equal {a:3, b:2}
-      extend org, b:4, c:2
-      org.should.be.deep.equal {a:3, b:4, c:2}
-    it "should extend many object", ->
-      org = {a: 1, b:2}
-      add = {a: 3}
-      third = {c:4}
-      extend(org, add, third, {d:5, b:0}).should.be.equal org
-      org.should.be.deep.equal {a:3, b:0, c:4, d:5}
-
-  describe "extend", ->
-    extend = require('../src/extend')
-    it "should extend an object", ->
-      org = {a: 1, b:2}
-      add = {a: 3}
-      extend(org, add).should.be.equal org
-      org.should.be.deep.equal {a:3, b:2}
-      extend org, b:4, c:2
-      org.should.be.deep.equal {a:3, b:4, c:2}
-    it "should extend many object", ->
-      org = {a: 1, b:2}
-      add = {a: 3}
-      third = {c:4}
-      extend(org, [add, third, {d:5, b:0}]).should.be.equal org
-      org.should.be.deep.equal {a:3, b:0, c:4, d:5}
-    it "should extend an object and filter properties", ->
-      org = {}
-      add = {a:3,b:3,c:2,d:123}
-      extend(org, add, (k,v)->k in ['a','c']).should.be.equal org
-      org.should.be.deep.equal {a:3,c:2}
-
-  describe "is/string/int", ->
-    isInt = require('../src/is/string/int')
-    it "should test string whether integer", ->
-      assert.equal isInt("12883"), true
-      assert.equal isInt("012883"), true
-      assert.equal isInt("0x12883"), true
-      assert.equal isInt("128.83"), false
-      assert.equal isInt("128e83"), false
-  describe "is/string/json", ->
-    isJson = require('../src/is/string/json')
-    it "should test string integer whether json", ->
-      assert.equal isJson("12883"), true
-      assert.equal isJson("012883"), true
-      assert.equal isJson("0x12883"), false
-      assert.equal isJson("128.83"), true
-      assert.equal isJson("128e83"), true
-    it "should test string whether json", ->
-      assert.equal isJson("'12883'"), false
-      assert.equal isJson('"012883"'), true
-      assert.equal isJson("'0x12883\""), false
-    it "should test string object whether json", ->
-      assert.equal isJson('{"a":12883}'), true
-      assert.equal isJson("{a:12883}"), false
-      assert.equal isJson("{a:12883,b}", true), true #almost json = true
-    it "should test string array whether json", ->
-      assert.equal isJson('["a", 12883]'), true
-      assert.equal isJson('["a", 12883'), false
-      assert.equal isJson('["a, 12883]'), false
-      assert.equal isJson('["a, 12883]', true), true
-
-  describe "defineProperty", ->
-    defineProperty = require('../src/defineProperty')
-    it "should define a property", ->
-      obj = {}
-      defineProperty obj, 'prop', 128
-      obj.should.have.property 'prop', 128
-      keys = Object.keys obj
-      keys.should.have.length 0
-    it "should define an enumerable property", ->
-      obj = {}
-      defineProperty obj, 'prop', 128, enumerable:true
-      obj.should.have.property 'prop', 128
-      keys = Object.keys obj
-      keys.should.be.deep.equal ['prop']
-    it "should define a property with getter", ->
-      obj = {}
-      defineProperty obj, 'prop', null,
-        get:->128
-        writable:false
-      obj.should.have.property 'prop', 128
-      keys = Object.keys obj
-      keys.should.have.length 0
-    it "should define a configurable property", ->
-      obj = {}
-      defineProperty obj, 'prop', 128
-      obj.should.have.property 'prop', 128
-      delete obj.prop
-      obj.should.not.have.ownProperty 'prop'
-    it "should define a non-configurable property", ->
-      obj = {}
-      defineProperty obj, 'prop', 128, configurable:false
-      obj.should.have.property 'prop', 128
-      delete obj.prop
-      obj.should.have.ownProperty 'prop'
-
+describe "isEmpty", ->
+  isEmpty = util.isEmpty
+  isEmptyFunction = util.isEmptyFunction
+
+  it "should be empty", ->
+    assert.ok isEmpty(null)
+    assert.ok isEmpty("")
+    assert.ok isEmpty({})
+    assert.ok isEmpty([])
+    (->
+      assert.ok isEmpty(arguments)
+    )()
+    assert.notOk isEmpty("hi")
+    assert.notOk isEmpty(length: 0)
+    assert.notOk isEmpty([1])
+    (->
+      assert.notOk isEmpty(arguments)
+    ) 1
+
+  it "should be empty function", ->
+    emptyFunc = ->
+    isEmpty(emptyFunc).should.be.true "emptyFunc"
+    emptyFunc = (abc, ase)->
+    isEmpty(emptyFunc).should.be.true "emptyFunc2"
+    isEmptyFunction("function(arg1, arg2, arg3){\n}").should.be.true
+    isEmptyFunction("function(arg1, arg2, arg3){\n;}").should.be.true
+    isEmptyFunction("function   asFn  (arg1, arg2, arg3){\n\n;}").should.be.true
+    isEmptyFunction("function(arg1, arg2, arg3){abs;}").should.not.be.true
+
+describe "inject", ->
+  inject = util.inject
+  it "should inject a function before execution", ->
+    run = (a,b,c)->[a,b,c]
+    onBefore = sinon.spy()
+    run = inject(run, onBefore)
+    run(1,"b",3).should.be.deep.equal [1,"b",3]
+    onBefore.should.have.been.calledWith(1,"b",3)
+    onBefore.should.have.been.calledOnce
+
+  it "should inject a function before execution and change the arguments", ->
+    runOrg = sinon.spy (a,b,c)->[a,b,c]
+    onBefore = sinon.spy (a,b,c)->a=2;b="B";c=4;arguments
+    run = inject(runOrg, onBefore)
+    run(1,"b",3).should.be.deep.equal [2,"B", 4]
+    onBefore.should.have.been.calledWith(1,"b",3)
+    onBefore.should.have.been.calledOnce
+    runOrg.should.have.been.calledOnce
+
+  it "should inject a function before execution and deny the original function execution", ->
+    runOrg = sinon.spy (a,b,c)->[a,b,c]
+    onBefore = sinon.spy ->false
+    run = inject(runOrg, onBefore)
+    run(1,"b",3).should.be.false
+    onBefore.should.have.been.calledWith(1,"b",3)
+    onBefore.should.have.been.calledOnce
+    runOrg.should.have.not.been.called
+
+  it "should inject a function after execution", ->
+    runOrg = sinon.spy (a,b,c)->[a,b,c]
+    onAfter = sinon.spy (a,b,c, result, isDenied)->
+      a.should.be.equal 1
+      b.should.be.equal "b"
+      c.should.be.equal 3
+      result.should.be.deep.equal [1,"b",3]
+      isDenied.should.be.false
+      return
+    run = inject(runOrg, null, onAfter)
+    run(1,"b",3).should.be.deep.equal [1,"b",3]
+    onAfter.should.have.been.calledWith(1,"b",3)
+    onAfter.should.have.been.calledOnce
+    runOrg.should.have.been.calledOnce
+
+  it "should inject a function after execution and change result", ->
+    runOrg = sinon.spy (a,b,c)->[a,b,c]
+    onAfter = sinon.spy (a,b,c, result, isDenied)->
+      a.should.be.equal 1
+      b.should.be.equal "b"
+      c.should.be.equal 3
+      result.should.be.deep.equal [1,"b",3]
+      isDenied.should.be.false
+      [1,2,3]
+    run = inject(runOrg, null, onAfter)
+    run(1,"b",3).should.be.deep.equal [1,2,3]
+    onAfter.should.have.been.calledWith(1,"b",3)
+    onAfter.should.have.been.calledOnce
+    runOrg.should.have.been.calledOnce
+
+  it "should inject a function before and after execution", ->
+    runOrg = sinon.spy (a,b,c)->[a,b,c]
+    onBefore = sinon.spy()
+    onAfter = sinon.spy (a,b,c, result, isDenied)->
+      a.should.be.equal 1
+      b.should.be.equal "b"
+      c.should.be.equal 3
+      result.should.be.deep.equal [1,"b",3]
+      isDenied.should.be.false
+      return
+    run = inject(runOrg, onBefore, onAfter)
+    run(1,"b",3).should.be.deep.equal [1,"b",3]
+    onBefore.should.have.been.calledWith(1,"b",3)
+    onBefore.should.have.been.calledOnce
+    onAfter.should.have.been.calledWith(1,"b",3)
+    onAfter.should.have.been.calledOnce
+    runOrg.should.have.been.calledOnce
+
+describe "isRegExp", ->
+  isRegExp = util.isRegExp
+  it "should check a RegExp instance correct", ->
+    isRegExp(/ahi/).should.be.true
+    isRegExp(new RegExp()).should.be.true
+  it "should check an illegal RegExp argument correct", ->
+    isRegExp().should.be.false
+    isRegExp(RegExp).should.be.false
+    isRegExp("/sdd/g").should.be.false
+
+describe "isDate", ->
+  isDate = util.isDate
+  it "should check a Date instance correct", ->
+    isDate(new Date()).should.be.true
+    isDate(new Date(2015,1,1)).should.be.true
+  it "should check an illegal date argument correct", ->
+    isDate().should.be.false
+    isDate(Date).should.be.false
+    isDate("2015-01-01").should.be.false
+
+describe "isUndefined", ->
+  isUndefined = util.isUndefined
+  it "should check undefined type correct", ->
+    isUndefined(undefined).should.be.true
+    isUndefined(`undefined`).should.be.true
+  it "should check an other type to false", ->
+    isUndefined(null).should.be.false
+    isUndefined(Date).should.be.false
+    isUndefined(false).should.be.false
+    isUndefined(0).should.be.false
+    isUndefined('undefined').should.be.false
+
+describe "isNullOrUndefined", ->
+  isNullOrUndefined = util.isNullOrUndefined
+  it "should check undefined type correct", ->
+    isNullOrUndefined(undefined).should.be.true
+  it "should check null type correct", ->
+    isNullOrUndefined(null).should.be.true
+  it "should check an other type to false", ->
+    isNullOrUndefined(Date).should.be.false
+    isNullOrUndefined(false).should.be.false
+    isNullOrUndefined(0).should.be.false
+    isNullOrUndefined('undefined').should.be.false
+
+describe "isObject", ->
+  isObject = util.isObject
+  it "should check object type correct", ->
+    Obj = ->
+    obj = Object.create(null)
+    isObject({}).should.be.true
+    isObject(obj).should.be.true
+    isObject(new Obj()).should.be.true
+    isObject(new Date()).should.be.true
+    isObject(/dd/).should.be.true
+  it "should check an other type to false", ->
+    isObject(null).should.be.false
+    isObject("object").should.be.false
+    isObject(false).should.be.false
+    isObject(true).should.be.false
+    isObject(0).should.be.false
+    isObject(->).should.be.false
+
+describe "isFunction", ->
+  isFunction = util.isFunction
+  it "should check function type correct", ->
+    isFunction(->).should.be.true
+    isFunction(Date).should.be.true
+    isFunction(RegExp).should.be.true
+  it "should check an other type to false", ->
+    isFunction(new RegExp()).should.be.false
+    isFunction(new ->).should.be.false
+    isFunction(false).should.be.false
+    isFunction(true).should.be.false
+    isFunction(0).should.be.false
+    isFunction(null).should.be.false
+    isFunction(undefined).should.be.false
+    isFunction("").should.be.false
+
+describe "isString", ->
+  isString = util.isString
+  it "should check string type correct", ->
+    isString("").should.be.true
+    isString("hello").should.be.true
+  it "should check an other type to false", ->
+    isString(new RegExp()).should.be.false
+    isString(new ->).should.be.false
+    isString(false).should.be.false
+    isString(true).should.be.false
+    isString(0).should.be.false
+    isString(null).should.be.false
+    isString(undefined).should.be.false
+describe "_extend", ->
+  extend = util._extend
+  it "should extend an object", ->
+    org = {a: 1, b:2}
+    add = {a: 3}
+    extend(org, add).should.be.equal org
+    org.should.be.deep.equal {a:3, b:2}
+    extend org, b:4, c:2
+    org.should.be.deep.equal {a:3, b:4, c:2}
+  it "should extend many object", ->
+    org = {a: 1, b:2}
+    add = {a: 3}
+    third = {c:4}
+    extend(org, add, third, {d:5, b:0}).should.be.equal org
+    org.should.be.deep.equal {a:3, b:0, c:4, d:5}
+
+describe "extend", ->
+  extend = require('../src/extend')
+  it "should extend an object", ->
+    org = {a: 1, b:2}
+    add = {a: 3}
+    extend(org, add).should.be.equal org
+    org.should.be.deep.equal {a:3, b:2}
+    extend org, b:4, c:2
+    org.should.be.deep.equal {a:3, b:4, c:2}
+  it "should extend many object", ->
+    org = {a: 1, b:2}
+    add = {a: 3}
+    third = {c:4}
+    extend(org, [add, third, {d:5, b:0}]).should.be.equal org
+    org.should.be.deep.equal {a:3, b:0, c:4, d:5}
+  it "should extend an object and filter properties", ->
+    org = {}
+    add = {a:3,b:3,c:2,d:123}
+    extend(org, add, (k,v)->k in ['a','c']).should.be.equal org
+    org.should.be.deep.equal {a:3,c:2}
+
+describe "is/string/int", ->
+  isInt = require('../src/is/string/int')
+  it "should test string whether integer", ->
+    assert.equal isInt("12883"), true
+    assert.equal isInt("012883"), true
+    assert.equal isInt("0x12883"), true
+    assert.equal isInt("128.83"), false
+    assert.equal isInt("128e83"), false
+describe "is/string/json", ->
+  isJson = require('../src/is/string/json')
+  it "should test string integer whether json", ->
+    assert.equal isJson("12883"), true
+    assert.equal isJson("012883"), true
+    assert.equal isJson("0x12883"), false
+    assert.equal isJson("128.83"), true
+    assert.equal isJson("128e83"), true
+  it "should test string whether json", ->
+    assert.equal isJson("'12883'"), false
+    assert.equal isJson('"012883"'), true
+    assert.equal isJson("'0x12883\""), false
+  it "should test string object whether json", ->
+    assert.equal isJson('{"a":12883}'), true
+    assert.equal isJson("{a:12883}"), false
+    assert.equal isJson("{a:12883,b}", true), true #almost json = true
+  it "should test string array whether json", ->
+    assert.equal isJson('["a", 12883]'), true
+    assert.equal isJson('["a", 12883'), false
+    assert.equal isJson('["a, 12883]'), false
+    assert.equal isJson('["a, 12883]', true), true
+
+describe "defineProperty", ->
+  defineProperty = require('../src/defineProperty')
+  it "should define a property", ->
+    obj = {}
+    defineProperty obj, 'prop', 128
+    obj.should.have.property 'prop', 128
+    keys = Object.keys obj
+    keys.should.have.length 0
+  it "should define an enumerable property", ->
+    obj = {}
+    defineProperty obj, 'prop', 128, enumerable:true
+    obj.should.have.property 'prop', 128
+    keys = Object.keys obj
+    keys.should.be.deep.equal ['prop']
+  it "should define a property with getter", ->
+    obj = {}
+    defineProperty obj, 'prop', null,
+      get:->128
+      writable:false
+    obj.should.have.property 'prop', 128
+    keys = Object.keys obj
+    keys.should.have.length 0
+  it "should define a configurable property", ->
+    obj = {}
+    defineProperty obj, 'prop', 128
+    obj.should.have.property 'prop', 128
+    delete obj.prop
+    obj.should.not.have.ownProperty 'prop'
+  it "should define a non-configurable property", ->
+    obj = {}
+    defineProperty obj, 'prop', 128, configurable:false
+    obj.should.have.property 'prop', 128
+    delete obj.prop
+    obj.should.have.ownProperty 'prop'
+


Fixed regex for card image urls

Fixed regex for card image urls

--- assets/js/site.coffee
+++ assets/js/site.coffee
@@ -1,5 +1,5 @@
 MOBILE_WIDTH = 600
-lower = (str) -> return str.replace(/[\s']/g, '').toLowerCase()
+lower = (str) -> return str.replace(/[\s'-]/g, '').toLowerCase()

 class window.Set
    constructor: (data) ->


updated doneable

updated doneable

--- async/Doneable.coffee
+++ async/Doneable.coffee
@@ -1 +1,9 @@
-JSUtils.Doneable = new JSUtils.Sequence()
+# JSUtils.Doneable = new JSUtils.Sequence()
+Object.defineProperties JSUtils, {
+    Doneable: {
+        get: () ->
+            return new App.Sequence()
+        set: () ->
+            return false
+    }
+}


Change description on tests of training.

--- tests/training1st_test.coffee
+++ tests/training1st_test.coffee
@@ -3,18 +3,18 @@

 expect = chai.expect

-helper = new Helper('../scripts/hello.coffee')
+helper = new Helper('../scripts/training1st.coffee')

-describe 'Training 1', ->
+describe 'The 1st day of Training', ->
   beforeEach ->
     @room = helper.createRoom()

   afterEach ->
     @room.destroy()

-  it 'start Training 1', ->
-    @room.user.say('miura', '@customer 三浦と申します。監視ソフトウェアの構築をしていました。').then =>
+  it 'should reply greeting to trainee', ->
+    @room.user.say('miura', '@hubot 三浦と申します。監視ソフトウェアの構築をしていました。').then =>
       expect(@room.messages).to.eql [
-        ['miura', '@customer 三浦と申します。監視ソフトウェアの構築をしていました。']
+        ['miura', '@hubot 三浦と申します。監視ソフトウェアの構築をしていました。']
         ['hubot', '@miura はじめまして、三浦さん。これからよろしくお願いします。']
       ]


[google-fonts] forgot to provide @props context to protocol

--- base/GoogleFonts.cjsx
+++ base/GoogleFonts.cjsx
@@ -47,7 +47,7 @@
         text = @props.text
         text = "&text=#{ text }" if text

-        font_url = "#{ protocol }://fonts.googleapis.com/css?family=#{ fonts.join('|') }#{ text }"
+        font_url = "#{ @props.protocol }://fonts.googleapis.com/css?family=#{ fonts.join('|') }#{ text }"

         if @props.defer
             return <DeferredStylesheet href=font_url />


process-output-view: cleanup comments + names

--- lib/views/process-output-view.coffee
+++ lib/views/process-output-view.coffee
@@ -164,20 +164,19 @@
       addNewLine = true;

   # append line to output panel
-  #   uses three kinds of patterns:
-  #     - WLN = whole-line expressions
-  #     - PLN = part-of-line expressions
-  #     - PTH = path expressions
-  #   - PTHs are roughly the same as PLNs
-  #   - PTHs are converted to PathViews if a corresponding file exists
-  #   - other PLNs are converted to span objects
-  #   - converting is done sequentially: match(line) while(remaining) match(remaining)
-  #   - resulting objects are collected together with strings between them (line_exprs)
-  #   - while converting, the matching parts are replaced by place-holders (<PATH> and <pattern-name>) in the originally line
-  #   - this way the place-holder can be matched afterwards
-  #   - WLNs can be detected by using `^` at the start
-  #   - WLNs is matched against the line with the place-holders
-  #   - the first matching WLN outputs a span object of the collected array and stops matching
+  #   uses three kinds of patterns (processed in three loops in this sequence):
+  #     * PTHEXP = path expression
+  #     * INLEXP = inline expression
+  #     * LINEXP = (whole-)line expression
+  #   * PTHEXPs are roughly the same as INLEXPs, but create clickable objects (PathView)
+  #   * PTHEXPs only match if a corresponding file exists (this allows very loose path regexps)
+  #   * INLEXPs are converted to span objects
+  #   * converting is done sequentially: remaining = match(line) ; while(remaining) remaining = match(remaining)
+  #   * resulting objects and the remaining strings are collected in an array (line_parts)
+  #   * LINEXPs are detected by using `^` at the start
+  #   *   because of that INLEXP anchored at the start of the line need to use a fake group or similar
+  #   * the first matching LINEXP stops matching outputs a span object wrapping the collected array
+
   appendLine: (line) ->

     #console.log("<< " + line)
@@ -185,7 +184,7 @@
     #@outputPanel.append @sanitizeOutput(line) + "<br>"

     ##### process path patterns
-    line_exprs = []
+    line_parts = []
     remaining = line
     any_match = true
     while remaining.length > 0 and any_match
@@ -200,20 +199,20 @@
               any_match = true
               #console.log(["path exist", match.match, remaining])
               cwd = @processController.getCwd()
-              line_exprs.push match.pre
+              line_parts.push match.pre
               obj = new PathView(cwd, match)
               obj.name = "path"
-              line_exprs.push obj
+              line_parts.push obj
               remaining = match.post
               break # process remaining
             remaining = match.post
-            line_exprs.push match.pre + match.match
+            line_parts.push match.pre + match.match
     if remaining.length >= 0
-      line_exprs.push remaining
-
-    ##### process inline patterns patterns
-    parts = line_exprs
-    line_exprs = []
+      line_parts.push remaining
+
+    ##### process inline patterns
+    parts = line_parts
+    line_parts = []
     for part in parts
       if typeof part == "string"
         remaining = part
@@ -227,42 +226,42 @@
               if match?
                 any_match = true
                 #console.log(["expr match", match, remaining])
-                line_exprs.push match.pre
+                line_parts.push match.pre
                 obj = $$ -> @span {class: pattern.config.name}, => @raw(match.match)
                 obj.name = pattern.config.name
-                line_exprs.push obj
+                line_parts.push obj
                 remaining = match.post
                 break # process remaining
         if any_match
           continue # next part
         if remaining.length >= 0
-          line_exprs.push remaining
+          line_parts.push remaining
       else
-        line_exprs.push part
-
-    if line_exprs.length == 0
-      line_exprs = [line]
-
-    #console.log(["line_exprs", line_exprs])
+        line_parts.push part
+
+    if line_parts.length == 0
+      line_parts = [line]
+
+    #console.log(["line_parts", line_parts])
     #console.log(">> " + line_processed)

     ##### replace all objects in line for whole-line matching
     line_processed = ""
-    for part in line_exprs
+    for part in line_parts
       if typeof part == "string"
         line_processed += part
       else
         line_processed += "<" + part.name + ">"
     #console.log("== " + line_processed)

-    ##### whole-line matching
+    ##### (whole-)line matching
     for pattern in @patterns
       if pattern.config.isLineExpression
         match = line_processed.match(pattern.regex)
         if match?
           #console.log(["line match", match.match, line_processed])
           line_span = $$ -> @span {class: pattern.config.name}
-          for part in line_exprs
+          for part in line_parts
             if typeof part == "string"
               line_span.append $$ -> @text(part)
             else
@@ -270,7 +269,7 @@
           @outputPanel.append(line_span)
           return

-    for part in line_exprs
+    for part in line_parts
       if typeof part == "string"
         part = @sanitizeOutput(part)
       @outputPanel.append(part)
@@ -279,7 +278,6 @@
   destroy: ->
     if @processController
       @processController.removeProcessCallback(@);
-
     @buttonsView.destroy();
     @disposables.dispose();
     @element.remove();
@@ -292,5 +290,4 @@
     output = escapeHTML(output);
     # Convert ANSI escape sequences (ex. colors) to HTML
     output = @ansiConvert.toHtml(output);
-
     return output;


Clean up and format git-plus.cson for menu

--- menus/git-plus.cson
+++ menus/git-plus.cson
@@ -2,7 +2,7 @@
 'context-menu':
   'atom-text-editor:not(.mini)': [
     {
-      'label': 'Git add file',
+      'label': 'Git add file'
       'command': 'git-plus:add'
     }
   ]
@@ -13,20 +13,62 @@
     'submenu': [
       'label': 'Git Plus'
       'submenu': [
-        { 'label': 'Add', 'command': 'git-plus:add' }
-        { 'label': 'Add All', 'command': 'git-plus:add-all' }
-        { 'label': 'Commit', 'command': 'git-plus:commit' }
-        { 'label': 'Diff', 'command': 'git-plus:diff' }
-        { 'label': 'Difftool', 'command': 'git-plus:difftool' }
-        { 'label': 'Diff All', 'command': 'git-plus:diff-all' }
-        { 'label': 'Add & Commit', 'command': 'git-plus:add-and-commit'}
-        { 'label': 'Add All & Commit', 'command': 'git-plus:add-all-and-commit'}
-        { 'label': 'Add All & Commit & Push', 'command': 'git-plus:add-all-commit-and-push'}
-        { 'label': 'Push', 'command': 'git-plus:push' }
-        { 'label': 'Log', 'command': 'git-plus:log' }
-        { 'label': 'Merge', 'command': 'git-plus:merge'},
-        { 'label': 'Pull Using Rebase', 'command': 'git-plus:pull-using-rebase'}
-        { 'label': 'Rebase', 'command': 'git-plus:rebase'}
+        {
+          'label': 'Add'
+          'command': 'git-plus:add'
+        }
+        {
+          'label': 'Add All'
+          'command': 'git-plus:add-all'
+        }
+        {
+          'label': 'Commit'
+          'command': 'git-plus:commit'
+        }
+        {
+          'label': 'Diff'
+          'command': 'git-plus:diff'
+        }
+        {
+          'label': 'Difftool'
+          'command': 'git-plus:difftool'
+        }
+        {
+          'label': 'Diff All'
+          'command': 'git-plus:diff-all'
+        }
+        {
+          'label': 'Add & Commit'
+          'command': 'git-plus:add-and-commit'
+        }
+        {
+          'label': 'Add All & Commit'
+          'command': 'git-plus:add-all-and-commit'
+        }
+        {
+          'label': 'Add All & Commit & Push'
+          'command': 'git-plus:add-all-commit-and-push'
+        }
+        {
+          'label': 'Push'
+          'command': 'git-plus:push'
+        }
+        {
+          'label': 'Log'
+          'command': 'git-plus:log'
+        }
+        {
+          'label': 'Merge'
+          'command': 'git-plus:merge'
+        }
+        {
+          'label': 'Pull Using Rebase'
+          'command': 'git-plus:pull-using-rebase'
+        }
+        {
+          'label': 'Rebase'
+          'command': 'git-plus:rebase'
+        }
       ]
     ]
   }


Remove minVersion slider.

Remove minVersion slider.

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -66,12 +66,12 @@
     comparisons.classList.remove 'empty'

 document.addEventListener 'DOMContentLoaded', ->
-  slider = document.querySelector('.version-slider')
-
-  do handleChange = ->
-    setMinVersion slider.value
-
-  slider.addEventListener 'change', handleChange
+  #slider = document.querySelector('.version-slider')
+  #
+  #do handleChange = ->
+  #  setMinVersion slider.value
+  #
+  #slider.addEventListener 'change', handleChange

   search = document.querySelector('input[type="search"]')


bin/patchboard exits with 1 when validation fails

--- coffee/tools.coffee
+++ coffee/tools.coffee
@@ -51,8 +51,10 @@
         # the exact objects in the data and schema that are failing.
         # JSV may have some useful functions for this.
         console.log "Invalid API.  Errors:", report.errors
+        process.exit(1)
     catch error
       console.log "Problem reading API description:", error.message
+      process.exit(1)

   generate: (type) ->
     if type == "json"


backend_pubsub: implement RequestCache

backend_pubsub: implement RequestCache


call it 'CoffeeScript' in the command_line, so that --run scripts may access it as such

--- command_line.coffee
+++ command_line.coffee
@@ -1,7 +1,7 @@
-fs:       require 'fs'
-path:     require 'path'
-coffee:   require 'coffee-script'
-optparse: require('optparse')
+fs:           require 'fs'
+path:         require 'path'
+optparse:     require 'optparse'
+CoffeeScript: require 'coffee-script'

 BANNER: '''
   coffee compiles CoffeeScript source files into JavaScript.
@@ -56,7 +56,7 @@

 # The "--version" message.
 version: ->
-  puts "CoffeeScript version " + coffee.VERSION
+  puts "CoffeeScript version " + CoffeeScript.VERSION
   process.exit 0

 # Compiles the source CoffeeScript, returning the desired JavaScript, tokens,
@@ -71,10 +71,10 @@
 compile_script: (source, code) ->
   o: options
   try
-    if      o.tokens            then print_tokens coffee.tokenize code
-    else if o.tree              then puts coffee.tree(code).toString()
+    if      o.tokens            then print_tokens CoffeeScript.tokenize code
+    else if o.tree              then puts CoffeeScript.tree(code).toString()
     else
-      js: coffee.compile code, compile_options()
+      js: CoffeeScript.compile code, compile_options()
       if      o.run             then eval js
       else if o.lint            then lint js
       else if o.print or o.eval then print js
@@ -89,7 +89,7 @@
   process.stdio.addListener 'data', (string) ->
     code += string if string
   process.stdio.addListener 'close', ->
-    process.stdio.write coffee.compile code, compile_options()
+    process.stdio.write CoffeeScript.compile code, compile_options()

 # Watch a list of source CoffeeScript files, recompiling them every time the
 # files are updated.


Minor bug fix in help text of password changing function

--- lib/core/commands/password.coffee
+++ lib/core/commands/password.coffee
@@ -2,7 +2,7 @@
     name: 'password'
     aliases: ['pword']
     description: "Changes the password on the current account."
-    help: "Usage: password [old password] [new password]"
+    help: "Usage: password"
     action: (context, request, callback) ->
         {mob, room, session} = context
         {verb, args} = request


Expose more info about the servers

Expose more info about the servers

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -28,6 +28,7 @@
 connect = require "connect"
 seaport = require "seaport"
 logger = require "winston"
+uuid = require "node-uuid"
 moment = require "moment"
 colors = require "colors"
 assert = require "assert"
@@ -67,6 +68,7 @@
         msg = "Got HTTP port from the Seaport: %s"
         assert identica = @constructor.identica()
         cfg = config: config, identica: identica
+        _.extend cfg, uuid: uuid.v4(), type: "http"
         record = @seaport.register identica, cfg
         assert _.isNumber(record), "got mistaken"
         logger.info msg.green, "#{record}".bold
@@ -84,6 +86,7 @@
         msg = "Got HTTPS port from the Seaport: %s"
         assert identica = @constructor.identica()
         cfg = config: config, identica: identica
+        _.extend cfg, uuid: uuid.v4(), type: "https"
         record = @seaport.register identica, cfg
         assert _.isNumber(record), "got mistaken"
         logger.info msg.green, "#{record}".bold


fix current module usage

fix current module usage


moved stray log statement

moved stray log statement

--- test/core.coffee
+++ test/core.coffee
@@ -277,10 +277,9 @@
     it 'allows new method to be added to the resulting promise', ->
       @Z.mixinAsync({
         f1: (a1, a2) ->
-          setTimeout (=>
-            console.log "value", @value
+          setTimeout =>
             @done(null, [@value, a1, a2])
-          ), 1
+          , 1
       })
       x = @Z(50)
       val = x.f1(100, 200)


Check that req.route.path is set

Check that req.route.path is set

--- libraries/metrics/http.coffee
+++ libraries/metrics/http.coffee
@@ -8,22 +8,24 @@
 		res.end = () ->
 			end.apply(this, arguments)
 			responseTime = new Date() - startTime
-			routePath = req.route.path.toString().replace(/\//g, '_').replace(/\:/g, '').slice(1)
-			key = "http-requests.#{routePath}.#{req.method}.#{res.statusCode}"
+			if req.route?.path?
+				routePath = req.route.path.toString().replace(/\//g, '_').replace(/\:/g, '').slice(1)
+				key = "http-requests.#{routePath}.#{req.method}.#{res.statusCode}"

-			Metrics.timing(key, responseTime)
-			logger.log
-				req:
-					url: req.originalUrl || req.url
-					method: req.method
-					referrer: req.headers['referer'] || req.headers['referrer']
-					"remote-addr": req.ip || req.socket?.socket?.remoteAddress || req.socket?.remoteAddress
-					"user-agent": req.headers["user-agent"]
-					"content-length": req.headers["content-length"]
-				res:
-					"content-length": res._headers?["content-length"]
-					statusCode: res.statusCode
-				"response-time": responseTime
-				"http request"
+				Metrics.timing(key, responseTime)
+				logger.log
+					req:
+						url: req.originalUrl || req.url
+						method: req.method
+						referrer: req.headers['referer'] || req.headers['referrer']
+						"remote-addr": req.ip || req.socket?.socket?.remoteAddress || req.socket?.remoteAddress
+						"user-agent": req.headers["user-agent"]
+						"content-length": req.headers["content-length"]
+					res:
+						"content-length": res._headers?["content-length"]
+						statusCode: res.statusCode
+					"response-time": responseTime
+					"http request"
+
 		next()


These are very critical fixes, check if its mounted before trying to umount, otherwise it can crash the server

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -63,7 +63,6 @@

         switch mount.remotetype
           when "ftp"
-            console.log "Calling....."
             @mountFtpDrive mount, callback

   # Safe
@@ -150,8 +149,6 @@
     options.mountpoint = escapePath path.join config.usersPath, username, config.baseMountDir, remoteuser
     options.mountpoint+= "@#{escapePath remotehost}"

-    console.log options
-
     unless safeForUser username, options.mountpoint
       console.error "User [#{username}] is trying to make something bad: ", options.mountpoint
       callback "You are not authorized to do this."
@@ -179,7 +176,6 @@
           console.log "[OK] #{remotehost} is mounted for user #{username} to #{mountpoint}"
           res = {mounted: true, remotehost, mountpoint}

-      console.log res
       rs.destroy()

       callback null, res
@@ -213,24 +209,29 @@
       callback "You are not authorized to do this."
       return no

-    @spawnWrapper '/sbin/fuser', ['-mk',options.mountpoint],(err,res)=>
-      @spawnWrapper '/bin/umount', [options.mountpoint],(err, res)=>
-        if err?
-          log.error error = "Can't umount #{options.mountpoint}: #{err}"
-          callback error
-        else
-          log.info info = "Directory #{options.mountpoint} umounted"
-          @remountVE options,(err,res)=>
-            if err
-              callback err
+    @checkMountPoint options, (err, state)=>
+      if not state.mounted
+        callback "Its already unmounted."
+      else
+        # This is too dangerous
+        @spawnWrapper '/sbin/fuser', ['-mk', options.mountpoint], (err, res)=>
+          @spawnWrapper '/bin/umount', [options.mountpoint], (err, res)=>
+            if err?
+              log.error error = "Can't umount #{options.mountpoint}: #{err}"
+              callback error
             else
-              fs.rmdir options.mountpoint, (err)->
-                # not a big issue
-                if err?
-                  console.warn "Couldn't remove mountpoint #{options.mountpoint}: #{err.message}"
+              log.info info = "Directory #{options.mountpoint} umounted"
+              @remountVE options,(err,res)=>
+                if err
+                  callback err
                 else
-                  console.log "mountpoint #{options.mountpoint} has been removed"
-                callback null, info
+                  fs.rmdir options.mountpoint, (err)->
+                    # not a big issue
+                    if err?
+                      console.warn "Couldn't remove mountpoint #{options.mountpoint}: #{err.message}"
+                    else
+                      console.log "mountpoint #{options.mountpoint} has been removed"
+                    callback null, info

   # Safe
   readMountInfo: (options, callback)->
@@ -505,7 +506,7 @@
         log.info info = "User: #{username} virtual environment remounted."
         callback null, info

-  spawnWrapper : (command, args , callback)->
+  spawnWrapper : (command, args, callback)->
     wrapper = spawn command,args
     wrapperErr = ""
     wrapper.stderr.on 'data',(data)->


Added a test for the new autoQueue option

--- test/test.coffee
+++ test/test.coffee
@@ -986,6 +986,19 @@
           done()
         ), 10

+      it "should not add the file to the queue if autoQueue is false", ->
+        doneFunction = null
+        dropzone.options.autoQueue = false
+        dropzone.accept = (file, done) -> doneFunction = done
+        dropzone.processFile = ->
+        dropzone.uploadFile = ->
+
+        dropzone.addFile mockFile
+
+        mockFile.status.should.eql Dropzone.ADDED
+        doneFunction()
+        mockFile.status.should.eql Dropzone.ADDED
+
       it "should create a remove link if configured to do so", ->
         dropzone.options.addRemoveLinks = true
         dropzone.processFile = ->