diff --git a/Dockerfile b/Dockerfile
index 27dd71ba8fad9292bd9824f3587f35446ee0803d..b6ca95ea7b3c151ec77c237af92de65eca02e6f2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,3 +6,4 @@ RUN	apt-get update && \
     build-essential
 
 COPY ./themes/ /usr/src/redmine/public/themes/
+COPY ./plugins/ /usr/src/redmine/plugins/
diff --git a/plugins/easy_gantt/Gemfile b/plugins/easy_gantt/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..772940c268343a35ecb97816c338df3dec60a5f4
--- /dev/null
+++ b/plugins/easy_gantt/Gemfile
@@ -0,0 +1 @@
+gem 'redmine_extensions' unless Dir.exist?(File.expand_path('../../easyproject', __FILE__))
diff --git a/plugins/easy_gantt/LICENSE b/plugins/easy_gantt/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..1255e47665f3a5cbb114d8ef964a1a6fc32b09f3
--- /dev/null
+++ b/plugins/easy_gantt/LICENSE
@@ -0,0 +1,60 @@
+LICENCE
+
+All Easy Redmine Extensions are distributed under GNU/GPL 2 license (see below).
+If not otherwise stated, all images, cascading style sheets, and included JavaScript are NOT GPL, and are released under the Easy Redmine Commercial Use License (see below).
+
+GNU GENERAL PUBLIC LICENSE
+Version 2, June 1991
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.
+When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
+To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
+For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
+We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
+Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
+Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
+The precise terms and conditions for copying, distribution and modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
+1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
+b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
+c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
+In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
+a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
+b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
+c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
+The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
+4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
+6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
+7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
+10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+NO WARRANTY
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+END OF TERMS AND CONDITIONS
+
+EASY REDMINE COMMERCIAL USE LICENSE
+The Easy Redmine Commercial Use License is a GPL compatible license that pertains only to the images, cascading style sheets and JavaScript elements of Easy Redmine Themes and Styles produced by Easy Software Ltd. As stated by the GPL version 2.0 license, these elements of product that are not compiled together but are sent independently of GPL code, and combined in a client's browser, do not have to be GPL themselves. These images, cascading style sheets and JavaScript elements are copyright Easy Software Ltd. and can be used and manipulated for your own or if you have signed Easy Redmine Partner Agreement for your clients purposes. You cannot redistribute these files as your own, or include them in any package or extension of your own without prior consent of Easy Software Ltd.
+Unauthorised distribution or making it accessible to a third party without prior Easy Software Ltd. consent, authorizes Easy Software Ltd. to invoice contractual penalty in the amount of 10 000 EUR for any breach of the this License.
+
diff --git a/plugins/easy_gantt/README.md b/plugins/easy_gantt/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..93b6f5ae664efd22590aaf3ed0c341a7394a1bff
--- /dev/null
+++ b/plugins/easy_gantt/README.md
@@ -0,0 +1,3 @@
+# Easy Gantt
+
+For documentation and requirements, go to https://www.easyredmine.com/redmine-gantt-plugin
diff --git a/plugins/easy_gantt/after_init.rb b/plugins/easy_gantt/after_init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ccc1389d50fd7a6382cb6fcca820ef5bee425712
--- /dev/null
+++ b/plugins/easy_gantt/after_init.rb
@@ -0,0 +1,182 @@
+easy_extensions = Redmine::Plugin.installed?(:easy_extensions)
+app_dir = File.join(File.dirname(__FILE__), 'app')
+lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_gantt')
+
+# Redmine patches
+patch_path = File.join(lib_dir, 'redmine_patch', '**', '*.rb')
+Dir.glob(patch_path).each do |file|
+  require file
+end
+
+ActiveSupport::Dependencies.autoload_paths << File.join(app_dir, 'models', 'queries')
+
+if easy_extensions
+  ActiveSupport::Dependencies.autoload_paths << File.join(app_dir, 'models', 'easy_queries')
+  EasyQuery.register(EasyGanttEasyIssueQuery)
+  EasyQuery.register(EasyGanttEasyProjectQuery)
+
+  # RedmineExtensions::QueryOutput.whitelist_outputs_for_query 'EasyGanttEasyIssueQuery', 'list'
+  # RedmineExtensions::QueryOutput.whitelist_outputs_for_query 'EasyGanttEasyProjectQuery', 'list'
+
+  Rails.application.configure do
+    config.assets.precompile.concat [
+      proc { |logical_path, filename|
+        if logical_path.start_with?('easy_gantt/')
+          basename = File.basename(logical_path)
+          if basename.start_with?('_')
+            false
+          else
+            true
+          end
+        end
+       }
+    ]
+  end
+end
+
+# this block is called every time rails are reloading code
+# in development it means after each change in observed file
+# in production it means once just after server has started
+# in this block should be used require_dependency, but only if necessary.
+# better is to place a class in file named by rails naming convency and let it be loaded automatically
+# Here goes query registering, custom fields registering and so on
+ActionDispatch::Reloader.to_prepare do
+  require 'easy_gantt/easy_gantt'
+  require 'easy_gantt/hooks'
+
+  RedmineExtensions::EasySettingPresenter.boolean_keys << :easy_gantt_show_holidays << :easy_gantt_show_project_progress << :easy_gantt_show_lowest_progress_tasks << :easy_gantt_show_task_soonest_start << :easy_gantt_relation_delay_in_workdays << :easy_gantt_fixed_delay
+end
+
+
+#Redmine::MenuManager.map :top_menu do |menu|
+#  menu.push(:easy_gantt, { controller: 'easy_gantt', action: 'index', set_filter: 0 },
+#    caption: :label_easy_gantt,
+#    after: :documents,
+#    html: { class: 'icon icon-stats' },
+#    if: proc { User.current.allowed_to_globally?(:view_global_easy_gantt) })
+#end
+
+Redmine::MenuManager.map :project_menu do |menu|
+  menu.push(:easy_gantt, { controller: 'easy_gantt', action: 'index' },
+    param: :project_id,
+    caption: :button_project_menu_easy_gantt,
+    if: proc { |p| User.current.allowed_to?(:view_easy_gantt, p) })
+end
+
+Redmine::MenuManager.map :easy_gantt_tools do |menu|
+  menu.push(:back, 'javascript:void(0)',
+            param: :project_id,
+            caption: :button_back,
+            html: { icon: 'icon-back' })
+
+  menu.push(:task_control, 'javascript:void(0)',
+            param: :project_id,
+            caption: :button_edit,
+            html: { icon: 'icon-edit' })
+
+  menu.push(:add_task, 'javascript:void(0)',
+            param: :project_id,
+            caption: :label_new,
+            html: { trial: true, icon: 'icon-add' },
+            if: proc { |p| p.present? })
+
+  menu.push(:critical, 'javascript:void(0)',
+            param: :project_id,
+            caption: :'easy_gantt.button.critical_path',
+            html: { trial: true, icon: 'icon-summary' },
+            if: proc { |p| p.present? })
+
+  menu.push(:baseline, 'javascript:void(0)',
+            param: :project_id,
+            caption: :'easy_gantt.button.create_baseline',
+            html: { trial: true, icon: 'icon-projects icon-project' },
+            if: proc { |p| p.present? })
+
+  menu.push(:resource, proc { |project| defined?(EasyUserAllocations) ? { controller: 'user_allocation_gantt', project_id: project } : nil },
+            param: :project_id,
+            caption: :'easy_gantt.button.resource_management',
+            html: { trial: true, icon: 'icon-stats', easy_text: defined?(EasyExtensions) },
+            if: proc { |p| p.present? })
+
+end
+
+
+# this block is executed once just after Redmine is started
+# means after all plugins are initialized
+# it is place for plain requires, not require_dependency
+# it should contain hooks, permissions - base class in Redmine is required thus is not reloaded
+ActiveSupport.on_load(:easyproject, yield: true) do
+
+  if easy_extensions
+    Redmine::MenuManager.map :projects_easy_page_layout_service_box do |menu|
+      menu.push(:project_easy_gantt, :easy_gantt_path,
+        caption: :label_easy_gantt,
+        html: { class: 'icon icon-stats button button-2' },
+        if: proc { User.current.allowed_to_globally?(:view_global_easy_gantt) })
+    end
+
+    Redmine::MenuManager.map :easy_quick_top_menu do |menu|
+      menu.push(:project_easy_gantt, { controller: 'easy_gantt', action: 'index', id: nil, set_filter: 0 },
+        caption: :label_easy_gantt,
+        parent: :projects,
+        html: { class: 'icon icon-stats' },
+        if: proc { User.current.allowed_to_globally?(:view_global_easy_gantt) })
+
+      menu.push(:issues_easy_gantt, { controller: 'easy_gantt', action: 'index', id: nil, set_filter: 0 },
+        caption: :label_easy_gantt,
+        parent: :issues,
+        html: { class: 'icon icon-stats project-gantt' },
+        if: proc { User.current.allowed_to_globally?(:view_global_easy_gantt) })
+    end
+  end
+
+end
+
+ActionDispatch::Reloader.to_prepare do
+
+  Redmine::AccessControl.map do |map|
+    map.project_module :easy_gantt do |pmap|
+      # View project level
+      pmap.permission(:view_easy_gantt, {
+        easy_gantt: [:index, :issues, :projects],
+        easy_gantt_pro: [:lowest_progress_tasks, :cashflow_data],
+        easy_gantt_resources: [:index, :project_data, :users_sums, :projects_sums, :allocated_issues],
+        easy_resource_limits: [:index]
+      }, read: true)
+
+      # Edit project level
+      pmap.permission(:edit_easy_gantt, {
+        easy_gantt: [:change_issue_relation_delay, :reschedule_project],
+        easy_gantt_resources: [:bulk_update_or_create],
+        easy_resource_limits: [:new, :create, :edit, :update, :destroy]
+      }, require: :member)
+
+      # View global level
+      pmap.permission(:view_global_easy_gantt, {
+        easy_gantt: [:index, :issues, :projects, :project_issues],
+        easy_gantt_pro: [:lowest_progress_tasks, :cashflow_data],
+        easy_gantt_resources: [:index, :project_data, :global_data, :projects_sums, :allocated_issues],
+        easy_resource_limits: [:index]
+      }, global: true, read: true)
+
+      # Edit global level
+      pmap.permission(:edit_global_easy_gantt, {
+        easy_gantt_resources: [:bulk_update_or_create],
+        easy_resource_limits: [:new, :create, :edit, :update, :destroy]
+      }, global: true, require: :loggedin)
+
+      # View personal level
+      pmap.permission(:view_personal_easy_gantt, {
+        easy_gantt_resources: [:global_data],
+        easy_resource_limits: [:index]
+      }, global: true, read: true)
+
+      # Edit personal level
+      pmap.permission(:edit_personal_easy_gantt, {
+        easy_gantt_resources: [:bulk_update_or_create],
+        easy_resource_limits: [:new, :create, :edit, :update, :destroy]
+      }, global: true, require: :loggedin)
+    end
+  end
+
+end
diff --git a/plugins/easy_gantt/app/controllers/easy_gantt_controller.rb b/plugins/easy_gantt/app/controllers/easy_gantt_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..61cb0235e01d67db32e812b2c82fe393b375079b
--- /dev/null
+++ b/plugins/easy_gantt/app/controllers/easy_gantt_controller.rb
@@ -0,0 +1,267 @@
+class EasyGanttController < ApplicationController
+  accept_api_auth :index, :issues, :projects, :project_issues, :change_issue_relation_delay, :reschedule_project
+  menu_item :easy_gantt
+
+  RELATION_TYPES_TO_LOAD = ['relates', 'blocks', 'blocked', 'precedes', 'follows', 'start_to_start', 'finish_to_finish', 'start_to_finish']
+
+  before_action :find_optional_project, except: [:reschedule_project, :project_issues]
+  before_action :find_opened_project, except: [:reschedule_project]
+
+  before_action :authorize, if: proc { @project.present? }
+  before_action :authorize_global, if: proc { @project.nil? }
+
+  before_action :check_rest_api_enabled, only: [:index]
+  before_action :find_relation, only: [:change_issue_relation_delay]
+
+  include_query_helpers
+  helper :custom_fields
+
+  def index
+    retrieve_query
+  end
+
+  # Data retrieve method
+  def issues
+    retrieve_query
+
+    load_projects
+    load_issues
+    load_versions
+    load_relations
+    build_dates @issues, :start_date, :due_date
+  end
+
+  # Data retrieve method
+  def projects
+    retrieve_query
+
+    load_projects
+    build_dates @projects, :gantt_start_date, :gantt_due_date
+
+    @projects_issues_counts = Issue.visible.gantt_opened.where(project_id: @projects).group(:project_id).count(:id)
+  end
+
+  def project_issues
+    # TODO: Global route to skip rights
+    @issues = Issue.visible.gantt_opened.where(project_id: params[:project_id]).order(:start_date)
+    @issue_ids = @issues.map(&:id)
+    load_relations
+
+    version_ids = @issues.map(&:fixed_version_id).uniq.compact
+    @versions = Version.open.where('id IN (?) OR project_id = ?', version_ids, params[:project_id]).sorted
+  end
+
+  def change_issue_relation_delay
+    if !User.current.allowed_to?(:manage_issue_relations, @project)
+      return render_403
+    end
+
+    @relation.update_column(:delay, params[:delay].to_i)
+
+    respond_to do |format|
+      format.api { render_api_ok }
+    end
+  end
+
+  # You cannot use issue.reschedule_on because it will
+  # also set start_date which is not desirable !!!
+  def reschedule_project
+    begin
+      # Do not used callback `find_project` because it will test access rights
+      # to project context. Method wont work if project does not have gantt enabled.
+      project = Project.find(params[:id])
+    rescue ActiveRecord::RecordNotFound
+      render_404
+      return
+    end
+
+    project.gantt_reschedule(params[:days].to_i)
+
+    respond_to do |format|
+      format.api { render_api_ok }
+    end
+  end
+
+  def current_menu_item
+    @current_menu_item ||= if params[:gantt_type] == 'rm'
+                             :resource
+                           else
+                             :easy_gantt
+                           end
+  end
+
+  private
+
+    def check_rest_api_enabled
+      if Setting.rest_api_enabled != '1'
+        render_error message: l('easy_gantt.errors.no_rest_api')
+        return false
+      end
+    end
+
+    def find_relation
+      @relation = IssueRelation.find(params[:id])
+    rescue ActiveRecord::RecordNotFound
+      render_404
+    end
+
+    def query_class
+      if EasyGantt.easy_extensions?
+        @project ? EasyGanttEasyIssueQuery : EasyGanttEasyProjectQuery
+      else
+        @project ? EasyGantt::EasyGanttIssueQuery : EasyGanttProjectQuery
+      end
+    end
+
+    def retrieve_query
+      if params[:query_id].present?
+        cond = 'project_id IS NULL'
+
+        if @project
+          cond << " OR project_id = #{@project.id}"
+
+          # In Easy Project query can be defined for subprojects
+          if !@project.root? && EasyGantt.easy_extensions?
+            ancestors = @project.ancestors.select(:id).to_sql
+            cond << " OR (is_for_subprojects = #{Project.connection.quoted_true} AND project_id IN (#{ancestors}))"
+          end
+        end
+
+        @query = query_class.where(cond).find_by(id: params[:query_id])
+        raise ActiveRecord::RecordNotFound if @query.nil?
+        raise Unauthorized unless @query.visible?
+
+        @query.project = @project
+        sort_clear
+      else
+        @query = query_class.new(name: '_')
+        @query.project = @project
+        @query.from_params(params)
+      end
+
+      if @opened_project
+        @query.opened_project = @opened_project
+      end
+    end
+
+    # Load version from loaded task and from opened projects
+    # TO_CONSIDER: Send versions if there is no tasks?
+    def load_versions
+      version_ids = @issues.map(&:fixed_version_id).uniq.compact
+      @versions = Version.open.where("id IN (?) OR project_id = ?", version_ids, @opened_project.id).sorted
+    end
+
+    # Load subproject of opened project which contains filtered tasks
+    #
+    # Project 1
+    # |-- Project 1.1
+    # |   `-- Project 1.1.1
+    # |       |-- Task 1
+    # |       `-- Task 2
+    # `-- Project 1.2
+    #
+    # If Project 1 is opened, Project 1.1 must be send even if there is no task
+    #
+    # TO_CONSIDER: Send full tree and load only opened_project's issues
+    #
+    def load_projects
+      p_table = Project.table_name
+
+      @projects = []
+
+      # Project gantt is opened, normally only subprojects will be sent
+      # but there is not any root project yet
+      if @project && @opened_project == @project
+        @projects << @project
+      end
+
+      projects = @query.without_opened_project { |q|
+          scope = q.create_entity_scope
+
+          # Not necessary, will take only subprojects
+          if @opened_project
+            scope = scope.where("#{p_table}.lft >= ? AND #{p_table}.rgt <= ?", @opened_project.lft, @opened_project.rgt)
+          end
+
+          scope.distinct.pluck("#{p_table}.lft, #{p_table}.rgt, #{p_table}.parent_id")
+        }
+
+      if projects.blank?
+        return
+      end
+
+      # All ancestors conditions
+      tree_conditions = []
+      projects.each do |lft, rgt|
+        tree_conditions << "(lft <= #{lft} AND rgt >= #{rgt})"
+      end
+      tree_conditions = tree_conditions.join(' OR ')
+
+      @parent_ids = projects.map(&:last)
+
+      # From ancestors take only current opened level
+      @projects.concat Project.where(tree_conditions).where(parent_id: @opened_project.try(:id)).to_a
+
+      Project.load_gantt_dates(@projects)
+      if EasySetting.value(:easy_gantt_show_project_progress)
+        Project.load_gantt_completed_percent(@projects)
+      end
+    end
+
+    # Only between loaded tasks
+    def load_relations
+      if @issue_ids.empty?
+        @relations = []
+      else
+        @relations = IssueRelation.where('issue_from_id IN (?) OR issue_to_id IN (?)', @issue_ids, @issue_ids).
+                                   where(relation_type: RELATION_TYPES_TO_LOAD)
+      end
+    end
+
+    def load_issues
+      preloads = [:project, :author, :assigned_to, :relations_to]
+
+      if EasySetting.value(:easy_gantt_show_task_soonest_start)
+        preloads << :parent
+        preloads << { relations_to: :issue_from }
+      else
+        preloads << :relations_to
+      end
+
+      @issues = @query.entities(
+          includes: [:project, :status, :assigned_to, :fixed_version, :tracker, :priority, :custom_values],
+          preload: preloads,
+          order: "#{Issue.table_name}.start_date, #{Issue.table_name}.id"
+        )
+
+      @issue_ids = @issues.map(&:id)
+    end
+
+    def build_dates(data, starts, ends)
+      starts = data.map(&starts).compact
+      ends = data.map(&ends).compact
+
+      @start_date = (starts.min || ends.min || Date.today) - 1.day
+      @end_date = (ends.max || starts.max || Date.today) + 1.day
+    end
+
+    def find_optional_project
+      # Easy query workaround
+      if params[:set_filter] == '1' && params[:project_id].present? && params[:project_id].start_with?('=', '!*', '*')
+        return
+      end
+
+      super
+    end
+
+    def find_opened_project
+      if params[:opened_project_id].present?
+        @opened_project = Project.find(params[:opened_project_id])
+      else
+        @opened_project = @project
+      end
+    rescue ActiveRecord::RecordNotFound
+      render_404
+    end
+
+end
diff --git a/plugins/easy_gantt/app/helpers/easy_gantt_helper.rb b/plugins/easy_gantt/app/helpers/easy_gantt_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86c5ece6f4dad4ce0fbd54a2cbd6fc2295879a16
--- /dev/null
+++ b/plugins/easy_gantt/app/helpers/easy_gantt_helper.rb
@@ -0,0 +1,267 @@
+module EasyGanttHelper
+
+  def easy_gantt_include_js(*javascripts, from_plugin: 'easy_gantt')
+    plugin = (EasyGantt.easy_extensions? ? nil : from_plugin)
+
+    result = ''
+    javascripts.flatten!
+    javascripts.compact!
+    javascripts.each do |javascript|
+      result << javascript_include_tag("#{from_plugin}/#{javascript}", plugin: plugin)
+    end
+    result.html_safe
+  end
+
+  def easy_gantt_include_css(*stylesheets, media: 'screen', from_plugin: 'easy_gantt')
+    plugin = (EasyGantt.easy_extensions? ? nil : from_plugin)
+
+    result = ''
+    stylesheets.flatten!
+    stylesheets.compact!
+    stylesheets.each do |stylesheet|
+      result << stylesheet_link_tag("#{from_plugin}/#{stylesheet}", plugin: plugin, media: media)
+    end
+    result.html_safe
+  end
+
+  def easy_gantt_js_button(text, options={})
+    if text.is_a?(Symbol)
+      lang_key = text
+      text = l(lang_key, scope: [:easy_gantt, :button])
+      options[:title] ||= l(lang_key, scope: [:easy_gantt, :title], default: text)
+    end
+    options[:class] = "gantt-menu-button #{options[:class]}"
+    options[:class] << ' button' unless options.delete(:no_button)
+    if (icon = options.delete(:icon))
+      options[:class] << " icon #{icon}"
+    end
+    link_to(text, options[:url] || 'javascript:void(0)', options)
+  end
+
+  def easy_gantt_help_button(*args)
+    options = args.extract_options!
+    feature = args.shift
+    text = args.shift
+
+    options[:class] = "gantt-menu-help-button #{options[:class]}"
+    options[:icon] ||= 'icon-help'
+    options[:id] = 'button_' + feature.to_s + '_help'
+
+    help_text = raw l(options.delete(:easy_text) ? :easy_text : :text, scope: [:easy_gantt, :popup, feature])
+
+    easy_gantt_js_button(text || '&#8203;'.html_safe, options) + %Q(
+    <div id="#{feature}_help_modal" style="display:none">
+      <h3 class="title">#{raw l(:heading, scope: [:easy_gantt, :popup, feature]) }</h3>
+      <p>#{help_text}</p>
+     </div>
+    ).html_safe
+  end
+
+  def api_render_versions(api, versions)
+    return if versions.blank?
+
+    api.array :versions do
+      versions.each do |version|
+        api.version do
+          api.id version.id
+          api.name version.name
+          api.start_date version.effective_date
+          api.project_id version.project_id
+          api.permissions do
+            api.editable version.gantt_editable?
+          end
+        end
+      end
+    end
+
+  end
+
+  def api_render_columns(api, query)
+    api.array :columns do
+      query.columns.each do |c|
+        api.column do
+          api.name c.name
+          api.title c.caption
+        end
+      end
+    end
+  end
+
+  def api_render_scheme(api, table)
+    if table.is_a?(Symbol)
+      if Object.const_defined?(table)
+        table = Object.const_get(table)
+      else
+        return
+      end
+    end
+
+    return unless table.column_names.include?('easy_color_scheme')
+
+    col = table.arel_table[:easy_color_scheme]
+    records = table.where(col.not_eq(nil).and(col.not_eq(''))).pluck(:id, :easy_color_scheme)
+
+    api.array table.to_s do
+      records.each do |id, scheme|
+        api.entity do
+          api.id id
+          api.scheme scheme
+        end
+      end
+    end
+  end
+
+  def api_render_holidays(api, startdt, enddt)
+    wc = User.current.try(:current_working_time_calendar)
+    return if wc.nil?
+
+    api.array :holidays do
+      startdt.upto(enddt) do |date|
+        api.date date if wc.holiday?(date)
+      end
+    end
+
+  end
+
+  def api_render_issues(api, issues, with_columns: false)
+    api.array :issues do
+      issues.each do |issue|
+        api.issue do
+          api.id issue.id
+          api.name issue.subject
+          api.start_date issue.start_date
+          api.due_date issue.due_date
+          api.estimated_hours issue.estimated_hours
+          api.done_ratio issue.done_ratio
+          api.closed issue.closed?
+          api.fixed_version_id issue.fixed_version_id
+          api.overdue issue.overdue?
+          api.parent_issue_id issue.parent_id
+          api.project_id issue.project_id
+          api.tracker_id issue.tracker_id
+          api.priority_id issue.priority_id
+          api.status_id issue.status_id
+          api.assigned_to_id issue.assigned_to_id
+
+          if EasySetting.value(:easy_gantt_show_task_soonest_start) && @project.nil?
+            api.soonest_start issue.soonest_start
+          end
+          if EasySetting.value(:easy_gantt_show_task_latest_due) && @project.nil?
+            api.latest_due issue.latest_due
+          end
+
+          api.is_planned !!issue.project.try(:is_planned)
+
+          api.permissions do
+            api.editable issue.gantt_editable?
+          end
+
+          if with_columns
+            api.array :columns do
+              @query.columns.each do |c|
+                api.column do
+                  api.name c.name
+                  api.value gantt_format_column(issue, c, c.value(issue))
+                end
+              end
+            end
+          end
+
+        end
+      end
+    end
+  end
+
+  def api_render_relations(api, relations)
+    api.array :relations do
+      relations.each do |rel|
+        api.relation do
+          api.id rel.id
+          api.source_id rel.issue_from_id
+          api.target_id rel.issue_to_id
+          api.type rel.relation_type
+          api.delay rel.delay.to_i
+        end
+      end
+    end
+  end
+
+  def api_render_projects(api, projects, with_columns: false)
+    api.array :projects do
+      projects.each do |project|
+        api.project do
+          api.id project.id
+          api.name project.name
+          api.start_date project.gantt_start_date || Date.today
+          api.due_date project.gantt_due_date || Date.today
+          api.parent_id project.parent_id
+          api.is_baseline project.try(:easy_baseline_for_id?)
+
+          # Schema
+          api.status_id project.status
+          api.priority_id project.try(:easy_priority_id)
+
+          api.permissions do
+            api.editable project.gantt_editable?
+          end
+
+          if EasySetting.value(:easy_gantt_show_project_progress)
+            api.done_ratio project.gantt_completed_percent
+          end
+
+          if @projects_issues_counts && @projects_issues_counts.has_key?(project.id)
+            api.issues_count @projects_issues_counts[project.id]
+          end
+
+          if @parent_ids && @parent_ids.include?(project.id)
+            api.has_subprojects true
+          end
+
+          if with_columns
+            api.array :columns do
+              @query.columns.each do |c|
+                api.column do
+                  api.name c.name
+                  api.value gantt_format_column(project, c, c.value(project))
+                end
+              end
+            end
+          end
+
+        end
+      end
+    end
+  end
+
+  # This method exist because
+  #   1. EntityAttributeHelper is for complex html formating
+  #   2. Redmine doest not have it
+  # Gantt should show light and non-html values
+  def gantt_format_column(entity, column, value)
+    if entity.is_a?(Project) && column.name == :status && respond_to?(:format_project_attribute)
+      format_project_attribute(Project, column, value)
+    elsif value.is_a?(Float)
+      locale = User.current.language.presence || ::I18n.locale
+      number_with_precision(value, locale: locale).to_s
+    elsif value.is_a?(Array)
+      value.join(', ')
+    else
+      value.to_s
+    end
+  end
+
+  def prepare_test_includes(tests)
+    includes = []
+    tests.each do |test_file|
+      plugin = 'easy_gantt'
+      if test_file.include?('/')
+        split = test_file.split('/')
+        plugin = split[0]
+        test_file = split[1]
+      end
+      includes << [test_file, plugin]
+    end
+    includes
+  end
+
+end
diff --git a/plugins/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb b/plugins/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..580a954f48580468b2a7457bf42b34a948289cb0
--- /dev/null
+++ b/plugins/easy_gantt/app/models/easy_gantt/easy_gantt_issue_query.rb
@@ -0,0 +1,82 @@
+module EasyGantt
+  class EasyGanttIssueQuery < IssueQuery
+
+    attr_accessor :entity_scope
+    attr_accessor :opened_project
+
+    def default_columns_names
+      [:subject, :priority, :assigned_to]
+    end
+
+    def entity
+      Issue
+    end
+
+    def from_params(params)
+      build_from_params(params)
+    end
+
+    def to_params
+      params = { set_filter: 1, type: self.class.name, f: [], op: {}, v: {} }
+
+      filters.each do |filter_name, opts|
+        params[:f] << filter_name
+        params[:op][filter_name] = opts[:operator]
+        params[:v][filter_name] = opts[:values]
+      end
+
+      params[:c] = column_names
+      params
+    end
+
+    def to_partial_path
+      'easy_gantt/easy_queries/show'
+    end
+
+    def initialize_available_filters
+      super
+      @available_filters.delete('subproject_id')
+    end
+
+    def entity_scope
+      @entity_scope ||= begin
+        scope = Issue.visible
+        if Project.column_names.include? 'easy_baseline_for_id'
+          scope = scope.where(Project.table_name => {easy_baseline_for_id: nil})
+        end
+        scope
+      end
+    end
+
+    def create_entity_scope(options={})
+      entity_scope.includes(options[:includes]).
+                   references(options[:includes]).
+                   preload(options[:preload]).
+                   where(statement).
+                   where(options[:conditions])
+    end
+
+    def entities(options={})
+      create_entity_scope(options).order(options[:order])
+    end
+
+    def project_statement
+      p_table = Project.table_name
+
+      conditions = "#{p_table}.status = #{Project::STATUS_ACTIVE}"
+      if opened_project
+        conditions = "#{conditions} AND #{Project.table_name}.id = #{opened_project.id}"
+      end
+      conditions
+    end
+
+    def without_opened_project
+      _opened_project = opened_project
+      self.opened_project = nil
+      yield self
+    ensure
+      self.opened_project = _opened_project
+    end
+
+  end
+end
diff --git a/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_issue_query.rb b/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_issue_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d9f3e201200dd723add9f9c7fe2077ded5f98d06
--- /dev/null
+++ b/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_issue_query.rb
@@ -0,0 +1,128 @@
+class EasyGanttEasyIssueQuery < EasyIssueQuery
+
+  KEEP_AVAILABLE_COLUMNS = [
+    # Issues
+    :id, :project, :parent, :status, :tracker, :priority, :fixed_version, :subject, :start_date, :due_date, :created_on, :updated_on, :easy_status_updated_on, :open_duration_in_hours, :easy_last_updated_by, :done_ratio, :easy_due_date_time_remaining, :closed_on, :easy_closed_by, :is_private, :category, :assigned_to, :author, :estimated_hours, :total_estimated_hours, :spent_hours, :total_spent_hours, :remaining_timeentries, :total_remaining_timeentries, :spent_estimated_timeentries, :total_spent_estimated_timeentries,
+
+    # Projects
+   :"projects.name", :"projects.parent", :"projects.root", :"projects.status", :"projects.start_date", :"projects.due_date", :"projects.created_on", :"projects.updated_on", :"projects.easy_indicator", :"projects.identifier", :"projects.completed_percent", :"projects.priority", :"projects.sum_estimated_hours", :"projects.total_sum_estimated_hours", :"projects.sum_of_timeentries", :"projects.remaining_timeentries", :"projects.total_remaining_timeentries", :"projects.total_spent_hours", :"projects.author"
+  ]
+
+  attr_accessor :opened_project
+
+  def query_after_initialize
+    super
+
+    self.display_filter_group_by_on_index = false
+    self.display_filter_sort_on_index = false
+    self.display_filter_settings_on_index = false
+
+    self.display_filter_group_by_on_edit = false
+    self.display_filter_sort_on_edit = false
+    self.display_filter_settings_on_edit = false
+
+    self.display_show_sum_row = false
+    self.display_load_groups_opened = false
+
+    self.export_formats = {}
+    self.is_tagged = true if new_record?
+  end
+
+  def available_outputs
+    []
+  end
+
+  def groupable_columns
+    []
+  end
+
+  def available_outputs
+    ['list']
+  end
+
+  def default_list_columns
+    super.presence || ['subject', 'assigned_to']
+  end
+
+  def easy_query_entity_controller
+    'easy_gantt'
+  end
+
+  def easy_query_entity_action
+    'index'
+  end
+
+  def column_groups_ordering
+    [
+      l(:label_most_used),
+      l("label_filter_group_#{class_name_underscored}"),
+      EasyQuery.column_filter_group_name(nil),
+      l(:label_filter_group_easy_time_entry_query),
+      l(:label_filter_group_status_time),
+      l(:label_filter_group_status_count),
+      l(:label_filter_group_easy_project_query),
+      EasyQuery.column_filter_group_name(:project),
+      l(:label_user_plural)
+    ]
+  end
+
+  def entity_easy_query_path(**options)
+    project = options[:project] || project
+    if project
+      easy_gantt_path(project, options)
+    end
+  end
+
+  # Delete these filters because of project_scope
+  def initialize_available_filters
+    super
+    @available_filters.delete('subproject_id')
+  end
+
+  def initialize_available_columns
+    super
+    add_associated_columns EasyProjectQuery
+
+    @available_columns.keep_if { |column|
+      KEEP_AVAILABLE_COLUMNS.include?(column.name) ||
+      column.name.to_s.start_with?('cf_') ||
+      column.name.to_s.start_with?('projects.cf_')
+    }
+  end
+
+  def project_scope
+    # Except closed and archived
+    scope = Project.active_and_planned
+
+    # Templates should be included only on template context
+    if project.nil? || !project.easy_is_easy_template?
+      scope = scope.non_templates
+    end
+
+    if opened_project
+      scope = scope.where(id: opened_project.id)
+    end
+
+    if has_filter?('is_planned')
+      op = value_for('is_planned') == '1' ? '=' : '<>'
+      scope = scope.where("#{Project.table_name}.status #{op} ?", Project::STATUS_PLANNED)
+    end
+
+    scope
+  end
+
+  def without_opened_project
+    _opened_project = opened_project
+    self.opened_project = nil
+    self.additional_scope = nil
+    yield self
+  ensure
+    self.opened_project = _opened_project
+    self.additional_scope = nil
+  end
+
+  def self.chart_support?
+    false
+  end
+
+end
diff --git a/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_project_query.rb b/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_project_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb52ed88abd69c5bcdf156d2d995b4f5301b8e3d
--- /dev/null
+++ b/plugins/easy_gantt/app/models/easy_queries/easy_gantt_easy_project_query.rb
@@ -0,0 +1,87 @@
+class EasyGanttEasyProjectQuery < EasyProjectQuery
+
+  attr_accessor :opened_project
+
+  def query_after_initialize
+    super
+
+    self.display_filter_group_by_on_index = false
+    self.display_filter_sort_on_index = false
+    self.display_filter_settings_on_index = false
+
+    self.display_filter_group_by_on_edit = false
+    self.display_filter_sort_on_edit = false
+    self.display_filter_settings_on_edit = false
+
+    self.display_show_sum_row = false
+    self.display_load_groups_opened = false
+
+    self.export_formats = {}
+    self.is_tagged = true if new_record?
+  end
+
+  def groupable_columns
+    []
+  end
+
+  def available_outputs
+    ['list']
+  end
+
+  def default_list_columns
+    super.presence || ['name']
+  end
+
+  def filter_groups_ordering
+    [
+      l(:label_most_used),
+      l(:label_filter_group_easy_project_query),
+      EasyQuery.column_filter_group_name(nil)
+    ]
+  end
+
+  def column_groups_ordering
+    [
+      l(:label_most_used),
+      l(:label_filter_group_easy_project_query),
+      EasyQuery.column_filter_group_name(nil),
+      l(:label_filter_group_easy_time_entry_query),
+      l(:label_user_plural)
+    ]
+  end
+
+  def easy_query_entity_controller
+    'easy_gantt'
+  end
+
+  def easy_query_entity_action
+    'index'
+  end
+
+  def entity_easy_query_path(**options)
+    easy_gantt_path(options)
+  end
+
+  def additional_scope
+    if opened_project
+      Project.where(id: opened_project.id)
+    else
+      nil
+    end
+  end
+
+  def without_opened_project
+    _opened_project = opened_project
+    self.opened_project = nil
+    self.additional_scope = nil
+    yield self
+  ensure
+    self.opened_project = _opened_project
+    self.additional_scope = nil
+  end
+
+  def self.chart_support?
+    false
+  end
+
+end
diff --git a/plugins/easy_gantt/app/models/queries/easy_gantt_project_query.rb b/plugins/easy_gantt/app/models/queries/easy_gantt_project_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8ebf9decbf59393bb6de3da29b07d5cf73fa882a
--- /dev/null
+++ b/plugins/easy_gantt/app/models/queries/easy_gantt_project_query.rb
@@ -0,0 +1,90 @@
+class EasyGanttProjectQuery < Query
+
+  attr_accessor :opened_project
+
+  self.queried_class = Project
+
+  self.available_columns = [
+    QueryColumn.new(:name, sortable: "#{Project.table_name}.name"),
+    QueryColumn.new(:created_on, sortable: "#{Project.table_name}.created_on"),
+    QueryColumn.new(:updated_on, sortable: "#{Project.table_name}.updated_on"),
+  ]
+
+  def initialize(*args)
+    super
+    self.filters ||= {}
+  end
+
+  def default_columns_names
+    [:name]
+  end
+
+  def initialize_available_filters
+    add_available_filter 'name', type: :text
+    add_available_filter 'created_on', type: :date_past
+    add_available_filter 'updated_on', type: :date_past
+  end
+
+  def from_params(params)
+    build_from_params(params)
+  end
+
+  def to_params
+    params = { set_filter: 1, type: self.class.name, f: [], op: {}, v: {} }
+
+    filters.each do |filter_name, opts|
+      params[:f] << filter_name
+      params[:op][filter_name] = opts[:operator]
+      params[:v][filter_name] = opts[:values]
+    end
+
+    params[:c] = column_names
+    params
+  end
+
+  def to_partial_path
+    'easy_gantt/easy_queries/show'
+  end
+
+  def entities(options={})
+    scope = Project.active.visible
+
+    if Project.column_names.include?('easy_baseline_for_id')
+      scope = scope.where(Project.table_name => { easy_baseline_for_id: nil })
+    end
+
+    scope = scope.includes(options[:includes]).
+                  references(options[:includes]).
+                  preload(options[:preload]).
+                  where(statement).
+                  where(options[:conditions]).
+                  order(options[:order])
+
+    if opened_project
+      scope = scope(projects: { id: opened_project.id })
+    end
+
+    scope.to_a
+  end
+
+  def entity_scope
+    Project.visible
+  end
+
+  def create_entity_scope(options={})
+    entity_scope.includes(options[:includes]).
+                 references(options[:includes]).
+                 preload(options[:preload]).
+                 where(statement).
+                 where(options[:conditions])
+  end
+
+  def without_opened_project
+    _opened_project = opened_project
+    self.opened_project = nil
+    yield self
+  ensure
+    self.opened_project = _opened_project
+  end
+
+end
diff --git a/plugins/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb b/plugins/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e2180455978b70c27c9b4a0036e96ad277b281e5
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/_already_active_error.html.erb
@@ -0,0 +1,3 @@
+<p class="error">
+  <%= l(:error_epm_easy_gantt_already_active) %>
+</p>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/_includes.html.erb b/plugins/easy_gantt/app/views/easy_gantt/_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..93d3fadbae8138b800d512a2d9bae995a8ee218c
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/_includes.html.erb
@@ -0,0 +1,56 @@
+<%
+  heads_for_wiki_formatter
+
+  if EasyGantt.easy_extensions?
+    main_css = 'easy_gantt'
+  else
+    main_css = 'generated/easy_gantt'
+  end
+%>
+
+<%= content_for :header_tags do %>
+  <%= easy_gantt_include_css(main_css, media: 'all') %>
+  <% if EasyGantt.combine_by_pipeline?(params) %>
+    <%= javascript_include_tag('easy_gantt/easy_gantt') %>
+  <% else %>
+    <%= easy_gantt_include_js(
+            'utils',
+            'dhtmlxgantt',
+            'dhtmlxgantt_marker',
+            'main',
+            'data',
+            'loader',
+            'saver',
+            'logger',
+            'widget',
+            'panel_widget',
+            'gantt_widget',
+            'view',
+            'history',
+            'dhtml_modif',
+            'dhtml_addons',
+            'dhtml_rewrite',
+            ('dhtml_relations' if EasySetting.value(:easy_gantt_fixed_delay)),
+            'background',
+            'pro_manager',
+            'storage',
+            'tooltip',
+            'toolpanel',
+            'print',
+            'left_grid',
+            'sumrow',
+            'bars',
+            'problem_finder',
+            'collapsor',
+            'libs/libs',
+            'libs/svg.min',
+        ) %>
+  <% end %>
+  <%= easy_gantt_include_js(
+          ('sample' unless EasyGantt.easy_gantt_pro?),
+          ('libs/moment' unless EasyGantt.easy_extensions?)
+      ) %>
+  <script type="application/javascript">
+    $(ysy.initGantt);
+  </script>
+<% end %>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb b/plugins/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3c7f30c39c1be4670ef4bb845cc42ec31dcda49f
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/_js_prepare.html.erb
@@ -0,0 +1,149 @@
+<%= content_for :header_tags do %>
+  <script type="text/javascript">
+    window.ysy = window.ysy || {};
+    ysy.settings = ysy.settings || {};
+    $.extend(true, ysy.settings, <%= {
+      platform: EasyGantt.platform,
+      easyRedmine: EasyGantt.easy_extensions?,
+      isGantt: (params[:controller] == 'easy_gantt'),
+      language: I18n.locale.to_s,
+      project: ({ id: @project.id, name: @project.name } if @project),
+      dateFormat: (Setting.date_format.presence || I18n.t('date.formats.default')),
+      nonWorkingWeekDays: EasyGantt.non_working_week_days,
+      holidays: [],
+      milestonePush: ((EasyGantt.easy_extensions? && !EasySetting.value('milestone_effective_date_from_issue_due_date')) ? true : false),
+      workDayDelays: EasySetting.value(:easy_gantt_relation_delay_in_workdays),
+      fixedRelations: EasySetting.value(:easy_gantt_fixed_delay),
+      defaultZoom: EasySetting.value(:easy_gantt_default_zoom),
+      paths: {
+        rootPath: home_path
+      },
+      labels: {
+        buttons: {
+          button_delete: l(:button_delete),
+          button_submit: l(:button_submit),
+          button_yes: l(:general_text_Yes),
+          button_no: l(:general_text_No),
+          button_cancel: l(:button_cancel),
+          button_reload: l('easy_gantt.button.reload'),
+          button_save: l(:button_save)
+        },
+        sample_text: l('easy_gantt.sample.text').html_safe,
+        sample_global_free_text: l('easy_gantt.sample_global_free.text').html_safe,
+        date: {
+          month_full: Array(l('date.month_names')).compact,
+          month_short: Array(l('date.abbr_month_names')).compact,
+          day_full: Array(l('date.day_names')).compact,
+          day_short: Array(l('date.abbr_day_names')).compact
+        },
+        types: {
+          project: l(:field_project),
+          issue: l(:field_issue),
+          milestone: l(:field_version),
+          relation: l(:field_relation)
+        },
+        errors: Array(l('activerecord.errors.messages')).compact,
+        errors2: {
+          unsaved_parent: l('easy_gantt.errors.unsaved_parent')
+        },
+        problems: {
+          overMilestone: l('easy_gantt.errors.overmile'),
+          too_short: l('easy_gantt.errors.too_short'),
+          overdue: l('easy_gantt.errors.overdue'),
+          progressDateOverdue: l('easy_gantt.errors.progress_date_overdue'),
+          shortDelay: l('easy_gantt.errors.short_delay')
+        },
+        gateway: {
+          sendFailed: l('easy_gantt.gateway.send_failed'),
+          entitySaveFailed: l('easy_gantt.gateway.entity_save_failed'),
+          allSended: l(:notice_successful_update)
+        },
+        titles: {
+          easyGantt: l(:heading_easy_gantts_issues)
+        }
+      },
+      styles: {
+        backgrounds:{
+          selected: '#fff3a1',
+          line: 'rgba(200,200,200,0.5)',
+          line_month: '#aaaaff'
+        }
+      }
+    }.to_json.html_safe %>)
+
+    ysy.view = ysy.view || {};
+    ysy.view.templates = $.extend(ysy.view.templates, <%= {
+      TaskTooltip: %{
+        <h3 class="gantt-tooltip-header">{{name}}</h3>
+        {{#start_date}}
+          <div class="gantt-tooltip-start_date"><span class="gantt-tooltip-label">#{l(:field_start_date)}:</span> {{start_date}}</div>
+        {{/start_date}}
+        {{#end_date}}
+          <div class="gantt-tooltip-end_date"><span class="gantt-tooltip-label">#{l(:field_due_date)}:</span> {{end_date}}</div>
+        {{/end_date}}
+        {{#milestone}}
+          <div class="gantt-tooltip-milestone"><span class="gantt-tooltip-label">#{l(:field_version)}:</span> {{milestone.name}}</div>
+        {{/milestone}}
+        {{#columns}}
+          <div class="gantt-tooltip-column-{{name}}"><span class="gantt-tooltip-label">{{label}}:</span> {{value}}</div>
+        {{/columns}}
+        {{#problems}}
+          <div class="gantt-tooltip-problem">{{.}}</div>
+        {{/problems}}
+      },
+      Button: %{
+        <span class="button {{active}}" title="{{title}}">
+          <a id="{{elid}}_button_in" class="gantt_button{{icon}}" href="javascript:void(0)">{{name}}</a>
+        </span>
+      },
+      LinkButton: %{
+        <a class="{{css}}" title="{{title}}" href="javascript:void(0)">{{name}}</a>
+      },
+      SuperPanel: %{},
+      # reloadModal: %{
+      #   <h3 class="title">#{l('easy_gantt.reload_modal.title')}</h3>
+      #   <h4>#{l('easy_gantt.reload_modal.label_errors')}:</h4>
+      #   <ul class="gantt-reload-modal-errors">
+      #     {{#errors}}
+      #       <li class="gantt-reload-model-error">{{.}}</li>
+      #     {{/errors}}
+      #   </ul>
+      #   <p>#{l('easy_gantt.reload_modal.text_reload_appeal')}</p>
+      # },
+      preBlocker: %{
+        <div style="left:{{pos_x}}px" class="gantt_task_relation_stop gantt_task_relation_stop_left" title="#{l('easy_gantt.text_blocker_move_pre')}">
+        </div>
+      },
+      endBlocker: %{
+        <div style="left:{{pos_x}}px" class="gantt_task_relation_stop gantt_task_relation_stop_right" title="#{l('easy_gantt.text_blocker_move_end')}">
+        </div>
+      },
+      ProblemFinder: %{
+        #{l('easy_gantt.button.problem_finder')}:
+        <span class="gantt-menu-problems-count{{#count}} gantt-with-problems{{/count}}">{{count}}</span>
+      },
+      ProblemFinderList: %{
+        <ol>
+          {{#entities}}
+            <li>
+              <a href="javascript:ysy.pro.problemFinder.scrollToEntity('{{entityId}}')">
+                {{#isProject}}#{l(:field_project)}{{/isProject}}{{^isProject}}#{l(:field_issue)}{{/isProject}}: <strong>{{name}}</strong>
+                <br>
+                <span class="gantt-menu-problems-reason">{{text}}</span>
+              </a>
+            </li>
+          {{/entities}}
+          {{#relations}}
+            <li>
+              <a href="javascript:ysy.pro.problemFinder.scrollToEntity('{{entityId}}')">
+                #{l(:field_relation)}: <strong>{{sourceName}}</strong> - <strong>{{targetName}}</strong>
+                <br>
+                <span class="gantt-menu-problems-reason">{{text}}</span>
+              </a>
+            </li>
+          {{/relations}}
+        </ol>
+      }
+    }.to_json.html_safe %>)
+  </script>
+<% end %>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb b/plugins/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d70895166d17ccd0301fedc97e39a18fc912e6f3
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/_menu_graph.html.erb
@@ -0,0 +1,70 @@
+<%
+  unless defined?(show_menu_items)
+    show_menu_items = true
+  end
+
+  zooms = { 'day' => 'calendar-day', 'week' => 'calendar-week', 'month' => 'calendar-month' }
+%>
+<span id="close_all_something">
+  <a id="button_close_all_projects" title="<%= l(:close_all, scope: [:easy_gantt, :button])+' '+l(:label_project_plural) %>" class="gantt-button-symbol icon-folder" href="javascript:void(0)"></a>
+  <a id="button_close_all_milestones" title="<%= l(:close_all, scope: [:easy_gantt, :button])+' '+l(:label_version_plural) %>" class="gantt-button-symbol gantt-icon-milestone" href="javascript:void(0)"></a>
+  <a id="button_close_all_parent_issues" title="<%= l(:close_all, scope: [:easy_gantt, :button])+' '+l(:label_parent_issue_plural) %>" class="gantt-button-symbol gantt-icon-parent-issue" href="javascript:void(0)"></a>
+</span>
+<div id="supertop_panel" class="gantt-supertop-panel easy-gantt__supertop-panel clear"></div>
+<div id="easy_gantt_menu" class="easy-gantt__menu clear">
+  <div class="push-left">
+    <% if Rails.env.development? %>
+      <%= easy_gantt_js_button l(:button_test), id: 'button_test' %>
+    <% end %>
+
+    <%= easy_gantt_js_button('&#8203;'.html_safe, id: 'button_jump_today', title: l(:jump_today, scope: [:easy_gantt, :title]), icon: 'icon-calendar') %>
+
+    <% zooms.each do |name, icon| %>
+      <%= easy_gantt_js_button(:"#{name}_zoom", id: "button_#{name}_zoom", icon: "icon-#{icon}") %>
+    <% end %>
+  </div>
+
+  <div class="push-right">
+    <% if show_menu_items %>
+      <%= easy_gantt_js_button(:problem_finder, {
+          url: 'javascript:void(0)',
+          id: 'button_problem_finder',
+          class: 'problem-finder',
+          no_button: true
+      }) %>
+      <div class="easy-gantt__menu-group easy-gantt__menu-group--tooltiped easy-gantt__menu-tools">
+        <a href="javascript:void(0)" class="button gantt-menu-button icon icon-settings easy-gantt__menu-tools-button"><%= l(:tool_panel, :scope => [:easy_gantt, :button]) %></a>
+        <ul id="easy_gantt_tool_panel" class="easy-gantt__menu-item">
+          <% menu_items_for(:easy_gantt_tools, @project) do |node| %>
+            <li>
+              <%
+                opts = node.html_options.dup
+                opts[:url] = (node.url.is_a?(Proc) ? node.url.call(@project) : node.url)
+                opts[:id] = "button_#{node.name}"
+                opts[:no_button] = 'true'
+
+                caption = opts[:caption].is_a?(Proc) ? opts[:caption].call(params[:gantt_type]) : node.caption
+              %>
+              <% if opts.delete(:trial) %>
+                <%= easy_gantt_help_button(node.name, caption, opts) %>
+              <% else %>
+                <%= easy_gantt_js_button(caption, opts) %>
+              <% end %>
+            </li>
+          <% end %>
+        </ul>
+      </div>
+      <%= easy_gantt_js_button(l(:button_save), {
+          url: 'javascript:void(0)',
+          id: 'button_save',
+          no_button: true,
+          class: 'button-positive button-1 icon icon-save'
+      }) %>
+    <% end %>
+  </div>
+  <%= call_hook(:view_easy_gantts_issues_toolbars, project: @project) %>
+</div>
+
+<!-- This is container for gantt -->
+<div id="gantt_cont" style="width: 100%;" class="clear"></div>
+<!-- End container for gantt -->
diff --git a/plugins/easy_gantt/app/views/easy_gantt/_test_includes.html.erb b/plugins/easy_gantt/app/views/easy_gantt/_test_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..cac236d9a5b30bccb1179245a3d79f1d204b411e
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/_test_includes.html.erb
@@ -0,0 +1,33 @@
+<%=
+  easy_gantt_include_js(
+      # test framework
+      'jasmine/helpers/test',
+      'jasmine/jasmine_lib/jasmine',
+      'jasmine/jasmine_lib/jasmine-html',
+      'jasmine/jasmine_lib/boot',
+
+      # common tests
+      'jasmine/main',
+      'jasmine/loader',
+      'jasmine/working_helper',
+      'jasmine/working_helper_add',
+      'jasmine/pos_date',
+      'jasmine/history',
+      'jasmine/model_relations',
+      'jasmine/load_reorder'
+  )
+%>
+<% extra_test_names = params[:run_jasmine_tests]
+   if extra_test_names != 'true'
+     if extra_test_names.is_a?(String)
+       extra_tests = prepare_test_includes([extra_test_names])
+     elsif extra_test_names.is_a?(Array)
+       extra_tests = prepare_test_includes(extra_test_names)
+     else
+       extra_tests = []
+     end
+     extra_tests.each do |test, plugin| %>
+    <%= easy_gantt_include_js("jasmine/#{test}", from_plugin: plugin) %>
+  <% end %>
+<% end %>
+<%= easy_gantt_include_css('jasmine', media: 'all') %>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb b/plugins/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7750fd378e693195e7dc026bb41310f97f66ea97
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/easy_queries/_show.html.erb
@@ -0,0 +1,66 @@
+<%
+  unless defined?(form_options)
+    form_options = {}
+  end
+
+  query ||= @query
+  form_path ||= easy_gantt_path(@project)
+  form_options[:additional_elements_to_serialize] ||= 'null'
+%>
+
+<div class="content-title"><%= title(easy_query_name) %></div>
+
+<%= form_tag(form_path, method: :get, id: 'query_form') do %>
+  <div id="query_form_with_buttons" class="hide-when-print">
+    <%= hidden_field_tag 'set_filter', '1' %>
+
+    <div id="query_form_content">
+      <fieldset id="filters" class="collapsible collapsed">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+        <div style="display: none;">
+          <%= render 'queries/filters', query: query %>
+        </div>
+      </fieldset>
+
+      <fieldset class="collapsible collapsed">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
+        <div style="display: none;">
+          <table>
+            <tr>
+              <td><%= l(:field_column_names) %></td>
+              <td><%= render_query_columns_selection(query) %></td>
+            </tr>
+          </table>
+        </div>
+      </fieldset>
+    </div>
+
+    <p class="buttons">
+      <%= link_to_function l(:button_apply), 'applyEasyGanttQuery()', class: 'icon icon-checked' %>
+      <%= link_to l(:button_clear), { set_filter: 1, project_id: @project }, class: 'icon icon-reload'  %>
+    </p>
+  </div>
+<% end %>
+
+<script>
+  var additionalElementsToSerialize;
+
+  $(document).ready(function(){
+    additionalElementsToSerialize = <%=raw form_options[:additional_elements_to_serialize] %>;
+  });
+
+  function applyEasyGanttQuery(){
+    if (additionalElementsToSerialize) {
+      var data = additionalElementsToSerialize.serializeArray()[0];
+      if (data) {
+        var newInput = $("<input />").attr("type", "hidden")
+                                     .attr("name", data.name)
+                                     .attr("value", data.value);
+
+        newInput.appendTo("#query_form");
+      }
+    }
+
+    $("#query_form").submit();
+  }
+</script>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/index.html.erb b/plugins/easy_gantt/app/views/easy_gantt/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..03e9613ee5fb4648de8bc8884f2bdc931041c626
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/index.html.erb
@@ -0,0 +1,236 @@
+<%
+  plugin = Redmine::Plugin.find('easy_gantt')
+  easy_extensions = EasyGantt.easy_extensions?
+  query ||= @query
+
+  main_gantt_params = query.to_params.merge(key: User.current.api_key, format: 'json')
+  main_gantt_path = if @project
+      issues_easy_gantt_path(@project, main_gantt_params)
+    else
+      projects_easy_gantt_path(main_gantt_params)
+    end
+
+  unless defined?(show_query)
+    show_query = true
+  end
+
+  if EasyGantt.easy_gantt_pro?
+    sample_path = ''
+  # elsif easy_extensions
+  #   sample_path = "#{home_path}assets/easy_gantt/sample_{{version}}.json"
+  else
+    sample_path = "#{home_path}plugin_assets/easy_gantt/data/sample_{{version}}.json"
+  end
+
+  back_url = URI.unescape(request.fullpath)
+%>
+
+<div id="easy_gantt" class="<%= easy_extensions ? 'easy' : 'redmine' %> gantt clear">
+  <% if show_query %>
+    <% if User.current.admin? && @project.nil? %>
+      <div class="contextual settings">
+        <% # Easy is called URI.escape before redirect to back
+           # So a%7Cb" is transformed into "a%257Cb" (original "a|b") %>
+        <%= link_to '', plugin_settings_path(plugin, back_url: back_url), class: 'icon icon-settings', title: l(:label_easy_gantt_settings)%>
+      </div>
+    <% end %>
+
+    <%= render query, easy_query_name: l(:heading_easy_gantts_issues),
+                      wrapper_class: '',
+                      form_options: { additional_elements_to_serialize: '$("input#easy_gantt_type")' },
+                      options: { show_free_search: false,
+                                 show_custom_formatting: false,
+                                 additional_tagged_url_options: { gantt_type: params[:gantt_type] } } %>
+  <% end %>
+
+  <%= hidden_field_tag 'gantt_type', '', id: 'easy_gantt_type' %>
+
+  <%= render 'easy_gantt/menu_graph' %>
+
+  <div id="easy_gantt_footer" class="gantt-footer">
+    <div id="easy_gantt_footer_legend" class="gantt-footer-legend"></div>
+    <div id="gantt_footer_buttons" class="gantt-footer-menu">
+      <% # Print doesn't work on RM but be carefull of page modules %>
+      <% if EasyGantt.easy_printable_templates? && !params[:gantt_type].present? %>
+        <%
+          # Prepare options for selection
+          # - easy_gantt category should be first
+          # - select first option on easy_gantt category if this category contains only one option
+          gantt_category = EasyPrintableTemplate.category_caption('easy_gantt')
+          options = { gantt_category => [] }
+          print_template_url = preview_easy_printable_template_path(':id', entity_type: 'Project', entity_id: @project.try(:id), back_url: back_url);
+
+          templates = EasyPrintableTemplate.pluck(:name, :category, :id)
+          templates.each do |name, category, id|
+            key = EasyPrintableTemplate.category_caption(category)
+            options[key] ||= []
+            options[key] << [name, id]
+          end
+
+          if options[gantt_category].size == 1
+            selected_key = options[gantt_category].first
+            prompt = false
+          else
+            prompt = true
+          end
+        %>
+
+        <%= select_tag :print, grouped_options_for_select(options, selected_key, prompt: prompt), id: 'easy-printable-templates', style: 'width: auto' %>
+        <%= link_to_function l(:button_print), 'printableTemplatePrint()', class: 'button button-2 icon icon-print' %>
+
+        <label title="<%= l('easy_gantt.title.print_fit') %>">
+          <%= l('easy_gantt.button.print_fit') %>
+          <input type="checkbox" id="easy_gantt_print_fit_checkbox" checked>
+        </label>
+      <% else %>
+        <%= easy_gantt_js_button(l(:button_print), icon: 'icon-print', id: 'button_print') %>
+      <% end %>
+
+      <% unless EasyGantt.easy_gantt_pro? %>
+        <%= easy_gantt_js_button(:load_sample_data, id: 'button_sample') %>
+      <% end %>
+    </div>
+
+    <% unless EasyGantt.easy_extensions? %>
+      <p><%= link_to l(:text_easy_gantt_footer), l(:link_easy_gantt_plugin), target: '_blank' %></p>
+    <% end %>
+  </div>
+</div>
+
+<%= render 'easy_gantt/includes' %>
+<%= render 'easy_gantt/test_includes' if params[:run_jasmine_tests] || params[:spec] %>
+<%= render 'easy_gantt/js_prepare' %>
+
+<%= content_for :header_tags do %>
+  <script type="text/javascript">
+    window.ysy = window.ysy || {};
+    ysy.settings = ysy.settings || {};
+    ysy.view = ysy.view || {};
+
+    $.extend(true, ysy.settings, <%= {
+      hoursPerDay: 8,
+      parentIssueDates: (Setting.parent_issue_dates == 'derived'),
+      schemeBy: (@project ? EasySetting.value('issue_color_scheme_for') : 'project_priority'),
+      paths: {
+        mainGantt: main_gantt_path.html_safe,
+        issuePOST: issues_path(format: 'json', key: User.current.api_key),
+        issuePUT: issue_path(':issueID', format: 'json', key: User.current.api_key),
+        issueDELETE: issue_path(':issueID', format: 'json', key: User.current.api_key),
+        relationPOST: issue_relations_path(':issueID', format: 'json', key: User.current.api_key),
+        relationPUT: relation_easy_gantt_path(':projectID', ':relaID', format: 'json', key: User.current.api_key),
+        relationDELETE: relation_path(':relaID', format: 'json', key: User.current.api_key),
+        versionPOST: project_versions_path(':projectID', format: 'json', key: User.current.api_key),
+        versionPUT: version_path(':versionID', format: 'json', key: User.current.api_key),
+        sample_data: sample_path
+      },
+      labels: {
+        links: {
+          start_to_start: l(:label_start_to_start),
+          start_to_finish: l(:label_start_to_finish),
+          finish_to_finish: l(:label_finish_to_finish),
+          precedes: l(:label_precedes),
+          relates: l(:label_relates_to),
+          copied_to: l(:label_copied_to),
+          blocks: l(:label_blocks),
+          duplicates: l(:label_duplicates)
+        },
+        errors2:{
+          loop_link: l('easy_gantt.errors.loop_link'),
+          link_target_new: l('easy_gantt.errors.link_target_new'),
+          link_target_readonly:  l('easy_gantt.errors.link_target_readonly'),
+          unsupported_link_type: l('easy_gantt.errors.unsupported_link_type'),
+          duplicate_link: l('easy_gantt.errors.duplicate_link')
+        }
+      }
+    }.to_json.html_safe %>);
+
+    ysy.view.templates = $.extend(ysy.view.templates, <%= {
+      LinkConfigPopup: %{
+        <h3 class='title'>#{l(:heading_delay_popup)}</h3>
+        <label for='link_delay_input'>#{l(:field_delay)}:</label>
+        <span style="width:100px;display:inline-block;"></span>
+        <input id='link_delay_input' type='number' min='{{minimal}}' value='{{delay}}' size='3'>
+        <!--<a id='link_fix_actual' class='button icon icon-link' href='javascript:void(0)'>#{l(:button_use_actual_delay)}</a>-->
+        <!--<a id='link_remove_delay' class='button icon icon-link' href='javascript:void(0)' >#{l('easy_gantt.button.remove_delay')}</a>-->
+        <div id='link_popup_button_cont' >
+          <hr>
+          <a id='link_delete' class='icon icon-unlink button'  href='javascript:void(0)'>#{l(:button_delete)}</a>
+          <a id='link_close' class='icon icon-save button-positive'  href='javascript:void(0)' style='float:right'>#{l(:button_submit)}</a>
+        </div>
+      },
+      SuperPanel: %{
+        {{#sample}}
+        <div id="sample_cont" class="flash notice gantt-sample-flash">
+        <h2 id="sample_label">#{l('easy_gantt.sample.header')}</h2>
+        <p>{{{text}}}</p>
+        <p class="" style="text-align:center">
+          <a id="sample_video_button" class="icon icon-youtube" href="javascript:void(0)">#{l('easy_gantt.sample.video_label')}</a>
+          {{^global_free}}
+          <a id="sample_close_button" class="gantt-sample-close-button button button-important" href="javascript:void(0)" title="#{l('easy_gantt.sample.close_label')}">#{l('easy_gantt.sample.close_label')}</a>
+          {{/global_free}}
+          {{#global_free}}
+          <a id="sample_upgrade_button" class="button button-positive" href="#{l(:link_easy_gantt_plugin)}" target="_blank" title="#{l('easy_gantt.label_pro_upgrade')}">#{l('easy_gantt.label_pro_upgrade')}</a>
+          {{/global_free}}
+        </p>
+        <div class="clear"></div>
+        </div>
+        {{/sample}}
+      },
+      easy_unimplemented: %{
+        <h3 class="title">{{modal.title}}</h3><span>{{{modal.text}}}</span>
+      },
+      video_modal: %{
+        <h3 class="title">#{l('easy_gantt.sample.video.title')}</h3>
+        <iframe class="gantt-modal-video" width="800" height="450" src="//www.youtube.com/embed/#{l('easy_gantt.sample.video.video_id')}?autoplay=1">
+        </iframe>
+        <p>#{l('easy_gantt.sample.video.text')}</p>
+      },
+      video_modal_global: %{
+        <h3 class="title">#{l('easy_gantt.sample_global_free.video.title')}</h3>
+        <iframe class="gantt-modal-video" width="800" height="450" src="//www.youtube.com/embed/#{l('easy_gantt.sample_global_free.video.video_id')}?autoplay=1">
+        </iframe>
+        <p>#{l('easy_gantt.sample_global_free.video.text')}</p>
+      },
+      legend: %{
+        {{text}}
+      },
+      linkDragModal: %{
+        {{#errorReason}}
+          <b>{{errorReason}}</b>
+        {{/errorReason}}
+        {{^errorReason}}
+          #{l(:label_relation_new)}{{#type}} <b>{{type}}</b>{{/type}}<br>
+          <b>{{from}}</b> #{l('easy_gantt.link_dir.link_end')}<br>
+          {{#to}}<b>{{to}}</b> #{l('easy_gantt.link_dir.link_start')}{{/to}}
+        {{/errorReason}}
+      },
+      printIncludes: %{
+        #{easy_gantt_include_css('dhtmlxgantt', media: 'all')}
+        #{easy_gantt_include_css('easy_gantt', media: 'all')}
+      }
+    }.to_json.html_safe %>);
+
+    function printableTemplatePrint(){
+      var $select = $("#easy-printable-templates");
+      var value = $select.val();
+
+      if (value === "") {
+        // $select.css({border: "1px solid red"})
+        return false
+      }
+      var url = "<%= print_template_url %>";
+      url = url.replace(":id", value);
+
+      easyModel.print.preview({href: url})
+    }
+  </script>
+<% end %>
+
+<%= call_hook(:view_easy_gantt_index_bottom, project: @project, query: query) %>
+
+<% content_for :sidebar do %>
+  <%= call_hook(:view_easy_gantt_index_sidebar, project: @project, query: query, gantt_type: params[:gantt_type]) %>
+
+  <% # DEPRECATED %>
+  <%= call_hook(:view_easy_gantts_issues_sidebar, project: @project, query: query) %>
+<% end %>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/issues.api.rsb b/plugins/easy_gantt/app/views/easy_gantt/issues.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..7a0454dfcc204c679db2aa1aff9d7bfd2c13be4b
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/issues.api.rsb
@@ -0,0 +1,26 @@
+api.easy_gantt_data do
+  api.start_date @start_date
+  api.end_date @end_date
+
+  api_render_columns(api, @query)
+  api_render_projects(api, @projects)
+  api_render_issues(api, @issues, with_columns: true)
+
+  api_render_relations(api, @relations)
+
+  api_render_versions(api, @versions)
+
+  if EasySetting.value(:easy_gantt_show_holidays) && params[:subproject_loading].blank?
+    api_render_holidays(api, @start_date - 1.month, @end_date + 1.month)
+  end
+
+  # Load only on first request
+  if params[:subproject_loading].nil?
+    api.schemes do
+      api_render_scheme(api, IssuePriority)
+      api_render_scheme(api, IssueStatus)
+      api_render_scheme(api, Tracker)
+    end
+  end
+
+end
diff --git a/plugins/easy_gantt/app/views/easy_gantt/printable_templates/_token_list.html.erb b/plugins/easy_gantt/app/views/easy_gantt/printable_templates/_token_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0b0bf004ecf7d15f719fc2b04aebf5fbdae6bcb0
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/printable_templates/_token_list.html.erb
@@ -0,0 +1,11 @@
+<tr>
+  <td colspan="2"><%= l(:label_easy_gantt) %>:</td>
+</tr>
+<tr>
+  <td>
+    <%= easy_printable_template_link_to_add_token('%easy_gantt_current%', f, l(:label_easy_gantt)) %>
+  </td>
+  <td>
+    <%= l(:text_easy_gantt_print_easy_gantt_current) %>
+  </td>
+</tr>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/printable_templates/default.html b/plugins/easy_gantt/app/views/easy_gantt/printable_templates/default.html
new file mode 100644
index 0000000000000000000000000000000000000000..54c1d1172cac79c786ceb351e5162da85a06fff1
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/printable_templates/default.html
@@ -0,0 +1,33 @@
+<div style="background: #009ee0; width: 100%; height: 5px">&nbsp;</div>
+
+<p>&nbsp;</p>
+
+<table border="0" cellpadding="1" cellspacing="1" style="width:500px">
+  <tbody>
+    <tr>
+      <td style="width: 100px"><img alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4QINEAYbWX8kIAAADbFJREFUaN7tW3l0W9WZ/9339LRatmzL+yLJVuIkwg2JszlxFoJDIQEOZJi2UELpciiUgTIlbTkwk87QaaeFDrMwwDktdEiTNocZpimUQgohp4lJnMSO0ySOY8dWLMXyEnmVtdiS3nt3/ugAetZ98iraOdP7n+697737u/dbft/3XQH/zxpJ14tramqEwODgYpmQKo6QKgpYAFgIRQYFDRNKwpTQIKFcp8Sjg+f59q6urvH/U4ArSyud4KSdALZSgjoCmGbxuATQsyDkCEfIbzq7uxsA0D85wE6nU4d4/F4K7osAXb9Qm0iBbo5gnwi85PF4Bv7ogO12u54DHiQUuwGUpFEKJwD6Ci9JP7js8/X+UQBX2mxbCcgLFFjyCdqbCCF4Vmcyff/ixYuxTwSw0+nMRFx8iQL3zPF7kyBkEjI0IDRjjiu+QGT5c11Xr7alFbDTZltBKXkNBItmMD0mGzPbJxavGQkv2WSK2lx5cWt5KeU1msRJmtGBazrfpX5DV1PA1HpMrxnrqwKllpmcNggedns8r6YFcKXdfjOleH06qytrje3B2p3+kW1frpaNluxZL0YW4+bTb53Nfu9lqhntXQ2AS2nYKP7xylXPU7Ox5tMCrrDb7yYUewEIqh/WmS4O3vXkRLBm+6qFUljtQGd3wb4n+7T9XbWpgFOQV654ux8AIM8bsLPMcTvl5P8GiIb9NBkdrf9K68jND24A4bh0WCpjR2NrwX98Q+Bik1UpQL94xdv98LwAV5RX1BEivwvAwBRfo/l8z+OvWcXsouJUH9AQYJVF01eTIwzka2ncquVohkBjWQKne+z8RLUnLBunXaQkxop//FCjvrN5k/qayR63t/u7cwLscDgKOJn+HkAhazxatqyh99G9tVON0IdtpUXj/7pT676lQNBadcROgFzWvM3HgiPHhqScD3/X5mr6Tg6LxWoKaTm670TuG8+tAqBlqzS99YrX+3YqwCwx5DhZ3q8GNrKs7qjvsf11LLB3FAudPbeYT57ZmpF1X7m2Nk9HatTAstrr60z9gdssl3cv0rayTmJs8671A7t+2AogwlQwYK+zxFk6K8CVNsfXAFLPPFn78ob+rzy/GYQo1lNh5IZa680NB9eZ7KUGfh0A3ZwUVqYwC1j8bLXxur4dWS012Xz/1CnhFTet9H/u6VaAigzMVsqLL88YsMPhKAAoUw/i2YWnfY/8dMPU/r8oEc61f9occWXyG1NZ8tm2Qh1Z2XyDWf+jan3j1LHgmtvWBLZ84YSKkn660ma7c0aAiYzv4Q9h3JSX8L2+bxxwTrXEf7dE3/T6WlOlQEh5muhk9uOL9OtObDGf4InS7Qzd/teb4vn2RhXT9M8ul0ubErDD4bAR0PtYkwbu/6FfNllyEvv2LNEf/84yfTWADKS3kdocfv25+oyTU0H7vv4zFzjOz3jGFg2FdqUETGR8kyWS8YKKE+HqG1ck9v1lqdD298v0KwDop1ttVMYViVLffFG7zJr1hzaYTilU3mDOHN7+V50q4eUTAHgm4OLiYqPK6cb7vvwvtsSOIh0Z+flqkx5AKv8peSK05Z6miNv4xliFf1KeUTxLpyFC9fnC2j1L9c0Ky731i+tlnfESQzCcTru9ngnYqNHtBGBOigBKqk6J1jJFrPv2BlO7QFChGg5J1HPv6UiP41Bg5YGeWKU8i5zFjsZI8bWo3JrKyO5Zoi+tMHLBRGQjOx4JsHeQ7GICpoTezZo/uPPb1sTf2wqE9ustmhq11fRE5FbHu8HCn/ti9rmI7bkxsbD8nXHXyWHxrNocnqBw72rjhcS+wIbPrKEc158sMfTO0tJSBVPkXC6XlgKbkyYL+q5JxwpFgP/icuOYmo/ti+L80sPBpQMTsn4+uhqTQTYcC61oHlUHXZerqV6UwX98qoTjJqrWXWZRcS2nVbhSbiI4sYYV9kVcdQpD4zDx15wZ5Dr2ImnfqiPjFWGR8gthlmUKbGkIf2o0JqsF+ea/qdK1J3aM3nC/VeVtWxWACeT1rGnBVbcr4tlvLdZdUXNBz3XFxvon5AV1T2GR8p9viugBiKzx24sFhSRNVqxcAkICDL65XgmYgBF2UTGyeK1CnG8pEGQVt+N7+tLE0nQ44HeuiRUDUco8ZYtAFhfquY85NcfzotnKclEKHBwrEUd5rY9qtLrEkKrMwGZT7/njvgkpfQn9Az1xSWXIUJ/PX1GoVr6DFVQU2O12S6KVTkqxyubcQYXvNXCDHAEz7v11XzwznTTrrf54mdrY9Vn82JSwlR2Ty3JJgg4n+1/JkKFIgy7P0gRYrAUA2oJyXjoBHx+OWyk7HESRgVeomWSxsoMXjjMninSSsZF0JoUY5Qg0qrag0ThN6wlHZUCSMcrUY43SoMmGbK1KKiozVQKAMZ9Iqsk2wraiU3Z4fjpOVKkonTKPyesk6eN+jgChJDYTDSvENxiXVQP6AgMXSOcJazmABztpHxaVW8FFRuNMdsbRYKJIJ5Uo+YlxhWicDdBMAMxTXpHJDM9mnQ5Wazfma/oIIVmsse6IpFgnPzYYZ3MPOZgg0iSpQMUFRxWG6GpEKqRAP5MAFAmTM4iD5gz4rmKtagGtdVxW2A99zyXmPJHjehNEmnYkp0VjpUSMJgIhfZPUy3rZmlxNhVVPUoLmydxT1neVCGpGMfRWX0wRtQn+blbIOuDxeMYSRJq0MyRQY+w4pdiI966JUZUMQv6zLkNbOvT3AbvQmSmQxayx4RjtHBM/9jBEEkVNcIg1t0O5Xioxk2HmpjcVruCZyxOLAIRZc79g09quy+KvqUv07EXarCHxf6o2EjX9/1W/qJAqQ3dLByjLRZLjCsCWvLzTAE2y1MZLHygYzqWgXOadoM0qFin36MaMoSwtW7TpHHT4yCbThQyBOFXc0ch3LkYU/D3r/VeH2HstHVEAPnPmTBzgjiaBiEcr9e4WhRV4qCWcA4AJKkdLXB3bMjsK9CTC8OMzBsoT4P2NGU2rLJqVanPeHYi39U4mlFVlSTJ0nq5ibEw4JssnkjMekH/BenHeL38wPCV6qT4XkI6r+mQdWe65OXPgjhJt95QvzwjxIjM3dvmmzJateZrVqskywHtvc+R6xek2vNZEZIlRKaEHfT7fRBLgmCQdBJBEILT9nbVav0eRCLitMXy9ROFRW5CeIxUH1xrL2m/KbNleJPQCgIakrkSUGkjotTWmkx3bMmMVJm5lqoTIg2ciw0OxBCJCKc19+/kcNsHj9qkSgkqb7d8A8khS2jK/vPHqE2/UJvbdVChc+O16UzGmrxvRkIjLRh6FHEESefjttXjTUjPPlRu5RQCm5eT/1Rv/3WdOhbck9mUffvmDnLdfqGMoeqf7qmfpVML0EYXMsVpbIdOHp0ZFfDhQFsuvaIkXVRZ92OcOyQXDMXphe6GQDXYl76MN1XKwEsLOXzsz+JIsgRRj+loUPT4s/u6W4+EtiYfEhQNjxT9+JAegxmSqQ781Ggi0MLOWAOB2u3tA8Crra4X7nyzkQ2MjiX3/7o6uvb85ch7Agt2hUlPb3/TH3687Gto81UWV/Ou97YBsZRir7myrdZ8Kb0g8Tv5vAUYoRqXisuc+64YsKcRj79VY7baG0EiMwp0OpBTwf7dj8sStjeH6qWu1HnzmqHbIt44JSiaP/cH7TAPY7Xb7KcFTTHcx5l9d+vz9SSTl8KC4LO/NMWvjiHRIzWXNBWvfJG1c/v54eM/FyY1TBzNP/upUVsMvNqg8e6irp/tNVbeXFNCPjbVkZ1lqCUFlUqok4LfpPeePhmq22xKda5RC/4on5mwaFTs3WPk2i8DlY26l0/hgjJ7afWHCf8/pyFp/lCbdBDK3vNOcf2BPNUBYej/IS+KO4fHx4KzCtsrKynyI0lmAnceKFVV94Ht8/1rKaZigrsvkrzy9TO+7MV8wZGqwFKkrjJGQSC+dGBZD3+uI5h8bElUzoP9rkdeqbKYMgh1uj+fQnOJUp81WS0EOQ6VoJulMF327D1jE3LKU9yxzBARvLtT2lBq54XwB8SyBcBEZoj9Ktd6InH34mlg8EJVTujciRieLXvzqaYPn3CZ1IPSpLq/3+/MKzCvKK24lRDqY4tpSYHTLfedGbn20Lm3Xltoazhfu3W0k8Zgzhco/7/Z6H51HtihBvO32z4LiZ6n8rawztg/f8e3x8TW3rZ4VcU7RdL3t7oJ9T1wT/N7a1OukP3F7vQ9iIS6mfdgc5Y5tHJF/CZCU5RQq6C+Pr7uzf7T+Sy7JbLXOOlcnxqLmU2+ctRz5KSeMDqyaPslI/8Ht9e7BQl49/Ai0w/EpTqb/CaBqBl5FlA2Z7ROVNUORZRsNk7bqvHierSSxmgEqy5rR/gFdT9uAsbMpaGo7auTHBpcA1DyD94cI5R7qutq9f4ESoOzmynNlTBpDLwJk1xwlNQZCIpRSYZZ/D0gkI7+XCe72eDztC5jxnUavbbYbAPICgLQU0dTc1yd+QVzhtpxOHY1JDwD0myAoSydQEPxEI0nPdPT09M33ZfO2qC6XSzsZDt8D4EugqMPC/VPGTSn2EYF/ye12+xdq9xa0zFllszlEYCdAtgJ003RWfSqXoUAzKI4QQt9ye72N+FP8G49a2wJoem22RTKwhFKymOOQTYFsQpFBCUKUkhBHaEim9DJkvkMwCh0dHR1B/LktbPsfTqw3z5AwTsEAAAAASUVORK5CYII=" style="border:0px solid black; height:60px; margin-bottom:0px; margin-left:0px; margin-right:0px; margin-top:0px; width:60px" /></td>
+      <td>
+      <p>&nbsp;<span style="font-size:28px"><strong>EasyGantt</strong></span></p>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<h1>&nbsp;</h1>
+
+<h1>%project_name%</h1>
+
+<table border="0" cellpadding="2" cellspacing="2" style="width:500px">
+  <tbody>
+    <tr>
+      <td style="width: 150px"><strong>Start date:</strong></td>
+      <td>%project_start_date%</td>
+    </tr>
+    <tr>
+      <td style="width: 150px"><strong>Due date:</strong></td>
+      <td>%project_due_date%</td>
+    </tr>
+  </tbody>
+</table>
+
+<p>%easy_gantt_current%</p>
diff --git a/plugins/easy_gantt/app/views/easy_gantt/project_issues.api.rsb b/plugins/easy_gantt/app/views/easy_gantt/project_issues.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..df7ebb4408e3bf0da625dc06b5ee48747e10de6a
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/project_issues.api.rsb
@@ -0,0 +1,5 @@
+api.easy_gantt_data do
+  api_render_issues(api, @issues)
+  api_render_relations(api, @relations)
+  api_render_versions(api, @versions)
+end
diff --git a/plugins/easy_gantt/app/views/easy_gantt/projects.api.rsb b/plugins/easy_gantt/app/views/easy_gantt/projects.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..edbf2ebc37184e4cf337636d72e21e47624e1de4
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt/projects.api.rsb
@@ -0,0 +1,18 @@
+api.easy_gantt_data do
+  api.start_date @start_date
+  api.end_date @end_date
+
+  api_render_columns(api, @query)
+  api_render_projects(api, @projects, with_columns: true)
+
+  if EasySetting.value(:easy_gantt_show_holidays)
+    api_render_holidays(api, @start_date - 1.month, @end_date + 1.month)
+  end
+
+  # Load only on first request
+  if params[:subproject_loading].nil?
+    api.schemes do
+      api_render_scheme(api, :EasyProjectPriority)
+    end
+  end
+end
diff --git a/plugins/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb b/plugins/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7876c5a46327c1371b6b1c5ab6864a7d2ce9dbcf
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_gantt_easy_issue_queries/_filters_custom_formatting.html.erb
@@ -0,0 +1,5 @@
+<% # Just for hiding %>
+
+<script>
+  $("#custom_formatting").remove();
+</script>
diff --git a/plugins/easy_gantt/app/views/easy_settings/_easy_gantt.html.erb b/plugins/easy_gantt/app/views/easy_settings/_easy_gantt.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..72be94005c6e8575490db438e72922c3f8d21204
--- /dev/null
+++ b/plugins/easy_gantt/app/views/easy_settings/_easy_gantt.html.erb
@@ -0,0 +1,42 @@
+<%
+  default_zoom_options = [
+    [l('easy_gantt.button.day_zoom'), 'day'],
+    [l('easy_gantt.button.week_zoom'), 'week'],
+    [l('easy_gantt.button.month_zoom'), 'month']
+  ]
+%>
+
+<%= title l(:title_easy_gantt_settings) %>
+
+<div class="box tabular">
+  <p>
+    <%= form.label :show_holidays, l(:field_easy_gantt_show_holidays) %>
+    <%= form.check_box :show_holidays %>
+    <em class="small"><%= l(:text_easy_gantt_show_holidays) %></em>
+  </p>
+
+  <p>
+    <%= form.label :relation_delay_in_workdays, l(:field_easy_gantt_relation_delay_in_workdays) %>
+    <%= form.check_box :relation_delay_in_workdays %>
+    <em class="small"><%= l(:text_easy_gantt_relation_delay_in_workdays) %></em>
+  </p>
+
+  <p>
+    <%= form.label :show_project_progress, l(:field_easy_gantt_show_project_progress) %>
+    <%= form.check_box :show_project_progress %>
+    <em class="small"><%= l(:text_easy_gantt_show_project_progress) %></em>
+  </p>
+
+  <p>
+    <%= form.label :show_task_soonest_start, l(:field_easy_gantt_show_task_soonest_start) %>
+    <%= form.check_box :show_task_soonest_start %>
+    <em class="small"><%= l(:text_easy_gantt_show_task_soonest_start) %></em>
+  </p>
+
+  <p>
+    <%= form.label :default_zoom, l(:field_easy_gantt_default_zoom) %>
+    <%= form.select :default_zoom, options_for_select(default_zoom_options, form.object.default_zoom) %>
+  </p>
+
+  <%= call_hook :view_easy_gantt_settings, form: form %>
+</div>
diff --git a/plugins/easy_gantt/assets/data/gantt.json b/plugins/easy_gantt/assets/data/gantt.json
new file mode 100644
index 0000000000000000000000000000000000000000..25966488a82fb7bc8eeb9c81c86302bb115bf23d
--- /dev/null
+++ b/plugins/easy_gantt/assets/data/gantt.json
@@ -0,0 +1,84 @@
+{
+  "start_date": "yyyy-mm-dd",
+  "due_date":"yyyy-mm-dd",
+  "columns": [
+    {
+      "name": "name",
+      "title": "Jméno",
+      "mapped":true,
+      "type":"text"
+    },
+    {
+      "name":"start_date",
+      "title":"Počátek",
+      "mapped":true
+    },
+    {
+      "name":"end_date",
+      "title":"Konec",
+      "mapped":true
+    },
+    {
+      "name": "assignee",
+      "title": "Přirazeno",
+      "mapped":true,
+      "type":"select",
+      "completes":"/easy_auto_completer/assignable_users"
+    },
+    {
+      "name": "address",
+      "title": "Adresa",
+      "type":"text"
+    },
+    {
+      "name": "priority",
+      "title": "Priorita",
+      "type":"select",
+      "completes":"/easy_auto_completer/issue_priorities"
+    },
+    {
+      "name":"estimated",
+      "title":"Odhad",
+      "type":"text"
+    }
+  ],
+  "issues": [
+    {
+      "id": 0,
+      "name": "název úkolu",
+      "start_date": "yyyy-mm-dd",
+      "end_date": "yyyy-mm-dd",
+      "estimated": 25,
+      "progress": 0.10,
+      "css": "issue-open project-35 tracker-5 status-1",
+      "assignee": {"id": 65, "name": "lukas"},
+      "version":65,
+
+      "columns": {
+        "address":"Levá 5, Horní Dolní",
+        "priority":"Vysoká",
+        "estimated":256
+      }
+    }
+  ],
+  "projects":[],
+  "versions":[
+    {
+      "id": 65,
+      "name": "název milníku",
+      "start_date": "yyyy-mm-dd",
+      "css": "issue-open project-35 tracker-5 status-1",
+      "milestone":true
+    }
+      
+  ],
+  "relations": [
+    {
+      "id": 5,
+      "source": 6,
+      "target": 7,
+      "type": "precedes",
+      "delay": 0
+    }
+  ]
+}
diff --git a/plugins/easy_gantt/assets/data/sample_1.json b/plugins/easy_gantt/assets/data/sample_1.json
new file mode 100644
index 0000000000000000000000000000000000000000..6f78b52ad37e56ebb1882f313beeb09ca11eefc6
--- /dev/null
+++ b/plugins/easy_gantt/assets/data/sample_1.json
@@ -0,0 +1,681 @@
+{
+  "easy_gantt_data": {
+    "start_date": "2016-03-28",
+    "end_date": "2016-06-08",
+    "columns": [
+      {
+        "name": "subject",
+        "title": "Subject"
+      },
+      {
+        "name": "assigned_to",
+        "title": "Assignee"
+      }
+    ],
+    "projects": [],
+    "issues": [
+      {
+        "id": 371,
+        "name": "Features Specification",
+        "start_date": "2016-03-29",
+        "due_date": "2016-03-29",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Features Specification"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 372,
+        "name": "Creative brief",
+        "start_date": "2016-04-04",
+        "due_date": "2016-04-08",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Creative brief"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 373,
+        "name": "Marketing brief",
+        "start_date": "2016-04-04",
+        "due_date": "2016-04-04",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Marketing brief"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 374,
+        "name": "Wireframes",
+        "start_date": "2016-04-11",
+        "due_date": "2016-04-11",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Wireframes"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 375,
+        "name": "Graphics",
+        "start_date": "2016-04-15",
+        "due_date": "2016-04-18",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Graphics"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 376,
+        "name": "XHTML + CSS coding",
+        "start_date": "2016-04-25",
+        "due_date": "2016-05-02",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "XHTML + CSS coding"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 378,
+        "name": "VirtueMart implementation",
+        "start_date": "2016-05-03",
+        "due_date": "2016-05-03",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "VirtueMart implementation"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 383,
+        "name": "On-line payments",
+        "start_date": "2016-05-09",
+        "due_date": "2016-05-10",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "On-line payments"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 379,
+        "name": "Web structure",
+        "start_date": "2016-05-11",
+        "due_date": "2016-05-11",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 55,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Web structure"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 377,
+        "name": "CMS - implementation + setting",
+        "start_date": "2016-05-12",
+        "due_date": "2016-05-12",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "CMS - implementation + setting"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 380,
+        "name": "Products",
+        "start_date": "2016-05-16",
+        "due_date": "2016-05-17",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 55,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Products"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 381,
+        "name": "Specific features",
+        "start_date": "2016-05-18",
+        "due_date": "2016-05-18",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 54,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Specific features"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 382,
+        "name": "Easy Redmine Integration",
+        "start_date": "2016-05-23",
+        "due_date": "2016-05-23",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 54,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Easy Redmine Integration"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 384,
+        "name": "Google Analytics",
+        "start_date": "2016-05-25",
+        "due_date": "2016-05-30",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Google Analytics"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 385,
+        "name": "Testing  ",
+        "start_date": "2016-05-30",
+        "due_date": "2016-05-31",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Testing  "
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 386,
+        "name": "Debug",
+        "start_date": "2016-06-02",
+        "due_date": "2016-06-07",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 21,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Debug"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Ezr"
+          }
+        ]
+      },
+      {
+        "id": 387,
+        "name": "Training",
+        "start_date": "2016-06-06",
+        "due_date": "2016-06-07",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Training"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      }
+    ],
+    "relations": [
+      {
+        "id": 27,
+        "source_id": 374,
+        "target_id": 375,
+        "type": "precedes",
+        "delay": 3
+      },
+      {
+        "id": 28,
+        "source_id": 375,
+        "target_id": 376,
+        "type": "precedes",
+        "delay": 4
+      },
+      {
+        "id": 29,
+        "source_id": 376,
+        "target_id": 377,
+        "type": "precedes",
+        "delay": 7
+      },
+      {
+        "id": 30,
+        "source_id": 385,
+        "target_id": 386,
+        "type": "precedes",
+        "delay": 1
+      }
+    ],
+    "versions": [
+      {
+        "id": 58,
+        "name": "Analysis",
+        "start_date": "2016-04-04",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 57,
+        "name": "Design",
+        "start_date": "2016-05-02",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 56,
+        "name": "CMS + E-commerce platform",
+        "start_date": "2016-05-13",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 55,
+        "name": "Content + Products",
+        "start_date": "2016-05-17",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 54,
+        "name": "Easy Redmine integration",
+        "start_date": "2016-05-23",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 53,
+        "name": "Public Launch",
+        "start_date": "2016-06-10",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      }
+    ],
+    "schemes": {
+      "IssuePriority": [
+        {
+          "id": 8,
+          "scheme": "scheme-5"
+        },
+        {
+          "id": 11,
+          "scheme": "scheme-2"
+        },
+        {
+          "id": 10,
+          "scheme": "scheme-0"
+        },
+        {
+          "id": 15,
+          "scheme": "scheme-1"
+        }
+      ],
+      "IssueStatus": [
+        {
+          "id": 2,
+          "scheme": "scheme-4"
+        },
+        {
+          "id": 3,
+          "scheme": "scheme-5"
+        },
+        {
+          "id": 4,
+          "scheme": "scheme-1"
+        },
+        {
+          "id": 5,
+          "scheme": "scheme-7"
+        },
+        {
+          "id": 6,
+          "scheme": "scheme-6"
+        },
+        {
+          "id": 7,
+          "scheme": "scheme-7"
+        },
+        {
+          "id": 8,
+          "scheme": "scheme-7"
+        },
+        {
+          "id": 9,
+          "scheme": "scheme-3"
+        },
+        {
+          "id": 10,
+          "scheme": "scheme-2"
+        }
+      ],
+      "Tracker": [
+        {
+          "id": 1,
+          "scheme": "scheme-6"
+        },
+        {
+          "id": 3,
+          "scheme": "scheme-4"
+        },
+        {
+          "id": 4,
+          "scheme": "scheme-3"
+        },
+        {
+          "id": 5,
+          "scheme": "scheme-2"
+        },
+        {
+          "id": 6,
+          "scheme": "scheme-5"
+        },
+        {
+          "id": 13,
+          "scheme": "scheme-1"
+        },
+        {
+          "id": 14,
+          "scheme": "scheme-1"
+        },
+        {
+          "id": 15,
+          "scheme": "scheme-6"
+        },
+        {
+          "id": 16,
+          "scheme": "scheme-0"
+        },
+        {
+          "id": 17,
+          "scheme": "scheme-1"
+        },
+        {
+          "id": 18,
+          "scheme": "scheme-7"
+        },
+        {
+          "id": 19,
+          "scheme": "scheme-3"
+        },
+        {
+          "id": 31,
+          "scheme": "scheme-2"
+        },
+        {
+          "id": 33,
+          "scheme": "scheme-7"
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/data/sample_global.json b/plugins/easy_gantt/assets/data/sample_global.json
new file mode 100644
index 0000000000000000000000000000000000000000..248c8b52cc631d0bf5c997d023b832bd47aae6b3
--- /dev/null
+++ b/plugins/easy_gantt/assets/data/sample_global.json
@@ -0,0 +1,9666 @@
+{
+  "easy_gantt_data": {
+    "start_date": "2016-03-28",
+    "end_date": "2016-12-25",
+    "columns": [
+      {
+        "name": "subject",
+        "title": "Subject"
+      },
+      {
+        "name": "status",
+        "title": "Status"
+      },
+      {
+        "name": "priority",
+        "title": "Priority"
+      },
+      {
+        "name": "assigned_to",
+        "title": "Assignee"
+      }
+    ],
+    "projects": [
+      {
+        "id": 3,
+        "name": "3. IT Projects",
+        "start_date": "2016-03-29",
+        "due_date": "2016-11-01",
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 4.335324043353241
+      },
+      {
+        "id": 62,
+        "name": "4. Product Development",
+        "start_date": "2016-06-20",
+        "due_date": "2016-07-04",
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 100.0
+      },
+      {
+        "id": 63,
+        "name": "1. Administrative Projects",
+        "start_date": "2016-06-27",
+        "due_date": "2016-07-13",
+        "status_id": 15,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 100.0
+      },
+      {
+        "id": 77,
+        "name": "2. HR Projects",
+        "start_date": "2016-05-05",
+        "due_date": "2016-09-14",
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 4.366107838695061
+      },
+      {
+        "id": 10,
+        "name": "Client Project",
+        "start_date": "2016-05-30",
+        "due_date": "2016-11-01",
+        "parent_id": 3,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 32.664756446991404
+      },
+      {
+        "id": 28,
+        "name": "Continuous Software Development",
+        "start_date": "2016-06-06",
+        "due_date": "2016-09-21",
+        "parent_id": 3,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 0.0
+      },
+      {
+        "id": 40,
+        "name": "Implementation of IS",
+        "start_date": "2016-06-01",
+        "due_date": "2016-08-27",
+        "parent_id": 3,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 14.07035175879397
+      },
+      {
+        "id": 41,
+        "name": "E-commerce Project Implementation",
+        "start_date": "2016-03-29",
+        "due_date": "2016-06-07",
+        "parent_id": 3,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 0.0
+      },
+      {
+        "id": 88,
+        "name": "Trainings",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-27",
+        "parent_id": 77,
+        "status_id": 15,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 0.0
+      },
+      {
+        "id": 106,
+        "name": "Design to Schedule Model",
+        "start_date": "2016-06-20",
+        "due_date": "2016-07-04",
+        "parent_id": 62,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "done_ratio": 100.0
+      }
+    ],
+    "issues": [
+      {
+        "id": 741,
+        "name": "Managing projects",
+        "start_date": "2016-06-27",
+        "due_date": "2016-09-12",
+        "estimated_hours": 400.0,
+        "done_ratio": 0,
+        "project_id": 3,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 31,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Managing projects"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Project Manager"
+          }
+        ]
+      },
+      {
+        "id": 415,
+        "name": "Subtask for Doc",
+        "start_date": "2016-05-30",
+        "due_date": "2016-05-30",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "fixed_version_id": 10,
+        "overdue": true,
+        "parent_issue_id": 38,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Subtask for Doc"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 416,
+        "name": "subtask",
+        "start_date": "2016-06-14",
+        "due_date": "2016-06-14",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 9,
+        "overdue": true,
+        "parent_issue_id": 36,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "subtask"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 36,
+        "name": "Documentation for Block no 2",
+        "start_date": "2016-06-22",
+        "due_date": "2016-06-22",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 9,
+        "overdue": true,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Documentation for Block no 2"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 38,
+        "name": "Planning phase review",
+        "start_date": "2016-06-29",
+        "due_date": "2016-06-29",
+        "estimated_hours": 0.0,
+        "done_ratio": 100,
+        "fixed_version_id": 10,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Planning phase review"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 488,
+        "name": "Review meeting",
+        "start_date": "2016-07-01",
+        "due_date": "2016-07-02",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 10,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Review meeting"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 33,
+        "name": "Block no 2 constructing",
+        "start_date": "2016-07-15",
+        "due_date": "2016-07-17",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "fixed_version_id": 8,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 8,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Block no 2 constructing"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Dominka Support Operative"
+          }
+        ]
+      },
+      {
+        "id": 37,
+        "name": "Documentation for Block no 1",
+        "start_date": "2016-07-16",
+        "due_date": "2016-07-23",
+        "estimated_hours": 50.0,
+        "done_ratio": 0,
+        "fixed_version_id": 9,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Documentation for Block no 1"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 32,
+        "name": "Block no 3 constructing",
+        "start_date": "2016-07-17",
+        "due_date": "2016-07-25",
+        "estimated_hours": 40.0,
+        "done_ratio": 0,
+        "fixed_version_id": 8,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 8,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Block no 3 constructing"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Dominka Support Operative"
+          }
+        ]
+      },
+      {
+        "id": 34,
+        "name": "Block no 1 constructing",
+        "start_date": "2016-07-25",
+        "due_date": "2016-07-25",
+        "estimated_hours": 24.0,
+        "done_ratio": 0,
+        "fixed_version_id": 8,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Block no 1 constructing"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 35,
+        "name": "Documentation for Block no 3",
+        "start_date": "2016-07-26",
+        "due_date": "2016-07-26",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 9,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Documentation for Block no 3"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 39,
+        "name": "Review meeting",
+        "start_date": "2016-08-24",
+        "due_date": "2016-10-03",
+        "estimated_hours": 90.0,
+        "done_ratio": 70,
+        "fixed_version_id": 10,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 2,
+        "assigned_to_id": 16,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Review meeting"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Française "
+          }
+        ]
+      },
+      {
+        "id": 31,
+        "name": "Testing",
+        "start_date": "2016-08-29",
+        "due_date": "2016-09-12",
+        "estimated_hours": 50.0,
+        "done_ratio": 30,
+        "fixed_version_id": 7,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Testing"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 40,
+        "name": "Evaluation of draft documentation",
+        "start_date": "2016-10-09",
+        "due_date": "2016-10-10",
+        "estimated_hours": 40.0,
+        "done_ratio": 90,
+        "fixed_version_id": 10,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 10,
+        "status_id": 1,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Evaluation of draft documentation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 30,
+        "name": "Installation of new products - prepare \"Instal team\"",
+        "start_date": "2016-10-13",
+        "due_date": "2016-11-01",
+        "done_ratio": 30,
+        "css": " closed",
+        "fixed_version_id": 6,
+        "project_id": 10,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Installation of new products - prepare \"Instal team\""
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 330,
+        "name": "Data templates do not import estimated time",
+        "start_date": "2016-06-06",
+        "due_date": "2016-07-25",
+        "estimated_hours": 200.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 20,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Data templates do not import estimated time"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel  Rosicky"
+          }
+        ]
+      },
+      {
+        "id": 159,
+        "name": "Workshop with client",
+        "start_date": "2016-06-11",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Workshop with client"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 160,
+        "name": "Business Analysis",
+        "start_date": "2016-06-11",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Business Analysis"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 161,
+        "name": "Target Group",
+        "start_date": "2016-06-11",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Target Group"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 303,
+        "name": "Gantt Chart - new task from context menu",
+        "start_date": "2016-06-14",
+        "due_date": "2016-06-21",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 10,
+        "status_id": 6,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gantt Chart - new task from context menu"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 324,
+        "name": "Task list loading - ",
+        "start_date": "2016-06-14",
+        "due_date": "2016-06-21",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 51,
+        "overdue": true,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Task list loading - "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 329,
+        "name": "Timer bug",
+        "start_date": "2016-06-14",
+        "due_date": "2016-06-21",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Timer bug"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 154,
+        "name": "Backend environment setup",
+        "start_date": "2016-06-27",
+        "due_date": "2016-07-03",
+        "estimated_hours": 35.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 8,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Backend environment setup"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Dominka Support Operative"
+          }
+        ]
+      },
+      {
+        "id": 745,
+        "name": "Complete refactor of query system",
+        "start_date": "2016-06-27",
+        "due_date": "2016-09-10",
+        "estimated_hours": 400.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 21,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Complete refactor of query system"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Ezr"
+          }
+        ]
+      },
+      {
+        "id": 746,
+        "name": "Complete documentation of the software",
+        "start_date": "2016-06-27",
+        "due_date": "2016-09-10",
+        "estimated_hours": 400.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 15,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Complete documentation of the software"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel Kucera"
+          }
+        ]
+      },
+      {
+        "id": 162,
+        "name": "Backend features",
+        "start_date": "2016-06-28",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Backend features"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 163,
+        "name": "Backend testing",
+        "start_date": "2016-06-28",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Backend testing"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 164,
+        "name": "Ecommerce platform",
+        "start_date": "2016-06-28",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Ecommerce platform"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 165,
+        "name": "Payment gateways",
+        "start_date": "2016-06-28",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 8,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Payment gateways"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Dominka Support Operative"
+          }
+        ]
+      },
+      {
+        "id": 166,
+        "name": "Shippment methods",
+        "start_date": "2016-06-28",
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Shippment methods"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 292,
+        "name": "CRM - sales plans",
+        "start_date": "2016-07-11",
+        "due_date": "2016-07-11",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 11,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "CRM - sales plans"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 293,
+        "name": "Meeting Planner tweaks",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-09",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 40,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Meeting Planner tweaks"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 299,
+        "name": "Computed custom field - recalculation",
+        "start_date": "2016-07-11",
+        "due_date": "2016-07-11",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Computed custom field - recalculation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 301,
+        "name": "Help Desk - automatically enters external emails",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-23",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Help Desk - automatically enters external emails"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 302,
+        "name": "Bulk time entries - save and go back",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-23",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Bulk time entries - save and go back"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 310,
+        "name": "Issues filter - project is not planned",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-23",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Issues filter - project is not planned"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 311,
+        "name": "Agile board - display custom fields",
+        "start_date": "2016-07-11",
+        "due_date": "2016-09-05",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 42,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Agile board - display custom fields"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 312,
+        "name": "Help Desk - ticket to group",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-09",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 40,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Help Desk - ticket to group"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 319,
+        "name": "Gantt - default filters",
+        "start_date": "2016-07-11",
+        "due_date": "2016-08-09",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 40,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gantt - default filters"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 325,
+        "name": "Meeting - repeating does not work",
+        "start_date": "2016-07-11",
+        "due_date": "2016-07-30",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Meeting - repeating does not work"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 326,
+        "name": "Document inserts 2x",
+        "start_date": "2016-07-11",
+        "due_date": "2016-07-18",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 42,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 11,
+        "status_id": 2,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Document inserts 2x"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 295,
+        "name": "Agile Board - Fixed Backlog",
+        "start_date": "2016-07-13",
+        "due_date": "2016-07-14",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Agile Board - Fixed Backlog"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 327,
+        "name": "User edit - apply from template",
+        "start_date": "2016-07-13",
+        "due_date": "2016-07-14",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 3,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "User edit - apply from template"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 296,
+        "name": "Custom fields - confirmation",
+        "start_date": "2016-07-18",
+        "due_date": "2016-07-18",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 42,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 3,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Custom fields - confirmation"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 297,
+        "name": "Booking calendar - permissions",
+        "start_date": "2016-07-18",
+        "due_date": "2016-07-18",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Booking calendar - permissions"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 298,
+        "name": "Meetings for others",
+        "start_date": "2016-07-18",
+        "due_date": "2016-08-05",
+        "estimated_hours": 70.0,
+        "done_ratio": 0,
+        "fixed_version_id": 42,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 8,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Meetings for others"
+          },
+          {
+            "name": "status",
+            "value": "Sequence pending"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 305,
+        "name": "Log time on task from more menu - modal window",
+        "start_date": "2016-07-18",
+        "due_date": "2016-07-18",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 42,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 10,
+        "status_id": 3,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Log time on task from more menu - modal window"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 328,
+        "name": "Booking callendar - 25 hours per day",
+        "start_date": "2016-07-18",
+        "due_date": "2016-07-18",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 20,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Booking callendar - 25 hours per day"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel  Rosicky"
+          }
+        ]
+      },
+      {
+        "id": 322,
+        "name": "Printable templates - project fileds not visible",
+        "start_date": "2016-07-21",
+        "due_date": "2016-07-22",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 10,
+        "status_id": 3,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Printable templates - project fileds not visible"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 331,
+        "name": "Gantt view - tasks do not line-up ",
+        "start_date": "2016-07-23",
+        "due_date": "2016-07-23",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gantt view - tasks do not line-up "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 158,
+        "name": "Current state evaluation",
+        "start_date": "2016-07-23",
+        "due_date": "2016-07-30",
+        "estimated_hours": 40.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "project_id": 28,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Current state evaluation"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 294,
+        "name": "Personal Page Modul - Activity on my tasks/projects",
+        "start_date": "2016-07-25",
+        "due_date": "2016-08-10",
+        "estimated_hours": 80.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Personal Page Modul - Activity on my tasks/projects"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 321,
+        "name": "Social Wall tweaking",
+        "start_date": "2016-07-26",
+        "due_date": "2016-07-26",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 39,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 10,
+        "status_id": 2,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Social Wall tweaking"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 320,
+        "name": "Invoicing - default invoice template",
+        "start_date": "2016-07-31",
+        "due_date": "2016-07-31",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Invoicing - default invoice template"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 300,
+        "name": "Restrict roles creation of issues in certain trackers",
+        "start_date": "2016-08-02",
+        "due_date": "2016-08-02",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Restrict roles creation of issues in certain trackers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 309,
+        "name": "Resource Management Setting in tab",
+        "start_date": "2016-08-02",
+        "due_date": "2016-08-02",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 10,
+        "status_id": 2,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Resource Management Setting in tab"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 304,
+        "name": "Advanced calendar on personal page - Ajax",
+        "start_date": "2016-08-07",
+        "due_date": "2016-08-07",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 10,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Advanced calendar on personal page - Ajax"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 313,
+        "name": "CRM features - Accounts",
+        "start_date": "2016-08-07",
+        "due_date": "2016-08-07",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "CRM features - Accounts"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 314,
+        "name": "Help Desk - SMS notify",
+        "start_date": "2016-08-07",
+        "due_date": "2016-08-07",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Help Desk - SMS notify"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 307,
+        "name": "Long running operation to cron",
+        "start_date": "2016-08-08",
+        "due_date": "2016-08-08",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 8,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Long running operation to cron"
+          },
+          {
+            "name": "status",
+            "value": "Sequence pending"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 317,
+        "name": "Gantt Relations - follows ",
+        "start_date": "2016-08-09",
+        "due_date": "2016-08-09",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gantt Relations - follows "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 323,
+        "name": "Sequnces - blank page",
+        "start_date": "2016-08-10",
+        "due_date": "2016-08-10",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 14,
+        "priority_id": 9,
+        "status_id": 3,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Sequnces - blank page"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 315,
+        "name": "Redmine 3.0 mearge",
+        "start_date": "2016-08-14",
+        "due_date": "2016-08-30",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Redmine 3.0 mearge"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 308,
+        "name": "Agile - Sprint - Milestone",
+        "start_date": "2016-08-21",
+        "due_date": "2016-08-24",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Agile - Sprint - Milestone"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 316,
+        "name": "Attendance REST API",
+        "start_date": "2016-08-21",
+        "due_date": "2016-08-21",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Attendance REST API"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 318,
+        "name": "Attendance monitoring - remaining days of vacation",
+        "start_date": "2016-08-21",
+        "due_date": "2016-08-22",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Attendance monitoring - remaining days of vacation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 291,
+        "name": "GitLab integration",
+        "start_date": "2016-08-25",
+        "due_date": "2016-09-21",
+        "estimated_hours": 60.0,
+        "done_ratio": 0,
+        "fixed_version_id": 41,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 5,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "GitLab integration"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 306,
+        "name": "Invocing 1",
+        "start_date": "2016-08-27",
+        "due_date": "2016-08-31",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "project_id": 28,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 18,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Invocing 1"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Lukas Developer"
+          }
+        ]
+      },
+      {
+        "id": 371,
+        "name": "Features Specification",
+        "start_date": "2016-03-29",
+        "due_date": "2016-03-29",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Features Specification"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 372,
+        "name": "Creative brief",
+        "start_date": "2016-04-04",
+        "due_date": "2016-04-08",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Creative brief"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 373,
+        "name": "Marketing brief",
+        "start_date": "2016-04-04",
+        "due_date": "2016-04-04",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "fixed_version_id": 58,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Marketing brief"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 374,
+        "name": "Wireframes",
+        "start_date": "2016-04-11",
+        "due_date": "2016-04-11",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Wireframes"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 375,
+        "name": "Graphics",
+        "start_date": "2016-04-15",
+        "due_date": "2016-04-18",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Graphics"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 376,
+        "name": "XHTML + CSS coding",
+        "start_date": "2016-04-25",
+        "due_date": "2016-05-02",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "fixed_version_id": 57,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "XHTML + CSS coding"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 378,
+        "name": "VirtueMart implementation",
+        "start_date": "2016-05-03",
+        "due_date": "2016-05-03",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "VirtueMart implementation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 383,
+        "name": "On-line payments",
+        "start_date": "2016-05-09",
+        "due_date": "2016-05-10",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "On-line payments"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 379,
+        "name": "Web structure",
+        "start_date": "2016-05-11",
+        "due_date": "2016-05-11",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 55,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Web structure"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 377,
+        "name": "CMS - implementation + setting",
+        "start_date": "2016-05-13",
+        "due_date": "2016-05-13",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 56,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "CMS - implementation + setting"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 380,
+        "name": "Products",
+        "start_date": "2016-05-16",
+        "due_date": "2016-05-17",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 55,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Products"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 381,
+        "name": "Specific features",
+        "start_date": "2016-05-18",
+        "due_date": "2016-05-18",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 54,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Specific features"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 382,
+        "name": "Easy Redmine Integration",
+        "start_date": "2016-05-23",
+        "due_date": "2016-05-23",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 54,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Easy Redmine Integration"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 384,
+        "name": "Google Analytics",
+        "start_date": "2016-05-25",
+        "due_date": "2016-05-30",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Google Analytics"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 385,
+        "name": "Testing  ",
+        "start_date": "2016-05-30",
+        "due_date": "2016-05-31",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Testing  "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 386,
+        "name": "Debug",
+        "start_date": "2016-06-02",
+        "due_date": "2016-06-07",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 13,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 21,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Debug"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Ezr"
+          }
+        ]
+      },
+      {
+        "id": 387,
+        "name": "Training",
+        "start_date": "2016-06-06",
+        "due_date": "2016-06-07",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 53,
+        "overdue": true,
+        "project_id": 41,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Training"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 748,
+        "name": "task 1",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-28",
+        "estimated_hours": 0.0,
+        "done_ratio": 100,
+        "fixed_version_id": 116,
+        "project_id": 63,
+        "tracker_id": 6,
+        "priority_id": 9,
+        "status_id": 4,
+        "assigned_to_id": 14,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 1"
+          },
+          {
+            "name": "status",
+            "value": "To check"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 749,
+        "name": "task 2",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-28",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 116,
+        "project_id": 63,
+        "tracker_id": 6,
+        "priority_id": 9,
+        "status_id": 2,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 2"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 750,
+        "name": "task 3",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-28",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 116,
+        "project_id": 63,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 4,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 3"
+          },
+          {
+            "name": "status",
+            "value": "To check"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 751,
+        "name": "task 4",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-28",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 116,
+        "project_id": 63,
+        "tracker_id": 4,
+        "priority_id": 8,
+        "status_id": 3,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 4"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Low"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 752,
+        "name": "task 5",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-28",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 116,
+        "project_id": 63,
+        "tracker_id": 13,
+        "priority_id": 10,
+        "status_id": 10,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 5"
+          },
+          {
+            "name": "status",
+            "value": "Approved"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 755,
+        "name": "task 8",
+        "start_date": "2016-07-03",
+        "due_date": "2016-07-04",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 117,
+        "project_id": 63,
+        "tracker_id": 5,
+        "priority_id": 10,
+        "status_id": 8,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 8"
+          },
+          {
+            "name": "status",
+            "value": "Sequence pending"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 753,
+        "name": "task 6",
+        "start_date": "2016-07-04",
+        "due_date": "2016-07-05",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 117,
+        "project_id": 63,
+        "tracker_id": 3,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 5,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 6"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Manager Manager"
+          }
+        ]
+      },
+      {
+        "id": 754,
+        "name": "task 7",
+        "start_date": "2016-07-04",
+        "due_date": "2016-07-05",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 117,
+        "project_id": 63,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 4,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 7"
+          },
+          {
+            "name": "status",
+            "value": "To check"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 758,
+        "name": "task 11",
+        "start_date": "2016-07-10",
+        "due_date": "2016-07-11",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 117,
+        "project_id": 63,
+        "tracker_id": 15,
+        "priority_id": 11,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 11"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 756,
+        "name": "task 9",
+        "start_date": "2016-07-12",
+        "due_date": "2016-07-13",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 117,
+        "project_id": 63,
+        "tracker_id": 4,
+        "priority_id": 10,
+        "status_id": 2,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 9"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 757,
+        "name": "task 10",
+        "start_date": "2016-07-12",
+        "due_date": "2016-07-13",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "parent_issue_id": 756,
+        "project_id": 63,
+        "tracker_id": 16,
+        "priority_id": 15,
+        "status_id": 3,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "task 10"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Critical"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2653,
+        "name": "Venue",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-27",
+        "done_ratio": 0,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Venue"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2654,
+        "name": "Space",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-06",
+        "done_ratio": 0,
+        "parent_issue_id": 2653,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Space"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2655,
+        "name": "List suitable possibilities according to size",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-20",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2654,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List suitable possibilities according to size"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2699,
+        "name": "Venue booking",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-27",
+        "done_ratio": 0,
+        "parent_issue_id": 2653,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Venue booking"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2700,
+        "name": "List suitable possibilities according to type",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-20",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List suitable possibilities according to type"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2701,
+        "name": "List suitable possibilities according to location",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-20",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List suitable possibilities according to location"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2707,
+        "name": "Programme",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-18",
+        "done_ratio": 0,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Programme"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2708,
+        "name": "Official",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-18",
+        "done_ratio": 0,
+        "parent_issue_id": 2707,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Official"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2709,
+        "name": "Main programme",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-18",
+        "done_ratio": 0,
+        "parent_issue_id": 2708,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Main programme"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2710,
+        "name": "Define purpose of the event",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-17",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2709,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Define purpose of the event"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2715,
+        "name": "Secondary activities",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-26",
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2708,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Secondary activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2717,
+        "name": "Find how the main activities can be supported",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-16",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2715,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Find how the main activities can be supported"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2720,
+        "name": "Workshops",
+        "start_date": "2016-06-16",
+        "due_date": "2016-07-03",
+        "done_ratio": 0,
+        "parent_issue_id": 2708,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Workshops"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2721,
+        "name": "Create a list of most suitable workshops",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-27",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2720,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Create a list of most suitable workshops"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2744,
+        "name": "People",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-01",
+        "done_ratio": 0,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "People"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2745,
+        "name": "Guests",
+        "start_date": "2016-06-16",
+        "due_date": "2016-08-01",
+        "done_ratio": 0,
+        "parent_issue_id": 2744,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2749,
+        "name": "Estimate attendance",
+        "start_date": "2016-06-16",
+        "due_date": "2016-06-17",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Estimate attendance"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2711,
+        "name": "Define desired outcomes of the event",
+        "start_date": "2016-06-17",
+        "due_date": "2016-06-18",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2709,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Define desired outcomes of the event"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2746,
+        "name": "Prepare online ticket/registration",
+        "start_date": "2016-06-17",
+        "due_date": "2016-06-30",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare online ticket/registration"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2751,
+        "name": "Invite guests",
+        "start_date": "2016-06-17",
+        "due_date": "2016-07-21",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Invite guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2752,
+        "name": "Speakers",
+        "start_date": "2016-06-17",
+        "due_date": "2016-07-04",
+        "done_ratio": 0,
+        "parent_issue_id": 2744,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Speakers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2753,
+        "name": "Put together a list of desired speakers",
+        "start_date": "2016-06-17",
+        "due_date": "2016-06-24",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2752,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Put together a list of desired speakers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2756,
+        "name": "Staff",
+        "start_date": "2016-06-17",
+        "due_date": "2016-07-04",
+        "done_ratio": 0,
+        "parent_issue_id": 2744,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Staff"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2757,
+        "name": "Plan required capacities",
+        "start_date": "2016-06-17",
+        "due_date": "2016-06-23",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2756,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan required capacities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2760,
+        "name": "Entertainers",
+        "start_date": "2016-06-17",
+        "due_date": "2016-07-04",
+        "done_ratio": 0,
+        "parent_issue_id": 2744,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2761,
+        "name": "Put together list of desired entertainers",
+        "start_date": "2016-06-17",
+        "due_date": "2016-06-24",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2760,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Put together list of desired entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2712,
+        "name": "List activities leading to succeed in the purpose",
+        "start_date": "2016-06-18",
+        "due_date": "2016-06-23",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2709,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List activities leading to succeed in the purpose"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2764,
+        "name": "Budget",
+        "start_date": "2016-06-18",
+        "due_date": "2016-07-22",
+        "done_ratio": 0,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Budget"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2765,
+        "name": "Budget for venue",
+        "start_date": "2016-06-18",
+        "due_date": "2016-07-22",
+        "done_ratio": 0,
+        "parent_issue_id": 2764,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Budget for venue"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2766,
+        "name": "Compile 1st venue budget estimate",
+        "start_date": "2016-06-18",
+        "due_date": "2016-06-20",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2765,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile 1st venue budget estimate"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2769,
+        "name": "Budget for activities",
+        "start_date": "2016-06-18",
+        "due_date": "2016-07-22",
+        "done_ratio": 0,
+        "parent_issue_id": 2764,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Budget for activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2770,
+        "name": "Compile 1st programme budget estimate",
+        "start_date": "2016-06-18",
+        "due_date": "2016-06-20",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2769,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile 1st programme budget estimate"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2773,
+        "name": "Budget for people",
+        "start_date": "2016-06-18",
+        "due_date": "2016-07-22",
+        "done_ratio": 0,
+        "parent_issue_id": 2764,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Budget for people"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2774,
+        "name": "Compile 1st personal budget estimate",
+        "start_date": "2016-06-18",
+        "due_date": "2016-06-20",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2773,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile 1st personal budget estimate"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2702,
+        "name": "Contact venues for quotations",
+        "start_date": "2016-06-19",
+        "due_date": "2016-06-25",
+        "estimated_hours": 24.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Contact venues for quotations"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2716,
+        "name": "Define supporting activities ",
+        "start_date": "2016-06-19",
+        "due_date": "2016-06-20",
+        "estimated_hours": 12.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2715,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Define supporting activities "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2723,
+        "name": "Entertainment",
+        "start_date": "2016-06-19",
+        "due_date": "2016-07-02",
+        "done_ratio": 0,
+        "parent_issue_id": 2707,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Entertainment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2724,
+        "name": "Music",
+        "start_date": "2016-06-19",
+        "due_date": "2016-06-23",
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2723,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Music"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2725,
+        "name": "List suitable genres",
+        "start_date": "2016-06-19",
+        "due_date": "2016-06-19",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2724,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List suitable genres"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2718,
+        "name": "Gather equipment requirements for secondary activities",
+        "start_date": "2016-06-20",
+        "due_date": "2016-06-23",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2715,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gather equipment requirements for secondary activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2727,
+        "name": "Design form of music entertainment",
+        "start_date": "2016-06-20",
+        "due_date": "2016-06-21",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2724,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Design form of music entertainment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2719,
+        "name": "Book sufficient equipment",
+        "start_date": "2016-06-22",
+        "due_date": "2016-06-26",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2715,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book sufficient equipment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2713,
+        "name": "Gather equipment requirements for main activities",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-02",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2709,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Gather equipment requirements for main activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2726,
+        "name": "Finalize music programme",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-23",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2724,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize music programme"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2728,
+        "name": "Performance",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-24",
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2723,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Performance"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2729,
+        "name": "Compile a list of suitable performance forms",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-23",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2728,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile a list of suitable performance forms"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2730,
+        "name": "Finalize performance programme",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-24",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2728,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize performance programme"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2731,
+        "name": "Shops/Booths",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-02",
+        "done_ratio": 0,
+        "parent_issue_id": 2723,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Shops/Booths"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2732,
+        "name": "List relevant merchandise",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-27",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2731,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "List relevant merchandise"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2734,
+        "name": "Interactive",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-25",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2723,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Interactive"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2735,
+        "name": "Find relevant interactive activites",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-24",
+        "estimated_hours": 6.0,
+        "done_ratio": 0,
+        "fixed_version_id": 0,
+        "overdue": true,
+        "parent_issue_id": 2734,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Find relevant interactive activites"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2736,
+        "name": "Finalize list of interactive activities",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-25",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 0,
+        "overdue": true,
+        "parent_issue_id": 2734,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize list of interactive activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2737,
+        "name": "Schedule",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-25",
+        "done_ratio": 0,
+        "parent_issue_id": 2707,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Schedule"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2738,
+        "name": "Compile main programme timetable",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-08",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile main programme timetable"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2758,
+        "name": "Find personal agency",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-26",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 2756,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Find personal agency"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2767,
+        "name": "Continuously precise venue budget according known data",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-18",
+        "estimated_hours": 25.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2765,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Continuously precise venue budget according known data"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2771,
+        "name": "Continuously precise programme budget according to known data",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-18",
+        "estimated_hours": 25.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2769,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Continuously precise programme budget according to known data"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2775,
+        "name": "Continuously precise personal budget according to known data",
+        "start_date": "2016-06-23",
+        "due_date": "2016-07-18",
+        "estimated_hours": 25.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2773,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Continuously precise personal budget according to known data"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2739,
+        "name": "Compile secondary activities timetable",
+        "start_date": "2016-06-24",
+        "due_date": "2016-06-30",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile secondary activities timetable"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2722,
+        "name": "Finalize list of approved workshops",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-03",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2720,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize list of approved workshops"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2740,
+        "name": "Compile workshops timetable",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-01",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile workshops timetable"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2741,
+        "name": "Compile entertainment timetable",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-09",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Compile entertainment timetable"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2742,
+        "name": "Set catering schedule",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-01",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Set catering schedule"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2754,
+        "name": "Invite speakers from list",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-01",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2752,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Invite speakers from list"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2762,
+        "name": "Invite entertainers from list",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-01",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2760,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Invite entertainers from list"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2704,
+        "name": "Decide on the best offer",
+        "start_date": "2016-06-26",
+        "due_date": "2016-06-30",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Decide on the best offer"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2733,
+        "name": "Finalize list of shops",
+        "start_date": "2016-06-26",
+        "due_date": "2016-07-02",
+        "estimated_hours": 8.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2731,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize list of shops"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2686,
+        "name": "Transport",
+        "start_date": "2016-06-27",
+        "due_date": "2016-08-19",
+        "done_ratio": 0,
+        "parent_issue_id": 2653,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2694,
+        "name": "Transport of staff",
+        "start_date": "2016-06-27",
+        "due_date": "2016-07-22",
+        "done_ratio": 0,
+        "parent_issue_id": 2686,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport of staff"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2696,
+        "name": "Plan capacity of staff transport",
+        "start_date": "2016-06-27",
+        "due_date": "2016-07-11",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2694,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan capacity of staff transport"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2759,
+        "name": "Acquire suitable capacities",
+        "start_date": "2016-06-27",
+        "due_date": "2016-07-04",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2756,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Acquire suitable capacities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2687,
+        "name": "Transport of guests",
+        "start_date": "2016-06-30",
+        "due_date": "2016-08-12",
+        "done_ratio": 0,
+        "parent_issue_id": 2686,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport of guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2688,
+        "name": "Plan capacity of guest transport",
+        "start_date": "2016-06-30",
+        "due_date": "2016-07-22",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2687,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan capacity of guest transport"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2703,
+        "name": "Book selected venue",
+        "start_date": "2016-06-30",
+        "due_date": "2016-06-30",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book selected venue"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2706,
+        "name": "Agree on booking conditions",
+        "start_date": "2016-06-30",
+        "due_date": "2016-07-01",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Agree on booking conditions"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2714,
+        "name": "Book sufficient equipment",
+        "start_date": "2016-06-30",
+        "due_date": "2016-08-18",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2709,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book sufficient equipment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2755,
+        "name": "Finalize approved speakers list",
+        "start_date": "2016-06-30",
+        "due_date": "2016-07-04",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2752,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize approved speakers list"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2763,
+        "name": "Finalize approved entertainers",
+        "start_date": "2016-06-30",
+        "due_date": "2016-07-04",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2760,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize approved entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2743,
+        "name": "Aggregate complete schedule",
+        "start_date": "2016-07-01",
+        "due_date": "2016-07-25",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2737,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Aggregate complete schedule"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2750,
+        "name": "Launch online registration",
+        "start_date": "2016-07-01",
+        "due_date": "2016-07-03",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Launch online registration"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2658,
+        "name": "Services",
+        "start_date": "2016-07-04",
+        "due_date": "2016-08-20",
+        "done_ratio": 0,
+        "parent_issue_id": 2653,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Services"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2659,
+        "name": "Accommodation",
+        "start_date": "2016-07-04",
+        "due_date": "2016-08-20",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Accommodation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2660,
+        "name": "Verify capacity",
+        "start_date": "2016-07-04",
+        "due_date": "2016-08-01",
+        "estimated_hours": 12.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2659,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Verify capacity"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2748,
+        "name": "Plan reserve for unregistered guests",
+        "start_date": "2016-07-04",
+        "due_date": "2016-07-23",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan reserve for unregistered guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2665,
+        "name": "Catering",
+        "start_date": "2016-07-07",
+        "due_date": "2016-08-15",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Catering"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2668,
+        "name": "Decide on the form of catering",
+        "start_date": "2016-07-07",
+        "due_date": "2016-07-15",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2665,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Decide on the form of catering"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2695,
+        "name": "Book transport service for staff",
+        "start_date": "2016-07-07",
+        "due_date": "2016-07-22",
+        "estimated_hours": 6.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2694,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book transport service for staff"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2690,
+        "name": "Transport of speakers",
+        "start_date": "2016-07-08",
+        "due_date": "2016-07-16",
+        "done_ratio": 0,
+        "parent_issue_id": 2686,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport of speakers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2691,
+        "name": "Book transport service for speakers",
+        "start_date": "2016-07-08",
+        "due_date": "2016-07-16",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2690,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book transport service for speakers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2692,
+        "name": "Transport of entertainers",
+        "start_date": "2016-07-08",
+        "due_date": "2016-07-16",
+        "done_ratio": 0,
+        "parent_issue_id": 2686,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport of entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2693,
+        "name": "Book transport service for entertainers",
+        "start_date": "2016-07-08",
+        "due_date": "2016-07-16",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2692,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book transport service for entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2669,
+        "name": "Prepare menu",
+        "start_date": "2016-07-09",
+        "due_date": "2016-08-04",
+        "done_ratio": 0,
+        "parent_issue_id": 2665,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare menu"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2670,
+        "name": "Find out client's preference ",
+        "start_date": "2016-07-09",
+        "due_date": "2016-08-01",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2669,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Find out client's preference "
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2656,
+        "name": "Prepare spacial layout for ALL activities",
+        "start_date": "2016-07-14",
+        "due_date": "2016-07-29",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2654,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare spacial layout for ALL activities"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2662,
+        "name": "Assign rooms to entertainers",
+        "start_date": "2016-07-14",
+        "due_date": "2016-08-11",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2659,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Assign rooms to entertainers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2663,
+        "name": "Assign rooms to speakers",
+        "start_date": "2016-07-14",
+        "due_date": "2016-08-11",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2659,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Assign rooms to speakers"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2664,
+        "name": "Assign rooms to staff",
+        "start_date": "2016-07-14",
+        "due_date": "2016-08-11",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2659,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Assign rooms to staff"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2666,
+        "name": "Plan number of meals",
+        "start_date": "2016-07-14",
+        "due_date": "2016-08-06",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2665,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan number of meals"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2689,
+        "name": "Book transport service for guests",
+        "start_date": "2016-07-21",
+        "due_date": "2016-08-12",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2687,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Book transport service for guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2768,
+        "name": "Finalize venue budget",
+        "start_date": "2016-07-21",
+        "due_date": "2016-07-22",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2765,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize venue budget"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2772,
+        "name": "Finalize programme budget",
+        "start_date": "2016-07-21",
+        "due_date": "2016-07-22",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2769,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize programme budget"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2776,
+        "name": "Finalize personal budget",
+        "start_date": "2016-07-21",
+        "due_date": "2016-07-22",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2773,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize personal budget"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2675,
+        "name": "Reception",
+        "start_date": "2016-07-23",
+        "due_date": "2016-08-05",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Reception"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2676,
+        "name": "Arrange ticket office for new guests",
+        "start_date": "2016-07-23",
+        "due_date": "2016-08-01",
+        "estimated_hours": 25.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2675,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Arrange ticket office for new guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2678,
+        "name": "Arrange guest registration",
+        "start_date": "2016-07-25",
+        "due_date": "2016-07-31",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2675,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Arrange guest registration"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2747,
+        "name": "Finalize approved guestlist",
+        "start_date": "2016-07-25",
+        "due_date": "2016-08-01",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2745,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize approved guestlist"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2657,
+        "name": "Finalize spacial layout",
+        "start_date": "2016-07-28",
+        "due_date": "2016-08-06",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2654,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Finalize spacial layout"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2679,
+        "name": "Prepare capacity of cloakroom",
+        "start_date": "2016-07-28",
+        "due_date": "2016-07-31",
+        "estimated_hours": 3.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2675,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare capacity of cloakroom"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2680,
+        "name": "Child care",
+        "start_date": "2016-07-28",
+        "due_date": "2016-08-18",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Child care"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2681,
+        "name": "Plan child care capacity",
+        "start_date": "2016-07-28",
+        "due_date": "2016-08-14",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2680,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan child care capacity"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2671,
+        "name": "Prepare meal menu",
+        "start_date": "2016-07-29",
+        "due_date": "2016-08-04",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 0,
+        "parent_issue_id": 2669,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare meal menu"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2661,
+        "name": "Assign rooms to guests",
+        "start_date": "2016-07-31",
+        "due_date": "2016-08-20",
+        "estimated_hours": 25.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2659,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Assign rooms to guests"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2667,
+        "name": "Arrange seating order",
+        "start_date": "2016-07-31",
+        "due_date": "2016-08-15",
+        "estimated_hours": 12.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2665,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Arrange seating order"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2677,
+        "name": "Arrange guest welcoming",
+        "start_date": "2016-07-31",
+        "due_date": "2016-08-05",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2675,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Arrange guest welcoming"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2697,
+        "name": "Transport of equipment",
+        "start_date": "2016-07-31",
+        "due_date": "2016-08-19",
+        "done_ratio": 0,
+        "parent_issue_id": 2686,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Transport of equipment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2698,
+        "name": "Arrange transport of equipment",
+        "start_date": "2016-07-31",
+        "due_date": "2016-08-19",
+        "estimated_hours": 30.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2697,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Arrange transport of equipment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2672,
+        "name": "Prepare drink menu and volume",
+        "start_date": "2016-08-01",
+        "due_date": "2016-08-07",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2665,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Prepare drink menu and volume"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2673,
+        "name": "Valet",
+        "start_date": "2016-08-05",
+        "due_date": "2016-08-13",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Valet"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2674,
+        "name": "Plan valet capacity",
+        "start_date": "2016-08-05",
+        "due_date": "2016-08-13",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2673,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan valet capacity"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2682,
+        "name": "Find suitable space",
+        "start_date": "2016-08-08",
+        "due_date": "2016-08-18",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2680,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Find suitable space"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2683,
+        "name": "Hygiene",
+        "start_date": "2016-08-11",
+        "due_date": "2016-08-19",
+        "done_ratio": 0,
+        "parent_issue_id": 2658,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Hygiene"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2684,
+        "name": "Plan capacity of portables",
+        "start_date": "2016-08-11",
+        "due_date": "2016-08-18",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2683,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Plan capacity of portables"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2685,
+        "name": "Aqcuire portables",
+        "start_date": "2016-08-14",
+        "due_date": "2016-08-19",
+        "estimated_hours": 16.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2683,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Aqcuire portables"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2705,
+        "name": "Make the payment",
+        "start_date": "2016-08-25",
+        "due_date": "2016-08-27",
+        "estimated_hours": 1.0,
+        "done_ratio": 0,
+        "parent_issue_id": 2699,
+        "project_id": 88,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Make the payment"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 2777,
+        "name": "Event",
+        "start_date": "2016-08-25",
+        "due_date": "2016-08-27",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "project_id": 88,
+        "tracker_id": 19,
+        "priority_id": 9,
+        "status_id": 1,
+        "is_planned": true,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Event"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 702,
+        "name": "Analysis",
+        "start_date": "2016-06-20",
+        "done_ratio": 0,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 2,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Analysis"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 707,
+        "name": "Testing",
+        "start_date": "2016-06-20",
+        "done_ratio": 0,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Testing"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 712,
+        "name": "fefe",
+        "start_date": "2016-06-20",
+        "done_ratio": 0,
+        "parent_issue_id": 705,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 714,
+        "name": "fefe",
+        "start_date": "2016-06-20",
+        "done_ratio": 0,
+        "parent_issue_id": 705,
+        "project_id": 62,
+        "tracker_id": 17,
+        "priority_id": 15,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Critical"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 708,
+        "name": "analyse client needs",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-23",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 702,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "analyse client needs"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 709,
+        "name": "fefe",
+        "start_date": "2016-06-24",
+        "due_date": "2016-06-24",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 702,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 8,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Low"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 703,
+        "name": "Launch to production",
+        "start_date": "2016-06-25",
+        "due_date": "2016-06-25",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 10,
+        "status_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Launch to production"
+          },
+          {
+            "name": "status",
+            "value": "Estimated"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 711,
+        "name": "fefe",
+        "start_date": "2016-06-25",
+        "due_date": "2016-06-25",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 702,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 3,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "Consultation"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 713,
+        "name": "fefe",
+        "start_date": "2016-06-25",
+        "due_date": "2016-06-25",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "parent_issue_id": 702,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 704,
+        "name": "Design",
+        "start_date": "2016-06-26",
+        "due_date": "2016-06-26",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 11,
+        "status_id": 4,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Design"
+          },
+          {
+            "name": "status",
+            "value": "To check"
+          },
+          {
+            "name": "priority",
+            "value": "Easy task"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 705,
+        "name": "Front End Implementation",
+        "start_date": "2016-06-26",
+        "due_date": "2016-06-26",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Front End Implementation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 706,
+        "name": "Back End",
+        "start_date": "2016-06-26",
+        "due_date": "2016-06-26",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Back End"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 710,
+        "name": "fefe",
+        "start_date": "2016-06-27",
+        "due_date": "2016-06-27",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "fixed_version_id": 0,
+        "overdue": true,
+        "parent_issue_id": 705,
+        "project_id": 62,
+        "tracker_id": 1,
+        "priority_id": 10,
+        "status_id": 10,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "fefe"
+          },
+          {
+            "name": "status",
+            "value": "Approved"
+          },
+          {
+            "name": "priority",
+            "value": "High"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 3194,
+        "name": "Concept Creation and Requirement Gathering:",
+        "start_date": "2016-06-20",
+        "due_date": "2016-06-21",
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 106,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Concept Creation and Requirement Gathering:"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 3195,
+        "name": "Planning and Designing",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-24",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "overdue": true,
+        "project_id": 106,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Planning and Designing"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 3196,
+        "name": "Implementation:",
+        "start_date": "2016-06-26",
+        "due_date": "2016-06-30",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "project_id": 106,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Implementation:"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 3197,
+        "name": "Evaluation",
+        "start_date": "2016-07-02",
+        "due_date": "2016-07-04",
+        "estimated_hours": 0.0,
+        "done_ratio": 0,
+        "project_id": 106,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Evaluation"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": ""
+          }
+        ]
+      },
+      {
+        "id": 352,
+        "name": "Areas of usage",
+        "start_date": "2016-06-01",
+        "due_date": "2016-06-02",
+        "estimated_hours": 3.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Areas of usage"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 353,
+        "name": "Trackers and statuses",
+        "start_date": "2016-06-03",
+        "due_date": "2016-06-03",
+        "estimated_hours": 1.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "parent_issue_id": 352,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Trackers and statuses"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 351,
+        "name": "Project templates",
+        "start_date": "2016-06-10",
+        "due_date": "2016-06-10",
+        "estimated_hours": 1.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "parent_issue_id": 350,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Project templates"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 701,
+        "name": "dwdw",
+        "start_date": "2016-06-19",
+        "due_date": "2016-06-26",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "parent_issue_id": 349,
+        "project_id": 40,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 22,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "dwdw"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Client *"
+          }
+        ]
+      },
+      {
+        "id": 350,
+        "name": "Project structure definition",
+        "start_date": "2016-06-23",
+        "due_date": "2016-06-23",
+        "estimated_hours": 1.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Project structure definition"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 349,
+        "name": "User roles and personal pages",
+        "start_date": "2016-06-25",
+        "due_date": "2016-07-04",
+        "estimated_hours": 10.0,
+        "done_ratio": 80,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "parent_issue_id": 354,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "User roles and personal pages"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 354,
+        "name": "Users and groups",
+        "start_date": "2016-06-26",
+        "due_date": "2016-07-10",
+        "estimated_hours": 10.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Users and groups"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 360,
+        "name": "Personal Pages Templates",
+        "start_date": "2016-06-29",
+        "due_date": "2016-06-29",
+        "estimated_hours": 4.0,
+        "done_ratio": 100,
+        "css": " closed",
+        "parent_issue_id": 354,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Personal Pages Templates"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 699,
+        "name": "second analysis",
+        "start_date": "2016-07-02",
+        "due_date": "2016-07-02",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "project_id": 40,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "second analysis"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 358,
+        "name": "Extensions uninstall",
+        "start_date": "2016-07-09",
+        "due_date": "2016-07-09",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 52,
+        "parent_issue_id": 352,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Extensions uninstall"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 355,
+        "name": "Desired Outputs",
+        "start_date": "2016-07-10",
+        "due_date": "2016-07-12",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "parent_issue_id": 352,
+        "project_id": 40,
+        "tracker_id": 5,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Desired Outputs"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 361,
+        "name": "Users \u0026 groups",
+        "start_date": "2016-07-12",
+        "due_date": "2016-07-12",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "fixed_version_id": 52,
+        "parent_issue_id": 354,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Users \u0026 groups"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 357,
+        "name": "Roles, Trackers, Statuses, Custom Fields",
+        "start_date": "2016-07-13",
+        "due_date": "2016-07-17",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "parent_issue_id": 354,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 6,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Roles, Trackers, Statuses, Custom Fields"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Peter Project Man"
+          }
+        ]
+      },
+      {
+        "id": 366,
+        "name": "Deployment into Production",
+        "start_date": "2016-07-17",
+        "due_date": "2016-07-19",
+        "estimated_hours": 20.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "project_id": 40,
+        "tracker_id": 6,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 19,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Deployment into Production"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ondrej Moravcik"
+          }
+        ]
+      },
+      {
+        "id": 359,
+        "name": "Projects Structure + Templates",
+        "start_date": "2016-07-18",
+        "due_date": "2016-07-18",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "css": " closed",
+        "parent_issue_id": 350,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 6,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Projects Structure + Templates"
+          },
+          {
+            "name": "status",
+            "value": "Done"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 362,
+        "name": "E-learning for Admins",
+        "start_date": "2016-07-23",
+        "due_date": "2016-07-24",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "fixed_version_id": 51,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 22,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "E-learning for Admins"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Client *"
+          }
+        ]
+      },
+      {
+        "id": 363,
+        "name": "E-learning for users",
+        "start_date": "2016-07-23",
+        "due_date": "2016-07-27",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 51,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 10,
+        "assigned_to_id": 22,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "E-learning for users"
+          },
+          {
+            "name": "status",
+            "value": "Approved"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Client *"
+          }
+        ]
+      },
+      {
+        "id": 367,
+        "name": "Testing in production",
+        "start_date": "2016-07-23",
+        "due_date": "2016-08-15",
+        "estimated_hours": 15.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 14,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Testing in production"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Alena Staffer"
+          }
+        ]
+      },
+      {
+        "id": 369,
+        "name": "Review Meeting ",
+        "start_date": "2016-07-23",
+        "due_date": "2016-07-26",
+        "estimated_hours": 5.0,
+        "done_ratio": 0,
+        "fixed_version_id": 49,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 20,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Review Meeting "
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel  Rosicky"
+          }
+        ]
+      },
+      {
+        "id": 364,
+        "name": "Admin Training",
+        "start_date": "2016-07-26",
+        "due_date": "2016-07-26",
+        "estimated_hours": 2.0,
+        "done_ratio": 0,
+        "fixed_version_id": 51,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 9,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Admin Training"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Robert Kovacik"
+          }
+        ]
+      },
+      {
+        "id": 370,
+        "name": "Re-Set Up",
+        "start_date": "2016-07-28",
+        "due_date": "2016-07-29",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "parent_issue_id": 366,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 7,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Re-Set Up"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Ricky Account Manager"
+          }
+        ]
+      },
+      {
+        "id": 365,
+        "name": "Training for users",
+        "start_date": "2016-07-30",
+        "due_date": "2016-07-30",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 51,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 9,
+        "assigned_to_id": 31,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Training for users"
+          },
+          {
+            "name": "status",
+            "value": "Estimated"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Project Manager"
+          }
+        ]
+      },
+      {
+        "id": 558,
+        "name": "Final check",
+        "start_date": "2016-08-20",
+        "due_date": "2016-08-20",
+        "estimated_hours": 10.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "parent_issue_id": 367,
+        "project_id": 40,
+        "tracker_id": 1,
+        "priority_id": 9,
+        "status_id": 1,
+        "assigned_to_id": 15,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Final check"
+          },
+          {
+            "name": "status",
+            "value": "New"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel Kucera"
+          }
+        ]
+      },
+      {
+        "id": 368,
+        "name": "Help Desk Set-up",
+        "start_date": "2016-08-20",
+        "due_date": "2016-08-20",
+        "estimated_hours": 4.0,
+        "done_ratio": 0,
+        "fixed_version_id": 50,
+        "project_id": 40,
+        "tracker_id": 4,
+        "priority_id": 9,
+        "status_id": 2,
+        "assigned_to_id": 15,
+        "permissions": {
+          "editable": true
+        },
+        "columns": [
+          {
+            "name": "subject",
+            "value": "Help Desk Set-up"
+          },
+          {
+            "name": "status",
+            "value": "Realisation"
+          },
+          {
+            "name": "priority",
+            "value": "Normal"
+          },
+          {
+            "name": "assigned_to",
+            "value": "Pavel Kucera"
+          }
+        ]
+      }
+    ],
+    "relations": [
+      {
+        "id": 10,
+        "source_id": 39,
+        "target_id": 40,
+        "type": "precedes",
+        "delay": 5
+      },
+      {
+        "id": 65,
+        "source_id": 37,
+        "target_id": 40,
+        "type": "copied_to",
+        "delay": 0
+      },
+      {
+        "id": 66,
+        "source_id": 321,
+        "target_id": 304,
+        "type": "relates",
+        "delay": 0
+      },
+      {
+        "id": 27,
+        "source_id": 374,
+        "target_id": 375,
+        "type": "precedes",
+        "delay": 3
+      },
+      {
+        "id": 28,
+        "source_id": 375,
+        "target_id": 376,
+        "type": "precedes",
+        "delay": 4
+      },
+      {
+        "id": 29,
+        "source_id": 376,
+        "target_id": 377,
+        "type": "precedes",
+        "delay": 10
+      },
+      {
+        "id": 30,
+        "source_id": 385,
+        "target_id": 386,
+        "type": "precedes",
+        "delay": 1
+      },
+      {
+        "id": 136,
+        "source_id": 754,
+        "target_id": 757,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 135,
+        "source_id": 755,
+        "target_id": 756,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 109,
+        "source_id": 708,
+        "target_id": 709,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 110,
+        "source_id": 709,
+        "target_id": 713,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 111,
+        "source_id": 713,
+        "target_id": 706,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 205,
+        "source_id": 3194,
+        "target_id": 3195,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 206,
+        "source_id": 3195,
+        "target_id": 3196,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 207,
+        "source_id": 3196,
+        "target_id": 3197,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 22,
+        "source_id": 358,
+        "target_id": 357,
+        "type": "precedes",
+        "delay": 3
+      },
+      {
+        "id": 23,
+        "source_id": 362,
+        "target_id": 364,
+        "type": "precedes",
+        "delay": 1
+      },
+      {
+        "id": 24,
+        "source_id": 363,
+        "target_id": 365,
+        "type": "precedes",
+        "delay": 1
+      },
+      {
+        "id": 25,
+        "source_id": 366,
+        "target_id": 367,
+        "type": "precedes",
+        "delay": 1
+      },
+      {
+        "id": 26,
+        "source_id": 369,
+        "target_id": 370,
+        "type": "precedes",
+        "delay": 1
+      },
+      {
+        "id": 31,
+        "source_id": 361,
+        "target_id": 366,
+        "type": "precedes",
+        "delay": 4
+      },
+      {
+        "id": 32,
+        "source_id": 355,
+        "target_id": 362,
+        "type": "precedes",
+        "delay": 10
+      },
+      {
+        "id": 33,
+        "source_id": 367,
+        "target_id": 368,
+        "type": "precedes",
+        "delay": 2
+      },
+      {
+        "id": 35,
+        "source_id": 360,
+        "target_id": 364,
+        "type": "precedes",
+        "delay": 2
+      },
+      {
+        "id": 93,
+        "source_id": 350,
+        "target_id": 349,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 105,
+        "source_id": 364,
+        "target_id": 365,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 107,
+        "source_id": 357,
+        "target_id": 359,
+        "type": "precedes",
+        "delay": 0
+      },
+      {
+        "id": 108,
+        "source_id": 360,
+        "target_id": 699,
+        "type": "precedes",
+        "delay": 0
+      }
+    ],
+    "versions": [
+      {
+        "id": 9,
+        "name": "Phase 1. - Development",
+        "start_date": "2016-07-26",
+        "project_id": 10,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 8,
+        "name": "Phase 2. - Construction",
+        "start_date": "2016-08-28",
+        "project_id": 10,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 7,
+        "name": "Phase 3. - Testing",
+        "start_date": "2016-09-12",
+        "project_id": 10,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 6,
+        "name": "Phase 4. - Installation",
+        "start_date": "2016-09-19",
+        "project_id": 10,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 10,
+        "name": "Phase 0. Planning - Analysis",
+        "start_date": "2016-10-10",
+        "project_id": 10,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 39,
+        "name": "Easy Redmine 2014 - Minor 3.1 (2014-08-14)",
+        "start_date": "2016-07-30",
+        "project_id": 28,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 51,
+        "name": "Settings \u0026 Training",
+        "start_date": "2016-08-02",
+        "project_id": 40,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 40,
+        "name": "Easy Redmine 2014 - Minor 3.2. (2014-09-04)",
+        "start_date": "2016-08-20",
+        "project_id": 28,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 42,
+        "name": "Easy Redmine 2014 - Major 4.0 (2014-10-01)",
+        "start_date": "2016-09-05",
+        "project_id": 28,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 41,
+        "name": "Easy Redmine 2014 - Minor 3.3. (2014-09-18)",
+        "start_date": "2016-09-21",
+        "project_id": 28,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 58,
+        "name": "Analysis",
+        "start_date": "2016-04-04",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 57,
+        "name": "Design",
+        "start_date": "2016-05-02",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 53,
+        "name": "Public Launch",
+        "start_date": "2016-05-10",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 56,
+        "name": "CMS + E-commerce platform",
+        "start_date": "2016-05-13",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 55,
+        "name": "Content + Products",
+        "start_date": "2016-05-17",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 54,
+        "name": "Easy Redmine integration",
+        "start_date": "2016-05-23",
+        "project_id": 41,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 116,
+        "name": "Milestone 2",
+        "start_date": "2016-06-28",
+        "project_id": 63,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 117,
+        "name": "Milestone 1",
+        "start_date": "2016-07-13",
+        "project_id": 63,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 52,
+        "name": "Analysis",
+        "start_date": "2016-07-12",
+        "project_id": 40,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 49,
+        "name": "Review",
+        "start_date": "2016-07-26",
+        "project_id": 40,
+        "permissions": {
+          "editable": true
+        }
+      },
+      {
+        "id": 50,
+        "name": "Deployment",
+        "start_date": "2016-08-27",
+        "project_id": 40,
+        "permissions": {
+          "editable": true
+        }
+      }
+    ],
+    "schemes": {
+      "EasyProjectPriority": []
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.eot b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.eot
new file mode 100644
index 0000000000000000000000000000000000000000..c1cc0f7fd9a9233ce0235a17d8761f224c1b2aea
Binary files /dev/null and b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.eot differ
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.otf b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.otf
new file mode 100644
index 0000000000000000000000000000000000000000..f8500754524df47597aace5f694a24f3125e129b
Binary files /dev/null and b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.otf differ
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.svg b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b46f165d8b9fb8f172ddcc20456e072546e4188a
--- /dev/null
+++ b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.svg
@@ -0,0 +1,2411 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
+<metadata>
+Created by FontForge 20161004 at Mon Feb 06 14:01:27 2017
+ By milos
+Copyright 2015 Google, Inc. All Rights Reserved.
+</metadata>
+<defs>
+<font id="MaterialIcons-Regular" horiz-adv-x="512" >
+  <font-face 
+    font-family="Material Icons"
+    font-weight="400"
+    font-stretch="normal"
+    units-per-em="512"
+    panose-1="2 0 5 3 0 0 0 0 0 0"
+    ascent="512"
+    descent="0"
+    bbox="0 0 512 512.5"
+    underline-thickness="50"
+    underline-position="-200"
+    unicode-range="U+0030-10FFFD"
+  />
+<missing-glyph 
+d="M17 0v341h136v-341h-136zM34 17h102v307h-102v-307z" />
+    <glyph glyph-name="uniE000" unicode="error" 
+d="M277 235v128h-42v-128h42zM277 149v43h-42v-43h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE001" unicode="error_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 363h42v-128h-42v128z
+M235 192h42v-43h-42v43z" />
+    <glyph glyph-name="uniE002" unicode="warning" 
+d="M277 213v86h-42v-86h42zM277 128v43h-42v-43h42zM21 64l235 405l235 -405h-470z" />
+    <glyph glyph-name="uniE003" unicode="add_alert" 
+d="M341 234v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM403 153l45 -45v-23h-384v23l45 45v124q0 51 32 91.5t81 51.5v15q0 14 10 24t24 10t24 -10t10 -24v-15q49 -11 81 -51.5t32 -91.5v-124zM214 64h84q0 -17 -12.5 -30t-29.5 -13t-29.5 13t-12.5 30z" />
+    <glyph glyph-name="uniE019" unicode="album" 
+d="M256 277q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM256 160q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE01B" unicode="av_timer" 
+d="M128 256q0 9 6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15zM384 256q0 -9 -6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6t15 -6t6 -15zM235 448h21q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136q0 96 77 153v1l145 -145l-30 -30l-116 115
+q-33 -41 -33 -94q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5t43.5 105.5q0 56 -37 98t-91 50v-41h-42v85zM235 149q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15t-15 -6t-15 6t-6 15z" />
+    <glyph glyph-name="uniE01C" unicode="closed_caption" 
+d="M384 277v22q0 9 -6 15t-15 6h-64q-9 0 -15.5 -6t-6.5 -15v-86q0 -9 6.5 -15t15.5 -6h64q9 0 15 6t6 15v22h-32v-11h-43v64h43v-11h32zM235 277v22q0 9 -6.5 15t-15.5 6h-64q-9 0 -15 -6t-6 -15v-86q0 -9 6 -15t15 -6h64q9 0 15.5 6t6.5 15v22h-32v-11h-43v64h43v-11h32z
+M405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE01D" unicode="equalizer" 
+d="M341 320h86v-235h-86v235zM85 85v171h86v-171h-86zM213 85v342h86v-342h-86z" />
+    <glyph glyph-name="uniE01E" unicode="explicit" 
+d="M320 320v43h-128v-214h128v43h-85v43h85v42h-85v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE01F" unicode="fast_forward" 
+d="M277 384l182 -128l-182 -128v256zM85 128v256l182 -128z" />
+    <glyph glyph-name="uniE020" unicode="fast_rewind" 
+d="M245 256l182 128v-256zM235 128l-182 128l182 128v-256z" />
+    <glyph glyph-name="uniE021" unicode="games" 
+d="M352 320h117v-128h-117l-64 64zM192 160l64 64l64 -64v-117h-128v117zM160 320l64 -64l-64 -64h-117v128h117zM320 352l-64 -64l-64 64v117h128v-117z" />
+    <glyph glyph-name="uniE023" unicode="hearing" 
+d="M245 320q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5zM163 456q-56 -56 -56 -136t56 -136l-30 -30q-69 69 -69 166t69 166zM363 85q17 0 29.5 13t12.5 30h43q0 -35 -25 -60t-60 -25q-20 0 -35 7q-41 21 -59 76q-7 22 -36 44
+q-41 30 -61 67q-23 41 -23 83q0 63 43.5 106t106.5 43t106 -43t43 -106h-43q0 45 -30.5 76t-75.5 31t-76 -31t-31 -76q0 -32 17 -63q14 -27 50 -54q40 -30 51 -64q13 -38 36 -50q8 -4 17 -4z" />
+    <glyph glyph-name="uniE024" unicode="high_quality" 
+d="M309 224v64h43v-64h-43zM384 213v86q0 9 -6 15t-15 6h-64q-9 0 -15.5 -6t-6.5 -15v-86q0 -9 6.5 -15t15.5 -6h16v-32h32v32h16q9 0 15 6t6 15zM235 192v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298
+q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE028" unicode="loop" 
+d="M256 128v64l85 -85l-85 -86v64q-70 0 -120.5 50.5t-50.5 120.5q0 50 27 91l31 -31q-15 -27 -15 -60q0 -53 37.5 -90.5t90.5 -37.5zM256 427q70 0 120.5 -50.5t50.5 -120.5q0 -50 -27 -91l-31 31q15 27 15 60q0 53 -37.5 90.5t-90.5 37.5v-64l-85 85l85 86v-64z" />
+    <glyph glyph-name="uniE029" unicode="mic" 
+d="M369 277h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5t79.5 30.5t33.5 77.5zM256 213q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE02A" unicode="mic_none" 
+d="M369 277h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5t79.5 30.5t33.5 77.5zM230 407v-132q0 -10 7.5 -17.5t18.5 -7.5q10 0 17.5 7t7.5 18l1 132q0 11 -8 18.5t-18 7.5t-18 -7.5t-8 -18.5zM256 213
+q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE02B" unicode="mic_off" 
+d="M91 448l357 -357l-27 -27l-89 89q-22 -14 -55 -19v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5q25 0 49 11l-35 35q-8 -2 -14 -2q-26 0 -45 19t-19 45v16l-128 128zM320 274l-128 127v4q0 26 19 45t45 19t45 -19t19 -45v-131zM405 277
+q0 -37 -19 -70l-26 27q9 20 9 43h36z" />
+    <glyph glyph-name="uniE02C" unicode="movie" 
+d="M384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -86h64l-42 86h42l43 -86h64l-43 86h43l43 -86h64z" />
+    <glyph glyph-name="uniE02E" unicode="library_add" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE02F" unicode="library_books" 
+d="M405 363v42h-213v-42h213zM320 192v43h-128v-43h128zM405 277v43h-213v-43h213zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5
+v299h42z" />
+    <glyph glyph-name="uniE030" unicode="library_music" 
+d="M85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42zM384 363v42h-85v-117q-14 11 -32 11q-22 0 -38 -16t-16 -38t16 -37.5t38 -15.5t37.5 15.5t15.5 37.5v118h64zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13
+t-13 30v256q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE031" unicode="new_releases" 
+d="M277 235v128h-42v-128h42zM277 149v43h-42v-43h42zM491 256l-52 -59l7 -79l-77 -17l-40 -68l-73 31l-73 -31l-40 67l-77 18l7 79l-52 59l52 60l-7 78l77 17l40 68l73 -31l73 31l40 -68l77 -17l-7 -79z" />
+    <glyph glyph-name="uniE033" unicode="not_interested" 
+d="M391 151q36 45 36 105q0 70 -50.5 120.5t-120.5 50.5q-60 0 -105 -36zM256 85q60 0 105 36l-240 240q-36 -45 -36 -105q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5
+z" />
+    <glyph glyph-name="uniE034" unicode="pause" 
+d="M299 405h85v-298h-85v298zM128 107v298h85v-298h-85z" />
+    <glyph glyph-name="uniE035" unicode="pause_circle_filled" 
+d="M320 171v170h-43v-170h43zM235 171v170h-43v-170h43zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE036" unicode="pause_circle_outline" 
+d="M277 171v170h43v-170h-43zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z
+M192 171v170h43v-170h-43z" />
+    <glyph glyph-name="uniE037" unicode="play_arrow" 
+d="M171 405l234 -149l-234 -149v298z" />
+    <glyph glyph-name="uniE038" unicode="play_circle_filled" 
+d="M213 160l128 96l-128 96v-192zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE039" unicode="play_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM213 160v192l128 -96z" />
+    <glyph glyph-name="uniE03B" unicode="playlist_add" 
+d="M43 171v42h170v-42h-170zM384 213h85v-42h-85v-86h-43v86h-85v42h85v86h43v-86zM299 384v-43h-256v43h256zM299 299v-43h-256v43h256z" />
+    <glyph glyph-name="uniE03C" unicode="queue" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE03D" unicode="queue_music" 
+d="M363 384h106v-43h-64v-192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19q8 0 22 -4v175zM64 171v42h171v-42h-171zM320 299v-43h-256v43h256zM320 384v-43h-256v43h256z" />
+    <glyph glyph-name="uniE03E" unicode="radio" 
+d="M427 256v85h-342v-85h256v43h43v-43h43zM149 85q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM69 381l270 110l14 -36l-176 -71h250q18 0 30 -12.5t12 -30.5v-256q0 -17 -12 -29.5t-30 -12.5h-342q-18 0 -30 12.5t-12 29.5v256q0 30 26 40z" />
+    <glyph glyph-name="uniE03F" unicode="recent_actors" 
+d="M267 149v16q0 22 -33 35t-63 13t-63 -13t-33 -35v-16h192zM171 347q-19 0 -33.5 -14.5t-14.5 -33.5t14.5 -33.5t33.5 -14.5t33.5 14.5t14.5 33.5t-14.5 33.5t-33.5 14.5zM299 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-256q-9 0 -15.5 6t-6.5 15v256q0 9 6.5 15
+t15.5 6h256zM363 107v298h42v-298h-42zM448 405h43v-298h-43v298z" />
+    <glyph glyph-name="uniE040" unicode="repeat" 
+d="M363 149v86h42v-128h-256v-64l-85 85l85 85v-64h214zM149 363v-86h-42v128h256v64l85 -85l-85 -85v64h-214z" />
+    <glyph glyph-name="uniE041" unicode="repeat_one" 
+d="M277 192h-32v85h-32v22l43 21h21v-128zM363 149v86h42v-128h-256v-64l-85 85l85 85v-64h214zM149 363v-86h-42v128h256v64l85 -85l-85 -85v64h-214z" />
+    <glyph glyph-name="uniE042" unicode="replay" 
+d="M256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE043" unicode="shuffle" 
+d="M316 226l67 -67l44 44v-118h-118l44 44l-67 67zM309 427h118v-118l-44 44l-268 -268l-30 30l268 268zM226 316l-30 -30l-111 111l30 30z" />
+    <glyph glyph-name="uniE044" unicode="skip_next" 
+d="M341 384h43v-256h-43v256zM128 128v256l181 -128z" />
+    <glyph glyph-name="uniE045" unicode="skip_previous" 
+d="M203 256l181 128v-256zM128 384h43v-256h-43v256z" />
+    <glyph glyph-name="uniE046" unicode="snooze" 
+d="M192 277v43h128v-38l-77 -90h77v-43h-128v39l77 89h-77zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE047" unicode="stop" 
+d="M128 384h256v-256h-256v256z" />
+    <glyph glyph-name="uniE048" unicode="subtitles" 
+d="M427 213v43h-214v-43h214zM427 128v43h-86v-43h86zM299 128v43h-214v-43h214zM85 256v-43h86v43h-86zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE049" unicode="surround_sound" 
+d="M256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM377 135q50 50 50 121t-50 121l-31 -31q38 -38 38 -90q0 -54 -37 -91zM256 171q35 0 60 25t25 60t-25 60t-60 25t-60 -25t-25 -60t25 -60t60 -25zM166 166q-38 38 -38 90q0 54 37 91l-30 30
+q-50 -50 -50 -121t50 -121zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE04A" unicode="video_collection" 
+d="M256 203l128 96l-128 96v-192zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE04B" unicode="videocam" 
+d="M363 288l85 85v-234l-85 85v-75q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h256q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE04C" unicode="videocam_off" 
+d="M70 469l378 -378l-27 -27l-68 68q-6 -4 -12 -4h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h16l-58 58zM448 373v-228l-239 239h132q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE04D" unicode="volume_down" 
+d="M107 320h85l107 107v-342l-107 107h-85v128zM395 256q0 -59 -54 -86v172q54 -27 54 -86z" />
+    <glyph glyph-name="uniE04E" unicode="volume_mute" 
+d="M149 320h86l106 107v-342l-106 107h-86v128z" />
+    <glyph glyph-name="uniE04F" unicode="volume_off" 
+d="M256 427v-90l-45 45zM91 448l357 -357l-27 -27l-44 44q-37 -29 -78 -39v44q25 7 48 25l-91 91v-144l-107 107h-85v128h101l-101 101zM405 256q0 51 -29.5 90t-76.5 53v44q65 -14 107 -66.5t42 -120.5q0 -48 -22 -89l-32 33q11 27 11 56zM352 256q0 -9 -1 -13l-52 52v47
+q53 -26 53 -86z" />
+    <glyph glyph-name="uniE050" unicode="volume_up" 
+d="M299 443q65 -14 107 -66.5t42 -120.5t-42 -120.5t-107 -66.5v44q47 14 76.5 53t29.5 90t-29.5 90t-76.5 53v44zM352 256q0 -60 -53 -86v172q53 -26 53 -86zM64 320h85l107 107v-342l-107 107h-85v128z" />
+    <glyph glyph-name="uniE051" unicode="web" 
+d="M427 128v192h-86v-192h86zM320 235v85h-235v-85h235zM320 128v85h-235v-85h235zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE052" unicode="hd" 
+d="M309 224v64h43v-64h-43zM277 320v-128h86q9 0 15 6t6 15v86q0 9 -6 15t-15 6h-86zM235 192v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE053" unicode="sort_by_alpha" 
+d="M336 168h130v-34h-182v27l126 183h-125v34h177v-27zM106 221h83l-42 111zM130 378h35l96 -244h-39l-20 52h-109l-20 -52h-39zM219 99h99l-50 -50zM319 413h-101l50 50z" />
+    <glyph glyph-name="uniE055" unicode="airplay" 
+d="M448 448q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5t-30 -12.5h-85v42h85v256h-384v-256h85v-42h-85q-17 0 -30 12.5t-13 29.5v256q0 17 13 30t30 13h384zM128 43l128 128l128 -128h-256z" />
+    <glyph glyph-name="uniE056" unicode="forward_&#x31;&#x30;" 
+d="M282 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4q-3 -4 -3 -6v-43q3 -4 3 -6zM322 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-9 0 -13 2q-2 1 -5 3t-5 3q-9 5 -9 30v15q0 13 2 17l7 13q6 6 10 6q2 0 6.5 1
+t6.5 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM233 171h-20v70l-21 -6v15l38 12h3v-91zM85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50
+t-50.5 121z" />
+    <glyph glyph-name="uniE057" unicode="forward_&#x33;&#x30;" 
+d="M85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121zM284 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4
+q-2 -4 -2 -6v-43q2 -4 2 -6zM326 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-7 0 -23 8q-2 1 -6 13q-3 9 -3 17v15q0 11 3 17l6 13q7 6 11 6q2 0 6 1t6 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM213 224q15 0 15 13v4q-2 2 -2 4t-4 2h-11
+q-2 -2 -4 -2t-2 -4v-4h-22q0 8 5.5 15.5t12.5 7.5q1 0 5 1t5 1q12 0 24 -6q8 -4 8 -19v-7q-2 -4 -2 -6q0 -4 -4 -4q-2 0 -7 -5q9 -5 11 -8q4 -8 4 -13q0 -9 -2 -11q-1 -1 -3 -4t-3 -4q-4 -4 -11 -4q-2 0 -6.5 -1t-6.5 -1q-8 0 -10 2q-1 1 -5 2t-6 2q-9 5 -9 21h18v-4
+q2 -2 2 -4t4 -2h11q2 2 4 2t2 4v11q-2 2 -2 4t-4 2h-13v15h8z" />
+    <glyph glyph-name="uniE058" unicode="forward_&#x35;" 
+d="M250 222q-7 -3 -7 -4l-2 -3h-13l5 47h51v-15h-37l-2 -19q2 0 2 2q0 1 1.5 1.5t1.5 1.5h4h4q8 0 11 -3q1 -1 4 -3t4 -3q9 -9 9 -23q0 -9 -2 -11q-1 -1 -3 -5t-4 -6q-8 -8 -23 -8q-9 0 -11 2q-1 1 -4.5 2t-5.5 2q-9 5 -9 19h17q0 -10 13 -10q4 0 6 2l5 4q2 4 2 6v13l-2 4
+l-5 5q-4 2 -6 2h-4zM85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121z" />
+    <glyph glyph-name="uniE059" unicode="replay_&#x31;&#x30;" 
+d="M282 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4q-3 -4 -3 -6v-43q3 -4 3 -6zM324 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-9 0 -13 2q-2 1 -5 3t-5 3q-9 5 -9 30v15q0 13 2 17l7 13q6 6 10 6q2 0 6.5 1
+t6.5 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM233 171h-20v70l-21 -6v15l38 12h3v-91zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107
+v-86z" />
+    <glyph glyph-name="uniE05A" unicode="replay_&#x33;&#x30;" 
+d="M286 188q0 -7 11 -7q4 0 6 2l4 5q2 4 2 6v43q0 1 -1 3t-1 3q0 2 -4 4.5t-6 2.5q-4 0 -7 -3l-4 -4q-2 -4 -2 -6v-43q2 -4 2 -6zM326 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-7 0 -23 8q-2 1 -6 13q-3 9 -3 17v15q0 11 3 17l6 13q7 6 11 6q2 0 6 1
+t6 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM213 224q15 0 15 13v4q-2 2 -2 4t-4 2h-11q-2 -2 -4 -2t-2 -4v-4h-22q0 8 5.5 15.5t12.5 7.5q1 0 5 1t5 1q12 0 24 -6q8 -4 8 -19v-7q-2 -4 -2 -6q0 -4 -4 -4q-2 0 -7 -5q9 -5 11 -8q4 -8 4 -13q0 -9 -2 -11
+q-1 -1 -3 -4t-3 -4q-4 -4 -11 -4q-2 0 -6.5 -1t-6.5 -1q-8 0 -10 2q-1 1 -5 2t-6 2q-9 5 -9 21h18v-4q2 -2 2 -4t4 -2h11q2 2 4 2t2 4v11q-2 2 -2 4t-4 2h-13v15h8zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5
+t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE05B" unicode="replay_&#x35;" 
+d="M252 222q-7 -3 -7 -4l-2 -3h-15l5 47h51v-15h-37l-2 -19q2 0 2 2q0 1 1.5 1.5t1.5 1.5h4h4q8 0 11 -3q1 -1 4 -3t4 -3q9 -9 9 -23q0 -9 -2 -11q-1 -1 -3 -5t-4 -6t-4.5 -3.5t-3.5 -2.5q-2 -2 -13 -2q-9 0 -11 2q-1 1 -4.5 2t-5.5 2q-9 5 -9 19h17q0 -10 13 -10q4 0 6 2
+l5 4q2 4 2 6v13l-2 4l-5 5q-4 2 -6 2h-4zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE05C" unicode="add_to_queue" 
+d="M341 299v-43h-64v-64h-42v64h-64v43h64v64h42v-64h64zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE05D" unicode="fiber_dvr" 
+d="M448 267v21q0 14 -9 23t-23 9h-75v-128h32v43h25l18 -43h32l-19 45q19 9 19 30zM269 192l38 128h-32l-22 -73l-21 73h-32l37 -128h32zM171 224v64q0 14 -9.5 23t-22.5 9h-75v-128h75q13 0 22.5 9t9.5 23zM448 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -17 -12.5 -30
+t-30.5 -13h-384q-18 0 -30.5 13t-12.5 30v298q0 18 12.5 30.5t30.5 12.5h384zM96 288h43v-64h-43v64zM373 288h43v-21h-43v21z" />
+    <glyph glyph-name="uniE05E" unicode="fiber_new" 
+d="M437 213v107h-26v-96h-24v75h-27v-75h-24v96h-27v-107q0 -9 6.5 -15t15.5 -6h85q9 0 15 6t6 15zM288 293v27h-85v-128h85v27h-53v23h53v27h-53v24h53zM181 192v128h-26v-75l-54 75h-26v-128h26v75l55 -75h25zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5
+t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE05F" unicode="playlist_play" 
+d="M363 235l106 -64l-106 -64v128zM43 192v43h277v-43h-277zM405 405v-42h-362v42h362zM405 320v-43h-362v43h362z" />
+    <glyph glyph-name="uniE060" unicode="art_track" 
+d="M224 192l-48 64l-37 -48l-27 32l-37 -48h149zM256 320v-128q0 -17 -13 -30t-30 -13h-128q-17 0 -29.5 13t-12.5 30v128q0 17 12.5 30t29.5 13h128q17 0 30 -13t13 -30zM299 149v43h170v-43h-170zM469 363v-43h-170v43h170zM469 235h-170v42h170v-42z" />
+    <glyph glyph-name="uniE061" unicode="fiber_manual_record" 
+d="M85 256q0 70 50.5 120.5t120.5 50.5t120.5 -50.5t50.5 -120.5t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5z" />
+    <glyph glyph-name="uniE062" unicode="fiber_smart_record" 
+d="M363 421q56 -14 92 -60t36 -105t-36 -105t-92 -60v44q38 13 61.5 46t23.5 75t-23.5 75t-61.5 46v44zM21 256q0 70 50.5 120.5t120.5 50.5t120.5 -50.5t50.5 -120.5t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5z" />
+    <glyph glyph-name="uniE063" unicode="music_video" 
+d="M171 192q0 26 19 45t45 19q7 0 21 -4v132h107v-43h-64v-150q0 -26 -19 -44.5t-45 -18.5t-45 19t-19 45zM448 107v298h-384v-298h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE064" unicode="subscriptions" 
+d="M341 171l-128 69v-139zM469 256v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v171q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30zM384 469v-42h-256v42h256zM427 341h-342v43h342v-43z" />
+    <glyph glyph-name="uniE065" unicode="playlist_add_check" 
+d="M459 267l32 -32l-149 -150l-97 96l32 32l65 -64zM43 171v42h170v-42h-170zM299 384v-43h-256v43h256zM299 299v-43h-256v43h256z" />
+    <glyph glyph-name="uniE066" unicode="queue_play_next" 
+d="M512 128l-96 -96l-32 32l64 64l-64 64l32 32zM277 299h64v-43h-64v-64h-42v64h-64v43h64v64h42v-64zM448 448q17 0 30 -12.5t13 -30.5v-170h-43v170h-384v-256h320v-42h-43v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE067" unicode="remove_from_queue" 
+d="M341 299v-43h-170v43h170zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE068" unicode="slow_motion_video" 
+d="M469 256q0 -82 -55 -143t-136 -69v43q63 8 106 56.5t43 112.5t-43 112.5t-106 56.5v43q81 -8 136 -69t55 -143zM121 91l30 30q37 -28 84 -34v-43q-63 6 -114 47zM87 235q6 -47 34 -83l-30 -31q-41 51 -47 114h43zM121 361q-28 -37 -34 -84h-43q6 63 47 114zM235 425
+q-47 -6 -84 -34l-30 30q51 41 114 47v-43zM278 303l63 -47q-63 -47 -128 -96z" />
+    <glyph glyph-name="uniE069" unicode="web_asset" 
+d="M405 128v213h-298v-213h298zM405 427q18 0 30.5 -13t12.5 -30v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE06A" unicode="fiber_pin" 
+d="M427 192v128h-27v-75l-53 75h-27v-128h27v75l54 -75h26zM267 192v128h-32v-128h32zM192 267v21q0 14 -9 23t-23 9h-75v-128h32v43h43q14 0 23 9.5t9 22.5zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256
+q0 18 12 30.5t30 12.5h342zM117 288h43v-21h-43v21z" />
+    <glyph glyph-name="uniE06B" unicode="branding_watermark" 
+d="M448 107v128h-192v-128h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06C" unicode="call_to_action" 
+d="M448 107v64h-384v-64h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06D" unicode="featured_play_list" 
+d="M256 363v42h-192v-42h192zM256 277v43h-192v-43h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06E" unicode="featured_video" 
+d="M256 256v149h-192v-149h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06F" unicode="note" 
+d="M320 395v-118h117zM469 299v-171q0 -17 -12.5 -29.5t-29.5 -12.5l-342 -1q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h256z" />
+    <glyph glyph-name="uniE070" unicode="video_call" 
+d="M299 235v42h-64v64h-43v-64h-64v-42h64v-64h43v64h64zM363 288l85 85v-234l-85 85v-75q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h256q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE071" unicode="video_label" 
+d="M448 171v234h-384v-234h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE0AF" unicode="business" 
+d="M384 192v-43h-43v43h43zM384 277v-42h-43v42h43zM427 107v213h-171v-43h43v-42h-43v-43h43v-43h-43v-42h171zM213 363v42h-42v-42h42zM213 277v43h-42v-43h42zM213 192v43h-42v-43h42zM213 107v42h-42v-42h42zM128 363v42h-43v-42h43zM128 277v43h-43v-43h43zM128 192v43
+h-43v-43h43zM128 107v42h-43v-42h43zM256 363h213v-299h-426v384h213v-85z" />
+    <glyph glyph-name="uniE0B0" unicode="call" 
+d="M141 282q48 -93 141 -141l47 47q6.66667 6.66667 14.2222 6.66667q3.77778 0 7.77778 -1.66667q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q1.23077 -4 1.23077 -7.6213
+q0 -8.14793 -6.23077 -14.3787z" />
+    <glyph glyph-name="uniE0B1" unicode="call_end" 
+d="M256 320q-52 0 -98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-25 24 -57 39q-12 5 -12 19v66q-50 16 -98 16z" />
+    <glyph glyph-name="uniE0B2" unicode="call_made" 
+d="M192 405h213v-213h-42v141l-248 -248l-30 30l248 248h-141v42z" />
+    <glyph glyph-name="uniE0B3" unicode="call_merge" 
+d="M160 341l96 96l96 -96h-75v-136l-128 -128l-30 30l116 115v119h-75zM363 77l-73 72l30 30l73 -72z" />
+    <glyph glyph-name="uniE0B4" unicode="call_missed" 
+d="M418 363l30 -30l-192 -192l-149 149v-98h-43v171h171v-43h-98l119 -119z" />
+    <glyph glyph-name="uniE0B5" unicode="call_received" 
+d="M427 397l-248 -248h141v-42h-213v213h42v-141l248 248z" />
+    <glyph glyph-name="uniE0B6" unicode="call_split" 
+d="M213 427l-49 -49l113 -113v-180h-42v162l-101 101l-49 -49v128h128zM299 427h128v-128l-49 49l-62 -62l-30 30l62 62z" />
+    <glyph glyph-name="uniE0B7" unicode="chat" 
+d="M384 341v43h-256v-43h256zM299 213v43h-171v-43h171zM128 320v-43h256v43h-256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0B8" unicode="clear_all" 
+d="M149 363h299v-43h-299v43zM64 149v43h299v-43h-299zM107 235v42h298v-42h-298z" />
+    <glyph glyph-name="uniE0B9" unicode="comment" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM469 427v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -29.5z" />
+    <glyph glyph-name="uniE0BA" unicode="contacts" 
+d="M363 149v32q0 24 -36.5 39t-70.5 15t-70.5 -15t-36.5 -39v-32h214zM256 368q-20 0 -34 -14t-14 -34t14 -34t34 -14t34 14t14 34t-14 34t-34 14zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13
+h342zM85 0v43h342v-43h-342zM427 512v-43h-342v43h342z" />
+    <glyph glyph-name="uniE0BB" unicode="dialer_sip" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM427 405v22h-22v-22h22zM384 448h64v-64h-43v-43h-21v107z
+M320 405v-64h-64v22h43v21h-43v64h64v-21h-43v-22h43zM363 448v-107h-22v107h22z" />
+    <glyph glyph-name="uniE0BC" unicode="dialpad" 
+d="M256 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 363q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 363q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 235q17 0 30 -13t13 -30t-13 -30
+t-30 -13t-30 13t-13 30t13 30t30 13zM256 235q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 405q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13zM128 235q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 363
+q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 107q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE0BE" unicode="email" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE0BF" unicode="forum" 
+d="M363 256q0 -9 -6.5 -15t-15.5 -6h-213l-85 -86v299q0 9 6 15t15 6h277q9 0 15.5 -6t6.5 -15v-192zM448 384q9 0 15 -6t6 -15v-320l-85 85h-235q-9 0 -15 6t-6 15v43h277v192h43z" />
+    <glyph glyph-name="uniE0C3" unicode="import_export" 
+d="M341 149h64l-85 -85l-85 85h64v150h42v-150zM192 448l85 -85h-64v-150h-42v150h-64z" />
+    <glyph glyph-name="uniE0C4" unicode="invert_colors_off" 
+d="M256 403l-49 -48l-30 30l79 79l121 -121q38 -38 47 -91.5t-13 -100.5l-155 154v98zM256 94v103l-102 102q-26 -34 -26 -77q0 -52 38 -90t90 -38zM441 67l7 -8l-27 -27l-58 58q-47 -38 -107 -38q-71 0 -121 50q-46 47 -49.5 112.5t37.5 115.5l-59 59l27 27
+q299 -299 350 -349z" />
+    <glyph glyph-name="uniE0C6" unicode="live_help" 
+d="M321 293q20 20 20 48q0 35 -25 60.5t-60 25.5t-60 -25.5t-25 -60.5h42q0 17 13 30t30 13t30 -13t13 -30t-13 -30l-26 -27q-25 -27 -25 -60v-11h42q0 34 25 61zM277 128v43h-42v-43h42zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64
+h-85q-18 0 -30.5 13t-12.5 30v299q0 17 12.5 29.5t30.5 12.5h298z" />
+    <glyph glyph-name="uniE0C7" unicode="location_off" 
+d="M250 267q109 -108 177 -176l-27 -27l-72 71q-16 -24 -34 -47t-28 -34l-10 -11q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 11 4 33l-68 68l27 27l178 -178zM256 373q-23 0 -39 -18l-69 68q44 46 108 46q62 0 105.5 -43.5t43.5 -105.5q0 -48 -36 -117l-77 78
+q17 15 17 39q0 22 -15.5 37.5t-37.5 15.5z" />
+    <glyph glyph-name="uniE0C8" unicode="location_on" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE0C9" unicode="message" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CA" unicode="chat_bubble" 
+d="M427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CB" unicode="chat_bubble_outline" 
+d="M427 171v256h-342v-299l43 43h299zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CC" unicode="no_sim" 
+d="M78 429l373 -372l-28 -28l-40 41q-12 -6 -20 -6h-214q-17 0 -29.5 13t-12.5 30v239l-56 56zM405 405v-249l-242 242l50 50h150q17 0 29.5 -13t12.5 -30z" />
+    <glyph glyph-name="uniE0CD" unicode="phone" 
+d="M141 282q48 -93 141 -141l47 47q10 10 22 5q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22z" />
+    <glyph glyph-name="uniE0CE" unicode="portable_wifi_off" 
+d="M70 459l21 -22l357 -357l-27 -27l-160 161h-1l-4 -1q-17 0 -30 13t-13 30l1 4l-34 34q-9 -18 -9 -38q0 -49 42 -74l-21 -37q-29 17 -46.5 46.5t-17.5 64.5q0 38 20 69l-30 31q-33 -46 -33 -100q0 -46 23 -85.5t62 -62.5l-21 -37q-49 28 -77.5 77.5t-28.5 107.5
+q0 74 44 131l-44 45zM256 427q-43 0 -80 -20l-31 31q50 31 111 31q88 0 150.5 -62.5t62.5 -150.5q0 -61 -31 -111l-32 31q21 39 21 80q0 70 -50.5 120.5t-120.5 50.5zM375 208l-35 35q1 4 1 13q0 35 -25 60t-60 25q-9 0 -13 -1l-35 35q22 9 48 9q53 0 90.5 -37.5t37.5 -90.5
+q0 -26 -9 -48z" />
+    <glyph glyph-name="uniE0CF" unicode="contact_phone" 
+d="M381 213q-8 21 -8 43t8 43h35l32 42l-42 43q-44 -33 -59 -85q-6 -21 -6 -43t6 -43q15 -52 59 -85l42 43l-32 42h-35zM299 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM171 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM469 448
+q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h426z" />
+    <glyph glyph-name="uniE0D0" unicode="contact_mail" 
+d="M469 256v128h-170v-128h170zM299 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM171 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM469 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30v298
+q0 17 13 30t30 13h426zM448 341l-64 -42l-64 42v22l64 -43l64 43v-22z" />
+    <glyph glyph-name="uniE0D1" unicode="ring_volume" 
+d="M137 303q-74 74 -76 75l30 31l76 -76zM277 469v-106h-42v106h42zM451 378l-76 -75l-30 30l76 76zM506 156q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-28 26 -57 40q-12 5 -12 19v66q-46 15 -98 15t-98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6
+l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100z" />
+    <glyph glyph-name="uniE0D2" unicode="speaker_phone" 
+d="M320 85v171h-128v-171h128zM317 298q10 0 17 -7t7 -17v-207q0 -10 -7 -17t-17 -7h-122q-10 0 -17 7t-7 17v207q0 10 7 17.5t17 7.5zM256 491q96 0 165 -69l-30 -30q-56 56 -135 56t-135 -56l-30 30q69 69 165 69zM149 361q44 44 107 44t107 -44l-31 -30q-31 31 -76 31
+t-76 -31z" />
+    <glyph glyph-name="uniE0D3" unicode="stay_current_landscape" 
+d="M405 363h-298v-214h298v214zM22 363q0 17 12.5 29.5t29.5 12.5h384q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5z" />
+    <glyph glyph-name="uniE0D4" unicode="stay_current_portrait" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE0D5" unicode="stay_primary_landscape" 
+d="M405 363h-298v-214h298v214zM22 363q0 17 12.5 29.5t29.5 12.5h384q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5z" />
+    <glyph glyph-name="uniE0D6" unicode="stay_primary_portrait" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE0D7" unicode="swap_calls" 
+d="M384 427l85 -86h-64v-149q0 -35 -25 -60t-60 -25t-60 25t-25 60v149q0 17 -13 30t-30 13t-30 -13t-13 -30v-149h64l-85 -85l-85 85h64v149q0 35 25 60.5t60 25.5t60 -25.5t25 -60.5v-149q0 -17 13 -30t30 -13t30 13t13 30v149h-64z" />
+    <glyph glyph-name="uniE0D8" unicode="textsms" 
+d="M363 277v43h-43v-43h43zM277 277v43h-42v-43h42zM192 277v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0D9" unicode="voicemail" 
+d="M395 192q31 0 52.5 22t21.5 53t-21.5 52.5t-52.5 21.5t-53 -21.5t-22 -52.5t22 -53t53 -22zM117 192q31 0 53 22t22 53t-22 52.5t-53 21.5t-52.5 -21.5t-21.5 -52.5t21.5 -53t52.5 -22zM395 384q49 0 83 -34t34 -83t-34 -83.5t-83 -34.5h-278q-49 0 -83 34.5t-34 83.5
+t34 83t83 34t83.5 -34t34.5 -83q0 -43 -27 -75h96q-27 32 -27 75q0 49 34.5 83t83.5 34z" />
+    <glyph glyph-name="uniE0DA" unicode="vpn_key" 
+d="M149 213q17 0 30 13t13 30t-13 30t-30 13t-29.5 -13t-12.5 -30t12.5 -30t29.5 -13zM270 299h221v-86h-43v-85h-85v85h-93q-13 -38 -46 -61.5t-75 -23.5q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q42 0 75 -23.5t46 -61.5z" />
+    <glyph glyph-name="uniE0DB" unicode="phonelink_erase" 
+d="M405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213zM277 337l-85 -85l85 -86l-21 -21l-85 85l-86 -85l-21 21l85 86l-85 85l21 21l86 -85l85 85z" />
+    <glyph glyph-name="uniE0DC" unicode="phonelink_lock" 
+d="M203 277v32q0 12 -9.5 20t-22.5 8t-22.5 -8t-9.5 -20v-32h64zM230 277q10 0 18 -8t8 -19v-75q0 -10 -8.5 -18t-19.5 -8h-117q-10 0 -18 8.5t-8 19.5v75q0 10 8 17.5t18 7.5v32q0 22 18.5 38t41.5 16t41 -16t18 -38v-32zM405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30
+t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213z" />
+    <glyph glyph-name="uniE0DD" unicode="phonelink_ring" 
+d="M299 85v342h-214v-342h214zM299 491q17 0 29.5 -13t12.5 -30v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13h214zM384 303q20 -21 20 -47t-20 -45l-21 22q18 25 0 49zM429 348q40 -38 40 -91.5t-40 -90.5l-22 22q29 31 29 70.5
+t-29 67.5z" />
+    <glyph glyph-name="uniE0DE" unicode="phonelink_setup" 
+d="M405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213zM171 213q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13t-30 -13t-13 -30t13 -30t30 -13zM252 245l23 -19q4 -4 2 -6l-21 -37
+q-2 -2 -6 -2l-28 11q-14 -9 -19 -11l-5 -27q-5 -5 -6 -5h-43q-2 0 -3.5 2t-0.5 3l-4 27q-5 2 -19 11l-30 -9q-2 -2 -7 3l-21 36q0 4 2 8l24 17v22l-24 17q-4 4 -2 6l21 37q2 2 7 2l27 -11q13 9 20 11l4 27q5 5 6 5h43q6 0 6 -5l5 -27q5 -2 19 -11l28 9q2 1 6 -3l21 -36
+q0 -4 -2 -6l-23 -17v-22z" />
+    <glyph glyph-name="uniE0DF" unicode="present_to_all" 
+d="M213 256h-42l85 85l85 -85h-42v-85h-86v85zM448 106v300h-384v-300h384zM448 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE0E0" unicode="import_contacts" 
+d="M448 117v246q-33 10 -75 10q-65 0 -117 -32v-245q52 32 117 32q39 0 75 -11zM373 416q76 0 118 -32v-311q0 -4 -3.5 -7.5t-7.5 -3.5q-3 0 -5 1q-41 22 -102 22q-65 0 -117 -32q-43 32 -117 32q-54 0 -102 -23q-1 0 -2.5 -0.5t-2.5 -0.5q-4 0 -7.5 3t-3.5 7v313
+q43 32 118 32q74 0 117 -32q43 32 117 32z" />
+    <glyph glyph-name="uniE0E1" unicode="mail_outline" 
+d="M256 277l171 107h-342zM427 128v213l-171 -106l-171 106v-213h342zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE0E2" unicode="screen_share" 
+d="M277 203l86 80l-86 80v-46q-105 -15 -128 -125q43 58 128 58v-47zM427 128h85v-43h-512v43h85q-18 0 -30 12.5t-12 30.5v213q0 18 12 30.5t30 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE0E3" unicode="stop_screen_share" 
+d="M149 192q29 40 78 52l-34 34q-32 -31 -44 -86zM51 475l421 -421l-27 -27l-58 58h-387v43h85q-18 0 -30 12.5t-12 29.5v214q0 19 14 31l-33 33zM469 170q0 -25 -22 -37l-118 118l34 32l-86 79v-45q-2 -1 -5.5 -1t-5.5 -1l-112 111h273q17 0 29.5 -12t12.5 -30v-214z
+M453 128h59v-43h-17z" />
+    <glyph glyph-name="uniE0E4" unicode="call_missed_outgoing" 
+d="M64 333l30 30l162 -162l119 119h-98v43h171v-171h-43v98l-149 -149z" />
+    <glyph glyph-name="uniE0E5" unicode="rss_feed" 
+d="M85 297q88 0 150 -62t62 -150h-61q0 62 -44.5 106.5t-106.5 44.5v61zM85 417q137 0 234.5 -97.5t97.5 -234.5h-60q0 113 -79.5 192.5t-192.5 79.5v60zM85 132q0 19 13.5 32.5t33.5 13.5t33 -13t13 -33t-13.5 -33.5t-32.5 -13.5q-20 0 -33.5 13.5t-13.5 33.5z" />
+    <glyph glyph-name="uniE145" unicode="add" 
+d="M405 235h-128v-128h-42v128h-128v42h128v128h42v-128h128v-42z" />
+    <glyph glyph-name="uniE146" unicode="add_box" 
+d="M363 235v42h-86v86h-42v-86h-86v-42h86v-86h42v86h86zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE147" unicode="add_circle" 
+d="M363 235v42h-86v86h-42v-86h-86v-42h86v-86h42v86h86zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE148" unicode="add_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86h86v-42h-86v-86h-42
+v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE149" unicode="archive" 
+d="M109 405h294l-20 22h-256zM256 139l117 117h-74v43h-86v-43h-74zM438 400q10 -12 10 -27v-266q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v266q0 15 10 27l29 36q10 12 25 12h256q15 0 25 -12z" />
+    <glyph glyph-name="uniE14A" unicode="backspace" 
+d="M405 179l-76 77l76 77l-30 30l-76 -77l-77 77l-30 -30l77 -77l-77 -77l30 -30l77 77l76 -77zM469 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-320q-20 0 -34 19l-115 173l115 173q14 19 34 19h320z" />
+    <glyph glyph-name="uniE14B" unicode="block" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 60 -36 105l-240 -240q45 -36 105 -36zM85 256q0 -60 36 -105l240 240q-45 36 -105 36q-70 0 -120.5 -50.5t-50.5 -120.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE14C" unicode="clear" 
+d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" />
+    <glyph glyph-name="uniE14D" unicode="content_copy" 
+d="M405 64v299h-234v-299h234zM405 405q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-234q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h234zM341 491v-43h-256v-299h-42v299q0 17 12.5 30t29.5 13h256z" />
+    <glyph glyph-name="uniE14E" unicode="content_cut" 
+d="M405 448h64v-21l-149 -150l-43 43zM256 245q11 0 11 11t-11 11t-11 -11t11 -11zM128 85q17 0 30 12.5t13 30.5t-13 30.5t-30 12.5t-30 -12.5t-13 -30.5t13 -30.5t30 -12.5zM128 341q17 0 30 12.5t13 30.5t-13 30.5t-30 12.5t-30 -12.5t-13 -30.5t13 -30.5t30 -12.5z
+M206 349l263 -264v-21h-64l-149 149l-50 -50q7 -15 7 -35q0 -35 -25 -60t-60 -25t-60 25t-25 60t25 60t60 25q20 0 35 -7l50 50l-50 50q-15 -7 -35 -7q-35 0 -60 25t-25 60t25 60t60 25t60 -25t25 -60q0 -20 -7 -35z" />
+    <glyph glyph-name="uniE14F" unicode="content_paste" 
+d="M405 85v342h-42v-64h-214v64h-42v-342h298zM256 469q-9 0 -15 -6t-6 -15t6 -15t15 -6t15 6t6 15t-6 15t-15 6zM405 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h89q7 19 23 31t37 12t37 -12
+t23 -31h89z" />
+    <glyph glyph-name="uniE150" unicode="create" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE151" unicode="drafts" 
+d="M256 235l176 110l-176 103l-176 -103zM469 341v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v213q0 25 20 37l193 113l193 -113q20 -12 20 -37z" />
+    <glyph glyph-name="uniE152" unicode="filter_list" 
+d="M128 235v42h256v-42h-256zM64 384h384v-43h-384v43zM213 128v43h86v-43h-86z" />
+    <glyph glyph-name="uniE153" unicode="flag" 
+d="M307 384h120v-213h-150l-8 42h-120v-149h-42v363h192z" />
+    <glyph glyph-name="uniE154" unicode="forward" 
+d="M256 341v86l171 -171l-171 -171v86h-171v170h171z" />
+    <glyph glyph-name="uniE155" unicode="gesture" 
+d="M296 116q14 0 28 18.5t18 56.5q-30 -8 -46 -27t-16 -32q0 -7 5 -11.5t11 -4.5zM98 365l-37 36q10 12 18 20q27 27 58 27q19 0 36.5 -15t17.5 -46q0 -30 -28 -70q-28 -39 -39 -75q-6 -17 -3.5 -29t10.5 -12q9 0 24 18q22 22 49 58q48 60 105 60q42 0 62.5 -27.5
+t23.5 -61.5h53v-53h-52q-6 -69 -37 -100t-64 -31q-28 0 -48 19.5t-20 46.5q0 33 30 69t85 46q-1 8 -1.5 11t-3 9.5t-6 9.5t-10.5 5.5t-17 2.5q-28 0 -87 -73q-17 -21 -23.5 -28.5t-18.5 -17.5t-23 -13q-35 -11 -60 13t-25 60q0 15 5.5 33t14.5 35t17 30.5t15.5 24.5t8.5 12
+q17 28 6 32q-7 3 -36 -26z" />
+    <glyph glyph-name="uniE156" unicode="inbox" 
+d="M405 192v213h-299v-213h86q0 -26 19 -45t45 -19t45 19t19 45h85zM405 448q17 0 30 -12.5t13 -30.5v-298q0 -17 -13 -30t-30 -13h-299q-18 0 -30 12.5t-12 30.5v298q0 18 12 30.5t30 12.5h299z" />
+    <glyph glyph-name="uniE157" unicode="link" 
+d="M363 363q44 0 75 -31.5t31 -75.5t-31 -75.5t-75 -31.5h-86v41h86q27 0 46.5 19.5t19.5 46.5t-19.5 46.5t-46.5 19.5h-86v41h86zM171 235v42h170v-42h-170zM83 256q0 -27 19.5 -46.5t46.5 -19.5h86v-41h-86q-44 0 -75 31.5t-31 75.5t31 75.5t75 31.5h86v-41h-86
+q-27 0 -46.5 -19.5t-19.5 -46.5z" />
+    <glyph glyph-name="uniE158" unicode="mail" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE159" unicode="markunread" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE15A" unicode="redo" 
+d="M393 286l76 77v-192h-192l78 77q-48 40 -110 40q-56 0 -100.5 -32.5t-61.5 -84.5l-50 16q22 68 80.5 111t131.5 43q85 0 148 -55z" />
+    <glyph glyph-name="uniE15B" unicode="remove" 
+d="M405 235h-298v42h298v-42z" />
+    <glyph glyph-name="uniE15C" unicode="remove_circle" 
+d="M363 235v42h-214v-42h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE15D" unicode="remove_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 277h214v-42h-214v42z" />
+    <glyph glyph-name="uniE15E" unicode="reply" 
+d="M213 320q104 -15 160.5 -79.5t74.5 -155.5q-77 109 -235 109v-87l-149 149l149 149v-85z" />
+    <glyph glyph-name="uniE15F" unicode="reply_all" 
+d="M277 320q104 -15 160.5 -79.5t74.5 -155.5q-77 109 -235 109v-87l-149 149l149 149v-85zM149 341l-85 -85l85 -85v-64l-149 149l149 149v-64z" />
+    <glyph glyph-name="uniE160" unicode="report" 
+d="M277 235v128h-42v-128h42zM256 143q11 0 19.5 8.5t8.5 19.5t-8.5 19t-19.5 8t-19.5 -8t-8.5 -19t8.5 -19.5t19.5 -8.5zM336 448l112 -112v-160l-112 -112h-160l-112 112v160l112 112h160z" />
+    <glyph glyph-name="uniE161" unicode="save" 
+d="M320 320v85h-213v-85h213zM256 107q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM363 448l85 -85v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h256z" />
+    <glyph glyph-name="uniE162" unicode="select_all" 
+d="M192 320v-128h128v128h-128zM149 149v214h214v-214h-214zM320 405v43h43v-43h-43zM320 64v43h43v-43h-43zM405 149v43h43v-43h-43zM405 320v43h43v-43h-43zM405 64v43h43q0 -17 -13 -30t-30 -13zM405 235v42h43v-42h-43zM235 64v43h42v-43h-42zM192 448v-43h-43v43h43z
+M64 149v43h43v-43h-43zM107 64q-17 0 -30 13t-13 30h43v-43zM405 448q17 0 30 -13t13 -30h-43v43zM277 448v-43h-42v43h42zM64 320v43h43v-43h-43zM149 64v43h43v-43h-43zM64 235v42h43v-42h-43zM64 405q0 17 13 30t30 13v-43h-43z" />
+    <glyph glyph-name="uniE163" unicode="send" 
+d="M43 64v149l320 43l-320 43v149l448 -192z" />
+    <glyph glyph-name="uniE164" unicode="sort" 
+d="M64 235v42h256v-42h-256zM64 384h384v-43h-384v43zM64 128v43h128v-43h-128z" />
+    <glyph glyph-name="uniE165" unicode="text_format" 
+d="M256 384l-40 -107h80zM203 239l-20 -47h-44l101 235h32l101 -235h-44l-20 47h-106zM107 149h298v-42h-298v42z" />
+    <glyph glyph-name="uniE166" unicode="undo" 
+d="M267 341q73 0 131 -43t81 -111l-50 -16q-17 52 -61.5 84.5t-100.5 32.5q-62 0 -110 -40l78 -77h-192v192l76 -77q63 55 148 55z" />
+    <glyph glyph-name="uniE167" unicode="font_download" 
+d="M340 117h45l-109 278h-40l-109 -278h45l24 64h120zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342zM212 224l44 118l44 -118h-88z" />
+    <glyph glyph-name="uniE168" unicode="move_to_inbox" 
+d="M341 299l-85 -86l-85 86h42v64h86v-64h42zM405 192v213h-299v-213h86q0 -26 19 -45t45 -19t45 19t19 45h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-299q-18 0 -30 12.5t-12 30.5v298q0 18 12 30.5t30 12.5h299z" />
+    <glyph glyph-name="uniE169" unicode="unarchive" 
+d="M109 405h294l-20 22h-256zM256 309l-117 -117h74v-43h86v43h74zM438 401q10 -12 10 -28v-266q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v266q0 16 10 28l29 35q10 12 25 12h256q15 0 25 -12z" />
+    <glyph glyph-name="uniE16A" unicode="next_week" 
+d="M235 117l85 86l-85 85l-22 -21l64 -64l-64 -64zM213 405v-42h86v42h-86zM299 448q17 0 29.5 -12.5t12.5 -30.5v-42h86q17 0 29.5 -13t12.5 -30v-235q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v235q0 17 12.5 30t29.5 13h86v42q0 17 12.5 30
+t29.5 13h86z" />
+    <glyph glyph-name="uniE16B" unicode="weekend" 
+d="M384 405q17 0 30 -12.5t13 -29.5v-46q-19 -7 -31 -23t-12 -37v-44h-256v44q0 21 -12 37t-31 23v46q0 17 13 29.5t30 12.5h256zM448 299q17 0 30 -13t13 -30v-107q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v107q0 17 13 30t30 13t30 -13t13 -30v-64h298v64
+q0 17 13 30t30 13z" />
+    <glyph glyph-name="uniE16C" unicode="delete_sweep" 
+d="M299 405v-42h-256v42h64l21 22h85l22 -22h64zM64 128v213h213v-213q0 -17 -12.5 -30t-29.5 -13h-128q-17 0 -30 13t-13 30zM320 256h128v-43h-128v43zM320 341h149v-42h-149v42zM320 171h85v-43h-85v43z" />
+    <glyph glyph-name="uniE16D" unicode="low_priority" 
+d="M43 267q0 57 40.5 97.5t97.5 40.5h75v-42h-75q-40 0 -68 -28t-28 -68t28 -68t68 -28h11v42l64 -64l-64 -64v43h-11q-57 0 -97.5 41t-40.5 98zM299 171h170v-43h-170v43zM299 288h170v-43h-170v43zM299 405h170v-42h-170v42z" />
+    <glyph glyph-name="uniE190" unicode="access_alarm" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM267 341v-112l85 -50l-16 -26l-101 60v128h32zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE191" unicode="access_alarms" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136t56 136t136 56zM267 341v-113l85 -51l-17 -26l-100 62v128h32zM169 439l-99 -81l-27 32
+l98 81zM469 390l-27 -32l-99 84l28 32z" />
+    <glyph glyph-name="uniE192" unicode="access_time" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE193" unicode="add_alarm" 
+d="M277 320v-64h64v-43h-64v-64h-42v64h-64v43h64v64h42zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE194" unicode="airplanemode_inactive" 
+d="M64 400l27 27l336 -336l-27 -27l-123 122v-79l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l127 80zM277 320l171 -107v-42l-68 21l-167 167v78q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117z" />
+    <glyph glyph-name="uniE195" unicode="airplanemode_active" 
+d="M217 320zM448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE19C" unicode="battery_alert" 
+d="M277 213v107h-42v-107h42zM277 128v43h-42v-43h42zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A3" unicode="battery_charging_full" 
+d="M235 85l85 160h-43v118l-85 -160h43v-118zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A4" unicode="battery_full" 
+d="M334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A5" unicode="battery_std" 
+d="M334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A6" unicode="battery_unknown" 
+d="M305 241q15 15 15 36q0 26 -19 45t-45 19t-45 -19t-19 -45h32q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5t-9 -22l-20 -20q-20 -20 -20 -43h34q0 16 18 34zM276 129v41h-40v-41h40zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20
+v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A7" unicode="bluetooth" 
+d="M317 164l-40 41v-81zM277 388v-81l40 41zM378 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21z" />
+    <glyph glyph-name="uniE1A8" unicode="bluetooth_connected" 
+d="M405 299l43 -43l-43 -43l-42 43zM317 164l-40 41v-81zM277 388v-81l40 41zM378 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM149 256l-42 -43l-43 43l43 43z" />
+    <glyph glyph-name="uniE1A9" unicode="bluetooth_disabled" 
+d="M277 124l40 40l-40 41v-81zM115 427l312 -312l-30 -30l-49 49l-92 -91h-21v162l-98 -98l-30 30l119 119l-141 141zM277 388v-69l-42 43v107h21l122 -121l-65 -65l-30 30l34 35z" />
+    <glyph glyph-name="uniE1AA" unicode="bluetooth_searching" 
+d="M275 164l-40 41v-81zM235 388v-81l40 41zM335 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM417 369q31 -50 31 -111t-33 -113l-25 25q21 42 21 86t-21 86zM304 256l49 49q10 -25 10 -49q0 -25 -10 -50z" />
+    <glyph glyph-name="uniE1AB" unicode="brightness_auto" 
+d="M305 171h41l-69 192h-42l-69 -192h41l15 42h68zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100zM231 242l25 78l25 -78h-50z" />
+    <glyph glyph-name="uniE1AC" unicode="brightness_high" 
+d="M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100
+l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE1AD" unicode="brightness_low" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE1AE" unicode="brightness_medium" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-256zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE1AF" unicode="data_usage" 
+d="M256 107q72 0 116 56l56 -33q-64 -87 -172 -87q-88 0 -150.5 62.5t-62.5 150.5q0 83 55.5 143.5t136.5 68.5v-64q-54 -8 -91 -50t-37 -98q0 -62 43.5 -105.5t105.5 -43.5zM277 468q81 -8 136.5 -68.5t55.5 -143.5q0 -48 -18 -87l-56 33q10 28 10 54q0 56 -37 98t-91 50
+v64z" />
+    <glyph glyph-name="uniE1B0" unicode="developer_mode" 
+d="M363 107v42h42v-85q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v85h42v-42h214zM213 188l-30 -30l-98 98l98 98l30 -30l-67 -68zM329 158l-30 30l67 68l-67 68l30 30l98 -98zM149 405v-42h-42v85q0 17 12.5 30t29.5 13l214 -1q17 0 29.5 -12.5t12.5 -29.5
+v-85h-42v42h-214z" />
+    <glyph glyph-name="uniE1B1" unicode="devices" 
+d="M469 149v150h-85v-150h85zM491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15.5 6.5t-6.5 15.5v213q0 9 6.5 15t15.5 6h128zM85 384v-235h214v-64h-299v64h43v235q0 17 12.5 30t29.5 13h384v-43h-384z" />
+    <glyph glyph-name="uniE1B2" unicode="dvr" 
+d="M149 256v-43h-42v43h42zM149 341v-42h-42v42h42zM405 256v-43h-234v43h234zM405 341v-42h-234v42h234zM448 149v256h-384v-256h384zM448 448q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-17 0 -30 12.5t-13 29.5v256q0 17 13 30
+t30 13h384z" />
+    <glyph glyph-name="uniE1B3" unicode="gps_fixed" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE1B4" unicode="gps_not_fixed" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+" />
+    <glyph glyph-name="uniE1B5" unicode="gps_off" 
+d="M347 138l-209 209q-31 -41 -31 -91q0 -62 43.5 -105.5t105.5 -43.5q50 0 91 31zM64 421l27 27l357 -357l-27 -27l-44 44q-45 -37 -100 -43v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q6 55 43 100zM447 277h44v-42h-44q-5 -39 -21 -68l-32 32q11 27 11 57
+q0 62 -43.5 105.5t-105.5 43.5q-30 0 -57 -11l-32 32q31 16 68 21v44h42v-44q67 -7 115 -55t55 -115z" />
+    <glyph glyph-name="uniE1B6" unicode="location_disabled" 
+d="M347 138l-209 209q-31 -41 -31 -91q0 -62 43.5 -105.5t105.5 -43.5q50 0 91 31zM64 421l27 27l357 -357l-27 -27l-44 44q-45 -37 -100 -43v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q6 55 43 100zM447 277h44v-42h-44q-5 -39 -21 -68l-32 32q11 27 11 57
+q0 62 -43.5 105.5t-105.5 43.5q-30 0 -57 -11l-32 32q31 16 68 21v44h42v-44q67 -7 115 -55t55 -115z" />
+    <glyph glyph-name="uniE1B7" unicode="location_searching" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+" />
+    <glyph glyph-name="uniE1B8" unicode="graphic_eq" 
+d="M405 299h43v-86h-43v86zM320 128v256h43v-256h-43zM64 213v86h43v-86h-43zM235 43v426h42v-426h-42zM149 128v256h43v-256h-43z" />
+    <glyph glyph-name="uniE1B9" unicode="network_cell" 
+d="M469 43h-426l426 426v-426z" />
+    <glyph glyph-name="uniE1BA" unicode="network_wifi" 
+d="M436 279l1 -1l-181 -224l-181 224l1 1l-68 84q121 85 248 85t248 -85z" />
+    <glyph glyph-name="uniE1BB" unicode="nfc" 
+d="M384 384v-256h-256v256h85v-43h-42v-170h170v170h-64v-48q22 -12 22 -37q0 -17 -13 -30t-30 -13t-30 13t-13 30q0 25 22 37v48q0 17 12.5 30t29.5 13h107zM427 85v342h-342v-342h342zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342
+q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE1BC" unicode="now_wallpaper" 
+d="M85 235v-150h150v-42h-150q-17 0 -29.5 12.5t-12.5 29.5v150h42zM427 85v150h42v-150q0 -17 -12.5 -29.5t-29.5 -12.5h-150v42h150zM427 469q17 0 29.5 -12.5t12.5 -29.5v-150h-42v150h-150v42h150zM363 331q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5
+t22.5 9.5t22.5 -9.5t9.5 -22.5zM213 235l64 -79l43 57l64 -85h-256zM85 427v-150h-42v150q0 17 12.5 29.5t29.5 12.5h150v-42h-150z" />
+    <glyph glyph-name="uniE1BD" unicode="now_widgets" 
+d="M355 476l121 -121l-121 -120h93v-171h-171v171h78l-120 120v-78h-171v171h171v-93zM64 64v171h171v-171h-171z" />
+    <glyph glyph-name="uniE1BE" unicode="screen_lock_landscape" 
+d="M230 299v-22h52v22q0 10 -7.5 17.5t-18.5 7.5t-18.5 -7.5t-7.5 -17.5zM213 171q-9 0 -15 6t-6 15v64q0 9 6 15t15 6v22q0 17 12.5 29.5t30.5 12.5t30.5 -12t12.5 -30v-22q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-86zM405 149v214h-298v-214h298zM448 405q17 0 30 -12.5
+t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE1BF" unicode="screen_lock_portrait" 
+d="M363 107v298h-214v-298h214zM363 491q17 0 29.5 -13t12.5 -30v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13h214zM230 299v-22h52v22q0 10 -7.5 17.5t-18.5 7.5t-18.5 -7.5t-7.5 -17.5zM213 171q-9 0 -15 6t-6 15v64q0 9 6 15
+t15 6v22q0 17 12.5 29.5t30.5 12.5t30.5 -12t12.5 -30v-22q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-86z" />
+    <glyph glyph-name="uniE1C0" unicode="screen_lock_rotation" 
+d="M358 459v-11h73v11q0 15 -10.5 25.5t-25.5 10.5t-26 -10.5t-11 -25.5zM341 320q-9 0 -15 6t-6 15v86q0 9 6 15t15 6v11q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5v-11q9 0 15 -6t6 -15v-86q0 -9 -6 -15t-15 -6h-107zM181 75l28 28l81 -81l-14 -1q-100 0 -173.5 68
+t-81.5 167h32q6 -59 40.5 -107.5t87.5 -73.5zM496 240q10 -9 10 -22.5t-10 -23.5l-136 -135q-9 -10 -22 -10t-23 10l-256 256q-10 9 -10 22t10 23l135 136q9 10 22.5 10t23.5 -10l52 -52l-30 -30l-45 44l-121 -120l242 -242l120 121l-47 47l30 30z" />
+    <glyph glyph-name="uniE1C1" unicode="screen_rotation" 
+d="M160 54l29 28l81 -81l-14 -1q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73zM316 60l136 136l-256 256l-136 -136zM218 475l257 -257q10 -9 10 -22t-10 -23l-136 -136q-9 -10 -22 -10t-23 10l-257 257q-10 9 -10 22t10 23l136 136q9 10 22 10t23 -10zM352 458
+l-29 -28l-81 81l14 1q100 0 173.5 -68t81.5 -167h-32q-6 60 -40 108t-87 73z" />
+    <glyph glyph-name="uniE1C2" unicode="sd_storage" 
+d="M384 341v86h-43v-86h43zM320 341v86h-43v-86h43zM256 341v86h-43v-86h43zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE1C3" unicode="settings_system_daydream" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM192 171q-26 0 -45 19t-19 45q0 25 16.5 43t40.5 20h4q21 43 67 43q28 0 49 -18.5t25 -45.5h1q22 0 37.5 -15.5t15.5 -37.5
+t-15.5 -37.5t-37.5 -15.5h-139z" />
+    <glyph glyph-name="uniE1C8" unicode="signal_cellular_&#x34;_bar" 
+d="M43 43l426 426v-426h-426z" />
+    <glyph glyph-name="uniE1CD" unicode="signal_cellular_connected_no_internet_&#x34;_bar" 
+d="M43 43l426 426v-128h-85v-298h-341zM427 43v42h42v-42h-42zM427 128v171h42v-171h-42z" />
+    <glyph glyph-name="uniE1CE" unicode="signal_cellular_no_sim" 
+d="M78 429l373 -372l-28 -28l-40 41q-12 -6 -20 -6h-214q-17 0 -29.5 13t-12.5 30v239l-56 56zM405 405v-249l-242 242l50 50h150q17 0 29.5 -13t12.5 -30z" />
+    <glyph glyph-name="uniE1CF" unicode="signal_cellular_null" 
+d="M469 469v-426h-426zM427 366l-281 -281h281v281z" />
+    <glyph glyph-name="uniE1D0" unicode="signal_cellular_off" 
+d="M102 416l367 -368l-27 -27l-42 43h-379l189 189l-135 136zM448 491v-367l-183 183z" />
+    <glyph glyph-name="uniE1D8" unicode="signal_wifi_&#x34;_bar" 
+d="M256 54l-248 309q121 85 248 85t248 -85z" />
+    <glyph glyph-name="uniE1D9" unicode="signal_wifi_&#x34;_bar_lock" 
+d="M331 203v-56l-75 -94l-247 310l6 5q6 4 12.5 8.5t17.5 11.5t23.5 13t28.5 13.5t34 13t37.5 10.5t42 7.5t45.5 2.5t45.5 -2.5t42 -7.5t37.5 -10.5t34 -13t28.5 -13.5t23.5 -13t17.5 -11.5t12.5 -8.5l6 -5l-44 -56q-6 2 -22 2q-45 0 -75.5 -30.5t-30.5 -75.5zM469 171v32
+q0 13 -9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5v-32h64zM491 171q8 0 14.5 -7t6.5 -15v-85q0 -8 -6.5 -14.5t-14.5 -6.5h-107q-8 0 -14.5 6.5t-6.5 14.5v85q0 8 6.5 15t14.5 7v32q0 23 15 38t38 15t38.5 -15.5t15.5 -37.5v-32z" />
+    <glyph glyph-name="uniE1DA" unicode="signal_wifi_off" 
+d="M70 481q12 -12 153.5 -153t213.5 -215l-27 -27l-71 71l-83 -103l-248 309q33 27 78 47l-43 44zM504 363l-116 -145l-221 220q45 10 89 10q127 0 248 -85z" />
+    <glyph glyph-name="uniE1DB" unicode="storage" 
+d="M85 277v-42h43v42h-43zM43 213v86h426v-86h-426zM128 363v42h-43v-42h43zM43 427h426v-86h-426v86zM85 149v-42h43v42h-43zM43 85v86h426v-86h-426z" />
+    <glyph glyph-name="uniE1E0" unicode="usb" 
+d="M320 363h85v-86h-21v-42q0 -18 -12.5 -30.5t-30.5 -12.5h-64v-65q26 -14 26 -42q0 -19 -13.5 -33t-33.5 -14t-33.5 14t-13.5 33q0 28 26 42v65h-64q-18 0 -30.5 12.5t-12.5 30.5v44q-26 14 -26 41q0 20 14 33.5t33 13.5t33 -13.5t14 -33.5q0 -28 -25 -41v-44h64v170h-43
+l64 86l64 -86h-43v-170h64v42h-21v86z" />
+    <glyph glyph-name="uniE1E1" unicode="wifi_lock" 
+d="M469 171v32q0 13 -9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5v-32h64zM491 171q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v85q0 9 6 15.5t15 6.5v32q0 22 15.5 37.5t37.5 15.5t38 -15.5t16 -37.5v-32zM437 309q-44 0 -75 -31t-31 -75v-61
+l-75 -99l-256 341q112 85 256 85t256 -85l-57 -76q-6 1 -18 1z" />
+    <glyph glyph-name="uniE1E2" unicode="wifi_tethering" 
+d="M256 448q88 0 150.5 -62t62.5 -151q0 -58 -28.5 -107.5t-77.5 -77.5l-22 37q39 23 62.5 62.5t23.5 85.5q0 70 -50 120t-121 50t-121 -50t-50 -120q0 -47 23 -86t62 -62l-21 -37q-49 28 -77.5 77.5t-28.5 107.5q0 89 62.5 151t150.5 62zM384 235q0 -35 -17.5 -64.5
+t-46.5 -46.5l-21 37q42 25 42 74q0 35 -25 60t-60 25t-60 -25t-25 -60q0 -49 42 -74l-21 -37q-29 17 -46.5 46.5t-17.5 64.5q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM256 277q17 0 30 -12.5t13 -29.5t-13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5z" />
+    <glyph glyph-name="uniE226" unicode="attach_file" 
+d="M352 384h32v-245q0 -49 -34 -83.5t-83 -34.5t-83.5 34.5t-34.5 83.5v266q0 35 25.5 60.5t60.5 25.5t60 -25.5t25 -60.5v-224q0 -22 -15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5v203h32v-203q0 -9 6.5 -15t15.5 -6t15 6t6 15v224q0 22 -15.5 38t-37.5 16t-38 -16t-16 -38
+v-266q0 -35 25.5 -60.5t60.5 -25.5t60 25.5t25 60.5v245z" />
+    <glyph glyph-name="uniE227" unicode="attach_money" 
+d="M252 279q22 -6 36.5 -12t30.5 -16.5t24.5 -26.5t8.5 -38q0 -31 -20.5 -50.5t-54.5 -25.5v-46h-64v46q-33 7 -54.5 28t-23.5 54h47q4 -45 63 -45q31 0 44.5 11.5t13.5 26.5q0 36 -64 52q-100 23 -100 88q0 29 21 49.5t53 27.5v46h64v-47q33 -8 50.5 -30t18.5 -51h-47
+q-2 45 -54 45q-26 0 -41.5 -11t-15.5 -29q0 -29 64 -46z" />
+    <glyph glyph-name="uniE228" unicode="border_all" 
+d="M405 277v128h-128v-128h128zM405 107v128h-128v-128h128zM235 277v128h-128v-128h128zM235 107v128h-128v-128h128zM64 448h384v-384h-384v384z" />
+    <glyph glyph-name="uniE229" unicode="border_bottom" 
+d="M107 192v-43h-43v43h43zM64 64v43h384v-43h-384zM107 277v-42h-43v42h43zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM107 363v-43h-43v43h43zM405 149v43h43v-43h-43zM405 235v42h43v-42h-43zM363 448v-43h-43v43h43zM277 448v-43h-42v43h42zM363 277v-42h-43v42
+h43zM277 363v-43h-42v43h42zM107 448v-43h-43v43h43zM277 277v-42h-42v42h42zM192 448v-43h-43v43h43zM277 192v-43h-42v43h42zM192 277v-42h-43v42h43z" />
+    <glyph glyph-name="uniE22A" unicode="border_clear" 
+d="M320 405v43h43v-43h-43zM320 235v42h43v-42h-43zM320 64v43h43v-43h-43zM235 405v43h42v-43h-42zM405 448h43v-43h-43v43zM235 320v43h42v-43h-42zM405 320v43h43v-43h-43zM405 64v43h43v-43h-43zM405 235v42h43v-42h-43zM405 149v43h43v-43h-43zM235 235v42h42v-42h-42z
+M64 405v43h43v-43h-43zM64 320v43h43v-43h-43zM64 235v42h43v-42h-43zM64 149v43h43v-43h-43zM64 64v43h43v-43h-43zM235 64v43h42v-43h-42zM235 149v43h42v-43h-42zM149 64v43h43v-43h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22B" unicode="border_color" 
+d="M0 85h512v-85h-512v85zM442 426l-42 -42l-80 80l42 42q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM379 363l-214 -214h-80v80l214 214z" />
+    <glyph glyph-name="uniE22C" unicode="border_horizontal" 
+d="M405 64v43h43v-43h-43zM320 64v43h43v-43h-43zM235 149v43h42v-43h-42zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM64 235v42h384v-42h-384zM235 64v43h42v-43h-42zM405 149v43h43v-43h-43zM277 448v-43h-42v43h42zM277 363v-43h-42v43h42zM363 448v-43h-43v43h43z
+M192 448v-43h-43v43h43zM107 448v-43h-43v43h43zM149 64v43h43v-43h-43zM64 149v43h43v-43h-43zM107 363v-43h-43v43h43zM64 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22D" unicode="border_inner" 
+d="M405 149v43h43v-43h-43zM405 64v43h43v-43h-43zM277 448v-171h171v-42h-171v-171h-42v171h-171v42h171v171h42zM320 64v43h43v-43h-43zM405 448h43v-43h-43v43zM405 320v43h43v-43h-43zM363 448v-43h-43v43h43zM107 448v-43h-43v43h43zM192 448v-43h-43v43h43zM64 149v43
+h43v-43h-43zM107 363v-43h-43v43h43zM149 64v43h43v-43h-43zM64 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22E" unicode="border_left" 
+d="M320 405v43h43v-43h-43zM320 235v42h43v-42h-43zM405 64v43h43v-43h-43zM405 235v42h43v-42h-43zM405 448h43v-43h-43v43zM405 149v43h43v-43h-43zM320 64v43h43v-43h-43zM405 320v43h43v-43h-43zM64 64v384h43v-384h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43z
+M149 64v43h43v-43h-43zM235 235v42h42v-42h-42zM235 320v43h42v-43h-42zM235 405v43h42v-43h-42zM235 149v43h42v-43h-42zM235 64v43h42v-43h-42z" />
+    <glyph glyph-name="uniE22F" unicode="border_outer" 
+d="M192 277v-42h-43v42h43zM277 192v-43h-42v43h42zM405 107v298h-298v-298h298zM64 448h384v-384h-384v384zM363 277v-42h-43v42h43zM277 277v-42h-42v42h42zM277 363v-43h-42v43h42z" />
+    <glyph glyph-name="uniE230" unicode="border_right" 
+d="M235 320v43h42v-43h-42zM235 405v43h42v-43h-42zM235 235v42h42v-42h-42zM320 405v43h43v-43h-43zM320 64v43h43v-43h-43zM405 448h43v-384h-43v384zM320 235v42h43v-42h-43zM235 149v43h42v-43h-42zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z
+M235 64v43h42v-43h-42zM64 64v43h43v-43h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43zM64 405v43h43v-43h-43zM149 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE231" unicode="border_style" 
+d="M405 320v43h43v-43h-43zM64 448h384v-43h-341v-341h-43v384zM405 235v42h43v-42h-43zM405 149v43h43v-43h-43zM235 64v43h42v-43h-42zM149 64v43h43v-43h-43zM405 64v43h43v-43h-43zM320 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE232" unicode="border_top" 
+d="M320 235v42h43v-42h-43zM405 64v43h43v-43h-43zM235 320v43h42v-43h-42zM320 64v43h43v-43h-43zM405 149v43h43v-43h-43zM64 448h384v-43h-384v43zM405 235v42h43v-42h-43zM405 320v43h43v-43h-43zM235 149v43h42v-43h-42zM64 320v43h43v-43h-43zM64 235v42h43v-42h-43z
+M64 64v43h43v-43h-43zM64 149v43h43v-43h-43zM235 64v43h42v-43h-42zM235 235v42h42v-42h-42zM149 235v42h43v-42h-43zM149 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE233" unicode="border_vertical" 
+d="M320 235v42h43v-42h-43zM320 64v43h43v-43h-43zM320 405v43h43v-43h-43zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM405 235v42h43v-42h-43zM405 64v43h43v-43h-43zM235 64v384h42v-384h-42zM405 149v43h43v-43h-43zM149 405v43h43v-43h-43zM64 149v43h43v-43h-43z
+M64 64v43h43v-43h-43zM64 235v42h43v-42h-43zM149 235v42h43v-42h-43zM149 64v43h43v-43h-43zM64 405v43h43v-43h-43zM64 320v43h43v-43h-43z" />
+    <glyph glyph-name="uniE234" unicode="format_align_center" 
+d="M64 448h384v-43h-384v43zM149 363h214v-43h-214v43zM64 235v42h384v-42h-384zM64 64v43h384v-43h-384zM149 192h214v-43h-214v43z" />
+    <glyph glyph-name="uniE235" unicode="format_align_justify" 
+d="M64 448h384v-43h-384v43zM64 320v43h384v-43h-384zM64 235v42h384v-42h-384zM64 149v43h384v-43h-384zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE236" unicode="format_align_left" 
+d="M64 448h384v-43h-384v43zM64 64v43h384v-43h-384zM64 235v42h384v-42h-384zM320 363v-43h-256v43h256zM320 192v-43h-256v43h256z" />
+    <glyph glyph-name="uniE237" unicode="format_align_right" 
+d="M64 448h384v-43h-384v43zM192 320v43h256v-43h-256zM64 235v42h384v-42h-384zM192 149v43h256v-43h-256zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE238" unicode="format_bold" 
+d="M288 181q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5h-75v-64h75zM213 373v-64h64q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5h-64zM333 282q46 -21 46 -73q0 -34 -22.5 -57.5t-56.5 -23.5h-151v299h134q36 0 60.5 -25t24.5 -61t-35 -59z" />
+    <glyph glyph-name="uniE239" unicode="format_clear" 
+d="M128 405h299v-64h-124l-34 -80l-45 44l15 36h-51l-60 60v4zM70 405l6 -5l308 -309l-27 -27l-121 121l-33 -78h-64l52 123l-148 148z" />
+    <glyph glyph-name="uniE23A" unicode="format_color_fill" 
+d="M0 85h512v-85h-512v85zM405 267q43 -47 43 -75q0 -17 -13 -30t-30 -13t-29.5 13t-12.5 30q0 12 10.5 31t20.5 31zM111 299h205l-103 102zM353 321q10 -10 10 -23t-10 -22l-117 -117q-10 -10 -23 -10q-12 0 -22 10l-118 117q-10 9 -10 22t10 23l110 110l-51 51l31 30z" />
+    <glyph glyph-name="uniE23B" unicode="format_color_reset" 
+d="M112 400l312 -312l-27 -27l-57 56q-37 -32 -84 -32q-53 0 -90.5 37.5t-37.5 90.5q0 34 28 88l-71 71zM384 213q0 -15 -3 -28l-183 184l58 75q14 -16 35.5 -43t57 -88.5t35.5 -99.5z" />
+    <glyph glyph-name="uniE23C" unicode="format_color_text" 
+d="M205 256h102l-51 135zM235 448h42l117 -299h-48l-23 64h-134l-24 -64h-48zM0 85h512v-85h-512v85z" />
+    <glyph glyph-name="uniE23D" unicode="format_indent_decrease" 
+d="M235 235v42h213v-42h-213zM235 320v43h213v-43h-213zM64 448h384v-43h-384v43zM64 64v43h384v-43h-384zM64 256l85 85v-170zM235 149v43h213v-43h-213z" />
+    <glyph glyph-name="uniE23E" unicode="format_indent_increase" 
+d="M235 235v42h213v-42h-213zM235 320v43h213v-43h-213zM64 448h384v-43h-384v43zM235 149v43h213v-43h-213zM64 341l85 -85l-85 -85v170zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE23F" unicode="format_italic" 
+d="M213 427h171v-64h-60l-72 -171h47v-64h-171v64h60l72 171h-47v64z" />
+    <glyph glyph-name="uniE240" unicode="format_line_spacing" 
+d="M213 235v42h256v-42h-256zM213 107v42h256v-42h-256zM213 405h256v-42h-256v42zM128 363v-214h53l-74 -74l-75 74h53v214h-53l75 74l74 -74h-53z" />
+    <glyph glyph-name="uniE241" unicode="format_list_bulleted" 
+d="M149 405h299v-42h-299v42zM149 235v42h299v-42h-299zM149 107v42h299v-42h-299zM85 160q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM85 416q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9z
+M85 288q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9z" />
+    <glyph glyph-name="uniE242" unicode="format_list_numbered" 
+d="M149 235v42h299v-42h-299zM149 107v42h299v-42h-299zM149 405h299v-42h-299v42zM43 277v22h64v-20l-39 -44h39v-22h-64v20l38 44h-38zM64 341v64h-21v22h42v-86h-21zM43 149v22h64v-86h-64v22h42v10h-21v22h21v10h-42z" />
+    <glyph glyph-name="uniE243" unicode="format_paint" 
+d="M384 427h64v-171h-171v-192q0 -9 -6 -15t-15 -6h-43q-9 0 -15 6t-6 15v235h213v85h-21v-21q0 -9 -6 -15.5t-15 -6.5h-256q-9 0 -15.5 6.5t-6.5 15.5v85q0 9 6.5 15t15.5 6h256q9 0 15 -6t6 -15v-21z" />
+    <glyph glyph-name="uniE244" unicode="format_quote" 
+d="M299 149l42 86h-64v128h128v-128l-42 -86h-64zM128 149l43 86h-64v128h128v-128l-43 -86h-64z" />
+    <glyph glyph-name="uniE245" unicode="format_size" 
+d="M64 256v64h192v-64h-64v-149h-64v149h-64zM192 427h277v-64h-106v-256h-64v256h-107v64z" />
+    <glyph glyph-name="uniE246" unicode="format_strikethrough" 
+d="M64 213v43h384v-43h-384zM107 427h298v-64h-106v-64h-86v64h-106v64zM213 107v64h86v-64h-86z" />
+    <glyph glyph-name="uniE247" unicode="format_textdirection_l_to_r" 
+d="M448 128l-85 -85v64h-256v42h256v64zM192 299q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-43v235h-42v-235h-43v107z" />
+    <glyph glyph-name="uniE248" unicode="format_textdirection_r_to_l" 
+d="M171 149h256v-42h-256v-64l-86 85l86 85v-64zM213 299q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-42v235h-43v-235h-43v107z" />
+    <glyph glyph-name="uniE249" unicode="format_underlined" 
+d="M107 107h298v-43h-298v43zM256 149q-53 0 -90.5 37.5t-37.5 90.5v171h53v-171q0 -31 22 -52.5t53 -21.5t53 21.5t22 52.5v171h53v-171q0 -53 -37.5 -90.5t-90.5 -37.5z" />
+    <glyph glyph-name="uniE24A" unicode="functions" 
+d="M384 427v-64h-149l106 -107l-106 -107h149v-64h-256v43l139 128l-139 128v43h256z" />
+    <glyph glyph-name="uniE24B" unicode="insert_chart" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE24C" unicode="insert_comment" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE24D" unicode="insert_drive_file" 
+d="M277 320h118l-118 117v-117zM128 469h171l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5z" />
+    <glyph glyph-name="uniE24E" unicode="insert_emoticon" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE24F" unicode="insert_invitation" 
+d="M405 107v234h-298v-234h298zM341 491h43v-43h21q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43zM363 256v-107h-107v107h107z" />
+    <glyph glyph-name="uniE250" unicode="insert_link" 
+d="M363 363q44 0 75 -31.5t31 -75.5t-31 -75.5t-75 -31.5h-86v41h86q27 0 46.5 19.5t19.5 46.5t-19.5 46.5t-46.5 19.5h-86v41h86zM171 235v42h170v-42h-170zM83 256q0 -27 19.5 -46.5t46.5 -19.5h86v-41h-86q-44 0 -75 31.5t-31 75.5t31 75.5t75 31.5h86v-41h-86
+q-27 0 -46.5 -19.5t-19.5 -46.5z" />
+    <glyph glyph-name="uniE251" unicode="insert_photo" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE252" unicode="merge_type" 
+d="M160 341l96 96l96 -96h-75v-136l-128 -128l-30 30l116 115v119h-75zM363 77l-73 72l30 30l73 -72z" />
+    <glyph glyph-name="uniE253" unicode="mode_comment" 
+d="M469 427v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -29.5z" />
+    <glyph glyph-name="uniE254" unicode="mode_edit" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE255" unicode="publish" 
+d="M107 213l149 150l149 -150h-85v-128h-128v128h-85zM107 427h298v-43h-298v43z" />
+    <glyph glyph-name="uniE256" unicode="space_bar" 
+d="M384 320h43v-128h-342v128h43v-85h256v85z" />
+    <glyph glyph-name="uniE257" unicode="strikethrough_s" 
+d="M200 232q0 -54 62 -54q49 0 49 36q0 16 -6.5 23t-23.5 15q-1 0 -3.5 1t-4.5 2t-4 1h-205v43h384v-43h-83q0 -1 1.5 -3.5t2.5 -3.5q7 -17 7 -35q0 -61 -67 -80q-21 -6 -46 -6q-16 0 -31 3q-36 7 -56 22q-39 29 -39 79h63zM311 352q0 45 -51 45q-36 0 -47 -22q-3 -6 -3 -14
+q0 -15 16 -26q16 -10 30 -15h-98q0 1 -2 2.5t-2 2.5q-8 13 -8 36q0 37 32 63q34 24 83 24q53 0 83 -27q31 -28 31 -69h-64z" />
+    <glyph glyph-name="uniE258" unicode="vertical_align_bottom" 
+d="M85 107h342v-43h-342v43zM341 235l-85 -86l-85 86h64v213h42v-213h64z" />
+    <glyph glyph-name="uniE259" unicode="vertical_align_center" 
+d="M85 277h342v-42h-342v42zM341 405l-85 -85l-85 85h64v86h42v-86h64zM171 107l85 85l85 -85h-64v-86h-42v86h-64z" />
+    <glyph glyph-name="uniE25A" unicode="vertical_align_top" 
+d="M85 448h342v-43h-342v43zM171 277l85 86l85 -86h-64v-213h-42v213h-64z" />
+    <glyph glyph-name="uniE25B" unicode="wrap_text" 
+d="M363 277q35 0 60 -25t25 -60t-25 -60t-60 -25h-43v-43l-64 64l64 64v-43h48q17 0 30 13t13 30t-13 30t-30 13h-283v42h278zM427 405v-42h-342v42h342zM85 107v42h128v-42h-128z" />
+    <glyph glyph-name="uniE25C" unicode="money_off" 
+d="M114 425l311 -312l-27 -27l-47 48q-19 -17 -52 -24v-46h-64v46q-33 7 -55 28t-24 54h47q4 -45 64 -45q37 0 51 20l-75 74q-83 25 -83 84l-73 73zM267 365q-19 0 -33 -6l-31 31q14 7 32 12v46h64v-47q32 -8 49.5 -30t18.5 -51h-47q-2 45 -53 45z" />
+    <glyph glyph-name="uniE25D" unicode="drag_handle" 
+d="M85 192v43h342v-43h-342zM427 320v-43h-342v43h342z" />
+    <glyph glyph-name="uniE25E" unicode="format_shapes" 
+d="M228 240h56l-28 82zM293 213h-75l-15 -42h-35l73 192h30l72 -192h-34zM405 405h43v43h-43v-43zM448 64v43h-43v-43h43zM363 107v42h42v214h-42v42h-214v-42h-42v-214h42v-42h214zM107 64v43h-43v-43h43zM64 448v-43h43v43h-43zM491 363h-43v-214h43v-128h-128v43h-214
+v-43h-128v128h43v214h-43v128h128v-43h214v43h128v-128z" />
+    <glyph glyph-name="uniE25F" unicode="highlight" 
+d="M362 372l45 45l30 -30l-45 -46zM75 387l30 30l45 -45l-30 -31zM235 469h42v-64h-42v64zM128 213v107h256v-107l-64 -64v-106h-128v106z" />
+    <glyph glyph-name="uniE260" unicode="linear_scale" 
+d="M416 309q22 0 37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5q-36 0 -49 32h-62q-13 -32 -49 -32t-49 32h-62q-13 -32 -49 -32q-22 0 -37.5 15.5t-15.5 37.5t15.5 37.5t37.5 15.5q36 0 49 -32h62q13 32 49 32t49 -32h62q13 32 49 32z" />
+    <glyph glyph-name="uniE261" unicode="short_text" 
+d="M85 235h214v-43h-214v43zM85 320h342v-43h-342v43z" />
+    <glyph glyph-name="uniE262" unicode="text_fields" 
+d="M459 320v-64h-64v-149h-64v149h-64v64h192zM53 427h278v-64h-107v-256h-64v256h-107v64z" />
+    <glyph glyph-name="uniE263" unicode="monetization_on" 
+d="M286 126q67 13 67 67q0 61 -90 84q-56 14 -56 41q0 15 13.5 25t37.5 10q45 0 47 -40h42q-2 57 -61 72v42h-57v-42q-29 -6 -47.5 -23.5t-18.5 -44.5q0 -56 89 -78q57 -13 57 -46q0 -13 -11.5 -23.5t-39.5 -10.5q-53 0 -57 40h-42q3 -57 70 -72v-42h57v41zM256 469
+q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE264" unicode="title" 
+d="M107 427h298v-64h-117v-256h-64v256h-117v64z" />
+    <glyph glyph-name="uniE2BC" unicode="attachment" 
+d="M43 245q0 49 34 83.5t83 34.5h224q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25h-181q-22 0 -38 15.5t-16 37.5t16 38t38 16h160v-43h-162q-9 0 -9 -10.5t9 -10.5h183q17 0 30 12.5t13 29.5t-13 30t-30 13h-224q-31 0 -53 -22t-22 -53t22 -52.5t53 -21.5h203v-43h-203
+q-49 0 -83 34t-34 83z" />
+    <glyph glyph-name="uniE2BD" unicode="cloud" 
+d="M413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2BE" unicode="cloud_circle" 
+d="M352 171q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5h-11q0 35 -25 60.5t-60 25.5q-30 0 -52.5 -18.5t-29.5 -46.5l-3 1q-26 0 -45 -19t-19 -45t19 -45t45 -19h181zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5
+t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE2BF" unicode="cloud_done" 
+d="M213 149l141 141l-30 30l-111 -110l-44 44l-30 -30zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C0" unicode="cloud_download" 
+d="M363 235h-64v85h-86v-85h-64l107 -107zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C1" unicode="cloud_off" 
+d="M165 299h-37q-35 0 -60 -25.5t-25 -60.5t25 -60t60 -25h208zM64 400l27 27l357 -357l-27 -27l-43 42h-250q-53 0 -90.5 37.5t-37.5 90.5q0 52 36 89t87 39zM413 298q41 -3 70 -33.5t29 -72.5q0 -55 -45 -87l-31 31q33 18 33 56q0 26 -19 45t-45 19h-32v11q0 49 -34 83
+t-83 34q-31 0 -54 -13l-32 31q39 25 86 25q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C2" unicode="cloud_queue" 
+d="M405 128q26 0 45 19t19 45t-19 45t-45 19h-32v11q0 49 -34 83t-83 34q-40 0 -71 -24t-42 -61h-15q-35 0 -60 -25.5t-25 -60.5t25 -60t60 -25h277zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5
+t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C3" unicode="cloud_upload" 
+d="M299 235h64l-107 106l-107 -106h64v-86h86v86zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C4" unicode="file_download" 
+d="M107 128h298v-43h-298v43zM405 320l-149 -149l-149 149h85v128h128v-128h85z" />
+    <glyph glyph-name="uniE2C6" unicode="file_upload" 
+d="M107 128h298v-43h-298v43zM192 171v128h-85l149 149l149 -149h-85v-128h-128z" />
+    <glyph glyph-name="uniE2C7" unicode="folder" 
+d="M213 427l43 -43h171q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128z" />
+    <glyph glyph-name="uniE2C8" unicode="folder_open" 
+d="M427 128v213h-342v-213h342zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE2C9" unicode="folder_shared" 
+d="M405 149v22q0 19 -29.5 30.5t-55.5 11.5t-55.5 -11.5t-29.5 -30.5v-22h170zM320 320q-17 0 -30 -13t-13 -30t13 -29.5t30 -12.5t30 12.5t13 29.5t-13 30t-30 13zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256
+q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE2CC" unicode="create_new_folder" 
+d="M405 213v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM427 384q18 0 30 -12.5t12 -30.5v-213q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h128l43 -43h171z" />
+    <glyph glyph-name="uniE307" unicode="cast" 
+d="M21 299q97 0 166 -69t69 -166h-43q0 80 -56.5 136t-135.5 56v43zM21 213q62 0 106 -43.5t44 -105.5h-43q0 44 -31.5 75.5t-75.5 31.5v42zM21 128q26 0 45 -19t19 -45h-64v64zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-149v43h149v298h-384v-64h-43v64
+q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE308" unicode="cast_connected" 
+d="M448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-149v43h149v298h-384v-64h-43v64q0 17 13 30t30 13h384zM21 299q97 0 166 -69t69 -166h-43q0 80 -56.5 136t-135.5 56v43zM405 363v-214h-120q-20 63 -67.5 111t-110.5 68v35h298zM21 213q62 0 106 -43.5
+t44 -105.5h-43q0 44 -31.5 75.5t-75.5 31.5v42zM21 128q26 0 45 -19t19 -45h-64v64z" />
+    <glyph glyph-name="uniE30A" unicode="computer" 
+d="M85 384v-213h342v213h-342zM427 128h85v-43h-512v43h85q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE30B" unicode="desktop_mac" 
+d="M448 213v214h-384v-214h384zM448 469q17 0 30 -12.5t13 -29.5v-256q0 -17 -13 -30t-30 -13h-149l42 -64v-21h-170v21l42 64h-149q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE30C" unicode="desktop_windows" 
+d="M448 171v256h-384v-256h384zM448 469q17 0 30 -12.5t13 -29.5v-256q0 -17 -13 -30t-30 -13h-149v-43h42v-42h-170v42h42v43h-149q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE30D" unicode="developer_board" 
+d="M256 277h85v-128h-85v128zM128 363h107v-107h-107v107zM256 363h85v-64h-85v64zM128 235h107v-86h-107v86zM384 107v298h-299v-298h299zM469 320h-42v-43h42v-42h-42v-43h42v-43h-42v-42q0 -17 -13 -30t-30 -13h-299q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13
+h299q17 0 30 -13t13 -30v-42h42v-43z" />
+    <glyph glyph-name="uniE30E" unicode="dock" 
+d="M341 192v213h-170v-213h170zM341 490q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -29.5t-30 -12.5h-170q-17 0 -30 12.5t-13 29.5v299q0 17 13 30t30 13zM171 21v43h170v-43h-170z" />
+    <glyph glyph-name="uniE30F" unicode="gamepad" 
+d="M352 320h117v-128h-117l-64 64zM192 160l64 64l64 -64v-117h-128v117zM160 320l64 -64l-64 -64h-117v128h117zM320 352l-64 -64l-64 64v117h128v-117z" />
+    <glyph glyph-name="uniE310" unicode="headset" 
+d="M256 491q80 0 136 -56.5t56 -135.5v-150q0 -26 -19 -45t-45 -19h-64v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-26 0 -45 19t-19 45v150q0 79 56 135.5t136 56.5z" />
+    <glyph glyph-name="uniE311" unicode="headset_mic" 
+d="M256 491q80 0 136 -56.5t56 -135.5v-214q0 -26 -19 -45t-45 -19h-128v43h149v21h-85v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-26 0 -45 19t-19 45v150q0 79 56 135.5t136 56.5z" />
+    <glyph glyph-name="uniE312" unicode="keyboard" 
+d="M405 299v42h-42v-42h42zM405 235v42h-42v-42h42zM341 299v42h-42v-42h42zM341 235v42h-42v-42h42zM341 149v43h-170v-43h170zM149 299v42h-42v-42h42zM149 235v42h-42v-42h42zM171 277v-42h42v42h-42zM171 341v-42h42v42h-42zM235 277v-42h42v42h-42zM235 341v-42h42v42
+h-42zM427 405q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE313" unicode="keyboard_arrow_down" 
+d="M158 345l98 -98l98 98l30 -30l-128 -128l-128 128z" />
+    <glyph glyph-name="uniE314" unicode="keyboard_arrow_left" 
+d="M329 169l-30 -30l-128 128l128 128l30 -30l-98 -98z" />
+    <glyph glyph-name="uniE315" unicode="keyboard_arrow_right" 
+d="M183 163l98 98l-98 98l30 30l128 -128l-128 -128z" />
+    <glyph glyph-name="uniE316" unicode="keyboard_arrow_up" 
+d="M158 183l-30 30l128 128l128 -128l-30 -30l-98 98z" />
+    <glyph glyph-name="uniE317" unicode="keyboard_backspace" 
+d="M448 277v-42h-302l76 -77l-30 -30l-128 128l128 128l30 -30l-76 -77h302z" />
+    <glyph glyph-name="uniE318" unicode="keyboard_capslock" 
+d="M128 128v43h256v-43h-256zM256 333l-98 -98l-30 30l128 128l128 -128l-30 -30z" />
+    <glyph glyph-name="uniE31A" unicode="keyboard_hide" 
+d="M256 21l-85 86h170zM405 341v43h-42v-43h42zM405 277v43h-42v-43h42zM341 341v43h-42v-43h42zM341 277v43h-42v-43h42zM341 192v43h-170v-43h170zM149 341v43h-42v-43h42zM149 277v43h-42v-43h42zM171 320v-43h42v43h-42zM171 384v-43h42v43h-42zM235 320v-43h42v43h-42z
+M235 384v-43h42v43h-42zM427 448q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE31B" unicode="keyboard_return" 
+d="M405 363h43v-128h-324l77 -77l-30 -30l-128 128l128 128l30 -30l-77 -77h281v86z" />
+    <glyph glyph-name="uniE31C" unicode="keyboard_tab" 
+d="M427 384h42v-256h-42v256zM247 354l30 30l128 -128l-128 -128l-30 30l77 77h-303v42h303z" />
+    <glyph glyph-name="uniE31D" unicode="keyboard_voice" 
+d="M369 256h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -78t79.5 -31t79.5 31t33.5 78zM256 192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE31E" unicode="laptop" 
+d="M85 384v-213h342v213h-342zM427 128h85v-43h-512v43h85q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE31F" unicode="laptop_chromebook" 
+d="M427 192v213h-342v-213h342zM299 128v21h-86v-21h86zM469 128h43v-43h-512v43h43v320h426v-320z" />
+    <glyph glyph-name="uniE320" unicode="laptop_mac" 
+d="M256 107q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM85 405v-234h342v234h-342zM427 128h85q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30h85q-17 0 -29.5 13t-12.5 30v234q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-234q0 -17 -12.5 -30
+t-29.5 -13z" />
+    <glyph glyph-name="uniE321" unicode="laptop_windows" 
+d="M85 405v-213h342v213h-342zM427 128h85v-43h-512v43h85v21q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13v-21z" />
+    <glyph glyph-name="uniE322" unicode="memory" 
+d="M363 149v214h-214v-214h214zM448 277h-43v-42h43v-43h-43v-43q0 -17 -12.5 -29.5t-29.5 -12.5h-43v-43h-43v43h-42v-43h-43v43h-43q-17 0 -29.5 12.5t-12.5 29.5v43h-43v43h43v42h-43v43h43v43q0 17 12.5 29.5t29.5 12.5h43v43h43v-43h42v43h43v-43h43q17 0 29.5 -12.5
+t12.5 -29.5v-43h43v-43zM277 235v42h-42v-42h42zM320 320v-128h-128v128h128z" />
+    <glyph glyph-name="uniE323" unicode="mouse" 
+d="M235 489v-169h-150q0 65 43.5 113t106.5 56zM85 192v85h342v-85q0 -70 -50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5zM277 489q63 -8 106.5 -56t43.5 -113h-150v169z" />
+    <glyph glyph-name="uniE324" unicode="phone_android" 
+d="M368 128v299h-224v-299h224zM299 64v21h-86v-21h86zM341 491q26 0 45 -19t19 -45v-342q0 -26 -19 -45t-45 -19h-170q-26 0 -45 19t-19 45v342q0 26 19 45t45 19h170z" />
+    <glyph glyph-name="uniE325" unicode="phone_iphone" 
+d="M341 128v299h-192v-299h192zM245 43q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM331 491q22 0 37.5 -16t15.5 -38v-362q0 -22 -15.5 -38t-37.5 -16h-171q-22 0 -37.5 16t-15.5 38v362q0 22 15.5 38t37.5 16h171z" />
+    <glyph glyph-name="uniE326" unicode="phonelink" 
+d="M469 149v150h-85v-150h85zM491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15.5 6.5t-6.5 15.5v213q0 9 6.5 15t15.5 6h128zM85 384v-235h214v-64h-299v64h43v235q0 17 12.5 30t29.5 13h384v-43h-384z" />
+    <glyph glyph-name="uniE327" unicode="phonelink_off" 
+d="M491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-4l-64 64h46v150h-85v-111l-43 43v89q0 9 6.5 15t15.5 6h128zM85 378v-229h229zM41 477q81 -81 230.5 -231t183.5 -184l-27 -27l-50 50h-378v64h43v235q0 15 10 27l-39 39zM469 384h-281l-43 43h324v-43z" />
+    <glyph glyph-name="uniE328" unicode="router" 
+d="M320 128v43h-43v-43h43zM245 128v43h-42v-43h42zM171 128v43h-43v-43h43zM405 235q17 0 30 -13t13 -30v-85q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v85q0 17 13 30t30 13h213v85h43v-85h42zM412 369l-17 -17q-21 21 -54 21q-32 0 -53 -21l-17 17q30 30 70 30
+q41 0 71 -30zM431 386q-41 36 -90 36q-48 0 -89 -36l-17 17q45 45 106 45q62 0 107 -45z" />
+    <glyph glyph-name="uniE329" unicode="scanner" 
+d="M405 149v43h-213v-43h213zM149 149v43h-42v-43h42zM422 284q11 -3 18.5 -14.5t7.5 -24.5v-117q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v85q0 17 13 30t30 13h268l-300 109l15 40z" />
+    <glyph glyph-name="uniE32A" unicode="security" 
+d="M256 491l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128zM256 256v-191q59 19 100 71.5t49 119.5h-149zM256 256v188l-149 -66v-122h149z" />
+    <glyph glyph-name="uniE32B" unicode="sim_card" 
+d="M363 192v85h-43v-85h43zM277 235v42h-42v-42h42zM277 107v85h-42v-85h42zM192 192v85h-43v-85h43zM363 107v42h-43v-42h43zM192 107v42h-43v-42h43zM426 427l1 -342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v256l128 128h171q17 0 29.5 -12.5t12.5 -29.5
+z" />
+    <glyph glyph-name="uniE32C" unicode="smartphone" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE32D" unicode="speaker" 
+d="M256 256q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 85q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 427q-18 0 -30.5 -13t-12.5 -30t12.5 -30t30.5 -13q17 0 30 13t13 30t-13 30
+t-30 13zM363 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-214q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h214z" />
+    <glyph glyph-name="uniE32E" unicode="speaker_group" 
+d="M128 405v-341h213v-43h-213q-18 0 -30.5 13t-12.5 30v341h43zM245 245q0 22 16 38t38 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5zM299 160q35 0 60 25t25 60t-25 60.5t-60 25.5t-60.5 -25.5t-25.5 -60.5t25.5 -60t60.5 -25zM299 448
+q-17 0 -30 -12.5t-13 -30.5t12.5 -30t30.5 -12q17 0 29.5 12t12.5 30t-12.5 30.5t-29.5 12.5zM388 491q16 0 27.5 -11.5t11.5 -27.5v-307q0 -16 -11.5 -27t-27.5 -11h-179q-16 0 -27 11t-11 27v307q0 16 11 27.5t27 11.5h179z" />
+    <glyph glyph-name="uniE32F" unicode="tablet" 
+d="M405 128v256h-298v-256h298zM448 427q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -30t-29.5 -13h-384q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE330" unicode="tablet_android" 
+d="M411 107v341h-310v-341h310zM299 43v21h-86v-21h86zM384 512q26 0 45 -19t19 -45v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h256z" />
+    <glyph glyph-name="uniE331" unicode="tablet_mac" 
+d="M405 107v341h-320v-341h320zM245 21q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM395 512q22 0 37.5 -15.5t15.5 -37.5v-406q0 -22 -15.5 -37.5t-37.5 -15.5h-299q-22 0 -37.5 15.5t-15.5 37.5v406q0 22 15.5 37.5
+t37.5 15.5h299z" />
+    <glyph glyph-name="uniE332" unicode="toys" 
+d="M256 256q0 -48 -34.5 -82.5t-82.5 -34.5t-83 34.5t-35 82.5h235zM256 256q-48 0 -82.5 34.5t-34.5 82.5t34.5 83t82.5 35v-235zM256 256q48 0 82.5 -34.5t34.5 -82.5t-34.5 -83t-82.5 -35v235zM256 256q0 48 34.5 82.5t82.5 34.5t83 -34.5t35 -82.5h-235z" />
+    <glyph glyph-name="uniE333" unicode="tv" 
+d="M448 149v256h-384v-256h384zM448 448q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-17 0 -30 12.5t-13 29.5v256q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE334" unicode="watch" 
+d="M128 256q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM427 256q0 -82 -65 -134l-21 -122h-170l-21 122q-65 50 -65 134t65 134l21 122h170l21 -122q65 -52 65 -134z" />
+    <glyph glyph-name="uniE335" unicode="device_hub" 
+d="M363 171h85v-107h-107v65l-85 90l-85 -90v-65h-107v107h85l86 85v68q-19 7 -31 23t-12 37q0 26 19 45t45 19t45 -19t19 -45q0 -21 -12 -37t-31 -23v-68z" />
+    <glyph glyph-name="uniE336" unicode="power_input" 
+d="M341 192v43h107v-43h-107zM192 192v43h107v-43h-107zM43 192v43h106v-43h-106zM43 320h405v-43h-405v43z" />
+    <glyph glyph-name="uniE337" unicode="devices_other" 
+d="M448 128v171h-85v-171h85zM469 341q8 0 15 -6.5t7 -14.5v-213q0 -8 -7 -15t-15 -7h-128q-8 0 -14.5 7t-6.5 15v213q0 8 6.5 14.5t14.5 6.5h128zM235 139q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM277 256v-38
+q22 -20 22 -47q0 -28 -22 -48v-38h-85v38q-21 19 -21 48q0 28 21 47v38h85zM64 384v-256h85v-43h-85q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384v-43h-384z" />
+    <glyph glyph-name="uniE338" unicode="videogame_asset" 
+d="M416 256q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM331 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM235 235v42h-64v64h-43v-64h-64v-42h64v-64h43v64h64zM448 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-384
+q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE39D" unicode="add_to_photos" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE39E" unicode="adjust" 
+d="M320 256q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5
+t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE39F" unicode="assistant" 
+d="M296 237l88 40l-88 40l-40 88l-40 -88l-88 -40l88 -40l40 -88zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64h-85q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3A0" unicode="assistant_photo" 
+d="M307 384h120v-213h-150l-8 42h-120v-149h-42v363h192z" />
+    <glyph glyph-name="uniE3A1" unicode="audiotrack" 
+d="M256 448h149v-64h-85v-235h-1q-4 -36 -31 -60.5t-64 -24.5q-40 0 -68 28t-28 68t28 68t68 28q17 0 32 -6v198z" />
+    <glyph glyph-name="uniE3A2" unicode="blur_circular" 
+d="M299 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15.5t15.5 6.5zM299 160q10 0 10 -11q0 -10 -10 -10q-11 0 -11 10q0 11 11 11zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5z
+M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM363 309q10 0 10 -10q0 -11 -10 -11q-11 0 -11 11q0 10 11 10zM363 224q10 0 10 -11q0 -10 -10 -10q-11 0 -11 10q0 11 11 11zM299 352q-11 0 -11 11
+q0 10 11 10q10 0 10 -10q0 -11 -10 -11zM299 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15.5 6.5t-6.5 15.5t6.5 15t15.5 6zM213 352q-10 0 -10 11q0 10 10 10q11 0 11 -10q0 -11 -11 -11zM149 224q11 0 11 -11q0 -10 -11 -10q-10 0 -10 10q0 11 10 11zM213 160q11 0 11 -11
+q0 -10 -11 -10q-10 0 -10 10q0 11 10 11zM149 309q11 0 11 -10q0 -11 -11 -11q-10 0 -10 11q0 10 10 10zM213 235q9 0 15.5 -6.5t6.5 -15.5t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15.5t15 6.5zM213 320q9 0 15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5t-15 6.5t-6 15.5t6 15t15 6z
+" />
+    <glyph glyph-name="uniE3A3" unicode="blur_linear" 
+d="M277 149q-9 0 -15 6.5t-6 15.5t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5zM277 235q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM277 320q-9 0 -15 6t-6 15t6 15.5t15 6.5t15.5 -6.5t6.5 -15.5t-6.5 -15t-15.5 -6zM363 245q-11 0 -11 11
+t11 11q10 0 10 -11t-10 -11zM363 331q-11 0 -11 10q0 11 11 11q10 0 10 -11q0 -10 -10 -10zM64 448h384v-43h-384v43zM363 160q-11 0 -11 11q0 10 11 10q10 0 10 -10q0 -11 -10 -11zM192 149q-9 0 -15 6.5t-6 15.5t6 15t15 6t15 -6t6 -15t-6 -15.5t-15 -6.5zM107 224
+q-13 0 -22.5 9t-9.5 23t9.5 23t22.5 9t22.5 -9t9.5 -23t-9.5 -23t-22.5 -9zM107 309q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM64 64v43h384v-43h-384zM192 320q-9 0 -15 6t-6 15t6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15
+t-15 -6zM192 235q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15t-15 -6zM107 139q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5z" />
+    <glyph glyph-name="uniE3A4" unicode="blur_off" 
+d="M64 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM128 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM213 75q11 0 11 -11t-11 -11q-10 0 -10 11t10 11zM64 309q11 0 11 -10q0 -11 -11 -11t-11 11q0 10 11 10zM128 235q9 0 15 -6.5t6 -15.5t-6 -15
+t-15 -6t-15 6t-6 15t6 15.5t15 6.5zM448 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM213 149q9 0 15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15t15 6zM53 400l27 27l347 -347l-28 -27l-80 81q1 -2 1 -6q0 -9 -6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6
+q4 0 6 -1l-60 60q-2 -11 -11 -19t-21 -8q-13 0 -22.5 9.5t-9.5 22.5q0 12 8 21t19 11l-60 60q1 -2 1 -6q0 -9 -6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6l6 -1zM299 75q10 0 10 -11t-10 -11q-11 0 -11 11t11 11zM384 363q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15
+t-15 -6zM384 277q-9 0 -15 6.5t-6 15.5t6 15t15 6t15 -6t6 -15t-6 -15.5t-15 -6.5zM384 192q-9 0 -15 6t-6 15t6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15t-15 -6zM213 363q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM448 288q-11 0 -11 11q0 10 11 10
+t11 -10q0 -11 -11 -11zM213 437q-10 0 -10 11t10 11q11 0 11 -11t-11 -11zM299 437q-11 0 -11 11t11 11q10 0 10 -11t-10 -11zM294 267q-10 1 -18 9t-9 18v5q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5h-5zM299 363q-9 0 -15.5 6t-6.5 15t6.5 15
+t15.5 6t15 -6t6 -15t-6 -15t-15 -6z" />
+    <glyph glyph-name="uniE3A5" unicode="blur_on" 
+d="M299 331q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM299 245q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM213 149q9 0 15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15
+t15 6zM213 331q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM299 75q10 0 10 -11t-10 -11q-11 0 -11 11t11 11zM299 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6zM448 224q11 0 11 -11q0 -10 -11 -10
+t-11 10q0 11 11 11zM384 405q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM384 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6zM384 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM384 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6
+t-15 6t-6 15t6 15.5t15 6.5zM213 245q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM213 363q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM213 437q-10 0 -10 11t10 11q11 0 11 -11t-11 -11zM213 75
+q11 0 11 -11t-11 -11q-10 0 -10 11t10 11zM64 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM299 437q-11 0 -11 11t11 11q10 0 10 -11t-10 -11zM299 363q-9 0 -15.5 6t-6.5 15t6.5 15t15.5 6t15 -6t6 -15t-6 -15t-15 -6zM448 288q-11 0 -11 11q0 10 11 10t11 -10
+q0 -11 -11 -11zM128 405q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM64 309q11 0 11 -10q0 -11 -11 -11t-11 11q0 10 11 10zM128 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6zM128 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15
+t15 6zM128 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6t-15 6t-6 15t6 15.5t15 6.5z" />
+    <glyph glyph-name="uniE3A6" unicode="brightness_&#x31;" 
+d="M43 256q0 88 62.5 150.5t150.5 62.5t150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5z" />
+    <glyph glyph-name="uniE3A7" unicode="brightness_&#x32;" 
+d="M213 469q89 0 151.5 -62.5t62.5 -150.5t-62.5 -150.5t-151.5 -62.5q-58 0 -106 28q49 28 77.5 77.5t28.5 107.5t-28.5 107.5t-77.5 77.5q48 28 106 28z" />
+    <glyph glyph-name="uniE3A8" unicode="brightness_&#x33;" 
+d="M192 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5q-34 0 -64 9q66 20 107.5 76.5t41.5 127.5t-41.5 127.5t-107.5 76.5q30 9 64 9z" />
+    <glyph glyph-name="uniE3A9" unicode="brightness_&#x34;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5q-26 0 -53 -12q33 -15 53.5 -46.5t20.5 -69.5t-20.5 -69.5t-53.5 -46.5q27 -12 53 -12zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE3AA" unicode="brightness_&#x35;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE3AB" unicode="brightness_&#x36;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-256zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE3AC" unicode="brightness_&#x37;" 
+d="M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100
+l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE3AD" unicode="broken_image" 
+d="M384 268l64 -64v-97q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v140l64 -64l85 86l86 -86zM448 405v-140l-64 64l-85 -86l-86 86l-85 -86l-64 65v97q0 17 13 30t30 13h298q17 0 30 -13t13 -30z" />
+    <glyph glyph-name="uniE3AE" unicode="brush" 
+d="M442 413q6 -6 6 -15t-6 -15l-191 -191l-59 59l191 191q6 6 15 6t15 -6zM149 213q26 0 45 -19t19 -45q0 -35 -25 -60t-60 -25q-52 0 -85 43q15 0 28.5 11.5t13.5 30.5q0 26 19 45t45 19z" />
+    <glyph glyph-name="uniE3AF" unicode="camera" 
+d="M210 48q4 7 43 75t60 103l78 -135q-59 -48 -135 -48q-21 0 -46 5zM52 192h207l-79 -135q-46 17 -79.5 52.5t-48.5 82.5zM99 400l108 -187h-160q-4 19 -4 43q0 83 56 144zM465 299q4 -19 4 -43q0 -83 -56 -144l-102 176l-6 11h160zM460 320h-207l79 135q46 -17 79.5 -52.5
+t48.5 -82.5zM201 288l-2 -2l-78 135q59 48 135 48q21 0 46 -5z" />
+    <glyph glyph-name="uniE3B0" unicode="camera_alt" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE3B1" unicode="camera_front" 
+d="M149 469v-224q0 24 36.5 39t70.5 15t70.5 -15t36.5 -39v224h-214zM363 512q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-150l64 -64l-64 -64v43h-106v42h106v43h-64q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214zM256 341q-17 0 -29.5 13
+t-12.5 30t12.5 30t29.5 13t30 -13t13 -30t-13 -30t-30 -13zM299 85h106v-42h-106v42z" />
+    <glyph glyph-name="uniE3B2" unicode="camera_rear" 
+d="M256 384q17 0 29.5 13t12.5 30t-12.5 29.5t-29.5 12.5t-30 -12.5t-13 -29.5t12.5 -30t30.5 -13zM363 512q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-150l64 -64l-64 -64v43h-106v42h106v43h-64q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214z
+M299 85h106v-42h-106v42z" />
+    <glyph glyph-name="uniE3B3" unicode="camera_roll" 
+d="M427 320v43h-43v-43h43zM427 128v43h-43v-43h43zM341 320v43h-42v-43h42zM341 128v43h-42v-43h42zM256 320v43h-43v-43h43zM256 128v43h-43v-43h43zM299 405h170v-320h-170q0 -17 -13 -29.5t-30 -12.5h-171q-17 0 -29.5 12.5t-12.5 29.5v320q0 17 12.5 30t29.5 13h22v21
+q0 9 6 15.5t15 6.5h85q9 0 15.5 -6.5t6.5 -15.5v-21h21q17 0 30 -13t13 -30z" />
+    <glyph glyph-name="uniE3B4" unicode="center_focus_strong" 
+d="M405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43zM256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25
+t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE3B5" unicode="center_focus_weak" 
+d="M256 213q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405
+v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43z" />
+    <glyph glyph-name="uniE3B6" unicode="collections" 
+d="M43 384h42v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299zM235 256l-64 -85h256l-86 106l-63 -79zM469 171q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256q17 0 29.5 -12.5t12.5 -29.5v-256z" />
+    <glyph glyph-name="uniE3B7" unicode="color_lens" 
+d="M373 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM309 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM203 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5
+t9.5 -22.5t22.5 -9.5zM139 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 448q79 0 135.5 -50t56.5 -121q0 -44 -31.5 -75t-75.5 -31h-37q-14 0 -23 -9.5t-9 -22.5q0 -11 8 -21t8 -22q0 -14 -9 -23t-23 -9q-80 0 -136 56t-56 136t56 136
+t136 56z" />
+    <glyph glyph-name="uniE3B8" unicode="colorize" 
+d="M148 107l172 172l-41 41l-172 -172zM442 392q15 -15 0 -30l-67 -67l41 -41l-30 -30l-30 30l-191 -190h-101v101l190 191l-30 30l30 30l41 -41l67 67q6 6 15 6t15 -6z" />
+    <glyph glyph-name="uniE3B9" unicode="compare" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-106v192l106 -128v277h-106v43h106zM213 128v128l-106 -128h106zM213 448v43h43v-470h-43v43h-106q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h106z" />
+    <glyph glyph-name="uniE3BA" unicode="control_point" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86h86v-42h-86v-86h-42
+v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE3BB" unicode="control_point_duplicate" 
+d="M320 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM320 448q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136t56 136t136 56zM43 256q0 -44 23 -80.5t62 -54.5v-46q-56 20 -92 70t-36 111
+t36 111t92 70v-46q-39 -18 -62 -54.5t-23 -80.5zM341 341v-64h64v-42h-64v-64h-42v64h-64v42h64v64h42z" />
+    <glyph glyph-name="uniE3BC" unicode="crop_&#x31;&#x36;_&#x39;" 
+d="M405 171v170h-298v-170h298zM405 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3BD" unicode="crop_&#x33;_&#x32;" 
+d="M405 128v256h-298v-256h298zM405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3BE" unicode="crop" 
+d="M149 149h342v-42h-86v-86h-42v86h-214q-17 0 -29.5 12.5t-12.5 29.5v214h-86v42h86v86h42v-342zM363 192v171h-171v42h171q17 0 29.5 -12.5t12.5 -29.5v-171h-42z" />
+    <glyph glyph-name="uniE3BF" unicode="crop_&#x35;_&#x34;" 
+d="M405 149v214h-298v-214h298zM405 405q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3C0" unicode="crop_&#x37;_&#x35;" 
+d="M405 192v128h-298v-128h298zM405 363q17 0 30 -13t13 -30v-128q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v128q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C1" unicode="crop_din" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C2" unicode="crop_free" 
+d="M405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43zM64 405q0 17 13 30t30 13h85v-43h-85v-85h-43v85z" />
+    <glyph glyph-name="uniE3C3" unicode="crop_landscape" 
+d="M405 149v214h-298v-214h298zM405 405q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3C4" unicode="crop_original" 
+d="M298 250l75 -101h-234l58 76l42 -51zM405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C5" unicode="crop_portrait" 
+d="M363 107v298h-214v-298h214zM363 448q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE3C6" unicode="crop_square" 
+d="M384 128v256h-256v-256h256zM384 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h256z" />
+    <glyph glyph-name="uniE3C7" unicode="dehaze" 
+d="M43 395h426v-43h-426v43zM43 288h426v-43h-426v43zM43 181h426v-42h-426v42z" />
+    <glyph glyph-name="uniE3C8" unicode="details" 
+d="M136 384l120 -213l120 213h-240zM64 427h384l-192 -342z" />
+    <glyph glyph-name="uniE3C9" unicode="edit" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE3CA" unicode="exposure" 
+d="M427 85v342l-342 -342h342zM107 405v-42h128v42h-128zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342zM320 149h-43v43h43v43h43v-43h42v-43h-42v-42h-43v42z" />
+    <glyph glyph-name="uniE3CB" unicode="exposure_minus_&#x31;" 
+d="M405 128h-42v227l-64 -22v36l100 36h6v-277zM85 277h171v-42h-171v42z" />
+    <glyph glyph-name="uniE3CC" unicode="exposure_minus_&#x32;" 
+d="M43 277h170v-42h-170v42zM351 405q86 0 86 -75q0 -14 -4 -25q-7 -19 -11 -25q-17 -27 -40 -50l-61 -66h127v-36h-184v32l89 97q18 18 31 40q7 12 7 28q0 13 -2 18q-10 26 -38 26q-46 0 -46 -49h-46q0 36 24 60q25 25 68 25z" />
+    <glyph glyph-name="uniE3CD" unicode="exposure_plus_&#x31;" 
+d="M427 128h-43v227l-64 -22v36l100 36h7v-277zM213 363v-86h86v-42h-86v-86h-42v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE3CE" unicode="exposure_plus_&#x32;" 
+d="M171 363v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43zM342 164h127v-36h-184v32l89 97q18 18 31 40q8 13 8 28q0 16 -11 31q-10 13 -30 13q-21 0 -35 -14q-11 -11 -11 -35h-46q0 36 24 60q14 14 29 19q18 6 40 6q18 0 36 -5q20 -8 27 -15q23 -20 23 -55q0 -24 -16 -50
+q-12 -20 -17 -25q-14 -16 -23 -25z" />
+    <glyph glyph-name="uniE3CF" unicode="exposure_zero" 
+d="M299 296q0 40 -11 57q-4 6 -14 12q-7 4 -18 4t-18 -4q-10 -6 -14 -12q-11 -17 -11 -57v-57q0 -56 25 -71q7 -4 18 -4q21 0 32 17q12 18 12 58v57h-1zM168 289q0 116 88 116q65 0 82 -62q7 -26 7 -54v-44h-1q0 -59 -24 -90q-14 -16 -28 -21q-16 -6 -36 -6t-36 6
+q-14 5 -28 21q-24 27 -24 90v44z" />
+    <glyph glyph-name="uniE3D0" unicode="filter_&#x31;" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM299 192v171h-43v42h85v-213h-42zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D1" unicode="filter_&#x32;" 
+d="M363 235v-43h-128v85q0 18 12.5 30.5t29.5 12.5h43v43h-85v42h85q18 0 30.5 -12t12.5 -30v-43q0 -18 -13 -30.5t-30 -12.5h-43v-42h86zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299
+q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D2" unicode="filter_&#x33;" 
+d="M363 235q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-43v43h43v43h-85v42h85q18 0 30.5 -12t12.5 -30v-32q0 -13 -9.5 -22.5t-22.5 -9.5q13 0 22.5 -9.5t9.5 -22.5v-32zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM448 149v299h-299v-299h299zM448 491
+q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299z" />
+    <glyph glyph-name="uniE3D3" unicode="filter" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM340 292l76 -100h-235l59 75l42 -50z" />
+    <glyph glyph-name="uniE3D4" unicode="filter_&#x34;" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM320 192v85h-85v128h42v-85h43v85h43v-213h-43zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D5" unicode="filter_&#x35;" 
+d="M363 235q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-85v128h128v-42h-86v-43h43q17 0 30 -12.5t13 -30.5v-42zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299z" />
+    <glyph glyph-name="uniE3D6" unicode="filter_&#x36;" 
+d="M277 277v-42h43v42h-43zM277 192q-17 0 -29.5 12.5t-12.5 30.5v128q0 18 12.5 30t29.5 12h86v-42h-86v-43h43q17 0 30 -12.5t13 -30.5v-42q0 -18 -13 -30.5t-30 -12.5h-43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D7" unicode="filter_&#x37;" 
+d="M277 192h-42l85 171h-85v42h128v-42zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D8" unicode="filter_&#x38;" 
+d="M277 277v-42h43v42h-43zM277 363v-43h43v43h-43zM277 192q-17 0 -29.5 12.5t-12.5 30.5v32q0 13 9.5 22.5t22.5 9.5q-13 0 -22.5 9.5t-9.5 22.5v32q0 18 12.5 30t29.5 12h43q18 0 30.5 -12t12.5 -30v-32q0 -13 -9.5 -22.5t-22.5 -9.5q13 0 22.5 -9.5t9.5 -22.5v-32
+q0 -18 -13 -30.5t-30 -12.5h-43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D9" unicode="filter_&#x39;" 
+d="M320 320v43h-43v-43h43zM320 405q18 0 30.5 -12t12.5 -30v-128q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-43q-17 0 -29.5 12.5t-12.5 30.5v43q0 18 12.5 30t29.5 12h43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3DA" unicode="filter_&#x39;_plus" 
+d="M448 320v128h-299v-299h299v128h-43v-42h-42v42h-43v43h43v43h42v-43h43zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM235 320h21v21h-21v-21zM299 256q0 -18 -13 -30.5t-30 -12.5h-64
+v43h64v21h-21q-17 0 -30 12.5t-13 30.5v21q0 18 13 30.5t30 12.5h21q17 0 30 -12.5t13 -30.5v-85zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3DB" unicode="filter_b_and_w" 
+d="M405 107v298h-149v-128zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM256 277l-149 -170h149v170z" />
+    <glyph glyph-name="uniE3DC" unicode="filter_center_focus" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85
+q-17 0 -30 13t-13 30v85h43z" />
+    <glyph glyph-name="uniE3DD" unicode="filter_drama" 
+d="M405 128q26 0 45 19t19 45t-19 45t-45 19h-32v11q0 49 -34 83t-83 34q-58 0 -94 -47q41 -11 67.5 -45.5t26.5 -78.5h-43q0 35 -25 60.5t-60 25.5t-60 -25.5t-25 -60.5t25 -60t60 -25h277zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277
+q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE3DE" unicode="filter_frames" 
+d="M384 341h-256v-213h256v213zM427 85v299h-96l-74 75l-75 -75h-97v-299h342zM427 427q17 0 29.5 -13t12.5 -30v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h86l85 85l85 -85h86z" />
+    <glyph glyph-name="uniE3DF" unicode="filter_hdr" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE3E0" unicode="filter_none" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3E2" unicode="filter_tilt_shift" 
+d="M121 91l30 30q37 -28 84 -34v-43q-63 6 -114 47zM277 87q47 6 83 34l31 -30q-51 -41 -114 -47v43zM391 151q28 38 34 83h43q-6 -62 -47 -113zM320 256q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM87 235q6 -47 34 -83l-30 -31q-41 51 -47 114h43z
+M121 361q-28 -37 -34 -84h-43q6 63 47 114zM425 277q-6 47 -34 84l30 30q41 -51 47 -114h-43zM391 421l-30 -30q-37 28 -84 34v43q63 -6 114 -47zM235 425q-47 -6 -84 -34l-30 30q51 41 114 47v-43z" />
+    <glyph glyph-name="uniE3E3" unicode="filter_vintage" 
+d="M256 171q35 0 60 25t25 60t-25 60t-60 25t-60 -25t-25 -60t25 -60t60 -25zM49 137q0 38 22.5 72.5t59.5 46.5l-18 9q-30 18 -47 47.5t-17 62.5q64 37 128 0q9 -4 17 -11q-2 14 -2 20q0 35 17.5 64.5t46.5 46.5q29 -17 46.5 -46.5t17.5 -64.5q0 -6 -2 -20q8 7 17 11
+q64 37 128 0q0 -33 -17 -62.5t-47 -47.5q-2 -1 -5.5 -3t-6.5 -3.5t-6 -2.5q3 -1 6 -2.5t6.5 -3.5t5.5 -3q30 -18 47 -47.5t17 -62.5q-64 -37 -128 0q-9 4 -17 11q2 -14 2 -20q0 -35 -17.5 -64.5t-46.5 -46.5q-29 17 -46.5 46.5t-17.5 64.5q0 6 2 20q-8 -7 -17 -11
+q-64 -37 -128 0z" />
+    <glyph glyph-name="uniE3E4" unicode="flare" 
+d="M235 21v128h42v-128h-42zM120 150l46 46l30 -30l-46 -46zM316 166l30 30l46 -46l-30 -30zM256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM363 277h128v-42h-128v42zM392 362l-46 -46l-30 30l46 46zM277 491v-128h-42v128h42zM196 346l-30 -30
+l-46 46l30 30zM149 277v-42h-128v42h128z" />
+    <glyph glyph-name="uniE3E5" unicode="flash_auto" 
+d="M359 349h50l-25 78zM405 469l69 -192h-41l-15 43h-68l-15 -43h-41l69 192h42zM64 469h213l-85 -192h85l-149 -256v192h-64v256z" />
+    <glyph glyph-name="uniE3E6" unicode="flash_off" 
+d="M363 299l-33 -57l-181 181v46h214l-86 -170h86zM70 448l335 -336l-27 -27l-88 89l-77 -131v192h-64v79l-106 107z" />
+    <glyph glyph-name="uniE3E7" unicode="flash_on" 
+d="M149 469h214l-86 -170h86l-150 -256v192h-64v234z" />
+    <glyph glyph-name="uniE3E8" unicode="flip" 
+d="M405 64v43h43q0 -17 -13 -30t-30 -13zM405 235v42h43v-42h-43zM320 405v43h43v-43h-43zM405 149v43h43v-43h-43zM235 21v470h42v-470h-42zM405 448q17 0 30 -13t13 -30h-43v43zM64 405q0 17 13 30t30 13h85v-43h-85v-298h85v-43h-85q-17 0 -30 13t-13 30v298zM405 320v43
+h43v-43h-43zM320 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE3E9" unicode="gradient" 
+d="M405 277v128h-298v-128h42v-42h43v-43h43v43h42v-43h43v43h43v42h42zM363 128v43h-43v-43h43zM277 128v43h-42v-43h42zM192 128v43h-43v-43h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM149 320h43
+v-43h-43v43zM320 320h43v-43h-43v43zM235 320h42v-43h43v-42h-43v42h-42v-42h-43v42h43v43zM363 235v-43h42v43h-42zM149 235h-42v-43h42v43z" />
+    <glyph glyph-name="uniE3EA" unicode="grain" 
+d="M213 427q17 0 30 -13t13 -30t-13 -30t-30 -13t-29.5 13t-12.5 30t12.5 30t29.5 13zM299 341q17 0 29.5 -12.5t12.5 -29.5t-12.5 -30t-29.5 -13t-30 13t-13 30t13 29.5t30 12.5zM384 256q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-30 12.5t-13 29.5t13 30t30 13zM299 171
+q17 0 29.5 -13t12.5 -30t-12.5 -30t-29.5 -13t-30 13t-13 30t13 30t30 13zM384 341q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13zM128 171q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 341q17 0 30 -12.5t13 -29.5t-13 -30
+t-30 -13t-30 13t-13 30t13 29.5t30 12.5zM213 256q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE3EB" unicode="grid_off" 
+d="M341 85h31l-31 31v-31zM299 85v74l-12 12h-74v-86h86zM171 213v74l-12 12h-74v-86h86zM171 85v86h-86v-86h86zM85 372v-31h31zM213 244v-31h31zM27 485l458 -458l-27 -27l-43 43h-330q-17 0 -29.5 12.5t-12.5 29.5v330l-43 43zM341 427v-86h86v86h-86zM171 427h-31
+l-43 42h330q17 0 29.5 -12.5t12.5 -29.5v-330l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31z" />
+    <glyph glyph-name="uniE3EC" unicode="grid_on" 
+d="M427 341v86h-86v-86h86zM427 213v86h-86v-86h86zM427 85v86h-86v-86h86zM299 341v86h-86v-86h86zM299 213v86h-86v-86h86zM299 85v86h-86v-86h86zM171 341v86h-86v-86h86zM171 213v86h-86v-86h86zM171 85v86h-86v-86h86zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342
+q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE3ED" unicode="hdr_off" 
+d="M53 459q98 -99 408 -406l-24 -23l-162 162h-72v73l-32 32v-105h-32v53h-43v-53h-32v128h32v-43h43v43h8l-117 117zM277 288h-8l-32 32h40q13 0 22.5 -9.5t9.5 -22.5v-41l-32 32v9zM373 288v-21h43v21h-43zM373 192h-8l-24 23v105h75q13 0 22.5 -9.5t9.5 -22.5v-21
+q0 -23 -19 -30l19 -45h-32l-19 43h-24v-43z" />
+    <glyph glyph-name="uniE3EE" unicode="hdr_on" 
+d="M277 224v64h-42v-64h42zM277 320q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-74v128h74zM139 277v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43zM416 267v21h-43v-21h43zM448 267q0 -20 -19 -30l19 -45h-32l-19 43h-24v-43h-32v128h75q13 0 22.5 -9.5
+t9.5 -22.5v-21z" />
+    <glyph glyph-name="uniE3F1" unicode="hdr_strong" 
+d="M107 213q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13t-30 -13t-13 -30t13 -30t30 -13zM107 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60.5 25t-25.5 60t25.5 60t60.5 25zM363 384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5
+t90.5 37.5z" />
+    <glyph glyph-name="uniE3F2" unicode="hdr_weak" 
+d="M363 171q35 0 60 25t25 60t-25 60t-60 25t-60.5 -25t-25.5 -60t25.5 -60t60.5 -25zM363 384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM107 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60.5 25t-25.5 60t25.5 60
+t60.5 25z" />
+    <glyph glyph-name="uniE3F3" unicode="healing" 
+d="M355 78l78 78l-78 77l-77 -78zM299 277q-9 0 -15.5 -6t-6.5 -15t6.5 -15t15.5 -6t15 6t6 15t-6 15t-15 6zM256 192q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM213 235q9 0 15.5 6t6.5 15t-6.5 15t-15.5 6t-15 -6t-6 -15t6 -15t15 -6zM156 278l77 78
+l-77 77l-78 -78zM256 320q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM378 256l85 -85q6 -6 6 -15t-6 -15l-92 -93q-6 -6 -15 -6q-10 0 -16 6l-84 85l-85 -85q-6 -6 -15 -6t-15 6l-93 93q-6 6 -6 15t6 15l85 85l-85 84q-6 6 -6 15.5t6 15.5l93 92
+q6 6 15 6t15 -6l85 -85l84 85q6 6 15.5 6t15.5 -6l92 -92q6 -6 6 -15.5t-6 -15.5z" />
+    <glyph glyph-name="uniE3F4" unicode="image" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE3F5" unicode="image_aspect_ratio" 
+d="M427 128v256h-342v-256h342zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342zM256 299v-43h-43v43h43zM171 299v-43h-43v43h43zM341 213v-42h-42v42h42zM341 299v-43h-42v43h42z" />
+    <glyph glyph-name="uniE3F6" unicode="iso" 
+d="M363 149h-107v32h107v-32zM405 107v298l-298 -298h298zM117 352v-32h43v-43h32v43h43v32h-43v43h-32v-43h-43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3F7" unicode="landscape" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE3F8" unicode="leak_add" 
+d="M299 64q0 62 43.5 105.5t105.5 43.5v-42q-44 0 -75.5 -31.5t-31.5 -75.5h-42zM384 64q0 26 19 45t45 19v-64h-64zM213 64q0 97 69 166t166 69v-43q-80 0 -136 -56t-56 -136h-43zM213 448q0 -62 -43.5 -105.5t-105.5 -43.5v42q44 0 75.5 31.5t31.5 75.5h42zM299 448
+q0 -97 -69 -166t-166 -69v43q80 0 136 56t56 136h43zM128 448q0 -26 -19 -45t-45 -19v64h64z" />
+    <glyph glyph-name="uniE3F9" unicode="leak_remove" 
+d="M328 265q57 34 120 34v-43q-47 0 -89 -22zM425 168l-34 34q27 11 57 11v-42q-12 0 -23 -3zM299 448q0 -63 -34 -120l-31 31q22 42 22 89h43zM64 421l27 27l357 -357l-27 -27l-61 61q-19 -27 -19 -61h-42q0 50 31 91l-31 30q-43 -53 -43 -121h-43q0 86 56 152l-53 53
+q-66 -56 -152 -56v43q69 0 122 43l-31 31q-41 -31 -91 -31v42q34 0 61 19zM213 448q0 -30 -11 -57l-34 34q3 11 3 23h42z" />
+    <glyph glyph-name="uniE3FA" unicode="lens" 
+d="M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE3FB" unicode="looks_&#x33;" 
+d="M320 288v32q0 18 -12.5 30.5t-29.5 12.5h-86v-43h86v-43h-43v-42h43v-43h-86v-43h86q17 0 29.5 12.5t12.5 30.5v32q0 14 -9 23t-23 9q14 0 23 9t9 23zM406 448q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-299q-17 0 -30 13t-13 30v298q0 17 13 30t30 13
+h299z" />
+    <glyph glyph-name="uniE3FC" unicode="looks" 
+d="M256 384q97 0 166 -69t69 -166h-43q0 79 -56 135.5t-136 56.5t-136 -56.5t-56 -135.5h-43q0 97 69 166t166 69zM256 299q61 0 105 -44t44 -106h-42q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5h-42q0 62 44 106t105 44z" />
+    <glyph glyph-name="uniE3FD" unicode="looks_&#x34;" 
+d="M320 149v214h-43v-86h-42v86h-43v-128h85v-86h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3FE" unicode="looks_&#x35;" 
+d="M320 320v43h-128v-128h85v-43h-85v-43h85q17 0 30 12.5t13 30.5v43q0 18 -12.5 30t-30.5 12h-42v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3FF" unicode="looks_&#x36;" 
+d="M320 320v43h-85q-17 0 -30 -12.5t-13 -30.5v-128q0 -18 13 -30.5t30 -12.5h42q17 0 30 12.5t13 30.5v43q0 18 -12.5 30t-30.5 12h-42v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM235 192v43h42
+v-43h-42z" />
+    <glyph glyph-name="uniE400" unicode="looks_one" 
+d="M299 149v214h-86v-43h43v-171h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE401" unicode="looks_two" 
+d="M320 277v43q0 18 -13 30.5t-30 12.5h-85v-43h85v-43h-42q-18 0 -30.5 -12t-12.5 -30v-86h128v43h-85v43h42q18 0 30.5 12t12.5 30zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE402" unicode="loupe" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-171q-88 0 -150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86
+h86v-42h-86v-86h-42v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE403" unicode="monochrome_photos" 
+d="M427 107v256h-171v-22q45 0 76 -30.5t31 -75.5t-31 -76t-76 -31v38q-28 0 -48 20t-20 49t20 48.5t48 19.5v-137q28 0 48 20t20 49t-20 48.5t-48 19.5v38q-45 0 -76 -30.5t-31 -75.5t31 -76t76 -31v-21h171zM427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30
+t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h69l38 43h128l38 -43h69z" />
+    <glyph glyph-name="uniE404" unicode="movie_creation" 
+d="M384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -86h64l-42 86h42l43 -86h64l-43 86h43l43 -86h64z" />
+    <glyph glyph-name="uniE405" unicode="music_note" 
+d="M256 448h128v-85h-85v-214q0 -35 -25.5 -60t-60.5 -25t-60 25t-25 60t25 60.5t60 25.5q21 0 43 -12v225z" />
+    <glyph glyph-name="uniE406" unicode="nature" 
+d="M277 168v-83h128v-42h-298v42h128v84q-54 9 -89.5 50.5t-35.5 96.5q0 62 44 106t106 44t105.5 -44t43.5 -106q0 -57 -38 -99t-94 -49z" />
+    <glyph glyph-name="uniE407" unicode="nature_people" 
+d="M96 277q-14 0 -23 9.5t-9 22.5t9 22.5t23 9.5t23 -9.5t9 -22.5t-9 -22.5t-23 -9.5zM473 316q0 -57 -38 -99t-94 -49v-83h64v-42h-341v106h-21v86q0 9 6 15t15 6h64q9 0 15 -6t6 -15v-86h-21v-64h171v84q-54 9 -89.5 50.5t-35.5 96.5q0 62 44 106t106 44t105.5 -44
+t43.5 -106z" />
+    <glyph glyph-name="uniE408" unicode="navigate_before" 
+d="M329 354l-98 -98l98 -98l-30 -30l-128 128l128 128z" />
+    <glyph glyph-name="uniE409" unicode="navigate_next" 
+d="M213 384l128 -128l-128 -128l-30 30l98 98l-98 98z" />
+    <glyph glyph-name="uniE40A" unicode="palette" 
+d="M373 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM309 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM203 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5
+t9.5 -22.5t22.5 -9.5zM139 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 448q79 0 135.5 -50t56.5 -121q0 -44 -31.5 -75t-75.5 -31h-37q-14 0 -23 -9.5t-9 -22.5q0 -11 8 -21t8 -22q0 -14 -9 -23t-23 -9q-80 0 -136 56t-56 136t56 136
+t136 56z" />
+    <glyph glyph-name="uniE40B" unicode="panorama" 
+d="M181 245l-74 -96h298l-96 128l-74 -96zM491 128q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384q17 0 30 -13t13 -30v-256z" />
+    <glyph glyph-name="uniE40C" unicode="panorama_fish_eye" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE40D" unicode="panorama_horizontal" 
+d="M457 427q12 0 12 -14v-314q0 -14 -12 -14q-2 0 -6 2q-96 35 -195 35t-195 -35q-4 -2 -6 -2q-12 0 -12 14v314q0 14 12 14q2 0 6 -2q96 -35 195 -35t195 35q4 2 6 2zM427 372q-78 -24 -171 -24q-88 0 -171 24v-232q82 24 171 24q88 0 171 -24v232z" />
+    <glyph glyph-name="uniE40E" unicode="panorama_vertical" 
+d="M140 85h232q-24 83 -24 171t24 171h-232q24 -82 24 -171q0 -88 -24 -171zM425 61q2 -4 2 -6q0 -12 -14 -12h-314q-14 0 -14 12q0 2 2 6q35 96 35 195t-35 195q-2 4 -2 6q0 12 14 12h314q14 0 14 -12q0 -2 -2 -6q-35 -96 -35 -195t35 -195z" />
+    <glyph glyph-name="uniE40F" unicode="panorama_wide_angle" 
+d="M256 427q77 0 170 -16l19 -3l6 -19q18 -66 18 -133t-18 -133l-6 -19l-19 -3q-93 -16 -170 -16t-170 16l-19 3l-6 19q-18 66 -18 133t18 133l6 19l19 3q93 16 170 16zM256 384q-70 0 -156 -14q-15 -57 -15 -114t15 -114q86 -14 156 -14t156 14q15 57 15 114t-15 114
+q-86 14 -156 14z" />
+    <glyph glyph-name="uniE410" unicode="photo" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE411" unicode="photo_album" 
+d="M128 107h256l-82 109l-64 -82l-46 55zM128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE412" unicode="photo_camera" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE413" unicode="photo_library" 
+d="M43 384h42v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299zM235 256l-64 -85h256l-86 106l-63 -79zM469 171q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256q17 0 29.5 -12.5t12.5 -29.5v-256z" />
+    <glyph glyph-name="uniE415" unicode="picture_as_pdf" 
+d="M299 267v64h21v-64h-21zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42zM192 309v22h21v-22h-21zM437 331v32h-64v-128h32v42h32v32h-32v22h32zM352 267v64q0 13 -9 22.5t-23 9.5h-53v-128h53q14 0 23 9.5t9 22.5zM245 309v22q0 13 -9.5 22.5t-22.5 9.5
+h-53v-128h32v42h21q13 0 22.5 9.5t9.5 22.5zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE416" unicode="portrait" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM352 165v-16h-192v16q0 22 33 35t63 13t63 -13t33 -35zM256 251q-20 0 -34 14.5t-14 33.5t14 33.5t34 14.5t34 -14.5t14 -33.5
+t-14 -33.5t-34 -14.5z" />
+    <glyph glyph-name="uniE417" unicode="remove_red_eye" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 416q79 0 143 -44t92 -116q-28 -72 -92 -116t-143 -44t-143 44t-92 116
+q28 72 92 116t143 44z" />
+    <glyph glyph-name="uniE418" unicode="rotate_&#x39;&#x30;_degrees_ccw" 
+d="M413 370q56 -56 56 -135.5t-56 -135.5q-55 -56 -136 -56q-49 0 -92 24l32 31q27 -13 60 -13q62 0 106 44q43 43 43 105t-43 106q-44 44 -106 44v-69l-90 90l90 91v-69q81 0 136 -57zM79 237l78 -78l78 78l-78 78zM157 375l138 -138l-138 -138l-139 138z" />
+    <glyph glyph-name="uniE419" unicode="rotate_left" 
+d="M277 425q63 -8 106.5 -56t43.5 -113t-43.5 -113t-106.5 -56v43q46 8 76.5 43.5t30.5 82.5t-30.5 82.5t-76.5 43.5v-83l-97 95l97 97v-66zM151 121l31 31q23 -17 53 -22v-43q-48 6 -84 34zM130 235q5 -29 21 -53l-30 -30q-28 37 -34 83h43zM152 330q-18 -26 -22 -53h-43
+q6 45 35 83z" />
+    <glyph glyph-name="uniE41A" unicode="rotate_right" 
+d="M360 182q17 23 22 53h43q-6 -46 -34 -83zM277 130q30 5 53 22l31 -31q-36 -28 -84 -34v43zM425 277h-43q-5 30 -22 53l31 30q28 -37 34 -83zM332 394l-97 -95v83q-46 -8 -76.5 -43.5t-30.5 -82.5t30.5 -82.5t76.5 -43.5v-43q-63 8 -106.5 56t-43.5 113t43.5 113t106.5 56
+v66z" />
+    <glyph glyph-name="uniE41B" unicode="slideshow" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM213 341l107 -85l-107 -85v170z" />
+    <glyph glyph-name="uniE41C" unicode="straighten" 
+d="M448 171v170h-43v-85h-42v85h-43v-85h-43v85h-42v-85h-43v85h-43v-85h-42v85h-43v-170h384zM448 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE41D" unicode="style" 
+d="M125 91v135l74 -178h-31q-17 0 -30 13t-13 30zM168 325q9 0 15 6.5t6 15.5t-6 15t-15 6t-15 -6t-6 -15t6 -15.5t15 -6.5zM470 172q7 -16 0 -32.5t-23 -23.5l-157 -65q-8 -3 -17 -3q-28 0 -39 26l-106 256q-3 9 -3 17q0 27 26 38l158 65q9 3 17 3q27 0 38 -26zM54 93
+q-16 7 -23 23t0 32l52 125v-192z" />
+    <glyph glyph-name="uniE41E" unicode="switch_camera" 
+d="M320 181l75 75l-75 75v-54h-128v54l-75 -75l75 -75v54h128v-54zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68l39 42h128l39 -42h68z" />
+    <glyph glyph-name="uniE41F" unicode="switch_video" 
+d="M277 181l75 75l-75 75v-54h-128v54l-74 -75l74 -75v54h128v-54zM384 309l85 86v-278l-85 86v-75q0 -9 -6 -15t-15 -6h-299q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h299q9 0 15 -6t6 -15v-75z" />
+    <glyph glyph-name="uniE420" unicode="tag_faces" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE421" unicode="texture" 
+d="M198 64l250 250v-61l-189 -189h-61zM448 107q0 -17 -13 -30t-30 -13h-42l85 85v-42zM107 448h42l-85 -85v42q0 17 13 30t30 13zM253 448h61l-250 -250v61zM416 446q25 -7 31 -30l-351 -350q-23 7 -30 30z" />
+    <glyph glyph-name="uniE422" unicode="timelapse" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM346 346q38 -38 38 -90t-37 -90
+t-90 -38t-91 38l90 90v128q52 0 90 -38z" />
+    <glyph glyph-name="uniE423" unicode="timer_&#x31;&#x30;" 
+d="M275 230v54q0 36 -10 53q-7 15 -30 15q-10 0 -18 -3q-23 -14 -23 -66v-53q0 -24 2 -33q1 -5 9 -21t30 -16t30 16q6 12 8 21t2 33zM152 277q0 109 83 109q41 0 60 -24q22 -28 22 -85v-41q0 -110 -82 -110q-83 0 -83 110v41zM436 289q-30 0 -30 -23q0 -3 0.5 -5.5t1.5 -4
+l2 -3t3 -2.5l3 -1.5t4.5 -2t4.5 -1.5q9 -4 19 -5q9 -2 28 -8q8 -3 22 -12q8 -5 13 -16q5 -10 5 -21q0 -37 -44 -52q-12 -4 -30 -4q-57 0 -74 39q-5 12 -5 22h41q0 -30 38 -30q33 0 33 23q0 6 -3 10.5t-6 6t-10 4.5t-24.5 7.5t-23.5 6.5q-8 3 -20 11q-19 13 -19 36
+q0 38 42 52q12 4 29 4q33 0 54 -15t21 -44h-42q0 21 -18 25q-12 3 -15 3zM0 347l101 37h6v-256h-43v205l-64 -22v36z" />
+    <glyph glyph-name="uniE424" unicode="timer_&#x33;" 
+d="M374 289q-31 0 -31 -23q0 -3 0.5 -5t1 -3.5t2 -3t2.5 -2t3 -2t3 -1.5t4 -1.5t4 -1.5q9 -4 18 -5q11 -2 29 -8q8 -3 22 -12q7 -5 13 -16q5 -10 5 -21q0 -37 -44 -52q-12 -4 -31 -4q-56 0 -73 39q-5 12 -5 22h40q0 -15 11 -22.5t28 -7.5q33 0 33 23q0 6 -3 10.5t-6 6
+t-10 4.5q-5 2 -21 6q-28 6 -48 19q-18 12 -18 36q0 38 42 52q12 4 29 4q33 0 54 -15t21 -44h-42q0 20 -19 25q-12 3 -14 3zM215 259q42 -16 42 -58q0 -37 -24 -55q-23 -20 -60 -20t-60.5 19t-23.5 53h43q0 -20 11 -28q13 -10 30 -10q42 0 42 41t-46 41h-26v33h25q32 0 41 23
+q2 5 2 16q0 38 -38 38q-26 0 -36 -20q-3 -6 -3 -15h-42q0 45 48 64q17 5 33 5q38 0 59 -18t21 -54t-38 -55z" />
+    <glyph glyph-name="uniE425" unicode="timer" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM406 354q42 -53 42 -119q0 -79 -56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5q65 0 120 -43l30 31q16 -13 30 -30zM235 213v128h42v-128h-42z
+M320 491v-43h-128v43h128z" />
+    <glyph glyph-name="uniE426" unicode="timer_off" 
+d="M256 85q39 0 75 21l-204 204q-20 -34 -20 -75q0 -62 43.5 -106t105.5 -44zM64 427l379 -379l-27 -27l-54 54q-50 -32 -106 -32q-80 0 -136 56.5t-56 135.5q0 58 32 106l-59 59zM235 311v30h42v-73zM320 491v-43h-128v43h128zM406 415l30 -30l-30 -31q42 -53 42 -119
+q0 -58 -32 -106l-31 31q20 34 20 75q0 62 -43.5 105.5t-105.5 43.5q-40 0 -74 -20l-32 31q48 32 106 32q67 0 120 -42z" />
+    <glyph glyph-name="uniE427" unicode="tonality" 
+d="M421 213q2 6 4 22h-148v-22h144zM389 149q12 17 15 22h-127v-22h112zM277 87q32 4 62 20h-62v-20zM277 299v-22h148q-2 16 -4 22h-144zM277 363v-22h127q-3 5 -15 22h-112zM277 425v-20h62q-30 16 -62 20zM235 87v338q-63 -8 -106.5 -56t-43.5 -113t43.5 -113t106.5 -56z
+M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE428" unicode="transform" 
+d="M213 341v43h128q17 0 30 -13t13 -30v-128h-43v128h-128zM469 128h-85v-43h43l-64 -64l-64 64h42v43h-170q-17 0 -30 13t-13 30v170h-85v43h85v43h-43l64 64l64 -64h-42v-256h298v-43z" />
+    <glyph glyph-name="uniE429" unicode="tune" 
+d="M320 320v128h43v-43h85v-42h-85v-43h-43zM448 235h-213v42h213v-42zM149 320h43v-128h-43v43h-85v42h85v43zM277 64h-42v128h42v-43h171v-42h-171v-43zM64 405h213v-42h-213v42zM64 149h128v-42h-128v42z" />
+    <glyph glyph-name="uniE42A" unicode="view_comfortable" 
+d="M384 405h85v-85h-85v85zM384 107v85h85v-85h-85zM277 107v85h86v-85h-86zM171 107v85h85v-85h-85zM64 107v85h85v-85h-85zM384 213v86h85v-86h-85zM277 405h86v-85h-86v85zM171 320v85h85v-85h-85zM277 213v86h86v-86h-86zM171 213v86h85v-86h-85zM64 213v86h85v-86h-85z
+M64 320v85h85v-85h-85z" />
+    <glyph glyph-name="uniE42B" unicode="view_compact" 
+d="M64 405h405v-128h-405v128zM213 107v149h256v-149h-256zM64 107v149h128v-149h-128z" />
+    <glyph glyph-name="uniE42C" unicode="wb_auto" 
+d="M220 171h40l-68 192h-43l-68 -192h41l15 42h68zM469 363h39l-44 -192h-37l-32 130l-32 -130h-38l-2 9q-21 -43 -62 -69t-90 -26q-71 0 -121 50.5t-50 120.5t50 120.5t121 50.5q82 0 133 -64h16l26 -135l32 135h34l32 -135zM146 242l25 78l24 -78h-49z" />
+    <glyph glyph-name="uniE42D" unicode="wb_cloudy" 
+d="M413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE42E" unicode="wb_incandescent" 
+d="M368 125l30 29l38 -38l-30 -30zM427 288h64v-43h-64v43zM320 377q29 -17 46.5 -46t17.5 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 35 17.5 64t46.5 46v103h128v-103zM85 288v-43h-64v43h64zM235 33v63h42v-63h-42zM76 116l38 39l30 -30l-38 -39z" />
+    <glyph glyph-name="uniE430" unicode="wb_sunny" 
+d="M76 116l38 39l30 -30l-38 -39zM235 33v63h42v-63h-42zM256 395q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM427 288h64v-43h-64v43zM368 125l30 29l38 -38l-30 -30zM436 417l-38 -38l-30 30l38 38zM277 500v-63
+h-42v63h42zM85 288v-43h-64v43h64zM144 409l-30 -30l-38 38l30 30z" />
+    <glyph glyph-name="uniE431" unicode="collections_bookmark" 
+d="M427 256v171h-107v-171l53 32zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE432" unicode="photo_size_select_actual" 
+d="M107 149h298l-96 128l-74 -96l-54 64zM448 448q16 0 29.5 -13.5t13.5 -29.5v-298q0 -16 -13.5 -29.5t-29.5 -13.5h-384q-17 0 -30 13t-13 30v298q0 16 13.5 29.5t29.5 13.5h384z" />
+    <glyph glyph-name="uniE433" unicode="photo_size_select_large" 
+d="M64 107h213l-68 91l-53 -69l-39 46zM21 277h299v-213h-256q-17 0 -30 13t-13 30v170zM107 448h42v-43h-42v43zM192 448h43v-43h-43v43zM64 448v-43h-43q0 16 13.5 29.5t29.5 13.5zM363 107h42v-43h-42v43zM363 448h42v-43h-42v43zM21 363h43v-43h-43v43zM448 448
+q16 0 29.5 -13.5t13.5 -29.5h-43v43zM448 363h43v-43h-43v43zM277 448h43v-43h-43v43zM491 107q0 -16 -13.5 -29.5t-29.5 -13.5v43h43zM448 277h43v-42h-43v42zM448 192h43v-43h-43v43z" />
+    <glyph glyph-name="uniE434" unicode="photo_size_select_small" 
+d="M149 448v-43h-42v43h42zM235 448v-43h-43v43h43zM64 277v-42h-43v42h43zM64 448v-43h-43q0 16 13.5 29.5t29.5 13.5zM405 107v-43h-42v43h42zM405 448v-43h-42v43h42zM320 107v-43h-43v43h43zM64 363v-43h-43v43h43zM64 64q-17 0 -30 13t-13 30v85h214v-128h-171z
+M448 448q16 0 29.5 -13.5t13.5 -29.5h-43v43zM491 363v-43h-43v43h43zM320 448v-43h-43v43h43zM491 107q0 -16 -13.5 -29.5t-29.5 -13.5v43h43zM491 277v-42h-43v42h43zM491 192v-43h-43v43h43z" />
+    <glyph glyph-name="uniE435" unicode="vignette" 
+d="M256 128q71 0 121 37.5t50 90.5t-50 90.5t-121 37.5t-121 -37.5t-50 -90.5t50 -90.5t121 -37.5zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE436" unicode="wb_iridescent" 
+d="M106 86l-30 31l38 38l30 -30zM76 417l30 30l38 -38l-30 -30zM436 116l-30 -30l-38 39l30 30zM277 33h-42v63h42v-63zM406 447l30 -30l-38 -38l-30 30zM235 500h42v-63h-42v63zM107 203v128h298v-128h-298z" />
+    <glyph glyph-name="uniE437" unicode="crop_rotate" 
+d="M171 171h256v-43h-43v-43h-43v43h-170q-18 0 -30.5 13t-12.5 30v170h-43v43h43v43h43v-256zM341 213v128h-128v43h128q17 0 30 -12.5t13 -30.5v-128h-43zM257 512q100 0 173.5 -68t81.5 -167h-32q-6 60 -40 108t-87 73l-29 -28l-81 81q3 0 7 0.5t7 0.5zM159 54l29 28
+l81 -81q-3 0 -7 -0.5t-7 -0.5q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73z" />
+    <glyph glyph-name="uniE438" unicode="linked_camera" 
+d="M341 384q18 0 30.5 -12.5t12.5 -30.5h28q0 29 -21 50t-50 21v-28v0zM256 107q44 0 75.5 31t31.5 75t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75t75.5 -31zM363 320h106v-235q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v256
+q0 17 12.5 30t29.5 13h68l39 43h128v-64q18 0 30.5 -12.5t12.5 -30.5zM341 441v28q53 0 90.5 -37.5t37.5 -90.5h-28q0 41 -29.5 70.5t-70.5 29.5zM188 213q0 29 20 49t48 20t48 -20t20 -49t-20 -48.5t-48 -19.5t-48 19.5t-20 48.5z" />
+    <glyph glyph-name="uniE439" unicode="add_a_photo" 
+d="M209 213q0 29 20 49t48 20q29 0 49 -20t20 -49q0 -28 -20 -48t-49 -20t-48.5 19.5t-19.5 48.5zM277 107q44 0 75.5 31t31.5 75t-31.5 75.5t-75.5 31.5t-75 -31.5t-31 -75.5t31 -75t75 -31zM128 299v64h64v64h149l39 -43h68q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5
+t-30 -12.5h-341q-17 0 -30 12.5t-13 29.5v214h64zM64 427v64h43v-64h64v-43h-64v-64h-43v64h-64v43h64z" />
+    <glyph glyph-name="uniE43A" unicode="movie_filter" 
+d="M361 257l44 20l-44 20l-20 44l-20 -44l-44 -20l44 -20l20 -44zM240 187l59 26l-59 27l-27 59l-26 -59l-59 -27l59 -26l26 -59zM384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -64h64l-42 64h42l43 -64h64
+l-43 64h43l43 -64h64z" />
+    <glyph glyph-name="uniE43B" unicode="photo_filter" 
+d="M283 283l58 -27l-58 -27l-27 -58l-27 58l-58 27l58 27l27 58zM363 299l-20 44l-44 20l44 20l20 44l20 -44l44 -20l-44 -20zM406 299h42v-192q0 -17 -12.5 -30t-29.5 -13h-299q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h192v-43h-192v-298h299v192z" />
+    <glyph glyph-name="uniE43C" unicode="burst_mode" 
+d="M235 149h213l-68 90l-54 -68l-38 46zM469 405q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h256zM107 405h42v-298h-42v298zM21 405h43v-298h-43v298z" />
+    <glyph glyph-name="uniE52D" unicode="beenhere" 
+d="M213 171l192 192l-30 30l-162 -162l-76 76l-30 -30zM405 491q17 0 30 -13t13 -30v-276q0 -21 -19 -35l-173 -116l-173 116q-19 14 -19 35v276q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE52E" unicode="directions" 
+d="M299 203l74 74l-74 75v-53h-107q-9 0 -15 -6.5t-6 -15.5v-85h42v64h86v-53zM463 271q14 -16 0 -30l-192 -192q-6 -6 -15 -6t-15 6l-192 192q-6 6 -6 15t6 15l192 192q6 6 15 6t15 -6z" />
+    <glyph glyph-name="uniE52F" unicode="directions_bike" 
+d="M405 75q31 0 53 21.5t22 52.5t-22 53t-53 22t-52.5 -22t-21.5 -53t21.5 -52.5t52.5 -21.5zM405 256q45 0 76 -31t31 -76t-31 -75.5t-76 -30.5t-75.5 30.5t-30.5 75.5t30.5 76t75.5 31zM230 288l47 -49v-132h-42v106l-69 60q-12 8 -12 30q0 18 12 30l60 60q8 12 30 12
+q19 0 34 -12l41 -41q32 -32 76 -32v-43q-63 0 -108 45l-17 17zM107 75q31 0 52.5 21.5t21.5 52.5t-21.5 53t-52.5 22t-53 -22t-22 -53t22 -52.5t53 -21.5zM107 256q45 0 75.5 -31t30.5 -76t-30.5 -75.5t-75.5 -30.5t-76 30.5t-31 75.5t31 76t76 31zM331 395q-17 0 -30 12.5
+t-13 29.5t13 30t30 13t29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5z" />
+    <glyph glyph-name="uniE530" unicode="directions_bus" 
+d="M384 277v107h-256v-107h256zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM85 171v213q0 51 44 68t127 17t127 -17t44 -68v-213q0 -28 -22 -48v-38
+q0 -9 -6 -15t-15 -6h-21q-9 0 -15.5 6t-6.5 15v22h-170v-22q0 -9 -6.5 -15t-15.5 -6h-21q-9 0 -15 6t-6 15v38q-22 20 -22 48z" />
+    <glyph glyph-name="uniE531" unicode="directions_car" 
+d="M107 277h298l-32 96h-234zM373 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM139 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM404 384l44 -128v-171q0 -9 -6 -15
+t-15 -6h-22q-9 0 -15 6t-6 15v22h-256v-22q0 -9 -6 -15t-15 -6h-22q-9 0 -15 6t-6 15v171l44 128q6 21 31 21h234q25 0 31 -21z" />
+    <glyph glyph-name="uniE532" unicode="directions_ferry" 
+d="M128 384v-85l128 42l128 -42v85h-256zM84 107l-40 142q-7 20 14 27l27 9v99q0 17 13 30t30 13h64v64h128v-64h64q17 0 30 -13t13 -30v-99l27 -9q21 -7 14 -27l-40 -142h-1q-49 0 -86 42q-37 -42 -85 -42t-85 42q-37 -42 -86 -42h-1zM427 64h42v-43h-42q-45 0 -86 21
+q-85 -44 -170 0q-41 -21 -86 -21h-42v43h42q46 0 86 28q39 -27 85 -27t85 27q40 -28 86 -28z" />
+    <glyph glyph-name="uniE533" unicode="directions_subway" 
+d="M384 277v107h-107v-107h107zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM235 277v107h-107v-107h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 51 44 68t127 17z" />
+    <glyph glyph-name="uniE534" unicode="directions_railway" 
+d="M384 299v106h-256v-106h256zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM85 181v224q0 51 44 68.5t127 17.5t127 -17.5t44 -68.5v-224q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5z" />
+    <glyph glyph-name="uniE535" unicode="directions_transit" 
+d="M384 277v107h-107v-107h107zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM235 277v107h-107v-107h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 51 44 68t127 17z" />
+    <glyph glyph-name="uniE536" unicode="directions_walk" 
+d="M209 322l-60 -301h45l39 171l44 -43v-128h43v160l-45 43l13 64q46 -53 117 -53v42q-62 0 -91 52l-22 34q-15 21 -36 21q-3 0 -8.5 -1t-8.5 -1l-111 -47v-100h43v72l38 15v0zM288 395q-17 0 -30 12.5t-13 29.5t13 30t30 13t30 -13t13 -30t-13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE539" unicode="flight" 
+d="M217 320zM448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE53A" unicode="hotel" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-192h-43v64h-384v-64h-43v320h43v-192h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE53B" unicode="layers" 
+d="M256 171q-9 7 -81.5 63.5t-110.5 85.5l192 149l192 -149q-38 -29 -110 -85t-82 -64zM256 116l157 123l35 -27l-192 -149l-192 149l35 27z" />
+    <glyph glyph-name="uniE53C" unicode="layers_clear" 
+d="M70 491l399 -400l-27 -27l-80 81l-106 -82l-192 149l35 27l157 -123l75 59l-30 30l-45 -34q-9 7 -81.5 63.5t-110.5 85.5l69 54l-90 90zM448 320q-27 -21 -86 -67l-168 168l62 48zM423 192l-31 31l25 19l31 -30z" />
+    <glyph glyph-name="uniE53D" unicode="local_airport" 
+d="M448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE53E" unicode="local_atm" 
+d="M427 128v256h-342v-256h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342zM235 149v22h-43v42h85v22h-64q-9 0 -15 6t-6 15v64q0 9 6 15t15 6h22v22h42v-22h43v-42h-85v-22h64q9 0 15 -6
+t6 -15v-64q0 -9 -6 -15t-15 -6h-22v-22h-42z" />
+    <glyph glyph-name="uniE53F" unicode="local_attraction" 
+d="M332 154l-23 87l70 58l-90 5l-33 84l-33 -84l-91 -5l71 -58l-23 -87l76 49zM427 256q0 -17 12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 17 12.5 30t29.5 13h342q17 0 29.5 -13
+t12.5 -30v-85q-17 0 -29.5 -13t-12.5 -30z" />
+    <glyph glyph-name="uniE540" unicode="local_bar" 
+d="M159 363h194l38 42h-270zM448 405l-171 -192v-106h107v-43h-256v43h107v106l-171 192v43h384v-43z" />
+    <glyph glyph-name="uniE541" unicode="local_cafe" 
+d="M43 64v43h384v-43h-384zM427 341v64h-43v-64h43zM427 448q18 0 30 -12.5t12 -30.5v-64q0 -18 -12 -30t-30 -12h-43v-64q0 -35 -25 -60.5t-60 -25.5h-128q-35 0 -60.5 25.5t-25.5 60.5v213h342z" />
+    <glyph glyph-name="uniE542" unicode="local_car_wash" 
+d="M107 235h298l-32 96h-234zM373 128q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 128q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 341l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22zM149 405q-13 0 -22.5 9.5t-9.5 22.5q0 9 8 23.5t16 24.5l8 10q32 -37 32 -58q0 -13 -9.5 -22.5t-22.5 -9.5zM256 405q-14 0 -23 9.5t-9 22.5q0 9 8 23.5t16 24.5l8 10
+q32 -37 32 -58q0 -13 -9 -22.5t-23 -9.5zM363 405q-13 0 -22.5 9.5t-9.5 22.5q0 9 8 23.5t16 24.5l8 10q32 -37 32 -58q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+    <glyph glyph-name="uniE543" unicode="local_convenience_store" 
+d="M341 256v107h-21v-43h-21v43h-22v-64h43v-43h21zM235 299v64h-64v-22h42v-21h-42v-64h64v21h-43v22h43zM405 363h64v-278h-170v86h-86v-86h-170v278h64v64h298v-64z" />
+    <glyph glyph-name="uniE544" unicode="local_drink" 
+d="M391 341l9 86h-288l9 -86h270zM256 107q26 0 45 19t19 45q0 19 -16 48t-32 48l-16 19q-64 -72 -64 -115q0 -26 19 -45t45 -19zM64 469h384l-43 -389q-2 -16 -14 -26.5t-28 -10.5h-214q-16 0 -28 10.5t-14 26.5z" />
+    <glyph glyph-name="uniE545" unicode="local_florist" 
+d="M256 395q-22 0 -37.5 -16t-15.5 -38t15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 38t-37.5 16zM119 293q0 33 31 48q-31 15 -31 48q0 22 16 38t38 16q15 0 30 -10v4q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-4q15 10 30 10q22 0 38 -16t16 -38q0 -33 -31 -48
+q31 -15 31 -48q0 -22 -16 -37.5t-38 -15.5q-17 0 -30 9v-4q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v4q-13 -9 -30 -9q-22 0 -38 15.5t-16 37.5zM256 43q-80 0 -136 56.5t-56 135.5q80 0 136 -56.5t56 -135.5zM256 43q0 79 56 135.5t136 56.5q0 -79 -56 -135.5
+t-136 -56.5z" />
+    <glyph glyph-name="uniE546" unicode="local_gas_station" 
+d="M384 299q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM256 299v106h-128v-106h128zM422 358q15 -15 15 -38v-203q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v107h-32v-160h-214v341q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-149h21q17 0 30 -13
+t13 -30v-96q0 -9 6 -15t15 -6t15 6t6 15v154q-9 -4 -21 -4q-22 0 -37.5 15.5t-15.5 37.5q0 36 34 50l-45 45l23 22z" />
+    <glyph glyph-name="uniE547" unicode="local_grocery_store" 
+d="M363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5t-30 12.5t-13 29.5t13 30t30 13zM21 469h70l20 -42h316q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22h-159l-19 -35l-1 -3q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53
+l-77 162h-43v42zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE548" unicode="local_hospital" 
+d="M384 213v86h-85v85h-86v-85h-85v-86h85v-85h86v85h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE549" unicode="local_hotel" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-192h-43v64h-384v-64h-43v320h43v-192h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE54A" unicode="local_laundry_service" 
+d="M256 85q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM149 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15.5 6t6.5 15t-6.5 15.5t-15.5 6.5zM213 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15.5 6t6.5 15t-6.5 15.5
+t-15.5 6.5zM384 469q18 0 30.5 -12t12.5 -30v-342q0 -18 -12.5 -30t-30.5 -12h-256q-18 0 -30.5 12t-12.5 30v342q0 18 12.5 30t30.5 12h256zM196 153l120 121q25 -25 25 -60.5t-25 -60.5t-60 -25t-60 25z" />
+    <glyph glyph-name="uniE54B" unicode="local_library" 
+d="M256 341q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM256 266q80 75 192 75v-234q-111 0 -192 -76q-81 76 -192 76v234q112 0 192 -75z" />
+    <glyph glyph-name="uniE54C" unicode="local_mall" 
+d="M256 235q44 0 75.5 31t31.5 75h-43q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43q0 -44 31.5 -75t75.5 -31zM256 448q-26 0 -45 -19t-19 -45h128q0 26 -19 45t-45 19zM405 384q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v256
+q0 17 13 30t30 13h42q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5h42z" />
+    <glyph glyph-name="uniE54D" unicode="local_movies" 
+d="M384 320v43h-43v-43h43zM384 235v42h-43v-42h43zM384 149v43h-43v-43h43zM171 320v43h-43v-43h43zM171 235v42h-43v-42h43zM171 149v43h-43v-43h43zM384 448h43v-384h-43v43h-43v-43h-170v43h-43v-43h-43v384h43v-43h43v43h170v-43h43v43z" />
+    <glyph glyph-name="uniE54E" unicode="local_offer" 
+d="M117 363q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM457 265q12 -12 12 -30t-12 -30l-150 -150q-12 -12 -30 -12t-30 12l-192 192q-12 12 -12 30v150q0 17 12.5 29.5t29.5 12.5h150q18 0 30 -12z" />
+    <glyph glyph-name="uniE54F" unicode="local_parking" 
+d="M282 277q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13h-69v-86h69zM277 448q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-64v-128h-85v384h149z" />
+    <glyph glyph-name="uniE550" unicode="local_pharmacy" 
+d="M341 213v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM448 405v-42l-43 -128l43 -128v-43h-384v43l43 128l-43 128v42h271l31 86l50 -19l-24 -67h56z" />
+    <glyph glyph-name="uniE551" unicode="local_phone" 
+d="M141 282q48 -93 141 -141l47 47q10 10 22 5q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22z" />
+    <glyph glyph-name="uniE552" unicode="local_pizza" 
+d="M256 192q17 0 30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5t13 -30t30 -13zM149 363q0 -17 13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5zM256 469q115 0 192 -85l-192 -341l-192 341q77 85 192 85z" />
+    <glyph glyph-name="uniE553" unicode="local_play" 
+d="M332 154l-23 87l70 58l-90 5l-33 84l-33 -84l-91 -5l71 -58l-23 -87l76 49zM427 256q0 -17 12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 17 12.5 30t29.5 13h342q17 0 29.5 -13
+t12.5 -30v-85q-17 0 -29.5 -13t-12.5 -30z" />
+    <glyph glyph-name="uniE554" unicode="local_post_office" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE555" unicode="local_print_shop" 
+d="M384 448v-85h-256v85h256zM405 256q9 0 15.5 6t6.5 15t-6.5 15.5t-15.5 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM341 107v106h-170v-106h170zM405 341q26 0 45 -19t19 -45v-128h-85v-85h-256v85h-85v128q0 26 19 45t45 19h298z" />
+    <glyph glyph-name="uniE556" unicode="local_restaurant" 
+d="M317 266l-31 -31l147 -147l-30 -30l-147 147l-147 -147l-30 30l208 208q-12 24 -3.5 56t33.5 57q31 31 69 35.5t61 -18.5t18.5 -61.5t-35.5 -69.5q-25 -25 -57 -33t-56 4zM173 227l-90 90q-25 25 -25 60t25 60l150 -149z" />
+    <glyph glyph-name="uniE557" unicode="local_see" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE558" unicode="local_shipping" 
+d="M384 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM416 309h-53v-53h95zM128 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM427 341l64 -85v-107h-43q0 -26 -19 -45t-45 -19t-45 19t-19 45h-128
+q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43v235q0 17 13 30t30 13h299v-86h64z" />
+    <glyph glyph-name="uniE559" unicode="local_taxi" 
+d="M107 277h298l-32 96h-234zM373 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM139 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM404 384l44 -128v-171q0 -9 -6 -15
+t-15 -6h-22q-9 0 -15 6t-6 15v22h-256v-22q0 -9 -6 -15t-15 -6h-22q-9 0 -15 6t-6 15v171l44 128q6 21 31 21h53v43h128v-43h53q25 0 31 -21z" />
+    <glyph glyph-name="uniE55A" unicode="location_history" 
+d="M384 171v19q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-19h256zM256 399q-24 0 -41 -17t-17 -41t17 -40.5t41 -16.5t41 16.5t17 40.5t-17 41t-41 17zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64h-85q-18 0 -30.5 13t-12.5 30v299
+q0 17 12.5 29.5t30.5 12.5h298z" />
+    <glyph glyph-name="uniE55B" unicode="map" 
+d="M320 107v253l-128 45v-253zM437 448q11 0 11 -11v-322q0 -8 -8 -10l-120 -41l-128 45l-114 -44l-3 -1q-11 0 -11 11v322q0 8 8 10l120 41l128 -45l114 44z" />
+    <glyph glyph-name="uniE55C" unicode="my_location" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE55D" unicode="navigation" 
+d="M256 469l160 -390l-15 -15l-145 64l-145 -64l-15 15z" />
+    <glyph glyph-name="uniE55E" unicode="pin_drop" 
+d="M107 85h298v-42h-298v42zM213 341q0 -17 13 -29.5t30 -12.5q18 0 30.5 12.5t12.5 29.5t-13 30t-30 13t-30 -13t-13 -30zM384 341q0 -43 -32 -101.5t-64 -95.5l-32 -37q-14 15 -35.5 41t-57 88t-35.5 105q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5z" />
+    <glyph glyph-name="uniE55F" unicode="place" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE560" unicode="rate_review" 
+d="M384 213v43h-117l-43 -43h160zM128 213h53l147 147q8 8 0 15l-38 38q-8 7 -15 0l-147 -147v-53zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE561" unicode="restaurant_menu" 
+d="M317 266l-31 -31l147 -147l-30 -30l-147 147l-147 -147l-30 30l208 208q-12 24 -3.5 56t33.5 57q31 31 69 35.5t61 -18.5t18.5 -61.5t-35.5 -69.5q-25 -25 -57 -33t-56 4zM173 227l-90 90q-25 25 -25 60t25 60l150 -149z" />
+    <glyph glyph-name="uniE562" unicode="satellite" 
+d="M107 128h298l-96 128l-74 -96l-54 64zM107 256q62 0 105.5 44t43.5 106h-43q0 -44 -31 -75.5t-75 -31.5v-43zM107 406v-65q26 0 45 19.5t19 45.5h-64zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE563" unicode="store_mall_directory" 
+d="M256 128v85h-128v-85h128zM448 213h-21v-128h-43v128h-85v-128h-214v128h-21v43l21 107h342l21 -107v-43zM427 427v-43h-342v43h342z" />
+    <glyph glyph-name="uniE564" unicode="terrain" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE565" unicode="traffic" 
+d="M256 320q18 0 30.5 13t12.5 30q0 18 -12.5 30t-30.5 12t-30.5 -12t-12.5 -30q0 -17 12.5 -30t30.5 -13zM256 213q18 0 30.5 13t12.5 30t-13 30t-30 13q-18 0 -30.5 -13t-12.5 -30t12.5 -30t30.5 -13zM256 107q18 0 30.5 12.5t12.5 29.5t-13 30t-30 13q-18 0 -30.5 -13
+t-12.5 -30t12.5 -29.5t30.5 -12.5zM427 299q0 -30 -18 -52.5t-46 -30.5v-24h64q0 -30 -18 -52t-46 -30v-25q0 -9 -6.5 -15t-15.5 -6h-170q-9 0 -15.5 6t-6.5 15v25q-28 8 -46 30t-18 52h64v24q-28 8 -46 30.5t-18 52.5h64v24q-28 8 -46 30t-18 52h64v22q0 9 6.5 15t15.5 6
+h170q9 0 15.5 -6t6.5 -15v-22h64q0 -30 -18 -52t-46 -30v-24h64z" />
+    <glyph glyph-name="uniE566" unicode="directions_run" 
+d="M211 99l-149 29l8 43l105 -21l34 173l-39 -15v-73h-42v100l111 47q3 0 8.5 1t8.5 1q21 0 36 -21l21 -34q29 -51 92 -51v-43q-71 0 -117 53l-13 -64l45 -42v-160h-43v128l-45 42zM288 395q-17 0 -30 13t-13 30t13 29.5t30 12.5t29.5 -12.5t12.5 -29.5t-12.5 -30t-29.5 -13
+z" />
+    <glyph glyph-name="uniE567" unicode="add_location" 
+d="M341 299v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE568" unicode="edit_location" 
+d="M318 351q6 5 0 11l-20 20q-6 6 -11 0l-15 -15l31 -31zM223 256l71 71l-31 31l-71 -71v-31h31zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5
+t105.5 43.5z" />
+    <glyph glyph-name="uniE569" unicode="near_me" 
+d="M448 448l-161 -384h-21l-56 146l-146 56v21z" />
+    <glyph glyph-name="uniE56A" unicode="person_pin_circle" 
+d="M256 213q55 0 85 46q0 19 -29.5 31.5t-55.5 12.5t-55.5 -12.5t-29.5 -31.5q30 -46 85 -46zM256 427q-17 0 -30 -13t-13 -30q0 -18 13 -30.5t30 -12.5t30 12.5t13 30.5q0 17 -13 30t-30 13zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5
+t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE56B" unicode="zoom_out_map" 
+d="M448 192v-128h-128l49 49l-62 61l31 31l61 -62zM192 64h-128v128l49 -49l61 62l31 -31l-62 -61zM64 320v128h128l-49 -49l62 -61l-31 -31l-61 62zM320 448h128v-128l-49 49l-61 -62l-31 31l62 61z" />
+    <glyph glyph-name="uniE56C" unicode="restaurant" 
+d="M341 384q0 30 32.5 57.5t74.5 27.5v-426h-53v170h-54v171zM235 320v149h42v-149q0 -34 -23 -58.5t-57 -26.5v-192h-53v192q-34 2 -57 26.5t-23 58.5v149h43v-149h42v149h43v-149h43z" />
+    <glyph glyph-name="uniE56D" unicode="ev_station" 
+d="M171 128l85 149h-43v107l-85 -160h43v-96zM384 299q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM422 358q15 -15 15 -38v-203q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v107h-32v-160h-214v341q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-149h21
+q17 0 30 -13t13 -30v-96q0 -9 6 -15t15 -6t15 6t6 15v154q-9 -4 -21 -4q-22 0 -37.5 15.5t-15.5 37.5q0 36 34 50l-45 45l23 22z" />
+    <glyph glyph-name="uniE56E" unicode="streetview" 
+d="M245 384q0 -57 41 -98l-209 -209q-13 13 -13 30v298q0 17 13 30t30 13h154q-16 -30 -16 -64zM277 384q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5t-31.5 -75.5t-75.5 -31.5t-75.5 31.5t-31.5 75.5zM268 206q52 39 116 39q33 0 64 -11v-127q0 -17 -13 -30t-30 -13
+h-149v117q0 15 12 25z" />
+    <glyph glyph-name="uniE56F" unicode="subway" 
+d="M384 173v147q0 38 -33 51t-95 13q-59 0 -93.5 -13t-34.5 -51v-147q0 -23 16.5 -39.5t39.5 -16.5l-24 -24v-8h36l32 32h60l32 -32h32v8l-24 24q23 0 39.5 16.5t16.5 39.5zM380 452q43 -16 66 -50t23 -79v-280h-426v280q0 45 23 79t66 50q43 17 124 17t124 -17zM150 320
+h213v-107h-213v107zM160 171q0 9 6 15t15 6t15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5t-15 6.5t-6 15.5zM309 171q0 9 6.5 15t15.5 6t15 -6t6 -15t-6 -15.5t-15 -6.5t-15.5 6.5t-6.5 15.5z" />
+    <glyph glyph-name="uniE570" unicode="train" 
+d="M352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM277 299h107v85h-107v-85zM235 299v85h-107v-85h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-43l-42 43h-81l-42 -43h-48v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 27 14 45t40.5 26t53 11t63.5 3z" />
+    <glyph glyph-name="uniE571" unicode="tram" 
+d="M363 213v107h-214v-107h214zM256 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM405 151q0 -28 -14.5 -47t-40.5 -19h2l32 -32v-10h-43l-42 42h-81l-42 -42h-48v10l34 34q-23 5 -39 23t-16 41v180q0 41 34 56.5t93 17.5l17 32h-102v32h214
+v-32h-70l-16 -32q63 -2 95.5 -17t32.5 -57v-180z" />
+    <glyph glyph-name="uniE572" unicode="transfer_within_a_station" 
+d="M123 322l-59 -301h45l37 171l46 -43v-128h43v161l-44 44l13 64q45 -55 116 -55v42q-29 0 -53.5 14.5t-39.5 38.5l-20 34q-11 20 -36 20q-9 0 -16 -3l-112 -46v-100h42v71l38 16v0zM203 395q-17 0 -30 12.5t-13 29.5t13 30t30 13t29.5 -13t12.5 -30t-12.5 -29.5
+t-29.5 -12.5zM416 91v37l53 -53l-53 -54v38h-117v32h117zM352 181h117v-32h-117v-37l-53 53l53 54v-38z" />
+    <glyph glyph-name="uniE5C3" unicode="apps" 
+d="M341 85v86h86v-86h-86zM341 213v86h86v-86h-86zM213 341v86h86v-86h-86zM341 427h86v-86h-86v86zM213 213v86h86v-86h-86zM85 213v86h86v-86h-86zM85 85v86h86v-86h-86zM213 85v86h86v-86h-86zM85 341v86h86v-86h-86z" />
+    <glyph glyph-name="uniE5C4" unicode="arrow_back" 
+d="M427 277v-42h-260l119 -120l-30 -30l-171 171l171 171l30 -30l-119 -120h260z" />
+    <glyph glyph-name="uniE5C5" unicode="arrow_drop_down" 
+d="M149 299h214l-107 -107z" />
+    <glyph glyph-name="uniE5C6" unicode="arrow_drop_down_circle" 
+d="M256 213l85 86h-170zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE5C7" unicode="arrow_drop_up" 
+d="M149 213l107 107l107 -107h-214z" />
+    <glyph glyph-name="uniE5C8" unicode="arrow_forward" 
+d="M256 427l171 -171l-171 -171l-30 30l119 120h-260v42h260l-119 120z" />
+    <glyph glyph-name="uniE5C9" unicode="cancel" 
+d="M363 179l-77 77l77 77l-30 30l-77 -77l-77 77l-30 -30l77 -77l-77 -77l30 -30l77 77l77 -77zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE5CA" unicode="check" 
+d="M192 167l226 226l30 -30l-256 -256l-119 119l30 30z" />
+    <glyph glyph-name="uniE5CB" unicode="chevron_left" 
+d="M329 354l-98 -98l98 -98l-30 -30l-128 128l128 128z" />
+    <glyph glyph-name="uniE5CC" unicode="chevron_right" 
+d="M213 384l128 -128l-128 -128l-30 30l98 98l-98 98z" />
+    <glyph glyph-name="uniE5CD" unicode="close" 
+d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" />
+    <glyph glyph-name="uniE5CE" unicode="expand_less" 
+d="M256 341l128 -128l-30 -30l-98 98l-98 -98l-30 30z" />
+    <glyph glyph-name="uniE5CF" unicode="expand_more" 
+d="M354 329l30 -30l-128 -128l-128 128l30 30l98 -98z" />
+    <glyph glyph-name="uniE5D0" unicode="fullscreen" 
+d="M299 405h106v-106h-42v64h-64v42zM363 149v64h42v-106h-106v42h64zM107 299v106h106v-42h-64v-64h-42zM149 213v-64h64v-42h-106v106h42z" />
+    <glyph glyph-name="uniE5D1" unicode="fullscreen_exit" 
+d="M341 341h64v-42h-106v106h42v-64zM299 107v106h106v-42h-64v-64h-42zM171 341v64h42v-106h-106v42h64zM107 171v42h106v-106h-42v64h-64z" />
+    <glyph glyph-name="uniE5D2" unicode="menu" 
+d="M64 384h384v-43h-384v43zM64 235v42h384v-42h-384zM64 128v43h384v-43h-384z" />
+    <glyph glyph-name="uniE5D3" unicode="keyboard_control" 
+d="M256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE5D4" unicode="more_vert" 
+d="M256 171q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 341q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE5D5" unicode="refresh" 
+d="M377 377l50 50v-150h-150l69 69q-38 38 -90 38q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5q42 0 75 23.5t46 61.5h44q-14 -56 -60 -92t-105 -36q-70 0 -120 50t-50 121t50 121t120 50q71 0 121 -50z" />
+    <glyph glyph-name="uniE5D6" unicode="unfold_less" 
+d="M354 397l-98 -98l-98 98l30 30l68 -68l68 68zM158 115l98 98l98 -98l-30 -30l-68 68l-68 -68z" />
+    <glyph glyph-name="uniE5D7" unicode="unfold_more" 
+d="M256 124l68 68l30 -30l-98 -98l-98 98l30 30zM256 388l-68 -68l-30 30l98 98l98 -98l-30 -30z" />
+    <glyph glyph-name="uniE5D8" unicode="arrow_upward" 
+d="M85 256l171 171l171 -171l-31 -30l-119 119v-260h-42v260l-120 -119z" />
+    <glyph glyph-name="uniE5D9" unicode="subdirectory_arrow_left" 
+d="M235 320l30 -30l-77 -77h196v214h43v-256h-239l77 -77l-30 -30l-128 128z" />
+    <glyph glyph-name="uniE5DA" unicode="subdirectory_arrow_right" 
+d="M405 192l-128 -128l-30 30l77 77h-239v256h43v-214h196l-77 77l30 30z" />
+    <glyph glyph-name="uniE5DB" unicode="arrow_downward" 
+d="M427 256l-171 -171l-171 171l31 30l119 -119v260h42v-260l120 119z" />
+    <glyph glyph-name="uniE5DC" unicode="first_page" 
+d="M128 384h43v-256h-43v256zM393 158l-30 -30l-128 128l128 128l30 -30l-98 -98z" />
+    <glyph glyph-name="uniE5DD" unicode="last_page" 
+d="M341 384h43v-256h-43v256zM119 354l30 30l128 -128l-128 -128l-30 30l98 98z" />
+    <glyph glyph-name="uniE60E" unicode="adb" 
+d="M320 320q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM192 320q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM344 419q61 -45 61 -120v-22h-298v22q0 75 61 120l-45 45l18 17l49 -49q32 16 66 16t66 -16l49 49l18 -17zM107 171v85
+h298v-85q0 -62 -43.5 -106t-105.5 -44t-105.5 44t-43.5 106z" />
+    <glyph glyph-name="uniE60F" unicode="bluetooth_audio" 
+d="M275 164l-40 41v-81zM235 388v-81l40 41zM335 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM417 369q31 -50 31 -111t-33 -113l-25 25q21 42 21 86t-21 86zM304 256l49 49q10 -25 10 -49q0 -25 -10 -50z" />
+    <glyph glyph-name="uniE610" unicode="disc_full" 
+d="M213 213q17 0 30 13t13 30t-13 30t-30 13t-29.5 -13t-12.5 -30t12.5 -30t29.5 -13zM213 427q71 0 121 -50.5t50 -120.5t-50 -120.5t-121 -50.5q-70 0 -120 50t-50 121t50 121t120 50zM427 363h42v-107h-42v107zM427 171v42h42v-42h-42z" />
+    <glyph glyph-name="uniE611" unicode="do_not_disturb_alt" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 57 -37 105l-239 -239q48 -37 105 -37zM85 256q0 -57 37 -105l239 239q-48 37 -105 37q-70 0 -120.5 -50.5t-50.5 -120.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE612" unicode="do_not_disturb" 
+d="M391 151q36 45 36 105q0 70 -50.5 120.5t-120.5 50.5q-60 0 -105 -36zM256 85q60 0 105 36l-240 240q-36 -45 -36 -105q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5
+z" />
+    <glyph glyph-name="uniE613" unicode="drive_eta" 
+d="M107 299h298l-32 96h-234zM373 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 405l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22z" />
+    <glyph glyph-name="uniE614" unicode="event_available" 
+d="M405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM353 276l-127 -127l-68 68l23 23l45 -45l104 104z" />
+    <glyph glyph-name="uniE615" unicode="event_busy" 
+d="M405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM199 149l-23 23l52 52l-52 52l23 23l52 -52l52 52l22 -23l-52 -52l52 -52l-22 -23l-52 52z
+" />
+    <glyph glyph-name="uniE616" unicode="event_note" 
+d="M299 213v-42h-150v42h150zM405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM363 299v-43h-214v43h214z" />
+    <glyph glyph-name="uniE617" unicode="folder_special" 
+d="M383 149l-17 71l55 48l-72 6l-29 67l-29 -67l-72 -6l55 -48l-17 -71l63 37zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE618" unicode="mms" 
+d="M107 213h298l-96 128l-74 -96l-54 64zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE619" unicode="more" 
+d="M405 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM299 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM192 224q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM469 448q17 0 30 -13t13 -30v-298
+q0 -17 -13 -30t-30 -13h-318q-22 0 -36 19l-115 173l115 173q14 19 34 19h320z" />
+    <glyph glyph-name="uniE61A" unicode="network_locked" 
+d="M448 171v32q0 13 -9 22.5t-23 9.5t-23 -9.5t-9 -22.5v-32h64zM469 171q9 0 15.5 -6.5t6.5 -15.5v-85q0 -9 -6.5 -15t-15.5 -6h-106q-9 0 -15.5 6t-6.5 15v85q0 9 6.5 15.5t15.5 6.5v32q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5v-32zM416 299q-40 0 -68 -28
+t-28 -68v-6q-21 -19 -21 -48v-64h-278l406 406v-193q-2 0 -5.5 0.5t-5.5 0.5z" />
+    <glyph glyph-name="uniE61B" unicode="phone_bluetooth_speaker" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM384 358v-40l20 20zM384 450v-40l20 20zM314 309l-15 15
+l59 60l-59 60l15 15l49 -49v81h10l61 -61l-46 -46l46 -46l-61 -61h-10v81z" />
+    <glyph glyph-name="uniE61C" unicode="phone_forwarded" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM384 277v64h-85v86h85v64l107 -107z" />
+    <glyph glyph-name="uniE61D" unicode="phone_in_talk" 
+d="M320 256q0 26 -19 45t-45 19v43q44 0 75.5 -31.5t31.5 -75.5h-43zM405 256q0 62 -43.5 105.5t-105.5 43.5v43q80 0 136 -56t56 -136h-43zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15
+q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE61E" unicode="phone_locked" 
+d="M410 427v10q0 15 -11 26t-26 11t-25.5 -11t-10.5 -26v-10h73zM427 427q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v85q0 9 6 15.5t15 6.5v10q0 22 15.5 38t37.5 16t38 -16t16 -38v-10zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6
+q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE61F" unicode="phone_missed" 
+d="M506 156q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-28 26 -57 40q-12 5 -12 19v66q-46 15 -98 15t-98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100zM139 395v-75h-32v128h128v-32h-75l96 -96
+l128 128l21 -21l-149 -150z" />
+    <glyph glyph-name="uniE620" unicode="phone_paused" 
+d="M405 448h43v-149h-43v149zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM363 448v-149h-43v149h43z" />
+    <glyph glyph-name="uniE623" unicode="sd_card" 
+d="M384 341v86h-43v-86h43zM320 341v86h-43v-86h43zM256 341v86h-43v-86h43zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE624" unicode="sim_card_alert" 
+d="M277 235v106h-42v-106h42zM277 149v43h-42v-43h42zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE625" unicode="sms" 
+d="M363 277v43h-43v-43h43zM277 277v43h-42v-43h42zM192 277v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE626" unicode="sms_failed" 
+d="M277 299v85h-42v-85h42zM277 213v43h-42v-43h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE627" unicode="sync" 
+d="M256 128v64l85 -85l-85 -86v64q-70 0 -120.5 50.5t-50.5 120.5q0 50 27 91l31 -31q-15 -27 -15 -60q0 -53 37.5 -90.5t90.5 -37.5zM256 427q70 0 120.5 -50.5t50.5 -120.5q0 -50 -27 -91l-31 31q15 27 15 60q0 53 -37.5 90.5t-90.5 37.5v-64l-85 85l85 86v-64z" />
+    <glyph glyph-name="uniE628" unicode="sync_disabled" 
+d="M427 427l-51 -51q51 -51 51 -120q0 -48 -26 -90l-32 31q15 30 15 59q0 52 -38 90l-47 -47v128h128zM61 397l27 27l335 -336l-27 -27l-50 50q-24 -14 -48 -20v44q8 3 17 8l-172 172q-15 -30 -15 -59q0 -52 38 -90l47 47v-128h-128l51 51q-51 51 -51 120q0 48 26 90z
+M213 377q-6 -2 -16 -8l-31 32q25 15 47 20v-44z" />
+    <glyph glyph-name="uniE629" unicode="sync_problem" 
+d="M235 235v128h42v-128h-42zM448 427l-50 -51q50 -50 50 -120q0 -59 -36 -105t-92 -60v44q38 13 61.5 46t23.5 75q0 53 -37 90l-48 -47v128h128zM235 149v43h42v-43h-42zM64 256q0 59 36 105t92 60v-44q-38 -13 -61.5 -46t-23.5 -75q0 -53 37 -90l48 47v-128h-128l50 51
+q-50 50 -50 120z" />
+    <glyph glyph-name="uniE62A" unicode="system_update" 
+d="M341 235l-85 -86l-85 86h64v106h42v-106h64zM363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE62B" unicode="tap_and_play" 
+d="M363 490q17 0 29.5 -12.5t12.5 -29.5v-363q0 -17 -12.5 -29.5t-29.5 -12.5h-45q-3 43 -20 85h65v277h-214v-128q-25 11 -42 14v157q0 17 12.5 30t29.5 13zM43 256q97 0 165.5 -68.5t68.5 -166.5h-42q0 79 -56.5 135.5t-135.5 56.5v43zM43 85q26 0 45 -19t19 -45h-64v64z
+M43 171q62 0 105.5 -44t43.5 -106h-43q0 44 -31 75.5t-75 31.5v43z" />
+    <glyph glyph-name="uniE62C" unicode="time_to_leave" 
+d="M107 299h298l-32 96h-234zM373 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 405l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22z" />
+    <glyph glyph-name="uniE62D" unicode="vibration" 
+d="M341 107v298h-170v-298h170zM352 448q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192zM405 149v214h43v-214h-43zM469 320h43v-128h-43v128zM64 149v214h43v-214h-43zM0 192v128h43v-128h-43z" />
+    <glyph glyph-name="uniE62E" unicode="voice_chat" 
+d="M384 213v171l-85 -68v68h-171v-171h171v69zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE62F" unicode="vpn_lock" 
+d="M213 65v42q-17 0 -29.5 12.5t-12.5 29.5v22l-103 102q-4 -16 -4 -38q0 -65 43 -113.5t106 -56.5zM404 256h43q1 -7 1 -21q0 -89 -62.5 -151.5t-150.5 -62.5q-89 0 -151.5 62.5t-62.5 151.5q0 88 62.5 150.5t151.5 62.5q31 0 64 -10v-54q0 -17 -13 -30t-30 -13h-43v-42
+q0 -9 -6 -15.5t-15 -6.5h-43v-42h128q9 0 15.5 -6.5t6.5 -15.5v-64h21q31 0 41 -29q44 47 44 115q0 14 -1 21zM452 427v10q0 15 -10.5 26t-25.5 11t-25.5 -11t-10.5 -26v-10h72zM469 427q9 0 15.5 -6.5t6.5 -15.5v-85q0 -9 -6.5 -15t-15.5 -6h-106q-9 0 -15.5 6t-6.5 15v85
+q0 9 6.5 15.5t15.5 6.5v10q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-10z" />
+    <glyph glyph-name="uniE630" unicode="airline_seat_flat" 
+d="M152 254q-19 -19 -45 -19.5t-45 18.5t-19.5 45t18.5 45t45 19.5t45 -18.5t19.5 -45t-18.5 -45zM43 213h426v-42h-128v-43h-170v43h-128v42zM469 277v-42h-277v128h192q35 0 60 -25.5t25 -60.5z" />
+    <glyph glyph-name="uniE631" unicode="airline_seat_flat_angled" 
+d="M156 294q-24 -11 -49.5 -2.5t-36.5 32.5t-2.5 49.5t32.5 36.5t49.5 2.5t36.5 -32.5t2.5 -49.5t-32.5 -36.5zM32 253l15 40l405 -146l-14 -40l-97 34v-34h-170v96zM475 207l-15 -40l-264 95l45 121l182 -66q34 -12 49 -44t3 -66z" />
+    <glyph glyph-name="uniE632" unicode="airline_seat_individual_suite" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-128h-470v214h43v-150h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE633" unicode="airline_seat_legroom_extra" 
+d="M487 144q7 -12 2 -25t-17 -19l-79 -36l-73 149h-149q-26 0 -45 19t-19 45v171h128v-128h75q27 0 38 -24l72 -149l24 11q12 5 24.5 1t18.5 -15zM85 256q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75 31.5t-31 75.5v192h42v-192z" />
+    <glyph glyph-name="uniE634" unicode="airline_seat_legroom_normal" 
+d="M437 128q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9h-96v149h-149q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -13t12.5 -30v-149h32zM107 256q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192z" />
+    <glyph glyph-name="uniE635" unicode="airline_seat_legroom_reduced" 
+d="M107 256q0 -26 19 -45t45 -19h85v-43h-85q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM426 102q3 -15 -6.5 -26.5t-24.5 -11.5h-96v64l21 85h-128q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -13t12.5 -30l-42 -149h30q12 0 21.5 -7.5t11.5 -18.5z" />
+    <glyph glyph-name="uniE636" unicode="airline_seat_recline_extra" 
+d="M346 192l123 -96l-32 -32l-81 64h-146q-23 0 -40.5 14.5t-22.5 37.5l-29 126q-3 20 8.5 36t30.5 20h1q22 3 37 -9l35 -27q48 -37 100 -27v-46q-48 -8 -110 26l22 -87h104zM341 107v-43h-150q-40 0 -70 25.5t-36 64.5l-42 209h42l42 -202q4 -23 22 -38.5t42 -15.5h150z
+M114 392q-14 10 -17 27.5t7 31.5t27.5 17.5t31.5 -6.5q14 -11 17.5 -28.5t-6.5 -31.5t-28 -17t-32 7z" />
+    <glyph glyph-name="uniE637" unicode="airline_seat_recline_normal" 
+d="M427 84l-31 -31l-75 75h-108q-26 0 -45 19t-19 45v123q0 19 14.5 33.5t33.5 14.5h1q19 0 35 -16l30 -33q17 -19 45.5 -31t54.5 -12v-47q-62 0 -118 47v-79h74zM128 171q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM162 397
+q-13 13 -13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13t-30 13z" />
+    <glyph glyph-name="uniE638" unicode="confirmation_number" 
+d="M277 331v42h-42v-42h42zM277 235v42h-42v-42h42zM277 139v42h-42v-42h42zM469 299q-17 0 -29.5 -13t-12.5 -30t12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 18 12.5 30.5
+t29.5 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-85z" />
+    <glyph glyph-name="uniE639" unicode="live_tv" 
+d="M192 299l149 -86l-149 -85v171zM448 85v256h-384v-256h384zM448 384q17 0 30 -12.5t13 -30.5v-256q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v256q0 18 13 30.5t30 12.5h162l-70 70l15 15l85 -85l85 85l15 -15l-70 -70h162z" />
+    <glyph glyph-name="uniE63A" unicode="ondemand_video" 
+d="M341 277l-149 -85v171zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE63B" unicode="personal_video" 
+d="M448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE63C" unicode="power" 
+d="M342 363q16 0 29 -13.5t13 -29.5v-117l-75 -75v-64h-106v64l-75 75v117q0 16 13 29.5t29 13.5h1v85h42v-85h86v85h42z" />
+    <glyph glyph-name="uniE63D" unicode="wc" 
+d="M352 384q-18 0 -30.5 12.5t-12.5 30.5t12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5zM160 384q-18 0 -30.5 12.5t-12.5 30.5t12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5zM384 43h-64v128h-64l54 162q11 30 41 30h2q30 0 41 -30l54 -162
+h-64v-128zM117 43v160h-32v117q0 17 13 30t30 13h64q17 0 30 -13t13 -30v-117h-32v-160h-86z" />
+    <glyph glyph-name="uniE63E" unicode="wifi" 
+d="M107 235q62 61 149.5 61t148.5 -61l-42 -43q-44 44 -107 44t-107 -44zM192 149q26 26 64 26t64 -26l-64 -64zM21 320q98 97 235.5 97t234.5 -97l-43 -43q-80 79 -192 79t-192 -79z" />
+    <glyph glyph-name="uniE63F" unicode="enhanced_encryption" 
+d="M341 171v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM190 384v-43h132v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h21v43
+q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE640" unicode="network_check" 
+d="M107 235q64 64 156 61l-28 -61q-49 -6 -86 -43zM363 192q-11 11 -26 21l12 62q32 -16 56 -40zM448 277q-39 39 -88 59l11 60q69 -25 120 -76zM21 320q59 59 136.5 82.5t156.5 9.5l-25 -57q-61 8 -120.5 -12.5t-104.5 -65.5zM339 405q11 0 11 -10l-52 -275v-1
+q-3 -14 -15 -24t-27 -10q-18 0 -30.5 12.5t-12.5 30.5q0 11 5 21l111 248q3 8 10 8z" />
+    <glyph glyph-name="uniE641" unicode="no_encryption" 
+d="M190 384v-26l-39 39q5 40 34.5 67t70.5 27q44 0 75.5 -31.5t31.5 -75.5v-43h21q17 0 30 -12.5t13 -29.5v-178l-221 220h116v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM448 47l-26 -26l-24 24q-8 -2 -14 -2h-256q-17 0 -30 12.5t-13 29.5v214q0 25 23 37
+l-44 43l26 26z" />
+    <glyph glyph-name="uniE642" unicode="rv_hookup" 
+d="M363 469l64 -64l-64 -64v43h-171v43h171v42zM384 213v64h-85v-64h85zM235 85q9 0 15 6.5t6 15.5t-6 15t-15 6t-15.5 -6t-6.5 -15t6.5 -15.5t15.5 -6.5zM427 149h42v-42h-170q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43q-17 0 -30 12.5t-13 29.5v64h150v64h-86v-42l-64 64
+l64 64v-43h235q17 0 30 -13t13 -30v-128z" />
+    <glyph glyph-name="uniE643" unicode="do_not_disturb_off" 
+d="M149 235h74l-43 42h-31v-42zM48 464l416 -416l-28 -27l-59 60q-54 -38 -121 -38q-88 0 -150.5 62.5t-62.5 150.5q0 67 38 121l-60 59zM363 277h-74l-154 154q54 38 121 38q88 0 150.5 -62.5t62.5 -150.5q0 -67 -38 -121l-99 100h31v42z" />
+    <glyph glyph-name="uniE644" unicode="do_not_disturb_on" 
+d="M363 235v42h-214v-42h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE645" unicode="priority_high" 
+d="M213 448h86v-256h-86v256zM213 107q0 18 12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE6C4" unicode="pie_chart" 
+d="M278 234h191q-7 -76 -61 -130t-130 -61v191zM278 469q76 -7 130 -61t61 -130h-191v191zM235 469v-426q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69z" />
+    <glyph glyph-name="uniE6C5" unicode="pie_chart_outlined" 
+d="M277 87q58 7 99.5 48.5t48.5 99.5h-148v-148zM85 256q0 -65 43 -113t107 -56v338q-63 -8 -106.5 -56t-43.5 -113zM277 425v-148h148q-7 58 -48.5 99.5t-99.5 48.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE6DD" unicode="bubble_chart" 
+d="M222 324q0 43 29.5 73t72.5 30t73 -30t30 -73t-30 -72.5t-73 -29.5t-72.5 29.5t-29.5 72.5zM273 128q0 18 12.5 30.5t30.5 12.5t30 -12.5t12 -30.5t-12 -30.5t-30 -12.5t-30.5 12.5t-12.5 30.5zM85 205q0 28 20 48t49 20q28 0 48 -19.5t20 -48.5t-20 -48.5t-48 -19.5
+q-29 0 -49 20t-20 48z" />
+    <glyph glyph-name="uniE6DF" unicode="multiline_chart" 
+d="M469 364l-65 -73q37 -59 44 -131h-43q-6 51 -31 97l-86 -97l-85 86l-128 -128l-32 32l160 160l85 -86l61 70q-59 69 -144 69q-73 0 -132 -52l-30 30q72 64 162 64q102 0 173 -79l61 68z" />
+    <glyph glyph-name="uniE6E1" unicode="show_chart" 
+d="M75 118l-32 32l160 160l85 -86l151 170l30 -30l-181 -204l-85 86z" />
+    <glyph glyph-name="uniE7E9" unicode="cake" 
+d="M384 320q26 0 45 -19t19 -45v-33q0 -17 -12.5 -29.5t-29.5 -12.5t-29 12l-46 46l-46 -46q-12 -12 -29.5 -12t-29.5 12l-45 46l-46 -46q-12 -12 -29 -12t-29.5 12.5t-12.5 29.5v33q0 26 19 45t45 19h107v43h42v-43h107zM354 171q22 -22 52 -22q22 0 42 13v-98q0 -9 -6 -15
+t-15 -6h-342q-9 0 -15 6t-6 15v98q19 -13 42 -13q30 0 52 22l23 23l23 -23q21 -21 52 -21t52 21l23 23zM256 384q-17 0 -30 13t-13 30q0 11 7 22l36 63l36 -63q7 -11 7 -22q0 -17 -12.5 -30t-30.5 -13z" />
+    <glyph glyph-name="uniE7EE" unicode="domain" 
+d="M384 192v-43h-43v43h43zM384 277v-42h-43v42h43zM427 107v213h-171v-43h43v-42h-43v-43h43v-43h-43v-42h171zM213 363v42h-42v-42h42zM213 277v43h-42v-43h42zM213 192v43h-42v-43h42zM213 107v42h-42v-42h42zM128 363v42h-43v-42h43zM128 277v43h-43v-43h43zM128 192v43
+h-43v-43h43zM128 107v42h-43v-42h43zM256 363h213v-299h-426v384h213v-85z" />
+    <glyph glyph-name="uniE7EF" unicode="group" 
+d="M341 235q28 0 61 -8t61 -26t28 -41v-53h-128v53q0 44 -42 74q7 1 20 1zM171 235q28 0 61 -8t60.5 -26t27.5 -41v-53h-299v53q0 23 28 41t61 26t61 8zM171 277q-26 0 -45 19t-19 45t19 45t45 19t44.5 -19t18.5 -45t-18.5 -45t-44.5 -19zM341 277q-26 0 -45 19t-19 45
+t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE7F0" unicode="group_add" 
+d="M277 235q40 0 84 -17.5t44 -46.5v-43h-256v43q0 29 44 46.5t84 17.5zM419 231q37 -6 65 -21.5t28 -38.5v-43h-64v43q0 34 -29 60zM277 277q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM384 277q-10 0 -19 3q19 27 19 61t-19 61q9 3 19 3
+q26 0 45 -19t19 -45t-19 -45t-45 -19zM171 299v-43h-64v-64h-43v64h-64v43h64v64h43v-64h64z" />
+    <glyph glyph-name="uniE7F1" unicode="location_city" 
+d="M405 192v43h-42v-43h42zM405 107v42h-42v-42h42zM277 363v42h-42v-42h42zM277 277v43h-42v-43h42zM277 192v43h-42v-43h42zM277 107v42h-42v-42h42zM149 277v43h-42v-43h42zM149 192v43h-42v-43h42zM149 107v42h-42v-42h42zM320 277h128v-213h-384v299h128v42l64 64
+l64 -64v-128z" />
+    <glyph glyph-name="uniE7F2" unicode="mood" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE7F3" unicode="mood_bad" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-218q13 33 42.5 53.5t66.5 20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE7F4" unicode="notifications" 
+d="M384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-18 0 -30.5 12.5t-12.5 29.5h86q0 -17 -13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE7F5" unicode="notifications_none" 
+d="M341 149v128q0 41 -23 68.5t-62 27.5t-62 -27.5t-23 -68.5v-128h170zM384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-17 0 -30 12.5t-13 29.5h86q0 -17 -13 -29.5
+t-30 -12.5z" />
+    <glyph glyph-name="uniE7F6" unicode="notifications_off" 
+d="M384 199l-191 201q3 1 8 3.5l7 3.5h1l6 3q1 0 4 1t5 1v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-78zM256 43q-18 0 -30.5 12t-12.5 30h86q0 -18 -12.5 -30t-30.5 -12zM167 381q24 -25 125.5 -129t155.5 -161l-27 -27l-43 43h-293v21l43 43
+v107q0 41 17 73l-60 59l27 28z" />
+    <glyph glyph-name="uniE7F7" unicode="notifications_active" 
+d="M256 43q-18 0 -30.5 12.5t-12.5 29.5h85q0 -18 -12 -30t-30 -12zM384 277v-106l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87zM426 288q-5 86 -75 137l30 30q83 -64 88 -167h-43zM162 425
+q-71 -50 -76 -137h-43q5 103 88 167z" />
+    <glyph glyph-name="uniE7F8" unicode="notifications_paused" 
+d="M309 303v38h-106v-38h59l-59 -73v-38h106v38h-59zM384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-18 0 -30.5 12.5t-12.5 29.5h86q0 -17 -13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE7F9" unicode="pages" 
+d="M405 448q17 0 30 -13t13 -30v-128h-107l22 86l-86 -22v107h128zM363 149l-22 86h107v-128q0 -17 -13 -30t-30 -13h-128v107zM171 235l-22 -86l86 22v-107h-128q-17 0 -30 13t-13 30v128h107zM64 405q0 17 13 30t30 13h128v-107l-86 22l22 -86h-107v128z" />
+    <glyph glyph-name="uniE7FA" unicode="party_mode" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5q0 7 -2 21h-45q4 -14 4 -21q0 -26 -19 -45t-45 -19h-85q33 -43 85 -43zM256 363q-44 0 -75.5 -31.5t-31.5 -75.5q0 -7 2 -21h45q-4 14 -4 21q0 26 19 45t45 19h85q-33 43 -85 43zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30
+t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68l39 42h128l39 -42h68z" />
+    <glyph glyph-name="uniE7FB" unicode="people" 
+d="M341 235q28 0 61 -8t61 -26t28 -41v-53h-128v53q0 44 -42 74q7 1 20 1zM171 235q28 0 61 -8t60.5 -26t27.5 -41v-53h-299v53q0 23 28 41t61 26t61 8zM171 277q-26 0 -45 19t-19 45t19 45t45 19t44.5 -19t18.5 -45t-18.5 -45t-44.5 -19zM341 277q-26 0 -45 19t-19 45
+t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE7FC" unicode="people_outline" 
+d="M352 373q-17 0 -30 -12.5t-13 -29.5t13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5zM352 256q-31 0 -53 22t-22 53t22 52.5t53 21.5t53 -21.5t22 -52.5t-22 -53t-53 -22zM160 373q-17 0 -30 -12.5t-13 -29.5t13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5zM160 256
+q-31 0 -53 22t-22 53t22 52.5t53 21.5t53 -21.5t22 -52.5t-22 -53t-53 -22zM459 139v26q0 10 -35.5 24t-71.5 14q-26 0 -64 -12q11 -13 11 -26v-26h160zM267 139v26q0 10 -35.5 24t-71.5 14t-71.5 -14t-35.5 -24v-26h214zM352 235q43 0 91 -19.5t48 -50.5v-58h-470v58
+q0 31 48 50.5t91 19.5q47 0 96 -22q49 22 96 22z" />
+    <glyph glyph-name="uniE7FD" unicode="person" 
+d="M256 213q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM256 256q-35 0 -60 25t-25 60t25 60.5t60 25.5t60 -25.5t25 -60.5t-25 -60t-60 -25z" />
+    <glyph glyph-name="uniE7FE" unicode="person_add" 
+d="M320 213q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM128 299h64v-43h-64v-64h-43v64h-64v43h64v64h43v-64zM320 256q-35 0 -60 25t-25 60t25 60.5t60 25.5t60 -25.5t25 -60.5t-25 -60t-60 -25z" />
+    <glyph glyph-name="uniE7FF" unicode="person_outline" 
+d="M256 235q32 0 70 -9t69.5 -30t31.5 -47v-64h-342v64q0 26 31.5 47t69.5 30t70 9zM256 427q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25t-60 25t-25 60t25 60.5t60 25.5zM256 194q-44 0 -87 -16.5t-43 -28.5v-23h260v23q0 12 -43 28.5t-87 16.5zM256 386q-19 0 -32 -13
+t-13 -32t13 -31.5t32 -12.5t32 12.5t13 31.5t-13 32t-32 13z" />
+    <glyph glyph-name="uniE800" unicode="plus_one" 
+d="M309 382l96 23v-277h-42v226l-54 -11v39zM213 341v-85h86v-43h-86v-85h-42v85h-86v43h86v85h42z" />
+    <glyph glyph-name="uniE801" unicode="poll" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE80B" unicode="public" 
+d="M382 141q45 48 45 115q0 53 -29.5 96t-77.5 62v-9q0 -17 -13 -29.5t-30 -12.5h-42v-43q0 -9 -6.5 -15t-15.5 -6h-42v-43h128q9 0 15 -6t6 -15v-64h21q30 0 41 -30zM235 87v41q-17 0 -30 13t-13 30v21l-102 102q-5 -20 -5 -38q0 -65 43.5 -113t106.5 -56zM256 469
+q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE80C" unicode="school" 
+d="M256 448l235 -128v-171h-43v148l-192 -105l-235 128zM107 231l149 -82l149 82v-86l-149 -81l-149 81v86z" />
+    <glyph glyph-name="uniE80D" unicode="share" 
+d="M384 169q26 0 44 -18.5t18 -43.5q0 -26 -18.5 -44.5t-43.5 -18.5t-43.5 18.5t-18.5 44.5q0 10 1 14l-151 88q-19 -17 -44 -17q-26 0 -45 19t-19 45t19 45t45 19q25 0 44 -17l150 87q-2 10 -2 15q0 26 19 45t45 19t45 -19t19 -45t-19 -45t-45 -19q-24 0 -44 18l-150 -88
+q2 -10 2 -15t-2 -15l152 -88q18 16 42 16z" />
+    <glyph glyph-name="uniE80E" unicode="whatshot" 
+d="M250 107q43 0 72.5 29.5t29.5 72.5q0 44 -12 86q-31 -41 -99 -55q-60 -13 -60 -66q0 -28 20 -47.5t49 -19.5zM288 498q64 -52 101.5 -126.5t37.5 -158.5q0 -70 -50 -120t-121 -50t-121 50t-50 120q0 108 69 190v-8q0 -33 22 -56t55 -23q32 0 52.5 22.5t20.5 56.5
+q0 20 -4 46t-8 41z" />
+    <glyph glyph-name="uniE811" unicode="sentiment_dissatisfied" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-35q-25 42 -74 42t-74 -42h-35q13 33 42.5 53.5t66.5 20.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5
+t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5z" />
+    <glyph glyph-name="uniE812" unicode="sentiment_neutral" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5
+t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM192 213h128v-32h-128v32z" />
+    <glyph glyph-name="uniE813" unicode="sentiment_satisfied" 
+d="M256 171q49 0 74 42h35q-13 -33 -42.5 -53.5t-66.5 -20.5t-66.5 20.5t-42.5 53.5h35q25 -42 74 -42zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5
+t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5
+t-9.5 22.5z" />
+    <glyph glyph-name="uniE814" unicode="sentiment_very_dissatisfied" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-218q13 33 42.5 53.5t66.5 20.5zM167 256l-23 23l23 22l-23 23l23 22l22 -22l23 22l23 -22l-23 -23l23 -22l-23 -23l-23 23zM345 346l23 -22l-23 -23l23 -22l-23 -23l-22 23l-23 -23l-23 23l23 22l-23 23l23 22l23 -22zM256 85
+q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE815" unicode="sentiment_very_satisfied" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM189 300l-22 -23l-23 23l45 45l46 -45l-23 -23zM277 300l46 45l45 -45l-23 -23l-22 23l-23 -23zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5
+t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE834" unicode="check_box" 
+d="M213 149l192 192l-30 31l-162 -162l-76 76l-30 -30zM405 448q18 0 30.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-30.5 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE835" unicode="check_box_outline_blank" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM405 405h-298v-298h298v298z" />
+    <glyph glyph-name="uniE836" unicode="radio_button_unchecked" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE837" unicode="radio_button_checked" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM256 363q44 0 75.5 -31.5
+t31.5 -75.5t-31.5 -75.5t-75.5 -31.5t-75.5 31.5t-31.5 75.5t31.5 75.5t75.5 31.5z" />
+    <glyph glyph-name="uniE838" unicode="star" 
+d="M256 144l-132 -80l35 150l-116 101l153 13l60 141l60 -141l153 -13l-116 -101l35 -150z" />
+    <glyph glyph-name="uniE839" unicode="star_half" 
+d="M256 183l80 -48l-21 91l71 62l-94 8l-36 86v-199zM469 315l-116 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141z" />
+    <glyph glyph-name="uniE83A" unicode="star_outline" 
+d="M256 183l80 -48l-21 91l71 62l-94 8l-36 86l-36 -86l-94 -8l71 -62l-21 -91zM469 315l-116 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141z" />
+    <glyph glyph-name="uniE84D" unicode="&#x33;d_rotation" 
+d="M256 512q100 0 173.5 -67.5t81.5 -166.5h-32q-5 59 -39.5 107t-87.5 73l-29 -28l-81 81zM353 260q0 57 -47 57h-20v-123h19q36 0 46 34q2 7 2 24v8zM306 341q53 0 72 -47q5 -12 5 -34v-8q0 -38 -21 -59q-22 -22 -57 -22h-49v170h50zM207 258q28 -11 28 -39q0 -10 -5 -20
+q-6 -12 -11 -16q-15 -12 -40 -12q-24 0 -39 12t-15 35h27q0 -11 7.5 -18t19.5 -7q28 0 28 27t-31 27h-16v22h16q29 0 29 25t-26 25q-25 0 -25 -23h-28q0 17 15 32q16 13 38 13q35 0 49 -27q4 -8 4 -20q0 -23 -25 -36zM160 54l29 28l81 -81l-14 -1q-100 0 -173.5 68
+t-81.5 167h32q6 -60 40 -108t87 -73z" />
+    <glyph glyph-name="uniE84E" unicode="accessibility" 
+d="M448 320h-128v-277h-43v128h-42v-128h-43v277h-128v43h384v-43zM256 469q17 0 30 -12.5t13 -29.5t-13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5z" />
+    <glyph glyph-name="uniE84F" unicode="account_balance" 
+d="M245 491l203 -107v-43h-405v43zM341 299h64v-150h-64v150zM43 43v64h405v-64h-405zM213 299h64v-150h-64v150zM85 299h64v-150h-64v150z" />
+    <glyph glyph-name="uniE850" unicode="account_balance_wallet" 
+d="M341 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 171v170h213v-170h-213zM448 128v-21q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298q17 0 30 -13t13 -30v-21h-192q-18 0 -30.5 -13t-12.5 -30
+v-170q0 -17 12.5 -30t30.5 -13h192z" />
+    <glyph glyph-name="uniE851" unicode="account_box" 
+d="M128 149v-21h256v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5zM320 320q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM64 405q0 17 12.5 30t30.5 13h298q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298z" />
+    <glyph glyph-name="uniE853" unicode="account_circle" 
+d="M256 102q81 0 128 69q-1 28 -45 47t-83 19t-83 -18.5t-45 -47.5q47 -69 128 -69zM256 405q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE854" unicode="add_shopping_cart" 
+d="M153 197q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53l-77 162h-43v42h70q20 -42 40 -85q5 -9 23 -47.5t28 -59.5h150q75 136 82 150l37 -21l-82 -149q-12 -22 -37 -22h-159l-19 -35zM363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5
+t-30 12.5t-13 29.5t13 30t30 13zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13zM235 320v64h-64v43h64v64h42v-64h64v-43h-64v-64h-42z" />
+    <glyph glyph-name="uniE855" unicode="alarm" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM267 341v-112l85 -50l-16 -26l-101 60v128h32zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE856" unicode="alarm_add" 
+d="M277 320v-64h64v-43h-64v-64h-42v64h-64v43h64v64h42zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE857" unicode="alarm_off" 
+d="M171 442l-18 -15l-31 30l19 15zM351 120l-210 210q-34 -42 -34 -95q0 -62 43.5 -106t105.5 -44q52 0 95 35zM62 463q76 -76 216.5 -216t177.5 -177l-27 -27l-47 47q-55 -47 -126 -47q-80 0 -136 56.5t-56 135.5q0 70 47 125l-17 17l-24 -20l-30 31l24 19l-29 29zM469 390
+l-27 -33l-98 83l27 32zM256 384q-27 0 -51 -9l-33 32q42 20 84 20q80 0 136 -56.5t56 -135.5q0 -44 -19 -84l-33 32q9 24 9 52q0 62 -43.5 105.5t-105.5 43.5z" />
+    <glyph glyph-name="uniE858" unicode="alarm_on" 
+d="M225 202l105 106l23 -23l-128 -128l-68 68l22 22zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE859" unicode="android" 
+d="M320 405v22h-21v-22h21zM213 405v22h-21v-22h21zM331 466q53 -38 53 -103h-256q0 66 52 103l-28 28q-8 8 0 15t15 0l32 -32q26 14 57 14q30 0 56 -14l32 32q8 7 15 0t0 -15zM437 341q13 0 22.5 -9.5t9.5 -22.5v-149q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v149
+q0 13 9.5 22.5t22.5 9.5zM75 341q13 0 22.5 -9.5t9.5 -22.5v-149q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v149q0 13 9.5 22.5t22.5 9.5zM128 128v213h256v-213q0 -9 -6 -15t-15 -6h-22v-75q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v75h-42v-75q0 -14 -9.5 -23t-22.5 -9
+t-22.5 9t-9.5 23v75h-22q-9 0 -15 6t-6 15z" />
+    <glyph glyph-name="uniE85A" unicode="announcement" 
+d="M277 192v43h-42v-43h42zM277 277v128h-42v-128h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE85B" unicode="aspect_ratio" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM149 320v-64h-42v107h106v-43h-64zM405 256v-107h-106v43h64v64h42z" />
+    <glyph glyph-name="uniE85C" unicode="assessment" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE85D" unicode="assignment" 
+d="M363 320v43h-214v-43h214zM363 235v42h-214v-42h214zM299 149v43h-150v-43h150zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89
+q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE85E" unicode="assignment_ind" 
+d="M384 107v30q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-30h256zM256 363q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30
+t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE85F" unicode="assignment_late" 
+d="M256 405q9 0 15 6.5t6 15.5t-6 15t-15 6t-15 -6t-6 -15t6 -15.5t15 -6.5zM277 213v128h-42v-128h42zM277 128v43h-42v-43h42zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31
+h89z" />
+    <glyph glyph-name="uniE860" unicode="assignment_return" 
+d="M341 192v85h-85v64l-107 -106l107 -107v64h85zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89
+z" />
+    <glyph glyph-name="uniE861" unicode="assignment_returned" 
+d="M256 128l107 107h-64v85h-86v-85h-64zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE862" unicode="assignment_turned_in" 
+d="M213 149l171 171l-30 30l-141 -140l-55 55l-30 -30zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12
+t23 -31h89z" />
+    <glyph glyph-name="uniE863" unicode="autorenew" 
+d="M400 347q27 -41 27 -91q0 -70 -50.5 -120.5t-120.5 -50.5v-64l-85 86l85 85v-64q53 0 90.5 37.5t37.5 90.5q0 30 -15 60zM256 384q-53 0 -90.5 -37.5t-37.5 -90.5q0 -33 15 -60l-31 -31q-27 41 -27 91q0 70 50.5 120.5t120.5 50.5v64l85 -86l-85 -85v64z" />
+    <glyph glyph-name="uniE864" unicode="backup" 
+d="M299 235h64l-107 106l-107 -106h64v-86h86v86zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE865" unicode="book" 
+d="M128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE866" unicode="bookmark" 
+d="M363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE867" unicode="bookmark_outline" 
+d="M363 128v277h-214v-277l107 47zM363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE868" unicode="bug_report" 
+d="M299 256v43h-86v-43h86zM299 171v42h-86v-42h86zM427 341v-42h-45q2 -14 2 -22v-21h43v-43h-43v-21q0 -7 -2 -21h45v-43h-60q-17 -29 -46.5 -46.5t-64.5 -17.5t-64.5 17.5t-46.5 46.5h-60v43h45q-2 14 -2 21v21h-43v43h43v21q0 8 2 22h-45v42h60q15 25 39 42l-35 35
+l30 30l47 -46q15 3 30 3t30 -3l47 46l30 -30l-35 -35q24 -17 39 -42h60z" />
+    <glyph glyph-name="uniE869" unicode="build" 
+d="M484 107q7 -4 6.5 -13.5t-8.5 -16.5l-49 -49q-15 -15 -30 0l-194 194q-36 -15 -76.5 -6.5t-70.5 38.5q-32 32 -40 76t12 82l94 -92l64 64l-92 92q38 18 82 11t76 -39q30 -30 38.5 -70.5t-6.5 -76.5z" />
+    <glyph glyph-name="uniE86A" unicode="cached" 
+d="M128 256h64l-85 -85l-86 85h64q0 70 50.5 120.5t120.5 50.5q50 0 91 -27l-31 -31q-27 15 -60 15q-53 0 -90.5 -37.5t-37.5 -90.5zM405 341l86 -85h-64q0 -70 -50.5 -120.5t-120.5 -50.5q-50 0 -91 27l31 31q27 -15 60 -15q53 0 90.5 37.5t37.5 90.5h-64z" />
+    <glyph glyph-name="uniE86B" unicode="change_history" 
+d="M256 427l213 -342h-426zM256 346l-136 -218h272z" />
+    <glyph glyph-name="uniE86C" unicode="check_circle" 
+d="M213 149l192 192l-30 31l-162 -162l-76 76l-30 -30zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE86D" unicode="chrome_reader_mode" 
+d="M448 107v277h-192v-277h192zM448 427q17 0 30 -13t13 -30v-277q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v277q0 17 13 30t30 13h384zM277 203h150v-32h-150v32zM277 309h150v-32h-150v32zM277 256h150v-32h-150v32z" />
+    <glyph glyph-name="uniE86E" unicode="class" 
+d="M128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE86F" unicode="code" 
+d="M311 158l99 98l-99 98l30 30l128 -128l-128 -128zM201 158l-30 -30l-128 128l128 128l30 -30l-99 -98z" />
+    <glyph glyph-name="uniE870" unicode="credit_card" 
+d="M427 341v43h-342v-43h342zM427 128v128h-342v-128h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE871" unicode="dashboard" 
+d="M277 448h171v-128h-171v128zM277 64v213h171v-213h-171zM64 64v128h171v-128h-171zM64 235v213h171v-213h-171z" />
+    <glyph glyph-name="uniE872" unicode="delete" 
+d="M405 427v-43h-298v43h74l22 21h106l22 -21h74zM128 107v256h256v-256q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30z" />
+    <glyph glyph-name="uniE873" unicode="description" 
+d="M277 320h118l-118 117v-117zM341 213v43h-170v-43h170zM341 128v43h-170v-43h170zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE875" unicode="dns" 
+d="M149 320q17 0 30 13t13 30t-13 29.5t-30 12.5t-29.5 -12.5t-12.5 -29.5t12.5 -30t29.5 -13zM427 448q9 0 15 -6t6 -15v-128q0 -9 -6 -15.5t-15 -6.5h-342q-9 0 -15 6.5t-6 15.5v128q0 9 6 15t15 6h342zM149 107q17 0 30 12.5t13 29.5t-13 30t-30 13t-29.5 -13t-12.5 -30
+t12.5 -29.5t29.5 -12.5zM427 235q9 0 15 -6.5t6 -15.5v-128q0 -9 -6 -15t-15 -6h-342q-9 0 -15 6t-6 15v128q0 9 6 15.5t15 6.5h342z" />
+    <glyph glyph-name="uniE876" unicode="done" 
+d="M192 166l226 227l30 -30l-256 -256l-119 119l29 30z" />
+    <glyph glyph-name="uniE877" unicode="done_all" 
+d="M9 226l30 30l119 -119l-30 -30zM474 393l31 -30l-256 -256l-120 119l31 30l89 -89zM384 363l-135 -136l-30 30l135 136z" />
+    <glyph glyph-name="uniE878" unicode="event" 
+d="M405 107v234h-298v-234h298zM341 491h43v-43h21q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43zM363 256v-107h-107v107h107z" />
+    <glyph glyph-name="uniE879" unicode="exit_to_app" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v85h43v-85h298v298h-298v-85h-43v85q0 17 12.5 30t30.5 13h298zM215 179l55 56h-206v42h206l-55 56l30 30l107 -107l-107 -107z" />
+    <glyph glyph-name="uniE87A" unicode="explore" 
+d="M303 209l81 175l-175 -81l-81 -175zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM256 279q10 0 16.5 -6.5t6.5 -16.5t-6.5 -16.5t-16.5 -6.5t-16.5 6.5t-6.5 16.5t6.5 16.5t16.5 6.5z" />
+    <glyph glyph-name="uniE87B" unicode="extension" 
+d="M437 277q22 0 38 -15.5t16 -37.5t-16 -37.5t-38 -15.5h-32v-86q0 -17 -12.5 -29.5t-29.5 -12.5h-81v32q0 24 -17 40.5t-41 16.5t-41 -16.5t-17 -40.5v-32h-81q-17 0 -29.5 12.5t-12.5 29.5v81h32q24 0 40.5 17t16.5 41t-16.5 41t-40.5 17h-32v81q0 17 12.5 29.5
+t29.5 12.5h86v32q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-32h86q17 0 29.5 -12.5t12.5 -29.5v-86h32z" />
+    <glyph glyph-name="uniE87C" unicode="face" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 22 -7 48q-19 -5 -48 -5q-110 0 -174 90q-33 -80 -112 -115q-1 -6 -1 -18q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z
+M320 261q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-19 8t-8 19t8 18.5t19 7.5zM192 261q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-19 8t-8 19t8 18.5t19 7.5z" />
+    <glyph glyph-name="uniE87D" unicode="favorite" 
+d="M256 57l-31 28q-53 48 -77 71t-53.5 57t-40.5 61.5t-11 56.5q0 49 33.5 83t83.5 34q58 0 96 -45q38 45 96 45q50 0 83.5 -34t33.5 -83q0 -39 -26 -81t-56.5 -73t-99.5 -93z" />
+    <glyph glyph-name="uniE87E" unicode="favorite_outline" 
+d="M258 116q48 43 71 65t50 52t37.5 53t10.5 45q0 32 -21.5 53t-53.5 21q-25 0 -46.5 -14t-29.5 -36h-40q-8 22 -29.5 36t-46.5 14q-32 0 -53.5 -21t-21.5 -53q0 -22 10.5 -45t37.5 -53t50 -52t71 -65l2 -2zM352 448q50 0 83.5 -34t33.5 -83q0 -29 -11 -56.5t-40.5 -61.5
+t-53.5 -57t-77 -71l-31 -28l-31 27q-69 62 -99.5 93t-56.5 73t-26 81q0 49 33.5 83t83.5 34q58 0 96 -45q38 45 96 45z" />
+    <glyph glyph-name="uniE87F" unicode="feedback" 
+d="M277 299v85h-42v-85h42zM277 213v43h-42v-43h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE880" unicode="find_in_page" 
+d="M192 235q0 26 19 45t45 19t45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45zM427 94l-82 82q18 28 18 59q0 44 -31.5 75t-75.5 31t-75.5 -31t-31.5 -75t31.5 -75.5t75.5 -31.5q31 0 59 18l94 -95q-11 -8 -25 -8h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5
+t29.5 12.5h171l128 -128v-247z" />
+    <glyph glyph-name="uniE881" unicode="find_replace" 
+d="M355 189l103 -104l-31 -31l-104 103q-40 -29 -88 -29q-62 0 -106 44l-44 -44v128h128l-54 -54q31 -31 76 -31q39 0 67.5 24t36.5 61h43q-4 -36 -27 -67zM235 384q-39 0 -68 -24t-37 -61h-43q8 54 50 91t98 37q61 0 105 -44l44 44v-128h-128l54 54q-31 31 -75 31z" />
+    <glyph glyph-name="uniE882" unicode="flip_to_back" 
+d="M320 149v43h43v-43h-43zM320 405v43h43v-43h-43zM107 363v-256h256v-43h-256q-18 0 -30.5 13t-12.5 30v256h43zM405 149v43h43q0 -17 -13 -30t-30 -13zM405 320v43h43v-43h-43zM405 235v42h43v-42h-43zM192 149q-18 0 -30.5 13t-12.5 30h43v-43zM277 448v-43h-42v43h42z
+M405 448q17 0 30 -13t13 -30h-43v43zM277 192v-43h-42v43h42zM192 448v-43h-43q0 17 12.5 30t30.5 13zM192 277v-42h-43v42h43zM192 363v-43h-43v43h43z" />
+    <glyph glyph-name="uniE883" unicode="flip_to_front" 
+d="M149 64v43h43v-43h-43zM235 64v43h42v-43h-42zM405 192v213h-213v-213h213zM405 448q17 0 30 -13t13 -30v-213q0 -17 -13 -30t-30 -13h-213q-18 0 -30.5 13t-12.5 30v213q0 17 12.5 30t30.5 13h213zM320 64v43h43v-43h-43zM64 320v43h43v-43h-43zM107 64q-18 0 -30.5 13
+t-12.5 30h43v-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z" />
+    <glyph glyph-name="uniE884" unicode="get_app" 
+d="M107 128h298v-43h-298v43zM405 320l-149 -149l-149 149h85v128h128v-128h85z" />
+    <glyph glyph-name="uniE885" unicode="grade" 
+d="M256 144l-132 -80l35 150l-116 101l153 13l60 141l60 -141l153 -13l-116 -101l35 -150z" />
+    <glyph glyph-name="uniE886" unicode="group_work" 
+d="M341 139q22 0 38 15.5t16 37.5t-16 37.5t-38 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM203 341q0 -22 15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 38t-37.5 16t-37.5 -16t-15.5 -38zM171 139q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5
+t-38 -15.5t-16 -37.5t16 -37.5t38 -15.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE887" unicode="help" 
+d="M321 272q20 20 20 48q0 35 -25 60t-60 25t-60 -25t-25 -60h42q0 17 13 30t30 13t30 -13t13 -30t-13 -30l-26 -27q-25 -27 -25 -60v-11h42q0 33 25 60zM277 107v42h-42v-42h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5
+t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE888" unicode="highlight_remove" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM311 341l30 -30l-55 -55l55 -55
+l-30 -30l-55 55l-55 -55l-30 30l55 55l-55 55l30 30l55 -55z" />
+    <glyph glyph-name="uniE889" unicode="history" 
+d="M256 341h32v-90l75 -45l-16 -26l-91 55v106zM277 448q79 0 135.5 -56t56.5 -136t-56.5 -136t-135.5 -56t-135 56l30 31q44 -44 105 -44q62 0 106 43.5t44 105.5t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -86l-2 3l-83 83h64q0 80 56.5 136t135.5 56z" />
+    <glyph glyph-name="uniE88A" unicode="home" 
+d="M213 85h-106v171h-64l213 192l213 -192h-64v-171h-106v128h-86v-128z" />
+    <glyph glyph-name="uniE88B" unicode="hourglass_empty" 
+d="M256 267l85 85v75h-170v-75zM341 160l-85 85l-85 -85v-75h170v75zM128 469h256v-128l-85 -85l85 -85v-128h-256v128l85 85l-85 85v128z" />
+    <glyph glyph-name="uniE88C" unicode="hourglass_full" 
+d="M128 469h256v-128l-85 -85l85 -85v-128h-256v128l85 85l-85 85v128z" />
+    <glyph glyph-name="uniE88D" unicode="https" 
+d="M322 341v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5v-43h132zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5
+t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE88E" unicode="info" 
+d="M277 320v43h-42v-43h42zM277 149v128h-42v-128h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE88F" unicode="info_outline" 
+d="M235 320v43h42v-43h-42zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 149
+v128h42v-128h-42z" />
+    <glyph glyph-name="uniE890" unicode="input" 
+d="M235 171v64h-214v42h214v64l85 -85zM448 448q17 0 30 -12.5t13 -30.5v-299q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v86h43v-86h384v300h-384v-86h-43v85q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE891" unicode="invert_colors_on" 
+d="M256 94v309l-90 -90q-38 -38 -38 -91q0 -52 38 -90t90 -38zM377 343q50 -50 50 -120.5t-50 -120.5t-121 -50t-121 50t-50 120.5t50 120.5l121 121z" />
+    <glyph glyph-name="uniE892" unicode="label" 
+d="M376 387l93 -131l-93 -131q-13 -18 -35 -18h-234q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h234q22 0 35 -18z" />
+    <glyph glyph-name="uniE893" unicode="label_outline" 
+d="M341 149l76 107l-76 107h-234v-214h234zM376 387l93 -131l-93 -131q-13 -18 -35 -18h-234q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h234q22 0 35 -18z" />
+    <glyph glyph-name="uniE894" unicode="language" 
+d="M349 213h72q6 28 6 43t-6 43h-72q3 -21 3 -43t-3 -43zM311 95q61 20 93 76h-63q-10 -40 -30 -76zM306 213q3 21 3 43t-3 43h-100q-3 -21 -3 -43t3 -43h100zM256 86q28 41 41 85h-82q13 -44 41 -85zM171 341q10 40 30 76q-61 -20 -93 -76h63zM108 171q32 -56 93 -76
+q-20 36 -30 76h-63zM91 213h72q-3 21 -3 43t3 43h-72q-6 -28 -6 -43t6 -43zM256 426q-28 -41 -41 -85h82q-13 44 -41 85zM404 341q-32 56 -93 76q20 -36 30 -76h63zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE895" unicode="launch" 
+d="M299 448h149v-149h-43v76l-209 -209l-30 30l209 209h-76v43zM405 107v149h43v-149q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h149v-43h-149v-298h298z" />
+    <glyph glyph-name="uniE896" unicode="list" 
+d="M149 363h299v-43h-299v43zM149 149v43h299v-43h-299zM149 235v42h299v-42h-299zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z" />
+    <glyph glyph-name="uniE897" unicode="lock" 
+d="M322 341v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5v-43h132zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5
+t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE898" unicode="lock_open" 
+d="M384 85v214h-256v-214h256zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h194v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5h-41q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5
+v-43h21zM256 149q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE899" unicode="lock_outline" 
+d="M384 85v214h-256v-214h256zM190 384v-43h132v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5
+t75.5 -31.5t31.5 -75.5v-43h21zM256 149q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE89A" unicode="loyalty" 
+d="M368 186q16 16 16 38t-15.5 37.5t-37.5 15.5q-23 0 -38 -15l-16 -16l-15 16q-15 15 -38 15q-22 0 -37.5 -15.5t-15.5 -37.5q0 -23 15 -38l91 -91zM117 363q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM457 265
+q12 -12 12 -30t-12 -30l-150 -150q-12 -12 -30 -12t-30 12l-192 192q-12 12 -12 30v150q0 17 12.5 29.5t29.5 12.5h150q18 0 30 -12z" />
+    <glyph glyph-name="uniE89B" unicode="markunread_mailbox" 
+d="M427 384q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v256q0 17 12.5 30t29.5 13h43v128h171v-85h-128v-171h42v128h214z" />
+    <glyph glyph-name="uniE89C" unicode="note_add" 
+d="M277 320h118l-118 117v-117zM341 171v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE89D" unicode="open_in_browser" 
+d="M256 299l85 -86h-64v-128h-42v128h-64zM405 427q18 0 30.5 -13t12.5 -30v-256q0 -17 -13 -30t-30 -13h-85v43h85v213h-298v-213h85v-43h-85q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE89E" unicode="open_in_new" 
+d="M299 448h149v-149h-43v76l-209 -209l-30 30l209 209h-76v43zM405 107v149h43v-149q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h149v-43h-149v-298h298z" />
+    <glyph glyph-name="uniE89F" unicode="open_with" 
+d="M299 192v-64h64l-107 -107l-107 107h64v64h86zM491 256l-107 -107v64h-64v86h64v64zM192 299v-86h-64v-64l-107 107l107 107v-64h64zM213 320v64h-64l107 107l107 -107h-64v-64h-86z" />
+    <glyph glyph-name="uniE8A0" unicode="pageview" 
+d="M358 124l30 30l-62 62q15 25 15 51q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28q26 0 51 15zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342zM245 320q22 0 38 -15.5t16 -37.5
+t-16 -38t-38 -16t-37.5 16t-15.5 38t15.5 37.5t37.5 15.5z" />
+    <glyph glyph-name="uniE8A1" unicode="payment" 
+d="M427 341v43h-342v-43h342zM427 128v128h-342v-128h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE8A2" unicode="perm_camera_mic" 
+d="M299 235v85q0 17 -13 30t-30 13t-30 -13t-13 -30v-85q0 -17 13 -30t30 -13t30 13t13 30zM427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-150v45q45 8 76 43.5t31 82.5h-43q0 -35 -25 -60.5t-60 -25.5t-60 25.5t-25 60.5h-43q0 -47 31 -82.5
+t76 -43.5v-45h-150q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h68l39 43h128l39 -43h68z" />
+    <glyph glyph-name="uniE8A3" unicode="perm_contact_calendar" 
+d="M384 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM256 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43
+v-43h170v43h43v-43h21z" />
+    <glyph glyph-name="uniE8A4" unicode="perm_data_setting" 
+d="M405 75q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM484 96l23 -17q3 -3 1 -7l-21 -37q-3 -5 -7 -3l-26 11q-3 -3 -18 -10l-4 -29q0 -4 -6 -4h-42q-6 0 -6 4l-4 29q-8 4 -18 10l-26 -11q-5 -2 -7 3l-21 37q-2 4 1 7l23 17
+q0 1 -0.5 5t-0.5 6t0.5 5.5t0.5 4.5l-23 18q-3 3 -1 7l21 37q2 4 7 2l26 -11q12 8 18 11l4 28q0 4 6 4h42q6 0 6 -4l4 -28q4 -2 18 -11l26 11q5 2 7 -2l21 -37q2 -4 -1 -7l-23 -18q1 -3 1 -10q0 -2 -0.5 -6t-0.5 -5zM405 267q-66 0 -113 -47t-47 -113q0 -8 2 -22h-247
+l427 427l-1 -247q-14 2 -21 2z" />
+    <glyph glyph-name="uniE8A5" unicode="perm_device_information" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13zM277 277v-128h-42v128h42zM277 363v-43h-42v43h42z" />
+    <glyph glyph-name="uniE8A6" unicode="perm_identity" 
+d="M256 235q32 0 70 -9t69.5 -30t31.5 -47v-64h-342v64q0 26 31.5 47t69.5 30t70 9zM256 427q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25t-60 25t-25 60t25 60.5t60 25.5zM256 194q-44 0 -87 -16.5t-43 -28.5v-23h260v23q0 12 -43 28.5t-87 16.5zM256 386q-19 0 -32 -13
+t-13 -32t13 -31.5t32 -12.5t32 12.5t13 31.5t-13 32t-32 13z" />
+    <glyph glyph-name="uniE8A7" unicode="perm_media" 
+d="M149 192h299l-75 96l-53 -64l-75 96zM469 427q17 0 30 -13t13 -30v-213q0 -17 -13 -30t-30 -13h-341q-17 0 -30 13t-13 30l1 256q0 17 12.5 29.5t29.5 12.5h128l43 -42h170zM43 384v-299h384v-42h-384q-17 0 -30 12.5t-13 29.5v299h43z" />
+    <glyph glyph-name="uniE8A8" unicode="perm_phone_msg" 
+d="M256 448h192v-149h-128l-64 -64v213zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q48 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE8A9" unicode="perm_scan_wifi" 
+d="M235 341h42v43h-42v-43zM277 171v128h-42v-128h42zM256 448q136 0 256 -91l-256 -314l-256 315q118 90 256 90z" />
+    <glyph glyph-name="uniE8AA" unicode="picture_in_picture" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM405 363v-128h-170v128h170z" />
+    <glyph glyph-name="uniE8AB" unicode="polymer" 
+d="M405 427l96 -171l-96 -171h-85l96 171l-56 99l-168 -270h-85l-96 171l96 171h85l-96 -171l56 -99l168 270h85z" />
+    <glyph glyph-name="uniE8AC" unicode="power_settings_new" 
+d="M380 402q68 -58 68 -146q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 88 68 146l30 -30q-55 -45 -55 -116q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5t43.5 105.5q0 71 -55 115zM277 448v-213h-42v213h42z" />
+    <glyph glyph-name="uniE8AD" unicode="print" 
+d="M384 448v-85h-256v85h256zM405 256q9 0 15.5 6t6.5 15t-6.5 15.5t-15.5 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM341 107v106h-170v-106h170zM405 341q26 0 45 -19t19 -45v-128h-85v-85h-256v85h-85v128q0 26 19 45t45 19h298z" />
+    <glyph glyph-name="uniE8AE" unicode="query_builder" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE8AF" unicode="question_answer" 
+d="M363 256q0 -9 -6.5 -15t-15.5 -6h-213l-85 -86v299q0 9 6 15t15 6h277q9 0 15.5 -6t6.5 -15v-192zM448 384q9 0 15 -6t6 -15v-320l-85 85h-235q-9 0 -15 6t-6 15v43h277v192h43z" />
+    <glyph glyph-name="uniE8B0" unicode="receipt" 
+d="M64 43v426l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32v-426l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32zM384 320v43h-256v-43h256zM384 235v42h-256v-42h256zM384 149v43h-256v-43h256z
+" />
+    <glyph glyph-name="uniE8B1" unicode="redeem" 
+d="M427 213v128h-109l45 -60l-35 -25q-64 87 -72 98q-8 -11 -72 -98l-35 25l45 60h-109v-128h342zM427 107v42h-342v-42h342zM192 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5t-15 6.5zM320 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5
+t-15 6.5zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h47q-4 14 -4 21q0 26 19 45t45 19q33 0 53 -28l11 -15l11 15q20 28 53 28q26 0 45 -19t19 -45q0 -7 -4 -21h47z" />
+    <glyph glyph-name="uniE8B2" unicode="report_problem" 
+d="M277 213v86h-42v-86h42zM277 128v43h-42v-43h42zM21 64l235 405l235 -405h-470z" />
+    <glyph glyph-name="uniE8B3" unicode="restore" 
+d="M256 341h32v-90l75 -45l-16 -26l-91 55v106zM277 448q79 0 135.5 -56t56.5 -136t-56.5 -136t-135.5 -56t-135 56l30 31q44 -44 105 -44q62 0 106 43.5t44 105.5t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -86l-2 3l-83 83h64q0 80 56.5 136t135.5 56z" />
+    <glyph glyph-name="uniE8B4" unicode="room" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE8B5" unicode="schedule" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE8B6" unicode="search" 
+d="M203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE8B8" unicode="settings" 
+d="M256 181q31 0 53 22t22 53t-22 53t-53 22t-53 -22t-22 -53t22 -53t53 -22zM415 235l45 -35q7 -5 2 -14l-43 -74q-4 -7 -13 -4l-53 21q-21 -15 -36 -21l-8 -56q-2 -9 -10 -9h-86q-8 0 -10 9l-8 56q-19 8 -36 21l-53 -21q-9 -3 -13 4l-43 74q-5 9 2 14l45 35q-1 7 -1 21
+t1 21l-45 35q-7 5 -2 14l43 74q4 7 13 4l53 -21q21 15 36 21l8 56q2 9 10 9h86q8 0 10 -9l8 -56q19 -8 36 -21l53 21q9 3 13 -4l43 -74q5 -9 -2 -14l-45 -35q1 -7 1 -21t-1 -21z" />
+    <glyph glyph-name="uniE8B9" unicode="settings_applications" 
+d="M368 256q0 10 -1 15l32 24q5 4 1 10l-30 52q-3 5 -9 3l-37 -15q-13 10 -25 15l-6 39q-2 6 -7 6h-60q-6 0 -7 -6l-6 -40q-16 -7 -25 -14l-37 15q-5 2 -9 -4l-30 -51q-4 -6 1 -10l32 -24q-1 -5 -1 -15t1 -15l-32 -24q-5 -4 -1 -10l30 -52q3 -5 9 -3l37 15q13 -10 25 -15
+l6 -39q2 -6 7 -6h60q6 0 7 6l6 40q16 7 25 14l37 -15q5 -2 9 4l30 51q4 6 -1 10l-32 24q1 5 1 15zM405 448q18 0 30.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-30.5 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298zM256 299q17 0 30 -13t13 -30t-13 -30
+t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE8BA" unicode="settings_backup_restore" 
+d="M256 448q80 0 136 -56t56 -136t-56 -136t-136 -56q-66 0 -117 40l30 30q40 -27 87 -27q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -85l-85 85h64q0 80 56 136t136 56zM299 256q0 -17 -13 -30t-30 -13t-30 13t-13 30t13 30
+t30 13t30 -13t13 -30z" />
+    <glyph glyph-name="uniE8BB" unicode="settings_bluetooth" 
+d="M317 207l-40 40v-80zM277 430v-80l40 40zM378 390l-92 -91l92 -92l-122 -122h-21v162l-98 -98l-30 30l119 120l-119 119l30 30l98 -98v162h21zM320 0v43h43v-43h-43zM149 0v43h43v-43h-43zM235 0v43h42v-43h-42z" />
+    <glyph glyph-name="uniE8BC" unicode="settings_cell" 
+d="M341 171v256h-170v-256h170zM341 512q17 0 30 -13t13 -30v-341q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30v341q0 17 13 30t30 13h170zM320 0v43h43v-43h-43zM235 0v43h42v-43h-42zM149 0v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8BD" unicode="settings_brightness" 
+d="M256 320v-128q26 0 45 19t19 45t-19 45t-45 19zM171 171v53l-32 32l32 32v53h53l32 32l32 -32h53v-53l32 -32l-32 -32v-53h-53l-32 -32l-32 32h-53zM448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298
+q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE8BE" unicode="settings_ethernet" 
+d="M379 395l116 -139l-116 -139l-33 27l93 112l-93 112zM235 235v42h42v-42h-42zM363 277v-42h-43v42h43zM149 235v42h43v-42h-43zM166 368l-93 -112l93 -112l-33 -27l-116 139l116 139z" />
+    <glyph glyph-name="uniE8BF" unicode="settings_input_antenna" 
+d="M256 491q97 0 166 -69t69 -166h-43q0 80 -56 136t-136 56t-136 -56t-56 -136h-43q0 97 69 166t166 69zM277 207v-70l73 -73l-30 -30l-64 64l-64 -64l-30 30l73 73v70q-32 13 -32 49q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5q0 -36 -32 -49zM256 405
+q62 0 105.5 -43.5t43.5 -105.5h-42q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5h-42q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE8C0" unicode="settings_input_component" 
+d="M363 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-43v90q-42 15 -42 60zM277 469v-85h43v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5zM448 384h43v-128h-128v128h42v85q0 9 6.5 15.5t15.5 6.5t15 -6.5t6 -15.5v-85zM21 171v42h128v-42q0 -45 -42 -60v-90h-43
+v90q-19 7 -31 23t-12 37zM192 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-42v90q-19 7 -31 23t-12 37zM107 469v-85h42v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15.5 -6.5t6.5 -15.5z" />
+    <glyph glyph-name="uniE8C1" unicode="settings_input_composite" 
+d="M363 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-43v90q-42 15 -42 60zM277 469v-85h43v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5zM448 384h43v-128h-128v128h42v85q0 9 6.5 15.5t15.5 6.5t15 -6.5t6 -15.5v-85zM21 171v42h128v-42q0 -45 -42 -60v-90h-43
+v90q-19 7 -31 23t-12 37zM192 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-42v90q-19 7 -31 23t-12 37zM107 469v-85h42v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15.5 -6.5t6.5 -15.5z" />
+    <glyph glyph-name="uniE8C2" unicode="settings_input_hdmi" 
+d="M171 427v-64h42v42h22v-42h42v42h22v-42h42v64h-170zM384 363h21v-128l-64 -128v-64h-170v64l-64 128v128h21v64q0 17 13 29.5t30 12.5h170q17 0 30 -12.5t13 -29.5v-64z" />
+    <glyph glyph-name="uniE8C3" unicode="settings_input_svideo" 
+d="M331 192q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9zM373 299q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM256 64q80 0 136 56t56 136t-56 136t-136 56t-136 -56t-56 -136t56 -136t136 -56z
+M256 491q97 0 166 -69t69 -166t-69 -166t-166 -69t-166 69t-69 166t69 166t166 69zM181 192q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9zM320 373q0 -13 -9 -22.5t-23 -9.5h-64q-14 0 -23 9.5t-9 22.5t9 22.5t23 9.5h64q14 0 23 -9.5t9 -22.5z
+M171 267q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5z" />
+    <glyph glyph-name="uniE8C4" unicode="settings_overscan" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM299 171l-43 -54l-43 54h86zM128 299v-86l-53 43zM384 299l53 -43l-53 -43v86zM256 395l43 -54h-86z" />
+    <glyph glyph-name="uniE8C5" unicode="settings_phone" 
+d="M405 320h43v-43h-43v43zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q48 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12zM363 320v-43h-43v43h43zM277 320v-43
+h-42v43h42z" />
+    <glyph glyph-name="uniE8C6" unicode="settings_power" 
+d="M320 0v43h43v-43h-43zM353 417q74 -52 74 -140q0 -70 -50 -120t-121 -50t-121 50t-50 120q0 88 74 140l30 -30q-61 -38 -61 -110q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5q0 72 -62 109zM277 469v-213h-42v213h42zM235 0v43h42v-43h-42zM149 0v43h43v-43h-43z
+" />
+    <glyph glyph-name="uniE8C7" unicode="settings_remote" 
+d="M256 512q99 0 166 -69l-30 -30q-56 56 -136 56t-136 -56l-30 30q69 69 166 69zM150 383q44 44 106 44t106 -44l-30 -30q-31 31 -76 31t-76 -31zM256 192q17 0 30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5t13 -30t30 -13zM320 320q9 0 15 -6t6 -15v-256
+q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15 6.5t-6 15.5v256q0 9 6 15t15 6h128z" />
+    <glyph glyph-name="uniE8C8" unicode="settings_voice" 
+d="M405 299q0 -54 -37.5 -95t-90.5 -49v-70h-42v70q-53 8 -90.5 49t-37.5 95h36q0 -47 33.5 -78t79.5 -31t79.5 31t33.5 78h36zM320 0v43h43v-43h-43zM235 0v43h42v-43h-42zM256 235q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z
+M149 0v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8C9" unicode="shop" 
+d="M192 128l160 107l-160 85v-192zM213 427v-43h86v43h-86zM341 384h128v-277q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v277h128v43q0 18 12 30t30 12h86q18 0 30 -12t12 -30v-43z" />
+    <glyph glyph-name="uniE8CA" unicode="shop_two" 
+d="M256 192l117 85l-117 64v-149zM256 448v-43h85v43h-85zM384 405h107v-234q0 -18 -12.5 -30.5t-30.5 -12.5h-299q-18 0 -30 12.5t-12 30.5v234h106v43q0 18 12.5 30.5t30.5 12.5h85q18 0 30.5 -12.5t12.5 -30.5v-43zM64 320v-235h341q0 -18 -12 -30t-30 -12h-299
+q-18 0 -30.5 12t-12.5 30v235h43z" />
+    <glyph glyph-name="uniE8CB" unicode="shopping_basket" 
+d="M256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM192 320h128l-64 94zM367 320h102q9 0 15.5 -6t6.5 -15q-10 -40 -30 -112.5t-25 -91.5q-9 -31 -41 -31h-278q-32 0 -41 31l-54 198q-1 2 -1 6q0 9 6.5 15t15.5 6h102l93 140q6 9 18 9t18 -9z" />
+    <glyph glyph-name="uniE8CC" unicode="shopping_cart" 
+d="M363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5t-30 12.5t-13 29.5t13 30t30 13zM21 469h70l20 -42h316q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22h-159l-19 -35l-1 -3q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53
+l-77 162h-43v42zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE8CD" unicode="speaker_notes" 
+d="M384 341v43h-171v-43h171zM384 277v43h-171v-43h171zM320 213v43h-107v-43h107zM171 341v43h-43v-43h43zM171 277v43h-43v-43h43zM171 213v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5
+t29.5 12.5h342z" />
+    <glyph glyph-name="uniE8CE" unicode="spellcheck" 
+d="M461 265l30 -30l-203 -203l-108 109l30 30l78 -79zM137 277h88l-44 118zM266 171l-25 64h-120l-24 -64h-45l109 277h40l109 -277h-44z" />
+    <glyph glyph-name="uniE8D0" unicode="stars" 
+d="M346 128l-24 103l80 69l-105 9l-41 96l-41 -97l-105 -8l80 -69l-24 -103l90 54zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE8D1" unicode="store" 
+d="M256 128v85h-128v-85h128zM448 213h-21v-128h-43v128h-85v-128h-214v128h-21v43l21 107h342l21 -107v-43zM427 427v-43h-342v43h342z" />
+    <glyph glyph-name="uniE8D2" unicode="subject" 
+d="M85 405h342v-42h-342v42zM85 192v43h342v-43h-342zM427 320v-43h-342v43h342zM299 149v-42h-214v42h214z" />
+    <glyph glyph-name="uniE8D3" unicode="supervisor_account" 
+d="M192 235q22 0 51 -6q-51 -28 -51 -74v-48h-149v53q0 23 27.5 41t60.5 26t61 8zM352 213q37 0 77 -16t40 -42v-48h-234v48q0 26 40 42t77 16zM192 277q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM352 256q-22 0 -37.5 15.5t-15.5 37.5t15.5 38
+t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5z" />
+    <glyph glyph-name="uniE8D4" unicode="swap_horiz" 
+d="M448 320l-85 -85v64h-150v42h150v64zM149 277v-64h150v-42h-150v-64l-85 85z" />
+    <glyph glyph-name="uniE8D5" unicode="swap_vert" 
+d="M192 448l85 -85h-64v-150h-42v150h-64zM341 149h64l-85 -85l-85 85h64v150h42v-150z" />
+    <glyph glyph-name="uniE8D6" unicode="swap_vertical_circle" 
+d="M373 192h-53v85h-43v-85h-53l75 -75zM139 320h53v-85h43v85h53l-75 75zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE8D7" unicode="system_update_alt" 
+d="M448 437q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h128v-42h-128v-299h384v299h-128v42h128zM256 160l-85 85h64v192h42v-192h64z" />
+    <glyph glyph-name="uniE8D8" unicode="tab" 
+d="M448 107v213h-171v85h-213v-298h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE8D9" unicode="tab_unselected" 
+d="M363 64v43h42v-43h-42zM277 64v43h43v-43h-43zM448 235v42h43v-42h-43zM448 64v43h43q0 -17 -13 -30t-30 -13zM107 405v43h42v-43h-42zM107 64v43h42v-43h-42zM192 405v43h43v-43h-43zM448 149v43h43v-43h-43zM448 448q17 0 30 -13t13 -30v-85h-214v128h171zM64 64
+q-17 0 -30 13t-13 30h43v-43zM21 149v43h43v-43h-43zM192 64v43h43v-43h-43zM21 405q0 17 13 30t30 13v-43h-43zM21 235v42h43v-42h-43zM21 320v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8DA" unicode="theaters" 
+d="M384 320v43h-43v-43h43zM384 235v42h-43v-42h43zM384 149v43h-43v-43h43zM171 320v43h-43v-43h43zM171 235v42h-43v-42h43zM171 149v43h-43v-43h43zM384 448h43v-384h-43v43h-43v-43h-170v43h-43v-43h-43v384h43v-43h43v43h170v-43h43v43z" />
+    <glyph glyph-name="uniE8DB" unicode="thumb_down" 
+d="M405 448h86v-256h-86v256zM320 448q17 0 30 -13t13 -30v-213q0 -17 -13 -30l-140 -141l-23 23q-9 9 -9 22v7l21 98h-135q-17 0 -30 12.5t-13 29.5l1 2h-1v41q0 8 3 16l65 150q10 26 39 26h192z" />
+    <glyph glyph-name="uniE8DC" unicode="thumb_up" 
+d="M491 299l-1 -2h1v-41q0 -8 -3 -16l-65 -150q-10 -26 -39 -26h-192q-17 0 -30 13t-13 30v213q0 17 13 30l140 141l23 -23q9 -9 9 -22v-7l-21 -98h135q17 0 30 -12.5t13 -29.5zM21 64v256h86v-256h-86z" />
+    <glyph glyph-name="uniE8DD" unicode="thumbs_up_down" 
+d="M480 299q14 0 23 -9.5t9 -22.5v-139q0 -14 -9 -23l-106 -105l-17 17q-7 7 -7 17q3 14 8.5 40t6.5 33h-111q-9 0 -15 6t-6 15v27q0 3 2 11l49 113q9 20 29 20h144zM256 384v-27q0 -3 -2 -11l-49 -113q-9 -20 -29 -20h-144q-14 0 -23 9.5t-9 22.5v139q0 14 9 23l106 105
+l17 -17q7 -7 7 -17q-3 -14 -8.5 -40t-6.5 -33h111q9 0 15 -6t6 -15z" />
+    <glyph glyph-name="uniE8DE" unicode="toc" 
+d="M405 235v42h43v-42h-43zM405 363h43v-43h-43v43zM405 149v43h43v-43h-43zM64 149v43h299v-43h-299zM64 235v42h299v-42h-299zM64 320v43h299v-43h-299z" />
+    <glyph glyph-name="uniE8DF" unicode="today" 
+d="M149 299h107v-107h-107v107zM405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21z" />
+    <glyph glyph-name="uniE8E0" unicode="toll" 
+d="M64 256q0 -42 23.5 -75t61.5 -46v-44q-56 14 -92 60t-36 105t36 105t92 60v-44q-38 -13 -61.5 -46t-23.5 -75zM320 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM320 427q70 0 120.5 -50.5t50.5 -120.5
+t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5t50.5 120.5t120.5 50.5z" />
+    <glyph glyph-name="uniE8E1" unicode="track_changes" 
+d="M407 407q62 -62 62 -151q0 -88 -62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5h21v-176q22 -12 22 -37q0 -17 -13 -30t-30 -13t-30 13t-13 30q0 25 22 37v45q-28 -8 -46 -30t-18 -52q0 -35 25 -60t60 -25t60 25t25 60q0 33 -25 60l30 30
+q38 -38 38 -90q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 47 30.5 82.5t76.5 43.5v43q-63 -8 -106.5 -56t-43.5 -113q0 -70 50.5 -120.5t120.5 -50.5t120.5 50.5t50.5 120.5q0 71 -50 121z" />
+    <glyph glyph-name="uniE8E2" unicode="translate" 
+d="M339 149h69l-35 93zM395 299l96 -256h-43l-24 64h-101l-24 -64h-43l96 256h43zM275 191l-17 -44l-66 66l-107 -106l-30 30l109 107q-40 44 -64 97h43q21 -40 49 -71q46 51 68 114h-239v43h150v42h42v-42h150v-43h-63q-26 -80 -79 -139l-1 -1z" />
+    <glyph glyph-name="uniE8E3" unicode="trending_down" 
+d="M341 128l49 49l-104 104l-85 -85l-158 158l30 30l128 -128l85 85l134 -134l49 49v-128h-128z" />
+    <glyph glyph-name="uniE8E4" unicode="trending_neutral" 
+d="M469 256l-85 -85v64h-320v42h320v64z" />
+    <glyph glyph-name="uniE8E5" unicode="trending_up" 
+d="M341 384h128v-128l-49 49l-134 -134l-85 85l-128 -128l-30 30l158 158l85 -85l104 104z" />
+    <glyph glyph-name="uniE8E6" unicode="turned_in" 
+d="M363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE8E7" unicode="turned_in_not" 
+d="M363 128v277h-214v-277l107 47zM363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE8E8" unicode="verified_user" 
+d="M213 149l171 171l-30 30l-141 -140l-55 55l-30 -30zM256 491l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128z" />
+    <glyph glyph-name="uniE8E9" unicode="view_agenda" 
+d="M427 448q9 0 15 -6t6 -15v-128q0 -9 -6 -15.5t-15 -6.5h-363q-9 0 -15 6.5t-6 15.5v128q0 9 6 15t15 6h363zM427 235q9 0 15 -6.5t6 -15.5v-128q0 -9 -6 -15t-15 -6h-363q-9 0 -15 6t-6 15v128q0 9 6 15.5t15 6.5h363z" />
+    <glyph glyph-name="uniE8EA" unicode="view_array" 
+d="M171 128v277h192v-277h-192zM384 405h64v-277h-64v277zM85 128v277h64v-277h-64z" />
+    <glyph glyph-name="uniE8EB" unicode="view_carousel" 
+d="M384 384h85v-235h-85v235zM43 149v235h85v-235h-85zM149 107v320h214v-320h-214z" />
+    <glyph glyph-name="uniE8EC" unicode="view_column" 
+d="M341 405h107v-277h-107v277zM85 128v277h107v-277h-107zM213 128v277h107v-277h-107z" />
+    <glyph glyph-name="uniE8ED" unicode="view_day" 
+d="M43 448h405v-64h-405v64zM427 341q9 0 15 -6t6 -15v-128q0 -9 -6 -15t-15 -6h-363q-9 0 -15 6t-6 15v128q0 9 6 15t15 6h363zM43 64v64h405v-64h-405z" />
+    <glyph glyph-name="uniE8EE" unicode="view_headline" 
+d="M85 405h342v-42h-342v42zM85 277v43h342v-43h-342zM85 107v42h342v-42h-342zM85 192v43h342v-43h-342z" />
+    <glyph glyph-name="uniE8EF" unicode="view_list" 
+d="M192 405h256v-85h-256v85zM192 107v85h256v-85h-256zM192 213v86h256v-86h-256zM85 320v85h86v-85h-86zM85 107v85h86v-85h-86zM85 213v86h86v-86h-86z" />
+    <glyph glyph-name="uniE8F0" unicode="view_module" 
+d="M341 405h107v-128h-107v128zM213 277v128h107v-128h-107zM341 128v128h107v-128h-107zM213 128v128h107v-128h-107zM85 128v128h107v-128h-107zM85 277v128h107v-128h-107z" />
+    <glyph glyph-name="uniE8F1" unicode="view_quilt" 
+d="M213 405h235v-128h-235v128zM341 128v128h107v-128h-107zM85 128v277h107v-277h-107zM213 128v128h107v-128h-107z" />
+    <glyph glyph-name="uniE8F2" unicode="view_stream" 
+d="M85 405h363v-128h-363v128zM85 128v128h363v-128h-363z" />
+    <glyph glyph-name="uniE8F3" unicode="view_week" 
+d="M277 405q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-64q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h64zM427 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-64q-9 0 -15.5 6t-6.5 15v256q0 9 6.5 15t15.5 6h64zM128 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-64
+q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h64z" />
+    <glyph glyph-name="uniE8F4" unicode="visibility" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 416q79 0 143 -44t92 -116q-28 -72 -92 -116t-143 -44t-143 44t-92 116
+q28 72 92 116t143 44z" />
+    <glyph glyph-name="uniE8F5" unicode="visibility_off" 
+d="M253 320h3q26 0 45 -19t19 -45v-4zM161 303q-12 -24 -12 -47q0 -44 31.5 -75.5t75.5 -31.5q23 0 47 12l-33 33q-8 -2 -14 -2q-26 0 -45 19t-19 45q0 6 2 14zM43 421l27 27l378 -378l-27 -27q-5 5 -31.5 31t-40.5 40q-43 -18 -93 -18q-79 0 -143 44t-92 116q25 62 80 106
+q-12 12 -33.5 34t-24.5 25zM256 363q-20 0 -39 -8l-46 46q39 15 85 15q79 0 142.5 -44t91.5 -116q-24 -59 -73 -101l-62 62q8 19 8 39q0 44 -31.5 75.5t-75.5 31.5z" />
+    <glyph glyph-name="uniE8F6" unicode="card_giftcard" 
+d="M427 213v128h-109l45 -60l-35 -25q-64 87 -72 98q-8 -11 -72 -98l-35 25l45 60h-109v-128h342zM427 107v42h-342v-42h342zM192 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5t-15 6.5zM320 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5
+t-15 6.5zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h47q-4 14 -4 21q0 26 19 45t45 19q33 0 53 -28l11 -15l11 15q20 28 53 28q26 0 45 -19t19 -45q0 -7 -4 -21h47z" />
+    <glyph glyph-name="uniE8F7" unicode="card_membership" 
+d="M427 299v128h-342v-128h342zM427 192v43h-342v-43h342zM427 469q18 0 30 -12t12 -30v-235q0 -18 -12 -30.5t-30 -12.5h-86v-106l-85 42l-85 -42v106h-86q-18 0 -30 12.5t-12 30.5v235q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniE8F8" unicode="card_travel" 
+d="M427 213v128h-64v-42h-43v42h-128v-42h-43v42h-64v-128h342zM427 107v42h-342v-42h342zM192 427v-43h128v43h-128zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h64v43q0 18 12.5 30t30.5 12
+h128q18 0 30.5 -12t12.5 -30v-43h64z" />
+    <glyph glyph-name="uniE8F9" unicode="work" 
+d="M299 384v43h-86v-43h86zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h86v43q0 18 12 30t30 12h86q18 0 30 -12t12 -30v-43h86z" />
+    <glyph glyph-name="uniE8FA" unicode="youtube_searched_for" 
+d="M363 213l106 -106l-31 -32l-107 107v16l-6 6q-38 -33 -90 -33q-38 0 -71 19l32 31q19 -8 39 -8q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68h74l-88 -85l-82 85h53q0 57 41 98t98 41q58 0 98.5 -40.5t40.5 -98.5q0 -51 -34 -90l6 -6h17z" />
+    <glyph glyph-name="uniE8FB" unicode="eject" 
+d="M256 405l142 -213h-284zM107 149h298v-42h-298v42z" />
+    <glyph glyph-name="uniE8FC" unicode="camera_enhance" 
+d="M256 149l-27 59l-58 27l58 26l27 59l27 -59l58 -26l-58 -27zM256 128q44 0 75.5 31.5t31.5 75.5t-31.5 75t-75.5 31t-75.5 -31t-31.5 -75t31.5 -75.5t75.5 -31.5zM192 448h128l39 -43h68q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13
+t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h68z" />
+    <glyph glyph-name="uniE8FD" unicode="help_outline" 
+d="M256 384q35 0 60 -25t25 -60q0 -27 -32 -55.5t-32 -51.5h-42q0 23 10 39.5t22 24t22 18.5t10 25q0 17 -13 29.5t-30 12.5t-30 -12.5t-13 -29.5h-42q0 35 25 60t60 25zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5
+t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 128v43h42v-43h-42z" />
+    <glyph glyph-name="uniE8FE" unicode="reorder" 
+d="M64 405h384v-42h-384v42zM64 277v43h384v-43h-384zM64 107v42h384v-42h-384zM64 192v43h384v-43h-384z" />
+    <glyph glyph-name="uniE8FF" unicode="zoom_in" 
+d="M256 299h-43v-43h-21v43h-43v21h43v43h21v-43h43v-21zM203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5
+q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE900" unicode="zoom_out" 
+d="M149 320h107v-21h-107v21zM203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE902" unicode="http" 
+d="M459 267v21h-43v-21h43zM459 320q13 0 22.5 -9.5t9.5 -22.5v-21q0 -13 -9.5 -22.5t-22.5 -9.5h-43v-43h-32v128h75zM267 288v32h96v-32h-32v-96h-32v96h-32zM149 288v32h96v-32h-32v-96h-32v96h-32zM96 277v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43z" />
+    <glyph glyph-name="uniE903" unicode="event_seat" 
+d="M363 235h-214v170q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-170zM43 299h64v-64h-64v64zM405 299h64v-64h-64v64zM85 64v128h342v-128h-64v64h-214v-64h-64z" />
+    <glyph glyph-name="uniE904" unicode="flight_land" 
+d="M299 204q-82 23 -206 55l-34 10v110l31 -8l20 -50l106 -28v176l41 -11l59 -192l113 -30q13 -4 19.5 -15.5t3.5 -24.5q-4 -13 -15 -19t-24 -3zM53 107h406v-43h-406v43z" />
+    <glyph glyph-name="uniE905" unicode="flight_takeoff" 
+d="M471 306q3 -13 -3.5 -24t-19.5 -15q-124 -33 -206 -55l-113 -30l-34 -10l-56 96l31 8l42 -32l106 28l-88 153l41 11l147 -137l114 30q13 4 24.5 -3t14.5 -20zM53 107h406v-43h-406v43z" />
+    <glyph glyph-name="uniE906" unicode="play_for_work" 
+d="M128 213h43q0 -35 25 -60t60 -25t60 25t25 60h43q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM235 405h42v-119h75l-96 -96l-96 96h75v119z" />
+    <glyph glyph-name="uniE908" unicode="gif" 
+d="M405 288h-64v-21h43v-32h-43v-43h-32v128h96v-32zM192 320q9 0 15 -6t6 -15v-11h-74v-64h42v32h32v-43q0 -9 -6 -15t-15 -6h-64q-9 0 -15 6t-6 15v86q0 9 6 15t15 6h64zM245 320h32v-128h-32v128z" />
+    <glyph glyph-name="uniE909" unicode="indeterminate_check_box" 
+d="M363 235v42h-214v-42h214zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE90A" unicode="offline_pin" 
+d="M220 213l143 143l-30 30l-113 -113l-41 41l-30 -30zM363 128v43h-214v-43h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE90B" unicode="all_out" 
+d="M343 170q34 34 34 82.5t-34 82.5t-82.5 34t-82.5 -34t-34 -82.5t34 -82.5t82.5 -34t82.5 34zM366 358q44 -44 44 -106t-44 -105t-106 -43t-105 43t-43 105t43 106t105 44t106 -44zM90 338v85h85zM175 82h-85v85zM431 167v-85h-85zM346 423h85v-85z" />
+    <glyph glyph-name="uniE90C" unicode="copyright" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM253 317q-40 0 -40 -58v-6
+q0 -58 40 -58q15 0 25 8.5t10 21.5h38q0 -25 -22 -44q-21 -18 -51 -18q-40 0 -61 24t-21 66v6q0 41 20 64q24 27 62 27q33 0 52 -19q21 -21 21 -49h-38q0 7 -3 13q-5 10 -7 12q-10 10 -25 10z" />
+    <glyph glyph-name="uniE90D" unicode="fingerprint" 
+d="M318 43h-3q-46 12 -79 45q-46 46 -46 111q0 26 19 44.5t46 18.5t46.5 -18.5t19.5 -44.5q0 -17 12.5 -29t31.5 -12t32 12t13 29q0 60 -45.5 103t-109.5 43q-46 0 -84 -23.5t-57 -62.5q-12 -25 -12 -60q0 -40 14 -77q3 -10 -7 -13t-13 6q-16 45 -16 84t15 69q21 45 64.5 72
+t95.5 27q73 0 124.5 -49.5t51.5 -118.5q0 -26 -19.5 -44t-46.5 -18t-46 18t-19 44q0 17 -13 29.5t-32 12.5t-31.5 -12.5t-12.5 -29.5q0 -56 40 -96q28 -28 70 -39q9 -1 7 -13q-2 -8 -10 -8zM265 199q0 -37 27.5 -64t68.5 -27q2 0 9 1t11.5 1t9 -1.5t5.5 -6.5q2 -11 -9 -13
+q-12 -2 -26 -2q-40 0 -66 19q-51 35 -51 93q0 11 11 11q10 0 10 -11zM208 47q-4 0 -7 3q-27 27 -43 57q-23 40 -23 92q0 47 35.5 81t85.5 34t85.5 -34t35.5 -81q0 -10 -11 -10t-11 10q0 39 -29 66.5t-70 27.5t-70 -27.5t-29 -66.5q0 -48 19 -82q13 -23 40 -52q8 -7 0 -15
+q-3 -3 -8 -3zM75 305q-7 0 -10 5t1 11q32 46 80 70q50 26 110 26t110 -26q49 -24 80 -69q6 -10 -3 -15q-10 -6 -15 3q-27 39 -72 62q-46 23 -100 23.5t-100 -23.5q-45 -23 -73 -63q-3 -4 -8 -4zM380 417q-4 0 -5 1q-59 30 -119 30q-65 0 -119 -30q-6 -3 -10.5 0.5t-4.5 9
+t5 9.5q58 32 129 32q65 0 129 -32q7 -4 5 -12t-10 -8z" />
+    <glyph glyph-name="uniE90E" unicode="gavel" 
+d="M82 310l120 -121l-60 -60l-121 120zM263 491l120 -121l-60 -60l-121 120zM112 340l60 60l302 -302l-60 -60zM21 64h256v-43h-256v43z" />
+    <glyph glyph-name="uniE90F" unicode="lightbulb_outline" 
+d="M317 233q46 32 46 87q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5q0 -55 46 -87l18 -13v-49h86v49zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -78 -64 -122v-49q0 -9 -6 -15t-15 -6h-128q-9 0 -15 6t-6 15v49q-64 44 -64 122q0 62 43.5 105.5t105.5 43.5z
+M192 64v21h128v-21q0 -9 -6 -15t-15 -6h-86q-9 0 -15 6t-6 15z" />
+    <glyph glyph-name="uniE911" unicode="picture_in_picture_alt" 
+d="M448 106v300h-384v-300h384zM491 107q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h384q17 0 30 -12.5t13 -29.5v-299zM405 277v-128h-170v128h170z" />
+    <glyph glyph-name="uniE912" unicode="important_devices" 
+d="M255 320h65l-53 -38l20 -62l-52 39l-53 -39l20 62l-53 38h65l21 64zM427 469q17 0 29.5 -12t12.5 -30v-107h-42v107h-384v-256h277v-43h-43v-43h43v-42h-171v42h43v43h-149q-18 0 -30.5 13t-12.5 30v256q0 18 12.5 30t30.5 12h384zM491 85v150h-107v-150h107zM491 277
+q9 0 15 -6t6 -15v-192q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v192q0 9 6 15t15 6h107z" />
+    <glyph glyph-name="uniE913" unicode="touch_app" 
+d="M402 173q19 -9 19 -29v-4l-16 -113q-1 -12 -10 -19.5t-21 -7.5h-145q-13 0 -22 9l-106 106l17 17q7 7 17 7q1 0 2.5 -0.5t2.5 -0.5l73 -15v229q0 14 9.5 23t22.5 9t22.5 -9t9.5 -23v-128h17q3 0 11 -2zM192 272q-43 28 -43 80q0 40 28 68t68 28t68 -28t28 -68
+q0 -53 -42 -80v80q0 22 -16 37.5t-38 15.5t-37.5 -15.5t-15.5 -37.5v-80z" />
+    <glyph glyph-name="uniE914" unicode="accessible" 
+d="M274 128h44q-8 -37 -37 -61t-68 -24q-44 0 -75 31t-31 75q0 39 24 68t61 37v-44q-19 -7 -31 -23.5t-12 -37.5q0 -26 19 -45t45 -19q21 0 37.5 12t23.5 31zM213 318q0 24 21 38t43 1h1v-1q7 -3 13 -9l28 -31q36 -39 86 -39v-42q-56 0 -106 41v-73h64q17 0 29.5 -13
+t12.5 -30v-117h-42v106h-107q-17 0 -30 13t-13 30v126zM213 427q0 18 12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE915" unicode="compare_arrows" 
+d="M320 235l-85 85l85 85v-64h149v-42h-149v-64zM192 213v64l85 -85l-85 -85v64h-149v42h149z" />
+    <glyph glyph-name="uniE916" unicode="date_range" 
+d="M405 85v235h-298v-235h298zM405 427q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v299q0 17 12.5 30t30.5 13h21v42h43v-42h170v42h43v-42h21zM363 277v-42h-43v42h43zM277 277v-42h-42v42h42zM192 277v-42h-43v42h43z" />
+    <glyph glyph-name="uniE917" unicode="donut_large" 
+d="M277 108q48 7 84.5 43t43.5 84h64q-8 -80 -60 -132.5t-132 -59.5v65zM405 277q-7 48 -43.5 84t-84.5 43v65q80 -8 132 -60t60 -132h-64zM235 404q-50 -8 -89 -51t-39 -97t39 -97t89 -51v-65q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69v-65z" />
+    <glyph glyph-name="uniE918" unicode="donut_small" 
+d="M277 195q27 8 40 40h152q-8 -77 -60.5 -131t-131.5 -61v152zM317 277q-12 32 -40 40v152q79 -7 131.5 -61t60.5 -131h-152zM235 317q-17 -7 -30 -24t-13 -37t13 -37t30 -24v-152q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69v-152z" />
+    <glyph glyph-name="uniE919" unicode="line_style" 
+d="M64 427h384v-86h-384v86zM277 256v43h171v-43h-171zM64 256v43h171v-43h-171zM405 85v43h43v-43h-43zM320 85v43h43v-43h-43zM235 85v43h42v-43h-42zM149 85v43h43v-43h-43zM64 85v43h43v-43h-43zM341 171v42h107v-42h-107zM203 171v42h106v-42h-106zM64 171v42h107v-42
+h-107z" />
+    <glyph glyph-name="uniE91A" unicode="line_weight" 
+d="M64 427h384v-86h-384v86zM64 235v64h384v-64h-384zM64 85v22h384v-22h-384zM64 149v43h384v-43h-384z" />
+    <glyph glyph-name="uniE91B" unicode="motorcycle" 
+d="M405 149q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM167 192h-60v43h60q-7 19 -23.5 30.5t-36.5 11.5q-26 0 -45 -19t-19 -45t19 -45t45 -19q20 0 36.5 12t23.5 31zM415 319q41 -3 69 -33t28 -73q0 -45 -31 -75.5t-76 -30.5t-75.5 30.5t-30.5 75.5
+q0 20 6 38l-59 -59h-35q-8 -37 -36.5 -61t-67.5 -24q-45 0 -76 30.5t-31 75.5t31 76t76 31h247l-43 43h-76v42h94z" />
+    <glyph glyph-name="uniE91C" unicode="opacity" 
+d="M128 213h256q0 55 -38 93l-90 94l-90 -93q-38 -38 -38 -94zM377 341q50 -50 50 -120q0 -71 -50 -121t-121 -50t-121 50t-50 121q0 70 50 120l121 121z" />
+    <glyph glyph-name="uniE91D" unicode="pets" 
+d="M370 195l8.5 -8.5l8.5 -8.5t8.5 -9t8 -9.5t7.5 -9.5t6.5 -10t5 -11t3 -11.5t1 -12t-0.5 -12.5q-11 -42 -50 -50q-7 -1 -48.5 4t-69.5 5h-4q-28 0 -69.5 -5t-48.5 -4q-39 8 -50 50q-3 20 7 41t18.5 30t30.5 31q10 11 26.5 31t26.5 31q18 22 37 28q4 2 7 2q6 1 17 1
+q12 0 17 -1q3 0 7 -2q19 -6 37 -28q9 -11 26 -31t27 -31zM363 309q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5zM267 395q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM139 395
+q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM43 309q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5z" />
+    <glyph glyph-name="uniE91E" unicode="pregnant_woman" 
+d="M341 235v-86h-64v-106h-64v106h-42v150q0 26 19 45t45 19t45 -19t19 -45q42 -17 42 -64zM192 427q0 18 12.5 30t30.5 12t30 -12t12 -30t-12 -30.5t-30 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE91F" unicode="record_voice_over" 
+d="M428 469q62 -65 62 -150.5t-62 -147.5l-35 34q44 51 44 116.5t-44 113.5zM358 398q32 -35 32 -79t-32 -76l-36 36q14 19 14 41.5t-14 41.5zM192 192q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM107 320q0 35 25 60t60 25t60 -25t25 -60
+t-25 -60t-60 -25t-60 25t-25 60z" />
+    <glyph glyph-name="uniE920" unicode="rounded_corner" 
+d="M448 341v-106h-43v106q0 26 -19 45t-45 19h-106v43h106q44 0 75.5 -31.5t31.5 -75.5zM64 64v43h43v-43h-43zM149 64v43h43v-43h-43zM235 64v43h42v-43h-42zM149 405v43h43v-43h-43zM64 405v43h43v-43h-43zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42
+h-43zM405 149v43h43v-43h-43zM405 107h43v-43h-43v43z" />
+    <glyph glyph-name="uniE921" unicode="rowing" 
+d="M448 64l-64 -64l-64 64v32l-151 151q-7 -1 -20 -1v46q26 -1 54 11.5t46 31.5l30 33q16 16 35 16h1q19 0 33.5 -14t14.5 -34v-123q0 -26 -20 -46l-76 76v49q-20 -17 -49 -30l134 -134h32zM320 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM181 203
+l54 -54h-43l-75 -74l-32 32z" />
+    <glyph glyph-name="uniE922" unicode="timeline" 
+d="M491 341q0 -17 -13 -29.5t-30 -12.5q-8 0 -11 1l-76 -76q2 -6 2 -11q0 -17 -13 -29.5t-30 -12.5t-30 12.5t-13 29.5q0 5 2 11l-55 55q-6 -2 -11 -2t-11 2l-97 -97q2 -6 2 -11q0 -17 -13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5q8 0 11 -1l97 97q-1 3 -1 11
+q0 17 12.5 30t29.5 13t30 -13t13 -30q0 -8 -1 -11l54 -54q3 1 11 1t11 -1l76 75q-2 6 -2 11q0 17 13 30t30 13t30 -13t13 -30z" />
+    <glyph glyph-name="uniE923" unicode="update" 
+d="M267 341v-90l74 -45l-15 -26l-91 55v106h32zM448 296h-145l59 60q-44 44 -105.5 44.5t-105.5 -42.5q-43 -44 -43 -104t43 -104t105 -44t106 44q43 43 43 104h43q0 -79 -56 -134q-56 -56 -136 -56t-136 56q-56 55 -56 133.5t56 134.5t135 56t135 -56l58 60v-152z" />
+    <glyph glyph-name="uniE924" unicode="watch_later" 
+d="M346 166l17 28l-96 58v111h-32v-128zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE925" unicode="pan_tool" 
+d="M491 395v-310q0 -35 -25.5 -60t-60.5 -25h-155q-36 0 -61 25l-168 171q27 26 28 26q7 6 17 6q7 0 13 -3l92 -52v254q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-150h21v203q0 14 9 23t23 9t23 -9t9 -23v-203h21v182q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-182
+h22v118q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5z" />
+    <glyph glyph-name="uniE926" unicode="euro_symbol" 
+d="M320 117q51 0 90 34l38 -38q-54 -49 -128 -49q-62 0 -111.5 36t-69.5 92h-75v43h65q-1 7 -1 21t1 21h-65v43h75q20 56 69.5 92t111.5 36q74 0 128 -49l-38 -38q-39 34 -90 34q-39 0 -72 -20.5t-51 -54.5h123v-43h-137q-2 -14 -2 -21t2 -21h137v-43h-123
+q18 -34 50.5 -54.5t72.5 -20.5z" />
+    <glyph glyph-name="uniE927" unicode="g_translate" 
+d="M448 85v278q0 9 -6 15t-15 6h-188l25 -86h41v22h23v-22h77v-22h-27q-10 -39 -41 -75l58 -57l-15 -16l-58 57l-19 -19l17 -59l-43 -43h150q9 0 15 6t6 15zM298 253q9 -19 24 -36q12 14 20 28.5t10 22.5l3 8h-85l7 -23h21zM282 237l13 -47l12 11q-14 15 -25 36zM237 286
+q0 11 -2 15h-84v-33h47q-3 -12 -14 -22t-31 -10q-21 0 -36 15.5t-15 36.5t15 36.5t36 15.5q19 0 32 -13l2 -1l26 25l-2 1q-25 23 -58 23q-36 0 -61.5 -25.5t-25.5 -61.5t25.5 -61.5t61.5 -25.5q37 0 60.5 24t23.5 61zM427 405q17 0 29.5 -12.5t12.5 -29.5v-278
+q0 -17 -12.5 -29.5t-29.5 -12.5h-171l-21 64h-150q-17 0 -29.5 12.5t-12.5 29.5v278q0 17 12.5 29.5t29.5 12.5h128l19 -64h195z" />
+    <glyph glyph-name="uniE928" unicode="remove_shopping_cart" 
+d="M149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13zM332 235l-192 192h287q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22zM158 192h108l-43 43h-50l-19 -35l-1 -3q0 -5 5 -5zM485 27l-27 -27l-61 61q-13 -18 -34 -18
+q-17 0 -30 12.5t-13 29.5q0 22 18 35l-30 29h-159q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53l-47 99l-94 94l27 27z" />
+    <glyph glyph-name="uniE929" unicode="restore_page" 
+d="M256 128q44 0 75.5 31.5t31.5 75.5t-31.5 75t-75.5 31q-58 0 -90 -49l-27 28v-85h85l-34 34q20 40 66 40q31 0 53 -21.5t22 -52.5t-22 -53t-53 -22q-39 0 -61 32h-37q12 -29 38.5 -46.5t59.5 -17.5zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256
+q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE92A" unicode="speaker_notes_off" 
+d="M427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12 -29.5t-29 -13.5l-149 149h105v43h-148l-21 21h169v43h-171v-41l-126 126h340zM128 277h43l-43 43v-43zM171 213v43h-43v-43h43zM27 475l442 -442l-27 -27l-122 122h-192l-85 -85v362l-43 43z" />
+    <glyph glyph-name="uniE92B" unicode="delete_forever" 
+d="M331 427h74v-43h-298v43h74l22 21h106zM180 259l46 -46l-45 -45l30 -30l45 45l45 -45l30 30l-45 45l45 46l-30 30l-45 -46l-45 46zM128 107v256h256v-256q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30z" />
+    <glyph glyph-name="uniEB3B" unicode="ac_unit" 
+d="M469 277v-42h-89l69 -69l-30 -31l-99 100h-43v-43l100 -99l-31 -30l-69 69v-89h-42v89l-69 -69l-31 30l100 99v43h-43l-99 -100l-30 31l69 69h-89v42h89l-69 69l30 31l99 -100h43v43l-100 99l31 30l69 -69v89h42v-89l69 69l31 -30l-100 -99v-43h43l99 100l30 -31l-69 -69
+h89z" />
+    <glyph glyph-name="uniEB3C" unicode="airport_shuttle" 
+d="M320 277h107l-86 86h-21v-86zM373 139q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM277 277v86h-85v-86h85zM128 139q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM64 277h85v86h-85v-86z
+M363 405l128 -128v-106h-54q0 -26 -19 -45t-45 -19t-45 19t-19 45h-117q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43v192q0 18 12.5 30t30.5 12h299z" />
+    <glyph glyph-name="uniEB3D" unicode="all_inclusive" 
+d="M397 371q48 0 81.5 -34t33.5 -81t-33.5 -81t-81.5 -34t-82 34l-27 24l32 28l25 -21q22 -22 52 -22t51 21t21 51t-21 51t-51 21t-51 -21q-71 -62 -90 -80l-60 -53q-33 -33 -81 -33t-81.5 34t-33.5 81t33.5 81t81.5 34t82 -34l27 -24l-33 -28l-24 21q-22 22 -52 22t-51 -21
+t-21 -51t21 -51t51 -21t51 21q71 62 90 80l60 53q33 33 81 33z" />
+    <glyph glyph-name="uniEB3E" unicode="beach_access" 
+d="M371 324q-50 50 -115.5 66t-127.5 -5q48 6 105.5 -18.5t106.5 -73.5l-122 -122q-49 49 -73.5 106.5t-18.5 105.5q-21 -62 -5 -127.5t66 -115.5l-61 -61q-63 63 -63 152.5t63 152.5q0 1 1 1q70 70 168 62q81 -6 137 -62zM279 201l31 31l137 -138l-31 -30z" />
+    <glyph glyph-name="uniEB3F" unicode="business_center" 
+d="M299 363v42h-86v-42h86zM427 363q17 0 29.5 -13t12.5 -30v-64q0 -17 -12.5 -30t-29.5 -13h-128v43h-86v-43h-128q-18 0 -30 12.5t-12 30.5v64q0 17 12.5 30t29.5 13h85v42l43 43h85l43 -43v-42h86zM213 171h86v21h149v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-298
+q-18 0 -30.5 12.5t-12.5 30.5v85h149v-21z" />
+    <glyph glyph-name="uniEB40" unicode="casino" 
+d="M352 320q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM352 128q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM256 224q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM160 320q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9z
+M160 128q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniEB41" unicode="child_care" 
+d="M160 213h192q-12 -29 -38 -46.5t-58 -17.5t-58 17.5t-38 46.5zM256 107q51 0 90.5 30t52.5 77q1 0 3 -0.5t3 -0.5q17 0 30 13t13 30t-13 30t-30 13q-1 0 -3 -0.5t-3 -0.5q-13 47 -52.5 77t-90.5 30t-90.5 -30t-52.5 -77q-1 0 -3 0.5t-3 0.5q-17 0 -30 -13t-13 -30t13 -30
+t30 -13q1 0 3 0.5t3 0.5q13 -47 52.5 -77t90.5 -30zM489 242q-4 -24 -20 -42.5t-39 -25.5q-22 -47 -69.5 -78.5t-104.5 -31.5t-104 31.5t-69 78.5q-23 7 -39.5 25.5t-20.5 42.5q-2 8 -2 14t2 14q4 24 20.5 42.5t39.5 25.5q17 38 46 62q54 48 127 48q58 0 104.5 -31t68.5 -79
+q23 -7 39.5 -25.5t20.5 -42.5q2 -8 2 -14t-2 -14zM176 288q0 11 8 19t19 8t18.5 -8t7.5 -19t-7.5 -19t-18.5 -8t-19 8t-8 19zM283 288q0 11 7.5 19t18.5 8t19 -8t8 -19t-8 -19t-19 -8t-18.5 8t-7.5 19z" />
+    <glyph glyph-name="uniEB42" unicode="child_friendly" 
+d="M363 85q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM171 85q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM412 173q25 -23 25 -56q0 -31 -21.5 -52.5t-52.5 -21.5
+q-28 0 -49 18.5t-25 45.5h-45q-4 -27 -24.5 -45.5t-48.5 -18.5q-31 0 -53 21.5t-22 52.5q0 44 39 66l-45 94h-47v43h74l20 -43h311q0 -57 -36 -104zM277 469q71 0 121 -50t50 -120h-171v170z" />
+    <glyph glyph-name="uniEB43" unicode="fitness_center" 
+d="M439 195l30 -31l-45 -45l30 -31l-30 -30l-31 30l-45 -45l-31 30l-30 -30l-31 30l76 76l-183 183l-76 -76l-30 31l30 30l-30 31l45 45l-30 31l30 30l31 -30l45 45l31 -30l30 30l31 -30l-76 -76l183 -183l76 76l30 -31z" />
+    <glyph glyph-name="uniEB44" unicode="free_breakfast" 
+d="M85 107h342v-43h-342v43zM427 341v64h-43v-64h43zM427 448q18 0 30 -12.5t12 -30.5v-64q0 -17 -12 -29.5t-30 -12.5h-43v-64q0 -35 -25 -60.5t-60 -25.5h-128q-35 0 -60.5 25.5t-25.5 60.5v213h342z" />
+    <glyph glyph-name="uniEB45" unicode="golf_course" 
+d="M363 386l-128 -66v-193q46 -2 76 -14t30 -28q0 -17 -37.5 -29.5t-90.5 -12.5t-90.5 12.5t-37.5 29.5q0 25 64 37v-37h43v384zM384 96q0 14 9 23t23 9t23 -9t9 -23t-9 -23t-23 -9t-23 9t-9 23z" />
+    <glyph glyph-name="uniEB46" unicode="hot_tub" 
+d="M313 387q33 -32 27 -79l-1 -9h-41l3 12q5 27 -16 48q-34 34 -28 80l1 9h41l-2 -13q-5 -27 14 -47zM398 387q34 -33 28 -79l-2 -9h-40l2 12q5 27 -14 47l-2 1q-34 34 -28 80l2 9h40l-2 -13q-5 -27 14 -47zM405 85v128h-42v-128h42zM320 85v128h-43v-128h43zM235 85v128
+h-43v-128h43zM149 85v128h-42v-128h42zM238 256h231v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v171h64v16q0 20 14.5 34t33.5 14q20 0 36 -16l29 -33q4 -5 18 -15zM107 384q0 18 12 30.5t30 12.5t30.5 -12.5t12.5 -30.5t-12.5 -30.5
+t-30.5 -12.5t-30 12.5t-12 30.5z" />
+    <glyph glyph-name="uniEB47" unicode="kitchen" 
+d="M171 256h42v-107h-42v107zM171 405h42v-64h-42v64zM384 320v107h-256v-107h256zM384 85v193h-256v-193h256zM384 469q18 0 30.5 -12t12.5 -30v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 18 12.5 30t30.5 12h256z" />
+    <glyph glyph-name="uniEB48" unicode="pool" 
+d="M299 395q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM185 256q-12 0 -25 8q-4 3 -16 8l69 69l-21 22q-32 32 -85 32v53q41 0 67 -9.5t50 -33.5l137 -136q-6 -4 -9 -5q-13 -8 -25 -8q-11 0 -24 8q-22 13 -47 13t-47 -13
+q-13 -8 -24 -8zM469 160q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14v43q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7
+q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7v-43zM469 64q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14t-47 14q-11 7 -24 7q-14 0 -25 -7q-23 -14 -46 -14v43
+q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7q23 14 47 14t47 -14q11 -7 24 -7q14 0 25 7q23 14 46 14q24 0 47 -14q11 -7 24 -7v-43z" />
+    <glyph glyph-name="uniEB49" unicode="room_service" 
+d="M295 346q63 -13 106 -61.5t47 -113.5h-384q4 65 47 113.5t106 61.5q-4 10 -4 17q0 17 13 29.5t30 12.5t30 -12.5t13 -29.5q0 -7 -4 -17zM43 149h426v-42h-426v42z" />
+    <glyph glyph-name="uniEB4A" unicode="smoke_free" 
+d="M363 172l-63 63h63v-63zM309 326q-29 0 -50 21.5t-21 50.5t21 50t50 21v-32q-17 0 -28 -10.5t-11 -26.5q0 -17 11.5 -30t27.5 -13h33q30 0 52 -19.5t22 -47.5v-34h-32v27q0 20 -12.5 31.5t-29.5 11.5h-33zM402 408q30 -14 48.5 -43t18.5 -65v-44h-32v44q0 36 -24.5 61.5
+t-60.5 25.5v32q16 0 27.5 11.5t11.5 28.5h32q0 -30 -21 -51zM384 235h32v-64h-32v64zM437 235h32v-64h-32v64zM43 384l26 27l363 -363l-27 -27l-149 150h-213v64h149z" />
+    <glyph glyph-name="uniEB4B" unicode="smoking_rooms" 
+d="M342 294q31 0 52.5 -19t21.5 -48v-35h-32v28q0 20 -12.5 31.5t-29.5 11.5h-33q-29 0 -50 21.5t-21 50.5t21 50t50 21v-32q-17 0 -28 -10.5t-11 -26.5q0 -17 11.5 -30t27.5 -13h33zM402 347q30 -14 48.5 -43t18.5 -64v-48h-32v48q0 36 -24.5 61t-60.5 25v32
+q16 0 27.5 11.5t11.5 28.5q0 16 -11.5 27.5t-27.5 11.5v32q29 0 50 -21t21 -50q0 -30 -21 -51zM384 171h32v-64h-32v64zM437 171h32v-64h-32v64zM43 171h320v-64h-320v64z" />
+    <glyph glyph-name="uniEB4C" unicode="spa" 
+d="M330 307q-42 -23 -74 -57q-32 34 -74 57q8 95 75 162q67 -67 73 -162zM43 299q68 0 124 -33t89 -84q33 51 89 84t124 33q0 -84 -47.5 -151t-123.5 -94q-17 -6 -42 -11q-21 3 -42 11q-76 27 -123.5 94t-47.5 151z" />
+    <glyph glyph-name="u10FFFD" unicode="goat" 
+d="M511 318q1 -1 1 -2.5t-1 -2.5l-25 -32q-2 -2 -4 -2l-15 3l-7 -22q-2 -4 -6.5 -4t-6.5 4l-14 30l-23 5l-47 -112l17 -136q0 -4 -4 -4h-20q-2 0 -4 3l-20 81l-10 17l-25 -98q0 -3 -4 -3h-21q-4 0 -4 4l23 135h-135l-35 -66l8 -68q2 -5 -4 -5h-20q-3 0 -4 2l-28 102l-34 -39
+l6 -60q2 -5 -4 -5h-22q-4 0 -4 2l-13 56l21 82v144q-23 9 -23 30h274q48 -1 95 33q-8 22 6 36q28 -20 36 -25q7 -4 11.5 1.5t2.5 12.5q-9 28 -45 42q-1 0 -3 1q-12 3 -10 8q0 3 4 3q29 -4 49.5 -22.5t27.5 -35.5l4 -3q3 -4 6.5 -8t6 -11.5t1.5 -15.5q0 -5 2 -7z" />
+    <glyph glyph-name=".notdef" 
+d="M17 0v341h136v-341h-136zM34 17h102v307h-102v-307z" />
+    <glyph glyph-name="glyph1" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="glyph2" 
+ />
+    <glyph glyph-name="zero" unicode="0" 
+d="M0 0z" />
+    <glyph glyph-name="one" unicode="1" 
+d="M0 0z" />
+    <glyph glyph-name="two" unicode="2" 
+d="M0 0z" />
+    <glyph glyph-name="three" unicode="3" 
+d="M0 0z" />
+    <glyph glyph-name="four" unicode="4" 
+d="M0 0z" />
+    <glyph glyph-name="five" unicode="5" 
+d="M0 0z" />
+    <glyph glyph-name="six" unicode="6" 
+d="M0 0z" />
+    <glyph glyph-name="seven" unicode="7" 
+d="M0 0z" />
+    <glyph glyph-name="eight" unicode="8" 
+d="M0 0z" />
+    <glyph glyph-name="nine" unicode="9" 
+d="M0 0z" />
+    <glyph glyph-name="underscore" unicode="_" 
+d="M0 0z" />
+    <glyph glyph-name="a" unicode="a" 
+d="M0 0z" />
+    <glyph glyph-name="b" unicode="b" 
+d="M0 0z" />
+    <glyph glyph-name="c" unicode="c" 
+d="M0 0z" />
+    <glyph glyph-name="d" unicode="d" 
+d="M0 0z" />
+    <glyph glyph-name="e" unicode="e" 
+d="M0 0z" />
+    <glyph glyph-name="f" unicode="f" 
+d="M0 0z" />
+    <glyph glyph-name="g" unicode="g" 
+d="M0 0z" />
+    <glyph glyph-name="h" unicode="h" 
+d="M0 0z" />
+    <glyph glyph-name="i" unicode="i" 
+d="M0 0z" />
+    <glyph glyph-name="j" unicode="j" 
+d="M0 0z" />
+    <glyph glyph-name="k" unicode="k" 
+d="M0 0z" />
+    <glyph glyph-name="l" unicode="l" 
+d="M0 0z" />
+    <glyph glyph-name="m" unicode="m" 
+d="M0 0z" />
+    <glyph glyph-name="n" unicode="n" 
+d="M0 0z" />
+    <glyph glyph-name="o" unicode="o" 
+d="M0 0z" />
+    <glyph glyph-name="p" unicode="p" 
+d="M0 0z" />
+    <glyph glyph-name="q" unicode="q" 
+d="M0 0z" />
+    <glyph glyph-name="r" unicode="r" 
+d="M0 0z" />
+    <glyph glyph-name="s" unicode="s" 
+d="M0 0z" />
+    <glyph glyph-name="t" unicode="t" 
+d="M0 0z" />
+    <glyph glyph-name="u" unicode="u" 
+d="M0 0z" />
+    <glyph glyph-name="v" unicode="v" 
+d="M0 0z" />
+    <glyph glyph-name="w" unicode="w" 
+d="M0 0z" />
+    <glyph glyph-name="x" unicode="x" 
+d="M0 0z" />
+    <glyph glyph-name="y" unicode="y" 
+d="M0 0z" />
+    <glyph glyph-name="z" unicode="z" 
+d="M0 0z" />
+    <glyph glyph-name="uniEC2E" unicode="&#xec2e;" 
+d="M142 313zM323 220q41 -17 68.5 -53t33.5 -82h-41q-8 47 -44 78t-84 31t-84.5 -31t-43.5 -78h-42q6 45 33.5 81.5t68.5 54.5q-21 16 -33.5 40t-12.5 52q0 47 33 80t80.5 33t80.5 -33t33 -81q0 -27 -12.5 -51.5t-33.5 -40.5zM183 313q0 -30 21.5 -51.5t51.5 -21.5
+t51.5 21.5t21.5 51.5t-21.5 51.5t-51.5 21.5t-51.5 -21.5t-21.5 -51.5v0z" />
+    <glyph glyph-name="uniEDDE" unicode="&#xedde;" 
+d="M405 427h-53q-31.999 -1.00098 -53 -22q-21 -21 -22 -53v-53h-42v-64h42v-150h64v150h64v64h-64v42q1.00098 9 7 15q6 6 15 7h42v64zM427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDDF" unicode="&#xeddf;" 
+d="M405 107v121q0 31.999 -21.5 53q-21.499 21 -52.5 22q-15 0 -30 -8q-15 -7.99902 -24 -22v26h-64v-192h64v113q1.00098 13.001 10 22q9 9 22.5 9t22.5 -9t9 -22v-113h64zM139 335q16.001 0 27 11q10.999 10.999 11 27.5q0 16.5 -11 27.5q-10.999 10.999 -27.5 11
+q-16.5 0 -27.5 -11q-10.999 -10.999 -11 -27.5q0 -16.5 11 -27.5q10.999 -10.999 28 -11zM171 107v192h-64v-192h64zM427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDE0" unicode="&#xede0;" 
+d="M427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342zM427 256h-43v43h-21v-43h-43v-21h43v-43h21v43h43v21zM192 271v-36h61q-1.99902 -13.001 -15.5 -29q-13.5 -16.001 -45.5 -17q-28.001 1.00098 -46.5 20
+q-18.499 19.001 -18.5 47q0 28.001 18.5 47q18.499 19.001 46.5 20q16.001 0 26.5 -5.5q10.501 -5.50098 16.5 -11.5l29 28q-28.001 28.001 -72 29q-46.001 -1.00098 -76 -31q-30 -30 -30 -76q0 -46.001 30 -76q30 -30 76 -30q46.001 0 74 28q28.001 28.001 28 75
+q1.00098 10.001 -2 18h-100z" />
+    <glyph glyph-name="uniEDE1" unicode="&#xede1;" 
+d="M378 309q7.99902 6 15.5 14q7.49805 7.99707 11.5 16q-13.001 -7.00098 -30 -9q16.001 10.999 24 32q-16.001 -10.001 -37 -14q-28.999 28.999 -66.5 11.5q-37.5 -17.501 -31.5 -63.5q-40.999 3 -69 19.5t-49 39.5q-10.999 -21 -4.5 -44q6.49902 -22.999 21.5 -32
+q-13.999 1.00098 -24 7q1.00098 -24 13 -37.5q12 -13.5 31 -19.5q-12 -3 -24 -1q10.999 -34.999 52 -40q-15 -13.001 -38 -19.5q-22.999 -6.49902 -45 -3.5q18 -12 40 -19.5q22.001 -7.5 51 -6.5q72 4 114.5 49.5q42.499 45.499 44.5 120.5zM427 465q18 0 30 -12t12 -30
+v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDE0" unicode="&#xede0;" 
+d="M427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342zM427 256h-43v43h-21v-43h-43v-21h43v-43h21v43h43v21zM192 271v-36h61q-2 -13 -15 -29q-14 -16 -46 -17q-1.13636 -0.0454545 -2.26446 -0.0454545
+q-23.6901 0 -43.7355 20.0455q-19 19 -19 47t18 47q18 18 47 20q2.14286 0.142857 4.20408 0.142857q12.3673 0 21.7959 -5.14286q11 -6 17 -12l29 28q-28 28 -72 29q-1.33333 0.030303 -2.65565 0.030303q-42.314 0 -73.3444 -31.0303q-30 -30 -30 -76t30 -76t76 -30t74 28
+t28 75q0 13 -2 18h-100z" />
+  </font>
+</defs></svg>
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.ttf b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..567bc7822e146735ca48da310f6839b1f39ce3ce
Binary files /dev/null and b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.ttf differ
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff
new file mode 100644
index 0000000000000000000000000000000000000000..ceb41a11158b2497932b035bc1c46258beaa8130
Binary files /dev/null and b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff differ
diff --git a/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2 b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..6fbc47406ef0bcbe8b57b6da13670dcb60fd0f2d
Binary files /dev/null and b/plugins/easy_gantt/assets/fonts/EasyMaterialIcons-Regular.woff2 differ
diff --git a/plugins/easy_gantt/assets/images/.gitkeep b/plugins/easy_gantt/assets/images/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/plugins/easy_gantt/assets/images/yt.png b/plugins/easy_gantt/assets/images/yt.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c752df0c42df7ac7f86ad1385a6959cdc5999aa
Binary files /dev/null and b/plugins/easy_gantt/assets/images/yt.png differ
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/background.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/background.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e1bcf198903edf6d1e666f9ea98239b38e80abe
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/background.js
@@ -0,0 +1,304 @@
+/* background.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.getGanttBackground = function () {
+  var colors = ysy.settings.styles.backgrounds;
+
+  function getBackgroundColor() {
+    var defaultColor = "rgba(200, 200, 200, 0.25)";
+    if (!window.getComputedStyle) return defaultColor;
+    var styles = window.getComputedStyle(document.getElementsByTagName('body')[0]);
+    var colorString = styles.getPropertyValue('background-color') || "";
+    if (!colorString.match(/rgba?\([0-9, ]+\)/)) return defaultColor;
+    var colorSplit = colorString.replace(/^rgba?\(|\s+|\)$/g,'').split(',');
+    var colorArray = [parseInt(colorSplit[0]), parseInt(colorSplit[1]), parseInt(colorSplit[2])];
+    var strongColorArray = [];
+    var stronger = 3;
+    for (var i = 0; i < 3; i++) {
+      var component = colorArray[i];
+      if (component > 255 * (stronger - 1) / stronger) {
+        strongColorArray.push(255 - (255 - component) * stronger);
+      } else {
+        strongColorArray.push(Math.floor(component / stronger));
+      }
+    }
+    return "rgba(" + strongColorArray.join(", ") + ", 0.25)";
+  }
+
+  var backgroundColor = getBackgroundColor();
+  return {
+    fullCanvasRender: false,
+    container: gantt.$task_bg,
+    renderer: true,
+    filter: gantt._create_filter(['_filter_task', '_is_chart_visible', '_is_std_background']),
+    lastItems: null,
+    lastPos: null,
+    svg: null,
+    lastElements: {},
+    _render_bg_canvas: function (svg, items, limits) {
+      var rowHeight = gantt.config.row_height;
+      var cfg = gantt._tasks;
+      var widths = cfg.width;
+      var fullHeight = rowHeight * (limits.toY - limits.fromY);
+      var fullWidth = 0;
+      var width;
+      var partWidth;
+      for (var i = limits.fromX; i < limits.toX; i++) {
+        fullWidth += widths[i];
+      }
+      svg.size(fullWidth, fullHeight);
+      svg.node.style.left = cfg.left[limits.fromX] + "px";
+      svg.node.style.top = rowHeight * limits.fromY + "px";
+      //  -- CLEARING --
+      svg.clear();
+      //  -- SELECTED --
+      for (i = limits.fromY; i < limits.toY; i++) {
+        if (gantt.getState().selected_task == items[i].id) {
+          svg.rect(fullWidth, rowHeight).x(0).y((i - limits.fromY) * rowHeight).attr('fill', colors.selected);
+          break;
+        }
+      }
+      //  -- HORIZONTAL LINES --
+      var commands = [];
+      var lineCmd = "l " + fullWidth + " 0";
+      for (i = 1; i <= limits.toY - limits.fromY; i++) {
+        commands.push("M 0 " + (i * rowHeight - 0.5));
+        commands.push(lineCmd);
+      }
+      //  -- VERTICAL LINES --
+      partWidth = -0.5;
+      lineCmd = "l 0 " + fullHeight;
+      for (i = limits.fromX; i < limits.toX; i++) {
+        width = widths[i];
+        if (width <= 0) continue; //do not render skipped columns
+        partWidth += width;
+        commands.push("M " + partWidth + " 0");
+        commands.push(lineCmd);
+      }
+      svg.path(commands.join("")).attr("stroke", colors.line);
+
+      if (gantt.config.scale_unit === "day") {
+        var weekendGroup = svg.group().attr('fill', backgroundColor);
+        if (ysy.settings.resource.open) {
+          //  -- USER WEEKENDS BACKGROUND --
+          partWidth = 0;
+
+          for (i = limits.fromX; i < limits.toX; i++) {
+            width = widths[i];
+            var top = 0;
+            var mDate = moment(cfg.trace_x[i]);
+            var iDate = mDate.format("YYYY-MM-DD");
+            var lastWeekend = false;
+            var firstEntity = items[limits.fromY];
+            if (firstEntity.type !== "assignee") {
+              var assignee = ysy.data.assignees.getByID(firstEntity.widget.model.assigned_to_id || "unassigned");
+              if (assignee) {
+                lastWeekend = assignee.getMaxHours(iDate, mDate) === 0;
+              }
+            }
+            for (var j = limits.fromY; j < limits.toY; j++) {
+              if (items[j].type !== "assignee") continue;
+              var hours = items[j].widget.model.getMaxHours(iDate, mDate);
+              if ((hours === 0) === lastWeekend) continue;
+              if (lastWeekend) {
+                weekendGroup.rect(width, (j - limits.fromY) * rowHeight - top).x(partWidth).y(top);
+              } else {
+                top = (j - limits.fromY) * rowHeight;
+              }
+              lastWeekend = !lastWeekend;
+            }
+            if (lastWeekend) {
+              weekendGroup.rect(width, (limits.toY + 1) * rowHeight).x(partWidth).y(top);
+            }
+            partWidth += width;
+          }
+        } else {
+          //  -- WEEKENDS BACKGROUND --
+          if (!cfg.weekends) {
+            cfg.weekends = [];
+            for (var d = 0; d < cfg.trace_x.length; d++) {
+              cfg.weekends.push(!gantt._working_time_helper.is_working_day(cfg.trace_x[d]));
+            }
+          }
+          partWidth = 0;
+          for (i = limits.fromX; i < limits.toX; i++) {
+            width = widths[i];
+            if (cfg.weekends[i]) {
+              weekendGroup.rect(width, fullHeight).x(partWidth);
+            }
+            partWidth += width;
+          }
+
+        }
+      }
+      if (ysy.settings.resource.open) {
+        //  -- ASSIGNEE BACKGROUND --
+        var assigneeGroup = svg.group().attr('fill', backgroundColor);
+        for (i = limits.fromY; i < limits.toY; i++) {
+          if (items[i].type === "assignee") {
+            assigneeGroup.rect(fullWidth, rowHeight).y((i - limits.fromY) * rowHeight);
+          }
+        }
+        //  -- DARK LIMITS --
+        var darkLimitGroup = svg.group().attr('fill', backgroundColor.replace("0.25)", "0.5)"));
+        var ganttLimits = ysy.data.limits;
+        if (ganttLimits.start_date) {
+          var left = gantt.posFromDate(ganttLimits.start_date) - gantt.posFromDate(cfg.trace_x[limits.fromX]);
+          if (left > 0) {
+            darkLimitGroup.rect(left, fullHeight);
+          }
+        }
+        if (ganttLimits.end_date) {
+          var right = gantt.posFromDate(ganttLimits.end_date) - gantt.posFromDate(cfg.trace_x[limits.fromX]);
+          if (right > 0 && right < fullWidth) {
+            darkLimitGroup.rect(fullWidth - right, fullHeight).x(right);
+          }
+        }
+      }
+      //  -- BLUE LINE --
+      if (gantt.config.scale_unit === "day") {
+        partWidth = -0.5;
+        commands = [];
+        lineCmd = "l 0 " + fullHeight;
+        for (i = limits.fromX; i < limits.toX; i++) {
+          width = cfg.width[i];
+          var first = moment(cfg.trace_x[i]).date() === 1;
+          if (first) {
+            commands.push("M " + partWidth + " 0");
+            commands.push(lineCmd);
+          }
+          partWidth += width;
+        }
+        svg.path(commands.join("")).attr("stroke", colors.line_month);
+      }
+    },
+    render_bg_line: function (canvas, index, item) {
+
+    },
+    render_item: function (item, container) {
+      ysy.log.debug("render_item BG", "canvas_bg");
+    },
+    render_items: function (items, container) {
+      ysy.log.debug("render_items FULL BG", "canvas_bg");
+      container = container || this.node;
+      if (items) {
+        this.lastItems = items;
+      } else {
+        items = this.lastItems;
+        if (!items) return;
+      }
+      if (this.fullCanvasRender) {
+        this.render_full_svg(items, container);
+      } else {
+        this.render_shrunken_svg(items, container);
+      }
+      var fullHeight = gantt.config.row_height * items.length;
+      container.style.height = fullHeight + "px";
+      var lastEvent;
+      $(container)
+          .off("mousedown.bg")
+          .on("mousedown.bg", function (e) {
+            lastEvent = e;
+          })
+          .off("click.bg")
+          .on("click.bg", function (e) {
+            if (!lastEvent) return;
+            if (Math.abs(lastEvent.pageX - e.pageX) > 2 || Math.abs(lastEvent.pageY - e.pageY) > 2) return;
+            var order = gantt._order;
+            var offsetTop = $(container).offset().top;
+            var index = Math.floor((e.pageY - offsetTop) / gantt.config.row_height);
+            if (index < 0 || index >= order.length) return;
+            var taskId = order[index];
+            if (!gantt.isTaskExists(taskId)) return;
+            if (gantt._selected_task == taskId) {
+              gantt.unselectTask();
+            } else {
+              gantt.selectTask(taskId);
+            }
+          })
+    },
+    render_shrunken_svg: function (items, container) {
+      var cfg = gantt._tasks;
+      var scrollPos = gantt.getCachedScroll();
+      // var nodeWidth = this.node.innerWidth;
+      // if(scrollPos.x > Math.max(nodeWidth - window.innerWidth, 0)){
+      //   scrollPos.x = gantt.$task.scrollLeft;
+      // }
+      this.lastPos = scrollPos;
+      //ysy.log.debug("render_one_canvas ["+scrollPos.x+","+scrollPos.y+"]","canvas_bg");
+      var rowHeight = gantt.config.row_height;
+      var colWidth = cfg.col_width;
+      var countX = cfg.count;
+      var countY = items.length;
+      var limits = this.getCanvasLimits();
+      var partCountX = Math.ceil(limits.x / colWidth),
+          partCountY = Math.ceil(limits.y / rowHeight);
+      var startX = Math.max(scrollPos.x - (limits.x - window.innerWidth) / 2, 0);
+      var startY = Math.max(scrollPos.y - (limits.y - window.innerHeight) / 2, 0);
+      var startCountX = Math.floor(startX / colWidth);
+      var startCountY = Math.floor(startY / rowHeight);
+      if (startCountX + partCountX > countX) {
+        startCountX = countX - partCountX;
+      }
+      if (startCountY + partCountY > countY) {
+        startCountY = countY - partCountY;
+      }
+      var svg = this.svg;
+      if (!svg) {
+        svg = this._createSvg(container);
+      }
+      this._render_bg_canvas(svg, items, {
+        fromX: Math.max(startCountX, 0),
+        toX: startCountX + partCountX,
+        fromY: Math.max(startCountY, 0),
+        toY: startCountY + partCountY
+      });
+    },
+    render_full_svg: function (items, container) {
+      ysy.log.debug("render_items FULL BG", "canvas_bg");
+      var cfg = gantt._tasks;
+      var countX = cfg.count;
+      var countY = items.length;
+      var svg = this.svg;
+      if (!svg) {
+        svg = this._createSvg(container);
+      }
+      this._render_bg_canvas(svg, items, {
+        fromX: 0,
+        toX: countX,
+        fromY: 0,
+        toY: countY
+      });
+    },
+    _createSvg: function (container) {
+      var svg = SVG(container);
+      $(svg.node).css({position: "absolute"});
+      this.svg = svg;
+      return svg;
+    },
+    switchFullRender: function (fullRender) {
+      if (this.fullCanvasRender === fullRender) return;
+      this.fullCanvasRender = fullRender;
+      this.render_items();
+    },
+    isScrolledOut: function (x, y) {
+      if (this.fullCanvasRender) return false;
+      if (this.forceRender) return true;
+      var lastPos = this.lastPos;
+      if (!lastPos) return true;
+      var limits = this.getCanvasLimits();
+      if (x !== undefined) {
+        var bufferX = (limits.x - window.innerWidth) / 2;
+        if (Math.abs(x - lastPos.x) > bufferX) return true;
+      }
+      if (y !== undefined) {
+        var bufferY = (limits.y - window.innerHeight) / 2;
+        if (Math.abs(y - lastPos.y) > bufferY) return true;
+      }
+    },
+    getCanvasLimits: function () {
+      return {x: window.innerWidth + 600, y: window.innerHeight + 600};
+    }
+  };
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/bars.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/bars.js
new file mode 100644
index 0000000000000000000000000000000000000000..b10fde7ef2c35546ec8c8df12af44d87fd71e606
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/bars.js
@@ -0,0 +1,278 @@
+/* bars.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+
+ysy.view.bars = ysy.view.bars || {};
+$.extend(ysy.view.bars, {
+  _dateCache: {}, // for faster parsing YYYY-MM-DD to moment
+  _rendererStack: {},
+
+  registerRenderer: function (entity, renderer) {
+    if (this._rendererStack[entity] === undefined) {
+      this._rendererStack[entity] = [];
+    }
+    var renderers = this._rendererStack[entity];
+    var found = false;
+    for (var i = 0; i < renderers.length; i++) {
+      if (renderers[i] === renderer) found = true;
+    }
+    if (found) return;
+    renderers.push(renderer);
+    this.reconstructRenderer(entity);
+  },
+  removeRenderer: function (entity, renderer) {
+    var renderers = this._rendererStack[entity];
+    if (!renderers) return;
+    for (var i = 0; i < renderers.length; i++) {
+      if (renderers[i] === renderer) {
+        renderers.splice(i, 1);
+        this.reconstructRenderer(entity);
+        return;
+      }
+    }
+  },
+  reconstructRenderer: function (entity) {
+    var renderers = this._rendererStack[entity];
+    if (renderers.length === 0) {
+      gantt.config.type_renderers[entity] = gantt._task_default_render;
+      return;
+    }
+    gantt.config.type_renderers[entity] = function (task) {
+      var i = renderers.length - 1;
+      var nextRenderer = function () {
+        if (i < 0) return gantt._task_default_render;
+        return renderers[i--];
+      };
+      return nextRenderer().call(this, task, nextRenderer);
+    }
+  },
+
+  getFromDateCache: function (allodate) {
+    var alloMoment = this._dateCache[allodate];
+    if (alloMoment === undefined) {
+      alloMoment = moment(allodate);
+      this._dateCache[allodate] = alloMoment;
+    }
+    return alloMoment;
+  },
+  insertCanvas:function (canvas,rootDiv) {
+    var taskLeftElements = rootDiv.getElementsByClassName("task_left");
+    if (taskLeftElements.length === 0) {
+      rootDiv.appendChild(canvas);
+    } else {
+      rootDiv.insertBefore(canvas, taskLeftElements[0]);
+    }
+  },
+  canvasListBuilder: function () {
+    return {
+      __proto__: this.canvasListPrototype
+    };
+    //return canvasList;
+  },
+  canvasListPrototype: {
+    limit: 8170,
+    build: function (task, gantt, start_date, end_date) {
+      // initialization
+      this.canvases = [];
+      this.contexts = [];
+      this.starts = [];
+      this.gantt = gantt;
+      this.isAssignee = task.type === "assignee";
+      this.el = null;
+      this.height = this.isAssignee ? gantt.config.row_height : gantt._tasks.bar_height;
+      this.columnWidth = gantt._tasks.col_width;
+
+      var startX = gantt.posFromDate(start_date || task.start_date);
+      var endX = gantt.posFromDate(end_date || task.end_date);
+      //var fullWidth = gantt._get_task_width(task);
+      var fullWidth = endX - startX;
+      this.startX = startX;
+      this.fullWidth = fullWidth;
+      var config = gantt._tasks;
+
+      if (fullWidth < this.limit) {
+        this.el = this.createCanvas(fullWidth);
+        this.starts.push(startX);
+        this.staticPack = {
+          ctx: this.contexts[0],
+          canvas: this.el,
+          start: startX,
+          end: startX + fullWidth
+        };
+      } else {
+        this.el = document.createElement("div");
+        var lefts = config.left;
+        for (var i = 0; i < lefts.length; i++) {
+          if (lefts[i] >= startX) break;
+        }
+        var partX = startX;
+        for (; i < lefts.length; i++) {
+          if (lefts[i] > fullWidth + startX) break;
+          if (lefts[i] >= partX + this.limit) {
+            var canvas = this.createCanvas(lefts[i - 1] - partX);
+            this.el.appendChild(canvas);
+            this.starts.push(partX);
+            partX = lefts[i - 1];
+          }
+        }
+        canvas = this.createCanvas(startX + fullWidth - partX);
+        this.el.appendChild(canvas);
+        this.starts.push(partX);
+
+      }
+      this.el.className += " gantt-task-bar-line";
+      if (this.isAssignee) {
+        var y = this.gantt.getTaskTop(task.id);
+        this.el.style.left = startX + "px";
+        this.el.style.top = y + "px";
+      }
+    },
+    createCanvas: function (width) {
+      var el = document.createElement("canvas");
+      //var height = this.gantt._tasks.bar_height;
+      width = Math.round(width);
+      el.style.width = width + "px";
+      el.width = width;
+      el.height = this.height - 1;
+      el.className = "gantt-task-bar-canvas";
+      this.canvases.push(el);
+      var ctx = el.getContext("2d");
+      ctx.textAlign = 'center';
+      ctx.textBaseline = 'middle';
+      this.contexts.push(ctx);
+      return el;
+    },
+    getElement: function () {
+      return this.el;
+    },
+    inRange: function (date) {
+      var pos = gantt.posFromDateCached(date);
+      return pos + this.columnWidth >= this.startX && pos < this.fullWidth + this.startX;
+    },
+    fillTextAt: function (date, text, styles) {
+      var x = gantt.posFromDateCached(date);
+      var pack = this.getPack(x);
+      var posPack = this.getPosPack(x, pack);
+      var ctx = pack.ctx;
+      if (styles.backgroundColor) {
+        this.fillRectAtPosPack(posPack, pack, styles.backgroundColor);
+      }
+      ctx.font = styles.fontStyle;
+      ctx.fillStyle = styles.textColor;
+      text = this.fitTextInWidth(text, posPack.width, ctx);
+      ctx.fillText(text, posPack.middle, this.height / 2 + 1);
+    },
+    fillFormattedTextAt: function (date, formatter, value, styles) {
+      var x = gantt.posFromDateCached(date);
+      var pack = this.getPack(x);
+      var posPack = this.getPosPack(x, pack);
+      var ctx = pack.ctx;
+      if (styles.backgroundColor) {
+        this.fillRectAtPosPack(posPack, pack, styles.backgroundColor, styles.shrink);
+      }
+      ctx.font = styles.fontStyle;
+      ctx.fillStyle = styles.textColor;
+      var text = formatter(value, posPack.width);
+      text = this.fitTextInWidth(text, posPack.width, ctx);
+      ctx.fillText(text, posPack.middle, this.height / 2 + 1);
+    },
+    fillTwoTextAt: function (date, textUpper, textBottom, styles) {
+      var x = gantt.posFromDateCached(date);
+      var pack = this.getPack(x);
+      var posPack = this.getPosPack(x, pack);
+      var ctx = pack.ctx;
+      if (styles.backgroundColor) {
+        this.fillRectAtPosPack(posPack, pack, styles.backgroundColor, styles.shrink);
+      }
+      var bottomLine = this.height / 2 + 1;
+      ctx.fillStyle = styles.textColor;
+      if (textUpper) {
+        ctx.font = styles.fontStyle.replace("12px", "9px");
+        bottomLine = bottomLine * 13.0 / 10;
+        textUpper = this.fitTextInWidth(textUpper, posPack.width, ctx);
+        ctx.fillText(textUpper, posPack.middle, this.height * 3 / 12);
+      }
+      if (textBottom) {
+        ctx.font = styles.fontStyle;
+        // ctx.fillStyle = styles.textColor;
+        textBottom = this.fitTextInWidth(textBottom, posPack.width, ctx);
+        ctx.fillText(textBottom, posPack.middle, bottomLine);
+      }
+    },
+    fillRectAtPosPack: function (posPack, pack, fillColor, shrink) {
+      pack.ctx.fillStyle = fillColor;
+      if (shrink) {
+        pack.ctx.fillRect(posPack.start + 1, 1, posPack.width - 3, this.height - 3);
+      } else {
+        pack.ctx.fillRect(posPack.start, 0, posPack.width, this.height);
+      }
+    },
+    roundTo1: function (number) {
+      if (number === undefined) return "";
+      var modulated = number % 1;
+      if (modulated < 0) {
+        modulated += 1;
+      }
+      if (modulated < this.MARGIN || modulated > (1 - this.MARGIN)) {
+        return number.toFixed();
+      }
+      return number.toFixed(1);
+    },
+    fitTextInWidth: function (text, width, ctx) {
+      width -= 2;
+      if (text.length * 7.2 < width) return text;
+      ctx.font = ctx.font.replace("12px", "9px");
+      if (text.length * 5.3 < width) return text;
+      var splitPos = Math.floor(width / 5.3 - 1);
+      return text.substring(0, splitPos) + "#";
+    },
+    getPosPack: function (x, pack) {
+      var start, end, width = this.columnWidth;
+      if (!pack) return null;
+      start = x - pack.start;
+      if (x < pack.start || x > pack.end - width) {
+        end = start + width + Math.min(0, pack.end - width - x);
+        start = Math.max(start, 0);
+        return {
+          start: start,
+          end: end,
+          middle: Math.floor((start + end) / 2),
+          width: end - start
+        };
+      } else {
+        return {
+          start: start,
+          end: start + width,
+          middle: Math.floor(start + width / 2),
+          width: width
+        };
+      }
+    },
+    getPack: function (pos) {
+      if (this.staticPack) return this.staticPack;
+      //var pos = gantt.posFromDateCached(date);
+      // var min = this.startX;
+      var mid = pos + this.columnWidth / 2;
+      // if (pos + width < min) return null;
+      // if (pos >= this.fullWidth + min) return null;
+
+      for (var i = 1; i < this.starts.length; i++) {
+        if (this.starts[i] > mid) break;
+      }
+      if (i >= this.starts.length) {
+        i = this.starts.length;
+        var end = this.startX + this.fullWidth;
+      } else {
+        end = this.starts[i];
+      }
+
+      return {
+        ctx: this.contexts[i - 1],
+        canvas: this.canvases[i - 1],
+        start: this.starts[i - 1],
+        end: end
+      };
+    }
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/collapsor.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/collapsor.js
new file mode 100644
index 0000000000000000000000000000000000000000..83892ae56416219128e7588096749a1296038399
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/collapsor.js
@@ -0,0 +1,148 @@
+/* collapsor.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.collapsor = ysy.pro.collapsor || {};
+$.extend(ysy.pro.collapsor, {
+  templateHtml: null,
+  patch: function () {
+    var $sourceDiv = $("#close_all_something");
+    this.templateHtml = '<div id="gantt_grid_collapsors" class="gantt-grid-header-collapse-buttons">' + $sourceDiv.html() + '</div>';
+    $sourceDiv.remove();
+
+  },
+  extendees: [
+    {
+      id: "close_all_parent_issues",
+
+      bind: function () {
+        this.model = ysy.data.limits;
+      },
+      func: function () {
+        var openings = this.model.openings;
+        var issues = ysy.data.issues.getArray();
+        this.model.parentsIssuesClosed = !this.model.parentsIssuesClosed;
+        if (this.model.parentsIssuesClosed) {
+          for (var i = 0; i < issues.length; i++) {
+            openings[issues[i].getID()] = false;
+          }
+        } else {
+          for (i = 0; i < issues.length; i++) {
+            delete openings[issues[i].getID()];
+          }
+        }
+        this.model._fireChanges(this, "close_all_parent_issues");
+        return false;
+      },
+      isOn: function () {
+        return this.model.parentsIssuesClosed;
+      }
+    },
+    {
+      id: "close_all_milestones",
+      bind: function () {
+        this.model = ysy.data.limits;
+      },
+      func: function () {
+        var openings = this.model.openings;
+        var milestones = ysy.data.milestones.getArray();
+        this.model.milestonesClosed = !this.model.milestonesClosed;
+        if (this.model.milestonesClosed) {
+          for (var i = 0; i < milestones.length; i++) {
+            openings[milestones[i].getID()] = false;
+          }
+        } else {
+          for (i = 0; i < milestones.length; i++) {
+            delete openings[milestones[i].getID()];
+          }
+        }
+        this.model._fireChanges(this, "close_all_milestones");
+        return false;
+      },
+      isOn: function () {
+        return this.model.milestonesClosed;
+      }
+    },
+    {
+      id: "close_all_projects",
+      bind: function () {
+        this.model = ysy.data.limits;
+      },
+      func: function () {
+        var openings = this.model.openings;
+        var projects = ysy.data.projects.getArray();
+        this.model.projectsClosed = !this.model.projectsClosed;
+        if (this.model.projectsClosed) {
+          for (var i = 0; i < projects.length; i++) {
+            if (projects[i].id === ysy.settings.projectID) continue;
+            delete openings[projects[i].getID()];
+            // gantt.close(projects[i].getID());
+          }
+        } else {
+          for (i = 0; i < projects.length; i++) {
+            if (!projects[i].needLoad) {
+              openings[projects[i].getID()] = true;
+            }
+            //gantt.open(projects[i].getID());
+          }
+        }
+        this.model._fireChanges(this, "close_all_projects");
+        return false;
+      },
+      isOn: function () {
+        return this.model.projectsClosed;
+      }
+    }
+  ]
+});
+//#############################################################################################
+ysy.view.Collapsors = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Collapsors, {
+  name: "CollapsorsWidget",
+  _postInit:function(){
+    this.model = ysy.settings.resource;
+    this.model.unregister(this);
+    this.model.register(this.requestRepaint,this);
+  },
+  _updateChildren: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      this.children.destroy();
+    }
+    this.children = [];
+    var collapsorClass = ysy.pro.collapsor;
+    for (i = 0; i < collapsorClass.extendees.length; i++) {
+      var extendee = collapsorClass.extendees[i];
+      var button = new ysy.view.Button();
+      $.extend(button, extendee);
+      button.init();
+      this.children.push(button);
+    }
+  },
+  repaint: function (force) {
+    var $target = $("#gantt_grid_collapsors");
+    if(this.repaintRequested){
+      if(this.model.open){
+        $target.hide();
+        return;
+      }else{
+        $target.show();
+      }
+      $target.off("click").on("click",function(){
+        return false;
+      });
+    }
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      child.$target = this.getChildTarget(child);
+      if(!child.$target.length) continue;
+      child.repaint(force || this.repaintRequested);
+    }
+    this.repaintRequested=false;
+  },
+  getChildTarget: function (child) {
+    return this.$target.find("#" + child.elementPrefix + child.id);
+  }
+});
+
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/data.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/data.js
new file mode 100644
index 0000000000000000000000000000000000000000..92a4fa333a289cfb8c4b633f1fe4fa01ddb4ef33
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/data.js
@@ -0,0 +1,1046 @@
+/* data.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.data = ysy.data || {};
+ysy.data.Data = function () {
+  this._onChange = [];
+  this._deleted = false;
+  this._created = false;
+  this._changed = false;
+  this._cache = null;
+};
+ysy.data.Data.prototype = {
+  permissions: null,
+  problems: {},
+  init: function (obj, parent) {
+    this._old = obj;
+    $.extend(this, obj);
+    this._parent = parent;
+    this._postInit();
+    return this;
+  },
+  _postInit: function () {
+  },
+  set: function (key, val) {
+    // in the case of object as a first parameter:
+    // - parameter key is object and parameter val is not used.
+    if (typeof key === "object") {
+      var nObj = key;
+    } else {
+      nObj = {};
+      nObj[key] = val;
+    }
+    var rev = {};
+    for (var k in nObj) {
+      if (!nObj.hasOwnProperty(k)) continue;
+      var nObjk = nObj[k];
+      var thisk = this[k];
+      if (nObjk !== thisk) {
+        if (ysy.main.isSameMoment(thisk, nObjk)) {
+          ysy.log.debug("date filtered as same", "set");
+          continue;
+        }
+        rev[k] = thisk;
+        if (rev[k] === undefined) {
+          rev[k] = null;
+        }
+      } else {
+        ysy.log.debug(k + "=" + nObjk + " filtered as same", "set");
+      }
+    }
+    if ($.isEmptyObject(rev)) {
+      return false;
+    }
+    rev._changed = this._changed;
+    $.extend(this, nObj);
+    this._fireChanges(this, "set");
+    ysy.history.add(rev, this);
+    this._changed = true;
+    return true;
+  },
+  register: function (func, ctx) {
+    for (var i = 0; i < this._onChange.length; i++) {
+      var reg = this._onChange[i];
+      if (reg.ctx === ctx) {
+        this._onChange[i] = {func: func, ctx: ctx};
+        return;
+      }
+    }
+    this._onChange.push({func: func, ctx: ctx});
+  },
+  unregister: function (ctx) {
+    var nonch = [];
+    for (var i = 0; i < this._onChange.length; i++) {
+      var reg = this._onChange[i];
+      if (reg.ctx !== ctx) {
+        nonch.push(reg);
+      }
+    }
+    this._onChange = nonch;
+  },
+  setSilent: function (key, val) {
+    if (typeof key === "object") {
+      var different;
+      var keyk, thisk;
+      for (var k in key) {
+        if (!key.hasOwnProperty(k)) continue;
+        keyk = key[k];
+        thisk = this[k];
+        if (keyk === thisk)continue;
+        if (ysy.main.isSameMoment(thisk, keyk)) continue;
+        this[k] = keyk;
+        different = true;
+      }
+      return different || false;
+      //$.extend(this, key);
+    } else {
+      if (this[key] === val) return false;
+      this[key] = val;
+      return true;
+    }
+  },
+  _fireChanges: function (who, reason, onlyChildChanged) {
+    if (who) {
+      var reasonPart = "";
+      if (reason) {
+        reasonPart = " because of " + reason;
+      }
+      if (who === this) {
+        var targetPart = "itself";
+      } else {
+        targetPart = this._name;
+      }
+      var name = who._name;
+      if (!name) {
+        name = who.name;
+      }
+      if (!name) {
+        ysy.log.warning(who);
+      }
+      ysy.log.log("* " + name + " ordered repaint on " + targetPart + reasonPart);
+
+    }
+    if (onlyChildChanged) {
+      var onChangeArray = this._onChildChange;
+    } else {
+      onChangeArray = this._onChange;
+      this._cache = null;
+    }
+    if (onChangeArray.length > 0) {
+      ysy.log.log("- " + this._name + (onlyChildChanged ? " on ChildChange" : " onChange") + " fired for " + onChangeArray.length + " widgets");
+    } else {
+      ysy.log.log("- no " + (onlyChildChanged ? "childChange" : "change") + " for " + this._name);
+    }
+    for (var i = 0; i < onChangeArray.length; i++) {
+      var ctx = onChangeArray[i].ctx;
+      if (!ctx || ctx.deleted) {
+        onChangeArray.splice(i, 1);
+        continue;
+      }
+      //this.onChangeNew[i].func();
+      ysy.log.log("-- changes to " + (ctx.name ? ctx.name : ctx._name) + " widget");
+      //console.log(ctx);
+      onChangeArray[i].func.call(ctx, reason);
+    }
+  },
+  remove: function () {
+    if (this._deleted) return;
+    var prevChanged = this._changed;
+    this._changed = true;
+    this._deleted = true;
+    if (this._parent && this._parent.isArray) {
+      this._parent.pop(this);
+    }
+    ysy.history.add(function () {
+      this._changed = prevChanged;
+      this._deleted = false;
+      if (this._parent && this._parent.isArray) {
+        this._parent._fireChanges(this, "revert parent");
+      }
+    }, this);
+    this._fireChanges(this, "remove");
+  },
+  removeSilent: function () {
+    if (this._deleted) return;
+    this._deleted = true;
+  },
+  clearCache: function () {
+    this._cache = null;
+  },
+  getDiff: function (newObj) {
+    var diff = {};
+    var any = false;
+    for (var key in newObj) {
+      if (!newObj.hasOwnProperty(key)) continue;
+      var newItem = newObj[key];
+      if (newItem != this._old[key]) {
+        if (moment.isMoment(newItem)) {
+          if (newItem.format("YYYY-MM-DD") === this._old[key]) continue;
+        }
+        diff[key] = newObj[key];
+        any = true;
+      }
+    }
+    if (!any) return null;
+    return diff;
+  },
+  isEditable: function () {
+    if (!this.permissions) return false;
+    return !!this.permissions.editable;
+  },
+  getProblems: function () {
+    if (this._cache && this._cache.problems !== undefined) {
+      return this._cache.problems;
+    }
+    var ret = [];
+    for (var problemType in this.problems) {
+      if (!this.problems.hasOwnProperty(problemType)) continue;
+      var res = this.problems[problemType].call(this);
+      if (res) ret.push(res);
+    }
+    if (ret.length === 0) {
+      ret = false;
+    }
+    if (!this._cache) {
+      this._cache = {};
+    }
+    this._cache.problems = ret;
+    return ret;
+  }
+};
+//###########################################################################################x
+ysy.data.Array = function () {
+  ysy.data.Data.call(this);
+  this.array = [];
+  this.dict = {};
+  this._onChildChange = [];
+};
+ysy.main.extender(ysy.data.Data, ysy.data.Array, {
+  isArray: true,
+  get: function (i) {
+    if (i < 0 || i >= this.array.length) return null;
+    return this.array[i];
+  },
+  getArray: function () {
+    if (!this._cache) {
+      var cache = [];
+      for (var i = 0; i < this.array.length; i++) {
+        if (this.array[i]._deleted) continue;
+        cache.push(this.array[i]);
+      }
+      this._cache = cache;
+    }
+    return this._cache;
+  },
+  getByID: function (id) {
+    if (id === undefined || id === null) return null;
+    var el = this.dict[id];
+    if (el) return el;
+    for (var i = 0; i < this.array.length; i++) {
+      if (id === this.array[i].id) {
+        this.dict[id] = this.array[i];
+        return this.array[i];
+      }
+    }
+  },
+  pushSilent: function (elem) {
+    if (elem.id) {
+      var same = this.getByID(elem.id);
+      if (same) {
+        var needFire = false;
+        if (same._deleted !== elem._deleted) {
+          needFire = true;
+        }
+        elem._onChange = same._onChange;
+        same.setSilent(elem);
+        same._fireChanges(this, "pushSame");
+        if (needFire) {
+          this._fireChanges(this, "pushSame");
+        }
+        return same;
+      }
+    }
+    if (!elem._parent) {
+      elem._parent = this;
+    }
+    elem.register(function () {
+      // registered for observing changes in own children
+      // do not propagate to full this._fireChanges()
+      this.orderFireChildChange(elem);
+    }, this);
+    this.array.push(elem);
+    if (elem.id) {
+      this.dict[elem.id] = elem;
+    }
+    return elem;
+
+  },
+  childRegister: function (func, ctx) {
+    for (var i = 0; i < this._onChildChange.length; i++) {
+      var reg = this._onChildChange[i];
+      if (reg.ctx === ctx) {
+        this._onChildChange[i] = {func: func, ctx: ctx};
+        return;
+      }
+    }
+    this._onChildChange.push({func: func, ctx: ctx});
+  },
+  push: function (elem) {
+    //var rev=this.array.slice();
+    elem._changed = true;
+    elem._created = true;
+    elem = this.pushSilent(elem);
+    this._fireChanges(this, "push");
+    ysy.history.add(function () {
+      //this.pop(elem);
+      this._deleted = true;
+      this._parent._fireChanges(this, "push revert");
+      //this._fireChanges(this,"push revert");
+    }, elem);
+
+  },
+  pop: function (model) {
+    //ysy.data.history.saveDelete(this);
+    if (model === undefined) {
+      return false;
+    }
+    if (!model._deleted) {
+      model.remove();
+      return true;
+    } else {
+
+    }
+    this._fireChanges(this, "pop");
+    /*if(model._created){
+     var rev=this.array.slice();
+     this.cache=null;
+     var arr=this.array;
+     for(var i=0;i<arr;i++){
+     if(arr[i]===model){
+     this.array.splice(i, 1);
+     console.log("removed item No. " + i);
+     this._fireChanges(this,"pop");
+     ysy.history.add(rev,this);
+     return true;
+     }
+     }
+     return false;
+     }*/
+    //this.array[i].deleted=true;
+  },
+  clear: function () {
+    this.array = [];
+    this.dict = {};
+    this._fireChanges(this, "clear all");
+  },
+  clearSilent: function () {
+    this.array = [];
+    this.dict = {};
+    this._cache = null;
+  },
+  orderFireChildChange: function (elem) {
+    if (this.childChangeTimeout) {
+      return;
+    }
+    var self = this;
+    this.childChangeTimeout = setTimeout(function () {
+      self._fireChanges(elem, "child updated", true);
+      self.childChangeTimeout = null;
+    }, 0);
+  }
+  /*size: function () {
+   return this.getArray().length;
+   }*/
+
+});
+//##############################################################################
+ysy.data.IssuesArray = {
+  _name: "IssuesArray"
+};
+//##############################################################################
+ysy.data.Issue = function () {
+  ysy.data.Data.call(this);
+};
+ysy.main.extender(ysy.data.Data, ysy.data.Issue, {
+  _name: "Issue",
+  ganttType: "task",
+  isIssue: true,
+  closed: false,
+  css: '',
+  _postInit: function () {
+    if (this.start_date) {
+      if (typeof this.start_date === "string") {
+        this.start_date = moment(this.start_date, "YYYY-MM-DD");
+      } else if (!this.start_date._isAMomentObject) {
+        console.error("start_date is not string");
+        this.start_date = moment(this.start_date).startOf("day");
+      }
+    }
+    if (this.due_date) {
+      this.end_date = moment(this.due_date, "YYYY-MM-DD");
+      delete this.due_date;
+    }
+    if (this.start_date) {
+      this._start_date = this.start_date;
+    } else {
+      if (this.end_date && this.end_date.isBefore(moment())) {
+        this._start_date = moment(this.end_date);
+      } else {
+        this._start_date = moment().startOf("day");
+      }
+    }
+    if (this.end_date) {
+      this._end_date = this.end_date;
+    } else {
+      if (this.start_date && this.start_date.isAfter(moment())) {
+        this._end_date = moment(this.start_date);
+      } else {
+        this._end_date = moment().startOf("day");
+      }
+    }
+    //this.end_date._isEndDate = true;
+    this._end_date._isEndDate = true;
+    if (this.soonest_start) {
+      this.soonest_start = moment(this.soonest_start, "YYYY-MM-DD");
+    }
+    if (this.latest_due) {
+      this.latest_due = moment(this.latest_due, "YYYY-MM-DD");
+      this.latest_due._isEndDate = true;
+    }
+    this._transformColumns();
+  },
+  _transformColumns: function () {
+    var cols = this.columns;
+    if (!cols) return;
+    var ncols = {};
+    for (var i = 0; i < cols.length; i++) {
+      var col = cols[i];
+      ncols[col.name] = col.value;
+      if (col.value_id !== undefined) {
+        ncols[col.name + "_id"] = col.value_id;
+      }
+    }
+    this.columns = ncols;
+  },
+  set: function (key, val) {
+    // in the case of object as a first parameter:
+    // - parameter key is object and parameter val is not used.
+    if (typeof key === "object") {
+      var nObj = key;
+    } else {
+      nObj = {};
+      nObj[key] = val;
+    }
+    nObj = this._dateSetHelper(nObj);
+    return ysy.data.Data.prototype.set.call(this, nObj);
+  },
+  setSilent: function (key, val) {
+    // in the case of object as a first parameter:
+    // - parameter key is object and parameter val is not used.
+    if (typeof key === "object") {
+      var nObj = key;
+    } else {
+      nObj = {};
+      nObj[key] = val;
+    }
+    nObj = this._dateSetHelper(nObj);
+    return ysy.data.Data.prototype.setSilent.call(this, nObj);
+  },
+  _dateSetHelper: function (nObj) {
+    if (nObj.start_date) {
+      if (nObj.start_date.isSame(this._start_date)) {
+        delete nObj.start_date;
+      } else {
+        nObj._start_date = nObj.start_date
+      }
+    }
+    if (nObj.end_date) {
+      if (nObj.end_date.isSame(this._end_date)) {
+        delete nObj.end_date;
+      } else {
+        nObj._end_date = nObj.end_date
+      }
+    }
+    return nObj;
+  },
+  getID: function () {
+    return this.id;
+  },
+  getParent: function () {
+    var parent = ysy.data.issues.getByID(this.parent_issue_id);
+    if (parent) {
+      if (this.project_id === parent.project_id) {
+        return this.parent_issue_id;
+      }
+    }
+    if (this.fixed_version_id) {
+      parent = ysy.data.milestones.getByID(this.fixed_version_id + "p" + this.project_id);
+      if (parent) return "m" + this.fixed_version_id + "p" + this.project_id;
+      parent = ysy.data.milestones.getByID(this.fixed_version_id);
+      if (parent) {
+        if (this.project_id === parent.project_id) {
+          return "m" + this.fixed_version_id;
+        }
+      }
+    }
+    if (ysy.data.projects.getByID(this.project_id)) {
+      return "p" + this.project_id;
+    }
+    return false;
+  },
+  problems: {
+    checkOverMile: function () {
+      if (!this.fixed_version_id) {
+        return;
+      }
+      var milestone = ysy.data.milestones.getByID(this.fixed_version_id);
+      if (!milestone) {
+        return;
+        /*ysy.error("Error: Issue "+this.id+" not found its milestone");*/
+      }
+      if (milestone.start_date.diff(this._end_date, "days") < 0)
+        return ysy.settings.labels.problems.overMilestone
+            .replace("%{effective_date}", milestone.start_date.format(gantt.config.date_format));
+    },
+    overDue: function () {
+      if (
+          !this.closed
+          && this.end_date
+          && this.end_date.isBefore(moment().subtract(1, "day")))
+        return ysy.settings.labels.problems.overdue;
+    }
+  },
+  getDuration: function (unit) {
+    unit = unit || "days";
+    if (this._cache && this._cache.duration) {
+      return this._cache.duration[unit];
+    }
+    var durationPack = gantt._working_time_helper.get_work_units_between(this._start_date, this._end_date, "all");
+    if (!this._cache) {
+      this._cache = {};
+    }
+    this._cache.duration = durationPack;
+    return durationPack[unit];
+  },
+
+  correctPosition: function (allRequests) {
+    if (allRequests === undefined) allRequests = {};
+    var request = this.getMoveRequest(allRequests);
+    if (!request.needBroadcast) return;
+    request.needBroadcast = false;
+    request.counter++;
+    if (request.counter > 200) return;
+    ysy.log.debug(this.name + " - correctingPosition", "moveRequest");
+    if (ysy.settings.milestonePush) {
+      var milestone = ysy.data.milestones.getByID(this.fixed_version_id);
+      if (milestone) {
+        var milestoneRequest = milestone.getMoveRequest(allRequests);
+        request.setLimits(null, milestoneRequest.softEnd);
+      }
+    }
+    var relations = ysy.data.relations.getArray();
+    for (var i = 0; i < relations.length; i++) {
+      var relation = relations[i];
+      if (relation.getTarget() === this || relation.getSource() === this) {
+        relation.sendMoveRequest(allRequests);
+      }
+    }
+    if (ysy.settings.parentIssueDates) {
+      var issues = ysy.data.issues.getArray();
+      var childRequests = [];
+      for (var j = 0; j < issues.length; j++) {
+        if (issues[j].parent_issue_id !== this.id) continue;
+        var child = issues[j];
+        var childRequest = child.getMoveRequest(allRequests);
+        childRequest.setLimits(request.hardStart, request.hardEnd, true);
+        childRequests.push(childRequest);
+      }
+      for (j = 0; j < childRequests.length; j++) {
+        childRequests[j].entity.correctPosition(allRequests);
+      }
+      var parent = ysy.data.issues.getByID(this.parent_issue_id);
+      if (parent) {
+        var parentRequest = parent.getMoveRequest(allRequests);
+        parentRequest.resetByChildren(allRequests);
+      }
+    }
+  },
+  getMoveRequest: function (allRequests) {
+    var request = allRequests[this.getID()];
+    if (!request) {
+      ysy.log.debug(this.name + " - new moveRequest", "moveRequest");
+      allRequests[this.getID()] = request = new ysy.data.MoveRequest();
+      request.init(this, allRequests);
+    }
+    return request;
+  },
+  isOpened: function () {
+    var opened = ysy.data.limits.openings[this.getID()];
+    if (opened === undefined) {
+      return true;
+    }
+    return opened;
+  }
+});
+//############################################################################
+ysy.data.Relation = function () {
+  ysy.data.Data.call(this);
+  this.unlocked = !ysy.settings.fixedRelations;
+};
+ysy.main.extender(ysy.data.Data, ysy.data.Relation, {
+  _name: "Relation",
+  _postInit: function () {
+    //if(this.delay&&this.delay>0){this.delay--;}
+  },
+  getID: function () {
+    return "r" + this.id;
+  },
+  getActDelay: function () {
+    var sourceDate = this.getSourceDate();
+    var targetDate = this.getTargetDate();
+    if (!sourceDate || !targetDate) return this.delay;
+    if (ysy.settings.workDayDelays) {
+      return gantt._working_time_helper.get_work_units_between(sourceDate, targetDate, "day");
+    }
+    var correction = 0;
+    if (sourceDate._isEndDate) correction -= 1;
+    if (targetDate._isEndDate) correction += 1;
+    return targetDate.diff(sourceDate, "days") + correction;
+  },
+  getSourceDate: function (source) {
+    if (!source) source = this.getSource();
+    if (!source) return null;
+    if (this.type === "precedes") return source._end_date;
+    if (this.type === "finish_to_finish") return source._end_date;
+    if (this.type === "start_to_start") return source._start_date;
+    if (this.type === "start_to_finish") return source._start_date;
+    return null;
+  },
+  getTargetDate: function (target) {
+    if (!target) target = this.getTarget();
+    if (!target) return null;
+    if (this.type === "precedes") return target._start_date;
+    if (this.type === "finish_to_finish") return target._end_date;
+    if (this.type === "start_to_start") return target._start_date;
+    if (this.type === "start_to_finish") return target._end_date;
+    return null;
+  },
+  getSourceCorrection: function () {
+    if (this.type === "precedes") return 1;
+    if (this.type === "finish_to_finish") return 1;
+    return 0;
+  },
+  getTargetCorrection: function () {
+    if (this.type === "start_to_finish") return -1;
+    if (this.type === "finish_to_finish") return -1;
+    return 0;
+  },
+  //getOtherDate: function (date, forSource) {
+  //  var otherDate = gantt._working_time_helper.add_worktime(date, forSource ? -this.delay : this.delay, "day");
+  //},
+  getProblems: function () {
+    var del = this.getActDelay();
+    var diff = (this.delay || 0) - del;
+    if (diff > 0)
+      return [
+        ysy.settings.labels.problems.shortDelay
+            .replace("%{diff}", diff.toFixed(0))
+      ];
+    return [];
+  },
+  checkDelay: function () {
+    var del = this.getActDelay();
+    return del >= (this.delay || 0);
+  },
+  getSource: function () {
+    return ysy.data.issues.getByID(this.source_id);
+  },
+  getTarget: function () {
+    return ysy.data.issues.getByID(this.target_id);
+  },
+  makeDelayFixedForSave: function () {
+    var delay = this.getActDelay();
+    if (this.set({delay: delay})) {
+      this._fireChanges(this, "makeDelayFixedForSave()");
+    }
+  },
+  sendMoveRequest: function (allRequests) {
+    var source = this.getSource();
+    var target = this.getTarget();
+    if (!source || !target) return true; // HALF LINK
+    var sourceRequest = source.getMoveRequest(allRequests);
+    var targetRequest = target.getMoveRequest(allRequests);
+    if (this.getSourceCorrection() === 1) {
+      var sourceDate = sourceRequest.softEnd;
+      sourceDate._isEndDate = true;
+    } else {
+      sourceDate = sourceRequest.softStart;
+    }
+    if (ysy.settings.workDayDelays) {
+      var earliestTarget = gantt._working_time_helper.add_worktime(sourceDate, this.delay, "day", this.getTargetCorrection() === -1);
+    } else {
+      earliestTarget = moment(sourceDate).add(this.delay + this.getTargetCorrection() + this.getSourceCorrection(), "days");
+    }
+    gantt._working_time_helper.round_date(earliestTarget);
+    targetRequest.setLimits(earliestTarget, null, true);
+
+    if (this.getTargetCorrection() === 0) {
+      earliestTarget = gantt._working_time_helper.add_worktime(earliestTarget, targetRequest.duration, "day", false);
+    }
+
+    if (targetRequest.hardEnd && earliestTarget.isAfter(targetRequest.hardEnd)) {
+      var targetDate = targetRequest.hardEnd;
+      targetDate._isEndDate = true;
+      if (this.getTargetCorrection() === 0) {
+        targetDate = gantt._working_time_helper.add_worktime(targetDate, -targetRequest.duration, "day", false);
+      }
+      if (ysy.settings.workDayDelays) {
+        var latestDate = gantt._working_time_helper.add_worktime(targetDate, -this.delay, "day", sourceDate._isEndDate === true);
+      } else {
+        latestDate = moment(targetDate).add(-this.delay - this.getTargetCorrection() - this.getSourceCorrection(), "days");
+      }
+      if (this.getSourceCorrection() === 0) {
+        latestDate = gantt._working_time_helper.add_worktime(latestDate, sourceRequest.duration, "day", false);
+      }
+      latestDate._isEndDate = true;
+      gantt._working_time_helper.round_date(latestDate, "past");
+      ysy.log.debug(source.name + " - milestonePush to " + latestDate.format("DD.MM.YYYY"), "moveRequest");
+      source.getMoveRequest(allRequests)
+          .setLimits(null, latestDate);
+      return;
+    }
+    ysy.log.debug(target.name + " - classicPush to (end) " + earliestTarget.format("DD.MM.YYYY"), "moveRequest");
+    target.correctPosition(allRequests);
+
+  },
+  isEditable: function () {
+    var source = this.getSource();
+    if (!source) return false;
+    if (source.isEditable()) return true;
+    var target = this.getTarget();
+    if (!target) return false;
+    if (target.isEditable()) return true;
+  },
+  isHalfLink: function () {
+    return !(ysy.data.issues.getByID(this.source_id) && ysy.data.issues.getByID(this.target_id));
+  }
+});
+//############################################################################
+ysy.data.SimpleRelation = function () {
+  ysy.data.Data.call(this);
+};
+ysy.main.extender(ysy.data.Relation, ysy.data.SimpleRelation, {
+  _name: "SimpleRelation",
+  isSimple: true,
+  sendMoveRequest: function (allRequests) {
+    return false
+  },
+  isEditable: function () {
+    return false;
+  }
+});
+//##############################################################################
+ysy.data.Milestone = function () {
+  ysy.data.Data.call(this);
+};
+ysy.main.extender(ysy.data.Data, ysy.data.Milestone, {
+  _name: "Milestone",
+  ganttType: "milestone",
+  milestone: true,
+  _postInit: function () {
+    if (this.start_date) {
+      if (typeof this.start_date === "string") {
+        this.start_date = moment(this.start_date, "YYYY-MM-DD");
+      } else {
+        this.start_date = moment(this.start_date).startOf("day");
+      }
+    }
+    if (!this.start_date) {
+      this.start_date = moment().startOf("day");
+    }
+  },
+  getID: function () {
+    return "m" + this.id;
+  },
+  getIssues: function () {
+    var retissues = [];
+    var issues = ysy.data.issues.getArray();
+    for (var i = 0; i < issues.length; i++) {
+      if (issues[i].fixed_version_id === this.id) {
+        retissues.push(issues[i]);
+      }
+    }
+    return retissues;
+  },
+  _fireChanges: function (who, reason) {
+    var prototype = ysy.data.Data.prototype;
+    prototype._fireChanges.call(this, who, reason);
+    var childs = this.getIssues();
+    for (var i = 0; i < childs.length; i++) {
+      childs[i]._fireChanges(this, "milestone change");
+    }
+  },
+  getProblems: function () {
+    return false;
+  },
+  getParent: function () {
+    if (ysy.data.projects.getByID(this.project_id)) {
+      return "p" + this.project_id;
+    }
+    return false;
+  },
+  correctPosition: function (allRequests) {
+    if (allRequests === undefined) allRequests = {};
+    var request = this.getMoveRequest(allRequests);
+    if (!request.needBroadcast) return;
+    request.needBroadcast = false;
+    ysy.log.debug(this.name + " - correctingPosition", "moveRequest");
+    var issues = ysy.data.issues.getArray();
+    for (var j = 0; j < issues.length; j++) {
+      if (issues[j].fixed_version_id !== this.id) continue;
+      var child = issues[j];
+      child.getMoveRequest(allRequests).setLimits(null, request.softEnd);
+    }
+  },
+  getMoveRequest: function (allRequests) {
+    var request = allRequests[this.getID()];
+    if (!request) {
+      ysy.log.debug(this.name + " - new moveRequest", "moveRequest");
+      allRequests[this.getID()] = request = {
+        allRequests: allRequests,
+        entity: this,
+        setPosition: function (sortStart, softEnd) {
+          this.softStart = sortStart;
+          this.softEnd = moment(sortStart);
+          this.softEnd._isEndDate = true;
+        }
+      };
+      request.setPosition(this.start_date, null);
+      // request.init(this, allRequests);
+    }
+    return request;
+  },
+  isOpened: function () {
+    var opened = ysy.data.limits.openings[this.getID()];
+    if (opened === undefined) {
+      return true;
+    }
+    return opened;
+  }
+});
+//##############################################################################
+ysy.data.SharedMilestone = function () {
+  ysy.data.Milestone.call(this);
+};
+ysy.main.extender(ysy.data.Milestone, ysy.data.SharedMilestone, {
+  _name: "SharedMilestone",
+  isShared: true,
+  ganttSubtype: "shared_milestone",
+  _postInit: function () {
+    this.real_id = this.id;
+    this.id = this.id + "p" + this.project_id;
+    this.css = "gantt-milestone-shared";
+    this.__proto__.__proto__._postInit.call(this);
+    this.real_milestone.register(function (reason) {
+      if (reason === "revert") return;
+      if (this.start_date.isSame(this.real_milestone.start_date)) return;
+      this.set({start_date: moment(this.real_milestone.start_date)});
+    }, this);
+  },
+  isEditable: function () {
+    return false;
+  }
+});
+//##############################################################################
+ysy.data.Project = function () {
+  ysy.data.Data.call(this);
+};
+ysy.main.extender(ysy.data.Data, ysy.data.Project, {
+  _name: "Project",
+  ganttType: "project",
+  isProject: true,
+  needLoad: true,
+  issues_count: 0,
+  has_subprojects: false,
+  _shift: 0,
+  _postInit: function () {
+    this.start_date = this.start_date ? moment(this.start_date, "YYYY-MM-DD") : null;
+    //this.end_date=moment(this.due_date).add(1, "d");
+    if (this.due_date) {
+      this.end_date = moment(this.due_date, "YYYY-MM-DD");
+      this.end_date._isEndDate = true;
+    }
+    delete this.due_date;
+    if (this.is_baseline) {
+      this._ignore = true;
+    }
+    this.project_id = this.id;
+    if (ysy.settings.projectID === this.id) {
+      ysy.data.limits.openings[this.getID()] = true;
+      this.needLoad = false;
+    }
+    if (ysy.settings.global) {
+      this.needLoad = this.needLoad && this.has_subprojects;
+    }
+    this._transformColumns();
+  },
+  _transformColumns: function () {
+    var cols = this.columns;
+    if (!cols) return;
+    var ncols = {};
+    for (var i = 0; i < cols.length; i++) {
+      var col = cols[i];
+      ncols[col.name] = col.value;
+      if (col.value_id !== undefined) {
+        ncols[col.name + "_id"] = col.value_id;
+      }
+    }
+    this.columns = ncols;
+  },
+  getID: function () {
+    return "p" + this.id;
+  },
+  problems: {},
+  getProgress: function () {
+    return this.done_ratio / 100.0 || 0;
+  },
+  getParent: function () {
+    if (ysy.data.projects.getByID(this.parent_id)) {
+      return "p" + this.parent_id;
+    }
+    return false;
+  },
+  isOpened: function () {
+    return ysy.data.limits.openings[this.getID()] || false;
+  }
+});
+//##############################################################################
+ysy.data.MoveRequest = function () {
+  this.needBroadcast = false;
+  this.counter = 0;
+};
+ysy.main.extender(Object, ysy.data.MoveRequest, {
+  _name: "MoveRequest",
+  init: function (issue, allRequests) {
+    this.entity = issue;
+    this.allRequests = allRequests;
+    this.softStart = issue._start_date;
+    this.softEnd = issue._end_date;
+    this.duration = issue.getDuration();
+    if (!this.entity.isEditable()) {
+      this.hardStart = issue._start_date;
+      this.hardEnd = issue._end_date;
+    }
+    return this;
+  },
+  setLimits: function (hardStart, hardEnd, silent) {
+    var startSet = false, endSet = false, oldHardStart = this.hardStart;
+    if (hardStart) {
+      if (!this.hardStart || this.hardStart.isBefore(hardStart)) {
+        this.hardStart = moment(hardStart);
+        startSet = true;
+      }
+    }
+    if (hardEnd) {
+      if (!this.hardEnd || this.hardEnd.isAfter(hardEnd)) {
+        this.hardEnd = moment(hardEnd);
+        this.hardEnd._isEndDate = true;
+        endSet = true;
+      }
+    }
+    if (startSet || endSet) {
+      if (this.hardEnd) {
+        var lastStart = gantt._working_time_helper.add_worktime(this.hardEnd, -this.duration, "day");
+        if (this.hardStart && this.hardStart.isAfter(lastStart)) {
+          this.hardStart = lastStart;
+        }
+        if (this.softEnd && this.softEnd.isAfter(this.hardEnd)) {
+          ysy.log.debug("start: " + this.softStart.format("DD.MM.YYYY") + "=>" + lastStart.format("DD.MM.YYYY") +
+              " end: " + this.softEnd.format("DD.MM.YYYY") + "=>" + this.hardEnd.format("DD.MM.YYYY"), "moveRequest");
+          this.softEnd = moment(this.hardEnd);
+          this.softEnd._isEndDate = true;
+          this.softStart = lastStart;
+          this.needBroadcast = true;
+        }
+      }
+      if (this.hardStart) {
+        var earliestEnd = gantt._working_time_helper.add_worktime(this.hardStart, this.duration, "day", true);
+        if (this.softStart && this.softStart.isBefore(this.hardStart)) {
+          ysy.log.debug("start: " + this.softStart.format("DD.MM.YYYY") + "=>" + this.hardStart.format("DD.MM.YYYY") +
+              " end: " + this.softEnd.format("DD.MM.YYYY") + "=>" + earliestEnd.format("DD.MM.YYYY"), "moveRequest");
+          this.softStart = moment(this.hardStart);
+          this.softEnd = earliestEnd;
+          this.needBroadcast = true;
+        }
+      }
+    }
+    // if (this.needBroadcast) {
+    //   ysy.log.debug(this.entity.name + " - setLimits(" + (hardStart ? hardStart.format("DD.MM.YYYY") : "null") + "," + (hardEnd ? hardEnd.format("DD.MM.YYYY") : "null") + ") => " + this.needBroadcast,"moveRequest");
+    // }
+    if (!silent) {
+      this.entity.correctPosition(this.allRequests);
+    }
+  },
+  resetByChildren: function (allRequests) {
+    var issues = ysy.data.issues.getArray();
+    var lastStart;
+    var earliestEnd;
+    var startLimit = undefined;
+    var endLimit = undefined;
+    for (var j = 0; j < issues.length; j++) {
+      if (issues[j].parent_issue_id !== this.entity.id) continue;
+      var childRequest = issues[j].getMoveRequest(allRequests);
+      if (!lastStart || lastStart.isAfter(childRequest.softStart)) {
+        lastStart = childRequest.softStart;
+      }
+      if (startLimit !== false) {
+        if (childRequest.hardStart) {
+          if (!startLimit || startLimit.isAfter(childRequest.hardStart)) {
+            startLimit = childRequest.hardStart;
+          }
+        } else {
+          startLimit = false;
+        }
+      }
+      if (!earliestEnd || earliestEnd.isBefore(childRequest.softEnd)) {
+        earliestEnd = childRequest.softEnd;
+      }
+      if (endLimit !== false) {
+        if (childRequest.hardEnd) {
+          if (!endLimit || endLimit.isBefore(childRequest.hardEnd)) {
+            endLimit = childRequest.hardEnd;
+          }
+        } else {
+          endLimit = false;
+        }
+      }
+    }
+    this.needBroadcast = true;
+    this.softStart = moment(lastStart);
+    this.softEnd = moment(earliestEnd);
+    this.softEnd._isEndDate = true;
+    this.hardStart = startLimit || undefined;
+    this.hardEnd = endLimit || undefined;
+    ysy.log.debug(this.entity.name + " - resetByChildren (" + this.softStart.format("DD.MM.YYYY") + "," + this.softEnd.format("DD.MM.YYYY") + ")", "moveRequest");
+    this.duration = gantt._working_time_helper.get_work_units_between(this.softStart, this.softEnd, "day");
+    this.entity.correctPosition(this.allRequests);
+  },
+  setPosition: function (startDate, endDate, silent) {
+    if (!this.entity.isEditable()) return;
+    this.duration = gantt._working_time_helper.get_work_units_between(startDate, endDate, "day");
+    if (!this.hardStart || !this.hardStart.isAfter(startDate)) {
+      this.softStart = startDate;
+      this.needBroadcast = true;
+    } else {
+      endDate = gantt._working_time_helper.add_worktime(this.hardStart, this.duration, "day", true);
+    }
+    if (!this.hardEnd || !this.hardEnd.isBefore(endDate)) {
+      this.softEnd = endDate;
+      this.needBroadcast = true;
+    }
+    ysy.log.debug(this.entity.name + " - setPosition to (" + this.softStart.format("DD.MM.YYYY") + "," + this.softEnd.format("DD.MM.YYYY") + ") => " + this.needBroadcast, "moveRequest");
+    if (!silent) {
+      this.entity.correctPosition(this.allRequests);
+    }
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js
new file mode 100644
index 0000000000000000000000000000000000000000..71e558c6a384f5289fc37eb147a1c3588668038e
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_addons.js
@@ -0,0 +1,478 @@
+/* dhtml_addons.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.addGanttAddons = function () {
+  gantt.ascStop = function (task, start_date, end_date, ancestorLink) {
+    var diff;
+    if (task.soonest_start && start_date && start_date.isBefore(task.soonest_start)) {
+      diff = task.soonest_start.diff(start_date, "seconds");
+      start_date.add(diff, "seconds");
+      if (end_date) end_date.add(diff, "seconds");
+    }
+    if (task.latest_due && end_date && end_date.isAfter(task.latest_due)) {
+      diff = task.latest_due.diff(end_date, "seconds");
+      end_date.add(diff, "seconds");
+      if (start_date) start_date.add(diff, "seconds");
+    }
+    var sumMove = 0;
+    for (var i = 0; i < task.$target.length; i++) {
+      var lid = task.$target[i];
+      if (ancestorLink && parseInt(lid) === ancestorLink.id) continue; // skip link sourcing moved parent
+      var link = gantt._lpull[lid];
+      if (link.isSimple) continue;
+      var targetDate;
+      if (link.type === "precedes" || link.type == "start_to_start") {
+        if (!start_date) continue;
+        targetDate = start_date;
+      } else if (link.type === "finish_to_finish" || link.type == "start_to_finish") {
+        if (!end_date) continue;
+        targetDate = end_date;
+      } else continue;
+      //if (link.type !== "precedes") continue;
+      var source = gantt._pull[link.source];
+      var sourceDate = gantt.getLinkSourceDate(source, link.type);
+      if (ysy.settings.workDayDelays) {
+        var linkLast = gantt._working_time_helper.add_worktime(sourceDate, link.delay, "day", targetDate._isEndDate === true);
+        diff = -targetDate.diff(linkLast, "seconds");
+      } else {
+        diff = -targetDate.diff(sourceDate, "seconds") + (link.delay + gantt.getLinkCorrection(link.type)) * 24 * 60 * 60;
+      }
+      // ysy.log.debug("sourceDate="+sourceDate.toISOString()+" diff="+diff);
+      if (diff > 0) {
+        sumMove += diff;
+        //ysy.log.debug("ascStop linkLast=" + linkLast.format("YYYY-MM-DD") + " diff=" + diff, "task_drag");
+        if (start_date) {
+          start_date.add(diff, "seconds");
+        }
+        if (end_date) {
+          end_date.add(diff, "seconds");
+        }
+      }
+    }
+    return sumMove;
+  };
+  gantt.multiStop = gantt.ascStop;
+  gantt.moveDesc = function (task, diff, ancestorLink, resizing) {
+    if (typeof(diff) === "object") {
+      diff = task.start_date.diff(diff.old_start, "seconds");
+    }
+    // runs after task dates are already modified.
+    ysy.log.debug("moveDesc():  task=" + task.text + " diff=" + (diff / 86400) + (ancestorLink ? " ancestorLink=" + ancestorLink.id : ""), "task_drag");
+    var first = !ancestorLink;
+    if (first && task.type === "milestone") {
+      var oldTask = gantt._pull[task.id];
+      oldTask.end_date = task.end_date;
+    }
+    var bothDir = false;
+    // diff in seconds
+    if (diff === 0) return 0;
+    if (diff <= 0 && !bothDir) {
+      gantt.moveChildren(task, diff, first);
+      return 0;
+    }
+    var mileBack = 0;
+    if (diff > 0) {
+      if (ysy.settings.milestonePush) {
+        mileBack = gantt.milestoneDescStop(task, first ? 0 : diff);
+        if (mileBack !== 0) {
+          diff -= mileBack;
+          ysy.log.debug("task=" + task.text + " oldDiff=" + ((diff + mileBack) / 86400) + " newDiff=" + (diff / 86400) + " mileDiff=" + (mileBack / 86400), "task_drag_milestone");
+        }
+      } else {
+        gantt.milestoneMoveBy(task, first ? 0 : diff);
+      }
+    }
+    if (first && mileBack !== 0) {
+      if (resizing !== "right") {
+        task.start_date.add(Math.floor(-mileBack), 'seconds');
+      }
+      if (resizing !== "left") {
+        task.end_date.add(Math.floor(-mileBack), 'seconds');
+      }
+      gantt.refreshTask(task.id);
+      ysy.log.debug("FIRST " + task.text + " corrected by mileDiff=" + (mileBack / 3600 / 24) + " to " + task.start_date.format("YYYY-MM-DD"), "task_drag_milestone");
+    }
+    var movedByChildren = gantt.moveChildren(task, diff, first);
+
+    if (!first && (diff > 0 || bothDir)) {
+      if(!movedByChildren){
+        ysy.log.debug("Task " + task.text + " pushed by " + (diff / 86400) + " days", "task_drag");
+        task.start_date.add(Math.floor(diff), 'seconds');
+        task.end_date.add(Math.floor(diff), 'seconds');
+      }
+      // var oldStartDate = moment(task.start_date);
+      task._changed = gantt.config.drag_mode.move;
+      gantt.refreshTask(task.id);
+    }
+    if (!first && diff < 0) {
+      var ascDiff = gantt.ascStop(task, task.start_date, task.end_date, ancestorLink);
+      if (ascDiff !== 0) {
+        // ysy.log.debug("diff=" + diff + " ascStopDiff=" + ascDiff);
+        diff += ascDiff;
+      }
+    }
+    var start_date = +task.start_date / 1000;
+    var end_date = +task.end_date / 1000;
+    for (var i = 0; i < task.$source.length; i++) {
+      var lid = task.$source[i];
+      var link = gantt._lpull[lid];
+      if (link.isSimple) continue;
+      var desc = gantt._pull[link.target];
+      if (diff < 0 && bothDir) {
+        // ysy.log.debug("desc=" + desc.text +" diff="+diff);
+        gantt.moveDesc(desc, diff, link);
+        // ysy.log.debug("task=" + task.text + " diff=" + diff + " reduced by " + backDiff);
+      } else {
+        var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date);
+        if (descDiff <= 0) continue;
+        // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff);
+        gantt.moveDesc(desc, descDiff, link);
+      }
+    }
+    //ysy.log.debug("Task " + task.text + " pushed back by " + (diff / 86400) + " days", "task_drag");
+    return 0;
+  };
+  gantt.moveChildren = function (task, shift, first) {
+    if (!(gantt._get_safe_type(task.type) === "task" && ysy.settings.parentIssueDates)) {
+      if (task.$open && gantt.isTaskVisible(task.id)) {
+        if (task.type === "milestone") gantt.moveMilestoneChildren(task);
+        return;
+      }
+    }
+    var branch = gantt._branches[task.id];
+    if (!branch || branch.length === 0) return;
+    ysy.log.debug("Shift children of \"" + task.text + "\" by " + shift + " seconds", "parent");
+    ysy.log.debug("moveChildren():  of \"" + task.text + "\" by " + shift + " seconds", "task_drag");
+    var parentStartDate = +task.start_date;
+    if (first) {
+      parentStartDate -= shift * 1000;
+    }
+    var shiftedParentDate = moment(parentStartDate + shift * 1000);
+    for (var i = 0; i < branch.length; i++) {
+      var childId = branch[i];
+      //if(gantt.isTaskVisible(childId)){continue;}
+      var child = gantt.getTask(childId);
+      if (!child._parent_offset) {
+        child._parent_offset = gantt._working_time_helper.get_work_units_between(parentStartDate, child.start_date, "day");
+      }
+      var childStartDiff = gantt._working_time_helper.add_worktime(
+              shiftedParentDate, child._parent_offset, "day", false
+          ) - child.start_date;
+      child.start_date.add(childStartDiff, 'milliseconds');
+      var childEndDiff = gantt._working_time_helper.add_worktime(child.start_date, child.duration, 'day', true) - child.end_date;
+      child.end_date.add(childEndDiff, 'milliseconds');
+      gantt.ascStop(child, child.start_date, child.end_date);
+      child._changed = gantt.config.drag_mode.move;
+      gantt.refreshTask(child.id);
+      gantt.moveDesc(child, childStartDiff);
+    }
+  };
+  gantt.moveMilestoneChildren = function (milestone) {
+    var branch = gantt._branches[milestone.id];
+    if (!branch || branch.length === 0) return 0;
+    // ysy.log.debug("Shift children of \"" + milestone.text + "\" by " + shift + " seconds", "parent");
+    // ysy.log.debug("moveChildren():  of \"" + milestone.text + "\" by " + shift + " seconds", "task_drag");
+    for (var i = 0; i < branch.length; i++) {
+      var childId = branch[i];
+      var child = gantt.getTask(childId);
+      if (child.end_date.isAfter(milestone.end_date)) {
+        var shift = milestone.end_date.diff(child.end_date, "seconds");
+        child.start_date.add(shift, 'seconds');
+        child.end_date.add(shift, 'seconds');
+        child._changed = gantt.config.drag_mode.move;
+        gantt.refreshTask(child.id);
+        gantt.moveDesc(child, shift);
+      }
+    }
+    return 0;
+  };
+  gantt.milestoneStop = function (task, diff) {
+    var issue = task.widget && task.widget.model;
+    if (!issue) return 0;
+    var milestone = ysy.data.milestones.getByID(issue.fixed_version_id);
+    if (!milestone) return 0;
+    var ganttMilestone = gantt._pull[milestone.getID()];
+    if (ganttMilestone) {
+      var milDiff = ganttMilestone.end_date.diff(task.end_date, "seconds");
+    } else {
+      milDiff = milestone.start_date.diff(task.end_date, "seconds");
+    }
+    milDiff -= diff;
+    if (milDiff < 0) {
+      ysy.log.debug("milestoneStop() for " + task.text + " milDiff=" + (milDiff / 86400) +
+          " diff=" + (diff / 86400) + " at " + milestone.start_date.format("YYYY-MM-DD"), "task_drag_milestone");
+      return -milDiff;
+    }
+    return 0;
+  };
+  gantt.milestoneDescStop = function (task, diff) {
+    ysy.log.debug("milestoneDescStop(): task " + task.text + " moving by " + (diff / 86400), "task_drag_milestone");
+    var backDiff = gantt.milestoneStop(task, diff);
+    var start_date = +task.start_date / 1000 + diff;
+    var end_date = +task.end_date / 1000 + diff;
+    for (var i = 0; i < task.$source.length; i++) {
+      var lid = task.$source[i];
+      var link = gantt._lpull[lid];
+      if (link.isSimple) continue;
+      var desc = gantt._pull[link.target];
+      var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date);
+      if (descDiff <= 0) continue;
+      backDiff = Math.max(backDiff, gantt.milestoneDescStop(desc, descDiff));
+    }
+    return backDiff;
+  };
+  gantt.milestoneMoveBy = function (task, diff) {
+    ysy.log.debug("milestoneMoveBy(): task " + task.text + " moving by " + (diff / 86400), "task_drag");
+    var issue = task.widget && task.widget.model;
+    if (!issue) return;
+    var milestone = ysy.data.milestones.getByID(issue.fixed_version_id);
+    if (!milestone) return;
+    var ganttMilestone = gantt._pull[milestone.getID()];
+    var taskDate;
+    if (diff === 0) {
+      taskDate = task.end_date;
+    } else {
+      taskDate = moment(task.end_date).add(diff, "seconds");
+    }
+    if (ganttMilestone) {
+      if (ganttMilestone.end_date.isBefore(taskDate)) {
+        var moveDiff = taskDate.diff(ganttMilestone.end_date);
+        ganttMilestone.end_date.add(moveDiff, "milliseconds");
+        ganttMilestone.start_date.add(moveDiff, "milliseconds");
+        ganttMilestone._changed = gantt.config.drag_mode.move;
+        gantt.refreshTask(ganttMilestone.id);
+      }
+    }
+
+  };
+  gantt.getLinkSourceDate = function (source, type) {
+    if (type === "precedes") return source.end_date;
+    if (type === "finish_to_finish") return source.end_date;
+    if (type === "start_to_start") return source.start_date;
+    if (type === "start_to_finish") return source.start_date;
+    return null;
+  };
+  gantt.getLinkTargetDate = function (target, type) {
+    if (type === "precedes") return target.start_date;
+    if (type === "finish_to_finish") return target.end_date;
+    if (type === "start_to_start") return target.start_date;
+    if (type === "start_to_finish") return target.end_date;
+    return null;
+  };
+  gantt.getLinkCorrection = function (type) {
+    if (type === "precedes") return 1;
+    if (type === "start_to_finish") return -1;
+    return 0;
+  };
+  gantt.getFreeDelay = function (link, desc, ascStartDate, ascEndDate) {
+    if (!desc) desc = gantt._pull[link.target];
+    var sourceDate;
+    var daysToSeconds = 60 * 60 * 24;
+    if (!ascStartDate) {
+      var asc = gantt._pull[link.source];
+      ascStartDate = asc.start_date / 1000;
+      ascEndDate = asc.end_date / 1000;
+    }
+    if (link.type === "precedes" || link.type === "finish_to_finish") {
+      if (!ascEndDate) return 0;
+      sourceDate = ascEndDate;
+    } else if (link.type === "start_to_finish" || link.type === "start_to_start") {
+      if (!ascStartDate) return 0;
+      sourceDate = ascStartDate;
+    } else return 0;
+    var targetDate = gantt.getLinkTargetDate(desc, link.type);
+    var correction = gantt.getLinkCorrection(link.type);
+    var delay = (targetDate / 1000 - sourceDate) / daysToSeconds;
+    return (link.delay + correction - delay) * daysToSeconds;
+
+  };
+  gantt.updateAllTask = function (seed_task) {
+    ysy.history.openBrack();
+    var toUpdate = {};
+    // sort + reverse in order to process milestones before tasks
+    var pullIds = Object.getOwnPropertyNames(gantt._pull).sort().reverse();
+    var allRequests = {};
+    if (ysy.settings.fixedRelations) {
+      for (var i = 0; i < pullIds.length; i++) {
+        var task = gantt._pull[pullIds[i]];
+        if (task._changed) {
+          //gantt._tasks_dnd._fix_dnd_scale_time(task,{mode:task._changed});
+          gantt._tasks_dnd._fix_working_times(task, {mode: task._changed});
+          gantt._update_parents(task.id, false);
+          delete task._parent_offset;
+          var parentId = gantt.getParent(task.id);
+          while (parentId) {
+            var parent = gantt._pull[parentId];
+            if (!parent) break;
+            toUpdate[parentId] = parent;
+            parentId = gantt.getParent(parentId);
+          }
+
+          toUpdate[task.id] = task;
+          // var issue = task.widget.model;
+          // if (issue.getMoveRequest) {
+          //   var request = issue.getMoveRequest(allRequests);
+          //   request.setPosition(task.start_date, task.end_date, true);
+          // }
+          task._changed = false;
+        }
+      }
+    } else {
+      for (i = 0; i < pullIds.length; i++) {
+        task = gantt._pull[pullIds[i]];
+        if (task._changed) {
+          //gantt._tasks_dnd._fix_dnd_scale_time(task,{mode:task._changed});
+          gantt._tasks_dnd._fix_working_times(task, {mode: task._changed});
+          gantt._update_parents(task.id, false);
+          delete task._parent_offset;
+          toUpdate[task.id] = task;
+          var issue = task.widget.model;
+          if (issue.getMoveRequest) {
+            var request = issue.getMoveRequest(allRequests);
+            request.setPosition(task.start_date, task.end_date, true);
+          }
+          task._changed = false;
+          ysy.log.debug("UpdateAllTask update " + task.text, "task_drag");
+        }
+      }
+    }
+    for (var id in allRequests) {
+      if (!allRequests.hasOwnProperty(id)) continue;
+      request = allRequests[id];
+      request.entity.correctPosition(allRequests);
+    }
+    for (id in allRequests) {
+      if (!allRequests.hasOwnProperty(id)) continue;
+      request = allRequests[id];
+      task = toUpdate[id];
+      if (task) {
+        $.extend(task, {start_date: request.softStart, end_date: request.softEnd});
+      } else {
+        if (!request.entity.set({start_date: request.softStart, end_date: request.softEnd})) {
+          request.entity._fireChanges({_name: "UpdateAll"}, "updateAll");
+        }
+      }
+    }
+    for (id in toUpdate) {
+      if (!toUpdate.hasOwnProperty(id)) continue;
+      task = toUpdate[id];
+      ysy.log.debug("UpdateAllTask update " + task.text, "task_drag");
+      task.widget.update(task);
+      // for (var j = 0; j < task.$target.length; j++) {
+      //   var link = gantt._lpull[task.$target[j]];
+      //   if (!link) continue;
+      //   if (!link.unlocked) continue;
+      //   var source = gantt._pull[link.source];
+      //   if (!source) continue;
+      //   var targetDate = gantt.getLinkTargetDate(task, link.type);
+      //   var sourceDate = gantt.getLinkSourceDate(source, link.type);
+      //   var correction = gantt.getLinkCorrection(link.type);
+      //   var delay = targetDate.diff(sourceDate, "days") - correction;
+      //   // ysy.log.debug("Link from "+source.id+" to "+task.id+" delay="+delay );
+      //   var relation = link.widget.model;
+      //   if (!relation) continue;
+      //   relation.set("delay", delay);
+      // }
+    }
+    ysy.history.closeBrack();
+  };
+  gantt.applyMoveRequests = function (allRequests) {
+    for (var id in allRequests) {
+      if (!allRequests.hasOwnProperty(id)) continue;
+      var request = allRequests[id];
+      if (!request.entity.set({start_date: request.softStart, end_date: request.softEnd})) {
+        request.entity._fireChanges({_name: "applyMoveRequests"}, "applyMoveRequests");
+      }
+    }
+  };
+  gantt.checkLoopedLink = function (target, bannedId) {
+    var relations = ysy.data.relations.getArray();
+    for (var i = 0; i < relations.length; i++) {
+      if (relations[i].source_id !== target.id) continue;
+      var relation = relations[i];
+      if (relation.target_id == bannedId) return false;
+      var nextTarget = relation.getTarget();
+      if (!gantt.checkLoopedLink(nextTarget, bannedId)) return false;
+    }
+    return true;
+  };
+  //###############################################################################
+  gantt.render_delay_element = function (link, pos) {
+    if (link.widget && link.widget.model.isSimple) return null;
+    //if(link.delay===0){return null;}
+    var sourceDate = gantt.getLinkSourceDate(gantt._pull[link.source], link.type);
+    var targetDate = gantt.getLinkTargetDate(gantt._pull[link.target], link.type);
+    if (ysy.settings.workDayDelays) {
+      var actualDelay = gantt._working_time_helper.get_work_units_between(sourceDate, targetDate, "day");
+      actualDelay = Math.round(actualDelay);
+    } else {
+      actualDelay = targetDate.diff(sourceDate, "hours") / 24;
+      actualDelay = Math.round(actualDelay) - gantt.getLinkCorrection(link.type);
+    }
+    var text = (link.delay ? link.delay : '') + (actualDelay !== link.delay ? ' (' + actualDelay + ')' : '');
+    return $('<div>')
+        .css({position: "absolute", left: pos.x, top: pos.y})
+        // .html(link.delay+" ("+actualDelay + ")")[0];
+        .html(text)[0];
+  };
+  //##############################################################################
+  /*
+   * Přepsané funkce z dhtmlxganttu, kvůli efektivnějšímu napojení či kvůli odstranění bugů
+   */
+  //##########################################################################################
+  gantt.allowedParent = function (child, parent) {
+    if (child === parent) return false;
+    var type = child.type;
+    if (!type) {
+      type = "task";
+    }
+    var allowed = gantt.config["allowedParent_" + type];
+    if (!allowed) return false;
+    if (parent.real_id > 1000000000000) return false;
+    var parentType = parent.type || "task";
+    return allowed.indexOf(parentType) >= 0;
+  };
+  gantt.getShowDate = function () {
+    var pos = gantt._restore_scroll_state();
+    if (!pos) return null;
+    return this.dateFromPos(pos.x + this.config.task_scroll_offset);
+  };
+  gantt.silentMoveTask = function (task, parentId) {
+    ysy.log.debug("silentMoveTask", "move_task");
+    var id = task.id;
+    var sourceId = this.getParent(id);
+    if (sourceId == parentId) return;
+
+    this._replace_branch_child(sourceId, id);
+    var tbranch = this.getChildren(parentId);
+    tbranch.push(id);
+
+    this.setParent(task, parentId);
+    this._branches[parentId] = tbranch;
+
+    var childTree = this._getTaskTree(id);
+    for (var i = 0; i < childTree.length; i++) {
+      var item = this._pull[childTree[i]];
+      if (item)
+        item.$level = this.calculateTaskLevel(item);
+    }
+    task.$level = gantt.calculateTaskLevel(task);
+    this.refreshData();
+
+  };
+  gantt.getCachedScroll = function () {
+    if (!gantt._cached_scroll_pos) return {x: 0, y: 0};
+    return {x: gantt._cached_scroll_pos.x || 0, y: gantt._cached_scroll_pos.y || 0};
+  };
+  gantt.reconstructTree = function () {
+    var tasks = gantt._pull;
+    var ids = Object.getOwnPropertyNames(tasks);
+    for (var i = 0; i < ids.length; i++) {
+      var task = tasks[ids[i]];
+      if (task.realParent === undefined) continue;
+      gantt.silentMoveTask(task, task.realParent);
+      delete task.realParent;
+    }
+  }
+};
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js
new file mode 100644
index 0000000000000000000000000000000000000000..35dde3d617e70e10ba566d6e00b34f81b07af647
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_modif.js
@@ -0,0 +1,694 @@
+/* dhtml_modif.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.initGantt = function () {
+  var toMomentFormat = function (rubyFormat) {
+    switch (rubyFormat) {
+      case '%Y-%m-%d':
+        return 'YYYY-MM-DD';
+      case '%Y/%m/%d':
+        return 'YYYY/MM/DD';
+      case '%d/%m/%Y':
+        return 'DD/MM/YYYY';
+      case '%d.%m.%Y':
+        return 'DD.MM.YYYY';
+      case '%d-%m-%Y':
+        return 'DD-MM-YYYY';
+      case '%m/%d/%Y':
+        return 'MM/DD/YYYY';
+      case '%d %b %Y':
+        return 'DD MMM YYYY';
+      case '%d %B %Y':
+        return 'DD MMMM YYYY';
+      case '%b %d, %Y':
+        return 'MMM DD, YYYY';
+      case '%B %d, %Y':
+        return 'MMMM DD, YYYY';
+      default:
+        return 'D. M. YYYY';
+    }
+  };
+
+  function getERUISassValue(varName, defaultValue) {
+    if (window.ERUI && ERUI.sassData !== undefined && ERUI.sassData[varName] !== undefined) {
+      return parseInt(ERUI.sassData[varName]);
+    }
+    return defaultValue;
+  }
+
+  $.extend(gantt.config, {
+    //xml_date: "%Y-%m-%d",
+    //scale_unit: "week",
+    //date_scale: "Week #%W",
+    //autosize:"y",
+    details_on_dblclick: false,
+    readonly_project: true,
+    //autofit:true,
+    drag_empty: true,
+    work_time: true,
+    //min_duration:24*60*60*1000, // 1*24*60*60*1000s = 1 day
+    correct_work_time: true,
+    //date_grid: "%j %M %Y",
+    date_format: toMomentFormat(ysy.settings.dateFormat),
+    date_grid: "%j.%n.%Y",
+    links: {
+      finish_to_start: "precedes",
+      start_to_start: "start_to_start",
+      start_to_finish: "start_to_finish",
+      finish_to_finish: "finish_to_finish"
+    },
+    step: 1,
+    duration_unit: "day",
+    fit_tasks: true,
+    row_height: getERUISassValue('gantt-row-height', 25),
+    task_height: getERUISassValue('gantt-task-height', 20),
+    min_column_width: 36,
+    autosize: "y",
+    link_line_width: 0,
+    scale_height: 60,
+    start_on_monday: true,
+    order_branch: true,
+    rearrange_branch: true,
+    grid_resize: true,
+    grid_width: ysy.data.limits.columnsWidth.grid_width,
+    task_scroll_offset: 250,
+    controls_task: {progress: true, resize: true, links: true},
+    controls_milestone: {},
+    start_date: ysy.data.limits.start_date,
+    end_date: ysy.data.limits.end_date,
+    controls_project: {show_progress: true, resize: false},
+    allowedParent_task: ["project", "milestone", "empty"],
+    allowedParent_task_global: ["project", "milestone"],
+    allowedParent_milestone: ["project"],
+    allowedParent_project: ["empty"]
+  });
+  gantt.config.columns = ysy.view.leftGrid.constructColumns(ysy.data.columns);
+  ysy.proManager.fireEvent("ganttConfig", gantt.config);
+  gantt._pull["empty"] = {
+    type: "empty",
+    id: "empty",
+    $target: [],
+    $source: [],
+    columns: {},
+    text: "",
+    start_date: moment()
+  };
+};
+ysy.view.applyGanttPatch = function () {
+  ysy.view.leftGrid.patch();
+  gantt.locale.date = ysy.settings.labels.date;
+  $.extend(gantt.templates, {
+    task_cell_class: function (item, date) {
+      if (gantt.config.scale_unit === "day") {
+        //var css="";
+        if (moment(date).date() === 1) {
+          return true;
+          //  css+=" first-date";
+        }
+        //return css;
+      }
+      return false;
+    },
+    scale_cell_class: function (date) {
+      if (gantt.config.scale_unit === "day") {
+        var css = "";
+        if (!gantt._working_time_helper.is_working_day(date)) {
+          css += " weekend";
+        }
+        //if(date.getDate()===1){
+        if (moment(date).date() === 1) {
+          css += " first-date";
+        }
+        return css;
+      }
+    },
+    task_text: function (start, end, task) {
+      return "";
+    },
+    task_class: function (start, end, task) {
+      var css = "";
+      if (task.css) {
+        css = task.css;
+      }
+      css += " " + (task.type || "task") + "-type";
+      if (task.widget && task.widget.model) {
+        var problems = task.widget.model.getProblems();
+        if (problems) {
+          css += " wrong";
+        }
+      }
+      return css;
+    },
+    grid_row_class: function (start, end, task) {
+      var ret = "";
+      if (task.css) {
+        ret = task.css;
+      }
+      if (task.widget && task.widget.model) {
+        var problems = task.widget.model.getProblems();
+        if (problems) {
+          ret += " wrong";
+        }
+      }
+      ret += " " + (task.type || "task") + "-type";
+      return ret;//+'" data-url="/issues/'+task.id+' data-none="';
+      //return task.css+" "+task.type+"-type";
+    },
+    /*grid_file: function (item) {
+     return "";
+     //return "<div class='gantt_tree_icon gantt_file'></div>";
+     },*/
+    link_class: function (link) {
+      var css = "type-" + link.type;
+      if (link.widget) {
+        var relation = link.widget.model;
+        if (relation) {
+          if (!relation.checkDelay()) {
+            css += " wrong";
+          }
+          if (relation.isSimple) {
+            css += " gantt-relation-simple";
+          }
+          if (link.unlocked) {
+            css += " gantt-relation-unlocked";
+          }
+        }
+      }
+      return css;
+    },
+    drag_link: function (from, from_start, to, to_start) {
+      var labels = ysy.view.getLabel("links");
+      if (!gantt._get_link_type(from_start, to_start)) {
+        var reason = "unsupported_link_type";
+      } else if (from === to) {
+        reason = "loop_link";
+      } else if (to && to.length > 12) {
+        reason = "link_target_new";
+      } else if (to && gantt.getTask(to).readonly) {
+        reason = "link_target_readonly";
+      } else {
+        reason = "other";
+      }
+      var obj = {
+        errorReason: ysy.view.getLabel("errors2")[reason],
+        from: gantt.getTask(from).text
+      };
+      if (to) {
+        obj.to = gantt.getTask(to).text;
+        var ganttLinkType = (from_start ? "start" : "finish") + "_to_" + (to_start ? "start" : "finish");
+        obj.type = labels[gantt.config.links[ganttLinkType]];
+      }
+      return Mustache.render(ysy.view.templates.linkDragModal, obj);
+    },
+    scale_row_class: function (scale) {
+      return scale.className || "";
+    }
+  });
+
+  gantt.attachEvent("onRowDragStart", function (id /*, elem*/) {
+    //$(".gantt_grid_data").addClass("dragging");
+    var task = gantt.getTask(id);
+    $(".gantt_row").each(function () {
+      var target = gantt._pull[$(this).attr("task_id")];
+      if (!target) return;
+      if (gantt.allowedParent(task, target)) {
+        $(this).addClass("gantt_drag_to_allowed");
+      }
+    });
+    return true;
+  });
+  gantt.attachEvent("onRowDragEnd", function (id, elem) {
+    //$(".gantt_grid_data").removeClass("dragging");
+    $(".gantt_drag_to_allowed").removeClass("gantt_drag_to_allowed");
+  });
+
+  // Funkce pro vytvoření a posunování Today markeru
+  function initTodayMarker() {
+    var date_to_str = gantt.date.date_to_str(gantt.config.task_date);
+    var id = gantt.addMarker({start_date: new Date(), css: "today", title: date_to_str(new Date())});
+    setInterval(function () {
+      var today = gantt.getMarker(id);
+      today.start_date = new Date();
+      today.title = date_to_str(today.start_date);
+      gantt.updateMarker(id);
+    }, 1000 * 60 * 60);
+  }
+
+  initTodayMarker();
+
+  //gantt.initProjectMarker=function initProjectMarker(start,end) {
+  //    if(start&&start.isValid()){
+  //        var startMarker = gantt.addMarker({start_date: start.toDate(), css: "start", title: "Project start"});
+  //    }
+  //    if(end&&end.isValid()){
+  //        var endMarker = gantt.addMarker({start_date: end.toDate(), css: "end", title: "Project due time"});
+  //    }
+  //};
+  //initProjectMarker();
+
+//##################################################################################
+  if (!ysy.settings.fixedRelations) {
+    gantt.attachEvent("onLinkClick", function (id, mouseEvent) {
+      if (!gantt.config.drag_links) return;
+      ysy.log.debug("LinkClick on " + id, "link_config");
+      var link = gantt.getLink(id);
+      if (gantt._is_readonly(link)) return;
+      var source = gantt._pull[link.source];
+      if (!source) return;
+      var target = gantt._pull[link.target];
+      if (!target) return;
+      if (source.readonly && target.readonly) return;
+
+      var linkConfigWidget = new ysy.view.LinkPopup().init(link.widget.model, link);
+      linkConfigWidget.$target = $("#ajax-modal");//$dialog;
+      linkConfigWidget.repaint();
+      showModal("ajax-modal", "auto");
+      return false;
+    });
+  }
+  gantt.attachEvent("onAfterLinkDelete", function (id, elem) {
+    if (elem.deleted) return;
+    if (!elem.widget.model._deleted) {
+      elem.widget.model.remove();
+    }
+  });
+  gantt.attachEvent("onBeforeLinkAdd", function (id, link) {
+    if (link.widget) return true;
+    var relations = ysy.data.relations;
+    var data;
+    data = {
+      id: id,
+      source_id: parseInt(link.source),
+      target_id: parseInt(link.target),
+      delay: 0,
+      unlocked: true,
+      permissions: {
+        editable: true
+      },
+      type: link.type
+    };
+    var relArray = relations.getArray();
+    for (var i = 0; i < relArray.length; i++) {
+      var relation = relArray[i];
+      if (relation.source_id === data.source_id && relation.target_id === data.target_id) {
+        dhtmlx.message(ysy.view.getLabel("errors2", "duplicate_link"), "error");
+        return false;
+      }
+    }
+    var rel = new ysy.data.Relation();
+    rel.init(data, relations);
+    //rel.delay=rel.getActDelay();  // created link have maximal delay
+    if (!gantt.checkLoopedLink(rel.getTarget(), rel.source_id)) {
+      dhtmlx.message(ysy.view.getLabel("errors2", "loop_link"), "error");
+      return false;
+    }
+
+    ysy.history.openBrack();
+    relations.push(rel);
+    var allRequests = {};
+    rel.sendMoveRequest(allRequests);
+    gantt.applyMoveRequests(allRequests);
+    ysy.history.closeBrack();
+    return false;
+  });
+
+  ysy.view.taskTooltip.taskTooltipInit();
+
+  dhtmlx.message = function (msg, type, delay) {
+    if (!type) {
+      type = msg.type;
+      msg = msg.text;
+      delay = msg.delay;
+    }
+    window.showFlashMessage(type, msg, delay && delay > 0 ? delay : undefined);
+    //if (type !== "notice") {
+    //  var flashElement = $("#content").children(".flash")[0];
+    //  var adjust = -10;
+    //  if (ysy.settings.easyRedmine) {
+    //    $(document).scrollTop(flashElement.offsetTop + adjust + "px");
+    //    //window.scrollTo(".flash",adjust);
+    //  } else {
+    //    window.scrollTo(0, flashElement.offsetTop + adjust);
+    //  }
+    //}
+  };
+
+  if (!window.showFlashMessage) {
+    window.showFlashMessage = function (type, message) {
+      var $content = $("#content");
+      $content.find(".flash").remove();
+      var template = '<div class="flash {{type}}"><a href="javascript:void(0)" class="close-icon close_button" style="float:right"></a><span>{{{message}}}</span></div>';
+      var closeFunction = function (event) {
+        $(this)
+            .closest('.flash')
+            .fadeOut(500, function () {
+              $(this).remove();
+            })
+      };
+      var rendered = Mustache.render(template, {message: message, type: type});
+      $content.prepend($(rendered));
+      $content.find(".close_button").click(closeFunction);
+    }
+  }
+  if (!dhtmlx.dragScroll) {
+    dhtmlx.dragScroll = function () {
+      var $background = $(".gantt_task_bg");
+      if (!$background.hasClass("inited")) {
+        $background.addClass("inited");
+        var dnd = new dhtmlxDnD($background[0], {});
+        var lastScroll = null;
+        dnd.attachEvent("onDragStart", function () {
+          lastScroll = gantt.getCachedScroll();
+        });
+        dnd.attachEvent("onDragMove", function () {
+          var diff = dnd.getDiff();
+          gantt.scrollTo(lastScroll.x - diff.x, undefined);
+        });
+      }
+    };
+  }
+  gantt.attachEvent("onTaskOpened", function (id) {
+    ysy.data.limits.openings[id] = true;
+    var task = gantt._pull[id];
+    if (!task || !task.widget) return true;
+    var entity = task.widget.model;
+    if (entity.needLoad) {
+      entity.needLoad = false;
+      ysy.data.loader.loadSubEntity(task.type, entity.id);
+    }
+  });
+  gantt.attachEvent("onTaskClosed", function (id) {
+    ysy.data.limits.openings[id] = false;
+  });
+  gantt.attachEvent("onTaskSelected", function (id) {
+    var data = gantt._get_tasks_data();
+    gantt._backgroundRenderer.render_items(data);
+  });
+  gantt.attachEvent("onTaskUnselected", function (id, ignore) {
+    if (ignore) return;
+    var data = gantt._get_tasks_data();
+    gantt._backgroundRenderer.render_items(data);
+  });
+  gantt.attachEvent("onLinkValidation", function (link) {
+    if (link.source.length > 12) return false;
+    if (link.target.length > 12) return false;
+    var source = gantt.getTask(link.source);
+    var target = gantt.getTask(link.target);
+    if (source.readonly && target.readonly) return false;
+    var parentId = source.id;
+    while (parentId !== 0) {
+      var parent = gantt.getTask(parentId);
+      if (parent === target) return false;
+      parentId = parent.parent;
+    }
+    parentId = target.id;
+    while (parentId !== 0) {
+      parent = gantt.getTask(parentId);
+      if (parent === source) return false;
+      parentId = parent.parent;
+    }
+    return true;
+  });
+  gantt.attachEvent("onAfterTaskMove", function (sid, parent, tindex) {
+    this.open(parent);
+    return true;
+  });
+  gantt._filter_task = function (id, task) {
+    // commented out because pushing task out of bounds removed the task and its project
+    //var min = null, max = null;
+    //if(this.config.start_date && this.config.end_date){
+    //  min = this.config.start_date.valueOf();
+    //  max = this.config.end_date.valueOf();
+    //
+    //  if(+task.start_date > max || +task.end_date < +min)
+    //    return false;
+    //}
+    return ysy.proManager.eventFilterTask(id, task);
+  };
+  //var oldPosFromDate = gantt.posFromDate;
+  //gantt.posFromDate = function(date){
+  //  ysy.log.debug("old: "+oldPosFromDate.call(gantt,date)+" new: "+gantt.posFromDate2(date));
+  //  return gantt.posFromDate2(date);
+  //};
+  gantt.posFromDate = function (date) {
+    var scale = this._tasks;
+    if (typeof date === "string") {
+      date = moment(date);
+    }
+
+    var tdate = date.valueOf();
+    var units = {
+      day: 86400000, // 24 * 60 * 60 * 1000
+      week: 604800000, // 7 * 24 * 60 * 60 * 1000
+      //month: 2592000000  // 30 * 24 * 60 * 60 * 1000
+      month: 2629800000  // 30.4375 * 24 * 60 * 60 * 1000
+    };
+    if (date._isEndDate) {
+      tdate += units.day;
+    }
+
+    if (tdate <= this._min_date)
+      return 0;
+
+    if (tdate >= this._max_date)
+      return scale.full_width;
+
+    var unitRatio = (tdate - scale.trace_x[0]) / units[scale.unit];
+    var index = Math.floor(unitRatio);
+    index = Math.min(scale.count - 1, Math.max(0, index));
+    if (scale.count === index + 1) {
+      return scale.left[index]
+          + scale.width[index]
+          * (tdate - scale.trace_x[index])
+          / (gantt._max_date - scale.trace_x[index]);
+    }
+    if (tdate > scale.trace_x[index + 1]) {
+      index++;
+      if (scale.count === index + 1) {
+        return scale.left[index]
+            + scale.width[index]
+            * (tdate - scale.trace_x[index])
+            / (gantt._max_date - scale.trace_x[index]);
+      }
+    } else {
+      while (index !== 0 && tdate < scale.trace_x[index]) index--;
+    }
+    var restRatio = (tdate - scale.trace_x[index]) / (scale.trace_x[index + 1] - scale.trace_x[index]);
+    return scale.left[index] + scale.width[index] * restRatio;
+  };
+  gantt.dateFromPos2 = function (x) {
+    // TODO tasks ends
+    var scale = this._tasks;
+    if (x < 0 || x > scale.full_width || !scale.full_width) {
+      scale.needRescale = true;
+      ysy.log.debug("needRescale", "outer");
+    }
+    if (!scale.trace_x.length) {
+      return 0;
+    }
+    // var units = {
+    //   day: 86400000, // 24 * 60 * 60 * 1000
+    //   week: 604800000, // 7 * 24 * 60 * 60 * 1000
+    //   //month: 2592000000  // 30 * 24 * 60 * 60 * 1000
+    //   month: 2629800000  // 30.4375 * 24 * 60 * 60 * 1000
+    // };
+    var unitRatio = x / (scale.full_width / scale.count);
+    var index = Math.floor(unitRatio);
+    index = Math.min(scale.count - 1, Math.max(0, index));
+    if (index === scale.count - 1) {
+      return gantt.date.Date(
+          scale.trace_x[index].valueOf()
+          + (gantt._max_date - scale.trace_x[index])
+          * (x - scale.left[index])
+          / scale.width[index]);
+    }
+    if (x > scale.left[index + 1]) {
+      index++;
+      if (index === scale.count - 1) {
+        return gantt.date.Date(
+            scale.trace_x[index].valueOf()
+            + (gantt._max_date - scale.trace_x[index])
+            * (x - scale.left[index])
+            / scale.width[index]);
+      }
+    }
+    return gantt.date.Date(
+        scale.trace_x[index].valueOf()
+        + (scale.trace_x[index + 1] - scale.trace_x[index])
+        * (x - scale.left[index])
+        / scale.width[index]);
+  };
+  ysy.view.bars.registerRenderer("task", function (task, next) {
+    var $div = $(next().call(this, task, next));
+    if (this.hasChild(task.id) || $div.hasClass("parent")) {
+      $div.addClass("gantt_parent_task-subtype");
+      var $ticks = $("<div class='gantt_task_ticks'></div>");
+      var width = $div.width();
+      if (width < 20) {
+        $ticks.css({borderLeftWidth: width / 2, borderRightWidth: width / 2});
+      }
+      $div.append($ticks);
+
+
+    }
+    if (task.latest_due) {
+      var stop_x = this.posFromDate(task.widget.model.latest_due);
+      var issue_x = this.posFromDate(task.start_date);
+      var pos_x = stop_x - issue_x;
+      var $preStop = $(ysy.view.templates.endBlocker.replace("{{pos_x}}", pos_x.toString()));
+      $div.prepend($preStop);
+    }
+    if (task.soonest_start) {
+      var stop_x = this.posFromDate(task.widget.model.soonest_start);
+      var issue_x = this.posFromDate(task.start_date);
+      var pos_x = stop_x - issue_x;
+      var $preStop = $(ysy.view.templates.preBlocker.replace("{{pos_x}}", pos_x.toString()));
+      $div.prepend($preStop);
+    }
+    return $div[0];
+  });
+  // var progressRenderer = gantt._render_task_progress;
+  // gantt._render_task_progress = function (task, element, maxWidth) {
+  //   var width = progressRenderer.call(this, task, element, maxWidth);
+  //   if (task.type !== "project") return width;
+  //   var pos = gantt.posFromDate(task.start_date);
+  //   var todayPos = gantt.posFromDate(moment());
+  //   if (task.progress < 1 && pos + width < todayPos) {
+  //     element.className += " gantt-project-overdue";
+  //   }
+  //   return width;
+  // };
+  gantt._default_task_date = function (item, parent_id) {
+    return moment();
+  };
+  gantt.attachEvent("onScrollTo", function (x, y) {
+    var renderer = gantt._backgroundRenderer;
+    var needRender = renderer.isScrolledOut(x, y);
+    if (needRender) {
+      //ysy.log.debug("render_one_canvas on [" + x + "," + y + "]", "scrollRender");
+      renderer.render_items();
+    }
+  });
+  var ganttOffsetTop;
+  $(document).add("#content").on("scroll", function (e) {
+    if (!ganttOffsetTop) {
+      if (!gantt.$task) return;
+      ganttOffsetTop = $(gantt.$task).offset().top;
+    }
+    var scroll = $(this).scrollTop();
+    gantt.scrollTo(undefined, scroll - ganttOffsetTop);
+  });
+  gantt.showTask = function (id) {
+    var el = this.getTaskNode(id);
+    if (!el) return;
+    var left = Math.max(el.offsetLeft - this.config.task_scroll_offset, 0);
+    var top = $(el).offset().top - 200;
+    $(window).scrollTop(top);
+    this.scrollTo(left, top);
+  };
+  gantt.getScrollState = function () {
+    if (!this.$task || !this.$task_data) return null;
+    return {x: this.$task.scrollLeft, y: $(window).scrollTop()};
+  };
+  gantt._path_builder.get_endpoint = function (link) {
+    var types = gantt.config.links;
+    var from_start = false, to_start = false;
+
+    if (link.type == types.start_to_start) {
+      from_start = to_start = true;
+    } else if (link.type == types.finish_to_finish) {
+      from_start = to_start = false;
+    } else if (link.type == types.finish_to_start) {
+      from_start = false;
+      to_start = true;
+    } else if (link.type == types.start_to_finish) {
+      from_start = true;
+      to_start = false;
+    } else {
+      dhtmlx.assert(false, "Invalid link type");
+    }
+    var source = gantt._pull[link.source];
+    var target = gantt._pull[link.target];
+    var sourceShift = 0, targetShift = 0;
+    var fromMount = from_start ? source.$startMount : source.$endMount;
+    var toMount = to_start ? target.$startMount : target.$endMount;
+    if (fromMount.length > 1) {
+      for (var i = 0; i < fromMount.length; i++) {
+        if (link.id == fromMount[i]) break;
+      }
+      sourceShift = (i * 2) / (fromMount.length - 1) * 6 - 6;
+    }
+    if (toMount.length > 1) {
+      for (i = 0; i < toMount.length; i++) {
+        if (link.id == toMount[i]) break;
+      }
+      targetShift = (i * 2) / (toMount.length - 1) * 6 - 6;
+    }
+    var from = gantt._get_task_visible_pos(gantt._pull[link.source], from_start);
+    var to = gantt._get_task_visible_pos(gantt._pull[link.target], to_start);
+
+    return {
+      x: from.x,
+      e_x: to.x,
+      y: from.y + sourceShift,
+      e_y: to.y + targetShift
+    };
+  };
+  gantt._sync_links = function () {
+    for (var id in this._pull) {
+      if (!this._pull.hasOwnProperty(id)) continue;
+      var task = this._pull[id];
+      task.$source = [];
+      task.$target = [];
+      task.$startMount = [];
+      task.$endMount = [];
+    }
+    for (id in this._lpull) {
+      if (!this._lpull.hasOwnProperty(id)) continue;
+      var link = this._lpull[id];
+      var types = gantt.config.links;
+      if (this._pull[link.source]) {
+        this._pull[link.source].$source.push(id);
+        if (link.type == types.start_to_start || link.type == types.start_to_finish) {
+          this._pull[link.source].$startMount.push(id);
+        } else {
+          this._pull[link.source].$endMount.push(id);
+        }
+      }
+      if (this._pull[link.target]) {
+        this._pull[link.target].$target.push(id);
+        if (link.type == types.start_to_start || link.type == types.finish_to_start) {
+          this._pull[link.target].$startMount.push(id);
+        } else {
+          this._pull[link.target].$endMount.push(id);
+        }
+      }
+    }
+  };
+  gantt._has_children = function (id) {
+    var item = gantt._pull[id];
+    if (item.widget && item.widget.model && item.widget.model.needLoad) {
+      return true;
+    }
+    return this.getChildren(id).length > 0;
+  };
+  gantt.setParent = function (task, new_pid) {
+    if (ysy.settings.parentIssueDates) {
+      var parentTask = this._pull[new_pid];
+      if (parentTask && gantt._get_safe_type(parentTask.type) === "task") {
+        parentTask.$no_start = true;
+        parentTask.$no_end = true;
+      }
+    }
+    task.parent = new_pid;
+  };
+  gantt.attachEvent("onAfterTaskMove", function (taskId) {
+    var task = gantt._pull[taskId];
+    if (!task) return;
+    task._parentChanged = true;
+  });
+  $("#main").on("resize", function () {
+    gantt.render();
+  });
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7c036d1df47b1d4b4b3739ba2c66dc3e0d4d30a
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_relations.js
@@ -0,0 +1,623 @@
+/* dhtml_relations.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.relations = {
+  patch: function () {
+    if (!ysy.settings.fixedRelations) {
+      ysy.pro.relations = null;
+      return;
+    }
+    var patch = function () {
+      gantt.multiStop = function (task, start_date, end_date, previous) {
+        var diff;
+        var sumMove = 0;
+        if (!previous) {
+          previous = {
+            visitedLinks: [],
+            visitedParents: [],
+            alreadyMoved: []
+          };
+          // ysy.log.debug("previous created");
+        }
+        if (task.soonest_start && start_date && start_date.isBefore(task.soonest_start)) {
+          diff = task.soonest_start.diff(start_date, "seconds");
+          start_date.add(diff, "seconds");
+          if (end_date) end_date.add(diff, "seconds");
+          sumMove += diff;
+          ysy.log.debug("AscStop(): soonest_start task=" + task.text + " diff=" + diff, "asc");
+        }
+        if (task.latest_due && end_date && end_date.isAfter(task.latest_due)) {
+          diff = task.latest_due.diff(end_date, "seconds");
+          end_date.add(diff, "seconds");
+          if (start_date) start_date.add(diff, "seconds");
+          sumMove += diff;
+          ysy.log.debug("AscStop(): latest_end task=" + task.text + " diff=" + diff, "asc");
+        }
+        if (end_date) {
+          if (ysy.settings.milestonePush) {
+            var newStartDate = gantt.descStartByMilestone(task, +start_date, +end_date);
+            if (newStartDate) {
+              ysy.log.debug("AscStop(): milePush task=" + task.text + " start_date=" + newStartDate.format("YYYY-MM-DD"), "asc");
+              var newEndDate = gantt._working_time_helper.add_worktime(newStartDate, task.duration, "day", true);
+              var mileDiff = (newEndDate - end_date) / 1000;
+              end_date.add(mileDiff, "seconds");
+              if (start_date) {
+                mileDiff = (newStartDate - start_date) / 1000;
+                start_date.add(mileDiff, "seconds");
+              }
+              ysy.log.debug("task=" + task.text + " mileDiff=" + (mileDiff / 86400), "task_drag_milestone");
+              sumMove += mileDiff;
+            }
+          }
+        }
+        //  ####################
+        sumMove += gantt.ascStop(task, start_date, end_date, previous);
+        var parentId = gantt.getParent(task.id);
+        var parent = gantt._pull[parentId];
+        if (parent && gantt._get_safe_type(parent.type) === "task") {
+          // if(parent.start_date.isAfter(start_date) || parent.end_date.isAfter(end_date)){
+          var branch = gantt._branches[parent.id];
+          if (branch && branch.length > 0) {
+            var minDate = start_date;
+            var maxDate = end_date;
+            for (var i = 0; i < branch.length; i++) {
+              var childId = branch[i];
+              var child = gantt.getTask(childId);
+              if (child.id === task.id) continue;
+
+              if (!minDate || minDate.isAfter(child.start_date)) {
+                minDate = child.start_date;
+              }
+              if (!maxDate || maxDate.isBefore(child.end_date)) {
+                maxDate = child.end_date;
+              }
+            }
+          }
+          var parentSumMove = gantt.multiStop(parent, moment(minDate), moment(maxDate));
+          if (start_date) {
+            start_date.add(parentSumMove, "seconds");
+            if (end_date) {
+              var new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP
+              end_date.add(new_end - end_date, "milliseconds");
+            }
+          } else {
+            end_date.add(parentSumMove, "seconds");
+          }
+          if (parentSumMove > sumMove) {
+            ysy.log.debug("AscStop(): parent task=" + task.text + " diff=" + parentSumMove, "asc");
+            sumMove = parentSumMove;
+          }
+          // }
+        }
+        return sumMove;
+      };
+      gantt.ascStop = function (task, start_date, end_date, previous) {
+        var shortestDiff;
+        var diff, sumMove = 0;
+        for (var i = 0; i < task.$target.length; i++) {
+          var lid = task.$target[i];
+          if (previous.visitedLinks.indexOf(lid) > -1) continue; // skip link sourcing moved parent
+          var link = gantt._lpull[lid];
+          if (link.isSimple) continue;
+          var targetDate;
+          if (link.type === "precedes" || link.type === "start_to_start") {
+            if (!start_date) continue;
+            targetDate = start_date;
+          } else if (link.type === "finish_to_finish" || link.type === "start_to_finish") {
+            if (!end_date) continue;
+            targetDate = end_date;
+          } else continue;
+          var source = gantt._pull[link.source];
+          var sourceDate = gantt.getLinkSourceDate(source, link.type);
+          diff = -targetDate.diff(sourceDate, "seconds") + gantt.getLinkCorrection(link.type) * 24 * 60 * 60;
+          if (!link.unlocked) {
+            diff += link.delay * 24 * 60 * 60;
+            if (diff <= 0) {
+              if (shortestDiff === undefined || shortestDiff < diff) {
+                shortestDiff = diff;
+              }
+            }
+          }
+          if (diff > 0) {
+            sumMove += diff;
+            ysy.log.debug("AscStop(): relDiff<0 task=" + task.text + " diff=" + diff, "asc");
+            if (start_date) {
+              start_date.add(diff, "seconds");
+            }
+            if (end_date) {
+              var new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP
+              end_date.add(new_end - end_date, "milliseconds");
+              // end_date.add(diff, "seconds");
+            }
+            shortestDiff = 0;
+          }
+        }
+        if (shortestDiff) {
+          sumMove += shortestDiff;
+          ysy.log.debug("AscStop(): shortest_diff task=" + task.text + " diff=" + shortestDiff, "asc");
+          if (start_date) {
+            start_date.add(shortestDiff, "seconds");
+          }
+          if (end_date) {
+            new_end = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true); //< HOSEKP
+            end_date.add(new_end - end_date, "milliseconds");
+            // end_date.add(shortestDiff, "seconds");
+          }
+        }
+        return sumMove;
+      };
+      /**
+       *
+       * @param task
+       * @param {{[old_start]:{},[old_end]:{},[fromParent]:boolean,[fromChild]:boolean,[asc]:boolean}} options
+       * @param previous
+       * @return {number}
+       */
+      gantt.moveDesc = function (task, options, previous/*, resizing*/) {
+        // runs after task dates are already modified.
+        if (!previous) {
+          previous = {
+            visitedLinks: [],
+            visitedParents: [],
+            alreadyMovedFrom: {},
+            central: task,
+            parentsToRecalculate: []
+          };
+          previous.alreadyMovedFrom[task.id] = options.old_start;
+          // ysy.log.debug("previous created");
+        }
+        if (!options.old_start) {
+          options.old_start = previous.alreadyMovedFrom[task.id];
+        }
+        var shouldMove = gantt.shouldMoveChildren(task);
+        if (!options.fromChild && shouldMove.move) {
+          var children = gantt.moveChildren(task, $.extend({}, options, {fromParent: true}), previous);
+        } else if (shouldMove.milestoneMove) {
+          this.moveMilestoneChildren(task, {old_start: options.old_start}, previous);
+        }
+        if (shouldMove.adjust) {
+          if (!children) {
+            var branch = gantt._branches[task.id];
+            if (branch && branch.length !== 0) {
+              children = [];
+              for (i = 0; i < branch.length; i++) {
+                children.push(gantt.getTask(branch[i]));
+              }
+            }
+          }
+          if (children && children.length) {
+            var dates = {start_date: null, end_date: null};
+            for (i = 0; i < children.length; i++) {
+              var child = children[i];
+              if (dates.start_date === null || dates.start_date.isAfter(child.start_date)) {
+                dates.start_date = child.start_date;
+              }
+              if (dates.end_date === null || dates.end_date.isBefore(child.end_date)) {
+                dates.end_date = child.end_date;
+              }
+            }
+            task.start_date.add(dates.start_date - task.start_date, "milliseconds");
+            task.end_date.add(dates.end_date - task.end_date, "milliseconds");
+          }
+        }
+
+        var start_date = +task.start_date;
+        var end_date = +task.end_date;
+        if (!options.asc) {
+          /** DESCENDANTS */
+          for (var i = 0; i < task.$source.length; i++) {
+            var lid = task.$source[i];
+            var link = gantt._lpull[lid];
+            if (previous.visitedLinks.indexOf(lid) > -1) continue;
+            previous.visitedLinks.push(lid);
+            if (link.isSimple) continue;
+            var desc = gantt._pull[link.target];
+            // var isTargetingEnd = link.type === "finish_to_finish" || link.type === "start_to_finish";
+            if (!link.unlocked) {
+              var descDates = this.getDescDates(link, start_date, end_date);
+              // var diff = gantt.getFreeDelay(link, desc, start_date, end_date);
+              if (descDates.end_date) {
+                debugger
+              } else {
+                gantt.safeMoveToStartDate(desc, descDates.start_date, previous);
+              }
+              //ysy.log.debug("pushing "+desc.text+" by freeDelay "+diff);
+              gantt.moveDesc(desc, {}, previous);
+            } else {
+              descDates = this.getDescDates(link, start_date, end_date);
+              // var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date);
+              // if (descDiff <= 0) continue;
+              // console.log("OVER");
+              if (descDates.start_date && descDates.start_date.isAfter(desc.start_date)) {
+                gantt.safeMoveToStartDate(desc, descDates.start_date, previous);
+              }
+              if (descDates.end_date && descDates.end_date.isAfter(desc.end_date)) {
+                debugger;
+                gantt.safeMoveToStartDate(desc, descDates.start_date, previous);
+              }
+              // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff);
+              gantt.moveDesc(desc, {}, previous);
+            }
+          }
+        } else {
+          /** ASCENDANTS */
+          for (i = 0; i < task.$target.length; i++) {
+            lid = task.$target[i];
+            link = gantt._lpull[lid];
+            if (previous.visitedLinks.indexOf(lid) > -1) continue;
+            previous.visitedLinks.push(lid);
+            if (link.isSimple) continue;
+            var asc = gantt._pull[link.source];
+            // var isTargetingEnd = link.type === "finish_to_finish" || link.type === "start_to_finish";
+            if (!link.unlocked) {
+              var ascDates = this.getAscDates(link, start_date, end_date);
+              // var diff = gantt.getFreeDelay(link, desc, start_date, end_date);
+              if (ascDates.end_date) {
+                debugger
+              } else {
+                gantt.safeMoveToStartDate(asc, ascDates.start_date, previous);
+              }
+              //ysy.log.debug("pushing "+desc.text+" by freeDelay "+diff);
+              gantt.moveDesc(asc, {asc: true}, previous);
+            } else {
+              var ascDiff = gantt.getFreeDelay(link, task, asc.start_date, asc.end_date);
+              if (ascDiff <= 0) continue;
+              // ysy.log.debug("desc=" + desc.text +" diff="+diff+" descDiff="+descDiff);
+              gantt.moveDesc(asc, {asc: true}, previous);
+            }
+          }
+        }
+        if (!options.fromParent) {
+          gantt.moveParent(task, {}, previous);
+        }
+      };
+
+      gantt.moveChildren = function (task, options, previous) {
+        var branch = gantt._branches[task.id];
+        if (!branch || branch.length === 0) return null;
+        var children = [];
+        for (var i = 0; i < branch.length; i++) {
+          var childId = branch[i];
+          //if(gantt.isTaskVisible(childId)){continue;}
+          var child = gantt.getTask(childId);
+          if (!child._parent_offset) {
+            child._parent_offset = gantt._working_time_helper.get_work_units_between(options.old_start, child.start_date, "day");
+          }
+          children.push(child);
+          if (previous.alreadyMovedFrom[childId]) continue;
+          var childOldStart = previous.alreadyMovedFrom[childId] || child.start_date;
+          var childNewStart = gantt._working_time_helper.add_worktime(task.start_date, child._parent_offset, "day", false);
+          gantt.safeMoveToStartDate(child, childNewStart, previous);
+          // child.start_date.add(childNewStart - child.start_date, "milliseconds");
+          child._changed = gantt.config.drag_mode.move;
+          gantt.moveDesc(child, {old_start: childOldStart, fromParent: options.fromParent}, previous);
+          gantt.refreshTask(childId);
+        }
+        // gantt._update_parents(task.id);
+        return children;
+      };
+      gantt.shouldMoveChildren = function (task) {
+        if (task.type === "milestone") return {milestoneMove: true};
+        if (gantt._get_safe_type(task.type) === "task" && ysy.settings.parentIssueDates) return {
+          move: true,
+          adjust: true
+        };
+        if (task.$open && gantt.isTaskVisible(task.id)) return {move: true};
+        return false;
+      };
+      gantt.safeMoveToStartDate = function (task, start_date, previous) {
+        if (task.start_date.isSame(start_date)) {
+          previous.alreadyMovedFrom[task.id] = moment(start_date);
+          return 0;
+        }
+        previous.alreadyMovedFrom[task.id] = moment(task.start_date);
+        if (task.start_date.isAfter(start_date)) {
+          // moved forward
+          var end_date = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true);
+          task.start_date.add(start_date - task.start_date, "milliseconds");
+          task.end_date.add(end_date - task.end_date, "milliseconds");
+          task._changed = gantt.config.drag_mode.move;
+          gantt.refreshTask(task.id);
+          return 0;
+        } else {
+          // moved backward
+          end_date = gantt._working_time_helper.add_worktime(start_date, task.duration, "day", true);
+          task.start_date.add(start_date - task.start_date, "milliseconds");
+          task.end_date.add(end_date - task.end_date, "milliseconds");
+          task._changed = gantt.config.drag_mode.move;
+          gantt.refreshTask(task.id);
+          var ascDiff = gantt.ascStop(task, task.start_date, task.end_date, previous);
+          if (ascDiff !== 0) {
+            // ysy.log.debug("diff=" + diff + " ascStopDiff=" + ascDiff);
+            return ascDiff;
+          }
+        }
+      };
+      gantt.moveMilestoneChildren = function (milestone, options, previous) {
+        var branch = gantt._branches[milestone.id];
+        if (!branch || branch.length === 0) return 0;
+        // ysy.log.debug("Shift children of \"" + milestone.text + "\" by " + shift + " seconds", "parent");
+        ysy.log.debug("moveChildren():  of \"" + milestone.text + "\"(" + milestone.end_date.toISOString() + ") from " + options.old_start.toISOString(), "task_drag");
+        // var milestoneEndDate=moment(milestone.end_date).add(shift,"seconds");
+        // milestoneEndDate._isEndDate=true;
+        for (var i = 0; i < branch.length; i++) {
+          var childId = branch[i];
+          var child = gantt.getTask(childId);
+          if (child.end_date.isAfter(milestone.end_date)) {
+            // var shift = milestone.end_date.diff(child.end_date, "seconds");
+            // child.start_date.add(shift, 'seconds');
+            var startDateValue = +child.start_date;
+            // child.end_date = moment(milestone.end_date);
+            // child.end_date._isEndDate = true;
+            child.end_date.add(milestone.end_date - child.end_date, "milliseconds");
+            var childNewStart = gantt._working_time_helper.add_worktime(child.end_date, -child.duration, "day", false);
+            var childOldStart = moment(child.start_date);
+            child.start_date.add(childNewStart - child.start_date, "milliseconds");
+            // ysy.log.debug(child.start_date.toISOString()+"  "+child.end_date.toISOString());
+            child._changed = gantt.config.drag_mode.move;
+            gantt.refreshTask(child.id);
+            previous.alreadyMovedFrom[child.id] = previous.alreadyMovedFrom[child.id] || childOldStart;
+            gantt.moveDesc(child, {old_date: moment(startDateValue), asc: true}, previous);
+          }
+        }
+        return 0;
+      };
+      gantt.moveParent = function (child, option, previous) {
+        if (!ysy.settings.parentIssueDates) return;
+        var parentId = gantt.getParent(child.id);
+        if (previous.central.id === parentId) {
+          parent = previous.central;
+        } else {
+          var parent = gantt._pull[parentId];
+        }
+        if (gantt._get_safe_type(parent.type) !== "task") return 0;
+        // previous.visitedParents.push(parent.id);
+        // if (parent.end_date.isBefore(child.end_date)) {
+        //   parent.end_date.add(child.end_date - parent.end_date, "milliseconds");
+        //   parent._changed = gantt.config.drag_mode.move;
+        //   gantt.refreshTask(parent.id);
+        //   // if(option.skipParent)
+        // }
+        gantt.moveDesc(parent, {fromChild: true/*old_start:moment(parent.start_date)*/}, previous);
+      };
+      gantt.startByMilestone = function (task, end_date) {
+        var issue = task.widget && task.widget.model;
+        if (!issue) return 0;
+        var milestone = ysy.data.milestones.getByID(issue.fixed_version_id);
+        if (!milestone) return 0;
+        var ganttMilestone = gantt._pull[milestone.getID()];
+        if (ganttMilestone) {
+          var milestoneDate = ganttMilestone.end_date;
+        } else {
+          milestoneDate = milestone.start_date;
+        }
+        if (milestoneDate.isBefore(end_date)) {
+          ysy.log.debug("milestoneStop() for " + task.text +
+              " at " + milestone.start_date.format("YYYY-MM-DD"), "task_drag_milestone");
+          return gantt._working_time_helper.toMoment(
+              gantt._working_time_helper.add_worktime(milestoneDate, -task.duration, "day", false)
+          );
+        }
+        return null;
+      };
+      gantt.descStartByMilestone = function (task, start_date, end_date) {
+        var newStartDate = gantt.startByMilestone(task, end_date);
+        // var start_date = +task.start_date / 1000 + diff;
+        // var end_date = +task.end_date / 1000 + diff;
+        for (var i = 0; i < task.$source.length; i++) {
+          var lid = task.$source[i];
+          var link = gantt._lpull[lid];
+          if (link.isSimple) continue;
+          var desc = gantt._pull[link.target];
+          var descDiff = gantt.getFreeDelay(link, desc, start_date, end_date);
+          if (descDiff <= 0) continue;
+          var descEndDate = gantt._working_time_helper.add_worktime(desc.start_date + descDiff * 1000, desc.duration, "day", true);
+          var correctedDescStartDate = gantt.descStartByMilestone(desc, desc.start_date + descDiff * 1000, descEndDate);
+          if (!correctedDescStartDate) continue;
+          switch (link.type) {
+            case "precedes":
+              if (!link.unlocked) {
+                correctedDescStartDate.subtract(link.delay, "days");
+              }
+              var correctedStartDate = gantt._working_time_helper.add_worktime(correctedDescStartDate, -task.duration, "day", false);
+              break;
+            default:
+              debugger;
+          }
+          if (!newStartDate || (correctedStartDate && correctedStartDate.isBefore(newStartDate))) {
+            newStartDate = correctedStartDate;
+          }
+        }
+        var childrenStartDate = gantt.childrenStartByMilestone(task, (newStartDate ? newStartDate : start_date) - task.start_date);
+        if (childrenStartDate) {
+          newStartDate = childrenStartDate;
+        }
+        if (newStartDate) {
+          ysy.log.debug("milestoneDescStop(): task " + task.text + " start set to " + newStartDate.format("YYYY-MM-DD"), "task_drag_milestone");
+        }
+        return newStartDate;
+      };
+      gantt.childrenStartByMilestone = function (task, diff) {
+        if (task.type === "milestone") return null;
+        var branch = gantt._branches[task.id];
+        if (!branch || branch.length === 0) return 0;
+        var newStartDate;
+        for (var i = 0; i < branch.length; i++) {
+          var childId = branch[i];
+          //if(gantt.isTaskVisible(childId)){continue;}
+          var child = gantt.getTask(childId);
+          var childEndDate = gantt._working_time_helper.add_worktime(child.start_date + diff, child.duration, "day", true);
+          var correctedStartDate = gantt.descStartByMilestone(child, child.start_date + diff, childEndDate);
+          if (!newStartDate || (correctedStartDate && correctedStartDate.isBefore(newStartDate))) {
+            newStartDate = correctedStartDate;
+          }
+        }
+        if (newStartDate) {
+          ysy.log.debug("milestoneChildrenStop():  \"" + task.text + "\" start_date set to " + newStartDate.format("YYYY-MM-DD"), "task_drag");
+        }
+        return newStartDate;
+      };
+      gantt.getLinkSourceDate = function (source, type) {
+        if (type === "precedes") return source.end_date;
+        if (type === "finish_to_finish") return source.end_date;
+        if (type === "start_to_start") return source.start_date;
+        if (type === "start_to_finish") return source.start_date;
+        return null;
+      };
+      gantt.getLinkTargetDate = function (target, type) {
+        if (type === "precedes") return target.start_date;
+        if (type === "finish_to_finish") return target.end_date;
+        if (type === "start_to_start") return target.start_date;
+        if (type === "start_to_finish") return target.end_date;
+        return null;
+      };
+      gantt.getLinkCorrection = function (type) {
+        if (type === "precedes") return 1;
+        if (type === "start_to_finish") return -1;
+        return 0;
+      };
+      /**
+       * How much can be distance between two task shortened
+       * @param link - gantt link
+       * @param desc - gantt task
+       * @param {number} ascStartDate - number of milliseconds since epoch
+       * @param {number} ascEndDate - number of milliseconds since epoch
+       * @return {number} - number of seconds
+       */
+      gantt.getFreeDelay = function (link, desc, ascStartDate, ascEndDate) {
+        if (!desc) desc = gantt._pull[link.target];
+        var sourceDate;
+        var daysToSeconds = 60 * 60 * 24;
+        if (!ascStartDate) {
+          var asc = gantt._pull[link.source];
+          ascStartDate = +asc.start_date;
+          ascEndDate = +asc.end_date;
+        }
+        if (link.type === "precedes" || link.type === "finish_to_finish") {
+          if (!ascEndDate) return 0;
+          sourceDate = ascEndDate;
+        } else if (link.type === "start_to_finish" || link.type === "start_to_start") {
+          if (!ascStartDate) return 0;
+          sourceDate = ascStartDate;
+        } else return 0;
+        var targetDate = +gantt.getLinkTargetDate(desc, link.type);
+        var correction = gantt.getLinkCorrection(link.type);
+        var delay = (targetDate - sourceDate) / daysToSeconds / 1000;
+        return ((link.unlocked ? 0 : link.delay) + correction - delay) * daysToSeconds;
+      };
+      /**
+       *
+       * @param {{type:String,delay:int,source:int,target:int,unlocked:boolean}} link
+       * @param {int} ascStartDate
+       * @param {int} ascEndDate
+       * @return {{[start_date]:Object,[end_date]:Object}}
+       */
+      gantt.getDescDates = function (link, ascStartDate, ascEndDate) {
+        //if (!desc) desc = gantt._pull[link.target];
+        var sourceDate;
+        if (!ascStartDate) {
+          var asc = gantt._pull[link.source];
+          ascStartDate = +asc.start_date;
+          ascEndDate = +asc.end_date;
+        }
+        if (link.type === "precedes" || link.type === "finish_to_finish") {
+          if (!ascEndDate) return {};
+          sourceDate = ascEndDate;
+        } else if (link.type === "start_to_finish" || link.type === "start_to_start") {
+          if (!ascStartDate) return {};
+          sourceDate = ascStartDate;
+        } else return {};
+        var correction = gantt.getLinkCorrection(link.type);
+        if (link.type === "finish_to_finish" || link.type === "start_to_finish") {
+          var descEndDate = moment(sourceDate).add((link.unlocked ? 0 : link.delay) + correction, "days");
+          descEndDate._isEndDate = true;
+          var descSafeEnd = gantt._working_time_helper.get_closest_worktime({
+            dir: "future",
+            date: descEndDate
+          });
+          return {end_date: descSafeEnd};
+        } else {
+          var descStartDate = moment(sourceDate).add((link.unlocked ? 0 : link.delay) + correction, "days");
+          var descSafeStart = gantt._working_time_helper.get_closest_worktime({
+            dir: "future",
+            date: descStartDate
+          });
+          return {start_date: descSafeStart};
+        }
+      };
+      /**
+       *
+       * @param {{type:String,delay:int,source:int,target:int,unlocked:boolean}} link
+       * @param {int} descStartDate
+       * @param {int} descEndDate
+       * @return {{[start_date]:moment,[end_date]:moment}}
+       */
+      gantt.getAscDates = function (link, descStartDate, descEndDate) {
+        var targetDate;
+        if (!descStartDate) {
+          var desc = gantt._pull[link.target];
+          descStartDate = +desc.start_date;
+          descEndDate = +desc.end_date;
+        }
+        if (link.type === "start_to_finish" || link.type === "finish_to_finish") {
+          if (!descEndDate) return {};
+          targetDate = descEndDate;
+        } else if (link.type === "precedes" || link.type === "start_to_start") {
+          if (!descStartDate) return {};
+          targetDate = descStartDate;
+        } else return {};
+        var correction = gantt.getLinkCorrection(link.type);
+        if (link.type === "finish_to_finish" || link.type === "start_to_finish") {
+          var ascEndDate = moment(targetDate).subtract((link.unlocked ? 0 : link.delay) + correction, "days");
+          ascEndDate._isEndDate = true;
+          var ascSafeEnd = gantt._working_time_helper.get_closest_worktime({
+            dir: "past",
+            date: ascEndDate
+          });
+          return {end_date: ascSafeEnd};
+        } else {
+          var ascStartDate = moment(targetDate).subtract((link.unlocked ? 0 : link.delay) + correction, "days");
+          var ascSafeStart = gantt._working_time_helper.get_closest_worktime({
+            dir: "past",
+            date: ascStartDate
+          });
+          return {start_date: ascSafeStart};
+        }
+      };
+      //##################################################################################################################
+      gantt.attachEvent("onLinkClick", function (id, mouseEvent) {
+        // if (!gantt.config.drag_links) return;
+        ysy.log.debug("LinkClick on " + id, "link_config");
+        var link = gantt.getLink(id);
+        if (gantt._is_readonly(link)) return;
+        var source = gantt._pull[link.source];
+        if (!source) return;
+        var target = gantt._pull[link.target];
+        if (!target) return;
+        if (source.readonly && target.readonly) return;
+        var relation = link.widget.model;
+        var isUnlocked = relation.unlocked;
+        relation.set({unlocked: !isUnlocked, delay: isUnlocked ? relation.getActDelay() : 0});
+        return false;
+      });
+      gantt.attachEvent("onContextMenu", function (taskId, id /*, mouseEvent*/) {
+        if (taskId) return;
+        if (!gantt.config.drag_links) return;
+        ysy.log.debug("LinkClick on " + id, "link_config");
+        var link = gantt.getLink(id);
+        if (gantt._is_readonly(link)) return;
+        var source = gantt._pull[link.source];
+        if (!source) return;
+        var target = gantt._pull[link.target];
+        if (!target) return;
+        if (source.readonly && target.readonly) return;
+        var linkConfigWidget = new ysy.view.LinkPopup().init(link.widget.model, link);
+        linkConfigWidget.$target = $("#ajax-modal");//$dialog;
+        linkConfigWidget.repaint();
+        showModal("ajax-modal", "auto");
+      });
+    };
+    patch();
+  }
+};
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js
new file mode 100644
index 0000000000000000000000000000000000000000..17750a063168fdc05152f41914bd731c0216eef3
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtml_rewrite.js
@@ -0,0 +1,705 @@
+/* dhtml_rewrite.js */
+/* global ysy */
+/**
+ * @external Moment
+ */
+// noinspection JSCommentMatchesSignature
+/**
+ * @function moment
+ * @param {*} [arg1]
+ * @return {Moment}
+ */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.applyGanttRewritePatch = function () {
+  ysy.view.initNonworkingDays = function () {
+    var work_helper = gantt._working_time_helper;
+    work_helper.defHours = ysy.settings.hoursPerDay;
+    var i;
+    // Now we specify working days
+    var nonWorking = ysy.settings.nonWorkingWeekDays;
+    for (i in nonWorking) {
+      work_helper.set_time({day: nonWorking[i] % 7, hours: false});
+    }
+    // Now we specify holidays
+    var holidays = ysy.settings.holidays;
+    if (holidays) {
+      for (i = 0; i < holidays.length; i++) {
+        work_helper.set_time({date: moment(holidays[i]), hours: false});
+      }
+    }
+    work_helper._cache = {};
+  };
+  gantt._working_time_helper = {
+    defHours: 8,
+    timeformat: "YYYY-MM-DD",
+    days: {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true},
+    dates: {},
+    _cache: {},
+    oneDaySeconds: 1000 * 60 * 60 * 24,
+    oneHourSeconds: 1000 * 60 * 60,
+    //formatDate: function (date) {
+    //    if (date._isAMomentObject) {
+    //        return date.format(this.timeformat);
+    //    }
+    //    return moment(date).format(this.timeformat);
+    //},
+    toMoment: function (date) {
+      if (date._isAMomentObject) {
+        return date;
+      }
+      return moment(date);
+    },
+    set_time: function (settings) {
+      var hours = settings.hours !== undefined ? settings.hours : true;
+      if (settings.day !== undefined) {
+        this.days[settings.day] = hours;
+      } else if (settings.date !== undefined) {
+        this.dates[+settings.date] = hours;
+      }
+
+    },
+    is_weekend: function (date) {
+      date = this.toMoment(date);
+      return !this.days[date.day()];
+    },
+    is_working_day: function (date) {
+      date = this.toMoment(date);
+      if (!this.days[date.day()]) {
+        return false;//week day
+      }
+      var val = this.get_working_hours(date);
+      return val > 0;
+    },
+    get_working_hours: function (date) {
+      //return 8;
+      //console.log("get_working_hours: "+date.toString());
+      //var t = this._timestamp({date: date});
+      date = this.toMoment(date);
+      var t = +date;
+      var hours = true;
+      //date = moment(date);
+      if (this.dates[t] !== undefined) {
+        hours = this.dates[t];//custom day
+      } else if (this.days[date.day()] !== undefined) {
+        hours = this.days[date.day()];//week day
+      }
+      if (hours === true) {
+        return this.defHours;
+      } else if (hours) {
+        return hours;
+      }
+      return 0;
+    },
+    is_working_unit: function (date, unit, order) {
+      if (!gantt.config.work_time)
+        return true;
+      return this.is_working_day(date);
+    },
+    save_working_unit: function (first_date, end_date, unit) {
+      var weekID = first_date.isoWeek();
+      var workDays = this.get_work_units_between(first_date, end_date, "day");
+      var workHours = this.get_work_units_between(first_date, end_date, "hour");
+      this.weeks[weekID] = {days: workDays, hours: workHours};
+    },
+    _work_times_between: function (from, to) {
+      var spanID = from.valueOf() + "_" + to.valueOf();
+      if (this._cache[spanID]) {
+        this._cache[spanID].cached = true;
+        return this._cache[spanID];
+      }
+      if (from.isBefore(to)) {
+        var ret = this._work_times_forward(from, to);
+      } else {
+        ret = this._work_times_backward(from, to);
+      }
+      this._cache[spanID] = ret;
+      return ret;
+    },
+    _work_times_forward: function (from, to) {
+      var sumHours = 0;
+      var sumDays = 0;
+
+      if ((+from % this.oneHourSeconds) !== 0 || from.hours()!==0) {
+        var mover = moment(from).startOf("day");
+        var first = true;
+      } else {
+        mover = from;
+      }
+      var count = 0;
+      while (mover.isBefore(to)) {
+        if ((count++) > 3000) {
+          ysy.log.error("_work_times_forward: Probably task with start_date=" + from.format("YYYY-MM-DD")
+              + " and end_date=" + to.format("YYYY-MM-DD") + " is too long");
+          break;
+        }
+        var hours = this.get_working_hours(mover);
+        if (hours > 0) {
+          if (first) {
+            var rest = 1 - (from - mover) / this.oneDaySeconds;
+            sumDays += rest;
+            sumHours += hours * rest;
+          } else {
+            sumDays++;
+            sumHours += hours;
+          }
+        }
+        first = false;
+        mover.add(1, "days");
+      }
+      if (hours > 0 && mover.isAfter(to)) {
+        rest = (mover - to) / this.oneDaySeconds;
+        sumDays -= rest;
+        sumHours -= hours * rest;
+      }
+      return {days: sumDays, hours: sumHours};
+    },
+    _work_times_backward: function (from, to) {
+      var sumHours = 0;
+      var sumDays = 0;
+      if ((+from % this.oneHourSeconds) !== 0 || from.hours()!==0) {
+        var mover = moment(from).startOf("day");
+        var first = true;
+        mover.add(1, "day");
+      } else {
+        mover = from;
+      }
+      var count = 0;
+      while (mover.isAfter(to)) {
+        mover.subtract(1, "days");
+        if ((count++) > 3000) {
+          ysy.log.error("_work_times_backward: Probably task with start_date=" + to.format("YYYY-MM-DD")
+              + " and end_date=" + from.format("YYYY-MM-DD") + " is too long");
+          break;
+        }
+        var hours = this.get_working_hours(mover);
+        if (hours > 0) {
+          if (first) {
+            var rest = (from - mover) / this.oneDaySeconds;
+            sumDays += rest;
+            sumHours += hours * rest;
+          } else {
+            sumDays++;
+            sumHours += hours;
+          }
+        }
+        first = false;
+      }
+      if (hours > 0 && mover.isBefore(to)) {
+        var rest2 = (to - mover) / this.oneDaySeconds;
+        sumDays -= rest2;
+        sumHours -= hours * rest2;
+      }
+      //ysy.log.debug(mover.format() + " hours=" + hours + " rest=" + rest + " rest2=" + rest2);
+      return {days: -1 * sumDays, hours: -1 * sumHours};
+    },
+    get_work_units_between: function (from, to, unit) {
+      if (!unit) {
+        ysy.log.error("missing unit");
+        return 0;
+      }
+      var safeFrom = moment(from);
+      if (to._isEndDate) {
+        var safeTo = moment(to).add(1, "days");
+      }else{
+        safeTo = moment(to)
+      }
+      if (from._isEndDate) {
+        safeFrom = safeFrom.add(1, "days");
+      }
+      var workSums = this._work_times_between(safeFrom, safeTo);
+      ysy.log.debug("get_work_units_between: " + this.toMoment(from).format("DD.MM.YYYY") + "(" + from._isEndDate + ") " + this.toMoment(to).format("DD.MM.YYYY") + "(" + to._isEndDate + ") " + unit + " " + (workSums.cached ? "(cached)" : ""), "date_helper");
+      if (unit === "day") {
+        return workSums.days;
+      } else if (unit === "all") {
+        return workSums;
+      } else {
+        return workSums.hours;
+      }
+    },
+    is_work_units_between: function (from, to, unit) {
+      if (!unit) {
+        return false;
+      }
+      return this.get_work_units_between(from, to, unit) > 0;
+    },
+    /**
+     *
+     * @param {Moment|Date} from
+     * @param {boolean} from._isEndDate
+     * @param {int} duration
+     * @param {string} unit
+     * @param {boolean} toIsEndDate
+     * @return {Moment|Date}
+     */
+    add_worktime: function (from, duration, unit, toIsEndDate) {
+      var hours;
+      ysy.log.debug("add_worktime: " + from.toString() + " " + duration + " " + unit, "date_helper");
+      if (!unit) {
+        throw "Missing unit";
+      } else {
+        unit = unit + "s";
+      }
+      var added = 0;
+      var safeFrom = moment(from);
+      if (from._isEndDate) {
+        safeFrom.add(1, "days");
+      }
+      if ((+from % this.oneHourSeconds) !== 0 || safeFrom.hours()!==0) {
+        var mover = moment(safeFrom).startOf("day");
+        var diff = safeFrom - mover;
+        var first = true;
+      } else {
+        mover = safeFrom;
+      }
+      //if (!gantt.config.work_time||unit==="weeks") {
+      // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover before");
+      if (gantt.config.work_time && (unit === "days" || unit === "hours")) {
+        var count = 0;
+        if (duration === 0) {
+          while (true) {
+            if (count++ > 3000) {
+              ysy.log.error("_add_worktime: Probably task with start_date=" + moment(from).format("YYYY-MM-DD")
+                  + " and duration " + duration + " is too long");
+              break;
+            }
+            hours = this.get_working_hours(mover);
+            if (hours) {
+              if (first) {
+                mover.add(diff, "milliseconds");
+              }
+              break;
+            }
+            first = false;
+            mover.add(1, "days");
+          }
+        } else if (duration > 0) {
+          while (added < duration) {
+            if (count++ > 3000) {
+              ysy.log.error("_add_worktime: Probably task with start_date=" + moment(from).format("YYYY-MM-DD")
+                  + " and duration " + duration + " is too long");
+              break;
+            }
+            hours = this.get_working_hours(mover);
+            if (hours > 0) {
+              var rest = 1;
+              if (first) {
+                rest = 1 - diff / this.oneDaySeconds;
+              }
+              if (unit === "hours") {
+                added += rest * hours;
+              } else {
+                added += rest;
+              }
+            }
+            first = false;
+            mover.add(1, "days");
+          }
+          if (hours && added > duration) {
+            mover.subtract((added - duration) * (unit === "hours" ? 3600 : 3600 * 24), "seconds");
+          }
+        } else {
+          while (added < -duration) {
+            if (count++ > 3000) {
+              ysy.log.error("_add_worktime: Probably task with end_date=" + moment(from).format("YYYY-MM-DD")
+                  + " and duration " + duration + " is too long");
+              break;
+            }
+            if (!first || !diff) {
+              mover.add(-1, "days");
+            }
+            hours = this.get_working_hours(mover);
+            if (hours > 0) {
+              rest = 1;
+              if (first) {
+                rest = diff / this.oneDaySeconds;
+              }
+              if (unit === "hours") {
+                added += rest * hours;
+              } else {
+                added += rest;
+              }
+              // console.log(mover.format("YYYY-MM-DD HH:mm")+" moved rest="+rest+" added="+added);
+            }
+            first = false;
+          }
+          if (hours && added > -duration) {
+            mover.add((added + duration) * (unit === "hours" ? 3600 : 3600 * 24), "seconds");
+          }
+        }
+      } else {
+        mover.add(duration, unit);
+      }
+      // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover after");
+      if (toIsEndDate || !from._isEndDate && toIsEndDate === undefined) {
+        mover.add(-1, "days");
+        mover._isEndDate = true;
+      }
+      // console.log(mover.format("YYYY-MM-DD HH:mm")+" mover endDated");
+      if (moment.isMoment(from)) {
+        return mover;
+      }
+      return mover.toDate();
+    },
+    round_date: function (date, direction) {
+      ysy.log.debug("round_date: " + date.toString(), "date_helper");
+      if (date._isAMomentObject) {
+        var momentDate = date;
+      } else {
+        momentDate = moment(date);
+      }
+      momentDate.add(12, "hours").startOf("day");
+      var count = 0;
+      if (gantt.config.work_time) {
+        if (direction === 'past') {
+          while (!this.is_working_day(momentDate) && (count++) < 1000) {
+            momentDate.subtract(1, "days");
+          }
+        } else {
+          while (!this.is_working_day(momentDate) && (count++) < 1000) {
+            momentDate.add(1, "days");
+          }
+        }
+      }
+      if (!date._isAMomentObject) {
+        date.setTime(momentDate.valueOf());
+      }
+
+      //end.add(12,"hours").startOf("day");
+
+    },
+    /**
+     * @param {String} settings.dir
+     * @param {Moment|int} settings.date
+     * @param {String} [settings.unit]
+     * @param {int} [settings.length] - minimal size of working span
+     * @return {*}
+     */
+    get_closest_worktime: function (settings) {
+      ysy.log.debug("get_closest_worktime: " + settings.date.toString(), "date_helper");
+
+      var isMoment = moment.isMoment(settings.date);
+      var unit = settings.unit || "day";
+      var dayStart = moment(settings.date).startOf(unit);
+      var diff = settings.date - dayStart; // in milliseconds
+
+
+      var goFuture = settings.dir === 'any' || settings.dir === 'future',
+          goPast = settings.dir === 'any' || settings.dir === 'past';
+
+      if (!settings.length) {
+        if (this.is_working_day(dayStart)) {
+          dayStart.add(diff, "milliseconds");
+          if (isMoment) return dayStart;
+          return dayStart.toDate();
+        }
+      }
+      if (goPast) {
+        var past_target = moment(dayStart);
+        if (settings.length) {
+          past_target.add(settings.length, "days");
+        }
+      }
+      if (goFuture) {
+        var future_target = dayStart;
+        if (diff !== 0) {
+          future_target.add(1, unit);
+        }
+      }
+      if (goPast) {
+        // ysy.log.debug("checking past " + past_target.format("YYYY-MM-DD"));
+        if (this.is_working_day(past_target)) {
+          if (!settings.length) {
+            past_target.add(diff, "milliseconds");
+            if (isMoment) return past_target;
+            return past_target.toDate();
+          } else {
+            past_target.subtract(1, "days");
+            if (this.is_working_day(past_target)) {
+              past_target.add(diff, "milliseconds");
+              if (isMoment) return past_target;
+              return past_target.toDate();
+            }
+          }
+        }
+        past_target.add(-1, "days");
+      }
+      //will seek closest working hour in future or in past, one step in both direction per iteration
+      for (var count = 0; count < 3000; count++) { //be extra sure we won't fall into infinite loop, 3k seems big enough
+        if (goPast) {
+          // ysy.log.debug("checking past " + past_target.format("YYYY-MM-DD"));
+          if (this.is_working_day(past_target)) {
+            if (isMoment) return past_target;
+            return past_target.toDate();
+          }
+          past_target.add(-1, "days");
+        }
+        if (goFuture) {
+          // ysy.log.debug("checking future " + future_target.format("YYYY-MM-DD"));
+          if (this.is_working_day(future_target)) {
+            if (isMoment) return future_target;
+            return future_target.toDate();
+          }
+          future_target.add(1, "days");
+        }
+      }
+      dhtmlx.assert(false, "Invalid working time check");
+      return false;
+    }
+
+
+  };
+//#######################################################################
+  gantt.date = {
+    now: function () {
+      return moment();
+    },
+    Date: function (date, isEndDate) {
+      if (!date) {
+        //return moment();
+        return new Date();
+      }
+      if (date._isAMomentObject/*||!isNaN(date)*/) {
+        var ndate = moment(date);
+        if (isEndDate === undefined) {
+          isEndDate = date._isEndDate;
+        }
+      } else {
+        //ysy.log.warning("date created as new Date(), not as moment()");
+        ndate = moment(date).toDate();
+      }
+      ndate._isEndDate = isEndDate;
+      return ndate;
+    },
+    toMoment: function (date) {
+      if (!date) {
+        ysy.log.error("No date to convert to Moment");
+        return;
+      }
+      if (date._isAMomentObject) {
+        return date;
+      }
+      return moment(date);
+    },
+    fromMoment: function (date, momentDate) {
+      if (date._isAMomentObject) {
+        return date;
+      }
+      date.setTime(momentDate.valueOf());
+      return date;
+    },
+    init: function () {
+      return;
+      var s = gantt.locale.date.month_short;
+      var t = gantt.locale.date.month_short_hash = {};
+      for (var i = 0; i < s.length; i++)
+        t[s[i]] = i;
+
+      var s = gantt.locale.date.month_full;
+      var t = gantt.locale.date.month_full_hash = {};
+      for (var i = 0; i < s.length; i++)
+        t[s[i]] = i;
+    },
+    _startOf: function (date, unit) {
+      var momentDate = this.toMoment(date);
+      momentDate.startOf(unit);
+      return this.fromMoment(date, momentDate);
+    },
+    date_part: function (date) {
+      return this._startOf(date, "day");
+    },
+    time_part: function (date) {
+      alert("Forbidden function");
+      return (date.valueOf() / 1000 - date.getTimezoneOffset() * 60) % 86400;
+    },
+    week_start: function (date) {
+      return this._startOf(date, "isoweek");
+    },
+    month_start: function (date) {
+      return this._startOf(date, "month");
+    },
+    year_start: function (date) {
+      return this._startOf(date, "year");
+    },
+    day_start: function (date) {
+      return this._startOf(date, "day");
+    },
+    hour_start: function (date) {
+      alert("Forbidden function");
+      if (date.getMinutes())
+        date.setMinutes(0);
+      this.minute_start(date);
+
+      return date;
+    },
+    minute_start: function (date) {
+      alert("Forbidden function");
+      if (date.getSeconds())
+        date.setSeconds(0);
+      if (date.getMilliseconds())
+        date.setMilliseconds(0);
+      return date;
+    },
+    /*_add_days: function (date, inc) {
+     var ndate = new Date(date.valueOf());
+
+     ndate.setDate(ndate.getDate() + inc);
+     if (inc >= 0 && (!date.getHours() && ndate.getHours()) && //shift to yesterday on dst
+     (ndate.getDate() < date.getDate() || ndate.getMonth() < date.getMonth() || ndate.getFullYear() < date.getFullYear()))
+     ndate.setTime(ndate.getTime() + 60 * 60 * 1000 * (24 - ndate.getHours()));
+     return ndate;
+     },*/
+    add: function (date, inc, mode) {
+      var momentDate = this.toMoment(date);
+      momentDate.add(inc, mode + "s");
+      return momentDate.toDate();
+    },
+    to_fixed: function (num) {
+      if (num < 10)
+        return "0" + num;
+      return num;
+    },
+    copy: function (date) {
+      var momentDate = moment(date);
+      if (date._isAMomentObject) {
+        return momentDate;
+      }
+      return momentDate.toDate();
+      //return new Date(date.valueOf());
+    },
+    date_to_str: function (format, utc) {
+      ysy.log.debug("date_to_str " + format, "date_format");
+      format = format.replace(/%[a-zA-Z]/g, function (a) {
+        switch (a) {
+          case "%d":
+            return "\"+gantt.date.to_fixed(date.getDate())+\"";
+          case "%m":
+            return "\"+gantt.date.to_fixed((date.getMonth()+1))+\"";
+          case "%j":
+            return "\"+date.getDate()+\"";
+          case "%n":
+            return "\"+(date.getMonth()+1)+\"";
+          case "%y":
+            return "\"+gantt.date.to_fixed(date.getFullYear()%100)+\"";
+          case "%Y":
+            return "\"+date.getFullYear()+\"";
+          case "%D":
+            return "\"+gantt.locale.date.day_short[date.getDay()]+\"";
+          case "%l":
+            return "\"+gantt.locale.date.day_full[date.getDay()]+\"";
+          case "%M":
+            return "\"+gantt.locale.date.month_short[date.getMonth()]+\"";
+          case "%F":
+            return "\"+gantt.locale.date.month_full[date.getMonth()]+\"";
+          case "%h":
+            return "\"+gantt.date.to_fixed((date.getHours()+11)%12+1)+\"";
+          case "%g":
+            return "\"+((date.getHours()+11)%12+1)+\"";
+          case "%G":
+            return "\"+date.getHours()+\"";
+          case "%H":
+            return "\"+gantt.date.to_fixed(date.getHours())+\"";
+          case "%i":
+            return "\"+gantt.date.to_fixed(date.getMinutes())+\"";
+          case "%a":
+            return "\"+(date.getHours()>11?\"pm\":\"am\")+\"";
+          case "%A":
+            return "\"+(date.getHours()>11?\"PM\":\"AM\")+\"";
+          case "%s":
+            return "\"+gantt.date.to_fixed(date.getSeconds())+\"";
+          case "%W":
+            return "\"+gantt.date.to_fixed(gantt.date.getISOWeek(date))+\"";
+          default:
+            return a;
+        }
+      });
+      if (utc)
+        format = format.replace(/date\.get/g, "date.getUTC");
+      return new Function("date", "return \"" + format + "\";");
+    },
+    str_to_date: function (format, utc) {
+      ysy.log.debug("str_to_date " + format, "date_format");
+      var splt = "var temp=date.match(/[a-zA-Z]+|[0-9]+/g);";
+      var mask = format.match(/%[a-zA-Z]/g);
+      for (var i = 0; i < mask.length; i++) {
+        switch (mask[i]) {
+          case "%j":
+          case "%d":
+            splt += "set[2]=temp[" + i + "]||1;";
+            break;
+          case "%n":
+          case "%m":
+            splt += "set[1]=(temp[" + i + "]||1)-1;";
+            break;
+          case "%y":
+            splt += "set[0]=temp[" + i + "]*1+(temp[" + i + "]>50?1900:2000);";
+            break;
+          case "%g":
+          case "%G":
+          case "%h":
+          case "%H":
+            splt += "set[3]=temp[" + i + "]||0;";
+            break;
+          case "%i":
+            splt += "set[4]=temp[" + i + "]||0;";
+            break;
+          case "%Y":
+            splt += "set[0]=temp[" + i + "]||0;";
+            break;
+          case "%a":
+          case "%A":
+            splt += "set[3]=set[3]%12+((temp[" + i + "]||'').toLowerCase()=='am'?0:12);";
+            break;
+          case "%s":
+            splt += "set[5]=temp[" + i + "]||0;";
+            break;
+          case "%M":
+            splt += "set[1]=gantt.locale.date.month_short_hash[temp[" + i + "]]||0;";
+            break;
+          case "%F":
+            splt += "set[1]=gantt.locale.date.month_full_hash[temp[" + i + "]]||0;";
+            break;
+          default:
+            break;
+        }
+      }
+      var code = "set[0],set[1],set[2],set[3],set[4],set[5]";
+      if (utc)
+        code = " Date.UTC(" + code + ")";
+      return new Function("date", "var set=[0,0,1,0,0,0]; " + splt + " return new Date(" + code + ");");
+    },
+    getISOWeek: function (ndate) {
+      if (!ndate)
+        return false;
+      var mom = this.toMoment(ndate);
+      return mom.isoWeek();
+    },
+    getUTCISOWeek: function (ndate) {
+      return this.getISOWeek(ndate);
+    },
+    convert_to_utc: function (date) {
+      var mom = this.toMoment(date);
+      mom.utc();
+      return this.fromMoment(date, mom);
+
+      //return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
+    },
+    parseDate: function (date, format) {
+      if (typeof (date) == "string") {
+        ysy.log.debug("parseDate() " + date + " " + format, "date_format");
+        if (dhtmlx.defined(format)) {
+          if (typeof (format) == "string")
+            format = dhtmlx.defined(gantt.templates[format]) ? gantt.templates[format] : gantt.date.str_to_date(format);
+          else
+            format = gantt.templates.xml_date;
+        }
+        if (date)
+          date = format(date);
+        else
+          date = null;
+      }
+      return this.toMoment(date);
+    }
+  };
+  ysy.view.initNonworkingDays();
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8929d418f0ccd11f6caa85fc02977bfb728ca05
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt.js
@@ -0,0 +1,9439 @@
+/*
+@license
+
+dhtmlxGantt v.3.2.1 Stardard
+This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
+
+(c) Dinamenta, UAB.
+*/
+if (typeof(window.dhx4) == "undefined") {
+
+  window.dhx4 = {
+
+    version: "4.1.3",
+
+    skin: null, // allow to be set by user
+
+    skinDetect: function(comp) {
+      return {10:"dhx_skyblue",20:"dhx_web",30:"dhx_terrace"}[this.readFromCss(comp+"_skin_detect")]||null;
+    },
+
+    // read value from css
+    readFromCss: function(className, property) {
+      var t = document.createElement("DIV");
+      t.className = className;
+      if (document.body.firstChild != null) document.body.insertBefore(t, document.body.firstChild); else document.body.appendChild(t);
+      var w = t[property||"offsetWidth"];
+      t.parentNode.removeChild(t);
+      t = null;
+      return w;
+    },
+
+    // id manager
+    lastId: 1,
+    newId: function() {
+      return this.lastId++;
+    },
+
+    // z-index manager
+    zim: {
+      data: {},
+      step: 5,
+      first: function() {
+        return 100;
+      },
+      last: function() {
+        var t = this.first();
+        for (var a in this.data) t = Math.max(t, this.data[a]);
+        return t;
+      },
+      reserve: function(id) {
+        this.data[id] = this.last()+this.step;
+        return this.data[id];
+      },
+      clear: function(id) {
+        if (this.data[id] != null) {
+          this.data[id] = null;
+          delete this.data[id];
+        }
+      }
+    },
+
+    // string to boolean
+    s2b: function(r) {
+      if (typeof(r) == "string") r = r.toLowerCase();
+      return (r == true || r == 1 || r == "true" || r == "1" || r == "yes" || r == "y");
+    },
+
+    // string to json
+    s2j: function(s) {
+      var obj = null;
+      dhx4.temp = null;
+      try { eval("dhx4.temp="+s); } catch(e) { dhx4.temp = null; }
+      obj = dhx4.temp;
+      dhx4.temp = null;
+      return obj;
+    },
+
+    // absolute top/left position on screen
+    absLeft: function(obj) {
+      if (typeof(obj) == "string") obj = document.getElementById(obj);
+      return this.getOffset(obj).left;
+    },
+    absTop: function(obj) {
+      if (typeof(obj) == "string") obj = document.getElementById(obj);
+      return this.getOffset(obj).top;
+    },
+    _aOfs: function(elem) {
+      var top = 0, left = 0;
+      while (elem) {
+        top = top + parseInt(elem.offsetTop);
+        left = left + parseInt(elem.offsetLeft);
+        elem = elem.offsetParent;
+      }
+      return {top: top, left: left};
+    },
+    _aOfsRect: function(elem) {
+      var box = elem.getBoundingClientRect();
+      var body = document.body;
+      var docElem = document.documentElement;
+      var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
+      var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
+      var clientTop = docElem.clientTop || body.clientTop || 0;
+      var clientLeft = docElem.clientLeft || body.clientLeft || 0;
+      var top  = box.top +  scrollTop - clientTop;
+      var left = box.left + scrollLeft - clientLeft;
+      return { top: Math.round(top), left: Math.round(left) };
+    },
+    getOffset: function(elem) {
+      if (elem.getBoundingClientRect) {
+        return this._aOfsRect(elem);
+      } else {
+        return this._aOfs(elem);
+      }
+    },
+
+    // copy obj
+    _isObj: function(k) {
+      return (k != null && typeof(k) == "object" && typeof(k.length) == "undefined");
+    },
+    _copyObj: function(r) {
+      if (this._isObj(r)) {
+        var t = {};
+        for (var a in r) {
+          if (typeof(r[a]) == "object" && r[a] != null) t[a] = this._copyObj(r[a]); else t[a] = r[a];
+        }
+      } else {
+        var t = [];
+        for (var a=0; a<r.length; a++) {
+          if (typeof(r[a]) == "object" && r[a] != null) t[a] = this._copyObj(r[a]); else t[a] = r[a];
+        }
+      }
+      return t;
+    },
+
+    // screen dim
+    screenDim: function() {
+      var isIE = (navigator.userAgent.indexOf("MSIE") >= 0);
+      var dim = {};
+      dim.left = document.body.scrollLeft;
+      dim.right = dim.left+(window.innerWidth||document.body.clientWidth);
+      dim.top = Math.max((isIE?document.documentElement:document.getElementsByTagName("html")[0]).scrollTop, document.body.scrollTop);
+      dim.bottom = dim.top+(isIE?Math.max(document.documentElement.clientHeight||0,document.documentElement.offsetHeight||0):window.innerHeight);
+      return dim;
+    },
+
+    // input/textarea range selection
+    selectTextRange: function(inp, start, end) {
+
+      inp = (typeof(inp)=="string"?document.getElementById(inp):inp);
+
+      var len = inp.value.length;
+      start = Math.max(Math.min(start, len), 0);
+      end = Math.min(end, len);
+
+      if (inp.setSelectionRange) {
+        try {inp.setSelectionRange(start, end);} catch(e){}; // combo in grid under IE requires try/catch
+      } else if (inp.createTextRange) {
+        var range = inp.createTextRange();
+        range.moveStart("character", start);
+        range.moveEnd("character", end-len);
+        try {range.select();} catch(e){};
+      }
+    },
+    // transition
+    transData: null,
+    transDetect: function() {
+
+      if (this.transData == null) {
+
+        this.transData = {transProp: false, transEv: null};
+
+        // transition, MozTransition, WebkitTransition, msTransition, OTransition
+        var k = {
+          "MozTransition": "transitionend",
+          "WebkitTransition": "webkitTransitionEnd",
+          "OTransition": "oTransitionEnd",
+          "msTransition": "transitionend",
+          "transition": "transitionend"
+        };
+
+        for (var a in k) {
+          if (this.transData.transProp == false && document.documentElement.style[a] != null) {
+            this.transData.transProp = a;
+            this.transData.transEv = k[a];
+          }
+        }
+        k = null;
+      }
+
+      return this.transData;
+
+    },
+
+    // xml parser
+    _xmlNodeValue: function(node) {
+      var value = "";
+      for (var q=0; q<node.childNodes.length; q++) {
+        value += (node.childNodes[q].nodeValue!=null?node.childNodes[q].nodeValue.toString().replace(/^[\n\r\s]{0,}/,"").replace(/[\n\r\s]{0,}$/,""):"");
+      }
+      return value;
+    }
+
+  };
+
+  // browser
+  window.dhx4.isIE = (navigator.userAgent.indexOf("MSIE") >= 0 || navigator.userAgent.indexOf("Trident") >= 0);
+  window.dhx4.isIE6 = (window.XMLHttpRequest == null && navigator.userAgent.indexOf("MSIE") >= 0);
+  window.dhx4.isIE7 = (navigator.userAgent.indexOf("MSIE 7.0") >= 0 && navigator.userAgent.indexOf("Trident") < 0);
+  window.dhx4.isIE8 = (navigator.userAgent.indexOf("MSIE 8.0") >= 0 && navigator.userAgent.indexOf("Trident") >= 0);
+  window.dhx4.isOpera = (navigator.userAgent.indexOf("Opera") >= 0);
+  window.dhx4.isChrome = (navigator.userAgent.indexOf("Chrome") >= 0);
+  window.dhx4.isKHTML = (navigator.userAgent.indexOf("Safari") >= 0 || navigator.userAgent.indexOf("Konqueror") >= 0);
+  window.dhx4.isFF = (navigator.userAgent.indexOf("Firefox") >= 0);
+  window.dhx4.isIPad = (navigator.userAgent.search(/iPad/gi) >= 0);
+};
+
+
+
+/*
+if (typeof(window.dhx4.ajax) == "undefined") {
+
+  window.dhx4.ajax = {
+
+    // if false - dhxr param will added to prevent caching on client side (default),
+    // if true - do not add extra params
+    cache: false,
+
+    // default method for load/loadStruct, post/get allowed
+    // get - since 4.1.1, this should fix 412 error for macos safari
+    method: "get",
+
+    parse: function(data) {
+      if (typeof data !== "string") return data;
+
+      data = data.replace(/^[\s]+/,"");
+      if (window.DOMParser && !dhx4.isIE) { // ff,ie9
+        var obj = (new window.DOMParser()).parseFromString(data, "text/xml");
+      } else if (window.ActiveXObject !== window.undefined) {
+        var obj = new window.ActiveXObject("Microsoft.XMLDOM");
+        obj.async = "false";
+        obj.loadXML(data);
+      }
+      return obj;
+    },
+    xmltop: function(tagname, xhr, obj) {
+      if (typeof xhr.status == "undefined" || xhr.status < 400) {
+        var xml = (!xhr.responseXML) ? dhx4.ajax.parse(xhr.responseText || xhr) : (xhr.responseXML || xhr);
+        if (xml && xml.documentElement !== null && !xml.getElementsByTagName("parsererror").length) {
+          return xml.getElementsByTagName(tagname)[0];
+        }
+      }
+      if (obj !== -1) dhx4.callEvent("onLoadXMLError",["Incorrect XML", arguments[1], obj]);
+      return document.createElement("DIV");
+    },
+    xpath: function(xpathExp, docObj) {
+      if (!docObj.nodeName) docObj = docObj.responseXML || docObj;
+      if (dhx4.isIE) {
+        return docObj.selectNodes(xpathExp)||[];
+      } else {
+        var rows = [];
+        var first;
+        var col = (docObj.ownerDocument||docObj).evaluate(xpathExp, docObj, null, XPathResult.ANY_TYPE, null);
+        while (first = col.iterateNext()) rows.push(first);
+        return rows;
+      }
+    },
+    query: function(config) {
+      dhx4.ajax._call(
+        (config.method || "GET"),
+        config.url,
+        config.data || "",
+        (config.async || true),
+        config.callback,
+        null,
+        config.headers
+      );
+    },
+    get: function(url, onLoad) {
+      this._call("GET", url, null, true, onLoad);
+    },
+    getSync: function(url) {
+      return this._call("GET", url, null, false);
+    },
+    put: function(url, postData, onLoad) {
+      this._call("PUT", url, postData, true, onLoad);
+    },
+    del: function(url, postData, onLoad) {
+      this._call("DELETE", url, postData, true, onLoad);
+    },
+    post: function(url, postData, onLoad) {
+      if (arguments.length == 1) {
+        postData = "";
+      } else if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]) == "function")) {
+        onLoad = postData;
+        postData = "";
+      } else {
+        postData = String(postData);
+      }
+      this._call("POST", url, postData, true, onLoad);
+    },
+    postSync: function(url, postData) {
+      postData = (postData == null ? "" : String(postData));
+      return this._call("POST", url, postData, false);
+    },
+    getLong: function(url, onLoad) {
+      this._call("GET", url, null, true, onLoad, {url:url});
+    },
+    postLong: function(url, postData, onLoad) {
+      if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]))) {
+        onLoad = postData;
+        postData = "";
+      }
+      this._call("POST", url, postData, true, onLoad, {url:url, postData:postData});
+    },
+    _call: function(method, url, postData, async, onLoad, longParams, headers) {
+
+      var t = (window.XMLHttpRequest && !dhx4.isIE ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
+      var isQt = (navigator.userAgent.match(/AppleWebKit/) != null && navigator.userAgent.match(/Qt/) != null && navigator.userAgent.match(/Safari/) != null);
+
+      if (async == true) {
+        t.onreadystatechange = function() {
+          if ((t.readyState == 4) || (isQt == true && t.readyState == 3)) { // what for long response and status 404?
+            if (t.status != 200 || t.responseText == "")
+              if (!dhx4.callEvent("onAjaxError", [t])) return;
+
+            window.setTimeout(function(){
+              if (typeof(onLoad) == "function") {
+                onLoad.apply(window, [{xmlDoc:t}]); // dhtmlx-compat, response.xmlDoc.responseXML/responseText
+              }
+              if (longParams != null) {
+                if (typeof(longParams.postData) != "undefined") {
+                  dhx4.ajax.postLong(longParams.url, longParams.postData, onLoad);
+                } else {
+                  dhx4.ajax.getLong(longParams.url, onLoad);
+                }
+              }
+              onLoad = null;
+              t = null;
+            },1);
+          }
+        }
+      }
+
+      if (method == "GET" && this.cache != true) {
+        url += (url.indexOf("?")>=0?"&":"?")+"dhxr"+new Date().getTime()+"=1";
+      }
+
+      t.open(method, url, async);
+
+      if (headers){
+        for (var key in headers)
+          t.setRequestHeader(key, headers[key]);
+      } else if (method.toUpperCase() == "POST" || method == "PUT" || method == "DELETE") {
+        t.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+      } else if (method == "GET") {
+        postData = null;
+      }
+
+      t.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+      t.send(postData);
+
+      if (!async) return {xmlDoc:t}; // dhtmlx-compat, response.xmlDoc.responseXML/responseText
+
+    }
+  };
+
+};
+*/
+
+if (typeof(window.dhx4._eventable) == "undefined") {
+
+  window.dhx4._eventable = function(obj, mode) {
+
+    if (mode == "clear") {
+
+      obj.detachAllEvents();
+
+      obj.dhxevs = null;
+
+      obj.attachEvent = null;
+      obj.detachEvent = null;
+      obj.checkEvent = null;
+      obj.callEvent = null;
+      obj.detachAllEvents = null;
+
+      obj = null;
+
+      return;
+
+    }
+
+    obj.dhxevs = { data: {} };
+
+    obj.attachEvent = function(name, func) {
+      name = String(name).toLowerCase();
+      if (!this.dhxevs.data[name]) this.dhxevs.data[name] = {};
+      var eventId = window.dhx4.newId();
+      this.dhxevs.data[name][eventId] = func;
+      return eventId;
+    }
+
+    obj.detachEvent = function(eventId) {
+      for (var a in this.dhxevs.data) {
+        var k = 0;
+        for (var b in this.dhxevs.data[a]) {
+          if (b == eventId) {
+            this.dhxevs.data[a][b] = null;
+            delete this.dhxevs.data[a][b];
+          } else {
+            k++;
+          }
+        }
+        if (k == 0) {
+          this.dhxevs.data[a] = null;
+          delete this.dhxevs.data[a];
+        }
+      }
+    }
+
+    obj.checkEvent = function(name) {
+      name = String(name).toLowerCase();
+      return (this.dhxevs.data[name] != null);
+    }
+
+    obj.callEvent = function(name, params) {
+      name = String(name).toLowerCase();
+      if (this.dhxevs.data[name] == null) return true;
+      var r = true;
+      for (var a in this.dhxevs.data[name]) {
+        r = this.dhxevs.data[name][a].apply(this, params) && r;
+      }
+      return r;
+    }
+
+    obj.detachAllEvents = function() {
+      for (var a in this.dhxevs.data) {
+        for (var b in this.dhxevs.data[a]) {
+          this.dhxevs.data[a][b] = null;
+          delete this.dhxevs.data[a][b];
+        }
+        this.dhxevs.data[a] = null;
+        delete this.dhxevs.data[a];
+      }
+    }
+
+    obj = null;
+  };
+
+  dhx4._eventable(dhx4);
+
+};
+
+if (typeof(window.dhtmlx) == "undefined") {
+  window.dhtmlx={
+    extend:function(a, b){
+      for (var key in b)
+        if (!a[key])
+          a[key]=b[key];
+      return a;
+    },
+    extend_api:function(name,map,ext){
+      var t = window[name];
+      if (!t) return; //component not defined
+      window[name]=function(obj){
+        if (obj && typeof obj == "object" && !obj.tagName){
+          var that = t.apply(this,(map._init?map._init(obj):arguments));
+          //global settings
+          for (var a in dhtmlx)
+            if (map[a]) this[map[a]](dhtmlx[a]);
+          //local settings
+          for (var a in obj){
+            if (map[a]) this[map[a]](obj[a]);
+            else if (a.indexOf("on")===0){
+              this.attachEvent(a,obj[a]);
+            }
+          }
+        } else
+          var that = t.apply(this,arguments);
+        if (map._patch) map._patch(this);
+        return that||this;
+      };
+      window[name].prototype=t.prototype;
+      if (ext)
+        dhtmlx.extend(window[name].prototype,ext);
+    },
+    url:function(str){
+      if (str.indexOf("?") != -1)
+        return "&";
+      else
+        return "?";
+    }
+  };
+};
+
+ _isFF = false;
+ _isIE = false;
+ _isOpera = false;
+ _isKHTML = false;
+ _isMacOS = false;
+ _isChrome = false;
+ _FFrv = false;
+ _KHTMLrv = false;
+ _OperaRv = false;
+
+if (navigator.userAgent.indexOf('Macintosh') != -1)
+  _isMacOS=true;
+
+
+if (navigator.userAgent.toLowerCase().indexOf('chrome')>-1)
+  _isChrome=true;
+
+if ((navigator.userAgent.indexOf('Safari') != -1)||(navigator.userAgent.indexOf('Konqueror') != -1)){
+   _KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari')+7, 5));
+
+  if (_KHTMLrv > 525){ //mimic FF behavior for Safari 3.1+
+    _isFF=true;
+     _FFrv = 1.9;
+  } else
+    _isKHTML=true;
+} else if (navigator.userAgent.indexOf('Opera') != -1){
+  _isOpera=true;
+  _OperaRv=parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera')+6, 3));
+}
+
+
+else if (navigator.appName.indexOf("Microsoft") != -1){
+  _isIE=true;
+  if ((navigator.appVersion.indexOf("MSIE 8.0")!= -1 ||
+     navigator.appVersion.indexOf("MSIE 9.0")!= -1 ||
+     navigator.appVersion.indexOf("MSIE 10.0")!= -1 ||
+     document.documentMode > 7) &&
+      document.compatMode != "BackCompat"){
+    _isIE=8;
+  }
+} else if (navigator.appName  == 'Netscape' && navigator.userAgent.indexOf("Trident") != -1){
+  //ie11
+  _isIE=8;
+} else {
+  _isFF=true;
+   _FFrv = parseFloat(navigator.userAgent.split("rv:")[1])
+}
+
+if (typeof(window.dhtmlxEvent) == "undefined") {
+
+  function dhtmlxEvent(el, event, handler){
+    if (el.addEventListener)
+      el.addEventListener(event, handler, false);
+
+    else if (el.attachEvent)
+      el.attachEvent("on"+event, handler);
+  }
+};
+
+if (dhtmlxEvent.touchDelay == null) {
+  dhtmlxEvent.touchDelay = 2000;
+};
+
+if (typeof(dhtmlxEvent.initTouch) == "undefined") {
+
+  dhtmlxEvent.initTouch = function(){
+    var longtouch;
+    var target;
+    var tx, ty;
+
+    dhtmlxEvent(document.body, "touchstart", function(ev){
+      target = ev.touches[0].target;
+      tx = ev.touches[0].clientX;
+      ty = ev.touches[0].clientY;
+      longtouch = window.setTimeout(touch_event, dhtmlxEvent.touchDelay);
+    });
+    function touch_event(){
+      if (target){
+        var ev = document.createEvent("HTMLEvents"); // for chrome and firefox
+        ev.initEvent("dblclick", true, true);
+        target.dispatchEvent(ev);
+        longtouch = target = null;
+      }
+    };
+    dhtmlxEvent(document.body, "touchmove", function(ev){
+      if (longtouch){
+        if (Math.abs(ev.touches[0].clientX - tx) > 50 || Math.abs(ev.touches[0].clientY - ty) > 50 ){
+          window.clearTimeout(longtouch);
+          longtouch = target = false;
+        }
+      }
+    });
+    dhtmlxEvent(document.body, "touchend", function(ev){
+      if (longtouch){
+        window.clearTimeout(longtouch);
+        longtouch = target = false;
+      }
+    });
+
+    dhtmlxEvent.initTouch = function(){};
+  };
+};
+
+if(!window.dhtmlx)
+  window.dhtmlx = {};
+
+(function(){
+  var _dhx_msg_cfg = null;
+  function callback(config, result){
+      var usercall = config.callback;
+      modality(false);
+      config.box.parentNode.removeChild(config.box);
+      _dhx_msg_cfg = config.box = null;
+      if (usercall)
+        usercall(result);
+  }
+  function modal_key(e){
+    if (_dhx_msg_cfg){
+      e = e||event;
+      var code = e.which||event.keyCode;
+      if (dhtmlx.message.keyboard){
+        if (code == 13 || code == 32)
+          callback(_dhx_msg_cfg, true);
+        if (code == 27)
+          callback(_dhx_msg_cfg, false);
+      }
+      if (e.preventDefault)
+        e.preventDefault();
+      return !(e.cancelBubble = true);
+    }
+  }
+  if (document.attachEvent)
+    document.attachEvent("onkeydown", modal_key);
+  else
+    document.addEventListener("keydown", modal_key, true);
+
+  function modality(mode){
+    if(!modality.cover){
+      modality.cover = document.createElement("DIV");
+      //necessary for IE only
+      modality.cover.onkeydown = modal_key;
+      modality.cover.className = "dhx_modal_cover";
+      document.body.appendChild(modality.cover);
+    }
+    var height =  document.body.scrollHeight;
+    modality.cover.style.display = mode?"inline-block":"none";
+  }
+
+  function button(text, result){
+    var button_css = "dhtmlx_"+text.toLowerCase().replace(/ /g, "_")+"_button"; // dhtmlx_ok_button, dhtmlx_click_me_button
+    return "<div class='dhtmlx_popup_button "+button_css+"' result='"+result+"' ><div>"+text+"</div></div>";
+  }
+
+  function info(text){
+    if (!t.area){
+      t.area = document.createElement("DIV");
+      t.area.className = "dhtmlx_message_area";
+      t.area.style[t.position]="5px";
+      document.body.appendChild(t.area);
+    }
+
+    t.hide(text.id);
+    var message = document.createElement("DIV");
+    message.innerHTML = "<div>"+text.text+"</div>";
+    //message.className = "dhtmlx-info dhtmlx-" + text.type;
+    message.className = "dhtmlx-message dhtmlx-" + text.type;  // HOSEK
+    message.onclick = function(){
+      t.hide(text.id);
+      text = null;
+    };
+
+    if (t.position == "bottom" && t.area.firstChild)
+      t.area.insertBefore(message,t.area.firstChild);
+    else
+      t.area.appendChild(message);
+
+    if (text.expire > 0)
+      t.timers[text.id]=window.setTimeout(function(){
+        t.hide(text.id);
+      }, text.expire);
+
+    t.pull[text.id] = message;
+    message = null;
+
+    return text.id;
+  }
+  function _boxStructure(config, ok, cancel){
+    var box = document.createElement("DIV");
+    box.className = " dhtmlx_modal_box dhtmlx-"+config.type;
+    box.setAttribute("dhxbox", 1);
+
+    var inner = '';
+
+    if (config.width)
+      box.style.width = config.width;
+    if (config.height)
+      box.style.height = config.height;
+    if (config.title)
+      inner+='<div class="dhtmlx_popup_title">'+config.title+'</div>';
+    inner+='<div class="dhtmlx_popup_text"><span>'+(config.content?'':config.text)+'</span></div><div  class="dhtmlx_popup_controls">';
+    if (ok)
+      inner += button(config.ok || "OK", true);
+    if (cancel)
+      inner += button(config.cancel || "Cancel", false);
+    if (config.buttons){
+      for (var i=0; i<config.buttons.length; i++)
+        inner += button(config.buttons[i],i);
+    }
+    inner += '</div>';
+    box.innerHTML = inner;
+
+    if (config.content){
+      var node = config.content;
+      if (typeof node == "string")
+        node = document.getElementById(node);
+      if (node.style.display == 'none')
+        node.style.display = "";
+      box.childNodes[config.title?1:0].appendChild(node);
+    }
+
+    box.onclick = function(e){
+      e = e ||event;
+      var source = e.target || e.srcElement;
+      if (!source.className) source = source.parentNode;
+      if (source.className.split(" ")[0] == "dhtmlx_popup_button"){
+        var result = source.getAttribute("result");
+        result = (result == "true")||(result == "false"?false:result);
+        callback(config, result);
+      }
+    };
+    config.box = box;
+    if (ok||cancel)
+      _dhx_msg_cfg = config;
+
+    return box;
+  }
+  function _createBox(config, ok, cancel){
+    var box = config.tagName ? config : _boxStructure(config, ok, cancel);
+
+    if (!config.hidden)
+      modality(true);
+    document.body.appendChild(box);
+    var x = Math.abs(Math.floor(((window.innerWidth||document.documentElement.offsetWidth) - box.offsetWidth)/2));
+    var y = Math.abs(Math.floor(((window.innerHeight||document.documentElement.offsetHeight) - box.offsetHeight)/2));
+    if (config.position == "top")
+      box.style.top = "-3px";
+    else
+      box.style.top = y+'px';
+    box.style.left = x+'px';
+    //necessary for IE only
+    box.onkeydown = modal_key;
+
+    box.focus();
+    if (config.hidden)
+      dhtmlx.modalbox.hide(box);
+
+    return box;
+  }
+
+  function alertPopup(config){
+    return _createBox(config, true, false);
+  }
+  function confirmPopup(config){
+    return _createBox(config, true, true);
+  }
+  function boxPopup(config){
+    return _createBox(config);
+  }
+  function box_params(text, type, callback){
+    if (typeof text != "object"){
+      if (typeof type == "function"){
+        callback = type;
+        type = "";
+      }
+      text = {text:text, type:type, callback:callback };
+    }
+    return text;
+  }
+  function params(text, type, expire, id){
+    if (typeof text != "object")
+      text = {text:text, type:type, expire:expire, id:id};
+    text.id = text.id||t.uid();
+    text.expire = text.expire||t.expire;
+    return text;
+  }
+  dhtmlx.alert = function(){
+    var text = box_params.apply(this, arguments);
+    text.type = text.type || "confirm";
+    return alertPopup(text);
+  };
+  dhtmlx.confirm = function(){
+    var text = box_params.apply(this, arguments);
+    text.type = text.type || "alert";
+    return confirmPopup(text);
+  };
+  dhtmlx.modalbox = function(){
+    var text = box_params.apply(this, arguments);
+    text.type = text.type || "alert";
+    return boxPopup(text);
+  };
+  dhtmlx.modalbox.hide = function(node){
+    while (node && node.getAttribute && !node.getAttribute("dhxbox"))
+      node = node.parentNode;
+    if (node){
+      node.parentNode.removeChild(node);
+      modality(false);
+    }
+  };
+  var t = dhtmlx.message = function(text, type, expire, id){
+    text = params.apply(this, arguments);
+    text.type = text.type||"info";
+
+    var subtype = text.type.split("-")[0];
+    switch (subtype){
+      case "alert":
+        return alertPopup(text);
+      case "confirm":
+        return confirmPopup(text);
+      case "modalbox":
+        return boxPopup(text);
+      default:
+        return info(text);
+    }
+  };
+
+  t.seed = (new Date()).valueOf();
+  t.uid = function(){return t.seed++;};
+  t.expire = 4000;
+  t.keyboard = true;
+  t.position = "top";
+  t.pull = {};
+  t.timers = {};
+
+  t.hideAll = function(){
+    for (var key in t.pull)
+      t.hide(key);
+  };
+  t.hide = function(id){
+    var obj = t.pull[id];
+    if (obj && obj.parentNode){
+      window.setTimeout(function(){
+        obj.parentNode.removeChild(obj);
+        obj = null;
+      },2000);
+      obj.className+=" hidden";
+
+      if(t.timers[id])
+        window.clearTimeout(t.timers[id]);
+      delete t.pull[id];
+    }
+  };
+})();
+gantt = {
+  version:"3.2.1"
+};
+
+/*jsl:ignore*/
+//import from dhtmlxcommon.js
+
+function dhtmlxDetachEvent(el, event, handler){
+    if (el.removeEventListener)
+        el.removeEventListener(event, handler, false);
+
+    else if (el.detachEvent)
+        el.detachEvent("on"+event, handler);
+}
+
+
+/** Overrides event functionality.
+ *  Includes all default methods from dhtmlx.common but adds _silentStart, _silendEnd
+ *   @desc:
+ *   @type: private
+ */
+dhtmlxEventable=function(obj){
+    obj._silent_mode = false;
+    obj._silentStart = function() {
+        this._silent_mode = true;
+    };
+    obj._silentEnd = function() {
+        this._silent_mode = false;
+    };
+  obj.attachEvent=function(name, catcher, callObj){
+    name='ev_'+name.toLowerCase();
+    if (!this[name])
+      this[name]=new this._eventCatcher(callObj||this);
+
+    return(name+':'+this[name].addEvent(catcher)); //return ID (event name & event ID)
+  };
+  obj.callEvent=function(name, arg0){
+        if (this._silent_mode) return true;
+    name='ev_'+name.toLowerCase();
+    if (this[name])
+      return this[name].apply(this, arg0);
+    return true;
+  };
+  obj.checkEvent=function(name){
+    return (!!this['ev_'+name.toLowerCase()]);
+  };
+  obj._eventCatcher=function(obj){
+    var dhx_catch = [];
+    var z = function(){
+      var res = true;
+      for (var i = 0; i < dhx_catch.length; i++){
+        if (dhx_catch[i]){
+          var zr = dhx_catch[i].apply(obj, arguments);
+          res=res&&zr;
+        }
+      }
+      return res;
+    };
+    z.addEvent=function(ev){
+      if (typeof (ev) != "function")
+        ev=eval(ev);
+      if (ev)
+        return dhx_catch.push(ev)-1;
+      return false;
+    };
+    z.removeEvent=function(id){
+      dhx_catch[id]=null;
+    };
+    return z;
+  };
+  obj.detachEvent=function(id){
+    if (id){
+      var list = id.split(':');           //get EventName and ID
+      this[list[0]].removeEvent(list[1]); //remove event
+    }
+  };
+  obj.detachAllEvents = function(){
+    for (var name in this){
+      if (name.indexOf("ev_") === 0)
+        delete this[name];
+    }
+  };
+  obj = null;
+};
+
+
+/*jsl:end*/
+
+dhtmlx.copy = function (object) {
+  var i, t, result; // iterator, types array, result
+
+  if (object && typeof object === "object") {
+    result = {};
+    t = [Array, Date, Number, String, Boolean];
+    for (i = 0; i < t.length; i++) {
+      if (object instanceof t[i])
+        result = i ? new t[i](object) : new t[i](); // first one is array
+    }
+    if(moment.isMoment(object)){
+      result = moment(object);
+      result._isEndDate = object._isEndDate;
+      return result;
+    }
+    for (i in object) {
+      // HOSEK V
+      if (i === "model" || i === "widget" || i === "columns" ) {
+        continue;
+        //result[i]=object[i];
+      }
+      // HOSEK A
+      if (Object.prototype.hasOwnProperty.apply(object, [i])) {
+        result[i] = dhtmlx.copy(object[i]);
+      }
+    }
+  }
+  return result || object;
+};
+
+dhtmlx.mixin = function(target, source, force){
+    for (var f in source)
+        if ((!target[f] || force)) target[f]=source[f];
+    return target;
+};
+
+
+dhtmlx.defined = function(obj) {
+    return typeof(obj) != "undefined";
+};
+
+dhtmlx.uid = function() {
+    if (!this._seed)
+        this._seed = gantt.date.now().valueOf();
+
+    this._seed++;
+    return this._seed;
+};
+
+
+//creates function with specified "this" pointer
+dhtmlx.bind=function(functor, object){
+  if(functor.bind)
+    return functor.bind(object);
+  else
+    return function(){ return functor.apply(object,arguments); };
+};
+
+
+//returns position of html element on the page
+gantt._get_position = function(elem) {
+  var top=0, left=0;
+    if (elem.getBoundingClientRect) { //HTML5 method
+        var box = elem.getBoundingClientRect();
+        var body = document.body;
+        var docElem = document.documentElement;
+        var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
+        var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
+        var clientTop = docElem.clientTop || body.clientTop || 0;
+        var clientLeft = docElem.clientLeft || body.clientLeft || 0;
+        top  = box.top +  scrollTop - clientTop;
+        left = box.left + scrollLeft - clientLeft;
+        return { y: Math.round(top), x: Math.round(left), width:elem.offsetWidth, height:elem.offsetHeight };
+    } else { //fallback to naive approach
+        while(elem) {
+            top = top + parseInt(elem.offsetTop,10);
+            left = left + parseInt(elem.offsetLeft,10);
+            elem = elem.offsetParent;
+        }
+        return { y: top, x: left, width:elem.offsetWidth, height: elem.offsetHeight};
+    }
+};
+
+
+gantt._detectScrollSize = function(){
+    var div = document.createElement("div");
+    div.style.cssText="visibility:hidden;position:absolute;left:-1000px;width:100px;padding:0px;margin:0px;height:110px;min-height:100px;overflow-y:scroll;";
+
+    document.body.appendChild(div);
+    var width = div.offsetWidth-div.clientWidth;
+    document.body.removeChild(div);
+
+    return width;
+};
+
+if (window.dhtmlx){
+
+  if (!dhtmlx.attaches)
+    dhtmlx.attaches = {};
+
+  dhtmlx.attaches.attachGantt=function(start, end, gantt){
+    var obj = document.createElement("DIV");
+    obj.id = "gantt_"+dhtmlx.uid();
+    obj.style.width = "100%";
+    obj.style.height = "100%";
+    obj.cmp = "grid";
+    gantt = gantt || window.gantt;
+    document.body.appendChild(obj);
+    this.attachObject(obj.id);
+
+    var that = this.vs[this.av];
+    that.grid = gantt;
+
+    gantt.init(obj.id, start, end);
+    obj.firstChild.style.border = "none";
+
+    that.gridId = obj.id;
+    that.gridObj = obj;
+
+    var method_name="_viewRestore";
+    return this.vs[this[method_name]()].grid;
+  };
+
+}
+if (typeof(window.dhtmlXCellObject) != "undefined") {
+
+  dhtmlXCellObject.prototype.attachGantt=function(start, end, gantt){
+    var obj = document.createElement("DIV");
+    obj.id = "gantt_"+dhtmlx.uid();
+    obj.style.width = "100%";
+    obj.style.height = "100%";
+    obj.cmp = "grid";
+
+    document.body.appendChild(obj);
+    this.attachObject(obj.id);
+    gantt = gantt || window.gantt;
+    gantt.init(obj.id, start, end);
+    obj.firstChild.style.border = "none";
+    var method_name="_viewRestore";
+    obj = null;
+    this.callEvent("_onContentAttach",[]);
+
+    return this.dataObj;
+  };
+
+}
+
+
+dhtmlxEventable(gantt);
+
+//dhx4.ajax.cache = true;
+
+gantt._click = {};
+gantt._dbl_click = {};
+gantt._context_menu = {};
+gantt._on_click = function(e) {
+    e = e || window.event;
+    var trg = e.target || e.srcElement;
+    var id = gantt.locate(e);
+
+  var res = true;
+  if (id !== null){
+    res = !gantt.checkEvent("onTaskClick") || gantt.callEvent("onTaskClick", [id, e]);
+  }else{
+    res = gantt.callEvent("onEmptyClick", [e]);
+  }
+
+  if(res){
+    var default_action = gantt._find_ev_handler(e, trg, gantt._click, id);
+    if(!default_action)
+      return;
+
+    if(id && gantt.getTask(id) && gantt.config.select_task){
+      if(gantt._selected_task == id){
+        gantt.unselectTask();
+      }else{
+        gantt.selectTask(id);
+      }
+    }
+  }
+
+};
+gantt._on_contextmenu = function(e){
+  e = e || window.event;
+  var src = e.target||e.srcElement,
+    taskId = gantt.locate(src),
+    linkId = gantt.locate(src, gantt.config.link_attribute);
+
+  var res = !gantt.checkEvent("onContextMenu") || gantt.callEvent("onContextMenu", [taskId, linkId, e]);
+  if(!res){
+    if(e.preventDefault)
+      e.preventDefault();
+    else
+      e.returnValue = false;
+  }
+  return res;
+};
+gantt._find_ev_handler = function(e, trg, hash, id){
+  var res = true;
+  while (trg){
+    var css = trg.className || "";
+    if (typeof(css) === "string") {
+      css = css.split(" ");
+      for (var i = 0; i < css.length; i++) {
+        if (!css[i]) continue;
+        if (hash[css[i]]){
+          var handler = hash[css[i]].call(gantt, e, id, trg);
+          res = res && !(typeof handler != "undefined" && handler !== true);
+        }
+      }
+    }
+    trg=trg.parentNode;
+  }
+  return res;
+};
+gantt._on_dblclick = function(e) {
+  e = e || window.event;
+  var trg = e.target || e.srcElement;
+    var id = gantt.locate(e);
+  var res = !gantt.checkEvent("onTaskDblClick") || gantt.callEvent("onTaskDblClick", [id, e]);
+  if(res){
+    var default_action = gantt._find_ev_handler(e, trg, gantt._dbl_click, id);
+    if(!default_action)
+      return;
+
+    if (id !== null && gantt.getTask(id)){
+      if(res && gantt.config.details_on_dblclick){
+        gantt.showLightbox(id);
+      }
+    }
+  }
+};
+
+gantt._on_mousemove = function(e){
+  if (gantt.checkEvent("onMouseMove")){
+      var id = gantt.locate(e);
+      gantt._last_move_event = e;
+    gantt.callEvent("onMouseMove", [id,e]);
+  }
+};
+function dhtmlxDnD(obj, config) {
+    if(config){
+        this._settings = config;
+    }
+    //if($("#gantt_markers").length===0){
+    //	$("body").append($("<div id='gantt_markers' ></div>"));
+    //}
+    dhtmlxEventable(this);
+    dhtmlxEvent(obj, "mousedown", dhtmlx.bind(function(e) {
+        config.original_target = {target : e.target || e.srcElement};
+        this.dragStart(obj, e);
+    }, this));
+
+}
+dhtmlxDnD.prototype = {
+  dragStart: function(obj, e) {
+    this.config = {
+      obj: obj,
+      marker: null,
+      started: false,
+      pos: this.getPosition(e),
+      sensitivity: 4,
+      offset:$(obj).offset()
+    };
+    if(this._settings)
+        dhtmlx.mixin(this.config, this._settings, true);
+    e.preventDefault();
+
+    var mousemove = dhtmlx.bind(function(e) { return this.dragMove(obj, e); }, this);
+    var scroll = dhtmlx.bind(function(e) { return this.dragScroll(obj, e); }, this);
+    var limitation = false;
+    var limited_mousemove = dhtmlx.bind(function(e) {
+      //if(dhtmlx.defined(this.config.updates_per_second)){
+      //    if(!gantt._checkTimeout(this, this.config.updates_per_second))
+      //        return true;
+      //}
+      if(limitation) return;
+      limitation = true;
+      var res = mousemove(e);
+      limitation = false;
+      return res;
+    }, this);
+
+    var mouseup = dhtmlx.bind(function(e) {
+        dhtmlxDetachEvent(document.body, "mousemove", limited_mousemove);
+        dhtmlxDetachEvent(document.body, "mouseup", mouseup);
+        return this.dragEnd(obj);
+    }, this);
+    // initialize dnd marker
+    if(this.config.marker){
+      var marker = this.config.marker = document.createElement("div");
+      marker.className = "gantt_drag_marker";
+      //marker.innerHTML = "Dragging object";
+      document.body.appendChild(marker);
+      //$("#gantt_markers")[0].appendChild(marker);
+      //obj.appendChild(marker);
+    }
+    e.pos = this.getPosition(e);
+    this.actPos= e.pos;   // HOSEK
+    dhtmlxEvent(document.body, "mousemove", limited_mousemove);
+    dhtmlxEvent(document.body, "mouseup", mouseup);
+    // document.body.className += " gantt_noselect";
+    this.callEvent("onDragStart", [obj,e]);
+  },
+  dragMove: function(obj, e) {
+    if (/*!this.config.marker &&*/ !this.config.started) {
+      var pos = this.getPosition(e);
+      var diff_x = pos.x - this.config.pos.x;
+      var diff_y = pos.y - this.config.pos.y;
+      var distance = Math.sqrt(Math.pow(Math.abs(diff_x), 2) + Math.pow(Math.abs(diff_y), 2));
+
+      if (distance > this.config.sensitivity) {
+        // real drag starts here,
+        // when user moves mouse at first time after onmousedown
+        /*if(this.config.started){
+          return;
+        }*/
+        this.config.started = true;
+        this.config.ignore = false;
+        if (this.callEvent("onBeforeDragStart", [obj, this.config.original_target]) === false) {
+          this.config.ignore = true;
+          return true;
+        }
+        this.callEvent("onAfterDragStart", [obj, this.config.original_target]);
+      } else
+        this.config.ignore = true;
+    }
+    if (!this.config.ignore) {
+      e.pos = this.getPosition(e);
+      this.actPos= e.pos;   // HOSEK
+      if(this.config.marker){
+        this.config.marker.style.left = e.pos.x + "px";
+        this.config.marker.style.top = e.pos.y + "px";
+      }
+      this.callEvent("onDragMove", [obj,e]);
+    }
+  },
+
+  dragEnd: function(obj) {
+    if (this.config.marker) {
+      this.destroyMarker();
+    }
+    if(this.config.started&&!this.config.ignore){
+      this.callEvent("onDragEnd", []);
+    }
+    // document.body.className = document.body.className.replace(" gantt_noselect", "");
+  },
+  getDiff: function(e){
+    //var pos=this.getPosition(e);
+    return {x:(this.actPos.x-this.config.pos.x),y:(this.actPos.y-this.config.pos.y)};
+  },
+  getPosition: function(e) {
+    var x = 0, y = 0;
+    e = e || window.event;
+    if (e.pageX || e.pageY) {
+      x = e.pageX;
+      y = e.pageY;
+    } else if (e.clientX || e.clientY) 	{
+      x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
+      y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
+    }
+    return { x:x, y:y };
+  },
+  destroyMarker:function(){
+    this.config.marker.parentNode.removeChild(this.config.marker);
+    this.config.marker = null;
+  },
+  getRelativePos:function(){
+    return {x:(this.actPos.x-this.config.offset.left),y:(this.actPos.y-this.config.offset.top)}
+  }
+};
+gantt._init_grid = function () {
+  this._click.gantt_close = dhtmlx.bind(function (e, id, trg) {
+    this.close(id);
+    return false;
+  }, this);
+  this._click.gantt_open = dhtmlx.bind(function (e, id, trg) {
+    this.open(id);
+    return false;
+  }, this);
+
+
+  this._click.gantt_row = dhtmlx.bind(function (e, id, trg) {
+    if (id !== null) {
+      var task = this.getTask(id);
+      //if(this.config.scroll_on_click)
+      //	this.showDate(task.start_date);
+      this.callEvent("onTaskRowClick", [id, trg]);
+    }
+  }, this);
+
+  this._click.gantt_grid_head_cell = dhtmlx.bind(function (e, id, trg) {
+    var column = trg.getAttribute("column_id");
+
+    if (!this.callEvent("onGridHeaderClick", [column, e]))
+      return;
+
+    if (column == "add") {
+      this._click.gantt_add(e, this.config.root_id);
+    } else if (this.config.sort) {
+      var sort = (this._sort && this._sort.direction && this._sort.name == column) ? this._sort.direction : "desc";
+      // invert sort direction
+      sort = (sort == "desc") ? "asc" : "desc";
+      this._sort = {
+        name: column,
+        direction: sort
+      };
+      this.sort(column, sort == "desc");
+    }
+  }, this);
+
+  if (!this.config.sort && (this.config.order_branch || this.config.rearrange_branch)) {
+    this._init_dnd(this.config.rearrange_branch);
+  }
+
+  this._click.gantt_add = dhtmlx.bind(function (e, id, trg) {
+    if (this.config.readonly) return;
+
+    var item = { };
+    this.createTask(item, id ? id : this.config.root_id);
+
+    return false;
+  }, this);
+
+  if(this._init_resize){
+    this._init_resize();
+  }
+
+};
+
+gantt._render_grid = function () {
+  if (this._is_grid_visible()) {
+    this._calc_grid_width();
+    this._render_grid_header();
+  }
+};
+
+gantt._calc_grid_width = function () {
+  var columns = this.getGridColumns();
+  var cols_width = 0;
+  var unknown = [];
+  var width = [];
+
+  for (var i = 0; i < columns.length; i++) {
+    var v = parseInt(columns[i].width, 10);
+    if (window.isNaN(v)) {
+      v = 50;
+      unknown.push(i);
+    }
+    width[i] = v;
+    cols_width += v;
+  }
+
+  if (this.config.autofit || unknown.length) {
+    var diff = this._get_grid_width() - cols_width;
+    // TODO: logic may be improved for proportional changing of width
+    var step = diff / (unknown.length > 0 ? unknown.length : (width.length > 0 ? width.length : 1));
+    if (unknown.length > 0) {
+      // there are several columns with undefined width
+      var delta = diff / (unknown.length ? unknown.length : 1);
+      for (var i = 0; i < unknown.length; i++) {
+        var index = unknown[i];
+        width[index] += delta;
+      }
+    } else {
+      // delta must be added for all columns
+      var delta = diff / (width.length ? width.length : 1);
+      for (var i = 0; i < width.length; i++)
+        width[i] += delta;
+    }
+
+    for (var i = 0; i < width.length; i++) {
+      columns[i].width = width[i];
+    }
+  }else{
+    this.config.grid_width = cols_width;
+  }
+};
+
+gantt._render_grid_header = function () {
+  var columns = this.getGridColumns();
+  var cells = [];
+  var width = 0,
+    labels = this.locale.labels;
+
+  var lineHeigth = this.config.scale_height - 2;
+
+  for (var i = 0; i < columns.length; i++) {
+    var last = i == columns.length - 1;
+    var col = columns[i];
+    if (last && this._get_grid_width() > width + col.width)
+      col.width = this._get_grid_width() - width;
+    width += col.width;
+    var sort = (this._sort && col.name == this._sort.name) ? ("<div class='gantt_sort gantt_" + this._sort.direction + "'></div>") : "";
+    var cssClass = ["gantt_grid_head_cell",
+      ("gantt_grid_head_" + col.name),
+      (last ? "gantt_last_cell" : ""),
+      this.templates.grid_header_class(col.name, col)].join(" ");
+
+    var style = "width:" + (col.width - (last ? 1 : 0)) + "px;";
+    var label = (col.label || labels["column_" + col.name]);
+    label = label || "";
+    var cell = "<div class='" + cssClass + "' style='" + style + "' column_id='" + col.name + "'>" + label + sort + "</div>";
+    cells.push(cell);
+  }
+  this.$grid_scale.style.height = (this.config.scale_height - 1) + "px";
+  this.$grid_scale.style.lineHeight = lineHeigth + "px";
+  this.$grid_scale.style.width = (width - 1) + "px";
+  this.$grid_scale.innerHTML = cells.join("");
+};
+
+
+gantt._render_grid_item = function (item) {
+  if (!gantt._is_grid_visible())
+    return null;
+  if(!item.columns){
+    return gantt._render_grid_superitem(item);
+  }
+  var columns = this.getGridColumns();
+  var cells = [];
+  var width = 0;
+  for (var i = 0; i < columns.length; i++) {
+    var last = i == columns.length - 1;
+    var col = columns[i];
+    var cell;
+
+    var value;
+    if (col.name == "add") {
+      value = "<div class='gantt_add'></div>";
+    } else {
+      if (col.template)
+        value = col.template(item);
+      else
+        value = item[col.name];
+
+      if (value instanceof Date)
+        value = this.templates.date_grid(value);
+      value = "<div class='gantt_tree_content'>" + value + "</div>";
+    }
+    var css = "gantt_cell" +(col.css?" "+col.css:"")+ (last ? " gantt_last_cell" : "");
+
+    var tree = "";
+    if (col.tree) {
+      for (var j = 0; j < item.$level; j++)
+        tree += this.templates.grid_indent(item);
+
+      var has_child = this._has_children(item.id);
+      if (has_child) {
+        tree += this.templates.grid_open(item);
+        tree += this.templates.grid_folder(item);
+      } else {
+        tree += this.templates.grid_blank(item);
+        tree += this.templates.grid_file(item);
+      }
+    }
+    var style = "width:" + (col.width - (last ? 1 : 0)) + "px;";
+    if (dhtmlx.defined(col.align))
+      style += "text-align:" + col.align + ";";
+    cell = "<div class='" + css + "' style='" + style + "'>" + tree + value + "</div>";
+    cells.push(cell);
+  }
+  var css = item.$index % 2 === 0 ? " odd" : "";
+  css += (item.$transparent) ? " gantt_transparent" : "";
+  if (this.templates.grid_row_class) {
+    var css_template = this.templates.grid_row_class.call(this, item.start_date, item.end_date, item);
+    if (css_template)
+      css += " " + css_template;
+  }
+
+  if (this.getState().selected_task == item.id) {
+    css += " gantt_selected";
+  }
+  var el = document.createElement("div");
+  el.className = "gantt_row" + css;
+  el.setAttribute("data-url","/issues/"+item.id+".json");  // HOSEK
+  el.style.height = this.config.row_height + "px";
+  el.style.lineHeight = (gantt.config.row_height) + "px";
+  el.setAttribute(this.config.task_attribute, item.id);
+  el.innerHTML = cells.join("");
+  return el;
+};
+
+
+gantt.open = function (id) {
+  if (this.callEvent("onBeforeTaskOpened", [id]) === false) return;
+  gantt._set_item_state(id, true);
+  this.callEvent("onTaskOpened", [id]);
+};
+gantt.close = function (id) {
+  gantt._set_item_state(id, false);
+  this.callEvent("onTaskClosed", [id]);
+};
+gantt._set_item_state = function (id, state) {
+  if (id && this._pull[id]) {
+    this._pull[id].$open = state;
+    this.refreshData();
+  }
+};
+
+gantt._is_grid_visible = function () {
+  return (this.config.grid_width && this.config.show_grid);
+};
+gantt._get_grid_width = function () {
+  if (this._is_grid_visible()) {
+    if (this._is_chart_visible()) {
+      return this.config.grid_width;
+    } else {
+      return this._x;
+    }
+  } else {
+    return 0;
+  }
+};
+gantt.getTaskIndex = function (id) {
+  var branch = this.getChildren(this.getParent(id));
+  for (var i = 0; i < branch.length; i++)
+    if (branch[i] == id)
+      return i;
+
+  return -1;
+};
+gantt.getGlobalTaskIndex = function (id) {
+  var branch = this._order;
+  for (var i = 0; i < branch.length; i++)
+    if (branch[i] == id)
+      return i;
+
+  return -1;
+};
+gantt.moveTask = function (sid, tindex, parent) {
+  ysy.log.debug("moveTask","move_task");
+  //target id as 4th parameter
+  var id = arguments[3];
+  if (id) {
+    if (id === sid) return;
+
+    parent = this.getParent(id);
+    tindex = this.getTaskIndex(id);
+  }
+  if(sid == parent){
+    return;
+  }
+  parent = parent || this.config.root_id;
+  var source = this.getTask(sid);
+  var source_pid = this.getParent(source.id);
+  var sbranch = this.getChildren(this.getParent(source.id));
+
+  var tbranch = this.getChildren(parent);
+  if (tindex == -1)
+    tindex = tbranch.length + 1;
+  if (source_pid == parent) {
+    var sindex = this.getTaskIndex(sid);
+    if (sindex == tindex) return;
+  }
+
+
+  /*
+  prevent moving to another sub-branch:
+
+  gantt.attachEvent("onBeforeTaskMove", function(id, parent, tindex){
+   var task = gantt.getTask(id);
+   if(task.parent != parent)
+    return false;
+   return true;
+  });
+  */
+  if(this.callEvent("onBeforeTaskMove", [sid, parent, tindex]) === false)
+    return;
+
+  this._replace_branch_child(source_pid, sid);
+  tbranch = this.getChildren(parent);
+
+  var tid = tbranch[tindex];
+  if (!tid) //adding as last element
+    tbranch.push(sid);
+  else
+    tbranch = tbranch.slice(0, tindex).concat([ sid ]).concat(tbranch.slice(tindex));
+
+  this.setParent(source, parent);
+  this._branches[parent] = tbranch;
+
+  var childTree = this._getTaskTree(sid);
+  for(var i = 0; i < childTree.length; i++){
+    var item = this._pull[childTree[i]];
+    if(item)
+      item.$level = this.calculateTaskLevel(item);
+  }
+
+  if(tindex*1 > 0){
+    if(id){
+      source.$drop_target = (this.getTaskIndex(sid) > this.getTaskIndex(id) ? "next:" : '') + id;
+    }else{
+      source.$drop_target = "next:" + gantt.getPrevSibling(sid);
+    }
+  }else if(tbranch[tindex*1 + 1]){
+    source.$drop_target = tbranch[tindex*1 + 1];
+  }else{
+    source.$drop_target = parent;
+  }
+
+  if(!this.callEvent("onAfterTaskMove", [sid, parent, tindex]))
+    return;
+  source.$level = gantt.calculateTaskLevel(source);
+  this.refreshData();
+
+};
+
+gantt._init_dnd = function (rearrange) {
+  var dnd = new dhtmlxDnD(this.$grid_data, {marker:true,updates_per_second: 60});
+  if (dhtmlx.defined(this.config.dnd_sensitivity))
+    dnd.config.sensitivity = this.config.dnd_sensitivity;
+
+  dnd.attachEvent("onBeforeDragStart", dhtmlx.bind(function (obj, e) {
+    var target = e.target;
+    ysy.log.debug("Grid onBeforeDragStart "+target.className,"move_task");
+    if(!$(target).hasClass("gantt_drag_handle")){
+      return false;
+    }
+    var el = this._locateHTML(e);
+    if (!el) return false;
+    if (this.hideQuickInfo) this._hideQuickInfo();
+
+    var id = this.locate(e);
+
+    var task = gantt.getTask(id);
+
+    if(gantt._is_readonly(task))
+      return false;
+    if(task.type===gantt.config.types.milestone){
+      return false;
+    }
+
+    dnd.config.initial_open_state = task.$open;
+    if (!this.callEvent("onRowDragStart", [id, e.target || e.srcElement, e])) {
+      return false;
+    }
+
+  }, this));
+
+  dnd.attachEvent("onAfterDragStart", dhtmlx.bind(function (obj, e) {
+    var el = this._locateHTML(e);
+    ysy.log.debug("Grid onAfterDragStart "+e.target.className,"move_task");
+    dnd.config.marker.innerHTML = el.outerHTML;
+    dnd.config.id = this.locate(e);
+    var task = this.getTask(dnd.config.id);
+    dnd.config.index = this.getTaskIndex(dnd.config.id);
+    dnd.config.parent = task.parent;
+    task.$open = false;
+    task.$transparent = true;
+    //this.refreshData();
+  }, this));
+
+
+  dnd.lastTaskOfLevel = function (level) {
+    var ids = gantt._order,
+      pull = gantt._pull,
+      last_item = null;
+    for (var i = 0, len = ids.length; i < len; i++) {
+      if (pull[ids[i]].$level == level) {
+        last_item = pull[ids[i]];
+      }
+    }
+    return last_item ? last_item.id : null;
+  };
+  dnd._getGridPos = dhtmlx.bind( function(e){
+    var pos = this._get_position(this.$grid_data);
+
+    // row offset
+    var x = pos.x;
+    var y = e.pos.y - 10;
+
+    // prevent moving row out of grid_data container
+    if (y < pos.y) y = pos.y;
+    if (y > pos.y + this.$grid_data.offsetHeight - this.config.row_height) y = pos.y + this.$grid_data.offsetHeight - this.config.row_height;
+
+    pos.x = x;
+    pos.y = y;
+    return pos;
+  }, this);
+  dnd.attachEvent("onDragMove", dhtmlx.bind(function (obj, e) {
+    ysy.log.debug("Grid onDragMove","move_task");
+    var dd = dnd.config;
+    var pos = dnd._getGridPos(e);
+
+    // setting position of row
+    dd.marker.style.left = pos.x + 10 + "px";
+    dd.marker.style.top = pos.y + "px";
+
+    //previous action might cause page scroll appear thus change position of the gantt, need to recalculate
+    pos = dnd._getGridPos(e);
+
+    var x = pos.x,
+      y = pos.y;
+
+    // highlight row when mouseover
+    var $window = $(window);
+    //var target = document.elementFromPoint(pos.x - document.body.scrollLeft + 1, y - document.body.scrollTop+9);
+    var target = document.elementFromPoint(pos.x - $window.scrollLeft() + 1, y - $window.scrollTop()+9);
+    var el = this.locate(target);
+
+    var item = this.getTask(dnd.config.id);
+    if (!this.isTaskExists(el)) {
+      el = dnd.lastTaskOfLevel(item.$level);
+      if (el == dnd.config.id) {
+        el = null;
+      }
+    }
+
+    if (this.isTaskExists(el)) {
+      var box = gantt._get_position(target);
+      var over = this.getTask(el);
+
+      if(rearrange){  // HOSEK
+        ysy.log.debug("over="+over.id+" "+over.text,"sort_over");
+        if(dnd.config.over!==over){
+          dnd.config.over=over;
+          $(target).closest(".gantt_grid_data").find(".gantt_drag_hover").removeClass("gantt_drag_hover");
+          $(target).closest(".gantt_row").addClass("gantt_drag_hover");
+        }
+        return true;
+      }    // HOSEK
+
+      if (box.y + target.offsetHeight / 2 < y) {
+        //hovering over bottom part of item, check can be drop to bottom
+        var index = this.getGlobalTaskIndex(over.id);
+        var next = this._pull[this._order[index + 1]]; //adds +1 when hovering over placeholder
+        if (next) {
+          if (next.id != item.id)
+            over = next; //there is a valid target
+          else
+            return;
+        } else {
+          //we at end of the list, check and drop at the end of list
+          next = this._pull[this._order[index]];
+          if (next.$level == item.$level && next.id != item.id) {
+            this.moveTask(item.id, -1, this.getParent(next.id));
+
+            return;
+          }
+        }
+      }
+      //if item is on different level, check the one before it
+      var index = this.getGlobalTaskIndex(over.id),
+        prev = this._pull[this._order[index-1]];
+
+      var shift = 1;
+      while((!prev || prev.id == over.id) && index - shift >= 0){
+        prev = this._pull[this._order[index-shift]];
+        shift++;
+      }
+
+      if (item.id == over.id) return;
+      //replacing item under cursor
+      if (over.$level == item.$level && item.id != over.id) {
+        this.moveTask(item.id, 0, 0, over.id);
+
+      }else if(over.$level == item.$level - 1 && !gantt.getChildren(over.id).length){
+        this.moveTask(item.id, 0, over.id);
+
+      } else if(prev && (prev.$level == item.$level) && (item.id != prev.id)){
+        this.moveTask(item.id, -1, this.getParent(prev.id));
+
+      }
+    }
+    return true;
+  }, this));
+
+
+  dnd.attachEvent("onDragEnd", dhtmlx.bind(function () {
+    ysy.log.debug("Grid onDragEnd","move_task");
+    //if(!dnd.config.started){return;}
+    var task = this.getTask(dnd.config.id);
+    if(this.callEvent("onBeforeRowDragEnd",[dnd.config.id, dnd.config.parent, dnd.config.index]) === false) {
+      this.moveTask(dnd.config.id, dnd.config.index, dnd.config.parent);
+      task.$drop_target = null;
+    }else{
+      if(rearrange){    //  HOSEK
+        var over=dnd.config.over;
+        var allowed = gantt.allowedParent(task,over);
+        while(!allowed){
+          if(over.real_id>1000000000000){
+            dhtmlx.message(ysy.view.getLabel("errors2","unsaved_parent"),"error");
+            over = null;
+            break;
+          }
+          var parentID = over.parent;
+          if(parentID === 0){
+            over = null;
+            break;
+          }
+          over = gantt.getTask(parentID);
+          allowed = gantt.allowedParent(task,over);
+        }
+        if(over){
+          if(this.callEvent("onBeforeTaskMove", [task.id, over.id, 0]) === false)
+            return;
+          //this.moveTask(task.id, -1, over.id);
+          task.$drop_target = null;
+          //task.widget.update(task,["fixed_version_id"]);
+          gantt.silentMoveTask(task, over.id);
+          this.callEvent("onAfterTaskMove", [task.id, over.id, 0]);
+          task.widget.update(task);
+        }
+      }                 //  HOSEK
+      this.callEvent("onRowDragEnd", [dnd.config.id, task.$drop_target]);
+    }
+
+    task.$transparent = false;
+    task.$open = dnd.config.initial_open_state;
+    this.refreshData();
+
+  }, this));
+};
+
+/* will be overwriten in order to provide hide/show column functionality in some editions */
+gantt.getGridColumns = function () {
+  return this.config.columns;
+};
+
+
+gantt._has_children = function(id){
+  return this.getChildren(id).length > 0;
+};
+// --#include core/grid_resize.js
+// --#include core/dynamic_loading.js
+// --#include core/grid_column_api.js
+
+gantt._scale_helpers = {
+  getSum : function(sizes, from, to){
+    if(to === undefined)
+      to = sizes.length - 1;
+    if(from === undefined)
+      from = 0;
+
+    var summ = 0;
+    for(var i=from; i <= to; i++)
+      summ += sizes[i];
+
+    return summ;
+  },
+  setSumWidth : function(sum_width, scale, from, to){
+    var parts = scale.width;
+
+    if(to === undefined)
+      to = parts.length - 1;
+    if(from === undefined)
+      from = 0;
+    var length = to - from + 1;
+
+    if(from > parts.length - 1 || length <= 0 || to > parts.length - 1)
+      return;
+
+    var oldWidth = this.getSum(parts, from, to);
+
+    var diff = sum_width - oldWidth;
+
+    this.adjustSize(diff, parts, from, to);
+    this.adjustSize(- diff, parts, to + 1);
+
+    scale.full_width = this.getSum(parts);
+  },
+  splitSize : function(width, count){
+    var arr = [];
+    for(var i=0; i < count; i++) arr[i] = 0;
+
+    this.adjustSize(width, arr);
+    return arr;
+
+  },
+  adjustSize : function(width, parts, from, to){
+    if(!from)
+      from = 0;
+    if(to === undefined)
+      to = parts.length - 1;
+
+    var length = to - from + 1;
+
+    var full = this.getSum(parts, from, to);
+
+    var shared = 0;
+
+    for(var i = from; i <= to; i++){
+      var share = Math.floor(width*(full ? (parts[i]/full) : (1/length)));
+
+      full -= parts[i];
+      width -= share;
+      length--;
+
+      parts[i] += share;
+      shared += share;
+    }
+    parts[parts.length - 1] += width;
+    //parts[parts.length - 1] += width - shared;
+  },
+        // HOSEK V
+  sortScales : function(scales){
+    scales.sort(function(a, b){
+      var durA=moment.duration(a.step,a.unit+"s");
+      var durB=moment.duration(b.step,b.unit+"s")
+      if(durA < durB){
+        return 1;
+      }else if(durA > durB){
+        return -1;
+      }else{
+        return 0;
+      }
+    });
+  },
+  primaryScale : function(){
+
+    gantt._init_template("date_scale");
+
+    return {
+      unit: gantt.config.scale_unit,
+      step: gantt.config.step,
+      template : gantt.templates.date_scale,
+      date : gantt.config.date_scale,
+      css: gantt.templates.scale_cell_class
+    };
+  },
+
+  prepareConfigs : function(scales, min_coll_width, container_width, scale_height){
+    var heights = this.splitSize(scale_height, scales.length);
+    var full_width = container_width;
+
+    var configs = [];
+    for(var i=scales.length-1; i >= 0; i--){
+      var main_scale = (i == scales.length - 1);
+      var cfg = this.initScaleConfig(scales[i]);
+      if(main_scale){
+        this.processIgnores(cfg);
+      }
+
+      this.initColSizes(cfg, min_coll_width, full_width, heights[i]);
+      this.limitVisibleRange(cfg);
+
+      if(main_scale){
+        full_width = cfg.full_width;
+      }
+
+      configs.unshift(cfg);
+    }
+
+
+    for( var i =0; i < configs.length-1; i++){
+      this.alineScaleColumns(configs[configs.length-1], configs[i]);
+    }
+    for(var i = 0; i < configs.length; i++){
+      this.setPosSettings(configs[i]);
+    }
+    return configs;
+
+  },
+  setPosSettings: function(config){
+    for(var i = 0, len = config.trace_x.length; i < len; i++){
+      config.left.push((config.width[i - 1] || 0) + (config.left[i - 1] || 0));
+    }
+  },
+
+  _ignore_time_config : function(date){
+    if(this.config.skip_off_time){
+      return !this.isWorkTime(date);
+    }
+    return false;
+  },
+  //defined in an extension
+  processIgnores : function(config){
+    config.ignore_x = {};
+    config.display_count = config.count;
+  },
+  initColSizes : function(config, min_col_width, full_width, line_height){
+    var cont_width = full_width;
+
+    config.height = line_height;
+
+    var column_count = config.display_count === undefined ? config.count : config.display_count;
+
+    if(!column_count)
+      column_count = 1;
+
+    config.col_width = Math.floor(cont_width/column_count);
+
+    if(min_col_width){
+      if (config.col_width < min_col_width){
+        config.col_width = min_col_width;
+        cont_width = config.col_width * column_count;
+      }
+    }
+    config.width = [];
+    var ignores = config.ignore_x || {};
+    for(var i =0; i < config.trace_x.length; i++){
+      if(ignores[config.trace_x[i].valueOf()] || (config.display_count == config.count)){
+        config.width[i] = 0;
+      }else{
+        config.width[i] = 1;
+      }
+    }
+
+    this.adjustSize(cont_width - this.getSum(config.width)/* 1 width per column from the code above */, config.width);
+    config.full_width = this.getSum(config.width);
+  },
+  initScaleConfig : function(config){
+    var cfg = dhtmlx.mixin({
+      count:0,
+      col_width:0,
+      full_width:0,
+      height:0,
+      width:[],
+      left:[],
+      trace_x:[],
+      date_cache:{}
+    }, config);
+
+    this.eachColumn(config.unit, config.step, function(date){
+      cfg.count++;
+      var dateObj=gantt.date.Date(date);
+      cfg.trace_x.push(dateObj);
+    });
+
+    return cfg;
+  },
+  iterateScales : function(lower_scale, upper_scale, from, to, callback){
+    var upper_dates = upper_scale.trace_x;
+    var lower_dates = lower_scale.trace_x;
+
+    var prev = from || 0;
+    var end = to || (lower_dates.length - 1);
+    var prevUpper = 0;
+    for(var up=1; up < upper_dates.length; up++){
+      for(var next=prev; next <= end; next++){
+        if(+lower_dates[next] == +upper_dates[up]){
+          if(callback){
+            callback.apply(this, [prevUpper, up, prev, next]);
+          }
+          prev = next;
+          prevUpper = up;
+          continue;
+        }
+      }
+    }
+  },
+  alineScaleColumns : function(lower_scale, upper_scale, from, to){
+    this.iterateScales(lower_scale, upper_scale, from, to, function(upper_start, upper_end, lower_start, lower_end){
+      var targetWidth = this.getSum(lower_scale.width, lower_start, lower_end - 1);
+      var actualWidth = this.getSum(upper_scale.width, upper_start, upper_end - 1);
+      if(actualWidth != targetWidth){
+        this.setSumWidth(targetWidth, upper_scale, upper_start, upper_end - 1);
+      }
+
+    });
+  },
+
+  eachColumn : function(unit, step, callback){
+    var start = gantt.date.Date(gantt._min_date),
+      end = gantt.date.Date(gantt._max_date);
+    if(gantt.date[unit + "_start"]){
+      start = gantt.date[unit + "_start"](start);
+    }
+
+    var curr = moment(start).toDate();
+    if(+curr >= +end){
+      end = gantt.date.add(curr, step, unit);
+    }
+    while(+curr < +end){
+      callback.call(this, gantt.date.Date(curr));
+      var tzOffset = curr.getTimezoneOffset();
+      curr = gantt.date.add(curr, step, unit);
+      curr = gantt._correct_dst_change(curr, tzOffset, step, unit);
+      if(gantt.date[unit + '_start'])
+        curr = gantt.date[unit + "_start"](curr);
+    }
+  },
+  limitVisibleRange : function(cfg){
+    var dates = cfg.trace_x;
+
+    var left = 0, right = cfg.width.length-1;
+    var diff = 0;
+    if(+dates[0] < +gantt._min_date && left != right){
+      var width = Math.floor(cfg.width[0] * ((dates[1] - gantt._min_date)/ (dates[1] - dates[0])));
+      diff += cfg.width[0] - width;
+      cfg.width[0] = width;
+
+      dates[0] = moment(gantt._min_date).toDate();
+    }
+
+    var last = dates.length - 1;
+    var lastDate = dates[last];
+    var outDate = gantt.date.add(lastDate, cfg.step, cfg.unit);
+    if(+outDate > +gantt._max_date && last > 0){
+      var width = cfg.width[last] - Math.floor(cfg.width[last] * ((outDate - gantt._max_date)/(outDate - lastDate)));
+      diff += cfg.width[last] - width;
+      cfg.width[last] = width;
+    }
+
+    if(diff){
+      var full = this.getSum(cfg.width);
+      var shared = 0;
+      for(var i =0; i < cfg.width.length; i++){
+        var share = Math.floor(diff*(cfg.width[i]/full));
+        cfg.width[i] += share;
+        shared += share;
+      }
+      this.adjustSize(diff - shared, cfg.width);
+    }
+
+  }
+};
+// --#include core/scales_ignore.js
+gantt._tasks_dnd = {
+  drag : null,
+  _events:{
+    before_start:{},
+    before_finish:{},
+    after_finish:{}
+  },
+  _handlers:{},
+  init:function(){
+    this.clear_drag_state();
+    var drag = gantt.config.drag_mode;
+    this.set_actions();
+
+    var evs = {
+      "before_start":"onBeforeTaskDrag",
+      "before_finish":"onBeforeTaskChanged",
+      "after_finish":"onAfterTaskDrag"
+    };
+    //for now, all drag operations will trigger the same events
+    for(var stage in this._events){
+      for(var mode in drag){
+        this._events[stage][mode] = evs[stage];
+      }
+    }
+
+    this._handlers[drag.move] = this._handlers[drag.move] || this._move;
+    this._handlers[drag.resize] = this._handlers[drag.resize] || this._resize;
+    this._handlers[drag.progress] = this._handlers[drag.progress] || this._resize_progress;
+
+  },
+  set_actions:function(){
+    var data = gantt.$task_data;
+    dhtmlxEvent(data, "mousemove", dhtmlx.bind(function(e){
+      this.on_mouse_move(e||event);
+    }, this));
+    dhtmlxEvent(data, "mousedown", dhtmlx.bind(function(e){
+      this.on_mouse_down(e||event);
+    }, this));
+    dhtmlxEvent(data, "mouseup", dhtmlx.bind(function(e){
+      this.on_mouse_up(e||event);
+    }, this));
+  },
+
+  clear_drag_state : function(){
+    this.drag = {
+      id:null,
+      mode:null,
+      pos:null,
+      start_x:null,
+      start_y:null,
+      obj:null,
+      left:null,
+      last_event:null
+    };
+  },
+  _resize : function(ev, shift, drag){
+    var cfg = gantt.config;
+    var coords_x = this._drag_task_coords(ev, drag);
+    if(drag.left){
+      var old_start = ev.start_date;
+      var start_date = moment(gantt.dateFromPos(coords_x.start + shift.x));
+      gantt.multiStop(ev, start_date);
+      ev.start_date = start_date;
+      if(!ev.start_date){
+        ev.start_date = gantt.date.Date(gantt.getState().min_date);
+      }
+    }else{
+      var old_end = ev.end_date;
+      var end_date = moment(gantt.dateFromPos(coords_x.end + shift.x)).subtract(1, "days");
+      end_date._isEndDate = true;
+      gantt.multiStop(ev, undefined, end_date);
+      ev.end_date = end_date;
+      if(!ev.end_date){
+        ev.end_date = gantt.date.Date(gantt.getState().max_date);
+      }
+      ev.end_date._isEndDate = true;  // HOSEK < A
+    }
+    if (ev.end_date - ev.start_date < cfg.min_duration){
+      if(drag.left)
+        ev.start_date = gantt.calculateEndDate(ev.end_date, -1);
+      else
+        ev.end_date = gantt.calculateEndDate(ev.start_date, 1);
+    }
+    if(drag.left){
+      gantt.moveDesc(ev, ev.start_date.diff(old_start, "seconds"),null, 'left');
+    }else{
+      gantt.moveDesc(ev, ev.end_date.diff(old_end, "seconds"), null, 'right');
+    }
+    //gantt.moveChildren(ev,ev.start_date.diff(old_start,"seconds"));
+    return;  // HOSEK
+    gantt._init_task_timing(ev);
+  },
+  _resize_progress:function(ev, shift, drag){
+    var coords_x = this._drag_task_coords(ev, drag);
+
+    var diff = Math.max(0, drag.pos.x - coords_x.start);
+    ev.progress = Math.min(1, diff / (coords_x.end - coords_x.start));
+  },
+  _move : function(ev, shift, drag){
+    // TODO konce tasku
+    var coords_x = this._drag_task_coords(ev, drag);
+    if (ev.type==="milestone"){
+      new_end = moment(gantt.dateFromPos(coords_x.start + shift.x));
+      new_end = gantt._working_time_helper.get_closest_worktime({date:new_end, unit:"day", dir: 'any', length:1});
+      new_end._isEndDate = true;
+      new_start = moment(new_end);
+    } else {
+      var new_start = gantt._working_time_helper.get_closest_worktime({
+        date:moment(gantt.dateFromPos(coords_x.start + shift.x)),
+        dir:"future"
+      });
+      var new_end = gantt._working_time_helper.add_worktime(new_start, ev.duration, "day", true); //< HOSEKP
+    }
+    if (!new_start.isValid() || !new_end.isValid()) {
+      debugger;
+    }
+    // console.log(new_start.format("DD.MM.YYYY HH:mm")+" "+new_end.format("DD.MM.YYYY HH:mm"));
+    gantt.multiStop(ev, new_start, new_end);
+    var old_start = ev.start_date;
+    if(!new_start){
+      ev.start_date = gantt.date.Date(gantt.getState().min_date);
+      ev.end_date = gantt.dateFromPos(gantt.posFromDate(ev.start_date) + (coords_x.end - coords_x.start));
+    }else if(!new_end){
+      ev.end_date = gantt.date.Date(gantt.getState().max_date);
+      ev.start_date = gantt.dateFromPos(gantt.posFromDate(ev.end_date) - (coords_x.end - coords_x.start));
+    }else{
+      ev.start_date = new_start;
+      ev.end_date = new_end;
+    }
+    if (ev.type === "project") {
+      ev.maximal_start = null;
+      ev.minimal_end = null;
+    } else {
+      gantt.moveDesc(ev, {old_start: old_start});
+    }
+    //console.log(new_start.toString()+" "+new_end.toString());
+  },
+  _drag_task_coords : function(t, drag){
+    // TODO konce tasku
+    var start = drag.obj_s_x = drag.obj_s_x || gantt.posFromDate(t.start_date);
+    var end = drag.obj_e_x = drag.obj_e_x || gantt.posFromDate(t.end_date);
+    return {
+      start : start,
+      end : end
+    };
+  },
+  on_mouse_move : function(e){
+    if(this.drag.start_drag)
+      this._start_dnd(e);
+
+    var drag = this.drag;
+
+    if (drag.mode){
+      //if(!e.movementX && !e.movementY) return;
+      if(!drag.last_event){
+        setTimeout($.proxy(this._update_on_move,this),5);
+      }
+      drag.last_event=e;
+    }
+  },
+  _update_on_move : function(){
+    var drag = this.drag;
+
+    if (drag&&drag.mode){
+      var e=drag.last_event;
+      if(!e) return;
+      drag.last_event = null;
+      var pos = gantt._get_mouse_pos(e);
+      if (drag.pos) {
+        if (drag.pos.x == pos.x
+            && drag.pos.y == pos.y) return;
+      } else {
+        if (Math.abs(drag.start_x - pos.x) < 10
+            && Math.abs(drag.start_y - pos.y) < 10) return;
+      }
+
+      drag.pos = pos;
+      // TODO konce tasku
+      //ysy.log.debug("pos.x="+pos.x,"task_drag");
+      var curr_date = gantt.dateFromPos(pos.x);
+      if(!curr_date || isNaN( curr_date.getTime() ))
+        return;
+
+      //ysy.log.debug(curr_date,"task_drag");
+      var shift = {x:pos.x - drag.start_x,y:pos.y - drag.start_y};
+      var ev = gantt.getTask(drag.id);
+
+
+      if(this._handlers[drag.mode]){
+        var original = dhtmlx.mixin({}, ev);
+        var copy =  dhtmlx.mixin({}, ev);
+        this._handlers[drag.mode].apply(this, [copy, shift, drag]);
+        dhtmlx.mixin(ev, copy, true);
+        //return;
+        //gantt._update_parents(gantt.getParent(drag.id),false);
+        gantt.callEvent("onTaskDrag", [ev.id, drag.mode, copy, original, e]);
+        //gantt.moveDesc(ev, ev.end_date);
+        //gantt.moveChildren(ev,ev.start_date.diff(original.start_date,"seconds"));
+        //gantt._update_parents();
+        ev._changed = drag.mode;
+
+        gantt.refreshTask(drag.id);
+      }
+    }
+  },
+
+  on_mouse_down : function(e, src){
+    // on Mac we do not get onmouseup event when clicking right mouse button leaving us in dnd state
+    // let's ignore right mouse button then
+    if (e.button == 2)
+      return false;
+
+    var id =gantt.locate(e);
+    var task = null;
+    if(gantt.isTaskExists(id)){
+      task = gantt.getTask(id);
+    }
+    if(task==null){   // HOSEK < V
+      ysy.log.debug("No task under click","empty_field");
+      this.clear_drag_state();
+      return;
+    }  // HOSEK A
+    if (gantt._is_readonly(task) || this.drag.mode) return false;
+
+    this.clear_drag_state();
+
+    src = src||(e.target||e.srcElement);
+
+    var className = gantt._trim(src.className || "");
+    if(!className || !this._get_drag_mode(className)){
+      if(src.parentNode)
+        return this.on_mouse_down(e, src.parentNode);
+      else
+        return false;
+    }
+
+    var drag = this._get_drag_mode(className);
+    ysy.log.debug("drag.mode="+(drag?drag.mode:"null"),"empty_field");
+    if(!drag){
+      if (gantt.checkEvent("onMouseDown") && gantt.callEvent("onMouseDown", [className.split(" ")[0]])) {
+        if (src.parentNode)
+          return this.on_mouse_down(e,src.parentNode);
+
+      }
+    }else{
+      if (drag.mode && drag.mode != gantt.config.drag_mode.ignore && gantt.config["drag_" + drag.mode]){
+        id =  gantt.locate(src);
+        if(id!=null){
+          task = dhtmlx.copy(gantt.getTask(id) || {});
+
+          if(gantt._is_readonly(task)){
+            this.clear_drag_state();
+            return false;
+          }
+
+          /*if(gantt._is_flex_task(task) && drag.mode != gantt.config.drag_mode.progress){//only progress drag is allowed for tasks with flexible duration
+            this.clear_drag_state();
+            return;
+          }*/  // HOSEK
+          drag.id = id;
+          drag.obj = task;
+        }
+        var pos = gantt._get_mouse_pos(e);
+        drag.start_x = pos.x;
+        drag.start_y = pos.y;
+        this.drag.start_drag = drag;
+      }else
+        this.clear_drag_state();
+    }
+    return false;
+  },
+  _fix_dnd_scale_time:function(task, drag){
+    alert("Forbidden function _fix_dnd_scale_time");
+    var unit = gantt._tasks.unit,
+      step = gantt._tasks.step;
+    if(!gantt.config.round_dnd_dates){
+      unit = 'minute';
+      step = gantt.config.time_step;
+    }
+    unit="day";  // HOSEK
+
+    function fixStart(task){
+      if(!gantt.isWorkTime(task.start_date))
+        task.start_date = gantt.calculateEndDate(task.start_date, 0, gantt.config.duration_unit); // HOSEK (originally -1)
+    }
+    function fixEnd(task){
+      if(!gantt.isWorkTime(gantt.date.Date(task.end_date - 1)))
+        task.end_date = gantt.calculateEndDate(task.end_date, 1, gantt.config.duration_unit);
+    }
+    if(drag.mode == gantt.config.drag_mode.resize){
+      if(drag.left){
+        task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step});
+        fixStart(task);
+      }else{
+        task.end_date = gantt.roundDate({date:task.end_date, unit:unit, step:step});
+        fixEnd(task);
+      }
+      task.duration=gantt.calculateDuration(task.start_date,task.end_date);
+    }else if(drag.mode == gantt.config.drag_mode.move){
+      task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step});
+      fixStart(task);
+
+      task.end_date = gantt.calculateEndDate(task.start_date, task.duration, gantt.config.duration_unit);
+    }
+  },
+  _fix_working_times:function(task, drag){
+    //console.log("_fix_working_times REWRITED");
+    gantt._working_time_helper.round_date(task.start_date);
+    if(drag.mode == gantt.config.drag_mode.resize){
+      gantt._working_time_helper.round_date(task.end_date, "past");
+        task.duration=gantt._working_time_helper.get_work_units_between(task.start_date,task.end_date,"day");
+    }
+    task.end_date=gantt._working_time_helper.add_worktime(task.start_date,task.duration,"day");
+    /*return;
+    var drag = drag || {mode : gantt.config.drag_mode.move};
+    if(gantt.config.work_time && gantt.config.correct_work_time){
+      if(drag.mode == gantt.config.drag_mode.resize){
+        if(drag.left){
+          task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'});
+        }else{
+          task.end_date = gantt.getClosestWorkTime({date:task.end_date, dir:'past'});
+        }
+      }else if(drag.mode == gantt.config.drag_mode.move){
+        gantt.correctTaskWorkTime(task);
+      }
+    }*/
+  },
+  on_mouse_up : function(e){
+    var drag = this.drag;
+    if (drag.mode && drag.id) {
+      //drop
+      var ev = gantt.getTask(drag.id);
+
+      //if(gantt.config.work_time && gantt.config.correct_work_time){
+      // this._fix_working_times(ev, drag);
+      //}
+      //gantt.resetProjectDates(ev);
+      //this._fix_dnd_scale_time(ev, drag);
+
+      //gantt._init_task_timing(ev);
+      // HOSEK
+      this._fireEvent("before_finish", drag.mode, [drag.id, drag.mode, e]);
+      gantt.updateAllTask(ev);
+
+      if (gantt._tasks.needRescale) {
+        gantt._tasks.needRescale = false;
+        //gantt._adjust_scales();
+        //gantt.refreshData();
+        gantt.render();
+        ysy.log.debug("Scale rescaled", "outer");
+      }
+      this.clear_drag_state();
+      return;
+      // HOSEK
+      //if (!this._fireEvent("before_finish", drag.mode, [drag.id, drag.mode, dhtmlx.copy(drag.obj), e])) {
+      //	drag.obj._dhx_changed = false;
+      //	dhtmlx.mixin(ev, drag.obj, true);
+      //
+      //	gantt.updateTask(ev.id);
+      //} else {
+      //	var drag_id = drag.id;
+      //
+      //	gantt._init_task_timing(ev);
+      //	this._fireEvent("after_finish", drag.mode, [drag_id, drag.mode, e]);
+      //	this.clear_drag_state();
+      //	gantt.updateTask(ev.id);
+      //}
+    }
+    this.clear_drag_state();
+  },
+  _get_drag_mode : function(className){
+    var modes = gantt.config.drag_mode;
+    var classes = (className || "").split(" ");
+    var classname = classes[0];
+    var drag = {mode:null, left:null};
+    switch (classname) {
+      case "gantt_task_line":
+      case "gantt_task_content":
+        drag.mode = modes.move;
+        break;
+      case "gantt_task_drag":
+        drag.mode = modes.resize;
+        if(classes[1] && classes[1].indexOf("left", classes[1].length - "left".length) !== -1){
+          drag.left = true;
+        }else{
+          drag.left = false;
+        }
+        break;
+      case "gantt_task_progress_drag":
+        drag.mode = modes.progress;
+        break;
+      case "gantt_link_control":
+      case "gantt_link_point":
+        drag.mode = modes.ignore;
+        break;
+      case "gantt_task_cell":
+        drag.mode = "empty";    // HOSEK  < A V
+        break;
+      default:
+        drag = null;
+        break;
+    }
+    return drag;
+
+  },
+
+  _start_dnd : function(e){
+    var drag = this.drag = this.drag.start_drag;
+    delete drag.start_drag;
+
+    var cfg = gantt.config;
+    var id = drag.id;
+    if (!cfg["drag_"+drag.mode] || !gantt.callEvent("onBeforeDrag",[id, drag.mode, e]) || !this._fireEvent("before_start", drag.mode, [id, drag.mode, e])){
+      this.clear_drag_state();
+    }else {
+      delete drag.start_drag;
+    }
+
+  },
+  _fireEvent:function(stage, mode, params){
+    dhtmlx.assert(this._events[stage], "Invalid stage:{" + stage + "}");
+
+    var trigger = this._events[stage][mode];
+
+    dhtmlx.assert(trigger, "Unknown after drop mode:{" + mode + "}");
+    dhtmlx.assert(params, "Invalid event arguments");
+
+
+    if(!gantt.checkEvent(trigger))
+      return true;
+
+    return gantt.callEvent(trigger, params);
+  }
+};
+
+gantt.roundTaskDates = function(task){
+  alert("Forbidden function roundTaskDates");
+  var drag_state = gantt._tasks_dnd.drag;
+
+  if(!drag_state){
+    drag_state = {mode:gantt.config.drag_mode.move};
+  }
+  gantt._tasks_dnd._fix_dnd_scale_time(task, drag_state);
+};
+
+
+
+
+
+
+
+gantt._render_link = function(id){
+  var link = this.getLink(id);
+  var renders = gantt._get_link_renderers();
+  //ysy.log.debug("_render_link() id="+id+" rend="+renders.length,"link_render");
+  for(var i = 0; i < renders.length; i++)
+    renders[i].render_item(link);
+};
+
+gantt._get_link_type = function(from_start, to_start){
+  var type = null;
+  if(from_start && to_start){
+    type = gantt.config.links.start_to_start;
+  }else if(!from_start && to_start){
+    type = gantt.config.links.finish_to_start;
+  }else if(!from_start && !to_start){
+    type = gantt.config.links.finish_to_finish;
+  }else if(from_start && !to_start){
+    type = gantt.config.links.start_to_finish;
+  }
+  return type;
+};
+
+gantt.isLinkAllowed = function(from, to, from_start, to_start){
+  var link = null;
+  if(typeof(from) == "object"){
+    link = from;
+  }else{
+    link = {source:from, target:to, type: this._get_link_type(from_start, to_start)};
+  }
+
+  if(!link) return false;
+  if(!(link.source && link.target && link.type)) return false;
+  if(link.source == link.target) return false;
+
+  var res = true;
+  //any custom rules
+  if(this.checkEvent("onLinkValidation"))
+    res = this.callEvent("onLinkValidation", [link]);
+
+  return res;
+};
+
+gantt._render_link_element = function(link){
+  //ysy.log.debug("render_link_element() link="+link.id,"link_render");
+  var dots = this._path_builder.get_points(link);
+  var drawer = gantt._drawer;
+  var lines = drawer.get_lines(dots);
+
+  var div = document.createElement("div");
+
+
+  var css = "gantt_task_link";
+
+  if(link.color){
+    css += " gantt_link_inline_color";
+  }
+  var cssTemplate = this.templates.link_class ? this.templates.link_class(link) : "";
+  if(cssTemplate){
+    css += " " + cssTemplate;
+  }
+
+  if(this.config.highlight_critical_path && this.isCriticalLink){
+    if(this.isCriticalLink(link))
+      css += " gantt_critical_link";
+  }
+
+  div.className = css;
+  div.setAttribute(gantt.config.link_attribute, link.id);
+  for(var i=0; i < lines.length; i++){
+    if(i == lines.length - 1){
+      lines[i].size -= gantt.config.link_arrow_size;
+    }
+    var el = drawer.render_line(lines[i], lines[i+1]);
+    if(link.color){
+      el.firstChild.style.backgroundColor = link.color;
+    }
+    div.appendChild(el);
+  }
+
+  var direction = lines[lines.length - 1].direction;
+  var endpoint = gantt._render_link_arrow(dots[dots.length - 1], direction);
+  if(link.color){
+    endpoint.style.borderColor = link.color;
+  }
+  div.appendChild(endpoint);
+  // HOSEK
+  // if (link.delay !== 0) {
+    var delaypos = {
+      x: (dots[dots.length - 2].x + dots[dots.length - 1].x) / 2 - 5,
+      y: dots[dots.length - 2].y - 2
+    };
+    var delay_element = gantt.render_delay_element(link, delaypos);
+    if(delay_element)
+      div.appendChild(delay_element);
+  // }
+  // HOSEK
+  return div;
+};
+
+gantt._render_link_arrow = function(point, direction){
+  var div = document.createElement("div");
+  var drawer = gantt._drawer;
+  var top = point.y;
+  var left = point.x;
+
+  var size = gantt.config.link_arrow_size;
+  var line_width = gantt.config.row_height;
+  var className = "gantt_link_arrow gantt_link_arrow_" + direction;
+  switch (direction){
+    case drawer.dirs.right:
+      top -= (size - line_width)/2;
+      left -= size;
+      break;
+    case drawer.dirs.left:
+      top -= (size - line_width)/2;
+      break;
+    case drawer.dirs.up:
+      left -= (size - line_width)/2;
+      break;
+    case drawer.dirs.down:
+      top -= size;
+      left -= (size - line_width)/2;
+      break;
+    default:
+      break;
+  }
+  div.style.cssText = [
+    "top:"+top + "px",
+    "left:"+left+'px'].join(';');
+  div.className = className;
+
+  return div;
+};
+
+
+gantt._drawer = {
+  current_pos:null,
+  dirs:{"left":'left',"right":'right',"up":'up', "down":'down'},
+  path:[],
+  clear:function(){
+    this.current_pos = null;
+    this.path = [];
+  },
+  point:function(pos){
+    this.current_pos = dhtmlx.copy(pos);
+  },
+  get_lines:function(dots){
+    this.clear();
+    this.point(dots[0]);
+    for(var i=1; i<dots.length ; i++){
+      this.line_to(dots[i]);
+    }
+    return this.get_path();
+  },
+  line_to:function(pos){
+    var next = dhtmlx.copy(pos);
+    var prev = this.current_pos;
+
+    var line = this._get_line(prev, next);
+    this.path.push(line);
+    this.current_pos = next;
+  },
+  get_path:function(){
+    return this.path;
+  },
+  get_wrapper_sizes :function(v){
+    var res,
+      wrapper_size = gantt.config.link_wrapper_width,
+      line_size = gantt.config.link_line_width,
+      y = v.y + Math.floor((gantt.config.row_height - wrapper_size)/2);
+    switch (v.direction){
+      case this.dirs.left:
+        res = {	top : y,
+          height : wrapper_size,
+          lineHeight : wrapper_size,
+          left : v.x - v.size - wrapper_size/2 ,
+          width : v.size +wrapper_size};
+        break;
+      case this.dirs.right:
+        res = {	top : y,
+          lineHeight : wrapper_size,
+          height : wrapper_size,
+          left : v.x - wrapper_size/2,
+          width : v.size + wrapper_size};
+        break;
+      case this.dirs.up:
+        res = {	top : y - v.size,
+          lineHeight: v.size + wrapper_size,
+          height : v.size + wrapper_size,
+          left : v.x - wrapper_size/2,
+          width : wrapper_size};
+        break;
+      case this.dirs.down:
+        res = {	top : y,
+          lineHeight: v.size + wrapper_size,
+          height : v.size + wrapper_size,
+          left : v.x - wrapper_size/2,
+          width : wrapper_size};
+        break;
+      default:
+        break;
+    }
+
+    return res;
+  },
+  get_line_sizes : function(v){
+    var res,
+      line_size = gantt.config.link_line_width,
+      wrapper_size = gantt.config.link_wrapper_width,
+      size =  v.size + line_size;
+    switch (v.direction){
+      case this.dirs.left:
+      case this.dirs.right:
+        res = {
+          height : line_size,
+          width : size,
+          marginTop: (wrapper_size - line_size)/2-1,
+          marginLeft: (wrapper_size - line_size)/2
+        };
+        break;
+      case this.dirs.up:
+      case this.dirs.down:
+        res = {
+          height : size,
+          width : line_size,
+          marginTop: (wrapper_size - line_size)/2-1,
+          marginLeft: (wrapper_size - line_size)/2
+        };
+        break;
+      default:
+        break;
+    }
+
+
+
+    return res;
+  },
+  render_line : function(v){
+    //ysy.log.debug("render_line()","link_render");
+    var pos = this.get_wrapper_sizes(v);
+    var wrapper = document.createElement("div");
+    wrapper.style.cssText = [
+      "top:" + pos.top + "px",
+      "left:" + pos.left + "px",
+      "height:" + pos.height + "px",
+      "width:" + pos.width + "px"
+    ].join(';');
+    wrapper.className = "gantt_line_wrapper";
+
+    var innerPos = this.get_line_sizes(v);
+    var inner = document.createElement("div");
+    inner.style.cssText = [
+      "height:" + innerPos.height + "px",
+      "width:" + innerPos.width + "px",
+      "margin-top:" + innerPos.marginTop + "px",
+      "margin-left:" + innerPos.marginLeft + "px"
+    ].join(";");
+
+    inner.className = "gantt_link_line_" + v.direction;
+    wrapper.appendChild(inner);
+
+    return wrapper;
+  },
+  _get_line:function(from, to){
+    var direction = this.get_direction(from, to);
+    var vect = {
+      x : from.x,
+      y : from.y,
+      direction : this.get_direction(from, to)
+    };
+    if(direction == this.dirs.left || direction == this.dirs.right){
+      vect.size =  Math.abs(from.x - to.x);
+    }else{
+      vect.size =  Math.abs(from.y - to.y);
+    }
+    return vect;
+  },
+  get_direction:function(from, to){
+    var direction = 0;
+    if(to.x < from.x){
+      direction = this.dirs.left;
+    }else if (to.x > from.x){
+      direction = this.dirs.right;
+    }else if (to.y > from.y){
+      direction = this.dirs.down;
+    }else {
+      direction = this.dirs.up;
+    }
+    return direction;
+  }
+
+};
+gantt._y_from_ind = function(index){
+  return (index)*gantt.config.row_height;
+};
+gantt._path_builder = {
+
+  path:[],
+  clear:function(){
+    this.path = [];
+  },
+  current:function(){
+    return this.path[this.path.length - 1];
+  },
+  point:function(next){
+    if(!next)
+      return this.current();
+
+    this.path.push(dhtmlx.copy(next));
+    return next;
+  },
+  point_to:function(direction, diff, point){
+    if(!point)
+      point = dhtmlx.copy(this.point());
+    else
+      point = {x:point.x, y:point.y};
+    var dir = gantt._drawer.dirs;
+    switch (direction){
+      case (dir.left):
+        point.x -= diff;
+        break;
+      case (dir.right):
+        point.x += diff;
+        break;
+      case (dir.up):
+        point.y -= diff;
+        break;
+      case (dir.down):
+        point.y += diff;
+        break;
+      default:
+        break;
+    }
+    return this.point(point);
+  },
+  get_points:function(link){
+    var pt = this.get_endpoint(link);
+    var xy = gantt.config;
+
+
+    var dy = pt.e_y - pt.y;
+    var dx = pt.e_x - pt.x;
+
+    var dir = gantt._drawer.dirs;
+
+    this.clear();
+    this.point({x: pt.x, y : pt.y});
+
+    var shiftX = ((link.id % 3) / 3 + 1.5) * xy.link_arrow_size;//just random size for first line
+    //var shiftX = Math.max((link.delay-1)*gantt._tasks.col_width/gantt._get_line(gantt.config.scale_unit)*gantt._get_line("day"),2*xy.link_arrow_size);
+
+
+    var forward = (pt.e_x > pt.x);
+    if(link.type == gantt.config.links.start_to_start){
+      this.point_to(dir.left, shiftX);
+      if(forward){
+        this.point_to(dir.down, dy);
+        this.point_to(dir.right,  dx);
+      }else{
+        this.point_to(dir.right, dx);
+        this.point_to(dir.down, dy);
+      }
+      this.point_to(dir.right, shiftX);
+
+    }else if(link.type == gantt.config.links.finish_to_start){
+      forward = (pt.e_x > (pt.x + 2*shiftX));
+      this.point_to(dir.right, shiftX);
+      if(forward){
+        dx -= shiftX;
+        this.point_to(dir.down, dy);
+        this.point_to(dir.right, dx);
+      }else{
+        dx -= 2*shiftX;
+        var sign = dy > 0 ? 1 : -1;
+
+        this.point_to(dir.down, sign * (xy.row_height/2));
+        this.point_to(dir.right, dx);
+        this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2)));
+        this.point_to(dir.right, shiftX);
+      }
+
+    }else if(link.type == gantt.config.links.finish_to_finish){
+      this.point_to(dir.right, shiftX);
+      if(forward){
+        this.point_to(dir.right, dx);
+        this.point_to(dir.down, dy);
+      }else{
+        this.point_to(dir.down, dy);
+        this.point_to(dir.right, dx);
+      }
+      this.point_to(dir.left, shiftX);
+    }else if(link.type == gantt.config.links.start_to_finish){
+
+      forward = (pt.e_x > (pt.x - 2*shiftX));
+      this.point_to(dir.left, shiftX);
+
+      if(!forward){
+        dx += shiftX;
+        this.point_to(dir.down, dy);
+        this.point_to(dir.right,  dx);
+      }else{
+        dx += 2*shiftX;
+        var sign = dy > 0 ? 1 : -1;
+        this.point_to(dir.down, sign * (xy.row_height/2));
+        this.point_to(dir.right, dx);
+        this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2)));
+        this.point_to(dir.left, shiftX);
+      }
+
+    }
+
+    return this.path;
+  },
+  get_endpoint : function(link){
+    var types = gantt.config.links;
+    var from_start = false, to_start = false;
+
+    if(link.type == types.start_to_start){
+      from_start = to_start = true;
+    }else if(link.type == types.finish_to_finish){
+      from_start = to_start = false;
+    }else if(link.type == types.finish_to_start){
+      from_start = false;
+      to_start = true;
+    }else if(link.type == types.start_to_finish){
+      from_start = true;
+      to_start = false;
+    }else{
+      dhtmlx.assert(false, "Invalid link type");
+    }
+
+    var from = gantt._get_task_visible_pos(gantt._pull[link.source], from_start);
+    var to = gantt._get_task_visible_pos(gantt._pull[link.target], to_start);
+
+    return {
+      x :  from.x,
+      e_x : to.x,
+      y : from.y ,
+      e_y : to.y
+    };
+  }
+};
+
+gantt._init_links_dnd = function() {
+  var dnd = new dhtmlxDnD(this.$task_bars, { marker:true,sensitivity : 0, updates_per_second : 60 }),
+    start_marker = "task_left",
+    end_marker = "task_right",
+    link_edge_marker = "gantt_link_point",
+    link_landing_hover_area = "gantt_link_control";
+
+  dnd.attachEvent("onBeforeDragStart", dhtmlx.bind(function(obj,e) {
+//console.log("init_links_dnd");
+    var target = (e.target||e.srcElement);
+    resetDndState();
+    if(gantt.getState().drag_id)
+      return false;
+
+
+    if(gantt._locate_css(target, link_edge_marker)){
+      if(gantt._locate_css(target, start_marker))
+        gantt._link_source_task_start = true;
+
+      var sid = gantt._link_source_task = this.locate(e);
+
+
+      var t = gantt.getTask(sid);
+      if(gantt._is_readonly(t)){
+        resetDndState();
+        return false;
+      }
+
+      var shift = 0;
+      if(gantt._get_safe_type(t.type) == gantt.config.types.milestone){
+        shift = (gantt._get_visible_milestone_width() - gantt._get_milestone_width())/2;
+      }
+
+      this._dir_start = getLinePos(t, !!gantt._link_source_task_start, shift);
+      return true;
+    }else{
+      return false;
+    }
+
+  }, this));
+
+  dnd.attachEvent("onAfterDragStart", dhtmlx.bind(function(obj,e) {
+    updateMarkedHtml(dnd.config.marker);
+  }, this));
+
+  function getLinePos(task, to_start, shift){
+    var pos = gantt._get_task_pos(task, !!to_start);
+    pos.y += gantt._get_task_height()/2;
+
+    shift = shift || 0;
+    pos.x += (to_start ? -1 : 1)*shift;
+    return pos;
+  }
+
+  dnd.attachEvent("onDragMove", dhtmlx.bind(function(obj,e) {
+    var dd = dnd.config;
+    var pos = dnd.getPosition(e);
+    //advanceMarker(dd.marker, pos);
+    var landing = false;// = gantt._is_link_drop_area(e);
+
+    var prevTarget = gantt._link_target_task;
+    var prevLanding = gantt._link_landing;
+    var prevToStart = gantt._link_target_task_start;
+    var to_start = true;
+    if(gantt._locate_css(e,"gantt_task_line")){
+      var targ = gantt.locate(e);
+    }else{
+      targ = null;
+    }
+    ysy.log.debug("landing=" + landing + " target=" + targ, "link_drag");
+    if (targ){
+      //refreshTask
+      to_start = !gantt._locate_css(e, end_marker);
+      var link = getDndState();
+      var landing = gantt.isLinkAllowed(link.from, link.to, link.from_start, link.to_start);
+    }
+
+    gantt._link_target_task = targ;
+    gantt._link_landing = landing;
+    gantt._link_target_task_start = to_start;
+
+    if(landing){
+      var t = gantt.getTask(targ);
+
+      var node = gantt._locate_css(e, link_landing_hover_area);
+      var shift = 0;
+      if(node){
+        shift = Math.floor(node.offsetWidth  / 2);
+      }
+
+      this._dir_end = getLinePos(t, !!gantt._link_target_task_start,shift);
+    }else{
+      this._dir_end = gantt._get_mouse_pos(e);
+    }
+
+    var targetChanged = !(prevLanding == landing && prevTarget == targ && prevToStart == to_start);
+    if(targetChanged){
+      if(prevTarget)
+        gantt.refreshTask(prevTarget, false);
+      if(targ)
+        gantt.refreshTask(targ, false);
+    }
+
+    if(targetChanged){
+      updateMarkedHtml(dd.marker);
+    }
+
+
+
+    showDirectingLine(this._dir_start.x, this._dir_start.y, this._dir_end.x, this._dir_end.y);
+
+    return true;
+  }, this));
+
+
+  dnd.attachEvent("onDragEnd", dhtmlx.bind(function() {
+    var drag = getDndState();
+
+    if(drag.from && drag.to && drag.from != drag.to){
+      var type = gantt._get_link_type(drag.from_start, drag.to_start);
+
+      var link = {source : drag.from, target: drag.to, type:type};
+      if(link.type && gantt.isLinkAllowed(link))
+        gantt.addLink(link);
+    }
+
+    resetDndState();
+
+    if(drag.from)
+      gantt.refreshTask(drag.from, false);
+    if(drag.to)
+      gantt.refreshTask(drag.to, false);
+    removeDirectionLine();
+  }, this));
+
+  function updateMarkedHtml(marker){
+    var link = getDndState();
+
+    var css = ["gantt_link_tooltip"];
+    var allowed = gantt.isLinkAllowed(link.from, link.to, link.from_start, link.to_start);
+    if(link.from && link.to){
+      if (allowed){
+        css.push("gantt_allowed_link");
+      } else {
+        css.push("gantt_invalid_link");
+      }
+    }
+
+    var className = gantt.templates.drag_link_class(link.from, link.from_start, link.to, link.to_start);
+    if(className)
+      css.push(className);
+
+    var html = "<div class='"+className+ "'>" +
+        gantt.templates.drag_link(link.from, link.from_start, link.to, link.to_start) +
+      "</div>";
+    marker.innerHTML = html;
+  }
+
+  function advanceMarker(marker, pos){
+    marker.style.left = pos.x + 5 + "px";
+    marker.style.top = pos.y + 5 + "px";
+  }
+  function getDndState(){
+    return { from : gantt._link_source_task,
+        to : gantt._link_target_task,
+        from_start : gantt._link_source_task_start,
+        to_start : gantt._link_target_task_start};
+  }
+  function resetDndState(){
+    gantt._link_source_task =
+      gantt._link_source_task_start =
+        gantt._link_target_task = null;
+    gantt._link_target_task_start = true;
+  }
+  function showDirectingLine(s_x, s_y, e_x, e_y){
+    var div = getDirectionLine();
+
+    var link = getDndState();
+
+    var css = ["gantt_link_direction"];
+    if(gantt.templates.link_direction_class){
+      css.push(gantt.templates.link_direction_class(link.from, link.from_start, link.to, link.to_start));
+    }
+
+    var dist =Math.sqrt( (Math.pow(e_x - s_x, 2)) + (Math.pow(e_y - s_y, 2)) );
+    dist = Math.max(0, dist - 3);
+    if(!dist)
+      return;
+
+    div.className = css.join(" ");
+    var tan = (e_y - s_y)/(e_x - s_x),
+      angle = Math.atan(tan);
+
+    if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 2){
+      angle += Math.PI;
+    }else if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 3){
+      angle -= Math.PI;
+    }
+
+
+
+    var sin = Math.sin(angle),
+      cos = Math.cos(angle),
+      top = Math.round(s_y),
+      left = Math.round(s_x);
+
+
+    var style = [
+      "-webkit-transform: rotate("+angle+"rad)",
+      "-moz-transform: rotate("+angle+"rad)",
+      "-ms-transform: rotate("+angle+"rad)",
+      "-o-transform: rotate("+angle+"rad)",
+      "transform: rotate("+angle+"rad)",
+      "width:" + Math.round(dist) + "px"
+    ];
+
+    if(window.navigator.userAgent.indexOf("MSIE 8.0") != -1){
+      //ms-filter breaks styles in ie9, so add it only for 8th
+      style.push("-ms-filter: \"" + ieTransform(sin, cos) + "\"");
+
+      var shiftLeft = Math.abs(Math.round(s_x - e_x)),
+        shiftTop = Math.abs(Math.round(e_y - s_y));
+      //fix rotation axis
+      switch(coordinateCircleQuarter(s_x, e_x, s_y, e_y)){
+        case 1:
+          top -= shiftTop;
+          break;
+        case 2:
+          left -= shiftLeft;
+          top -= shiftTop;
+          break;
+        case 3:
+          left -= shiftLeft;
+          break;
+        default:
+          break;
+      }
+
+    }
+
+    style.push("top:" +  top + "px");
+    style.push("left:" +  left + "px");
+
+    div.style.cssText = style.join(";");
+  }
+
+  function ieTransform(sin, cos){
+    return "progid:DXImageTransform.Microsoft.Matrix("+
+      "M11 = "+cos+","+
+      "M12 = -"+sin+","+
+      "M21 = "+sin+","+
+      "M22 = "+cos+","+
+      "SizingMethod = 'auto expand'"+
+    ")";
+  }
+  function coordinateCircleQuarter(sX, eX, sY, eY){
+    if(eX >= sX){
+      if(eY <= sY){
+        return 1;
+      }else{
+        return 4;
+      }
+    }else{
+      if(eY <= sY){
+        return 2;
+      }else{
+        return 3;
+      }
+    }
+
+  }
+  function getDirectionLine(){
+    if(!dnd._direction){
+      dnd._direction = document.createElement("div");
+      gantt.$task_links.appendChild(dnd._direction);
+    }
+    return dnd._direction;
+  }
+  function removeDirectionLine(){
+    if(dnd._direction){
+      if (dnd._direction.parentNode)	//the event line can be detached because of data refresh
+        dnd._direction.parentNode.removeChild(dnd._direction);
+
+      dnd._direction = null;
+    }
+  }
+
+  gantt._is_link_drop_area = function(e){
+    return !!gantt._locate_css(e, link_landing_hover_area);
+  };
+};
+gantt._get_link_state = function(){
+  return {
+    link_landing_area : this._link_landing,
+    link_target_id : this._link_target_task,
+    link_target_start : this._link_target_task_start,
+    link_source_id : this._link_source_task,
+    link_source_start : this._link_source_task_start
+  };
+};
+
+
+gantt._init_tasks = function(){
+  //store temporary configs
+  this._tasks = {
+    col_width:this.config.columnWidth,
+    width: [], // width of each column
+    full_width: 0, // width of all columns
+    trace_x:[],
+    rendered:{}
+  };
+
+
+  this._click.gantt_task_link = dhtmlx.bind(function(e, trg){
+    var id = this.locate(e, gantt.config.link_attribute);
+    if(id){
+      this.callEvent("onLinkClick", [id, e]);
+    }
+  }, this);
+
+  this._click.gantt_scale_cell = dhtmlx.bind(function(e, trg){
+    var pos = gantt._get_mouse_pos(e);
+    var date = gantt.dateFromPos(pos.x);
+    var coll = Math.floor(gantt._day_index_by_date(date));
+
+    var coll_date = gantt._tasks.trace_x[coll];
+
+    gantt.callEvent("onScaleClick", [e, coll_date]);
+  }, this);
+
+  /*this._dbl_click.gantt_task_link = dhtmlx.bind(function(e, id, trg){    // HOSEK
+    var id = this.locate(e, gantt.config.link_attribute);
+    this._delete_link_handler(id, e);
+  }, this);
+
+  this._dbl_click.gantt_link_point = dhtmlx.bind(function(e, id, trg){
+    var id = this.locate(e),
+      task = this.getTask(id);
+
+
+    var link = null;
+    if(trg.parentNode && trg.parentNode.className){
+      if(trg.parentNode.className.indexOf("_left") > -1){
+        link = task.$target[0];
+      }else{
+        link = task.$source[0];
+      }
+    }
+    if(link)
+      this._delete_link_handler(link, e);
+    return false;
+  }, this);*/
+
+  this._tasks_dnd.init();
+  this._init_links_dnd();
+
+  this._link_layers.clear();
+
+  var links_layer = this.addLinkLayer({
+    renderer: this._render_link_element,
+    container: this.$task_links,
+    filter: gantt._create_filter(['_filter_link', '_is_chart_visible'])
+  });
+  gantt._link_layers.process();
+  this._linkRenderer = this._link_layers.getRenderer(links_layer);
+
+  this._task_layers.clear();
+  var bar_layer = this.addTaskLayer({
+    renderer: this._render_task_element,
+    container: this.$task_bars,
+    filter: gantt._create_filter(['_filter_task', '_is_chart_visible'])
+  });
+
+  var grid_layer = this.addTaskLayer({
+    renderer: this._render_grid_item,
+    container: this.$grid_data,
+    filter: gantt._create_filter(['_filter_task', '_is_grid_visible'])
+  });
+  //var background_layer = this.addTaskLayer({
+  //	renderer: this._render_bg_line,
+  //	container: this.$task_bg,
+  //	filter: gantt._create_filter(['_filter_task', '_is_chart_visible', '_is_std_background'])
+  //});
+  var background_layer = this.addTaskLayer(ysy.view.getGanttBackground());
+  gantt._task_layers.process();
+  this._taskRenderer = this._task_layers.getRenderer(bar_layer);
+  this._gridRenderer = this._task_layers.getRenderer(grid_layer);
+  this._backgroundRenderer = this._task_layers.getRenderer(background_layer);
+
+
+  //function refreshId(renders, oldId, newId, item){
+  //	for(var i =0; i < renders.length; i++){
+  //		renders[i].change_id(oldId, newId);
+  //		renders[i].render_item(item);
+  //	}
+  //}
+  //if(this._onTaskIdChange)
+  //	this.detachEvent(this._onTaskIdChange);
+  //
+  //this._onTaskIdChange = this.attachEvent("onTaskIdChange", function(oldId, newId){
+  //	var render = this._get_task_renderers();
+  //	refreshId(render, oldId, newId, this.getTask(newId));
+  //});
+  //
+  //if(this._onLinkIdChange)
+  //	this.detachEvent(this._onLinkIdChange);
+  //
+  //this._onLinkIdChange = this.attachEvent("onLinkIdChange", function(oldId, newId){
+  //	var render = this._get_link_renderers();
+  //	refreshId(render, oldId, newId, this.getLink(newId));
+  //});
+};
+
+gantt._create_filter = function(filter_methods){
+  if(!(filter_methods instanceof Array)){
+    filter_methods = Array.prototype.slice.call(arguments, 0);
+  }
+
+  return function(obj){
+    var res = true;
+    for(var i = 0, len = filter_methods.length; i < len; i++){
+      var filter_method = filter_methods[i];
+      if(gantt[filter_method]){
+        res = res && (gantt[filter_method].apply(gantt, [obj.id, obj]) !== false);
+      }
+    }
+
+    return res;
+  };
+};
+
+gantt._is_chart_visible = function(){
+  return !!this.config.show_chart;
+};
+
+gantt._filter_task = function(id, task){
+  var min = null, max = null;
+  if(this.config.start_date && this.config.end_date){
+    min = this.config.start_date.valueOf();
+    max = this.config.end_date.valueOf();
+
+    if(+task.start_date > max || +task.end_date < +min)
+      return false;
+  }
+  return true;
+};
+gantt._filter_link = function(id, link){
+  if(!this.config.show_links){
+    return false;
+  }
+  // TODO enable rendering task even out of chart
+  if(!(gantt.isTaskVisible(link.source) && gantt.isTaskVisible(link.target)))
+    return false;
+
+  return this.callEvent("onBeforeLinkDisplay", [id, link]);
+};
+gantt._is_std_background = function(){
+  return !this.config.static_background;
+};
+
+/*gantt._delete_link_handler = function(id, e){    //  HOSEK
+  if(id && this.callEvent("onLinkDblClick", [id, e])){
+    var link = gantt.getLink(id);
+    if(gantt._is_readonly(link)) return;
+
+    var title = "";
+    var question = gantt.locale.labels.link + " " +this.templates.link_description(this.getLink(id)) + " " + gantt.locale.labels.confirm_link_deleting;
+
+    window.setTimeout(function(){
+      gantt._dhtmlx_confirm(question, title, function(){
+        gantt.deleteLink(id);
+      });
+    },(gantt.config.touch ? 300 : 1));
+  }
+};*/
+gantt.getTaskNode = function(id){
+  return this._taskRenderer.rendered[id];
+};
+gantt.getLinkNode = function(id){
+  return this._linkRenderer.rendered[id];
+};
+
+
+
+
+
+gantt._get_tasks_data = function(){
+  var rows = [];
+  for(var i=0; i < this._order.length; i++){
+    var item = this._pull[this._order[i]];
+    item.$index = i;
+    //this._update_parents(item.id, true);
+    this.resetProjectDates(item);
+    rows.push(item);
+  }
+  return rows;
+};
+gantt._get_links_data = function(){
+  var links = [];
+  for(var i in this._lpull)
+    links.push(this._lpull[i]);
+
+  return links;
+};
+gantt._render_data = function(){
+  this.callEvent("onBeforeDataRender", []);
+  //if(!this._is_render_active())
+  //	return;
+
+  this._sync_order();
+  this._update_layout_sizes();
+
+  if(this.config.static_background)
+    this._render_bg_canvas();
+
+  var data = this._get_tasks_data();
+
+  var renderers = this._get_task_renderers();
+  for(var i=0; i < renderers.length; i++){
+    renderers[i].render_items(data);
+  }
+
+  var links = gantt._get_links_data();
+  renderers = this._get_link_renderers();
+  for(var i=0; i < renderers.length; i++)
+    renderers[i].render_items(links);
+
+  this.callEvent("onDataRender", []);
+};
+
+gantt._update_layout_sizes = function(){
+  var cfg = this._tasks;
+
+  cfg.bar_height = this._get_task_height();
+
+  //task bars layer
+  this.$task_data.style.height = Math.max(this.$task.offsetHeight - this.config.scale_height, 0) + 'px';
+  this.$task_bg.style.height = "";
+  this.$task_bg.style.backgroundImage = "";
+
+  //timeline area layers
+  var data_els = this.$task_data.childNodes;
+  for(var i= 0, len = data_els.length; i < len; i++){
+    var el = data_els[i];
+    if(this._is_layer(el) && el.style)
+      el.style.width = cfg.full_width + "px";
+  }
+
+  //grid area
+  if(this._is_grid_visible()){
+    var columns = this.getGridColumns();
+    var width = 0;
+    for (var i = 0; i < columns.length; i++)
+      width += columns[i].width;
+    this.$grid_data.style.width = Math.max(width-1, 0) + "px";
+  }
+};
+
+gantt._scale_range_unit = function(){
+  var unit = this.config.scale_unit;
+  if(this.config.scale_offset_minimal){
+    var scales = this._get_scales();
+    unit = scales[scales.length - 1].unit;
+  }
+  return unit;
+};
+
+gantt._init_tasks_range = function(){
+  var unit = this._scale_range_unit();
+
+  //reset project timing
+  this._sync_order();    // HOSEK
+  this._get_tasks_data();
+
+  var range = this.getSubtaskDates();
+  this._min_date = range.start_date || moment();
+  this._max_date = range.end_date || moment();
+
+  if(this.config.start_date && +this.config.start_date < +this._min_date){
+    this._min_date = this.date[unit + "_start"]( this.date.Date(this.config.start_date));
+  }
+  if(this.config.end_date && +this.config.end_date > +this._max_date){
+    this._max_date = this.date[unit + "_start"]( this.date.Date(this.config.end_date));
+  }
+  if(!(this._max_date && this._max_date)){
+    this._min_date = gantt.date.Date();
+    this._max_date = gantt.date.Date(this._min_date);
+  }
+  var coef=1; // HOSEK
+  if(unit==="day"){coef=3;}
+  this._min_date = this.date[unit + "_start"](this._min_date);
+  //this._min_date = this.calculateEndDate(this.date[unit + "_start"](this._min_date), -1, unit);//one free column before first task
+  this._min_date = this.date.add(this._min_date,-coef,unit);  // HOSEK
+
+  this._max_date = this.date[unit + "_start"](this._max_date);
+  //this._max_date = this.calculateEndDate(this._max_date, 2, unit);//one free column after last task
+  this._max_date = this.date.add(this._max_date,3*coef,unit);  // HOSEK
+};
+
+
+
+gantt._prepare_scale_html = function(config){
+  var cells = [];
+  var date = null, content = null, css = null;
+
+  if(config.template || config.date){
+    content = config.template || this.date.date_to_str(config.date);
+  }
+
+
+  css = config.css || function(){};
+  if(!config.css && this.config.inherit_scale_class){
+    css = gantt.templates.scale_cell_class;
+  }
+
+  for (var i = 0; i < config.count; i++) {
+    date = gantt.date.Date(config.trace_x[i]);
+    var value = content.call(this, date),
+      width = config.width[i],
+      style = "",
+      template = "",
+      cssclass = "";
+
+    if(width){
+      style = "width:"+(width)+"px;";
+      cssclass = "gantt_scale_cell" + (i == config.count-1 ? " gantt_last_cell" : "");
+
+      template = css.call(this, date);
+      if(template) cssclass += " " + template;
+      var cell = "<div class='" + cssclass + "' style='" + style + "'>" + value + "</div>";
+      cells.push(cell);
+    }else{
+      //do not render ignored cells
+    }
+
+  }
+  return cells.join("");
+};
+gantt._get_scales = function(){
+  var helpers = this._scale_helpers;
+  var scales = [helpers.primaryScale()].concat(this.config.subscales);
+
+  helpers.sortScales(scales);
+  return scales;
+};
+
+gantt._render_tasks_scales = function() {
+  this._init_tasks_range();
+  this._scroll_resize();
+  this._set_sizes();
+
+  var scales_html = "",
+    outer_width = 0,
+    data_width = 0,
+    scale_height = 0;
+
+  if(this._is_chart_visible()){
+    var helpers = this._scale_helpers;
+    var scales = this._get_scales();
+    scale_height = (this.config.scale_height-1);
+    var resize = this._get_resize_options();
+    var avail_width = resize.x ? Math.max(this.config.autosize_min_width, 0) : this.$task.offsetWidth;
+
+    var cfgs = helpers.prepareConfigs(scales,this.config.min_column_width, avail_width, scale_height);
+    var cfg = this._tasks = cfgs[cfgs.length - 1];
+
+    var html = [];
+
+    var css = this.templates.scale_row_class;
+    for(var i=0; i < cfgs.length; i++){
+      var cssClass = "gantt_scale_line";
+      var tplClass = css(cfgs[i]);
+      if(tplClass){
+        cssClass += " " + tplClass;
+      }
+
+      html.push("<div class=\""+cssClass+"\" style=\"height:"+(cfgs[i].height)+"px;line-height:"+(cfgs[i].height)+"px\">" + this._prepare_scale_html(cfgs[i]) + "</div>");
+    }
+
+    scales_html = html.join("");
+    outer_width = cfg.full_width + this.$scroll_ver.offsetWidth + "px";
+    data_width = cfg.full_width + "px";
+    scale_height += "px";
+  }
+
+  if(this._is_chart_visible()){
+    this.$task.style.display = "";
+  }else{
+    this.$task.style.display = "none";
+  }
+
+  this.$task_scale.style.height = scale_height;
+
+  this.$task_data.style.width =
+  this.$task_scale.style.width = outer_width;
+
+  this.$task_scale.innerHTML = scales_html;
+
+};
+
+gantt._render_bg_line = function(item){
+  var cfg = gantt._tasks;
+  var count = cfg.count;
+  var row = document.createElement("div");
+  if(gantt.config.show_task_cells){
+    for (var j = 0; j < count; j++) {
+      var width = cfg.width[j],
+        cssclass = "";
+
+      if(width > 0){//do not render skipped columns
+        var cell = document.createElement("div");
+        cell.style.width = (width)+"px";
+
+        cssclass = "gantt_task_cell" + (j == count-1 ? " gantt_last_cell" : "");
+        cssTemplate = this.templates.task_cell_class(item, cfg.trace_x[j]);
+        if(cssTemplate)
+          cssclass += " " + cssTemplate;
+        cell.className = cssclass;
+
+        row.appendChild(cell);
+      }
+
+    }
+  }
+  var odd = item.$index%2 !== 0;
+  var cssTemplate = gantt.templates.task_row_class(item.start_date, item.end_date, item);
+  var css = "gantt_task_row" + (odd ? " odd" : "") + (cssTemplate ? ' '+cssTemplate : '');
+
+  if(this.getState().selected_task == item.id){
+    css += " gantt_selected";
+  }
+
+  //var row = "<div class='" + css + "' " + this.config.task_attribute + "='" + item.id + "'>" + cells.join("") + "</div>";
+
+  row.className = css;
+  row.style.height = (gantt.config.row_height)+"px";
+  row.setAttribute(this.config.task_attribute, item.id);
+  return row;
+};
+
+//defined in an extension
+gantt._render_bg_canvas = function(){};
+
+
+gantt._adjust_scales = function(){
+  if(this.config.fit_tasks){
+    var old_min = +this._min_date,
+      old_max = +this._max_date;
+    this._init_tasks_range();
+    if(+this._min_date != old_min || +this._max_date != old_max){
+      this.render();
+
+      this.callEvent("onScaleAdjusted", []);
+      return true;
+    }
+  }
+  return false;
+};
+
+//refresh task and related links
+gantt.refreshTask = function(taskId, refresh_links){
+  gantt._update_parents(gantt.getParent(taskId));
+  this.refresher.refreshTask(taskId);
+};
+gantt._refreshTask = function(taskId){
+  var i;
+  var renders = this._get_task_renderers();
+
+  var task = this.getTask(taskId);
+  //if(task && this.isTaskVisible(taskId)){
+  if(!task){
+    // nothing
+  }else
+  if(+this._min_date < +task.start_date && +this._max_date > +task.end_date){
+    for(i =0; i < renders.length; i++)
+      renders[i].render_item(task);
+
+    for(i=0; i < task.$source.length; i++){
+      gantt.refreshLink(task.$source[i]);
+    }
+    for(i=0; i < task.$target.length; i++){
+      gantt.refreshLink(task.$target[i]);
+    }
+  }else{
+    this.render();   //  HOSEK
+  }
+};
+gantt.refreshLink = function(linkId){
+  this.refresher.refreshLink(linkId);
+};
+gantt._refreshLink = function(linkId){
+  //if(!this._is_render_active())
+  //	return;
+  //ysy.log.debug("Link "+linkId+" is being refreshed","link_render");
+
+  if(this.isLinkExists(linkId)){
+    this._render_link(linkId);
+  }else{
+    var renders = this._get_link_renderers();
+    for(var i =0; i < renders.length; i++)
+      renders[i].remove_item(linkId);
+  }
+};
+
+
+
+gantt._combine_item_class = function(basic, template, itemId){
+  var css = [basic];
+  if(template)
+    css.push(template);
+
+  var state = gantt.getState();
+
+  var task = this.getTask(itemId);
+
+
+  if(task.type){css.push("gantt_"+task.type+"-type");}
+  //if(this._get_safe_type(task.type) == this.config.types.milestone){
+  //	css.push("gantt_milestone");
+  //}
+  //
+  //if(this._get_safe_type(task.type) == this.config.types.project){
+  //	css.push("gantt_project");
+  //}
+
+  if(this._is_flex_task(task))
+    css.push("gantt_dependent_task");
+
+  if(this.config.select_task && itemId == state.selected_task)
+    css.push("gantt_selected");
+
+  if(itemId == state.drag_id){
+    css.push("gantt_drag_" + state.drag_mode);
+    if(state.touch_drag){
+      css.push("gantt_touch_" + state.drag_mode);
+    }
+  }
+  var links = gantt._get_link_state();
+  if(links.link_source_id == itemId)
+    css.push("gantt_link_source");
+
+  if(links.link_target_id == itemId)
+    css.push("gantt_link_target");
+
+
+  if(this.config.highlight_critical_path && this.isCriticalTask){
+    if(this.isCriticalTask(task))
+      css.push("gantt_critical_task");
+  }
+
+  if(links.link_landing_area &&
+    (links.link_target_id && links.link_source_id) &&
+    (links.link_target_id != links.link_source_id)){
+
+    var from_id = links.link_source_id;
+    var from_start = links.link_source_start;
+    var to_start = links.link_target_start;
+
+    var allowDrag = gantt.isLinkAllowed(from_id, itemId, from_start, to_start);
+
+    var dragClass = "";
+    if(allowDrag){
+      if(to_start)
+        dragClass = "link_start_allow";
+      else
+        dragClass = "link_finish_allow";
+    }else{
+      if(to_start)
+        dragClass = "link_start_deny";
+      else
+        dragClass = "link_finish_deny";
+    }
+    css.push(dragClass);
+  }
+  return css.join(" ");
+};
+
+gantt._render_pair = function(parent, css, task, content){
+  var state = gantt.getState();
+
+  if (+task.start_date >= +state.min_date)
+    parent.appendChild(content(css + " task_left"));
+
+  if(+task.end_date <= +state.max_date) {
+    parent.appendChild(content(css + " task_right"));
+  }
+};
+
+gantt._get_task_height = function(){
+  // height of the bar item
+  var height = this.config.task_height;
+  if(height == "full")
+    height = this.config.row_height - 5;
+  //item height cannot be bigger than row height
+  height = Math.min(height, this.config.row_height);
+  return Math.max(height, 0);
+};
+
+gantt._get_milestone_width = function(){
+  return this._get_task_height();
+};
+gantt._get_visible_milestone_width = function(){
+  var origWidth = gantt._get_task_height();//m-s have square shape
+  return Math.sqrt(2*origWidth*origWidth);
+};
+
+// TODO: remove reduntant methods for task positioning
+gantt.getTaskPosition = function(task, start_date, end_date){
+  var x = this.posFromDate(start_date || task.start_date);
+  var x2 = this.posFromDate(end_date || task.end_date);
+  x2 = Math.max(x, x2);
+  var y = this.getTaskTop(task.id);
+  var height = this.config.task_height;
+  return {
+    left:x,
+    top:y,
+    height : height,
+    width: Math.max((x2 - x), 0)
+  };
+};
+
+gantt._get_task_width = function(task, start, end ){
+  return Math.round(this._get_task_pos(task, false).x - this._get_task_pos(task, true).x);
+};
+
+gantt._is_readonly = function(item){
+  if(item && item[this.config.editable_property]){
+    return false;
+  }else{
+    return (item && item[this.config.readonly_property]) || this.config.readonly || this.config["readonly_"+gantt._get_safe_type(item.type)];
+  }
+};
+gantt._task_default_render = function(task){
+  var pos = this._get_task_pos(task);
+
+  var cfg = this.config;
+  var height = this._get_task_height();
+  var type = this._get_safe_type(task.type);
+  var controls = cfg["controls_"+type] || {};
+
+  var padd = Math.floor((this.config.row_height - height)/2);
+  if(type == cfg.types.milestone && cfg.link_line_width > 1){
+    //little adjust milestone position, so horisontal corners would match link arrow when thickness of link line is more than 1px
+    padd += 1;
+  }
+
+  var div = document.createElement("div");
+  var width = gantt._get_task_width(task);
+
+
+  div.setAttribute(this.config.task_attribute, task.id);
+
+  if(cfg.show_progress && (controls.show_progress || controls.progress)){
+    var progress = this._render_task_progress(task,div, width);
+    if(!this._is_readonly(task) && controls.progress){
+      this._render_task_progress_drag(div,progress);
+    }
+  }
+
+  //use separate div to display content above progress bar
+  var content = gantt._render_task_content(task, width);
+  if(task.textColor){
+    content.style.color = task.textColor;
+  }
+  div.appendChild(content);
+
+  var css = this._combine_item_class("gantt_task_line",
+    this.templates.task_class(task.start_date, task.end_date, task),
+    task.id);
+  if(task.color || task.progressColor || task.textColor){
+    css += " gantt_task_inline_color";
+  }
+  div.className = css;
+
+  var styles = [
+    "left:" + pos.x + "px",
+    "top:" + (padd + pos.y) + 'px',
+    "height:" + height + 'px',
+    "line-height:" + height + 'px',
+    "width:" + width + 'px'
+  ];
+  if(task.color){
+    styles.push("background-color:" + task.color);
+  }
+  if(task.textColor){
+    styles.push("color:" + task.textColor);
+  }
+
+  div.style.cssText = styles.join(";");
+  var side = this._render_leftside_content(task);
+  if(side) div.appendChild(side);
+
+  side = this._render_rightside_content(task);
+  if(side) div.appendChild(side);
+
+  if(!this._is_readonly(task)){
+    if(cfg.drag_resize && !this._is_flex_task(task) && controls.resize){
+      gantt._render_pair(div, "gantt_task_drag", task, function(css){
+        var el = document.createElement("div");
+        el.className = css;
+        return el;
+      });
+    }
+    if(cfg.drag_links && this.config.show_links && controls.links){
+      gantt._render_pair(div, "gantt_link_control", task, function(css){
+        var outer = document.createElement("div");
+        outer.className = css;
+        outer.style.cssText = [
+          "height:" + height + 'px',
+          "line-height:" + height + 'px'
+        ].join(";");
+        var inner = document.createElement("div");
+        inner.className = "gantt_link_point";
+        outer.appendChild(inner);
+        return outer;
+      });
+    }
+  }
+  return div;
+};
+
+gantt._render_task_element = function(task){
+  var painters = this.config.type_renderers;
+  var renderer = painters[this._get_safe_type(task.type)],
+    defaultRenderer = this._task_default_render;
+
+  if(!renderer){
+    renderer = defaultRenderer;
+  }
+  return renderer.call(this, task, dhtmlx.bind(defaultRenderer, this));
+};
+
+gantt._render_side_content = function(task, template, cssClass){
+  if(!template) return null;
+
+  var text = template(task.start_date, task.end_date, task);
+  if(!text) return null;
+  var content = document.createElement("div");
+  content.className = "gantt_side_content " + cssClass;
+  content.innerHTML = text;
+  return content;
+};
+
+
+
+gantt._render_leftside_content = function(task){
+  var css = "gantt_left " + gantt._get_link_crossing_css(true, task);
+  return gantt._render_side_content(task, this.templates.leftside_text, css);
+};
+gantt._render_rightside_content = function(task){
+  var css = "gantt_right " + gantt._get_link_crossing_css(false, task);
+  return gantt._render_side_content(task, this.templates.rightside_text, css);
+};
+
+gantt._get_conditions = function(leftside){
+  if(leftside){
+    return {
+      $source : [
+        gantt.config.links.start_to_start
+      ],
+      $target : [
+        gantt.config.links.start_to_start,
+        gantt.config.links.finish_to_start
+      ]
+    };
+  }else{
+    return {
+      $source : [
+        gantt.config.links.finish_to_start,
+        gantt.config.links.finish_to_finish
+      ],
+      $target : [
+        gantt.config.links.finish_to_finish
+      ]
+    };
+  }
+};
+
+gantt._get_link_crossing_css = function(left, task){
+  var cond = gantt._get_conditions(left);
+
+  for(var i in cond){
+    var links = task[i];
+    for(var ln =0; ln < links.length; ln++){
+      var link = gantt.getLink(links[ln]);
+
+      for(var tp =0; tp < cond[i].length; tp++){
+        if(link.type == cond[i][tp]){
+          return "gantt_link_crossing";
+        }
+      }
+    }
+  }
+  return "";
+};
+
+
+
+gantt._render_task_content = function(task, width){
+  var content = document.createElement("div");
+  if(this._get_safe_type(task.type) != this.config.types.milestone)
+    content.innerHTML = this.templates.task_text(task.start_date, task.end_date, task);
+  content.className = "gantt_task_content";
+  //content.style.width = width + 'px';
+  return content;
+};
+gantt._render_task_progress = function(task, element, maxWidth){
+  var done = task.progress*1 || 0;
+
+  maxWidth = Math.max(maxWidth - 2, 0);//2px for borders
+  var pr = document.createElement("div");
+  var width = Math.round(maxWidth*done);
+
+  width = Math.min(maxWidth, width);
+  if(task.progressColor){
+    pr.style.backgroundColor = task.progressColor;
+    pr.style.opacity = 1;
+  }
+  pr.style.width = width + 'px';
+  pr.className = "gantt_task_progress";
+  pr.innerHTML = this.templates.progress_text(task.start_date, task.end_date, task);
+  element.appendChild(pr);
+  return width;
+  //if(this.config.drag_progress && !gantt._is_readonly(task)){
+  //}
+};
+gantt._render_task_progress_drag = function(element,width){
+  var drag = document.createElement("div");
+  drag.style.left = width + 'px';
+  drag.className = "gantt_task_progress_drag";
+  //pr.appendChild(drag);
+  element.appendChild(drag);
+}
+gantt._get_line = function(step) {
+  var steps = {
+    "second": 1,
+    "minute": 60,
+    "hour": 60*60,
+    "day": 60*60*24,
+    "week": 60*60*24*7,
+    "month": 60*60*24*30,
+    "year": 60*60*24*365
+  };
+  return steps[step] || 0;
+};
+
+
+gantt.dateFromPos = function(x){
+  return gantt.dateFromPos2(x);
+  var scale = this._tasks;
+  if(x < 0 || x > scale.full_width || !scale.full_width){
+    return null;
+  }
+
+  var ind = this._findBinary(this._tasks.left, x);
+  var summ = this._tasks.left[ind];
+  //ysy.log.debug("ind="+ind+" summ="+summ,"task_drag");
+  var col_width = scale.width[ind] || scale.col_width;
+  var part = 0;
+  if(col_width)
+    part = (x - summ)/col_width;
+
+  var unit = 0;
+  if(part){
+    unit =  gantt._get_coll_duration(scale, scale.trace_x[ind]);
+  }
+  //ysy.log.debug("trace="+scale.trace_x[ind].valueOf()+" part="+part+" unit="+unit,"task_drag");
+  var date = gantt.date.Date(scale.trace_x[ind].valueOf() + Math.round(part*unit));
+  //ysy.log.debug("old:"+date.toString()+" new:"+gantt.dateFromPos2(x).toString(),"task_drag");
+  return date;
+};
+
+gantt.posFromDate = function(date){
+  var ind = gantt._day_index_by_date(date);
+  dhtmlx.assert(ind >= 0, "Invalid day index");
+
+  var wholeCells = Math.floor(ind);
+  var partCell = ind % 1;
+
+  var pos = gantt._tasks.left[Math.min(wholeCells, gantt._tasks.width.length - 1)];
+  if(wholeCells == gantt._tasks.width.length)
+    pos += gantt._tasks.width[gantt._tasks.width.length - 1];
+  //for(var i=1; i <= wholeCells; i++)
+  //	pos += gantt._tasks.width[i-1];
+
+  if(partCell){
+    if(wholeCells < gantt._tasks.width.length){
+      pos += gantt._tasks.width[wholeCells]*(partCell % 1);
+    }else{
+      pos += 1;
+    }
+
+  }
+  return pos;
+};
+gantt.posFromDateCached = function(date){
+  if (typeof date !== "string") {
+    var momentDate = date;
+    date = date.toISOString();
+  }
+  var pos = gantt._tasks.date_cache[date];
+  if(!pos){
+    pos = gantt.posFromDate(momentDate || date);
+    gantt._tasks.date_cache[date] = pos;
+  }
+  return pos;
+};
+
+gantt._day_index_by_date = function(date){
+  //ysy.log.debug(date,"task_drag");
+  /*if(date.isValid&&!date.isValid()){
+    debugger;
+  }*/
+  var tdate=gantt.date.Date(date);// HOSEK
+  if(date._isEndDate){tdate=moment(tdate).add(1,"days");}
+  var pos = tdate.valueOf();
+  var days = gantt._tasks.trace_x,
+    ignores = gantt._tasks.ignore_x || {};
+
+  if(pos <= this._min_date)
+    return 0;
+
+  if(pos >= this._max_date)
+    return days.length;
+
+  /*var day = null;
+  for (var xind = 0, length = days.length-1; xind < length; xind++) {
+    // | 8:00, 8:30 | 8:15 should be checked against 8:30
+    // clicking at the most left part of the cell, say 8:30 should create event in that cell, not previous one
+    day = +days[xind+1];
+    if (pos < day && !ignores[day])
+      break;
+  }*/
+
+  var day_ind = gantt._findBinary(days, pos);
+  var day = +gantt._tasks.trace_x[day_ind];
+  while(ignores[day]){
+    day = gantt._tasks.trace_x[++day_ind];
+  }
+
+  if(!day) return 0;
+
+  return day_ind + ((tdate - days[day_ind]) / gantt._get_coll_duration(gantt._tasks, days[day_ind]));
+
+
+};
+gantt._findBinary = function(array, target) {
+  // modified binary search, target value not exactly match array elements, looking for closest one
+
+  var low = 0, high = array.length - 1, i, item, prev;
+  while (low <= high) {
+
+    i = Math.floor((low + high) / 2);
+    item = +array[i];
+    prev = +array[i - 1];
+    if (item < target){
+      low = i + 1; continue;
+    }
+    if (item > target){
+      if(!(!isNaN(prev) && prev < target)) {
+        high = i - 1; continue;
+      }else{
+        // if target is between 'i' and 'i-1' return 'i - 1'
+        return i - 1;
+      }
+
+    }
+
+    return i;
+  }
+  return array.length - 1;
+};
+gantt._get_coll_duration = function(scale, date){
+  return gantt.date.add(date, scale.step, scale.unit) -  date;
+};
+
+gantt._get_x_pos = function(task, to_start){
+  to_start = to_start !== false;
+  var x = gantt.posFromDate(to_start ? task.start_date : task.end_date);
+};
+
+gantt.getTaskTop = function(task_id){
+  return this._y_from_ind(this._get_visible_order(task_id));
+};
+
+gantt._get_task_coord = function(task, to_start, x_correction){
+  to_start = to_start !== false;
+  x_correction = x_correction || 0;
+  var isMilestone = (this._get_safe_type(task.type) == this.config.types.milestone);
+
+  var date = null;
+
+  if(to_start && !isMilestone){
+    date = (task.start_date || this._default_task_date(task));
+  }else{
+    date = (task.end_date || this.calculateEndDate(this._default_task_date(task)));
+  }
+  var x = this.posFromDate(date),
+    y = this.getTaskTop(task.id);
+
+  if(isMilestone){
+    if(to_start){
+      x -= x_correction;
+    }else{
+      x += x_correction;
+    }
+  }
+  return {x:x, y:y};
+};
+gantt._get_task_pos = function(task, to_start){
+  to_start = to_start !== false;
+  var mstoneCorrection = gantt._get_milestone_width()/2;
+  return this._get_task_coord(task, to_start, mstoneCorrection);
+};
+
+gantt._get_task_visible_pos = function(task, to_start){
+  to_start = to_start !== false;
+  var mstoneCorrection = gantt._get_visible_milestone_width()/2;
+  return this._get_task_coord(task, to_start, mstoneCorrection);
+};
+
+
+gantt._correct_shift=function(start, back){
+  return start-=((gantt.date.Date(gantt._min_date)).getTimezoneOffset()-(gantt.date.Date(start)).getTimezoneOffset())*60000*(back?-1:1);
+};
+
+
+
+gantt._get_mouse_pos = function(ev){
+  if (ev.pageX || ev.pageY) {
+    var pos = {x: ev.pageX, y: ev.pageY};
+  } else {
+    var d = _isIE ? document.documentElement : document.body;
+    pos = {
+      x:ev.clientX + d.scrollLeft - d.clientLeft,
+      y:ev.clientY + d.scrollTop - d.clientTop
+    };
+  }
+
+  var box = gantt._get_position(gantt.$task_data);
+  pos.x = pos.x - box.x + gantt.$task_data.scrollLeft;
+  pos.y = pos.y - box.y + gantt.$task_data.scrollTop;
+  //ysy.log.debug("POS=["+pos.x+","+pos.y+"]");
+  return pos;
+};
+
+gantt._is_layer = function(dom_element){
+  return (dom_element && dom_element.hasAttribute && dom_element.hasAttribute(this.config.layer_attribute));
+};
+//helper for rendering bars and links
+gantt._task_renderer = function(layer){
+//gantt._task_renderer = function(id, render_one, node, filter){
+  //hash of dom elements is needed to redraw single bar/link
+  if(typeof layer !== "object"){
+    var id=layer;
+  }else{
+    var id = layer.id;
+    var render_one = layer.renderer;
+    var node = layer.container;
+    var filter = layer.filter;
+  }
+  if(!this._task_area_pulls)
+    this._task_area_pulls = {};
+
+  if(!this._task_area_renderers)
+    this._task_area_renderers = {};
+
+  if(this._task_area_renderers[id])
+    return this._task_area_renderers[id];
+
+  if(!render_one)
+    dhtmlx.assert(false, "Invalid renderer call");
+
+  if(node)
+    node.setAttribute(this.config.layer_attribute, true);
+
+  this._task_area_renderers[id] = $.extend({
+    render_item : function(item, container){
+      var pull = gantt._task_area_pulls[id];
+      container = container || node;
+
+
+      if(filter){
+        if(!filter(item)){
+          ysy.log.debug("_task_area_renderers() filtered id="+item.id,"link_render");
+          this.remove_item(item.id);
+          return;
+        }
+      }
+      var dom = render_one.call(gantt, item, pull[item.id]);
+      if(!dom) return;
+      if(pull[item.id]){
+        this.replace_item(item.id, dom);
+      }else{
+        pull[item.id] = dom;
+        container.appendChild(dom);
+      }
+    },
+    clear : function(container){
+      this.rendered = gantt._task_area_pulls[id] = {};
+      container = container || node;
+      if(container)
+        container.innerHTML = "";
+    },
+    render_items : function(items, container){
+      container = container || node;
+      this.clear(container);
+      var buffer = document.createDocumentFragment();
+      for(var i= 0, vis = items.length; i < vis; i++){
+        this.render_item(items[i], buffer);
+      }
+      container.appendChild(buffer);
+    },
+    replace_item: function(item_id, newNode){
+      var item = this.rendered[item_id];
+      if(item && item.parentNode){
+        item.parentNode.replaceChild(newNode, item);
+      }
+      this.rendered[item_id] = newNode;
+    },
+    remove_item:function(item_id){
+      var item = this.rendered[item_id];
+      if(item && item.parentNode){
+        item.parentNode.removeChild(item);
+      }
+      delete this.rendered[item_id];
+    },
+    change_id: function(oldid, newid) {
+        this.rendered[newid] = this.rendered[oldid];
+        delete this.rendered[oldid];
+    },
+    rendered : this._task_area_pulls[id] || {},
+    node: node,
+    unload : function(){
+      this.clear();
+      delete gantt._task_area_renderers[id];
+      delete gantt._task_area_pulls[id];
+    }
+  },layer);
+
+  return this._task_area_renderers[id];
+};
+
+gantt._clear_renderers = function(){
+  for(var i in this._task_area_renderers){
+    this._task_renderer(i).unload();
+  }
+};
+
+
+
+// --#include core/tasks_canvas_render.js
+gantt.attachEvent("onGanttReady", function(){
+  gantt._task_layers.add();
+  gantt._link_layers.add();
+});
+
+gantt._layers = {
+  prepareConfig: function(config){
+    if(typeof config == "function"){
+      config = {renderer: config};
+    }
+
+    var id = config.id = dhtmlx.uid();
+
+    if(!config.container)
+      config.container = document.createElement("div");
+
+    return config;
+  },
+  create: function(get_container, rel_root){
+    return {
+      tempCollection:[],
+      renderers:{},
+      container: get_container,
+      getRenderers: function(){
+        var res = [];
+        for (var i in this.renderers){
+          res.push(this.renderers[i]);
+        }
+        return res;
+      },
+      getRenderer: function(id){
+        return this.renderers[id];
+      },
+      add: function(layer){
+        if(layer)
+          this.tempCollection.push(layer);
+        //this.process();
+      },
+      // TODO put process to all layer creators
+      process: function(){
+        if(!this.container()) return;
+        var container = this.container();
+        var pending = this.tempCollection;
+        for(var i =0; i < pending.length; i++){
+          var layer = pending[i];
+          var node = layer.container,
+              id = layer.id,
+              topmost = layer.topmost;
+          if(!node.parentNode){
+            //insert on top or below the tasks
+            if(topmost){
+              container.appendChild(node);
+            }else{
+              var rel = rel_root ? rel_root() : container.firstChild;
+              if(rel)
+                container.insertBefore(node, rel);
+              else
+                container.appendChild(node);
+            }
+          }
+          this.renderers[id] = gantt._task_renderer(layer);
+          //this.renderers[id] = gantt._task_renderer(id, layer.renderer, node, layer.filter);
+          this.tempCollection.splice(i,1);
+          i--;
+        }
+      },
+      remove: function(id){
+        this.renderers[id].unload();
+        delete this.renderers[id];
+      },
+      clear: function(){
+        for(var i in this.renderers){
+          this.renderers[i].unload();
+        }
+        this.renderers = {};
+      }
+    };
+  }
+};
+
+gantt._create_filter = function(filter_methods){
+  if(!(filter_methods instanceof Array)){
+    filter_methods = Array.prototype.slice.call(arguments, 0);
+  }
+
+  return function(obj){
+    var res = true;
+    for(var i = 0, len = filter_methods.length; i < len; i++){
+      var filter_method = filter_methods[i];
+      if(gantt[filter_method]){
+        res = res && (gantt[filter_method].call(gantt, obj.id, obj) !== false);
+      }
+    }
+
+    return res;
+  };
+};
+
+gantt._add_generic_layer = function(layersManager, filters){
+  return function(config){
+    if(config.filter === undefined){
+      config.filter = gantt._create_filter(filters);
+    }
+    config = gantt._layers.prepareConfig(config);
+    layersManager.add(config);
+    return config.id;
+  };
+};
+
+gantt._task_layers = gantt._layers.create(function(){return gantt.$task_data; }, function(){return gantt.$task_links;});
+
+gantt._link_layers = gantt._layers.create(function(){return gantt.$task_data; });
+
+gantt.addTaskLayer = gantt._add_generic_layer(gantt._task_layers, ['_filter_task', '_is_chart_visible']);
+
+gantt.removeTaskLayer = function(id){
+  gantt._task_layers.remove(id);
+};
+
+gantt.addLinkLayer = gantt._add_generic_layer(gantt._link_layers, ['_filter_link', '_is_chart_visible']);
+gantt.removeLinkLayer = function(id){
+  gantt._link_layers.remove(id);
+};
+
+gantt._get_task_renderers = function(){
+  return this._task_layers.getRenderers();
+};
+gantt._get_link_renderers = function(){
+  return this._link_layers.getRenderers();
+};
+
+gantt._pull = {};
+gantt._branches = {};
+gantt._order = [];
+gantt._lpull = {};
+
+gantt.load = function(url, type, callback){
+  this._load_url = url;
+  dhtmlx.assert(arguments.length, "Invalid load arguments");
+  this.callEvent("onLoadStart", []);
+  var tp = 'json', cl = null;
+  if(arguments.length >= 3){
+    tp = type;
+    cl = callback;
+  }else{
+    if(typeof arguments[1] == "string")
+      tp = arguments[1];
+    else if(typeof arguments[1] == "function")
+      cl = arguments[1];
+  }
+
+  this._load_type = tp;
+
+  dhx4.ajax.get(url, dhtmlx.bind(function(l) {
+    this.on_load(l, tp);
+    this.callEvent("onLoadEnd", []);
+    if(typeof cl == "function")
+      cl.call(this);
+  }, this));
+};
+gantt.parse = function(data, type) {
+  this.on_load({xmlDoc: {responseText: data}}, type);
+};
+
+gantt.serialize = function(type){
+  type = type || "json";
+  return this[type].serialize();
+};
+
+/*
+tasks and relations
+{
+data:[
+  {
+    "id":"string",
+    "text":"...",
+    "start_date":"Date or string",
+    "end_date":"Date or string",
+    "duration":"number",
+    "progress":"0..1",
+    "parent_id":"string",
+    "order":"number"
+  },...],
+links:[
+  {
+    id:"string",
+    source:"string",
+    target:"string",
+    type:"string"
+  },...],
+collections:{
+    collectionName:[
+      {key:, label:, optional:...},...
+    ],...
+  }
+}
+
+ gantt._pull - id to object hash
+ gantt._branch - array of per branch arrays of objects|ids
+ gantt._order - array of visible elements
+ gantt._order_full - array of all elements
+
+ gantt._links
+* */
+
+gantt.on_load = function(resp, type){
+  this.callEvent("onBeforeParse", []);
+  if(!type)
+    type = "json";
+  dhtmlx.assert(this[type], "Invalid data type:'" + type + "'");
+
+  var raw = resp.xmlDoc.responseText;
+
+  var data = this[type].parse(raw, resp);
+  this._process_loading(data);
+};
+
+
+
+gantt._process_loading = function(data){
+  if(data.collections)
+    this._load_collections(data.collections);
+
+  var tasks = data.data;
+  var task;
+  for (var i = 0; i < tasks.length; i++) {
+    task = tasks[i];
+    this._init_task(task);
+    if (!this.callEvent("onTaskLoading", [task])) continue;
+    this._pull[task.id] = task;
+  }
+
+  for (var i in this._pull){
+    task = this._pull[i];
+    this.setParent(task, this.getParent(task) || this.config.root_id);
+  }
+
+    // calculating $level for each item
+  for (var i in this._pull){
+    task = this._pull[i];
+    this._add_branch(task, true);
+    task.$level = this.calculateTaskLevel(task);
+  }
+  this._sync_order();
+  this._init_links(data.links || (data.collections ? data.collections.links : []));
+  this.callEvent("onParse", []);
+  this.render();
+  if(this.config.initial_scroll){
+    var id = (this._order[0] || this.config.root_id);
+    if(id)
+      this.showTask(id);
+  }
+};
+
+
+gantt._init_links = function(links){
+  if (links)
+    for(var i=0; i < links.length; i++){
+      if(links[i]){
+          var link = this._init_link(links[i]);
+          this._lpull[link.id] = link;
+      }
+    }
+    this._sync_links();
+};
+
+
+gantt._load_collections = function(collections){
+  var collections_loaded = false;
+  for (var key in collections) {
+    if (collections.hasOwnProperty(key)) {
+      collections_loaded = true;
+      var collection = collections[key];
+      var arr = this.serverList[key];
+      if (!arr) continue;
+      arr.splice(0, arr.length); //clear old options
+      for (var j = 0; j < collection.length; j++) {
+        var option = collection[j];
+        var obj =  dhtmlx.copy(option);
+        obj.key = obj.value;// resulting option object
+
+        for (var option_key in option) {
+          if (option.hasOwnProperty(option_key)) {
+            if (option_key == "value" || option_key == "label")
+              continue;
+            obj[option_key] = option[option_key]; // obj['value'] = option['value']
+          }
+        }
+        arr.push(obj);
+      }
+    }
+  }
+  if (collections_loaded)
+    this.callEvent("onOptionsLoad", []);
+};
+
+gantt._sync_order = function(silent) {
+  this._order = [];
+  this._sync_order_item({parent:this.config.root_id, $open:true, $ignore:true, id:this.config.root_id});
+  this._order.push("empty");
+
+  if(!silent){
+    this._scroll_resize();
+    this._set_sizes();
+  }
+};
+gantt.attachEvent("onBeforeTaskDisplay", function(id, task){
+  return !task.$ignore;
+});
+gantt._sync_order_item = function(item) {
+  if(!item){
+    ysy.log.debug("error: no item");
+  }
+  if (!item.id
+      || this._filter_task(item.id, item)
+      && this.callEvent("onBeforeTaskDisplay", [item.id, item])) {
+    if (item.id) { //do not trigger event for virtual root
+      this._order.push(item.id);
+    }
+    if (item.$open) {
+      var children = this.getChildren(item.id);
+      if (children)
+        for (var i = 0; i < children.length; i++)
+          this._sync_order_item(this._pull[children[i]]);
+    }
+  }
+};
+
+gantt._get_visible_order = function(id){
+  dhtmlx.assert(id, "Invalid argument");
+  var ord = this._order;
+  for(var i= 0, count = ord.length; i < count; i++)
+    if(ord[i] == id) return i;
+
+  return -1;
+};
+
+
+
+gantt.eachTask = function(code, parent, master){
+  parent = parent || this.config.root_id;
+  master = master || this;
+
+  var branch = this.getChildren(parent);
+  if (branch)
+    for (var i=0; i<branch.length; i++){
+      var item = this._pull[branch[i]];
+      code.call(master, item);
+      if (this.hasChild(item.id))
+        this.eachTask(code, item.id, master);
+    }
+};
+
+gantt.json = {
+  parse : function(data){
+    dhtmlx.assert(data, "Invalid data");
+
+    if (typeof data == "string") {
+      if(window.JSON)
+        data = JSON.parse(data);
+      else{
+        gantt._temp = eval("(" + data + ")");
+        data = gantt._temp || {};
+        gantt._temp = null;
+      }
+    }
+
+    if (data.dhx_security)
+      dhtmlx.security_key = data.dhx_security;
+    return data;
+  },
+  _copyLink:function(obj){
+    var copy = {};
+    for (var key in obj)
+      copy[key] = obj[key];
+    return copy;
+  },
+  _copyObject:function(obj){
+    var copy = {};
+    for (var key in obj){
+      if (key.charAt(0) == "$")
+        continue;
+      copy[key] = obj[key];
+
+      if(copy[key] instanceof Date){
+        copy[key] = gantt.templates.xml_format(copy[key]);
+      }
+    }
+    return copy;
+  },
+  serialize:function(){
+    var tasks = [];
+    var links = [];
+
+    gantt.eachTask(function(obj){
+      gantt.resetProjectDates(obj);
+      tasks.push(this._copyObject(obj));
+    }, gantt.config.root_id, this);
+    for (var key in gantt._lpull)
+      links.push(this._copyLink(gantt._lpull[key]));
+
+    return {
+      data : tasks,
+      links: links
+    };
+  }
+};
+
+/*
+<data>
+  <task id:"some" parent_id="0" progress="0.5">
+    <text>My task 1</text>
+    <start_date>16.08.2013</start_date>
+    <end_date>22.08.2013</end_date>
+  </task>
+  <coll_options>
+    <links>
+      <link source='a1' target='b2' type='c3' />
+    </links>
+  </coll_options>
+</data>
+*/
+
+gantt.xml = {
+  _xmlNodeToJSON:function(node, attrs_only){
+    var t = {};
+    for (var i = 0; i < node.attributes.length; i++)
+      t[node.attributes[i].name] = node.attributes[i].value;
+
+    if (!attrs_only){
+      for (var i = 0; i < node.childNodes.length; i++) {
+        var child = node.childNodes[i];
+        if (child.nodeType == 1)
+          t[child.tagName] = child.firstChild ? child.firstChild.nodeValue : "";
+      }
+
+      if (!t.text) t.text = node.firstChild ? node.firstChild.nodeValue : "";
+    }
+
+    return t;
+  },
+  _getCollections:function(loader){
+    var collection = {};
+    var opts =   dhx4.ajax.xpath("//coll_options", loader);
+    for (var i = 0; i < opts.length; i++) {
+      var bind = opts[i].getAttribute("for");
+      var arr = collection[bind] = [];
+      var itms =  dhx4.ajax.xpath(".//item", opts[i]);
+      for (var j = 0; j < itms.length; j++) {
+        var itm = itms[j];
+        var attrs = itm.attributes;
+        var obj = { key: itms[j].getAttribute("value"), label: itms[j].getAttribute("label")};
+        for (var k = 0; k < attrs.length; k++) {
+          var attr = attrs[k];
+          if (attr.nodeName == "value" || attr.nodeName == "label")
+            continue;
+          obj[attr.nodeName] = attr.nodeValue;
+        }
+        arr.push(obj);
+      }
+    }
+    return collection;
+  },
+  _getXML:function(text, loader, toptag){
+    toptag = toptag || "data";
+    if (!loader.getXMLTopNode){
+      loader = dhx4.ajax.parse(loader);
+    }
+
+    var xml = dhx4.ajax.xmltop(toptag, loader.xmlDoc);
+    if (xml.tagName != toptag) throw "Invalid XML data";
+
+    var skey = xml.getAttribute("dhx_security");
+    if (skey)
+      dhtmlx.security_key = skey;
+
+    return xml;
+  },
+  parse:function(text, loader){
+    loader = this._getXML(text, loader);
+    var data = { };
+
+    var evs = data.data = [];
+    var xml = dhx4.ajax.xpath("//task", loader);
+
+    for (var i = 0; i < xml.length; i++)
+      evs[i] = this._xmlNodeToJSON(xml[i]);
+
+    data.collections = this._getCollections(loader);
+    return data;
+  },
+  _copyLink:function(obj){
+    return "<item id='"+obj.id+"' source='"+obj.source+"' target='"+obj.target+"' type='"+obj.type+"' />";
+  },
+  _copyObject:function(obj){
+    return "<task id='"+obj.id+"' parent='"+(obj.parent||"")+"' start_date='"+obj.start_date+"' duration='"+obj.duration+"' open='"+(!!obj.open)+"' progress='"+obj.progress+"' end_date='"+obj.end_date+"'><![CDATA["+obj.text+"]]></task>";
+  },
+  serialize:function(){
+    var tasks = [];
+    var links = [];
+
+    var json = gantt.json.serialize();
+    for(var i= 0, len = json.data.length; i < len; i++){
+      tasks.push(this._copyObject(json.data[i]));
+    }
+    for(var i= 0, len = json.links.length; i < len; i++){
+      links.push(this._copyLink(json.links[i]));
+    }
+    return "<data>"+tasks.join("")+"<coll_options for='links'>"+links.join("")+"</coll_options></data>";
+  }
+};
+
+
+gantt.oldxml = {
+  parse:function(text, loader){
+    loader = gantt.xml._getXML(text, loader, "projects");
+    var data = { collections:{ links:[] } };
+
+    var evs = data.data = [];
+    var xml = dhx4.ajax.xpath("//task", loader);
+
+    for (var i = 0; i < xml.length; i++){
+      evs[i] = gantt.xml._xmlNodeToJSON(xml[i]);
+      var parent = xml[i].parentNode;
+
+      if (parent.tagName == "project")
+        evs[i].parent = "project-"+parent.getAttribute("id");
+      else
+        evs[i].parent = parent.parentNode.getAttribute("id");
+    }
+
+    xml = dhx4.ajax.xpath("//project", loader);
+    for (var i = 0; i < xml.length; i++){
+      var ev = gantt.xml._xmlNodeToJSON(xml[i], true);
+      ev.id ="project-"+ev.id;
+      evs.push(ev);
+    }
+
+    for (var i=0; i<evs.length; i++){
+      var ev = evs[i];
+      ev.start_date = ev.startdate || ev.est;
+      ev.end_date = ev.enddate;
+      ev.text = ev.name;
+      ev.duration = ev.duration / 8;
+      ev.open = 1;
+      if (!ev.duration && !ev.end_date) ev.duration = 1;
+      if (ev.predecessortasks)
+        data.collections.links.push({ target:ev.id, source:ev.predecessortasks, type:gantt.config.links.finish_to_start });
+    }
+
+    return data;
+  },
+  serialize:function(){
+    dhtmlx.message("Serialization to 'old XML' is not implemented");
+  }
+};
+
+gantt.serverList = function(name, array) {
+  if (array) {
+    this.serverList[name] = array.slice(0);
+  }else if(!this.serverList[name]){
+    this.serverList[name] = [];
+  }
+  return this.serverList[name];
+};
+
+gantt.getTask = function(id) {
+  dhtmlx.assert(id, "Invalid argument for gantt.getTask");
+  dhtmlx.assert(this._pull[id], "Task not found id=" + id);
+  return this._pull[id];
+};
+gantt.getTaskByTime = function(from, to){
+  var p = this._pull,
+    res = [],
+    pos = 0,
+    taken = 0;
+
+  if(!(from || to)){
+        for (var t in p) res.push(p[t]);
+  }else{
+    from = +from || -Infinity;
+    to = +to || Infinity;
+    for (var t in p){
+      var task = p[t];
+      if (+task.start_date < to && +task.end_date > from)
+        res.push(task);
+    }
+  }
+
+  return res;
+};
+
+gantt.isTaskExists = function(id) {
+  return dhtmlx.defined(this._pull[id]);
+};
+
+gantt.isTaskVisible = function(id){
+  if(!this._pull[id])
+    return false;
+
+  if(!(+this._pull[id].start_date < +this._max_date && +this._pull[id].end_date > +this._min_date))
+    return false;
+
+  for(var i= 0, count = this._order.length; i < count; i++)
+    if(this._order[i] == id) return true;
+  return false;
+};
+
+
+gantt.updateTask = function (id, item) {
+  if (!dhtmlx.defined(item))
+    item = this.getTask(id);
+  if (this.callEvent("onBeforeTaskUpdate", [id, item]) === false)
+    return false;
+
+  this._pull[item.id] = item;
+  if (!this._is_parent_sync(item)) {
+    this._resync_parent(item);
+  }
+  //this._update_parents(item.id);
+  item.widget.update(item);
+  this.refreshTask(item.id);
+  this.callEvent("onAfterTaskUpdate", [id, item]);
+  this._sync_order();
+  this._adjust_scales();
+};
+gantt._add_branch = function(task, silent){
+  var pid = this.getParent(task);
+  if (!this.hasChild(pid))
+    this._branches[pid] = [];
+  var branch = this.getChildren(pid);
+  var added_already = false;
+  for(var i = 0, length = branch.length; i < length; i++){
+    if(branch[i] == task.id){
+      added_already = true;
+      break;
+    }
+  }
+  if(!added_already)
+    branch.push(task.id);
+
+  this._sync_parent(task);
+  if(!silent){
+    this._sync_order(silent);
+  }
+};
+
+gantt._move_branch = function(task, old_parent, new_parent){
+  this.setParent(task, new_parent);
+  this._sync_parent(task);
+  this._replace_branch_child(old_parent, task.id);
+  if(this.isTaskExists(new_parent) || new_parent == this.config.root_id){
+
+    this._add_branch(task);
+  }else{
+    delete this._branches[task.id];
+  }
+  task.$level =  this.calculateTaskLevel(task);
+  //this._sync_order();    // HOSEK
+};
+gantt._resync_parent = function(task){
+  this._move_branch(task, task.$rendered_parent, this.getParent(task));
+};
+gantt._sync_parent = function(task){
+  task.$rendered_parent = this.getParent(task);
+};
+gantt._is_parent_sync = function(task){
+  return (task.$rendered_parent == this.getParent(task));
+};
+
+
+gantt._replace_branch_child = function(node, old_id, new_id){
+  var branch;
+  ysy.log.debug("_replace_branch_child","sort");
+  /*if(!node){  // HOSEK
+    ysy.log.warning("deprecated: has to be node");
+    return;
+    for(var i in this._branches){
+      this._replace_branch_child(i,old_id,new_id);
+    }
+    return;
+  }*/
+  branch = this.getChildren(node);
+  if (branch){
+    var newbranch = [];
+    for (var i=0; i<branch.length; i++){
+      if (branch[i] != old_id)
+        newbranch.push(branch[i]);
+      else if (new_id)
+        newbranch.push(new_id);
+    }
+    ysy.log.debug("new Branch="+JSON.stringify(newbranch),"sort");
+    this._branches[node] = newbranch;
+    //this._sync_order();
+  }
+};
+
+gantt.addTask = function(item, parent) {
+  if (!dhtmlx.defined(parent)) parent = this.getParent(item) || 0;
+  if (!this.isTaskExists(parent)) parent = 0;
+  this.setParent(item, parent);
+  item = this._init_task(item);
+
+  if (this.callEvent("onBeforeTaskAdd", [item.id, item])===false) return false;
+
+  this._pull[item.id] = item;
+
+  this._add_branch(item);
+
+  this.refreshData();
+  this._adjust_scales();
+  this.callEvent("onAfterTaskAdd", [item.id, item]);
+  return item.id;
+};
+
+gantt.addTaskFaster = function (item, parent) {
+  ysy.log.debug("addTaskNoDraw()","add_task");
+  if (!dhtmlx.defined(parent)) parent = this.getParent(item) || 0;
+  if (!this.isTaskExists(parent)) parent = 0;
+  this.setParent(item, parent);
+  item = this._init_task(item);
+
+  this._pull[item.id] = item;
+
+  this._add_branch(item,true);
+
+  this.refreshData();
+  if(+this._min_date > +item.start_date || +this._max_date < +item.end_date){
+    this.render();
+  }
+
+  return item.id;
+};
+
+gantt._default_task_date = function(item, parent_id){
+  var parent = (parent_id && parent_id != this.config.root_id) ? this.getTask(parent_id) : false,
+    startDate = '';
+  if(parent){
+    startDate = parent.start_date;
+  }else{
+    var first = this._order[0];
+    startDate = first ? this.getTask(first).start_date : (this.config.start_date || this.getState().min_date);
+  }
+  return gantt.date.Date(startDate);
+};
+
+gantt._set_default_task_timing = function(task){
+  task.start_date = task.start_date || gantt._default_task_date(task, this.getParent(task));
+  task.duration = task.duration || this.config.duration_step;
+  task.end_date = task.end_date || this.calculateEndDate(task.start_date, task.duration);
+};
+
+/*gantt.createTask = function(item, parent){
+  item = item || {};
+  item.id = dhtmlx.uid();
+  if(!item.start_date){
+    item.start_date = gantt._default_task_date(item, parent);
+  }
+  if(item.text === undefined){
+    item.text = gantt.locale.labels.new_task;
+  }
+  if(item.duration === undefined){
+    item.duration = 1;
+  }
+
+  if(parent){
+    this.setParent(item, parent);
+    parent = this.getTask(parent);
+    parent.$open = true;
+  }
+
+  if(!this.callEvent("onTaskCreated", [item])){
+    return null;
+  }
+  if (this.config.details_on_create){
+    item.$new = true;
+    this._pull[item.id] = this._init_task(item);
+
+    this._add_branch(item);
+    item.$level = this.calculateTaskLevel(item);
+    this.selectTask(item.id);
+    this.refreshData();
+    this.showLightbox(item.id);
+  }else{
+    if (this.addTask(item)){
+      this.showTask(item.id);
+      this.selectTask(item.id);
+    }
+  }
+  return item.id;
+};*/
+
+gantt.deleteTask = function(id) {
+  return this._deleteTask(id);
+};
+
+//TODO: do something with overcomplicated dataprocessor logic
+gantt._getChildLinks = function(id){
+  var item = this.getTask(id);
+  if(!item){
+    return [];
+  }
+
+  var links = item.$source.concat(item.$target);
+
+  var branches = this.getChildren(item.id);
+  for (var i = 0; i < branches.length; i++) {
+    links = links.concat(this._getChildLinks(branches[i]));
+  }
+
+  var res = {};
+  for(var i=0; i < links.length; i++){
+    res[links[i]] = true;
+  }
+  links = [];
+  for(var i in res){
+    links.push(i);
+  }
+
+  return links;
+};
+gantt._getTaskTree = function(id){
+  var item = this.getTask(id);
+  if(!item){
+    return [];
+  }
+
+  var items = [];
+  var branches = this.getChildren(item.id);
+  for (var i = 0; i < branches.length; i++) {
+    items.push(branches[i]);
+    items = items.concat(this._getTaskTree(branches[i]));
+  }
+  return items;
+};
+gantt._deleteRelatedLinks = function(links, silent){
+  var use_dp = (this._dp && !silent);
+  var prev_mode = '';
+  var send_changes = use_dp ? this._dp.updateMode != 'off' : false;
+  if (use_dp){
+    prev_mode = this._dp.updateMode;
+    this._dp.setUpdateMode("off");
+  }
+  for(var i =0; i < links.length; i++){
+    if (use_dp) {
+      this._dp.setGanttMode("links");
+      this._dp.setUpdated(links[i],true,"deleted");
+    }
+    this._deleteLink(links[i], true);
+  }
+
+  if(use_dp){
+    this._dp.setUpdateMode(prev_mode);
+    if(send_changes)
+      this._dp.sendAllData();
+  }
+};
+gantt._deleteRelatedTasks = function(id, silent){
+  var use_dp = (this._dp && !silent);
+  var prev_mode = '';
+
+  if (use_dp) {
+    prev_mode = this._dp.updateMode;
+    this._dp.setGanttMode("tasks");
+    this._dp.setUpdateMode("off");
+  }
+  var tree = this._getTaskTree(id);
+  for (var i = 0; i < tree.length; i++) {
+    // add deleted subrow into dataprocessor update list manually
+    // because silent mode is on
+    var t_id = tree[i];
+    this._unset_task(t_id);
+    if(use_dp){
+      this._dp.setUpdated(t_id,true,"deleted");
+    }
+  }
+  if(use_dp){
+
+    this._dp.setUpdateMode(prev_mode);
+  }
+};
+gantt._unset_task = function(id){
+  var item = this.getTask(id);
+  this._update_flags(id, null);
+  delete this._pull[id];
+  this._move_branch(item, this.getParent(item), null);
+};
+gantt._deleteTask = function(id, silent) {
+  var item = this.getTask(id);
+  if (!silent && this.callEvent("onBeforeTaskDelete", [id, item])===false) return false;
+
+  var links = gantt._getChildLinks(id);
+  this._deleteRelatedTasks(id, silent);
+  this._deleteRelatedLinks(links, silent);
+  this._unset_task(id);
+  if (!silent) {
+    this.callEvent("onAfterTaskDelete", [id, item]);
+    this.refreshData();
+  }
+  return true;
+};
+
+gantt.clearAll = function() {
+  this._clear_data();
+  this.callEvent("onClear", []);
+  this.refreshData();
+};
+gantt._clear_data = function(){
+  this._pull = {};
+  this._branches = {};
+  this._order = [];
+  this._order_full = [];
+  this._lpull = {};
+  this._update_flags();
+  this.userdata = {};
+};
+
+gantt._update_flags = function(oldid, newid){
+  // TODO: need a proper way to update all possible flags
+  if(oldid === undefined){
+    this._lightbox_id = this._selected_task = null;
+    if (this._tasks_dnd.drag){
+      this._tasks_dnd.drag.id = null;
+    }
+  }else{
+    if (this._lightbox_id == oldid)
+      this._lightbox_id = newid;
+    if (this._selected_task == oldid){
+      this._selected_task = newid;
+    }
+    if (this._tasks_dnd.drag && this._tasks_dnd.drag.id == oldid){
+      this._tasks_dnd.drag.id = newid;
+    }
+  }
+};
+//gantt.changeTaskId = function(oldid, newid) {
+//    var item = this._pull[newid] = this._pull[oldid];
+//    this._pull[newid].id = newid;
+//    delete this._pull[oldid];
+//    for (var id in this._pull) {
+//		var task = this._pull[id];
+//		if(this.getParent(task) == oldid)
+//			this.setParent(task, newid);
+//    }
+//	this._update_flags(oldid, newid);
+//    this._replace_branch_child(this.getParent(item), oldid, newid);
+//
+//	this.callEvent("onTaskIdChange", [oldid, newid]);
+//};
+
+gantt._get_duration_unit = function(){
+  return (gantt._get_line(this.config.duration_unit)*1000) || this.config.duration_unit;
+};
+
+gantt._get_safe_type = function(type){
+  return type || "task";
+};
+gantt._get_type_name = function(type_value){
+  for(var i in this.config.types){
+    if(this.config.types[i] == type_value){
+      return i;
+    }
+  }
+  return "task";
+};
+gantt.getWorkHours = function(date){
+  return this._working_time_helper.get_working_hours(date);
+};
+
+gantt.setWorkTime = function(config){
+  this._working_time_helper.set_time(config);
+};
+
+gantt.isWorkTime = function(date, unit){
+  var helper = this._working_time_helper;
+  return helper.is_working_unit(date, unit || this.config.duration_unit);
+};
+
+gantt.correctTaskWorkTime = function(task){
+    alert("Forbidden function correctTaskWorkTime");
+  if(gantt.config.work_time && gantt.config.correct_work_time){
+    if(!gantt.isWorkTime(task.start_date)){
+      task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'});
+      task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
+    }else if(!gantt.isWorkTime(new Date(+task.end_date - 1))){
+      task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
+    }
+  }
+};
+
+gantt.getClosestWorkTime = function(config){
+  var helper = this._working_time_helper;
+  if(config instanceof Date){
+    config = {
+      date:config
+    };
+  }
+  config.dir = config.dir || 'any';
+  config.unit = config.unit || this.config.duration_unit;
+  return helper.get_closest_worktime(config);
+};
+
+gantt.calculateDuration = function(start_date, end_date){
+  var helper = this._working_time_helper;
+  return helper.get_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step);
+};
+gantt._hasDuration = function(start_date, end_date){
+  var helper = this._working_time_helper;
+  return helper.is_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step);
+};
+
+gantt.calculateEndDate = function(start, duration, unit){
+  var helper = this._working_time_helper;
+  return helper.add_worktime(start, duration, unit || this.config.duration_unit);
+};
+
+gantt._init_task = function(task){
+  if (!dhtmlx.defined(task.id))
+     task.id = dhtmlx.uid();
+
+  if(task.start_date)
+    task.start_date = gantt.date.parseDate(task.start_date, "xml_date");
+  if(task.end_date)
+    task.end_date = gantt.date.parseDate(task.end_date, "xml_date");
+
+
+
+  if(task.start_date){
+    if(!task.end_date && task.duration){
+      task.end_date = this.calculateEndDate(task.start_date, task.duration);
+    }
+  }
+
+  gantt._init_task_timing(task);
+  if(task.start_date && task.end_date){
+    //gantt.correctTaskWorkTime(task);   // HOSEK neopravovat počátky a konce dat ze serveru
+  }
+
+  task.$source = [];
+  task.$target = [];
+  if(task.parent === undefined){
+    this.setParent(task, this.config.root_id);
+  }
+  task.$open = dhtmlx.defined(task.open) ? task.open : this.config.open_tree_initially;
+  task.$level = this.calculateTaskLevel(task);
+  return task;
+};
+
+gantt._init_task_timing = function(task){
+  var task_type = this._get_safe_type(task.type);
+
+  if(task.$rendered_type === undefined){
+    task.$rendered_type = task_type;
+  }else if(task.$rendered_type != task_type){
+    delete task.$no_end;
+    delete task.$no_start;
+    task.$rendered_type = task_type;
+  }
+
+  if((task.$no_end === undefined || task.$no_start === undefined) && task_type != this.config.types.milestone){
+    if(task_type == this.config.types.project){
+      //project duration is always defined by children duration
+      task.$no_end = task.$no_start = true;
+      this._set_default_task_timing(task);
+    }else{
+      //tasks can have fixed duration, children duration(as projects), or one date fixed, and other defined by nested items
+      task.$no_end = !(task.end_date || task.duration);
+      task.$no_start = !task.start_date;
+    }
+  }
+
+  if (task.start_date && task.end_date){
+    task.duration = Math.max(this.calculateDuration(task.start_date, task.end_date),1);   // HOSEK - because of weekend issues
+  }
+  task.duration = task.duration || 0;
+};
+gantt._is_flex_task = function(task){
+  return !!(task.$no_end || task.$no_start);
+};
+
+// downward calculation of project duration
+gantt.resetProjectDates = function(task){
+  if(task.$no_end || task.$no_start){
+    var dates = this.getSubtaskDates(task.id);
+    if (task.minimal_end && task.minimal_end > dates.end_date) {
+      dates.end_date = moment(task.minimal_end);
+      dates.end_date._isEndDate = true;
+    }
+    if (task.maximal_start && task.maximal_start < dates.start_date) {
+      dates.start_date = moment(task.maximal_start);
+    }
+    ysy.log.debug("resetProjectDates to start="+dates.start_date+" end="+dates.end_date,"parent");
+    this._assign_project_dates(task, dates.start_date, dates.end_date);
+  }
+};
+
+gantt.getSubtaskDates = function(task_id){
+  var min = null,
+    max = null,
+    root = task_id !== undefined ? task_id : gantt.config.root_id;
+
+  this.eachTask(function(child){
+    //if(this._get_safe_type(child.type) == gantt.config.types.project)
+    //	return;
+    //var dates=gantt.getSubtaskDates(child.id);
+    if(child.start_date){
+      if(!min || min > child.start_date.valueOf()){
+        min=child.start_date.valueOf();
+      }
+    }
+    if(child.end_date){
+      if(!max || max < child.end_date.valueOf()){
+        max=child.end_date.valueOf();
+      }
+    }
+    //ysy.log.debug("subtaskDates: root: "+task_id+" child: "+child.id+" min:"+moment(min).format("YYYY-MM-DD"));
+    //if((child.start_date && !child.$no_start) && (!min || min > child.start_date.valueOf()))
+    //	min = child.start_date.valueOf();
+    //if((child.end_date && !child.$no_end) && (!max || max < child.end_date.valueOf()))
+    //	max = child.end_date.valueOf();
+  }, root);
+  if((!min || !max) && task_id){
+    var task = gantt.getTask(task_id);
+    min = min || task.start_date;
+    max = max || task.end_date;
+  }
+  var start_date = moment(min);
+  var end_date = moment(max);
+  end_date._isEndDate=true;
+  return {
+    start_date:start_date.isValid()?start_date:null,
+    end_date:end_date.isValid()?end_date:null
+  };
+  return {
+    start_date: min ? gantt.date.Date(min) : null,
+    end_date: max ? gantt.date.Date(max): null
+  };
+};
+
+gantt._assign_project_dates = function(task, from, to){
+  if(task.$no_start){
+    if(from && from != Infinity){
+      task.start_date = gantt.date.Date(from);
+    }else{
+      task.start_date = this._default_task_date(task, this.getParent(task));
+    }
+  }
+
+  if(task.$no_end){
+    if(to && to != -Infinity){
+      task.end_date = gantt.date.Date(to);
+    }else{
+      task.end_date = this.calculateEndDate(task.start_date, this.config.duration_step);
+    }
+  }
+  if(task.$no_start || task.$no_end){
+    this._init_task_timing(task);
+  }
+};
+
+// upward calculation of project duration
+gantt._update_parents = function(taskId, silent){
+  ysy.log.debug("_update_parents on ID="+taskId,"parent");
+  if(!taskId) return;
+
+  var task = this.getTask(taskId);
+  var pid = this.getParent(task);
+
+  while(!(task.$no_end || task.$no_start) && pid && this.isTaskExists(pid)){
+    task = this.getTask(pid);
+    pid = this.getParent(task);
+  }
+
+  if(task.$no_start || task.$no_end){
+    gantt.resetProjectDates(task);
+
+    if(!silent)
+      this.refreshTask(task.id, true);
+  }
+
+  if(pid && this.isTaskExists(pid)){
+    this._update_parents(pid, silent);
+  }
+};
+gantt.isChildOf = function(child_id, parent_id){
+  if(!this.isTaskExists(child_id))
+    return false;
+  if(parent_id === this.config.root_id)
+    return this.isTaskExists(child_id);
+
+  var task = this.getTask(child_id);
+  var pid = this.getParent(child_id);
+
+  while(task && this.isTaskExists(pid)){
+    task = this.getTask(pid);
+
+    if(task && task.id == parent_id)
+      return true;
+    pid = this.getParent(task);
+  }
+  return false;
+};
+
+gantt.roundDate = function(config){
+  alert("Forbidden function roundDate");
+  if(config instanceof Date){
+    config = {
+      date: config,
+      unit: gantt._tasks.unit,
+      step: gantt._tasks.step
+    };
+  }
+  var date = config.date,
+      steps = config.step,
+      unit = config.unit;
+      var rounding=this._get_line(unit)*1000;
+      var tzOffset = date.getTimezoneOffset();  // HOSEK
+  return moment(Math.round((+date) / rounding) * rounding).add(tzOffset,"m").toDate();
+  /*var upper, lower;
+  if(unit == gantt._tasks.unit && steps == gantt._tasks.step &&
+    +date >= +gantt._min_date && +date <= +gantt._max_date){
+    //find date in time scale config
+    var colIndex = Math.floor(gantt._day_index_by_date(date));
+    lower = new Date(gantt._tasks.trace_x[colIndex]);
+    upper = new Date(lower);
+    if(.trace_x[colIndex + 1])
+      upper = new Date(gantt._tasks.trace_x[colIndex + 1]);
+  }else{
+    upper = gantt.date[unit + "_start"](new Date(this._min_date));
+    while(+upper < +date){
+      upper = gantt.date[unit + "_start"](gantt.date.add(upper, steps, unit));
+
+      var tzOffset = upper.getTimezoneOffset();
+      upper = gantt.date.add(upper, steps, unit);
+      upper = gantt._correct_dst_change(upper, tzOffset, upper, unit);
+      if(gantt.date[unit + '_start'])
+        upper = gantt.date[unit + '_start'](upper);
+    }
+
+    lower = gantt.date.add(upper, -1*steps, unit);
+
+  }
+  if(config.dir && config.dir == 'future')
+    return upper;
+  if(config.dir && config.dir == 'past')
+    return lower;
+
+  if(Math.abs(date - lower) < Math.abs(upper - date)){
+    return lower;
+  }else{
+    return upper;
+  }*/
+
+};
+
+
+gantt.attachEvent("onBeforeTaskUpdate", function(id, task){
+  gantt._init_task_timing(task);
+  return true;
+});
+gantt.attachEvent("onBeforeTaskAdd", function(id, task){
+  gantt._init_task_timing(task);
+  return true;
+});
+
+gantt.calculateTaskLevel = function (item) {
+    var level = 0;
+    while (this.getParent(item)) {
+        if (!this.isTaskExists(this.getParent(item))) break;
+        item = this.getTask(this.getParent(item));
+        level++;
+    }
+    return level;
+};
+
+
+gantt.sort = function(field, desc, parent, silent) {
+    var render = !silent;//4th argument to cancel redraw after sorting
+
+    if (!this.isTaskExists(parent)) {
+        parent = this.config.root_id;
+    }
+
+    if (!field) field = "order";
+    var criteria = (typeof(field) == "string") ? (function(a, b) {
+    if(a[field] == b[field]){
+      return 0;
+    }
+
+        var result = a[field] > b[field];
+        if (desc) result = !result;
+        return result ? 1 : -1;
+    }) : field;
+
+
+    var els = this.getChildren(parent);
+    if (els){
+        var temp = [];
+        for (var i = els.length - 1; i >= 0; i--)
+            temp[i] = this._pull[els[i]];
+
+        temp.sort(criteria);
+
+        for (var i = 0; i < temp.length; i++) {
+            els[i] = temp[i].id;
+            this.sort(field, desc, els[i], true);
+        }
+    }
+
+    if (render) {
+    this.render();
+    }
+};
+
+gantt.getNext = function(id) {
+    for (var i = 0; i < this._order.length-1; i++) {
+        if (this._order[i] == id)
+            return this._order[i+1];
+    }
+    return null;
+};
+gantt.getPrev = function(id) {
+    for (var i = 1; i < this._order.length; i++) {
+        if (this._order[i] == id)
+            return this._order[i-1];
+    }
+    return null;
+};
+
+gantt._get_parent_id = function(task){
+  var parent = this.config.root_id;
+  if(task){
+    parent = task.parent;
+  }
+  return parent;
+};
+
+gantt.getParent = function(id){
+  var task = null;
+  if(id.id){
+    task = id;
+  }else if(this.isTaskExists(id)){
+    task = gantt.getTask(id);
+  }
+
+  return this._get_parent_id(task);
+};
+
+
+
+gantt.setParent = function(task, new_pid){
+  task.parent = new_pid;
+};
+
+gantt.getSiblings = function(id){
+  if(!this.isTaskExists(id)){
+    return [];
+  }
+  var parent = this.getParent(id);
+  return this.getChildren(parent);
+};
+gantt.getNextSibling = function(id){
+  var siblings = this.getSiblings(id);
+  for(var i= 0, len = siblings.length; i < len; i++){
+    if(siblings[i] == id)
+      return siblings[i+1] || null;
+  }
+  return null;
+};
+gantt.getPrevSibling = function(id){
+  var siblings = this.getSiblings(id);
+  for(var i= 0, len = siblings.length; i < len; i++){
+    if(siblings[i] == id)
+      return siblings[i-1] || null;
+  }
+  return null;
+};
+
+/*gantt._dp_init = function(dp) {
+    dp.setTransactionMode("POST", true);
+    dp.serverProcessor += (dp.serverProcessor.indexOf("?") != -1 ? "&" : "?") + "editing=true";
+    dp._serverProcessor = dp.serverProcessor;
+
+    dp.styles = {
+        updated:"gantt_updated",
+        inserted:"gantt_inserted",
+        deleted:"gantt_deleted",
+        invalid:"gantt_invalid",
+        error:"gantt_error",
+        clear:""
+    };
+
+    dp._methods=["_row_style","setCellTextStyle","_change_id","_delete_task"];
+
+  dp.setGanttMode = function(mode){
+    var modes = dp.modes || {};
+    if(dp._ganttMode){
+      modes[dp._ganttMode] = {
+        _in_progress : dp._in_progress,
+        _invalid : dp._invalid,
+        updatedRows : dp.updatedRows
+      };
+    }
+
+    var newState = modes[mode];
+    if(!newState){
+      newState = modes[mode] = {
+        _in_progress : {},
+        _invalid : {},
+        updatedRows : []
+      };
+    }
+    dp._in_progress = newState._in_progress;
+    dp._invalid = newState._invalid;
+    dp.updatedRows = newState.updatedRows;
+    dp.modes = modes;
+    dp._ganttMode = mode;
+  };
+
+  this._sendTaskOrder = function(id, item){
+    if(item.$drop_target){
+      dp.setGanttMode("tasks");
+      this.getTask(id).target = item.$drop_target;
+      dp.setUpdated(id, true,"order");
+      delete this.getTask(id).$drop_target;
+    }
+  };
+    this.attachEvent("onAfterTaskAdd", function(id, item) {
+        dp.setGanttMode("tasks");
+        dp.setUpdated(id,true,"inserted");
+    });
+    this.attachEvent("onAfterTaskUpdate", function(id, item) {
+        dp.setGanttMode("tasks");
+        dp.setUpdated(id,true);
+
+    gantt._sendTaskOrder(id, item);
+    });
+    this.attachEvent("onAfterTaskDelete", function(id, item) {
+        dp.setGanttMode("tasks");
+        dp.setUpdated(id,true,"deleted");
+
+    if(dp.updateMode != 'off' && !dp._tSend){
+      dp.sendAllData();
+    }
+
+    });
+
+    this.attachEvent("onAfterLinkUpdate", function(id, item) {
+        dp.setGanttMode("links");
+        dp.setUpdated(id, true);
+    });
+    this.attachEvent("onAfterLinkAdd", function(id, item) {
+        dp.setGanttMode("links");
+        dp.setUpdated(id, true,"inserted");
+    });
+    this.attachEvent("onAfterLinkDelete", function(id, item) {
+        dp.setGanttMode("links");
+        dp.setUpdated(id, true,"deleted");
+    });
+    this.attachEvent("onRowDragEnd", function(id, target) {
+        gantt._sendTaskOrder(id, gantt.getTask(id));
+    });
+
+    dp.attachEvent("onBeforeDataSending", function() {
+    var url = this._serverProcessor;
+    if(this._tMode == "REST"){
+      var mode = this._ganttMode.substr(0, this._ganttMode.length - 1);// links, tasks -> /link/id, /task/id
+
+      url = url.substring(0, url.indexOf("?") > -1 ? url.indexOf("?") : url.length);
+      //editing=true&
+      this.serverProcessor = url + (url.slice(-1) == "/" ? "" : "/") + mode;
+    }else{
+      this.serverProcessor = url + window.dhtmlx.url(url) + "gantt_mode=" + this._ganttMode;
+    }
+
+        return true;
+    });
+
+
+  var afterUpdate = dp.afterUpdate;
+  dp.afterUpdate = function(){
+    var xml;
+    if(arguments.length == 3){
+      xml = arguments[1];
+    }else{
+      // old dataprocessor
+      xml = arguments[4];
+    }
+    var mode = dp._ganttMode;
+    var reqUrl = xml.filePath;
+
+    if(this._tMode != "REST"){
+      if (reqUrl.indexOf("gantt_mode=links") != -1) {
+        mode = "links";
+      }else{
+        mode = "tasks";
+      }
+    }else{
+      if(reqUrl.indexOf("/link") > reqUrl.indexOf("/task")){
+        mode = "links";
+      }else{
+        mode = "tasks";
+      }
+    }
+    dp.setGanttMode(mode);
+
+    var res = afterUpdate.apply(dp, arguments);
+    dp.setGanttMode(mode);
+    return res;
+  };
+
+    dp._getRowData=dhtmlx.bind(function(id, pref) {
+        var task;
+        if (dp._ganttMode == "tasks")
+            task = this.isTaskExists(id) ? this.getTask(id) : { id: id };
+        else
+            task = this.isLinkExists(id) ? this.getLink(id) : { id: id };
+
+    task = dhtmlx.copy(task);
+
+        var data = {};
+        for (var key in task) {
+            if (key.substr(0, 1) == "$") continue;
+            var value = task[key];
+            if (value instanceof Date)
+                data[key] = this.templates.xml_format(value);
+            else if(value === null)
+        data[key] = "";
+            else
+                data[key] = value;
+        }
+    if(task.$no_start){
+      task.start_date = "";
+      task.duration = "";
+    }
+    if(task.$no_end){
+      task.end_date = "";
+      task.duration = "";
+    }
+        data[dp.action_param] = this.getUserData(id, dp.action_param);
+        return data;
+    }, this);
+
+    this._change_id = dhtmlx.bind(function(oldid, newid) {
+        if (dp._ganttMode != "tasks")
+            this.changeLinkId(oldid, newid);
+        else
+            this.changeTaskId(oldid, newid);
+    }, this);
+
+    this._row_style = function(row_id, classname){
+        if (dp._ganttMode != "tasks") return;
+        var el = gantt.getTaskRowNode(row_id);
+        if (!el) return;
+        if (!classname) {
+            var regexp = / (gantt_updated|gantt_inserted|gantt_deleted|gantt_invalid|gantt_error)/g;
+            el.className = el.className.replace(regexp, "");
+        } else
+            el.className += " " + classname;
+    };
+
+    // fake method for dataprocessor
+    this._delete_task = function(row_id, node){};
+
+    this._dp = dp;
+
+  gantt._patch_dhx4_ajax();
+};
+*//*
+gantt._patch_dhx4_ajax = function(){
+  var dhxVersion = parseInt((dhx4.version || "").split(".").join(""), 10);
+
+  // dhtmlx 4.0.0 - 4.1.3 does not provide filePath parameter to callback, patch won't be needed for future versions
+  if(dhxVersion <= 413){
+    window.dhx4.ajax._call = function(method, url, postData, async, onLoad, longParams, headers) {
+
+      var t = (window.XMLHttpRequest && !dhx4.isIE ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
+      var isQt = (navigator.userAgent.match(/AppleWebKit/) != null && navigator.userAgent.match(/Qt/) != null && navigator.userAgent.match(/Safari/) != null);
+
+      if (async == true) {
+        t.onreadystatechange = function() {
+          if ((t.readyState == 4) || (isQt == true && t.readyState == 3)) { // what for long response and status 404?
+            if (t.status != 200 || t.responseText == "")
+              if (!dhx4.callEvent("onAjaxError", [t])) return;
+
+            window.setTimeout(function(){
+              if (typeof(onLoad) == "function") {
+                onLoad.apply(window, [{xmlDoc:t, filePath:url}]); // dhtmlx-compat, response.xmlDoc.responseXML/responseText
+              }
+              if (longParams != null) {
+                if (typeof(longParams.postData) != "undefined") {
+                  dhx4.ajax.postLong(longParams.url, longParams.postData, onLoad);
+                } else {
+                  dhx4.ajax.getLong(longParams.url, onLoad);
+                }
+              }
+              onLoad = null;
+              t = null;
+            },1);
+          }
+        }
+      }
+
+      if (method == "GET" && this.cache != true) {
+        url += (url.indexOf("?")>=0?"&":"?")+"dhxr"+new Date().getTime()+"=1";
+      }
+
+      t.open(method, url, async);
+
+      if (headers){
+        for (var key in headers)
+          t.setRequestHeader(key, headers[key]);
+      } else if (method.toUpperCase() == "POST" || method == "PUT" || method == "DELETE") {
+        t.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+      } else if (method == "GET") {
+        postData = null;
+      }
+
+      t.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+      t.send(postData);
+
+      if (!async) return {xmlDoc:t, filePath:url}; // dhtmlx-compat, response.xmlDoc.responseXML/responseText
+
+    };
+  }
+
+  gantt._patch_dhx4_ajax = function(){};
+};
+
+gantt.getUserData = function(id, name) {
+    if (!this.userdata) this.userdata = {};
+    if (this.userdata[id] && this.userdata[id][name]) return this.userdata[id][name];
+    return "";
+};
+gantt.setUserData = function(id, name, value) {
+    if (!this.userdata) this.userdata = {};
+    if (!this.userdata[id]) this.userdata[id] = {};
+    this.userdata[id][name] = value;
+};
+*/
+
+gantt._init_link = function(link) {
+    if (!dhtmlx.defined(link.id))
+        link.id = dhtmlx.uid();
+    return link;
+};
+
+gantt._sync_links = function() {
+    for (var id in this._pull) {
+        this._pull[id].$source = [];
+        this._pull[id].$target = [];
+    }
+    for (var id in this._lpull) {
+        var link = this._lpull[id];
+        if(this._pull[link.source])
+            this._pull[link.source].$source.push(id);
+        if(this._pull[link.target])
+            this._pull[link.target].$target.push(id);
+    }
+};
+
+gantt.getLink = function(id) {
+    dhtmlx.assert(this._lpull[id], "Link doesn't exist");
+    return this._lpull[id];
+};
+
+gantt.getLinks = function(){
+  var links = [];
+  for (var key in gantt._lpull)
+    links.push(gantt._lpull[key]);
+  return links;
+};
+
+gantt.isLinkExists = function(id) {
+    return dhtmlx.defined(this._lpull[id]);
+};
+
+gantt.addLink = function(link) {
+    link = this._init_link(link);
+
+    if (this.callEvent("onBeforeLinkAdd", [link.id, link])===false) return false;
+
+    this._lpull[link.id] = link;
+    this._sync_links();
+    //this._render_link(link.id);
+    this.refreshLink(link.id);
+    this.callEvent("onAfterLinkAdd", [link.id, link]);
+    return link.id;
+};
+
+gantt.updateLink = function (id, data) {
+  if (!dhtmlx.defined(data))
+    data = this.getLink(id);
+
+  if (this.callEvent("onBeforeLinkUpdate", [id, data]) === false)
+    return false;
+
+  this._lpull[id] = data;
+  data.widget.update(data);
+  this._sync_links();
+  //this._render_link(id);
+  this.refreshLink(id);
+  this.callEvent("onAfterLinkUpdate", [id, data]);
+  return true;
+};
+
+gantt.deleteLink = function(id) {
+    return this._deleteLink(id);
+};
+
+gantt._deleteLink = function(id, silent) {
+    var link = this.getLink(id);
+    if (!silent && this.callEvent("onBeforeLinkDelete", [id, link])===false) return false;
+
+    delete this._lpull[id];
+    this._sync_links();
+    this.refreshLink(id);
+    if (!silent) this.callEvent("onAfterLinkDelete", [id, link]);
+    return true;
+};
+
+//gantt.changeLinkId = function(oldid, newid) {
+//    this._lpull[newid] = this._lpull[oldid];
+//    this._lpull[newid].id = newid;
+//    delete this._lpull[oldid];
+//
+//    this._sync_links();
+//	this.callEvent("onLinkIdChange", [oldid, newid]);
+//};
+
+
+gantt.getChildren = function(id) {
+    return dhtmlx.defined(this._branches[id]) ? this._branches[id] : [];
+};
+gantt.hasChild = function(id) {
+    return (dhtmlx.defined(this._branches[id]) && this._branches[id].length);
+};
+
+
+gantt.refreshData = function(){
+  this.refresher.renderData();
+  //this._render_data();
+};
+
+
+gantt._configure = function(col, data, force){
+  for (var key in data)
+    if (typeof col[key] == "undefined" || force)
+      col[key] = data[key];
+};
+gantt._init_skin = function(){
+  gantt._get_skin(false);
+  gantt._init_skin = function(){};
+};
+gantt._get_skin = function(force){
+  if (!gantt.skin || force){
+    var links = document.getElementsByTagName("link");
+    for (var i = 0; i < links.length; i++) {
+      var res = links[i].href.match("dhtmlxgantt_([a-z]+).css");
+      if (res){
+        gantt.skin = res[1];
+        break;
+      }
+    }
+  }
+
+  if (!gantt.skin) gantt.skin = "terrace";
+  var skinset = gantt.skins[gantt.skin];
+
+  //apply skin related settings
+  this._configure(gantt.config, skinset.config, force);
+
+  var config = gantt.getGridColumns();
+  if (config[1] && typeof config[1].width == "undefined")
+    config[1].width = skinset._second_column_width;
+  if (config[2] && typeof config[2].width == "undefined")
+    config[2].width = skinset._third_column_width;
+
+  if (skinset._lightbox_template)
+    gantt._lightbox_template = skinset._lightbox_template;
+
+  //gantt.resetLightbox();  //HOSEK
+};
+gantt.resetSkin = function(){
+  this.skin = "";
+  this._get_skin(true);
+};
+gantt.skins = {};
+
+/*
+gantt._lightbox_methods = {};
+gantt._lightbox_template="<div class='gantt_cal_ltitle'><span class='gantt_mark'>&nbsp;</span><span class='gantt_time'></span><span class='gantt_title'></span></div><div class='gantt_cal_larea'></div>";
+
+gantt.showLightbox=function(id){
+    if (!id || gantt._is_readonly(this.getTask(id))) return;
+    if (!this.callEvent("onBeforeLightbox",[id])) return;
+
+  var task = this.getTask(id);
+
+    var box = this.getLightbox(this._get_safe_type(task.type));
+    this._center_lightbox(box);
+    this.showCover();
+    this._fill_lightbox(id,box);
+    this.callEvent("onLightbox",[id]);
+};
+gantt._get_timepicker_step = function(){
+  if(this.config.round_dnd_dates){
+    var scale = gantt._tasks,
+      step = (this._get_line(scale.unit) * scale.step)/60;//timepicker step is measured in minutes
+    if(step >= 60*24 || !this._is_chart_visible()){
+      step = this.config.time_step;
+    }
+    return step;
+  }
+  return this.config.time_step;
+};
+gantt.getLabel = function(property, key) {
+    var sections = this._get_typed_lightbox_config();
+    for (var i=0; i<sections.length; i++) {
+        if(sections[i].map_to == property) {
+            var options = sections[i].options;
+            for (var j=0; j<options.length; j++) {
+                if(options[j].key == key) {
+                    return options[j].label;
+                }
+            }
+        }
+    }
+    return "";
+};
+
+gantt.updateCollection = function(list_name, collection) {
+  collection = collection.slice(0);
+  var list = gantt.serverList(list_name);
+  if (!list) return false;
+  list.splice(0, list.length);
+  list.push.apply(list, collection || []);
+  gantt.resetLightbox();
+};
+gantt.getLightboxType = function(){
+  return this._get_safe_type(this._lightbox_type);
+};
+gantt.getLightbox = function(type){
+  if(type === undefined)
+    type = this.getLightboxType();
+
+    if (!this._lightbox || this.getLightboxType() != this._get_safe_type(type)){
+    this._lightbox_type = this._get_safe_type(type);
+        var d=document.createElement("DIV");
+        d.className="gantt_cal_light";
+
+        var full_width = this._is_lightbox_timepicker();
+        if (gantt.config.wide_form || full_width)
+            d.className+=" gantt_cal_light_wide";
+
+        if (full_width) {
+            gantt.config.wide_form = true;
+            d.className+=" gantt_cal_light_full";
+        }
+
+
+        d.style.visibility="hidden";
+        var html = this._lightbox_template;
+
+        var buttons = this.config.buttons_left;
+        for (var i = 0; i < buttons.length; i++){
+      // needed to migrate from 'dhx_something' to 'gantt_something' naming in a lightbox
+      var button = this.config._migrate_buttons[buttons[i]] ? this.config._migrate_buttons[buttons[i]] : buttons[i];
+
+            html+="<div class='gantt_btn_set gantt_left_btn_set "+button+"_set'><div dhx_button='1' class='"+button+"'></div><div>"+this.locale.labels[button]+"</div></div>";
+
+    }
+        buttons = this.config.buttons_right;
+        for (var i = 0; i < buttons.length; i++){
+      var button = this.config._migrate_buttons[buttons[i]] ? this.config._migrate_buttons[buttons[i]] : buttons[i];
+            html+="<div class='gantt_btn_set gantt_right_btn_set "+button+"_set' style='float:right;'><div dhx_button='1' class='"+button+"'></div><div>"+this.locale.labels[button]+"</div></div>";
+
+    }
+        html+="</div>";
+        d.innerHTML=html;
+
+        if (gantt.config.drag_lightbox){
+            d.firstChild.onmousedown = gantt._ready_to_dnd;
+            d.firstChild.onselectstart = function(){ return false; };
+            d.firstChild.style.cursor = "pointer";
+            gantt._init_dnd_events();
+
+        }
+
+        document.body.insertBefore(d,document.body.firstChild);
+        this._lightbox=d;
+
+        var sns = this._get_typed_lightbox_config(type);
+        html = this._render_sections(sns);
+
+        var ds=d.getElementsByTagName("div");
+        for (var i=0; i<ds.length; i++) {
+            var t_ds = ds[i];
+            if (t_ds.className == "gantt_cal_larea") {
+                t_ds.innerHTML = html;
+                break;
+            }
+        }
+
+        //sizes
+        this.resizeLightbox();
+
+        this._init_lightbox_events(this);
+        d.style.display="none";
+        d.style.visibility="visible";
+    }
+    return this._lightbox;
+};
+
+gantt._render_sections = function(sns) {
+  var html="";
+  for (var i=0; i < sns.length; i++) {
+    var block=this.form_blocks[sns[i].type];
+    if (!block) continue; //ignore incorrect blocks
+    sns[i].id="area_"+dhtmlx.uid();
+
+    var display = sns[i].hidden ? " style='display:none'" : "";
+    var button = "";
+    if (sns[i].button){
+      button = "<div class='gantt_custom_button' index='"+i+"'><div class='gantt_custom_button_"+sns[i].button+"'></div><div>"+this.locale.labels["button_"+sns[i].button]+"</div></div>";
+    }
+    if (this.config.wide_form){
+      html+="<div class='gantt_wrap_section' " + display+">";
+    }
+    html+="<div id='"+sns[i].id+"' class='gantt_cal_lsection'>"+button+this.locale.labels["section_"+sns[i].name]+"</div>"+block.render.call(this,sns[i]);
+    html+="</div>";
+  }
+  return html;
+};
+
+
+gantt.resizeLightbox=function(){
+  var d = this._lightbox;
+  if (!d) return;
+
+  var con = d.childNodes[1];
+  con.style.height="0px";
+  con.style.height=con.scrollHeight+"px";
+  d.style.height=con.scrollHeight+this.config.lightbox_additional_height+"px";
+  con.style.height=con.scrollHeight+"px"; //it is incredible , how ugly IE can be
+
+
+};
+
+gantt._center_lightbox = function(box) {
+  if (box){
+    box.style.display="block";
+
+    var scroll_top = window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop;
+    var scroll_left = window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft;
+
+    var view_height = window.innerHeight||document.documentElement.clientHeight;
+
+    if(scroll_top) // if vertical scroll on window
+      box.style.top=Math.round(scroll_top+Math.max((view_height-box.offsetHeight)/2, 0))+"px";
+    else // vertical scroll on body
+      box.style.top=Math.round(Math.max(((view_height-box.offsetHeight)/2), 0) + 9)+"px"; // +9 for compatibility with auto tests
+
+    // not quite accurate but used for compatibility reasons
+    if(document.documentElement.scrollWidth > document.body.offsetWidth) // if horizontal scroll on the window
+      box.style.left=Math.round(scroll_left+(document.body.offsetWidth-box.offsetWidth)/2)+"px";
+    else // horizontal scroll on the body
+      box.style.left=Math.round((document.body.offsetWidth-box.offsetWidth)/2)+"px";
+  }
+};
+gantt.showCover = function(){
+  if(this._cover) return;
+
+  this._cover=document.createElement("DIV");
+  this._cover.className="gantt_cal_cover";
+  var _document_height = ((document.height !== undefined) ? document.height : document.body.offsetHeight);
+  var _scroll_height = ((document.documentElement) ? document.documentElement.scrollHeight : 0);
+  this._cover.style.height = Math.max(_document_height, _scroll_height) + 'px';
+  document.body.appendChild(this._cover);
+};
+
+
+gantt._init_lightbox_events = function(){
+    gantt.lightbox_events = {};
+
+
+    gantt.lightbox_events["gantt_save_btn"] = function(e) {
+        gantt._save_lightbox();
+    };
+
+
+    gantt.lightbox_events["gantt_delete_btn"] = function(e) {
+    if(!gantt.callEvent("onLightboxDelete", [gantt._lightbox_id]))
+      return;
+
+    if(gantt.isTaskExists(gantt._lightbox_id)){
+      gantt.$click.buttons["delete"](gantt._lightbox_id);
+    }else{
+      gantt.hideLightbox();
+    }
+
+    };
+
+
+    gantt.lightbox_events["gantt_cancel_btn"] = function(e) {
+        gantt._cancel_lightbox();
+    };
+
+
+    gantt.lightbox_events["default"] = function(e, src) {
+        if (src.getAttribute("dhx_button")) {
+            gantt.callEvent("onLightboxButton", [src.className, src, e]);
+        } else {
+            var index, block, sec;
+            if (src.className.indexOf("gantt_custom_button") != -1) {
+                if (src.className.indexOf("gantt_custom_button_") != -1) {
+                    index = src.parentNode.getAttribute("index");
+                    sec = src.parentNode.parentNode;
+                } else {
+                    index = src.getAttribute("index");
+                    sec = src.parentNode;
+                    src = src.firstChild;
+                }
+            }
+
+      var sections = gantt._get_typed_lightbox_config();
+
+            if (index) {
+                block = gantt.form_blocks[sections[index].type];
+                block.button_click(index, src, sec, sec.nextSibling);
+            }
+        }
+    };
+    dhtmlxEvent(gantt.getLightbox(), "click", function(e) {
+        e = e || window.event;
+        var src = e.target ? e.target : e.srcElement;
+
+        if (!src.className)
+            src = src.previousSibling;
+        if (src && src.className && src.className.indexOf("gantt_btn_set") === 0)
+            src = src.firstChild;
+        if (src && src.className) {
+            var func = dhtmlx.defined(gantt.lightbox_events[src.className]) ? gantt.lightbox_events[src.className] : gantt.lightbox_events["default"];
+            return func(e, src);
+        }
+        return false;
+    });
+
+    gantt.getLightbox().onkeydown=function(e){
+        switch((e||event).keyCode){
+            case gantt.keys.edit_save:
+                if ((e||event).shiftKey) return;
+                gantt._save_lightbox();
+                break;
+            case gantt.keys.edit_cancel:
+                gantt._cancel_lightbox();
+                break;
+            default:
+                break;
+        }
+    };
+};
+
+gantt._cancel_lightbox=function(){
+  var task = this.getLightboxValues();
+    this.callEvent("onLightboxCancel",[this._lightbox_id, task.$new]);
+  if(gantt.isTaskExists(task.id) && task.$new){
+    this._deleteTask(task.id, true);
+  }
+
+  this.refreshData();
+    this.hideLightbox();
+};
+
+gantt._save_lightbox=function(){
+    var task = this.getLightboxValues();
+  if(!this.callEvent("onLightboxSave", [this._lightbox_id, task, !!task.$new]))
+    return;
+
+  if (task.$new){
+    delete task.$new;
+    this.addTask(task);
+  }else if(this.isTaskExists(task.id)){
+    dhtmlx.mixin(this.getTask(task.id), task, true);
+    this.updateTask(task.id);
+  }
+  this.refreshData();
+
+    // TODO: do we need any blockable events here to prevent closing lightbox?
+    this.hideLightbox();
+};
+
+gantt._resolve_default_mapping = function(section) {
+  var mapping = section.map_to;
+  var time_controls = {"time":true, "duration":true};
+  if(time_controls[section.type]){
+    if(section.map_to == 'auto'){
+      mapping = {start_date: "start_date", end_date: "end_date", duration: "duration"};
+    }else if(typeof(section.map_to) === "string"){
+      mapping = {start_date: section.map_to};
+    }
+  }
+
+  return mapping;
+};
+
+gantt.getLightboxValues=function(){
+    var task = {};
+
+    if(gantt.isTaskExists(this._lightbox_id)) {
+        task = dhtmlx.mixin({}, this.getTask(this._lightbox_id));
+    }
+
+    var sns = this._get_typed_lightbox_config();
+    for (var i=0; i < sns.length; i++) {
+        var node = document.getElementById(sns[i].id);
+        node=(node?node.nextSibling:node);
+        var block=this.form_blocks[sns[i].type];
+    if(!block) continue;
+        var res=block.get_value.call(this,node,task, sns[i]);
+        var map_to = gantt._resolve_default_mapping(sns[i]);
+        if (typeof map_to == "string" && map_to != "auto") {
+            task[map_to] = res;
+    } else if(typeof map_to == "object") {
+      for(var property in map_to) {
+        if(map_to[property])
+          task[map_to[property]] = res[property];
+      }
+    }
+    }
+    return task;
+};
+
+
+gantt.hideLightbox=function(){
+    var box = this.getLightbox();
+    if (box) box.style.display="none";
+    this._lightbox_id=null;
+
+    this.hideCover();
+    this.callEvent("onAfterLightbox",[]);
+};
+gantt.hideCover=function(){
+    if (this._cover)
+        this._cover.parentNode.removeChild(this._cover);
+    this._cover=null;
+};
+
+gantt.resetLightbox = function(){
+    if (gantt._lightbox && !gantt._custom_lightbox)
+        gantt._lightbox.parentNode.removeChild(gantt._lightbox);
+    gantt._lightbox = null;
+};
+gantt._set_lightbox_values = function(data, box){
+  var task = data;
+  var s = box.getElementsByTagName("span");
+  if (gantt.templates.lightbox_header) {
+    s[1].innerHTML = "";
+    s[2].innerHTML = gantt.templates.lightbox_header(task.start_date, task.end_date, task);
+  } else {
+    s[1].innerHTML = this.templates.task_time(task.start_date, task.end_date, task);
+    s[2].innerHTML = (this.templates.task_text(task.start_date, task.end_date, task) || "").substr(0, 70); //IE6 fix
+  }
+
+
+  var sns = this._get_typed_lightbox_config(this.getLightboxType());
+  for (var i = 0; i < sns.length; i++) {
+    var section = sns[i];
+
+    if(!this.form_blocks[section.type]){
+      continue;//skip incorrect sections, same check is done during rendering
+    }
+
+
+    var node = document.getElementById(section.id).nextSibling;
+    var block = this.form_blocks[section.type];
+    var map_to = gantt._resolve_default_mapping(sns[i]);
+    var value = dhtmlx.defined(task[map_to]) ? task[map_to] : section.default_value;
+    block.set_value.call(gantt, node, value, task, section);
+
+    if (section.focus)
+      block.focus.call(gantt, node);
+  }
+  if(data.id)
+    gantt._lightbox_id = data.id;
+};
+gantt._fill_lightbox = function(id, box) {
+    var task = this.getTask(id);
+    this._set_lightbox_values(task, box);
+};
+
+
+gantt.getLightboxSection = function(name){
+    var config = this._get_typed_lightbox_config();
+    var i =0;
+    for (i; i < config.length; i++)
+        if (config[i].name == name)
+            break;
+    var section = config[i];
+    if(!section)
+        return null;
+
+    if (!this._lightbox)
+        this.getLightbox();
+    var header = document.getElementById(section.id);
+    var node = header.nextSibling;
+
+    var result = {
+        section: section,
+        header: header,
+        node: node,
+        getValue:function(ev){
+            return gantt.form_blocks[section.type].get_value.call(gantt, node, (ev||{}), section);
+        },
+        setValue:function(value, ev){
+            return gantt.form_blocks[section.type].set_value.call(gantt, node, value, (ev||{}), section);
+        }
+    };
+
+    var handler = this._lightbox_methods["get_"+section.type+"_control"];
+    return handler?handler(result):result;
+};
+
+gantt._lightbox_methods.get_template_control = function(result) {
+    result.control = result.node;
+    return result;
+};
+gantt._lightbox_methods.get_select_control = function(result) {
+    result.control = result.node.getElementsByTagName('select')[0];
+    return result;
+};
+gantt._lightbox_methods.get_textarea_control = function(result) {
+    result.control = result.node.getElementsByTagName('textarea')[0];
+    return result;
+};
+gantt._lightbox_methods.get_time_control = function(result) {
+    result.control = result.node.getElementsByTagName('select'); // array
+    return result;
+};
+
+
+
+*/
+
+gantt._init_dnd_events = function(){
+    dhtmlxEvent(document.body, "mousemove", gantt._move_while_dnd);
+    dhtmlxEvent(document.body, "mouseup", gantt._finish_dnd);
+    gantt._init_dnd_events = function(){};
+};
+gantt._move_while_dnd = function(e){
+    if (gantt._dnd_start_lb){
+        if (!document.gantt_unselectable){
+            document.body.className += " gantt_unselectable";
+            document.gantt_unselectable = true;
+        }
+        var lb = gantt.getLightbox();
+        var now = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY];
+        lb.style.top = gantt._lb_start[1]+now[1]-gantt._dnd_start_lb[1]+"px";
+        lb.style.left = gantt._lb_start[0]+now[0]-gantt._dnd_start_lb[0]+"px";
+    }
+};
+gantt._ready_to_dnd = function(e){
+    var lb = gantt.getLightbox();
+    gantt._lb_start = [parseInt(lb.style.left,10), parseInt(lb.style.top,10)];
+    gantt._dnd_start_lb = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY];
+};
+gantt._finish_dnd = function(){
+    if (gantt._lb_start){
+        gantt._lb_start = gantt._dnd_start_lb = false;
+        document.body.className = document.body.className.replace(" gantt_unselectable","");
+        document.gantt_unselectable = false;
+    }
+};
+
+
+
+
+gantt._focus = function(node, select){
+    if (node && node.focus){
+        if (gantt.config.touch){
+            //do not focus editor, to prevent auto-zoom
+        } else {
+            try {
+                if (select && node.select) node.select();
+                node.focus();
+            }catch(e){ }
+        }
+    }
+};
+
+
+/*gantt.form_blocks={
+    getTimePicker: function(sns, hidden) {
+    var time_format = sns.time_format;
+        if (!time_format) {
+            // default order
+            var time_format = ["%d", "%m", "%Y"];
+      if(gantt._get_line(gantt._tasks.unit) < gantt._get_line("day")){
+        time_format.push("%H:%i");
+      }
+        }
+        // map: default order => real one
+        sns._time_format_order = { size:0 };
+
+
+        var cfg = this.config;
+        var dt = this.date.date_part(gantt.date.Date(gantt._min_date));
+        var last = 24*60, first = 0;
+        if(gantt.config.limit_time_select){
+            last = 60*cfg.last_hour+1;
+            first = 60*cfg.first_hour;
+            dt.setHours(cfg.first_hour);
+        }
+        var html = "";
+
+        for (var p = 0; p < time_format.length; p++) {
+            var time_option = time_format[p];
+
+            // adding spaces between selects
+            if (p > 0) {
+                html += " ";
+            }
+
+      var options = '';
+            switch (time_option) {
+                case "%Y":
+                    sns._time_format_order[2] = p;
+                    sns._time_format_order.size++;
+                    //year
+
+          var range, offset, start_year, end_year;
+
+          if(sns.year_range){
+            if(!isNaN(sns.year_range)){
+              range = sns.year_range;
+            }else if(sns.year_range.push){
+              // if
+              start_year = sns.year_range[0];
+              end_year = sns.year_range[1];
+            }
+          }
+
+          range = range || 10;
+          offset = offset || Math.floor(range/2);
+          start_year = start_year || dt.getFullYear() - offset;
+          end_year = end_year || start_year + range;
+
+
+                    for (var i=start_year; i < end_year; i++)
+            options+="<option value='"+(i)+"'>"+(i)+"</option>";
+                    break;
+                case "%m":
+                    sns._time_format_order[1] = p;
+                    sns._time_format_order.size++;
+                    //month
+                    for (var i=0; i < 12; i++)
+            options+="<option value='"+i+"'>"+this.locale.date.month_full[i]+"</option>";
+                    break;
+                case "%d":
+                    sns._time_format_order[0] = p;
+                    sns._time_format_order.size++;
+                    //days
+                    for (var i=1; i < 32; i++)
+            options+="<option value='"+i+"'>"+i+"</option>";
+                    break;
+                case "%H:%i":
+                  //  var last = 24*60, first = 0;
+                    sns._time_format_order[3] = p;
+                    sns._time_format_order.size++;
+                    //hours
+                    var i = first;
+                    var tdate = dt.getDate();
+                    sns._time_values = [];
+
+                    while(i<last){
+                        var time=this.templates.time_picker(dt);
+            options+="<option value='"+i+"'>"+time+"</option>";
+                        sns._time_values.push(i);
+                        dt.setTime(dt.valueOf()+this._get_timepicker_step()*60*1000);
+                        var diff = (dt.getDate()!=tdate)?1:0; // moved or not to the next day
+                        i=diff*24*60+dt.getHours()*60+dt.getMinutes();
+                    }
+                    break;
+                default:
+                    break;
+            }
+
+      if(options){
+        var readonly = sns.readonly ? "disabled='disabled'" : "";
+        var display = hidden ? " style='display:none'" : "";
+        html += "<select "+readonly+display +">"+options+"</select>";
+      }
+        }
+        return html;
+    },
+    _fill_lightbox_select: function (s,i,d,map,cfg) {
+        s[i+map[0]].value=d.getDate();
+        s[i+map[1]].value=d.getMonth();
+        s[i+map[2]].value=d.getFullYear();
+        if (dhtmlx.defined(map[3])) {
+            var v = d.getHours()*60+ d.getMinutes();
+            v = Math.round(v/gantt._get_timepicker_step())*gantt._get_timepicker_step();
+      var input = s[i+map[3]];
+      input.value= v;
+      //in case option not shown
+      input.setAttribute('data-value', v);
+        }
+    },
+    template:{
+        render: function(sns){
+            var height=(sns.height||"30")+"px";
+            return "<div class='gantt_cal_ltext gantt_cal_template' style='height:"+height+";'></div>";
+        },
+        set_value:function(node,value,ev,config){
+            node.innerHTML = value||"";
+        },
+        get_value:function(node,ev,config){
+            return node.innerHTML||"";
+        },
+        focus: function(node){
+        }
+    },
+    textarea:{
+        render:function(sns){
+            var height=(sns.height||"130")+"px";
+            return "<div class='gantt_cal_ltext' style='height:"+height+";'><textarea></textarea></div>";
+        },
+        set_value:function(node,value,ev){
+            node.firstChild.value=value||"";
+        },
+        get_value:function(node,ev){
+            return node.firstChild.value;
+        },
+        focus:function(node){
+            var a=node.firstChild; gantt._focus(a, true);
+        }
+    },
+    select:{
+        render:function(sns){
+            var height=(sns.height||"23")+"px";
+            var html="<div class='gantt_cal_ltext' style='height:"+height+";'><select style='width:100%;'>";
+            for (var i=0; i < sns.options.length; i++)
+                html+="<option value='"+sns.options[i].key+"'>"+sns.options[i].label+"</option>";
+            html+="</select></div>";
+            return html;
+        },
+        set_value:function(node,value,ev,sns){
+            var select = node.firstChild;
+            if (!select._dhx_onchange && sns.onchange) {
+                select.onchange = sns.onchange;
+                select._dhx_onchange = true;
+            }
+            if (typeof value == "undefined")
+                value = (select.options[0]||{}).value;
+            select.value=value||"";
+        },
+        get_value:function(node,ev){
+            return node.firstChild.value;
+        },
+        focus:function(node){
+            var a=node.firstChild; gantt._focus(a, true);
+        }
+    },
+    time:{
+        render:function(sns) {
+            var time = this.form_blocks.getTimePicker.call(this, sns);
+      var parts = ["<div style='height:"+(sns.height || 30)+"px;padding-top:0px;font-size:inherit;text-align:center;' class='gantt_section_time'>"];
+      parts.push(time);
+
+      if(sns.single_date){
+        time = this.form_blocks.getTimePicker.call(this, sns, true);
+        parts.push("<span></span>");
+      }else{
+        parts.push("<span style='font-weight:normal; font-size:10pt;'> &nbsp;&ndash;&nbsp; </span>");
+      }
+
+      parts.push(time);
+      parts.push("</div>");
+            return parts.join('');
+        },
+        set_value:function(node,value,ev,config){
+            var cfg = config;
+            var s=node.getElementsByTagName("select");
+
+            var map = config._time_format_order;
+            var map_size = config._time_format_size;
+
+            if(cfg.auto_end_date) {
+                var _update_lightbox_select = function() {
+                    start_date = new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,0,0);
+                    end_date =  gantt.calculateEndDate(start_date, 1);
+                    this.form_blocks._fill_lightbox_select(s,map.size, end_date,map,cfg);
+                };
+                for(var i=0; i<4; i++) {
+                    s[i].onchange = _update_lightbox_select;
+                }
+            }
+
+      var mapping = gantt._resolve_default_mapping(config);
+
+            if(typeof(mapping) === "string") mapping = {start_date: mapping};
+
+      var start_date = ev[mapping.start_date] || new Date();
+      var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1);
+
+            this.form_blocks._fill_lightbox_select(s,0,start_date,map,cfg);
+            this.form_blocks._fill_lightbox_select(s,map.size,end_date,map,cfg);
+        },
+
+        get_value:function(node, ev, config) {
+            var s=node.getElementsByTagName("select");
+            var map = config._time_format_order;
+
+            var hours = 0, minutes = 0;
+            if (dhtmlx.defined(map[3])) {
+                var time = parseInt(s[map[3]].value, 10);
+                hours = Math.floor(time/60);
+                minutes = time%60;
+            }
+            var start_date=new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes);
+
+            hours = minutes = 0;
+            if (dhtmlx.defined(map[3])) {
+                var time = parseInt(s[map.size+map[3]].value, 10);
+                hours = Math.floor(time/60);
+                minutes = time%60;
+            }
+            var end_date=new Date(s[map[2]+map.size].value,s[map[1]+map.size].value,s[map[0]+map.size].value,hours,minutes);
+
+            if (end_date <= start_date)
+                end_date = gantt.date.add(start_date, gantt._get_timepicker_step(),"minute");
+
+      var mapped_fields = gantt._resolve_default_mapping(config);
+
+      var res = {
+        start_date: new Date(start_date),
+        end_date: new Date(end_date)
+      };
+      if(typeof mapped_fields == "string"){
+        return res.start_date;
+      }else{
+        return res;
+      }
+        },
+        focus:function(node){
+            gantt._focus(node.getElementsByTagName("select")[0]);
+        }
+    },
+    duration:{
+        render:function(sns) {
+            var time = this.form_blocks.getTimePicker.call(this, sns);
+            time = "<div class='gantt_time_selects'>"+time+"</div>";
+            var label = this.locale.labels[this.config.duration_unit + "s"];
+
+      var singleDate = sns.single_date ? ' style="display:none"' : "";
+      var readonly = sns.readonly ? " disabled='disabled'" : "";
+
+            var duration = "<div class='gantt_duration' "+singleDate+">" +
+        "<input type='button' class='gantt_duration_dec' value='-'"+readonly+">" +
+        "<input type='text' value='5' class='gantt_duration_value'"+readonly+">" +
+        "<input type='button' class='gantt_duration_inc' value='+'"+readonly+"> " + label + " <span></span>" +
+        "</div>";
+            var html = "<div style='height:"+(sns.height || 30)+"px;padding-top:0px;font-size:inherit;' class='gantt_section_time'>"+time+" "+duration+"</div>";
+            return html;
+        },
+        set_value:function(node,value,ev,config){
+            var cfg = config;
+            var s=node.getElementsByTagName("select");
+          var inps = node.getElementsByTagName("input");
+
+            var duration = inps[1];
+            var btns=[inps[0],inps[2]];
+            var endspan = node.getElementsByTagName("span")[0];
+
+            var map = config._time_format_order;
+
+            function _calc_date() {
+                var start_date = gantt.form_blocks.duration._get_start_date.call(gantt, node ,config);
+                var duration = gantt.form_blocks.duration._get_duration.call(gantt, node ,config);
+                var end_date = gantt.calculateEndDate(start_date, duration);
+
+                endspan.innerHTML = gantt.templates.task_date(end_date);
+            }
+
+            function _change_duration(step) {
+                var value = duration.value;
+                value = parseInt(value, 10);
+                if (window.isNaN(value))
+                    value = 0;
+                value+=step;
+                if (value < 1) value = 1;
+                duration.value = value;
+                _calc_date();
+            }
+
+            btns[0].onclick = dhtmlx.bind(function() { _change_duration(-1*this.config.duration_step); }, this);
+            btns[1].onclick = dhtmlx.bind(function() { _change_duration(1*this.config.duration_step); }, this);
+            s[0].onchange = _calc_date;
+            s[1].onchange = _calc_date;
+            s[2].onchange = _calc_date;
+            if (s[3]) s[3].onchange = _calc_date;
+            duration.onkeydown = dhtmlx.bind(function(e) {
+                e = e || window.event;
+                // up
+                var code = (e.charCode || e.keyCode || e.which);
+
+                if (code == 40) {
+                    _change_duration(-1*this.config.duration_step);
+                    return false;
+                }
+                // down
+                if (code == 38) {
+                    _change_duration(1*this.config.duration_step);
+                    return false;
+                }
+                window.setTimeout(function(e) {
+                    _calc_date();
+                }, 1);
+            }, this);
+
+            duration.onchange = dhtmlx.bind(function(e) { _calc_date(); }, this);
+
+      var mapping = gantt._resolve_default_mapping(config);
+      if(typeof(mapping) === "string") mapping = {start_date: mapping};
+
+      var start_date = ev[mapping.start_date] || new Date();
+      var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1);
+      var duration_val = Math.round(ev[mapping.duration]) || gantt.calculateDuration(start_date, end_date);
+
+      gantt.form_blocks._fill_lightbox_select(s, 0, start_date, map, cfg);
+            duration.value = duration_val;
+            _calc_date();
+        },
+
+        _get_start_date: function(node, config) {
+            var s=node.getElementsByTagName("select");
+            var map = config._time_format_order;
+            var hours = 0;
+            var minutes = 0;
+            if (dhtmlx.defined(map[3])) {
+        var input = s[map[3]];
+                var time = parseInt(input.value, 10);
+        if(isNaN(time) && input.hasAttribute("data-value")){
+          time = parseInt(input.getAttribute("data-value"), 10);
+        }
+
+                hours = Math.floor(time/60);
+                minutes = time%60;
+            }
+            return new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes);
+        },
+        _get_duration: function(node, config) {
+            var duration = node.getElementsByTagName("input")[1];
+            duration = parseInt(duration.value, 10);
+            if (!duration || window.isNaN(duration)) duration = 1;
+            if (duration < 0) duration *= -1;
+            return duration;
+        },
+
+        get_value:function(node, ev, config) {
+            var start_date = gantt.form_blocks.duration._get_start_date(node, config);
+            var duration = gantt.form_blocks.duration._get_duration(node, config);
+
+            var end_date = gantt.calculateEndDate(start_date, duration);
+      var mapped_fields = gantt._resolve_default_mapping(config);
+      var res = {
+        start_date: new Date(start_date),
+        end_date: new Date(end_date),
+        duration: duration
+      };
+      if(typeof mapped_fields == "string"){
+        return res.start_date;
+      }else{
+        return res;
+      }
+        },
+        focus:function(node){
+            gantt._focus(node.getElementsByTagName("select")[0]);
+        }
+    },
+  parent: {
+    _filter : function(options, config, item_id){
+      var filter = config.filter || function(){ return true;};
+
+      options = options.slice(0);
+
+      for(var i=0; i < options.length; i++){
+        var task = options[i];
+        if(task.id == item_id || gantt.isChildOf(task.id, item_id) || filter(task.id, task) === false){
+          options.splice(i, 1);
+          i--;
+        }
+      }
+      return options;
+    },
+
+    _display : function(config, item_id){
+      var tasks = [],
+        options = [];
+      if(item_id){
+        tasks = gantt.getTaskByTime();
+        if(config.allow_root){
+          tasks.unshift({id:gantt.config.root_id, text:config.root_label || ""});
+        }
+        tasks = this._filter(tasks, config, item_id);
+        if(config.sort){
+          tasks.sort(config.sort);
+        }
+      }
+      var text = config.template || gantt.templates.task_text;
+      for(var i = 0; i < tasks.length; i++){
+        var label = text.apply(gantt, [tasks[i].start_date, tasks[i].end_date, tasks[i]]);
+        if(label === undefined){
+          label = "";
+        }
+        options.push({
+          key: tasks[i].id,
+          label: label
+        });
+      }
+      config.options = options;
+      config.map_to = config.map_to || "parent";
+      return gantt.form_blocks.select.render.apply(this, arguments);
+    },
+    render : function(sns){
+      return gantt.form_blocks.parent._display(sns, false);
+    },
+    set_value:function(node,value,ev,config){
+      var tmpDom = document.createElement("div");
+      tmpDom.innerHTML = gantt.form_blocks.parent._display(config, ev.id);
+      var newOptions = tmpDom.removeChild(tmpDom.firstChild);
+      node.onselect = null;
+      node.parentNode.replaceChild(newOptions, node);
+
+      return gantt.form_blocks.select.set_value.apply(gantt, [newOptions,value,ev,config]);
+    },
+    get_value:function(){
+      return gantt.form_blocks.select.get_value.apply(gantt, arguments);
+    },
+    focus:function(){
+      return gantt.form_blocks.select.focus.apply(gantt, arguments);
+    }
+  }
+};
+
+gantt._is_lightbox_timepicker = function() {
+    var s = this._get_typed_lightbox_config();
+    for (var i = 0; i < s.length; i++)
+        if (s[i].name == "time" && s[i].type == "time")
+            return true;
+    return false;
+};
+
+gantt._dhtmlx_confirm = function(message, title, callback, ok) {
+    if (!message)
+        return callback();
+    var opts = { text: message };
+    if (title)
+        opts.title = title;
+  if(ok){
+    opts.ok = ok;
+  }
+    if (callback) {
+        opts.callback = function(result) {
+            if (result)
+                callback();
+        };
+    }
+    dhtmlx.confirm(opts);
+};
+
+gantt._get_typed_lightbox_config = function(type){
+  if(type === undefined){
+    type = this.getLightboxType();
+  }
+
+  var field = this._get_type_name(type);
+
+  if(gantt.config.lightbox[field+"_sections"]){
+    return gantt.config.lightbox[field+"_sections"];
+  }else{
+    return gantt.config.lightbox.sections;
+  }
+};
+
+gantt._silent_redraw_lightbox = function(type){
+  var oldType = this.getLightboxType();
+
+  if(this.getState().lightbox){
+    var taskId = this.getState().lightbox;
+    var formData = this.getLightboxValues(),
+      task = dhtmlx.copy(this.getTask(taskId));
+
+    this.resetLightbox();
+
+    var updTask = dhtmlx.mixin(task, formData, true);
+    var box = this.getLightbox(type ? type : undefined);
+    this._center_lightbox(this.getLightbox());
+    this._set_lightbox_values(updTask, box);
+  }else{
+    this.resetLightbox();
+    this.getLightbox(type ? type : undefined);
+  }
+  this.callEvent("onLightboxChange", [oldType, this.getLightboxType()]);
+};
+gantt._extend_to_optional = function(lightbox_block){
+
+  var duration = lightbox_block;
+  var optional_time = {
+    render : duration.render,
+    focus : duration.focus,
+    set_value: function (node, value, task, section){
+      var mapping = gantt._resolve_default_mapping(section);
+      if(!task[mapping.start_date]){
+        optional_time.disable(node, section);
+        var val = {};
+
+        for(var i in mapping){
+          //take default values from the time control from task start/end dates
+          val[mapping[i]] = task[i];
+        }
+
+        return duration.set_value.call(gantt, node, value, val, section);//set default value
+      }else{
+        optional_time.enable(node, section);
+        return duration.set_value.call(gantt, node, value, task, section);
+      }
+    },
+    get_value: function (node, task, section){
+      if(section.disabled){
+        return {start_date: null};
+      }else{
+        return duration.get_value.call(gantt, node, task, section);
+      }
+    },
+    update_block : function(node, section){
+      gantt.callEvent("onSectionToggle", [gantt._lightbox_id, section]);
+      node.style.display = section.disabled ? "none" : "block";
+
+      if(section.button){
+        var button = node.previousSibling.firstChild.firstChild,
+          labels = gantt.locale.labels;
+
+        var button_text = section.disabled ? labels[section.name + "_enable_button"] : labels[section.name + "_disable_button"];
+
+        button.nextSibling.innerHTML = button_text;
+      }
+      gantt.resizeLightbox();
+    },
+    disable: function(node, section){
+      section.disabled = true;
+      optional_time.update_block(node, section);
+
+    },
+    enable:function(node, section){
+      section.disabled = false;
+      optional_time.update_block(node, section);
+    },
+    button_click: function(index, el, section, container){
+      if(gantt.callEvent("onSectionButton", [gantt._lightbox_id, section]) === false){
+        return;
+      }
+      var config = gantt._get_typed_lightbox_config()[index];
+      if(config.disabled){
+        optional_time.enable(container, config);
+      }else{
+        optional_time.disable(container, config);
+      }
+    }
+  };
+  return optional_time;
+};
+
+gantt.form_blocks.duration_optional = gantt._extend_to_optional(gantt.form_blocks.duration);
+gantt.form_blocks.time_optional = gantt._extend_to_optional(gantt.form_blocks.time);
+*/
+/**
+  * 	@desc: constructor, data processor object
+  *	@param: serverProcessorURL - url used for update
+  *	@type: public
+  */
+function dataProcessor(serverProcessorURL){
+    this.serverProcessor = serverProcessorURL;
+    this.action_param="!nativeeditor_status";
+
+  this.object = null;
+  this.updatedRows = []; //ids of updated rows
+
+  this.autoUpdate = true;
+  this.updateMode = "cell";
+  this._tMode="GET";
+  this._headers = null;
+  this._payload = null;
+  this.post_delim = "_";
+
+    this._waitMode=0;
+    this._in_progress={};//?
+    this._invalid={};
+    this.mandatoryFields=[];
+    this.messages=[];
+
+    this.styles={
+      updated:"font-weight:bold;",
+      inserted:"font-weight:bold;",
+      deleted:"text-decoration : line-through;",
+      invalid:"background-color:FFE0E0;",
+      invalid_cell:"border-bottom:2px solid red;",
+      error:"color:red;",
+      clear:"font-weight:normal;text-decoration:none;"
+    };
+
+    this.enableUTFencoding(true);
+    dhx4._eventable(this);
+
+    return this;
+    }
+
+dataProcessor.prototype={
+  setTransactionMode:function(mode,total){
+    if (typeof mode == "object"){
+      this._tMode = mode.mode || this._tMode;
+      this._headers = this._headers || mode.headers;
+      this._payload = this._payload || mode.payload;
+    } else {
+          this._tMode=mode;
+      this._tSend=total;
+    }
+
+    if (this._tMode == "REST"){
+      this._tSend = false;
+      this._endnm = true;
+    }
+    },
+    escape:function(data){
+      if (this._utf)
+        return encodeURIComponent(data);
+      else
+          return escape(data);
+  },
+    /**
+  * 	@desc: allows to set escaping mode
+  *	@param: true - utf based escaping, simple - use current page encoding
+  *	@type: public
+  */
+  enableUTFencoding:function(mode){
+        this._utf=dhx4.s2b(mode);
+    },
+    /**
+  * 	@desc: allows to define, which column may trigger update
+  *	@param: val - array or list of true/false values
+  *	@type: public
+  */
+  setDataColumns:function(val){
+    this._columns=(typeof val == "string")?val.split(","):val;
+    },
+    /**
+  * 	@desc: get state of updating
+  *	@returns:   true - all in sync with server, false - some items not updated yet.
+  *	@type: public
+  */
+  getSyncState:function(){
+    return !this.updatedRows.length;
+  },
+  /**
+  * 	@desc: enable/disable named field for data syncing, will use column ids for grid
+  *	@param:   mode - true/false
+  *	@type: public
+  */
+  enableDataNames:function(mode){
+    this._endnm=dhx4.s2b(mode);
+  },
+  /**
+  * 	@desc: enable/disable mode , when only changed fields and row id send to the server side, instead of all fields in default mode
+  *	@param:   mode - true/false
+  *	@type: public
+  */
+  enablePartialDataSend:function(mode){
+    this._changed=dhx4.s2b(mode);
+  },
+  /**
+  * 	@desc: set if rows should be send to server automaticaly
+  *	@param: mode - "row" - based on row selection changed, "cell" - based on cell editing finished, "off" - manual data sending
+  *	@type: public
+  */
+  setUpdateMode:function(mode,dnd){
+    this.autoUpdate = (mode=="cell");
+    this.updateMode = mode;
+    this.dnd=dnd;
+  },
+  ignore:function(code,master){
+    this._silent_mode=true;
+    code.call(master||window);
+    this._silent_mode=false;
+  },
+  /**
+  * 	@desc: mark row as updated/normal. check mandatory fields,initiate autoupdate (if turned on)
+  *	@param: rowId - id of row to set update-status for
+  *	@param: state - true for "updated", false for "not updated"
+  *	@param: mode - update mode name
+  *	@type: public
+  */
+  setUpdated:function(rowId,state,mode){
+    if (this._silent_mode) return;
+    var ind=this.findRow(rowId);
+
+    mode=mode||"updated";
+    var existing = this.obj.getUserData(rowId,this.action_param);
+    if (existing && mode == "updated") mode=existing;
+    if (state){
+      this.set_invalid(rowId,false); //clear previous error flag
+      this.updatedRows[ind]=rowId;
+      this.obj.setUserData(rowId,this.action_param,mode);
+      if (this._in_progress[rowId])
+        this._in_progress[rowId]="wait";
+    } else{
+      if (!this.is_invalid(rowId)){
+        this.updatedRows.splice(ind,1);
+        this.obj.setUserData(rowId,this.action_param,"");
+      }
+    }
+
+    //clear changed flag
+    if (!state)
+      this._clearUpdateFlag(rowId);
+
+    this.markRow(rowId,state,mode);
+    if (state && this.autoUpdate) this.sendData(rowId);
+  },
+  _clearUpdateFlag:function(id){},
+  markRow:function(id,state,mode){
+    var str="";
+    var invalid=this.is_invalid(id);
+    if (invalid){
+          str=this.styles[invalid];
+          state=true;
+      }
+    if (this.callEvent("onRowMark",[id,state,mode,invalid])){
+      //default logic
+      str=this.styles[state?mode:"clear"]+str;
+
+          this.obj[this._methods[0]](id,str);
+
+      if (invalid && invalid.details){
+        str+=this.styles[invalid+"_cell"];
+        for (var i=0; i < invalid.details.length; i++)
+          if (invalid.details[i])
+                this.obj[this._methods[1]](id,i,str);
+      }
+    }
+  },
+  getState:function(id){
+    return this.obj.getUserData(id,this.action_param);
+  },
+  is_invalid:function(id){
+    return this._invalid[id];
+  },
+  set_invalid:function(id,mode,details){
+    if (details) mode={value:mode, details:details, toString:function(){ return this.value.toString(); }};
+    this._invalid[id]=mode;
+  },
+  /**
+  * 	@desc: check mandatory fields and varify values of cells, initiate update (if specified)
+  *	@param: rowId - id of row to set update-status for
+  *	@type: public
+  */
+  checkBeforeUpdate:function(rowId){
+    return true;
+  },
+  /**
+  * 	@desc: send row(s) values to server
+  *	@param: rowId - id of row which data to send. If not specified, then all "updated" rows will be send
+  *	@type: public
+  */
+  sendData:function(rowId){
+    if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return;
+    if (this.obj.editStop) this.obj.editStop();
+
+
+    if(typeof rowId == "undefined" || this._tSend) return this.sendAllData();
+    if (this._in_progress[rowId]) return false;
+
+    this.messages=[];
+    if (!this.checkBeforeUpdate(rowId) && this.callEvent("onValidationError",[rowId,this.messages])) return false;
+    this._beforeSendData(this._getRowData(rowId),rowId);
+    },
+    _beforeSendData:function(data,rowId){
+      if (!this.callEvent("onBeforeUpdate",[rowId,this.getState(rowId),data])) return false;
+    this._sendData(data,rowId);
+    },
+    serialize:function(data, id){
+      if (typeof data == "string")
+        return data;
+      if (typeof id != "undefined")
+        return this.serialize_one(data,"");
+      else{
+        var stack = [];
+        var keys = [];
+        for (var key in data)
+          if (data.hasOwnProperty(key)){
+            stack.push(this.serialize_one(data[key],key+this.post_delim));
+            keys.push(key);
+        }
+        stack.push("ids="+this.escape(keys.join(",")));
+        if (dhtmlx.security_key)
+        stack.push("dhx_security="+dhtmlx.security_key);
+        return stack.join("&");
+      }
+    },
+    serialize_one:function(data, pref){
+      if (typeof data == "string")
+        return data;
+      var stack = [];
+      for (var key in data)
+        if (data.hasOwnProperty(key)){
+          if ((key == "id" || key == this.action_param) && this._tMode == "REST") continue;
+          stack.push(this.escape((pref||"")+key)+"="+this.escape(data[key]));
+        }
+    return stack.join("&");
+    },
+    _sendData:function(a1,rowId){
+      if (!a1) return; //nothing to send
+    if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false;
+
+      if (rowId)
+      this._in_progress[rowId]=(new Date()).valueOf();
+
+    var that = this;
+    var back = function(xml){
+      var ids = [];
+      if (rowId)
+        ids.push(rowId);
+      else if (a1)
+        for (var key in a1)
+          ids.push(key);
+
+      return that.afterUpdate(that,xml,ids);
+    };
+
+    var a3 = this.serverProcessor+(this._user?(dhtmlx.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):"");
+
+    if (this._tMode=="GET")
+          dhx4.ajax.get(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId), back);
+    else if (this._tMode == "POST")
+          dhx4.ajax.post(a3,this.serialize(a1,rowId), back);
+        else if (this._tMode == "REST"){
+          var state = this.getState(rowId);
+          var url = a3.replace(/(\&|\?)editing\=true/,"");
+          var data = "";
+          var method = "post";
+
+          if (state == "inserted"){
+            data = this.serialize(a1, rowId);
+          } else if (state == "deleted"){
+            method = "DELETE";
+            url = url + (url.slice(-1) == "/" ? "" : "/") + rowId;
+          } else {
+            method = "PUT";
+            data = this.serialize(a1, rowId);
+            url = url + (url.slice(-1) == "/" ? "" : "/") + rowId;
+          }
+
+
+          if (this._payload)
+            for (var key in this._payload)
+              url = url + dhtmlx.url(url) + this.escape(key) + "=" + this.escape(this._payload[key]);
+
+          dhx4.ajax.query({
+            url:url,
+            method:method,
+            headers:this._headers,
+            data:data,
+            callback:back
+          });
+        }
+
+    this._waitMode++;
+    },
+  sendAllData:function(){
+    if (!this.updatedRows.length) return;
+
+    this.messages=[]; var valid=true;
+    for (var i=0; i<this.updatedRows.length; i++)
+      valid&=this.checkBeforeUpdate(this.updatedRows[i]);
+    if (!valid && !this.callEvent("onValidationError",["",this.messages])) return false;
+
+    if (this._tSend)
+      this._sendData(this._getAllData());
+    else
+      for (var i=0; i<this.updatedRows.length; i++)
+        if (!this._in_progress[this.updatedRows[i]]){
+          if (this.is_invalid(this.updatedRows[i])) continue;
+          this._beforeSendData(this._getRowData(this.updatedRows[i]),this.updatedRows[i]);
+          if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; //block send all for tree
+        }
+  },
+
+
+
+
+
+
+
+
+  _getAllData:function(rowId){
+    var out={};
+    var has_one = false;
+    for(var i=0;i<this.updatedRows.length;i++){
+      var id=this.updatedRows[i];
+      if (this._in_progress[id] || this.is_invalid(id)) continue;
+      if (!this.callEvent("onBeforeUpdate",[id,this.getState(id), this._getRowData(id)])) continue;
+      out[id]=this._getRowData(id,id+this.post_delim);
+      has_one = true;
+      this._in_progress[id]=(new Date()).valueOf();
+    }
+    return has_one?out:null;
+  },
+
+
+  /**
+  * 	@desc: specify column which value should be varified before sending to server
+  *	@param: ind - column index (0 based)
+  *	@param: verifFunction - function (object) which should verify cell value (if not specified, then value will be compared to empty string). Two arguments will be passed into it: value and column name
+  *	@type: public
+  */
+  setVerificator:function(ind,verifFunction){
+    this.mandatoryFields[ind] = verifFunction||(function(value){return (value!=="");});
+  },
+  /**
+  * 	@desc: remove column from list of those which should be verified
+  *	@param: ind - column Index (0 based)
+  *	@type: public
+  */
+  clearVerificator:function(ind){
+    this.mandatoryFields[ind] = false;
+  },
+
+
+
+
+
+  findRow:function(pattern){
+    var i=0;
+      for(i=0;i<this.updatedRows.length;i++)
+        if(pattern==this.updatedRows[i]) break;
+      return i;
+    },
+
+
+
+
+
+
+
+
+
+
+
+  /**
+  * 	@desc: define custom actions
+  *	@param: name - name of action, same as value of action attribute
+  *	@param: handler - custom function, which receives a XMl response content for action
+  *	@type: private
+  */
+  defineAction:function(name,handler){
+        if (!this._uActions) this._uActions=[];
+            this._uActions[name]=handler;
+  },
+
+
+
+
+  /**
+*     @desc: used in combination with setOnBeforeUpdateHandler to create custom client-server transport system
+*     @param: sid - id of item before update
+*     @param: tid - id of item after up0ate
+*     @param: action - action name
+*     @type: public
+*     @topic: 0
+*/
+  afterUpdateCallback:function(sid, tid, action, btag) {
+    var marker = sid;
+    var correct=(action!="error" && action!="invalid");
+    if (!correct) this.set_invalid(sid,action);
+    if ((this._uActions)&&(this._uActions[action])&&(!this._uActions[action](btag)))
+      return (delete this._in_progress[marker]);
+
+    if (this._in_progress[marker]!="wait")
+        this.setUpdated(sid, false);
+
+      var soid = sid;
+
+      switch (action) {
+      case "inserted":
+      case "insert":
+          if (tid != sid) {
+              this.obj[this._methods[2]](sid, tid);
+              sid = tid;
+          }
+          break;
+      case "delete":
+      case "deleted":
+        this.obj.setUserData(sid, this.action_param, "true_deleted");
+          this.obj[this._methods[3]](sid);
+          delete this._in_progress[marker];
+          return this.callEvent("onAfterUpdate", [sid, action, tid, btag]);
+          break;
+      }
+
+      if (this._in_progress[marker]!="wait"){
+        if (correct) this.obj.setUserData(sid, this.action_param,'');
+        delete this._in_progress[marker];
+      } else {
+        delete this._in_progress[marker];
+        this.setUpdated(tid,true,this.obj.getUserData(sid,this.action_param));
+    }
+
+      this.callEvent("onAfterUpdate", [soid, action, tid, btag]);
+  },
+
+  /**
+  * 	@desc: response from server
+  *	@param: xml - XMLLoader object with response XML
+  *	@type: private
+  */
+  afterUpdate:function(that,xml,id){
+    //try to use json first
+    if (window.JSON){
+      try{
+        var tag = JSON.parse(xml.xmlDoc.responseText);
+        var action = tag.action || this.getState(id) || "updated";
+        var sid = tag.sid || id[0];
+        var tid = tag.tid || id[0];
+        that.afterUpdateCallback(sid, tid, action, tag);
+        that.finalizeUpdate();
+        return;
+      } catch(e){
+      }
+    }
+    //xml response
+    var top = dhx4.ajax.xmltop("data", xml.xmlDoc); //fix incorrect content type in IE
+    if (!top) return this.cleanUpdate(id);
+    var atag=dhx4.ajax.xpath("//data/action", top);
+    if (!atag.length) return this.cleanUpdate(id);
+
+    for (var i=0; i<atag.length; i++){
+          var btag=atag[i];
+      var action = btag.getAttribute("type");
+      var sid = btag.getAttribute("sid");
+      var tid = btag.getAttribute("tid");
+
+      that.afterUpdateCallback(sid,tid,action,btag);
+    }
+    that.finalizeUpdate();
+  },
+  cleanUpdate:function(id){
+    if (id)
+      for (var i = 0; i < id.length; i++)
+        delete this._in_progress[id[i]];
+  },
+  finalizeUpdate:function(){
+    if (this._waitMode) this._waitMode--;
+
+    if ((this.obj.mytype=="tree" || this.obj._h2) && this.updatedRows.length)
+      this.sendData();
+    this.callEvent("onAfterUpdateFinish",[]);
+    if (!this.updatedRows.length)
+      this.callEvent("onFullSync",[]);
+  },
+
+
+
+
+
+  /**
+  * 	@desc: initializes data-processor
+  *	@param: anObj - dhtmlxGrid object to attach this data-processor to
+  *	@type: public
+  */
+  init:function(anObj){
+    this.obj = anObj;
+    if (this.obj._dp_init)
+      this.obj._dp_init(this);
+  },
+
+
+  setOnAfterUpdate:function(ev){
+    this.attachEvent("onAfterUpdate",ev);
+  },
+  enableDebug:function(mode){
+  },
+  setOnBeforeUpdateHandler:function(func){
+    this.attachEvent("onBeforeDataSending",func);
+  },
+
+
+
+  /* starts autoupdate mode
+    @param interval
+      time interval for sending update requests
+  */
+  setAutoUpdate: function(interval, user) {
+    interval = interval || 2000;
+
+    this._user = user || (new Date()).valueOf();
+    this._need_update = false;
+    this._loader = null;
+    this._update_busy = false;
+
+    this.attachEvent("onAfterUpdate",function(sid,action,tid,xml_node){
+      this.afterAutoUpdate(sid, action, tid, xml_node);
+    });
+    this.attachEvent("onFullSync",function(){
+      this.fullSync();
+    });
+
+    var self = this;
+    window.setInterval(function(){
+      self.loadUpdate();
+    }, interval);
+  },
+
+
+  /* process updating request answer
+    if status == collision version is depricated
+    set flag for autoupdating immidiatly
+  */
+  afterAutoUpdate: function(sid, action, tid, xml_node) {
+    if (action == 'collision') {
+      this._need_update = true;
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+
+  /* callback function for onFillSync event
+    call update function if it's need
+  */
+  fullSync: function() {
+    if (this._need_update == true) {
+      this._need_update = false;
+      this.loadUpdate();
+    }
+    return true;
+  },
+
+
+  /* sends query to the server and call callback function
+  */
+  getUpdates: function(url,callback){
+    if (this._update_busy)
+      return false;
+    else
+      this._update_busy = true;
+
+    this._loader = this._loader || new dtmlXMLLoaderObject(true);
+
+    this._loader.async=true;
+    this._loader.waitCall=callback;
+    this._loader.loadXML(url);
+  },
+
+
+  /* returns xml node value
+    @param node
+      xml node
+  */
+  _v: function(node) {
+    if (node.firstChild) return node.firstChild.nodeValue;
+    return "";
+  },
+
+
+  /* returns values array of xml nodes array
+    @param arr
+      array of xml nodes
+  */
+  _a: function(arr) {
+    var res = [];
+    for (var i=0; i < arr.length; i++) {
+      res[i]=this._v(arr[i]);
+    };
+    return res;
+  },
+
+
+  /* loads updates and processes them
+  */
+  loadUpdate: function(){
+    var self = this;
+    var version = this.obj.getUserData(0,"version");
+    var url = this.serverProcessor+dhtmlx.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+version].join("&");
+    url = url.replace("editing=true&","");
+    this.getUpdates(url, function(){
+      var vers = self._loader.doXPath("//userdata");
+      self.obj.setUserData(0,"version",self._v(vers[0]));
+
+      var upds = self._loader.doXPath("//update");
+      if (upds.length){
+        self._silent_mode = true;
+
+        for (var i=0; i<upds.length; i++) {
+          var status = upds[i].getAttribute('status');
+          var id = upds[i].getAttribute('id');
+          var parent = upds[i].getAttribute('parent');
+          switch (status) {
+            case 'inserted':
+              self.callEvent("insertCallback",[upds[i], id, parent]);
+              break;
+            case 'updated':
+              self.callEvent("updateCallback",[upds[i], id, parent]);
+              break;
+            case 'deleted':
+              self.callEvent("deleteCallback",[upds[i], id, parent]);
+              break;
+          }
+        }
+
+        self._silent_mode = false;
+      }
+
+      self._update_busy = false;
+      self = null;
+    });
+  }
+
+};
+
+
+// --#include core/data_task_types.js
+
+/*
+  asserts will be removed in final code, so you can place them anythere
+  without caring about performance impacts
+*/
+dhtmlx.assert = function(check, message){
+    //jshint -W087
+  if (!check){
+    dhtmlx.message({ type:"error", text:message, expire:-1 });
+    debugger;
+  }
+};
+
+//initial initialization
+gantt.init = function(node, from, to){
+  this.callEvent("onBeforeGanttReady", []);
+  if(from && to){
+    this.config.start_date = this._min_date = gantt.date.Date(from);
+    this.config.end_date = this._max_date = gantt.date.Date(to);
+  }
+  this._init_skin();
+
+    if (!this.config.scroll_size)
+        this.config.scroll_size = this._detectScrollSize();
+
+  dhtmlxEvent(window, "resize", this._on_resize);
+
+  //can be called only once
+  this.init = function(node){
+    if (this.$container && this.$container.parentNode){
+      this.$container.parentNode.removeChild(this.$container);
+      this.$container = null;
+
+    }
+    this._reinit(node);
+  };
+
+  this._reinit(node);
+};
+
+gantt._reinit = function(node){
+    this._init_html_area(node);
+    this._set_sizes();
+
+  this._clear_renderers();
+  //this.resetLightbox();    // HOSEK
+  this._update_flags();
+    this._init_touch_events();
+    this._init_templates();
+    this._init_grid();
+    this._init_tasks();
+
+
+    this._set_scroll_events();
+
+    dhtmlxEvent(this.$container, "click", this._on_click);
+    dhtmlxEvent(this.$container, "dblclick", this._on_dblclick);
+    dhtmlxEvent(this.$container, "mousemove", this._on_mousemove);
+    dhtmlxEvent(this.$container, "contextmenu", this._on_contextmenu);
+
+  this.callEvent("onGanttReady", []);
+
+  this.render();
+};
+
+//renders initial html markup
+gantt._init_html_area = function(node){
+  ysy.log.debug("html_inited","render");
+  if (typeof node == "string")
+    this._obj = document.getElementById(node);
+  else
+    this._obj = node;
+  dhtmlx.assert(this._obj, "Invalid html container: "+node);
+    var html = "<div class='gantt_container'><div class='gantt_grid'></div><div class='gantt_task'></div><div class='gantt_grid_column_resize_wrap' data-column_id='grid_width'>\
+        </div>";
+    html += "<div class='gantt_ver_scroll'><div></div></div><div class='gantt_hor_scroll'><div></div></div></div>";
+  this._obj.innerHTML = html;
+  //store links for further reference
+    this.$container = this._obj.firstChild;
+    var childs = this.$container.childNodes;
+  this.$grid = childs[0];
+  this.$task = childs[1];
+  this.$grid_resize = childs[2];
+    this.$scroll_ver = childs[3];
+    this.$scroll_hor = childs[4];
+
+    this.$grid.innerHTML = "<div class='gantt_grid_scale'></div><div class='gantt_grid_data'></div>";
+    this.$grid_scale = this.$grid.childNodes[0];
+    this.$grid_data = this.$grid.childNodes[1];
+
+  this.$task.innerHTML = "<div class='gantt_task_scale'></div><div class='gantt_data_area'><div class='gantt_task_bg'></div><div class='gantt_marker_area'></div><div class='gantt_links_area'></div><div class='gantt_bars_area'></div></div>";
+  this.$task_scale = this.$task.childNodes[0];
+
+  this.$task_data = this.$task.childNodes[1];
+
+  this.$task_bg = this.$task_data.childNodes[0];
+  this.$marker_area = this.$task_data.childNodes[1];
+  this.$task_links = this.$task_data.childNodes[2];
+  this.$task_bars = this.$task_data.childNodes[3];
+};
+
+gantt.$click={
+    buttons:{
+        "edit":function(id){
+            gantt.showLightbox(id);
+        },
+        "delete":function(id){
+            var question = gantt.locale.labels.confirm_deleting;
+            var title = gantt.locale.labels.confirm_deleting_title;
+
+            gantt._dhtmlx_confirm(question, title, function(){
+        var task = gantt.getTask(id);
+        if(task.$new){
+          gantt._deleteTask(id, true);
+          gantt.refreshData();
+        }else{
+          gantt.deleteTask(id);
+        }
+
+                gantt.hideLightbox();
+            });
+        }
+    }
+};
+
+gantt._calculate_content_height = function(){
+  var scale_height = this.config.scale_height,
+    rows_height = this._order.length*this.config.row_height,
+    hor_scroll_height = this._scroll_hor ? this.config.scroll_size + 1 : 0;
+
+  if(!(this._is_grid_visible() || this._is_chart_visible())){
+    return 0;
+  }else{
+    return scale_height + rows_height + 2 + hor_scroll_height;
+  }
+};
+gantt._calculate_content_width = function(){
+  var grid_width = this._get_grid_width(),
+    chart_width = this._tasks ? this._tasks.full_width : 0,
+    ver_scroll_width = this._scroll_ver ? this.config.scroll_size + 1 : 0;
+
+  if(!this._is_chart_visible()){
+    chart_width = 0;
+  }
+  if(!this._is_grid_visible()){
+    grid_width = 0;
+  }
+  return grid_width + chart_width + 1;
+};
+
+gantt._get_resize_options = function(){
+  var res = {x:false, y:false};
+  if(this.config.autosize == "xy"){
+    res.x = res.y = true;
+  }else if(this.config.autosize == "y" || this.config.autosize === true){
+    res.y = true;
+  }else if(this.config.autosize == "x"){
+    res.x = true;
+  }
+  return res;
+};
+
+gantt._clean_el_size = function(value){
+  return ((value || "").toString().replace("px", "") * 1 || 0);
+};
+gantt._get_box_styles = function(){
+  var computed = null;
+  if(window.getComputedStyle){
+    computed = window.getComputedStyle(this._obj, null);
+  }else{
+    //IE with elem.currentStyle does not calculate sizes from %, so will use the default approach
+    computed = {
+      "width":this._obj.clientWidth,
+      "height":this._obj.clientHeight
+    };
+  }
+  var properties = [
+    "width",
+    "height",
+
+    "paddingTop",
+    "paddingBottom",
+    "paddingLeft",
+    "paddingRight",
+
+    "borderLeftWidth",
+    "borderRightWidth",
+    "borderTopWidth",
+    "borderBottomWidth"
+  ];
+  var styles = {
+    boxSizing:(computed.boxSizing == "border-box")
+  };
+
+  if(computed.MozBoxSizing){
+    styles.boxSizing = (computed.MozBoxSizing == "border-box");
+  }
+  for(var i =0; i < properties.length; i++){
+    styles[properties[i]] = computed[properties[i]] ? this._clean_el_size(computed[properties[i]]) : 0;
+  }
+
+
+  var box = {
+    horPaddings : (styles.paddingLeft + styles.paddingRight + styles.borderLeftWidth + styles.borderRightWidth),
+    vertPaddings : (styles.paddingTop + styles.paddingBottom + styles.borderTopWidth + styles.borderBottomWidth),
+    borderBox: styles.boxSizing,
+    innerWidth : styles.width,
+    innerHeight : styles.height,
+    outerWidth : styles.width,
+    outerHeight : styles.height
+  };
+
+
+  if(box.borderBox){
+    box.innerWidth -= box.horPaddings;
+    box.innerHeight -= box.vertPaddings;
+  }else{
+    box.outerWidth += box.horPaddings;
+    box.outerHeight += box.vertPaddings;
+  }
+
+  return box;
+};
+gantt._do_autosize = function(){
+  ysy.log.debug("_do_autosize()","print");
+  var resize = this._get_resize_options();
+  var boxSizes = this._get_box_styles();
+  if(resize.y){
+    var reqHeight = this._calculate_content_height();
+    if(boxSizes.borderBox){
+      reqHeight += boxSizes.vertPaddings;
+    }
+
+    this._obj.style.height = reqHeight + 'px';
+  }
+  if(resize.x){
+    var reqWidth = this._calculate_content_width();
+    if(boxSizes.borderBox){
+      reqWidth += boxSizes.horPaddings;
+    }
+    this._obj.style.width = reqWidth + 'px';
+  }
+};
+//set sizes to top level html element
+gantt._set_sizes = function(){
+  ysy.log.debug("_set_sizes()","print");
+  // TODO printable
+  this._do_autosize();
+
+  var boxSizes = this._get_box_styles();
+  this._y = boxSizes.innerHeight;
+
+    if (this._y < 20) return;
+
+  //same height
+  this.$grid.style.height = this.$task.style.height = Math.max(this._y - this.$scroll_hor.offsetHeight - 2, 0) +"px";
+
+  var dataHeight = Math.max((this._y - (this.config.scale_height||0) - this.$scroll_hor.offsetHeight - 2), 0);
+    this.$grid_data.style.height = this.$task_data.style.height =  dataHeight + "px";
+
+  //share width
+  var gridWidth = Math.max(this._get_grid_width()-1, 0);
+  this.$grid.style.width =  gridWidth +"px";
+  this.$grid.style.display = gridWidth === 0 ? 'none' : '';
+
+  boxSizes = this._get_box_styles();
+  this._x = boxSizes.innerWidth;
+
+  if (this._x < 20) return;
+
+    this.$grid_data.style.width = Math.max(this._get_grid_width()-1, 0) +"px";
+  this.$task.style.width = Math.max(this._x - this._get_grid_width() - 2, 0) +"px";
+};
+gantt._unset_sizes = function(){
+  ysy.log.debug("_unset_sizes()","print");
+  // TODO printable
+
+  this.$grid.style.height = this.$task.style.height = "";
+
+  //var dataHeight = Math.max((this._y - (this.config.scale_height||0) - this.$scroll_hor.offsetHeight - 2), 0);
+    this.$grid_data.style.height = this.$task_data.style.height =  "";
+
+  //share width
+  var gridWidth = Math.max(this._get_grid_width()-1, 0);
+  this.$grid.style.width =  gridWidth +"px";
+  this.$grid.style.display = gridWidth === 0 ? 'none' : '';
+
+  var boxSizes = this._get_box_styles();
+  this._x = boxSizes.innerWidth;
+
+  if (this._x < 20) return;
+
+    this.$grid_data.style.width = Math.max(this._get_grid_width()-1, 0) +"px";
+  this.$task.style.width = "";
+  this.$scroll_hor.style.display = "none";
+  this.$scroll_ver.style.display = "none";
+};
+
+gantt.getScrollState = function(){
+  if(this.$task && this.$task_data)
+    return { x:this.$task.scrollLeft, y:this.$task_data.scrollTop };
+  else
+    return null;
+};
+
+gantt._save_scroll_state = function (x, y) {
+  // according to Chrome profiler
+  // getting-setting scrollLeft for restoring scroll position after render takes surprisingly big amount of time
+  // 2x-3x times more than setting innerHTML (if using gantt.config.static_background)
+  // Will store scroll position in memory instead of getting actual values from DOM
+  var pos = {};
+  this._cached_scroll_pos = this._cached_scroll_pos || {x:0,y:0};
+  if (x !== undefined) {
+    pos.x = x;
+  }
+  if (y !== undefined) {
+    pos.y = y;
+  }
+  dhtmlx.mixin(this._cached_scroll_pos, pos, true);
+
+};
+gantt._restore_scroll_state = function(){
+  return this._cached_scroll_pos || null;
+};
+gantt.scrollTo = function(left, top){
+  if (left*1 == left){
+    var modLeft = Math.min(left, this.$task.scrollWidth - this.$task.offsetWidth);
+    this.$task.scrollLeft = modLeft;
+    this._save_scroll_state(modLeft, undefined);
+  }
+  if(top*1 == top){
+    // this.$task_data.scrollTop = top;
+    // this.$grid_data.scrollTop = top;
+    this._save_scroll_state(undefined, top);
+  }
+  ysy.log.debug("ScrollTo [" + left + " (" + modLeft + ")," + top + "]", "scroll");
+  this.callEvent("onScrollTo", [modLeft, top]);
+};
+
+gantt.showDate = function(date){
+  var date_x = Math.round(this.posFromDate(date));
+  var scroll_to = Math.max(date_x - this.config.task_scroll_offset, 0);
+  this.scrollTo(scroll_to);
+};
+gantt.showTask = function(id) {
+  var el = this.getTaskNode(id);
+  if(!el)
+    return;
+
+  var left = Math.max(el.offsetLeft - this.config.task_scroll_offset, 0);
+  var top = el.offsetTop - (this.$task_data.offsetHeight - this.config.row_height)/2;
+  this.scrollTo(left, top);
+};
+
+
+//called after window resize
+gantt._on_resize = gantt.setSizes = function(){
+    gantt._set_sizes();
+    gantt._scroll_resize();
+};
+
+//renders self
+gantt.render = function() {
+  this.refresher.renderAll();
+  return;
+  //if(!this._is_render_active()){
+  //	ysy.log.debug("render rejected","render");
+  //	return;
+  //}
+  //ysy.log.debug("render is pending","render");
+  //
+  //gantt._skip_render = true;
+  //setTimeout($.proxy(gantt._render,gantt),0);
+}
+gantt._render = function(){
+  ysy.log.debug("render is triggered","render");
+  this.callEvent("onBeforeGanttRender", []);
+
+  var pos = dhtmlx.copy(this._restore_scroll_state());
+  var visible_date = null;
+  if(pos){
+    visible_date = gantt.dateFromPos(pos.x + this.config.task_scroll_offset);
+  }
+
+  this._render_grid();	//grid.js
+  this._render_tasks_scales();	//tasks.js
+  this._scroll_resize();
+  this._on_resize();
+  this._render_data();
+
+  // if(gantt._selected_task){   //  TODO vyladit a povolit
+  //   if(gantt.isTaskExists(gantt._selected_task)){
+  //     gantt.showTask(gantt._selected_task);
+  //   }
+  // }else
+  if(this.config.preserve_scroll && pos){
+
+    var new_pos =gantt._restore_scroll_state();
+    var new_date = gantt.dateFromPos(new_pos.x + this.config.task_scroll_offset);
+    if(!(+visible_date == +new_date && new_pos.y == pos.y)){
+      /*if(visible_date){
+        this.showDate(visible_date);
+      }*/
+      ysy.log.debug("pos.x="+pos.x+" new_pos="+new_pos.x,"scroll");
+      this.showDate(new_date);
+      gantt.scrollTo(undefined, pos.y);
+      //gantt.scrollTo(pos.x,pos.y);
+    }
+  }else{
+    this.showDate(moment());
+  }
+
+  this.callEvent("onGanttRender", []);
+  gantt._skip_render = false;
+};
+
+
+gantt._set_scroll_events = function(){
+    dhtmlxEvent(this.$scroll_hor, "scroll", function() {
+      //in safari we can catch previous onscroll after setting new value from mouse-wheel event
+      //set delay to prevent value drifiting
+      if (gantt.date.now() - ( gantt._wheel_time || 0 ) < 100) return true;
+        if (gantt._touch_scroll_active) return;
+        var left = gantt.$scroll_hor.scrollLeft;
+        gantt.scrollTo(left);
+    });
+    dhtmlxEvent(this.$scroll_ver, "scroll", function() {
+        if (gantt._touch_scroll_active) return;
+        var top = gantt.$scroll_ver.scrollTop;
+        gantt.$grid_data.scrollTop = top;
+        gantt.scrollTo(null, top);
+    });
+    dhtmlxEvent(this.$task, "scroll", function() {
+        var left = gantt.$task.scrollLeft,
+      barLeft = gantt.$scroll_hor.scrollLeft;
+    if(barLeft != left)
+          gantt.$scroll_hor.scrollLeft = left;
+    });
+    dhtmlxEvent(this.$task_data, "scroll", function() {
+        var top = gantt.$task_data.scrollTop,
+      barTop = gantt.$scroll_ver.scrollTop;
+    if(barTop != top)
+          gantt.$scroll_ver.scrollTop = top;
+    });
+
+    var ff = _isFF && !window._KHTMLrv;
+  function onMouseWheel(e){
+    var res = gantt._get_resize_options();
+    gantt._wheel_time = gantt.date.Date();
+
+        var wx = ff ? (e.deltaX*-20) : e.wheelDeltaX*2;
+        var wy = ff ? (e.deltaY*-40) : e.wheelDelta;
+
+    if (wx && Math.abs(wx) > Math.abs(wy)){
+      if(res.x) return true;//no horisontal scroll, must not block scrolling
+
+      var dir  = wx/-40;
+      var left = gantt.$task.scrollLeft+dir*30;
+      gantt.scrollTo(left, null);
+      gantt.$scroll_hor.scrollTop = top;
+    } else {
+      if(res.y) return true;//no vertical scroll, must not block scrolling
+
+      var dir  = wy/-40;
+      if (typeof wy == "undefined")
+        dir = e.detail;
+
+      var top = gantt.$scroll_ver.scrollTop+dir*30;
+      if(!gantt.config.prevent_default_scroll && gantt._cached_scroll_pos && gantt._cached_scroll_pos.y == top) return true;
+
+      gantt.scrollTo(null, top);
+      gantt.$scroll_ver.scrollTop = top;
+    }
+
+    if (e.preventDefault)
+      e.preventDefault();
+    e.cancelBubble=true;
+    return false;
+  }
+
+    if (ff)
+        dhtmlxEvent(gantt.$container, "wheel", onMouseWheel);
+    else
+        dhtmlxEvent(gantt.$container, "mousewheel", onMouseWheel);
+
+};
+
+
+gantt._scroll_resize = function() {
+    if (this._x < 20 || this._y < 20) return;
+
+    var grid_width = this._get_grid_width();
+
+    var task_width = Math.max(this._x - grid_width, 0);
+    var task_height = Math.max(this._y - this.config.scale_height, 0);
+
+  var scroll_size = this.config.scroll_size + 1;//1px for inner content
+
+    var task_data_width = Math.max(this.$task_data.offsetWidth - scroll_size, 0);
+    var task_data_height = this.config.row_height*this._order.length;
+
+  var resize = this._get_resize_options();
+  var scroll_hor = this._scroll_hor = resize.x ? false : (task_data_width > task_width);
+    var scroll_ver = this._scroll_ver = resize.y ? false : (task_data_height > task_height);
+
+    this.$scroll_hor.style.display = scroll_hor ? "block" : "none";
+    this.$scroll_hor.style.height = (scroll_hor ? scroll_size : 0) + "px";
+    this.$scroll_hor.style.width = Math.max((this._x - (scroll_ver ? scroll_size : 2)), 0) + "px";
+    this.$scroll_hor.firstChild.style.width = (task_data_width + grid_width + scroll_size + 2) + "px";
+
+    this.$scroll_ver.style.display = scroll_ver ? "block" : "none";
+    this.$scroll_ver.style.width = (scroll_ver ? scroll_size : 0) + "px";
+    this.$scroll_ver.style.height = Math.max((this._y - (scroll_hor ? scroll_size : 0) - this.config.scale_height), 0) + "px";
+    this.$scroll_ver.style.top = this.config.scale_height + "px";
+    this.$scroll_ver.firstChild.style.height = (this.config.scale_height + task_data_height) + "px";
+};
+
+gantt.locate = function(e) {
+    var trg = gantt._get_target_node(e);
+
+    //ignore empty cells
+  var className = trg.className || "";
+  if(!className.indexOf){
+    //'className' exist but not a string - IE svg element in DOM
+    className = '';
+  }
+    if ((className || "").indexOf("gantt_task_cell") >= 0) return null;
+
+    var attribute = arguments[1] || this.config.task_attribute;
+
+    while (trg){
+        if (trg.getAttribute){	//text nodes has not getAttribute
+            var test = trg.getAttribute(attribute);
+            if (test) return test;
+        }
+        trg=trg.parentNode;
+    }
+    return null;
+};
+gantt._get_target_node = function(e){
+  var trg;
+  if (e.tagName)
+    trg = e;
+  else {
+    e=e||window.event;
+    trg=e.target||e.srcElement;
+  }
+  return trg;
+};
+gantt._trim = function(str){
+  var func = String.prototype.trim || function(){ return this.replace(/^\s+|\s+$/g, ""); };
+  return func.apply(str);
+};
+
+gantt._locate_css = function(e, classname, strict){
+  if(strict === undefined)
+    strict = true;
+
+  var trg = gantt._get_target_node(e);
+  var css = '';
+  var test = false;
+  while (trg){
+    css = trg.className;
+    if(css && !css.indexOf){
+      //'className' exist but not a string - IE svg element in DOM
+      css = '';
+    }
+
+    if(css){
+      var ind = css.indexOf(classname);
+      if (ind >= 0){
+        if (!strict)
+          return trg;
+
+        //check that we have exact match
+        var left = (ind === 0) || (!gantt._trim(css.charAt(ind - 1)));
+        var right = ((ind + classname.length >= css.length)) || (!gantt._trim(css.charAt(ind + classname.length)));
+
+        if (left && right)
+          return trg;
+      }
+    }
+
+    trg=trg.parentNode;
+  }
+  return null;
+};
+gantt._locateHTML = function(e, attribute) {
+  var trg = gantt._get_target_node(e);
+    attribute = attribute || this.config.task_attribute;
+
+    while (trg){
+        if (trg.getAttribute){	//text nodes has not getAttribute
+            var test = trg.getAttribute(attribute);
+            if (test) return trg;
+        }
+        trg=trg.parentNode;
+    }
+    return null;
+};
+
+gantt.getTaskRowNode = function(id) {
+    var els = this.$grid_data.childNodes;
+    var attribute = this.config.task_attribute;
+    for (var i = 0; i < els.length; i++) {
+        if (els[i].getAttribute) {
+            var value = els[i].getAttribute(attribute);
+            if (value == id) return els[i];
+        }
+    }
+    return null;
+};
+
+gantt.getState = function(){
+  return {
+    drag_id : this._tasks_dnd.drag.id,
+    drag_mode : this._tasks_dnd.drag.mode,
+    drag_from_start : this._tasks_dnd.drag.left,
+    selected_task : this._selected_task,
+    min_date : gantt.date.Date(this._min_date),
+    max_date : gantt.date.Date(this._max_date),
+    lightbox : this._lightbox_id,
+    touch_drag : this._touch_drag
+
+  };
+
+};
+
+
+gantt._checkTimeout = function(host, updPerSecond){
+  if(!updPerSecond)
+    return true;
+  var timeout = 1000/updPerSecond;
+  if(timeout < 1) return true;
+
+  if(host._on_timeout)
+    return false;
+
+  setTimeout(function(){
+    delete host._on_timeout;
+  }, timeout);
+
+  host._on_timeout = true;
+  return true;
+};
+
+gantt.selectTask = function(id){
+  if(!this.config.select_task)
+    return false;
+  if (id){
+
+    if(this._selected_task == id)
+      //return false;
+      return this._selected_task;
+
+    if(!this.callEvent("onBeforeTaskSelected", [id])){
+      return false;
+    }
+
+    this.unselectTask(true);
+    this._selected_task = id;
+
+    this.refreshTask(id);
+    if(this.config.scroll_on_click){
+      var task=gantt.getTask(id);
+      this.showDate(task.start_date);
+    }
+    this.callEvent("onTaskSelected", [id]);
+  }
+  return this._selected_task;
+};
+gantt.unselectTask = function(ignore){
+  var id = this._selected_task;
+  if(!id)
+    return;
+  this._selected_task = null;
+  this.refreshTask(id);
+  this.callEvent("onTaskUnselected", [id,ignore]);
+};
+gantt.getSelectedId = function() {
+    return dhtmlx.defined(this._selected_task) ? this._selected_task : null;
+};
+
+gantt.changeLightboxType = function(type){
+  if(this.getLightboxType() == type)
+    return true;
+  gantt._silent_redraw_lightbox(type);
+};
+
+gantt._is_render_active = function(){
+  return !this._skip_render;
+};
+
+gantt._correct_dst_change = function(date, prevOffset, step, unit){
+  var time_unit = gantt._get_line(unit) * step;
+  if(time_unit > 60*60 && time_unit < 60*60*24){
+    //correct dst change only if current unit is more than one hour and less than day (days have own checking), e.g. 12h
+    var offsetChanged = date.getTimezoneOffset() - prevOffset;
+    if(offsetChanged){
+      date = gantt.date.add(date, offsetChanged, "minute");
+    }
+  }
+  return date;
+};
+
+gantt.batchUpdate = function (callback) {
+  var call_dp = (this._dp && this._dp.updateMode != "off");
+  var dp_mode;
+  if (call_dp){
+    dp_mode = this._dp.updateMode;
+    this._dp.setUpdateMode("off");
+  }
+
+  this._skip_render = true;
+
+  try{
+    callback();
+  }catch(e){
+
+  }
+
+  this._skip_render = false;
+  this.render();
+  if (call_dp) {
+    this._dp.setUpdateMode(dp_mode);
+    this._dp.sendData();
+  }
+};
+
+/*gantt.date.quarter_start = function(date){
+  gantt.date.month_start(date);
+  var m = date.getMonth(),
+    res_month;
+
+  if(m >= 9){
+    res_month = 9;
+  }else if(m >= 6){
+    res_month = 6;
+  }else if(m >= 3){
+    res_month = 3;
+  }else{
+    res_month = 0;
+  }
+
+  date.setMonth(res_month);
+  return date;
+};
+gantt.date.add_quarter = function(date, inc){
+  return gantt.date.add(date, inc*3, "month");
+};*/
+/*
+ %d - the day as a number with a leading zero ( 01 to 31 );
+ %j - the day as a number without a leading zero ( 1 to 31 );
+ %D - the day as an abbreviation ( Sun to Sat );
+ %l - the day as a full name ( Sunday to Saturday );
+ %W - the ISO-8601 week number of the year. Weeks start on Monday; 1)
+ %m - the month as a number without a leading zero ( 1 to 12 );
+ %n - the month as a number with a leading zero ( 01 to 12);
+ %M - the month as an abbreviation ( Jan to Dec );
+ %F - the month as a full name ( January to December );
+ %y - the year as a two-digit number ( 00 to 99 );
+ %Y - the year as a four-digit number ( 1900–9999 );
+ %h - the hour based on the 12-hour clock ( 00 to 11 );
+ %H - the hour based on the 24-hour clock ( 00 to 23 );
+ %i - the minute as a number with a leading zero ( 00 to 59 );
+ %s - the second as a number without a leading zero ( 00 to 59 ); 2)
+ %a - displays am (for times from midnight until noon) and pm (for times from noon until midnight);
+ %A - displays AM (for times from midnight until noon) and PM (for times from noon until midnight).
+
+ */
+
+if(!gantt.config) gantt.config = {};
+if(!gantt.config) gantt.config = {};
+if(!gantt.templates) gantt.templates = {};
+
+(function(){
+
+dhtmlx.mixin(gantt.config,
+  {links : {
+    "finish_to_start":"0",
+    "start_to_start":"1",
+    "finish_to_finish":"2",
+    "start_to_finish":"3"
+  },
+  types : {
+    'task':'task',
+    'project':'project',
+    'milestone':'milestone'
+  },
+  duration_unit : "day",
+  work_time:false,
+  correct_work_time:false,
+  skip_off_time:false,
+
+  autosize:false,
+  autosize_min_width: 0,
+
+  show_links : true,
+  show_task_cells : true,
+  // replace backgroung of the task area with a canvas img
+  static_background: false,
+  branch_loading: false,
+  show_loading: false,
+  show_chart : true,
+  show_grid : true,
+  min_duration : 60*60*1000,
+  xml_date : "%d-%m-%Y %H:%i",
+  api_date : "%d-%m-%Y %H:%i",
+  start_on_monday: true,
+  server_utc : false,
+  show_progress:true,
+  fit_tasks : false,
+  select_task:true,
+  scroll_on_click: true,
+  preserve_scroll: true,
+  readonly:false,
+
+  /*grid */
+  date_grid: "%Y-%m-%d",
+
+  drag_links : true,
+  drag_progress:true,
+  drag_resize:true,
+  drag_move:true,
+  drag_mode:{
+    "resize":"resize",
+    "progress":"progress",
+    "move":"move",
+    "ignore":"ignore",
+    "empty":"empty"   // HOSEK
+  },
+  round_dnd_dates:true,
+  link_wrapper_width:20,
+  root_id:0,
+
+    autofit: false, // grid column automatic fit grid_width config
+  columns: [
+    {name:"text", tree:true, width:'*', resize:true },
+    {name:"start_date", align: "center", resize:true },
+    {name:"duration", align: "center" },
+    {name:"add", width:'44' }
+  ],
+
+  /*scale*/
+  step: 1,
+  scale_unit: "day",
+  scale_offset_minimal:true,
+  subscales : [
+
+  ],
+
+  inherit_scale_class:false,
+
+    time_step: 60,
+    duration_step: 1,
+  date_scale: "%d %M",
+    task_date: "%d %F %Y",
+    time_picker: "%H:%i",
+    task_attribute: "task_id",
+    link_attribute: "link_id",
+    layer_attribute: "data-layer",
+    buttons_left: [
+        "gantt_save_btn",
+        "gantt_cancel_btn"
+    ],
+  _migrate_buttons: {
+    "dhx_save_btn":"gantt_save_btn",
+    "dhx_cancel_btn":"gantt_cancel_btn",
+    "dhx_delete_btn":"gantt_delete_btn"
+  },
+    buttons_right: [
+        "gantt_delete_btn"
+    ],
+    lightbox: {
+        sections: [
+            {name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
+            {name: "time", type: "duration", map_to: "auto"}
+    ],
+    project_sections: [
+      {name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
+      {name: "type", type: "typeselect", map_to: "type"},
+      {name: "time", type: "duration", readonly:true, map_to: "auto"}
+    ],
+    milestone_sections: [
+      {name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
+      {name: "type", type: "typeselect", map_to: "type"},
+      {name: "time", type: "duration", single_date:true, map_to: "auto"}
+    ]
+    },
+    drag_lightbox: true,
+    sort: false,
+    details_on_create: true,
+  details_on_dblclick:true,
+  initial_scroll : true,
+  task_scroll_offset : 100,
+
+  task_height: "full",//number px of 'full' for row height
+  min_column_width:70,
+
+  // min width for grid column (when resizing)
+  min_grid_column_width:70,
+  // name of the attribute with column index for resize element
+  grid_resizer_column_attribute: "column_index",
+  // name of the attribute with column index for resize element
+  grid_resizer_attribute: "grid_resizer",
+
+  // grid width can be increased after the column has been resized
+  keep_grid_width:false,
+
+  // grid width can be adjusted
+  grid_resize:false,
+
+  //
+  readonly_property: "readonly",
+  editable_property: "editable",
+  type_renderers:{},
+
+  open_tree_initially: false,
+  optimize_render: 'auto',
+  prevent_default_scroll: false
+
+});
+gantt.keys={
+    edit_save:13,
+    edit_cancel:27
+};
+
+gantt._init_template = function(name, initial){
+  var registeredTemplates = this._reg_templates || {};
+
+  if(this.config[name] && registeredTemplates[name] != this.config[name]){
+    if(!(initial && this.templates[name])){
+      this.templates[name] = this.date.date_to_str(this.config[name]);
+      registeredTemplates[name] = this.config[name];
+    }
+  }
+  this._reg_templates = registeredTemplates;
+};
+gantt._init_templates = function(){
+  var labels = gantt.locale.labels;
+  labels.gantt_save_btn 	= labels.icon_save;
+  labels.gantt_cancel_btn 	= labels.icon_cancel;
+  labels.gantt_delete_btn 	= labels.icon_delete;
+
+
+
+  //build configuration based templates
+  var d = this.date.date_to_str;
+  var c = this.config;
+  gantt._init_template("date_scale", true);
+  gantt._init_template("date_grid", true);
+  gantt._init_template("task_date", true);
+
+
+
+  dhtmlx.mixin(this.templates,{
+    xml_date:this.date.str_to_date(c.xml_date,c.server_utc),
+    xml_format:d(c.xml_date,c.server_utc),
+    api_date:this.date.str_to_date(c.api_date),
+    progress_text:function(start, end, task){return "";},
+    grid_header_class : function(column, config){
+      return "";
+    },
+
+    task_text:function(start, end, task){
+      return task.text;
+    },
+    task_class:function(start, end, task){return "";},
+    grid_row_class:function(start, end, task){
+      return "";
+    },
+    task_row_class:function(start, end, task){
+      return "";
+    },
+    task_cell_class:function(item, date){return "";},
+    scale_cell_class:function(date){return "";},
+    scale_row_class:function(date){return "";},
+
+    grid_indent:function(item) {
+      return "<div class='gantt_tree_indent'></div>";
+    },
+    grid_folder:function(item) {
+      if(item.$open||gantt._get_safe_type(item.type)!==gantt.config.types.task){  //  HOSEK
+        return "<div class='gantt_tree_icon gantt_folder_" + (item.$open ? "open" : "closed") + "'></div>";
+      }else{
+        return "<div class='gantt_tree_icon'><div class='gantt_drag_handle gantt_alldirarrow'></div></div>";  // HOSEK
+      }
+    },
+    grid_file:function(item) {
+      if(gantt._get_safe_type(item.type)===gantt.config.types.task)
+        return "<div class='gantt_tree_icon'><div class='gantt_drag_handle gantt_alldirarrow'></div></div>";  // HOSEK
+      return "<div class='gantt_tree_icon gantt_folder_open'></div>";
+      //return "<div class='gantt_tree_icon gantt_file'></div>";
+    },
+    grid_open:function(item) {
+      return "<div class='gantt_tree_icon gantt_" + (item.$open ? "close" : "open") + "'></div>";
+    },
+    grid_blank:function(item) {
+      return "<div class='gantt_tree_icon gantt_blank'></div>";
+    },
+
+    task_time:function(start,end,ev){
+        return gantt.templates.task_date(start)+" - "+gantt.templates.task_date(end);
+    },
+    time_picker:d(c.time_picker),
+    link_class : function(link){
+      return "";
+    },
+    link_description : function(link){
+      var from = gantt.getTask(link.source),
+        to = gantt.getTask(link.target);
+
+      return "<b>" + from.text + "</b> &ndash;  <b>" + to.text+"</b>";
+    },
+
+    drag_link : function(from, from_start, to, to_start) {
+      from = gantt.getTask(from);
+      var labels = gantt.locale.labels;
+
+      var text = "<b>" + from.text + "</b> " + (from_start ? labels.link_start : labels.link_end)+"<br/>";
+      if(to){
+        to = gantt.getTask(to);
+        text += "<b> " + to.text + "</b> "+ (to_start ? labels.link_start : labels.link_end)+"<br/>";
+      }
+      return text;
+    },
+    drag_link_class: function(from, from_start, to, to_start) {
+      var add = "";
+
+      if(from && to){
+        var allowed = gantt.isLinkAllowed(from, to, from_start, to_start);
+        add = " " + (allowed ? "gantt_link_allow" : "gantt_link_deny");
+      }
+
+      return "gantt_link_tooltip" + add;
+    }
+    });
+
+  this.callEvent("onTemplatesReady",[]);
+};
+
+})();
+if (window.jQuery){
+
+(function( $ ){
+
+  var methods = [];
+  $.fn.dhx_gantt = function(config){
+    config = config || {};
+    if (typeof(config) === 'string') {
+      if (methods[config] ) {
+        return methods[config].apply(this, []);
+      }else {
+        $.error('Method ' +  config + ' does not exist on jQuery.dhx_gantt');
+      }
+    } else {
+      var views = [];
+      this.each(function() {
+        if (this && this.getAttribute){
+          if (!this.getAttribute("dhxgantt")){
+            for (var key in config)
+              if (key!="data")
+                gantt.config[key] = config[key];
+
+            gantt.init(this);
+            if (config.data)
+              gantt.parse(config.data);
+
+            views.push(gantt);
+          }
+        }
+      });
+
+
+      if (views.length === 1) return views[0];
+      return views;
+    }
+  };
+
+})(jQuery);
+
+}
+
+if (window.dhtmlx){
+
+  if (!dhtmlx.attaches)
+    dhtmlx.attaches = {};
+
+  dhtmlx.attaches.attachGantt=function(start, end){
+    var obj = document.createElement("DIV");
+    obj.id = "gantt_"+dhtmlx.uid();
+    obj.style.width = "100%";
+    obj.style.height = "100%";
+    obj.cmp = "grid";
+
+    document.body.appendChild(obj);
+    this.attachObject(obj.id);
+
+    var that = this.vs[this.av];
+    that.grid = gantt;
+
+    gantt.init(obj.id, start, end);
+    obj.firstChild.style.border = "none";
+
+    that.gridId = obj.id;
+    that.gridObj = obj;
+
+    var method_name="_viewRestore";
+    return this.vs[this[method_name]()].grid;
+  };
+
+}
+gantt.locale = {
+  //date:{
+  //	month_full:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+  //	month_short:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+  //	day_full:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+  //	day_short:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+  //},
+  labels:{
+    //new_task:"New task",
+    //icon_save:"Save",
+    //icon_cancel:"Cancel",
+    //icon_details:"Details",
+    //icon_edit:"Edit",
+    //icon_delete:"Delete",
+    //confirm_closing:"",//Your changes will be lost, are your sure ?
+    //confirm_deleting:"Task will be deleted permanently, are you sure?",
+     //   section_description:"Description",
+     //   section_time:"Time period",
+    //section_type:"Type",
+    //
+     //   /* grid columns */
+    //
+     //   column_text : "Task name",
+     //   column_start_date : "Start time",
+     //   column_duration : "Duration",
+     //   column_add : "",
+    //
+    ///* link confirmation */
+    //link: "Link",
+    //confirm_link_deleting:"will be deleted",
+    //link_start: " (start)",
+    //link_end: " (end)",
+    //
+    //type_task: "Task",
+    //type_project: "Project",
+    //type_milestone: "Milestone",
+    //
+     //   minutes: "Minutes",
+     //   hours: "Hours",
+     //   days: "Days",
+     //   weeks: "Week",
+     //   months: "Months",
+     //   years: "Years"
+  }
+};
+
+
+
+
+gantt.skins.skyblue = {
+  config:{
+    grid_width:350,
+    row_height: 27,
+    scale_height: 27,
+    link_line_width:1,
+    link_arrow_size:8,
+    lightbox_additional_height:75
+  },
+  _second_column_width:95,
+  _third_column_width:80
+};
+gantt.skins.meadow = {
+  config:{
+    grid_width:350,
+    row_height: 27,
+    scale_height: 30,
+    link_line_width:2,
+    link_arrow_size:6,
+    lightbox_additional_height:72
+  },
+  _second_column_width:95,
+  _third_column_width:80
+};
+
+gantt.skins.terrace = {
+  config:{
+    grid_width:360,
+    row_height: 35,
+    scale_height: 35,
+    link_line_width:2,
+    link_arrow_size:6,
+    lightbox_additional_height:75
+  },
+  _second_column_width:90,
+  _third_column_width:70
+};
+gantt.skins.broadway = {
+  config:{
+    grid_width:360,
+    row_height: 35,
+    scale_height: 35,
+    link_line_width:1,
+    link_arrow_size:7,
+    lightbox_additional_height:86
+  },
+  _second_column_width:90,
+  _third_column_width:80,
+
+  _lightbox_template:"<div class='gantt_cal_ltitle'><span class='gantt_mark'>&nbsp;</span><span class='gantt_time'></span><span class='gantt_title'></span><div class='gantt_cancel_btn'></div></div><div class='gantt_cal_larea'></div>",
+  _config_buttons_left: {},
+  _config_buttons_right: {
+    "gantt_delete_btn": "icon_delete",
+    "gantt_save_btn": "icon_save"
+  }
+};
+
+
+gantt.config.touch_drag = 500; //nearly immediate dnd
+gantt.config.touch = true;
+gantt.config.touch_feedback = true;
+
+
+gantt._touch_feedback = function(){
+  if(gantt.config.touch_feedback){
+    if(navigator.vibrate)
+      navigator.vibrate(1);
+  }
+};
+
+gantt._init_touch_events = function(){
+  if (this.config.touch != "force")
+    this.config.touch = this.config.touch &&
+       ((navigator.userAgent.indexOf("Mobile")!=-1)    ||
+        (navigator.userAgent.indexOf("iPad")!=-1)    ||
+        (navigator.userAgent.indexOf("Android")!=-1) ||
+        (navigator.userAgent.indexOf("Touch")!=-1));
+
+  if (this.config.touch){
+    if (window.navigator.msPointerEnabled){
+      this._touch_events(["MSPointerMove", "MSPointerDown", "MSPointerUp"], function(ev){
+        if (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE ) return null;
+        return ev;
+      }, function(ev){
+        return (!ev || ev.pointerType == ev.MSPOINTER_TYPE_MOUSE);
+      });
+    } else
+      this._touch_events(["touchmove", "touchstart", "touchend"], function(ev){
+        if (ev.touches && ev.touches.length > 1) return null;
+        if (ev.touches[0])
+          return {
+            target: ev.target,
+            pageX: ev.touches[0].pageX,
+            pageY: ev.touches[0].pageY,
+            clientX:ev.touches[0].clientX,
+            clientY:ev.touches[0].clientY
+          };
+        else
+          return ev;
+      }, function(){ return false; });
+  }
+};
+
+
+//we can't use native scrolling, as we need to sync momentum between different parts
+//so we will block native scroll and use the custom one
+//in future we can add custom momentum
+gantt._touch_events = function(names, accessor, ignore){
+  //webkit on android need to be handled separately
+  var dblclicktime = 0;
+  var action_mode = false;
+  var scroll_mode = false;
+  var dblclick_timer = 0;
+  var action_start = null;
+  var scroll_state;
+  var long_tap_timer = null;
+  var current_target = null;
+
+  //touch move
+  if (!this._gantt_touch_event_ready){
+    this._gantt_touch_event_ready = 1;
+    dhtmlxEvent(gantt.$container, names[0], function(e){
+      if (ignore(e)) return;
+
+      //ignore common and scrolling moves
+      if (!action_mode) return;
+
+      if (long_tap_timer) clearTimeout(long_tap_timer);
+
+      var source = accessor(e);
+      if (gantt._tasks_dnd.drag.id || gantt._tasks_dnd.drag.start_drag) {
+        gantt._tasks_dnd.on_mouse_move(source);
+        if (e.preventDefault)
+          e.preventDefault();
+        e.cancelBubble = true;
+        return false;
+      }
+      if (source && action_start){
+        var dx = action_start.pageX - source.pageX;
+        var dy = action_start.pageY - source.pageY;
+        if (!scroll_mode && (Math.abs(dx) > 5 || Math.abs(dy) > 5)){
+          gantt._touch_scroll_active = scroll_mode = true;
+          dblclicktime = 0;
+          scroll_state = gantt.getScrollState();
+        }
+
+        if (scroll_mode){
+          gantt.scrollTo(scroll_state.x + dx, scroll_state.y + dy);
+          var new_scroll_state = gantt.getScrollState();
+
+          if((scroll_state.x != new_scroll_state.x && dy > 2 * dx) ||
+            (scroll_state.y != new_scroll_state.y && dx > 2 * dy ))
+          {
+            return block_action(e);
+          }
+        }
+      }
+      return block_action(e);
+    });
+  }
+
+  //block touch context menu in IE10
+  dhtmlxEvent(this.$container, "contextmenu", function(e){
+    if (action_mode)
+      return block_action(e);
+  });
+
+  //touch start
+  dhtmlxEvent(this.$container, names[1], function(e){
+    if (ignore(e)) return;
+    if (e.touches && e.touches.length > 1){
+      action_mode = false;
+      return;
+    }
+
+    action_mode = true;
+    action_start = accessor(e);
+
+
+
+    //dbl-tap handling
+    if (action_start && dblclicktime){
+      var now = gantt.date.Date();
+      if ((now - dblclicktime) < 500 ){
+        gantt._on_dblclick(action_start);
+        block_action(e);
+      } else
+        dblclicktime = now;
+    } else {
+      dblclicktime = gantt.date.Date();
+    }
+
+    //long tap
+    long_tap_timer = setTimeout(function(){
+      var taskId = gantt.locate(action_start);
+      if(taskId && !gantt._locate_css(action_start, "gantt_link_control") &&  !gantt._locate_css(action_start, "gantt_grid_data")) {
+        gantt._tasks_dnd.on_mouse_down(action_start);
+        gantt._tasks_dnd._start_dnd(action_start);
+        gantt._touch_drag = true;
+        cloneTaskRendered(taskId);
+
+        gantt.refreshTask(taskId);
+
+        gantt._touch_feedback();
+      }
+
+      long_tap_timer = null;
+    }, gantt.config.touch_drag);
+  });
+
+  //touch end
+  dhtmlxEvent(this.$container, names[2], function(e){
+    if (ignore(e)) return;
+    if (long_tap_timer) clearTimeout(long_tap_timer);
+    gantt._touch_drag = false;
+    action_mode = false;
+    var source = accessor(e);
+    gantt._tasks_dnd.on_mouse_up(source);
+
+    if(current_target) {
+      gantt.refreshTask(gantt.locate(current_target));
+      current_target.parentNode.removeChild(current_target);
+      gantt._touch_feedback();
+    }
+
+    gantt._touch_scroll_active = action_mode = scroll_mode = false;
+    current_target = null;
+  });
+
+
+  //common helper, prevents event
+  function block_action(e){
+    if (e && e.preventDefault)
+      e.preventDefault();
+    (e||event).cancelBubble = true;
+    return false;
+  }
+
+  function cloneTaskRendered(taskId) {
+    var renders = gantt._task_area_pulls;
+    var task = gantt.getTask(taskId);
+    if(task && gantt.isTaskVisible(taskId)){
+      for(var i in renders) {
+        task = renders[i][taskId];
+        if(task && task.getAttribute("task_id") && task.getAttribute("task_id") == taskId) {
+          var copy = task.cloneNode(true);
+          current_target = task;
+          renders[i][taskId] = copy;
+          task.style.display="none";
+          copy.className += " gantt_drag_move ";
+          task.parentNode.appendChild(copy);
+          return copy;
+        }
+      }
+    }
+  }
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js
new file mode 100644
index 0000000000000000000000000000000000000000..56883743a360c77ddda1655965d6734821ca7a9c
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/dhtmlxgantt_marker.js
@@ -0,0 +1,127 @@
+/*
+@license
+
+dhtmlxGantt v.3.2.1 Stardard
+This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
+
+(c) Dinamenta, UAB.
+*/
+
+if(!gantt._markers)
+	gantt._markers = {};
+
+gantt.config.show_markers = true;
+
+gantt.attachEvent("onClear", function(){
+	gantt._markers = {};
+});
+
+gantt.attachEvent("onGanttReady", function(){
+	/*if(!gantt.$marker_area){  //   HOSEK
+		var markerArea=$(".gantt_marker_area");
+		if(markerArea.length===0){
+			markerArea = document.createElement("div");
+			markerArea.className = "gantt_marker_area";
+			gantt.$task_data.appendChild(markerArea);
+		}else{
+			markerArea=markerArea[0];
+		}
+		gantt.$marker_area = markerArea;
+	}*/    //  HOSEK
+
+	//gantt._markerRenderer = gantt._task_renderer("markers", render_marker, gantt.$marker_area, null);
+	gantt._markerRenderer = gantt._task_renderer({id:"markers",renderer: render_marker,container: gantt.$marker_area,filter: null});
+
+	function render_marker(marker){
+		if(!gantt.config.show_markers)
+			return false;
+
+		if(!marker.start_date)
+			return false;
+
+		var state = gantt.getState();
+		if(+marker.start_date > +state.max_date)
+			return;
+		if(+marker.end_date && +marker.end_date < +state.min_date || +marker.start_date < +state.min_date)
+			return;
+
+		var div = document.createElement("div");
+
+		div.setAttribute("marker_id", marker.id);
+
+		var css = "gantt_marker";
+		if(gantt.templates.marker_class)
+			css += " " + gantt.templates.marker_class(marker);
+
+		if(marker.css){
+			css += " " + marker.css;
+		}
+
+		if(marker.title){
+			div.title = marker.title;
+		}
+		div.className = css;
+
+		var start = gantt.posFromDate(marker.start_date);
+		div.style.left = start + "px";
+		div.style.height = Math.max(gantt._y_from_ind(gantt._order.length), 0) + "px";
+		if(marker.end_date){
+			var end = gantt.posFromDate(marker.end_date);
+			div.style.width = Math.max((end - start), 0) + "px";
+
+		}
+
+		if(marker.text){
+			div.innerHTML = "<div class='gantt_marker_content' >" + marker.text + "</div>";
+		}
+
+		return div;
+	}
+});
+
+
+gantt.attachEvent("onDataRender", function(){
+	gantt.renderMarkers();
+});
+
+gantt.getMarker = function(id){
+	if(!this._markers) return null;
+
+	return this._markers[id];
+};
+
+gantt.addMarker = function(marker){
+	marker.id = marker.id || dhtmlx.uid();
+
+	this._markers[marker.id] = marker;
+
+	return marker.id;
+};
+
+gantt.deleteMarker = function(id){
+	if(!this._markers || !this._markers[id])
+		return false;
+
+	delete this._markers[id];
+	return true;
+};
+gantt.updateMarker = function(id){
+	if(this._markerRenderer)
+		this._markerRenderer.render_item(id);
+};
+gantt.renderMarkers = function(){
+	if(!this._markers)
+		return false;
+
+	if(!this._markerRenderer)
+		return false;
+
+	var to_render = [];
+
+	for(var id in this._markers)
+		to_render.push(this._markers[id]);
+
+	this._markerRenderer.render_items(to_render);
+
+	return true;
+};
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js
new file mode 100644
index 0000000000000000000000000000000000000000..e198612b16a29537473894f43d466d04789f4474
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/easy_gantt.js
@@ -0,0 +1,9 @@
+/*
+ * = require_directory ./libs
+ * = require easy_gantt/utils
+ * = require easy_gantt/data
+ * = require easy_gantt/widget
+ * = require_directory .
+ * = stub easy_gantt/sample
+ * = stub easy_gantt/libs/moment
+*/
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..056e99ead02ae52c5c89ee51c238d2026ad6d6f2
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/gantt_widget.js
@@ -0,0 +1,574 @@
+/* gantt_widget.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+//#####################################################################
+ysy.view.Gantt = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttInitWidget";
+  ysy.log.message("widget Gantt created");
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Gantt, {
+  _updateChildren: function () {
+    if (this.children.length > 0) {
+      return;
+    }
+    var renderer = new ysy.view.GanttRender();
+    renderer.init(
+        ysy.data.limits,
+        ysy.settings.critical,
+        ysy.settings.zoom,
+        ysy.settings.sumRow
+    );
+    this.children.push(renderer);
+  },
+  _postInit: function () {
+    //gantt.initProjectMarker(this.model.start,this.model.end);
+  },
+  _repaintCore: function () {
+    if (this.$target === null) {
+      throw "Target is null for " + this.name;
+    }
+    if (!ysy.data.columns) {
+      //ysy.log.log("GanttInitWidget: columns are missing");
+      return true;
+    }
+    ysy.data.limits.setSilent("zoomDate", gantt.getShowDate() || moment());
+    ysy.view.initGantt();
+    this.addBaselineLayer();
+    gantt.init(this.$target); // REPAINT
+    ysy.log.debug("gantt.init()", "load");
+    this.tideFunctionality(); //   TIDE FUNCTIONALITY
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      child.$target = this.$target;  //   SET CHILD TARGET
+      child.repaint(true); //  CHILD REPAINT
+    }
+    if (!ysy.settings.controls.controls) {
+      $(".gantt_bars_area").addClass("no_task_controls");
+    }
+    dhtmlx.dragScroll();
+  },
+  addBaselineLayer: function () {
+  }
+});
+//##############################################################################
+ysy.view.GanttRender = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttRenderWidget";
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttRender, {
+  _updateChildren: function () {
+    if (!this.tasks) {
+      this.tasks = new ysy.view.GanttTasks();
+      this.tasks.init(
+          ysy.data.issues,
+          ysy.data.milestones,
+          ysy.data.projects,
+          ysy.settings.resource,
+          ysy.data.assignees,
+          ysy.settings.scheme
+      );
+      this.children.push(this.tasks);
+    }
+    if (!this.links) {
+      this.links = new ysy.view.GanttLinks();
+      this.links.init(
+          ysy.data.relations,
+          ysy.settings.resource
+      );
+      this.children.push(this.links);
+    }
+    if (!this.refresher) {
+      this.refresher = new ysy.view.GanttRefresher();
+      this.refresher.init(gantt);
+      this.children.push(this.refresher);
+    }
+    if (!this.sumRow && ysy.settings.sumRow.active) {
+      this.sumRow = new ysy.view.SumRow();
+      this.sumRow.init();
+      this.children.push(this.sumRow);
+    }
+  },
+  zoomTo: function (timespan) {
+    if (timespan === "month") {
+      $.extend(gantt.config, {
+        scale_unit: "month",
+        date_scale: "%M",
+        subscales: [
+          {unit: "year", step: 1, date: "%Y"}
+        ]
+      });
+    } else if (timespan === "week") {
+      $.extend(gantt.config, {
+        scale_unit: "week",
+        date_scale: "%W",
+        subscales: [
+          {unit: "month", step: 1, date: "%F %Y"}
+        ]
+      });
+    } else if (timespan === "day") {
+      $.extend(gantt.config, {
+        scale_unit: "day",
+        date_scale: "%d",
+        subscales: [
+          {unit: "month", step: 1, date: "%F %Y"}
+        ]
+      });
+    }
+    if (this.sumRow && ysy.settings.sumRow.active) {
+      gantt.config.subscales.push(this.sumRow.getSubScale(timespan));
+    }
+  },
+  _repaintCore: function () {
+    if (this.$target === null) {
+      throw "Target is null for " + this.name;
+    }
+    this.zoomTo(ysy.settings.zoom.zoom);
+    //this.addBaselineLayer();
+    //ysy.data.limits.setSilent("pos", gantt.getScrollState());
+    //var pos = ysy.data.limits.pos;
+    //if (pos)ysy.log.debug("scrollSave pos={x:" + pos.x + ",y:" + pos.y + "}", "scroll");
+    // gantt.render();
+    this.tideFunctionality(); //   TIDE FUNCTIONALITY
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      child.repaint(true); //  CHILD REPAINT
+    }
+
+  },
+  addBaselineLayer: function () {
+  }
+});
+//##############################################################################
+ysy.view.GanttTasks = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttTasksWidget";
+  ysy.view.ganttTasks = this;
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttTasks, {
+  _selectChildren: function () {
+    var issues = this.model.getArray();
+    var milestones = ysy.data.milestones.getArray();
+    var projects = ysy.data.projects.getArray();
+    return issues.concat(milestones,projects);
+  },
+  _updateChildren: function () {
+    var combined = this._selectChildren();
+    //var combined=issues.concat(milestones);
+    var i, model, task;
+    if (this.children.length === 0) {
+      for (i = 0; i < combined.length; i++) {
+        model = combined[i];
+        task = new ysy.view.GanttTask();
+        task.init(model);
+        task.parent = this;
+        task.order = i + 1;
+        this.children.push(task);
+      }
+    } else {
+      var narr = [];
+      var temp = {};
+      for (i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        temp[child.model.getID()] = child;
+      }
+      for (i = 0; i < combined.length; i++) {
+        model = combined[i];
+        task = temp[model.getID()];
+        if (!task) {
+          task = new ysy.view.GanttTask();
+          task.init(model);
+          task.parent = this;
+        } else {
+          delete temp[model.getID()];
+        }
+        task.order = i + 1;
+        narr.push(task);
+      }
+      for (var key in temp) {
+        if (temp.hasOwnProperty(key)) {
+          temp[key].destroy(true);
+        }
+      }
+      //var narr = [];
+      this.children = narr;
+    }
+    ysy.log.log("-- " + this.children.length + " Children updated in " + this.name);
+  },
+  _repaintCore: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      //this.setChildTarget(child, i); //   SET CHILD TARGET
+      child.repaint(true); //  CHILD REPAINT
+    }
+    gantt._sync_links();
+    gantt.reconstructTree();
+    gantt.sort(gantt._sort && gantt._sort.criteria);
+    //window.initInlineEditForContainer($("#gantt_cont")); // TODO
+  }
+});
+//##############################################################################
+ysy.view.GanttTask = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttTaskWidget";
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttTask, {
+  _repaintCore: function () {
+    var issue = this.model;
+    var gantt_issue = this._constructDhData(issue);
+    if (gantt._pull[gantt_issue.id]) {
+      if (this.dhdata.parent != gantt_issue.realParent) {
+        gantt.silentMoveTask(this.dhdata, gantt_issue.realParent);
+        delete gantt_issue.realParent;
+      }
+      $.extend(this.dhdata, gantt_issue);
+      gantt.refreshTask(gantt_issue.id);
+    } else {
+      this.destroyDhData();
+      this.dhdata = gantt_issue;
+      ysy.log.debug("addTaskNoDraw()", "load");
+      gantt.addTaskFaster(gantt_issue);
+    }
+    //window.initInlineEditForContainer($("#gantt_cont"));  // TODO
+  },
+  destroyDhData: function (silent) {
+    if (!this.dhdata) return;
+    this.dhdata.deleted = true;
+    if (gantt.isTaskExists(this.dhdata.id)) {
+      gantt._deleteTask(this.dhdata.id, silent);
+    }
+    this.dhdata = null;
+    ysy.log.debug("Destroy for " + this.name, "widget_destroy");
+  },
+  destroy: function (silent) {
+    this.destroyDhData(silent);
+    this.deleted = true;
+  },
+  _constructDhData: function (issue) {
+    // var parent = issue.getParent() || 0;
+    var gantt_issue = {
+      id: issue.getID(),
+      real_id: issue.id,
+      text: issue.name,
+      css: (issue.css || '') + (issue.closed ? ' closed' : ''),
+      //model:issue,
+      widget: this,
+      order: this.order,
+      open: issue.isOpened(),
+      start_date: issue.start_date ? moment(issue.start_date) : undefined,
+      $ignore: issue._ignore || false,
+      columns: issue.columns,
+      readonly: !issue.isEditable(),
+      realParent: issue.getParent() || 0,
+      type: issue.ganttType
+    };
+    gantt_issue.$open = gantt_issue.open;
+    if (issue.isProject) {
+      //  -- PROJECT --
+      $.extend(gantt_issue, {
+        progress: issue.getProgress(),
+        maximal_start: issue.start_date,
+        minimal_end: issue.end_date,
+        start_date: issue.start_date,
+        end_date: issue.end_date
+      });
+    } else if (issue.isIssue) {
+      //   -- ISSUE --
+      var end_date = moment(issue._end_date);
+      //if (!issue._end_date.isValid()) {
+      //  console.error("_end_date is not valid");
+      //  end_date = moment(issue._start_date).add(1, "d");
+      //}
+      end_date._isEndDate = true;
+      $.extend(gantt_issue, {
+        start_date: moment(issue._start_date),
+        end_date: end_date,
+        progress: (issue.done_ratio || 0) / 100.0,
+        //duration: issue.end_date.diff(issue.start_date, 'days'),
+        assigned_to: issue.assigned_to,
+        estimated: issue.estimated_hours || 0,
+        soonest_start: issue.soonest_start,
+        latest_due: issue.latest_due
+      });
+    } else if (issue.milestone) {
+      //  -- MILESTONE --
+      gantt_issue.end_date = moment(gantt_issue.start_date);
+      gantt_issue.end_date._isEndDate = true;
+    } else {
+      //  -- ASSIGNEE --
+    }
+    ysy.proManager.fireEvent("extendGanttTask", issue, gantt_issue);
+    return gantt_issue;
+  },
+  update: function (item, keys) {
+    var obj;
+    if (item.type === "milestone") {
+      this.model.set({
+        name: item.text,
+        start_date: moment(item.start_date)
+      });
+    } else if (item.type === "project") {
+      obj = {
+        start_date: moment(item.start_date),
+        end_date: moment(item.end_date),
+        _shift: item.start_date.diff(this.model.start_date, "days") + (this.model._shift || 0)
+      };
+      obj.end_date._isEndDate = true;
+      this.model.set(obj);
+    } else {
+      var fullObj = {
+        name: item.text,
+        //assignedto: item.assignee,
+        estimated_hours: item.estimated,
+        done_ratio: Math.round(item.progress * 10) * 10,
+        start_date: moment(item.start_date),
+        end_date: moment(item.end_date)
+      };
+      fullObj.end_date._isEndDate = true;
+      if (item._parentChanged) {
+        $.extend(fullObj, this._constructParentUpdate(item.parent));
+        item._parentChanged = false;
+      }
+      obj = fullObj;
+      if (keys !== undefined) {
+        obj = {};
+        for (var i = 0; i < keys.length; i++) {
+          var key = keys[i];
+          if (key === "fixed_version_id") {
+            if (typeof item.parent === "string") {
+              obj.fixed_version_id = parseInt(item.parent.substring(1));
+            } else {
+              obj.parent = item.parent;  // TODO subtask žížaly musí mít parent nebo něco
+            }
+            //this.parent.requestRepaint();
+          } else {
+            obj[key] = fullObj[key];
+          }
+        }
+      }
+      this.model.set(obj);
+    }
+    this.requestRepaint();
+  },
+  _constructParentUpdate: function (parentId) {
+    if (typeof parentId !== "string") {
+      var parent = gantt._pull[parentId];
+      if (!parent) return {};
+      var parentModel = parent.widget.model;
+      if (!parentModel) return {};
+      if (parentModel.fixed_version_id) {
+        return {
+          parent_issue_id: parentId,
+          fixed_version_id: parentModel.fixed_version_id,
+          project_id: parentModel.project_id
+        };
+      } else {
+        return {parent_issue_id: parentId, project_id: parentModel.project_id};
+      }
+    } else if (ysy.main.startsWith(parentId, "p")) {
+      return {
+        parent_issue_id: null, project_id: parseInt(parentId.substring(1)), fixed_version_id: null
+      };
+    } else if (ysy.main.startsWith(parentId, "m")) {
+      return {
+        parent_issue_id: null, fixed_version_id: parseInt(parentId.substring(1))
+      };
+    } else if (parentId === "empty") {
+      return {
+        parent_issue_id: null, project_id: ysy.settings.projectID, fixed_version_id: null
+      };
+    } else return null;
+  }
+});
+//##############################################################################
+ysy.view.GanttLinks = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttLinksWidget";
+  ysy.view.ganttLinks = this;
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttLinks, {
+  _updateChildren: function () {
+    var rela, link, i;
+    var model = this.model.getArray();
+    if (this.children.length === 0) {
+      for (i = 0; i < model.length; i++) {
+        rela = model[i];
+        if (rela.isHalfLink()) continue;
+        link = new ysy.view.GanttLink();
+        link.init(rela);
+        this.children.push(link);
+      }
+    } else {
+      var narr = [];
+      var temp = {};
+      for (i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        temp[child.model.id] = child;
+      }
+      for (i = 0; i < model.length; i++) {
+        rela = model[i];
+        if (rela.isHalfLink()) continue;
+        link = temp[rela.id];
+        if (!link) {
+          link = new ysy.view.GanttLink();
+          link.init(rela);
+        } else {
+          delete temp[rela.id];
+        }
+        narr.push(link);
+      }
+      for (var key in temp) {
+        if (temp.hasOwnProperty(key)) {
+          temp[key].destroy(true);
+        }
+      }
+      this.children = narr;
+    }
+    ysy.log.log("-- " + this.children.length + " Children updated in " + this.name);
+  },
+  _repaintCore: function () {
+    //this._updateTaskInGantt();
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      //this.setChildTarget(child, i); //   SET CHILD TARGET
+      child.repaint(true); //  CHILD REPAINT
+    }
+    //gantt.refreshData();
+  }
+});
+//##############################################################################
+ysy.view.GanttLink = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttLinkWidget";
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttLink, {
+  _repaintCore: function () {
+    var rela = this.model;
+    var link = this._constructDhData(rela);
+    if (gantt._lpull[link.id]) {
+      $.extend(this.dhdata, link);
+      gantt.refreshLink(link.id);
+      gantt.refreshTask(link.source);
+      gantt.refreshTask(link.target);
+    } else {
+      this.dhdata = link;
+      gantt.addLink(link);
+    }
+    //gantt.sort();
+  },
+  destroy: function (silent) {
+    if (this.dhdata) {
+      this.dhdata.deleted = true;
+      if (gantt.isLinkExists(this.model.id)) {
+        gantt._deleteLink(this.model.id, silent);
+      }
+      this.dhdata = null;
+      ysy.log.debug("Destroy for " + this.name, "widget_destroy");
+    }
+    this.deleted = true;
+  },
+  _constructDhData: function (model) {
+    return {
+      id: model.id,
+      source: model.source_id,
+      target: model.target_id,
+      type: model.isSimple ? "start_to_start" : model.type,
+      isSimple: model.isSimple,
+      unlocked: model.unlocked,
+      delay: model.delay,
+      readonly: !model.isEditable(),
+      widget: this
+    };
+  },
+  update: function (item) {
+    ysy.history.openBrack();
+    this.model.set({
+      // name: item.text,
+      // source_id: item.source,
+      // target_id: item.target,
+      type: this.model.type || item.type,
+      delay: item.delay
+    });
+    var allRequests = {};
+    this.model.sendMoveRequest(allRequests);
+    gantt.applyMoveRequests(allRequests);
+    ysy.history.closeBrack();
+  }
+
+});
+//##############################################################################
+ysy.view.GanttRefresher = function () {
+  ysy.view.Widget.call(this);
+  this.name = "GanttRefresherWidget";
+  this.all = false;
+  this.data = false;
+  this.tasks = [];
+  this.links = [];
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.GanttRefresher, {
+  _postInit: function () {
+    this.model.refresher = this;
+  },
+  _register: function () {
+  },
+  renderAll: function () {
+    this.all = true;
+    this.requestRepaint();
+  },
+  renderData: function () {
+    this.data = true;
+    this.requestRepaint();
+  },
+  refreshTask: function (taskId) {
+    for (var i = 0; i < this.tasks.length; i++) {
+      if (this.tasks[i] == taskId) return;
+    }
+    this.tasks.push(taskId);
+    this.requestRepaint();
+  },
+  refreshLink: function (linkId) {
+    for (var i = 0; i < this.links.length; i++) {
+      if (this.links[i] == linkId) return;
+    }
+    this.links.push(linkId);
+    this.requestRepaint();
+  },
+  _repaintCore: function () {
+    if (this.all) {
+      ysy.log.debug("---- Refresher: _renderAll", "refresher");
+      var visibleDate = gantt.getShowDate();
+      if (!visibleDate) {
+        visibleDate = ysy.data.limits.zoomDate;
+      }
+      gantt._backgroundRenderer.forceRender = false;
+      this.model._render();
+      gantt._backgroundRenderer.forceRender = true;
+      //ysy.log.debug(moment(visibleDate).toISOString(), "refresher");
+      gantt.showDate(visibleDate);
+      delete gantt._backgroundRenderer.forceRender;
+      ysy.view.affix.requestRepaint();
+    } else if (this.data) {
+      ysy.log.debug("---- Refresher: _renderData", "refresher");
+      this.model._render_data();
+      if (this.all) return true;
+    } else {
+      ysy.log.debug("---- Refresher: _render for " + this.tasks.length + " tasks and " + this.links.length + " links", "refresher");
+      for (var i = 0; i < this.tasks.length; i++) {
+        var taskId = this.tasks[i];
+        this.model._refreshTask(taskId);
+        if (this.all || this.data) return true;
+      }
+      for (i = 0; i < this.links.length; i++) {
+        var linkId = this.links[i];
+        this.model._refreshLink(linkId);
+        if (this.all || this.data) return true;
+      }
+    }
+    this.all = false;
+    this.data = false;
+    this.tasks = [];
+    this.links = [];
+  }
+
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/history.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/history.js
new file mode 100644
index 0000000000000000000000000000000000000000..27341902b7e94428720ba83e967a43033fc9c9f4
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/history.js
@@ -0,0 +1,130 @@
+/* history.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.history = ysy.history || {};
+$.extend(ysy.history, {
+  _name: "History",
+  _inbrack: 0,
+  stack: [],
+  _binded: false,
+  add: function (diff, ctx) {
+    this.stack.push({diff: diff, ctx: ctx});
+    this._fireChanges(this, "add");
+    this._bind();
+  },
+  revert: function (who) {
+    var first = true;
+    if (this.stack.length === 0) return;
+    while (this._inbrack || first) {
+      first = false;
+      var rev = this.stack.pop();
+      if (!rev) return;
+      if (typeof rev === "string") {
+        if (rev === "close") {
+          this._inbrack++;
+          continue;
+        } else if (rev === "open") {
+          this._inbrack--;
+          break;
+        }
+      }
+      this._revertOne(rev);
+    }
+    //console.log("Revert by "+(typeof rev.diff));
+    if (this.stack.length === 0) {
+      this._unbind();
+    }
+    this._fireChanges(who, "revert");
+  },
+  _revertOne: function (rev) {
+    if (typeof rev.diff === "array") {
+      rev.ctx.array = rev.diff;
+      rev.ctx._fireChanges(this, "revert");
+    } else if (typeof rev.diff === "function") {
+      $.proxy(rev.diff, rev.ctx)();
+      rev.ctx._fireChanges(this, "revert");
+    } else {
+      //rev.ctx.set(rev.diff,true);
+      $.extend(rev.ctx, rev.diff);
+      rev.ctx._fireChanges(this, "revert");
+    }
+
+  },
+  clear: function (who) {
+    if (this.stack.length > 0) {
+      this._unbind();
+    }
+    this.stack = [];
+    this._inbrack = 0;
+    this._fireChanges(who, "clear");
+  },
+  openBrack: function () {
+    if (this._inbrack) {
+
+    } else {
+      this.stack.push("open");
+    }
+    this._inbrack++;
+  },
+  closeBrack: function () {
+    if (this._inbrack === 0) {
+      ysy.log.error("History bracket is not opened");
+    }
+    if (this._inbrack === 1) {
+      if (this.stack[this.stack.length - 1] === "open") {
+        this.stack.pop();
+      } else {
+        this.stack.push("close");
+      }
+    }
+    this._inbrack--;
+  },
+  _bind: function () {
+    if (this._binded)return;
+    $(window).bind('beforeunload', function (e) {
+      var message = "Some changes are not saved!";
+      e.returnValue = message;
+      return message;
+    });
+  },
+  _unbind: function () {
+    $(window).unbind('beforeunload');
+    this._binded = false;
+  },
+  _onChange: [],
+  register: function (func, ctx) {
+    this._onChange.push({func: func, ctx: ctx});
+  },
+  inBracket: function () {
+    return this._inbrack > 0
+  },
+  isEmpty: function () {
+    return !this.stack.length;
+  },
+  _fireChanges: function (who, reason) {
+    if (who) {
+      var rest = "";
+      if (reason) {
+        rest = " because of " + reason;
+      }
+      ysy.log.log("* " + who._name + " ordered repaint on " + this._name + "" + rest);
+
+    }
+    if (this._onChange.length > 0) {
+      ysy.log.log("- " + this._name + " onChange fired for " + this._onChange.length + " widgets");
+    } else {
+      ysy.log.log("- no changes for " + this._name);
+    }
+    for (var i = 0; i < this._onChange.length; i++) {
+      var ctx = this._onChange[i].ctx;
+      if (!ctx || ctx.deleted) {
+        this._onChange.splice(i, 1);
+        continue;
+      }
+      //this.onChangeNew[i].func();
+      ysy.log.log("-- changes to " + ctx.name + " widget");
+      //console.log(ctx);
+      $.proxy(this._onChange[i].func, ctx)();
+    }
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/global_gantt.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/global_gantt.js
new file mode 100644
index 0000000000000000000000000000000000000000..bdddeea14d4d399956d89f3c643d1153d7e979aa
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/global_gantt.js
@@ -0,0 +1,8 @@
+describeExtra("global_gantt", function () {
+  describe("FREE Global gantt", function () {
+    it("should fail anywhere but global gantt", function () {
+      expect(ysy.settings.global).toBe(true);
+      expect(ysy.settings.isGantt).toBe(true);
+    });
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..636bad7cdfaac114717ca3ea4f64a5c0484c839f
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test.js
@@ -0,0 +1,90 @@
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.test = {
+  jasmineStarted: false,
+  startCounter: 0,
+  beatCallbacks: [],
+  extraTestNames: [],
+  extraTestFunctions: [],
+  patch: function () {
+    ysy.view.onRepaint.push($.proxy(this.beat, this));
+    this.loadExtraTests();
+  },
+  beat: function () {
+    if (!this.jasmineStarted) {
+      if (ysy.data.loader.loaded) {
+        if (this.startCounter++ > 3) {
+          this.jasmineStarted = true;
+          this.jasmineStart();
+        }
+      }
+    }
+    if (this.beatCallbacks.length) {
+      var newCallbacks = [];
+      for (var i = 0; i < this.beatCallbacks.length; i++) {
+        var callPack = this.beatCallbacks[i];
+        if (callPack.rounds === 1) {
+          callPack.callback();
+        } else {
+          callPack.rounds--;
+          newCallbacks.push(callPack);
+        }
+      }
+      this.beatCallbacks = newCallbacks;
+    }
+  },
+  fewBeatsAfter: function (callback, count) {
+    if (count === undefined) count = 2;
+    this.beatCallbacks.push({callback: callback, rounds: count});
+  },
+  loadExtraTests: function () {
+    var self = this;
+    describe("(EXTRA)", function () {
+      for (var i = 0; i < self.extraTestFunctions.length; i++) {
+        self.extraTestFunctions[i]();
+      }
+    });
+  },
+  parseResult: function () {
+    var specs = window.jsApiReporter.specs();
+    var shortReport = "";
+    var report = "";
+    var allPassed = true;
+    var result = "";
+    for (var i = 0; i < specs.length; i++) {
+      var spec = specs[i];
+      if (spec.status === "passed") {
+        shortReport += ".";
+      } else {
+        allPassed = false;
+        shortReport += "X";
+        report += "__TEST " + spec.fullName + "______\n";
+        for (var j = 0; j < spec.failedExpectations.length; j++) {
+          var fail = spec.failedExpectations[j];
+          var split = fail.stack.split("\n");
+          result+=window.location+"\n";
+          report += "   " + fail.message + "\n";
+          for (var k = 1; k < split.length; k++) {
+            if (split[k].indexOf("/jasmine_lib/") > -1) break;
+            report += split[k] + "\n";
+          }
+        }
+      }
+    }
+    if (allPassed) {
+      return "success";
+    }
+    result += " RESULTS: " + shortReport + "\n" + report;
+    $("#content").text(result.replace("\n","<br>"));
+    return result;
+  }
+};
+window.describeExtra = function (file, func) {
+  if (file.indexOf("/") === -1) {
+    file = "easy_gantt/" + file
+  }
+  ysy.pro.test.extraTestNames.push(file);
+  ysy.pro.test.extraTestFunctions.push(function () {
+    describe(file, func);
+  });
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test_old.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test_old.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a91562805803ae7f9e59f2e765b3bee851cb31b
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/helpers/test_old.js
@@ -0,0 +1,160 @@
+window.ysy = window.ysy || {};
+ysy.counter = 0;
+ysy.test = {
+  run: function (date) {
+    //this.test7();
+    var predate = moment(date || "2016-02-26");
+    var pos = gantt.posFromDate(predate);
+    var postdate = gantt.dateFromPos2(pos);
+    console.log("pre: " + predate.toISOString() + " post: " + postdate.toISOString());
+    console.log("pre: " + (predate.valueOf() - postdate.valueOf()));
+    //console.log("posFromDate: " + gantt.posFromDate(predate) + " posFromDate2: " + gantt.posFromDate2(predate));
+  },
+  test8: function () {
+    ysy.data.saver.openReloadModal(["error"]);
+  },
+  test7: function () {
+    ysy.data.critical.construct();
+  },
+  test6: function () {
+    window.initInlineEditForContainer($("#gantt_cont"));
+  },
+  test5: function () {
+  },
+  test1: function () {
+    ysy.data.loader.load();
+    gantt._unset_sizes();
+  },
+  test2: function () {
+    var issue0 = ysy.data.issues.get(0);
+    issue0.set({
+      start_date: moment(issue0.start_date).add(4, "days"),
+      end_date: moment(issue0.end_date).add(4, "days")
+    });
+    var rel = new ysy.data.Relation();
+    rel.init({delay: 2, id: 150, source_id: 489, target_id: 490, type: "precedes"});
+    ysy.data.relations.push(rel);
+    ysy.history.revert();
+    ysy.history.revert();
+    issue0 = ysy.data.issues.get(0);
+    issue0.set({
+      start_date: moment(issue0.start_date).add(4, "days"),
+      end_date: moment(issue0.end_date).add(4, "days")
+    });
+    rel = new ysy.data.Relation();
+    rel.init({delay: 2, id: 150, source_id: 489, target_id: 490, type: "precedes"});
+    ysy.history.revert();
+    ysy.history.revert();
+    if (!ysy.history.isEmpty()) {
+      alert("Test failed 1");
+    }
+  },
+  test3: function () {
+    dhtmlx.message("Warning message", "warning", -1);
+    //dhtmlx.message("Error message","error",-1);
+    //dhtmlx.message("Success message","notice",-1);
+    //dhtmlx.message("Confirm message","confirm");
+
+  },
+  test4: function () {
+    var projects = ysy.data.projects;
+    var project = projects.get(0);
+    /*var allid=[];
+     for(var i=0;i<projects.size();i++){
+     allid.push(projects.get(i).id);
+     }
+     allid.sort();
+     console.log("("+allid.join(",")+")");*/
+    //project.getIssues().size();
+    var issues = project.getIssues();
+    /*issues.register(function(){
+     //console.log(issues.size());
+     $.each(issues.array,function(index,issue){
+     //issue.getRelations();
+     issue.register(function(){
+     //console.log("Issue "+issue.id+" má ");
+     //console.log(issue.getRelations());
+     ysy.counter++;
+     console.log("Counter: "+ysy.counter);
+
+     },this);
+     });
+     },this);*/
+    console.log("Test 2");
+    ysy.data.loader.createIssueFromTask({
+      start_date: moment("2015-06-25"),
+      end_date: moment("2015-06-30"),
+      id: 25666,
+      text: "Ahoj",
+      estimated_hours: "253"
+    });
+    //return;
+    setTimeout(function () {
+      console.log("test1a");
+      issues.get(0).set({start: moment("2015-06-25"), end: moment("2015-06-30")});
+    }, 1000);
+    setTimeout(function () {
+      console.log("test1b");
+      var rel = new ysy.data.Relation();
+      rel.init({delay: 0, id: 150, issue_from_id: 489, issue_to_id: 490, type: "precedes"});
+      project.relations.push(rel, project.relations);
+    }, 2000);
+    /*for(var i=0;i<projects.size();i++){
+     projects.getByID(i).getIssues().size();
+     }*/
+  }
+};
+/*ysy.view.demo_tasks = {
+ data: [
+ {"id": 1, "text": "Office itinerancy", "type": gantt.config.types.project, "order": "10", progress: 0.4, open: false},
+ {"id": 2, "text": "Office facing", "type": gantt.config.types.project, "start_date": "02-04-2013", "duration": "8", "order": "10", progress: 0.6, "parent": "1", open: true},
+ {"id": 3, "text": "Furniture installation", "type": gantt.config.types.project, "start_date": "11-04-2013", "duration": "8", "order": "20", "parent": "1", progress: 0.6, open: true},
+ {"id": 4, "text": "The employee relocation", "type": gantt.config.types.project, "start_date": "13-04-2013", "duration": "6", "order": "30", "parent": "1", progress: 0.5, open: true},
+ {"id": 5, "text": "Interior office", "start_date": "02-04-2013", "duration": "7", "order": "3", "parent": "2", progress: 0.6, open: true},
+ {"id": 6, "text": "Air conditioners check", "start_date": "03-04-2013", "duration": "7", "order": "3", "parent": "2", progress: 0.6, open: true},
+ {"id": 7, "text": "Workplaces preparation", "start_date": "11-04-2013", "duration": "8", "order": "3", "parent": "3", progress: 0.6, open: true},
+ {"id": 8, "text": "Preparing workplaces", "start_date": "14-04-2013", "duration": "5", "order": "3", "parent": "4", progress: 0.5, open: true},
+ {"id": 9, "text": "Workplaces importation", "start_date": "14-04-2013", "duration": "4", "order": "3", "parent": "4", progress: 0.5, open: true},
+ {"id": 10, "text": "Workplaces exportation", "start_date": "14-04-2013", "duration": "3", "order": "3", "parent": "4", progress: 0.5, open: true},
+ {"id": 11, "text": "Product launch", "type": gantt.config.types.project, "order": "5", progress: 0.6, open: true},
+ {"id": 12, "text": "Perform Initial testing", "start_date": "03-04-2013", "duration": "5", "order": "3", "parent": "11", progress: 1, open: true},
+ {"id": 13, "text": "Development", "type": gantt.config.types.project, "start_date": "02-04-2013", "duration": "7", "order": "3", "parent": "11", progress: 0.5, open: true},
+ {"id": 14, "text": "Analysis", "start_date": "02-04-2013", "duration": "6", "order": "3", "parent": "11", progress: 0.8, open: true},
+ {"id": 15, "text": "Design", "type": gantt.config.types.project, "start_date": "02-04-2013", "duration": "5", "order": "3", "parent": "11", progress: 0.2, open: false},
+ {"id": 16, "text": "Documentation creation", "start_date": "02-04-2013", "duration": "7", "order": "3", "parent": "11", progress: 0, open: true},
+ {"id": 17, "text": "Develop System", "start_date": "03-04-2013", "duration": "2", "order": "3", "parent": "13", progress: 1, open: true},
+ {"id": 25, "text": "Beta Release", "start_date": "06-04-2013", "order": "3", "type": gantt.config.types.milestone, "parent": "13", progress: 0, open: true},
+ {"id": 18, "text": "Integrate System", "start_date": "08-04-2013", "duration": "2", "order": "3", "parent": "13", progress: 0.8, open: true},
+ {"id": 19, "text": "Test", "start_date": "10-04-2013", "duration": "4", "order": "3", "parent": "13", progress: 0.2, open: true},
+ {"id": 20, "text": "Marketing", "start_date": "10-04-2013", "duration": "4", "order": "3", "parent": "13", progress: 0, open: true},
+ {"id": 21, "text": "Design database", "start_date": "03-04-2013", "duration": "4", "order": "3", "parent": "15", progress: 0.5, open: true},
+ {"id": 22, "text": "Software design", "start_date": "03-04-2013", "duration": "4", "order": "3", "parent": "15", progress: 0.1, open: true},
+ {"id": 23, "text": "Interface setup", "start_date": "03-04-2013", "duration": "5", "order": "3", "parent": "15", progress: 0, open: true},
+ {"id": 24, "text": "Release v1.0", "start_date": "15-04-2013", "order": "3", "type": gantt.config.types.milestone, "parent": "11", progress: 0, open: true}
+ ],
+ links: [
+ {id: "1", source: "1", target: "2", type: "1"},
+ {id: "2", source: "2", target: "3", type: "0"},
+ {id: "3", source: "3", target: "4", type: "0"},
+ {id: "4", source: "2", target: "5", type: "2"},
+ {id: "5", source: "2", target: "6", type: "2"},
+ {id: "6", source: "3", target: "7", type: "2"},
+ {id: "7", source: "4", target: "8", type: "2"},
+ {id: "8", source: "4", target: "9", type: "2"},
+ {id: "9", source: "4", target: "10", type: "2"},
+ {id: "10", source: "11", target: "12", type: "1"},
+ {id: "11", source: "11", target: "13", type: "1"},
+ {id: "12", source: "11", target: "14", type: "1"},
+ {id: "13", source: "11", target: "15", type: "1"},
+ {id: "14", source: "11", target: "16", type: "1"},
+ {id: "15", source: "13", target: "17", type: "1"},
+ {id: "16", source: "17", target: "25", type: "0"},
+ {id: "23", source: "25", target: "18", type: "0"},
+ {id: "17", source: "18", target: "19", type: "0"},
+ {id: "18", source: "19", target: "20", type: "0"},
+ {id: "19", source: "15", target: "21", type: "2"},
+ {id: "20", source: "15", target: "22", type: "2"},
+ {id: "21", source: "15", target: "23", type: "2"},
+ {id: "22", source: "13", target: "24", type: "0"}
+ ]
+ };*/
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/history.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/history.js
new file mode 100644
index 0000000000000000000000000000000000000000..61d9fcd6f7a6837594fdf0261096def713c6d056
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/history.js
@@ -0,0 +1,31 @@
+describe("History",function(){
+  var issue;
+  var history;
+  beforeAll(function(){
+    history = ysy.history;
+    issue = new ysy.data.Issue();
+    issue.init({
+      id:12,
+      start_date:"2016-05-25",
+      due_date:"2016-05-29",
+      estimated: 25,
+      subject:"task H",
+      columns:[]
+    });
+  });
+  it("should revert estimated change",function(){
+    issue.set("estimated",60);
+    expect(issue.estimated).toBe(60);
+    expect(issue._changed).toBe(true);
+    history.revert();
+    expect(issue.estimated).toBe(25);
+    expect(issue._changed).toBe(false);
+  });
+  it("should revert start_date change",function(){
+    issue.set({start_date:moment("2016-05-15")});
+    expect(issue.start_date.isSame(moment("2016-05-15"))).toBe(true);
+    history.revert();
+    expect(issue.start_date.isSame(moment("2016-05-25"))).toBe(true);
+  });
+
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/boot.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/boot.js
new file mode 100644
index 0000000000000000000000000000000000000000..7f8bcb332b3009781a88cbe0f097e870e6cac042
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/boot.js
@@ -0,0 +1,131 @@
+/**
+ Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
+
+ If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
+
+ The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
+
+ [jasmine-gem]: http://github.com/pivotal/jasmine-gem
+ */
+
+(function() {
+
+  /**
+   * ## Require &amp; Instantiate
+   *
+   * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
+   */
+  window.jasmine = jasmineRequire.core(jasmineRequire);
+
+  /**
+   * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
+   */
+  jasmineRequire.html(jasmine);
+
+  /**
+   * Create the Jasmine environment. This is used to run all specs in a project.
+   */
+  var env = jasmine.getEnv();
+
+  /**
+   * ## The Global Interface
+   *
+   * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
+   */
+  var jasmineInterface = jasmineRequire.interface(jasmine, env);
+
+  /**
+   * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
+   */
+  extend(window, jasmineInterface);
+
+  /**
+   * ## Runner Parameters
+   *
+   * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
+   */
+
+  var queryString = new jasmine.QueryString({
+    getWindowLocation: function() { return window.location; }
+  });
+
+  var catchingExceptions = queryString.getParam("catch");
+  env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
+
+  var throwingExpectationFailures = queryString.getParam("throwFailures");
+  env.throwOnExpectationFailure(throwingExpectationFailures);
+
+  var random = queryString.getParam("random");
+  env.randomizeTests(random);
+
+  var seed = queryString.getParam("seed");
+  if (seed) {
+    env.seed(seed);
+  }
+
+  /**
+   * ## Reporters
+   * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
+   */
+  var htmlReporter = new jasmine.HtmlReporter({
+    env: env,
+    onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
+    onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
+    onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); },
+    addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
+    getContainer: function() { return document.body; },
+    createElement: function() { return document.createElement.apply(document, arguments); },
+    createTextNode: function() { return document.createTextNode.apply(document, arguments); },
+    timer: new jasmine.Timer()
+  });
+
+  /**
+   * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results  from JavaScript.
+   */
+  env.addReporter(jasmineInterface.jsApiReporter);
+  env.addReporter(htmlReporter);
+
+  /**
+   * Filter which specs will be run by matching the start of the full name against the `spec` query param.
+   */
+  var specFilter = new jasmine.HtmlSpecFilter({
+    filterString: function() { return queryString.getParam("spec"); }
+  });
+
+  env.specFilter = function(spec) {
+    return specFilter.matches(spec.getFullName());
+  };
+
+  /**
+   * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
+   */
+  window.setTimeout = window.setTimeout;
+  window.setInterval = window.setInterval;
+  window.clearTimeout = window.clearTimeout;
+  window.clearInterval = window.clearInterval;
+
+  /**
+   * ## Execution
+   *
+   * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
+   */
+  var currentWindowOnload = window.onload;
+
+  ysy.pro.test.jasmineStart = function() {
+  // window.onload = function() {   // HOSEKP
+    // if (currentWindowOnload) {
+    //   currentWindowOnload();
+    // }                            // HOSEKP
+    htmlReporter.initialize();
+    env.execute();
+  };
+
+  /**
+   * Helper function for readability above.
+   */
+  function extend(destination, source) {
+    for (var property in source) destination[property] = source[property];
+    return destination;
+  }
+
+}());
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine-html.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine-html.js
new file mode 100644
index 0000000000000000000000000000000000000000..da23532e9b661f71eb9b667f52a78e0cfa92e583
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine-html.js
@@ -0,0 +1,473 @@
+/*
+Copyright (c) 2008-2015 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+jasmineRequire.html = function(j$) {
+  j$.ResultsNode = jasmineRequire.ResultsNode();
+  j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
+  j$.QueryString = jasmineRequire.QueryString();
+  j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
+};
+
+jasmineRequire.HtmlReporter = function(j$) {
+
+  var noopTimer = {
+    start: function() {},
+    elapsed: function() { return 0; }
+  };
+
+  function HtmlReporter(options) {
+    var env = options.env || {},
+      getContainer = options.getContainer,
+      createElement = options.createElement,
+      createTextNode = options.createTextNode,
+      onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
+      onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
+      onRandomClick = options.onRandomClick || function() {},
+      addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
+      timer = options.timer || noopTimer,
+      results = [],
+      specsExecuted = 0,
+      failureCount = 0,
+      pendingSpecCount = 0,
+      htmlReporterMain,
+      symbols,
+      failedSuites = [];
+
+    this.initialize = function() {
+      clearPrior();
+      htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
+        createDom('div', {className: 'jasmine-banner'},
+          createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
+          createDom('span', {className: 'jasmine-version'}, j$.version)
+        ),
+        createDom('ul', {className: 'jasmine-symbol-summary'}),
+        createDom('div', {className: 'jasmine-alert'}),
+        createDom('div', {className: 'jasmine-results'},
+          createDom('div', {className: 'jasmine-failures'})
+        )
+      );
+      getContainer().appendChild(htmlReporterMain);
+    };
+
+    var totalSpecsDefined;
+    this.jasmineStarted = function(options) {
+      totalSpecsDefined = options.totalSpecsDefined || 0;
+      timer.start();
+    };
+
+    var summary = createDom('div', {className: 'jasmine-summary'});
+
+    var topResults = new j$.ResultsNode({}, '', null),
+      currentParent = topResults;
+
+    this.suiteStarted = function(result) {
+      currentParent.addChild(result, 'suite');
+      currentParent = currentParent.last();
+    };
+
+    this.suiteDone = function(result) {
+      if (result.status == 'failed') {
+        failedSuites.push(result);
+      }
+
+      if (currentParent == topResults) {
+        return;
+      }
+
+      currentParent = currentParent.parent;
+    };
+
+    this.specStarted = function(result) {
+      currentParent.addChild(result, 'spec');
+    };
+
+    var failures = [];
+    this.specDone = function(result) {
+      if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
+        console.error('Spec \'' + result.fullName + '\' has no expectations.');
+      }
+
+      if (result.status != 'disabled') {
+        specsExecuted++;
+      }
+
+      if (!symbols){
+        symbols = find('.jasmine-symbol-summary');
+      }
+
+      symbols.appendChild(createDom('li', {
+          className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status,
+          id: 'spec_' + result.id,
+          title: result.fullName
+        }
+      ));
+
+      if (result.status == 'failed') {
+        failureCount++;
+
+        var failure =
+          createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
+            createDom('div', {className: 'jasmine-description'},
+              createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
+            ),
+            createDom('div', {className: 'jasmine-messages'})
+          );
+        var messages = failure.childNodes[1];
+
+        for (var i = 0; i < result.failedExpectations.length; i++) {
+          var expectation = result.failedExpectations[i];
+          messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
+          messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
+        }
+
+        failures.push(failure);
+      }
+
+      if (result.status == 'pending') {
+        pendingSpecCount++;
+      }
+    };
+
+    this.jasmineDone = function(doneResult) {
+      var banner = find('.jasmine-banner');
+      var alert = find('.jasmine-alert');
+      var order = doneResult && doneResult.order;
+      alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
+
+      banner.appendChild(
+        createDom('div', { className: 'jasmine-run-options' },
+          createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+          createDom('div', { className: 'jasmine-payload' },
+            createDom('div', { className: 'jasmine-exceptions' },
+              createDom('input', {
+                className: 'jasmine-raise',
+                id: 'jasmine-raise-exceptions',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')),
+            createDom('div', { className: 'jasmine-throw-failures' },
+              createDom('input', {
+                className: 'jasmine-throw',
+                id: 'jasmine-throw-failures',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
+            createDom('div', { className: 'jasmine-random-order' },
+              createDom('input', {
+                className: 'jasmine-random',
+                id: 'jasmine-random-order',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order'))
+          )
+        ));
+
+      var raiseCheckbox = find('#jasmine-raise-exceptions');
+
+      raiseCheckbox.checked = !env.catchingExceptions();
+      raiseCheckbox.onclick = onRaiseExceptionsClick;
+
+      var throwCheckbox = find('#jasmine-throw-failures');
+      throwCheckbox.checked = env.throwingExpectationFailures();
+      throwCheckbox.onclick = onThrowExpectationsClick;
+
+      var randomCheckbox = find('#jasmine-random-order');
+      randomCheckbox.checked = env.randomTests();
+      randomCheckbox.onclick = onRandomClick;
+
+      var optionsMenu = find('.jasmine-run-options'),
+          optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'),
+          optionsPayload = optionsMenu.querySelector('.jasmine-payload'),
+          isOpen = /\bjasmine-open\b/;
+
+      optionsTrigger.onclick = function() {
+        if (isOpen.test(optionsPayload.className)) {
+          optionsPayload.className = optionsPayload.className.replace(isOpen, '');
+        } else {
+          optionsPayload.className += ' jasmine-open';
+        }
+      };
+
+      if (specsExecuted < totalSpecsDefined) {
+        var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-bar jasmine-skipped'},
+            createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
+          )
+        );
+      }
+      var statusBarMessage = '';
+      var statusBarClassName = 'jasmine-bar ';
+
+      if (totalSpecsDefined > 0) {
+        statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
+        if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
+        statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed';
+      } else {
+        statusBarClassName += 'jasmine-skipped';
+        statusBarMessage += 'No specs found';
+      }
+
+      var seedBar;
+      if (order && order.random) {
+        seedBar = createDom('span', {className: 'jasmine-seed-bar'},
+          ', randomized with seed ',
+          createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
+        );
+      }
+
+      alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
+
+      for(i = 0; i < failedSuites.length; i++) {
+        var failedSuite = failedSuites[i];
+        for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
+          var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
+          var errorBarClassName = 'jasmine-bar jasmine-errored';
+          alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
+        }
+      }
+
+      var results = find('.jasmine-results');
+      results.appendChild(summary);
+
+      summaryList(topResults, summary);
+
+      function summaryList(resultsTree, domParent) {
+        var specListNode;
+        for (var i = 0; i < resultsTree.children.length; i++) {
+          var resultNode = resultsTree.children[i];
+          if (resultNode.type == 'suite') {
+            var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
+              createDom('li', {className: 'jasmine-suite-detail'},
+                createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
+              )
+            );
+
+            summaryList(resultNode, suiteListNode);
+            domParent.appendChild(suiteListNode);
+          }
+          if (resultNode.type == 'spec') {
+            if (domParent.getAttribute('class') != 'jasmine-specs') {
+              specListNode = createDom('ul', {className: 'jasmine-specs'});
+              domParent.appendChild(specListNode);
+            }
+            var specDescription = resultNode.result.description;
+            if(noExpectations(resultNode.result)) {
+              specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+            }
+            if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
+              specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+            }
+            specListNode.appendChild(
+              createDom('li', {
+                  className: 'jasmine-' + resultNode.result.status,
+                  id: 'spec-' + resultNode.result.id
+                },
+                createDom('a', {href: specHref(resultNode.result)}, specDescription)
+              )
+            );
+          }
+        }
+      }
+
+      if (failures.length) {
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
+            createDom('span', {}, 'Spec List | '),
+            createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
+            createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
+            createDom('span', {}, ' | Failures ')));
+
+        find('.jasmine-failures-menu').onclick = function() {
+          setMenuModeTo('jasmine-failure-list');
+        };
+        find('.jasmine-spec-list-menu').onclick = function() {
+          setMenuModeTo('jasmine-spec-list');
+        };
+
+        setMenuModeTo('jasmine-failure-list');
+
+        var failureNode = find('.jasmine-failures');
+        for (var i = 0; i < failures.length; i++) {
+          failureNode.appendChild(failures[i]);
+        }
+      }
+    };
+
+    return this;
+
+    function find(selector) {
+      return getContainer().querySelector('.jasmine_html-reporter ' + selector);
+    }
+
+    function clearPrior() {
+      // return the reporter
+      var oldReporter = find('');
+
+      if(oldReporter) {
+        getContainer().removeChild(oldReporter);
+      }
+    }
+
+    function createDom(type, attrs, childrenVarArgs) {
+      var el = createElement(type);
+
+      for (var i = 2; i < arguments.length; i++) {
+        var child = arguments[i];
+
+        if (typeof child === 'string') {
+          el.appendChild(createTextNode(child));
+        } else {
+          if (child) {
+            el.appendChild(child);
+          }
+        }
+      }
+
+      for (var attr in attrs) {
+        if (attr == 'className') {
+          el[attr] = attrs[attr];
+        } else {
+          el.setAttribute(attr, attrs[attr]);
+        }
+      }
+
+      return el;
+    }
+
+    function pluralize(singular, count) {
+      var word = (count == 1 ? singular : singular + 's');
+
+      return '' + count + ' ' + word;
+    }
+
+    function specHref(result) {
+      return addToExistingQueryString('spec', result.fullName);
+    }
+
+    function seedHref(seed) {
+      return addToExistingQueryString('seed', seed);
+    }
+
+    function defaultQueryString(key, value) {
+      return '?' + key + '=' + value;
+    }
+
+    function setMenuModeTo(mode) {
+      htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
+    }
+
+    function noExpectations(result) {
+      return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
+        result.status === 'passed';
+    }
+  }
+
+  return HtmlReporter;
+};
+
+jasmineRequire.HtmlSpecFilter = function() {
+  function HtmlSpecFilter(options) {
+    var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+    var filterPattern = new RegExp(filterString);
+
+    this.matches = function(specName) {
+      return filterPattern.test(specName);
+    };
+  }
+
+  return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsNode = function() {
+  function ResultsNode(result, type, parent) {
+    this.result = result;
+    this.type = type;
+    this.parent = parent;
+
+    this.children = [];
+
+    this.addChild = function(result, type) {
+      this.children.push(new ResultsNode(result, type, this));
+    };
+
+    this.last = function() {
+      return this.children[this.children.length - 1];
+    };
+  }
+
+  return ResultsNode;
+};
+
+jasmineRequire.QueryString = function() {
+  function QueryString(options) {
+
+    this.navigateWithNewParam = function(key, value) {
+      options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
+    };
+
+    this.fullStringWithNewParam = function(key, value) {
+      var paramMap = queryStringToParamMap();
+      paramMap[key] = value;
+      return toQueryString(paramMap);
+    };
+
+    this.getParam = function(key) {
+      return queryStringToParamMap()[key];
+    };
+
+    return this;
+
+    function toQueryString(paramMap) {
+      var qStrPairs = [];
+      for (var prop in paramMap) {
+        qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
+      }
+      return '?' + qStrPairs.join('&');
+    }
+
+    function queryStringToParamMap() {
+      var paramStr = options.getWindowLocation().search.substring(1),
+        params = [],
+        paramMap = {};
+
+      if (paramStr.length > 0) {
+        params = paramStr.split('&');
+        for (var i = 0; i < params.length; i++) {
+          var p = params[i].split('=');
+          var value = decodeURIComponent(p[1]);
+          if (value === 'true' || value === 'false') {
+            value = JSON.parse(value);
+          }
+          paramMap[decodeURIComponent(p[0])] = value;
+        }
+      }
+
+      return paramMap;
+    }
+
+  }
+
+  return QueryString;
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine.js
new file mode 100644
index 0000000000000000000000000000000000000000..7156e6672e7ec6aabbd2a8a8321661c338deb299
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/jasmine_lib/jasmine.js
@@ -0,0 +1,4941 @@
+/*
+ Copyright (c) 2008-2017 Pivotal Labs
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+var getJasmineRequireObj = (function (jasmineGlobal) {
+  var jasmineRequire;
+
+  if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') {
+    if (typeof global !== 'undefined') {
+      jasmineGlobal = global;
+    } else {
+      jasmineGlobal = {};
+    }
+    jasmineRequire = exports;
+  } else {
+    if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') {
+      jasmineGlobal = window;
+    }
+    jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+  }
+
+  function getJasmineRequire() {
+    return jasmineRequire;
+  }
+
+  getJasmineRequire().core = function(jRequire) {
+    var j$ = {};
+
+    jRequire.base(j$, jasmineGlobal);
+    j$.util = jRequire.util();
+    j$.errors = jRequire.errors();
+    j$.formatErrorMsg = jRequire.formatErrorMsg();
+    j$.Any = jRequire.Any(j$);
+    j$.Anything = jRequire.Anything(j$);
+    j$.CallTracker = jRequire.CallTracker(j$);
+    j$.MockDate = jRequire.MockDate();
+    j$.getClearStack = jRequire.clearStack(j$);
+    j$.Clock = jRequire.Clock();
+    j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+    j$.Env = jRequire.Env(j$);
+    j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+    j$.Expectation = jRequire.Expectation();
+    j$.buildExpectationResult = jRequire.buildExpectationResult();
+    j$.JsApiReporter = jRequire.JsApiReporter();
+    j$.matchersUtil = jRequire.matchersUtil(j$);
+    j$.ObjectContaining = jRequire.ObjectContaining(j$);
+    j$.ArrayContaining = jRequire.ArrayContaining(j$);
+    j$.pp = jRequire.pp(j$);
+    j$.QueueRunner = jRequire.QueueRunner(j$);
+    j$.ReportDispatcher = jRequire.ReportDispatcher();
+    j$.Spec = jRequire.Spec(j$);
+    j$.Spy = jRequire.Spy(j$);
+    j$.SpyRegistry = jRequire.SpyRegistry(j$);
+    j$.SpyStrategy = jRequire.SpyStrategy(j$);
+    j$.StringMatching = jRequire.StringMatching(j$);
+    j$.Suite = jRequire.Suite(j$);
+    j$.Timer = jRequire.Timer();
+    j$.TreeProcessor = jRequire.TreeProcessor();
+    j$.version = jRequire.version();
+    j$.Order = jRequire.Order();
+    j$.DiffBuilder = jRequire.DiffBuilder(j$);
+    j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
+    j$.ObjectPath = jRequire.ObjectPath(j$);
+    j$.GlobalErrors = jRequire.GlobalErrors(j$);
+
+    j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+    return j$;
+  };
+
+  return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+  var availableMatchers = [
+        'toBe',
+        'toBeCloseTo',
+        'toBeDefined',
+        'toBeFalsy',
+        'toBeGreaterThan',
+        'toBeGreaterThanOrEqual',
+        'toBeLessThan',
+        'toBeLessThanOrEqual',
+        'toBeNaN',
+        'toBeNegativeInfinity',
+        'toBeNull',
+        'toBePositiveInfinity',
+        'toBeTruthy',
+        'toBeUndefined',
+        'toContain',
+        'toEqual',
+        'toHaveBeenCalled',
+        'toHaveBeenCalledBefore',
+        'toHaveBeenCalledTimes',
+        'toHaveBeenCalledWith',
+        'toMatch',
+        'toThrow',
+        'toThrowError'
+      ],
+      matchers = {};
+
+  for (var i = 0; i < availableMatchers.length; i++) {
+    var name = availableMatchers[i];
+    matchers[name] = jRequire[name](j$);
+  }
+
+  return matchers;
+};
+
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+  j$.unimplementedMethod_ = function() {
+    throw new Error('unimplemented method');
+  };
+
+  /**
+   * Maximum object depth the pretty printer will print to.
+   * Set this to a lower value to speed up pretty printing if you have large objects.
+   * @name jasmine.MAX_PRETTY_PRINT_DEPTH
+   */
+  j$.MAX_PRETTY_PRINT_DEPTH = 40;
+  /**
+   * Maximum number of array elements to display when pretty printing objects.
+   * Elements past this number will be ellipised.
+   * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH
+   */
+  j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+  /**
+   * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete.
+   * @name jasmine.DEFAULT_TIMEOUT_INTERVAL
+   */
+  j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+  j$.getGlobal = function() {
+    return jasmineGlobal;
+  };
+
+  /**
+   * Get the currently booted Jasmine Environment.
+   *
+   * @name jasmine.getEnv
+   * @function
+   * @return {Env}
+   */
+  j$.getEnv = function(options) {
+    var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+    //jasmine. singletons in here (setTimeout blah blah).
+    return env;
+  };
+
+  j$.isArray_ = function(value) {
+    return j$.isA_('Array', value);
+  };
+
+  j$.isObject_ = function(value) {
+    return !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value);
+  };
+
+  j$.isString_ = function(value) {
+    return j$.isA_('String', value);
+  };
+
+  j$.isNumber_ = function(value) {
+    return j$.isA_('Number', value);
+  };
+
+  j$.isFunction_ = function(value) {
+    return j$.isA_('Function', value);
+  };
+
+  j$.isA_ = function(typeName, value) {
+    return j$.getType_(value) === '[object ' + typeName + ']';
+  };
+
+  j$.getType_ = function(value) {
+    return Object.prototype.toString.apply(value);
+  };
+
+  j$.isDomNode = function(obj) {
+    return obj.nodeType > 0;
+  };
+
+  j$.fnNameFor = function(func) {
+    if (func.name) {
+      return func.name;
+    }
+
+    var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/);
+    return matches ? matches[1] : '<anonymous>';
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared is an instance of the specified class/constructor.
+   * @name jasmine.any
+   * @function
+   * @param {Constructor} clazz - The constructor to check against.
+   */
+  j$.any = function(clazz) {
+    return new j$.Any(clazz);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared is not `null` and not `undefined`.
+   * @name jasmine.anything
+   * @function
+   */
+  j$.anything = function() {
+    return new j$.Anything();
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared contains at least the keys and values.
+   * @name jasmine.objectContaining
+   * @function
+   * @param {Object} sample - The subset of properties that _must_ be in the actual.
+   */
+  j$.objectContaining = function(sample) {
+    return new j$.ObjectContaining(sample);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`.
+   * @name jasmine.stringMatching
+   * @function
+   * @param {RegExp|String} expected
+   */
+  j$.stringMatching = function(expected) {
+    return new j$.StringMatching(expected);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value is an `Array` that contains at least the elements in the sample.
+   * @name jasmine.arrayContaining
+   * @function
+   * @param {Array} sample
+   */
+  j$.arrayContaining = function(sample) {
+    return new j$.ArrayContaining(sample);
+  };
+
+  /**
+   * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it.
+   * @name jasmine.createSpy
+   * @function
+   * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
+   * @param {Function} [originalFn] - Function to act as the real implementation.
+   * @return {Spy}
+   */
+  j$.createSpy = function(name, originalFn) {
+    return j$.Spy(name, originalFn);
+  };
+
+  j$.isSpy = function(putativeSpy) {
+    if (!putativeSpy) {
+      return false;
+    }
+    return putativeSpy.and instanceof j$.SpyStrategy &&
+        putativeSpy.calls instanceof j$.CallTracker;
+  };
+
+  /**
+   * Create an object with multiple {@link Spy}s as its members.
+   * @name jasmine.createSpyObj
+   * @function
+   * @param {String} [baseName] - Base name for the spies in the object.
+   * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}.
+   * @return {Object}
+   */
+  j$.createSpyObj = function(baseName, methodNames) {
+    var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName);
+
+    if (baseNameIsCollection && j$.util.isUndefined(methodNames)) {
+      methodNames = baseName;
+      baseName = 'unknown';
+    }
+
+    var obj = {};
+    var spiesWereSet = false;
+
+    if (j$.isArray_(methodNames)) {
+      for (var i = 0; i < methodNames.length; i++) {
+        obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+        spiesWereSet = true;
+      }
+    } else if (j$.isObject_(methodNames)) {
+      for (var key in methodNames) {
+        if (methodNames.hasOwnProperty(key)) {
+          obj[key] = j$.createSpy(baseName + '.' + key);
+          obj[key].and.returnValue(methodNames[key]);
+          spiesWereSet = true;
+        }
+      }
+    }
+
+    if (!spiesWereSet) {
+      throw 'createSpyObj requires a non-empty array or object of method names to create spies for';
+    }
+
+    return obj;
+  };
+};
+
+getJasmineRequireObj().util = function() {
+
+  var util = {};
+
+  util.inherit = function(childClass, parentClass) {
+    var Subclass = function() {
+    };
+    Subclass.prototype = parentClass.prototype;
+    childClass.prototype = new Subclass();
+  };
+
+  util.htmlEscape = function(str) {
+    if (!str) {
+      return str;
+    }
+    return str.replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;');
+  };
+
+  util.argsToArray = function(args) {
+    var arrayOfArgs = [];
+    for (var i = 0; i < args.length; i++) {
+      arrayOfArgs.push(args[i]);
+    }
+    return arrayOfArgs;
+  };
+
+  util.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  util.arrayContains = function(array, search) {
+    var i = array.length;
+    while (i--) {
+      if (array[i] === search) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  util.clone = function(obj) {
+    if (Object.prototype.toString.apply(obj) === '[object Array]') {
+      return obj.slice();
+    }
+
+    var cloned = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        cloned[prop] = obj[prop];
+      }
+    }
+
+    return cloned;
+  };
+
+  util.getPropertyDescriptor = function(obj, methodName) {
+    var descriptor,
+        proto = obj;
+
+    do {
+      descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
+      proto = Object.getPrototypeOf(proto);
+    } while (!descriptor && proto);
+
+    return descriptor;
+  };
+
+  util.objectDifference = function(obj, toRemove) {
+    var diff = {};
+
+    for (var key in obj) {
+      if (util.has(obj, key) && !util.has(toRemove, key)) {
+        diff[key] = obj[key];
+      }
+    }
+
+    return diff;
+  };
+
+  util.has = function(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  };
+
+  return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+  function Spec(attrs) {
+    this.expectationFactory = attrs.expectationFactory;
+    this.resultCallback = attrs.resultCallback || function() {};
+    this.id = attrs.id;
+    this.description = attrs.description || '';
+    this.queueableFn = attrs.queueableFn;
+    this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+    this.userContext = attrs.userContext || function() { return {}; };
+    this.onStart = attrs.onStart || function() {};
+    this.getSpecName = attrs.getSpecName || function() { return ''; };
+    this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+    this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+    this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    if (!this.queueableFn.fn) {
+      this.pend();
+    }
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: [],
+      passedExpectations: [],
+      pendingReason: ''
+    };
+  }
+
+  Spec.prototype.addExpectationResult = function(passed, data, isError) {
+    var expectationResult = this.expectationResultFactory(data);
+    if (passed) {
+      this.result.passedExpectations.push(expectationResult);
+    } else {
+      this.result.failedExpectations.push(expectationResult);
+
+      if (this.throwOnExpectationFailure && !isError) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    }
+  };
+
+  Spec.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Spec.prototype.execute = function(onComplete, enabled) {
+    var self = this;
+
+    this.onStart(this);
+
+    if (!this.isExecutable() || this.markedPending || enabled === false) {
+      complete(enabled);
+      return;
+    }
+
+    var fns = this.beforeAndAfterFns();
+    var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
+
+    this.queueRunnerFactory({
+      queueableFns: allFns,
+      onException: function() { self.onException.apply(self, arguments); },
+      onComplete: complete,
+      userContext: this.userContext()
+    });
+
+    function complete(enabledAgain) {
+      self.result.status = self.status(enabledAgain);
+      self.resultCallback(self.result);
+
+      if (onComplete) {
+        onComplete();
+      }
+    }
+  };
+
+  Spec.prototype.onException = function onException(e) {
+    if (Spec.isPendingSpecException(e)) {
+      this.pend(extractCustomPendingMessage(e));
+      return;
+    }
+
+    if (e instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    this.addExpectationResult(false, {
+      matcherName: '',
+      passed: false,
+      expected: '',
+      actual: '',
+      error: e
+    }, true);
+  };
+
+  Spec.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Spec.prototype.pend = function(message) {
+    this.markedPending = true;
+    if (message) {
+      this.result.pendingReason = message;
+    }
+  };
+
+  Spec.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Spec.prototype.status = function(enabled) {
+    if (this.disabled || enabled === false) {
+      return 'disabled';
+    }
+
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'passed';
+    }
+  };
+
+  Spec.prototype.isExecutable = function() {
+    return !this.disabled;
+  };
+
+  Spec.prototype.getFullName = function() {
+    return this.getSpecName(this);
+  };
+
+  var extractCustomPendingMessage = function(e) {
+    var fullMessage = e.toString(),
+        boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+        boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+
+    return fullMessage.substr(boilerplateEnd);
+  };
+
+  Spec.pendingSpecExceptionMessage = '=> marked Pending';
+
+  Spec.isPendingSpecException = function(e) {
+    return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+  };
+
+  return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Spec = jasmineRequire.Spec;
+}
+
+/*jshint bitwise: false*/
+
+getJasmineRequireObj().Order = function() {
+  function Order(options) {
+    this.random = 'random' in options ? options.random : true;
+    var seed = this.seed = options.seed || generateSeed();
+    this.sort = this.random ? randomOrder : naturalOrder;
+
+    function naturalOrder(items) {
+      return items;
+    }
+
+    function randomOrder(items) {
+      var copy = items.slice();
+      copy.sort(function(a, b) {
+        return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id);
+      });
+      return copy;
+    }
+
+    function generateSeed() {
+      return String(Math.random()).slice(-5);
+    }
+
+    // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function
+    // used to get a different output when the key changes slighly.
+    // We use your return to sort the children randomly in a consistent way when
+    // used in conjunction with a seed
+
+    function jenkinsHash(key) {
+      var hash, i;
+      for(hash = i = 0; i < key.length; ++i) {
+        hash += key.charCodeAt(i);
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+      }
+      hash += (hash << 3);
+      hash ^= (hash >> 11);
+      hash += (hash << 15);
+      return hash;
+    }
+
+  }
+
+  return Order;
+};
+
+getJasmineRequireObj().Env = function(j$) {
+  /**
+   * _Note:_ Do not construct this directly, Jasmine will make one during booting.
+   * @name Env
+   * @classdesc The Jasmine environment
+   * @constructor
+   */
+  function Env(options) {
+    options = options || {};
+
+    var self = this;
+    var global = options.global || j$.getGlobal();
+
+    var totalSpecsDefined = 0;
+
+    var catchExceptions = true;
+
+    var realSetTimeout = j$.getGlobal().setTimeout;
+    var realClearTimeout = j$.getGlobal().clearTimeout;
+    var clearStack = j$.getClearStack(j$.getGlobal());
+    this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
+
+    var runnableResources = {};
+
+    var currentSpec = null;
+    var currentlyExecutingSuites = [];
+    var currentDeclarationSuite = null;
+    var throwOnExpectationFailure = false;
+    var random = false;
+    var seed = null;
+
+    var currentSuite = function() {
+      return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+    };
+
+    var currentRunnable = function() {
+      return currentSpec || currentSuite();
+    };
+
+    var reporter = new j$.ReportDispatcher([
+      'jasmineStarted',
+      'jasmineDone',
+      'suiteStarted',
+      'suiteDone',
+      'specStarted',
+      'specDone'
+    ]);
+
+    var globalErrors = new j$.GlobalErrors();
+
+    this.specFilter = function() {
+      return true;
+    };
+
+    this.addCustomEqualityTester = function(tester) {
+      if(!currentRunnable()) {
+        throw new Error('Custom Equalities must be added in a before function or a spec');
+      }
+      runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+    };
+
+    this.addMatchers = function(matchersToAdd) {
+      if(!currentRunnable()) {
+        throw new Error('Matchers must be added in a before function or a spec');
+      }
+      var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+      for (var matcherName in matchersToAdd) {
+        customMatchers[matcherName] = matchersToAdd[matcherName];
+      }
+    };
+
+    j$.Expectation.addCoreMatchers(j$.matchers);
+
+    var nextSpecId = 0;
+    var getNextSpecId = function() {
+      return 'spec' + nextSpecId++;
+    };
+
+    var nextSuiteId = 0;
+    var getNextSuiteId = function() {
+      return 'suite' + nextSuiteId++;
+    };
+
+    var expectationFactory = function(actual, spec) {
+      return j$.Expectation.Factory({
+        util: j$.matchersUtil,
+        customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+        customMatchers: runnableResources[spec.id].customMatchers,
+        actual: actual,
+        addExpectationResult: addExpectationResult
+      });
+
+      function addExpectationResult(passed, result) {
+        return spec.addExpectationResult(passed, result);
+      }
+    };
+
+    var defaultResourcesForRunnable = function(id, parentRunnableId) {
+      var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+      if(runnableResources[parentRunnableId]){
+        resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+        resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+      }
+
+      runnableResources[id] = resources;
+    };
+
+    var clearResourcesForRunnable = function(id) {
+      spyRegistry.clearSpies();
+      delete runnableResources[id];
+    };
+
+    var beforeAndAfterFns = function(suite) {
+      return function() {
+        var befores = [],
+            afters = [];
+
+        while(suite) {
+          befores = befores.concat(suite.beforeFns);
+          afters = afters.concat(suite.afterFns);
+
+          suite = suite.parentSuite;
+        }
+
+        return {
+          befores: befores.reverse(),
+          afters: afters
+        };
+      };
+    };
+
+    var getSpecName = function(spec, suite) {
+      var fullName = [spec.description],
+          suiteFullName = suite.getFullName();
+
+      if (suiteFullName !== '') {
+        fullName.unshift(suiteFullName);
+      }
+      return fullName.join(' ');
+    };
+
+    // TODO: we may just be able to pass in the fn instead of wrapping here
+    var buildExpectationResult = j$.buildExpectationResult,
+        exceptionFormatter = new j$.ExceptionFormatter(),
+        expectationResultFactory = function(attrs) {
+          attrs.messageFormatter = exceptionFormatter.message;
+          attrs.stackFormatter = exceptionFormatter.stack;
+
+          return buildExpectationResult(attrs);
+        };
+
+    // TODO: fix this naming, and here's where the value comes in
+    this.catchExceptions = function(value) {
+      catchExceptions = !!value;
+      return catchExceptions;
+    };
+
+    this.catchingExceptions = function() {
+      return catchExceptions;
+    };
+
+    var maximumSpecCallbackDepth = 20;
+    var currentSpecCallbackDepth = 0;
+
+    var catchException = function(e) {
+      return j$.Spec.isPendingSpecException(e) || catchExceptions;
+    };
+
+    this.throwOnExpectationFailure = function(value) {
+      throwOnExpectationFailure = !!value;
+    };
+
+    this.throwingExpectationFailures = function() {
+      return throwOnExpectationFailure;
+    };
+
+    this.randomizeTests = function(value) {
+      random = !!value;
+    };
+
+    this.randomTests = function() {
+      return random;
+    };
+
+    this.seed = function(value) {
+      if (value) {
+        seed = value;
+      }
+      return seed;
+    };
+
+    var queueRunnerFactory = function(options) {
+      options.catchException = catchException;
+      options.clearStack = options.clearStack || clearStack;
+      options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+      options.fail = self.fail;
+      options.globalErrors = globalErrors;
+
+      new j$.QueueRunner(options).execute();
+    };
+
+    var topSuite = new j$.Suite({
+      env: this,
+      id: getNextSuiteId(),
+      description: 'Jasmine__TopLevel__Suite',
+      expectationFactory: expectationFactory,
+      expectationResultFactory: expectationResultFactory
+    });
+    defaultResourcesForRunnable(topSuite.id);
+    currentDeclarationSuite = topSuite;
+
+    this.topSuite = function() {
+      return topSuite;
+    };
+
+    this.execute = function(runnablesToRun) {
+      if(!runnablesToRun) {
+        if (focusedRunnables.length) {
+          runnablesToRun = focusedRunnables;
+        } else {
+          runnablesToRun = [topSuite.id];
+        }
+      }
+
+      var order = new j$.Order({
+        random: random,
+        seed: seed
+      });
+
+      var processor = new j$.TreeProcessor({
+        tree: topSuite,
+        runnableIds: runnablesToRun,
+        queueRunnerFactory: queueRunnerFactory,
+        nodeStart: function(suite) {
+          currentlyExecutingSuites.push(suite);
+          defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+          reporter.suiteStarted(suite.result);
+        },
+        nodeComplete: function(suite, result) {
+          if (!suite.markedPending) {
+            clearResourcesForRunnable(suite.id);
+          }
+          currentlyExecutingSuites.pop();
+          reporter.suiteDone(result);
+        },
+        orderChildren: function(node) {
+          return order.sort(node.children);
+        }
+      });
+
+      if(!processor.processTree().valid) {
+        throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
+      }
+
+      reporter.jasmineStarted({
+        totalSpecsDefined: totalSpecsDefined
+      });
+
+      currentlyExecutingSuites.push(topSuite);
+
+      globalErrors.install();
+      processor.execute(function() {
+        clearResourcesForRunnable(topSuite.id);
+        currentlyExecutingSuites.pop();
+        globalErrors.uninstall();
+
+        reporter.jasmineDone({
+          order: order,
+          failedExpectations: topSuite.result.failedExpectations
+        });
+      });
+    };
+
+    /**
+     * Add a custom reporter to the Jasmine environment.
+     * @name Env#addReporter
+     * @function
+     * @see custom_reporter
+     */
+    this.addReporter = function(reporterToAdd) {
+      reporter.addReporter(reporterToAdd);
+    };
+
+    this.provideFallbackReporter = function(reporterToAdd) {
+      reporter.provideFallbackReporter(reporterToAdd);
+    };
+
+    this.clearReporters = function() {
+      reporter.clearReporters();
+    };
+
+    var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+      if(!currentRunnable()) {
+        throw new Error('Spies must be created in a before function or a spec');
+      }
+      return runnableResources[currentRunnable().id].spies;
+    }});
+
+    this.allowRespy = function(allow){
+      spyRegistry.allowRespy(allow);
+    };
+
+    this.spyOn = function() {
+      return spyRegistry.spyOn.apply(spyRegistry, arguments);
+    };
+
+    this.spyOnProperty = function() {
+      return spyRegistry.spyOnProperty.apply(spyRegistry, arguments);
+    };
+
+    var ensureIsFunction = function(fn, caller) {
+      if (!j$.isFunction_(fn)) {
+        throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn));
+      }
+    };
+
+    var suiteFactory = function(description) {
+      var suite = new j$.Suite({
+        env: self,
+        id: getNextSuiteId(),
+        description: description,
+        parentSuite: currentDeclarationSuite,
+        expectationFactory: expectationFactory,
+        expectationResultFactory: expectationResultFactory,
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      return suite;
+    };
+
+    this.describe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'describe');
+      var suite = suiteFactory(description);
+      if (specDefinitions.length > 0) {
+        throw new Error('describe does not expect any arguments');
+      }
+      if (currentDeclarationSuite.markedPending) {
+        suite.pend();
+      }
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    this.xdescribe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'xdescribe');
+      var suite = suiteFactory(description);
+      suite.pend();
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    var focusedRunnables = [];
+
+    this.fdescribe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'fdescribe');
+      var suite = suiteFactory(description);
+      suite.isFocused = true;
+
+      focusedRunnables.push(suite.id);
+      unfocusAncestor();
+      addSpecsToSuite(suite, specDefinitions);
+
+      return suite;
+    };
+
+    function addSpecsToSuite(suite, specDefinitions) {
+      var parentSuite = currentDeclarationSuite;
+      parentSuite.addChild(suite);
+      currentDeclarationSuite = suite;
+
+      var declarationError = null;
+      try {
+        specDefinitions.call(suite);
+      } catch (e) {
+        declarationError = e;
+      }
+
+      if (declarationError) {
+        self.it('encountered a declaration exception', function() {
+          throw declarationError;
+        });
+      }
+
+      currentDeclarationSuite = parentSuite;
+    }
+
+    function findFocusedAncestor(suite) {
+      while (suite) {
+        if (suite.isFocused) {
+          return suite.id;
+        }
+        suite = suite.parentSuite;
+      }
+
+      return null;
+    }
+
+    function unfocusAncestor() {
+      var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+      if (focusedAncestor) {
+        for (var i = 0; i < focusedRunnables.length; i++) {
+          if (focusedRunnables[i] === focusedAncestor) {
+            focusedRunnables.splice(i, 1);
+            break;
+          }
+        }
+      }
+    }
+
+    var specFactory = function(description, fn, suite, timeout) {
+      totalSpecsDefined++;
+      var spec = new j$.Spec({
+        id: getNextSpecId(),
+        beforeAndAfterFns: beforeAndAfterFns(suite),
+        expectationFactory: expectationFactory,
+        resultCallback: specResultCallback,
+        getSpecName: function(spec) {
+          return getSpecName(spec, suite);
+        },
+        onStart: specStarted,
+        description: description,
+        expectationResultFactory: expectationResultFactory,
+        queueRunnerFactory: queueRunnerFactory,
+        userContext: function() { return suite.clonedSharedUserContext(); },
+        queueableFn: {
+          fn: fn,
+          timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+        },
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      if (!self.specFilter(spec)) {
+        spec.disable();
+      }
+
+      return spec;
+
+      function specResultCallback(result) {
+        clearResourcesForRunnable(spec.id);
+        currentSpec = null;
+        reporter.specDone(result);
+      }
+
+      function specStarted(spec) {
+        currentSpec = spec;
+        defaultResourcesForRunnable(spec.id, suite.id);
+        reporter.specStarted(spec.result);
+      }
+    };
+
+    this.it = function(description, fn, timeout) {
+      // it() sometimes doesn't have a fn argument, so only check the type if
+      // it's given.
+      if (arguments.length > 1 && typeof fn !== 'undefined') {
+        ensureIsFunction(fn, 'it');
+      }
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      if (currentDeclarationSuite.markedPending) {
+        spec.pend();
+      }
+      currentDeclarationSuite.addChild(spec);
+      return spec;
+    };
+
+    this.xit = function(description, fn, timeout) {
+      // xit(), like it(), doesn't always have a fn argument, so only check the
+      // type when needed.
+      if (arguments.length > 1 && typeof fn !== 'undefined') {
+        ensureIsFunction(fn, 'xit');
+      }
+      var spec = this.it.apply(this, arguments);
+      spec.pend('Temporarily disabled with xit');
+      return spec;
+    };
+
+    this.fit = function(description, fn, timeout){
+      ensureIsFunction(fn, 'fit');
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      currentDeclarationSuite.addChild(spec);
+      focusedRunnables.push(spec.id);
+      unfocusAncestor();
+      return spec;
+    };
+
+    this.expect = function(actual) {
+      if (!currentRunnable()) {
+        throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
+      }
+
+      return currentRunnable().expect(actual);
+    };
+
+    this.beforeEach = function(beforeEachFunction, timeout) {
+      ensureIsFunction(beforeEachFunction, 'beforeEach');
+      currentDeclarationSuite.beforeEach({
+        fn: beforeEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.beforeAll = function(beforeAllFunction, timeout) {
+      ensureIsFunction(beforeAllFunction, 'beforeAll');
+      currentDeclarationSuite.beforeAll({
+        fn: beforeAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterEach = function(afterEachFunction, timeout) {
+      ensureIsFunction(afterEachFunction, 'afterEach');
+      currentDeclarationSuite.afterEach({
+        fn: afterEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterAll = function(afterAllFunction, timeout) {
+      ensureIsFunction(afterAllFunction, 'afterAll');
+      currentDeclarationSuite.afterAll({
+        fn: afterAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.pending = function(message) {
+      var fullMessage = j$.Spec.pendingSpecExceptionMessage;
+      if(message) {
+        fullMessage += message;
+      }
+      throw fullMessage;
+    };
+
+    this.fail = function(error) {
+      if (!currentRunnable()) {
+        throw new Error('\'fail\' was used when there was no current spec, this could be because an asynchronous test timed out');
+      }
+
+      var message = 'Failed';
+      if (error) {
+        message += ': ';
+        if (error.message) {
+          message += error.message;
+        } else if (jasmine.isString_(error)) {
+          message += error;
+        } else {
+          // pretty print all kind of objects. This includes arrays.
+          message += jasmine.pp(error);
+        }
+      }
+
+      currentRunnable().addExpectationResult(false, {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        message: message,
+        error: error && error.message ? error : null
+      });
+    };
+  }
+
+  return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+  var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  /**
+   * _Note:_ Do not construct this directly, use the global `jsApiReporter` to retrieve the instantiated object.
+   *
+   * @name jsApiReporter
+   * @classdesc Reporter added by default in `boot.js` to record results for retrieval in javascript code.
+   * @class
+   */
+  function JsApiReporter(options) {
+    var timer = options.timer || noopTimer,
+        status = 'loaded';
+
+    this.started = false;
+    this.finished = false;
+    this.runDetails = {};
+
+    this.jasmineStarted = function() {
+      this.started = true;
+      status = 'started';
+      timer.start();
+    };
+
+    var executionTime;
+
+    this.jasmineDone = function(runDetails) {
+      this.finished = true;
+      this.runDetails = runDetails;
+      executionTime = timer.elapsed();
+      status = 'done';
+    };
+
+    /**
+     * Get the current status for the Jasmine environment.
+     * @name jsApiReporter#status
+     * @function
+     * @return {String} - One of `loaded`, `started`, or `done`
+     */
+    this.status = function() {
+      return status;
+    };
+
+    var suites = [],
+        suites_hash = {};
+
+    this.suiteStarted = function(result) {
+      suites_hash[result.id] = result;
+    };
+
+    this.suiteDone = function(result) {
+      storeSuite(result);
+    };
+
+    /**
+     * Get the results for a set of suites.
+     *
+     * Retrievable in slices for easier serialization.
+     * @name jsApiReporter#suiteResults
+     * @function
+     * @param {Number} index - The position in the suites list to start from.
+     * @param {Number} length - Maximum number of suite results to return.
+     * @return {Object[]}
+     */
+    this.suiteResults = function(index, length) {
+      return suites.slice(index, index + length);
+    };
+
+    function storeSuite(result) {
+      suites.push(result);
+      suites_hash[result.id] = result;
+    }
+
+    /**
+     * Get all of the suites in a single object, with their `id` as the key.
+     * @name jsApiReporter#suites
+     * @function
+     * @return {Object}
+     */
+    this.suites = function() {
+      return suites_hash;
+    };
+
+    var specs = [];
+
+    this.specDone = function(result) {
+      specs.push(result);
+    };
+
+    /**
+     * Get the results for a set of specs.
+     *
+     * Retrievable in slices for easier serialization.
+     * @name jsApiReporter#specResults
+     * @function
+     * @param {Number} index - The position in the specs list to start from.
+     * @param {Number} length - Maximum number of specs results to return.
+     * @return {Object[]}
+     */
+    this.specResults = function(index, length) {
+      return specs.slice(index, index + length);
+    };
+
+    /**
+     * Get all spec results.
+     * @name jsApiReporter#specs
+     * @function
+     * @return {Object[]}
+     */
+    this.specs = function() {
+      return specs;
+    };
+
+    /**
+     * Get the number of milliseconds it took for the full Jasmine suite to run.
+     * @name jsApiReporter#executionTime
+     * @function
+     * @return {Number}
+     */
+    this.executionTime = function() {
+      return executionTime;
+    };
+
+  }
+
+  return JsApiReporter;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+
+  function Any(expectedObject) {
+    if (typeof expectedObject === 'undefined') {
+      throw new TypeError(
+          'jasmine.any() expects to be passed a constructor function. ' +
+          'Please pass one or use jasmine.anything() to match any object.'
+      );
+    }
+    this.expectedObject = expectedObject;
+  }
+
+  Any.prototype.asymmetricMatch = function(other) {
+    if (this.expectedObject == String) {
+      return typeof other == 'string' || other instanceof String;
+    }
+
+    if (this.expectedObject == Number) {
+      return typeof other == 'number' || other instanceof Number;
+    }
+
+    if (this.expectedObject == Function) {
+      return typeof other == 'function' || other instanceof Function;
+    }
+
+    if (this.expectedObject == Object) {
+      return typeof other == 'object';
+    }
+
+    if (this.expectedObject == Boolean) {
+      return typeof other == 'boolean';
+    }
+
+    return other instanceof this.expectedObject;
+  };
+
+  Any.prototype.jasmineToString = function() {
+    return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+  };
+
+  return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+
+  function Anything() {}
+
+  Anything.prototype.asymmetricMatch = function(other) {
+    return !j$.util.isUndefined(other) && other !== null;
+  };
+
+  Anything.prototype.jasmineToString = function() {
+    return '<jasmine.anything>';
+  };
+
+  return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+  function ArrayContaining(sample) {
+    this.sample = sample;
+  }
+
+  ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) {
+    var className = Object.prototype.toString.call(this.sample);
+    if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); }
+
+    for (var i = 0; i < this.sample.length; i++) {
+      var item = this.sample[i];
+      if (!j$.matchersUtil.contains(other, item, customTesters)) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ArrayContaining.prototype.jasmineToString = function () {
+    return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>';
+  };
+
+  return ArrayContaining;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+  function ObjectContaining(sample) {
+    this.sample = sample;
+  }
+
+  function getPrototype(obj) {
+    if (Object.getPrototypeOf) {
+      return Object.getPrototypeOf(obj);
+    }
+
+    if (obj.constructor.prototype == obj) {
+      return null;
+    }
+
+    return obj.constructor.prototype;
+  }
+
+  function hasProperty(obj, property) {
+    if (!obj) {
+      return false;
+    }
+
+    if (Object.prototype.hasOwnProperty.call(obj, property)) {
+      return true;
+    }
+
+    return hasProperty(getPrototype(obj), property);
+  }
+
+  ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) {
+    if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
+
+    for (var property in this.sample) {
+      if (!hasProperty(other, property) ||
+          !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ObjectContaining.prototype.jasmineToString = function() {
+    return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
+  };
+
+  return ObjectContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+
+  function StringMatching(expected) {
+    if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+      throw new Error('Expected is not a String or a RegExp');
+    }
+
+    this.regexp = new RegExp(expected);
+  }
+
+  StringMatching.prototype.asymmetricMatch = function(other) {
+    return this.regexp.test(other);
+  };
+
+  StringMatching.prototype.jasmineToString = function() {
+    return '<jasmine.stringMatching(' + this.regexp + ')>';
+  };
+
+  return StringMatching;
+};
+
+getJasmineRequireObj().CallTracker = function(j$) {
+
+  /**
+   * @namespace Spy#calls
+   */
+  function CallTracker() {
+    var calls = [];
+    var opts = {};
+
+    function argCloner(context) {
+      var clonedArgs = [];
+      var argsAsArray = j$.util.argsToArray(context.args);
+      for(var i = 0; i < argsAsArray.length; i++) {
+        if(Object.prototype.toString.apply(argsAsArray[i]).match(/^\[object/)) {
+          clonedArgs.push(j$.util.clone(argsAsArray[i]));
+        } else {
+          clonedArgs.push(argsAsArray[i]);
+        }
+      }
+      context.args = clonedArgs;
+    }
+
+    this.track = function(context) {
+      if(opts.cloneArgs) {
+        argCloner(context);
+      }
+      calls.push(context);
+    };
+
+    /**
+     * Check whether this spy has been invoked.
+     * @name Spy#calls#any
+     * @function
+     * @return {Boolean}
+     */
+    this.any = function() {
+      return !!calls.length;
+    };
+
+    /**
+     * Get the number of invocations of this spy.
+     * @name Spy#calls#count
+     * @function
+     * @return {Integer}
+     */
+    this.count = function() {
+      return calls.length;
+    };
+
+    /**
+     * Get the arguments that were passed to a specific invocation of this spy.
+     * @name Spy#calls#argsFor
+     * @function
+     * @param {Integer} index The 0-based invocation index.
+     * @return {Array}
+     */
+    this.argsFor = function(index) {
+      var call = calls[index];
+      return call ? call.args : [];
+    };
+
+    /**
+     * Get the raw calls array for this spy.
+     * @name Spy#calls#all
+     * @function
+     * @return {Spy.callData[]}
+     */
+    this.all = function() {
+      return calls;
+    };
+
+    /**
+     * Get all of the arguments for each invocation of this spy in the order they were received.
+     * @name Spy#calls#allArgs
+     * @function
+     * @return {Array}
+     */
+    this.allArgs = function() {
+      var callArgs = [];
+      for(var i = 0; i < calls.length; i++){
+        callArgs.push(calls[i].args);
+      }
+
+      return callArgs;
+    };
+
+    /**
+     * Get the first invocation of this spy.
+     * @name Spy#calls#first
+     * @function
+     * @return {ObjecSpy.callData}
+     */
+    this.first = function() {
+      return calls[0];
+    };
+
+    /**
+     * Get the most recent invocation of this spy.
+     * @name Spy#calls#mostRecent
+     * @function
+     * @return {ObjecSpy.callData}
+     */
+    this.mostRecent = function() {
+      return calls[calls.length - 1];
+    };
+
+    /**
+     * Reset this spy as if it has never been called.
+     * @name Spy#calls#reset
+     * @function
+     */
+    this.reset = function() {
+      calls = [];
+    };
+
+    /**
+     * Set this spy to do a shallow clone of arguments passed to each invocation.
+     * @name Spy#calls#saveArgumentsByValue
+     * @function
+     */
+    this.saveArgumentsByValue = function() {
+      opts.cloneArgs = true;
+    };
+
+  }
+
+  return CallTracker;
+};
+
+getJasmineRequireObj().clearStack = function(j$) {
+  function messageChannelImpl(global) {
+    var channel = new global.MessageChannel(),
+        head = {},
+        tail = head;
+
+    channel.port1.onmessage = function() {
+      head = head.next;
+      var task = head.task;
+      delete head.task;
+      task();
+    };
+
+    return function clearStack(fn) {
+      tail = tail.next = { task: fn };
+      channel.port2.postMessage(0);
+    };
+  }
+
+  function getClearStack(global) {
+    if (j$.isFunction_(global.setImmediate)) {
+      var realSetImmediate = global.setImmediate;
+      return function(fn) {
+        realSetImmediate(fn);
+      };
+    } else if (!j$.util.isUndefined(global.MessageChannel)) {
+      return messageChannelImpl(global);
+    } else {
+      var realSetTimeout = global.setTimeout;
+      return function clearStack(fn) {
+        Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
+      };
+    }
+  }
+
+  return getClearStack;
+};
+
+getJasmineRequireObj().Clock = function() {
+  /**
+   * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}.
+   * @class Clock
+   * @classdesc Jasmine's mock clock is used when testing time dependent code.
+   */
+  function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
+    var self = this,
+        realTimingFunctions = {
+          setTimeout: global.setTimeout,
+          clearTimeout: global.clearTimeout,
+          setInterval: global.setInterval,
+          clearInterval: global.clearInterval
+        },
+        fakeTimingFunctions = {
+          setTimeout: setTimeout,
+          clearTimeout: clearTimeout,
+          setInterval: setInterval,
+          clearInterval: clearInterval
+        },
+        installed = false,
+        delayedFunctionScheduler,
+        timer;
+
+
+    /**
+     * Install the mock clock over the built-in methods.
+     * @name Clock#install
+     * @function
+     * @return {Clock}
+     */
+    self.install = function() {
+      if(!originalTimingFunctionsIntact()) {
+        throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
+      }
+      replace(global, fakeTimingFunctions);
+      timer = fakeTimingFunctions;
+      delayedFunctionScheduler = delayedFunctionSchedulerFactory();
+      installed = true;
+
+      return self;
+    };
+
+    /**
+     * Uninstall the mock clock, returning the built-in methods to their places.
+     * @name Clock#uninstall
+     * @function
+     */
+    self.uninstall = function() {
+      delayedFunctionScheduler = null;
+      mockDate.uninstall();
+      replace(global, realTimingFunctions);
+
+      timer = realTimingFunctions;
+      installed = false;
+    };
+
+    /**
+     * Execute a function with a mocked Clock
+     *
+     * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes.
+     * @name Clock#withMock
+     * @function
+     * @param {closure} Function The function to be called.
+     */
+    self.withMock = function(closure) {
+      this.install();
+      try {
+        closure();
+      } finally {
+        this.uninstall();
+      }
+    };
+
+    /**
+     * Instruct the installed Clock to also mock the date returned by `new Date()`
+     * @name Clock#mockDate
+     * @function
+     * @param {Date} [initialDate=now] The `Date` to provide.
+     */
+    self.mockDate = function(initialDate) {
+      mockDate.install(initialDate);
+    };
+
+    self.setTimeout = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
+        }
+        return timer.setTimeout(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+    };
+
+    self.setInterval = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
+        }
+        return timer.setInterval(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+    };
+
+    self.clearTimeout = function(id) {
+      return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+    };
+
+    self.clearInterval = function(id) {
+      return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+    };
+
+    /**
+     * Tick the Clock forward, running any enqueued timeouts along the way
+     * @name Clock#tick
+     * @function
+     * @param {int} millis The number of milliseconds to tick.
+     */
+    self.tick = function(millis) {
+      if (installed) {
+        delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); });
+      } else {
+        throw new Error('Mock clock is not installed, use jasmine.clock().install()');
+      }
+    };
+
+    return self;
+
+    function originalTimingFunctionsIntact() {
+      return global.setTimeout === realTimingFunctions.setTimeout &&
+          global.clearTimeout === realTimingFunctions.clearTimeout &&
+          global.setInterval === realTimingFunctions.setInterval &&
+          global.clearInterval === realTimingFunctions.clearInterval;
+    }
+
+    function legacyIE() {
+      //if these methods are polyfilled, apply will be present
+      return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+    }
+
+    function replace(dest, source) {
+      for (var prop in source) {
+        dest[prop] = source[prop];
+      }
+    }
+
+    function setTimeout(fn, delay) {
+      return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+    }
+
+    function clearTimeout(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function setInterval(fn, interval) {
+      return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+    }
+
+    function clearInterval(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function argSlice(argsObj, n) {
+      return Array.prototype.slice.call(argsObj, n);
+    }
+  }
+
+  return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+  function DelayedFunctionScheduler() {
+    var self = this;
+    var scheduledLookup = [];
+    var scheduledFunctions = {};
+    var currentTime = 0;
+    var delayedFnCount = 0;
+
+    self.tick = function(millis, tickDate) {
+      millis = millis || 0;
+      var endTime = currentTime + millis;
+
+      runScheduledFunctions(endTime, tickDate);
+      currentTime = endTime;
+    };
+
+    self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+      var f;
+      if (typeof(funcToCall) === 'string') {
+        /* jshint evil: true */
+        f = function() { return eval(funcToCall); };
+        /* jshint evil: false */
+      } else {
+        f = funcToCall;
+      }
+
+      millis = millis || 0;
+      timeoutKey = timeoutKey || ++delayedFnCount;
+      runAtMillis = runAtMillis || (currentTime + millis);
+
+      var funcToSchedule = {
+        runAtMillis: runAtMillis,
+        funcToCall: f,
+        recurring: recurring,
+        params: params,
+        timeoutKey: timeoutKey,
+        millis: millis
+      };
+
+      if (runAtMillis in scheduledFunctions) {
+        scheduledFunctions[runAtMillis].push(funcToSchedule);
+      } else {
+        scheduledFunctions[runAtMillis] = [funcToSchedule];
+        scheduledLookup.push(runAtMillis);
+        scheduledLookup.sort(function (a, b) {
+          return a - b;
+        });
+      }
+
+      return timeoutKey;
+    };
+
+    self.removeFunctionWithId = function(timeoutKey) {
+      for (var runAtMillis in scheduledFunctions) {
+        var funcs = scheduledFunctions[runAtMillis];
+        var i = indexOfFirstToPass(funcs, function (func) {
+          return func.timeoutKey === timeoutKey;
+        });
+
+        if (i > -1) {
+          if (funcs.length === 1) {
+            delete scheduledFunctions[runAtMillis];
+            deleteFromLookup(runAtMillis);
+          } else {
+            funcs.splice(i, 1);
+          }
+
+          // intervals get rescheduled when executed, so there's never more
+          // than a single scheduled function with a given timeoutKey
+          break;
+        }
+      }
+    };
+
+    return self;
+
+    function indexOfFirstToPass(array, testFn) {
+      var index = -1;
+
+      for (var i = 0; i < array.length; ++i) {
+        if (testFn(array[i])) {
+          index = i;
+          break;
+        }
+      }
+
+      return index;
+    }
+
+    function deleteFromLookup(key) {
+      var value = Number(key);
+      var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+        return millis === value;
+      });
+
+      if (i > -1) {
+        scheduledLookup.splice(i, 1);
+      }
+    }
+
+    function reschedule(scheduledFn) {
+      self.scheduleFunction(scheduledFn.funcToCall,
+          scheduledFn.millis,
+          scheduledFn.params,
+          true,
+          scheduledFn.timeoutKey,
+          scheduledFn.runAtMillis + scheduledFn.millis);
+    }
+
+    function forEachFunction(funcsToRun, callback) {
+      for (var i = 0; i < funcsToRun.length; ++i) {
+        callback(funcsToRun[i]);
+      }
+    }
+
+    function runScheduledFunctions(endTime, tickDate) {
+      tickDate = tickDate || function() {};
+      if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+        tickDate(endTime - currentTime);
+        return;
+      }
+
+      do {
+        var newCurrentTime = scheduledLookup.shift();
+        tickDate(newCurrentTime - currentTime);
+
+        currentTime = newCurrentTime;
+
+        var funcsToRun = scheduledFunctions[currentTime];
+        delete scheduledFunctions[currentTime];
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          if (funcToRun.recurring) {
+            reschedule(funcToRun);
+          }
+        });
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          funcToRun.funcToCall.apply(null, funcToRun.params || []);
+        });
+      } while (scheduledLookup.length > 0 &&
+      // checking first if we're out of time prevents setTimeout(0)
+      // scheduled in a funcToRun from forcing an extra iteration
+      currentTime !== endTime  &&
+      scheduledLookup[0] <= endTime);
+
+      // ran out of functions to call, but still time left on the clock
+      if (currentTime !== endTime) {
+        tickDate(endTime - currentTime);
+      }
+    }
+  }
+
+  return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().errors = function() {
+  function ExpectationFailed() {}
+
+  ExpectationFailed.prototype = new Error();
+  ExpectationFailed.prototype.constructor = ExpectationFailed;
+
+  return {
+    ExpectationFailed: ExpectationFailed
+  };
+};
+getJasmineRequireObj().ExceptionFormatter = function() {
+  function ExceptionFormatter() {
+    this.message = function(error) {
+      var message = '';
+
+      if (error.name && error.message) {
+        message += error.name + ': ' + error.message;
+      } else {
+        message += error.toString() + ' thrown';
+      }
+
+      if (error.fileName || error.sourceURL) {
+        message += ' in ' + (error.fileName || error.sourceURL);
+      }
+
+      if (error.line || error.lineNumber) {
+        message += ' (line ' + (error.line || error.lineNumber) + ')';
+      }
+
+      return message;
+    };
+
+    this.stack = function(error) {
+      return error ? error.stack : null;
+    };
+  }
+
+  return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+  /**
+   * Matchers that come with Jasmine out of the box.
+   * @namespace matchers
+   */
+  function Expectation(options) {
+    this.util = options.util || { buildFailureMessage: function() {} };
+    this.customEqualityTesters = options.customEqualityTesters || [];
+    this.actual = options.actual;
+    this.addExpectationResult = options.addExpectationResult || function(){};
+    this.isNot = options.isNot;
+
+    var customMatchers = options.customMatchers || {};
+    for (var matcherName in customMatchers) {
+      this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
+    }
+  }
+
+  Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+    return function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+          expected = args.slice(0),
+          message = '';
+
+      args.unshift(this.actual);
+
+      var matcher = matcherFactory(this.util, this.customEqualityTesters),
+          matcherCompare = matcher.compare;
+
+      function defaultNegativeCompare() {
+        var result = matcher.compare.apply(null, args);
+        result.pass = !result.pass;
+        return result;
+      }
+
+      if (this.isNot) {
+        matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+      }
+
+      var result = matcherCompare.apply(null, args);
+
+      if (!result.pass) {
+        if (!result.message) {
+          args.unshift(this.isNot);
+          args.unshift(name);
+          message = this.util.buildFailureMessage.apply(null, args);
+        } else {
+          if (Object.prototype.toString.apply(result.message) === '[object Function]') {
+            message = result.message();
+          } else {
+            message = result.message;
+          }
+        }
+      }
+
+      if (expected.length == 1) {
+        expected = expected[0];
+      }
+
+      // TODO: how many of these params are needed?
+      this.addExpectationResult(
+          result.pass,
+          {
+            matcherName: name,
+            passed: result.pass,
+            message: message,
+            error: result.error,
+            actual: this.actual,
+            expected: expected // TODO: this may need to be arrayified/sliced
+          }
+      );
+    };
+  };
+
+  Expectation.addCoreMatchers = function(matchers) {
+    var prototype = Expectation.prototype;
+    for (var matcherName in matchers) {
+      var matcher = matchers[matcherName];
+      prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+    }
+  };
+
+  Expectation.Factory = function(options) {
+    options = options || {};
+
+    var expect = new Expectation(options);
+
+    // TODO: this would be nice as its own Object - NegativeExpectation
+    // TODO: copy instead of mutate options
+    options.isNot = true;
+    expect.not = new Expectation(options);
+
+    return expect;
+  };
+
+  return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+  function buildExpectationResult(options) {
+    var messageFormatter = options.messageFormatter || function() {},
+        stackFormatter = options.stackFormatter || function() {};
+
+    var result = {
+      matcherName: options.matcherName,
+      message: message(),
+      stack: stack(),
+      passed: options.passed
+    };
+
+    if(!result.passed) {
+      result.expected = options.expected;
+      result.actual = options.actual;
+    }
+
+    return result;
+
+    function message() {
+      if (options.passed) {
+        return 'Passed.';
+      } else if (options.message) {
+        return options.message;
+      } else if (options.error) {
+        return messageFormatter(options.error);
+      }
+      return '';
+    }
+
+    function stack() {
+      if (options.passed) {
+        return '';
+      }
+
+      var error = options.error;
+      if (!error) {
+        try {
+          throw new Error(message());
+        } catch (e) {
+          error = e;
+        }
+      }
+      return stackFormatter(error);
+    }
+  }
+
+  return buildExpectationResult;
+};
+
+getJasmineRequireObj().formatErrorMsg = function() {
+  function generateErrorMsg(domain, usage) {
+    var usageDefinition = usage ? '\nUsage: ' + usage : '';
+
+    return function errorMsg(msg) {
+      return domain + ' : ' + msg + usageDefinition;
+    };
+  }
+
+  return generateErrorMsg;
+};
+
+getJasmineRequireObj().GlobalErrors = function(j$) {
+  function GlobalErrors(global) {
+    var handlers = [];
+    global = global || j$.getGlobal();
+
+    var onerror = function onerror() {
+      var handler = handlers[handlers.length - 1];
+      handler.apply(null, Array.prototype.slice.call(arguments, 0));
+    };
+
+    this.uninstall = function noop() {};
+
+    this.install = function install() {
+      if (global.process && global.process.listeners && j$.isFunction_(global.process.on)) {
+        var originalHandlers = global.process.listeners('uncaughtException');
+        global.process.removeAllListeners('uncaughtException');
+        global.process.on('uncaughtException', onerror);
+
+        this.uninstall = function uninstall() {
+          global.process.removeListener('uncaughtException', onerror);
+          for (var i = 0; i < originalHandlers.length; i++) {
+            global.process.on('uncaughtException', originalHandlers[i]);
+          }
+        };
+      } else {
+        var originalHandler = global.onerror;
+        global.onerror = onerror;
+
+        this.uninstall = function uninstall() {
+          global.onerror = originalHandler;
+        };
+      }
+    };
+
+    this.pushListener = function pushListener(listener) {
+      handlers.push(listener);
+    };
+
+    this.popListener = function popListener() {
+      handlers.pop();
+    };
+  }
+
+  return GlobalErrors;
+};
+
+getJasmineRequireObj().DiffBuilder = function(j$) {
+  return function DiffBuilder() {
+    var path = new j$.ObjectPath(),
+        mismatches = [];
+
+    return {
+      record: function (actual, expected, formatter) {
+        formatter = formatter || defaultFormatter;
+        mismatches.push(formatter(actual, expected, path));
+      },
+
+      getMessage: function () {
+        return mismatches.join('\n');
+      },
+
+      withPath: function (pathComponent, block) {
+        var oldPath = path;
+        path = path.add(pathComponent);
+        block();
+        path = oldPath;
+      }
+    };
+
+    function defaultFormatter (actual, expected, path) {
+      return 'Expected ' +
+          path + (path.depth() ? ' = ' : '') +
+          j$.pp(actual) +
+          ' to equal ' +
+          j$.pp(expected) +
+          '.';
+    }
+  };
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+  // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+  return {
+    equals: equals,
+
+    contains: function(haystack, needle, customTesters) {
+      customTesters = customTesters || [];
+
+      if ((Object.prototype.toString.apply(haystack) === '[object Set]')) {
+        return haystack.has(needle);
+      }
+
+      if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+          (!!haystack && !haystack.indexOf))
+      {
+        for (var i = 0; i < haystack.length; i++) {
+          if (equals(haystack[i], needle, customTesters)) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      return !!haystack && haystack.indexOf(needle) >= 0;
+    },
+
+    buildFailureMessage: function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+          matcherName = args[0],
+          isNot = args[1],
+          actual = args[2],
+          expected = args.slice(3),
+          englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+      var message = 'Expected ' +
+          j$.pp(actual) +
+          (isNot ? ' not ' : ' ') +
+          englishyPredicate;
+
+      if (expected.length > 0) {
+        for (var i = 0; i < expected.length; i++) {
+          if (i > 0) {
+            message += ',';
+          }
+          message += ' ' + j$.pp(expected[i]);
+        }
+      }
+
+      return message + '.';
+    }
+  };
+
+  function isAsymmetric(obj) {
+    return obj && j$.isA_('Function', obj.asymmetricMatch);
+  }
+
+  function asymmetricMatch(a, b, customTesters, diffBuilder) {
+    var asymmetricA = isAsymmetric(a),
+        asymmetricB = isAsymmetric(b),
+        result;
+
+    if (asymmetricA && asymmetricB) {
+      return undefined;
+    }
+
+    if (asymmetricA) {
+      result = a.asymmetricMatch(b, customTesters);
+      diffBuilder.record(a, b);
+      return result;
+    }
+
+    if (asymmetricB) {
+      result = b.asymmetricMatch(a, customTesters);
+      diffBuilder.record(a, b);
+      return result;
+    }
+  }
+
+  function equals(a, b, customTesters, diffBuilder) {
+    customTesters = customTesters || [];
+    diffBuilder = diffBuilder || j$.NullDiffBuilder();
+
+    return eq(a, b, [], [], customTesters, diffBuilder);
+  }
+
+  // Equality function lovingly adapted from isEqual in
+  //   [Underscore](http://underscorejs.org)
+  function eq(a, b, aStack, bStack, customTesters, diffBuilder) {
+    var result = true, i;
+
+    var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder);
+    if (!j$.util.isUndefined(asymmetricResult)) {
+      return asymmetricResult;
+    }
+
+    for (i = 0; i < customTesters.length; i++) {
+      var customTesterResult = customTesters[i](a, b);
+      if (!j$.util.isUndefined(customTesterResult)) {
+        if (!customTesterResult) {
+          diffBuilder.record(a, b);
+        }
+        return customTesterResult;
+      }
+    }
+
+    if (a instanceof Error && b instanceof Error) {
+      result = a.message == b.message;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) {
+      result = a !== 0 || 1 / a == 1 / b;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    // A strict comparison is necessary because `null == undefined`.
+    if (a === null || b === null) {
+      result = a === b;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    var className = Object.prototype.toString.call(a);
+    if (className != Object.prototype.toString.call(b)) {
+      diffBuilder.record(a, b);
+      return false;
+    }
+    switch (className) {
+        // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        result = a == String(b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        result = a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        result = +a == +b;
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+        // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+            a.global == b.global &&
+            a.multiline == b.multiline &&
+            a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') {
+      diffBuilder.record(a, b);
+      return false;
+    }
+
+    var aIsDomNode = j$.isDomNode(a);
+    var bIsDomNode = j$.isDomNode(b);
+    if (aIsDomNode && bIsDomNode) {
+      // At first try to use DOM3 method isEqualNode
+      if (a.isEqualNode) {
+        result = a.isEqualNode(b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      }
+      // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
+      var aIsElement = a instanceof Element;
+      var bIsElement = b instanceof Element;
+      if (aIsElement && bIsElement) {
+        result = a.outerHTML == b.outerHTML;
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      }
+      if (aIsElement || bIsElement) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+      result = a.innerText == b.innerText && a.textContent == b.textContent;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    if (aIsDomNode || bIsDomNode) {
+      diffBuilder.record(a, b);
+      return false;
+    }
+
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) { return bStack[length] == b; }
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0;
+    // Recursively compare objects and arrays.
+    // Compare array lengths to determine if a deep comparison is necessary.
+    if (className == '[object Array]') {
+      size = a.length;
+      if (size !== b.length) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+
+      for (i = 0; i < size; i++) {
+        diffBuilder.withPath(i, function() {
+          result = eq(a[i], b[i], aStack, bStack, customTesters, diffBuilder) && result;
+        });
+      }
+      if (!result) {
+        return false;
+      }
+    } else if (className == '[object Set]') {
+      if (a.size != b.size) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+      var iterA = a.values(), iterB = b.values();
+      var valA, valB;
+      do {
+        valA = iterA.next();
+        valB = iterB.next();
+        if (!eq(valA.value, valB.value, aStack, bStack, customTesters, j$.NullDiffBuilder())) {
+          diffBuilder.record(a, b);
+          return false;
+        }
+      } while (!valA.done && !valB.done);
+    } else {
+
+      // Objects with different constructors are not equivalent, but `Object`s
+      // or `Array`s from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor &&
+          isFunction(aCtor) && isFunction(bCtor) &&
+          a instanceof aCtor && b instanceof bCtor &&
+          !(aCtor instanceof aCtor && bCtor instanceof bCtor)) {
+
+        diffBuilder.record(a, b, constructorsAreDifferentFormatter);
+        return false;
+      }
+    }
+
+    // Deep compare objects.
+    var aKeys = keys(a, className == '[object Array]'), key;
+    size = aKeys.length;
+
+    // Ensure that both objects contain the same number of properties before comparing deep equality.
+    if (keys(b, className == '[object Array]').length !== size) {
+      diffBuilder.record(a, b, objectKeysAreDifferentFormatter);
+      return false;
+    }
+
+    for (i = 0; i < size; i++) {
+      key = aKeys[i];
+      // Deep compare each member
+      if (!j$.util.has(b, key)) {
+        diffBuilder.record(a, b, objectKeysAreDifferentFormatter);
+        result = false;
+        continue;
+      }
+
+      diffBuilder.withPath(key, function() {
+        if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) {
+          result = false;
+        }
+      });
+    }
+
+    if (!result) {
+      return false;
+    }
+
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+
+    return result;
+  }
+
+  function keys(obj, isArray) {
+    var allKeys = Object.keys ? Object.keys(obj) :
+        (function(o) {
+          var keys = [];
+          for (var key in o) {
+            if (j$.util.has(o, key)) {
+              keys.push(key);
+            }
+          }
+          return keys;
+        })(obj);
+
+    if (!isArray) {
+      return allKeys;
+    }
+
+    if (allKeys.length === 0) {
+      return allKeys;
+    }
+
+    var extraKeys = [];
+    for (var i = 0; i < allKeys.length; i++) {
+      if (!/^[0-9]+$/.test(allKeys[i])) {
+        extraKeys.push(allKeys[i]);
+      }
+    }
+
+    return extraKeys;
+  }
+
+  function has(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  }
+
+  function isFunction(obj) {
+    return typeof obj === 'function';
+  }
+
+  function objectKeysAreDifferentFormatter(actual, expected, path) {
+    var missingProperties = j$.util.objectDifference(expected, actual),
+        extraProperties = j$.util.objectDifference(actual, expected),
+        missingPropertiesMessage = formatKeyValuePairs(missingProperties),
+        extraPropertiesMessage = formatKeyValuePairs(extraProperties),
+        messages = [];
+
+    if (!path.depth()) {
+      path = 'object';
+    }
+
+    if (missingPropertiesMessage.length) {
+      messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage);
+    }
+
+    if (extraPropertiesMessage.length) {
+      messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage);
+    }
+
+    return messages.join('\n');
+  }
+
+  function constructorsAreDifferentFormatter(actual, expected, path) {
+    if (!path.depth()) {
+      path = 'object';
+    }
+
+    return 'Expected ' +
+        path + ' to be a kind of ' +
+        j$.fnNameFor(expected.constructor) +
+        ', but was ' + j$.pp(actual) + '.';
+  }
+
+  function formatKeyValuePairs(obj) {
+    var formatted = '';
+    for (var key in obj) {
+      formatted += '\n    ' + key + ': ' + j$.pp(obj[key]);
+    }
+    return formatted;
+  }
+};
+
+getJasmineRequireObj().NullDiffBuilder = function(j$) {
+  return function() {
+    return {
+      withPath: function(_, block) {
+        block();
+      },
+      record: function() {}
+    };
+  };
+};
+
+getJasmineRequireObj().ObjectPath = function(j$) {
+  function ObjectPath(components) {
+    this.components = components || [];
+  }
+
+  ObjectPath.prototype.toString = function() {
+    if (this.components.length) {
+      return '$' + map(this.components, formatPropertyAccess).join('');
+    } else {
+      return '';
+    }
+  };
+
+  ObjectPath.prototype.add = function(component) {
+    return new ObjectPath(this.components.concat([component]));
+  };
+
+  ObjectPath.prototype.depth = function() {
+    return this.components.length;
+  };
+
+  function formatPropertyAccess(prop) {
+    if (typeof prop === 'number') {
+      return '[' + prop + ']';
+    }
+
+    if (isValidIdentifier(prop)) {
+      return '.' + prop;
+    }
+
+    return '[\'' + prop + '\']';
+  }
+
+  function map(array, fn) {
+    var results = [];
+    for (var i = 0; i < array.length; i++) {
+      results.push(fn(array[i]));
+    }
+    return results;
+  }
+
+  function isValidIdentifier(string) {
+    return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string);
+  }
+
+  return ObjectPath;
+};
+
+getJasmineRequireObj().toBe = function() {
+  /**
+   * {@link expect} the actual value to be `===` to the expected value.
+   * @function
+   * @name matchers#toBe
+   * @param {Object} expected - The expected value to compare against.
+   * @example
+   * expect(thing).toBe(realThing);
+   */
+  function toBe() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual === expected
+        };
+      }
+    };
+  }
+
+  return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+  /**
+   * {@link expect} the actual value to be within a specified precision of the expected value.
+   * @function
+   * @name matchers#toBeCloseTo
+   * @param {Object} expected - The expected value to compare against.
+   * @param {Number} [precision=2] - The number of decimal points to check.
+   * @example
+   * expect(number).toBeCloseTo(42.2, 3);
+   */
+  function toBeCloseTo() {
+    return {
+      compare: function(actual, expected, precision) {
+        if (precision !== 0) {
+          precision = precision || 2;
+        }
+
+        return {
+          pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+        };
+      }
+    };
+  }
+
+  return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+  /**
+   * {@link expect} the actual value to be defined. (Not `undefined`)
+   * @function
+   * @name matchers#toBeDefined
+   * @example
+   * expect(result).toBeDefined();
+   */
+  function toBeDefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: (void 0 !== actual)
+        };
+      }
+    };
+  }
+
+  return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+  /**
+   * {@link expect} the actual value to be falsy
+   * @function
+   * @name matchers#toBeFalsy
+   * @example
+   * expect(result).toBeFalsy();
+   */
+  function toBeFalsy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!!actual
+        };
+      }
+    };
+  }
+
+  return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+  /**
+   * {@link expect} the actual value to be greater than the expected value.
+   * @function
+   * @name matchers#toBeGreaterThan
+   * @param {Number} expected - The value to compare against.
+   * @example
+   * expect(result).toBeGreaterThan(3);
+   */
+  function toBeGreaterThan() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual > expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeGreaterThanOrEqual = function() {
+  /**
+   * {@link expect} the actual value to be greater than or equal to the expected value.
+   * @function
+   * @name matchers#toBeGreaterThanOrEqual
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeGreaterThanOrEqual(25);
+   */
+  function toBeGreaterThanOrEqual() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual >= expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThanOrEqual;
+};
+
+getJasmineRequireObj().toBeLessThan = function() {
+  /**
+   * {@link expect} the actual value to be less than the expected value.
+   * @function
+   * @name matchers#toBeLessThan
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeLessThan(0);
+   */
+  function toBeLessThan() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual < expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThan;
+};
+
+getJasmineRequireObj().toBeLessThanOrEqual = function() {
+  /**
+   * {@link expect} the actual value to be less than or equal to the expected value.
+   * @function
+   * @name matchers#toBeLessThanOrEqual
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeLessThanOrEqual(123);
+   */
+  function toBeLessThanOrEqual() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual <= expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThanOrEqual;
+};
+
+getJasmineRequireObj().toBeNaN = function(j$) {
+  /**
+   * {@link expect} the actual value to be `NaN` (Not a Number).
+   * @function
+   * @name matchers#toBeNaN
+   * @example
+   * expect(thing).toBeNaN();
+   */
+  function toBeNaN() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual !== actual)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual not to be NaN.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNegativeInfinity = function(j$) {
+  /**
+   * {@link expect} the actual value to be `-Infinity` (-infinity).
+   * @function
+   * @name matchers#toBeNegativeInfinity
+   * @example
+   * expect(thing).toBeNegativeInfinity();
+   */
+  function toBeNegativeInfinity() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual === Number.NEGATIVE_INFINITY)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual to be -Infinity.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be -Infinity.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNegativeInfinity;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+  /**
+   * {@link expect} the actual value to be `null`.
+   * @function
+   * @name matchers#toBeNull
+   * @example
+   * expect(result).toBeNull();
+   */
+  function toBeNull() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: actual === null
+        };
+      }
+    };
+  }
+
+  return toBeNull;
+};
+
+getJasmineRequireObj().toBePositiveInfinity = function(j$) {
+  /**
+   * {@link expect} the actual value to be `Infinity` (infinity).
+   * @function
+   * @name matchers#toBePositiveInfinity
+   * @example
+   * expect(thing).toBePositiveInfinity();
+   */
+  function toBePositiveInfinity() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual === Number.POSITIVE_INFINITY)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual to be Infinity.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be Infinity.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBePositiveInfinity;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+  /**
+   * {@link expect} the actual value to be truthy.
+   * @function
+   * @name matchers#toBeTruthy
+   * @example
+   * expect(thing).toBeTruthy();
+   */
+  function toBeTruthy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!actual
+        };
+      }
+    };
+  }
+
+  return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+  /**
+   * {@link expect} the actual value to be `undefined`.
+   * @function
+   * @name matchers#toBeUndefined
+   * @example
+   * expect(result).toBeUndefined():
+   */
+  function toBeUndefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: void 0 === actual
+        };
+      }
+    };
+  }
+
+  return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+  /**
+   * {@link expect} the actual value to contain a specific value.
+   * @function
+   * @name matchers#toContain
+   * @param {Object} expected - The value to look for.
+   * @example
+   * expect(array).toContain(anElement);
+   * expect(string).toContain(substring);
+   */
+  function toContain(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+
+        return {
+          pass: util.contains(actual, expected, customEqualityTesters)
+        };
+      }
+    };
+  }
+
+  return toContain;
+};
+
+getJasmineRequireObj().toEqual = function(j$) {
+  /**
+   * {@link expect} the actual value to be equal to the expected, using deep equality comparison.
+   * @function
+   * @name matchers#toEqual
+   * @param {Object} expected - Expected value
+   * @example
+   * expect(bigObject).toEqual({"foo": ['bar', 'baz']});
+   */
+  function toEqual(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+        var result = {
+              pass: false
+            },
+            diffBuilder = j$.DiffBuilder();
+
+        result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder);
+
+        // TODO: only set error message if test fails
+        result.message = diffBuilder.getMessage();
+
+        return result;
+      }
+    };
+  }
+
+  return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalled>', 'expect(<spyObj>).toHaveBeenCalled()');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called.
+   * @function
+   * @name matchers#toHaveBeenCalled
+   * @example
+   * expect(mySpy).toHaveBeenCalled();
+   * expect(mySpy).not.toHaveBeenCalled();
+   */
+  function toHaveBeenCalled() {
+    return {
+      compare: function(actual) {
+        var result = {};
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (arguments.length > 1) {
+          throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith'));
+        }
+
+        result.pass = actual.calls.any();
+
+        result.message = result.pass ?
+            'Expected spy ' + actual.and.identity() + ' not to have been called.' :
+            'Expected spy ' + actual.and.identity() + ' to have been called.';
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledBefore>', 'expect(<spyObj>).toHaveBeenCalledBefore(<spyObj>)');
+
+  /**
+   * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}.
+   * @function
+   * @name matchers#toHaveBeenCalledBefore
+   * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}.
+   * @example
+   * expect(mySpy).toHaveBeenCalledBefore(otherSpy);
+   */
+  function toHaveBeenCalledBefore() {
+    return {
+      compare: function(firstSpy, latterSpy) {
+        if (!j$.isSpy(firstSpy)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.'));
+        }
+        if (!j$.isSpy(latterSpy)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.'));
+        }
+
+        var result = { pass: false };
+
+        if (!firstSpy.calls.count()) {
+          result.message = 'Expected spy ' +  firstSpy.and.identity() + ' to have been called.';
+          return result;
+        }
+        if (!latterSpy.calls.count()) {
+          result.message = 'Expected spy ' +  latterSpy.and.identity() + ' to have been called.';
+          return result;
+        }
+
+        var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder;
+        var first2ndSpyCall = latterSpy.calls.first().invocationOrder;
+
+        result.pass = latest1stSpyCall < first2ndSpyCall;
+
+        if (result.pass) {
+          result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was';
+        } else {
+          var first1stSpyCall = firstSpy.calls.first().invocationOrder;
+          var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder;
+
+          if(first1stSpyCall < first2ndSpyCall) {
+            result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)';
+          } else if (latest2ndSpyCall > latest1stSpyCall) {
+            result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)';
+          } else {
+            result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity();
+          }
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledBefore;
+};
+
+getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledTimes>', 'expect(<spyObj>).toHaveBeenCalledTimes(<Number>)');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times.
+   * @function
+   * @name matchers#toHaveBeenCalledTimes
+   * @param {Number} expected - The number of invocations to look for.
+   * @example
+   * expect(mySpy).toHaveBeenCalledTimes(3);
+   */
+  function toHaveBeenCalledTimes() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        var args = Array.prototype.slice.call(arguments, 0),
+            result = { pass: false };
+
+        if (!j$.isNumber_(expected)){
+          throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.'));
+        }
+
+        actual = args[0];
+        var calls = actual.calls.count();
+        var timesMessage = expected === 1 ? 'once' : expected + ' times';
+        result.pass = calls === expected;
+        result.message = result.pass ?
+            'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' +  calls + ' times.' :
+            'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' +  calls + ' times.';
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledTimes;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledWith>', 'expect(<spyObj>).toHaveBeenCalledWith(...arguments)');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once.
+   * @function
+   * @name matchers#toHaveBeenCalledWith
+   * @param {...Object} - The arguments to look for
+   * @example
+   * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2);
+   */
+  function toHaveBeenCalledWith(util, customEqualityTesters) {
+    return {
+      compare: function() {
+        var args = Array.prototype.slice.call(arguments, 0),
+            actual = args[0],
+            expectedArgs = args.slice(1),
+            result = { pass: false };
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (!actual.calls.any()) {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
+          return result;
+        }
+
+        if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+          result.pass = true;
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+        } else {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toMatch>', 'expect(<expectation>).toMatch(<string> || <regexp>)');
+
+  /**
+   * {@link expect} the actual value to match a regular expression
+   * @function
+   * @name matchers#toMatch
+   * @param {RegExp|String} expected - Value to look for in the string.
+   * @example
+   * expect("my string").toMatch(/string$/);
+   * expect("other string").toMatch("her");
+   */
+  function toMatch() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+          throw new Error(getErrorMsg('Expected is not a String or a RegExp'));
+        }
+
+        var regexp = new RegExp(expected);
+
+        return {
+          pass: regexp.test(actual)
+        };
+      }
+    };
+  }
+
+  return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toThrow>', 'expect(function() {<expectation>}).toThrow()');
+
+  /**
+   * {@link expect} a function to `throw` something.
+   * @function
+   * @name matchers#toThrow
+   * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that something was thrown will be checked.
+   * @example
+   * expect(function() { return 'things'; }).toThrow('foo');
+   * expect(function() { return 'stuff'; }).toThrow();
+   */
+  function toThrow(util) {
+    return {
+      compare: function(actual, expected) {
+        var result = { pass: false },
+            threw = false,
+            thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          result.message = 'Expected function to throw an exception.';
+          return result;
+        }
+
+        if (arguments.length == 1) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; };
+
+          return result;
+        }
+
+        if (util.equals(thrown, expected)) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+        } else {
+          result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' +  j$.pp(thrown) + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+
+  var getErrorMsg =  j$.formatErrorMsg('<toThrowError>', 'expect(function() {<expectation>}).toThrowError(<ErrorConstructor>, <message>)');
+
+  /**
+   * {@link expect} a function to `throw` an `Error`.
+   * @function
+   * @name matchers#toThrowError
+   * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used.
+   * @param {RegExp|String} [message] - The message that should be set on the thrown `Error`
+   * @example
+   * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message');
+   * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/);
+   * expect(function() { return 'stuff'; }).toThrowError(MyCustomError);
+   * expect(function() { return 'other'; }).toThrowError(/foo/);
+   * expect(function() { return 'other'; }).toThrowError();
+   */
+  function toThrowError () {
+    return {
+      compare: function(actual) {
+        var threw = false,
+            pass = {pass: true},
+            fail = {pass: false},
+            thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        var errorMatcher = getMatcher.apply(null, arguments);
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          fail.message = 'Expected function to throw an Error.';
+          return fail;
+        }
+
+        // Get Error constructor of thrown
+        if (!isErrorObject(thrown)) {
+          fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; };
+          return fail;
+        }
+
+        if (errorMatcher.hasNoSpecifics()) {
+          pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.';
+          return pass;
+        }
+
+        if (errorMatcher.matches(thrown)) {
+          pass.message = function() {
+            return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
+          };
+          return pass;
+        } else {
+          fail.message = function() {
+            return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
+                ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+          };
+          return fail;
+        }
+      }
+    };
+
+    function getMatcher() {
+      var expected = null,
+          errorType = null;
+
+      if (arguments.length == 2) {
+        expected = arguments[1];
+        if (isAnErrorType(expected)) {
+          errorType = expected;
+          expected = null;
+        }
+      } else if (arguments.length > 2) {
+        errorType = arguments[1];
+        expected = arguments[2];
+        if (!isAnErrorType(errorType)) {
+          throw new Error(getErrorMsg('Expected error type is not an Error.'));
+        }
+      }
+
+      if (expected && !isStringOrRegExp(expected)) {
+        if (errorType) {
+          throw new Error(getErrorMsg('Expected error message is not a string or RegExp.'));
+        } else {
+          throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.'));
+        }
+      }
+
+      function messageMatch(message) {
+        if (typeof expected == 'string') {
+          return expected == message;
+        } else {
+          return expected.test(message);
+        }
+      }
+
+      return {
+        errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
+        thrownDescription: function(thrown) {
+          var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+              thrownMessage = '';
+
+          if (expected) {
+            thrownMessage = ' with message ' + j$.pp(thrown.message);
+          }
+
+          return thrownName + thrownMessage;
+        },
+        messageDescription: function() {
+          if (expected === null) {
+            return '';
+          } else if (expected instanceof RegExp) {
+            return ' with a message matching ' + j$.pp(expected);
+          } else {
+            return ' with message ' + j$.pp(expected);
+          }
+        },
+        hasNoSpecifics: function() {
+          return expected === null && errorType === null;
+        },
+        matches: function(error) {
+          return (errorType === null || error instanceof errorType) &&
+              (expected === null || messageMatch(error.message));
+        }
+      };
+    }
+
+    function isStringOrRegExp(potential) {
+      return potential instanceof RegExp || (typeof potential == 'string');
+    }
+
+    function isAnErrorType(type) {
+      if (typeof type !== 'function') {
+        return false;
+      }
+
+      var Surrogate = function() {};
+      Surrogate.prototype = type.prototype;
+      return isErrorObject(new Surrogate());
+    }
+
+    function isErrorObject(thrown) {
+      if (thrown instanceof Error) {
+        return true;
+      }
+      if (thrown && thrown.constructor && thrown.constructor.constructor &&
+          (thrown instanceof (thrown.constructor.constructor('return this')()).Error)) {
+        return true;
+      }
+      return false;
+    }
+  }
+
+  return toThrowError;
+};
+
+getJasmineRequireObj().MockDate = function() {
+  function MockDate(global) {
+    var self = this;
+    var currentTime = 0;
+
+    if (!global || !global.Date) {
+      self.install = function() {};
+      self.tick = function() {};
+      self.uninstall = function() {};
+      return self;
+    }
+
+    var GlobalDate = global.Date;
+
+    self.install = function(mockDate) {
+      if (mockDate instanceof GlobalDate) {
+        currentTime = mockDate.getTime();
+      } else {
+        currentTime = new GlobalDate().getTime();
+      }
+
+      global.Date = FakeDate;
+    };
+
+    self.tick = function(millis) {
+      millis = millis || 0;
+      currentTime = currentTime + millis;
+    };
+
+    self.uninstall = function() {
+      currentTime = 0;
+      global.Date = GlobalDate;
+    };
+
+    createDateProperties();
+
+    return self;
+
+    function FakeDate() {
+      switch(arguments.length) {
+        case 0:
+          return new GlobalDate(currentTime);
+        case 1:
+          return new GlobalDate(arguments[0]);
+        case 2:
+          return new GlobalDate(arguments[0], arguments[1]);
+        case 3:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+        case 4:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+        case 5:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4]);
+        case 6:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4], arguments[5]);
+        default:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4], arguments[5], arguments[6]);
+      }
+    }
+
+    function createDateProperties() {
+      FakeDate.prototype = GlobalDate.prototype;
+
+      FakeDate.now = function() {
+        if (GlobalDate.now) {
+          return currentTime;
+        } else {
+          throw new Error('Browser does not support Date.now()');
+        }
+      };
+
+      FakeDate.toSource = GlobalDate.toSource;
+      FakeDate.toString = GlobalDate.toString;
+      FakeDate.parse = GlobalDate.parse;
+      FakeDate.UTC = GlobalDate.UTC;
+    }
+  }
+
+  return MockDate;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+  function PrettyPrinter() {
+    this.ppNestLevel_ = 0;
+    this.seen = [];
+  }
+
+  function hasCustomToString(value) {
+    // value.toString !== Object.prototype.toString if value has no custom toString but is from another context (e.g.
+    // iframe, web worker)
+    return value.toString !== Object.prototype.toString && (value.toString() !== Object.prototype.toString.call(value));
+  }
+
+  PrettyPrinter.prototype.format = function(value) {
+    this.ppNestLevel_++;
+    try {
+      if (j$.util.isUndefined(value)) {
+        this.emitScalar('undefined');
+      } else if (value === null) {
+        this.emitScalar('null');
+      } else if (value === 0 && 1/value === -Infinity) {
+        this.emitScalar('-0');
+      } else if (value === j$.getGlobal()) {
+        this.emitScalar('<global>');
+      } else if (value.jasmineToString) {
+        this.emitScalar(value.jasmineToString());
+      } else if (typeof value === 'string') {
+        this.emitString(value);
+      } else if (j$.isSpy(value)) {
+        this.emitScalar('spy on ' + value.and.identity());
+      } else if (value instanceof RegExp) {
+        this.emitScalar(value.toString());
+      } else if (typeof value === 'function') {
+        this.emitScalar('Function');
+      } else if (typeof value.nodeType === 'number') {
+        this.emitScalar('HTMLNode');
+      } else if (value instanceof Date) {
+        this.emitScalar('Date(' + value + ')');
+      } else if (value.toString && value.toString() == '[object Set]') {
+        this.emitSet(value);
+      } else if (value.toString && typeof value === 'object' && !j$.isArray_(value) && hasCustomToString(value)) {
+        this.emitScalar(value.toString());
+      } else if (j$.util.arrayContains(this.seen, value)) {
+        this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
+      } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+        this.seen.push(value);
+        if (j$.isArray_(value)) {
+          this.emitArray(value);
+        } else {
+          this.emitObject(value);
+        }
+        this.seen.pop();
+      } else {
+        this.emitScalar(value.toString());
+      }
+    } finally {
+      this.ppNestLevel_--;
+    }
+  };
+
+  PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+    for (var property in obj) {
+      if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
+      fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+      obj.__lookupGetter__(property) !== null) : false);
+    }
+  };
+
+  PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitSet = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+  function StringPrettyPrinter() {
+    PrettyPrinter.call(this);
+
+    this.string = '';
+  }
+
+  j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+  StringPrettyPrinter.prototype.emitScalar = function(value) {
+    this.append(value);
+  };
+
+  StringPrettyPrinter.prototype.emitString = function(value) {
+    this.append('\'' + value + '\'');
+  };
+
+  StringPrettyPrinter.prototype.emitArray = function(array) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append('Array');
+      return;
+    }
+    var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+    this.append('[ ');
+    for (var i = 0; i < length; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(array[i]);
+    }
+    if(array.length > length){
+      this.append(', ...');
+    }
+
+    var self = this;
+    var first = array.length === 0;
+    this.iterateObject(array, function(property, isGetter) {
+      if (property.match(/^\d+$/)) {
+        return;
+      }
+
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(array, property, isGetter);
+    });
+
+    this.append(' ]');
+  };
+
+  StringPrettyPrinter.prototype.emitSet = function(set) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append('Set');
+      return;
+    }
+    this.append('Set( ');
+    var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+    var iter = set.values();
+    for (var i = 0; i < size; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(iter.next().value);
+    }
+    if (set.size > size){
+      this.append(', ...');
+    }
+    this.append(' )');
+  };
+
+  StringPrettyPrinter.prototype.emitObject = function(obj) {
+    var ctor = obj.constructor,
+        constructorName;
+
+    constructorName = typeof ctor === 'function' && obj instanceof ctor ?
+        j$.fnNameFor(obj.constructor) :
+        'null';
+
+    this.append(constructorName);
+
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      return;
+    }
+
+    var self = this;
+    this.append('({ ');
+    var first = true;
+
+    this.iterateObject(obj, function(property, isGetter) {
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(obj, property, isGetter);
+    });
+
+    this.append(' })');
+  };
+
+  StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
+    this.append(property);
+    this.append(': ');
+    if (isGetter) {
+      this.append('<getter>');
+    } else {
+      this.format(obj[property]);
+    }
+  };
+
+  StringPrettyPrinter.prototype.append = function(value) {
+    this.string += value;
+  };
+
+  return function(value) {
+    var stringPrettyPrinter = new StringPrettyPrinter();
+    stringPrettyPrinter.format(value);
+    return stringPrettyPrinter.string;
+  };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+
+  function once(fn) {
+    var called = false;
+    return function() {
+      if (!called) {
+        called = true;
+        fn();
+      }
+      return null;
+    };
+  }
+
+  function QueueRunner(attrs) {
+    this.queueableFns = attrs.queueableFns || [];
+    this.onComplete = attrs.onComplete || function() {};
+    this.clearStack = attrs.clearStack || function(fn) {fn();};
+    this.onException = attrs.onException || function() {};
+    this.catchException = attrs.catchException || function() { return true; };
+    this.userContext = attrs.userContext || {};
+    this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+    this.fail = attrs.fail || function() {};
+    this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} };
+  }
+
+  QueueRunner.prototype.execute = function() {
+    this.run(this.queueableFns, 0);
+  };
+
+  QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+    var length = queueableFns.length,
+        self = this,
+        iterativeIndex;
+
+
+    for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+      var queueableFn = queueableFns[iterativeIndex];
+      if (queueableFn.fn.length > 0) {
+        attemptAsync(queueableFn);
+        return;
+      } else {
+        attemptSync(queueableFn);
+      }
+    }
+
+    this.clearStack(this.onComplete);
+
+    function attemptSync(queueableFn) {
+      try {
+        queueableFn.fn.call(self.userContext);
+      } catch (e) {
+        handleException(e, queueableFn);
+      }
+    }
+
+    function attemptAsync(queueableFn) {
+      var clearTimeout = function () {
+            Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+          },
+          handleError = function(error) {
+            onException(error);
+            next();
+          },
+          next = once(function () {
+            clearTimeout(timeoutId);
+            self.globalErrors.popListener(handleError);
+            self.run(queueableFns, iterativeIndex + 1);
+          }),
+          timeoutId;
+
+      next.fail = function() {
+        self.fail.apply(null, arguments);
+        next();
+      };
+
+      self.globalErrors.pushListener(handleError);
+
+      if (queueableFn.timeout) {
+        timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
+          var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+          onException(error);
+          next();
+        }, queueableFn.timeout()]]);
+      }
+
+      try {
+        queueableFn.fn.call(self.userContext, next);
+      } catch (e) {
+        handleException(e, queueableFn);
+        next();
+      }
+    }
+
+    function onException(e) {
+      self.onException(e);
+    }
+
+    function handleException(e, queueableFn) {
+      onException(e);
+      if (!self.catchException(e)) {
+        //TODO: set a var when we catch an exception and
+        //use a finally block to close the loop in a nice way..
+        throw e;
+      }
+    }
+  };
+
+  return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+  function ReportDispatcher(methods) {
+
+    var dispatchedMethods = methods || [];
+
+    for (var i = 0; i < dispatchedMethods.length; i++) {
+      var method = dispatchedMethods[i];
+      this[method] = (function(m) {
+        return function() {
+          dispatch(m, arguments);
+        };
+      }(method));
+    }
+
+    var reporters = [];
+    var fallbackReporter = null;
+
+    this.addReporter = function(reporter) {
+      reporters.push(reporter);
+    };
+
+    this.provideFallbackReporter = function(reporter) {
+      fallbackReporter = reporter;
+    };
+
+    this.clearReporters = function() {
+      reporters = [];
+    };
+
+    return this;
+
+    function dispatch(method, args) {
+      if (reporters.length === 0 && fallbackReporter !== null) {
+        reporters.push(fallbackReporter);
+      }
+      for (var i = 0; i < reporters.length; i++) {
+        var reporter = reporters[i];
+        if (reporter[method]) {
+          reporter[method].apply(reporter, args);
+        }
+      }
+    }
+  }
+
+  return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+  var jasmineInterface = {
+    /**
+     * Create a group of specs (often called a suite).
+     *
+     * Calls to `describe` can be nested within other calls to compose your suite as a tree.
+     * @name describe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    describe: function(description, specDefinitions) {
+      return env.describe(description, specDefinitions);
+    },
+
+    /**
+     * A temporarily disabled [`describe`]{@link describe}
+     *
+     * Specs within an `xdescribe` will be marked pending and not executed
+     * @name xdescribe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    xdescribe: function(description, specDefinitions) {
+      return env.xdescribe(description, specDefinitions);
+    },
+
+    /**
+     * A focused [`describe`]{@link describe}
+     *
+     * If suites or specs are focused, only those that are focused will be executed
+     * @see fit
+     * @name fdescribe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    fdescribe: function(description, specDefinitions) {
+      return env.fdescribe(description, specDefinitions);
+    },
+
+    /**
+     * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the state of the code.
+     *
+     * A spec whose expectations all succeed will be passing and a spec with any failures will fail.
+     * @name it
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking
+     * @param {Function} [testFunction] Function that contains the code of your test. If not provided the test will be `pending`.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+     */
+    it: function() {
+      return env.it.apply(env, arguments);
+    },
+
+    /**
+     * A temporarily disabled [`it`]{@link it}
+     *
+     * The spec will report as `pending` and will not be executed.
+     * @name xit
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking.
+     * @param {Function} [testFunction] Function that contains the code of your test. Will not be executed.
+     */
+    xit: function() {
+      return env.xit.apply(env, arguments);
+    },
+
+    /**
+     * A focused [`it`]{@link it}
+     *
+     * If suites or specs are focused, only those that are focused will be executed.
+     * @name fit
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking.
+     * @param {Function} testFunction Function that contains the code of your test.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+     */
+    fit: function() {
+      return env.fit.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared setup before each of the specs in the {@link describe} in which it is called.
+     * @name beforeEach
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to setup your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeEach.
+     */
+    beforeEach: function() {
+      return env.beforeEach.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared teardown after each of the specs in the {@link describe} in which it is called.
+     * @name afterEach
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to teardown your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach.
+     */
+    afterEach: function() {
+      return env.afterEach.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared setup once before all of the specs in the {@link describe} are run.
+     *
+     * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail.
+     * @name beforeAll
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to setup your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll.
+     */
+    beforeAll: function() {
+      return env.beforeAll.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared teardown once before all of the specs in the {@link describe} are run.
+     *
+     * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail.
+     * @name afterAll
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to teardown your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll.
+     */
+    afterAll: function() {
+      return env.afterAll.apply(env, arguments);
+    },
+
+    /**
+     * Create an expectation for a spec.
+     * @name expect
+     * @function
+     * @global
+     * @param {Object} actual - Actual computed value to test expectations against.
+     * @return {matchers}
+     */
+    expect: function(actual) {
+      return env.expect(actual);
+    },
+
+    /**
+     * Mark a spec as pending, expectation results will be ignored.
+     * @name pending
+     * @function
+     * @global
+     * @param {String} [message] - Reason the spec is pending.
+     */
+    pending: function() {
+      return env.pending.apply(env, arguments);
+    },
+
+    /**
+     * Explicitly mark a spec as failed.
+     * @name fail
+     * @function
+     * @global
+     * @param {String|Error} [error] - Reason for the failure.
+     */
+    fail: function() {
+      return env.fail.apply(env, arguments);
+    },
+
+    /**
+     * Install a spy onto an existing object.
+     * @name spyOn
+     * @function
+     * @global
+     * @param {Object} obj - The object upon which to install the {@link Spy}.
+     * @param {String} methodName - The name of the method to replace with a {@link Spy}.
+     * @returns {Spy}
+     */
+    spyOn: function(obj, methodName) {
+      return env.spyOn(obj, methodName);
+    },
+
+    /**
+     * Install a spy on a property onto an existing object.
+     * @name spyOnProperty
+     * @function
+     * @global
+     * @param {Object} obj - The object upon which to install the {@link Spy}
+     * @param {String} propertyName - The name of the property to replace with a {@link Spy}.
+     * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on.
+     * @returns {Spy}
+     */
+    spyOnProperty: function(obj, methodName, accessType) {
+      return env.spyOnProperty(obj, methodName, accessType);
+    },
+
+    jsApiReporter: new jasmine.JsApiReporter({
+      timer: new jasmine.Timer()
+    }),
+
+    /**
+     * @namespace jasmine
+     */
+    jasmine: jasmine
+  };
+
+  /**
+   * Add a custom equality tester for the current scope of specs.
+   *
+   * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+   * @name jasmine.addCustomEqualityTester
+   * @function
+   * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or `false` comparison result if it knows how to compare them, and `undefined` otherwise.
+   * @see custom_equality
+   */
+  jasmine.addCustomEqualityTester = function(tester) {
+    env.addCustomEqualityTester(tester);
+  };
+
+  /**
+   * Add custom matchers for the current scope of specs.
+   *
+   * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+   * @name jasmine.addMatchers
+   * @function
+   * @param {Object} matchers - Keys from this object will be the new matcher names.
+   * @see custom_matcher
+   */
+  jasmine.addMatchers = function(matchers) {
+    return env.addMatchers(matchers);
+  };
+
+  /**
+   * Get the currently booted mock {Clock} for this Jasmine environment.
+   * @name jasmine.clock
+   * @function
+   * @returns {Clock}
+   */
+  jasmine.clock = function() {
+    return env.clock;
+  };
+
+  return jasmineInterface;
+};
+
+getJasmineRequireObj().Spy = function (j$) {
+
+  var nextOrder = (function() {
+    var order = 0;
+
+    return function() {
+      return order++;
+    };
+  })();
+
+  /**
+   * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj}
+   * @constructor
+   * @name Spy
+   */
+  function Spy(name, originalFn) {
+    var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0),
+        wrapper = makeFunc(numArgs, function () {
+          return spy.apply(this, Array.prototype.slice.call(arguments));
+        }),
+        spyStrategy = new j$.SpyStrategy({
+          name: name,
+          fn: originalFn,
+          getSpy: function () {
+            return wrapper;
+          }
+        }),
+        callTracker = new j$.CallTracker(),
+        spy = function () {
+          /**
+           * @name Spy.callData
+           * @property {object} object - `this` context for the invocation.
+           * @property {number} invocationOrder - Order of the invocation.
+           * @property {Array} args - The arguments passed for this invocation.
+           */
+          var callData = {
+            object: this,
+            invocationOrder: nextOrder(),
+            args: Array.prototype.slice.apply(arguments)
+          };
+
+          callTracker.track(callData);
+          var returnValue = spyStrategy.exec.apply(this, arguments);
+          callData.returnValue = returnValue;
+
+          return returnValue;
+        };
+
+    function makeFunc(length, fn) {
+      switch (length) {
+        case 1 : return function (a) { return fn.apply(this, arguments); };
+        case 2 : return function (a,b) { return fn.apply(this, arguments); };
+        case 3 : return function (a,b,c) { return fn.apply(this, arguments); };
+        case 4 : return function (a,b,c,d) { return fn.apply(this, arguments); };
+        case 5 : return function (a,b,c,d,e) { return fn.apply(this, arguments); };
+        case 6 : return function (a,b,c,d,e,f) { return fn.apply(this, arguments); };
+        case 7 : return function (a,b,c,d,e,f,g) { return fn.apply(this, arguments); };
+        case 8 : return function (a,b,c,d,e,f,g,h) { return fn.apply(this, arguments); };
+        case 9 : return function (a,b,c,d,e,f,g,h,i) { return fn.apply(this, arguments); };
+        default : return function () { return fn.apply(this, arguments); };
+      }
+    }
+
+    for (var prop in originalFn) {
+      if (prop === 'and' || prop === 'calls') {
+        throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
+      }
+
+      wrapper[prop] = originalFn[prop];
+    }
+
+    wrapper.and = spyStrategy;
+    wrapper.calls = callTracker;
+
+    return wrapper;
+  }
+
+  return Spy;
+};
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
+
+  function SpyRegistry(options) {
+    options = options || {};
+    var currentSpies = options.currentSpies || function() { return []; };
+
+    this.allowRespy = function(allow){
+      this.respy = allow;
+    };
+
+    this.spyOn = function(obj, methodName) {
+
+      if (j$.util.isUndefined(obj) || obj === null) {
+        throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()'));
+      }
+
+      if (j$.util.isUndefined(methodName) || methodName === null) {
+        throw new Error(getErrorMsg('No method name supplied'));
+      }
+
+      if (j$.util.isUndefined(obj[methodName])) {
+        throw new Error(getErrorMsg(methodName + '() method does not exist'));
+      }
+
+      if (obj[methodName] && j$.isSpy(obj[methodName])  ) {
+        if ( !!this.respy ){
+          return obj[methodName];
+        }else {
+          throw new Error(getErrorMsg(methodName + ' has already been spied upon'));
+        }
+      }
+
+      var descriptor;
+      try {
+        descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
+      } catch(e) {
+        // IE 8 doesn't support `definePropery` on non-DOM nodes
+      }
+
+      if (descriptor && !(descriptor.writable || descriptor.set)) {
+        throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter'));
+      }
+
+      var originalMethod = obj[methodName],
+          spiedMethod = j$.createSpy(methodName, originalMethod),
+          restoreStrategy;
+
+      if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
+        restoreStrategy = function() {
+          obj[methodName] = originalMethod;
+        };
+      } else {
+        restoreStrategy = function() {
+          if (!delete obj[methodName]) {
+            obj[methodName] = originalMethod;
+          }
+        };
+      }
+
+      currentSpies().push({
+        restoreObjectToOriginalState: restoreStrategy
+      });
+
+      obj[methodName] = spiedMethod;
+
+      return spiedMethod;
+    };
+
+    this.spyOnProperty = function (obj, propertyName, accessType) {
+      accessType = accessType || 'get';
+
+      if (j$.util.isUndefined(obj)) {
+        throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
+      }
+
+      if (j$.util.isUndefined(propertyName)) {
+        throw new Error('No property name supplied');
+      }
+
+      var descriptor;
+      try {
+        descriptor = j$.util.getPropertyDescriptor(obj, propertyName);
+      } catch(e) {
+        // IE 8 doesn't support `definePropery` on non-DOM nodes
+      }
+
+      if (!descriptor) {
+        throw new Error(propertyName + ' property does not exist');
+      }
+
+      if (!descriptor.configurable) {
+        throw new Error(propertyName + ' is not declared configurable');
+      }
+
+      if(!descriptor[accessType]) {
+        throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
+      }
+
+      if (j$.isSpy(descriptor[accessType])) {
+        //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+        throw new Error(propertyName + ' has already been spied upon');
+      }
+
+      var originalDescriptor = j$.util.clone(descriptor),
+          spy = j$.createSpy(propertyName, descriptor[accessType]),
+          restoreStrategy;
+
+      if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
+        restoreStrategy = function() {
+          Object.defineProperty(obj, propertyName, originalDescriptor);
+        };
+      } else {
+        restoreStrategy = function() {
+          delete obj[propertyName];
+        };
+      }
+
+      currentSpies().push({
+        restoreObjectToOriginalState: restoreStrategy
+      });
+
+      descriptor[accessType] = spy;
+
+      Object.defineProperty(obj, propertyName, descriptor);
+
+      return spy;
+    };
+
+    this.clearSpies = function() {
+      var spies = currentSpies();
+      for (var i = spies.length - 1; i >= 0; i--) {
+        var spyEntry = spies[i];
+        spyEntry.restoreObjectToOriginalState();
+      }
+    };
+  }
+
+  return SpyRegistry;
+};
+
+getJasmineRequireObj().SpyStrategy = function(j$) {
+
+  /**
+   * @namespace Spy#and
+   */
+  function SpyStrategy(options) {
+    options = options || {};
+
+    var identity = options.name || 'unknown',
+        originalFn = options.fn || function() {},
+        getSpy = options.getSpy || function() {},
+        plan = function() {};
+
+    /**
+     * Return the identifying information for the spy.
+     * @name Spy#and#identity
+     * @function
+     * @returns {String}
+     */
+    this.identity = function() {
+      return identity;
+    };
+
+    /**
+     * Execute the current spy strategy.
+     * @name Spy#and#exec
+     * @function
+     */
+    this.exec = function() {
+      return plan.apply(this, arguments);
+    };
+
+    /**
+     * Tell the spy to call through to the real implementation when invoked.
+     * @name Spy#and#callThrough
+     * @function
+     */
+    this.callThrough = function() {
+      plan = originalFn;
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to return the value when invoked.
+     * @name Spy#and#returnValue
+     * @function
+     * @param {*} value The value to return.
+     */
+    this.returnValue = function(value) {
+      plan = function() {
+        return value;
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked.
+     * @name Spy#and#returnValues
+     * @function
+     * @param {...*} values - Values to be returned on subsequent calls to the spy.
+     */
+    this.returnValues = function() {
+      var values = Array.prototype.slice.call(arguments);
+      plan = function () {
+        return values.shift();
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to throw an error when invoked.
+     * @name Spy#and#throwError
+     * @function
+     * @param {Error|String} something Thing to throw
+     */
+    this.throwError = function(something) {
+      var error = (something instanceof Error) ? something : new Error(something);
+      plan = function() {
+        throw error;
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to call a fake implementation when invoked.
+     * @name Spy#and#callFake
+     * @function
+     * @param {Function} fn The function to invoke with the passed parameters.
+     */
+    this.callFake = function(fn) {
+      if(!j$.isFunction_(fn)) {
+        throw new Error('Argument passed to callFake should be a function, got ' + fn);
+      }
+      plan = fn;
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to do nothing when invoked. This is the default.
+     * @name Spy#and#stub
+     * @function
+     */
+    this.stub = function(fn) {
+      plan = function() {};
+      return getSpy();
+    };
+  }
+
+  return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function(j$) {
+  function Suite(attrs) {
+    this.env = attrs.env;
+    this.id = attrs.id;
+    this.parentSuite = attrs.parentSuite;
+    this.description = attrs.description;
+    this.expectationFactory = attrs.expectationFactory;
+    this.expectationResultFactory = attrs.expectationResultFactory;
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    this.beforeFns = [];
+    this.afterFns = [];
+    this.beforeAllFns = [];
+    this.afterAllFns = [];
+
+    this.children = [];
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: []
+    };
+  }
+
+  Suite.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Suite.prototype.getFullName = function() {
+    var fullName = [];
+    for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) {
+      if (parentSuite.parentSuite) {
+        fullName.unshift(parentSuite.description);
+      }
+    }
+    return fullName.join(' ');
+  };
+
+  Suite.prototype.pend = function() {
+    this.markedPending = true;
+  };
+
+  Suite.prototype.beforeEach = function(fn) {
+    this.beforeFns.unshift(fn);
+  };
+
+  Suite.prototype.beforeAll = function(fn) {
+    this.beforeAllFns.push(fn);
+  };
+
+  Suite.prototype.afterEach = function(fn) {
+    this.afterFns.unshift(fn);
+  };
+
+  Suite.prototype.afterAll = function(fn) {
+    this.afterAllFns.unshift(fn);
+  };
+
+  Suite.prototype.addChild = function(child) {
+    this.children.push(child);
+  };
+
+  Suite.prototype.status = function() {
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'finished';
+    }
+  };
+
+  Suite.prototype.isExecutable = function() {
+    return !this.markedPending;
+  };
+
+  Suite.prototype.canBeReentered = function() {
+    return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
+  };
+
+  Suite.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Suite.prototype.sharedUserContext = function() {
+    if (!this.sharedContext) {
+      this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+    }
+
+    return this.sharedContext;
+  };
+
+  Suite.prototype.clonedSharedUserContext = function() {
+    return clone(this.sharedUserContext());
+  };
+
+  Suite.prototype.onException = function() {
+    if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    if(isAfterAll(this.children)) {
+      var data = {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        error: arguments[0]
+      };
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        child.onException.apply(child, arguments);
+      }
+    }
+  };
+
+  Suite.prototype.addExpectationResult = function () {
+    if(isAfterAll(this.children) && isFailure(arguments)){
+      var data = arguments[1];
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+      if(this.throwOnExpectationFailure) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        try {
+          child.addExpectationResult.apply(child, arguments);
+        } catch(e) {
+          // keep going
+        }
+      }
+    }
+  };
+
+  function isAfterAll(children) {
+    return children && children[0].result.status;
+  }
+
+  function isFailure(args) {
+    return !args[0];
+  }
+
+  function clone(obj) {
+    var clonedObj = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        clonedObj[prop] = obj[prop];
+      }
+    }
+
+    return clonedObj;
+  }
+
+  return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+  var defaultNow = (function(Date) {
+    return function() { return new Date().getTime(); };
+  })(Date);
+
+  function Timer(options) {
+    options = options || {};
+
+    var now = options.now || defaultNow,
+        startTime;
+
+    this.start = function() {
+      startTime = now();
+    };
+
+    this.elapsed = function() {
+      return now() - startTime;
+    };
+  }
+
+  return Timer;
+};
+
+getJasmineRequireObj().TreeProcessor = function() {
+  function TreeProcessor(attrs) {
+    var tree = attrs.tree,
+        runnableIds = attrs.runnableIds,
+        queueRunnerFactory = attrs.queueRunnerFactory,
+        nodeStart = attrs.nodeStart || function() {},
+        nodeComplete = attrs.nodeComplete || function() {},
+        orderChildren = attrs.orderChildren || function(node) { return node.children; },
+        stats = { valid: true },
+        processed = false,
+        defaultMin = Infinity,
+        defaultMax = 1 - Infinity;
+
+    this.processTree = function() {
+      processNode(tree, false);
+      processed = true;
+      return stats;
+    };
+
+    this.execute = function(done) {
+      if (!processed) {
+        this.processTree();
+      }
+
+      if (!stats.valid) {
+        throw 'invalid order';
+      }
+
+      var childFns = wrapChildren(tree, 0);
+
+      queueRunnerFactory({
+        queueableFns: childFns,
+        userContext: tree.sharedUserContext(),
+        onException: function() {
+          tree.onException.apply(tree, arguments);
+        },
+        onComplete: done
+      });
+    };
+
+    function runnableIndex(id) {
+      for (var i = 0; i < runnableIds.length; i++) {
+        if (runnableIds[i] === id) {
+          return i;
+        }
+      }
+    }
+
+    function processNode(node, parentEnabled) {
+      var executableIndex = runnableIndex(node.id);
+
+      if (executableIndex !== undefined) {
+        parentEnabled = true;
+      }
+
+      parentEnabled = parentEnabled && node.isExecutable();
+
+      if (!node.children) {
+        stats[node.id] = {
+          executable: parentEnabled && node.isExecutable(),
+          segments: [{
+            index: 0,
+            owner: node,
+            nodes: [node],
+            min: startingMin(executableIndex),
+            max: startingMax(executableIndex)
+          }]
+        };
+      } else {
+        var hasExecutableChild = false;
+
+        var orderedChildren = orderChildren(node);
+
+        for (var i = 0; i < orderedChildren.length; i++) {
+          var child = orderedChildren[i];
+
+          processNode(child, parentEnabled);
+
+          if (!stats.valid) {
+            return;
+          }
+
+          var childStats = stats[child.id];
+
+          hasExecutableChild = hasExecutableChild || childStats.executable;
+        }
+
+        stats[node.id] = {
+          executable: hasExecutableChild
+        };
+
+        segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
+
+        if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
+          stats = { valid: false };
+        }
+      }
+    }
+
+    function startingMin(executableIndex) {
+      return executableIndex === undefined ? defaultMin : executableIndex;
+    }
+
+    function startingMax(executableIndex) {
+      return executableIndex === undefined ? defaultMax : executableIndex;
+    }
+
+    function segmentChildren(node, orderedChildren, nodeStats, executableIndex) {
+      var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
+          result = [currentSegment],
+          lastMax = defaultMax,
+          orderedChildSegments = orderChildSegments(orderedChildren);
+
+      function isSegmentBoundary(minIndex) {
+        return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+      }
+
+      for (var i = 0; i < orderedChildSegments.length; i++) {
+        var childSegment = orderedChildSegments[i],
+            maxIndex = childSegment.max,
+            minIndex = childSegment.min;
+
+        if (isSegmentBoundary(minIndex)) {
+          currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
+          result.push(currentSegment);
+        }
+
+        currentSegment.nodes.push(childSegment);
+        currentSegment.min = Math.min(currentSegment.min, minIndex);
+        currentSegment.max = Math.max(currentSegment.max, maxIndex);
+        lastMax = maxIndex;
+      }
+
+      nodeStats.segments = result;
+    }
+
+    function orderChildSegments(children) {
+      var specifiedOrder = [],
+          unspecifiedOrder = [];
+
+      for (var i = 0; i < children.length; i++) {
+        var child = children[i],
+            segments = stats[child.id].segments;
+
+        for (var j = 0; j < segments.length; j++) {
+          var seg = segments[j];
+
+          if (seg.min === defaultMin) {
+            unspecifiedOrder.push(seg);
+          } else {
+            specifiedOrder.push(seg);
+          }
+        }
+      }
+
+      specifiedOrder.sort(function(a, b) {
+        return a.min - b.min;
+      });
+
+      return specifiedOrder.concat(unspecifiedOrder);
+    }
+
+    function executeNode(node, segmentNumber) {
+      if (node.children) {
+        return {
+          fn: function(done) {
+            nodeStart(node);
+
+            queueRunnerFactory({
+              onComplete: function() {
+                nodeComplete(node, node.getResult());
+                done();
+              },
+              queueableFns: wrapChildren(node, segmentNumber),
+              userContext: node.sharedUserContext(),
+              onException: function() {
+                node.onException.apply(node, arguments);
+              }
+            });
+          }
+        };
+      } else {
+        return {
+          fn: function(done) { node.execute(done, stats[node.id].executable); }
+        };
+      }
+    }
+
+    function wrapChildren(node, segmentNumber) {
+      var result = [],
+          segmentChildren = stats[node.id].segments[segmentNumber].nodes;
+
+      for (var i = 0; i < segmentChildren.length; i++) {
+        result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
+      }
+
+      if (!stats[node.id].executable) {
+        return result;
+      }
+
+      return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+    }
+  }
+
+  return TreeProcessor;
+};
+
+getJasmineRequireObj().version = function() {
+  return '2.6.1';
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/load_reorder.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/load_reorder.js
new file mode 100644
index 0000000000000000000000000000000000000000..a9dc33d4f14394241a16b3d4457b3d6b645b32b4
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/load_reorder.js
@@ -0,0 +1,72 @@
+describe("Loader", function () {
+  describe("reorder", function () {
+    var oldIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
+    var newIds = [1, 2, 4, 3, 5, 11, 6, 7, 8, 9, 10, 12, 19, 14, 16, 21, 15, 17, 22, 20];
+    var targetIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 19, 20, 21, 22];
+    var codeTable = null;
+
+    /**
+     *
+     * @param {Array.<{id:int}>} array
+     * @return {Array.<int>}
+     */
+    function getIdArray(array) {
+      return array.map(function (item) {
+        return item.id;
+      });
+      // var result = [];
+      // for (var i = 0; i < array.length; i++) {
+      //   result.push(array[i].id);
+      // }
+      // return result;
+    }
+
+    /**
+     *
+     * @param {Array.<int>} idArray
+     * @return {Array.<int>}
+     */
+    function encodeIdArray(idArray) {
+      if (codeTable === null) {
+        codeTable = [];
+        while (codeTable.length < 25) {
+          var code = getRandomInt(1000, 5000);
+          if (codeTable.indexOf(code) > -1) continue;
+          codeTable.push(code);
+        }
+      }
+      return idArray.map(function (id) {
+        return codeTable[id];
+      });
+    }
+
+    function getRandomInt(min, max) {
+      min = Math.ceil(min);
+      max = Math.floor(max);
+      return Math.floor(Math.random() * (max - min)) + min;
+    }
+
+    function toDataArray(idArray) {
+      var array = [];
+      for (var i = 0; i < idArray.length; i++) {
+        var entityId = idArray[i];
+        array.push({id: entityId, name: "entity #" + entityId});
+      }
+      return array;
+    }
+
+    it("should be defined", function () {
+      expect(typeof(ysy.data.loader.reorderArray)).toBe("function");
+    });
+    it("should reorder by ID array", function () {
+      var reordered = ysy.data.loader.reorderArray(toDataArray(newIds), oldIds);
+      var reorderedIdArray = getIdArray(reordered);
+      expect(reorderedIdArray).toEqual(targetIds);
+    });
+    it("should reorder encoded ID array", function () {
+      var reordered = ysy.data.loader.reorderArray(toDataArray(encodeIdArray(newIds)), encodeIdArray(oldIds));
+      var reorderedIdArray = getIdArray(reordered);
+      expect(reorderedIdArray).toEqual(encodeIdArray(targetIds));
+    });
+  });
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/loader.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/loader.js
new file mode 100644
index 0000000000000000000000000000000000000000..85a07d4e96a90436c4176bba91c81cce03369c9a
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/loader.js
@@ -0,0 +1,90 @@
+describe("Loader", function () {
+  describe("init", function () {
+    var sett = ysy.settings;
+    it("should prepare proper rootPath", function () {
+      expect(sett.paths.rootPath.substring(-1)).toBe("/");
+    });
+    it("should prepare global/projectID variables", function () {
+      if (sett.global) {
+        expect(sett.global).toBe(true);
+        expect(sett.projectID).toBeUndefined();
+      } else {
+        expect(sett.global).toBeUndefined();
+        expect(sett.projectID).toEqual(jasmine.any(Number));
+      }
+    });
+    it("should init many settings", function () {
+      expect(sett.zoom._name).toEqual("Zoom");
+      expect(sett.controls._name).toEqual("Task controls");
+      expect(sett.baseline._name).toEqual("Baselines");
+      expect(sett.critical._name).toEqual("Critical path");
+      expect(sett.addTask._name).toEqual("Add Task");
+      expect(sett.resource._name).toEqual("Resource Management");
+      expect(sett.scheme._name).toEqual("Schema switch");
+    });
+  });
+  describe("Project load", function () {
+    var projectsBackup;
+    beforeEach(function () {
+      projectsBackup = ysy.data.projects;
+      ysy.data.projects = new ysy.data.Array();
+    });
+    afterEach(function () {
+      ysy.data.projects = projectsBackup;
+    });
+    var projectsJson = [{
+      id: 25,
+      name: "superproject",
+      start_date: "2016-06-25",
+      end_date: "2016-07-16"
+    }, {
+      id: 26,
+      name: "subproject",
+      start_date: "2016-06-25",
+      end_date: "2016-07-16"
+    }, {
+      id: 31,
+      name: "project",
+      start_date: "2016-01-01",
+      end_date: "2016-02-16"
+    }];
+    it("should load project array", function () {
+      expect(ysy.data.projects.getArray().length).toBe(0);
+      ysy.data.loader._loadProjects(projectsJson);
+      expect(ysy.data.projects.getArray().length).toBe(3);
+    });
+  });
+  describe("Issue load", function () {
+    var issuesBackup;
+    beforeEach(function () {
+      issuesBackup = ysy.data.issues;
+      ysy.data.issues = new ysy.data.Array();
+    });
+    afterEach(function () {
+      ysy.data.issues = issuesBackup;
+    });
+    var issuesJson = [{
+      id: 25,
+      name: "superissue",
+      start_date: "2016-06-25",
+      end_date: "2016-07-16",
+      columns: []
+    }, {
+      id: 26,
+      name: "subissue",
+      start_date: "2016-06-25",
+      end_date: "2016-07-16",
+      columns: []
+    }];
+    it("should load issue array", function () {
+      expect(ysy.data.issues.getArray().length).toBe(0);
+      ysy.data.loader._loadIssues(issuesJson, "root");
+      expect(ysy.data.issues.getArray().length).toBe(2);
+    });
+    // it("should throw error when no columns property", function () {
+    //   expect(function () {
+    //     ysy.data.loader._loadIssues([{id: 25}])
+    //   }).toThrowError(TypeError)
+    // });
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/main.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b4c911efe25a3fc6374286276b72e519a52df83
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/main.js
@@ -0,0 +1,66 @@
+describe("Test framework", function () {
+  it("should load", function () {
+    expect(true).toBe(true);
+    // expect(false).toBe(true);
+  });
+
+  it("should start after gantt is loaded", function () {
+    expect($(".gantt_data_area").length).toBe(1);
+  });
+  it("should handle long tests", function (done) {
+    setTimeout(function () {
+      expect(true).toBe(true);
+      done();
+    }, 100);
+  });
+  var getQueryString = function () {
+    var query_string = {};
+    var query = window.location.search.substring(1);
+    var vars = query.split("&");
+    for (var i = 0; i < vars.length; i++) {
+      var pair = vars[i].split("=");
+      // If first entry with this name
+      if (typeof query_string[pair[0]] === "undefined") {
+        query_string[pair[0]] = decodeURIComponent(pair[1]);
+        // If second entry with this name
+      } else if (typeof query_string[pair[0]] === "string") {
+        query_string[pair[0]] = [query_string[pair[0]], decodeURIComponent(pair[1])];
+        // If third or later entry with this name
+      } else {
+        query_string[pair[0]].push(decodeURIComponent(pair[1]));
+      }
+    }
+    return query_string;
+  };
+  var prefixTestFile = function (file) {
+    if (file.indexOf("/") > -1) return file;
+    return "easy_gantt/" + file;
+  };
+  it("should load extra tests if any", function () {
+    var params = getQueryString();
+    var requestedTests = params["run_jasmine_tests"] || params["run_jasmine_tests[]"] || params["run_jasmine_tests%5B%5D"];
+    if (requestedTests === "true") {
+      requestedTests = [];
+    } else if (typeof(requestedTests) === "string") {
+      requestedTests = [prefixTestFile(requestedTests)];
+    } else if (typeof(requestedTests) === "object" && requestedTests.length !== undefined) {
+      requestedTests = requestedTests.map(function (requestedTest) {
+        return prefixTestFile(requestedTest);
+      })
+    } else {
+      throw "Wrong type of run_jasmine_tests - \""+requestedTests+"\" is not true|string|Array<String>";
+    }
+    var extraTests = ysy.pro.test.extraTestNames;
+    if (requestedTests.length > extraTests.length) {
+      for (var i = 0; i < requestedTests.length; i++) {
+        expect(extraTests).toContain(requestedTests[i], "extraTests missing " + requestedTests[i]);
+      }
+      return;
+    }
+    expect(requestedTests.length).toBe(extraTests.length, "requested tests !== loaded tests");
+    for (i = 0; i < extraTests.length; i++) {
+      expect(requestedTests).toContain(extraTests[i]);
+    }
+
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/model_relations.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/model_relations.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c15af7f1f1ab44381b7763ae34b31068c48d20a
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/model_relations.js
@@ -0,0 +1,3 @@
+describe("Model relations",function(){
+  
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/pos_date.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/pos_date.js
new file mode 100644
index 0000000000000000000000000000000000000000..8160dd0f4a6378199ef5fda9173d139c57ff28c9
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/pos_date.js
@@ -0,0 +1,75 @@
+describe("PosFromDate", function () {
+  beforeAll(function () {
+    this._max_date = gantt._max_date;
+    this._min_date = gantt._min_date;
+    this.fullWidth = gantt._tasks.full_width;
+    this.scale = gantt._tasks;
+  });
+  it("should return 0 if min_date", function () {
+    expect(gantt.posFromDate(this._min_date)).toBe(0);
+  });
+  it("should return fullWidth if max_date", function () {
+    expect(gantt.posFromDate(this._max_date)).toBe(this.fullWidth);
+  });
+  it("should work for all columns", function () {
+    for (var i = 0; i < this.scale.count; i++) {
+      expect(gantt.posFromDate(this.scale.trace_x[i])).toEqual(this.scale.left[i]);
+    }
+  });
+  it("should work for quarter-column", function () {
+    for (var i = 0; i < this.scale.count - 1; i++) {
+      // expect(gantt.posFromDate(moment(this.scale.trace_x[i]).add(6, "hours")))
+      expect(gantt.posFromDate(moment(
+          +this.scale.trace_x[i] + 0.25 * (this.scale.trace_x[i + 1] - this.scale.trace_x[i])
+      )))
+          .toEqual(this.scale.left[i] + 0.25 * (this.scale.left[i + 1] - this.scale.left[i]));
+    }
+  });
+});
+describe("DateFromPos", function () {
+  beforeAll(function () {
+    this._max_date = gantt._max_date;
+    this._min_date = gantt._min_date;
+    this.fullWidth = gantt._tasks.full_width;
+    this.scale = gantt._tasks;
+  });
+  it("should return min_date if 0", function () {
+    expect(gantt.dateFromPos(0)).toEqual(this._min_date);
+  });
+  it("should return max_date if fullWidth", function () {
+    expect(gantt.dateFromPos(this.fullWidth)).toEqual(this._max_date);
+  });
+  it("should work for all columns", function () {
+    for (var i = 0; i < this.scale.count; i++) {
+      expect(gantt.dateFromPos(this.scale.left[i])).toEqual(this.scale.trace_x[i]);
+    }
+  });
+  it("should work for quarter-column", function () {
+    for (var i = 0; i < this.scale.count - 1; i++) {
+      expect(gantt.dateFromPos(this.scale.left[i] + 0.25 * (this.scale.left[i + 1] - this.scale.left[i])))
+          .toEqual(moment(
+              +this.scale.trace_x[i] + 0.25 * (this.scale.trace_x[i + 1] - this.scale.trace_x[i])
+          ).toDate());
+    }
+  });
+});
+describe("DateFromPos => PosFromDate", function () {
+  beforeAll(function () {
+    this._max_date = gantt._max_date;
+    this._min_date = gantt._min_date;
+    this.fullWidth = gantt._tasks.full_width;
+    this.scale = gantt._tasks;
+  });
+  it("should work for 300 random values from 0-fullWidth", function () {
+    for (var i = 0; i < 300; i++) {
+      var value = Math.random() * this.fullWidth;
+      expect(gantt.posFromDate(gantt.dateFromPos(value))).toBeCloseTo(value, 0.00001);
+    }
+  });
+  it("should work for 300 random values from min_date-max_date", function () {
+    for (var i = 0; i < 300; i++) {
+      var value = gantt.date.Date(this._min_date.valueOf() + Math.random() * (this._max_date - this._min_date));
+      expect(gantt.dateFromPos(gantt.posFromDate(value))).toEqual(value);
+    }
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..c800ea8d655cc6e0930730caf32dc3ee3ecd6871
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper.js
@@ -0,0 +1,106 @@
+describe("Working time helper", function () {
+  var helper;
+  var defHours;
+  var workingDays;
+  var events;
+  var iso = "YYYY-MM-DD";
+
+  beforeAll(function () {
+    helper = gantt._working_time_helper;
+    defHours = helper.defHours;
+    helper.defHours = 8;
+    workingDays = helper.days;
+    helper.days = {0: false, 1: true, 2: true, 3: true, 4: true, 5: true, 6: false};
+    events = helper.dates;
+    helper.dates = [];
+  });
+  afterAll(function () {
+    helper.defHours = defHours;
+    helper.days = workingDays;
+    helper.dates = events;
+  });
+  beforeEach(function () {
+    helper._cache = {};
+  });
+
+  it("should mark 29.6.2016 as weekend", function () {
+    expect(helper.is_weekend(moment("2016-05-29"))).toBe(true);
+  });
+  it("should mark 30.6.2016 as non-weekend", function () {
+    expect(helper.is_weekend(moment("2016-05-30"))).toBe(false);
+  });
+  it("should return 8h for working day", function () {
+    expect(helper.get_working_hours(moment("2016-06-03"))).toBe(8);
+  });
+  it("should return 0h for weekend", function () {
+    expect(helper.get_working_hours(moment("2016-06-04"))).toBe(0);
+  });
+  describe("date ranges", function () {
+    var start = moment("2016-06-01");
+    var end = moment("2016-06-30");
+    it("should return 21d for June", function () {
+      expect(helper.get_work_units_between(start, end, "day")).toBe(21);
+    });
+    it("should return 168h for June", function () {
+      expect(helper.get_work_units_between(start, end, "hour")).toBe(168);
+    });
+  });
+  describe("date ranges with _end", function () {
+    var start = moment("2016-06-01");
+    var end = moment("2016-06-30");
+    end._isEndDate = true;
+    it("should return 22d for June", function () {
+      expect(helper.get_work_units_between(start, end, "day")).toBe(22);
+    });
+    it("should return 176h for June", function () {
+      expect(helper.get_work_units_between(start, end, "hour")).toBe(176);
+    });
+  });
+  describe("inverted date ranges with _end", function () {
+    var start = moment("2016-07-15");
+    var end = moment("2016-06-30");
+    end._isEndDate = true;
+    it("should return -10d for half July", function () {
+      expect(helper.get_work_units_between(start, end, "day")).toBe(-10);
+    });
+    it("should return -80h for half July", function () {
+      expect(helper.get_work_units_between(start, end, "hour")).toBe(-80);
+    });
+  });
+  describe("float date ranges with _end", function () {
+    var start = moment("2016-06-01").add(8, "hours");
+    var end = moment("2016-06-30").add(8, "hours");
+    end._isEndDate = true;
+    it("should return 22d for June", function () {
+      expect(helper.get_work_units_between(start, end, "day")).toBe(22);
+    });
+    it("should return 176h for June", function () {
+      expect(helper.get_work_units_between(start, end, "hour")).toBe(176);
+    });
+  });
+  describe("float date ranges on weekend", function () {
+    var start = moment("2016-05-22").add(6, "hours");
+    var end = moment("2016-05-30").add(6, "hours");
+    it("should return 5.25d", function () {
+      expect(helper.get_work_units_between(start, end, "day")).toBe(5.25);
+    });
+    it("should return 42h", function () {
+      expect(helper.get_work_units_between(start, end, "hour")).toBe(42);
+    });
+  });
+  describe("get_closest_worktime", function () {
+    it("should return Monday from middle of Saturday", function () {
+      var start_date = moment("2017-03-25 06:00");
+      var end_date = gantt._working_time_helper.get_closest_worktime({date: start_date, unit: "day", dir: "future"});
+      var compareDate = moment("2017-03-27 00:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return same from middle of Friday", function () {
+      var start_date = moment("2017-03-24 06:00");
+      var end_date = gantt._working_time_helper.get_closest_worktime({date: start_date, unit: "day", dir: "future"});
+      var compareDate = moment("2017-03-24 06:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+  });
+
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper_add.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper_add.js
new file mode 100644
index 0000000000000000000000000000000000000000..fdcba57939aceae981847ad2ed9a0ecc779680aa
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/jasmine/working_helper_add.js
@@ -0,0 +1,91 @@
+describe("Working time helper", function () {
+  var helper;
+  var defHours;
+  var workingDays;
+  var events;
+  var iso = "YYYY-MM-DD";
+
+  beforeAll(function () {
+    helper = gantt._working_time_helper;
+    defHours = helper.defHours;
+    helper.defHours = 8;
+    workingDays = helper.days;
+    helper.days = {0: false, 1: true, 2: true, 3: true, 4: true, 5: true, 6: false};
+    events = helper.dates;
+    helper.dates = [];
+  });
+  afterAll(function () {
+    helper.defHours = defHours;
+    helper.days = workingDays;
+    helper.dates = events;
+  });
+  beforeEach(function () {
+    helper._cache = {};
+  });
+
+  describe("addWorkingTime()", function () {
+    var start = moment("2016-05-20");
+    var start2 = moment(start);
+    start2._isEndDate = true;
+    it("should return 2016-05-24 after adding 2 days", function () {
+      expect(helper.add_worktime(start, 2, "day", false).format(iso)).toBe("2016-05-24");
+    });
+    it("should return 2016-05-23 after adding 2 days with _end", function () {
+      expect(helper.add_worktime(start, 2, "day", true).format(iso)).toBe("2016-05-23");
+    });
+    it("should return 2016-05-13 after subtracting 5 days", function () {
+      expect(helper.add_worktime(start, -5, "day", false).format(iso)).toBe("2016-05-13");
+    });
+    it("should return 2016-05-12 after subtracting 5 days", function () {
+      expect(helper.add_worktime(start, -5, "day", true).format(iso)).toBe("2016-05-12");
+    });
+    it("should return 2016-05-25 after adding 2 days with start._end", function () {
+      expect(helper.add_worktime(start2, 2, "day", false).format(iso)).toBe("2016-05-25");
+    });
+    it("should return 2016-05-16 after subtracting 5 days with start._end", function () {
+      expect(helper.add_worktime(start2, -5, "day", false).format(iso)).toBe("2016-05-16");
+    });
+    it("should return 2016-05-24+12 after adding 2 days", function () {
+      var start12 = moment(start).add(12, "hours");
+      expect(helper.add_worktime(start12, 2, "day", false).format()).toBe("2016-05-24T12:00:00+02:00");
+    });
+    it("should return 2017-04-05 23:00 after subtracting 2 days (end)", function () {
+      var end_date = moment("2017-04-06 23:00");
+      end_date._isEndDate = true;
+      var start_date = gantt._working_time_helper.add_worktime(end_date, -2, "day", false);
+      var compareDate = moment("2017-04-05 23:00");
+      expect(start_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return 2017-03-30 23:00 after subtracting 2 days (end)", function () {
+      var end_date = moment("2017-04-02 23:00");
+      end_date._isEndDate = true;
+      var start_date = gantt._working_time_helper.add_worktime(end_date, -2, "day", false);
+      var compareDate = moment("2017-03-30 23:00");
+      expect(start_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return", function () {
+      var start_date = moment("2017-04-05 23:00");
+      var end_date = gantt._working_time_helper.add_worktime(start_date, 2, "day", true);
+      var compareDate = moment("2017-04-06 23:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return", function () {
+      var start_date = moment("2017-03-30 23:00");
+      var end_date = gantt._working_time_helper.add_worktime(start_date, 2, "day", true);
+      var compareDate = moment("2017-04-02 23:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return same date in middle of Friday", function () {
+      var start_date = moment("2017-03-24 06:00");
+      var end_date = gantt._working_time_helper.add_worktime(start_date, -0, "day", false);
+      var compareDate = moment("2017-03-24 06:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+    it("should return Monday in middle of Saturday", function () {
+      var start_date = moment("2017-03-25 06:00");
+      var end_date = gantt._working_time_helper.add_worktime(start_date, -0, "day", false);
+      var compareDate = moment("2017-03-27 00:00");
+      expect(end_date.toISOString()).toBe(compareDate.toISOString());
+    });
+  });
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/left_grid.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/left_grid.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d749b5bfb1f0f2f0c31fc98f4eedc7eaecfe47d
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/left_grid.js
@@ -0,0 +1,329 @@
+/* left_grid.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.leftGrid = ysy.view.leftGrid || {};
+$.extend(ysy.view.leftGrid, {
+  columnsWidth: {
+    id: 60,
+    subject: 200,
+    name: 200,
+    project: 140,
+    other: 70,
+    updated_on: 85,
+    assigned_to: 100,
+    grid_width: 400
+  },
+  patch: function () {
+    ysy.data.limits.columnsWidth = $.extend({}, this.columnsWidth);
+    ysy.view.columnBuilders = ysy.view.columnBuilders || {};
+    $.extend(ysy.view.columnBuilders, {
+      id: function (obj) {
+        if (obj.id > 1000000000000) return '';
+        var path = ysy.settings.paths.rootPath + "issues/";
+        return "<a href='" + path + obj.id + "' title='" + obj.text + "' target='_blank'>#" + obj.id + "</a>";
+      },
+      updated_on: function (obj) {
+        if (!obj.columns)return "";
+        var value = obj.columns.updated_on;
+        if (value) {
+          return moment.utc(value, 'YYYY-MM-DD HH:mm:ss ZZ').fromNow();
+        } else {
+          return "";
+        }
+      },
+      done_ratio: function (obj) {
+        if (!obj.columns)return "";
+        //return '<span class="multieditable">'+Math.round(obj.progress*10)*10+'</span>';
+        return '<span >' + Math.round(obj.progress * 10) * 10 + '</span>';
+      },
+      estimated_hours: function (obj) {
+        if (!obj.columns)return "";
+        return '<span >' + obj.estimated + '</span>';
+      },
+      subject: function (obj) {
+        var id = parseInt(obj.real_id);
+        if (isNaN(id) || id > 1000000000000) return obj.text;
+        var path = ysy.settings.paths.rootPath + "issues/";
+        if (obj.type === "milestone") {
+          path = ysy.settings.paths.rootPath + "versions/"
+        } else if (obj.type === "project") {
+          path = ysy.settings.paths.rootPath + "projects/"
+        } else if (obj.type === "assignee") {
+          if (obj.subtype === "group") {
+            path = ysy.settings.paths.rootPath + "groups/"
+          } else {
+            path = ysy.settings.paths.rootPath + "users/"
+          }
+        }
+        return "<a href='" + path + id + "' title='" + obj.text + "' target='_blank'>" + obj.text + "</a>";
+      },
+      _default: function (col) {
+        return function (obj) {
+          if (!obj.columns) return "";
+          return obj.columns[col.name] || "";
+        };
+      }
+
+    });
+    gantt._render_grid_superitem = function (item) {
+      var subjectColumn = ysy.view.columnBuilders.subject;
+
+      var tree = "";
+      for (var j = 0; j < item.$level; j++)
+        tree += this.templates.grid_indent(item);
+      var has_child = this._has_children(item.id);
+      if (has_child) {
+        tree += this.templates.grid_open(item);
+        tree += this.templates.grid_folder(item);
+      } else {
+        tree += this.templates.grid_blank(item);
+        tree += this.templates.grid_file(item);
+      }
+      var afterText = this.templates.superitem_after_text(item, has_child);
+
+      var odd = item.$index % 2 === 0;
+      var style = "";//"width:" + (col.width - (last ? 1 : 0)) + "px;";
+      var cell = "<div class='gantt_grid_superitem' style='" + style + "'>" + tree + subjectColumn(item) + afterText + "</div>";
+
+      var css = odd ? " odd" : "";
+      if (this.templates.grid_row_class) {
+        var css_template = this.templates.grid_row_class.call(this, item.start_date, item.end_date, item);
+        if (css_template)
+          css += " " + css_template;
+      }
+
+      if (this.getState().selected_task == item.id) {
+        css += " gantt_selected";
+      }
+      var el = document.createElement("div");
+      el.className = "gantt_row" + css;
+      //el.setAttribute("data-url","/issues/"+item.id+".json");  // HOSEK
+      el.style.height = this.config.row_height + "px";
+      el.style.lineHeight = (gantt.config.row_height) + "px";
+      el.setAttribute(this.config.task_attribute, item.id);
+      el.innerHTML = cell;
+      return el;
+    };
+    $.extend(gantt.templates, {
+      grid_open:function(item) {
+        return "<div class='gantt_tree_icon gantt_" + (item.$open ? "close" : "open") + " easy-gantt__icon easy-gantt__icon--" + (item.$open ? "close" : "open") + "'></div>";
+      },
+      grid_folder: function (item) {
+        /// = HAS CHILDREN
+        if (this["grid_bullet_" + gantt._get_safe_type(item.type)]) {
+          return this["grid_bullet_" + gantt._get_safe_type(item.type)](item, true);
+        }
+        // default fallback
+        if (item.$open || gantt._get_safe_type(item.type) !== gantt.config.types.task) {
+          return "<div class='gantt_tree_icon gantt_folder_" + (item.$open ? "open" : "closed") + "'></div>";
+        } else {
+          return "<div class='gantt_tree_icon'><div class='gantt_drag_handle gantt_subtask_arrow'></div></div>";
+        }
+      },
+      grid_file: function (item) {
+        // = HAS NO CHILDREN
+        if (this["grid_bullet_" + gantt._get_safe_type(item.type)]) {
+          return this["grid_bullet_" + gantt._get_safe_type(item.type)](item, false);
+        }
+        // default fallback
+        if (gantt._get_safe_type(item.type) === gantt.config.types.task)
+          return "<div class='gantt_tree_icon'><div class='gantt_drag_handle gantt_subtask_arrow'></div></div>";
+        return "<div class='gantt_tree_icon gantt_folder_open'></div>";
+      },
+      grid_bullet_milestone: function (item, has_children) {
+        var rearrangable = false;
+        return "<div class='gantt_tree_icon " + (rearrangable ? "gantt_drag_handle" : "") + "'>" +
+            "<div class='gantt-milestone-icon gantt-grid-milestone-bullet" + (rearrangable ? " gantt-bullet-hover-hide" : "") + "'></div></div>";
+      },
+      grid_bullet_project: function (item, has_children) {
+        if (item.$open || !has_children) {
+          return "<div class='gantt_tree_icon gantt_folder_open'></div>";
+        } else {
+          return "<div class='gantt_tree_icon gantt_folder_closed'></div>";
+        }
+      },
+      grid_bullet_task: function (item, has_children) {
+        if (has_children) {
+          return "<div class='gantt_tree_icon gantt_drag_handle gantt_folder_" + (item.$open ? "open" : "closed") + "'></div>";
+        } else {
+          return "<div class='gantt_tree_icon'><div class='gantt_drag_handle gantt_subtask_arrow'></div></div>";
+        }
+      },
+      superitem_after_text: function (item, has_children) {
+        if (this["superitem_after_" + gantt._get_safe_type(item.type)]) {
+          return this["superitem_after_" + gantt._get_safe_type(item.type)](item, has_children);
+        }
+        return "";
+      }
+    });
+    gantt._render_grid_header = function () {
+      var columns = this.getGridColumns();
+      var cells = [];
+      var width = 0,
+          labels = this.locale.labels;
+
+      var lineHeigth = this.config.scale_height - 2;
+      var resizes = [];
+
+      for (var i = 0; i < columns.length; i++) {
+        var last = i === columns.length - 1;
+        var col = columns[i];
+        if (last && this._get_grid_width() > width + col.width)
+          col.width = this._get_grid_width() - width;
+        width += col.width;
+        var sort = (this._sort && col.name === this._sort.name) ? ("<div class='gantt_sort gantt_" + this._sort.direction + "'></div>") : "";
+        if (col.tree) {
+          if (!this._sort) sort = '<div class="gantt_sort gantt_none"></div>';
+          if (ysy.pro.collapsor) {
+            sort += ysy.pro.collapsor.templateHtml;
+          }
+        }
+        var cssClass = ["gantt_grid_head_cell",
+          ("gantt_grid_head_" + col.name),
+          (last ? "gantt_last_cell" : ""),
+          this.templates.grid_header_class(col.name, col)].join(" ");
+
+        var style = "width:" + (col.width - (last ? 1 : 0)) + "px;";
+        var label = (col.label || labels["column_" + col.name]);
+        label = label || "";
+        var cell = "<div class='" + cssClass + "' style='" + style + "' column_id='" + col.name + "'><div class='gantt-grid-header-multi'>" + label + sort + "</div></div>";
+        if (!last) {
+          resizes.push("<div style='left:" + (width - 6) + "px' class='gantt_grid_column_resize_wrap' data-column_id='" + col.name + "'></div>");
+        }
+        cells.push(cell);
+        //var resize='<div style="height:100%;background-color:red;width:10px;cursor: col-resize;position: absolute;left:'+(width-5)+'px;z-index:1"></div>';
+        /*var resize = '<div class="gantt_grid_column_resize_wrap" style="height:100%;left:' + (width - 7) + 'px;z-index:1" column-index="' + i + '">\
+         <div class="gantt_grid_column_resize"></div></div>';
+         resizes.push(resize);*/
+      }
+      //var resize = '<div class="gantt_grid_column_resize_wrap" style="height:100%;left:' + (this._get_grid_width() - 10) + 'px;z-index:1" >\
+      //<div class="gantt_grid_column_resize"></div></div>';
+      this.$grid_resize.style.left = (this._get_grid_width() - 6) + "px";
+      this.$grid_scale.style.height = (this.config.scale_height - 1) + "px";
+      this.$grid_scale.style.lineHeight = lineHeigth + "px";
+      this.$grid_scale.style.width = (width - 1) + "px";
+      this.$grid_scale.style.position = "relative";
+      this.$grid_scale.innerHTML = cells.join("") + resizes.join("");
+      ysy.view.leftGrid.resizeTable();
+      if (ysy.view.collapsors) {
+        ysy.view.collapsors.requestRepaint();
+      }
+      //resizeColumns();
+    };
+    gantt._calc_grid_width = function () {
+      var i;
+      var columns = this.getGridColumns();
+      var cols_width = 0;
+      var width = [];
+
+      for (i = 0; i < columns.length; i++) {
+        var v = parseInt(columns[i].min_width, 10);
+        width[i] = v;
+        cols_width += v;
+      }
+
+      var diff = this._get_grid_width() - cols_width;
+      if (this.config.autofit || diff > 0) {
+        var delta = Math.ceil(diff / (columns.length ? columns.length : 1));
+        //var ratio=1+diff/(cols_width?cols_width:1);
+        for (i = 0; i < width.length; i++) {
+          columns[i].width = columns[i].min_width + delta;//*ratio;
+        }
+      } else {
+        for (i = 0; i < columns.length; i++) {
+          columns[i].width = columns[i].min_width;
+        }
+        //this.config.grid_width = cols_width;
+      }
+    };
+  },
+  constructColumns: function (columns) {
+    var dcolumns = [];
+    var columnBuilders = ysy.view.columnBuilders;
+    var getBuilder = function (col) {
+      if (columnBuilders[col.name]) {
+        return columnBuilders[col.name];
+      } else if (columnBuilders[col.name + "Builder"]) {
+        return columnBuilders[col.name + "Builder"](col);
+      }
+      else return columnBuilders._default(col);
+    };
+    for (var i = 0; i < columns.length; i++) {
+      var col = columns[i];
+      var isMainColumn = col.name === "subject" || col.name === "name";
+      if (col.name === "id" && !ysy.settings.easyRedmine) continue;
+      var css = "gantt_grid_body_" + col.name;
+      if (col.name !== "") {
+        var width = ysy.data.limits.columnsWidth[col.name] || ysy.data.limits.columnsWidth["other"];
+        var dcolumn = {
+          name: col.name,
+          label: col.title,
+          min_width: width,
+          width: width,
+          tree: isMainColumn,
+          align: isMainColumn ? "left" : "center",
+          template: getBuilder(col),
+          css: css
+        };
+        if (isMainColumn) {
+          dcolumns.unshift(dcolumn);
+        } else {
+          dcolumns.push(dcolumn);
+        }
+      }
+    }
+    return dcolumns;
+  },
+  resizeTable: function () {
+    var $resizes = $(".gantt_grid_column_resize_wrap:not(inited)");
+    var colWidths = ysy.data.limits.columnsWidth;
+    var $gantt_grid = $(".gantt_grid");
+    var $gantt_grid_data = $(".gantt_grid_data");
+    var $gantt_grid_scale = $(".gantt_grid_scale");
+    $resizes.each(function (index, el) {
+      var config = {};
+      var $el = $(el);
+      var column = $el.data("column_id");
+      var dhtmlxDrag = new dhtmlxDnD(el, config);
+      var minWidth,
+          realWidth,
+          resizePos,
+          gridWidth;
+      dhtmlxDrag.attachEvent("onDragStart", function () {
+        if (this.config.started) return;
+        minWidth = colWidths[column] || colWidths.other;
+        realWidth = $gantt_grid.find(".gantt_grid_head_" + column).width();
+        gridWidth = $gantt_grid.width();
+        resizePos = $el.offset();
+      });
+      dhtmlxDrag.attachEvent("onDragMove", function (target, event) {
+        //var diff=Math.floor(event.pageX-lastPos);
+        var diff = Math.floor(dhtmlxDrag.getDiff().x);
+        ysy.log.debug("moveDrag diff=" + diff + "px width=" + realWidth + "px", "grid_resize");
+
+        $gantt_grid.width(gridWidth + diff);
+        $gantt_grid_data.width(gridWidth + diff);
+        $gantt_grid_scale.width(gridWidth + diff);
+        $el.offset({top: resizePos.top, left: resizePos.left + diff});
+        colWidths[column] = minWidth + diff;
+        var columns = gantt.config.columns;
+        if (index < columns.length - 1) {
+          gantt.config.columns[index].min_width = minWidth + diff;
+          gantt.config.columns[index].width = realWidth + diff + 1;
+          $gantt_grid.find(".gantt_grid_head_" + column + ", .gantt_grid_body_" + column).width(realWidth + diff + "px");
+        }
+        gantt.config.grid_width = gridWidth + diff;
+        colWidths.grid_width = gridWidth + diff;
+      });
+      dhtmlxDrag.attachEvent("onDragEnd", function (target, event) {
+        gantt.render();
+        //gantt._render_grid();
+        //var data = gantt._get_tasks_data();
+        //gantt._gridRenderer.render_items(data);
+        //ysy.view.ganttTasks.requestRepaint();
+      });
+    });
+    $resizes.addClass("inited");
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/libs.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/libs.js
new file mode 100644
index 0000000000000000000000000000000000000000..6663b378655f9f6ea0945d57b26761b7a4dd8f9d
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/libs.js
@@ -0,0 +1,359 @@
+//  Mustache
+(function (global, factory) {
+  if (typeof exports === "object" && exports) {
+    factory(exports)
+  } else if (typeof define === "function" && define.amd) {
+    define(["exports"], factory)
+  } else {
+    factory(global.Mustache = {})
+  }
+})(this, function (mustache) {
+  var Object_toString = Object.prototype.toString;
+  var isArray = Array.isArray || function (object) {
+        return Object_toString.call(object) === "[object Array]"
+      };
+
+  function isFunction(object) {
+    return typeof object === "function"
+  }
+
+  function escapeRegExp(string) {
+    return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&")
+  }
+
+  var RegExp_test = RegExp.prototype.test;
+
+  function testRegExp(re, string) {
+    return RegExp_test.call(re, string)
+  }
+
+  var nonSpaceRe = /\S/;
+
+  function isWhitespace(string) {
+    return !testRegExp(nonSpaceRe, string)
+  }
+
+  var entityMap = {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "/": "&#x2F;"};
+
+  function escapeHtml(string) {
+    return String(string).replace(/[&<>"'\/]/g, function (s) {
+      return entityMap[s]
+    })
+  }
+
+  var whiteRe = /\s*/;
+  var spaceRe = /\s+/;
+  var equalsRe = /\s*=/;
+  var curlyRe = /\s*\}/;
+  var tagRe = /#|\^|\/|>|\{|&|=|!/;
+
+  function parseTemplate(template, tags) {
+    if (!template)return [];
+    var sections = [];
+    var tokens = [];
+    var spaces = [];
+    var hasTag = false;
+    var nonSpace = false;
+
+    function stripSpace() {
+      if (hasTag && !nonSpace) {
+        while (spaces.length)delete tokens[spaces.pop()]
+      } else {
+        spaces = []
+      }
+      hasTag = false;
+      nonSpace = false
+    }
+
+    var openingTagRe, closingTagRe, closingCurlyRe;
+
+    function compileTags(tags) {
+      if (typeof tags === "string")tags = tags.split(spaceRe, 2);
+      if (!isArray(tags) || tags.length !== 2)throw new Error("Invalid tags: " + tags);
+      openingTagRe = new RegExp(escapeRegExp(tags[0]) + "\\s*");
+      closingTagRe = new RegExp("\\s*" + escapeRegExp(tags[1]));
+      closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tags[1]))
+    }
+
+    compileTags(tags || mustache.tags);
+    var scanner = new Scanner(template);
+    var start, type, value, chr, token, openSection;
+    while (!scanner.eos()) {
+      start = scanner.pos;
+      value = scanner.scanUntil(openingTagRe);
+      if (value) {
+        for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
+          chr = value.charAt(i);
+          if (isWhitespace(chr)) {
+            spaces.push(tokens.length)
+          } else {
+            nonSpace = true
+          }
+          tokens.push(["text", chr, start, start + 1]);
+          start += 1;
+          if (chr === "\n")stripSpace()
+        }
+      }
+      if (!scanner.scan(openingTagRe))break;
+      hasTag = true;
+      type = scanner.scan(tagRe) || "name";
+      scanner.scan(whiteRe);
+      if (type === "=") {
+        value = scanner.scanUntil(equalsRe);
+        scanner.scan(equalsRe);
+        scanner.scanUntil(closingTagRe)
+      } else if (type === "{") {
+        value = scanner.scanUntil(closingCurlyRe);
+        scanner.scan(curlyRe);
+        scanner.scanUntil(closingTagRe);
+        type = "&"
+      } else {
+        value = scanner.scanUntil(closingTagRe)
+      }
+      if (!scanner.scan(closingTagRe))throw new Error("Unclosed tag at " + scanner.pos);
+      token = [type, value, start, scanner.pos];
+      tokens.push(token);
+      if (type === "#" || type === "^") {
+        sections.push(token)
+      } else if (type === "/") {
+        openSection = sections.pop();
+        if (!openSection)throw new Error('Unopened section "' + value + '" at ' + start);
+        if (openSection[1] !== value)throw new Error('Unclosed section "' + openSection[1] + '" at ' + start)
+      } else if (type === "name" || type === "{" || type === "&") {
+        nonSpace = true
+      } else if (type === "=") {
+        compileTags(value)
+      }
+    }
+    openSection = sections.pop();
+    if (openSection)throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
+    return nestTokens(squashTokens(tokens))
+  }
+
+  function squashTokens(tokens) {
+    var squashedTokens = [];
+    var token, lastToken;
+    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
+      token = tokens[i];
+      if (token) {
+        if (token[0] === "text" && lastToken && lastToken[0] === "text") {
+          lastToken[1] += token[1];
+          lastToken[3] = token[3]
+        } else {
+          squashedTokens.push(token);
+          lastToken = token
+        }
+      }
+    }
+    return squashedTokens
+  }
+
+  function nestTokens(tokens) {
+    var nestedTokens = [];
+    var collector = nestedTokens;
+    var sections = [];
+    var token, section;
+    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
+      token = tokens[i];
+      switch (token[0]) {
+        case"#":
+        case"^":
+          collector.push(token);
+          sections.push(token);
+          collector = token[4] = [];
+          break;
+        case"/":
+          section = sections.pop();
+          section[5] = token[2];
+          collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
+          break;
+        default:
+          collector.push(token)
+      }
+    }
+    return nestedTokens
+  }
+
+  function Scanner(string) {
+    this.string = string;
+    this.tail = string;
+    this.pos = 0
+  }
+
+  Scanner.prototype.eos = function () {
+    return this.tail === ""
+  };
+  Scanner.prototype.scan = function (re) {
+    var match = this.tail.match(re);
+    if (!match || match.index !== 0)return "";
+    var string = match[0];
+    this.tail = this.tail.substring(string.length);
+    this.pos += string.length;
+    return string
+  };
+  Scanner.prototype.scanUntil = function (re) {
+    var index = this.tail.search(re), match;
+    switch (index) {
+      case-1:
+        match = this.tail;
+        this.tail = "";
+        break;
+      case 0:
+        match = "";
+        break;
+      default:
+        match = this.tail.substring(0, index);
+        this.tail = this.tail.substring(index)
+    }
+    this.pos += match.length;
+    return match
+  };
+  function Context(view, parentContext) {
+    this.view = view;
+    this.cache = {".": this.view};
+    this.parent = parentContext
+  }
+
+  Context.prototype.push = function (view) {
+    return new Context(view, this)
+  };
+  Context.prototype.lookup = function (name) {
+    var cache = this.cache;
+    var value;
+    if (name in cache) {
+      value = cache[name]
+    } else {
+      var context = this, names, index, lookupHit = false;
+      while (context) {
+        if (name.indexOf(".") > 0) {
+          value = context.view;
+          names = name.split(".");
+          index = 0;
+          while (value != null && index < names.length) {
+            if (index === names.length - 1 && value != null)lookupHit = typeof value === "object" && value.hasOwnProperty(names[index]);
+            value = value[names[index++]]
+          }
+        } else if (context.view != null && typeof context.view === "object") {
+          value = context.view[name];
+          lookupHit = context.view.hasOwnProperty(name)
+        }
+        if (lookupHit)break;
+        context = context.parent
+      }
+      cache[name] = value
+    }
+    if (isFunction(value))value = value.call(this.view);
+    return value
+  };
+  function Writer() {
+    this.cache = {}
+  }
+
+  Writer.prototype.clearCache = function () {
+    this.cache = {}
+  };
+  Writer.prototype.parse = function (template, tags) {
+    var cache = this.cache;
+    var tokens = cache[template];
+    if (tokens == null)tokens = cache[template] = parseTemplate(template, tags);
+    return tokens
+  };
+  Writer.prototype.render = function (template, view, partials) {
+    var tokens = this.parse(template);
+    var context = view instanceof Context ? view : new Context(view);
+    return this.renderTokens(tokens, context, partials, template)
+  };
+  Writer.prototype.renderTokens = function (tokens, context, partials, originalTemplate) {
+    var buffer = "";
+    var token, symbol, value;
+    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
+      value = undefined;
+      token = tokens[i];
+      symbol = token[0];
+      if (symbol === "#")value = this._renderSection(token, context, partials, originalTemplate); else if (symbol === "^")value = this._renderInverted(token, context, partials, originalTemplate); else if (symbol === ">")value = this._renderPartial(token, context, partials, originalTemplate); else if (symbol === "&")value = this._unescapedValue(token, context); else if (symbol === "name")value = this._escapedValue(token, context); else if (symbol === "text")value = this._rawValue(token);
+      if (value !== undefined)buffer += value
+    }
+    return buffer
+  };
+  Writer.prototype._renderSection = function (token, context, partials, originalTemplate) {
+    var self = this;
+    var buffer = "";
+    var value = context.lookup(token[1]);
+
+    function subRender(template) {
+      return self.render(template, context, partials)
+    }
+
+    if (!value)return;
+    if (isArray(value)) {
+      for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
+        buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate)
+      }
+    } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
+      buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate)
+    } else if (isFunction(value)) {
+      if (typeof originalTemplate !== "string")throw new Error("Cannot use higher-order sections without the original template");
+      value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
+      if (value != null)buffer += value
+    } else {
+      buffer += this.renderTokens(token[4], context, partials, originalTemplate)
+    }
+    return buffer
+  };
+  Writer.prototype._renderInverted = function (token, context, partials, originalTemplate) {
+    var value = context.lookup(token[1]);
+    if (!value || isArray(value) && value.length === 0)return this.renderTokens(token[4], context, partials, originalTemplate)
+  };
+  Writer.prototype._renderPartial = function (token, context, partials) {
+    if (!partials)return;
+    var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
+    if (value != null)return this.renderTokens(this.parse(value), context, partials, value)
+  };
+  Writer.prototype._unescapedValue = function (token, context) {
+    var value = context.lookup(token[1]);
+    if (value != null)return value
+  };
+  Writer.prototype._escapedValue = function (token, context) {
+    var value = context.lookup(token[1]);
+    if (value != null)return mustache.escape(value)
+  };
+  Writer.prototype._rawValue = function (token) {
+    return token[1]
+  };
+  mustache.name = "mustache.js";
+  mustache.version = "2.0.0";
+  mustache.tags = ["{{", "}}"];
+  var defaultWriter = new Writer;
+  mustache.clearCache = function () {
+    return defaultWriter.clearCache()
+  };
+  mustache.parse = function (template, tags) {
+    return defaultWriter.parse(template, tags)
+  };
+  mustache.render = function (template, view, partials) {
+    return defaultWriter.render(template, view, partials)
+  };
+  mustache.to_html = function (template, view, partials, send) {
+    var result = mustache.render(template, view, partials);
+    if (isFunction(send)) {
+      send(result)
+    } else {
+      return result
+    }
+  };
+  mustache.escape = escapeHtml;
+  mustache.Scanner = Scanner;
+  mustache.Context = Context;
+  mustache.Writer = Writer
+});
+
+
+//  RAF compatibility
+window.requestAnimFrame = (function () {
+  return window.requestAnimationFrame ||
+      window.webkitRequestAnimationFrame ||
+      window.mozRequestAnimationFrame ||
+      function (callback) {
+        window.setTimeout(callback, 1000 / 60);
+      };
+})();
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js
new file mode 100644
index 0000000000000000000000000000000000000000..e606ed7c8442d30c5856b6c08da39f5db3807447
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/moment.js
@@ -0,0 +1,12915 @@
+//! moment.js
+//! version : 2.17.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, (function () { 'use strict';
+
+var hookCallback;
+
+function hooks () {
+    return hookCallback.apply(null, arguments);
+}
+
+// This is done to register the method called with moment()
+// without creating circular dependencies.
+function setHookCallback (callback) {
+    hookCallback = callback;
+}
+
+function isArray(input) {
+    return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
+}
+
+function isObject(input) {
+    // IE8 will treat undefined and null as object if it wasn't for
+    // input != null
+    return input != null && Object.prototype.toString.call(input) === '[object Object]';
+}
+
+function isObjectEmpty(obj) {
+    var k;
+    for (k in obj) {
+        // even if its not own property I'd still call it non-empty
+        return false;
+    }
+    return true;
+}
+
+function isNumber(input) {
+    return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
+}
+
+function isDate(input) {
+    return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
+}
+
+function map(arr, fn) {
+    var res = [], i;
+    for (i = 0; i < arr.length; ++i) {
+        res.push(fn(arr[i], i));
+    }
+    return res;
+}
+
+function hasOwnProp(a, b) {
+    return Object.prototype.hasOwnProperty.call(a, b);
+}
+
+function extend(a, b) {
+    for (var i in b) {
+        if (hasOwnProp(b, i)) {
+            a[i] = b[i];
+        }
+    }
+
+    if (hasOwnProp(b, 'toString')) {
+        a.toString = b.toString;
+    }
+
+    if (hasOwnProp(b, 'valueOf')) {
+        a.valueOf = b.valueOf;
+    }
+
+    return a;
+}
+
+function createUTC (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, true).utc();
+}
+
+function defaultParsingFlags() {
+    // We need to deep clone this object.
+    return {
+        empty           : false,
+        unusedTokens    : [],
+        unusedInput     : [],
+        overflow        : -2,
+        charsLeftOver   : 0,
+        nullInput       : false,
+        invalidMonth    : null,
+        invalidFormat   : false,
+        userInvalidated : false,
+        iso             : false,
+        parsedDateParts : [],
+        meridiem        : null
+    };
+}
+
+function getParsingFlags(m) {
+    if (m._pf == null) {
+        m._pf = defaultParsingFlags();
+    }
+    return m._pf;
+}
+
+var some;
+if (Array.prototype.some) {
+    some = Array.prototype.some;
+} else {
+    some = function (fun) {
+        var t = Object(this);
+        var len = t.length >>> 0;
+
+        for (var i = 0; i < len; i++) {
+            if (i in t && fun.call(this, t[i], i, t)) {
+                return true;
+            }
+        }
+
+        return false;
+    };
+}
+
+var some$1 = some;
+
+function isValid(m) {
+    if (m._isValid == null) {
+        var flags = getParsingFlags(m);
+        var parsedParts = some$1.call(flags.parsedDateParts, function (i) {
+            return i != null;
+        });
+        var isNowValid = !isNaN(m._d.getTime()) &&
+            flags.overflow < 0 &&
+            !flags.empty &&
+            !flags.invalidMonth &&
+            !flags.invalidWeekday &&
+            !flags.nullInput &&
+            !flags.invalidFormat &&
+            !flags.userInvalidated &&
+            (!flags.meridiem || (flags.meridiem && parsedParts));
+
+        if (m._strict) {
+            isNowValid = isNowValid &&
+                flags.charsLeftOver === 0 &&
+                flags.unusedTokens.length === 0 &&
+                flags.bigHour === undefined;
+        }
+
+        if (Object.isFrozen == null || !Object.isFrozen(m)) {
+            m._isValid = isNowValid;
+        }
+        else {
+            return isNowValid;
+        }
+    }
+    return m._isValid;
+}
+
+function createInvalid (flags) {
+    var m = createUTC(NaN);
+    if (flags != null) {
+        extend(getParsingFlags(m), flags);
+    }
+    else {
+        getParsingFlags(m).userInvalidated = true;
+    }
+
+    return m;
+}
+
+function isUndefined(input) {
+    return input === void 0;
+}
+
+// Plugins that add properties should also add the key here (null value),
+// so we can properly clone ourselves.
+var momentProperties = hooks.momentProperties = [];
+
+function copyConfig(to, from) {
+    var i, prop, val;
+
+    if (!isUndefined(from._isAMomentObject)) {
+        to._isAMomentObject = from._isAMomentObject;
+    }
+    if (!isUndefined(from._i)) {
+        to._i = from._i;
+    }
+    if (!isUndefined(from._f)) {
+        to._f = from._f;
+    }
+    if (!isUndefined(from._l)) {
+        to._l = from._l;
+    }
+    if (!isUndefined(from._strict)) {
+        to._strict = from._strict;
+    }
+    if (!isUndefined(from._tzm)) {
+        to._tzm = from._tzm;
+    }
+    if (!isUndefined(from._isUTC)) {
+        to._isUTC = from._isUTC;
+    }
+    if (!isUndefined(from._offset)) {
+        to._offset = from._offset;
+    }
+    if (!isUndefined(from._pf)) {
+        to._pf = getParsingFlags(from);
+    }
+    if (!isUndefined(from._locale)) {
+        to._locale = from._locale;
+    }
+
+    if (momentProperties.length > 0) {
+        for (i in momentProperties) {
+            prop = momentProperties[i];
+            val = from[prop];
+            if (!isUndefined(val)) {
+                to[prop] = val;
+            }
+        }
+    }
+
+    return to;
+}
+
+var updateInProgress = false;
+
+// Moment prototype object
+function Moment(config) {
+    copyConfig(this, config);
+    this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+    if (!this.isValid()) {
+        this._d = new Date(NaN);
+    }
+    // Prevent infinite loop in case updateOffset creates new moment
+    // objects.
+    if (updateInProgress === false) {
+        updateInProgress = true;
+        hooks.updateOffset(this);
+        updateInProgress = false;
+    }
+}
+
+function isMoment (obj) {
+    return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+}
+
+function absFloor (number) {
+    if (number < 0) {
+        // -0 -> 0
+        return Math.ceil(number) || 0;
+    } else {
+        return Math.floor(number);
+    }
+}
+
+function toInt(argumentForCoercion) {
+    var coercedNumber = +argumentForCoercion,
+        value = 0;
+
+    if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+        value = absFloor(coercedNumber);
+    }
+
+    return value;
+}
+
+// compare two arrays, return the number of differences
+function compareArrays(array1, array2, dontConvert) {
+    var len = Math.min(array1.length, array2.length),
+        lengthDiff = Math.abs(array1.length - array2.length),
+        diffs = 0,
+        i;
+    for (i = 0; i < len; i++) {
+        if ((dontConvert && array1[i] !== array2[i]) ||
+            (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+            diffs++;
+        }
+    }
+    return diffs + lengthDiff;
+}
+
+function warn(msg) {
+    if (hooks.suppressDeprecationWarnings === false &&
+            (typeof console !==  'undefined') && console.warn) {
+        console.warn('Deprecation warning: ' + msg);
+    }
+}
+
+function deprecate(msg, fn) {
+    var firstTime = true;
+
+    return extend(function () {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(null, msg);
+        }
+        if (firstTime) {
+            var args = [];
+            var arg;
+            for (var i = 0; i < arguments.length; i++) {
+                arg = '';
+                if (typeof arguments[i] === 'object') {
+                    arg += '\n[' + i + '] ';
+                    for (var key in arguments[0]) {
+                        arg += key + ': ' + arguments[0][key] + ', ';
+                    }
+                    arg = arg.slice(0, -2); // Remove trailing comma and space
+                } else {
+                    arg = arguments[i];
+                }
+                args.push(arg);
+            }
+            warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
+            firstTime = false;
+        }
+        return fn.apply(this, arguments);
+    }, fn);
+}
+
+var deprecations = {};
+
+function deprecateSimple(name, msg) {
+    if (hooks.deprecationHandler != null) {
+        hooks.deprecationHandler(name, msg);
+    }
+    if (!deprecations[name]) {
+        warn(msg);
+        deprecations[name] = true;
+    }
+}
+
+hooks.suppressDeprecationWarnings = false;
+hooks.deprecationHandler = null;
+
+function isFunction(input) {
+    return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
+}
+
+function set (config) {
+    var prop, i;
+    for (i in config) {
+        prop = config[i];
+        if (isFunction(prop)) {
+            this[i] = prop;
+        } else {
+            this['_' + i] = prop;
+        }
+    }
+    this._config = config;
+    // Lenient ordinal parsing accepts just a number in addition to
+    // number + (possibly) stuff coming from _ordinalParseLenient.
+    this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source);
+}
+
+function mergeConfigs(parentConfig, childConfig) {
+    var res = extend({}, parentConfig), prop;
+    for (prop in childConfig) {
+        if (hasOwnProp(childConfig, prop)) {
+            if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                res[prop] = {};
+                extend(res[prop], parentConfig[prop]);
+                extend(res[prop], childConfig[prop]);
+            } else if (childConfig[prop] != null) {
+                res[prop] = childConfig[prop];
+            } else {
+                delete res[prop];
+            }
+        }
+    }
+    for (prop in parentConfig) {
+        if (hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])) {
+            // make sure changes to properties don't modify parent config
+            res[prop] = extend({}, res[prop]);
+        }
+    }
+    return res;
+}
+
+function Locale(config) {
+    if (config != null) {
+        this.set(config);
+    }
+}
+
+var keys;
+
+if (Object.keys) {
+    keys = Object.keys;
+} else {
+    keys = function (obj) {
+        var i, res = [];
+        for (i in obj) {
+            if (hasOwnProp(obj, i)) {
+                res.push(i);
+            }
+        }
+        return res;
+    };
+}
+
+var keys$1 = keys;
+
+var defaultCalendar = {
+    sameDay : '[Today at] LT',
+    nextDay : '[Tomorrow at] LT',
+    nextWeek : 'dddd [at] LT',
+    lastDay : '[Yesterday at] LT',
+    lastWeek : '[Last] dddd [at] LT',
+    sameElse : 'L'
+};
+
+function calendar (key, mom, now) {
+    var output = this._calendar[key] || this._calendar['sameElse'];
+    return isFunction(output) ? output.call(mom, now) : output;
+}
+
+var defaultLongDateFormat = {
+    LTS  : 'h:mm:ss A',
+    LT   : 'h:mm A',
+    L    : 'MM/DD/YYYY',
+    LL   : 'MMMM D, YYYY',
+    LLL  : 'MMMM D, YYYY h:mm A',
+    LLLL : 'dddd, MMMM D, YYYY h:mm A'
+};
+
+function longDateFormat (key) {
+    var format = this._longDateFormat[key],
+        formatUpper = this._longDateFormat[key.toUpperCase()];
+
+    if (format || !formatUpper) {
+        return format;
+    }
+
+    this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+        return val.slice(1);
+    });
+
+    return this._longDateFormat[key];
+}
+
+var defaultInvalidDate = 'Invalid date';
+
+function invalidDate () {
+    return this._invalidDate;
+}
+
+var defaultOrdinal = '%d';
+var defaultOrdinalParse = /\d{1,2}/;
+
+function ordinal (number) {
+    return this._ordinal.replace('%d', number);
+}
+
+var defaultRelativeTime = {
+    future : 'in %s',
+    past   : '%s ago',
+    s  : 'a few seconds',
+    m  : 'a minute',
+    mm : '%d minutes',
+    h  : 'an hour',
+    hh : '%d hours',
+    d  : 'a day',
+    dd : '%d days',
+    M  : 'a month',
+    MM : '%d months',
+    y  : 'a year',
+    yy : '%d years'
+};
+
+function relativeTime (number, withoutSuffix, string, isFuture) {
+    var output = this._relativeTime[string];
+    return (isFunction(output)) ?
+        output(number, withoutSuffix, string, isFuture) :
+        output.replace(/%d/i, number);
+}
+
+function pastFuture (diff, output) {
+    var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+    return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+}
+
+var aliases = {};
+
+function addUnitAlias (unit, shorthand) {
+    var lowerCase = unit.toLowerCase();
+    aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+}
+
+function normalizeUnits(units) {
+    return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+}
+
+function normalizeObjectUnits(inputObject) {
+    var normalizedInput = {},
+        normalizedProp,
+        prop;
+
+    for (prop in inputObject) {
+        if (hasOwnProp(inputObject, prop)) {
+            normalizedProp = normalizeUnits(prop);
+            if (normalizedProp) {
+                normalizedInput[normalizedProp] = inputObject[prop];
+            }
+        }
+    }
+
+    return normalizedInput;
+}
+
+var priorities = {};
+
+function addUnitPriority(unit, priority) {
+    priorities[unit] = priority;
+}
+
+function getPrioritizedUnits(unitsObj) {
+    var units = [];
+    for (var u in unitsObj) {
+        units.push({unit: u, priority: priorities[u]});
+    }
+    units.sort(function (a, b) {
+        return a.priority - b.priority;
+    });
+    return units;
+}
+
+function makeGetSet (unit, keepTime) {
+    return function (value) {
+        if (value != null) {
+            set$1(this, unit, value);
+            hooks.updateOffset(this, keepTime);
+            return this;
+        } else {
+            return get(this, unit);
+        }
+    };
+}
+
+function get (mom, unit) {
+    return mom.isValid() ?
+        mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+}
+
+function set$1 (mom, unit, value) {
+    if (mom.isValid()) {
+        mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+    }
+}
+
+// MOMENTS
+
+function stringGet (units) {
+    units = normalizeUnits(units);
+    if (isFunction(this[units])) {
+        return this[units]();
+    }
+    return this;
+}
+
+
+function stringSet (units, value) {
+    if (typeof units === 'object') {
+        units = normalizeObjectUnits(units);
+        var prioritized = getPrioritizedUnits(units);
+        for (var i = 0; i < prioritized.length; i++) {
+            this[prioritized[i].unit](units[prioritized[i].unit]);
+        }
+    } else {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units](value);
+        }
+    }
+    return this;
+}
+
+function zeroFill(number, targetLength, forceSign) {
+    var absNumber = '' + Math.abs(number),
+        zerosToFill = targetLength - absNumber.length,
+        sign = number >= 0;
+    return (sign ? (forceSign ? '+' : '') : '-') +
+        Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+}
+
+var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
+
+var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
+
+var formatFunctions = {};
+
+var formatTokenFunctions = {};
+
+// token:    'M'
+// padded:   ['MM', 2]
+// ordinal:  'Mo'
+// callback: function () { this.month() + 1 }
+function addFormatToken (token, padded, ordinal, callback) {
+    var func = callback;
+    if (typeof callback === 'string') {
+        func = function () {
+            return this[callback]();
+        };
+    }
+    if (token) {
+        formatTokenFunctions[token] = func;
+    }
+    if (padded) {
+        formatTokenFunctions[padded[0]] = function () {
+            return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+        };
+    }
+    if (ordinal) {
+        formatTokenFunctions[ordinal] = function () {
+            return this.localeData().ordinal(func.apply(this, arguments), token);
+        };
+    }
+}
+
+function removeFormattingTokens(input) {
+    if (input.match(/\[[\s\S]/)) {
+        return input.replace(/^\[|\]$/g, '');
+    }
+    return input.replace(/\\/g, '');
+}
+
+function makeFormatFunction(format) {
+    var array = format.match(formattingTokens), i, length;
+
+    for (i = 0, length = array.length; i < length; i++) {
+        if (formatTokenFunctions[array[i]]) {
+            array[i] = formatTokenFunctions[array[i]];
+        } else {
+            array[i] = removeFormattingTokens(array[i]);
+        }
+    }
+
+    return function (mom) {
+        var output = '', i;
+        for (i = 0; i < length; i++) {
+            output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
+        }
+        return output;
+    };
+}
+
+// format date using native date object
+function formatMoment(m, format) {
+    if (!m.isValid()) {
+        return m.localeData().invalidDate();
+    }
+
+    format = expandFormat(format, m.localeData());
+    formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+
+    return formatFunctions[format](m);
+}
+
+function expandFormat(format, locale) {
+    var i = 5;
+
+    function replaceLongDateFormatTokens(input) {
+        return locale.longDateFormat(input) || input;
+    }
+
+    localFormattingTokens.lastIndex = 0;
+    while (i >= 0 && localFormattingTokens.test(format)) {
+        format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+        localFormattingTokens.lastIndex = 0;
+        i -= 1;
+    }
+
+    return format;
+}
+
+var match1         = /\d/;            //       0 - 9
+var match2         = /\d\d/;          //      00 - 99
+var match3         = /\d{3}/;         //     000 - 999
+var match4         = /\d{4}/;         //    0000 - 9999
+var match6         = /[+-]?\d{6}/;    // -999999 - 999999
+var match1to2      = /\d\d?/;         //       0 - 99
+var match3to4      = /\d\d\d\d?/;     //     999 - 9999
+var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
+var match1to3      = /\d{1,3}/;       //       0 - 999
+var match1to4      = /\d{1,4}/;       //       0 - 9999
+var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
+
+var matchUnsigned  = /\d+/;           //       0 - inf
+var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+
+var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
+
+// any word (or two) characters or numbers including two/three word month in arabic.
+// includes scottish gaelic two word and hyphenated months
+var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+
+
+var regexes = {};
+
+function addRegexToken (token, regex, strictRegex) {
+    regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+        return (isStrict && strictRegex) ? strictRegex : regex;
+    };
+}
+
+function getParseRegexForToken (token, config) {
+    if (!hasOwnProp(regexes, token)) {
+        return new RegExp(unescapeFormat(token));
+    }
+
+    return regexes[token](config._strict, config._locale);
+}
+
+// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+function unescapeFormat(s) {
+    return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+        return p1 || p2 || p3 || p4;
+    }));
+}
+
+function regexEscape(s) {
+    return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+var tokens = {};
+
+function addParseToken (token, callback) {
+    var i, func = callback;
+    if (typeof token === 'string') {
+        token = [token];
+    }
+    if (isNumber(callback)) {
+        func = function (input, array) {
+            array[callback] = toInt(input);
+        };
+    }
+    for (i = 0; i < token.length; i++) {
+        tokens[token[i]] = func;
+    }
+}
+
+function addWeekParseToken (token, callback) {
+    addParseToken(token, function (input, array, config, token) {
+        config._w = config._w || {};
+        callback(input, config._w, config, token);
+    });
+}
+
+function addTimeToArrayFromToken(token, input, config) {
+    if (input != null && hasOwnProp(tokens, token)) {
+        tokens[token](input, config._a, config, token);
+    }
+}
+
+var YEAR = 0;
+var MONTH = 1;
+var DATE = 2;
+var HOUR = 3;
+var MINUTE = 4;
+var SECOND = 5;
+var MILLISECOND = 6;
+var WEEK = 7;
+var WEEKDAY = 8;
+
+var indexOf;
+
+if (Array.prototype.indexOf) {
+    indexOf = Array.prototype.indexOf;
+} else {
+    indexOf = function (o) {
+        // I know
+        var i;
+        for (i = 0; i < this.length; ++i) {
+            if (this[i] === o) {
+                return i;
+            }
+        }
+        return -1;
+    };
+}
+
+var indexOf$1 = indexOf;
+
+function daysInMonth(year, month) {
+    return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+}
+
+// FORMATTING
+
+addFormatToken('M', ['MM', 2], 'Mo', function () {
+    return this.month() + 1;
+});
+
+addFormatToken('MMM', 0, 0, function (format) {
+    return this.localeData().monthsShort(this, format);
+});
+
+addFormatToken('MMMM', 0, 0, function (format) {
+    return this.localeData().months(this, format);
+});
+
+// ALIASES
+
+addUnitAlias('month', 'M');
+
+// PRIORITY
+
+addUnitPriority('month', 8);
+
+// PARSING
+
+addRegexToken('M',    match1to2);
+addRegexToken('MM',   match1to2, match2);
+addRegexToken('MMM',  function (isStrict, locale) {
+    return locale.monthsShortRegex(isStrict);
+});
+addRegexToken('MMMM', function (isStrict, locale) {
+    return locale.monthsRegex(isStrict);
+});
+
+addParseToken(['M', 'MM'], function (input, array) {
+    array[MONTH] = toInt(input) - 1;
+});
+
+addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+    var month = config._locale.monthsParse(input, token, config._strict);
+    // if we didn't find a month name, mark the date as invalid.
+    if (month != null) {
+        array[MONTH] = month;
+    } else {
+        getParsingFlags(config).invalidMonth = input;
+    }
+});
+
+// LOCALES
+
+var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
+var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+function localeMonths (m, format) {
+    if (!m) {
+        return this._months;
+    }
+    return isArray(this._months) ? this._months[m.month()] :
+        this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+function localeMonthsShort (m, format) {
+    if (!m) {
+        return this._monthsShort;
+    }
+    return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+        this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+function handleStrictParse(monthName, format, strict) {
+    var i, ii, mom, llc = monthName.toLocaleLowerCase();
+    if (!this._monthsParse) {
+        // this is not used
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+        for (i = 0; i < 12; ++i) {
+            mom = createUTC([2000, i]);
+            this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+            this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'MMM') {
+            ii = indexOf$1.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf$1.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'MMM') {
+            ii = indexOf$1.call(this._shortMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf$1.call(this._longMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeMonthsParse (monthName, format, strict) {
+    var i, mom, regex;
+
+    if (this._monthsParseExact) {
+        return handleStrictParse.call(this, monthName, format, strict);
+    }
+
+    if (!this._monthsParse) {
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+    }
+
+    // TODO: add sorting
+    // Sorting makes sure if one month (or abbr) is a prefix of another
+    // see sorting in computeMonthsParse
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        if (strict && !this._longMonthsParse[i]) {
+            this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+            this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
+        }
+        if (!strict && !this._monthsParse[i]) {
+            regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+            this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (!strict && this._monthsParse[i].test(monthName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function setMonth (mom, value) {
+    var dayOfMonth;
+
+    if (!mom.isValid()) {
+        // No op
+        return mom;
+    }
+
+    if (typeof value === 'string') {
+        if (/^\d+$/.test(value)) {
+            value = toInt(value);
+        } else {
+            value = mom.localeData().monthsParse(value);
+            // TODO: Another silent failure?
+            if (!isNumber(value)) {
+                return mom;
+            }
+        }
+    }
+
+    dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+    mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+    return mom;
+}
+
+function getSetMonth (value) {
+    if (value != null) {
+        setMonth(this, value);
+        hooks.updateOffset(this, true);
+        return this;
+    } else {
+        return get(this, 'Month');
+    }
+}
+
+function getDaysInMonth () {
+    return daysInMonth(this.year(), this.month());
+}
+
+var defaultMonthsShortRegex = matchWord;
+function monthsShortRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsShortStrictRegex;
+        } else {
+            return this._monthsShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsShortRegex')) {
+            this._monthsShortRegex = defaultMonthsShortRegex;
+        }
+        return this._monthsShortStrictRegex && isStrict ?
+            this._monthsShortStrictRegex : this._monthsShortRegex;
+    }
+}
+
+var defaultMonthsRegex = matchWord;
+function monthsRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsStrictRegex;
+        } else {
+            return this._monthsRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            this._monthsRegex = defaultMonthsRegex;
+        }
+        return this._monthsStrictRegex && isStrict ?
+            this._monthsStrictRegex : this._monthsRegex;
+    }
+}
+
+function computeMonthsParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom;
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        shortPieces.push(this.monthsShort(mom, ''));
+        longPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.monthsShort(mom, ''));
+    }
+    // Sorting makes sure if one month (or abbr) is a prefix of another it
+    // will match the longer piece.
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 12; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+    }
+    for (i = 0; i < 24; i++) {
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._monthsShortRegex = this._monthsRegex;
+    this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+}
+
+// FORMATTING
+
+addFormatToken('Y', 0, 0, function () {
+    var y = this.year();
+    return y <= 9999 ? '' + y : '+' + y;
+});
+
+addFormatToken(0, ['YY', 2], 0, function () {
+    return this.year() % 100;
+});
+
+addFormatToken(0, ['YYYY',   4],       0, 'year');
+addFormatToken(0, ['YYYYY',  5],       0, 'year');
+addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+// ALIASES
+
+addUnitAlias('year', 'y');
+
+// PRIORITIES
+
+addUnitPriority('year', 1);
+
+// PARSING
+
+addRegexToken('Y',      matchSigned);
+addRegexToken('YY',     match1to2, match2);
+addRegexToken('YYYY',   match1to4, match4);
+addRegexToken('YYYYY',  match1to6, match6);
+addRegexToken('YYYYYY', match1to6, match6);
+
+addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+addParseToken('YYYY', function (input, array) {
+    array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+});
+addParseToken('YY', function (input, array) {
+    array[YEAR] = hooks.parseTwoDigitYear(input);
+});
+addParseToken('Y', function (input, array) {
+    array[YEAR] = parseInt(input, 10);
+});
+
+// HELPERS
+
+function daysInYear(year) {
+    return isLeapYear(year) ? 366 : 365;
+}
+
+function isLeapYear(year) {
+    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+// HOOKS
+
+hooks.parseTwoDigitYear = function (input) {
+    return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+};
+
+// MOMENTS
+
+var getSetYear = makeGetSet('FullYear', true);
+
+function getIsLeapYear () {
+    return isLeapYear(this.year());
+}
+
+function createDate (y, m, d, h, M, s, ms) {
+    //can't just apply() to create a date:
+    //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
+    var date = new Date(y, m, d, h, M, s, ms);
+
+    //the date constructor remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
+        date.setFullYear(y);
+    }
+    return date;
+}
+
+function createUTCDate (y) {
+    var date = new Date(Date.UTC.apply(null, arguments));
+
+    //the Date.UTC function remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
+        date.setUTCFullYear(y);
+    }
+    return date;
+}
+
+// start-of-first-week - start-of-year
+function firstWeekOffset(year, dow, doy) {
+    var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+        fwd = 7 + dow - doy,
+        // first-week day local weekday -- which local weekday is fwd
+        fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+    return -fwdlw + fwd - 1;
+}
+
+//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+    var localWeekday = (7 + weekday - dow) % 7,
+        weekOffset = firstWeekOffset(year, dow, doy),
+        dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+        resYear, resDayOfYear;
+
+    if (dayOfYear <= 0) {
+        resYear = year - 1;
+        resDayOfYear = daysInYear(resYear) + dayOfYear;
+    } else if (dayOfYear > daysInYear(year)) {
+        resYear = year + 1;
+        resDayOfYear = dayOfYear - daysInYear(year);
+    } else {
+        resYear = year;
+        resDayOfYear = dayOfYear;
+    }
+
+    return {
+        year: resYear,
+        dayOfYear: resDayOfYear
+    };
+}
+
+function weekOfYear(mom, dow, doy) {
+    var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+        week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+        resWeek, resYear;
+
+    if (week < 1) {
+        resYear = mom.year() - 1;
+        resWeek = week + weeksInYear(resYear, dow, doy);
+    } else if (week > weeksInYear(mom.year(), dow, doy)) {
+        resWeek = week - weeksInYear(mom.year(), dow, doy);
+        resYear = mom.year() + 1;
+    } else {
+        resYear = mom.year();
+        resWeek = week;
+    }
+
+    return {
+        week: resWeek,
+        year: resYear
+    };
+}
+
+function weeksInYear(year, dow, doy) {
+    var weekOffset = firstWeekOffset(year, dow, doy),
+        weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+    return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+}
+
+// FORMATTING
+
+addFormatToken('w', ['ww', 2], 'wo', 'week');
+addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+// ALIASES
+
+addUnitAlias('week', 'w');
+addUnitAlias('isoWeek', 'W');
+
+// PRIORITIES
+
+addUnitPriority('week', 5);
+addUnitPriority('isoWeek', 5);
+
+// PARSING
+
+addRegexToken('w',  match1to2);
+addRegexToken('ww', match1to2, match2);
+addRegexToken('W',  match1to2);
+addRegexToken('WW', match1to2, match2);
+
+addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+    week[token.substr(0, 1)] = toInt(input);
+});
+
+// HELPERS
+
+// LOCALES
+
+function localeWeek (mom) {
+    return weekOfYear(mom, this._week.dow, this._week.doy).week;
+}
+
+var defaultLocaleWeek = {
+    dow : 0, // Sunday is the first day of the week.
+    doy : 6  // The week that contains Jan 1st is the first week of the year.
+};
+
+function localeFirstDayOfWeek () {
+    return this._week.dow;
+}
+
+function localeFirstDayOfYear () {
+    return this._week.doy;
+}
+
+// MOMENTS
+
+function getSetWeek (input) {
+    var week = this.localeData().week(this);
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+function getSetISOWeek (input) {
+    var week = weekOfYear(this, 1, 4).week;
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+// FORMATTING
+
+addFormatToken('d', 0, 'do', 'day');
+
+addFormatToken('dd', 0, 0, function (format) {
+    return this.localeData().weekdaysMin(this, format);
+});
+
+addFormatToken('ddd', 0, 0, function (format) {
+    return this.localeData().weekdaysShort(this, format);
+});
+
+addFormatToken('dddd', 0, 0, function (format) {
+    return this.localeData().weekdays(this, format);
+});
+
+addFormatToken('e', 0, 0, 'weekday');
+addFormatToken('E', 0, 0, 'isoWeekday');
+
+// ALIASES
+
+addUnitAlias('day', 'd');
+addUnitAlias('weekday', 'e');
+addUnitAlias('isoWeekday', 'E');
+
+// PRIORITY
+addUnitPriority('day', 11);
+addUnitPriority('weekday', 11);
+addUnitPriority('isoWeekday', 11);
+
+// PARSING
+
+addRegexToken('d',    match1to2);
+addRegexToken('e',    match1to2);
+addRegexToken('E',    match1to2);
+addRegexToken('dd',   function (isStrict, locale) {
+    return locale.weekdaysMinRegex(isStrict);
+});
+addRegexToken('ddd',   function (isStrict, locale) {
+    return locale.weekdaysShortRegex(isStrict);
+});
+addRegexToken('dddd',   function (isStrict, locale) {
+    return locale.weekdaysRegex(isStrict);
+});
+
+addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+    var weekday = config._locale.weekdaysParse(input, token, config._strict);
+    // if we didn't get a weekday name, mark the date as invalid
+    if (weekday != null) {
+        week.d = weekday;
+    } else {
+        getParsingFlags(config).invalidWeekday = input;
+    }
+});
+
+addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+    week[token] = toInt(input);
+});
+
+// HELPERS
+
+function parseWeekday(input, locale) {
+    if (typeof input !== 'string') {
+        return input;
+    }
+
+    if (!isNaN(input)) {
+        return parseInt(input, 10);
+    }
+
+    input = locale.weekdaysParse(input);
+    if (typeof input === 'number') {
+        return input;
+    }
+
+    return null;
+}
+
+function parseIsoWeekday(input, locale) {
+    if (typeof input === 'string') {
+        return locale.weekdaysParse(input) % 7 || 7;
+    }
+    return isNaN(input) ? null : input;
+}
+
+// LOCALES
+
+var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+function localeWeekdays (m, format) {
+    if (!m) {
+        return this._weekdays;
+    }
+    return isArray(this._weekdays) ? this._weekdays[m.day()] :
+        this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+}
+
+var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+function localeWeekdaysShort (m) {
+    return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
+}
+
+var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+function localeWeekdaysMin (m) {
+    return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
+}
+
+function handleStrictParse$1(weekdayName, format, strict) {
+    var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._minWeekdaysParse = [];
+
+        for (i = 0; i < 7; ++i) {
+            mom = createUTC([2000, 1]).day(i);
+            this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+            this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+            this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'dddd') {
+            ii = indexOf$1.call(this._weekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf$1.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf$1.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'dddd') {
+            ii = indexOf$1.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf$1.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf$1.call(this._minWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf$1.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeWeekdaysParse (weekdayName, format, strict) {
+    var i, mom, regex;
+
+    if (this._weekdaysParseExact) {
+        return handleStrictParse$1.call(this, weekdayName, format, strict);
+    }
+
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._minWeekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._fullWeekdaysParse = [];
+    }
+
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+
+        mom = createUTC([2000, 1]).day(i);
+        if (strict && !this._fullWeekdaysParse[i]) {
+            this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+            this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+            this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+        }
+        if (!this._weekdaysParse[i]) {
+            regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+            this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function getSetDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+    if (input != null) {
+        input = parseWeekday(input, this.localeData());
+        return this.add(input - day, 'd');
+    } else {
+        return day;
+    }
+}
+
+function getSetLocaleDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+    return input == null ? weekday : this.add(input - weekday, 'd');
+}
+
+function getSetISODayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+
+    // behaves the same as moment#day except
+    // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+    // as a setter, sunday should belong to the previous week.
+
+    if (input != null) {
+        var weekday = parseIsoWeekday(input, this.localeData());
+        return this.day(this.day() % 7 ? weekday : weekday - 7);
+    } else {
+        return this.day() || 7;
+    }
+}
+
+var defaultWeekdaysRegex = matchWord;
+function weekdaysRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysStrictRegex;
+        } else {
+            return this._weekdaysRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            this._weekdaysRegex = defaultWeekdaysRegex;
+        }
+        return this._weekdaysStrictRegex && isStrict ?
+            this._weekdaysStrictRegex : this._weekdaysRegex;
+    }
+}
+
+var defaultWeekdaysShortRegex = matchWord;
+function weekdaysShortRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysShortStrictRegex;
+        } else {
+            return this._weekdaysShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+            this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+        }
+        return this._weekdaysShortStrictRegex && isStrict ?
+            this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+    }
+}
+
+var defaultWeekdaysMinRegex = matchWord;
+function weekdaysMinRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysMinStrictRegex;
+        } else {
+            return this._weekdaysMinRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+            this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+        }
+        return this._weekdaysMinStrictRegex && isStrict ?
+            this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+    }
+}
+
+
+function computeWeekdaysParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom, minp, shortp, longp;
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, 1]).day(i);
+        minp = this.weekdaysMin(mom, '');
+        shortp = this.weekdaysShort(mom, '');
+        longp = this.weekdays(mom, '');
+        minPieces.push(minp);
+        shortPieces.push(shortp);
+        longPieces.push(longp);
+        mixedPieces.push(minp);
+        mixedPieces.push(shortp);
+        mixedPieces.push(longp);
+    }
+    // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+    // will match the longer piece.
+    minPieces.sort(cmpLenRev);
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 7; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._weekdaysShortRegex = this._weekdaysRegex;
+    this._weekdaysMinRegex = this._weekdaysRegex;
+
+    this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+    this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
+}
+
+// FORMATTING
+
+function hFormat() {
+    return this.hours() % 12 || 12;
+}
+
+function kFormat() {
+    return this.hours() || 24;
+}
+
+addFormatToken('H', ['HH', 2], 0, 'hour');
+addFormatToken('h', ['hh', 2], 0, hFormat);
+addFormatToken('k', ['kk', 2], 0, kFormat);
+
+addFormatToken('hmm', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('hmmss', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+addFormatToken('Hmm', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('Hmmss', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+function meridiem (token, lowercase) {
+    addFormatToken(token, 0, 0, function () {
+        return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+    });
+}
+
+meridiem('a', true);
+meridiem('A', false);
+
+// ALIASES
+
+addUnitAlias('hour', 'h');
+
+// PRIORITY
+addUnitPriority('hour', 13);
+
+// PARSING
+
+function matchMeridiem (isStrict, locale) {
+    return locale._meridiemParse;
+}
+
+addRegexToken('a',  matchMeridiem);
+addRegexToken('A',  matchMeridiem);
+addRegexToken('H',  match1to2);
+addRegexToken('h',  match1to2);
+addRegexToken('HH', match1to2, match2);
+addRegexToken('hh', match1to2, match2);
+
+addRegexToken('hmm', match3to4);
+addRegexToken('hmmss', match5to6);
+addRegexToken('Hmm', match3to4);
+addRegexToken('Hmmss', match5to6);
+
+addParseToken(['H', 'HH'], HOUR);
+addParseToken(['a', 'A'], function (input, array, config) {
+    config._isPm = config._locale.isPM(input);
+    config._meridiem = input;
+});
+addParseToken(['h', 'hh'], function (input, array, config) {
+    array[HOUR] = toInt(input);
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('Hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+});
+addParseToken('Hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+});
+
+// LOCALES
+
+function localeIsPM (input) {
+    // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+    // Using charAt should be more compatible.
+    return ((input + '').toLowerCase().charAt(0) === 'p');
+}
+
+var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+function localeMeridiem (hours, minutes, isLower) {
+    if (hours > 11) {
+        return isLower ? 'pm' : 'PM';
+    } else {
+        return isLower ? 'am' : 'AM';
+    }
+}
+
+
+// MOMENTS
+
+// Setting the hour should keep the time, because the user explicitly
+// specified which hour he wants. So trying to maintain the same hour (in
+// a new timezone) makes sense. Adding/subtracting hours does not follow
+// this rule.
+var getSetHour = makeGetSet('Hours', true);
+
+// months
+// week
+// weekdays
+// meridiem
+var baseConfig = {
+    calendar: defaultCalendar,
+    longDateFormat: defaultLongDateFormat,
+    invalidDate: defaultInvalidDate,
+    ordinal: defaultOrdinal,
+    ordinalParse: defaultOrdinalParse,
+    relativeTime: defaultRelativeTime,
+
+    months: defaultLocaleMonths,
+    monthsShort: defaultLocaleMonthsShort,
+
+    week: defaultLocaleWeek,
+
+    weekdays: defaultLocaleWeekdays,
+    weekdaysMin: defaultLocaleWeekdaysMin,
+    weekdaysShort: defaultLocaleWeekdaysShort,
+
+    meridiemParse: defaultLocaleMeridiemParse
+};
+
+// internal storage for locale config files
+var locales = {};
+var localeFamilies = {};
+var globalLocale;
+
+function normalizeLocale(key) {
+    return key ? key.toLowerCase().replace('_', '-') : key;
+}
+
+// pick the locale from the array
+// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+function chooseLocale(names) {
+    var i = 0, j, next, locale, split;
+
+    while (i < names.length) {
+        split = normalizeLocale(names[i]).split('-');
+        j = split.length;
+        next = normalizeLocale(names[i + 1]);
+        next = next ? next.split('-') : null;
+        while (j > 0) {
+            locale = loadLocale(split.slice(0, j).join('-'));
+            if (locale) {
+                return locale;
+            }
+            if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+                //the next array item is better than a shallower substring of this one
+                break;
+            }
+            j--;
+        }
+        i++;
+    }
+    return null;
+}
+
+function loadLocale(name) {
+    var oldLocale = null;
+    // TODO: Find a better way to register and load all the locales in Node
+    if (!locales[name] && (typeof module !== 'undefined') &&
+            module && module.exports) {
+        try {
+            oldLocale = globalLocale._abbr;
+            require('./locale/' + name);
+            // because defineLocale currently also sets the global locale, we
+            // want to undo that for lazy loaded locales
+            getSetGlobalLocale(oldLocale);
+        } catch (e) { }
+    }
+    return locales[name];
+}
+
+// This function will load locale and then set the global locale.  If
+// no arguments are passed in, it will simply return the current global
+// locale key.
+function getSetGlobalLocale (key, values) {
+    var data;
+    if (key) {
+        if (isUndefined(values)) {
+            data = getLocale(key);
+        }
+        else {
+            data = defineLocale(key, values);
+        }
+
+        if (data) {
+            // moment.duration._locale = moment._locale = data;
+            globalLocale = data;
+        }
+    }
+
+    return globalLocale._abbr;
+}
+
+function defineLocale (name, config) {
+    if (config !== null) {
+        var parentConfig = baseConfig;
+        config.abbr = name;
+        if (locales[name] != null) {
+            deprecateSimple('defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                    'an existing locale. moment.defineLocale(localeName, ' +
+                    'config) should only be used for creating a new locale ' +
+                    'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
+            parentConfig = locales[name]._config;
+        } else if (config.parentLocale != null) {
+            if (locales[config.parentLocale] != null) {
+                parentConfig = locales[config.parentLocale]._config;
+            } else {
+                if (!localeFamilies[config.parentLocale]) {
+                    localeFamilies[config.parentLocale] = [];
+                }
+                localeFamilies[config.parentLocale].push({
+                    name: name,
+                    config: config
+                });
+                return null;
+            }
+        }
+        locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+        if (localeFamilies[name]) {
+            localeFamilies[name].forEach(function (x) {
+                defineLocale(x.name, x.config);
+            });
+        }
+
+        // backwards compat for now: also set the locale
+        // make sure we set the locale AFTER all child locales have been
+        // created, so we won't end up with the child locale set.
+        getSetGlobalLocale(name);
+
+
+        return locales[name];
+    } else {
+        // useful for testing
+        delete locales[name];
+        return null;
+    }
+}
+
+function updateLocale(name, config) {
+    if (config != null) {
+        var locale, parentConfig = baseConfig;
+        // MERGE
+        if (locales[name] != null) {
+            parentConfig = locales[name]._config;
+        }
+        config = mergeConfigs(parentConfig, config);
+        locale = new Locale(config);
+        locale.parentLocale = locales[name];
+        locales[name] = locale;
+
+        // backwards compat for now: also set the locale
+        getSetGlobalLocale(name);
+    } else {
+        // pass null for config to unupdate, useful for tests
+        if (locales[name] != null) {
+            if (locales[name].parentLocale != null) {
+                locales[name] = locales[name].parentLocale;
+            } else if (locales[name] != null) {
+                delete locales[name];
+            }
+        }
+    }
+    return locales[name];
+}
+
+// returns locale data
+function getLocale (key) {
+    var locale;
+
+    if (key && key._locale && key._locale._abbr) {
+        key = key._locale._abbr;
+    }
+
+    if (!key) {
+        return globalLocale;
+    }
+
+    if (!isArray(key)) {
+        //short-circuit everything else
+        locale = loadLocale(key);
+        if (locale) {
+            return locale;
+        }
+        key = [key];
+    }
+
+    return chooseLocale(key);
+}
+
+function listLocales() {
+    return keys$1(locales);
+}
+
+function checkOverflow (m) {
+    var overflow;
+    var a = m._a;
+
+    if (a && getParsingFlags(m).overflow === -2) {
+        overflow =
+            a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
+            a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+            a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+            a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
+            a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
+            a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+            -1;
+
+        if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+            overflow = DATE;
+        }
+        if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+            overflow = WEEK;
+        }
+        if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+            overflow = WEEKDAY;
+        }
+
+        getParsingFlags(m).overflow = overflow;
+    }
+
+    return m;
+}
+
+// iso 8601 regex
+// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+
+var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+var isoDates = [
+    ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+    ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+    ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+    ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+    ['YYYY-DDD', /\d{4}-\d{3}/],
+    ['YYYY-MM', /\d{4}-\d\d/, false],
+    ['YYYYYYMMDD', /[+-]\d{10}/],
+    ['YYYYMMDD', /\d{8}/],
+    // YYYYMM is NOT allowed by the standard
+    ['GGGG[W]WWE', /\d{4}W\d{3}/],
+    ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+    ['YYYYDDD', /\d{7}/]
+];
+
+// iso time formats and regexes
+var isoTimes = [
+    ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+    ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+    ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+    ['HH:mm', /\d\d:\d\d/],
+    ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+    ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+    ['HHmmss', /\d\d\d\d\d\d/],
+    ['HHmm', /\d\d\d\d/],
+    ['HH', /\d\d/]
+];
+
+var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
+// date from iso format
+function configFromISO(config) {
+    var i, l,
+        string = config._i,
+        match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+        allowTime, dateFormat, timeFormat, tzFormat;
+
+    if (match) {
+        getParsingFlags(config).iso = true;
+
+        for (i = 0, l = isoDates.length; i < l; i++) {
+            if (isoDates[i][1].exec(match[1])) {
+                dateFormat = isoDates[i][0];
+                allowTime = isoDates[i][2] !== false;
+                break;
+            }
+        }
+        if (dateFormat == null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[3]) {
+            for (i = 0, l = isoTimes.length; i < l; i++) {
+                if (isoTimes[i][1].exec(match[3])) {
+                    // match[2] should be 'T' or space
+                    timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                    break;
+                }
+            }
+            if (timeFormat == null) {
+                config._isValid = false;
+                return;
+            }
+        }
+        if (!allowTime && timeFormat != null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[4]) {
+            if (tzRegex.exec(match[4])) {
+                tzFormat = 'Z';
+            } else {
+                config._isValid = false;
+                return;
+            }
+        }
+        config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+        configFromStringAndFormat(config);
+    } else {
+        config._isValid = false;
+    }
+}
+
+// date from iso format or fallback
+function configFromString(config) {
+    var matched = aspNetJsonRegex.exec(config._i);
+
+    if (matched !== null) {
+        config._d = new Date(+matched[1]);
+        return;
+    }
+
+    configFromISO(config);
+    if (config._isValid === false) {
+        delete config._isValid;
+        hooks.createFromInputFallback(config);
+    }
+}
+
+hooks.createFromInputFallback = deprecate(
+    'value provided is not in a recognized ISO format. moment construction falls back to js Date(), ' +
+    'which is not reliable across all browsers and versions. Non ISO date formats are ' +
+    'discouraged and will be removed in an upcoming major release. Please refer to ' +
+    'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+    function (config) {
+        config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+    }
+);
+
+// Pick the first defined of two or three arguments.
+function defaults(a, b, c) {
+    if (a != null) {
+        return a;
+    }
+    if (b != null) {
+        return b;
+    }
+    return c;
+}
+
+function currentDateArray(config) {
+    // hooks is actually the exported moment object
+    var nowValue = new Date(hooks.now());
+    if (config._useUTC) {
+        return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
+    }
+    return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+}
+
+// convert an array to a date.
+// the array should mirror the parameters below
+// note: all values past the year are optional and will default to the lowest possible value.
+// [year, month, day , hour, minute, second, millisecond]
+function configFromArray (config) {
+    var i, date, input = [], currentDate, yearToUse;
+
+    if (config._d) {
+        return;
+    }
+
+    currentDate = currentDateArray(config);
+
+    //compute day of the year from weeks and weekdays
+    if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+        dayOfYearFromWeekInfo(config);
+    }
+
+    //if the day of the year is set, figure out what it is
+    if (config._dayOfYear) {
+        yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+        if (config._dayOfYear > daysInYear(yearToUse)) {
+            getParsingFlags(config)._overflowDayOfYear = true;
+        }
+
+        date = createUTCDate(yearToUse, 0, config._dayOfYear);
+        config._a[MONTH] = date.getUTCMonth();
+        config._a[DATE] = date.getUTCDate();
+    }
+
+    // Default to current date.
+    // * if no year, month, day of month are given, default to today
+    // * if day of month is given, default month and year
+    // * if month is given, default only year
+    // * if year is given, don't default anything
+    for (i = 0; i < 3 && config._a[i] == null; ++i) {
+        config._a[i] = input[i] = currentDate[i];
+    }
+
+    // Zero out whatever was not defaulted, including time
+    for (; i < 7; i++) {
+        config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    }
+
+    // Check for 24:00:00.000
+    if (config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0) {
+        config._nextDay = true;
+        config._a[HOUR] = 0;
+    }
+
+    config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+    // Apply timezone offset from input. The actual utcOffset can be changed
+    // with parseZone.
+    if (config._tzm != null) {
+        config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+    }
+
+    if (config._nextDay) {
+        config._a[HOUR] = 24;
+    }
+}
+
+function dayOfYearFromWeekInfo(config) {
+    var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
+
+    w = config._w;
+    if (w.GG != null || w.W != null || w.E != null) {
+        dow = 1;
+        doy = 4;
+
+        // TODO: We need to take the current isoWeekYear, but that depends on
+        // how we interpret now (local, utc, fixed offset). So create
+        // a now version of current config (take local/utc/offset flags, and
+        // create now).
+        weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
+        week = defaults(w.W, 1);
+        weekday = defaults(w.E, 1);
+        if (weekday < 1 || weekday > 7) {
+            weekdayOverflow = true;
+        }
+    } else {
+        dow = config._locale._week.dow;
+        doy = config._locale._week.doy;
+
+        var curWeek = weekOfYear(createLocal(), dow, doy);
+
+        weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+        // Default to current week.
+        week = defaults(w.w, curWeek.week);
+
+        if (w.d != null) {
+            // weekday -- low day numbers are considered next week
+            weekday = w.d;
+            if (weekday < 0 || weekday > 6) {
+                weekdayOverflow = true;
+            }
+        } else if (w.e != null) {
+            // local weekday -- counting starts from begining of week
+            weekday = w.e + dow;
+            if (w.e < 0 || w.e > 6) {
+                weekdayOverflow = true;
+            }
+        } else {
+            // default to begining of week
+            weekday = dow;
+        }
+    }
+    if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+        getParsingFlags(config)._overflowWeeks = true;
+    } else if (weekdayOverflow != null) {
+        getParsingFlags(config)._overflowWeekday = true;
+    } else {
+        temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+        config._a[YEAR] = temp.year;
+        config._dayOfYear = temp.dayOfYear;
+    }
+}
+
+// constant that refers to the ISO standard
+hooks.ISO_8601 = function () {};
+
+// date from string and format string
+function configFromStringAndFormat(config) {
+    // TODO: Move this to another part of the creation flow to prevent circular deps
+    if (config._f === hooks.ISO_8601) {
+        configFromISO(config);
+        return;
+    }
+
+    config._a = [];
+    getParsingFlags(config).empty = true;
+
+    // This array is used to make a Date, either with `new Date` or `Date.UTC`
+    var string = '' + config._i,
+        i, parsedInput, tokens, token, skipped,
+        stringLength = string.length,
+        totalParsedInputLength = 0;
+
+    tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+    for (i = 0; i < tokens.length; i++) {
+        token = tokens[i];
+        parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+        // console.log('token', token, 'parsedInput', parsedInput,
+        //         'regex', getParseRegexForToken(token, config));
+        if (parsedInput) {
+            skipped = string.substr(0, string.indexOf(parsedInput));
+            if (skipped.length > 0) {
+                getParsingFlags(config).unusedInput.push(skipped);
+            }
+            string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+            totalParsedInputLength += parsedInput.length;
+        }
+        // don't parse if it's not a known token
+        if (formatTokenFunctions[token]) {
+            if (parsedInput) {
+                getParsingFlags(config).empty = false;
+            }
+            else {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+            addTimeToArrayFromToken(token, parsedInput, config);
+        }
+        else if (config._strict && !parsedInput) {
+            getParsingFlags(config).unusedTokens.push(token);
+        }
+    }
+
+    // add remaining unparsed input length to the string
+    getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+    if (string.length > 0) {
+        getParsingFlags(config).unusedInput.push(string);
+    }
+
+    // clear _12h flag if hour is <= 12
+    if (config._a[HOUR] <= 12 &&
+        getParsingFlags(config).bigHour === true &&
+        config._a[HOUR] > 0) {
+        getParsingFlags(config).bigHour = undefined;
+    }
+
+    getParsingFlags(config).parsedDateParts = config._a.slice(0);
+    getParsingFlags(config).meridiem = config._meridiem;
+    // handle meridiem
+    config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+
+    configFromArray(config);
+    checkOverflow(config);
+}
+
+
+function meridiemFixWrap (locale, hour, meridiem) {
+    var isPm;
+
+    if (meridiem == null) {
+        // nothing to do
+        return hour;
+    }
+    if (locale.meridiemHour != null) {
+        return locale.meridiemHour(hour, meridiem);
+    } else if (locale.isPM != null) {
+        // Fallback
+        isPm = locale.isPM(meridiem);
+        if (isPm && hour < 12) {
+            hour += 12;
+        }
+        if (!isPm && hour === 12) {
+            hour = 0;
+        }
+        return hour;
+    } else {
+        // this is not supposed to happen
+        return hour;
+    }
+}
+
+// date from string and array of format strings
+function configFromStringAndArray(config) {
+    var tempConfig,
+        bestMoment,
+
+        scoreToBeat,
+        i,
+        currentScore;
+
+    if (config._f.length === 0) {
+        getParsingFlags(config).invalidFormat = true;
+        config._d = new Date(NaN);
+        return;
+    }
+
+    for (i = 0; i < config._f.length; i++) {
+        currentScore = 0;
+        tempConfig = copyConfig({}, config);
+        if (config._useUTC != null) {
+            tempConfig._useUTC = config._useUTC;
+        }
+        tempConfig._f = config._f[i];
+        configFromStringAndFormat(tempConfig);
+
+        if (!isValid(tempConfig)) {
+            continue;
+        }
+
+        // if there is any input that was not parsed add a penalty for that format
+        currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+        //or tokens
+        currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+        getParsingFlags(tempConfig).score = currentScore;
+
+        if (scoreToBeat == null || currentScore < scoreToBeat) {
+            scoreToBeat = currentScore;
+            bestMoment = tempConfig;
+        }
+    }
+
+    extend(config, bestMoment || tempConfig);
+}
+
+function configFromObject(config) {
+    if (config._d) {
+        return;
+    }
+
+    var i = normalizeObjectUnits(config._i);
+    config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+        return obj && parseInt(obj, 10);
+    });
+
+    configFromArray(config);
+}
+
+function createFromConfig (config) {
+    var res = new Moment(checkOverflow(prepareConfig(config)));
+    if (res._nextDay) {
+        // Adding is smart enough around DST
+        res.add(1, 'd');
+        res._nextDay = undefined;
+    }
+
+    return res;
+}
+
+function prepareConfig (config) {
+    var input = config._i,
+        format = config._f;
+
+    config._locale = config._locale || getLocale(config._l);
+
+    if (input === null || (format === undefined && input === '')) {
+        return createInvalid({nullInput: true});
+    }
+
+    if (typeof input === 'string') {
+        config._i = input = config._locale.preparse(input);
+    }
+
+    if (isMoment(input)) {
+        return new Moment(checkOverflow(input));
+    } else if (isDate(input)) {
+        config._d = input;
+    } else if (isArray(format)) {
+        configFromStringAndArray(config);
+    } else if (format) {
+        configFromStringAndFormat(config);
+    }  else {
+        configFromInput(config);
+    }
+
+    if (!isValid(config)) {
+        config._d = null;
+    }
+
+    return config;
+}
+
+function configFromInput(config) {
+    var input = config._i;
+    if (input === undefined) {
+        config._d = new Date(hooks.now());
+    } else if (isDate(input)) {
+        config._d = new Date(input.valueOf());
+    } else if (typeof input === 'string') {
+        configFromString(config);
+    } else if (isArray(input)) {
+        config._a = map(input.slice(0), function (obj) {
+            return parseInt(obj, 10);
+        });
+        configFromArray(config);
+    } else if (typeof(input) === 'object') {
+        configFromObject(config);
+    } else if (isNumber(input)) {
+        // from milliseconds
+        config._d = new Date(input);
+    } else {
+        hooks.createFromInputFallback(config);
+    }
+}
+
+function createLocalOrUTC (input, format, locale, strict, isUTC) {
+    var c = {};
+
+    if (locale === true || locale === false) {
+        strict = locale;
+        locale = undefined;
+    }
+
+    if ((isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)) {
+        input = undefined;
+    }
+    // object construction must be done this way.
+    // https://github.com/moment/moment/issues/1423
+    c._isAMomentObject = true;
+    c._useUTC = c._isUTC = isUTC;
+    c._l = locale;
+    c._i = input;
+    c._f = format;
+    c._strict = strict;
+
+    return createFromConfig(c);
+}
+
+function createLocal (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, false);
+}
+
+var prototypeMin = deprecate(
+    'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other < this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+var prototypeMax = deprecate(
+    'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other > this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+// Pick a moment m from moments so that m[fn](other) is true for all
+// other. This relies on the function fn to be transitive.
+//
+// moments should either be an array of moment objects or an array, whose
+// first element is an array of moment objects.
+function pickBy(fn, moments) {
+    var res, i;
+    if (moments.length === 1 && isArray(moments[0])) {
+        moments = moments[0];
+    }
+    if (!moments.length) {
+        return createLocal();
+    }
+    res = moments[0];
+    for (i = 1; i < moments.length; ++i) {
+        if (!moments[i].isValid() || moments[i][fn](res)) {
+            res = moments[i];
+        }
+    }
+    return res;
+}
+
+// TODO: Use [].sort instead?
+function min () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isBefore', args);
+}
+
+function max () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isAfter', args);
+}
+
+var now = function () {
+    return Date.now ? Date.now() : +(new Date());
+};
+
+function Duration (duration) {
+    var normalizedInput = normalizeObjectUnits(duration),
+        years = normalizedInput.year || 0,
+        quarters = normalizedInput.quarter || 0,
+        months = normalizedInput.month || 0,
+        weeks = normalizedInput.week || 0,
+        days = normalizedInput.day || 0,
+        hours = normalizedInput.hour || 0,
+        minutes = normalizedInput.minute || 0,
+        seconds = normalizedInput.second || 0,
+        milliseconds = normalizedInput.millisecond || 0;
+
+    // representation for dateAddRemove
+    this._milliseconds = +milliseconds +
+        seconds * 1e3 + // 1000
+        minutes * 6e4 + // 1000 * 60
+        hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+    // Because of dateAddRemove treats 24 hours as different from a
+    // day when working around DST, we need to store them separately
+    this._days = +days +
+        weeks * 7;
+    // It is impossible translate months into days without knowing
+    // which months you are are talking about, so we have to store
+    // it separately.
+    this._months = +months +
+        quarters * 3 +
+        years * 12;
+
+    this._data = {};
+
+    this._locale = getLocale();
+
+    this._bubble();
+}
+
+function isDuration (obj) {
+    return obj instanceof Duration;
+}
+
+function absRound (number) {
+    if (number < 0) {
+        return Math.round(-1 * number) * -1;
+    } else {
+        return Math.round(number);
+    }
+}
+
+// FORMATTING
+
+function offset (token, separator) {
+    addFormatToken(token, 0, 0, function () {
+        var offset = this.utcOffset();
+        var sign = '+';
+        if (offset < 0) {
+            offset = -offset;
+            sign = '-';
+        }
+        return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+    });
+}
+
+offset('Z', ':');
+offset('ZZ', '');
+
+// PARSING
+
+addRegexToken('Z',  matchShortOffset);
+addRegexToken('ZZ', matchShortOffset);
+addParseToken(['Z', 'ZZ'], function (input, array, config) {
+    config._useUTC = true;
+    config._tzm = offsetFromString(matchShortOffset, input);
+});
+
+// HELPERS
+
+// timezone chunker
+// '+10:00' > ['10',  '00']
+// '-1530'  > ['-15', '30']
+var chunkOffset = /([\+\-]|\d\d)/gi;
+
+function offsetFromString(matcher, string) {
+    var matches = (string || '').match(matcher);
+
+    if (matches === null) {
+        return null;
+    }
+
+    var chunk   = matches[matches.length - 1] || [];
+    var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+    var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+    return minutes === 0 ?
+      0 :
+      parts[0] === '+' ? minutes : -minutes;
+}
+
+// Return a moment from input, that is local/utc/zone equivalent to model.
+function cloneWithOffset(input, model) {
+    var res, diff;
+    if (model._isUTC) {
+        res = model.clone();
+        diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
+        // Use low-level api, because this fn is low-level api.
+        res._d.setTime(res._d.valueOf() + diff);
+        hooks.updateOffset(res, false);
+        return res;
+    } else {
+        return createLocal(input).local();
+    }
+}
+
+function getDateOffset (m) {
+    // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+    // https://github.com/moment/moment/pull/1871
+    return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+}
+
+// HOOKS
+
+// This function will be called whenever a moment is mutated.
+// It is intended to keep the offset in sync with the timezone.
+hooks.updateOffset = function () {};
+
+// MOMENTS
+
+// keepLocalTime = true means only change the timezone, without
+// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+// +0200, so we adjust the time as needed, to be valid.
+//
+// Keeping the time actually adds/subtracts (one hour)
+// from the actual represented time. That is why we call updateOffset
+// a second time. In case it wants us to change the offset again
+// _changeInProgress == true case, then we have to adjust, because
+// there is no such time in the given timezone.
+function getSetOffset (input, keepLocalTime) {
+    var offset = this._offset || 0,
+        localAdjust;
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    if (input != null) {
+        if (typeof input === 'string') {
+            input = offsetFromString(matchShortOffset, input);
+            if (input === null) {
+                return this;
+            }
+        } else if (Math.abs(input) < 16) {
+            input = input * 60;
+        }
+        if (!this._isUTC && keepLocalTime) {
+            localAdjust = getDateOffset(this);
+        }
+        this._offset = input;
+        this._isUTC = true;
+        if (localAdjust != null) {
+            this.add(localAdjust, 'm');
+        }
+        if (offset !== input) {
+            if (!keepLocalTime || this._changeInProgress) {
+                addSubtract(this, createDuration(input - offset, 'm'), 1, false);
+            } else if (!this._changeInProgress) {
+                this._changeInProgress = true;
+                hooks.updateOffset(this, true);
+                this._changeInProgress = null;
+            }
+        }
+        return this;
+    } else {
+        return this._isUTC ? offset : getDateOffset(this);
+    }
+}
+
+function getSetZone (input, keepLocalTime) {
+    if (input != null) {
+        if (typeof input !== 'string') {
+            input = -input;
+        }
+
+        this.utcOffset(input, keepLocalTime);
+
+        return this;
+    } else {
+        return -this.utcOffset();
+    }
+}
+
+function setOffsetToUTC (keepLocalTime) {
+    return this.utcOffset(0, keepLocalTime);
+}
+
+function setOffsetToLocal (keepLocalTime) {
+    if (this._isUTC) {
+        this.utcOffset(0, keepLocalTime);
+        this._isUTC = false;
+
+        if (keepLocalTime) {
+            this.subtract(getDateOffset(this), 'm');
+        }
+    }
+    return this;
+}
+
+function setOffsetToParsedOffset () {
+    if (this._tzm != null) {
+        this.utcOffset(this._tzm);
+    } else if (typeof this._i === 'string') {
+        var tZone = offsetFromString(matchOffset, this._i);
+        if (tZone != null) {
+            this.utcOffset(tZone);
+        }
+        else {
+            this.utcOffset(0, true);
+        }
+    }
+    return this;
+}
+
+function hasAlignedHourOffset (input) {
+    if (!this.isValid()) {
+        return false;
+    }
+    input = input ? createLocal(input).utcOffset() : 0;
+
+    return (this.utcOffset() - input) % 60 === 0;
+}
+
+function isDaylightSavingTime () {
+    return (
+        this.utcOffset() > this.clone().month(0).utcOffset() ||
+        this.utcOffset() > this.clone().month(5).utcOffset()
+    );
+}
+
+function isDaylightSavingTimeShifted () {
+    if (!isUndefined(this._isDSTShifted)) {
+        return this._isDSTShifted;
+    }
+
+    var c = {};
+
+    copyConfig(c, this);
+    c = prepareConfig(c);
+
+    if (c._a) {
+        var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+        this._isDSTShifted = this.isValid() &&
+            compareArrays(c._a, other.toArray()) > 0;
+    } else {
+        this._isDSTShifted = false;
+    }
+
+    return this._isDSTShifted;
+}
+
+function isLocal () {
+    return this.isValid() ? !this._isUTC : false;
+}
+
+function isUtcOffset () {
+    return this.isValid() ? this._isUTC : false;
+}
+
+function isUtc () {
+    return this.isValid() ? this._isUTC && this._offset === 0 : false;
+}
+
+// ASP.NET json date format regex
+var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
+
+// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+// and further modified to allow for strings containing both week and day
+var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;
+
+function createDuration (input, key) {
+    var duration = input,
+        // matching against regexp is expensive, do it on demand
+        match = null,
+        sign,
+        ret,
+        diffRes;
+
+    if (isDuration(input)) {
+        duration = {
+            ms : input._milliseconds,
+            d  : input._days,
+            M  : input._months
+        };
+    } else if (isNumber(input)) {
+        duration = {};
+        if (key) {
+            duration[key] = input;
+        } else {
+            duration.milliseconds = input;
+        }
+    } else if (!!(match = aspNetRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : 1;
+        duration = {
+            y  : 0,
+            d  : toInt(match[DATE])                         * sign,
+            h  : toInt(match[HOUR])                         * sign,
+            m  : toInt(match[MINUTE])                       * sign,
+            s  : toInt(match[SECOND])                       * sign,
+            ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
+        };
+    } else if (!!(match = isoRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : 1;
+        duration = {
+            y : parseIso(match[2], sign),
+            M : parseIso(match[3], sign),
+            w : parseIso(match[4], sign),
+            d : parseIso(match[5], sign),
+            h : parseIso(match[6], sign),
+            m : parseIso(match[7], sign),
+            s : parseIso(match[8], sign)
+        };
+    } else if (duration == null) {// checks for null or undefined
+        duration = {};
+    } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+        diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
+
+        duration = {};
+        duration.ms = diffRes.milliseconds;
+        duration.M = diffRes.months;
+    }
+
+    ret = new Duration(duration);
+
+    if (isDuration(input) && hasOwnProp(input, '_locale')) {
+        ret._locale = input._locale;
+    }
+
+    return ret;
+}
+
+createDuration.fn = Duration.prototype;
+
+function parseIso (inp, sign) {
+    // We'd normally use ~~inp for this, but unfortunately it also
+    // converts floats to ints.
+    // inp may be undefined, so careful calling replace on it.
+    var res = inp && parseFloat(inp.replace(',', '.'));
+    // apply sign while we're at it
+    return (isNaN(res) ? 0 : res) * sign;
+}
+
+function positiveMomentsDifference(base, other) {
+    var res = {milliseconds: 0, months: 0};
+
+    res.months = other.month() - base.month() +
+        (other.year() - base.year()) * 12;
+    if (base.clone().add(res.months, 'M').isAfter(other)) {
+        --res.months;
+    }
+
+    res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+    return res;
+}
+
+function momentsDifference(base, other) {
+    var res;
+    if (!(base.isValid() && other.isValid())) {
+        return {milliseconds: 0, months: 0};
+    }
+
+    other = cloneWithOffset(other, base);
+    if (base.isBefore(other)) {
+        res = positiveMomentsDifference(base, other);
+    } else {
+        res = positiveMomentsDifference(other, base);
+        res.milliseconds = -res.milliseconds;
+        res.months = -res.months;
+    }
+
+    return res;
+}
+
+// TODO: remove 'name' arg after deprecation is removed
+function createAdder(direction, name) {
+    return function (val, period) {
+        var dur, tmp;
+        //invert the arguments, but complain about it
+        if (period !== null && !isNaN(+period)) {
+            deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
+            'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
+            tmp = val; val = period; period = tmp;
+        }
+
+        val = typeof val === 'string' ? +val : val;
+        dur = createDuration(val, period);
+        addSubtract(this, dur, direction);
+        return this;
+    };
+}
+
+function addSubtract (mom, duration, isAdding, updateOffset) {
+    var milliseconds = duration._milliseconds,
+        days = absRound(duration._days),
+        months = absRound(duration._months);
+
+    if (!mom.isValid()) {
+        // No op
+        return;
+    }
+
+    updateOffset = updateOffset == null ? true : updateOffset;
+
+    if (milliseconds) {
+        mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+    }
+    if (days) {
+        set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
+    }
+    if (months) {
+        setMonth(mom, get(mom, 'Month') + months * isAdding);
+    }
+    if (updateOffset) {
+        hooks.updateOffset(mom, days || months);
+    }
+}
+
+var add      = createAdder(1, 'add');
+var subtract = createAdder(-1, 'subtract');
+
+function getCalendarFormat(myMoment, now) {
+    var diff = myMoment.diff(now, 'days', true);
+    return diff < -6 ? 'sameElse' :
+            diff < -1 ? 'lastWeek' :
+            diff < 0 ? 'lastDay' :
+            diff < 1 ? 'sameDay' :
+            diff < 2 ? 'nextDay' :
+            diff < 7 ? 'nextWeek' : 'sameElse';
+}
+
+function calendar$1 (time, formats) {
+    // We want to compare the start of today, vs this.
+    // Getting start-of-today depends on whether we're local/utc/offset or not.
+    var now = time || createLocal(),
+        sod = cloneWithOffset(now, this).startOf('day'),
+        format = hooks.calendarFormat(this, sod) || 'sameElse';
+
+    var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
+
+    return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
+}
+
+function clone () {
+    return new Moment(this);
+}
+
+function isAfter (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() > localInput.valueOf();
+    } else {
+        return localInput.valueOf() < this.clone().startOf(units).valueOf();
+    }
+}
+
+function isBefore (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() < localInput.valueOf();
+    } else {
+        return this.clone().endOf(units).valueOf() < localInput.valueOf();
+    }
+}
+
+function isBetween (from, to, units, inclusivity) {
+    inclusivity = inclusivity || '()';
+    return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+        (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+}
+
+function isSame (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input),
+        inputMs;
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(units || 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() === localInput.valueOf();
+    } else {
+        inputMs = localInput.valueOf();
+        return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+    }
+}
+
+function isSameOrAfter (input, units) {
+    return this.isSame(input, units) || this.isAfter(input,units);
+}
+
+function isSameOrBefore (input, units) {
+    return this.isSame(input, units) || this.isBefore(input,units);
+}
+
+function diff (input, units, asFloat) {
+    var that,
+        zoneDelta,
+        delta, output;
+
+    if (!this.isValid()) {
+        return NaN;
+    }
+
+    that = cloneWithOffset(input, this);
+
+    if (!that.isValid()) {
+        return NaN;
+    }
+
+    zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+    units = normalizeUnits(units);
+
+    if (units === 'year' || units === 'month' || units === 'quarter') {
+        output = monthDiff(this, that);
+        if (units === 'quarter') {
+            output = output / 3;
+        } else if (units === 'year') {
+            output = output / 12;
+        }
+    } else {
+        delta = this - that;
+        output = units === 'second' ? delta / 1e3 : // 1000
+            units === 'minute' ? delta / 6e4 : // 1000 * 60
+            units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60
+            units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
+            units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
+            delta;
+    }
+    return asFloat ? output : absFloor(output);
+}
+
+function monthDiff (a, b) {
+    // difference in months
+    var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+        // b is in (anchor - 1 month, anchor + 1 month)
+        anchor = a.clone().add(wholeMonthDiff, 'months'),
+        anchor2, adjust;
+
+    if (b - anchor < 0) {
+        anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor - anchor2);
+    } else {
+        anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor2 - anchor);
+    }
+
+    //check for negative zero, return zero if negative zero
+    return -(wholeMonthDiff + adjust) || 0;
+}
+
+hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+function toString () {
+    return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+}
+
+function toISOString () {
+    var m = this.clone().utc();
+    if (0 < m.year() && m.year() <= 9999) {
+        if (isFunction(Date.prototype.toISOString)) {
+            // native implementation is ~50x faster, use it when we can
+            return this.toDate().toISOString();
+        } else {
+            return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+        }
+    } else {
+        return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
+    }
+}
+
+/**
+ * Return a human readable representation of a moment that can
+ * also be evaluated to get a new moment which is the same
+ *
+ * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+ */
+function inspect () {
+    if (!this.isValid()) {
+        return 'moment.invalid(/* ' + this._i + ' */)';
+    }
+    var func = 'moment';
+    var zone = '';
+    if (!this.isLocal()) {
+        func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+        zone = 'Z';
+    }
+    var prefix = '[' + func + '("]';
+    var year = (0 < this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
+    var datetime = '-MM-DD[T]HH:mm:ss.SSS';
+    var suffix = zone + '[")]';
+
+    return this.format(prefix + year + datetime + suffix);
+}
+
+function format (inputString) {
+    if (!inputString) {
+        inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
+    }
+    var output = formatMoment(this, inputString);
+    return this.localeData().postformat(output);
+}
+
+function from (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function fromNow (withoutSuffix) {
+    return this.from(createLocal(), withoutSuffix);
+}
+
+function to (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function toNow (withoutSuffix) {
+    return this.to(createLocal(), withoutSuffix);
+}
+
+// If passed a locale key, it will set the locale for this
+// instance.  Otherwise, it will return the locale configuration
+// variables for this instance.
+function locale (key) {
+    var newLocaleData;
+
+    if (key === undefined) {
+        return this._locale._abbr;
+    } else {
+        newLocaleData = getLocale(key);
+        if (newLocaleData != null) {
+            this._locale = newLocaleData;
+        }
+        return this;
+    }
+}
+
+var lang = deprecate(
+    'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+    function (key) {
+        if (key === undefined) {
+            return this.localeData();
+        } else {
+            return this.locale(key);
+        }
+    }
+);
+
+function localeData () {
+    return this._locale;
+}
+
+function startOf (units) {
+    units = normalizeUnits(units);
+    // the following switch intentionally omits break keywords
+    // to utilize falling through the cases.
+    switch (units) {
+        case 'year':
+            this.month(0);
+            /* falls through */
+        case 'quarter':
+        case 'month':
+            this.date(1);
+            /* falls through */
+        case 'week':
+        case 'isoWeek':
+        case 'day':
+        case 'date':
+            this.hours(0);
+            /* falls through */
+        case 'hour':
+            this.minutes(0);
+            /* falls through */
+        case 'minute':
+            this.seconds(0);
+            /* falls through */
+        case 'second':
+            this.milliseconds(0);
+    }
+
+    // weeks are a special case
+    if (units === 'week') {
+        this.weekday(0);
+    }
+    if (units === 'isoWeek') {
+        this.isoWeekday(1);
+    }
+
+    // quarters are also special
+    if (units === 'quarter') {
+        this.month(Math.floor(this.month() / 3) * 3);
+    }
+
+    return this;
+}
+
+function endOf (units) {
+    units = normalizeUnits(units);
+    if (units === undefined || units === 'millisecond') {
+        return this;
+    }
+
+    // 'date' is an alias for 'day', so it should be considered as such.
+    if (units === 'date') {
+        units = 'day';
+    }
+
+    return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+}
+
+function valueOf () {
+    return this._d.valueOf() - ((this._offset || 0) * 60000);
+}
+
+function unix () {
+    return Math.floor(this.valueOf() / 1000);
+}
+
+function toDate () {
+    return new Date(this.valueOf());
+}
+
+function toArray () {
+    var m = this;
+    return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+}
+
+function toObject () {
+    var m = this;
+    return {
+        years: m.year(),
+        months: m.month(),
+        date: m.date(),
+        hours: m.hours(),
+        minutes: m.minutes(),
+        seconds: m.seconds(),
+        milliseconds: m.milliseconds()
+    };
+}
+
+function toJSON () {
+    // new Date(NaN).toJSON() === null
+    return this.isValid() ? this.toISOString() : null;
+}
+
+function isValid$1 () {
+    return isValid(this);
+}
+
+function parsingFlags () {
+    return extend({}, getParsingFlags(this));
+}
+
+function invalidAt () {
+    return getParsingFlags(this).overflow;
+}
+
+function creationData() {
+    return {
+        input: this._i,
+        format: this._f,
+        locale: this._locale,
+        isUTC: this._isUTC,
+        strict: this._strict
+    };
+}
+
+// FORMATTING
+
+addFormatToken(0, ['gg', 2], 0, function () {
+    return this.weekYear() % 100;
+});
+
+addFormatToken(0, ['GG', 2], 0, function () {
+    return this.isoWeekYear() % 100;
+});
+
+function addWeekYearFormatToken (token, getter) {
+    addFormatToken(0, [token, token.length], 0, getter);
+}
+
+addWeekYearFormatToken('gggg',     'weekYear');
+addWeekYearFormatToken('ggggg',    'weekYear');
+addWeekYearFormatToken('GGGG',  'isoWeekYear');
+addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+// ALIASES
+
+addUnitAlias('weekYear', 'gg');
+addUnitAlias('isoWeekYear', 'GG');
+
+// PRIORITY
+
+addUnitPriority('weekYear', 1);
+addUnitPriority('isoWeekYear', 1);
+
+
+// PARSING
+
+addRegexToken('G',      matchSigned);
+addRegexToken('g',      matchSigned);
+addRegexToken('GG',     match1to2, match2);
+addRegexToken('gg',     match1to2, match2);
+addRegexToken('GGGG',   match1to4, match4);
+addRegexToken('gggg',   match1to4, match4);
+addRegexToken('GGGGG',  match1to6, match6);
+addRegexToken('ggggg',  match1to6, match6);
+
+addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+    week[token.substr(0, 2)] = toInt(input);
+});
+
+addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+    week[token] = hooks.parseTwoDigitYear(input);
+});
+
+// MOMENTS
+
+function getSetWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy);
+}
+
+function getSetISOWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input, this.isoWeek(), this.isoWeekday(), 1, 4);
+}
+
+function getISOWeeksInYear () {
+    return weeksInYear(this.year(), 1, 4);
+}
+
+function getWeeksInYear () {
+    var weekInfo = this.localeData()._week;
+    return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+}
+
+function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+    var weeksTarget;
+    if (input == null) {
+        return weekOfYear(this, dow, doy).year;
+    } else {
+        weeksTarget = weeksInYear(input, dow, doy);
+        if (week > weeksTarget) {
+            week = weeksTarget;
+        }
+        return setWeekAll.call(this, input, week, weekday, dow, doy);
+    }
+}
+
+function setWeekAll(weekYear, week, weekday, dow, doy) {
+    var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+        date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+    this.year(date.getUTCFullYear());
+    this.month(date.getUTCMonth());
+    this.date(date.getUTCDate());
+    return this;
+}
+
+// FORMATTING
+
+addFormatToken('Q', 0, 'Qo', 'quarter');
+
+// ALIASES
+
+addUnitAlias('quarter', 'Q');
+
+// PRIORITY
+
+addUnitPriority('quarter', 7);
+
+// PARSING
+
+addRegexToken('Q', match1);
+addParseToken('Q', function (input, array) {
+    array[MONTH] = (toInt(input) - 1) * 3;
+});
+
+// MOMENTS
+
+function getSetQuarter (input) {
+    return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+}
+
+// FORMATTING
+
+addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+// ALIASES
+
+addUnitAlias('date', 'D');
+
+// PRIOROITY
+addUnitPriority('date', 9);
+
+// PARSING
+
+addRegexToken('D',  match1to2);
+addRegexToken('DD', match1to2, match2);
+addRegexToken('Do', function (isStrict, locale) {
+    return isStrict ? locale._ordinalParse : locale._ordinalParseLenient;
+});
+
+addParseToken(['D', 'DD'], DATE);
+addParseToken('Do', function (input, array) {
+    array[DATE] = toInt(input.match(match1to2)[0], 10);
+});
+
+// MOMENTS
+
+var getSetDayOfMonth = makeGetSet('Date', true);
+
+// FORMATTING
+
+addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+// ALIASES
+
+addUnitAlias('dayOfYear', 'DDD');
+
+// PRIORITY
+addUnitPriority('dayOfYear', 4);
+
+// PARSING
+
+addRegexToken('DDD',  match1to3);
+addRegexToken('DDDD', match3);
+addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+    config._dayOfYear = toInt(input);
+});
+
+// HELPERS
+
+// MOMENTS
+
+function getSetDayOfYear (input) {
+    var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+    return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+}
+
+// FORMATTING
+
+addFormatToken('m', ['mm', 2], 0, 'minute');
+
+// ALIASES
+
+addUnitAlias('minute', 'm');
+
+// PRIORITY
+
+addUnitPriority('minute', 14);
+
+// PARSING
+
+addRegexToken('m',  match1to2);
+addRegexToken('mm', match1to2, match2);
+addParseToken(['m', 'mm'], MINUTE);
+
+// MOMENTS
+
+var getSetMinute = makeGetSet('Minutes', false);
+
+// FORMATTING
+
+addFormatToken('s', ['ss', 2], 0, 'second');
+
+// ALIASES
+
+addUnitAlias('second', 's');
+
+// PRIORITY
+
+addUnitPriority('second', 15);
+
+// PARSING
+
+addRegexToken('s',  match1to2);
+addRegexToken('ss', match1to2, match2);
+addParseToken(['s', 'ss'], SECOND);
+
+// MOMENTS
+
+var getSetSecond = makeGetSet('Seconds', false);
+
+// FORMATTING
+
+addFormatToken('S', 0, 0, function () {
+    return ~~(this.millisecond() / 100);
+});
+
+addFormatToken(0, ['SS', 2], 0, function () {
+    return ~~(this.millisecond() / 10);
+});
+
+addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+addFormatToken(0, ['SSSS', 4], 0, function () {
+    return this.millisecond() * 10;
+});
+addFormatToken(0, ['SSSSS', 5], 0, function () {
+    return this.millisecond() * 100;
+});
+addFormatToken(0, ['SSSSSS', 6], 0, function () {
+    return this.millisecond() * 1000;
+});
+addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+    return this.millisecond() * 10000;
+});
+addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+    return this.millisecond() * 100000;
+});
+addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+    return this.millisecond() * 1000000;
+});
+
+
+// ALIASES
+
+addUnitAlias('millisecond', 'ms');
+
+// PRIORITY
+
+addUnitPriority('millisecond', 16);
+
+// PARSING
+
+addRegexToken('S',    match1to3, match1);
+addRegexToken('SS',   match1to3, match2);
+addRegexToken('SSS',  match1to3, match3);
+
+var token;
+for (token = 'SSSS'; token.length <= 9; token += 'S') {
+    addRegexToken(token, matchUnsigned);
+}
+
+function parseMs(input, array) {
+    array[MILLISECOND] = toInt(('0.' + input) * 1000);
+}
+
+for (token = 'S'; token.length <= 9; token += 'S') {
+    addParseToken(token, parseMs);
+}
+// MOMENTS
+
+var getSetMillisecond = makeGetSet('Milliseconds', false);
+
+// FORMATTING
+
+addFormatToken('z',  0, 0, 'zoneAbbr');
+addFormatToken('zz', 0, 0, 'zoneName');
+
+// MOMENTS
+
+function getZoneAbbr () {
+    return this._isUTC ? 'UTC' : '';
+}
+
+function getZoneName () {
+    return this._isUTC ? 'Coordinated Universal Time' : '';
+}
+
+var proto = Moment.prototype;
+
+proto.add               = add;
+proto.calendar          = calendar$1;
+proto.clone             = clone;
+proto.diff              = diff;
+proto.endOf             = endOf;
+proto.format            = format;
+proto.from              = from;
+proto.fromNow           = fromNow;
+proto.to                = to;
+proto.toNow             = toNow;
+proto.get               = stringGet;
+proto.invalidAt         = invalidAt;
+proto.isAfter           = isAfter;
+proto.isBefore          = isBefore;
+proto.isBetween         = isBetween;
+proto.isSame            = isSame;
+proto.isSameOrAfter     = isSameOrAfter;
+proto.isSameOrBefore    = isSameOrBefore;
+proto.isValid           = isValid$1;
+proto.lang              = lang;
+proto.locale            = locale;
+proto.localeData        = localeData;
+proto.max               = prototypeMax;
+proto.min               = prototypeMin;
+proto.parsingFlags      = parsingFlags;
+proto.set               = stringSet;
+proto.startOf           = startOf;
+proto.subtract          = subtract;
+proto.toArray           = toArray;
+proto.toObject          = toObject;
+proto.toDate            = toDate;
+proto.toISOString       = toISOString;
+proto.inspect           = inspect;
+proto.toJSON            = toJSON;
+proto.toString          = toString;
+proto.unix              = unix;
+proto.valueOf           = valueOf;
+proto.creationData      = creationData;
+
+// Year
+proto.year       = getSetYear;
+proto.isLeapYear = getIsLeapYear;
+
+// Week Year
+proto.weekYear    = getSetWeekYear;
+proto.isoWeekYear = getSetISOWeekYear;
+
+// Quarter
+proto.quarter = proto.quarters = getSetQuarter;
+
+// Month
+proto.month       = getSetMonth;
+proto.daysInMonth = getDaysInMonth;
+
+// Week
+proto.week           = proto.weeks        = getSetWeek;
+proto.isoWeek        = proto.isoWeeks     = getSetISOWeek;
+proto.weeksInYear    = getWeeksInYear;
+proto.isoWeeksInYear = getISOWeeksInYear;
+
+// Day
+proto.date       = getSetDayOfMonth;
+proto.day        = proto.days             = getSetDayOfWeek;
+proto.weekday    = getSetLocaleDayOfWeek;
+proto.isoWeekday = getSetISODayOfWeek;
+proto.dayOfYear  = getSetDayOfYear;
+
+// Hour
+proto.hour = proto.hours = getSetHour;
+
+// Minute
+proto.minute = proto.minutes = getSetMinute;
+
+// Second
+proto.second = proto.seconds = getSetSecond;
+
+// Millisecond
+proto.millisecond = proto.milliseconds = getSetMillisecond;
+
+// Offset
+proto.utcOffset            = getSetOffset;
+proto.utc                  = setOffsetToUTC;
+proto.local                = setOffsetToLocal;
+proto.parseZone            = setOffsetToParsedOffset;
+proto.hasAlignedHourOffset = hasAlignedHourOffset;
+proto.isDST                = isDaylightSavingTime;
+proto.isLocal              = isLocal;
+proto.isUtcOffset          = isUtcOffset;
+proto.isUtc                = isUtc;
+proto.isUTC                = isUtc;
+
+// Timezone
+proto.zoneAbbr = getZoneAbbr;
+proto.zoneName = getZoneName;
+
+// Deprecations
+proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
+proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
+
+function createUnix (input) {
+    return createLocal(input * 1000);
+}
+
+function createInZone () {
+    return createLocal.apply(null, arguments).parseZone();
+}
+
+function preParsePostFormat (string) {
+    return string;
+}
+
+var proto$1 = Locale.prototype;
+
+proto$1.calendar        = calendar;
+proto$1.longDateFormat  = longDateFormat;
+proto$1.invalidDate     = invalidDate;
+proto$1.ordinal         = ordinal;
+proto$1.preparse        = preParsePostFormat;
+proto$1.postformat      = preParsePostFormat;
+proto$1.relativeTime    = relativeTime;
+proto$1.pastFuture      = pastFuture;
+proto$1.set             = set;
+
+// Month
+proto$1.months            =        localeMonths;
+proto$1.monthsShort       =        localeMonthsShort;
+proto$1.monthsParse       =        localeMonthsParse;
+proto$1.monthsRegex       = monthsRegex;
+proto$1.monthsShortRegex  = monthsShortRegex;
+
+// Week
+proto$1.week = localeWeek;
+proto$1.firstDayOfYear = localeFirstDayOfYear;
+proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+// Day of Week
+proto$1.weekdays       =        localeWeekdays;
+proto$1.weekdaysMin    =        localeWeekdaysMin;
+proto$1.weekdaysShort  =        localeWeekdaysShort;
+proto$1.weekdaysParse  =        localeWeekdaysParse;
+
+proto$1.weekdaysRegex       =        weekdaysRegex;
+proto$1.weekdaysShortRegex  =        weekdaysShortRegex;
+proto$1.weekdaysMinRegex    =        weekdaysMinRegex;
+
+// Hours
+proto$1.isPM = localeIsPM;
+proto$1.meridiem = localeMeridiem;
+
+function get$1 (format, index, field, setter) {
+    var locale = getLocale();
+    var utc = createUTC().set(setter, index);
+    return locale[field](utc, format);
+}
+
+function listMonthsImpl (format, index, field) {
+    if (isNumber(format)) {
+        index = format;
+        format = undefined;
+    }
+
+    format = format || '';
+
+    if (index != null) {
+        return get$1(format, index, field, 'month');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 12; i++) {
+        out[i] = get$1(format, i, field, 'month');
+    }
+    return out;
+}
+
+// ()
+// (5)
+// (fmt, 5)
+// (fmt)
+// (true)
+// (true, 5)
+// (true, fmt, 5)
+// (true, fmt)
+function listWeekdaysImpl (localeSorted, format, index, field) {
+    if (typeof localeSorted === 'boolean') {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    } else {
+        format = localeSorted;
+        index = format;
+        localeSorted = false;
+
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    }
+
+    var locale = getLocale(),
+        shift = localeSorted ? locale._week.dow : 0;
+
+    if (index != null) {
+        return get$1(format, (index + shift) % 7, field, 'day');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 7; i++) {
+        out[i] = get$1(format, (i + shift) % 7, field, 'day');
+    }
+    return out;
+}
+
+function listMonths (format, index) {
+    return listMonthsImpl(format, index, 'months');
+}
+
+function listMonthsShort (format, index) {
+    return listMonthsImpl(format, index, 'monthsShort');
+}
+
+function listWeekdays (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+}
+
+function listWeekdaysShort (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+}
+
+function listWeekdaysMin (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+}
+
+getSetGlobalLocale('en', {
+    ordinalParse: /\d{1,2}(th|st|nd|rd)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (toInt(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    }
+});
+
+// Side effect imports
+hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
+hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
+
+var mathAbs = Math.abs;
+
+function abs () {
+    var data           = this._data;
+
+    this._milliseconds = mathAbs(this._milliseconds);
+    this._days         = mathAbs(this._days);
+    this._months       = mathAbs(this._months);
+
+    data.milliseconds  = mathAbs(data.milliseconds);
+    data.seconds       = mathAbs(data.seconds);
+    data.minutes       = mathAbs(data.minutes);
+    data.hours         = mathAbs(data.hours);
+    data.months        = mathAbs(data.months);
+    data.years         = mathAbs(data.years);
+
+    return this;
+}
+
+function addSubtract$1 (duration, input, value, direction) {
+    var other = createDuration(input, value);
+
+    duration._milliseconds += direction * other._milliseconds;
+    duration._days         += direction * other._days;
+    duration._months       += direction * other._months;
+
+    return duration._bubble();
+}
+
+// supports only 2.0-style add(1, 's') or add(duration)
+function add$1 (input, value) {
+    return addSubtract$1(this, input, value, 1);
+}
+
+// supports only 2.0-style subtract(1, 's') or subtract(duration)
+function subtract$1 (input, value) {
+    return addSubtract$1(this, input, value, -1);
+}
+
+function absCeil (number) {
+    if (number < 0) {
+        return Math.floor(number);
+    } else {
+        return Math.ceil(number);
+    }
+}
+
+function bubble () {
+    var milliseconds = this._milliseconds;
+    var days         = this._days;
+    var months       = this._months;
+    var data         = this._data;
+    var seconds, minutes, hours, years, monthsFromDays;
+
+    // if we have a mix of positive and negative values, bubble down first
+    // check: https://github.com/moment/moment/issues/2166
+    if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+            (milliseconds <= 0 && days <= 0 && months <= 0))) {
+        milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+        days = 0;
+        months = 0;
+    }
+
+    // The following code bubbles up values, see the tests for
+    // examples of what that means.
+    data.milliseconds = milliseconds % 1000;
+
+    seconds           = absFloor(milliseconds / 1000);
+    data.seconds      = seconds % 60;
+
+    minutes           = absFloor(seconds / 60);
+    data.minutes      = minutes % 60;
+
+    hours             = absFloor(minutes / 60);
+    data.hours        = hours % 24;
+
+    days += absFloor(hours / 24);
+
+    // convert days to months
+    monthsFromDays = absFloor(daysToMonths(days));
+    months += monthsFromDays;
+    days -= absCeil(monthsToDays(monthsFromDays));
+
+    // 12 months -> 1 year
+    years = absFloor(months / 12);
+    months %= 12;
+
+    data.days   = days;
+    data.months = months;
+    data.years  = years;
+
+    return this;
+}
+
+function daysToMonths (days) {
+    // 400 years have 146097 days (taking into account leap year rules)
+    // 400 years have 12 months === 4800
+    return days * 4800 / 146097;
+}
+
+function monthsToDays (months) {
+    // the reverse of daysToMonths
+    return months * 146097 / 4800;
+}
+
+function as (units) {
+    var days;
+    var months;
+    var milliseconds = this._milliseconds;
+
+    units = normalizeUnits(units);
+
+    if (units === 'month' || units === 'year') {
+        days   = this._days   + milliseconds / 864e5;
+        months = this._months + daysToMonths(days);
+        return units === 'month' ? months : months / 12;
+    } else {
+        // handle milliseconds separately because of floating point math errors (issue #1867)
+        days = this._days + Math.round(monthsToDays(this._months));
+        switch (units) {
+            case 'week'   : return days / 7     + milliseconds / 6048e5;
+            case 'day'    : return days         + milliseconds / 864e5;
+            case 'hour'   : return days * 24    + milliseconds / 36e5;
+            case 'minute' : return days * 1440  + milliseconds / 6e4;
+            case 'second' : return days * 86400 + milliseconds / 1000;
+            // Math.floor prevents floating point math errors here
+            case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+            default: throw new Error('Unknown unit ' + units);
+        }
+    }
+}
+
+// TODO: Use this.as('ms')?
+function valueOf$1 () {
+    return (
+        this._milliseconds +
+        this._days * 864e5 +
+        (this._months % 12) * 2592e6 +
+        toInt(this._months / 12) * 31536e6
+    );
+}
+
+function makeAs (alias) {
+    return function () {
+        return this.as(alias);
+    };
+}
+
+var asMilliseconds = makeAs('ms');
+var asSeconds      = makeAs('s');
+var asMinutes      = makeAs('m');
+var asHours        = makeAs('h');
+var asDays         = makeAs('d');
+var asWeeks        = makeAs('w');
+var asMonths       = makeAs('M');
+var asYears        = makeAs('y');
+
+function get$2 (units) {
+    units = normalizeUnits(units);
+    return this[units + 's']();
+}
+
+function makeGetter(name) {
+    return function () {
+        return this._data[name];
+    };
+}
+
+var milliseconds = makeGetter('milliseconds');
+var seconds      = makeGetter('seconds');
+var minutes      = makeGetter('minutes');
+var hours        = makeGetter('hours');
+var days         = makeGetter('days');
+var months       = makeGetter('months');
+var years        = makeGetter('years');
+
+function weeks () {
+    return absFloor(this.days() / 7);
+}
+
+var round = Math.round;
+var thresholds = {
+    s: 45,  // seconds to minute
+    m: 45,  // minutes to hour
+    h: 22,  // hours to day
+    d: 26,  // days to month
+    M: 11   // months to year
+};
+
+// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+    return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+}
+
+function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
+    var duration = createDuration(posNegDuration).abs();
+    var seconds  = round(duration.as('s'));
+    var minutes  = round(duration.as('m'));
+    var hours    = round(duration.as('h'));
+    var days     = round(duration.as('d'));
+    var months   = round(duration.as('M'));
+    var years    = round(duration.as('y'));
+
+    var a = seconds < thresholds.s && ['s', seconds]  ||
+            minutes <= 1           && ['m']           ||
+            minutes < thresholds.m && ['mm', minutes] ||
+            hours   <= 1           && ['h']           ||
+            hours   < thresholds.h && ['hh', hours]   ||
+            days    <= 1           && ['d']           ||
+            days    < thresholds.d && ['dd', days]    ||
+            months  <= 1           && ['M']           ||
+            months  < thresholds.M && ['MM', months]  ||
+            years   <= 1           && ['y']           || ['yy', years];
+
+    a[2] = withoutSuffix;
+    a[3] = +posNegDuration > 0;
+    a[4] = locale;
+    return substituteTimeAgo.apply(null, a);
+}
+
+// This function allows you to set the rounding function for relative time strings
+function getSetRelativeTimeRounding (roundingFunction) {
+    if (roundingFunction === undefined) {
+        return round;
+    }
+    if (typeof(roundingFunction) === 'function') {
+        round = roundingFunction;
+        return true;
+    }
+    return false;
+}
+
+// This function allows you to set a threshold for relative time strings
+function getSetRelativeTimeThreshold (threshold, limit) {
+    if (thresholds[threshold] === undefined) {
+        return false;
+    }
+    if (limit === undefined) {
+        return thresholds[threshold];
+    }
+    thresholds[threshold] = limit;
+    return true;
+}
+
+function humanize (withSuffix) {
+    var locale = this.localeData();
+    var output = relativeTime$1(this, !withSuffix, locale);
+
+    if (withSuffix) {
+        output = locale.pastFuture(+this, output);
+    }
+
+    return locale.postformat(output);
+}
+
+var abs$1 = Math.abs;
+
+function toISOString$1() {
+    // for ISO strings we do not use the normal bubbling rules:
+    //  * milliseconds bubble up until they become hours
+    //  * days do not bubble at all
+    //  * months bubble up until they become years
+    // This is because there is no context-free conversion between hours and days
+    // (think of clock changes)
+    // and also not between days and months (28-31 days per month)
+    var seconds = abs$1(this._milliseconds) / 1000;
+    var days         = abs$1(this._days);
+    var months       = abs$1(this._months);
+    var minutes, hours, years;
+
+    // 3600 seconds -> 60 minutes -> 1 hour
+    minutes           = absFloor(seconds / 60);
+    hours             = absFloor(minutes / 60);
+    seconds %= 60;
+    minutes %= 60;
+
+    // 12 months -> 1 year
+    years  = absFloor(months / 12);
+    months %= 12;
+
+
+    // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+    var Y = years;
+    var M = months;
+    var D = days;
+    var h = hours;
+    var m = minutes;
+    var s = seconds;
+    var total = this.asSeconds();
+
+    if (!total) {
+        // this is the same as C#'s (Noda) and python (isodate)...
+        // but not other JS (goog.date)
+        return 'P0D';
+    }
+
+    return (total < 0 ? '-' : '') +
+        'P' +
+        (Y ? Y + 'Y' : '') +
+        (M ? M + 'M' : '') +
+        (D ? D + 'D' : '') +
+        ((h || m || s) ? 'T' : '') +
+        (h ? h + 'H' : '') +
+        (m ? m + 'M' : '') +
+        (s ? s + 'S' : '');
+}
+
+var proto$2 = Duration.prototype;
+
+proto$2.abs            = abs;
+proto$2.add            = add$1;
+proto$2.subtract       = subtract$1;
+proto$2.as             = as;
+proto$2.asMilliseconds = asMilliseconds;
+proto$2.asSeconds      = asSeconds;
+proto$2.asMinutes      = asMinutes;
+proto$2.asHours        = asHours;
+proto$2.asDays         = asDays;
+proto$2.asWeeks        = asWeeks;
+proto$2.asMonths       = asMonths;
+proto$2.asYears        = asYears;
+proto$2.valueOf        = valueOf$1;
+proto$2._bubble        = bubble;
+proto$2.get            = get$2;
+proto$2.milliseconds   = milliseconds;
+proto$2.seconds        = seconds;
+proto$2.minutes        = minutes;
+proto$2.hours          = hours;
+proto$2.days           = days;
+proto$2.weeks          = weeks;
+proto$2.months         = months;
+proto$2.years          = years;
+proto$2.humanize       = humanize;
+proto$2.toISOString    = toISOString$1;
+proto$2.toString       = toISOString$1;
+proto$2.toJSON         = toISOString$1;
+proto$2.locale         = locale;
+proto$2.localeData     = localeData;
+
+// Deprecations
+proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
+proto$2.lang = lang;
+
+// Side effect imports
+
+// FORMATTING
+
+addFormatToken('X', 0, 0, 'unix');
+addFormatToken('x', 0, 0, 'valueOf');
+
+// PARSING
+
+addRegexToken('x', matchSigned);
+addRegexToken('X', matchTimestamp);
+addParseToken('X', function (input, array, config) {
+    config._d = new Date(parseFloat(input, 10) * 1000);
+});
+addParseToken('x', function (input, array, config) {
+    config._d = new Date(toInt(input));
+});
+
+// Side effect imports
+
+//! moment.js
+//! version : 2.17.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+hooks.version = '2.17.1';
+
+setHookCallback(createLocal);
+
+hooks.fn                    = proto;
+hooks.min                   = min;
+hooks.max                   = max;
+hooks.now                   = now;
+hooks.utc                   = createUTC;
+hooks.unix                  = createUnix;
+hooks.months                = listMonths;
+hooks.isDate                = isDate;
+hooks.locale                = getSetGlobalLocale;
+hooks.invalid               = createInvalid;
+hooks.duration              = createDuration;
+hooks.isMoment              = isMoment;
+hooks.weekdays              = listWeekdays;
+hooks.parseZone             = createInZone;
+hooks.localeData            = getLocale;
+hooks.isDuration            = isDuration;
+hooks.monthsShort           = listMonthsShort;
+hooks.weekdaysMin           = listWeekdaysMin;
+hooks.defineLocale          = defineLocale;
+hooks.updateLocale          = updateLocale;
+hooks.locales               = listLocales;
+hooks.weekdaysShort         = listWeekdaysShort;
+hooks.normalizeUnits        = normalizeUnits;
+hooks.relativeTimeRounding = getSetRelativeTimeRounding;
+hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+hooks.calendarFormat        = getCalendarFormat;
+hooks.prototype             = proto;
+
+//! moment.js locale configuration
+//! locale : Afrikaans [af]
+//! author : Werner Mollentze : https://github.com/wernerm
+
+hooks.defineLocale('af', {
+    months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'),
+    monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'),
+    weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'),
+    weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'),
+    weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'),
+    meridiemParse: /vm|nm/i,
+    isPM : function (input) {
+        return /^nm$/i.test(input);
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 12) {
+            return isLower ? 'vm' : 'VM';
+        } else {
+            return isLower ? 'nm' : 'NM';
+        }
+    },
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Vandag om] LT',
+        nextDay : '[Môre om] LT',
+        nextWeek : 'dddd [om] LT',
+        lastDay : '[Gister om] LT',
+        lastWeek : '[Laas] dddd [om] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'oor %s',
+        past : '%s gelede',
+        s : '\'n paar sekondes',
+        m : '\'n minuut',
+        mm : '%d minute',
+        h : '\'n uur',
+        hh : '%d ure',
+        d : '\'n dag',
+        dd : '%d dae',
+        M : '\'n maand',
+        MM : '%d maande',
+        y : '\'n jaar',
+        yy : '%d jaar'
+    },
+    ordinalParse: /\d{1,2}(ste|de)/,
+    ordinal : function (number) {
+        return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter
+    },
+    week : {
+        dow : 1, // Maandag is die eerste dag van die week.
+        doy : 4  // Die week wat die 4de Januarie bevat is die eerste week van die jaar.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Arabic (Algeria) [ar-dz]
+//! author : Noureddine LOUAHEDJ : https://github.com/noureddineme
+
+hooks.defineLocale('ar-dz', {
+    months : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    monthsShort : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort : 'احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin : 'أح_إث_ثلا_أر_خم_جم_سب'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[اليوم على الساعة] LT',
+        nextDay: '[غدا على الساعة] LT',
+        nextWeek: 'dddd [على الساعة] LT',
+        lastDay: '[أمس على الساعة] LT',
+        lastWeek: 'dddd [على الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'في %s',
+        past : 'منذ %s',
+        s : 'ثوان',
+        m : 'دقيقة',
+        mm : '%d دقائق',
+        h : 'ساعة',
+        hh : '%d ساعات',
+        d : 'يوم',
+        dd : '%d أيام',
+        M : 'شهر',
+        MM : '%d أشهر',
+        y : 'سنة',
+        yy : '%d سنوات'
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 4  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Arabic (Lybia) [ar-ly]
+//! author : Ali Hmer: https://github.com/kikoanis
+
+var symbolMap = {
+    '1': '1',
+    '2': '2',
+    '3': '3',
+    '4': '4',
+    '5': '5',
+    '6': '6',
+    '7': '7',
+    '8': '8',
+    '9': '9',
+    '0': '0'
+};
+var pluralForm = function (n) {
+    return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5;
+};
+var plurals = {
+    s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'],
+    m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'],
+    h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'],
+    d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'],
+    M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'],
+    y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام']
+};
+var pluralize = function (u) {
+    return function (number, withoutSuffix, string, isFuture) {
+        var f = pluralForm(number),
+            str = plurals[u][pluralForm(number)];
+        if (f === 2) {
+            str = str[withoutSuffix ? 0 : 1];
+        }
+        return str.replace(/%d/i, number);
+    };
+};
+var months$1 = [
+    'يناير',
+    'فبراير',
+    'مارس',
+    'أبريل',
+    'مايو',
+    'يونيو',
+    'يوليو',
+    'أغسطس',
+    'سبتمبر',
+    'أكتوبر',
+    'نوفمبر',
+    'ديسمبر'
+];
+
+hooks.defineLocale('ar-ly', {
+    months : months$1,
+    monthsShort : months$1,
+    weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'D/\u200FM/\u200FYYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /ص|م/,
+    isPM : function (input) {
+        return 'Ù…' === input;
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'ص';
+        } else {
+            return 'Ù…';
+        }
+    },
+    calendar : {
+        sameDay: '[اليوم عند الساعة] LT',
+        nextDay: '[غدًا عند الساعة] LT',
+        nextWeek: 'dddd [عند الساعة] LT',
+        lastDay: '[أمس عند الساعة] LT',
+        lastWeek: 'dddd [عند الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'بعد %s',
+        past : 'منذ %s',
+        s : pluralize('s'),
+        m : pluralize('m'),
+        mm : pluralize('m'),
+        h : pluralize('h'),
+        hh : pluralize('h'),
+        d : pluralize('d'),
+        dd : pluralize('d'),
+        M : pluralize('M'),
+        MM : pluralize('M'),
+        y : pluralize('y'),
+        yy : pluralize('y')
+    },
+    preparse: function (string) {
+        return string.replace(/\u200f/g, '').replace(/،/g, ',');
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap[match];
+        }).replace(/,/g, '،');
+    },
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Arabic (Morocco) [ar-ma]
+//! author : ElFadili Yassine : https://github.com/ElFadiliY
+//! author : Abdel Said : https://github.com/abdelsaid
+
+hooks.defineLocale('ar-ma', {
+    months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
+    monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
+    weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[اليوم على الساعة] LT',
+        nextDay: '[غدا على الساعة] LT',
+        nextWeek: 'dddd [على الساعة] LT',
+        lastDay: '[أمس على الساعة] LT',
+        lastWeek: 'dddd [على الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'في %s',
+        past : 'منذ %s',
+        s : 'ثوان',
+        m : 'دقيقة',
+        mm : '%d دقائق',
+        h : 'ساعة',
+        hh : '%d ساعات',
+        d : 'يوم',
+        dd : '%d أيام',
+        M : 'شهر',
+        MM : '%d أشهر',
+        y : 'سنة',
+        yy : '%d سنوات'
+    },
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Arabic (Saudi Arabia) [ar-sa]
+//! author : Suhail Alkowaileet : https://github.com/xsoh
+
+var symbolMap$1 = {
+    '1': 'Ù¡',
+    '2': 'Ù¢',
+    '3': 'Ù£',
+    '4': 'Ù¤',
+    '5': 'Ù¥',
+    '6': 'Ù¦',
+    '7': 'Ù§',
+    '8': 'Ù¨',
+    '9': 'Ù©',
+    '0': 'Ù '
+};
+var numberMap = {
+    'Ù¡': '1',
+    'Ù¢': '2',
+    'Ù£': '3',
+    'Ù¤': '4',
+    'Ù¥': '5',
+    'Ù¦': '6',
+    'Ù§': '7',
+    'Ù¨': '8',
+    'Ù©': '9',
+    'Ù ': '0'
+};
+
+hooks.defineLocale('ar-sa', {
+    months : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    monthsShort : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /ص|م/,
+    isPM : function (input) {
+        return 'Ù…' === input;
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'ص';
+        } else {
+            return 'Ù…';
+        }
+    },
+    calendar : {
+        sameDay: '[اليوم على الساعة] LT',
+        nextDay: '[غدا على الساعة] LT',
+        nextWeek: 'dddd [على الساعة] LT',
+        lastDay: '[أمس على الساعة] LT',
+        lastWeek: 'dddd [على الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'في %s',
+        past : 'منذ %s',
+        s : 'ثوان',
+        m : 'دقيقة',
+        mm : '%d دقائق',
+        h : 'ساعة',
+        hh : '%d ساعات',
+        d : 'يوم',
+        dd : '%d أيام',
+        M : 'شهر',
+        MM : '%d أشهر',
+        y : 'سنة',
+        yy : '%d سنوات'
+    },
+    preparse: function (string) {
+        return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
+            return numberMap[match];
+        }).replace(/،/g, ',');
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$1[match];
+        }).replace(/,/g, '،');
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale  :  Arabic (Tunisia) [ar-tn]
+//! author : Nader Toukabri : https://github.com/naderio
+
+hooks.defineLocale('ar-tn', {
+    months: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    monthsShort: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
+    weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat: {
+        LT: 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L: 'DD/MM/YYYY',
+        LL: 'D MMMM YYYY',
+        LLL: 'D MMMM YYYY HH:mm',
+        LLLL: 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar: {
+        sameDay: '[اليوم على الساعة] LT',
+        nextDay: '[غدا على الساعة] LT',
+        nextWeek: 'dddd [على الساعة] LT',
+        lastDay: '[أمس على الساعة] LT',
+        lastWeek: 'dddd [على الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime: {
+        future: 'في %s',
+        past: 'منذ %s',
+        s: 'ثوان',
+        m: 'دقيقة',
+        mm: '%d دقائق',
+        h: 'ساعة',
+        hh: '%d ساعات',
+        d: 'يوم',
+        dd: '%d أيام',
+        M: 'شهر',
+        MM: '%d أشهر',
+        y: 'سنة',
+        yy: '%d سنوات'
+    },
+    week: {
+        dow: 1, // Monday is the first day of the week.
+        doy: 4 // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Arabic [ar]
+//! author : Abdel Said: https://github.com/abdelsaid
+//! author : Ahmed Elkhatib
+//! author : forabi https://github.com/forabi
+
+var symbolMap$2 = {
+    '1': 'Ù¡',
+    '2': 'Ù¢',
+    '3': 'Ù£',
+    '4': 'Ù¤',
+    '5': 'Ù¥',
+    '6': 'Ù¦',
+    '7': 'Ù§',
+    '8': 'Ù¨',
+    '9': 'Ù©',
+    '0': 'Ù '
+};
+var numberMap$1 = {
+    'Ù¡': '1',
+    'Ù¢': '2',
+    'Ù£': '3',
+    'Ù¤': '4',
+    'Ù¥': '5',
+    'Ù¦': '6',
+    'Ù§': '7',
+    'Ù¨': '8',
+    'Ù©': '9',
+    'Ù ': '0'
+};
+var pluralForm$1 = function (n) {
+    return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5;
+};
+var plurals$1 = {
+    s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'],
+    m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'],
+    h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'],
+    d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'],
+    M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'],
+    y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام']
+};
+var pluralize$1 = function (u) {
+    return function (number, withoutSuffix, string, isFuture) {
+        var f = pluralForm$1(number),
+            str = plurals$1[u][pluralForm$1(number)];
+        if (f === 2) {
+            str = str[withoutSuffix ? 0 : 1];
+        }
+        return str.replace(/%d/i, number);
+    };
+};
+var months$2 = [
+    'كانون الثاني يناير',
+    'شباط فبراير',
+    'آذار مارس',
+    'نيسان أبريل',
+    'أيار مايو',
+    'حزيران يونيو',
+    'تموز يوليو',
+    'آب أغسطس',
+    'أيلول سبتمبر',
+    'تشرين الأول أكتوبر',
+    'تشرين الثاني نوفمبر',
+    'كانون الأول ديسمبر'
+];
+
+hooks.defineLocale('ar', {
+    months : months$2,
+    monthsShort : months$2,
+    weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+    weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
+    weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'D/\u200FM/\u200FYYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /ص|م/,
+    isPM : function (input) {
+        return 'Ù…' === input;
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'ص';
+        } else {
+            return 'Ù…';
+        }
+    },
+    calendar : {
+        sameDay: '[اليوم عند الساعة] LT',
+        nextDay: '[غدًا عند الساعة] LT',
+        nextWeek: 'dddd [عند الساعة] LT',
+        lastDay: '[أمس عند الساعة] LT',
+        lastWeek: 'dddd [عند الساعة] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'بعد %s',
+        past : 'منذ %s',
+        s : pluralize$1('s'),
+        m : pluralize$1('m'),
+        mm : pluralize$1('m'),
+        h : pluralize$1('h'),
+        hh : pluralize$1('h'),
+        d : pluralize$1('d'),
+        dd : pluralize$1('d'),
+        M : pluralize$1('M'),
+        MM : pluralize$1('M'),
+        y : pluralize$1('y'),
+        yy : pluralize$1('y')
+    },
+    preparse: function (string) {
+        return string.replace(/\u200f/g, '').replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
+            return numberMap$1[match];
+        }).replace(/،/g, ',');
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$2[match];
+        }).replace(/,/g, '،');
+    },
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Azerbaijani [az]
+//! author : topchiyev : https://github.com/topchiyev
+
+var suffixes = {
+    1: '-inci',
+    5: '-inci',
+    8: '-inci',
+    70: '-inci',
+    80: '-inci',
+    2: '-nci',
+    7: '-nci',
+    20: '-nci',
+    50: '-nci',
+    3: '-üncü',
+    4: '-üncü',
+    100: '-üncü',
+    6: '-ncı',
+    9: '-uncu',
+    10: '-uncu',
+    30: '-uncu',
+    60: '-ıncı',
+    90: '-ıncı'
+};
+
+hooks.defineLocale('az', {
+    months : 'yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr'.split('_'),
+    monthsShort : 'yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek'.split('_'),
+    weekdays : 'Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə'.split('_'),
+    weekdaysShort : 'Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən'.split('_'),
+    weekdaysMin : 'Bz_BE_ÇA_Çə_CA_Cü_Şə'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[bugün saat] LT',
+        nextDay : '[sabah saat] LT',
+        nextWeek : '[gələn həftə] dddd [saat] LT',
+        lastDay : '[dünən] LT',
+        lastWeek : '[keçən həftə] dddd [saat] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s sonra',
+        past : '%s əvvəl',
+        s : 'birneçə saniyyə',
+        m : 'bir dəqiqə',
+        mm : '%d dəqiqə',
+        h : 'bir saat',
+        hh : '%d saat',
+        d : 'bir gün',
+        dd : '%d gün',
+        M : 'bir ay',
+        MM : '%d ay',
+        y : 'bir il',
+        yy : '%d il'
+    },
+    meridiemParse: /gecə|səhər|gündüz|axşam/,
+    isPM : function (input) {
+        return /^(gündüz|axşam)$/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'gecÉ™';
+        } else if (hour < 12) {
+            return 'səhər';
+        } else if (hour < 17) {
+            return 'gündüz';
+        } else {
+            return 'axÅŸam';
+        }
+    },
+    ordinalParse: /\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,
+    ordinal : function (number) {
+        if (number === 0) {  // special case for zero
+            return number + '-ıncı';
+        }
+        var a = number % 10,
+            b = number % 100 - a,
+            c = number >= 100 ? 100 : null;
+        return number + (suffixes[a] || suffixes[b] || suffixes[c]);
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Belarusian [be]
+//! author : Dmitry Demidov : https://github.com/demidov91
+//! author: Praleska: http://praleska.pro/
+//! Author : Menelion Elensúle : https://github.com/Oire
+
+function plural(word, num) {
+    var forms = word.split('_');
+    return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
+}
+function relativeTimeWithPlural(number, withoutSuffix, key) {
+    var format = {
+        'mm': withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін',
+        'hh': withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін',
+        'dd': 'дзень_дні_дзён',
+        'MM': 'месяц_месяцы_месяцаў',
+        'yy': 'год_гады_гадоў'
+    };
+    if (key === 'm') {
+        return withoutSuffix ? 'хвіліна' : 'хвіліну';
+    }
+    else if (key === 'h') {
+        return withoutSuffix ? 'гадзіна' : 'гадзіну';
+    }
+    else {
+        return number + ' ' + plural(format[key], +number);
+    }
+}
+
+hooks.defineLocale('be', {
+    months : {
+        format: 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_'),
+        standalone: 'студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_')
+    },
+    monthsShort : 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'),
+    weekdays : {
+        format: 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_'),
+        standalone: 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'),
+        isFormat: /\[ ?[Вв] ?(?:мінулую|наступную)? ?\] ?dddd/
+    },
+    weekdaysShort : 'нд_пн_ат_ср_чц_пт_сб'.split('_'),
+    weekdaysMin : 'нд_пн_ат_ср_чц_пт_сб'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY г.',
+        LLL : 'D MMMM YYYY г., HH:mm',
+        LLLL : 'dddd, D MMMM YYYY г., HH:mm'
+    },
+    calendar : {
+        sameDay: '[Сёння ў] LT',
+        nextDay: '[Заўтра ў] LT',
+        lastDay: '[Учора ў] LT',
+        nextWeek: function () {
+            return '[У] dddd [ў] LT';
+        },
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                case 5:
+                case 6:
+                    return '[У мінулую] dddd [ў] LT';
+                case 1:
+                case 2:
+                case 4:
+                    return '[У мінулы] dddd [ў] LT';
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'праз %s',
+        past : '%s таму',
+        s : 'некалькі секунд',
+        m : relativeTimeWithPlural,
+        mm : relativeTimeWithPlural,
+        h : relativeTimeWithPlural,
+        hh : relativeTimeWithPlural,
+        d : 'дзень',
+        dd : relativeTimeWithPlural,
+        M : 'месяц',
+        MM : relativeTimeWithPlural,
+        y : 'год',
+        yy : relativeTimeWithPlural
+    },
+    meridiemParse: /ночы|раніцы|дня|вечара/,
+    isPM : function (input) {
+        return /^(дня|вечара)$/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'ночы';
+        } else if (hour < 12) {
+            return 'раніцы';
+        } else if (hour < 17) {
+            return 'дня';
+        } else {
+            return 'вечара';
+        }
+    },
+    ordinalParse: /\d{1,2}-(і|ы|га)/,
+    ordinal: function (number, period) {
+        switch (period) {
+            case 'M':
+            case 'd':
+            case 'DDD':
+            case 'w':
+            case 'W':
+                return (number % 10 === 2 || number % 10 === 3) && (number % 100 !== 12 && number % 100 !== 13) ? number + '-Ñ–' : number + '-Ñ‹';
+            case 'D':
+                return number + '-га';
+            default:
+                return number;
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Bulgarian [bg]
+//! author : Krasen Borisov : https://github.com/kraz
+
+hooks.defineLocale('bg', {
+    months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'),
+    monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'),
+    weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'),
+    weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'),
+    weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'D.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY H:mm',
+        LLLL : 'dddd, D MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay : '[Днес в] LT',
+        nextDay : '[Утре в] LT',
+        nextWeek : 'dddd [в] LT',
+        lastDay : '[Вчера в] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                case 6:
+                    return '[В изминалата] dddd [в] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[В изминалия] dddd [в] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'след %s',
+        past : 'преди %s',
+        s : 'няколко секунди',
+        m : 'минута',
+        mm : '%d минути',
+        h : 'час',
+        hh : '%d часа',
+        d : 'ден',
+        dd : '%d дни',
+        M : 'месец',
+        MM : '%d месеца',
+        y : 'година',
+        yy : '%d години'
+    },
+    ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/,
+    ordinal : function (number) {
+        var lastDigit = number % 10,
+            last2Digits = number % 100;
+        if (number === 0) {
+            return number + '-ев';
+        } else if (last2Digits === 0) {
+            return number + '-ен';
+        } else if (last2Digits > 10 && last2Digits < 20) {
+            return number + '-ти';
+        } else if (lastDigit === 1) {
+            return number + '-ви';
+        } else if (lastDigit === 2) {
+            return number + '-ри';
+        } else if (lastDigit === 7 || lastDigit === 8) {
+            return number + '-ми';
+        } else {
+            return number + '-ти';
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Bengali [bn]
+//! author : Kaushik Gandhi : https://github.com/kaushikgandhi
+
+var symbolMap$3 = {
+    '1': '১',
+    '2': '২',
+    '3': '৩',
+    '4': '৪',
+    '5': '৫',
+    '6': '৬',
+    '7': '৭',
+    '8': '৮',
+    '9': '৯',
+    '0': '০'
+};
+var numberMap$2 = {
+    '১': '1',
+    '২': '2',
+    '৩': '3',
+    '৪': '4',
+    '৫': '5',
+    '৬': '6',
+    '৭': '7',
+    '৮': '8',
+    '৯': '9',
+    '০': '0'
+};
+
+hooks.defineLocale('bn', {
+    months : 'জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'),
+    monthsShort : 'জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে'.split('_'),
+    weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার'.split('_'),
+    weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি'.split('_'),
+    weekdaysMin : 'রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm সময়',
+        LTS : 'A h:mm:ss সময়',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm সময়',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm সময়'
+    },
+    calendar : {
+        sameDay : '[আজ] LT',
+        nextDay : '[আগামীকাল] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[গতকাল] LT',
+        lastWeek : '[গত] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s পরে',
+        past : '%s আগে',
+        s : 'কয়েক সেকেন্ড',
+        m : 'এক মিনিট',
+        mm : '%d মিনিট',
+        h : 'এক ঘন্টা',
+        hh : '%d ঘন্টা',
+        d : 'এক দিন',
+        dd : '%d দিন',
+        M : 'এক মাস',
+        MM : '%d মাস',
+        y : 'এক বছর',
+        yy : '%d বছর'
+    },
+    preparse: function (string) {
+        return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) {
+            return numberMap$2[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$3[match];
+        });
+    },
+    meridiemParse: /রাত|সকাল|দুপুর|বিকাল|রাত/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if ((meridiem === 'রাত' && hour >= 4) ||
+                (meridiem === 'দুপুর' && hour < 5) ||
+                meridiem === 'বিকাল') {
+            return hour + 12;
+        } else {
+            return hour;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'রাত';
+        } else if (hour < 10) {
+            return 'সকাল';
+        } else if (hour < 17) {
+            return 'দুপুর';
+        } else if (hour < 20) {
+            return 'বিকাল';
+        } else {
+            return 'রাত';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Tibetan [bo]
+//! author : Thupten N. Chakrishar : https://github.com/vajradog
+
+var symbolMap$4 = {
+    '1': '༡',
+    '2': '༢',
+    '3': '༣',
+    '4': '༤',
+    '5': '༥',
+    '6': '༦',
+    '7': '༧',
+    '8': '༨',
+    '9': '༩',
+    '0': '༠'
+};
+var numberMap$3 = {
+    '༡': '1',
+    '༢': '2',
+    '༣': '3',
+    '༤': '4',
+    '༥': '5',
+    '༦': '6',
+    '༧': '7',
+    '༨': '8',
+    '༩': '9',
+    '༠': '0'
+};
+
+hooks.defineLocale('bo', {
+    months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'),
+    monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'),
+    weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'),
+    weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'),
+    weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm',
+        LTS : 'A h:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm'
+    },
+    calendar : {
+        sameDay : '[དི་རིང] LT',
+        nextDay : '[སང་ཉིན] LT',
+        nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT',
+        lastDay : '[ཁ་སང] LT',
+        lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s ལ་',
+        past : '%s སྔན་ལ',
+        s : 'ལམ་སང',
+        m : 'སྐར་མ་གཅིག',
+        mm : '%d སྐར་མ',
+        h : 'ཆུ་ཚོད་གཅིག',
+        hh : '%d ཆུ་ཚོད',
+        d : 'ཉིན་གཅིག',
+        dd : '%d ཉིན་',
+        M : 'ཟླ་བ་གཅིག',
+        MM : '%d ཟླ་བ',
+        y : 'ལོ་གཅིག',
+        yy : '%d ལོ'
+    },
+    preparse: function (string) {
+        return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) {
+            return numberMap$3[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$4[match];
+        });
+    },
+    meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if ((meridiem === 'མཚན་མོ' && hour >= 4) ||
+                (meridiem === 'ཉིན་གུང' && hour < 5) ||
+                meridiem === 'དགོང་དག') {
+            return hour + 12;
+        } else {
+            return hour;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'མཚན་མོ';
+        } else if (hour < 10) {
+            return 'ཞོགས་ཀས';
+        } else if (hour < 17) {
+            return 'ཉིན་གུང';
+        } else if (hour < 20) {
+            return 'དགོང་དག';
+        } else {
+            return 'མཚན་མོ';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Breton [br]
+//! author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou
+
+function relativeTimeWithMutation(number, withoutSuffix, key) {
+    var format = {
+        'mm': 'munutenn',
+        'MM': 'miz',
+        'dd': 'devezh'
+    };
+    return number + ' ' + mutation(format[key], number);
+}
+function specialMutationForYears(number) {
+    switch (lastNumber(number)) {
+        case 1:
+        case 3:
+        case 4:
+        case 5:
+        case 9:
+            return number + ' bloaz';
+        default:
+            return number + ' vloaz';
+    }
+}
+function lastNumber(number) {
+    if (number > 9) {
+        return lastNumber(number % 10);
+    }
+    return number;
+}
+function mutation(text, number) {
+    if (number === 2) {
+        return softMutation(text);
+    }
+    return text;
+}
+function softMutation(text) {
+    var mutationTable = {
+        'm': 'v',
+        'b': 'v',
+        'd': 'z'
+    };
+    if (mutationTable[text.charAt(0)] === undefined) {
+        return text;
+    }
+    return mutationTable[text.charAt(0)] + text.substring(1);
+}
+
+hooks.defineLocale('br', {
+    months : 'Genver_C\'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'),
+    monthsShort : 'Gen_C\'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'),
+    weekdays : 'Sul_Lun_Meurzh_Merc\'her_Yaou_Gwener_Sadorn'.split('_'),
+    weekdaysShort : 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'),
+    weekdaysMin : 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'h[e]mm A',
+        LTS : 'h[e]mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D [a viz] MMMM YYYY',
+        LLL : 'D [a viz] MMMM YYYY h[e]mm A',
+        LLLL : 'dddd, D [a viz] MMMM YYYY h[e]mm A'
+    },
+    calendar : {
+        sameDay : '[Hiziv da] LT',
+        nextDay : '[Warc\'hoazh da] LT',
+        nextWeek : 'dddd [da] LT',
+        lastDay : '[Dec\'h da] LT',
+        lastWeek : 'dddd [paset da] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'a-benn %s',
+        past : '%s \'zo',
+        s : 'un nebeud segondennoù',
+        m : 'ur vunutenn',
+        mm : relativeTimeWithMutation,
+        h : 'un eur',
+        hh : '%d eur',
+        d : 'un devezh',
+        dd : relativeTimeWithMutation,
+        M : 'ur miz',
+        MM : relativeTimeWithMutation,
+        y : 'ur bloaz',
+        yy : specialMutationForYears
+    },
+    ordinalParse: /\d{1,2}(añ|vet)/,
+    ordinal : function (number) {
+        var output = (number === 1) ? 'añ' : 'vet';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Bosnian [bs]
+//! author : Nedim Cholich : https://github.com/frontyard
+//! based on (hr) translation by Bojan Marković
+
+function translate(number, withoutSuffix, key) {
+    var result = number + ' ';
+    switch (key) {
+        case 'm':
+            return withoutSuffix ? 'jedna minuta' : 'jedne minute';
+        case 'mm':
+            if (number === 1) {
+                result += 'minuta';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'minute';
+            } else {
+                result += 'minuta';
+            }
+            return result;
+        case 'h':
+            return withoutSuffix ? 'jedan sat' : 'jednog sata';
+        case 'hh':
+            if (number === 1) {
+                result += 'sat';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'sata';
+            } else {
+                result += 'sati';
+            }
+            return result;
+        case 'dd':
+            if (number === 1) {
+                result += 'dan';
+            } else {
+                result += 'dana';
+            }
+            return result;
+        case 'MM':
+            if (number === 1) {
+                result += 'mjesec';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'mjeseca';
+            } else {
+                result += 'mjeseci';
+            }
+            return result;
+        case 'yy':
+            if (number === 1) {
+                result += 'godina';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'godine';
+            } else {
+                result += 'godina';
+            }
+            return result;
+    }
+}
+
+hooks.defineLocale('bs', {
+    months : 'januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar'.split('_'),
+    monthsShort : 'jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'),
+    weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'),
+    weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay  : '[danas u] LT',
+        nextDay  : '[sutra u] LT',
+        nextWeek : function () {
+            switch (this.day()) {
+                case 0:
+                    return '[u] [nedjelju] [u] LT';
+                case 3:
+                    return '[u] [srijedu] [u] LT';
+                case 6:
+                    return '[u] [subotu] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[u] dddd [u] LT';
+            }
+        },
+        lastDay  : '[jučer u] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                    return '[prošlu] dddd [u] LT';
+                case 6:
+                    return '[prošle] [subote] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[prošli] dddd [u] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past   : 'prije %s',
+        s      : 'par sekundi',
+        m      : translate,
+        mm     : translate,
+        h      : translate,
+        hh     : translate,
+        d      : 'dan',
+        dd     : translate,
+        M      : 'mjesec',
+        MM     : translate,
+        y      : 'godinu',
+        yy     : translate
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Catalan [ca]
+//! author : Juan G. Hurtado : https://github.com/juanghurtado
+
+hooks.defineLocale('ca', {
+    months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'),
+    monthsShort : 'gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'),
+    weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'),
+    weekdaysMin : 'Dg_Dl_Dt_Dc_Dj_Dv_Ds'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY H:mm',
+        LLLL : 'dddd D MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay : function () {
+            return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
+        },
+        nextDay : function () {
+            return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
+        },
+        nextWeek : function () {
+            return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
+        },
+        lastDay : function () {
+            return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
+        },
+        lastWeek : function () {
+            return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT';
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'd\'aquí %s',
+        past : 'fa %s',
+        s : 'uns segons',
+        m : 'un minut',
+        mm : '%d minuts',
+        h : 'una hora',
+        hh : '%d hores',
+        d : 'un dia',
+        dd : '%d dies',
+        M : 'un mes',
+        MM : '%d mesos',
+        y : 'un any',
+        yy : '%d anys'
+    },
+    ordinalParse: /\d{1,2}(r|n|t|è|a)/,
+    ordinal : function (number, period) {
+        var output = (number === 1) ? 'r' :
+            (number === 2) ? 'n' :
+            (number === 3) ? 'r' :
+            (number === 4) ? 't' : 'è';
+        if (period === 'w' || period === 'W') {
+            output = 'a';
+        }
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Czech [cs]
+//! author : petrbela : https://github.com/petrbela
+
+var months$3 = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_');
+var monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_');
+function plural$1(n) {
+    return (n > 1) && (n < 5) && (~~(n / 10) !== 1);
+}
+function translate$1(number, withoutSuffix, key, isFuture) {
+    var result = number + ' ';
+    switch (key) {
+        case 's':  // a few seconds / in a few seconds / a few seconds ago
+            return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami';
+        case 'm':  // a minute / in a minute / a minute ago
+            return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou');
+        case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$1(number) ? 'minuty' : 'minut');
+            } else {
+                return result + 'minutami';
+            }
+            break;
+        case 'h':  // an hour / in an hour / an hour ago
+            return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou');
+        case 'hh': // 9 hours / in 9 hours / 9 hours ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$1(number) ? 'hodiny' : 'hodin');
+            } else {
+                return result + 'hodinami';
+            }
+            break;
+        case 'd':  // a day / in a day / a day ago
+            return (withoutSuffix || isFuture) ? 'den' : 'dnem';
+        case 'dd': // 9 days / in 9 days / 9 days ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$1(number) ? 'dny' : 'dní');
+            } else {
+                return result + 'dny';
+            }
+            break;
+        case 'M':  // a month / in a month / a month ago
+            return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem';
+        case 'MM': // 9 months / in 9 months / 9 months ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$1(number) ? 'měsíce' : 'měsíců');
+            } else {
+                return result + 'měsíci';
+            }
+            break;
+        case 'y':  // a year / in a year / a year ago
+            return (withoutSuffix || isFuture) ? 'rok' : 'rokem';
+        case 'yy': // 9 years / in 9 years / 9 years ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$1(number) ? 'roky' : 'let');
+            } else {
+                return result + 'lety';
+            }
+            break;
+    }
+}
+
+hooks.defineLocale('cs', {
+    months : months$3,
+    monthsShort : monthsShort,
+    monthsParse : (function (months, monthsShort) {
+        var i, _monthsParse = [];
+        for (i = 0; i < 12; i++) {
+            // use custom parser to solve problem with July (červenec)
+            _monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i');
+        }
+        return _monthsParse;
+    }(months$3, monthsShort)),
+    shortMonthsParse : (function (monthsShort) {
+        var i, _shortMonthsParse = [];
+        for (i = 0; i < 12; i++) {
+            _shortMonthsParse[i] = new RegExp('^' + monthsShort[i] + '$', 'i');
+        }
+        return _shortMonthsParse;
+    }(monthsShort)),
+    longMonthsParse : (function (months) {
+        var i, _longMonthsParse = [];
+        for (i = 0; i < 12; i++) {
+            _longMonthsParse[i] = new RegExp('^' + months[i] + '$', 'i');
+        }
+        return _longMonthsParse;
+    }(months$3)),
+    weekdays : 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'),
+    weekdaysShort : 'ne_po_út_st_čt_pá_so'.split('_'),
+    weekdaysMin : 'ne_po_út_st_čt_pá_so'.split('_'),
+    longDateFormat : {
+        LT: 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd D. MMMM YYYY H:mm',
+        l : 'D. M. YYYY'
+    },
+    calendar : {
+        sameDay: '[dnes v] LT',
+        nextDay: '[zítra v] LT',
+        nextWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[v neděli v] LT';
+                case 1:
+                case 2:
+                    return '[v] dddd [v] LT';
+                case 3:
+                    return '[ve středu v] LT';
+                case 4:
+                    return '[ve čtvrtek v] LT';
+                case 5:
+                    return '[v pátek v] LT';
+                case 6:
+                    return '[v sobotu v] LT';
+            }
+        },
+        lastDay: '[včera v] LT',
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[minulou neděli v] LT';
+                case 1:
+                case 2:
+                    return '[minulé] dddd [v] LT';
+                case 3:
+                    return '[minulou středu v] LT';
+                case 4:
+                case 5:
+                    return '[minulý] dddd [v] LT';
+                case 6:
+                    return '[minulou sobotu v] LT';
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past : 'před %s',
+        s : translate$1,
+        m : translate$1,
+        mm : translate$1,
+        h : translate$1,
+        hh : translate$1,
+        d : translate$1,
+        dd : translate$1,
+        M : translate$1,
+        MM : translate$1,
+        y : translate$1,
+        yy : translate$1
+    },
+    ordinalParse : /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Chuvash [cv]
+//! author : Anatoly Mironov : https://github.com/mirontoli
+
+hooks.defineLocale('cv', {
+    months : 'кӑрлач_нарӑс_пуш_ака_май_ҫӗртме_утӑ_ҫурла_авӑн_юпа_чӳк_раштав'.split('_'),
+    monthsShort : 'кӑр_нар_пуш_ака_май_ҫӗр_утӑ_ҫур_авн_юпа_чӳк_раш'.split('_'),
+    weekdays : 'вырсарникун_тунтикун_ытларикун_юнкун_кӗҫнерникун_эрнекун_шӑматкун'.split('_'),
+    weekdaysShort : 'выр_тун_ытл_юн_кӗҫ_эрн_шӑм'.split('_'),
+    weekdaysMin : 'вр_тн_ыт_юн_кҫ_эр_шм'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD-MM-YYYY',
+        LL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]',
+        LLL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm',
+        LLLL : 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm'
+    },
+    calendar : {
+        sameDay: '[Паян] LT [сехетре]',
+        nextDay: '[Ыран] LT [сехетре]',
+        lastDay: '[Ӗнер] LT [сехетре]',
+        nextWeek: '[Ҫитес] dddd LT [сехетре]',
+        lastWeek: '[Иртнӗ] dddd LT [сехетре]',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : function (output) {
+            var affix = /сехет$/i.exec(output) ? 'рен' : /ҫул$/i.exec(output) ? 'тан' : 'ран';
+            return output + affix;
+        },
+        past : '%s каялла',
+        s : 'пӗр-ик ҫеккунт',
+        m : 'пӗр минут',
+        mm : '%d минут',
+        h : 'пӗр сехет',
+        hh : '%d сехет',
+        d : 'пӗр кун',
+        dd : '%d кун',
+        M : 'пӗр уйӑх',
+        MM : '%d уйӑх',
+        y : 'пӗр ҫул',
+        yy : '%d ҫул'
+    },
+    ordinalParse: /\d{1,2}-мӗш/,
+    ordinal : '%d-мӗш',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Welsh [cy]
+//! author : Robert Allen : https://github.com/robgallen
+//! author : https://github.com/ryangreaves
+
+hooks.defineLocale('cy', {
+    months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'),
+    monthsShort: 'Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag'.split('_'),
+    weekdays: 'Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn'.split('_'),
+    weekdaysShort: 'Sul_Llun_Maw_Mer_Iau_Gwe_Sad'.split('_'),
+    weekdaysMin: 'Su_Ll_Ma_Me_Ia_Gw_Sa'.split('_'),
+    weekdaysParseExact : true,
+    // time formats are the same as en-gb
+    longDateFormat: {
+        LT: 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L: 'DD/MM/YYYY',
+        LL: 'D MMMM YYYY',
+        LLL: 'D MMMM YYYY HH:mm',
+        LLLL: 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar: {
+        sameDay: '[Heddiw am] LT',
+        nextDay: '[Yfory am] LT',
+        nextWeek: 'dddd [am] LT',
+        lastDay: '[Ddoe am] LT',
+        lastWeek: 'dddd [diwethaf am] LT',
+        sameElse: 'L'
+    },
+    relativeTime: {
+        future: 'mewn %s',
+        past: '%s yn ôl',
+        s: 'ychydig eiliadau',
+        m: 'munud',
+        mm: '%d munud',
+        h: 'awr',
+        hh: '%d awr',
+        d: 'diwrnod',
+        dd: '%d diwrnod',
+        M: 'mis',
+        MM: '%d mis',
+        y: 'blwyddyn',
+        yy: '%d flynedd'
+    },
+    ordinalParse: /\d{1,2}(fed|ain|af|il|ydd|ed|eg)/,
+    // traditional ordinal numbers above 31 are not commonly used in colloquial Welsh
+    ordinal: function (number) {
+        var b = number,
+            output = '',
+            lookup = [
+                '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed
+                'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed' // 11eg to 20fed
+            ];
+        if (b > 20) {
+            if (b === 40 || b === 50 || b === 60 || b === 80 || b === 100) {
+                output = 'fed'; // not 30ain, 70ain or 90ain
+            } else {
+                output = 'ain';
+            }
+        } else if (b > 0) {
+            output = lookup[b];
+        }
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Danish [da]
+//! author : Ulrik Nielsen : https://github.com/mrbase
+
+hooks.defineLocale('da', {
+    months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
+    weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
+    weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'),
+    weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY HH:mm',
+        LLLL : 'dddd [d.] D. MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[I dag kl.] LT',
+        nextDay : '[I morgen kl.] LT',
+        nextWeek : 'dddd [kl.] LT',
+        lastDay : '[I går kl.] LT',
+        lastWeek : '[sidste] dddd [kl] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'om %s',
+        past : '%s siden',
+        s : 'få sekunder',
+        m : 'et minut',
+        mm : '%d minutter',
+        h : 'en time',
+        hh : '%d timer',
+        d : 'en dag',
+        dd : '%d dage',
+        M : 'en måned',
+        MM : '%d måneder',
+        y : 'et år',
+        yy : '%d år'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : German (Austria) [de-at]
+//! author : lluchs : https://github.com/lluchs
+//! author: Menelion Elensúle: https://github.com/Oire
+//! author : Martin Groller : https://github.com/MadMG
+//! author : Mikolaj Dadela : https://github.com/mik01aj
+
+function processRelativeTime(number, withoutSuffix, key, isFuture) {
+    var format = {
+        'm': ['eine Minute', 'einer Minute'],
+        'h': ['eine Stunde', 'einer Stunde'],
+        'd': ['ein Tag', 'einem Tag'],
+        'dd': [number + ' Tage', number + ' Tagen'],
+        'M': ['ein Monat', 'einem Monat'],
+        'MM': [number + ' Monate', number + ' Monaten'],
+        'y': ['ein Jahr', 'einem Jahr'],
+        'yy': [number + ' Jahre', number + ' Jahren']
+    };
+    return withoutSuffix ? format[key][0] : format[key][1];
+}
+
+hooks.defineLocale('de-at', {
+    months : 'Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
+    monthsShort : 'Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
+    weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
+    weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT: 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY HH:mm',
+        LLLL : 'dddd, D. MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[heute um] LT [Uhr]',
+        sameElse: 'L',
+        nextDay: '[morgen um] LT [Uhr]',
+        nextWeek: 'dddd [um] LT [Uhr]',
+        lastDay: '[gestern um] LT [Uhr]',
+        lastWeek: '[letzten] dddd [um] LT [Uhr]'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : 'vor %s',
+        s : 'ein paar Sekunden',
+        m : processRelativeTime,
+        mm : '%d Minuten',
+        h : processRelativeTime,
+        hh : '%d Stunden',
+        d : processRelativeTime,
+        dd : processRelativeTime,
+        M : processRelativeTime,
+        MM : processRelativeTime,
+        y : processRelativeTime,
+        yy : processRelativeTime
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : German [de]
+//! author : lluchs : https://github.com/lluchs
+//! author: Menelion Elensúle: https://github.com/Oire
+//! author : Mikolaj Dadela : https://github.com/mik01aj
+
+function processRelativeTime$1(number, withoutSuffix, key, isFuture) {
+    var format = {
+        'm': ['eine Minute', 'einer Minute'],
+        'h': ['eine Stunde', 'einer Stunde'],
+        'd': ['ein Tag', 'einem Tag'],
+        'dd': [number + ' Tage', number + ' Tagen'],
+        'M': ['ein Monat', 'einem Monat'],
+        'MM': [number + ' Monate', number + ' Monaten'],
+        'y': ['ein Jahr', 'einem Jahr'],
+        'yy': [number + ' Jahre', number + ' Jahren']
+    };
+    return withoutSuffix ? format[key][0] : format[key][1];
+}
+
+hooks.defineLocale('de', {
+    months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
+    monthsShort : 'Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
+    weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'),
+    weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT: 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY HH:mm',
+        LLLL : 'dddd, D. MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[heute um] LT [Uhr]',
+        sameElse: 'L',
+        nextDay: '[morgen um] LT [Uhr]',
+        nextWeek: 'dddd [um] LT [Uhr]',
+        lastDay: '[gestern um] LT [Uhr]',
+        lastWeek: '[letzten] dddd [um] LT [Uhr]'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : 'vor %s',
+        s : 'ein paar Sekunden',
+        m : processRelativeTime$1,
+        mm : '%d Minuten',
+        h : processRelativeTime$1,
+        hh : '%d Stunden',
+        d : processRelativeTime$1,
+        dd : processRelativeTime$1,
+        M : processRelativeTime$1,
+        MM : processRelativeTime$1,
+        y : processRelativeTime$1,
+        yy : processRelativeTime$1
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Maldivian [dv]
+//! author : Jawish Hameed : https://github.com/jawish
+
+var months$4 = [
+    'Þ–Þ¬Þ‚ÞªÞ‡Þ¦ÞƒÞ©',
+    'ÞŠÞ¬Þ„Þ°ÞƒÞªÞ‡Þ¦ÞƒÞ©',
+    'Þ‰Þ§ÞƒÞ¨Þ—Þª',
+    'އޭޕްރީލު',
+    'Þ‰Þ­',
+    'Þ–Þ«Þ‚Þ°',
+    'ޖުލައި',
+    'އޯގަސްޓު',
+    'ސެޕްޓެމްބަރު',
+    'Þ‡Þ®Þ†Þ°Þ“Þ¯Þ„Þ¦ÞƒÞª',
+    'Þ‚Þ®ÞˆÞ¬Þ‰Þ°Þ„Þ¦ÞƒÞª',
+    'ޑިސެމްބަރު'
+];
+var weekdays = [
+    'އާދިއްތަ',
+    'Þ€Þ¯Þ‰Þ¦',
+    'Þ‡Þ¦Þ‚Þ°ÞŽÞ§ÞƒÞ¦',
+    'Þ„ÞªÞ‹Þ¦',
+    'ބުރާސްފަތި',
+    'Þ€ÞªÞ†ÞªÞƒÞª',
+    'Þ€Þ®Þ‚Þ¨Þ€Þ¨ÞƒÞª'
+];
+
+hooks.defineLocale('dv', {
+    months : months$4,
+    monthsShort : months$4,
+    weekdays : weekdays,
+    weekdaysShort : weekdays,
+    weekdaysMin : 'Þ‡Þ§Þ‹Þ¨_Þ€Þ¯Þ‰Þ¦_Þ‡Þ¦Þ‚Þ°_Þ„ÞªÞ‹Þ¦_Þ„ÞªÞƒÞ§_Þ€ÞªÞ†Þª_Þ€Þ®Þ‚Þ¨'.split('_'),
+    longDateFormat : {
+
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'D/M/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /Þ‰Þ†|Þ‰ÞŠ/,
+    isPM : function (input) {
+        return 'Þ‰ÞŠ' === input;
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'Þ‰Þ†';
+        } else {
+            return 'Þ‰ÞŠ';
+        }
+    },
+    calendar : {
+        sameDay : '[Þ‰Þ¨Þ‡Þ¦Þ‹Þª] LT',
+        nextDay : '[Þ‰Þ§Þ‹Þ¦Þ‰Þ§] LT',
+        nextWeek : 'dddd LT',
+        lastDay : '[Þ‡Þ¨Þ‡Þ°Þ”Þ¬] LT',
+        lastWeek : '[ފާއިތުވި] dddd LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'ތެރޭގައި %s',
+        past : 'Þ†ÞªÞƒÞ¨Þ‚Þ° %s',
+        s : 'ސިކުންތުކޮޅެއް',
+        m : 'Þ‰Þ¨Þ‚Þ¨Þ“Þ¬Þ‡Þ°',
+        mm : 'Þ‰Þ¨Þ‚Þ¨Þ“Þª %d',
+        h : 'ÞŽÞ¦Þ‘Þ¨Þ‡Þ¨ÞƒÞ¬Þ‡Þ°',
+        hh : 'ÞŽÞ¦Þ‘Þ¨Þ‡Þ¨ÞƒÞª %d',
+        d : 'Þ‹ÞªÞˆÞ¦Þ€Þ¬Þ‡Þ°',
+        dd : 'ދުވަސް %d',
+        M : 'Þ‰Þ¦Þ€Þ¬Þ‡Þ°',
+        MM : 'މަސް %d',
+        y : 'Þ‡Þ¦Þ€Þ¦ÞƒÞ¬Þ‡Þ°',
+        yy : 'Þ‡Þ¦Þ€Þ¦ÞƒÞª %d'
+    },
+    preparse: function (string) {
+        return string.replace(/،/g, ',');
+    },
+    postformat: function (string) {
+        return string.replace(/,/g, '،');
+    },
+    week : {
+        dow : 7,  // Sunday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Greek [el]
+//! author : Aggelos Karalias : https://github.com/mehiel
+
+hooks.defineLocale('el', {
+    monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'),
+    monthsGenitiveEl : 'Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου'.split('_'),
+    months : function (momentToFormat, format) {
+        if (/D/.test(format.substring(0, format.indexOf('MMMM')))) { // if there is a day number before 'MMMM'
+            return this._monthsGenitiveEl[momentToFormat.month()];
+        } else {
+            return this._monthsNominativeEl[momentToFormat.month()];
+        }
+    },
+    monthsShort : 'Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ'.split('_'),
+    weekdays : 'Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο'.split('_'),
+    weekdaysShort : 'Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ'.split('_'),
+    weekdaysMin : 'Κυ_Δε_Τρ_Τε_Πε_Πα_Σα'.split('_'),
+    meridiem : function (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'μμ' : 'ΜΜ';
+        } else {
+            return isLower ? 'πμ' : 'ΠΜ';
+        }
+    },
+    isPM : function (input) {
+        return ((input + '').toLowerCase()[0] === 'μ');
+    },
+    meridiemParse : /[ΠΜ]\.?Μ?\.?/i,
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendarEl : {
+        sameDay : '[Σήμερα {}] LT',
+        nextDay : '[Αύριο {}] LT',
+        nextWeek : 'dddd [{}] LT',
+        lastDay : '[Χθες {}] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 6:
+                    return '[το προηγούμενο] dddd [{}] LT';
+                default:
+                    return '[την προηγούμενη] dddd [{}] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    calendar : function (key, mom) {
+        var output = this._calendarEl[key],
+            hours = mom && mom.hours();
+        if (isFunction(output)) {
+            output = output.apply(mom);
+        }
+        return output.replace('{}', (hours % 12 === 1 ? 'στη' : 'στις'));
+    },
+    relativeTime : {
+        future : 'σε %s',
+        past : '%s πριν',
+        s : 'λίγα δευτερόλεπτα',
+        m : 'ένα λεπτό',
+        mm : '%d λεπτά',
+        h : 'μία ώρα',
+        hh : '%d ώρες',
+        d : 'μία μέρα',
+        dd : '%d μέρες',
+        M : 'ένας μήνας',
+        MM : '%d μήνες',
+        y : 'ένας χρόνος',
+        yy : '%d χρόνια'
+    },
+    ordinalParse: /\d{1,2}η/,
+    ordinal: '%dη',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : English (Australia) [en-au]
+//! author : Jared Morse : https://github.com/jarcoal
+
+hooks.defineLocale('en-au', {
+    months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+    weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+    weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+    weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : '%s ago',
+        s : 'a few seconds',
+        m : 'a minute',
+        mm : '%d minutes',
+        h : 'an hour',
+        hh : '%d hours',
+        d : 'a day',
+        dd : '%d days',
+        M : 'a month',
+        MM : '%d months',
+        y : 'a year',
+        yy : '%d years'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : English (Canada) [en-ca]
+//! author : Jonathan Abourbih : https://github.com/jonbca
+
+hooks.defineLocale('en-ca', {
+    months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+    weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+    weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+    weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'YYYY-MM-DD',
+        LL : 'MMMM D, YYYY',
+        LLL : 'MMMM D, YYYY h:mm A',
+        LLLL : 'dddd, MMMM D, YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : '%s ago',
+        s : 'a few seconds',
+        m : 'a minute',
+        mm : '%d minutes',
+        h : 'an hour',
+        hh : '%d hours',
+        d : 'a day',
+        dd : '%d days',
+        M : 'a month',
+        MM : '%d months',
+        y : 'a year',
+        yy : '%d years'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    }
+});
+
+//! moment.js locale configuration
+//! locale : English (United Kingdom) [en-gb]
+//! author : Chris Gedrim : https://github.com/chrisgedrim
+
+hooks.defineLocale('en-gb', {
+    months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+    weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+    weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+    weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : '%s ago',
+        s : 'a few seconds',
+        m : 'a minute',
+        mm : '%d minutes',
+        h : 'an hour',
+        hh : '%d hours',
+        d : 'a day',
+        dd : '%d days',
+        M : 'a month',
+        MM : '%d months',
+        y : 'a year',
+        yy : '%d years'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : English (Ireland) [en-ie]
+//! author : Chris Cartlidge : https://github.com/chriscartlidge
+
+hooks.defineLocale('en-ie', {
+    months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+    weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+    weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+    weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD-MM-YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : '%s ago',
+        s : 'a few seconds',
+        m : 'a minute',
+        mm : '%d minutes',
+        h : 'an hour',
+        hh : '%d hours',
+        d : 'a day',
+        dd : '%d days',
+        M : 'a month',
+        MM : '%d months',
+        y : 'a year',
+        yy : '%d years'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : English (New Zealand) [en-nz]
+//! author : Luke McGregor : https://github.com/lukemcgregor
+
+hooks.defineLocale('en-nz', {
+    months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+    weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+    weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+    weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[Today at] LT',
+        nextDay : '[Tomorrow at] LT',
+        nextWeek : 'dddd [at] LT',
+        lastDay : '[Yesterday at] LT',
+        lastWeek : '[Last] dddd [at] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'in %s',
+        past : '%s ago',
+        s : 'a few seconds',
+        m : 'a minute',
+        mm : '%d minutes',
+        h : 'an hour',
+        hh : '%d hours',
+        d : 'a day',
+        dd : '%d days',
+        M : 'a month',
+        MM : '%d months',
+        y : 'a year',
+        yy : '%d years'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Esperanto [eo]
+//! author : Colin Dean : https://github.com/colindean
+//! komento: Mi estas malcerta se mi korekte traktis akuzativojn en tiu traduko.
+//!          Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni!
+
+hooks.defineLocale('eo', {
+    months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aÅ­gusto_septembro_oktobro_novembro_decembro'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aÅ­g_sep_okt_nov_dec'.split('_'),
+    weekdays : 'Dimanĉo_Lundo_Mardo_Merkredo_Ĵaŭdo_Vendredo_Sabato'.split('_'),
+    weekdaysShort : 'Dim_Lun_Mard_Merk_Ä´aÅ­_Ven_Sab'.split('_'),
+    weekdaysMin : 'Di_Lu_Ma_Me_Ä´a_Ve_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'YYYY-MM-DD',
+        LL : 'D[-an de] MMMM, YYYY',
+        LLL : 'D[-an de] MMMM, YYYY HH:mm',
+        LLLL : 'dddd, [la] D[-an de] MMMM, YYYY HH:mm'
+    },
+    meridiemParse: /[ap]\.t\.m/i,
+    isPM: function (input) {
+        return input.charAt(0).toLowerCase() === 'p';
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'p.t.m.' : 'P.T.M.';
+        } else {
+            return isLower ? 'a.t.m.' : 'A.T.M.';
+        }
+    },
+    calendar : {
+        sameDay : '[HodiaÅ­ je] LT',
+        nextDay : '[MorgaÅ­ je] LT',
+        nextWeek : 'dddd [je] LT',
+        lastDay : '[HieraÅ­ je] LT',
+        lastWeek : '[pasinta] dddd [je] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'je %s',
+        past : 'antaÅ­ %s',
+        s : 'sekundoj',
+        m : 'minuto',
+        mm : '%d minutoj',
+        h : 'horo',
+        hh : '%d horoj',
+        d : 'tago',//ne 'diurno', ĉar estas uzita por proksimumo
+        dd : '%d tagoj',
+        M : 'monato',
+        MM : '%d monatoj',
+        y : 'jaro',
+        yy : '%d jaroj'
+    },
+    ordinalParse: /\d{1,2}a/,
+    ordinal : '%da',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Spanish (Dominican Republic) [es-do]
+
+var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
+var monthsShort$1 = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
+
+hooks.defineLocale('es-do', {
+    months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
+    monthsShort : function (m, format) {
+        if (/-MMM-/.test(format)) {
+            return monthsShort$1[m.month()];
+        } else {
+            return monthsShortDot[m.month()];
+        }
+    },
+    monthsParseExact : true,
+    weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
+    weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
+    weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D [de] MMMM [de] YYYY',
+        LLL : 'D [de] MMMM [de] YYYY h:mm A',
+        LLLL : 'dddd, D [de] MMMM [de] YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : function () {
+            return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        nextDay : function () {
+            return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        nextWeek : function () {
+            return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        lastDay : function () {
+            return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        lastWeek : function () {
+            return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'en %s',
+        past : 'hace %s',
+        s : 'unos segundos',
+        m : 'un minuto',
+        mm : '%d minutos',
+        h : 'una hora',
+        hh : '%d horas',
+        d : 'un día',
+        dd : '%d días',
+        M : 'un mes',
+        MM : '%d meses',
+        y : 'un año',
+        yy : '%d años'
+    },
+    ordinalParse : /\d{1,2}º/,
+    ordinal : '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Spanish [es]
+//! author : Julio Napurí : https://github.com/julionc
+
+var monthsShortDot$1 = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_');
+var monthsShort$2 = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
+
+hooks.defineLocale('es', {
+    months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
+    monthsShort : function (m, format) {
+        if (/-MMM-/.test(format)) {
+            return monthsShort$2[m.month()];
+        } else {
+            return monthsShortDot$1[m.month()];
+        }
+    },
+    monthsParseExact : true,
+    weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
+    weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
+    weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D [de] MMMM [de] YYYY',
+        LLL : 'D [de] MMMM [de] YYYY H:mm',
+        LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
+    },
+    calendar : {
+        sameDay : function () {
+            return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        nextDay : function () {
+            return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        nextWeek : function () {
+            return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        lastDay : function () {
+            return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        lastWeek : function () {
+            return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT';
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'en %s',
+        past : 'hace %s',
+        s : 'unos segundos',
+        m : 'un minuto',
+        mm : '%d minutos',
+        h : 'una hora',
+        hh : '%d horas',
+        d : 'un día',
+        dd : '%d días',
+        M : 'un mes',
+        MM : '%d meses',
+        y : 'un año',
+        yy : '%d años'
+    },
+    ordinalParse : /\d{1,2}º/,
+    ordinal : '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Estonian [et]
+//! author : Henry Kehlmann : https://github.com/madhenry
+//! improvements : Illimar Tambek : https://github.com/ragulka
+
+function processRelativeTime$2(number, withoutSuffix, key, isFuture) {
+    var format = {
+        's' : ['mõne sekundi', 'mõni sekund', 'paar sekundit'],
+        'm' : ['ühe minuti', 'üks minut'],
+        'mm': [number + ' minuti', number + ' minutit'],
+        'h' : ['ühe tunni', 'tund aega', 'üks tund'],
+        'hh': [number + ' tunni', number + ' tundi'],
+        'd' : ['ühe päeva', 'üks päev'],
+        'M' : ['kuu aja', 'kuu aega', 'üks kuu'],
+        'MM': [number + ' kuu', number + ' kuud'],
+        'y' : ['ühe aasta', 'aasta', 'üks aasta'],
+        'yy': [number + ' aasta', number + ' aastat']
+    };
+    if (withoutSuffix) {
+        return format[key][2] ? format[key][2] : format[key][1];
+    }
+    return isFuture ? format[key][0] : format[key][1];
+}
+
+hooks.defineLocale('et', {
+    months        : 'jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember'.split('_'),
+    monthsShort   : 'jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets'.split('_'),
+    weekdays      : 'pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev'.split('_'),
+    weekdaysShort : 'P_E_T_K_N_R_L'.split('_'),
+    weekdaysMin   : 'P_E_T_K_N_R_L'.split('_'),
+    longDateFormat : {
+        LT   : 'H:mm',
+        LTS : 'H:mm:ss',
+        L    : 'DD.MM.YYYY',
+        LL   : 'D. MMMM YYYY',
+        LLL  : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay  : '[Täna,] LT',
+        nextDay  : '[Homme,] LT',
+        nextWeek : '[Järgmine] dddd LT',
+        lastDay  : '[Eile,] LT',
+        lastWeek : '[Eelmine] dddd LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s pärast',
+        past   : '%s tagasi',
+        s      : processRelativeTime$2,
+        m      : processRelativeTime$2,
+        mm     : processRelativeTime$2,
+        h      : processRelativeTime$2,
+        hh     : processRelativeTime$2,
+        d      : processRelativeTime$2,
+        dd     : '%d päeva',
+        M      : processRelativeTime$2,
+        MM     : processRelativeTime$2,
+        y      : processRelativeTime$2,
+        yy     : processRelativeTime$2
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Basque [eu]
+//! author : Eneko Illarramendi : https://github.com/eillarra
+
+hooks.defineLocale('eu', {
+    months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'),
+    monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'),
+    weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'),
+    weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'YYYY-MM-DD',
+        LL : 'YYYY[ko] MMMM[ren] D[a]',
+        LLL : 'YYYY[ko] MMMM[ren] D[a] HH:mm',
+        LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm',
+        l : 'YYYY-M-D',
+        ll : 'YYYY[ko] MMM D[a]',
+        lll : 'YYYY[ko] MMM D[a] HH:mm',
+        llll : 'ddd, YYYY[ko] MMM D[a] HH:mm'
+    },
+    calendar : {
+        sameDay : '[gaur] LT[etan]',
+        nextDay : '[bihar] LT[etan]',
+        nextWeek : 'dddd LT[etan]',
+        lastDay : '[atzo] LT[etan]',
+        lastWeek : '[aurreko] dddd LT[etan]',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s barru',
+        past : 'duela %s',
+        s : 'segundo batzuk',
+        m : 'minutu bat',
+        mm : '%d minutu',
+        h : 'ordu bat',
+        hh : '%d ordu',
+        d : 'egun bat',
+        dd : '%d egun',
+        M : 'hilabete bat',
+        MM : '%d hilabete',
+        y : 'urte bat',
+        yy : '%d urte'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Persian [fa]
+//! author : Ebrahim Byagowi : https://github.com/ebraminio
+
+var symbolMap$5 = {
+    '1': 'Û±',
+    '2': 'Û²',
+    '3': 'Û³',
+    '4': 'Û´',
+    '5': 'Ûµ',
+    '6': 'Û¶',
+    '7': 'Û·',
+    '8': 'Û¸',
+    '9': 'Û¹',
+    '0': 'Û°'
+};
+var numberMap$4 = {
+    'Û±': '1',
+    'Û²': '2',
+    'Û³': '3',
+    'Û´': '4',
+    'Ûµ': '5',
+    'Û¶': '6',
+    'Û·': '7',
+    'Û¸': '8',
+    'Û¹': '9',
+    'Û°': '0'
+};
+
+hooks.defineLocale('fa', {
+    months : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'),
+    monthsShort : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'),
+    weekdays : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'),
+    weekdaysShort : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'),
+    weekdaysMin : 'ی_د_س_چ_پ_ج_ش'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /قبل از ظهر|بعد از ظهر/,
+    isPM: function (input) {
+        return /بعد از ظهر/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'قبل از ظهر';
+        } else {
+            return 'بعد از ظهر';
+        }
+    },
+    calendar : {
+        sameDay : '[امروز ساعت] LT',
+        nextDay : '[فردا ساعت] LT',
+        nextWeek : 'dddd [ساعت] LT',
+        lastDay : '[دیروز ساعت] LT',
+        lastWeek : 'dddd [پیش] [ساعت] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'در %s',
+        past : '%s پیش',
+        s : 'چندین ثانیه',
+        m : 'یک دقیقه',
+        mm : '%d دقیقه',
+        h : 'یک ساعت',
+        hh : '%d ساعت',
+        d : 'یک روز',
+        dd : '%d روز',
+        M : 'یک ماه',
+        MM : '%d ماه',
+        y : 'یک سال',
+        yy : '%d سال'
+    },
+    preparse: function (string) {
+        return string.replace(/[Û°-Û¹]/g, function (match) {
+            return numberMap$4[match];
+        }).replace(/،/g, ',');
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$5[match];
+        }).replace(/,/g, '،');
+    },
+    ordinalParse: /\d{1,2}Ù…/,
+    ordinal : '%dÙ…',
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12 // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Finnish [fi]
+//! author : Tarmo Aidantausta : https://github.com/bleadof
+
+var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' ');
+var numbersFuture = [
+        'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden',
+        numbersPast[7], numbersPast[8], numbersPast[9]
+    ];
+function translate$2(number, withoutSuffix, key, isFuture) {
+    var result = '';
+    switch (key) {
+        case 's':
+            return isFuture ? 'muutaman sekunnin' : 'muutama sekunti';
+        case 'm':
+            return isFuture ? 'minuutin' : 'minuutti';
+        case 'mm':
+            result = isFuture ? 'minuutin' : 'minuuttia';
+            break;
+        case 'h':
+            return isFuture ? 'tunnin' : 'tunti';
+        case 'hh':
+            result = isFuture ? 'tunnin' : 'tuntia';
+            break;
+        case 'd':
+            return isFuture ? 'päivän' : 'päivä';
+        case 'dd':
+            result = isFuture ? 'päivän' : 'päivää';
+            break;
+        case 'M':
+            return isFuture ? 'kuukauden' : 'kuukausi';
+        case 'MM':
+            result = isFuture ? 'kuukauden' : 'kuukautta';
+            break;
+        case 'y':
+            return isFuture ? 'vuoden' : 'vuosi';
+        case 'yy':
+            result = isFuture ? 'vuoden' : 'vuotta';
+            break;
+    }
+    result = verbalNumber(number, isFuture) + ' ' + result;
+    return result;
+}
+function verbalNumber(number, isFuture) {
+    return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number;
+}
+
+hooks.defineLocale('fi', {
+    months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'),
+    monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
+    weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'),
+    weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'),
+    weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD.MM.YYYY',
+        LL : 'Do MMMM[ta] YYYY',
+        LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm',
+        LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm',
+        l : 'D.M.YYYY',
+        ll : 'Do MMM YYYY',
+        lll : 'Do MMM YYYY, [klo] HH.mm',
+        llll : 'ddd, Do MMM YYYY, [klo] HH.mm'
+    },
+    calendar : {
+        sameDay : '[tänään] [klo] LT',
+        nextDay : '[huomenna] [klo] LT',
+        nextWeek : 'dddd [klo] LT',
+        lastDay : '[eilen] [klo] LT',
+        lastWeek : '[viime] dddd[na] [klo] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s päästä',
+        past : '%s sitten',
+        s : translate$2,
+        m : translate$2,
+        mm : translate$2,
+        h : translate$2,
+        hh : translate$2,
+        d : translate$2,
+        dd : translate$2,
+        M : translate$2,
+        MM : translate$2,
+        y : translate$2,
+        yy : translate$2
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Faroese [fo]
+//! author : Ragnar Johannesen : https://github.com/ragnar123
+
+hooks.defineLocale('fo', {
+    months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'),
+    weekdays : 'sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur'.split('_'),
+    weekdaysShort : 'sun_mán_týs_mik_hós_frí_ley'.split('_'),
+    weekdaysMin : 'su_má_tý_mi_hó_fr_le'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D. MMMM, YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Í dag kl.] LT',
+        nextDay : '[Í morgin kl.] LT',
+        nextWeek : 'dddd [kl.] LT',
+        lastDay : '[Í gjár kl.] LT',
+        lastWeek : '[síðstu] dddd [kl] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'um %s',
+        past : '%s síðani',
+        s : 'fá sekund',
+        m : 'ein minutt',
+        mm : '%d minuttir',
+        h : 'ein tími',
+        hh : '%d tímar',
+        d : 'ein dagur',
+        dd : '%d dagar',
+        M : 'ein mánaði',
+        MM : '%d mánaðir',
+        y : 'eitt ár',
+        yy : '%d ár'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : French (Canada) [fr-ca]
+//! author : Jonathan Abourbih : https://github.com/jonbca
+
+hooks.defineLocale('fr-ca', {
+    months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
+    monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
+    weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
+    weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'YYYY-MM-DD',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Aujourd\'hui à] LT',
+        nextDay: '[Demain à] LT',
+        nextWeek: 'dddd [à] LT',
+        lastDay: '[Hier à] LT',
+        lastWeek: 'dddd [dernier à] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'dans %s',
+        past : 'il y a %s',
+        s : 'quelques secondes',
+        m : 'une minute',
+        mm : '%d minutes',
+        h : 'une heure',
+        hh : '%d heures',
+        d : 'un jour',
+        dd : '%d jours',
+        M : 'un mois',
+        MM : '%d mois',
+        y : 'un an',
+        yy : '%d ans'
+    },
+    ordinalParse: /\d{1,2}(er|e)/,
+    ordinal : function (number) {
+        return number + (number === 1 ? 'er' : 'e');
+    }
+});
+
+//! moment.js locale configuration
+//! locale : French (Switzerland) [fr-ch]
+//! author : Gaspard Bucher : https://github.com/gaspard
+
+hooks.defineLocale('fr-ch', {
+    months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
+    monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
+    weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
+    weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Aujourd\'hui à] LT',
+        nextDay: '[Demain à] LT',
+        nextWeek: 'dddd [à] LT',
+        lastDay: '[Hier à] LT',
+        lastWeek: 'dddd [dernier à] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'dans %s',
+        past : 'il y a %s',
+        s : 'quelques secondes',
+        m : 'une minute',
+        mm : '%d minutes',
+        h : 'une heure',
+        hh : '%d heures',
+        d : 'un jour',
+        dd : '%d jours',
+        M : 'un mois',
+        MM : '%d mois',
+        y : 'un an',
+        yy : '%d ans'
+    },
+    ordinalParse: /\d{1,2}(er|e)/,
+    ordinal : function (number) {
+        return number + (number === 1 ? 'er' : 'e');
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : French [fr]
+//! author : John Fischer : https://github.com/jfroffice
+
+hooks.defineLocale('fr', {
+    months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
+    monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
+    weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
+    weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Aujourd\'hui à] LT',
+        nextDay: '[Demain à] LT',
+        nextWeek: 'dddd [à] LT',
+        lastDay: '[Hier à] LT',
+        lastWeek: 'dddd [dernier à] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'dans %s',
+        past : 'il y a %s',
+        s : 'quelques secondes',
+        m : 'une minute',
+        mm : '%d minutes',
+        h : 'une heure',
+        hh : '%d heures',
+        d : 'un jour',
+        dd : '%d jours',
+        M : 'un mois',
+        MM : '%d mois',
+        y : 'un an',
+        yy : '%d ans'
+    },
+    ordinalParse: /\d{1,2}(er|)/,
+    ordinal : function (number) {
+        return number + (number === 1 ? 'er' : '');
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Frisian [fy]
+//! author : Robin van der Vliet : https://github.com/robin0van0der0v
+
+var monthsShortWithDots = 'jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.'.split('_');
+var monthsShortWithoutDots = 'jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_');
+
+hooks.defineLocale('fy', {
+    months : 'jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber'.split('_'),
+    monthsShort : function (m, format) {
+        if (/-MMM-/.test(format)) {
+            return monthsShortWithoutDots[m.month()];
+        } else {
+            return monthsShortWithDots[m.month()];
+        }
+    },
+    monthsParseExact : true,
+    weekdays : 'snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon'.split('_'),
+    weekdaysShort : 'si._mo._ti._wo._to._fr._so.'.split('_'),
+    weekdaysMin : 'Si_Mo_Ti_Wo_To_Fr_So'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD-MM-YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[hjoed om] LT',
+        nextDay: '[moarn om] LT',
+        nextWeek: 'dddd [om] LT',
+        lastDay: '[juster om] LT',
+        lastWeek: '[ôfrûne] dddd [om] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'oer %s',
+        past : '%s lyn',
+        s : 'in pear sekonden',
+        m : 'ien minút',
+        mm : '%d minuten',
+        h : 'ien oere',
+        hh : '%d oeren',
+        d : 'ien dei',
+        dd : '%d dagen',
+        M : 'ien moanne',
+        MM : '%d moannen',
+        y : 'ien jier',
+        yy : '%d jierren'
+    },
+    ordinalParse: /\d{1,2}(ste|de)/,
+    ordinal : function (number) {
+        return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Scottish Gaelic [gd]
+//! author : Jon Ashdown : https://github.com/jonashdown
+
+var months$5 = [
+    'Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'
+];
+
+var monthsShort$3 = ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh'];
+
+var weekdays$1 = ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne'];
+
+var weekdaysShort = ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis'];
+
+var weekdaysMin = ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa'];
+
+hooks.defineLocale('gd', {
+    months : months$5,
+    monthsShort : monthsShort$3,
+    monthsParseExact : true,
+    weekdays : weekdays$1,
+    weekdaysShort : weekdaysShort,
+    weekdaysMin : weekdaysMin,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[An-diugh aig] LT',
+        nextDay : '[A-màireach aig] LT',
+        nextWeek : 'dddd [aig] LT',
+        lastDay : '[An-dè aig] LT',
+        lastWeek : 'dddd [seo chaidh] [aig] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'ann an %s',
+        past : 'bho chionn %s',
+        s : 'beagan diogan',
+        m : 'mionaid',
+        mm : '%d mionaidean',
+        h : 'uair',
+        hh : '%d uairean',
+        d : 'latha',
+        dd : '%d latha',
+        M : 'mìos',
+        MM : '%d mìosan',
+        y : 'bliadhna',
+        yy : '%d bliadhna'
+    },
+    ordinalParse : /\d{1,2}(d|na|mh)/,
+    ordinal : function (number) {
+        var output = number === 1 ? 'd' : number % 10 === 2 ? 'na' : 'mh';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Galician [gl]
+//! author : Juan G. Hurtado : https://github.com/juanghurtado
+
+hooks.defineLocale('gl', {
+    months : 'xaneiro_febreiro_marzo_abril_maio_xuño_xullo_agosto_setembro_outubro_novembro_decembro'.split('_'),
+    monthsShort : 'xan._feb._mar._abr._mai._xuñ._xul._ago._set._out._nov._dec.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'domingo_luns_martes_mércores_xoves_venres_sábado'.split('_'),
+    weekdaysShort : 'dom._lun._mar._mér._xov._ven._sáb.'.split('_'),
+    weekdaysMin : 'do_lu_ma_mé_xo_ve_sá'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D [de] MMMM [de] YYYY',
+        LLL : 'D [de] MMMM [de] YYYY H:mm',
+        LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm'
+    },
+    calendar : {
+        sameDay : function () {
+            return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT';
+        },
+        nextDay : function () {
+            return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT';
+        },
+        nextWeek : function () {
+            return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT';
+        },
+        lastDay : function () {
+            return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT';
+        },
+        lastWeek : function () {
+            return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT';
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : function (str) {
+            if (str.indexOf('un') === 0) {
+                return 'n' + str;
+            }
+            return 'en ' + str;
+        },
+        past : 'hai %s',
+        s : 'uns segundos',
+        m : 'un minuto',
+        mm : '%d minutos',
+        h : 'unha hora',
+        hh : '%d horas',
+        d : 'un día',
+        dd : '%d días',
+        M : 'un mes',
+        MM : '%d meses',
+        y : 'un ano',
+        yy : '%d anos'
+    },
+    ordinalParse : /\d{1,2}º/,
+    ordinal : '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Hebrew [he]
+//! author : Tomer Cohen : https://github.com/tomer
+//! author : Moshe Simantov : https://github.com/DevelopmentIL
+//! author : Tal Ater : https://github.com/TalAter
+
+hooks.defineLocale('he', {
+    months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'),
+    monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳'.split('_'),
+    weekdays : 'ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת'.split('_'),
+    weekdaysShort : 'א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳'.split('_'),
+    weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D [ב]MMMM YYYY',
+        LLL : 'D [ב]MMMM YYYY HH:mm',
+        LLLL : 'dddd, D [ב]MMMM YYYY HH:mm',
+        l : 'D/M/YYYY',
+        ll : 'D MMM YYYY',
+        lll : 'D MMM YYYY HH:mm',
+        llll : 'ddd, D MMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[היום ב־]LT',
+        nextDay : '[מחר ב־]LT',
+        nextWeek : 'dddd [בשעה] LT',
+        lastDay : '[אתמול ב־]LT',
+        lastWeek : '[ביום] dddd [האחרון בשעה] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'בעוד %s',
+        past : 'לפני %s',
+        s : 'מספר שניות',
+        m : 'דקה',
+        mm : '%d דקות',
+        h : 'שעה',
+        hh : function (number) {
+            if (number === 2) {
+                return 'שעתיים';
+            }
+            return number + ' שעות';
+        },
+        d : 'יום',
+        dd : function (number) {
+            if (number === 2) {
+                return 'יומיים';
+            }
+            return number + ' ימים';
+        },
+        M : 'חודש',
+        MM : function (number) {
+            if (number === 2) {
+                return 'חודשיים';
+            }
+            return number + ' חודשים';
+        },
+        y : 'שנה',
+        yy : function (number) {
+            if (number === 2) {
+                return 'שנתיים';
+            } else if (number % 10 === 0 && number !== 10) {
+                return number + ' שנה';
+            }
+            return number + ' שנים';
+        }
+    },
+    meridiemParse: /אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i,
+    isPM : function (input) {
+        return /^(אחה"צ|אחרי הצהריים|בערב)$/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 5) {
+            return 'לפנות בוקר';
+        } else if (hour < 10) {
+            return 'בבוקר';
+        } else if (hour < 12) {
+            return isLower ? 'לפנה"צ' : 'לפני הצהריים';
+        } else if (hour < 18) {
+            return isLower ? 'אחה"צ' : 'אחרי הצהריים';
+        } else {
+            return 'בערב';
+        }
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Hindi [hi]
+//! author : Mayank Singhal : https://github.com/mayanksinghal
+
+var symbolMap$6 = {
+    '1': '१',
+    '2': '२',
+    '3': '३',
+    '4': '४',
+    '5': '५',
+    '6': '६',
+    '7': '७',
+    '8': '८',
+    '9': '९',
+    '0': '०'
+};
+var numberMap$5 = {
+    '१': '1',
+    '२': '2',
+    '३': '3',
+    '४': '4',
+    '५': '5',
+    '६': '6',
+    '७': '7',
+    '८': '8',
+    '९': '9',
+    '०': '0'
+};
+
+hooks.defineLocale('hi', {
+    months : 'जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर'.split('_'),
+    monthsShort : 'जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'),
+    weekdaysShort : 'रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि'.split('_'),
+    weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm बजे',
+        LTS : 'A h:mm:ss बजे',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm बजे',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm बजे'
+    },
+    calendar : {
+        sameDay : '[आज] LT',
+        nextDay : '[कल] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[कल] LT',
+        lastWeek : '[पिछले] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s में',
+        past : '%s पहले',
+        s : 'कुछ ही क्षण',
+        m : 'एक मिनट',
+        mm : '%d मिनट',
+        h : 'एक घंटा',
+        hh : '%d घंटे',
+        d : 'एक दिन',
+        dd : '%d दिन',
+        M : 'एक महीने',
+        MM : '%d महीने',
+        y : 'एक वर्ष',
+        yy : '%d वर्ष'
+    },
+    preparse: function (string) {
+        return string.replace(/[१२३४५६७८९०]/g, function (match) {
+            return numberMap$5[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$6[match];
+        });
+    },
+    // Hindi notation for meridiems are quite fuzzy in practice. While there exists
+    // a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi.
+    meridiemParse: /रात|सुबह|दोपहर|शाम/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'रात') {
+            return hour < 4 ? hour : hour + 12;
+        } else if (meridiem === 'सुबह') {
+            return hour;
+        } else if (meridiem === 'दोपहर') {
+            return hour >= 10 ? hour : hour + 12;
+        } else if (meridiem === 'शाम') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'रात';
+        } else if (hour < 10) {
+            return 'सुबह';
+        } else if (hour < 17) {
+            return 'दोपहर';
+        } else if (hour < 20) {
+            return 'शाम';
+        } else {
+            return 'रात';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Croatian [hr]
+//! author : Bojan Marković : https://github.com/bmarkovic
+
+function translate$3(number, withoutSuffix, key) {
+    var result = number + ' ';
+    switch (key) {
+        case 'm':
+            return withoutSuffix ? 'jedna minuta' : 'jedne minute';
+        case 'mm':
+            if (number === 1) {
+                result += 'minuta';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'minute';
+            } else {
+                result += 'minuta';
+            }
+            return result;
+        case 'h':
+            return withoutSuffix ? 'jedan sat' : 'jednog sata';
+        case 'hh':
+            if (number === 1) {
+                result += 'sat';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'sata';
+            } else {
+                result += 'sati';
+            }
+            return result;
+        case 'dd':
+            if (number === 1) {
+                result += 'dan';
+            } else {
+                result += 'dana';
+            }
+            return result;
+        case 'MM':
+            if (number === 1) {
+                result += 'mjesec';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'mjeseca';
+            } else {
+                result += 'mjeseci';
+            }
+            return result;
+        case 'yy':
+            if (number === 1) {
+                result += 'godina';
+            } else if (number === 2 || number === 3 || number === 4) {
+                result += 'godine';
+            } else {
+                result += 'godina';
+            }
+            return result;
+    }
+}
+
+hooks.defineLocale('hr', {
+    months : {
+        format: 'siječnja_veljače_ožujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca'.split('_'),
+        standalone: 'siječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac'.split('_')
+    },
+    monthsShort : 'sij._velj._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'),
+    weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'),
+    weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay  : '[danas u] LT',
+        nextDay  : '[sutra u] LT',
+        nextWeek : function () {
+            switch (this.day()) {
+                case 0:
+                    return '[u] [nedjelju] [u] LT';
+                case 3:
+                    return '[u] [srijedu] [u] LT';
+                case 6:
+                    return '[u] [subotu] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[u] dddd [u] LT';
+            }
+        },
+        lastDay  : '[jučer u] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                    return '[prošlu] dddd [u] LT';
+                case 6:
+                    return '[prošle] [subote] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[prošli] dddd [u] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past   : 'prije %s',
+        s      : 'par sekundi',
+        m      : translate$3,
+        mm     : translate$3,
+        h      : translate$3,
+        hh     : translate$3,
+        d      : 'dan',
+        dd     : translate$3,
+        M      : 'mjesec',
+        MM     : translate$3,
+        y      : 'godinu',
+        yy     : translate$3
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Hungarian [hu]
+//! author : Adam Brunner : https://github.com/adambrunner
+
+var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' ');
+function translate$4(number, withoutSuffix, key, isFuture) {
+    var num = number,
+        suffix;
+    switch (key) {
+        case 's':
+            return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce';
+        case 'm':
+            return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce');
+        case 'mm':
+            return num + (isFuture || withoutSuffix ? ' perc' : ' perce');
+        case 'h':
+            return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája');
+        case 'hh':
+            return num + (isFuture || withoutSuffix ? ' óra' : ' órája');
+        case 'd':
+            return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja');
+        case 'dd':
+            return num + (isFuture || withoutSuffix ? ' nap' : ' napja');
+        case 'M':
+            return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja');
+        case 'MM':
+            return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja');
+        case 'y':
+            return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve');
+        case 'yy':
+            return num + (isFuture || withoutSuffix ? ' év' : ' éve');
+    }
+    return '';
+}
+function week(isFuture) {
+    return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]';
+}
+
+hooks.defineLocale('hu', {
+    months : 'január_február_március_április_május_június_július_augusztus_szeptember_október_november_december'.split('_'),
+    monthsShort : 'jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec'.split('_'),
+    weekdays : 'vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat'.split('_'),
+    weekdaysShort : 'vas_hét_kedd_sze_csüt_pén_szo'.split('_'),
+    weekdaysMin : 'v_h_k_sze_cs_p_szo'.split('_'),
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'YYYY.MM.DD.',
+        LL : 'YYYY. MMMM D.',
+        LLL : 'YYYY. MMMM D. H:mm',
+        LLLL : 'YYYY. MMMM D., dddd H:mm'
+    },
+    meridiemParse: /de|du/i,
+    isPM: function (input) {
+        return input.charAt(1).toLowerCase() === 'u';
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 12) {
+            return isLower === true ? 'de' : 'DE';
+        } else {
+            return isLower === true ? 'du' : 'DU';
+        }
+    },
+    calendar : {
+        sameDay : '[ma] LT[-kor]',
+        nextDay : '[holnap] LT[-kor]',
+        nextWeek : function () {
+            return week.call(this, true);
+        },
+        lastDay : '[tegnap] LT[-kor]',
+        lastWeek : function () {
+            return week.call(this, false);
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s múlva',
+        past : '%s',
+        s : translate$4,
+        m : translate$4,
+        mm : translate$4,
+        h : translate$4,
+        hh : translate$4,
+        d : translate$4,
+        dd : translate$4,
+        M : translate$4,
+        MM : translate$4,
+        y : translate$4,
+        yy : translate$4
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Armenian [hy-am]
+//! author : Armendarabyan : https://github.com/armendarabyan
+
+hooks.defineLocale('hy-am', {
+    months : {
+        format: 'Õ°Õ¸Ö‚Õ¶Õ¾Õ¡Ö€Õ«_ÖƒÕ¥Õ¿Ö€Õ¾Õ¡Ö€Õ«_Õ´Õ¡Ö€Õ¿Õ«_Õ¡ÕºÖ€Õ«Õ¬Õ«_Õ´Õ¡ÕµÕ«Õ½Õ«_Õ°Õ¸Ö‚Õ¶Õ«Õ½Õ«_Õ°Õ¸Ö‚Õ¬Õ«Õ½Õ«_Ö…Õ£Õ¸Õ½Õ¿Õ¸Õ½Õ«_Õ½Õ¥ÕºÕ¿Õ¥Õ´Õ¢Õ¥Ö€Õ«_Õ°Õ¸Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€Õ«_Õ¶Õ¸ÕµÕ¥Õ´Õ¢Õ¥Ö€Õ«_Õ¤Õ¥Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€Õ«'.split('_'),
+        standalone: 'Õ°Õ¸Ö‚Õ¶Õ¾Õ¡Ö€_ÖƒÕ¥Õ¿Ö€Õ¾Õ¡Ö€_Õ´Õ¡Ö€Õ¿_Õ¡ÕºÖ€Õ«Õ¬_Õ´Õ¡ÕµÕ«Õ½_Õ°Õ¸Ö‚Õ¶Õ«Õ½_Õ°Õ¸Ö‚Õ¬Õ«Õ½_Ö…Õ£Õ¸Õ½Õ¿Õ¸Õ½_Õ½Õ¥ÕºÕ¿Õ¥Õ´Õ¢Õ¥Ö€_Õ°Õ¸Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€_Õ¶Õ¸ÕµÕ¥Õ´Õ¢Õ¥Ö€_Õ¤Õ¥Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€'.split('_')
+    },
+    monthsShort : 'Õ°Õ¶Õ¾_ÖƒÕ¿Ö€_Õ´Ö€Õ¿_Õ¡ÕºÖ€_Õ´ÕµÕ½_Õ°Õ¶Õ½_Õ°Õ¬Õ½_Ö…Õ£Õ½_Õ½ÕºÕ¿_Õ°Õ¯Õ¿_Õ¶Õ´Õ¢_Õ¤Õ¯Õ¿'.split('_'),
+    weekdays : 'Õ¯Õ«Ö€Õ¡Õ¯Õ«_Õ¥Ö€Õ¯Õ¸Ö‚Õ·Õ¡Õ¢Õ©Õ«_Õ¥Ö€Õ¥Ö„Õ·Õ¡Õ¢Õ©Õ«_Õ¹Õ¸Ö€Õ¥Ö„Õ·Õ¡Õ¢Õ©Õ«_Õ°Õ«Õ¶Õ£Õ·Õ¡Õ¢Õ©Õ«_Õ¸Ö‚Ö€Õ¢Õ¡Õ©_Õ·Õ¡Õ¢Õ¡Õ©'.split('_'),
+    weekdaysShort : 'Õ¯Ö€Õ¯_Õ¥Ö€Õ¯_Õ¥Ö€Ö„_Õ¹Ö€Ö„_Õ°Õ¶Õ£_Õ¸Ö‚Ö€Õ¢_Õ·Õ¢Õ©'.split('_'),
+    weekdaysMin : 'Õ¯Ö€Õ¯_Õ¥Ö€Õ¯_Õ¥Ö€Ö„_Õ¹Ö€Ö„_Õ°Õ¶Õ£_Õ¸Ö‚Ö€Õ¢_Õ·Õ¢Õ©'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY Õ©.',
+        LLL : 'D MMMM YYYY Õ©., HH:mm',
+        LLLL : 'dddd, D MMMM YYYY Õ©., HH:mm'
+    },
+    calendar : {
+        sameDay: '[Õ¡ÕµÕ½Ö…Ö€] LT',
+        nextDay: '[Õ¾Õ¡Õ²Õ¨] LT',
+        lastDay: '[Õ¥Ö€Õ¥Õ¯] LT',
+        nextWeek: function () {
+            return 'dddd [Ö…Ö€Õ¨ ÕªÕ¡Õ´Õ¨] LT';
+        },
+        lastWeek: function () {
+            return '[անցած] dddd [օրը ժամը] LT';
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : '%s Õ°Õ¥Õ¿Õ¸',
+        past : '%s Õ¡Õ¼Õ¡Õ»',
+        s : 'Õ´Õ« Ö„Õ¡Õ¶Õ« Õ¾Õ¡ÕµÖ€Õ¯ÕµÕ¡Õ¶',
+        m : 'Ö€Õ¸ÕºÕ¥',
+        mm : '%d Ö€Õ¸ÕºÕ¥',
+        h : 'ÕªÕ¡Õ´',
+        hh : '%d ÕªÕ¡Õ´',
+        d : 'Ö…Ö€',
+        dd : '%d Ö…Ö€',
+        M : 'Õ¡Õ´Õ«Õ½',
+        MM : '%d Õ¡Õ´Õ«Õ½',
+        y : 'Õ¿Õ¡Ö€Õ«',
+        yy : '%d Õ¿Õ¡Ö€Õ«'
+    },
+    meridiemParse: /գիշերվա|առավոտվա|ցերեկվա|երեկոյան/,
+    isPM: function (input) {
+        return /^(ցերեկվա|երեկոյան)$/.test(input);
+    },
+    meridiem : function (hour) {
+        if (hour < 4) {
+            return 'Õ£Õ«Õ·Õ¥Ö€Õ¾Õ¡';
+        } else if (hour < 12) {
+            return 'Õ¡Õ¼Õ¡Õ¾Õ¸Õ¿Õ¾Õ¡';
+        } else if (hour < 17) {
+            return 'ցերեկվա';
+        } else {
+            return 'Õ¥Ö€Õ¥Õ¯Õ¸ÕµÕ¡Õ¶';
+        }
+    },
+    ordinalParse: /\d{1,2}|\d{1,2}-(Õ«Õ¶|Ö€Õ¤)/,
+    ordinal: function (number, period) {
+        switch (period) {
+            case 'DDD':
+            case 'w':
+            case 'W':
+            case 'DDDo':
+                if (number === 1) {
+                    return number + '-Õ«Õ¶';
+                }
+                return number + '-Ö€Õ¤';
+            default:
+                return number;
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Indonesian [id]
+//! author : Mohammad Satrio Utomo : https://github.com/tyok
+//! reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan
+
+hooks.defineLocale('id', {
+    months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des'.split('_'),
+    weekdays : 'Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu'.split('_'),
+    weekdaysShort : 'Min_Sen_Sel_Rab_Kam_Jum_Sab'.split('_'),
+    weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sb'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY [pukul] HH.mm',
+        LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
+    },
+    meridiemParse: /pagi|siang|sore|malam/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'pagi') {
+            return hour;
+        } else if (meridiem === 'siang') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === 'sore' || meridiem === 'malam') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 11) {
+            return 'pagi';
+        } else if (hours < 15) {
+            return 'siang';
+        } else if (hours < 19) {
+            return 'sore';
+        } else {
+            return 'malam';
+        }
+    },
+    calendar : {
+        sameDay : '[Hari ini pukul] LT',
+        nextDay : '[Besok pukul] LT',
+        nextWeek : 'dddd [pukul] LT',
+        lastDay : '[Kemarin pukul] LT',
+        lastWeek : 'dddd [lalu pukul] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'dalam %s',
+        past : '%s yang lalu',
+        s : 'beberapa detik',
+        m : 'semenit',
+        mm : '%d menit',
+        h : 'sejam',
+        hh : '%d jam',
+        d : 'sehari',
+        dd : '%d hari',
+        M : 'sebulan',
+        MM : '%d bulan',
+        y : 'setahun',
+        yy : '%d tahun'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Icelandic [is]
+//! author : Hinrik Örn Sigurðsson : https://github.com/hinrik
+
+function plural$2(n) {
+    if (n % 100 === 11) {
+        return true;
+    } else if (n % 10 === 1) {
+        return false;
+    }
+    return true;
+}
+function translate$5(number, withoutSuffix, key, isFuture) {
+    var result = number + ' ';
+    switch (key) {
+        case 's':
+            return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum';
+        case 'm':
+            return withoutSuffix ? 'mínúta' : 'mínútu';
+        case 'mm':
+            if (plural$2(number)) {
+                return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum');
+            } else if (withoutSuffix) {
+                return result + 'mínúta';
+            }
+            return result + 'mínútu';
+        case 'hh':
+            if (plural$2(number)) {
+                return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum');
+            }
+            return result + 'klukkustund';
+        case 'd':
+            if (withoutSuffix) {
+                return 'dagur';
+            }
+            return isFuture ? 'dag' : 'degi';
+        case 'dd':
+            if (plural$2(number)) {
+                if (withoutSuffix) {
+                    return result + 'dagar';
+                }
+                return result + (isFuture ? 'daga' : 'dögum');
+            } else if (withoutSuffix) {
+                return result + 'dagur';
+            }
+            return result + (isFuture ? 'dag' : 'degi');
+        case 'M':
+            if (withoutSuffix) {
+                return 'mánuður';
+            }
+            return isFuture ? 'mánuð' : 'mánuði';
+        case 'MM':
+            if (plural$2(number)) {
+                if (withoutSuffix) {
+                    return result + 'mánuðir';
+                }
+                return result + (isFuture ? 'mánuði' : 'mánuðum');
+            } else if (withoutSuffix) {
+                return result + 'mánuður';
+            }
+            return result + (isFuture ? 'mánuð' : 'mánuði');
+        case 'y':
+            return withoutSuffix || isFuture ? 'ár' : 'ári';
+        case 'yy':
+            if (plural$2(number)) {
+                return result + (withoutSuffix || isFuture ? 'ár' : 'árum');
+            }
+            return result + (withoutSuffix || isFuture ? 'ár' : 'ári');
+    }
+}
+
+hooks.defineLocale('is', {
+    months : 'janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des'.split('_'),
+    weekdays : 'sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur'.split('_'),
+    weekdaysShort : 'sun_mán_þri_mið_fim_fös_lau'.split('_'),
+    weekdaysMin : 'Su_Má_Þr_Mi_Fi_Fö_La'.split('_'),
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY [kl.] H:mm',
+        LLLL : 'dddd, D. MMMM YYYY [kl.] H:mm'
+    },
+    calendar : {
+        sameDay : '[í dag kl.] LT',
+        nextDay : '[á morgun kl.] LT',
+        nextWeek : 'dddd [kl.] LT',
+        lastDay : '[í gær kl.] LT',
+        lastWeek : '[síðasta] dddd [kl.] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'eftir %s',
+        past : 'fyrir %s síðan',
+        s : translate$5,
+        m : translate$5,
+        mm : translate$5,
+        h : 'klukkustund',
+        hh : translate$5,
+        d : translate$5,
+        dd : translate$5,
+        M : translate$5,
+        MM : translate$5,
+        y : translate$5,
+        yy : translate$5
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Italian [it]
+//! author : Lorenzo : https://github.com/aliem
+//! author: Mattia Larentis: https://github.com/nostalgiaz
+
+hooks.defineLocale('it', {
+    months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'),
+    monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'),
+    weekdays : 'Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato'.split('_'),
+    weekdaysShort : 'Dom_Lun_Mar_Mer_Gio_Ven_Sab'.split('_'),
+    weekdaysMin : 'Do_Lu_Ma_Me_Gi_Ve_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Oggi alle] LT',
+        nextDay: '[Domani alle] LT',
+        nextWeek: 'dddd [alle] LT',
+        lastDay: '[Ieri alle] LT',
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[la scorsa] dddd [alle] LT';
+                default:
+                    return '[lo scorso] dddd [alle] LT';
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : function (s) {
+            return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s;
+        },
+        past : '%s fa',
+        s : 'alcuni secondi',
+        m : 'un minuto',
+        mm : '%d minuti',
+        h : 'un\'ora',
+        hh : '%d ore',
+        d : 'un giorno',
+        dd : '%d giorni',
+        M : 'un mese',
+        MM : '%d mesi',
+        y : 'un anno',
+        yy : '%d anni'
+    },
+    ordinalParse : /\d{1,2}º/,
+    ordinal: '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Japanese [ja]
+//! author : LI Long : https://github.com/baryon
+
+hooks.defineLocale('ja', {
+    months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+    monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+    weekdays : '日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日'.split('_'),
+    weekdaysShort : '日_月_火_水_木_金_土'.split('_'),
+    weekdaysMin : '日_月_火_水_木_金_土'.split('_'),
+    longDateFormat : {
+        LT : 'Ah時m分',
+        LTS : 'Ah時m分s秒',
+        L : 'YYYY/MM/DD',
+        LL : 'YYYY年M月D日',
+        LLL : 'YYYY年M月D日Ah時m分',
+        LLLL : 'YYYY年M月D日Ah時m分 dddd'
+    },
+    meridiemParse: /午前|午後/i,
+    isPM : function (input) {
+        return input === '午後';
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return '午前';
+        } else {
+            return '午後';
+        }
+    },
+    calendar : {
+        sameDay : '[今日] LT',
+        nextDay : '[明日] LT',
+        nextWeek : '[来週]dddd LT',
+        lastDay : '[昨日] LT',
+        lastWeek : '[前週]dddd LT',
+        sameElse : 'L'
+    },
+    ordinalParse : /\d{1,2}æ—¥/,
+    ordinal : function (number, period) {
+        switch (period) {
+            case 'd':
+            case 'D':
+            case 'DDD':
+                return number + 'æ—¥';
+            default:
+                return number;
+        }
+    },
+    relativeTime : {
+        future : '%s後',
+        past : '%s前',
+        s : '数秒',
+        m : '1分',
+        mm : '%d分',
+        h : '1時間',
+        hh : '%d時間',
+        d : '1æ—¥',
+        dd : '%dæ—¥',
+        M : '1ヶ月',
+        MM : '%dヶ月',
+        y : '1å¹´',
+        yy : '%då¹´'
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Javanese [jv]
+//! author : Rony Lantip : https://github.com/lantip
+//! reference: http://jv.wikipedia.org/wiki/Basa_Jawa
+
+hooks.defineLocale('jv', {
+    months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember'.split('_'),
+    monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des'.split('_'),
+    weekdays : 'Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu'.split('_'),
+    weekdaysShort : 'Min_Sen_Sel_Reb_Kem_Jem_Sep'.split('_'),
+    weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sp'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY [pukul] HH.mm',
+        LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
+    },
+    meridiemParse: /enjing|siyang|sonten|ndalu/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'enjing') {
+            return hour;
+        } else if (meridiem === 'siyang') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === 'sonten' || meridiem === 'ndalu') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 11) {
+            return 'enjing';
+        } else if (hours < 15) {
+            return 'siyang';
+        } else if (hours < 19) {
+            return 'sonten';
+        } else {
+            return 'ndalu';
+        }
+    },
+    calendar : {
+        sameDay : '[Dinten puniko pukul] LT',
+        nextDay : '[Mbenjang pukul] LT',
+        nextWeek : 'dddd [pukul] LT',
+        lastDay : '[Kala wingi pukul] LT',
+        lastWeek : 'dddd [kepengker pukul] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'wonten ing %s',
+        past : '%s ingkang kepengker',
+        s : 'sawetawis detik',
+        m : 'setunggal menit',
+        mm : '%d menit',
+        h : 'setunggal jam',
+        hh : '%d jam',
+        d : 'sedinten',
+        dd : '%d dinten',
+        M : 'sewulan',
+        MM : '%d wulan',
+        y : 'setaun',
+        yy : '%d taun'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Georgian [ka]
+//! author : Irakli Janiashvili : https://github.com/irakli-janiashvili
+
+hooks.defineLocale('ka', {
+    months : {
+        standalone: 'იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი'.split('_'),
+        format: 'იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს'.split('_')
+    },
+    monthsShort : 'იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ'.split('_'),
+    weekdays : {
+        standalone: 'კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი'.split('_'),
+        format: 'კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს'.split('_'),
+        isFormat: /(წინა|შემდეგ)/
+    },
+    weekdaysShort : 'კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ'.split('_'),
+    weekdaysMin : 'კვ_ორ_სა_ოთ_ხუ_პა_შა'.split('_'),
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[დღეს] LT[-ზე]',
+        nextDay : '[ხვალ] LT[-ზე]',
+        lastDay : '[გუშინ] LT[-ზე]',
+        nextWeek : '[შემდეგ] dddd LT[-ზე]',
+        lastWeek : '[წინა] dddd LT-ზე',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : function (s) {
+            return (/(წამი|წუთი|საათი|წელი)/).test(s) ?
+                s.replace(/ი$/, 'ში') :
+                s + 'ში';
+        },
+        past : function (s) {
+            if ((/(წამი|წუთი|საათი|დღე|თვე)/).test(s)) {
+                return s.replace(/(ი|ე)$/, 'ის წინ');
+            }
+            if ((/წელი/).test(s)) {
+                return s.replace(/წელი$/, 'წლის წინ');
+            }
+        },
+        s : 'რამდენიმე წამი',
+        m : 'წუთი',
+        mm : '%d წუთი',
+        h : 'საათი',
+        hh : '%d საათი',
+        d : 'დღე',
+        dd : '%d დღე',
+        M : 'თვე',
+        MM : '%d თვე',
+        y : 'წელი',
+        yy : '%d წელი'
+    },
+    ordinalParse: /0|1-ლი|მე-\d{1,2}|\d{1,2}-ე/,
+    ordinal : function (number) {
+        if (number === 0) {
+            return number;
+        }
+        if (number === 1) {
+            return number + '-ლი';
+        }
+        if ((number < 20) || (number <= 100 && (number % 20 === 0)) || (number % 100 === 0)) {
+            return 'მე-' + number;
+        }
+        return number + '-ე';
+    },
+    week : {
+        dow : 1,
+        doy : 7
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Kazakh [kk]
+//! authors : Nurlan Rakhimzhanov : https://github.com/nurlan
+
+var suffixes$1 = {
+    0: '-ші',
+    1: '-ші',
+    2: '-ші',
+    3: '-ші',
+    4: '-ші',
+    5: '-ші',
+    6: '-шы',
+    7: '-ші',
+    8: '-ші',
+    9: '-шы',
+    10: '-шы',
+    20: '-шы',
+    30: '-шы',
+    40: '-шы',
+    50: '-ші',
+    60: '-шы',
+    70: '-ші',
+    80: '-ші',
+    90: '-шы',
+    100: '-ші'
+};
+
+hooks.defineLocale('kk', {
+    months : 'қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан'.split('_'),
+    monthsShort : 'қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел'.split('_'),
+    weekdays : 'жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі'.split('_'),
+    weekdaysShort : 'жек_дүй_сей_сәр_бей_жұм_сен'.split('_'),
+    weekdaysMin : 'жк_дй_сй_ср_бй_жм_сн'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Бүгін сағат] LT',
+        nextDay : '[Ертең сағат] LT',
+        nextWeek : 'dddd [сағат] LT',
+        lastDay : '[Кеше сағат] LT',
+        lastWeek : '[Өткен аптаның] dddd [сағат] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s ішінде',
+        past : '%s бұрын',
+        s : 'бірнеше секунд',
+        m : 'бір минут',
+        mm : '%d минут',
+        h : 'бір сағат',
+        hh : '%d сағат',
+        d : 'бір күн',
+        dd : '%d күн',
+        M : 'бір ай',
+        MM : '%d ай',
+        y : 'бір жыл',
+        yy : '%d жыл'
+    },
+    ordinalParse: /\d{1,2}-(ші|шы)/,
+    ordinal : function (number) {
+        var a = number % 10,
+            b = number >= 100 ? 100 : null;
+        return number + (suffixes$1[number] || suffixes$1[a] || suffixes$1[b]);
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Cambodian [km]
+//! author : Kruy Vanna : https://github.com/kruyvanna
+
+hooks.defineLocale('km', {
+    months: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'),
+    monthsShort: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'),
+    weekdays: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'),
+    weekdaysShort: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'),
+    weekdaysMin: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'),
+    longDateFormat: {
+        LT: 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L: 'DD/MM/YYYY',
+        LL: 'D MMMM YYYY',
+        LLL: 'D MMMM YYYY HH:mm',
+        LLLL: 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar: {
+        sameDay: '[ថ្ងៃនេះ ម៉ោង] LT',
+        nextDay: '[ស្អែក ម៉ោង] LT',
+        nextWeek: 'dddd [ម៉ោង] LT',
+        lastDay: '[ម្សិលមិញ ម៉ោង] LT',
+        lastWeek: 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT',
+        sameElse: 'L'
+    },
+    relativeTime: {
+        future: '%sទៀត',
+        past: '%sមុន',
+        s: 'ប៉ុន្មានវិនាទី',
+        m: 'មួយនាទី',
+        mm: '%d នាទី',
+        h: 'មួយម៉ោង',
+        hh: '%d ម៉ោង',
+        d: 'មួយថ្ងៃ',
+        dd: '%d ថ្ងៃ',
+        M: 'មួយខែ',
+        MM: '%d ខែ',
+        y: 'មួយឆ្នាំ',
+        yy: '%d ឆ្នាំ'
+    },
+    week: {
+        dow: 1, // Monday is the first day of the week.
+        doy: 4 // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Korean [ko]
+//! author : Kyungwook, Park : https://github.com/kyungw00k
+//! author : Jeeeyul Lee <jeeeyul@gmail.com>
+
+hooks.defineLocale('ko', {
+    months : '1ì›”_2ì›”_3ì›”_4ì›”_5ì›”_6ì›”_7ì›”_8ì›”_9ì›”_10ì›”_11ì›”_12ì›”'.split('_'),
+    monthsShort : '1ì›”_2ì›”_3ì›”_4ì›”_5ì›”_6ì›”_7ì›”_8ì›”_9ì›”_10ì›”_11ì›”_12ì›”'.split('_'),
+    weekdays : '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'),
+    weekdaysShort : '일_월_화_수_목_금_토'.split('_'),
+    weekdaysMin : '일_월_화_수_목_금_토'.split('_'),
+    longDateFormat : {
+        LT : 'A h시 m분',
+        LTS : 'A h시 m분 s초',
+        L : 'YYYY.MM.DD',
+        LL : 'YYYY년 MMMM D일',
+        LLL : 'YYYY년 MMMM D일 A h시 m분',
+        LLLL : 'YYYY년 MMMM D일 dddd A h시 m분'
+    },
+    calendar : {
+        sameDay : '오늘 LT',
+        nextDay : '내일 LT',
+        nextWeek : 'dddd LT',
+        lastDay : '어제 LT',
+        lastWeek : '지난주 dddd LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s 후',
+        past : '%s ì „',
+        s : '몇 초',
+        ss : '%dì´ˆ',
+        m : '일분',
+        mm : '%d분',
+        h : '한 시간',
+        hh : '%d시간',
+        d : '하루',
+        dd : '%d일',
+        M : '한 달',
+        MM : '%d달',
+        y : '일 년',
+        yy : '%dë…„'
+    },
+    ordinalParse : /\d{1,2}일/,
+    ordinal : '%d일',
+    meridiemParse : /오전|오후/,
+    isPM : function (token) {
+        return token === '오후';
+    },
+    meridiem : function (hour, minute, isUpper) {
+        return hour < 12 ? '오전' : '오후';
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Kyrgyz [ky]
+//! author : Chyngyz Arystan uulu : https://github.com/chyngyz
+
+
+var suffixes$2 = {
+    0: '-чү',
+    1: '-чи',
+    2: '-чи',
+    3: '-чү',
+    4: '-чү',
+    5: '-чи',
+    6: '-чы',
+    7: '-чи',
+    8: '-чи',
+    9: '-чу',
+    10: '-чу',
+    20: '-чы',
+    30: '-чу',
+    40: '-чы',
+    50: '-чү',
+    60: '-чы',
+    70: '-чи',
+    80: '-чи',
+    90: '-чу',
+    100: '-чү'
+};
+
+hooks.defineLocale('ky', {
+    months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
+    monthsShort : 'янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'),
+    weekdays : 'Жекшемби_Дүйшөмбү_Шейшемби_Шаршемби_Бейшемби_Жума_Ишемби'.split('_'),
+    weekdaysShort : 'Жек_Дүй_Шей_Шар_Бей_Жум_Ише'.split('_'),
+    weekdaysMin : 'Жк_Дй_Шй_Шр_Бй_Жм_Иш'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Бүгүн саат] LT',
+        nextDay : '[Эртең саат] LT',
+        nextWeek : 'dddd [саат] LT',
+        lastDay : '[Кече саат] LT',
+        lastWeek : '[Өткен аптанын] dddd [күнү] [саат] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s ичинде',
+        past : '%s мурун',
+        s : 'бирнече секунд',
+        m : 'бир мүнөт',
+        mm : '%d мүнөт',
+        h : 'бир саат',
+        hh : '%d саат',
+        d : 'бир күн',
+        dd : '%d күн',
+        M : 'бир ай',
+        MM : '%d ай',
+        y : 'бир жыл',
+        yy : '%d жыл'
+    },
+    ordinalParse: /\d{1,2}-(чи|чы|чү|чу)/,
+    ordinal : function (number) {
+        var a = number % 10,
+            b = number >= 100 ? 100 : null;
+        return number + (suffixes$2[number] || suffixes$2[a] || suffixes$2[b]);
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Luxembourgish [lb]
+//! author : mweimerskirch : https://github.com/mweimerskirch
+//! author : David Raison : https://github.com/kwisatz
+
+function processRelativeTime$3(number, withoutSuffix, key, isFuture) {
+    var format = {
+        'm': ['eng Minutt', 'enger Minutt'],
+        'h': ['eng Stonn', 'enger Stonn'],
+        'd': ['een Dag', 'engem Dag'],
+        'M': ['ee Mount', 'engem Mount'],
+        'y': ['ee Joer', 'engem Joer']
+    };
+    return withoutSuffix ? format[key][0] : format[key][1];
+}
+function processFutureTime(string) {
+    var number = string.substr(0, string.indexOf(' '));
+    if (eifelerRegelAppliesToNumber(number)) {
+        return 'a ' + string;
+    }
+    return 'an ' + string;
+}
+function processPastTime(string) {
+    var number = string.substr(0, string.indexOf(' '));
+    if (eifelerRegelAppliesToNumber(number)) {
+        return 'viru ' + string;
+    }
+    return 'virun ' + string;
+}
+/**
+ * Returns true if the word before the given number loses the '-n' ending.
+ * e.g. 'an 10 Deeg' but 'a 5 Deeg'
+ *
+ * @param number {integer}
+ * @returns {boolean}
+ */
+function eifelerRegelAppliesToNumber(number) {
+    number = parseInt(number, 10);
+    if (isNaN(number)) {
+        return false;
+    }
+    if (number < 0) {
+        // Negative Number --> always true
+        return true;
+    } else if (number < 10) {
+        // Only 1 digit
+        if (4 <= number && number <= 7) {
+            return true;
+        }
+        return false;
+    } else if (number < 100) {
+        // 2 digits
+        var lastDigit = number % 10, firstDigit = number / 10;
+        if (lastDigit === 0) {
+            return eifelerRegelAppliesToNumber(firstDigit);
+        }
+        return eifelerRegelAppliesToNumber(lastDigit);
+    } else if (number < 10000) {
+        // 3 or 4 digits --> recursively check first digit
+        while (number >= 10) {
+            number = number / 10;
+        }
+        return eifelerRegelAppliesToNumber(number);
+    } else {
+        // Anything larger than 4 digits: recursively check first n-3 digits
+        number = number / 1000;
+        return eifelerRegelAppliesToNumber(number);
+    }
+}
+
+hooks.defineLocale('lb', {
+    months: 'Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'),
+    monthsShort: 'Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'),
+    monthsParseExact : true,
+    weekdays: 'Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg'.split('_'),
+    weekdaysShort: 'So._Mé._Dë._Më._Do._Fr._Sa.'.split('_'),
+    weekdaysMin: 'So_Mé_Dë_Më_Do_Fr_Sa'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat: {
+        LT: 'H:mm [Auer]',
+        LTS: 'H:mm:ss [Auer]',
+        L: 'DD.MM.YYYY',
+        LL: 'D. MMMM YYYY',
+        LLL: 'D. MMMM YYYY H:mm [Auer]',
+        LLLL: 'dddd, D. MMMM YYYY H:mm [Auer]'
+    },
+    calendar: {
+        sameDay: '[Haut um] LT',
+        sameElse: 'L',
+        nextDay: '[Muer um] LT',
+        nextWeek: 'dddd [um] LT',
+        lastDay: '[Gëschter um] LT',
+        lastWeek: function () {
+            // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule
+            switch (this.day()) {
+                case 2:
+                case 4:
+                    return '[Leschten] dddd [um] LT';
+                default:
+                    return '[Leschte] dddd [um] LT';
+            }
+        }
+    },
+    relativeTime : {
+        future : processFutureTime,
+        past : processPastTime,
+        s : 'e puer Sekonnen',
+        m : processRelativeTime$3,
+        mm : '%d Minutten',
+        h : processRelativeTime$3,
+        hh : '%d Stonnen',
+        d : processRelativeTime$3,
+        dd : '%d Deeg',
+        M : processRelativeTime$3,
+        MM : '%d Méint',
+        y : processRelativeTime$3,
+        yy : '%d Joer'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal: '%d.',
+    week: {
+        dow: 1, // Monday is the first day of the week.
+        doy: 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Lao [lo]
+//! author : Ryan Hart : https://github.com/ryanhart2
+
+hooks.defineLocale('lo', {
+    months : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'),
+    monthsShort : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'),
+    weekdays : 'ອາທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'),
+    weekdaysShort : 'ທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'),
+    weekdaysMin : 'ທ_ຈ_ອຄ_ພ_ພຫ_ສກ_ສ'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'ວັນdddd D MMMM YYYY HH:mm'
+    },
+    meridiemParse: /ຕອນເຊົ້າ|ຕອນແລງ/,
+    isPM: function (input) {
+        return input === 'ຕອນແລງ';
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'ຕອນເຊົ້າ';
+        } else {
+            return 'ຕອນແລງ';
+        }
+    },
+    calendar : {
+        sameDay : '[ມື້ນີ້ເວລາ] LT',
+        nextDay : '[ມື້ອື່ນເວລາ] LT',
+        nextWeek : '[ວັນ]dddd[ໜ້າເວລາ] LT',
+        lastDay : '[ມື້ວານນີ້ເວລາ] LT',
+        lastWeek : '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'ອີກ %s',
+        past : '%sຜ່ານມາ',
+        s : 'ບໍ່ເທົ່າໃດວິນາທີ',
+        m : '1 ນາທີ',
+        mm : '%d ນາທີ',
+        h : '1 ຊົ່ວໂມງ',
+        hh : '%d ຊົ່ວໂມງ',
+        d : '1 ມື້',
+        dd : '%d ມື້',
+        M : '1 ເດືອນ',
+        MM : '%d ເດືອນ',
+        y : '1 ປີ',
+        yy : '%d ປີ'
+    },
+    ordinalParse: /(ທີ່)\d{1,2}/,
+    ordinal : function (number) {
+        return 'ທີ່' + number;
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Lithuanian [lt]
+//! author : Mindaugas Mozūras : https://github.com/mmozuras
+
+var units = {
+    'm' : 'minutÄ—_minutÄ—s_minutÄ™',
+    'mm': 'minutės_minučių_minutes',
+    'h' : 'valanda_valandos_valandÄ…',
+    'hh': 'valandos_valandų_valandas',
+    'd' : 'diena_dienos_dienÄ…',
+    'dd': 'dienos_dienų_dienas',
+    'M' : 'mėnuo_mėnesio_mėnesį',
+    'MM': 'mėnesiai_mėnesių_mėnesius',
+    'y' : 'metai_metų_metus',
+    'yy': 'metai_metų_metus'
+};
+function translateSeconds(number, withoutSuffix, key, isFuture) {
+    if (withoutSuffix) {
+        return 'kelios sekundÄ—s';
+    } else {
+        return isFuture ? 'kelių sekundžių' : 'kelias sekundes';
+    }
+}
+function translateSingular(number, withoutSuffix, key, isFuture) {
+    return withoutSuffix ? forms(key)[0] : (isFuture ? forms(key)[1] : forms(key)[2]);
+}
+function special(number) {
+    return number % 10 === 0 || (number > 10 && number < 20);
+}
+function forms(key) {
+    return units[key].split('_');
+}
+function translate$6(number, withoutSuffix, key, isFuture) {
+    var result = number + ' ';
+    if (number === 1) {
+        return result + translateSingular(number, withoutSuffix, key[0], isFuture);
+    } else if (withoutSuffix) {
+        return result + (special(number) ? forms(key)[1] : forms(key)[0]);
+    } else {
+        if (isFuture) {
+            return result + forms(key)[1];
+        } else {
+            return result + (special(number) ? forms(key)[1] : forms(key)[2]);
+        }
+    }
+}
+hooks.defineLocale('lt', {
+    months : {
+        format: 'sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio'.split('_'),
+        standalone: 'sausis_vasaris_kovas_balandis_gegužė_birželis_liepa_rugpjūtis_rugsėjis_spalis_lapkritis_gruodis'.split('_'),
+        isFormat: /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/
+    },
+    monthsShort : 'sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd'.split('_'),
+    weekdays : {
+        format: 'sekmadienį_pirmadienį_antradienį_trečiadienį_ketvirtadienį_penktadienį_šeštadienį'.split('_'),
+        standalone: 'sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis'.split('_'),
+        isFormat: /dddd HH:mm/
+    },
+    weekdaysShort : 'Sek_Pir_Ant_Tre_Ket_Pen_Šeš'.split('_'),
+    weekdaysMin : 'S_P_A_T_K_Pn_Å '.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'YYYY-MM-DD',
+        LL : 'YYYY [m.] MMMM D [d.]',
+        LLL : 'YYYY [m.] MMMM D [d.], HH:mm [val.]',
+        LLLL : 'YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]',
+        l : 'YYYY-MM-DD',
+        ll : 'YYYY [m.] MMMM D [d.]',
+        lll : 'YYYY [m.] MMMM D [d.], HH:mm [val.]',
+        llll : 'YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]'
+    },
+    calendar : {
+        sameDay : '[Å iandien] LT',
+        nextDay : '[Rytoj] LT',
+        nextWeek : 'dddd LT',
+        lastDay : '[Vakar] LT',
+        lastWeek : '[Praėjusį] dddd LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'po %s',
+        past : 'prieš %s',
+        s : translateSeconds,
+        m : translateSingular,
+        mm : translate$6,
+        h : translateSingular,
+        hh : translate$6,
+        d : translateSingular,
+        dd : translate$6,
+        M : translateSingular,
+        MM : translate$6,
+        y : translateSingular,
+        yy : translate$6
+    },
+    ordinalParse: /\d{1,2}-oji/,
+    ordinal : function (number) {
+        return number + '-oji';
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Latvian [lv]
+//! author : Kristaps Karlsons : https://github.com/skakri
+//! author : Jānis Elmeris : https://github.com/JanisE
+
+var units$1 = {
+    'm': 'minūtes_minūtēm_minūte_minūtes'.split('_'),
+    'mm': 'minūtes_minūtēm_minūte_minūtes'.split('_'),
+    'h': 'stundas_stundām_stunda_stundas'.split('_'),
+    'hh': 'stundas_stundām_stunda_stundas'.split('_'),
+    'd': 'dienas_dienām_diena_dienas'.split('_'),
+    'dd': 'dienas_dienām_diena_dienas'.split('_'),
+    'M': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'),
+    'MM': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'),
+    'y': 'gada_gadiem_gads_gadi'.split('_'),
+    'yy': 'gada_gadiem_gads_gadi'.split('_')
+};
+/**
+ * @param withoutSuffix boolean true = a length of time; false = before/after a period of time.
+ */
+function format$1(forms, number, withoutSuffix) {
+    if (withoutSuffix) {
+        // E.g. "21 minūte", "3 minūtes".
+        return number % 10 === 1 && number % 100 !== 11 ? forms[2] : forms[3];
+    } else {
+        // E.g. "21 minūtes" as in "pēc 21 minūtes".
+        // E.g. "3 minūtēm" as in "pēc 3 minūtēm".
+        return number % 10 === 1 && number % 100 !== 11 ? forms[0] : forms[1];
+    }
+}
+function relativeTimeWithPlural$1(number, withoutSuffix, key) {
+    return number + ' ' + format$1(units$1[key], number, withoutSuffix);
+}
+function relativeTimeWithSingular(number, withoutSuffix, key) {
+    return format$1(units$1[key], number, withoutSuffix);
+}
+function relativeSeconds(number, withoutSuffix) {
+    return withoutSuffix ? 'dažas sekundes' : 'dažām sekundēm';
+}
+
+hooks.defineLocale('lv', {
+    months : 'janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec'.split('_'),
+    weekdays : 'svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena'.split('_'),
+    weekdaysShort : 'Sv_P_O_T_C_Pk_S'.split('_'),
+    weekdaysMin : 'Sv_P_O_T_C_Pk_S'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY.',
+        LL : 'YYYY. [gada] D. MMMM',
+        LLL : 'YYYY. [gada] D. MMMM, HH:mm',
+        LLLL : 'YYYY. [gada] D. MMMM, dddd, HH:mm'
+    },
+    calendar : {
+        sameDay : '[Å odien pulksten] LT',
+        nextDay : '[RÄ«t pulksten] LT',
+        nextWeek : 'dddd [pulksten] LT',
+        lastDay : '[Vakar pulksten] LT',
+        lastWeek : '[Pagājušā] dddd [pulksten] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'pēc %s',
+        past : 'pirms %s',
+        s : relativeSeconds,
+        m : relativeTimeWithSingular,
+        mm : relativeTimeWithPlural$1,
+        h : relativeTimeWithSingular,
+        hh : relativeTimeWithPlural$1,
+        d : relativeTimeWithSingular,
+        dd : relativeTimeWithPlural$1,
+        M : relativeTimeWithSingular,
+        MM : relativeTimeWithPlural$1,
+        y : relativeTimeWithSingular,
+        yy : relativeTimeWithPlural$1
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Montenegrin [me]
+//! author : Miodrag Nikač <miodrag@restartit.me> : https://github.com/miodragnikac
+
+var translator = {
+    words: { //Different grammatical cases
+        m: ['jedan minut', 'jednog minuta'],
+        mm: ['minut', 'minuta', 'minuta'],
+        h: ['jedan sat', 'jednog sata'],
+        hh: ['sat', 'sata', 'sati'],
+        dd: ['dan', 'dana', 'dana'],
+        MM: ['mjesec', 'mjeseca', 'mjeseci'],
+        yy: ['godina', 'godine', 'godina']
+    },
+    correctGrammaticalCase: function (number, wordKey) {
+        return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
+    },
+    translate: function (number, withoutSuffix, key) {
+        var wordKey = translator.words[key];
+        if (key.length === 1) {
+            return withoutSuffix ? wordKey[0] : wordKey[1];
+        } else {
+            return number + ' ' + translator.correctGrammaticalCase(number, wordKey);
+        }
+    }
+};
+
+hooks.defineLocale('me', {
+    months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'),
+    monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'),
+    monthsParseExact : true,
+    weekdays: 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'),
+    weekdaysShort: 'ned._pon._uto._sri._čet._pet._sub.'.split('_'),
+    weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat: {
+        LT: 'H:mm',
+        LTS : 'H:mm:ss',
+        L: 'DD.MM.YYYY',
+        LL: 'D. MMMM YYYY',
+        LLL: 'D. MMMM YYYY H:mm',
+        LLLL: 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar: {
+        sameDay: '[danas u] LT',
+        nextDay: '[sjutra u] LT',
+
+        nextWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[u] [nedjelju] [u] LT';
+                case 3:
+                    return '[u] [srijedu] [u] LT';
+                case 6:
+                    return '[u] [subotu] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[u] dddd [u] LT';
+            }
+        },
+        lastDay  : '[juče u] LT',
+        lastWeek : function () {
+            var lastWeekDays = [
+                '[prošle] [nedjelje] [u] LT',
+                '[prošlog] [ponedjeljka] [u] LT',
+                '[prošlog] [utorka] [u] LT',
+                '[prošle] [srijede] [u] LT',
+                '[prošlog] [četvrtka] [u] LT',
+                '[prošlog] [petka] [u] LT',
+                '[prošle] [subote] [u] LT'
+            ];
+            return lastWeekDays[this.day()];
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past   : 'prije %s',
+        s      : 'nekoliko sekundi',
+        m      : translator.translate,
+        mm     : translator.translate,
+        h      : translator.translate,
+        hh     : translator.translate,
+        d      : 'dan',
+        dd     : translator.translate,
+        M      : 'mjesec',
+        MM     : translator.translate,
+        y      : 'godinu',
+        yy     : translator.translate
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Maori [mi]
+//! author : John Corrigan <robbiecloset@gmail.com> : https://github.com/johnideal
+
+hooks.defineLocale('mi', {
+    months: 'Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea'.split('_'),
+    monthsShort: 'Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki'.split('_'),
+    monthsRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,
+    monthsStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,
+    monthsShortRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,
+    monthsShortStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,
+    weekdays: 'Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei'.split('_'),
+    weekdaysShort: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'),
+    weekdaysMin: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'),
+    longDateFormat: {
+        LT: 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L: 'DD/MM/YYYY',
+        LL: 'D MMMM YYYY',
+        LLL: 'D MMMM YYYY [i] HH:mm',
+        LLLL: 'dddd, D MMMM YYYY [i] HH:mm'
+    },
+    calendar: {
+        sameDay: '[i teie mahana, i] LT',
+        nextDay: '[apopo i] LT',
+        nextWeek: 'dddd [i] LT',
+        lastDay: '[inanahi i] LT',
+        lastWeek: 'dddd [whakamutunga i] LT',
+        sameElse: 'L'
+    },
+    relativeTime: {
+        future: 'i roto i %s',
+        past: '%s i mua',
+        s: 'te hēkona ruarua',
+        m: 'he meneti',
+        mm: '%d meneti',
+        h: 'te haora',
+        hh: '%d haora',
+        d: 'he ra',
+        dd: '%d ra',
+        M: 'he marama',
+        MM: '%d marama',
+        y: 'he tau',
+        yy: '%d tau'
+    },
+    ordinalParse: /\d{1,2}º/,
+    ordinal: '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Macedonian [mk]
+//! author : Borislav Mickov : https://github.com/B0k0
+
+hooks.defineLocale('mk', {
+    months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'),
+    monthsShort : 'јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек'.split('_'),
+    weekdays : 'недела_понеделник_вторник_среда_четврток_петок_сабота'.split('_'),
+    weekdaysShort : 'нед_пон_вто_сре_чет_пет_саб'.split('_'),
+    weekdaysMin : 'нe_пo_вт_ср_че_пе_сa'.split('_'),
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'D.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY H:mm',
+        LLLL : 'dddd, D MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay : '[Денес во] LT',
+        nextDay : '[Утре во] LT',
+        nextWeek : '[Во] dddd [во] LT',
+        lastDay : '[Вчера во] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                case 6:
+                    return '[Изминатата] dddd [во] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[Изминатиот] dddd [во] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'после %s',
+        past : 'пред %s',
+        s : 'неколку секунди',
+        m : 'минута',
+        mm : '%d минути',
+        h : 'час',
+        hh : '%d часа',
+        d : 'ден',
+        dd : '%d дена',
+        M : 'месец',
+        MM : '%d месеци',
+        y : 'година',
+        yy : '%d години'
+    },
+    ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/,
+    ordinal : function (number) {
+        var lastDigit = number % 10,
+            last2Digits = number % 100;
+        if (number === 0) {
+            return number + '-ев';
+        } else if (last2Digits === 0) {
+            return number + '-ен';
+        } else if (last2Digits > 10 && last2Digits < 20) {
+            return number + '-ти';
+        } else if (lastDigit === 1) {
+            return number + '-ви';
+        } else if (lastDigit === 2) {
+            return number + '-ри';
+        } else if (lastDigit === 7 || lastDigit === 8) {
+            return number + '-ми';
+        } else {
+            return number + '-ти';
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Malayalam [ml]
+//! author : Floyd Pink : https://github.com/floydpink
+
+hooks.defineLocale('ml', {
+    months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'),
+    monthsShort : 'ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച'.split('_'),
+    weekdaysShort : 'ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി'.split('_'),
+    weekdaysMin : 'ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm -നു',
+        LTS : 'A h:mm:ss -നു',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm -നു',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm -നു'
+    },
+    calendar : {
+        sameDay : '[ഇന്ന്] LT',
+        nextDay : '[നാളെ] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[ഇന്നലെ] LT',
+        lastWeek : '[കഴിഞ്ഞ] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s കഴിഞ്ഞ്',
+        past : '%s മുൻപ്',
+        s : 'അൽപ നിമിഷങ്ങൾ',
+        m : 'ഒരു മിനിറ്റ്',
+        mm : '%d മിനിറ്റ്',
+        h : 'ഒരു മണിക്കൂർ',
+        hh : '%d മണിക്കൂർ',
+        d : 'ഒരു ദിവസം',
+        dd : '%d ദിവസം',
+        M : 'ഒരു മാസം',
+        MM : '%d മാസം',
+        y : 'ഒരു വർഷം',
+        yy : '%d വർഷം'
+    },
+    meridiemParse: /രാത്രി|രാവിലെ|ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി/i,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if ((meridiem === 'രാത്രി' && hour >= 4) ||
+                meridiem === 'ഉച്ച കഴിഞ്ഞ്' ||
+                meridiem === 'വൈകുന്നേരം') {
+            return hour + 12;
+        } else {
+            return hour;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'രാത്രി';
+        } else if (hour < 12) {
+            return 'രാവിലെ';
+        } else if (hour < 17) {
+            return 'ഉച്ച കഴിഞ്ഞ്';
+        } else if (hour < 20) {
+            return 'വൈകുന്നേരം';
+        } else {
+            return 'രാത്രി';
+        }
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Marathi [mr]
+//! author : Harshad Kale : https://github.com/kalehv
+//! author : Vivek Athalye : https://github.com/vnathalye
+
+var symbolMap$7 = {
+    '1': '१',
+    '2': '२',
+    '3': '३',
+    '4': '४',
+    '5': '५',
+    '6': '६',
+    '7': '७',
+    '8': '८',
+    '9': '९',
+    '0': '०'
+};
+var numberMap$6 = {
+    '१': '1',
+    '२': '2',
+    '३': '3',
+    '४': '4',
+    '५': '5',
+    '६': '6',
+    '७': '7',
+    '८': '8',
+    '९': '9',
+    '०': '0'
+};
+
+function relativeTimeMr(number, withoutSuffix, string, isFuture)
+{
+    var output = '';
+    if (withoutSuffix) {
+        switch (string) {
+            case 's': output = 'काही सेकंद'; break;
+            case 'm': output = 'एक मिनिट'; break;
+            case 'mm': output = '%d मिनिटे'; break;
+            case 'h': output = 'एक तास'; break;
+            case 'hh': output = '%d तास'; break;
+            case 'd': output = 'एक दिवस'; break;
+            case 'dd': output = '%d दिवस'; break;
+            case 'M': output = 'एक महिना'; break;
+            case 'MM': output = '%d महिने'; break;
+            case 'y': output = 'एक वर्ष'; break;
+            case 'yy': output = '%d वर्षे'; break;
+        }
+    }
+    else {
+        switch (string) {
+            case 's': output = 'काही सेकंदां'; break;
+            case 'm': output = 'एका मिनिटा'; break;
+            case 'mm': output = '%d मिनिटां'; break;
+            case 'h': output = 'एका तासा'; break;
+            case 'hh': output = '%d तासां'; break;
+            case 'd': output = 'एका दिवसा'; break;
+            case 'dd': output = '%d दिवसां'; break;
+            case 'M': output = 'एका महिन्या'; break;
+            case 'MM': output = '%d महिन्यां'; break;
+            case 'y': output = 'एका वर्षा'; break;
+            case 'yy': output = '%d वर्षां'; break;
+        }
+    }
+    return output.replace(/%d/i, number);
+}
+
+hooks.defineLocale('mr', {
+    months : 'जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर'.split('_'),
+    monthsShort: 'जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'),
+    weekdaysShort : 'रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि'.split('_'),
+    weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm वाजता',
+        LTS : 'A h:mm:ss वाजता',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm वाजता',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm वाजता'
+    },
+    calendar : {
+        sameDay : '[आज] LT',
+        nextDay : '[उद्या] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[काल] LT',
+        lastWeek: '[मागील] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future: '%sमध्ये',
+        past: '%sपूर्वी',
+        s: relativeTimeMr,
+        m: relativeTimeMr,
+        mm: relativeTimeMr,
+        h: relativeTimeMr,
+        hh: relativeTimeMr,
+        d: relativeTimeMr,
+        dd: relativeTimeMr,
+        M: relativeTimeMr,
+        MM: relativeTimeMr,
+        y: relativeTimeMr,
+        yy: relativeTimeMr
+    },
+    preparse: function (string) {
+        return string.replace(/[१२३४५६७८९०]/g, function (match) {
+            return numberMap$6[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$7[match];
+        });
+    },
+    meridiemParse: /रात्री|सकाळी|दुपारी|सायंकाळी/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'रात्री') {
+            return hour < 4 ? hour : hour + 12;
+        } else if (meridiem === 'सकाळी') {
+            return hour;
+        } else if (meridiem === 'दुपारी') {
+            return hour >= 10 ? hour : hour + 12;
+        } else if (meridiem === 'सायंकाळी') {
+            return hour + 12;
+        }
+    },
+    meridiem: function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'रात्री';
+        } else if (hour < 10) {
+            return 'सकाळी';
+        } else if (hour < 17) {
+            return 'दुपारी';
+        } else if (hour < 20) {
+            return 'सायंकाळी';
+        } else {
+            return 'रात्री';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Malay [ms-my]
+//! note : DEPRECATED, the correct one is [ms]
+//! author : Weldan Jamili : https://github.com/weldan
+
+hooks.defineLocale('ms-my', {
+    months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'),
+    monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'),
+    weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'),
+    weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'),
+    weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY [pukul] HH.mm',
+        LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
+    },
+    meridiemParse: /pagi|tengahari|petang|malam/,
+    meridiemHour: function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'pagi') {
+            return hour;
+        } else if (meridiem === 'tengahari') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === 'petang' || meridiem === 'malam') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 11) {
+            return 'pagi';
+        } else if (hours < 15) {
+            return 'tengahari';
+        } else if (hours < 19) {
+            return 'petang';
+        } else {
+            return 'malam';
+        }
+    },
+    calendar : {
+        sameDay : '[Hari ini pukul] LT',
+        nextDay : '[Esok pukul] LT',
+        nextWeek : 'dddd [pukul] LT',
+        lastDay : '[Kelmarin pukul] LT',
+        lastWeek : 'dddd [lepas pukul] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'dalam %s',
+        past : '%s yang lepas',
+        s : 'beberapa saat',
+        m : 'seminit',
+        mm : '%d minit',
+        h : 'sejam',
+        hh : '%d jam',
+        d : 'sehari',
+        dd : '%d hari',
+        M : 'sebulan',
+        MM : '%d bulan',
+        y : 'setahun',
+        yy : '%d tahun'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Malay [ms]
+//! author : Weldan Jamili : https://github.com/weldan
+
+hooks.defineLocale('ms', {
+    months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'),
+    monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'),
+    weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'),
+    weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'),
+    weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY [pukul] HH.mm',
+        LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm'
+    },
+    meridiemParse: /pagi|tengahari|petang|malam/,
+    meridiemHour: function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'pagi') {
+            return hour;
+        } else if (meridiem === 'tengahari') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === 'petang' || meridiem === 'malam') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 11) {
+            return 'pagi';
+        } else if (hours < 15) {
+            return 'tengahari';
+        } else if (hours < 19) {
+            return 'petang';
+        } else {
+            return 'malam';
+        }
+    },
+    calendar : {
+        sameDay : '[Hari ini pukul] LT',
+        nextDay : '[Esok pukul] LT',
+        nextWeek : 'dddd [pukul] LT',
+        lastDay : '[Kelmarin pukul] LT',
+        lastWeek : 'dddd [lepas pukul] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'dalam %s',
+        past : '%s yang lepas',
+        s : 'beberapa saat',
+        m : 'seminit',
+        mm : '%d minit',
+        h : 'sejam',
+        hh : '%d jam',
+        d : 'sehari',
+        dd : '%d hari',
+        M : 'sebulan',
+        MM : '%d bulan',
+        y : 'setahun',
+        yy : '%d tahun'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Burmese [my]
+//! author : Squar team, mysquar.com
+//! author : David Rossellat : https://github.com/gholadr
+//! author : Tin Aung Lin : https://github.com/thanyawzinmin
+
+var symbolMap$8 = {
+    '1': '၁',
+    '2': '၂',
+    '3': '၃',
+    '4': '၄',
+    '5': '၅',
+    '6': '၆',
+    '7': '၇',
+    '8': '၈',
+    '9': '၉',
+    '0': '၀'
+};
+var numberMap$7 = {
+    '၁': '1',
+    '၂': '2',
+    '၃': '3',
+    '၄': '4',
+    '၅': '5',
+    '၆': '6',
+    '၇': '7',
+    '၈': '8',
+    '၉': '9',
+    '၀': '0'
+};
+
+hooks.defineLocale('my', {
+    months: 'ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ'.split('_'),
+    monthsShort: 'ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ'.split('_'),
+    weekdays: 'တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ'.split('_'),
+    weekdaysShort: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'),
+    weekdaysMin: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'),
+
+    longDateFormat: {
+        LT: 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L: 'DD/MM/YYYY',
+        LL: 'D MMMM YYYY',
+        LLL: 'D MMMM YYYY HH:mm',
+        LLLL: 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar: {
+        sameDay: '[ယနေ.] LT [မှာ]',
+        nextDay: '[မနက်ဖြန်] LT [မှာ]',
+        nextWeek: 'dddd LT [မှာ]',
+        lastDay: '[မနေ.က] LT [မှာ]',
+        lastWeek: '[ပြီးခဲ့သော] dddd LT [မှာ]',
+        sameElse: 'L'
+    },
+    relativeTime: {
+        future: 'လာမည့် %s မှာ',
+        past: 'လွန်ခဲ့သော %s က',
+        s: 'စက္ကန်.အနည်းငယ်',
+        m: 'တစ်မိနစ်',
+        mm: '%d မိနစ်',
+        h: 'တစ်နာရီ',
+        hh: '%d နာရီ',
+        d: 'တစ်ရက်',
+        dd: '%d ရက်',
+        M: 'တစ်လ',
+        MM: '%d လ',
+        y: 'တစ်နှစ်',
+        yy: '%d နှစ်'
+    },
+    preparse: function (string) {
+        return string.replace(/[၁၂၃၄၅၆၇၈၉၀]/g, function (match) {
+            return numberMap$7[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$8[match];
+        });
+    },
+    week: {
+        dow: 1, // Monday is the first day of the week.
+        doy: 4 // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Norwegian Bokmål [nb]
+//! authors : Espen Hovlandsdal : https://github.com/rexxars
+//!           Sigurd Gartmann : https://github.com/sigurdga
+
+hooks.defineLocale('nb', {
+    months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
+    monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'),
+    weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'),
+    weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY [kl.] HH:mm',
+        LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm'
+    },
+    calendar : {
+        sameDay: '[i dag kl.] LT',
+        nextDay: '[i morgen kl.] LT',
+        nextWeek: 'dddd [kl.] LT',
+        lastDay: '[i går kl.] LT',
+        lastWeek: '[forrige] dddd [kl.] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'om %s',
+        past : '%s siden',
+        s : 'noen sekunder',
+        m : 'ett minutt',
+        mm : '%d minutter',
+        h : 'en time',
+        hh : '%d timer',
+        d : 'en dag',
+        dd : '%d dager',
+        M : 'en måned',
+        MM : '%d måneder',
+        y : 'ett år',
+        yy : '%d år'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Nepalese [ne]
+//! author : suvash : https://github.com/suvash
+
+var symbolMap$9 = {
+    '1': '१',
+    '2': '२',
+    '3': '३',
+    '4': '४',
+    '5': '५',
+    '6': '६',
+    '7': '७',
+    '8': '८',
+    '9': '९',
+    '0': '०'
+};
+var numberMap$8 = {
+    '१': '1',
+    '२': '2',
+    '३': '3',
+    '४': '4',
+    '५': '5',
+    '६': '6',
+    '७': '7',
+    '८': '8',
+    '९': '9',
+    '०': '0'
+};
+
+hooks.defineLocale('ne', {
+    months : 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split('_'),
+    monthsShort : 'जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split('_'),
+    weekdaysShort : 'आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.'.split('_'),
+    weekdaysMin : 'आ._सो._मं._बु._बि._शु._श.'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'Aको h:mm बजे',
+        LTS : 'Aको h:mm:ss बजे',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, Aको h:mm बजे',
+        LLLL : 'dddd, D MMMM YYYY, Aको h:mm बजे'
+    },
+    preparse: function (string) {
+        return string.replace(/[१२३४५६७८९०]/g, function (match) {
+            return numberMap$8[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$9[match];
+        });
+    },
+    meridiemParse: /राति|बिहान|दिउँसो|साँझ/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'राति') {
+            return hour < 4 ? hour : hour + 12;
+        } else if (meridiem === 'बिहान') {
+            return hour;
+        } else if (meridiem === 'दिउँसो') {
+            return hour >= 10 ? hour : hour + 12;
+        } else if (meridiem === 'साँझ') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 3) {
+            return 'राति';
+        } else if (hour < 12) {
+            return 'बिहान';
+        } else if (hour < 16) {
+            return 'दिउँसो';
+        } else if (hour < 20) {
+            return 'साँझ';
+        } else {
+            return 'राति';
+        }
+    },
+    calendar : {
+        sameDay : '[आज] LT',
+        nextDay : '[भोलि] LT',
+        nextWeek : '[आउँदो] dddd[,] LT',
+        lastDay : '[हिजो] LT',
+        lastWeek : '[गएको] dddd[,] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%sमा',
+        past : '%s अगाडि',
+        s : 'केही क्षण',
+        m : 'एक मिनेट',
+        mm : '%d मिनेट',
+        h : 'एक घण्टा',
+        hh : '%d घण्टा',
+        d : 'एक दिन',
+        dd : '%d दिन',
+        M : 'एक महिना',
+        MM : '%d महिना',
+        y : 'एक बर्ष',
+        yy : '%d बर्ष'
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Dutch (Belgium) [nl-be]
+//! author : Joris Röling : https://github.com/jorisroling
+//! author : Jacob Middag : https://github.com/middagj
+
+var monthsShortWithDots$1 = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_');
+var monthsShortWithoutDots$1 = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
+
+var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i];
+var monthsRegex$1 = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;
+
+hooks.defineLocale('nl-be', {
+    months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
+    monthsShort : function (m, format) {
+        if (/-MMM-/.test(format)) {
+            return monthsShortWithoutDots$1[m.month()];
+        } else {
+            return monthsShortWithDots$1[m.month()];
+        }
+    },
+
+    monthsRegex: monthsRegex$1,
+    monthsShortRegex: monthsRegex$1,
+    monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,
+    monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,
+
+    monthsParse : monthsParse,
+    longMonthsParse : monthsParse,
+    shortMonthsParse : monthsParse,
+
+    weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
+    weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
+    weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[vandaag om] LT',
+        nextDay: '[morgen om] LT',
+        nextWeek: 'dddd [om] LT',
+        lastDay: '[gisteren om] LT',
+        lastWeek: '[afgelopen] dddd [om] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'over %s',
+        past : '%s geleden',
+        s : 'een paar seconden',
+        m : 'één minuut',
+        mm : '%d minuten',
+        h : 'één uur',
+        hh : '%d uur',
+        d : 'één dag',
+        dd : '%d dagen',
+        M : 'één maand',
+        MM : '%d maanden',
+        y : 'één jaar',
+        yy : '%d jaar'
+    },
+    ordinalParse: /\d{1,2}(ste|de)/,
+    ordinal : function (number) {
+        return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Dutch [nl]
+//! author : Joris Röling : https://github.com/jorisroling
+//! author : Jacob Middag : https://github.com/middagj
+
+var monthsShortWithDots$2 = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_');
+var monthsShortWithoutDots$2 = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
+
+var monthsParse$1 = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i];
+var monthsRegex$2 = /^(januari|februari|maart|april|mei|april|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i;
+
+hooks.defineLocale('nl', {
+    months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'),
+    monthsShort : function (m, format) {
+        if (/-MMM-/.test(format)) {
+            return monthsShortWithoutDots$2[m.month()];
+        } else {
+            return monthsShortWithDots$2[m.month()];
+        }
+    },
+
+    monthsRegex: monthsRegex$2,
+    monthsShortRegex: monthsRegex$2,
+    monthsStrictRegex: /^(januari|februari|maart|mei|ju[nl]i|april|augustus|september|oktober|november|december)/i,
+    monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i,
+
+    monthsParse : monthsParse$1,
+    longMonthsParse : monthsParse$1,
+    shortMonthsParse : monthsParse$1,
+
+    weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'),
+    weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'),
+    weekdaysMin : 'Zo_Ma_Di_Wo_Do_Vr_Za'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD-MM-YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[vandaag om] LT',
+        nextDay: '[morgen om] LT',
+        nextWeek: 'dddd [om] LT',
+        lastDay: '[gisteren om] LT',
+        lastWeek: '[afgelopen] dddd [om] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'over %s',
+        past : '%s geleden',
+        s : 'een paar seconden',
+        m : 'één minuut',
+        mm : '%d minuten',
+        h : 'één uur',
+        hh : '%d uur',
+        d : 'één dag',
+        dd : '%d dagen',
+        M : 'één maand',
+        MM : '%d maanden',
+        y : 'één jaar',
+        yy : '%d jaar'
+    },
+    ordinalParse: /\d{1,2}(ste|de)/,
+    ordinal : function (number) {
+        return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de');
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Nynorsk [nn]
+//! author : https://github.com/mechuwind
+
+hooks.defineLocale('nn', {
+    months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'),
+    weekdays : 'sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag'.split('_'),
+    weekdaysShort : 'sun_mån_tys_ons_tor_fre_lau'.split('_'),
+    weekdaysMin : 'su_må_ty_on_to_fr_lø'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY [kl.] H:mm',
+        LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm'
+    },
+    calendar : {
+        sameDay: '[I dag klokka] LT',
+        nextDay: '[I morgon klokka] LT',
+        nextWeek: 'dddd [klokka] LT',
+        lastDay: '[I går klokka] LT',
+        lastWeek: '[Føregåande] dddd [klokka] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'om %s',
+        past : '%s sidan',
+        s : 'nokre sekund',
+        m : 'eit minutt',
+        mm : '%d minutt',
+        h : 'ein time',
+        hh : '%d timar',
+        d : 'ein dag',
+        dd : '%d dagar',
+        M : 'ein månad',
+        MM : '%d månader',
+        y : 'eit år',
+        yy : '%d år'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Punjabi (India) [pa-in]
+//! author : Harpreet Singh : https://github.com/harpreetkhalsagtbit
+
+var symbolMap$10 = {
+    '1': '੧',
+    '2': '੨',
+    '3': 'à©©',
+    '4': '੪',
+    '5': 'à©«',
+    '6': '੬',
+    '7': 'à©­',
+    '8': 'à©®',
+    '9': '੯',
+    '0': '੦'
+};
+var numberMap$9 = {
+    '੧': '1',
+    '੨': '2',
+    'à©©': '3',
+    '੪': '4',
+    'à©«': '5',
+    '੬': '6',
+    'à©­': '7',
+    'à©®': '8',
+    '੯': '9',
+    '੦': '0'
+};
+
+hooks.defineLocale('pa-in', {
+    // There are months name as per Nanakshahi Calender but they are not used as rigidly in modern Punjabi.
+    months : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'),
+    monthsShort : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'),
+    weekdays : 'ਐਤਵਾਰ_ਸੋਮਵਾਰ_ਮੰਗਲਵਾਰ_ਬੁਧਵਾਰ_ਵੀਰਵਾਰ_ਸ਼ੁੱਕਰਵਾਰ_ਸ਼ਨੀਚਰਵਾਰ'.split('_'),
+    weekdaysShort : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'),
+    weekdaysMin : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm ਵਜੇ',
+        LTS : 'A h:mm:ss ਵਜੇ',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm ਵਜੇ',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm ਵਜੇ'
+    },
+    calendar : {
+        sameDay : '[ਅਜ] LT',
+        nextDay : '[ਕਲ] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[ਕਲ] LT',
+        lastWeek : '[ਪਿਛਲੇ] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s ਵਿੱਚ',
+        past : '%s ਪਿਛਲੇ',
+        s : 'ਕੁਝ ਸਕਿੰਟ',
+        m : 'ਇਕ ਮਿੰਟ',
+        mm : '%d ਮਿੰਟ',
+        h : 'ਇੱਕ ਘੰਟਾ',
+        hh : '%d ਘੰਟੇ',
+        d : 'ਇੱਕ ਦਿਨ',
+        dd : '%d ਦਿਨ',
+        M : 'ਇੱਕ ਮਹੀਨਾ',
+        MM : '%d ਮਹੀਨੇ',
+        y : 'ਇੱਕ ਸਾਲ',
+        yy : '%d ਸਾਲ'
+    },
+    preparse: function (string) {
+        return string.replace(/[੧੨੩੪੫੬੭੮੯੦]/g, function (match) {
+            return numberMap$9[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$10[match];
+        });
+    },
+    // Punjabi notation for meridiems are quite fuzzy in practice. While there exists
+    // a rigid notion of a 'Pahar' it is not used as rigidly in modern Punjabi.
+    meridiemParse: /ਰਾਤ|ਸਵੇਰ|ਦੁਪਹਿਰ|ਸ਼ਾਮ/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'ਰਾਤ') {
+            return hour < 4 ? hour : hour + 12;
+        } else if (meridiem === 'ਸਵੇਰ') {
+            return hour;
+        } else if (meridiem === 'ਦੁਪਹਿਰ') {
+            return hour >= 10 ? hour : hour + 12;
+        } else if (meridiem === 'ਸ਼ਾਮ') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'ਰਾਤ';
+        } else if (hour < 10) {
+            return 'ਸਵੇਰ';
+        } else if (hour < 17) {
+            return 'ਦੁਪਹਿਰ';
+        } else if (hour < 20) {
+            return 'ਸ਼ਾਮ';
+        } else {
+            return 'ਰਾਤ';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Polish [pl]
+//! author : Rafal Hirsz : https://github.com/evoL
+
+var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_');
+var monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
+function plural$3(n) {
+    return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1);
+}
+function translate$7(number, withoutSuffix, key) {
+    var result = number + ' ';
+    switch (key) {
+        case 'm':
+            return withoutSuffix ? 'minuta' : 'minutÄ™';
+        case 'mm':
+            return result + (plural$3(number) ? 'minuty' : 'minut');
+        case 'h':
+            return withoutSuffix  ? 'godzina'  : 'godzinÄ™';
+        case 'hh':
+            return result + (plural$3(number) ? 'godziny' : 'godzin');
+        case 'MM':
+            return result + (plural$3(number) ? 'miesiące' : 'miesięcy');
+        case 'yy':
+            return result + (plural$3(number) ? 'lata' : 'lat');
+    }
+}
+
+hooks.defineLocale('pl', {
+    months : function (momentToFormat, format) {
+        if (format === '') {
+            // Hack: if format empty we know this is used to generate
+            // RegExp by moment. Give then back both valid forms of months
+            // in RegExp ready format.
+            return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')';
+        } else if (/D MMMM/.test(format)) {
+            return monthsSubjective[momentToFormat.month()];
+        } else {
+            return monthsNominative[momentToFormat.month()];
+        }
+    },
+    monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'),
+    weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'),
+    weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'),
+    weekdaysMin : 'Nd_Pn_Wt_Åšr_Cz_Pt_So'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[DziÅ› o] LT',
+        nextDay: '[Jutro o] LT',
+        nextWeek: '[W] dddd [o] LT',
+        lastDay: '[Wczoraj o] LT',
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[W zeszłą niedzielę o] LT';
+                case 3:
+                    return '[W zeszłą środę o] LT';
+                case 6:
+                    return '[W zeszłą sobotę o] LT';
+                default:
+                    return '[W zeszły] dddd [o] LT';
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past : '%s temu',
+        s : 'kilka sekund',
+        m : translate$7,
+        mm : translate$7,
+        h : translate$7,
+        hh : translate$7,
+        d : '1 dzień',
+        dd : '%d dni',
+        M : 'miesiÄ…c',
+        MM : translate$7,
+        y : 'rok',
+        yy : translate$7
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Portuguese (Brazil) [pt-br]
+//! author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
+
+hooks.defineLocale('pt-br', {
+    months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
+    monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
+    weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'),
+    weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
+    weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D [de] MMMM [de] YYYY',
+        LLL : 'D [de] MMMM [de] YYYY [às] HH:mm',
+        LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm'
+    },
+    calendar : {
+        sameDay: '[Hoje às] LT',
+        nextDay: '[Amanhã às] LT',
+        nextWeek: 'dddd [às] LT',
+        lastDay: '[Ontem às] LT',
+        lastWeek: function () {
+            return (this.day() === 0 || this.day() === 6) ?
+                '[Último] dddd [às] LT' : // Saturday + Sunday
+                '[Última] dddd [às] LT'; // Monday - Friday
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'em %s',
+        past : '%s atrás',
+        s : 'poucos segundos',
+        m : 'um minuto',
+        mm : '%d minutos',
+        h : 'uma hora',
+        hh : '%d horas',
+        d : 'um dia',
+        dd : '%d dias',
+        M : 'um mês',
+        MM : '%d meses',
+        y : 'um ano',
+        yy : '%d anos'
+    },
+    ordinalParse: /\d{1,2}º/,
+    ordinal : '%dº'
+});
+
+//! moment.js locale configuration
+//! locale : Portuguese [pt]
+//! author : Jefferson : https://github.com/jalex79
+
+hooks.defineLocale('pt', {
+    months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'),
+    monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'),
+    weekdays : 'Domingo_Segunda-Feira_Terça-Feira_Quarta-Feira_Quinta-Feira_Sexta-Feira_Sábado'.split('_'),
+    weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'),
+    weekdaysMin : 'Dom_2ª_3ª_4ª_5ª_6ª_Sáb'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D [de] MMMM [de] YYYY',
+        LLL : 'D [de] MMMM [de] YYYY HH:mm',
+        LLLL : 'dddd, D [de] MMMM [de] YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Hoje às] LT',
+        nextDay: '[Amanhã às] LT',
+        nextWeek: 'dddd [às] LT',
+        lastDay: '[Ontem às] LT',
+        lastWeek: function () {
+            return (this.day() === 0 || this.day() === 6) ?
+                '[Último] dddd [às] LT' : // Saturday + Sunday
+                '[Última] dddd [às] LT'; // Monday - Friday
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'em %s',
+        past : 'há %s',
+        s : 'segundos',
+        m : 'um minuto',
+        mm : '%d minutos',
+        h : 'uma hora',
+        hh : '%d horas',
+        d : 'um dia',
+        dd : '%d dias',
+        M : 'um mês',
+        MM : '%d meses',
+        y : 'um ano',
+        yy : '%d anos'
+    },
+    ordinalParse: /\d{1,2}º/,
+    ordinal : '%dº',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Romanian [ro]
+//! author : Vlad Gurdiga : https://github.com/gurdiga
+//! author : Valentin Agachi : https://github.com/avaly
+
+function relativeTimeWithPlural$2(number, withoutSuffix, key) {
+    var format = {
+            'mm': 'minute',
+            'hh': 'ore',
+            'dd': 'zile',
+            'MM': 'luni',
+            'yy': 'ani'
+        },
+        separator = ' ';
+    if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) {
+        separator = ' de ';
+    }
+    return number + separator + format[key];
+}
+
+hooks.defineLocale('ro', {
+    months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'),
+    monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'),
+    weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'),
+    weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'),
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY H:mm',
+        LLLL : 'dddd, D MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay: '[azi la] LT',
+        nextDay: '[mâine la] LT',
+        nextWeek: 'dddd [la] LT',
+        lastDay: '[ieri la] LT',
+        lastWeek: '[fosta] dddd [la] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'peste %s',
+        past : '%s în urmă',
+        s : 'câteva secunde',
+        m : 'un minut',
+        mm : relativeTimeWithPlural$2,
+        h : 'o oră',
+        hh : relativeTimeWithPlural$2,
+        d : 'o zi',
+        dd : relativeTimeWithPlural$2,
+        M : 'o lună',
+        MM : relativeTimeWithPlural$2,
+        y : 'un an',
+        yy : relativeTimeWithPlural$2
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Russian [ru]
+//! author : Viktorminator : https://github.com/Viktorminator
+//! Author : Menelion Elensúle : https://github.com/Oire
+//! author : Коренберг Марк : https://github.com/socketpair
+
+function plural$4(word, num) {
+    var forms = word.split('_');
+    return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
+}
+function relativeTimeWithPlural$3(number, withoutSuffix, key) {
+    var format = {
+        'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут',
+        'hh': 'час_часа_часов',
+        'dd': 'день_дня_дней',
+        'MM': 'месяц_месяца_месяцев',
+        'yy': 'год_года_лет'
+    };
+    if (key === 'm') {
+        return withoutSuffix ? 'минута' : 'минуту';
+    }
+    else {
+        return number + ' ' + plural$4(format[key], +number);
+    }
+}
+var monthsParse$2 = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i];
+
+// http://new.gramota.ru/spravka/rules/139-prop : § 103
+// Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637
+// CLDR data:          http://www.unicode.org/cldr/charts/28/summary/ru.html#1753
+hooks.defineLocale('ru', {
+    months : {
+        format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'),
+        standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_')
+    },
+    monthsShort : {
+        // по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ?
+        format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'),
+        standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_')
+    },
+    weekdays : {
+        standalone: 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'),
+        format: 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_'),
+        isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/
+    },
+    weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
+    weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
+    monthsParse : monthsParse$2,
+    longMonthsParse : monthsParse$2,
+    shortMonthsParse : monthsParse$2,
+
+    // полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки
+    monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
+
+    // копия предыдущего
+    monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i,
+
+    // полные названия с падежами
+    monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i,
+
+    // Выражение, которое соотвествует только сокращённым формам
+    monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY г.',
+        LLL : 'D MMMM YYYY г., HH:mm',
+        LLLL : 'dddd, D MMMM YYYY г., HH:mm'
+    },
+    calendar : {
+        sameDay: '[Сегодня в] LT',
+        nextDay: '[Завтра в] LT',
+        lastDay: '[Вчера в] LT',
+        nextWeek: function (now) {
+            if (now.week() !== this.week()) {
+                switch (this.day()) {
+                    case 0:
+                        return '[В следующее] dddd [в] LT';
+                    case 1:
+                    case 2:
+                    case 4:
+                        return '[В следующий] dddd [в] LT';
+                    case 3:
+                    case 5:
+                    case 6:
+                        return '[В следующую] dddd [в] LT';
+                }
+            } else {
+                if (this.day() === 2) {
+                    return '[Во] dddd [в] LT';
+                } else {
+                    return '[В] dddd [в] LT';
+                }
+            }
+        },
+        lastWeek: function (now) {
+            if (now.week() !== this.week()) {
+                switch (this.day()) {
+                    case 0:
+                        return '[В прошлое] dddd [в] LT';
+                    case 1:
+                    case 2:
+                    case 4:
+                        return '[В прошлый] dddd [в] LT';
+                    case 3:
+                    case 5:
+                    case 6:
+                        return '[В прошлую] dddd [в] LT';
+                }
+            } else {
+                if (this.day() === 2) {
+                    return '[Во] dddd [в] LT';
+                } else {
+                    return '[В] dddd [в] LT';
+                }
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'через %s',
+        past : '%s назад',
+        s : 'несколько секунд',
+        m : relativeTimeWithPlural$3,
+        mm : relativeTimeWithPlural$3,
+        h : 'час',
+        hh : relativeTimeWithPlural$3,
+        d : 'день',
+        dd : relativeTimeWithPlural$3,
+        M : 'месяц',
+        MM : relativeTimeWithPlural$3,
+        y : 'год',
+        yy : relativeTimeWithPlural$3
+    },
+    meridiemParse: /ночи|утра|дня|вечера/i,
+    isPM : function (input) {
+        return /^(дня|вечера)$/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'ночи';
+        } else if (hour < 12) {
+            return 'утра';
+        } else if (hour < 17) {
+            return 'дня';
+        } else {
+            return 'вечера';
+        }
+    },
+    ordinalParse: /\d{1,2}-(й|го|я)/,
+    ordinal: function (number, period) {
+        switch (period) {
+            case 'M':
+            case 'd':
+            case 'DDD':
+                return number + '-й';
+            case 'D':
+                return number + '-го';
+            case 'w':
+            case 'W':
+                return number + '-я';
+            default:
+                return number;
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Northern Sami [se]
+//! authors : BÃ¥rd Rolstad Henriksen : https://github.com/karamell
+
+
+hooks.defineLocale('se', {
+    months : 'ođđajagemánnu_guovvamánnu_njukčamánnu_cuoŋománnu_miessemánnu_geassemánnu_suoidnemánnu_borgemánnu_čakčamánnu_golggotmánnu_skábmamánnu_juovlamánnu'.split('_'),
+    monthsShort : 'ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_skáb_juov'.split('_'),
+    weekdays : 'sotnabeaivi_vuossárga_maŋŋebárga_gaskavahkku_duorastat_bearjadat_lávvardat'.split('_'),
+    weekdaysShort : 'sotn_vuos_maŋ_gask_duor_bear_láv'.split('_'),
+    weekdaysMin : 's_v_m_g_d_b_L'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'MMMM D. [b.] YYYY',
+        LLL : 'MMMM D. [b.] YYYY [ti.] HH:mm',
+        LLLL : 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm'
+    },
+    calendar : {
+        sameDay: '[otne ti] LT',
+        nextDay: '[ihttin ti] LT',
+        nextWeek: 'dddd [ti] LT',
+        lastDay: '[ikte ti] LT',
+        lastWeek: '[ovddit] dddd [ti] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : '%s geažes',
+        past : 'maŋit %s',
+        s : 'moadde sekunddat',
+        m : 'okta minuhta',
+        mm : '%d minuhtat',
+        h : 'okta diimmu',
+        hh : '%d diimmut',
+        d : 'okta beaivi',
+        dd : '%d beaivvit',
+        M : 'okta mánnu',
+        MM : '%d mánut',
+        y : 'okta jahki',
+        yy : '%d jagit'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Sinhalese [si]
+//! author : Sampath Sitinamaluwa : https://github.com/sampathsris
+
+/*jshint -W100*/
+hooks.defineLocale('si', {
+    months : 'ජනවාරි_පෙබරවාරි_මාර්තු_අප්‍රේල්_මැයි_ජූනි_ජූලි_අගෝස්තු_සැප්තැම්බර්_ඔක්තෝබර්_නොවැම්බර්_දෙසැම්බර්'.split('_'),
+    monthsShort : 'ජන_පෙබ_මාර්_අප්_මැයි_ජූනි_ජූලි_අගෝ_සැප්_ඔක්_නොවැ_දෙසැ'.split('_'),
+    weekdays : 'ඉරිදා_සඳුදා_අඟහරුවාදා_බදාදා_බ්‍රහස්පතින්දා_සිකුරාදා_සෙනසුරාදා'.split('_'),
+    weekdaysShort : 'ඉරි_සඳු_අඟ_බදා_බ්‍රහ_සිකු_සෙන'.split('_'),
+    weekdaysMin : 'ඉ_ස_අ_බ_බ්‍ර_සි_සෙ'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'a h:mm',
+        LTS : 'a h:mm:ss',
+        L : 'YYYY/MM/DD',
+        LL : 'YYYY MMMM D',
+        LLL : 'YYYY MMMM D, a h:mm',
+        LLLL : 'YYYY MMMM D [වැනි] dddd, a h:mm:ss'
+    },
+    calendar : {
+        sameDay : '[අද] LT[ට]',
+        nextDay : '[හෙට] LT[ට]',
+        nextWeek : 'dddd LT[ට]',
+        lastDay : '[ඊයේ] LT[ට]',
+        lastWeek : '[පසුගිය] dddd LT[ට]',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%sකින්',
+        past : '%sකට පෙර',
+        s : 'තත්පර කිහිපය',
+        m : 'මිනිත්තුව',
+        mm : 'මිනිත්තු %d',
+        h : 'පැය',
+        hh : 'පැය %d',
+        d : 'දිනය',
+        dd : 'දින %d',
+        M : 'මාසය',
+        MM : 'මාස %d',
+        y : 'වසර',
+        yy : 'වසර %d'
+    },
+    ordinalParse: /\d{1,2} වැනි/,
+    ordinal : function (number) {
+        return number + ' වැනි';
+    },
+    meridiemParse : /පෙර වරු|පස් වරු|පෙ.ව|ප.ව./,
+    isPM : function (input) {
+        return input === 'ප.ව.' || input === 'පස් වරු';
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'ප.ව.' : 'පස් වරු';
+        } else {
+            return isLower ? 'පෙ.ව.' : 'පෙර වරු';
+        }
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Slovak [sk]
+//! author : Martin Minka : https://github.com/k2s
+//! based on work of petrbela : https://github.com/petrbela
+
+var months$6 = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_');
+var monthsShort$4 = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_');
+function plural$5(n) {
+    return (n > 1) && (n < 5);
+}
+function translate$8(number, withoutSuffix, key, isFuture) {
+    var result = number + ' ';
+    switch (key) {
+        case 's':  // a few seconds / in a few seconds / a few seconds ago
+            return (withoutSuffix || isFuture) ? 'pár sekúnd' : 'pár sekundami';
+        case 'm':  // a minute / in a minute / a minute ago
+            return withoutSuffix ? 'minúta' : (isFuture ? 'minútu' : 'minútou');
+        case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$5(number) ? 'minúty' : 'minút');
+            } else {
+                return result + 'minútami';
+            }
+            break;
+        case 'h':  // an hour / in an hour / an hour ago
+            return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou');
+        case 'hh': // 9 hours / in 9 hours / 9 hours ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$5(number) ? 'hodiny' : 'hodín');
+            } else {
+                return result + 'hodinami';
+            }
+            break;
+        case 'd':  // a day / in a day / a day ago
+            return (withoutSuffix || isFuture) ? 'deň' : 'dňom';
+        case 'dd': // 9 days / in 9 days / 9 days ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$5(number) ? 'dni' : 'dní');
+            } else {
+                return result + 'dňami';
+            }
+            break;
+        case 'M':  // a month / in a month / a month ago
+            return (withoutSuffix || isFuture) ? 'mesiac' : 'mesiacom';
+        case 'MM': // 9 months / in 9 months / 9 months ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$5(number) ? 'mesiace' : 'mesiacov');
+            } else {
+                return result + 'mesiacmi';
+            }
+            break;
+        case 'y':  // a year / in a year / a year ago
+            return (withoutSuffix || isFuture) ? 'rok' : 'rokom';
+        case 'yy': // 9 years / in 9 years / 9 years ago
+            if (withoutSuffix || isFuture) {
+                return result + (plural$5(number) ? 'roky' : 'rokov');
+            } else {
+                return result + 'rokmi';
+            }
+            break;
+    }
+}
+
+hooks.defineLocale('sk', {
+    months : months$6,
+    monthsShort : monthsShort$4,
+    weekdays : 'nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota'.split('_'),
+    weekdaysShort : 'ne_po_ut_st_Å¡t_pi_so'.split('_'),
+    weekdaysMin : 'ne_po_ut_st_Å¡t_pi_so'.split('_'),
+    longDateFormat : {
+        LT: 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd D. MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay: '[dnes o] LT',
+        nextDay: '[zajtra o] LT',
+        nextWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[v nedeľu o] LT';
+                case 1:
+                case 2:
+                    return '[v] dddd [o] LT';
+                case 3:
+                    return '[v stredu o] LT';
+                case 4:
+                    return '[vo Å¡tvrtok o] LT';
+                case 5:
+                    return '[v piatok o] LT';
+                case 6:
+                    return '[v sobotu o] LT';
+            }
+        },
+        lastDay: '[včera o] LT',
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[minulú nedeľu o] LT';
+                case 1:
+                case 2:
+                    return '[minulý] dddd [o] LT';
+                case 3:
+                    return '[minulú stredu o] LT';
+                case 4:
+                case 5:
+                    return '[minulý] dddd [o] LT';
+                case 6:
+                    return '[minulú sobotu o] LT';
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past : 'pred %s',
+        s : translate$8,
+        m : translate$8,
+        mm : translate$8,
+        h : translate$8,
+        hh : translate$8,
+        d : translate$8,
+        dd : translate$8,
+        M : translate$8,
+        MM : translate$8,
+        y : translate$8,
+        yy : translate$8
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Slovenian [sl]
+//! author : Robert Sedovšek : https://github.com/sedovsek
+
+function processRelativeTime$4(number, withoutSuffix, key, isFuture) {
+    var result = number + ' ';
+    switch (key) {
+        case 's':
+            return withoutSuffix || isFuture ? 'nekaj sekund' : 'nekaj sekundami';
+        case 'm':
+            return withoutSuffix ? 'ena minuta' : 'eno minuto';
+        case 'mm':
+            if (number === 1) {
+                result += withoutSuffix ? 'minuta' : 'minuto';
+            } else if (number === 2) {
+                result += withoutSuffix || isFuture ? 'minuti' : 'minutama';
+            } else if (number < 5) {
+                result += withoutSuffix || isFuture ? 'minute' : 'minutami';
+            } else {
+                result += withoutSuffix || isFuture ? 'minut' : 'minutami';
+            }
+            return result;
+        case 'h':
+            return withoutSuffix ? 'ena ura' : 'eno uro';
+        case 'hh':
+            if (number === 1) {
+                result += withoutSuffix ? 'ura' : 'uro';
+            } else if (number === 2) {
+                result += withoutSuffix || isFuture ? 'uri' : 'urama';
+            } else if (number < 5) {
+                result += withoutSuffix || isFuture ? 'ure' : 'urami';
+            } else {
+                result += withoutSuffix || isFuture ? 'ur' : 'urami';
+            }
+            return result;
+        case 'd':
+            return withoutSuffix || isFuture ? 'en dan' : 'enim dnem';
+        case 'dd':
+            if (number === 1) {
+                result += withoutSuffix || isFuture ? 'dan' : 'dnem';
+            } else if (number === 2) {
+                result += withoutSuffix || isFuture ? 'dni' : 'dnevoma';
+            } else {
+                result += withoutSuffix || isFuture ? 'dni' : 'dnevi';
+            }
+            return result;
+        case 'M':
+            return withoutSuffix || isFuture ? 'en mesec' : 'enim mesecem';
+        case 'MM':
+            if (number === 1) {
+                result += withoutSuffix || isFuture ? 'mesec' : 'mesecem';
+            } else if (number === 2) {
+                result += withoutSuffix || isFuture ? 'meseca' : 'mesecema';
+            } else if (number < 5) {
+                result += withoutSuffix || isFuture ? 'mesece' : 'meseci';
+            } else {
+                result += withoutSuffix || isFuture ? 'mesecev' : 'meseci';
+            }
+            return result;
+        case 'y':
+            return withoutSuffix || isFuture ? 'eno leto' : 'enim letom';
+        case 'yy':
+            if (number === 1) {
+                result += withoutSuffix || isFuture ? 'leto' : 'letom';
+            } else if (number === 2) {
+                result += withoutSuffix || isFuture ? 'leti' : 'letoma';
+            } else if (number < 5) {
+                result += withoutSuffix || isFuture ? 'leta' : 'leti';
+            } else {
+                result += withoutSuffix || isFuture ? 'let' : 'leti';
+            }
+            return result;
+    }
+}
+
+hooks.defineLocale('sl', {
+    months : 'januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december'.split('_'),
+    monthsShort : 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota'.split('_'),
+    weekdaysShort : 'ned._pon._tor._sre._čet._pet._sob.'.split('_'),
+    weekdaysMin : 'ne_po_to_sr_če_pe_so'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM YYYY',
+        LLL : 'D. MMMM YYYY H:mm',
+        LLLL : 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar : {
+        sameDay  : '[danes ob] LT',
+        nextDay  : '[jutri ob] LT',
+
+        nextWeek : function () {
+            switch (this.day()) {
+                case 0:
+                    return '[v] [nedeljo] [ob] LT';
+                case 3:
+                    return '[v] [sredo] [ob] LT';
+                case 6:
+                    return '[v] [soboto] [ob] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[v] dddd [ob] LT';
+            }
+        },
+        lastDay  : '[včeraj ob] LT',
+        lastWeek : function () {
+            switch (this.day()) {
+                case 0:
+                    return '[prejšnjo] [nedeljo] [ob] LT';
+                case 3:
+                    return '[prejšnjo] [sredo] [ob] LT';
+                case 6:
+                    return '[prejšnjo] [soboto] [ob] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[prejšnji] dddd [ob] LT';
+            }
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'čez %s',
+        past   : 'pred %s',
+        s      : processRelativeTime$4,
+        m      : processRelativeTime$4,
+        mm     : processRelativeTime$4,
+        h      : processRelativeTime$4,
+        hh     : processRelativeTime$4,
+        d      : processRelativeTime$4,
+        dd     : processRelativeTime$4,
+        M      : processRelativeTime$4,
+        MM     : processRelativeTime$4,
+        y      : processRelativeTime$4,
+        yy     : processRelativeTime$4
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Albanian [sq]
+//! author : Flakërim Ismani : https://github.com/flakerimi
+//! author : Menelion Elensúle : https://github.com/Oire
+//! author : Oerd Cukalla : https://github.com/oerd
+
+hooks.defineLocale('sq', {
+    months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'),
+    monthsShort : 'Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj'.split('_'),
+    weekdays : 'E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë'.split('_'),
+    weekdaysShort : 'Die_Hën_Mar_Mër_Enj_Pre_Sht'.split('_'),
+    weekdaysMin : 'D_H_Ma_Më_E_P_Sh'.split('_'),
+    weekdaysParseExact : true,
+    meridiemParse: /PD|MD/,
+    isPM: function (input) {
+        return input.charAt(0) === 'M';
+    },
+    meridiem : function (hours, minutes, isLower) {
+        return hours < 12 ? 'PD' : 'MD';
+    },
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[Sot në] LT',
+        nextDay : '[Nesër në] LT',
+        nextWeek : 'dddd [në] LT',
+        lastDay : '[Dje në] LT',
+        lastWeek : 'dddd [e kaluar në] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'në %s',
+        past : '%s më parë',
+        s : 'disa sekonda',
+        m : 'një minutë',
+        mm : '%d minuta',
+        h : 'një orë',
+        hh : '%d orë',
+        d : 'një ditë',
+        dd : '%d ditë',
+        M : 'një muaj',
+        MM : '%d muaj',
+        y : 'një vit',
+        yy : '%d vite'
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Serbian Cyrillic [sr-cyrl]
+//! author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
+
+var translator$1 = {
+    words: { //Different grammatical cases
+        m: ['један минут', 'једне минуте'],
+        mm: ['минут', 'минуте', 'минута'],
+        h: ['један сат', 'једног сата'],
+        hh: ['сат', 'сата', 'сати'],
+        dd: ['дан', 'дана', 'дана'],
+        MM: ['месец', 'месеца', 'месеци'],
+        yy: ['година', 'године', 'година']
+    },
+    correctGrammaticalCase: function (number, wordKey) {
+        return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
+    },
+    translate: function (number, withoutSuffix, key) {
+        var wordKey = translator$1.words[key];
+        if (key.length === 1) {
+            return withoutSuffix ? wordKey[0] : wordKey[1];
+        } else {
+            return number + ' ' + translator$1.correctGrammaticalCase(number, wordKey);
+        }
+    }
+};
+
+hooks.defineLocale('sr-cyrl', {
+    months: 'јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар'.split('_'),
+    monthsShort: 'јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.'.split('_'),
+    monthsParseExact: true,
+    weekdays: 'недеља_понедељак_уторак_среда_четвртак_петак_субота'.split('_'),
+    weekdaysShort: 'нед._пон._уто._сре._чет._пет._суб.'.split('_'),
+    weekdaysMin: 'не_по_ут_ср_че_пе_су'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat: {
+        LT: 'H:mm',
+        LTS : 'H:mm:ss',
+        L: 'DD.MM.YYYY',
+        LL: 'D. MMMM YYYY',
+        LLL: 'D. MMMM YYYY H:mm',
+        LLLL: 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar: {
+        sameDay: '[данас у] LT',
+        nextDay: '[сутра у] LT',
+        nextWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[у] [недељу] [у] LT';
+                case 3:
+                    return '[у] [среду] [у] LT';
+                case 6:
+                    return '[у] [суботу] [у] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[у] dddd [у] LT';
+            }
+        },
+        lastDay  : '[јуче у] LT',
+        lastWeek : function () {
+            var lastWeekDays = [
+                '[прошле] [недеље] [у] LT',
+                '[прошлог] [понедељка] [у] LT',
+                '[прошлог] [уторка] [у] LT',
+                '[прошле] [среде] [у] LT',
+                '[прошлог] [четвртка] [у] LT',
+                '[прошлог] [петка] [у] LT',
+                '[прошле] [суботе] [у] LT'
+            ];
+            return lastWeekDays[this.day()];
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'за %s',
+        past   : 'пре %s',
+        s      : 'неколико секунди',
+        m      : translator$1.translate,
+        mm     : translator$1.translate,
+        h      : translator$1.translate,
+        hh     : translator$1.translate,
+        d      : 'дан',
+        dd     : translator$1.translate,
+        M      : 'месец',
+        MM     : translator$1.translate,
+        y      : 'годину',
+        yy     : translator$1.translate
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Serbian [sr]
+//! author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
+
+var translator$2 = {
+    words: { //Different grammatical cases
+        m: ['jedan minut', 'jedne minute'],
+        mm: ['minut', 'minute', 'minuta'],
+        h: ['jedan sat', 'jednog sata'],
+        hh: ['sat', 'sata', 'sati'],
+        dd: ['dan', 'dana', 'dana'],
+        MM: ['mesec', 'meseca', 'meseci'],
+        yy: ['godina', 'godine', 'godina']
+    },
+    correctGrammaticalCase: function (number, wordKey) {
+        return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]);
+    },
+    translate: function (number, withoutSuffix, key) {
+        var wordKey = translator$2.words[key];
+        if (key.length === 1) {
+            return withoutSuffix ? wordKey[0] : wordKey[1];
+        } else {
+            return number + ' ' + translator$2.correctGrammaticalCase(number, wordKey);
+        }
+    }
+};
+
+hooks.defineLocale('sr', {
+    months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'),
+    monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'),
+    monthsParseExact: true,
+    weekdays: 'nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota'.split('_'),
+    weekdaysShort: 'ned._pon._uto._sre._čet._pet._sub.'.split('_'),
+    weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat: {
+        LT: 'H:mm',
+        LTS : 'H:mm:ss',
+        L: 'DD.MM.YYYY',
+        LL: 'D. MMMM YYYY',
+        LLL: 'D. MMMM YYYY H:mm',
+        LLLL: 'dddd, D. MMMM YYYY H:mm'
+    },
+    calendar: {
+        sameDay: '[danas u] LT',
+        nextDay: '[sutra u] LT',
+        nextWeek: function () {
+            switch (this.day()) {
+                case 0:
+                    return '[u] [nedelju] [u] LT';
+                case 3:
+                    return '[u] [sredu] [u] LT';
+                case 6:
+                    return '[u] [subotu] [u] LT';
+                case 1:
+                case 2:
+                case 4:
+                case 5:
+                    return '[u] dddd [u] LT';
+            }
+        },
+        lastDay  : '[juče u] LT',
+        lastWeek : function () {
+            var lastWeekDays = [
+                '[prošle] [nedelje] [u] LT',
+                '[prošlog] [ponedeljka] [u] LT',
+                '[prošlog] [utorka] [u] LT',
+                '[prošle] [srede] [u] LT',
+                '[prošlog] [četvrtka] [u] LT',
+                '[prošlog] [petka] [u] LT',
+                '[prošle] [subote] [u] LT'
+            ];
+            return lastWeekDays[this.day()];
+        },
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'za %s',
+        past   : 'pre %s',
+        s      : 'nekoliko sekundi',
+        m      : translator$2.translate,
+        mm     : translator$2.translate,
+        h      : translator$2.translate,
+        hh     : translator$2.translate,
+        d      : 'dan',
+        dd     : translator$2.translate,
+        M      : 'mesec',
+        MM     : translator$2.translate,
+        y      : 'godinu',
+        yy     : translator$2.translate
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : siSwati [ss]
+//! author : Nicolai Davies<mail@nicolai.io> : https://github.com/nicolaidavies
+
+
+hooks.defineLocale('ss', {
+    months : "Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split('_'),
+    monthsShort : 'Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo'.split('_'),
+    weekdays : 'Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo'.split('_'),
+    weekdaysShort : 'Lis_Umb_Lsb_Les_Lsi_Lsh_Umg'.split('_'),
+    weekdaysMin : 'Li_Us_Lb_Lt_Ls_Lh_Ug'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[Namuhla nga] LT',
+        nextDay : '[Kusasa nga] LT',
+        nextWeek : 'dddd [nga] LT',
+        lastDay : '[Itolo nga] LT',
+        lastWeek : 'dddd [leliphelile] [nga] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'nga %s',
+        past : 'wenteka nga %s',
+        s : 'emizuzwana lomcane',
+        m : 'umzuzu',
+        mm : '%d emizuzu',
+        h : 'lihora',
+        hh : '%d emahora',
+        d : 'lilanga',
+        dd : '%d emalanga',
+        M : 'inyanga',
+        MM : '%d tinyanga',
+        y : 'umnyaka',
+        yy : '%d iminyaka'
+    },
+    meridiemParse: /ekuseni|emini|entsambama|ebusuku/,
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 11) {
+            return 'ekuseni';
+        } else if (hours < 15) {
+            return 'emini';
+        } else if (hours < 19) {
+            return 'entsambama';
+        } else {
+            return 'ebusuku';
+        }
+    },
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'ekuseni') {
+            return hour;
+        } else if (meridiem === 'emini') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === 'entsambama' || meridiem === 'ebusuku') {
+            if (hour === 0) {
+                return 0;
+            }
+            return hour + 12;
+        }
+    },
+    ordinalParse: /\d{1,2}/,
+    ordinal : '%d',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Swedish [sv]
+//! author : Jens Alm : https://github.com/ulmus
+
+hooks.defineLocale('sv', {
+    months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
+    monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
+    weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'),
+    weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'),
+    weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'YYYY-MM-DD',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY [kl.] HH:mm',
+        LLLL : 'dddd D MMMM YYYY [kl.] HH:mm',
+        lll : 'D MMM YYYY HH:mm',
+        llll : 'ddd D MMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Idag] LT',
+        nextDay: '[Imorgon] LT',
+        lastDay: '[Igår] LT',
+        nextWeek: '[PÃ¥] dddd LT',
+        lastWeek: '[I] dddd[s] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'om %s',
+        past : 'för %s sedan',
+        s : 'några sekunder',
+        m : 'en minut',
+        mm : '%d minuter',
+        h : 'en timme',
+        hh : '%d timmar',
+        d : 'en dag',
+        dd : '%d dagar',
+        M : 'en månad',
+        MM : '%d månader',
+        y : 'ett år',
+        yy : '%d år'
+    },
+    ordinalParse: /\d{1,2}(e|a)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'e' :
+            (b === 1) ? 'a' :
+            (b === 2) ? 'a' :
+            (b === 3) ? 'e' : 'e';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Swahili [sw]
+//! author : Fahad Kassim : https://github.com/fadsel
+
+hooks.defineLocale('sw', {
+    months : 'Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba'.split('_'),
+    monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des'.split('_'),
+    weekdays : 'Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi'.split('_'),
+    weekdaysShort : 'Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos'.split('_'),
+    weekdaysMin : 'J2_J3_J4_J5_Al_Ij_J1'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[leo saa] LT',
+        nextDay : '[kesho saa] LT',
+        nextWeek : '[wiki ijayo] dddd [saat] LT',
+        lastDay : '[jana] LT',
+        lastWeek : '[wiki iliyopita] dddd [saat] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s baadaye',
+        past : 'tokea %s',
+        s : 'hivi punde',
+        m : 'dakika moja',
+        mm : 'dakika %d',
+        h : 'saa limoja',
+        hh : 'masaa %d',
+        d : 'siku moja',
+        dd : 'masiku %d',
+        M : 'mwezi mmoja',
+        MM : 'miezi %d',
+        y : 'mwaka mmoja',
+        yy : 'miaka %d'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Tamil [ta]
+//! author : Arjunkumar Krishnamoorthy : https://github.com/tk120404
+
+var symbolMap$11 = {
+    '1': '௧',
+    '2': '௨',
+    '3': '௩',
+    '4': '௪',
+    '5': '௫',
+    '6': '௬',
+    '7': '௭',
+    '8': '௮',
+    '9': '௯',
+    '0': '௦'
+};
+var numberMap$10 = {
+    '௧': '1',
+    '௨': '2',
+    '௩': '3',
+    '௪': '4',
+    '௫': '5',
+    '௬': '6',
+    '௭': '7',
+    '௮': '8',
+    '௯': '9',
+    '௦': '0'
+};
+
+hooks.defineLocale('ta', {
+    months : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'),
+    monthsShort : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'),
+    weekdays : 'ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை'.split('_'),
+    weekdaysShort : 'ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி'.split('_'),
+    weekdaysMin : 'ஞா_தி_செ_பு_வி_வெ_ச'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, HH:mm',
+        LLLL : 'dddd, D MMMM YYYY, HH:mm'
+    },
+    calendar : {
+        sameDay : '[இன்று] LT',
+        nextDay : '[நாளை] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[நேற்று] LT',
+        lastWeek : '[கடந்த வாரம்] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s இல்',
+        past : '%s முன்',
+        s : 'ஒரு சில விநாடிகள்',
+        m : 'ஒரு நிமிடம்',
+        mm : '%d நிமிடங்கள்',
+        h : 'ஒரு மணி நேரம்',
+        hh : '%d மணி நேரம்',
+        d : 'ஒரு நாள்',
+        dd : '%d நாட்கள்',
+        M : 'ஒரு மாதம்',
+        MM : '%d மாதங்கள்',
+        y : 'ஒரு வருடம்',
+        yy : '%d ஆண்டுகள்'
+    },
+    ordinalParse: /\d{1,2}வது/,
+    ordinal : function (number) {
+        return number + 'வது';
+    },
+    preparse: function (string) {
+        return string.replace(/[௧௨௩௪௫௬௭௮௯௦]/g, function (match) {
+            return numberMap$10[match];
+        });
+    },
+    postformat: function (string) {
+        return string.replace(/\d/g, function (match) {
+            return symbolMap$11[match];
+        });
+    },
+    // refer http://ta.wikipedia.org/s/1er1
+    meridiemParse: /யாமம்|வைகறை|காலை|நண்பகல்|எற்பாடு|மாலை/,
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 2) {
+            return ' யாமம்';
+        } else if (hour < 6) {
+            return ' வைகறை';  // வைகறை
+        } else if (hour < 10) {
+            return ' காலை'; // காலை
+        } else if (hour < 14) {
+            return ' நண்பகல்'; // நண்பகல்
+        } else if (hour < 18) {
+            return ' எற்பாடு'; // எற்பாடு
+        } else if (hour < 22) {
+            return ' மாலை'; // மாலை
+        } else {
+            return ' யாமம்';
+        }
+    },
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'யாமம்') {
+            return hour < 2 ? hour : hour + 12;
+        } else if (meridiem === 'வைகறை' || meridiem === 'காலை') {
+            return hour;
+        } else if (meridiem === 'நண்பகல்') {
+            return hour >= 10 ? hour : hour + 12;
+        } else {
+            return hour + 12;
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Telugu [te]
+//! author : Krishna Chaitanya Thota : https://github.com/kcthota
+
+hooks.defineLocale('te', {
+    months : 'జనవరి_ఫిబ్రవరి_మార్చి_ఏప్రిల్_మే_జూన్_జూలై_ఆగస్టు_సెప్టెంబర్_అక్టోబర్_నవంబర్_డిసెంబర్'.split('_'),
+    monthsShort : 'జన._ఫిబ్ర._మార్చి_ఏప్రి._మే_జూన్_జూలై_ఆగ._సెప్._అక్టో._నవ._డిసె.'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'ఆదివారం_సోమవారం_మంగళవారం_బుధవారం_గురువారం_శుక్రవారం_శనివారం'.split('_'),
+    weekdaysShort : 'ఆది_సోమ_మంగళ_బుధ_గురు_శుక్ర_శని'.split('_'),
+    weekdaysMin : 'ఆ_సో_మం_బు_గు_శు_శ'.split('_'),
+    longDateFormat : {
+        LT : 'A h:mm',
+        LTS : 'A h:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY, A h:mm',
+        LLLL : 'dddd, D MMMM YYYY, A h:mm'
+    },
+    calendar : {
+        sameDay : '[నేడు] LT',
+        nextDay : '[రేపు] LT',
+        nextWeek : 'dddd, LT',
+        lastDay : '[నిన్న] LT',
+        lastWeek : '[à°—à°¤] dddd, LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s లో',
+        past : '%s క్రితం',
+        s : 'కొన్ని క్షణాలు',
+        m : 'ఒక నిమిషం',
+        mm : '%d నిమిషాలు',
+        h : 'à°’à°• à°—à°‚à°Ÿ',
+        hh : '%d గంటలు',
+        d : 'ఒక రోజు',
+        dd : '%d రోజులు',
+        M : 'ఒక నెల',
+        MM : '%d నెలలు',
+        y : 'ఒక సంవత్సరం',
+        yy : '%d సంవత్సరాలు'
+    },
+    ordinalParse : /\d{1,2}à°µ/,
+    ordinal : '%dà°µ',
+    meridiemParse: /రాత్రి|ఉదయం|మధ్యాహ్నం|సాయంత్రం/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === 'రాత్రి') {
+            return hour < 4 ? hour : hour + 12;
+        } else if (meridiem === 'ఉదయం') {
+            return hour;
+        } else if (meridiem === 'మధ్యాహ్నం') {
+            return hour >= 10 ? hour : hour + 12;
+        } else if (meridiem === 'సాయంత్రం') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'రాత్రి';
+        } else if (hour < 10) {
+            return 'ఉదయం';
+        } else if (hour < 17) {
+            return 'మధ్యాహ్నం';
+        } else if (hour < 20) {
+            return 'సాయంత్రం';
+        } else {
+            return 'రాత్రి';
+        }
+    },
+    week : {
+        dow : 0, // Sunday is the first day of the week.
+        doy : 6  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Tetun Dili (East Timor) [tet]
+//! author : Joshua Brooks : https://github.com/joshbrooks
+//! author : Onorio De J. Afonso : https://github.com/marobo
+
+hooks.defineLocale('tet', {
+    months : 'Janeiru_Fevereiru_Marsu_Abril_Maiu_Juniu_Juliu_Augustu_Setembru_Outubru_Novembru_Dezembru'.split('_'),
+    monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Aug_Set_Out_Nov_Dez'.split('_'),
+    weekdays : 'Domingu_Segunda_Tersa_Kuarta_Kinta_Sexta_Sabadu'.split('_'),
+    weekdaysShort : 'Dom_Seg_Ters_Kua_Kint_Sext_Sab'.split('_'),
+    weekdaysMin : 'Do_Seg_Te_Ku_Ki_Sex_Sa'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Ohin iha] LT',
+        nextDay: '[Aban iha] LT',
+        nextWeek: 'dddd [iha] LT',
+        lastDay: '[Horiseik iha] LT',
+        lastWeek: 'dddd [semana kotuk] [iha] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'iha %s',
+        past : '%s liuba',
+        s : 'minutu balun',
+        m : 'minutu ida',
+        mm : 'minutus %d',
+        h : 'horas ida',
+        hh : 'horas %d',
+        d : 'loron ida',
+        dd : 'loron %d',
+        M : 'fulan ida',
+        MM : 'fulan %d',
+        y : 'tinan ida',
+        yy : 'tinan %d'
+    },
+    ordinalParse: /\d{1,2}(st|nd|rd|th)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Thai [th]
+//! author : Kridsada Thanabulpong : https://github.com/sirn
+
+hooks.defineLocale('th', {
+    months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'),
+    monthsShort : 'ม.ค._ก.พ._มี.ค._เม.ย._พ.ค._มิ.ย._ก.ค._ส.ค._ก.ย._ต.ค._พ.ย._ธ.ค.'.split('_'),
+    monthsParseExact: true,
+    weekdays : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์'.split('_'),
+    weekdaysShort : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์'.split('_'), // yes, three characters difference
+    weekdaysMin : 'อา._จ._อ._พ._พฤ._ศ._ส.'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'H:mm',
+        LTS : 'H:mm:ss',
+        L : 'YYYY/MM/DD',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY เวลา H:mm',
+        LLLL : 'วันddddที่ D MMMM YYYY เวลา H:mm'
+    },
+    meridiemParse: /ก่อนเที่ยง|หลังเที่ยง/,
+    isPM: function (input) {
+        return input === 'หลังเที่ยง';
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 12) {
+            return 'ก่อนเที่ยง';
+        } else {
+            return 'หลังเที่ยง';
+        }
+    },
+    calendar : {
+        sameDay : '[วันนี้ เวลา] LT',
+        nextDay : '[พรุ่งนี้ เวลา] LT',
+        nextWeek : 'dddd[หน้า เวลา] LT',
+        lastDay : '[เมื่อวานนี้ เวลา] LT',
+        lastWeek : '[วัน]dddd[ที่แล้ว เวลา] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'อีก %s',
+        past : '%sที่แล้ว',
+        s : 'ไม่กี่วินาที',
+        m : '1 นาที',
+        mm : '%d นาที',
+        h : '1 ชั่วโมง',
+        hh : '%d ชั่วโมง',
+        d : '1 วัน',
+        dd : '%d วัน',
+        M : '1 เดือน',
+        MM : '%d เดือน',
+        y : '1 ปี',
+        yy : '%d ปี'
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Tagalog (Philippines) [tl-ph]
+//! author : Dan Hagman : https://github.com/hagmandan
+
+hooks.defineLocale('tl-ph', {
+    months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'),
+    monthsShort : 'Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis'.split('_'),
+    weekdays : 'Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado'.split('_'),
+    weekdaysShort : 'Lin_Lun_Mar_Miy_Huw_Biy_Sab'.split('_'),
+    weekdaysMin : 'Li_Lu_Ma_Mi_Hu_Bi_Sab'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'MM/D/YYYY',
+        LL : 'MMMM D, YYYY',
+        LLL : 'MMMM D, YYYY HH:mm',
+        LLLL : 'dddd, MMMM DD, YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: 'LT [ngayong araw]',
+        nextDay: '[Bukas ng] LT',
+        nextWeek: 'LT [sa susunod na] dddd',
+        lastDay: 'LT [kahapon]',
+        lastWeek: 'LT [noong nakaraang] dddd',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'sa loob ng %s',
+        past : '%s ang nakalipas',
+        s : 'ilang segundo',
+        m : 'isang minuto',
+        mm : '%d minuto',
+        h : 'isang oras',
+        hh : '%d oras',
+        d : 'isang araw',
+        dd : '%d araw',
+        M : 'isang buwan',
+        MM : '%d buwan',
+        y : 'isang taon',
+        yy : '%d taon'
+    },
+    ordinalParse: /\d{1,2}/,
+    ordinal : function (number) {
+        return number;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Klingon [tlh]
+//! author : Dominika Kruk : https://github.com/amaranthrose
+
+var numbersNouns = 'pagh_wa’_cha’_wej_loS_vagh_jav_Soch_chorgh_Hut'.split('_');
+
+function translateFuture(output) {
+    var time = output;
+    time = (output.indexOf('jaj') !== -1) ?
+    time.slice(0, -3) + 'leS' :
+    (output.indexOf('jar') !== -1) ?
+    time.slice(0, -3) + 'waQ' :
+    (output.indexOf('DIS') !== -1) ?
+    time.slice(0, -3) + 'nem' :
+    time + ' pIq';
+    return time;
+}
+
+function translatePast(output) {
+    var time = output;
+    time = (output.indexOf('jaj') !== -1) ?
+    time.slice(0, -3) + 'Hu’' :
+    (output.indexOf('jar') !== -1) ?
+    time.slice(0, -3) + 'wen' :
+    (output.indexOf('DIS') !== -1) ?
+    time.slice(0, -3) + 'ben' :
+    time + ' ret';
+    return time;
+}
+
+function translate$9(number, withoutSuffix, string, isFuture) {
+    var numberNoun = numberAsNoun(number);
+    switch (string) {
+        case 'mm':
+            return numberNoun + ' tup';
+        case 'hh':
+            return numberNoun + ' rep';
+        case 'dd':
+            return numberNoun + ' jaj';
+        case 'MM':
+            return numberNoun + ' jar';
+        case 'yy':
+            return numberNoun + ' DIS';
+    }
+}
+
+function numberAsNoun(number) {
+    var hundred = Math.floor((number % 1000) / 100),
+    ten = Math.floor((number % 100) / 10),
+    one = number % 10,
+    word = '';
+    if (hundred > 0) {
+        word += numbersNouns[hundred] + 'vatlh';
+    }
+    if (ten > 0) {
+        word += ((word !== '') ? ' ' : '') + numbersNouns[ten] + 'maH';
+    }
+    if (one > 0) {
+        word += ((word !== '') ? ' ' : '') + numbersNouns[one];
+    }
+    return (word === '') ? 'pagh' : word;
+}
+
+hooks.defineLocale('tlh', {
+    months : 'tera’ jar wa’_tera’ jar cha’_tera’ jar wej_tera’ jar loS_tera’ jar vagh_tera’ jar jav_tera’ jar Soch_tera’ jar chorgh_tera’ jar Hut_tera’ jar wa’maH_tera’ jar wa’maH wa’_tera’ jar wa’maH cha’'.split('_'),
+    monthsShort : 'jar wa’_jar cha’_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa’maH_jar wa’maH wa’_jar wa’maH cha’'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'),
+    weekdaysShort : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'),
+    weekdaysMin : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[DaHjaj] LT',
+        nextDay: '[wa’leS] LT',
+        nextWeek: 'LLL',
+        lastDay: '[wa’Hu’] LT',
+        lastWeek: 'LLL',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : translateFuture,
+        past : translatePast,
+        s : 'puS lup',
+        m : 'wa’ tup',
+        mm : translate$9,
+        h : 'wa’ rep',
+        hh : translate$9,
+        d : 'wa’ jaj',
+        dd : translate$9,
+        M : 'wa’ jar',
+        MM : translate$9,
+        y : 'wa’ DIS',
+        yy : translate$9
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Turkish [tr]
+//! authors : Erhan Gundogan : https://github.com/erhangundogan,
+//!           Burak YiÄŸit Kaya: https://github.com/BYK
+
+var suffixes$3 = {
+    1: '\'inci',
+    5: '\'inci',
+    8: '\'inci',
+    70: '\'inci',
+    80: '\'inci',
+    2: '\'nci',
+    7: '\'nci',
+    20: '\'nci',
+    50: '\'nci',
+    3: '\'üncü',
+    4: '\'üncü',
+    100: '\'üncü',
+    6: '\'ncı',
+    9: '\'uncu',
+    10: '\'uncu',
+    30: '\'uncu',
+    60: '\'ıncı',
+    90: '\'ıncı'
+};
+
+hooks.defineLocale('tr', {
+    months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'),
+    monthsShort : 'Oca_Åžub_Mar_Nis_May_Haz_Tem_AÄŸu_Eyl_Eki_Kas_Ara'.split('_'),
+    weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'),
+    weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'),
+    weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[bugün saat] LT',
+        nextDay : '[yarın saat] LT',
+        nextWeek : '[haftaya] dddd [saat] LT',
+        lastDay : '[dün] LT',
+        lastWeek : '[geçen hafta] dddd [saat] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : '%s sonra',
+        past : '%s önce',
+        s : 'birkaç saniye',
+        m : 'bir dakika',
+        mm : '%d dakika',
+        h : 'bir saat',
+        hh : '%d saat',
+        d : 'bir gün',
+        dd : '%d gün',
+        M : 'bir ay',
+        MM : '%d ay',
+        y : 'bir yıl',
+        yy : '%d yıl'
+    },
+    ordinalParse: /\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,
+    ordinal : function (number) {
+        if (number === 0) {  // special case for zero
+            return number + '\'ıncı';
+        }
+        var a = number % 10,
+            b = number % 100 - a,
+            c = number >= 100 ? 100 : null;
+        return number + (suffixes$3[a] || suffixes$3[b] || suffixes$3[c]);
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Talossan [tzl]
+//! author : Robin van der Vliet : https://github.com/robin0van0der0v
+//! author : Iustì Canun
+
+// After the year there should be a slash and the amount of years since December 26, 1979 in Roman numerals.
+// This is currently too difficult (maybe even impossible) to add.
+hooks.defineLocale('tzl', {
+    months : 'Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar'.split('_'),
+    monthsShort : 'Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec'.split('_'),
+    weekdays : 'Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi'.split('_'),
+    weekdaysShort : 'Súl_Lún_Mai_Már_Xhú_Vié_Sát'.split('_'),
+    weekdaysMin : 'Sú_Lú_Ma_Má_Xh_Vi_Sá'.split('_'),
+    longDateFormat : {
+        LT : 'HH.mm',
+        LTS : 'HH.mm.ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D. MMMM [dallas] YYYY',
+        LLL : 'D. MMMM [dallas] YYYY HH.mm',
+        LLLL : 'dddd, [li] D. MMMM [dallas] YYYY HH.mm'
+    },
+    meridiemParse: /d\'o|d\'a/i,
+    isPM : function (input) {
+        return 'd\'o' === input.toLowerCase();
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'd\'o' : 'D\'O';
+        } else {
+            return isLower ? 'd\'a' : 'D\'A';
+        }
+    },
+    calendar : {
+        sameDay : '[oxhi à] LT',
+        nextDay : '[demà à] LT',
+        nextWeek : 'dddd [à] LT',
+        lastDay : '[ieiri à] LT',
+        lastWeek : '[sür el] dddd [lasteu à] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'osprei %s',
+        past : 'ja%s',
+        s : processRelativeTime$5,
+        m : processRelativeTime$5,
+        mm : processRelativeTime$5,
+        h : processRelativeTime$5,
+        hh : processRelativeTime$5,
+        d : processRelativeTime$5,
+        dd : processRelativeTime$5,
+        M : processRelativeTime$5,
+        MM : processRelativeTime$5,
+        y : processRelativeTime$5,
+        yy : processRelativeTime$5
+    },
+    ordinalParse: /\d{1,2}\./,
+    ordinal : '%d.',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+function processRelativeTime$5(number, withoutSuffix, key, isFuture) {
+    var format = {
+        's': ['viensas secunds', '\'iensas secunds'],
+        'm': ['\'n míut', '\'iens míut'],
+        'mm': [number + ' míuts', '' + number + ' míuts'],
+        'h': ['\'n þora', '\'iensa þora'],
+        'hh': [number + ' þoras', '' + number + ' þoras'],
+        'd': ['\'n ziua', '\'iensa ziua'],
+        'dd': [number + ' ziuas', '' + number + ' ziuas'],
+        'M': ['\'n mes', '\'iens mes'],
+        'MM': [number + ' mesen', '' + number + ' mesen'],
+        'y': ['\'n ar', '\'iens ar'],
+        'yy': [number + ' ars', '' + number + ' ars']
+    };
+    return isFuture ? format[key][0] : (withoutSuffix ? format[key][0] : format[key][1]);
+}
+
+//! moment.js locale configuration
+//! locale : Central Atlas Tamazight Latin [tzm-latn]
+//! author : Abdel Said : https://github.com/abdelsaid
+
+hooks.defineLocale('tzm-latn', {
+    months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'),
+    monthsShort : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'),
+    weekdays : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'),
+    weekdaysShort : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'),
+    weekdaysMin : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[asdkh g] LT',
+        nextDay: '[aska g] LT',
+        nextWeek: 'dddd [g] LT',
+        lastDay: '[assant g] LT',
+        lastWeek: 'dddd [g] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'dadkh s yan %s',
+        past : 'yan %s',
+        s : 'imik',
+        m : 'minuḍ',
+        mm : '%d minuḍ',
+        h : 'saɛa',
+        hh : '%d tassaɛin',
+        d : 'ass',
+        dd : '%d ossan',
+        M : 'ayowr',
+        MM : '%d iyyirn',
+        y : 'asgas',
+        yy : '%d isgasn'
+    },
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Central Atlas Tamazight [tzm]
+//! author : Abdel Said : https://github.com/abdelsaid
+
+hooks.defineLocale('tzm', {
+    months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'),
+    monthsShort : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'),
+    weekdays : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'),
+    weekdaysShort : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'),
+    weekdaysMin : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS: 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[ⴰⵙⴷⵅ ⴴ] LT',
+        nextDay: '[ⴰⵙⴽⴰ ⴴ] LT',
+        nextWeek: 'dddd [â´´] LT',
+        lastDay: '[ⴰⵚⴰⵏⵜ ⴴ] LT',
+        lastWeek: 'dddd [â´´] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s',
+        past : 'ⵢⴰⵏ %s',
+        s : 'ⵉⵎⵉⴽ',
+        m : 'ⵎⵉⵏⵓⴺ',
+        mm : '%d ⵎⵉⵏⵓⴺ',
+        h : 'ⵙⴰⵄⴰ',
+        hh : '%d ⵜⴰⵙⵙⴰⵄⵉⵏ',
+        d : 'ⴰⵙⵙ',
+        dd : '%d oⵙⵙⴰⵏ',
+        M : 'ⴰⵢoⵓⵔ',
+        MM : '%d ⵉⵢⵢⵉⵔⵏ',
+        y : 'ⴰⵙⴳⴰⵙ',
+        yy : '%d ⵉⵙⴳⴰⵙⵏ'
+    },
+    week : {
+        dow : 6, // Saturday is the first day of the week.
+        doy : 12  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Ukrainian [uk]
+//! author : zemlanin : https://github.com/zemlanin
+//! Author : Menelion Elensúle : https://github.com/Oire
+
+function plural$6(word, num) {
+    var forms = word.split('_');
+    return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]);
+}
+function relativeTimeWithPlural$4(number, withoutSuffix, key) {
+    var format = {
+        'mm': withoutSuffix ? 'хвилина_хвилини_хвилин' : 'хвилину_хвилини_хвилин',
+        'hh': withoutSuffix ? 'година_години_годин' : 'годину_години_годин',
+        'dd': 'день_дні_днів',
+        'MM': 'місяць_місяці_місяців',
+        'yy': 'рік_роки_років'
+    };
+    if (key === 'm') {
+        return withoutSuffix ? 'хвилина' : 'хвилину';
+    }
+    else if (key === 'h') {
+        return withoutSuffix ? 'година' : 'годину';
+    }
+    else {
+        return number + ' ' + plural$6(format[key], +number);
+    }
+}
+function weekdaysCaseReplace(m, format) {
+    var weekdays = {
+        'nominative': 'неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота'.split('_'),
+        'accusative': 'неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу'.split('_'),
+        'genitive': 'неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи'.split('_')
+    },
+    nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ?
+        'accusative' :
+        ((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ?
+            'genitive' :
+            'nominative');
+    return weekdays[nounCase][m.day()];
+}
+function processHoursFunction(str) {
+    return function () {
+        return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT';
+    };
+}
+
+hooks.defineLocale('uk', {
+    months : {
+        'format': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_'),
+        'standalone': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_')
+    },
+    monthsShort : 'січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'),
+    weekdays : weekdaysCaseReplace,
+    weekdaysShort : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
+    weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD.MM.YYYY',
+        LL : 'D MMMM YYYY р.',
+        LLL : 'D MMMM YYYY р., HH:mm',
+        LLLL : 'dddd, D MMMM YYYY р., HH:mm'
+    },
+    calendar : {
+        sameDay: processHoursFunction('[Сьогодні '),
+        nextDay: processHoursFunction('[Завтра '),
+        lastDay: processHoursFunction('[Вчора '),
+        nextWeek: processHoursFunction('[У] dddd ['),
+        lastWeek: function () {
+            switch (this.day()) {
+                case 0:
+                case 3:
+                case 5:
+                case 6:
+                    return processHoursFunction('[Минулої] dddd [').call(this);
+                case 1:
+                case 2:
+                case 4:
+                    return processHoursFunction('[Минулого] dddd [').call(this);
+            }
+        },
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : 'за %s',
+        past : '%s тому',
+        s : 'декілька секунд',
+        m : relativeTimeWithPlural$4,
+        mm : relativeTimeWithPlural$4,
+        h : 'годину',
+        hh : relativeTimeWithPlural$4,
+        d : 'день',
+        dd : relativeTimeWithPlural$4,
+        M : 'місяць',
+        MM : relativeTimeWithPlural$4,
+        y : 'рік',
+        yy : relativeTimeWithPlural$4
+    },
+    // M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason
+    meridiemParse: /ночі|ранку|дня|вечора/,
+    isPM: function (input) {
+        return /^(дня|вечора)$/.test(input);
+    },
+    meridiem : function (hour, minute, isLower) {
+        if (hour < 4) {
+            return 'ночі';
+        } else if (hour < 12) {
+            return 'ранку';
+        } else if (hour < 17) {
+            return 'дня';
+        } else {
+            return 'вечора';
+        }
+    },
+    ordinalParse: /\d{1,2}-(й|го)/,
+    ordinal: function (number, period) {
+        switch (period) {
+            case 'M':
+            case 'd':
+            case 'DDD':
+            case 'w':
+            case 'W':
+                return number + '-й';
+            case 'D':
+                return number + '-го';
+            default:
+                return number;
+        }
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 1st is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Uzbek [uz]
+//! author : Sardor Muminov : https://github.com/muminoff
+
+hooks.defineLocale('uz', {
+    months : 'январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр'.split('_'),
+    monthsShort : 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'),
+    weekdays : 'Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба'.split('_'),
+    weekdaysShort : 'Якш_Душ_Сеш_Чор_Пай_Жум_Шан'.split('_'),
+    weekdaysMin : 'Як_Ду_Се_Чо_Па_Жу_Ша'.split('_'),
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'D MMMM YYYY, dddd HH:mm'
+    },
+    calendar : {
+        sameDay : '[Бугун соат] LT [да]',
+        nextDay : '[Эртага] LT [да]',
+        nextWeek : 'dddd [куни соат] LT [да]',
+        lastDay : '[Кеча соат] LT [да]',
+        lastWeek : '[Утган] dddd [куни соат] LT [да]',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'Якин %s ичида',
+        past : 'Бир неча %s олдин',
+        s : 'фурсат',
+        m : 'бир дакика',
+        mm : '%d дакика',
+        h : 'бир соат',
+        hh : '%d соат',
+        d : 'бир кун',
+        dd : '%d кун',
+        M : 'бир ой',
+        MM : '%d ой',
+        y : 'бир йил',
+        yy : '%d йил'
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 7  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Vietnamese [vi]
+//! author : Bang Nguyen : https://github.com/bangnk
+
+hooks.defineLocale('vi', {
+    months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'),
+    monthsShort : 'Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy'.split('_'),
+    weekdaysShort : 'CN_T2_T3_T4_T5_T6_T7'.split('_'),
+    weekdaysMin : 'CN_T2_T3_T4_T5_T6_T7'.split('_'),
+    weekdaysParseExact : true,
+    meridiemParse: /sa|ch/i,
+    isPM : function (input) {
+        return /^ch$/i.test(input);
+    },
+    meridiem : function (hours, minutes, isLower) {
+        if (hours < 12) {
+            return isLower ? 'sa' : 'SA';
+        } else {
+            return isLower ? 'ch' : 'CH';
+        }
+    },
+    longDateFormat : {
+        LT : 'HH:mm',
+        LTS : 'HH:mm:ss',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM [năm] YYYY',
+        LLL : 'D MMMM [năm] YYYY HH:mm',
+        LLLL : 'dddd, D MMMM [năm] YYYY HH:mm',
+        l : 'DD/M/YYYY',
+        ll : 'D MMM YYYY',
+        lll : 'D MMM YYYY HH:mm',
+        llll : 'ddd, D MMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay: '[Hôm nay lúc] LT',
+        nextDay: '[Ngày mai lúc] LT',
+        nextWeek: 'dddd [tuần tới lúc] LT',
+        lastDay: '[Hôm qua lúc] LT',
+        lastWeek: 'dddd [tuần rồi lúc] LT',
+        sameElse: 'L'
+    },
+    relativeTime : {
+        future : '%s tá»›i',
+        past : '%s trÆ°á»›c',
+        s : 'vài giây',
+        m : 'một phút',
+        mm : '%d phút',
+        h : 'một giờ',
+        hh : '%d giờ',
+        d : 'một ngày',
+        dd : '%d ngày',
+        M : 'một tháng',
+        MM : '%d tháng',
+        y : 'một năm',
+        yy : '%d năm'
+    },
+    ordinalParse: /\d{1,2}/,
+    ordinal : function (number) {
+        return number;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Pseudo [x-pseudo]
+//! author : Andrew Hood : https://github.com/andrewhood125
+
+hooks.defineLocale('x-pseudo', {
+    months : 'J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér'.split('_'),
+    monthsShort : 'J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc'.split('_'),
+    monthsParseExact : true,
+    weekdays : 'S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý'.split('_'),
+    weekdaysShort : 'S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát'.split('_'),
+    weekdaysMin : 'S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá'.split('_'),
+    weekdaysParseExact : true,
+    longDateFormat : {
+        LT : 'HH:mm',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY HH:mm',
+        LLLL : 'dddd, D MMMM YYYY HH:mm'
+    },
+    calendar : {
+        sameDay : '[T~ódá~ý át] LT',
+        nextDay : '[T~ómó~rró~w át] LT',
+        nextWeek : 'dddd [át] LT',
+        lastDay : '[Ý~ést~érdá~ý át] LT',
+        lastWeek : '[L~ást] dddd [át] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'í~ñ %s',
+        past : '%s á~gó',
+        s : 'á ~féw ~sécó~ñds',
+        m : 'á ~míñ~úté',
+        mm : '%d m~íñú~tés',
+        h : 'á~ñ hó~úr',
+        hh : '%d h~óúrs',
+        d : 'á ~dáý',
+        dd : '%d d~áýs',
+        M : 'á ~móñ~th',
+        MM : '%d m~óñt~hs',
+        y : 'á ~ýéár',
+        yy : '%d ý~éárs'
+    },
+    ordinalParse: /\d{1,2}(th|st|nd|rd)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (~~(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    },
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Yoruba Nigeria [yo]
+//! author : Atolagbe Abisoye : https://github.com/andela-batolagbe
+
+hooks.defineLocale('yo', {
+    months : 'Sẹ́rẹ́_Èrèlè_Ẹrẹ̀nà_Ìgbé_Èbibi_Òkùdu_Agẹmo_Ògún_Owewe_Ọ̀wàrà_Bélú_Ọ̀pẹ̀̀'.split('_'),
+    monthsShort : 'Sẹ́r_Èrl_Ẹrn_Ìgb_Èbi_Òkù_Agẹ_Ògú_Owe_Ọ̀wà_Bél_Ọ̀pẹ̀̀'.split('_'),
+    weekdays : 'Àìkú_Ajé_Ìsẹ́gun_Ọjọ́rú_Ọjọ́bọ_Ẹtì_Àbámẹ́ta'.split('_'),
+    weekdaysShort : 'Àìk_Ajé_Ìsẹ́_Ọjr_Ọjb_Ẹtì_Àbá'.split('_'),
+    weekdaysMin : 'Àì_Aj_Ìs_Ọr_Ọb_Ẹt_Àb'.split('_'),
+    longDateFormat : {
+        LT : 'h:mm A',
+        LTS : 'h:mm:ss A',
+        L : 'DD/MM/YYYY',
+        LL : 'D MMMM YYYY',
+        LLL : 'D MMMM YYYY h:mm A',
+        LLLL : 'dddd, D MMMM YYYY h:mm A'
+    },
+    calendar : {
+        sameDay : '[Ònì ni] LT',
+        nextDay : '[Ọ̀la ni] LT',
+        nextWeek : 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT',
+        lastDay : '[AÌ€na ni] LT',
+        lastWeek : 'dddd [Ọsẹ̀ tólọ́] [ni] LT',
+        sameElse : 'L'
+    },
+    relativeTime : {
+        future : 'ní %s',
+        past : '%s kọjá',
+        s : 'ìsẹjú aayá die',
+        m : 'ìsẹjú kan',
+        mm : 'ìsẹjú %d',
+        h : 'wákati kan',
+        hh : 'wákati %d',
+        d : 'ọjọ́ kan',
+        dd : 'ọjọ́ %d',
+        M : 'osù kan',
+        MM : 'osù %d',
+        y : 'ọdún kan',
+        yy : 'ọdún %d'
+    },
+    ordinalParse : /ọjọ́\s\d{1,2}/,
+    ordinal : 'ọjọ́ %d',
+    week : {
+        dow : 1, // Monday is the first day of the week.
+        doy : 4 // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Chinese (China) [zh-cn]
+//! author : suupic : https://github.com/suupic
+//! author : Zeno Zeng : https://github.com/zenozeng
+
+hooks.defineLocale('zh-cn', {
+    months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
+    monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+    weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
+    weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'),
+    weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
+    longDateFormat : {
+        LT : 'Ah点mm分',
+        LTS : 'Ah点m分s秒',
+        L : 'YYYY-MM-DD',
+        LL : 'YYYYå¹´MMMDæ—¥',
+        LLL : 'YYYY年MMMD日Ah点mm分',
+        LLLL : 'YYYY年MMMD日ddddAh点mm分',
+        l : 'YYYY-MM-DD',
+        ll : 'YYYYå¹´MMMDæ—¥',
+        lll : 'YYYY年MMMD日Ah点mm分',
+        llll : 'YYYY年MMMD日ddddAh点mm分'
+    },
+    meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
+    meridiemHour: function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === '凌晨' || meridiem === '早上' ||
+                meridiem === '上午') {
+            return hour;
+        } else if (meridiem === '下午' || meridiem === '晚上') {
+            return hour + 12;
+        } else {
+            // '中午'
+            return hour >= 11 ? hour : hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        var hm = hour * 100 + minute;
+        if (hm < 600) {
+            return '凌晨';
+        } else if (hm < 900) {
+            return '早上';
+        } else if (hm < 1130) {
+            return '上午';
+        } else if (hm < 1230) {
+            return '中午';
+        } else if (hm < 1800) {
+            return '下午';
+        } else {
+            return '晚上';
+        }
+    },
+    calendar : {
+        sameDay : function () {
+            return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT';
+        },
+        nextDay : function () {
+            return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT';
+        },
+        lastDay : function () {
+            return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT';
+        },
+        nextWeek : function () {
+            var startOfWeek, prefix;
+            startOfWeek = hooks().startOf('week');
+            prefix = this.diff(startOfWeek, 'days') >= 7 ? '[下]' : '[本]';
+            return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
+        },
+        lastWeek : function () {
+            var startOfWeek, prefix;
+            startOfWeek = hooks().startOf('week');
+            prefix = this.unix() < startOfWeek.unix()  ? '[上]' : '[本]';
+            return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
+        },
+        sameElse : 'LL'
+    },
+    ordinalParse: /\d{1,2}(日|月|周)/,
+    ordinal : function (number, period) {
+        switch (period) {
+            case 'd':
+            case 'D':
+            case 'DDD':
+                return number + 'æ—¥';
+            case 'M':
+                return number + '月';
+            case 'w':
+            case 'W':
+                return number + '周';
+            default:
+                return number;
+        }
+    },
+    relativeTime : {
+        future : '%s内',
+        past : '%s前',
+        s : '几秒',
+        m : '1 分钟',
+        mm : '%d 分钟',
+        h : '1 小时',
+        hh : '%d 小时',
+        d : '1 天',
+        dd : '%d 天',
+        M : '1 个月',
+        MM : '%d 个月',
+        y : '1 å¹´',
+        yy : '%d å¹´'
+    },
+    week : {
+        // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
+        dow : 1, // Monday is the first day of the week.
+        doy : 4  // The week that contains Jan 4th is the first week of the year.
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Chinese (Hong Kong) [zh-hk]
+//! author : Ben : https://github.com/ben-lin
+//! author : Chris Lam : https://github.com/hehachris
+//! author : Konstantin : https://github.com/skfd
+
+hooks.defineLocale('zh-hk', {
+    months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
+    monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+    weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
+    weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'),
+    weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
+    longDateFormat : {
+        LT : 'Ah點mm分',
+        LTS : 'Ah點m分s秒',
+        L : 'YYYYå¹´MMMDæ—¥',
+        LL : 'YYYYå¹´MMMDæ—¥',
+        LLL : 'YYYY年MMMD日Ah點mm分',
+        LLLL : 'YYYY年MMMD日ddddAh點mm分',
+        l : 'YYYYå¹´MMMDæ—¥',
+        ll : 'YYYYå¹´MMMDæ—¥',
+        lll : 'YYYY年MMMD日Ah點mm分',
+        llll : 'YYYY年MMMD日ddddAh點mm分'
+    },
+    meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') {
+            return hour;
+        } else if (meridiem === '中午') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === '下午' || meridiem === '晚上') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        var hm = hour * 100 + minute;
+        if (hm < 600) {
+            return '凌晨';
+        } else if (hm < 900) {
+            return '早上';
+        } else if (hm < 1130) {
+            return '上午';
+        } else if (hm < 1230) {
+            return '中午';
+        } else if (hm < 1800) {
+            return '下午';
+        } else {
+            return '晚上';
+        }
+    },
+    calendar : {
+        sameDay : '[今天]LT',
+        nextDay : '[明天]LT',
+        nextWeek : '[下]ddddLT',
+        lastDay : '[昨天]LT',
+        lastWeek : '[上]ddddLT',
+        sameElse : 'L'
+    },
+    ordinalParse: /\d{1,2}(日|月|週)/,
+    ordinal : function (number, period) {
+        switch (period) {
+            case 'd' :
+            case 'D' :
+            case 'DDD' :
+                return number + 'æ—¥';
+            case 'M' :
+                return number + '月';
+            case 'w' :
+            case 'W' :
+                return number + '週';
+            default :
+                return number;
+        }
+    },
+    relativeTime : {
+        future : '%så…§',
+        past : '%s前',
+        s : '幾秒',
+        m : '1 分鐘',
+        mm : '%d 分鐘',
+        h : '1 小時',
+        hh : '%d 小時',
+        d : '1 天',
+        dd : '%d 天',
+        M : '1 個月',
+        MM : '%d 個月',
+        y : '1 å¹´',
+        yy : '%d å¹´'
+    }
+});
+
+//! moment.js locale configuration
+//! locale : Chinese (Taiwan) [zh-tw]
+//! author : Ben : https://github.com/ben-lin
+//! author : Chris Lam : https://github.com/hehachris
+
+hooks.defineLocale('zh-tw', {
+    months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
+    monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
+    weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
+    weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'),
+    weekdaysMin : '日_一_二_三_四_五_六'.split('_'),
+    longDateFormat : {
+        LT : 'Ah點mm分',
+        LTS : 'Ah點m分s秒',
+        L : 'YYYYå¹´MMMDæ—¥',
+        LL : 'YYYYå¹´MMMDæ—¥',
+        LLL : 'YYYY年MMMD日Ah點mm分',
+        LLLL : 'YYYY年MMMD日ddddAh點mm分',
+        l : 'YYYYå¹´MMMDæ—¥',
+        ll : 'YYYYå¹´MMMDæ—¥',
+        lll : 'YYYY年MMMD日Ah點mm分',
+        llll : 'YYYY年MMMD日ddddAh點mm分'
+    },
+    meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
+    meridiemHour : function (hour, meridiem) {
+        if (hour === 12) {
+            hour = 0;
+        }
+        if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') {
+            return hour;
+        } else if (meridiem === '中午') {
+            return hour >= 11 ? hour : hour + 12;
+        } else if (meridiem === '下午' || meridiem === '晚上') {
+            return hour + 12;
+        }
+    },
+    meridiem : function (hour, minute, isLower) {
+        var hm = hour * 100 + minute;
+        if (hm < 600) {
+            return '凌晨';
+        } else if (hm < 900) {
+            return '早上';
+        } else if (hm < 1130) {
+            return '上午';
+        } else if (hm < 1230) {
+            return '中午';
+        } else if (hm < 1800) {
+            return '下午';
+        } else {
+            return '晚上';
+        }
+    },
+    calendar : {
+        sameDay : '[今天]LT',
+        nextDay : '[明天]LT',
+        nextWeek : '[下]ddddLT',
+        lastDay : '[昨天]LT',
+        lastWeek : '[上]ddddLT',
+        sameElse : 'L'
+    },
+    ordinalParse: /\d{1,2}(日|月|週)/,
+    ordinal : function (number, period) {
+        switch (period) {
+            case 'd' :
+            case 'D' :
+            case 'DDD' :
+                return number + 'æ—¥';
+            case 'M' :
+                return number + '月';
+            case 'w' :
+            case 'W' :
+                return number + '週';
+            default :
+                return number;
+        }
+    },
+    relativeTime : {
+        future : '%så…§',
+        past : '%s前',
+        s : '幾秒',
+        m : '1 分鐘',
+        mm : '%d 分鐘',
+        h : '1 小時',
+        hh : '%d 小時',
+        d : '1 天',
+        dd : '%d 天',
+        M : '1 個月',
+        MM : '%d 個月',
+        y : '1 å¹´',
+        yy : '%d å¹´'
+    }
+});
+
+hooks.locale('en');
+
+return hooks;
+
+})));
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js
new file mode 100644
index 0000000000000000000000000000000000000000..06e1e1f628674649adb5347e91f0fa8cebfc8637
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.js
@@ -0,0 +1,5472 @@
+/*!
+* svg.js - A lightweight library for manipulating and animating SVG.
+* @version 2.4.0
+* https://svgdotjs.github.io/
+*
+* @copyright Wout Fierens <wout@mick-wout.com>
+* @license MIT
+*
+* BUILT: Sun Feb 05 2017 03:51:27 GMT+0100 (CET)
+*/;
+(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(function(){
+      return factory(root, root.document)
+    })
+  } else if (typeof exports === 'object') {
+    module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) }
+  } else {
+    root.SVG = factory(root, root.document)
+  }
+}(typeof window !== "undefined" ? window : this, function(window, document) {
+
+// The main wrapping element
+var SVG = this.SVG = function(element) {
+  if (SVG.supported) {
+    element = new SVG.Doc(element)
+
+    if(!SVG.parser.draw)
+      SVG.prepare()
+
+    return element
+  }
+}
+
+// Default namespaces
+SVG.ns    = 'http://www.w3.org/2000/svg'
+SVG.xmlns = 'http://www.w3.org/2000/xmlns/'
+SVG.xlink = 'http://www.w3.org/1999/xlink'
+SVG.svgjs = 'http://svgjs.com/svgjs'
+
+// Svg support test
+SVG.supported = (function() {
+  return !! document.createElementNS &&
+         !! document.createElementNS(SVG.ns,'svg').createSVGRect
+})()
+
+// Don't bother to continue if SVG is not supported
+if (!SVG.supported) return false
+
+// Element id sequence
+SVG.did  = 1000
+
+// Get next named element id
+SVG.eid = function(name) {
+  return 'Svgjs' + capitalize(name) + (SVG.did++)
+}
+
+// Method for element creation
+SVG.create = function(name) {
+  // create element
+  var element = document.createElementNS(this.ns, name)
+
+  // apply unique id
+  element.setAttribute('id', this.eid(name))
+
+  return element
+}
+
+// Method for extending objects
+SVG.extend = function() {
+  var modules, methods, key, i
+
+  // Get list of modules
+  modules = [].slice.call(arguments)
+
+  // Get object with extensions
+  methods = modules.pop()
+
+  for (i = modules.length - 1; i >= 0; i--)
+    if (modules[i])
+      for (key in methods)
+        modules[i].prototype[key] = methods[key]
+
+  // Make sure SVG.Set inherits any newly added methods
+  if (SVG.Set && SVG.Set.inherit)
+    SVG.Set.inherit()
+}
+
+// Invent new element
+SVG.invent = function(config) {
+  // Create element initializer
+  var initializer = typeof config.create == 'function' ?
+    config.create :
+    function() {
+      this.constructor.call(this, SVG.create(config.create))
+    }
+
+  // Inherit prototype
+  if (config.inherit)
+    initializer.prototype = new config.inherit
+
+  // Extend with methods
+  if (config.extend)
+    SVG.extend(initializer, config.extend)
+
+  // Attach construct method to parent
+  if (config.construct)
+    SVG.extend(config.parent || SVG.Container, config.construct)
+
+  return initializer
+}
+
+// Adopt existing svg elements
+SVG.adopt = function(node) {
+  // check for presence of node
+  if (!node) return null
+
+  // make sure a node isn't already adopted
+  if (node.instance) return node.instance
+
+  // initialize variables
+  var element
+
+  // adopt with element-specific settings
+  if (node.nodeName == 'svg')
+    element = node.parentNode instanceof SVGElement ? new SVG.Nested : new SVG.Doc
+  else if (node.nodeName == 'linearGradient')
+    element = new SVG.Gradient('linear')
+  else if (node.nodeName == 'radialGradient')
+    element = new SVG.Gradient('radial')
+  else if (SVG[capitalize(node.nodeName)])
+    element = new SVG[capitalize(node.nodeName)]
+  else
+    element = new SVG.Element(node)
+
+  // ensure references
+  element.type  = node.nodeName
+  element.node  = node
+  node.instance = element
+
+  // SVG.Class specific preparations
+  if (element instanceof SVG.Doc)
+    element.namespace().defs()
+
+  // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
+  element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
+
+  return element
+}
+
+// Initialize parsing element
+SVG.prepare = function() {
+  // Select document body and create invisible svg element
+  var body = document.getElementsByTagName('body')[0]
+    , draw = (body ? new SVG.Doc(body) :  new SVG.Doc(document.documentElement).nested()).size(2, 0)
+
+  // Create parser object
+  SVG.parser = {
+    body: body || document.documentElement
+  , draw: draw.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden')
+  , poly: draw.polyline().node
+  , path: draw.path().node
+  , native: SVG.create('svg')
+  }
+}
+
+SVG.parser = {
+  native: SVG.create('svg')
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  if(!SVG.parser.draw)
+    SVG.prepare()
+}, false)
+
+// Storage for regular expressions
+SVG.regex = {
+  // Parse unit value
+  numberAndUnit:    /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
+
+  // Parse hex value
+, hex:              /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
+
+  // Parse rgb value
+, rgb:              /rgb\((\d+),(\d+),(\d+)\)/
+
+  // Parse reference id
+, reference:        /#([a-z0-9\-_]+)/i
+
+  // Parse matrix wrapper
+, matrix:           /matrix\(|\)/g
+
+  // Elements of a matrix
+, matrixElements:   /,*\s+|,/
+
+  // Whitespace
+, whitespace:       /\s/g
+
+  // Test hex value
+, isHex:            /^#[a-f0-9]{3,6}$/i
+
+  // Test rgb value
+, isRgb:            /^rgb\(/
+
+  // Test css declaration
+, isCss:            /[^:]+:[^;]+;?/
+
+  // Test for blank string
+, isBlank:          /^(\s+)?$/
+
+  // Test for numeric string
+, isNumber:         /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
+
+  // Test for percent value
+, isPercent:        /^-?[\d\.]+%$/
+
+  // Test for image url
+, isImage:          /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
+
+  // The following regex are used to parse the d attribute of a path
+
+  // Replaces all negative exponents
+, negExp:           /e\-/gi
+
+  // Replaces all comma
+, comma:            /,/g
+
+  // Replaces all hyphens
+, hyphen:           /\-/g
+
+  // Replaces and tests for all path letters
+, pathLetters:      /[MLHVCSQTAZ]/gi
+
+  // yes we need this one, too
+, isPathLetter:     /[MLHVCSQTAZ]/i
+
+  // split at whitespaces
+, whitespaces:      /\s+/
+
+  // matches X
+, X:                /X/g
+}
+
+SVG.utils = {
+  // Map function
+  map: function(array, block) {
+    var i
+      , il = array.length
+      , result = []
+
+    for (i = 0; i < il; i++)
+      result.push(block(array[i]))
+
+    return result
+  }
+
+  // Filter function
+, filter: function(array, block) {
+    var i
+      , il = array.length
+      , result = []
+
+    for (i = 0; i < il; i++)
+      if (block(array[i]))
+        result.push(array[i])
+
+    return result
+  }
+
+  // Degrees to radians
+, radians: function(d) {
+    return d % 360 * Math.PI / 180
+  }
+
+  // Radians to degrees
+, degrees: function(r) {
+    return r * 180 / Math.PI % 360
+  }
+
+, filterSVGElements: function(nodes) {
+    return this.filter( nodes, function(el) { return el instanceof SVGElement })
+  }
+
+}
+
+SVG.defaults = {
+  // Default attribute values
+  attrs: {
+    // fill and stroke
+    'fill-opacity':     1
+  , 'stroke-opacity':   1
+  , 'stroke-width':     0
+  , 'stroke-linejoin':  'miter'
+  , 'stroke-linecap':   'butt'
+  , fill:               '#000000'
+  , stroke:             '#000000'
+  , opacity:            1
+    // position
+  , x:                  0
+  , y:                  0
+  , cx:                 0
+  , cy:                 0
+    // size
+  , width:              0
+  , height:             0
+    // radius
+  , r:                  0
+  , rx:                 0
+  , ry:                 0
+    // gradient
+  , offset:             0
+  , 'stop-opacity':     1
+  , 'stop-color':       '#000000'
+    // text
+  , 'font-size':        16
+  , 'font-family':      'Helvetica, Arial, sans-serif'
+  , 'text-anchor':      'start'
+  }
+
+}
+// Module for color convertions
+SVG.Color = function(color) {
+  var match
+
+  // initialize defaults
+  this.r = 0
+  this.g = 0
+  this.b = 0
+
+  if(!color) return
+
+  // parse color
+  if (typeof color === 'string') {
+    if (SVG.regex.isRgb.test(color)) {
+      // get rgb values
+      match = SVG.regex.rgb.exec(color.replace(/\s/g,''))
+
+      // parse numeric values
+      this.r = parseInt(match[1])
+      this.g = parseInt(match[2])
+      this.b = parseInt(match[3])
+
+    } else if (SVG.regex.isHex.test(color)) {
+      // get hex values
+      match = SVG.regex.hex.exec(fullHex(color))
+
+      // parse numeric values
+      this.r = parseInt(match[1], 16)
+      this.g = parseInt(match[2], 16)
+      this.b = parseInt(match[3], 16)
+
+    }
+
+  } else if (typeof color === 'object') {
+    this.r = color.r
+    this.g = color.g
+    this.b = color.b
+
+  }
+
+}
+
+SVG.extend(SVG.Color, {
+  // Default to hex conversion
+  toString: function() {
+    return this.toHex()
+  }
+  // Build hex value
+, toHex: function() {
+    return '#'
+      + compToHex(this.r)
+      + compToHex(this.g)
+      + compToHex(this.b)
+  }
+  // Build rgb value
+, toRgb: function() {
+    return 'rgb(' + [this.r, this.g, this.b].join() + ')'
+  }
+  // Calculate true brightness
+, brightness: function() {
+    return (this.r / 255 * 0.30)
+         + (this.g / 255 * 0.59)
+         + (this.b / 255 * 0.11)
+  }
+  // Make color morphable
+, morph: function(color) {
+    this.destination = new SVG.Color(color)
+
+    return this
+  }
+  // Get morphed color at given position
+, at: function(pos) {
+    // make sure a destination is defined
+    if (!this.destination) return this
+
+    // normalise pos
+    pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
+
+    // generate morphed color
+    return new SVG.Color({
+      r: ~~(this.r + (this.destination.r - this.r) * pos)
+    , g: ~~(this.g + (this.destination.g - this.g) * pos)
+    , b: ~~(this.b + (this.destination.b - this.b) * pos)
+    })
+  }
+
+})
+
+// Testers
+
+// Test if given value is a color string
+SVG.Color.test = function(color) {
+  color += ''
+  return SVG.regex.isHex.test(color)
+      || SVG.regex.isRgb.test(color)
+}
+
+// Test if given value is a rgb object
+SVG.Color.isRgb = function(color) {
+  return color && typeof color.r == 'number'
+               && typeof color.g == 'number'
+               && typeof color.b == 'number'
+}
+
+// Test if given value is a color
+SVG.Color.isColor = function(color) {
+  return SVG.Color.isRgb(color) || SVG.Color.test(color)
+}
+// Module for array conversion
+SVG.Array = function(array, fallback) {
+  array = (array || []).valueOf()
+
+  // if array is empty and fallback is provided, use fallback
+  if (array.length == 0 && fallback)
+    array = fallback.valueOf()
+
+  // parse array
+  this.value = this.parse(array)
+}
+
+SVG.extend(SVG.Array, {
+  // Make array morphable
+  morph: function(array) {
+    this.destination = this.parse(array)
+
+    // normalize length of arrays
+    if (this.value.length != this.destination.length) {
+      var lastValue       = this.value[this.value.length - 1]
+        , lastDestination = this.destination[this.destination.length - 1]
+
+      while(this.value.length > this.destination.length)
+        this.destination.push(lastDestination)
+      while(this.value.length < this.destination.length)
+        this.value.push(lastValue)
+    }
+
+    return this
+  }
+  // Clean up any duplicate points
+, settle: function() {
+    // find all unique values
+    for (var i = 0, il = this.value.length, seen = []; i < il; i++)
+      if (seen.indexOf(this.value[i]) == -1)
+        seen.push(this.value[i])
+
+    // set new value
+    return this.value = seen
+  }
+  // Get morphed array at given position
+, at: function(pos) {
+    // make sure a destination is defined
+    if (!this.destination) return this
+
+    // generate morphed array
+    for (var i = 0, il = this.value.length, array = []; i < il; i++)
+      array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos)
+
+    return new SVG.Array(array)
+  }
+  // Convert array to string
+, toString: function() {
+    return this.value.join(' ')
+  }
+  // Real value
+, valueOf: function() {
+    return this.value
+  }
+  // Parse whitespace separated string
+, parse: function(array) {
+    array = array.valueOf()
+
+    // if already is an array, no need to parse it
+    if (Array.isArray(array)) return array
+
+    return this.split(array)
+  }
+  // Strip unnecessary whitespace
+, split: function(string) {
+    return string.trim().split(/\s+/)
+  }
+  // Reverse array
+, reverse: function() {
+    this.value.reverse()
+
+    return this
+  }
+
+})
+// Poly points array
+SVG.PointArray = function(array, fallback) {
+  this.constructor.call(this, array, fallback || [[0,0]])
+}
+
+// Inherit from SVG.Array
+SVG.PointArray.prototype = new SVG.Array
+
+SVG.extend(SVG.PointArray, {
+  // Convert array to string
+  toString: function() {
+    // convert to a poly point string
+    for (var i = 0, il = this.value.length, array = []; i < il; i++)
+      array.push(this.value[i].join(','))
+
+    return array.join(' ')
+  }
+  // Convert array to line object
+, toLine: function() {
+    return {
+      x1: this.value[0][0]
+    , y1: this.value[0][1]
+    , x2: this.value[1][0]
+    , y2: this.value[1][1]
+    }
+  }
+  // Get morphed array at given position
+, at: function(pos) {
+    // make sure a destination is defined
+    if (!this.destination) return this
+
+    // generate morphed point string
+    for (var i = 0, il = this.value.length, array = []; i < il; i++)
+      array.push([
+        this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos
+      , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos
+      ])
+
+    return new SVG.PointArray(array)
+  }
+  // Parse point string
+, parse: function(array) {
+    var points = []
+
+    array = array.valueOf()
+
+    // if already is an array, no need to parse it
+    if (Array.isArray(array)) return array
+
+    // parse points
+    array = array.trim().split(/\s+|,/)
+
+    // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
+    // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
+    if (array.length % 2 !== 0) array.pop()
+
+    // wrap points in two-tuples and parse points as floats
+    for(var i = 0, len = array.length; i < len; i = i + 2)
+      points.push([ parseFloat(array[i]), parseFloat(array[i+1]) ])
+
+    return points
+  }
+  // Move point string
+, move: function(x, y) {
+    var box = this.bbox()
+
+    // get relative offset
+    x -= box.x
+    y -= box.y
+
+    // move every point
+    if (!isNaN(x) && !isNaN(y))
+      for (var i = this.value.length - 1; i >= 0; i--)
+        this.value[i] = [this.value[i][0] + x, this.value[i][1] + y]
+
+    return this
+  }
+  // Resize poly string
+, size: function(width, height) {
+    var i, box = this.bbox()
+
+    // recalculate position of all points according to new size
+    for (i = this.value.length - 1; i >= 0; i--) {
+      this.value[i][0] = ((this.value[i][0] - box.x) * width)  / box.width  + box.x
+      this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
+    }
+
+    return this
+  }
+  // Get bounding box of points
+, bbox: function() {
+    SVG.parser.poly.setAttribute('points', this.toString())
+
+    return SVG.parser.poly.getBBox()
+  }
+
+})
+// Path points array
+SVG.PathArray = function(array, fallback) {
+  this.constructor.call(this, array, fallback || [['M', 0, 0]])
+}
+
+// Inherit from SVG.Array
+SVG.PathArray.prototype = new SVG.Array
+
+SVG.extend(SVG.PathArray, {
+  // Convert array to string
+  toString: function() {
+    return arrayToString(this.value)
+  }
+  // Move path string
+, move: function(x, y) {
+    // get bounding box of current situation
+    var box = this.bbox()
+
+    // get relative offset
+    x -= box.x
+    y -= box.y
+
+    if (!isNaN(x) && !isNaN(y)) {
+      // move every point
+      for (var l, i = this.value.length - 1; i >= 0; i--) {
+        l = this.value[i][0]
+
+        if (l == 'M' || l == 'L' || l == 'T')  {
+          this.value[i][1] += x
+          this.value[i][2] += y
+
+        } else if (l == 'H')  {
+          this.value[i][1] += x
+
+        } else if (l == 'V')  {
+          this.value[i][1] += y
+
+        } else if (l == 'C' || l == 'S' || l == 'Q')  {
+          this.value[i][1] += x
+          this.value[i][2] += y
+          this.value[i][3] += x
+          this.value[i][4] += y
+
+          if (l == 'C')  {
+            this.value[i][5] += x
+            this.value[i][6] += y
+          }
+
+        } else if (l == 'A')  {
+          this.value[i][6] += x
+          this.value[i][7] += y
+        }
+
+      }
+    }
+
+    return this
+  }
+  // Resize path string
+, size: function(width, height) {
+    // get bounding box of current situation
+    var i, l, box = this.bbox()
+
+    // recalculate position of all points according to new size
+    for (i = this.value.length - 1; i >= 0; i--) {
+      l = this.value[i][0]
+
+      if (l == 'M' || l == 'L' || l == 'T')  {
+        this.value[i][1] = ((this.value[i][1] - box.x) * width)  / box.width  + box.x
+        this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
+
+      } else if (l == 'H')  {
+        this.value[i][1] = ((this.value[i][1] - box.x) * width)  / box.width  + box.x
+
+      } else if (l == 'V')  {
+        this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
+
+      } else if (l == 'C' || l == 'S' || l == 'Q')  {
+        this.value[i][1] = ((this.value[i][1] - box.x) * width)  / box.width  + box.x
+        this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
+        this.value[i][3] = ((this.value[i][3] - box.x) * width)  / box.width  + box.x
+        this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
+
+        if (l == 'C')  {
+          this.value[i][5] = ((this.value[i][5] - box.x) * width)  / box.width  + box.x
+          this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
+        }
+
+      } else if (l == 'A')  {
+        // resize radii
+        this.value[i][1] = (this.value[i][1] * width)  / box.width
+        this.value[i][2] = (this.value[i][2] * height) / box.height
+
+        // move position values
+        this.value[i][6] = ((this.value[i][6] - box.x) * width)  / box.width  + box.x
+        this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
+      }
+
+    }
+
+    return this
+  }
+  // Test if the passed path array use the same path data commands as this path array
+, equalCommands: function(pathArray) {
+    var i, il, equalCommands
+
+    pathArray = new SVG.PathArray(pathArray)
+
+    equalCommands = this.value.length === pathArray.value.length
+    for(i = 0, il = this.value.length; equalCommands && i < il; i++) {
+      equalCommands = this.value[i][0] === pathArray.value[i][0]
+    }
+
+    return equalCommands
+  }
+  // Make path array morphable
+, morph: function(pathArray) {
+    pathArray = new SVG.PathArray(pathArray)
+
+    if(this.equalCommands(pathArray)) {
+      this.destination = pathArray
+    } else {
+      this.destination = null
+    }
+
+    return this
+  }
+  // Get morphed path array at given position
+, at: function(pos) {
+    // make sure a destination is defined
+    if (!this.destination) return this
+
+    var sourceArray = this.value
+      , destinationArray = this.destination.value
+      , array = [], pathArray = new SVG.PathArray()
+      , i, il, j, jl
+
+    // Animate has specified in the SVG spec
+    // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
+    for (i = 0, il = sourceArray.length; i < il; i++) {
+      array[i] = [sourceArray[i][0]]
+      for(j = 1, jl = sourceArray[i].length; j < jl; j++) {
+        array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
+      }
+      // For the two flags of the elliptical arc command, the SVG spec say:
+      // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
+      // Elliptical arc command as an array followed by corresponding indexes:
+      // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
+      //   0    1   2        3                 4             5      6  7
+      if(array[i][0] === 'A') {
+        array[i][4] = +(array[i][4] != 0)
+        array[i][5] = +(array[i][5] != 0)
+      }
+    }
+
+    // Directly modify the value of a path array, this is done this way for performance
+    pathArray.value = array
+    return pathArray
+  }
+  // Absolutize and parse path to array
+, parse: function(array) {
+    // if it's already a patharray, no need to parse it
+    if (array instanceof SVG.PathArray) return array.valueOf()
+
+    // prepare for parsing
+    var i, x0, y0, s, seg, arr
+      , x = 0
+      , y = 0
+      , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7 }
+
+    if(typeof array == 'string'){
+
+      array = array
+        .replace(SVG.regex.negExp, 'X')         // replace all negative exponents with certain char
+        .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers
+        .replace(SVG.regex.hyphen, ' -')        // add space before hyphen
+        .replace(SVG.regex.comma, ' ')          // unify all spaces
+        .replace(SVG.regex.X, 'e-')             // add back the expoent
+        .trim()                                 // trim
+        .split(SVG.regex.whitespaces)           // split into array
+
+      // at this place there could be parts like ['3.124.854.32'] because we could not determine the point as seperator till now
+      // we fix this elements in the next loop
+      for(i = array.length; --i;){
+        if(array[i].indexOf('.') != array[i].lastIndexOf('.')){
+          var split = array[i].split('.') // split at the point
+          var first = [split.shift(), split.shift()].join('.') // join the first number together
+          array.splice.apply(array, [i, 1].concat(first, split.map(function(el){ return '.'+el }))) // add first and all other entries back to array
+        }
+      }
+
+    }else{
+      array = array.reduce(function(prev, curr){
+        return [].concat.apply(prev, curr)
+      }, [])
+    }
+
+    // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
+
+    var arr = []
+
+    do{
+
+      // Test if we have a path letter
+      if(SVG.regex.isPathLetter.test(array[0])){
+        s = array[0]
+        array.shift()
+      // If last letter was a move command and we got no new, it defaults to [L]ine
+      }else if(s == 'M'){
+        s = 'L'
+      }else if(s == 'm'){
+        s = 'l'
+      }
+
+      // add path letter as first element
+      seg = [s.toUpperCase()]
+
+      // push all necessary parameters to segment
+      for(i = 0; i < paramCnt[seg[0]]; ++i){
+        seg.push(parseFloat(array.shift()))
+      }
+
+      // upper case
+      if(s == seg[0]){
+
+        if(s == 'M' || s == 'L' || s == 'C' || s == 'Q' || s == 'S' || s == 'T'){
+          x = seg[paramCnt[seg[0]]-1]
+          y = seg[paramCnt[seg[0]]]
+        }else if(s == 'V'){
+          y = seg[1]
+        }else if(s == 'H'){
+          x = seg[1]
+        }else if(s == 'A'){
+          x = seg[6]
+          y = seg[7]
+        }
+
+      // lower case
+      }else{
+
+        // convert relative to absolute values
+        if(s == 'm' || s == 'l' || s == 'c' || s == 's' || s == 'q' || s == 't'){
+
+          seg[1] += x
+          seg[2] += y
+
+          if(seg[3] != null){
+            seg[3] += x
+            seg[4] += y
+          }
+
+          if(seg[5] != null){
+            seg[5] += x
+            seg[6] += y
+          }
+
+          // move pointer
+          x = seg[paramCnt[seg[0]]-1]
+          y = seg[paramCnt[seg[0]]]
+
+        }else if(s == 'v'){
+          seg[1] += y
+          y = seg[1]
+        }else if(s == 'h'){
+          seg[1] += x
+          x = seg[1]
+        }else if(s == 'a'){
+          seg[6] += x
+          seg[7] += y
+          x = seg[6]
+          y = seg[7]
+        }
+
+      }
+
+      if(seg[0] == 'M'){
+        x0 = x
+        y0 = y
+      }
+
+      if(seg[0] == 'Z'){
+        x = x0
+        y = y0
+      }
+
+      arr.push(seg)
+
+    }while(array.length)
+
+    return arr
+
+  }
+  // Get bounding box of path
+, bbox: function() {
+    SVG.parser.path.setAttribute('d', this.toString())
+
+    return SVG.parser.path.getBBox()
+  }
+
+})
+
+// Module for unit convertions
+SVG.Number = SVG.invent({
+  // Initialize
+  create: function(value, unit) {
+    // initialize defaults
+    this.value = 0
+    this.unit  = unit || ''
+
+    // parse value
+    if (typeof value === 'number') {
+      // ensure a valid numeric value
+      this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
+
+    } else if (typeof value === 'string') {
+      unit = value.match(SVG.regex.numberAndUnit)
+
+      if (unit) {
+        // make value numeric
+        this.value = parseFloat(unit[1])
+
+        // normalize
+        if (unit[5] == '%')
+          this.value /= 100
+        else if (unit[5] == 's')
+          this.value *= 1000
+
+        // store unit
+        this.unit = unit[5]
+      }
+
+    } else {
+      if (value instanceof SVG.Number) {
+        this.value = value.valueOf()
+        this.unit  = value.unit
+      }
+    }
+
+  }
+  // Add methods
+, extend: {
+    // Stringalize
+    toString: function() {
+      return (
+        this.unit == '%' ?
+          ~~(this.value * 1e8) / 1e6:
+        this.unit == 's' ?
+          this.value / 1e3 :
+          this.value
+      ) + this.unit
+    }
+  , toJSON: function() {
+      return this.toString()
+    }
+  , // Convert to primitive
+    valueOf: function() {
+      return this.value
+    }
+    // Add number
+  , plus: function(number) {
+      return new SVG.Number(this + new SVG.Number(number), this.unit)
+    }
+    // Subtract number
+  , minus: function(number) {
+      return this.plus(-new SVG.Number(number))
+    }
+    // Multiply number
+  , times: function(number) {
+      return new SVG.Number(this * new SVG.Number(number), this.unit)
+    }
+    // Divide number
+  , divide: function(number) {
+      return new SVG.Number(this / new SVG.Number(number), this.unit)
+    }
+    // Convert to different unit
+  , to: function(unit) {
+      var number = new SVG.Number(this)
+
+      if (typeof unit === 'string')
+        number.unit = unit
+
+      return number
+    }
+    // Make number morphable
+  , morph: function(number) {
+      this.destination = new SVG.Number(number)
+
+      return this
+    }
+    // Get morphed number at given position
+  , at: function(pos) {
+      // Make sure a destination is defined
+      if (!this.destination) return this
+
+      // Generate new morphed number
+      return new SVG.Number(this.destination)
+          .minus(this)
+          .times(pos)
+          .plus(this)
+    }
+
+  }
+})
+
+SVG.Element = SVG.invent({
+  // Initialize node
+  create: function(node) {
+    // make stroke value accessible dynamically
+    this._stroke = SVG.defaults.attrs.stroke
+
+    // initialize data object
+    this.dom = {}
+
+    // create circular reference
+    if (this.node = node) {
+      this.type = node.nodeName
+      this.node.instance = this
+
+      // store current attribute value
+      this._stroke = node.getAttribute('stroke') || this._stroke
+    }
+  }
+
+  // Add class methods
+, extend: {
+    // Move over x-axis
+    x: function(x) {
+      return this.attr('x', x)
+    }
+    // Move over y-axis
+  , y: function(y) {
+      return this.attr('y', y)
+    }
+    // Move by center over x-axis
+  , cx: function(x) {
+      return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
+    }
+    // Move by center over y-axis
+  , cy: function(y) {
+      return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2)
+    }
+    // Move element to given x and y values
+  , move: function(x, y) {
+      return this.x(x).y(y)
+    }
+    // Move element by its center
+  , center: function(x, y) {
+      return this.cx(x).cy(y)
+    }
+    // Set width of element
+  , width: function(width) {
+      return this.attr('width', width)
+    }
+    // Set height of element
+  , height: function(height) {
+      return this.attr('height', height)
+    }
+    // Set element size to given width and height
+  , size: function(width, height) {
+      var p = proportionalSize(this, width, height)
+
+      return this
+        .width(new SVG.Number(p.width))
+        .height(new SVG.Number(p.height))
+    }
+    // Clone element
+  , clone: function(parent) {
+      // clone element and assign new id
+      var clone = assignNewId(this.node.cloneNode(true))
+
+      // insert the clone in the given parent or after myself
+      if(parent) parent.add(clone)
+      else this.after(clone)
+
+      return clone
+    }
+    // Remove element
+  , remove: function() {
+      if (this.parent())
+        this.parent().removeElement(this)
+
+      return this
+    }
+    // Replace element
+  , replace: function(element) {
+      this.after(element).remove()
+
+      return element
+    }
+    // Add element to given container and return self
+  , addTo: function(parent) {
+      return parent.put(this)
+    }
+    // Add element to given container and return container
+  , putIn: function(parent) {
+      return parent.add(this)
+    }
+    // Get / set id
+  , id: function(id) {
+      return this.attr('id', id)
+    }
+    // Checks whether the given point inside the bounding box of the element
+  , inside: function(x, y) {
+      var box = this.bbox()
+
+      return x > box.x
+          && y > box.y
+          && x < box.x + box.width
+          && y < box.y + box.height
+    }
+    // Show element
+  , show: function() {
+      return this.style('display', '')
+    }
+    // Hide element
+  , hide: function() {
+      return this.style('display', 'none')
+    }
+    // Is element visible?
+  , visible: function() {
+      return this.style('display') != 'none'
+    }
+    // Return id on string conversion
+  , toString: function() {
+      return this.attr('id')
+    }
+    // Return array of classes on the node
+  , classes: function() {
+      var attr = this.attr('class')
+
+      return attr == null ? [] : attr.trim().split(/\s+/)
+    }
+    // Return true if class exists on the node, false otherwise
+  , hasClass: function(name) {
+      return this.classes().indexOf(name) != -1
+    }
+    // Add class to the node
+  , addClass: function(name) {
+      if (!this.hasClass(name)) {
+        var array = this.classes()
+        array.push(name)
+        this.attr('class', array.join(' '))
+      }
+
+      return this
+    }
+    // Remove class from the node
+  , removeClass: function(name) {
+      if (this.hasClass(name)) {
+        this.attr('class', this.classes().filter(function(c) {
+          return c != name
+        }).join(' '))
+      }
+
+      return this
+    }
+    // Toggle the presence of a class on the node
+  , toggleClass: function(name) {
+      return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
+    }
+    // Get referenced element form attribute value
+  , reference: function(attr) {
+      return SVG.get(this.attr(attr))
+    }
+    // Returns the parent element instance
+  , parent: function(type) {
+      var parent = this
+
+      // check for parent
+      if(!parent.node.parentNode) return null
+
+      // get parent element
+      parent = SVG.adopt(parent.node.parentNode)
+
+      if(!type) return parent
+
+      // loop trough ancestors if type is given
+      while(parent && parent.node instanceof SVGElement){
+        if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
+        parent = SVG.adopt(parent.node.parentNode)
+      }
+    }
+    // Get parent document
+  , doc: function() {
+      return this instanceof SVG.Doc ? this : this.parent(SVG.Doc)
+    }
+    // return array of all ancestors of given type up to the root svg
+  , parents: function(type) {
+      var parents = [], parent = this
+
+      do{
+        parent = parent.parent(type)
+        if(!parent || !parent.node) break
+
+        parents.push(parent)
+      } while(parent.parent)
+
+      return parents
+    }
+    // matches the element vs a css selector
+  , matches: function(selector){
+      return matches(this.node, selector)
+    }
+    // Returns the svg node to call native svg methods on it
+  , native: function() {
+      return this.node
+    }
+    // Import raw svg
+  , svg: function(svg) {
+      // create temporary holder
+      var well = document.createElement('svg')
+
+      // act as a setter if svg is given
+      if (svg && this instanceof SVG.Parent) {
+        // dump raw svg
+        well.innerHTML = '<svg>' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2></$1>') + '</svg>'
+
+        // transplant nodes
+        for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++)
+          this.node.appendChild(well.firstChild.firstChild)
+
+      // otherwise act as a getter
+      } else {
+        // create a wrapping svg element in case of partial content
+        well.appendChild(svg = document.createElement('svg'))
+
+        // write svgjs data to the dom
+        this.writeDataToDom()
+
+        // insert a copy of this node
+        svg.appendChild(this.node.cloneNode(true))
+
+        // return target element
+        return well.innerHTML.replace(/^<svg>/, '').replace(/<\/svg>$/, '')
+      }
+
+      return this
+    }
+  // write svgjs data to the dom
+  , writeDataToDom: function() {
+
+      // dump variables recursively
+      if(this.each || this.lines){
+        var fn = this.each ? this : this.lines();
+        fn.each(function(){
+          this.writeDataToDom()
+        })
+      }
+
+      // remove previously set data
+      this.node.removeAttribute('svgjs:data')
+
+      if(Object.keys(this.dom).length)
+        this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
+
+      return this
+    }
+  // set given data to the elements data property
+  , setData: function(o){
+      this.dom = o
+      return this
+    }
+  , is: function(obj){
+      return is(this, obj)
+    }
+  }
+})
+
+SVG.easing = {
+  '-': function(pos){return pos}
+, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5}
+, '>': function(pos){return  Math.sin(pos * Math.PI / 2)}
+, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1}
+}
+
+SVG.morph = function(pos){
+  return function(from, to) {
+    return new SVG.MorphObj(from, to).at(pos)
+  }
+}
+
+SVG.Situation = SVG.invent({
+
+  create: function(o){
+    this.init = false
+    this.reversed = false
+    this.reversing = false
+
+    this.duration = new SVG.Number(o.duration).valueOf()
+    this.delay = new SVG.Number(o.delay).valueOf()
+
+    this.start = +new Date() + this.delay
+    this.finish = this.start + this.duration
+    this.ease = o.ease
+
+    // this.loop is incremented from 0 to this.loops
+    // it is also incremented when in an infinite loop (when this.loops is true)
+    this.loop = 0
+    this.loops = false
+
+    this.animations = {
+      // functionToCall: [list of morphable objects]
+      // e.g. move: [SVG.Number, SVG.Number]
+    }
+
+    this.attrs = {
+      // holds all attributes which are not represented from a function svg.js provides
+      // e.g. someAttr: SVG.Number
+    }
+
+    this.styles = {
+      // holds all styles which should be animated
+      // e.g. fill-color: SVG.Color
+    }
+
+    this.transforms = [
+      // holds all transformations as transformation objects
+      // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix]
+    ]
+
+    this.once = {
+      // functions to fire at a specific position
+      // e.g. "0.5": function foo(){}
+    }
+
+  }
+
+})
+
+
+SVG.FX = SVG.invent({
+
+  create: function(element) {
+    this._target = element
+    this.situations = []
+    this.active = false
+    this.situation = null
+    this.paused = false
+    this.lastPos = 0
+    this.pos = 0
+    // The absolute position of an animation is its position in the context of its complete duration (including delay and loops)
+    // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1
+    this.absPos = 0
+    this._speed = 1
+  }
+
+, extend: {
+
+    /**
+     * sets or returns the target of this animation
+     * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation
+     * @param ease function || string Function which should be used for easing or easing keyword
+     * @param delay Number indicating the delay before the animation starts
+     * @return target || this
+     */
+    animate: function(o, ease, delay){
+
+      if(typeof o == 'object'){
+        ease = o.ease
+        delay = o.delay
+        o = o.duration
+      }
+
+      var situation = new SVG.Situation({
+        duration: o || 1000,
+        delay: delay || 0,
+        ease: SVG.easing[ease || '-'] || ease
+      })
+
+      this.queue(situation)
+
+      return this
+    }
+
+    /**
+     * sets a delay before the next element of the queue is called
+     * @param delay Duration of delay in milliseconds
+     * @return this.target()
+     */
+  , delay: function(delay){
+      // The delay is performed by an empty situation with its duration
+      // attribute set to the duration of the delay
+      var situation = new SVG.Situation({
+        duration: delay,
+        delay: 0,
+        ease: SVG.easing['-']
+      })
+
+      return this.queue(situation)
+    }
+
+    /**
+     * sets or returns the target of this animation
+     * @param null || target SVG.Element which should be set as new target
+     * @return target || this
+     */
+  , target: function(target){
+      if(target && target instanceof SVG.Element){
+        this._target = target
+        return this
+      }
+
+      return this._target
+    }
+
+    // returns the absolute position at a given time
+  , timeToAbsPos: function(timestamp){
+      return (timestamp - this.situation.start) / (this.situation.duration/this._speed)
+    }
+
+    // returns the timestamp from a given absolute positon
+  , absPosToTime: function(absPos){
+      return this.situation.duration/this._speed * absPos + this.situation.start
+    }
+
+    // starts the animationloop
+  , startAnimFrame: function(){
+      this.stopAnimFrame()
+      this.animationFrame = requestAnimationFrame(function(){ this.step() }.bind(this))
+    }
+
+    // cancels the animationframe
+  , stopAnimFrame: function(){
+      cancelAnimationFrame(this.animationFrame)
+    }
+
+    // kicks off the animation - only does something when the queue is currently not active and at least one situation is set
+  , start: function(){
+      // dont start if already started
+      if(!this.active && this.situation){
+        this.active = true
+        this.startCurrent()
+      }
+
+      return this
+    }
+
+    // start the current situation
+  , startCurrent: function(){
+      this.situation.start = +new Date + this.situation.delay/this._speed
+      this.situation.finish = this.situation.start + this.situation.duration/this._speed
+      return this.initAnimations().step()
+    }
+
+    /**
+     * adds a function / Situation to the animation queue
+     * @param fn function / situation to add
+     * @return this
+     */
+  , queue: function(fn){
+      if(typeof fn == 'function' || fn instanceof SVG.Situation)
+        this.situations.push(fn)
+
+      if(!this.situation) this.situation = this.situations.shift()
+
+      return this
+    }
+
+    /**
+     * pulls next element from the queue and execute it
+     * @return this
+     */
+  , dequeue: function(){
+      // stop current animation
+      this.situation && this.situation.stop && this.situation.stop()
+
+      // get next animation from queue
+      this.situation = this.situations.shift()
+
+      if(this.situation){
+        if(this.situation instanceof SVG.Situation) {
+          this.startCurrent()
+        } else {
+          // If it is not a SVG.Situation, then it is a function, we execute it
+          this.situation.call(this)
+        }
+      }
+
+      return this
+    }
+
+    // updates all animations to the current state of the element
+    // this is important when one property could be changed from another property
+  , initAnimations: function() {
+      var i
+      var s = this.situation
+
+      if(s.init) return this
+
+      for(i in s.animations){
+
+        if(i == 'viewbox'){
+          s.animations[i] = this.target().viewbox().morph(s.animations[i])
+        }else{
+
+          // TODO: this is not a clean clone of the array. We may have some unchecked references
+          s.animations[i].value = (i == 'plot' ? this.target().array().value : this.target()[i]())
+
+          // sometimes we get back an object and not the real value, fix this
+          if(s.animations[i].value.value){
+            s.animations[i].value = s.animations[i].value.value
+          }
+
+          if(s.animations[i].relative)
+            s.animations[i].destination.value = s.animations[i].destination.value + s.animations[i].value
+
+        }
+
+      }
+
+      for(i in s.attrs){
+        if(s.attrs[i] instanceof SVG.Color){
+          var color = new SVG.Color(this.target().attr(i))
+          s.attrs[i].r = color.r
+          s.attrs[i].g = color.g
+          s.attrs[i].b = color.b
+        }else{
+          s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value
+        }
+      }
+
+      for(i in s.styles){
+        s.styles[i].value = this.target().style(i)
+      }
+
+      s.initialTransformation = this.target().matrixify()
+
+      s.init = true
+      return this
+    }
+  , clearQueue: function(){
+      this.situations = []
+      return this
+    }
+  , clearCurrent: function(){
+      this.situation = null
+      return this
+    }
+    /** stops the animation immediately
+     * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately.
+     * @param clearQueue A Boolean indicating whether to remove queued animation as well.
+     * @return this
+     */
+  , stop: function(jumpToEnd, clearQueue){
+      if(!this.active) this.start()
+
+      if(clearQueue){
+        this.clearQueue()
+      }
+
+      this.active = false
+
+      if(jumpToEnd && this.situation){
+        this.atEnd()
+      }
+
+      this.stopAnimFrame()
+
+      return this.clearCurrent()
+    }
+
+    /** resets the element to the state where the current element has started
+     * @return this
+     */
+  , reset: function(){
+      if(this.situation){
+        var temp = this.situation
+        this.stop()
+        this.situation = temp
+        this.atStart()
+      }
+      return this
+    }
+
+    // Stop the currently-running animation, remove all queued animations, and complete all animations for the element.
+  , finish: function(){
+
+      this.stop(true, false)
+
+      while(this.dequeue().situation && this.stop(true, false));
+
+      this.clearQueue().clearCurrent()
+
+      return this
+    }
+
+    // set the internal animation pointer at the start position, before any loops, and updates the visualisation
+  , atStart: function() {
+    return this.at(0, true)
+  }
+
+    // set the internal animation pointer at the end position, after all the loops, and updates the visualisation
+  , atEnd: function() {
+    if (this.situation.loops === true) {
+      // If in a infinite loop, we end the current iteration
+      return this.at(this.situation.loop+1, true)
+    } else if(typeof this.situation.loops == 'number') {
+      // If performing a finite number of loops, we go after all the loops
+      return this.at(this.situation.loops, true)
+    } else {
+      // If no loops, we just go at the end
+      return this.at(1, true)
+    }
+  }
+
+    // set the internal animation pointer to the specified position and updates the visualisation
+    // if isAbsPos is true, pos is treated as an absolute position
+  , at: function(pos, isAbsPos){
+      var durDivSpd = this.situation.duration/this._speed
+
+      this.absPos = pos
+      // If pos is not an absolute position, we convert it into one
+      if (!isAbsPos) {
+        if (this.situation.reversed) this.absPos = 1 - this.absPos
+        this.absPos += this.situation.loop
+      }
+
+      this.situation.start = +new Date - this.absPos * durDivSpd
+      this.situation.finish = this.situation.start + durDivSpd
+
+      return this.step(true)
+    }
+
+    /**
+     * sets or returns the speed of the animations
+     * @param speed null || Number The new speed of the animations
+     * @return Number || this
+     */
+  , speed: function(speed){
+      if (speed === 0) return this.pause()
+
+      if (speed) {
+        this._speed = speed
+        // We use an absolute position here so that speed can affect the delay before the animation
+        return this.at(this.absPos, true)
+      } else return this._speed
+    }
+
+    // Make loopable
+  , loop: function(times, reverse) {
+      var c = this.last()
+
+      // store total loops
+      c.loops = (times != null) ? times : true
+      c.loop = 0
+
+      if(reverse) c.reversing = true
+      return this
+    }
+
+    // pauses the animation
+  , pause: function(){
+      this.paused = true
+      this.stopAnimFrame()
+
+      return this
+    }
+
+    // unpause the animation
+  , play: function(){
+      if(!this.paused) return this
+      this.paused = false
+      // We use an absolute position here so that the delay before the animation can be paused
+      return this.at(this.absPos, true)
+    }
+
+    /**
+     * toggle or set the direction of the animation
+     * true sets direction to backwards while false sets it to forwards
+     * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status)
+     * @return this
+     */
+  , reverse: function(reversed){
+      var c = this.last()
+
+      if(typeof reversed == 'undefined') c.reversed = !c.reversed
+      else c.reversed = reversed
+
+      return this
+    }
+
+
+    /**
+     * returns a float from 0-1 indicating the progress of the current animation
+     * @param eased Boolean indicating whether the returned position should be eased or not
+     * @return number
+     */
+  , progress: function(easeIt){
+      return easeIt ? this.situation.ease(this.pos) : this.pos
+    }
+
+    /**
+     * adds a callback function which is called when the current animation is finished
+     * @param fn Function which should be executed as callback
+     * @return number
+     */
+  , after: function(fn){
+      var c = this.last()
+        , wrapper = function wrapper(e){
+            if(e.detail.situation == c){
+              fn.call(this, c)
+              this.off('finished.fx', wrapper) // prevent memory leak
+            }
+          }
+
+      this.target().on('finished.fx', wrapper)
+      return this
+    }
+
+    // adds a callback which is called whenever one animation step is performed
+  , during: function(fn){
+      var c = this.last()
+        , wrapper = function(e){
+            if(e.detail.situation == c){
+              fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c)
+            }
+          }
+
+      // see above
+      this.target().off('during.fx', wrapper).on('during.fx', wrapper)
+
+      return this.after(function(){
+        this.off('during.fx', wrapper)
+      })
+    }
+
+    // calls after ALL animations in the queue are finished
+  , afterAll: function(fn){
+      var wrapper = function wrapper(e){
+            fn.call(this)
+            this.off('allfinished.fx', wrapper)
+          }
+
+      // see above
+      this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
+      return this
+    }
+
+    // calls on every animation step for all animations
+  , duringAll: function(fn){
+      var wrapper = function(e){
+            fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation)
+          }
+
+      this.target().off('during.fx', wrapper).on('during.fx', wrapper)
+
+      return this.afterAll(function(){
+        this.off('during.fx', wrapper)
+      })
+    }
+
+  , last: function(){
+      return this.situations.length ? this.situations[this.situations.length-1] : this.situation
+    }
+
+    // adds one property to the animations
+  , add: function(method, args, type){
+      this.last()[type || 'animations'][method] = args
+      setTimeout(function(){this.start()}.bind(this), 0)
+      return this
+    }
+
+    /** perform one step of the animation
+     *  @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time
+     *  @return this
+     */
+  , step: function(ignoreTime){
+
+      // convert current time to an absolute position
+      if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date)
+
+      // This part convert an absolute position to a position
+      if(this.situation.loops !== false) {
+        var absPos, absPosInt, lastLoop
+
+        // If the absolute position is below 0, we just treat it as if it was 0
+        absPos = Math.max(this.absPos, 0)
+        absPosInt = Math.floor(absPos)
+
+        if(this.situation.loops === true || absPosInt < this.situation.loops) {
+          this.pos = absPos - absPosInt
+          lastLoop = this.situation.loop
+          this.situation.loop = absPosInt
+        } else {
+          this.absPos = this.situation.loops
+          this.pos = 1
+          // The -1 here is because we don't want to toggle reversed when all the loops have been completed
+          lastLoop = this.situation.loop - 1
+          this.situation.loop = this.situation.loops
+        }
+
+        if(this.situation.reversing) {
+          // Toggle reversed if an odd number of loops as occured since the last call of step
+          this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2)
+        }
+
+      } else {
+        // If there are no loop, the absolute position must not be above 1
+        this.absPos = Math.min(this.absPos, 1)
+        this.pos = this.absPos
+      }
+
+      // while the absolute position can be below 0, the position must not be below 0
+      if(this.pos < 0) this.pos = 0
+
+      if(this.situation.reversed) this.pos = 1 - this.pos
+
+
+      // apply easing
+      var eased = this.situation.ease(this.pos)
+
+      // call once-callbacks
+      for(var i in this.situation.once){
+        if(i > this.lastPos && i <= eased){
+          this.situation.once[i].call(this.target(), this.pos, eased)
+          delete this.situation.once[i]
+        }
+      }
+
+      // fire during callback with position, eased position and current situation as parameter
+      if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation})
+
+      // the user may call stop or finish in the during callback
+      // so make sure that we still have a valid situation
+      if(!this.situation){
+        return this
+      }
+
+      // apply the actual animation to every property
+      this.eachAt()
+
+      // do final code when situation is finished
+      if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){
+
+        // stop animation callback
+        this.stopAnimFrame()
+
+        // fire finished callback with current situation as parameter
+        this.target().fire('finished', {fx:this, situation: this.situation})
+
+        if(!this.situations.length){
+          this.target().fire('allfinished')
+          this.target().off('.fx') // there shouldnt be any binding left, but to make sure...
+          this.active = false
+        }
+
+        // start next animation
+        if(this.active) this.dequeue()
+        else this.clearCurrent()
+
+      }else if(!this.paused && this.active){
+        // we continue animating when we are not at the end
+        this.startAnimFrame()
+      }
+
+      // save last eased position for once callback triggering
+      this.lastPos = eased
+      return this
+
+    }
+
+    // calculates the step for every property and calls block with it
+  , eachAt: function(){
+      var i, at, self = this, target = this.target(), s = this.situation
+
+      // apply animations which can be called trough a method
+      for(i in s.animations){
+
+        at = [].concat(s.animations[i]).map(function(el){
+          return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
+        })
+
+        target[i].apply(target, at)
+
+      }
+
+      // apply animation which has to be applied with attr()
+      for(i in s.attrs){
+
+        at = [i].concat(s.attrs[i]).map(function(el){
+          return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
+        })
+
+        target.attr.apply(target, at)
+
+      }
+
+      // apply animation which has to be applied with style()
+      for(i in s.styles){
+
+        at = [i].concat(s.styles[i]).map(function(el){
+          return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
+        })
+
+        target.style.apply(target, at)
+
+      }
+
+      // animate initialTransformation which has to be chained
+      if(s.transforms.length){
+
+        // get initial initialTransformation
+        at = s.initialTransformation
+        for(i = 0, len = s.transforms.length; i < len; i++){
+
+          // get next transformation in chain
+          var a = s.transforms[i]
+
+          // multiply matrix directly
+          if(a instanceof SVG.Matrix){
+
+            if(a.relative){
+              at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos)))
+            }else{
+              at = at.morph(a).at(s.ease(this.pos))
+            }
+            continue
+          }
+
+          // when transformation is absolute we have to reset the needed transformation first
+          if(!a.relative)
+            a.undo(at.extract())
+
+          // and reapply it after
+          at = at.multiply(a.at(s.ease(this.pos)))
+
+        }
+
+        // set new matrix on element
+        target.matrix(at)
+      }
+
+      return this
+
+    }
+
+
+    // adds an once-callback which is called at a specific position and never again
+  , once: function(pos, fn, isEased){
+
+      if(!isEased)pos = this.situation.ease(pos)
+
+      this.situation.once[pos] = fn
+
+      return this
+    }
+
+  }
+
+, parent: SVG.Element
+
+  // Add method to parent elements
+, construct: {
+    // Get fx module or create a new one, then animate with given duration and ease
+    animate: function(o, ease, delay) {
+      return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay)
+    }
+  , delay: function(delay){
+      return (this.fx || (this.fx = new SVG.FX(this))).delay(delay)
+    }
+  , stop: function(jumpToEnd, clearQueue) {
+      if (this.fx)
+        this.fx.stop(jumpToEnd, clearQueue)
+
+      return this
+    }
+  , finish: function() {
+      if (this.fx)
+        this.fx.finish()
+
+      return this
+    }
+    // Pause current animation
+  , pause: function() {
+      if (this.fx)
+        this.fx.pause()
+
+      return this
+    }
+    // Play paused current animation
+  , play: function() {
+      if (this.fx)
+        this.fx.play()
+
+      return this
+    }
+    // Set/Get the speed of the animations
+  , speed: function(speed) {
+      if (this.fx)
+        if (speed == null)
+          return this.fx.speed()
+        else
+          this.fx.speed(speed)
+
+      return this
+    }
+  }
+
+})
+
+// MorphObj is used whenever no morphable object is given
+SVG.MorphObj = SVG.invent({
+
+  create: function(from, to){
+    // prepare color for morphing
+    if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
+    // prepare number for morphing
+    if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
+
+    // prepare for plain morphing
+    this.value = 0
+    this.destination = to
+  }
+
+, extend: {
+    at: function(pos, real){
+      return real < 1 ? this.value : this.destination
+    },
+
+    valueOf: function(){
+      return this.value
+    }
+  }
+
+})
+
+SVG.extend(SVG.FX, {
+  // Add animatable attributes
+  attr: function(a, v, relative) {
+    // apply attributes individually
+    if (typeof a == 'object') {
+      for (var key in a)
+        this.attr(key, a[key])
+
+    } else {
+      // the MorphObj takes care about the right function used
+      this.add(a, new SVG.MorphObj(null, v), 'attrs')
+    }
+
+    return this
+  }
+  // Add animatable styles
+, style: function(s, v) {
+    if (typeof s == 'object')
+      for (var key in s)
+        this.style(key, s[key])
+
+    else
+      this.add(s, new SVG.MorphObj(null, v), 'styles')
+
+    return this
+  }
+  // Animatable x-axis
+, x: function(x, relative) {
+    if(this.target() instanceof SVG.G){
+      this.transform({x:x}, relative)
+      return this
+    }
+
+    var num = new SVG.Number().morph(x)
+    num.relative = relative
+    return this.add('x', num)
+  }
+  // Animatable y-axis
+, y: function(y, relative) {
+    if(this.target() instanceof SVG.G){
+      this.transform({y:y}, relative)
+      return this
+    }
+
+    var num = new SVG.Number().morph(y)
+    num.relative = relative
+    return this.add('y', num)
+  }
+  // Animatable center x-axis
+, cx: function(x) {
+    return this.add('cx', new SVG.Number().morph(x))
+  }
+  // Animatable center y-axis
+, cy: function(y) {
+    return this.add('cy', new SVG.Number().morph(y))
+  }
+  // Add animatable move
+, move: function(x, y) {
+    return this.x(x).y(y)
+  }
+  // Add animatable center
+, center: function(x, y) {
+    return this.cx(x).cy(y)
+  }
+  // Add animatable size
+, size: function(width, height) {
+    if (this.target() instanceof SVG.Text) {
+      // animate font size for Text elements
+      this.attr('font-size', width)
+
+    } else {
+      // animate bbox based size for all other elements
+      var box
+
+      if(!width || !height){
+        box = this.target().bbox()
+      }
+
+      if(!width){
+        width = box.width / box.height  * height
+      }
+
+      if(!height){
+        height = box.height / box.width  * width
+      }
+
+      this.add('width' , new SVG.Number().morph(width))
+          .add('height', new SVG.Number().morph(height))
+
+    }
+
+    return this
+  }
+  // Add animatable plot
+, plot: function(p) {
+    return this.add('plot', this.target().array().morph(p))
+  }
+  // Add leading method
+, leading: function(value) {
+    return this.target().leading ?
+      this.add('leading', new SVG.Number().morph(value)) :
+      this
+  }
+  // Add animatable viewbox
+, viewbox: function(x, y, width, height) {
+    if (this.target() instanceof SVG.Container) {
+      this.add('viewbox', new SVG.ViewBox(x, y, width, height))
+    }
+
+    return this
+  }
+, update: function(o) {
+    if (this.target() instanceof SVG.Stop) {
+      if (typeof o == 'number' || o instanceof SVG.Number) {
+        return this.update({
+          offset:  arguments[0]
+        , color:   arguments[1]
+        , opacity: arguments[2]
+        })
+      }
+
+      if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+      if (o.color   != null) this.attr('stop-color', o.color)
+      if (o.offset  != null) this.attr('offset', o.offset)
+    }
+
+    return this
+  }
+})
+
+SVG.BBox = SVG.invent({
+  // Initialize
+  create: function(element) {
+    // get values if element is given
+    if (element) {
+      var box
+
+      // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered
+      try {
+
+        // the element is NOT in the dom, throw error
+        if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom')
+
+        // find native bbox
+        box = element.node.getBBox()
+      } catch(e) {
+        if(element instanceof SVG.Shape){
+          var clone = element.clone(SVG.parser.draw).show()
+          box = clone.bbox()
+          clone.remove()
+        }else{
+          box = {
+            x:      element.node.clientLeft
+          , y:      element.node.clientTop
+          , width:  element.node.clientWidth
+          , height: element.node.clientHeight
+          }
+        }
+      }
+
+      // plain x and y
+      this.x = box.x
+      this.y = box.y
+
+      // plain width and height
+      this.width  = box.width
+      this.height = box.height
+    }
+
+    // add center, right and bottom
+    fullBox(this)
+  }
+
+  // Define Parent
+, parent: SVG.Element
+
+  // Constructor
+, construct: {
+    // Get bounding box
+    bbox: function() {
+      return new SVG.BBox(this)
+    }
+  }
+
+})
+
+SVG.TBox = SVG.invent({
+  // Initialize
+  create: function(element) {
+    // get values if element is given
+    if (element) {
+      var t   = element.ctm().extract()
+        , box = element.bbox()
+
+      // width and height including transformations
+      this.width  = box.width  * t.scaleX
+      this.height = box.height * t.scaleY
+
+      // x and y including transformations
+      this.x = box.x + t.x
+      this.y = box.y + t.y
+    }
+
+    // add center, right and bottom
+    fullBox(this)
+  }
+
+  // Define Parent
+, parent: SVG.Element
+
+  // Constructor
+, construct: {
+    // Get transformed bounding box
+    tbox: function() {
+      return new SVG.TBox(this)
+    }
+  }
+
+})
+
+
+SVG.RBox = SVG.invent({
+  // Initialize
+  create: function(element) {
+    if (element) {
+      var e    = element.doc().parent()
+        , box  = element.node.getBoundingClientRect()
+        , zoom = 1
+
+      // get screen offset
+      this.x = box.left
+      this.y = box.top
+
+      // subtract parent offset
+      this.x -= e.offsetLeft
+      this.y -= e.offsetTop
+
+      while (e = e.offsetParent) {
+        this.x -= e.offsetLeft
+        this.y -= e.offsetTop
+      }
+
+      // calculate cumulative zoom from svg documents
+      e = element
+      while (e.parent && (e = e.parent())) {
+        if (e.viewbox) {
+          zoom *= e.viewbox().zoom
+          this.x -= e.x() || 0
+          this.y -= e.y() || 0
+        }
+      }
+
+      // recalculate viewbox distortion
+      this.width  = box.width  /= zoom
+      this.height = box.height /= zoom
+    }
+
+    // add center, right and bottom
+    fullBox(this)
+
+    // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
+    this.x += window.pageXOffset
+    this.y += window.pageYOffset
+  }
+
+  // define Parent
+, parent: SVG.Element
+
+  // Constructor
+, construct: {
+    // Get rect box
+    rbox: function() {
+      return new SVG.RBox(this)
+    }
+  }
+
+})
+
+// Add universal merge method
+;[SVG.BBox, SVG.TBox, SVG.RBox].forEach(function(c) {
+
+  SVG.extend(c, {
+    // Merge rect box with another, return a new instance
+    merge: function(box) {
+      var b = new c()
+
+      // merge boxes
+      b.x      = Math.min(this.x, box.x)
+      b.y      = Math.min(this.y, box.y)
+      b.width  = Math.max(this.x + this.width,  box.x + box.width)  - b.x
+      b.height = Math.max(this.y + this.height, box.y + box.height) - b.y
+
+      return fullBox(b)
+    }
+
+  })
+
+})
+
+SVG.Matrix = SVG.invent({
+  // Initialize
+  create: function(source) {
+    var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0])
+
+    // ensure source as object
+    source = source instanceof SVG.Element ?
+      source.matrixify() :
+    typeof source === 'string' ?
+      stringToMatrix(source) :
+    arguments.length == 6 ?
+      arrayToMatrix([].slice.call(arguments)) :
+    typeof source === 'object' ?
+      source : base
+
+    // merge source
+    for (i = abcdef.length - 1; i >= 0; --i)
+      this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ?
+        source[abcdef[i]] : base[abcdef[i]]
+  }
+
+  // Add methods
+, extend: {
+    // Extract individual transformations
+    extract: function() {
+      // find delta transform points
+      var px    = deltaTransformPoint(this, 0, 1)
+        , py    = deltaTransformPoint(this, 1, 0)
+        , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90
+
+      return {
+        // translation
+        x:        this.e
+      , y:        this.f
+      , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b)
+      , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d)
+        // skew
+      , skewX:    -skewX
+      , skewY:    180 / Math.PI * Math.atan2(py.y, py.x)
+        // scale
+      , scaleX:   Math.sqrt(this.a * this.a + this.b * this.b)
+      , scaleY:   Math.sqrt(this.c * this.c + this.d * this.d)
+        // rotation
+      , rotation: skewX
+      , a: this.a
+      , b: this.b
+      , c: this.c
+      , d: this.d
+      , e: this.e
+      , f: this.f
+      , matrix: new SVG.Matrix(this)
+      }
+    }
+    // Clone matrix
+  , clone: function() {
+      return new SVG.Matrix(this)
+    }
+    // Morph one matrix into another
+  , morph: function(matrix) {
+      // store new destination
+      this.destination = new SVG.Matrix(matrix)
+
+      return this
+    }
+    // Get morphed matrix at a given position
+  , at: function(pos) {
+      // make sure a destination is defined
+      if (!this.destination) return this
+
+      // calculate morphed matrix at a given position
+      var matrix = new SVG.Matrix({
+        a: this.a + (this.destination.a - this.a) * pos
+      , b: this.b + (this.destination.b - this.b) * pos
+      , c: this.c + (this.destination.c - this.c) * pos
+      , d: this.d + (this.destination.d - this.d) * pos
+      , e: this.e + (this.destination.e - this.e) * pos
+      , f: this.f + (this.destination.f - this.f) * pos
+      })
+
+      // process parametric rotation if present
+      if (this.param && this.param.to) {
+        // calculate current parametric position
+        var param = {
+          rotation: this.param.from.rotation + (this.param.to.rotation - this.param.from.rotation) * pos
+        , cx:       this.param.from.cx
+        , cy:       this.param.from.cy
+        }
+
+        // rotate matrix
+        matrix = matrix.rotate(
+          (this.param.to.rotation - this.param.from.rotation * 2) * pos
+        , param.cx
+        , param.cy
+        )
+
+        // store current parametric values
+        matrix.param = param
+      }
+
+      return matrix
+    }
+    // Multiplies by given matrix
+  , multiply: function(matrix) {
+      return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native()))
+    }
+    // Inverses matrix
+  , inverse: function() {
+      return new SVG.Matrix(this.native().inverse())
+    }
+    // Translate matrix
+  , translate: function(x, y) {
+      return new SVG.Matrix(this.native().translate(x || 0, y || 0))
+    }
+    // Scale matrix
+  , scale: function(x, y, cx, cy) {
+      // support uniformal scale
+      if (arguments.length == 1) {
+        y = x
+      } else if (arguments.length == 3) {
+        cy = cx
+        cx = y
+        y = x
+      }
+
+      return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0))
+    }
+    // Rotate matrix
+  , rotate: function(r, cx, cy) {
+      // convert degrees to radians
+      r = SVG.utils.radians(r)
+
+      return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0))
+    }
+    // Flip matrix on x or y, at a given offset
+  , flip: function(a, o) {
+      return a == 'x' ? this.scale(-1, 1, o, 0) : this.scale(1, -1, 0, o)
+    }
+    // Skew
+  , skew: function(x, y, cx, cy) {
+      // support uniformal skew
+      if (arguments.length == 1) {
+        y = x
+      } else if (arguments.length == 3) {
+        cy = cx
+        cx = y
+        y = x
+      }
+
+      // convert degrees to radians
+      x = SVG.utils.radians(x)
+      y = SVG.utils.radians(y)
+
+      return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0))
+    }
+    // SkewX
+  , skewX: function(x, cx, cy) {
+      return this.skew(x, 0, cx, cy)
+    }
+    // SkewY
+  , skewY: function(y, cx, cy) {
+      return this.skew(0, y, cx, cy)
+    }
+    // Transform around a center point
+  , around: function(cx, cy, matrix) {
+      return this
+        .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0))
+        .multiply(matrix)
+        .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0))
+    }
+    // Convert to native SVGMatrix
+  , native: function() {
+      // create new matrix
+      var matrix = SVG.parser.native.createSVGMatrix()
+
+      // update with current values
+      for (var i = abcdef.length - 1; i >= 0; i--)
+        matrix[abcdef[i]] = this[abcdef[i]]
+
+      return matrix
+    }
+    // Convert matrix to string
+  , toString: function() {
+      return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'
+    }
+  }
+
+  // Define parent
+, parent: SVG.Element
+
+  // Add parent method
+, construct: {
+    // Get current matrix
+    ctm: function() {
+      return new SVG.Matrix(this.node.getCTM())
+    },
+    // Get current screen matrix
+    screenCTM: function() {
+      return new SVG.Matrix(this.node.getScreenCTM())
+    }
+
+  }
+
+})
+
+SVG.Point = SVG.invent({
+  // Initialize
+  create: function(x,y) {
+    var i, source, base = {x:0, y:0}
+
+    // ensure source as object
+    source = Array.isArray(x) ?
+      {x:x[0], y:x[1]} :
+    typeof x === 'object' ?
+      {x:x.x, y:x.y} :
+    x != null ?
+      {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value
+
+    // merge source
+    this.x = source.x
+    this.y = source.y
+  }
+
+  // Add methods
+, extend: {
+    // Clone point
+    clone: function() {
+      return new SVG.Point(this)
+    }
+    // Morph one point into another
+  , morph: function(x, y) {
+      // store new destination
+      this.destination = new SVG.Point(x, y)
+
+      return this
+    }
+    // Get morphed point at a given position
+  , at: function(pos) {
+      // make sure a destination is defined
+      if (!this.destination) return this
+
+      // calculate morphed matrix at a given position
+      var point = new SVG.Point({
+        x: this.x + (this.destination.x - this.x) * pos
+      , y: this.y + (this.destination.y - this.y) * pos
+      })
+
+      return point
+    }
+    // Convert to native SVGPoint
+  , native: function() {
+      // create new point
+      var point = SVG.parser.native.createSVGPoint()
+
+      // update with current values
+      point.x = this.x
+      point.y = this.y
+
+      return point
+    }
+    // transform point with matrix
+  , transform: function(matrix) {
+      return new SVG.Point(this.native().matrixTransform(matrix.native()))
+    }
+
+  }
+
+})
+
+SVG.extend(SVG.Element, {
+
+  // Get point
+  point: function(x, y) {
+    return new SVG.Point(x,y).transform(this.screenCTM().inverse());
+  }
+
+})
+
+SVG.extend(SVG.Element, {
+  // Set svg element attribute
+  attr: function(a, v, n) {
+    // act as full getter
+    if (a == null) {
+      // get an object of attributes
+      a = {}
+      v = this.node.attributes
+      for (n = v.length - 1; n >= 0; n--)
+        a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue
+
+      return a
+
+    } else if (typeof a == 'object') {
+      // apply every attribute individually if an object is passed
+      for (v in a) this.attr(v, a[v])
+
+    } else if (v === null) {
+        // remove value
+        this.node.removeAttribute(a)
+
+    } else if (v == null) {
+      // act as a getter if the first and only argument is not an object
+      v = this.node.getAttribute(a)
+      return v == null ?
+        SVG.defaults.attrs[a] :
+      SVG.regex.isNumber.test(v) ?
+        parseFloat(v) : v
+
+    } else {
+      // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0
+      if (a == 'stroke-width')
+        this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null)
+      else if (a == 'stroke')
+        this._stroke = v
+
+      // convert image fill and stroke to patterns
+      if (a == 'fill' || a == 'stroke') {
+        if (SVG.regex.isImage.test(v))
+          v = this.doc().defs().image(v, 0, 0)
+
+        if (v instanceof SVG.Image)
+          v = this.doc().defs().pattern(0, 0, function() {
+            this.add(v)
+          })
+      }
+
+      // ensure correct numeric values (also accepts NaN and Infinity)
+      if (typeof v === 'number')
+        v = new SVG.Number(v)
+
+      // ensure full hex color
+      else if (SVG.Color.isColor(v))
+        v = new SVG.Color(v)
+
+      // parse array values
+      else if (Array.isArray(v))
+        v = new SVG.Array(v)
+
+      // store parametric transformation values locally
+      else if (v instanceof SVG.Matrix && v.param)
+        this.param = v.param
+
+      // if the passed attribute is leading...
+      if (a == 'leading') {
+        // ... call the leading method instead
+        if (this.leading)
+          this.leading(v)
+      } else {
+        // set given attribute on node
+        typeof n === 'string' ?
+          this.node.setAttributeNS(n, a, v.toString()) :
+          this.node.setAttribute(a, v.toString())
+      }
+
+      // rebuild if required
+      if (this.rebuild && (a == 'font-size' || a == 'x'))
+        this.rebuild(a, v)
+    }
+
+    return this
+  }
+})
+SVG.extend(SVG.Element, {
+  // Add transformations
+  transform: function(o, relative) {
+    // get target in case of the fx module, otherwise reference this
+    var target = this
+      , matrix
+
+    // act as a getter
+    if (typeof o !== 'object') {
+      // get current matrix
+      matrix = new SVG.Matrix(target).extract()
+
+      return typeof o === 'string' ? matrix[o] : matrix
+    }
+
+    // get current matrix
+    matrix = new SVG.Matrix(target)
+
+    // ensure relative flag
+    relative = !!relative || !!o.relative
+
+    // act on matrix
+    if (o.a != null) {
+      matrix = relative ?
+        // relative
+        matrix.multiply(new SVG.Matrix(o)) :
+        // absolute
+        new SVG.Matrix(o)
+
+    // act on rotation
+    } else if (o.rotation != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // apply transformation
+      matrix = relative ?
+        // relative
+        matrix.rotate(o.rotation, o.cx, o.cy) :
+        // absolute
+        matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
+
+    // act on scale
+    } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // ensure scale values on both axes
+      o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
+      o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
+
+      if (!relative) {
+        // absolute; multiply inversed values
+        var e = matrix.extract()
+        o.scaleX = o.scaleX * 1 / e.scaleX
+        o.scaleY = o.scaleY * 1 / e.scaleY
+      }
+
+      matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy)
+
+    // act on skew
+    } else if (o.skew != null || o.skewX != null || o.skewY != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // ensure skew values on both axes
+      o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0
+      o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0
+
+      if (!relative) {
+        // absolute; reset skew values
+        var e = matrix.extract()
+        matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse())
+      }
+
+      matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy)
+
+    // act on flip
+    } else if (o.flip) {
+      matrix = matrix.flip(
+        o.flip
+      , o.offset == null ? target.bbox()['c' + o.flip] : o.offset
+      )
+
+    // act on translate
+    } else if (o.x != null || o.y != null) {
+      if (relative) {
+        // relative
+        matrix = matrix.translate(o.x, o.y)
+      } else {
+        // absolute
+        if (o.x != null) matrix.e = o.x
+        if (o.y != null) matrix.f = o.y
+      }
+    }
+
+    return this.attr('transform', matrix)
+  }
+})
+
+SVG.extend(SVG.FX, {
+  transform: function(o, relative) {
+    // get target in case of the fx module, otherwise reference this
+    var target = this.target()
+      , matrix
+
+    // act as a getter
+    if (typeof o !== 'object') {
+      // get current matrix
+      matrix = new SVG.Matrix(target).extract()
+
+      return typeof o === 'string' ? matrix[o] : matrix
+    }
+
+    // ensure relative flag
+    relative = !!relative || !!o.relative
+
+    // act on matrix
+    if (o.a != null) {
+      matrix = new SVG.Matrix(o)
+
+    // act on rotation
+    } else if (o.rotation != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // apply transformation
+      matrix = new SVG.Rotate(o.rotation, o.cx, o.cy)
+
+    // act on scale
+    } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // ensure scale values on both axes
+      o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
+      o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
+
+      matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy)
+
+    // act on skew
+    } else if (o.skewX != null || o.skewY != null) {
+      // ensure centre point
+      ensureCentre(o, target)
+
+      // ensure skew values on both axes
+      o.skewX = o.skewX != null ? o.skewX : 0
+      o.skewY = o.skewY != null ? o.skewY : 0
+
+      matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy)
+
+    // act on flip
+    } else if (o.flip) {
+      matrix = new SVG.Matrix().morph(new SVG.Matrix().flip(
+        o.flip
+      , o.offset == null ? target.bbox()['c' + o.flip] : o.offset
+      ))
+
+    // act on translate
+    } else if (o.x != null || o.y != null) {
+      matrix = new SVG.Translate(o.x, o.y)
+    }
+
+    if(!matrix) return this
+
+    matrix.relative = relative
+
+    this.last().transforms.push(matrix)
+
+    setTimeout(function(){this.start()}.bind(this), 0)
+
+    return this
+  }
+})
+
+SVG.extend(SVG.Element, {
+  // Reset all transformations
+  untransform: function() {
+    return this.attr('transform', null)
+  },
+  // merge the whole transformation chain into one matrix and returns it
+  matrixify: function() {
+
+    var matrix = (this.attr('transform') || '')
+      // split transformations
+      .split(/\)\s*,?\s*/).slice(0,-1).map(function(str){
+        // generate key => value pairs
+        var kv = str.trim().split('(')
+        return [kv[0], kv[1].split(SVG.regex.matrixElements).map(function(str){ return parseFloat(str) })]
+      })
+      // calculate every transformation into one matrix
+      .reduce(function(matrix, transform){
+
+        if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1]))
+        return matrix[transform[0]].apply(matrix, transform[1])
+
+      }, new SVG.Matrix())
+
+    return matrix
+  },
+  // add an element to another parent without changing the visual representation on the screen
+  toParent: function(parent) {
+    if(this == parent) return this
+    var ctm = this.screenCTM()
+    var temp = parent.rect(1,1)
+    var pCtm = temp.screenCTM().inverse()
+    temp.remove()
+
+    this.addTo(parent).untransform().transform(pCtm.multiply(ctm))
+
+    return this
+  },
+  // same as above with parent equals root-svg
+  toDoc: function() {
+    return this.toParent(this.doc())
+  }
+
+})
+
+SVG.Transformation = SVG.invent({
+
+  create: function(source, inversed){
+
+    if(arguments.length > 1 && typeof inversed != 'boolean'){
+      return this.create([].slice.call(arguments))
+    }
+
+    if(typeof source == 'object'){
+      for(var i = 0, len = this.arguments.length; i < len; ++i){
+        this[this.arguments[i]] = source[this.arguments[i]]
+      }
+    }
+
+    if(Array.isArray(source)){
+      for(var i = 0, len = this.arguments.length; i < len; ++i){
+        this[this.arguments[i]] = source[i]
+      }
+    }
+
+    this.inversed = false
+
+    if(inversed === true){
+      this.inversed = true
+    }
+
+  }
+
+, extend: {
+
+    at: function(pos){
+
+      var params = []
+
+      for(var i = 0, len = this.arguments.length; i < len; ++i){
+        params.push(this[this.arguments[i]])
+      }
+
+      var m = this._undo || new SVG.Matrix()
+
+      m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos)
+
+      return this.inversed ? m.inverse() : m
+
+    }
+
+  , undo: function(o){
+      for(var i = 0, len = this.arguments.length; i < len; ++i){
+        o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]]
+      }
+
+      // The method SVG.Matrix.extract which was used before calling this
+      // method to obtain a value for the parameter o doesn't return a cx and
+      // a cy so we use the ones that were provided to this object at its creation
+      o.cx = this.cx
+      o.cy = this.cy
+
+      this._undo = new SVG[capitalize(this.method)](o, true).at(1)
+
+      return this
+    }
+
+  }
+
+})
+
+SVG.Translate = SVG.invent({
+
+  parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+    if(typeof source == 'object') this.constructor.call(this, source, inversed)
+    else this.constructor.call(this, [].slice.call(arguments))
+  }
+
+, extend: {
+    arguments: ['transformedX', 'transformedY']
+  , method: 'translate'
+  }
+
+})
+
+SVG.Rotate = SVG.invent({
+
+  parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+    if(typeof source == 'object') this.constructor.call(this, source, inversed)
+    else this.constructor.call(this, [].slice.call(arguments))
+  }
+
+, extend: {
+    arguments: ['rotation', 'cx', 'cy']
+  , method: 'rotate'
+  , at: function(pos){
+      var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy)
+      return this.inversed ? m.inverse() : m
+    }
+  , undo: function(o){
+      this._undo = o
+    }
+  }
+
+})
+
+SVG.Scale = SVG.invent({
+
+  parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+    if(typeof source == 'object') this.constructor.call(this, source, inversed)
+    else this.constructor.call(this, [].slice.call(arguments))
+  }
+
+, extend: {
+    arguments: ['scaleX', 'scaleY', 'cx', 'cy']
+  , method: 'scale'
+  }
+
+})
+
+SVG.Skew = SVG.invent({
+
+  parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+    if(typeof source == 'object') this.constructor.call(this, source, inversed)
+    else this.constructor.call(this, [].slice.call(arguments))
+  }
+
+, extend: {
+    arguments: ['skewX', 'skewY', 'cx', 'cy']
+  , method: 'skew'
+  }
+
+})
+
+SVG.extend(SVG.Element, {
+  // Dynamic style generator
+  style: function(s, v) {
+    if (arguments.length == 0) {
+      // get full style
+      return this.node.style.cssText || ''
+
+    } else if (arguments.length < 2) {
+      // apply every style individually if an object is passed
+      if (typeof s == 'object') {
+        for (v in s) this.style(v, s[v])
+
+      } else if (SVG.regex.isCss.test(s)) {
+        // parse css string
+        s = s.split(';')
+
+        // apply every definition individually
+        for (var i = 0; i < s.length; i++) {
+          v = s[i].split(':')
+          this.style(v[0].replace(/\s+/g, ''), v[1])
+        }
+      } else {
+        // act as a getter if the first and only argument is not an object
+        return this.node.style[camelCase(s)]
+      }
+
+    } else {
+      this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v
+    }
+
+    return this
+  }
+})
+SVG.Parent = SVG.invent({
+  // Initialize node
+  create: function(element) {
+    this.constructor.call(this, element)
+  }
+
+  // Inherit from
+, inherit: SVG.Element
+
+  // Add class methods
+, extend: {
+    // Returns all child elements
+    children: function() {
+      return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) {
+        return SVG.adopt(node)
+      })
+    }
+    // Add given element at a position
+  , add: function(element, i) {
+      if (i == null)
+        this.node.appendChild(element.node)
+      else if (element.node != this.node.childNodes[i])
+        this.node.insertBefore(element.node, this.node.childNodes[i])
+
+      return this
+    }
+    // Basically does the same as `add()` but returns the added element instead
+  , put: function(element, i) {
+      this.add(element, i)
+      return element
+    }
+    // Checks if the given element is a child
+  , has: function(element) {
+      return this.index(element) >= 0
+    }
+    // Gets index of given element
+  , index: function(element) {
+      return [].slice.call(this.node.childNodes).indexOf(element.node)
+    }
+    // Get a element at the given index
+  , get: function(i) {
+      return SVG.adopt(this.node.childNodes[i])
+    }
+    // Get first child
+  , first: function() {
+      return this.get(0)
+    }
+    // Get the last child
+  , last: function() {
+      return this.get(this.node.childNodes.length - 1)
+    }
+    // Iterates over all children and invokes a given block
+  , each: function(block, deep) {
+      var i, il
+        , children = this.children()
+
+      for (i = 0, il = children.length; i < il; i++) {
+        if (children[i] instanceof SVG.Element)
+          block.apply(children[i], [i, children])
+
+        if (deep && (children[i] instanceof SVG.Container))
+          children[i].each(block, deep)
+      }
+
+      return this
+    }
+    // Remove a given child
+  , removeElement: function(element) {
+      this.node.removeChild(element.node)
+
+      return this
+    }
+    // Remove all elements in this container
+  , clear: function() {
+      // remove children
+      while(this.node.hasChildNodes())
+        this.node.removeChild(this.node.lastChild)
+
+      // remove defs reference
+      delete this._defs
+
+      return this
+    }
+  , // Get defs
+    defs: function() {
+      return this.doc().defs()
+    }
+  }
+
+})
+
+SVG.extend(SVG.Parent, {
+
+  ungroup: function(parent, depth) {
+    if(depth === 0 || this instanceof SVG.Defs) return this
+
+    parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent))
+    depth = depth || Infinity
+
+    this.each(function(){
+      if(this instanceof SVG.Defs) return this
+      if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1)
+      return this.toParent(parent)
+    })
+
+    this.node.firstChild || this.remove()
+
+    return this
+  },
+
+  flatten: function(parent, depth) {
+    return this.ungroup(parent, depth)
+  }
+
+})
+SVG.Container = SVG.invent({
+  // Initialize node
+  create: function(element) {
+    this.constructor.call(this, element)
+  }
+
+  // Inherit from
+, inherit: SVG.Parent
+
+})
+
+SVG.ViewBox = SVG.invent({
+
+  create: function(source) {
+    var i, base = [0, 0, 0, 0]
+
+    var x, y, width, height, box, view, we, he
+      , wm   = 1 // width multiplier
+      , hm   = 1 // height multiplier
+      , reg  = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi
+
+    if(source instanceof SVG.Element){
+
+      we = source
+      he = source
+      view = (source.attr('viewBox') || '').match(reg)
+      box = source.bbox
+
+      // get dimensions of current node
+      width  = new SVG.Number(source.width())
+      height = new SVG.Number(source.height())
+
+      // find nearest non-percentual dimensions
+      while (width.unit == '%') {
+        wm *= width.value
+        width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
+        we = we.parent()
+      }
+      while (height.unit == '%') {
+        hm *= height.value
+        height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
+        he = he.parent()
+      }
+
+      // ensure defaults
+      this.x      = 0
+      this.y      = 0
+      this.width  = width  * wm
+      this.height = height * hm
+      this.zoom   = 1
+
+      if (view) {
+        // get width and height from viewbox
+        x      = parseFloat(view[0])
+        y      = parseFloat(view[1])
+        width  = parseFloat(view[2])
+        height = parseFloat(view[3])
+
+        // calculate zoom accoring to viewbox
+        this.zoom = ((this.width / this.height) > (width / height)) ?
+          this.height / height :
+          this.width  / width
+
+        // calculate real pixel dimensions on parent SVG.Doc element
+        this.x      = x
+        this.y      = y
+        this.width  = width
+        this.height = height
+
+      }
+
+    }else{
+
+      // ensure source as object
+      source = typeof source === 'string' ?
+        source.match(reg).map(function(el){ return parseFloat(el) }) :
+      Array.isArray(source) ?
+        source :
+      typeof source == 'object' ?
+        [source.x, source.y, source.width, source.height] :
+      arguments.length == 4 ?
+        [].slice.call(arguments) :
+        base
+
+      this.x = source[0]
+      this.y = source[1]
+      this.width = source[2]
+      this.height = source[3]
+    }
+
+
+  }
+
+, extend: {
+
+    toString: function() {
+      return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+    }
+  , morph: function(v){
+
+      var v = arguments.length == 1 ?
+        [v.x, v.y, v.width, v.height] :
+        [].slice.call(arguments)
+
+      this.destination = new SVG.ViewBox(v)
+
+      return this
+
+    }
+
+  , at: function(pos) {
+
+    if(!this.destination) return this
+
+    return new SVG.ViewBox([
+        this.x + (this.destination.x - this.x) * pos
+      , this.y + (this.destination.y - this.y) * pos
+      , this.width + (this.destination.width - this.width) * pos
+      , this.height + (this.destination.height - this.height) * pos
+    ])
+
+    }
+
+  }
+
+  // Define parent
+, parent: SVG.Container
+
+  // Add parent method
+, construct: {
+
+    // get/set viewbox
+    viewbox: function(v) {
+      if (arguments.length == 0)
+        // act as a getter if there are no arguments
+        return new SVG.ViewBox(this)
+
+      // otherwise act as a setter
+      v = arguments.length == 1 ?
+        [v.x, v.y, v.width, v.height] :
+        [].slice.call(arguments)
+
+      return this.attr('viewBox', v)
+    }
+
+  }
+
+})
+// Add events to elements
+;[  'click'
+  , 'dblclick'
+  , 'mousedown'
+  , 'mouseup'
+  , 'mouseover'
+  , 'mouseout'
+  , 'mousemove'
+  // , 'mouseenter' -> not supported by IE
+  // , 'mouseleave' -> not supported by IE
+  , 'touchstart'
+  , 'touchmove'
+  , 'touchleave'
+  , 'touchend'
+  , 'touchcancel' ].forEach(function(event) {
+
+  // add event to SVG.Element
+  SVG.Element.prototype[event] = function(f) {
+    var self = this
+
+    // bind event to element rather than element node
+    this.node['on' + event] = typeof f == 'function' ?
+      function() { return f.apply(self, arguments) } : null
+
+    return this
+  }
+
+})
+
+// Initialize listeners stack
+SVG.listeners = []
+SVG.handlerMap = []
+SVG.listenerId = 0
+
+// Add event binder in the SVG namespace
+SVG.on = function(node, event, listener, binding) {
+  // create listener, get object-index
+  var l     = listener.bind(binding || node.instance || node)
+    , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1
+    , ev    = event.split('.')[0]
+    , ns    = event.split('.')[1] || '*'
+
+
+  // ensure valid object
+  SVG.listeners[index]         = SVG.listeners[index]         || {}
+  SVG.listeners[index][ev]     = SVG.listeners[index][ev]     || {}
+  SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {}
+
+  if(!listener._svgjsListenerId)
+    listener._svgjsListenerId = ++SVG.listenerId
+
+  // reference listener
+  SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l
+
+  // add listener
+  node.addEventListener(ev, l, false)
+}
+
+// Add event unbinder in the SVG namespace
+SVG.off = function(node, event, listener) {
+  var index = SVG.handlerMap.indexOf(node)
+    , ev    = event && event.split('.')[0]
+    , ns    = event && event.split('.')[1]
+
+  if(index == -1) return
+
+  if (listener) {
+    if(typeof listener == 'function') listener = listener._svgjsListenerId
+    if(!listener) return
+
+    // remove listener reference
+    if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) {
+      // remove listener
+      node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false)
+
+      delete SVG.listeners[index][ev][ns || '*'][listener]
+    }
+
+  } else if (ns && ev) {
+    // remove all listeners for a namespaced event
+    if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) {
+      for (listener in SVG.listeners[index][ev][ns])
+        SVG.off(node, [ev, ns].join('.'), listener)
+
+      delete SVG.listeners[index][ev][ns]
+    }
+
+  } else if (ns){
+    // remove all listeners for a specific namespace
+    for(event in SVG.listeners[index]){
+        for(namespace in SVG.listeners[index][event]){
+            if(ns === namespace){
+                SVG.off(node, [event, ns].join('.'))
+            }
+        }
+    }
+
+  } else if (ev) {
+    // remove all listeners for the event
+    if (SVG.listeners[index][ev]) {
+      for (namespace in SVG.listeners[index][ev])
+        SVG.off(node, [ev, namespace].join('.'))
+
+      delete SVG.listeners[index][ev]
+    }
+
+  } else {
+    // remove all listeners on a given node
+    for (event in SVG.listeners[index])
+      SVG.off(node, event)
+
+    delete SVG.listeners[index]
+
+  }
+}
+
+//
+SVG.extend(SVG.Element, {
+  // Bind given event to listener
+  on: function(event, listener, binding) {
+    SVG.on(this.node, event, listener, binding)
+
+    return this
+  }
+  // Unbind event from listener
+, off: function(event, listener) {
+    SVG.off(this.node, event, listener)
+
+    return this
+  }
+  // Fire given event
+, fire: function(event, data) {
+
+    // Dispatch event
+    if(event instanceof Event){
+        this.node.dispatchEvent(event)
+    }else{
+        this.node.dispatchEvent(new CustomEvent(event, {detail:data}))
+    }
+
+    return this
+  }
+})
+
+SVG.Defs = SVG.invent({
+  // Initialize node
+  create: 'defs'
+
+  // Inherit from
+, inherit: SVG.Container
+
+})
+SVG.G = SVG.invent({
+  // Initialize node
+  create: 'g'
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Move over x-axis
+    x: function(x) {
+      return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true)
+    }
+    // Move over y-axis
+  , y: function(y) {
+      return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true)
+    }
+    // Move by center over x-axis
+  , cx: function(x) {
+      return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2)
+    }
+    // Move by center over y-axis
+  , cy: function(y) {
+      return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2)
+    }
+  , gbox: function() {
+
+      var bbox  = this.bbox()
+        , trans = this.transform()
+
+      bbox.x  += trans.x
+      bbox.x2 += trans.x
+      bbox.cx += trans.x
+
+      bbox.y  += trans.y
+      bbox.y2 += trans.y
+      bbox.cy += trans.y
+
+      return bbox
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create a group element
+    group: function() {
+      return this.put(new SVG.G)
+    }
+  }
+})
+
+// ### This module adds backward / forward functionality to elements.
+
+//
+SVG.extend(SVG.Element, {
+  // Get all siblings, including myself
+  siblings: function() {
+    return this.parent().children()
+  }
+  // Get the curent position siblings
+, position: function() {
+    return this.parent().index(this)
+  }
+  // Get the next element (will return null if there is none)
+, next: function() {
+    return this.siblings()[this.position() + 1]
+  }
+  // Get the next element (will return null if there is none)
+, previous: function() {
+    return this.siblings()[this.position() - 1]
+  }
+  // Send given element one step forward
+, forward: function() {
+    var i = this.position() + 1
+      , p = this.parent()
+
+    // move node one step forward
+    p.removeElement(this).add(this, i)
+
+    // make sure defs node is always at the top
+    if (p instanceof SVG.Doc)
+      p.node.appendChild(p.defs().node)
+
+    return this
+  }
+  // Send given element one step backward
+, backward: function() {
+    var i = this.position()
+
+    if (i > 0)
+      this.parent().removeElement(this).add(this, i - 1)
+
+    return this
+  }
+  // Send given element all the way to the front
+, front: function() {
+    var p = this.parent()
+
+    // Move node forward
+    p.node.appendChild(this.node)
+
+    // Make sure defs node is always at the top
+    if (p instanceof SVG.Doc)
+      p.node.appendChild(p.defs().node)
+
+    return this
+  }
+  // Send given element all the way to the back
+, back: function() {
+    if (this.position() > 0)
+      this.parent().removeElement(this).add(this, 0)
+
+    return this
+  }
+  // Inserts a given element before the targeted element
+, before: function(element) {
+    element.remove()
+
+    var i = this.position()
+
+    this.parent().add(element, i)
+
+    return this
+  }
+  // Insters a given element after the targeted element
+, after: function(element) {
+    element.remove()
+
+    var i = this.position()
+
+    this.parent().add(element, i + 1)
+
+    return this
+  }
+
+})
+SVG.Mask = SVG.invent({
+  // Initialize node
+  create: function() {
+    this.constructor.call(this, SVG.create('mask'))
+
+    // keep references to masked elements
+    this.targets = []
+  }
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Unmask all masked elements and remove itself
+    remove: function() {
+      // unmask all targets
+      for (var i = this.targets.length - 1; i >= 0; i--)
+        if (this.targets[i])
+          this.targets[i].unmask()
+      this.targets = []
+
+      // remove mask from parent
+      this.parent().removeElement(this)
+
+      return this
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create masking element
+    mask: function() {
+      return this.defs().put(new SVG.Mask)
+    }
+  }
+})
+
+
+SVG.extend(SVG.Element, {
+  // Distribute mask to svg element
+  maskWith: function(element) {
+    // use given mask or create a new one
+    this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element)
+
+    // store reverence on self in mask
+    this.masker.targets.push(this)
+
+    // apply mask
+    return this.attr('mask', 'url("#' + this.masker.attr('id') + '")')
+  }
+  // Unmask element
+, unmask: function() {
+    delete this.masker
+    return this.attr('mask', null)
+  }
+
+})
+
+SVG.ClipPath = SVG.invent({
+  // Initialize node
+  create: function() {
+    this.constructor.call(this, SVG.create('clipPath'))
+
+    // keep references to clipped elements
+    this.targets = []
+  }
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Unclip all clipped elements and remove itself
+    remove: function() {
+      // unclip all targets
+      for (var i = this.targets.length - 1; i >= 0; i--)
+        if (this.targets[i])
+          this.targets[i].unclip()
+      this.targets = []
+
+      // remove clipPath from parent
+      this.parent().removeElement(this)
+
+      return this
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create clipping element
+    clip: function() {
+      return this.defs().put(new SVG.ClipPath)
+    }
+  }
+})
+
+//
+SVG.extend(SVG.Element, {
+  // Distribute clipPath to svg element
+  clipWith: function(element) {
+    // use given clip or create a new one
+    this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element)
+
+    // store reverence on self in mask
+    this.clipper.targets.push(this)
+
+    // apply mask
+    return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")')
+  }
+  // Unclip element
+, unclip: function() {
+    delete this.clipper
+    return this.attr('clip-path', null)
+  }
+
+})
+SVG.Gradient = SVG.invent({
+  // Initialize node
+  create: function(type) {
+    this.constructor.call(this, SVG.create(type + 'Gradient'))
+
+    // store type
+    this.type = type
+  }
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Add a color stop
+    at: function(offset, color, opacity) {
+      return this.put(new SVG.Stop).update(offset, color, opacity)
+    }
+    // Update gradient
+  , update: function(block) {
+      // remove all stops
+      this.clear()
+
+      // invoke passed block
+      if (typeof block == 'function')
+        block.call(this, this)
+
+      return this
+    }
+    // Return the fill id
+  , fill: function() {
+      return 'url(#' + this.id() + ')'
+    }
+    // Alias string convertion to fill
+  , toString: function() {
+      return this.fill()
+    }
+    // custom attr to handle transform
+  , attr: function(a, b, c) {
+      if(a == 'transform') a = 'gradientTransform'
+      return SVG.Container.prototype.attr.call(this, a, b, c)
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create gradient element in defs
+    gradient: function(type, block) {
+      return this.defs().gradient(type, block)
+    }
+  }
+})
+
+// Add animatable methods to both gradient and fx module
+SVG.extend(SVG.Gradient, SVG.FX, {
+  // From position
+  from: function(x, y) {
+    return (this._target || this).type == 'radial' ?
+      this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) :
+      this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) })
+  }
+  // To position
+, to: function(x, y) {
+    return (this._target || this).type == 'radial' ?
+      this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) :
+      this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) })
+  }
+})
+
+// Base gradient generation
+SVG.extend(SVG.Defs, {
+  // define gradient
+  gradient: function(type, block) {
+    return this.put(new SVG.Gradient(type)).update(block)
+  }
+
+})
+
+SVG.Stop = SVG.invent({
+  // Initialize node
+  create: 'stop'
+
+  // Inherit from
+, inherit: SVG.Element
+
+  // Add class methods
+, extend: {
+    // add color stops
+    update: function(o) {
+      if (typeof o == 'number' || o instanceof SVG.Number) {
+        o = {
+          offset:  arguments[0]
+        , color:   arguments[1]
+        , opacity: arguments[2]
+        }
+      }
+
+      // set attributes
+      if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+      if (o.color   != null) this.attr('stop-color', o.color)
+      if (o.offset  != null) this.attr('offset', new SVG.Number(o.offset))
+
+      return this
+    }
+  }
+
+})
+
+SVG.Pattern = SVG.invent({
+  // Initialize node
+  create: 'pattern'
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Return the fill id
+    fill: function() {
+      return 'url(#' + this.id() + ')'
+    }
+    // Update pattern by rebuilding
+  , update: function(block) {
+      // remove content
+      this.clear()
+
+      // invoke passed block
+      if (typeof block == 'function')
+        block.call(this, this)
+
+      return this
+    }
+    // Alias string convertion to fill
+  , toString: function() {
+      return this.fill()
+    }
+    // custom attr to handle transform
+  , attr: function(a, b, c) {
+      if(a == 'transform') a = 'patternTransform'
+      return SVG.Container.prototype.attr.call(this, a, b, c)
+    }
+
+  }
+
+  // Add parent method
+, construct: {
+    // Create pattern element in defs
+    pattern: function(width, height, block) {
+      return this.defs().pattern(width, height, block)
+    }
+  }
+})
+
+SVG.extend(SVG.Defs, {
+  // Define gradient
+  pattern: function(width, height, block) {
+    return this.put(new SVG.Pattern).update(block).attr({
+      x:            0
+    , y:            0
+    , width:        width
+    , height:       height
+    , patternUnits: 'userSpaceOnUse'
+    })
+  }
+
+})
+SVG.Doc = SVG.invent({
+  // Initialize node
+  create: function(element) {
+    if (element) {
+      // ensure the presence of a dom element
+      element = typeof element == 'string' ?
+        document.getElementById(element) :
+        element
+
+      // If the target is an svg element, use that element as the main wrapper.
+      // This allows svg.js to work with svg documents as well.
+      if (element.nodeName == 'svg') {
+        this.constructor.call(this, element)
+      } else {
+        this.constructor.call(this, SVG.create('svg'))
+        element.appendChild(this.node)
+        this.size('100%', '100%')
+      }
+
+      // set svg element attributes and ensure defs node
+      this.namespace().defs()
+    }
+  }
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Add namespaces
+    namespace: function() {
+      return this
+        .attr({ xmlns: SVG.ns, version: '1.1' })
+        .attr('xmlns:xlink', SVG.xlink, SVG.xmlns)
+        .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns)
+    }
+    // Creates and returns defs element
+  , defs: function() {
+      if (!this._defs) {
+        var defs
+
+        // Find or create a defs element in this instance
+        if (defs = this.node.getElementsByTagName('defs')[0])
+          this._defs = SVG.adopt(defs)
+        else
+          this._defs = new SVG.Defs
+
+        // Make sure the defs node is at the end of the stack
+        this.node.appendChild(this._defs.node)
+      }
+
+      return this._defs
+    }
+    // custom parent method
+  , parent: function() {
+      return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode
+    }
+    // Fix for possible sub-pixel offset. See:
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=608812
+  , spof: function(spof) {
+      var pos = this.node.getScreenCTM()
+
+      if (pos)
+        this
+          .style('left', (-pos.e % 1) + 'px')
+          .style('top',  (-pos.f % 1) + 'px')
+
+      return this
+    }
+
+      // Removes the doc from the DOM
+  , remove: function() {
+      if(this.parent()) {
+        this.parent().removeChild(this.node);
+      }
+
+      return this;
+    }
+  }
+
+})
+
+SVG.Shape = SVG.invent({
+  // Initialize node
+  create: function(element) {
+    this.constructor.call(this, element)
+  }
+
+  // Inherit from
+, inherit: SVG.Element
+
+})
+
+SVG.Bare = SVG.invent({
+  // Initialize
+  create: function(element, inherit) {
+    // construct element
+    this.constructor.call(this, SVG.create(element))
+
+    // inherit custom methods
+    if (inherit)
+      for (var method in inherit.prototype)
+        if (typeof inherit.prototype[method] === 'function')
+          this[method] = inherit.prototype[method]
+  }
+
+  // Inherit from
+, inherit: SVG.Element
+
+  // Add methods
+, extend: {
+    // Insert some plain text
+    words: function(text) {
+      // remove contents
+      while (this.node.hasChildNodes())
+        this.node.removeChild(this.node.lastChild)
+
+      // create text node
+      this.node.appendChild(document.createTextNode(text))
+
+      return this
+    }
+  }
+})
+
+
+SVG.extend(SVG.Parent, {
+  // Create an element that is not described by SVG.js
+  element: function(element, inherit) {
+    return this.put(new SVG.Bare(element, inherit))
+  }
+  // Add symbol element
+, symbol: function() {
+    return this.defs().element('symbol', SVG.Container)
+  }
+
+})
+SVG.Use = SVG.invent({
+  // Initialize node
+  create: 'use'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // Use element as a reference
+    element: function(element, file) {
+      // Set lined element
+      return this.attr('href', (file || '') + '#' + element, SVG.xlink)
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create a use element
+    use: function(element, file) {
+      return this.put(new SVG.Use).element(element, file)
+    }
+  }
+})
+SVG.Rect = SVG.invent({
+  // Initialize node
+  create: 'rect'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add parent method
+, construct: {
+    // Create a rect element
+    rect: function(width, height) {
+      return this.put(new SVG.Rect()).size(width, height)
+    }
+  }
+})
+SVG.Circle = SVG.invent({
+  // Initialize node
+  create: 'circle'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add parent method
+, construct: {
+    // Create circle element, based on ellipse
+    circle: function(size) {
+      return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0)
+    }
+  }
+})
+
+SVG.extend(SVG.Circle, SVG.FX, {
+  // Radius x value
+  rx: function(rx) {
+    return this.attr('r', rx)
+  }
+  // Alias radius x value
+, ry: function(ry) {
+    return this.rx(ry)
+  }
+})
+
+SVG.Ellipse = SVG.invent({
+  // Initialize node
+  create: 'ellipse'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add parent method
+, construct: {
+    // Create an ellipse
+    ellipse: function(width, height) {
+      return this.put(new SVG.Ellipse).size(width, height).move(0, 0)
+    }
+  }
+})
+
+SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, {
+  // Radius x value
+  rx: function(rx) {
+    return this.attr('rx', rx)
+  }
+  // Radius y value
+, ry: function(ry) {
+    return this.attr('ry', ry)
+  }
+})
+
+// Add common method
+SVG.extend(SVG.Circle, SVG.Ellipse, {
+    // Move over x-axis
+    x: function(x) {
+      return x == null ? this.cx() - this.rx() : this.cx(x + this.rx())
+    }
+    // Move over y-axis
+  , y: function(y) {
+      return y == null ? this.cy() - this.ry() : this.cy(y + this.ry())
+    }
+    // Move by center over x-axis
+  , cx: function(x) {
+      return x == null ? this.attr('cx') : this.attr('cx', x)
+    }
+    // Move by center over y-axis
+  , cy: function(y) {
+      return y == null ? this.attr('cy') : this.attr('cy', y)
+    }
+    // Set width of element
+  , width: function(width) {
+      return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2))
+    }
+    // Set height of element
+  , height: function(height) {
+      return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2))
+    }
+    // Custom size function
+  , size: function(width, height) {
+      var p = proportionalSize(this, width, height)
+
+      return this
+        .rx(new SVG.Number(p.width).divide(2))
+        .ry(new SVG.Number(p.height).divide(2))
+    }
+})
+SVG.Line = SVG.invent({
+  // Initialize node
+  create: 'line'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // Get array
+    array: function() {
+      return new SVG.PointArray([
+        [ this.attr('x1'), this.attr('y1') ]
+      , [ this.attr('x2'), this.attr('y2') ]
+      ])
+    }
+    // Overwrite native plot() method
+  , plot: function(x1, y1, x2, y2) {
+      if (typeof y1 !== 'undefined')
+        x1 = { x1: x1, y1: y1, x2: x2, y2: y2 }
+      else
+        x1 = new SVG.PointArray(x1).toLine()
+
+      return this.attr(x1)
+    }
+    // Move by left top corner
+  , move: function(x, y) {
+      return this.attr(this.array().move(x, y).toLine())
+    }
+    // Set element size to given width and height
+  , size: function(width, height) {
+      var p = proportionalSize(this, width, height)
+
+      return this.attr(this.array().size(p.width, p.height).toLine())
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create a line element
+    line: function(x1, y1, x2, y2) {
+      return this.put(new SVG.Line).plot(x1, y1, x2, y2)
+    }
+  }
+})
+
+SVG.Polyline = SVG.invent({
+  // Initialize node
+  create: 'polyline'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add parent method
+, construct: {
+    // Create a wrapped polyline element
+    polyline: function(p) {
+      return this.put(new SVG.Polyline).plot(p)
+    }
+  }
+})
+
+SVG.Polygon = SVG.invent({
+  // Initialize node
+  create: 'polygon'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add parent method
+, construct: {
+    // Create a wrapped polygon element
+    polygon: function(p) {
+      return this.put(new SVG.Polygon).plot(p)
+    }
+  }
+})
+
+// Add polygon-specific functions
+SVG.extend(SVG.Polyline, SVG.Polygon, {
+  // Get array
+  array: function() {
+    return this._array || (this._array = new SVG.PointArray(this.attr('points')))
+  }
+  // Plot new path
+, plot: function(p) {
+    return this.attr('points', (this._array = new SVG.PointArray(p)))
+  }
+  // Move by left top corner
+, move: function(x, y) {
+    return this.attr('points', this.array().move(x, y))
+  }
+  // Set element size to given width and height
+, size: function(width, height) {
+    var p = proportionalSize(this, width, height)
+
+    return this.attr('points', this.array().size(p.width, p.height))
+  }
+
+})
+// unify all point to point elements
+SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, {
+  // Define morphable array
+  morphArray:  SVG.PointArray
+  // Move by left top corner over x-axis
+, x: function(x) {
+    return x == null ? this.bbox().x : this.move(x, this.bbox().y)
+  }
+  // Move by left top corner over y-axis
+, y: function(y) {
+    return y == null ? this.bbox().y : this.move(this.bbox().x, y)
+  }
+  // Set width of element
+, width: function(width) {
+    var b = this.bbox()
+
+    return width == null ? b.width : this.size(width, b.height)
+  }
+  // Set height of element
+, height: function(height) {
+    var b = this.bbox()
+
+    return height == null ? b.height : this.size(b.width, height)
+  }
+})
+SVG.Path = SVG.invent({
+  // Initialize node
+  create: 'path'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // Define morphable array
+    morphArray:  SVG.PathArray
+    // Get array
+  , array: function() {
+      return this._array || (this._array = new SVG.PathArray(this.attr('d')))
+    }
+    // Plot new poly points
+  , plot: function(p) {
+      return this.attr('d', (this._array = new SVG.PathArray(p)))
+    }
+    // Move by left top corner
+  , move: function(x, y) {
+      return this.attr('d', this.array().move(x, y))
+    }
+    // Move by left top corner over x-axis
+  , x: function(x) {
+      return x == null ? this.bbox().x : this.move(x, this.bbox().y)
+    }
+    // Move by left top corner over y-axis
+  , y: function(y) {
+      return y == null ? this.bbox().y : this.move(this.bbox().x, y)
+    }
+    // Set element size to given width and height
+  , size: function(width, height) {
+      var p = proportionalSize(this, width, height)
+
+      return this.attr('d', this.array().size(p.width, p.height))
+    }
+    // Set width of element
+  , width: function(width) {
+      return width == null ? this.bbox().width : this.size(width, this.bbox().height)
+    }
+    // Set height of element
+  , height: function(height) {
+      return height == null ? this.bbox().height : this.size(this.bbox().width, height)
+    }
+
+  }
+
+  // Add parent method
+, construct: {
+    // Create a wrapped path element
+    path: function(d) {
+      return this.put(new SVG.Path).plot(d)
+    }
+  }
+})
+SVG.Image = SVG.invent({
+  // Initialize node
+  create: 'image'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // (re)load image
+    load: function(url) {
+      if (!url) return this
+
+      var self = this
+        , img  = document.createElement('img')
+
+      // preload image
+      img.onload = function() {
+        var p = self.parent(SVG.Pattern)
+
+        if(p === null) return
+
+        // ensure image size
+        if (self.width() == 0 && self.height() == 0)
+          self.size(img.width, img.height)
+
+        // ensure pattern size if not set
+        if (p && p.width() == 0 && p.height() == 0)
+          p.size(self.width(), self.height())
+
+        // callback
+        if (typeof self._loaded === 'function')
+          self._loaded.call(self, {
+            width:  img.width
+          , height: img.height
+          , ratio:  img.width / img.height
+          , url:    url
+          })
+      }
+
+      img.onerror = function(e){
+        if (typeof self._error === 'function'){
+            self._error.call(self, e)
+        }
+      }
+
+      return this.attr('href', (img.src = this.src = url), SVG.xlink)
+    }
+    // Add loaded callback
+  , loaded: function(loaded) {
+      this._loaded = loaded
+      return this
+    }
+
+  , error: function(error) {
+      this._error = error
+      return this
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // create image element, load image and set its size
+    image: function(source, width, height) {
+      return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0)
+    }
+  }
+
+})
+SVG.Text = SVG.invent({
+  // Initialize node
+  create: function() {
+    this.constructor.call(this, SVG.create('text'))
+
+    this.dom.leading = new SVG.Number(1.3)    // store leading value for rebuilding
+    this._rebuild = true                      // enable automatic updating of dy values
+    this._build   = false                     // disable build mode for adding multiple lines
+
+    // set default font
+    this.attr('font-family', SVG.defaults.attrs['font-family'])
+  }
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // Move over x-axis
+    x: function(x) {
+      // act as getter
+      if (x == null)
+        return this.attr('x')
+
+      // move lines as well if no textPath is present
+      if (!this.textPath)
+        this.lines().each(function() { if (this.dom.newLined) this.x(x) })
+
+      return this.attr('x', x)
+    }
+    // Move over y-axis
+  , y: function(y) {
+      var oy = this.attr('y')
+        , o  = typeof oy === 'number' ? oy - this.bbox().y : 0
+
+      // act as getter
+      if (y == null)
+        return typeof oy === 'number' ? oy - o : oy
+
+      return this.attr('y', typeof y === 'number' ? y + o : y)
+    }
+    // Move center over x-axis
+  , cx: function(x) {
+      return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2)
+    }
+    // Move center over y-axis
+  , cy: function(y) {
+      return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2)
+    }
+    // Set the text content
+  , text: function(text) {
+      // act as getter
+      if (typeof text === 'undefined'){
+        var text = ''
+        var children = this.node.childNodes
+        for(var i = 0, len = children.length; i < len; ++i){
+
+          // add newline if its not the first child and newLined is set to true
+          if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){
+            text += '\n'
+          }
+
+          // add content of this node
+          text += children[i].textContent
+        }
+
+        return text
+      }
+
+      // remove existing content
+      this.clear().build(true)
+
+      if (typeof text === 'function') {
+        // call block
+        text.call(this, this)
+
+      } else {
+        // store text and make sure text is not blank
+        text = text.split('\n')
+
+        // build new lines
+        for (var i = 0, il = text.length; i < il; i++)
+          this.tspan(text[i]).newLine()
+      }
+
+      // disable build mode and rebuild lines
+      return this.build(false).rebuild()
+    }
+    // Set font size
+  , size: function(size) {
+      return this.attr('font-size', size).rebuild()
+    }
+    // Set / get leading
+  , leading: function(value) {
+      // act as getter
+      if (value == null)
+        return this.dom.leading
+
+      // act as setter
+      this.dom.leading = new SVG.Number(value)
+
+      return this.rebuild()
+    }
+    // Get all the first level lines
+  , lines: function() {
+      var node = (this.textPath && this.textPath() || this).node
+
+      // filter tspans and map them to SVG.js instances
+      var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){
+        return SVG.adopt(el)
+      })
+
+      // return an instance of SVG.set
+      return new SVG.Set(lines)
+    }
+    // Rebuild appearance type
+  , rebuild: function(rebuild) {
+      // store new rebuild flag if given
+      if (typeof rebuild == 'boolean')
+        this._rebuild = rebuild
+
+      // define position of all lines
+      if (this._rebuild) {
+        var self = this
+          , blankLineOffset = 0
+          , dy = this.dom.leading * new SVG.Number(this.attr('font-size'))
+
+        this.lines().each(function() {
+          if (this.dom.newLined) {
+            if (!this.textPath)
+              this.attr('x', self.attr('x'))
+
+            if(this.text() == '\n') {
+              blankLineOffset += dy
+            }else{
+              this.attr('dy', dy + blankLineOffset)
+              blankLineOffset = 0
+            }
+          }
+        })
+
+        this.fire('rebuild')
+      }
+
+      return this
+    }
+    // Enable / disable build mode
+  , build: function(build) {
+      this._build = !!build
+      return this
+    }
+    // overwrite method from parent to set data properly
+  , setData: function(o){
+      this.dom = o
+      this.dom.leading = new SVG.Number(o.leading || 1.3)
+      return this
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create text element
+    text: function(text) {
+      return this.put(new SVG.Text).text(text)
+    }
+    // Create plain text element
+  , plain: function(text) {
+      return this.put(new SVG.Text).plain(text)
+    }
+  }
+
+})
+
+SVG.Tspan = SVG.invent({
+  // Initialize node
+  create: 'tspan'
+
+  // Inherit from
+, inherit: SVG.Shape
+
+  // Add class methods
+, extend: {
+    // Set text content
+    text: function(text) {
+      if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
+
+      typeof text === 'function' ? text.call(this, this) : this.plain(text)
+
+      return this
+    }
+    // Shortcut dx
+  , dx: function(dx) {
+      return this.attr('dx', dx)
+    }
+    // Shortcut dy
+  , dy: function(dy) {
+      return this.attr('dy', dy)
+    }
+    // Create new line
+  , newLine: function() {
+      // fetch text parent
+      var t = this.parent(SVG.Text)
+
+      // mark new line
+      this.dom.newLined = true
+
+      // apply new hy¡n
+      return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x())
+    }
+  }
+
+})
+
+SVG.extend(SVG.Text, SVG.Tspan, {
+  // Create plain text node
+  plain: function(text) {
+    // clear if build mode is disabled
+    if (this._build === false)
+      this.clear()
+
+    // create text node
+    this.node.appendChild(document.createTextNode(text))
+
+    return this
+  }
+  // Create a tspan
+, tspan: function(text) {
+    var node  = (this.textPath && this.textPath() || this).node
+      , tspan = new SVG.Tspan
+
+    // clear if build mode is disabled
+    if (this._build === false)
+      this.clear()
+
+    // add new tspan
+    node.appendChild(tspan.node)
+
+    return tspan.text(text)
+  }
+  // Clear all lines
+, clear: function() {
+    var node = (this.textPath && this.textPath() || this).node
+
+    // remove existing child nodes
+    while (node.hasChildNodes())
+      node.removeChild(node.lastChild)
+
+    return this
+  }
+  // Get length of text element
+, length: function() {
+    return this.node.getComputedTextLength()
+  }
+})
+
+SVG.TextPath = SVG.invent({
+  // Initialize node
+  create: 'textPath'
+
+  // Inherit from
+, inherit: SVG.Parent
+
+  // Define parent class
+, parent: SVG.Text
+
+  // Add parent method
+, construct: {
+    // Create path for text to run on
+    path: function(d) {
+      // create textPath element
+      var path  = new SVG.TextPath
+        , track = this.doc().defs().path(d)
+
+      // move lines to textpath
+      while (this.node.hasChildNodes())
+        path.node.appendChild(this.node.firstChild)
+
+      // add textPath element as child node
+      this.node.appendChild(path.node)
+
+      // link textPath to path and add content
+      path.attr('href', '#' + track, SVG.xlink)
+
+      return this
+    }
+    // Plot path if any
+  , plot: function(d) {
+      var track = this.track()
+
+      if (track)
+        track.plot(d)
+
+      return this
+    }
+    // Get the path track element
+  , track: function() {
+      var path = this.textPath()
+
+      if (path)
+        return path.reference('href')
+    }
+    // Get the textPath child
+  , textPath: function() {
+      if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath')
+        return SVG.adopt(this.node.firstChild)
+    }
+  }
+})
+SVG.Nested = SVG.invent({
+  // Initialize node
+  create: function() {
+    this.constructor.call(this, SVG.create('svg'))
+
+    this.style('overflow', 'visible')
+  }
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add parent method
+, construct: {
+    // Create nested svg document
+    nested: function() {
+      return this.put(new SVG.Nested)
+    }
+  }
+})
+SVG.A = SVG.invent({
+  // Initialize node
+  create: 'a'
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Link url
+    to: function(url) {
+      return this.attr('href', url, SVG.xlink)
+    }
+    // Link show attribute
+  , show: function(target) {
+      return this.attr('show', target, SVG.xlink)
+    }
+    // Link target attribute
+  , target: function(target) {
+      return this.attr('target', target)
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create a hyperlink element
+    link: function(url) {
+      return this.put(new SVG.A).to(url)
+    }
+  }
+})
+
+SVG.extend(SVG.Element, {
+  // Create a hyperlink element
+  linkTo: function(url) {
+    var link = new SVG.A
+
+    if (typeof url == 'function')
+      url.call(link, link)
+    else
+      link.to(url)
+
+    return this.parent().put(link).put(this)
+  }
+
+})
+SVG.Marker = SVG.invent({
+  // Initialize node
+  create: 'marker'
+
+  // Inherit from
+, inherit: SVG.Container
+
+  // Add class methods
+, extend: {
+    // Set width of element
+    width: function(width) {
+      return this.attr('markerWidth', width)
+    }
+    // Set height of element
+  , height: function(height) {
+      return this.attr('markerHeight', height)
+    }
+    // Set marker refX and refY
+  , ref: function(x, y) {
+      return this.attr('refX', x).attr('refY', y)
+    }
+    // Update marker
+  , update: function(block) {
+      // remove all content
+      this.clear()
+
+      // invoke passed block
+      if (typeof block == 'function')
+        block.call(this, this)
+
+      return this
+    }
+    // Return the fill id
+  , toString: function() {
+      return 'url(#' + this.id() + ')'
+    }
+  }
+
+  // Add parent method
+, construct: {
+    marker: function(width, height, block) {
+      // Create marker element in defs
+      return this.defs().marker(width, height, block)
+    }
+  }
+
+})
+
+SVG.extend(SVG.Defs, {
+  // Create marker
+  marker: function(width, height, block) {
+    // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
+    return this.put(new SVG.Marker)
+      .size(width, height)
+      .ref(width / 2, height / 2)
+      .viewbox(0, 0, width, height)
+      .attr('orient', 'auto')
+      .update(block)
+  }
+
+})
+
+SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, {
+  // Create and attach markers
+  marker: function(marker, width, height, block) {
+    var attr = ['marker']
+
+    // Build attribute name
+    if (marker != 'all') attr.push(marker)
+    attr = attr.join('-')
+
+    // Set marker attribute
+    marker = arguments[1] instanceof SVG.Marker ?
+      arguments[1] :
+      this.doc().marker(width, height, block)
+
+    return this.attr(attr, marker)
+  }
+
+})
+// Define list of available attributes for stroke and fill
+var sugar = {
+  stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset']
+, fill:   ['color', 'opacity', 'rule']
+, prefix: function(t, a) {
+    return a == 'color' ? t : t + '-' + a
+  }
+}
+
+// Add sugar for fill and stroke
+;['fill', 'stroke'].forEach(function(m) {
+  var i, extension = {}
+
+  extension[m] = function(o) {
+    if (typeof o == 'undefined')
+      return this
+    if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function'))
+      this.attr(m, o)
+
+    else
+      // set all attributes from sugar.fill and sugar.stroke list
+      for (i = sugar[m].length - 1; i >= 0; i--)
+        if (o[sugar[m][i]] != null)
+          this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]])
+
+    return this
+  }
+
+  SVG.extend(SVG.Element, SVG.FX, extension)
+
+})
+
+SVG.extend(SVG.Element, SVG.FX, {
+  // Map rotation to transform
+  rotate: function(d, cx, cy) {
+    return this.transform({ rotation: d, cx: cx, cy: cy })
+  }
+  // Map skew to transform
+, skew: function(x, y, cx, cy) {
+    return arguments.length == 1  || arguments.length == 3 ?
+      this.transform({ skew: x, cx: y, cy: cx }) :
+      this.transform({ skewX: x, skewY: y, cx: cx, cy: cy })
+  }
+  // Map scale to transform
+, scale: function(x, y, cx, cy) {
+    return arguments.length == 1  || arguments.length == 3 ?
+      this.transform({ scale: x, cx: y, cy: cx }) :
+      this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy })
+  }
+  // Map translate to transform
+, translate: function(x, y) {
+    return this.transform({ x: x, y: y })
+  }
+  // Map flip to transform
+, flip: function(a, o) {
+    return this.transform({ flip: a, offset: o })
+  }
+  // Map matrix to transform
+, matrix: function(m) {
+    return this.attr('transform', new SVG.Matrix(m))
+  }
+  // Opacity
+, opacity: function(value) {
+    return this.attr('opacity', value)
+  }
+  // Relative move over x axis
+, dx: function(x) {
+    return this.x((this instanceof SVG.FX ? 0 : this.x()) + x, true)
+  }
+  // Relative move over y axis
+, dy: function(y) {
+    return this.y((this instanceof SVG.FX ? 0 : this.y()) + y, true)
+  }
+  // Relative move over x and y axes
+, dmove: function(x, y) {
+    return this.dx(x).dy(y)
+  }
+})
+
+SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, {
+  // Add x and y radius
+  radius: function(x, y) {
+    var type = (this._target || this).type;
+    return type == 'radial' || type == 'circle' ?
+      this.attr('r', new SVG.Number(x)) :
+      this.rx(x).ry(y == null ? x : y)
+  }
+})
+
+SVG.extend(SVG.Path, {
+  // Get path length
+  length: function() {
+    return this.node.getTotalLength()
+  }
+  // Get point at length
+, pointAt: function(length) {
+    return this.node.getPointAtLength(length)
+  }
+})
+
+SVG.extend(SVG.Parent, SVG.Text, SVG.FX, {
+  // Set font
+  font: function(o) {
+    for (var k in o)
+      k == 'leading' ?
+        this.leading(o[k]) :
+      k == 'anchor' ?
+        this.attr('text-anchor', o[k]) :
+      k == 'size' || k == 'family' || k == 'weight' || k == 'stretch' || k == 'variant' || k == 'style' ?
+        this.attr('font-'+ k, o[k]) :
+        this.attr(k, o[k])
+
+    return this
+  }
+})
+
+SVG.Set = SVG.invent({
+  // Initialize
+  create: function(members) {
+    // Set initial state
+    Array.isArray(members) ? this.members = members : this.clear()
+  }
+
+  // Add class methods
+, extend: {
+    // Add element to set
+    add: function() {
+      var i, il, elements = [].slice.call(arguments)
+
+      for (i = 0, il = elements.length; i < il; i++)
+        this.members.push(elements[i])
+
+      return this
+    }
+    // Remove element from set
+  , remove: function(element) {
+      var i = this.index(element)
+
+      // remove given child
+      if (i > -1)
+        this.members.splice(i, 1)
+
+      return this
+    }
+    // Iterate over all members
+  , each: function(block) {
+      for (var i = 0, il = this.members.length; i < il; i++)
+        block.apply(this.members[i], [i, this.members])
+
+      return this
+    }
+    // Restore to defaults
+  , clear: function() {
+      // initialize store
+      this.members = []
+
+      return this
+    }
+    // Get the length of a set
+  , length: function() {
+      return this.members.length
+    }
+    // Checks if a given element is present in set
+  , has: function(element) {
+      return this.index(element) >= 0
+    }
+    // retuns index of given element in set
+  , index: function(element) {
+      return this.members.indexOf(element)
+    }
+    // Get member at given index
+  , get: function(i) {
+      return this.members[i]
+    }
+    // Get first member
+  , first: function() {
+      return this.get(0)
+    }
+    // Get last member
+  , last: function() {
+      return this.get(this.members.length - 1)
+    }
+    // Default value
+  , valueOf: function() {
+      return this.members
+    }
+    // Get the bounding box of all members included or empty box if set has no items
+  , bbox: function(){
+      var box = new SVG.BBox()
+
+      // return an empty box of there are no members
+      if (this.members.length == 0)
+        return box
+
+      // get the first rbox and update the target bbox
+      var rbox = this.members[0].rbox()
+      box.x      = rbox.x
+      box.y      = rbox.y
+      box.width  = rbox.width
+      box.height = rbox.height
+
+      this.each(function() {
+        // user rbox for correct position and visual representation
+        box = box.merge(this.rbox())
+      })
+
+      return box
+    }
+  }
+
+  // Add parent method
+, construct: {
+    // Create a new set
+    set: function(members) {
+      return new SVG.Set(members)
+    }
+  }
+})
+
+SVG.FX.Set = SVG.invent({
+  // Initialize node
+  create: function(set) {
+    // store reference to set
+    this.set = set
+  }
+
+})
+
+// Alias methods
+SVG.Set.inherit = function() {
+  var m
+    , methods = []
+
+  // gather shape methods
+  for(var m in SVG.Shape.prototype)
+    if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function')
+      methods.push(m)
+
+  // apply shape aliasses
+  methods.forEach(function(method) {
+    SVG.Set.prototype[method] = function() {
+      for (var i = 0, il = this.members.length; i < il; i++)
+        if (this.members[i] && typeof this.members[i][method] == 'function')
+          this.members[i][method].apply(this.members[i], arguments)
+
+      return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this
+    }
+  })
+
+  // clear methods for the next round
+  methods = []
+
+  // gather fx methods
+  for(var m in SVG.FX.prototype)
+    if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function')
+      methods.push(m)
+
+  // apply fx aliasses
+  methods.forEach(function(method) {
+    SVG.FX.Set.prototype[method] = function() {
+      for (var i = 0, il = this.set.members.length; i < il; i++)
+        this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments)
+
+      return this
+    }
+  })
+}
+
+
+
+
+SVG.extend(SVG.Element, {
+  // Store data values on svg nodes
+  data: function(a, v, r) {
+    if (typeof a == 'object') {
+      for (v in a)
+        this.data(v, a[v])
+
+    } else if (arguments.length < 2) {
+      try {
+        return JSON.parse(this.attr('data-' + a))
+      } catch(e) {
+        return this.attr('data-' + a)
+      }
+
+    } else {
+      this.attr(
+        'data-' + a
+      , v === null ?
+          null :
+        r === true || typeof v === 'string' || typeof v === 'number' ?
+          v :
+          JSON.stringify(v)
+      )
+    }
+
+    return this
+  }
+})
+SVG.extend(SVG.Element, {
+  // Remember arbitrary data
+  remember: function(k, v) {
+    // remember every item in an object individually
+    if (typeof arguments[0] == 'object')
+      for (var v in k)
+        this.remember(v, k[v])
+
+    // retrieve memory
+    else if (arguments.length == 1)
+      return this.memory()[k]
+
+    // store memory
+    else
+      this.memory()[k] = v
+
+    return this
+  }
+
+  // Erase a given memory
+, forget: function() {
+    if (arguments.length == 0)
+      this._memory = {}
+    else
+      for (var i = arguments.length - 1; i >= 0; i--)
+        delete this.memory()[arguments[i]]
+
+    return this
+  }
+
+  // Initialize or return local memory object
+, memory: function() {
+    return this._memory || (this._memory = {})
+  }
+
+})
+// Method for getting an element by id
+SVG.get = function(id) {
+  var node = document.getElementById(idFromReference(id) || id)
+  return SVG.adopt(node)
+}
+
+// Select elements by query string
+SVG.select = function(query, parent) {
+  return new SVG.Set(
+    SVG.utils.map((parent || document).querySelectorAll(query), function(node) {
+      return SVG.adopt(node)
+    })
+  )
+}
+
+SVG.extend(SVG.Parent, {
+  // Scoped select method
+  select: function(query) {
+    return SVG.select(query, this.node)
+  }
+
+})
+function is(el, obj){
+  return el instanceof obj
+}
+
+// tests if a given selector matches an element
+function matches(el, selector) {
+  return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector);
+}
+
+// Convert dash-separated-string to camelCase
+function camelCase(s) {
+  return s.toLowerCase().replace(/-(.)/g, function(m, g) {
+    return g.toUpperCase()
+  })
+}
+
+// Capitalize first letter of a string
+function capitalize(s) {
+  return s.charAt(0).toUpperCase() + s.slice(1)
+}
+
+// Ensure to six-based hex
+function fullHex(hex) {
+  return hex.length == 4 ?
+    [ '#',
+      hex.substring(1, 2), hex.substring(1, 2)
+    , hex.substring(2, 3), hex.substring(2, 3)
+    , hex.substring(3, 4), hex.substring(3, 4)
+    ].join('') : hex
+}
+
+// Component to hex value
+function compToHex(comp) {
+  var hex = comp.toString(16)
+  return hex.length == 1 ? '0' + hex : hex
+}
+
+// Calculate proportional width and height values when necessary
+function proportionalSize(element, width, height) {
+  if (width == null || height == null) {
+    var box = element.bbox()
+
+    if (width == null)
+      width = box.width / box.height * height
+    else if (height == null)
+      height = box.height / box.width * width
+  }
+
+  return {
+    width:  width
+  , height: height
+  }
+}
+
+// Delta transform point
+function deltaTransformPoint(matrix, x, y) {
+  return {
+    x: x * matrix.a + y * matrix.c + 0
+  , y: x * matrix.b + y * matrix.d + 0
+  }
+}
+
+// Map matrix array to object
+function arrayToMatrix(a) {
+  return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
+}
+
+// Parse matrix if required
+function parseMatrix(matrix) {
+  if (!(matrix instanceof SVG.Matrix))
+    matrix = new SVG.Matrix(matrix)
+
+  return matrix
+}
+
+// Add centre point to transform object
+function ensureCentre(o, target) {
+  o.cx = o.cx == null ? target.bbox().cx : o.cx
+  o.cy = o.cy == null ? target.bbox().cy : o.cy
+}
+
+// Convert string to matrix
+function stringToMatrix(source) {
+  // remove matrix wrapper and split to individual numbers
+  source = source
+    .replace(SVG.regex.whitespace, '')
+    .replace(SVG.regex.matrix, '')
+    .split(SVG.regex.matrixElements)
+
+  // convert string values to floats and convert to a matrix-formatted object
+  return arrayToMatrix(
+    SVG.utils.map(source, function(n) {
+      return parseFloat(n)
+    })
+  )
+}
+
+// Calculate position according to from and to
+function at(o, pos) {
+  // number recalculation (don't bother converting to SVG.Number for performance reasons)
+  return typeof o.from == 'number' ?
+    o.from + (o.to - o.from) * pos :
+
+  // instance recalculation
+  o instanceof SVG.Color || o instanceof SVG.Number || o instanceof SVG.Matrix ? o.at(pos) :
+
+  // for all other values wait until pos has reached 1 to return the final value
+  pos < 1 ? o.from : o.to
+}
+
+// PathArray Helpers
+function arrayToString(a) {
+  for (var i = 0, il = a.length, s = ''; i < il; i++) {
+    s += a[i][0]
+
+    if (a[i][1] != null) {
+      s += a[i][1]
+
+      if (a[i][2] != null) {
+        s += ' '
+        s += a[i][2]
+
+        if (a[i][3] != null) {
+          s += ' '
+          s += a[i][3]
+          s += ' '
+          s += a[i][4]
+
+          if (a[i][5] != null) {
+            s += ' '
+            s += a[i][5]
+            s += ' '
+            s += a[i][6]
+
+            if (a[i][7] != null) {
+              s += ' '
+              s += a[i][7]
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return s + ' '
+}
+
+// Deep new id assignment
+function assignNewId(node) {
+  // do the same for SVG child nodes as well
+  for (var i = node.childNodes.length - 1; i >= 0; i--)
+    if (node.childNodes[i] instanceof SVGElement)
+      assignNewId(node.childNodes[i])
+
+  return SVG.adopt(node).id(SVG.eid(node.nodeName))
+}
+
+// Add more bounding box properties
+function fullBox(b) {
+  if (b.x == null) {
+    b.x      = 0
+    b.y      = 0
+    b.width  = 0
+    b.height = 0
+  }
+
+  b.w  = b.width
+  b.h  = b.height
+  b.x2 = b.x + b.width
+  b.y2 = b.y + b.height
+  b.cx = b.x + b.width / 2
+  b.cy = b.y + b.height / 2
+
+  return b
+}
+
+// Get id from reference string
+function idFromReference(url) {
+  var m = url.toString().match(SVG.regex.reference)
+
+  if (m) return m[1]
+}
+
+// Create matrix array for looping
+var abcdef = 'abcdef'.split('')
+// Add CustomEvent to IE9 and IE10
+if (typeof CustomEvent !== 'function') {
+  // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
+  var CustomEvent = function(event, options) {
+    options = options || { bubbles: false, cancelable: false, detail: undefined }
+    var e = document.createEvent('CustomEvent')
+    e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail)
+    return e
+  }
+
+  CustomEvent.prototype = window.Event.prototype
+
+  window.CustomEvent = CustomEvent
+}
+
+// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish
+(function(w) {
+  var lastTime = 0
+  var vendors = ['moz', 'webkit']
+
+  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+    w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame']
+    w.cancelAnimationFrame  = w[vendors[x] + 'CancelAnimationFrame'] ||
+                              w[vendors[x] + 'CancelRequestAnimationFrame']
+  }
+
+  w.requestAnimationFrame = w.requestAnimationFrame ||
+    function(callback) {
+      var currTime = new Date().getTime()
+      var timeToCall = Math.max(0, 16 - (currTime - lastTime))
+
+      var id = w.setTimeout(function() {
+        callback(currTime + timeToCall)
+      }, timeToCall)
+
+      lastTime = currTime + timeToCall
+      return id
+    }
+
+  w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout;
+
+}(window))
+
+return SVG
+
+}));
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.min.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..0bfe629d79ebfcd33418e6588cd198ac32146738
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/libs/svg.min.js
@@ -0,0 +1,3 @@
+/*! svg.js v2.4.0 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e){return t instanceof e}function n(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function s(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function r(t){return t.charAt(0).toUpperCase()+t.slice(1)}function a(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function o(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function h(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function u(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function l(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function c(t){return t instanceof g.Matrix||(t=new g.Matrix(t)),t}function f(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function d(t){return t=t.replace(g.regex.whitespace,"").replace(g.regex.matrix,"").split(g.regex.matrixElements),l(g.utils.map(t,function(t){return parseFloat(t)}))}function p(t){for(var e=0,i=t.length,n="";e<i;e++)n+=t[e][0],null!=t[e][1]&&(n+=t[e][1],null!=t[e][2]&&(n+=" ",n+=t[e][2],null!=t[e][3]&&(n+=" ",n+=t[e][3],n+=" ",n+=t[e][4],null!=t[e][5]&&(n+=" ",n+=t[e][5],n+=" ",n+=t[e][6],null!=t[e][7]&&(n+=" ",n+=t[e][7])))));return n+" "}function m(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&m(t.childNodes[e]);return g.adopt(t).id(g.eid(t.nodeName))}function x(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=t.toString().match(g.regex.reference);if(e)return e[1]}var g=this.SVG=function(t){if(g.supported)return t=new g.Doc(t),g.parser.draw||g.prepare(),t};if(g.ns="http://www.w3.org/2000/svg",g.xmlns="http://www.w3.org/2000/xmlns/",g.xlink="http://www.w3.org/1999/xlink",g.svgjs="http://svgjs.com/svgjs",g.supported=function(){return!!e.createElementNS&&!!e.createElementNS(g.ns,"svg").createSVGRect}(),!g.supported)return!1;g.did=1e3,g.eid=function(t){return"Svgjs"+r(t)+g.did++},g.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},g.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];g.Set&&g.Set.inherit&&g.Set.inherit()},g.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,g.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&g.extend(e,t.extend),t.construct&&g.extend(t.parent||g.Container,t.construct),e},g.adopt=function(t){if(!t)return null;if(t.instance)return t.instance;var e;return e="svg"==t.nodeName?t.parentNode instanceof SVGElement?new g.Nested:new g.Doc:"linearGradient"==t.nodeName?new g.Gradient("linear"):"radialGradient"==t.nodeName?new g.Gradient("radial"):g[r(t.nodeName)]?new(g[r(t.nodeName)]):new g.Element(t),e.type=t.nodeName,e.node=t,t.instance=e,e instanceof g.Doc&&e.namespace().defs(),e.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}),e},g.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new g.Doc(t):new g.Doc(e.documentElement).nested()).size(2,0);g.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:i.path().node,native:g.create("svg")}},g.parser={native:g.create("svg")},e.addEventListener("DOMContentLoaded",function(){g.parser.draw||g.prepare()},!1),g.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,matrix:/matrix\(|\)/g,matrixElements:/,*\s+|,/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,negExp:/e\-/gi,comma:/,/g,hyphen:/\-/g,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,whitespaces:/\s+/,X:/X/g},g.utils={map:function(t,e){var i,n=t.length,s=[];for(i=0;i<n;i++)s.push(e(t[i]));return s},filter:function(t,e){var i,n=t.length,s=[];for(i=0;i<n;i++)e(t[i])&&s.push(t[i]);return s},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360},filterSVGElements:function(t){return this.filter(t,function(t){return t instanceof SVGElement})}},g.defaults={attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},g.Color=function(t){var e;this.r=0,this.g=0,this.b=0,t&&("string"==typeof t?g.regex.isRgb.test(t)?(e=g.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):g.regex.isHex.test(t)&&(e=g.regex.hex.exec(a(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b))},g.extend(g.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+o(this.r)+o(this.g)+o(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new g.Color(t),this},at:function(t){return this.destination?(t=t<0?0:t>1?1:t,new g.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),g.Color.test=function(t){return t+="",g.regex.isHex.test(t)||g.regex.isRgb.test(t)},g.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},g.Color.isColor=function(t){return g.Color.isRgb(t)||g.Color.test(t)},g.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},g.extend(g.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.indexOf(this.value[t])==-1&&i.push(this.value[t]);return this.value=i},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new g.Array(n)},toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)},split:function(t){return t.trim().split(/\s+/)},reverse:function(){return this.value.reverse(),this}}),g.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},g.PointArray.prototype=new g.Array,g.extend(g.PointArray,{toString:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.push(this.value[t].join(","));return i.join(" ")},toLine:function(){return{x1:this.value[0][0],y1:this.value[0][1],x2:this.value[1][0],y2:this.value[1][1]}},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push([this.value[e][0]+(this.destination[e][0]-this.value[e][0])*t,this.value[e][1]+(this.destination[e][1]-this.value[e][1])*t]);return new g.PointArray(n)},parse:function(t){var e=[];if(t=t.valueOf(),Array.isArray(t))return t;t=t.trim().split(/\s+|,/),t.length%2!==0&&t.pop();for(var i=0,n=t.length;i<n;i+=2)e.push([parseFloat(t[i]),parseFloat(t[i+1])]);return e},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n=this.value.length-1;n>=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x,this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y;return this},bbox:function(){return g.parser.poly.setAttribute("points",this.toString()),g.parser.poly.getBBox()}}),g.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},g.PathArray.prototype=new g.Array,g.extend(g.PathArray,{toString:function(){return p(this.value)},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n,s=this.value.length-1;s>=0;s--)n=this.value[s][0],"M"==n||"L"==n||"T"==n?(this.value[s][1]+=t,this.value[s][2]+=e):"H"==n?this.value[s][1]+=t:"V"==n?this.value[s][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[s][1]+=t,this.value[s][2]+=e,this.value[s][3]+=t,this.value[s][4]+=e,"C"==n&&(this.value[s][5]+=t,this.value[s][6]+=e)):"A"==n&&(this.value[s][6]+=t,this.value[s][7]+=e);return this},size:function(t,e){var i,n,s=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x,this.value[i][2]=(this.value[i][2]-s.y)*e/s.height+s.y):"H"==n?this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x:"V"==n?this.value[i][1]=(this.value[i][1]-s.y)*e/s.height+s.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x,this.value[i][2]=(this.value[i][2]-s.y)*e/s.height+s.y,this.value[i][3]=(this.value[i][3]-s.x)*t/s.width+s.x,this.value[i][4]=(this.value[i][4]-s.y)*e/s.height+s.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-s.x)*t/s.width+s.x,this.value[i][6]=(this.value[i][6]-s.y)*e/s.height+s.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/s.width,this.value[i][2]=this.value[i][2]*e/s.height,this.value[i][6]=(this.value[i][6]-s.x)*t/s.width+s.x,this.value[i][7]=(this.value[i][7]-s.y)*e/s.height+s.y);return this},equalCommands:function(t){var e,i,n;for(t=new g.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&e<i;e++)n=this.value[e][0]===t.value[e][0];return n},morph:function(t){return t=new g.PathArray(t),this.equalCommands(t)?this.destination=t:this.destination=null,this},at:function(t){if(!this.destination)return this;var e,i,n,s,r=this.value,a=this.destination.value,o=[],h=new g.PathArray;for(e=0,i=r.length;e<i;e++){for(o[e]=[r[e][0]],n=1,s=r[e].length;n<s;n++)o[e][n]=r[e][n]+(a[e][n]-r[e][n])*t;"A"===o[e][0]&&(o[e][4]=+(0!=o[e][4]),o[e][5]=+(0!=o[e][5]))}return h.value=o,h},parse:function(t){if(t instanceof g.PathArray)return t.valueOf();var e,i,n,s,r,a,o=0,h=0,u={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7};if("string"==typeof t){for(t=t.replace(g.regex.negExp,"X").replace(g.regex.pathLetters," $& ").replace(g.regex.hyphen," -").replace(g.regex.comma," ").replace(g.regex.X,"e-").trim().split(g.regex.whitespaces),e=t.length;--e;)if(t[e].indexOf(".")!=t[e].lastIndexOf(".")){var l=t[e].split("."),c=[l.shift(),l.shift()].join(".");t.splice.apply(t,[e,1].concat(c,l.map(function(t){return"."+t})))}}else t=t.reduce(function(t,e){return[].concat.apply(t,e)},[]);var a=[];do{for(g.regex.isPathLetter.test(t[0])?(s=t[0],t.shift()):"M"==s?s="L":"m"==s&&(s="l"),r=[s.toUpperCase()],e=0;e<u[r[0]];++e)r.push(parseFloat(t.shift()));s==r[0]?"M"==s||"L"==s||"C"==s||"Q"==s||"S"==s||"T"==s?(o=r[u[r[0]]-1],h=r[u[r[0]]]):"V"==s?h=r[1]:"H"==s?o=r[1]:"A"==s&&(o=r[6],h=r[7]):"m"==s||"l"==s||"c"==s||"s"==s||"q"==s||"t"==s?(r[1]+=o,r[2]+=h,null!=r[3]&&(r[3]+=o,r[4]+=h),null!=r[5]&&(r[5]+=o,r[6]+=h),o=r[u[r[0]]-1],h=r[u[r[0]]]):"v"==s?(r[1]+=h,h=r[1]):"h"==s?(r[1]+=o,o=r[1]):"a"==s&&(r[6]+=o,r[7]+=h,o=r[6],h=r[7]),"M"==r[0]&&(i=o,n=h),"Z"==r[0]&&(o=i,h=n),a.push(r)}while(t.length);return a},bbox:function(){return g.parser.path.setAttribute("d",this.toString()),g.parser.path.getBBox()}}),g.Number=g.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(g.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof g.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return new g.Number(this+new g.Number(t),this.unit)},minus:function(t){return this.plus(-new g.Number(t))},times:function(t){return new g.Number(this*new g.Number(t),this.unit)},divide:function(t){return new g.Number(this/new g.Number(t),this.unit)},to:function(t){var e=new g.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new g.Number(t),this},at:function(t){return this.destination?new g.Number(this.destination).minus(this).times(t).plus(this):this}}}),g.Element=g.invent({create:function(t){this._stroke=g.defaults.attrs.stroke,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=h(this,t,e);return this.width(new g.Number(i.width)).height(new g.Number(i.height))},clone:function(t){var e=m(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t<i.x+i.width&&e<i.y+i.height},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(/\s+/)},hasClass:function(t){return this.classes().indexOf(t)!=-1},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter(function(e){return e!=t}).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return g.get(this.attr(t))},parent:function(t){var e=this;if(!e.node.parentNode)return null;if(e=g.adopt(e.node.parentNode),!t)return e;for(;e&&e.node instanceof SVGElement;){if("string"==typeof t?e.matches(t):e instanceof t)return e;e=g.adopt(e.node.parentNode)}},doc:function(){return this instanceof g.Doc?this:this.parent(g.Doc)},parents:function(t){var e=[],i=this;do{if(i=i.parent(t),!i||!i.node)break;e.push(i)}while(i.parent);return e},matches:function(t){return n(this.node,t)},native:function(){return this.node},svg:function(t){var i=e.createElement("svg");if(!(t&&this instanceof g.Parent))return i.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),i.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");i.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var n=0,s=i.firstChild.childNodes.length;n<s;n++)this.node.appendChild(i.firstChild.firstChild);return this},writeDataToDom:function(){if(this.each||this.lines){var t=this.each?this:this.lines();t.each(function(){this.writeDataToDom()})}return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),this},setData:function(t){return this.dom=t,this},is:function(t){return i(this,t)}}}),g.easing={"-":function(t){return t},"<>":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return-Math.cos(t*Math.PI/2)+1}},g.morph=function(t){return function(e,i){return new g.MorphObj(e,i).at(t)}},g.Situation=g.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new g.Number(t.duration).valueOf(),this.delay=new g.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),g.FX=g.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new g.Situation({duration:t||1e3,delay:i||0,ease:g.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new g.Situation({duration:t,delay:0,ease:g.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof g.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof g.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.situation&&this.situation.stop&&this.situation.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof g.Situation?this.startCurrent():this.situation.call(this)),this},initAnimations:function(){var t,e=this.situation;if(e.init)return this;for(t in e.animations)"viewbox"==t?e.animations[t]=this.target().viewbox().morph(e.animations[t]):(e.animations[t].value="plot"==t?this.target().array().value:this.target()[t](),e.animations[t].value.value&&(e.animations[t].value=e.animations[t].value.value),e.animations[t].relative&&(e.animations[t].destination.value=e.animations[t].destination.value+e.animations[t].value));for(t in e.attrs)if(e.attrs[t]instanceof g.Color){var i=new g.Color(this.target().attr(t));e.attrs[t].r=i.r,e.attrs[t].g=i.g,e.attrs[t].b=i.b}else e.attrs[t].value=this.target().attr(t);for(t in e.styles)e.styles[t].value=this.target().style(t);return e.initialTransformation=this.target().matrixify(),e.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){return this.active||this.start(),e&&this.clearQueue(),this.active=!1,t&&this.situation&&this.atEnd(),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return this.situation.loops===!0?this.at(this.situation.loop+1,!0):"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return"undefined"==typeof t?e.reversed=!e.reversed:e.reversed=t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,g.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)})},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,g.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)})},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,setTimeout(function(){this.start()}.bind(this),0),this},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),this.situation.loops!==!1){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),this.situation.loops===!0||i<this.situation.loops?(this.pos=e-i,n=this.situation.loop,this.situation.loop=i):(this.absPos=this.situation.loops,this.pos=1,n=this.situation.loop-1,this.situation.loop=this.situation.loops),this.situation.reversing&&(this.situation.reversed=this.situation.reversed!=Boolean((this.situation.loop-n)%2))}else this.absPos=Math.min(this.absPos,1),this.pos=this.absPos;this.pos<0&&(this.pos=0),this.situation.reversed&&(this.pos=1-this.pos);var s=this.situation.ease(this.pos);for(var r in this.situation.once)r>this.lastPos&&r<=s&&(this.situation.once[r].call(this.target(),this.pos,s),delete this.situation.once[r]);return this.active&&this.target().fire("during",{pos:this.pos,eased:s,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.target().off(".fx"),this.active=!1),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=s,this):this},eachAt:function(){var t,e,i=this,n=this.target(),s=this.situation;for(t in s.animations)e=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(i.pos),i.pos):t}),n[t].apply(n,e);for(t in s.attrs)e=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(i.pos),i.pos):t}),n.attr.apply(n,e);for(t in s.styles)e=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(i.pos),i.pos):t}),n.style.apply(n,e);if(s.transforms.length){for(e=s.initialTransformation,t=0,len=s.transforms.length;t<len;t++){var r=s.transforms[t];r instanceof g.Matrix?e=r.relative?e.multiply((new g.Matrix).morph(r).at(s.ease(this.pos))):e.morph(r).at(s.ease(this.pos)):(r.relative||r.undo(e.extract()),e=e.multiply(r.at(s.ease(this.pos))))}n.matrix(e)}return this},once:function(t,e,i){return i||(t=this.situation.ease(t)),this.situation.once[t]=e,this}},parent:g.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new g.FX(this))).animate(t,e,i)},delay:function(t){return(this.fx||(this.fx=new g.FX(this))).delay(t)},stop:function(t,e){return this.fx&&this.fx.stop(t,e),this},finish:function(){return this.fx&&this.fx.finish(),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this},speed:function(t){if(this.fx){if(null==t)return this.fx.speed();this.fx.speed(t)}return this}}}),g.MorphObj=g.invent({create:function(t,e){return g.Color.isColor(e)?new g.Color(t).morph(e):g.regex.numberAndUnit.test(e)?new g.Number(t).morph(e):(this.value=0,void(this.destination=e))},extend:{at:function(t,e){return e<1?this.value:this.destination},valueOf:function(){return this.value}}}),g.extend(g.FX,{attr:function(t,e,i){if("object"==typeof t)for(var n in t)this.attr(n,t[n]);else this.add(t,new g.MorphObj(null,e),"attrs");return this},style:function(t,e){if("object"==typeof t)for(var i in t)this.style(i,t[i]);else this.add(t,new g.MorphObj(null,e),"styles");return this},x:function(t,e){if(this.target()instanceof g.G)return this.transform({x:t},e),this;var i=(new g.Number).morph(t);return i.relative=e,this.add("x",i)},y:function(t,e){if(this.target()instanceof g.G)return this.transform({y:t},e),this;var i=(new g.Number).morph(t);return i.relative=e,this.add("y",i)},cx:function(t){return this.add("cx",(new g.Number).morph(t))},cy:function(t){return this.add("cy",(new g.Number).morph(t))},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},size:function(t,e){if(this.target()instanceof g.Text)this.attr("font-size",t);else{var i;t&&e||(i=this.target().bbox()),t||(t=i.width/i.height*e),e||(e=i.height/i.width*t),this.add("width",(new g.Number).morph(t)).add("height",(new g.Number).morph(e))}return this},plot:function(t){return this.add("plot",this.target().array().morph(t))},leading:function(t){return this.target().leading?this.add("leading",(new g.Number).morph(t)):this},viewbox:function(t,e,i,n){return this.target()instanceof g.Container&&this.add("viewbox",new g.ViewBox(t,e,i,n)),this},update:function(t){if(this.target()instanceof g.Stop){if("number"==typeof t||t instanceof g.Number)return this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]});null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset)}return this}}),g.BBox=g.invent({create:function(t){if(t){var i;try{if(!e.documentElement.contains(t.node))throw new Exception("Element not in the dom");i=t.node.getBBox()}catch(e){if(t instanceof g.Shape){var n=t.clone(g.parser.draw).show();i=n.bbox(),n.remove()}else i={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=i.x,this.y=i.y,this.width=i.width,this.height=i.height}x(this)},parent:g.Element,construct:{bbox:function(){return new g.BBox(this)}}}),g.TBox=g.invent({create:function(t){if(t){var e=t.ctm().extract(),i=t.bbox();this.width=i.width*e.scaleX,this.height=i.height*e.scaleY,this.x=i.x+e.x,this.y=i.y+e.y}x(this)},parent:g.Element,construct:{tbox:function(){return new g.TBox(this)}}}),g.RBox=g.invent({create:function(e){if(e){var i=e.doc().parent(),n=e.node.getBoundingClientRect(),s=1;for(this.x=n.left,this.y=n.top,this.x-=i.offsetLeft,this.y-=i.offsetTop;i=i.offsetParent;)this.x-=i.offsetLeft,this.y-=i.offsetTop;for(i=e;i.parent&&(i=i.parent());)i.viewbox&&(s*=i.viewbox().zoom,this.x-=i.x()||0,this.y-=i.y()||0);this.width=n.width/=s,this.height=n.height/=s}x(this),this.x+=t.pageXOffset,this.y+=t.pageYOffset},parent:g.Element,construct:{rbox:function(){return new g.RBox(this)}}}),[g.BBox,g.TBox,g.RBox].forEach(function(t){g.extend(t,{merge:function(e){var i=new t;return i.x=Math.min(this.x,e.x),i.y=Math.min(this.y,e.y),i.width=Math.max(this.x+this.width,e.x+e.width)-i.x,i.height=Math.max(this.y+this.height,e.y+e.height)-i.y,x(i)}})}),g.Matrix=g.invent({create:function(t){var e,i=l([1,0,0,1,0,0]);for(t=t instanceof g.Element?t.matrixify():"string"==typeof t?d(t):6==arguments.length?l([].slice.call(arguments)):"object"==typeof t?t:i,e=w.length-1;e>=0;--e)this[w[e]]=t&&"number"==typeof t[w[e]]?t[w[e]]:i[w[e]]},extend:{extract:function(){var t=u(this,0,1),e=u(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new g.Matrix(this)}},clone:function(){return new g.Matrix(this)},morph:function(t){return this.destination=new g.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new g.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});if(this.param&&this.param.to){var i={rotation:this.param.from.rotation+(this.param.to.rotation-this.param.from.rotation)*t,cx:this.param.from.cx,cy:this.param.from.cy};e=e.rotate((this.param.to.rotation-2*this.param.from.rotation)*t,i.cx,i.cy),e.param=i}return e},multiply:function(t){return new g.Matrix(this.native().multiply(c(t).native()))},inverse:function(){return new g.Matrix(this.native().inverse())},translate:function(t,e){return new g.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new g.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=g.utils.radians(t),this.around(e,i,new g.Matrix(Math.cos(t),Math.sin(t),(-Math.sin(t)),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):this.scale(1,-1,0,e)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=g.utils.radians(t),e=g.utils.radians(e),this.around(i,n,new g.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new g.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new g.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=g.parser.native.createSVGMatrix(),e=w.length-1;e>=0;e--)t[w[e]]=this[w[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:g.Element,construct:{ctm:function(){return new g.Matrix(this.node.getCTM())},screenCTM:function(){return new g.Matrix(this.node.getScreenCTM())}}}),g.Point=g.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new g.Point(this)},morph:function(t,e){return this.destination=new g.Point(t,e),this},at:function(t){if(!this.destination)return this;var e=new g.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},native:function(){var t=g.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new g.Point(this.native().matrixTransform(t.native()))}}}),g.extend(g.Element,{point:function(t,e){return new g.Point(t,e).transform(this.screenCTM().inverse())}}),g.extend(g.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,
+i=e.length-1;i>=0;i--)t[e[i].nodeName]=g.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?g.defaults.attrs[t]:g.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(g.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof g.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new g.Number(e):g.Color.isColor(e)?e=new g.Color(e):Array.isArray(e)?e=new g.Array(e):e instanceof g.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),g.extend(g.Element,{transform:function(t,e){var i,n=this;if("object"!=typeof t)return i=new g.Matrix(n).extract(),"string"==typeof t?i[t]:i;if(i=new g.Matrix(n),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new g.Matrix(t)):new g.Matrix(t);else if(null!=t.rotation)f(t,n),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(f(t,n),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new g.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?i=i.flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),g.extend(g.FX,{transform:function(t,e){var i,n=this.target();return"object"!=typeof t?(i=new g.Matrix(n).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new g.Matrix(t):null!=t.rotation?(f(t,n),i=new g.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new g.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(f(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new g.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?i=(new g.Matrix).morph((new g.Matrix).flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset)):null==t.x&&null==t.y||(i=new g.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),setTimeout(function(){this.start()}.bind(this),0),this):this)}}),g.extend(g.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(/\)\s*,?\s*/).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(g.regex.matrixElements).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(l(e[1])):t[e[0]].apply(t,e[1])},new g.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.rect(1,1),n=i.screenCTM().inverse();return i.remove(),this.addTo(t).untransform().transform(n.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),g.Transformation=g.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.create([].slice.call(arguments));if("object"==typeof t)for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[this.arguments[i]];if(Array.isArray(t))for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[i];this.inversed=!1,e===!0&&(this.inversed=!0)},extend:{at:function(t){for(var e=[],i=0,n=this.arguments.length;i<n;++i)e.push(this[this.arguments[i]]);var s=this._undo||new g.Matrix;return s=(new g.Matrix).morph(g.Matrix.prototype[this.method].apply(s,e)).at(t),this.inversed?s.inverse():s},undo:function(t){for(var e=0,i=this.arguments.length;e<i;++e)t[this.arguments[e]]="undefined"==typeof this[this.arguments[e]]?0:t[this.arguments[e]];return t.cx=this.cx,t.cy=this.cy,this._undo=new(g[r(this.method)])(t,(!0)).at(1),this}}}),g.Translate=g.invent({parent:g.Matrix,inherit:g.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["transformedX","transformedY"],method:"translate"}}),g.Rotate=g.invent({parent:g.Matrix,inherit:g.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["rotation","cx","cy"],method:"rotate",at:function(t){var e=(new g.Matrix).rotate((new g.Number).morph(this.rotation-(this._undo?this._undo.rotation:0)).at(t),this.cx,this.cy);return this.inversed?e.inverse():e},undo:function(t){this._undo=t}}}),g.Scale=g.invent({parent:g.Matrix,inherit:g.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["scaleX","scaleY","cx","cy"],method:"scale"}}),g.Skew=g.invent({parent:g.Matrix,inherit:g.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["skewX","skewY","cx","cy"],method:"skew"}}),g.extend(g.Element,{style:function(t,e){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof t)for(e in t)this.style(e,t[e]);else{if(!g.regex.isCss.test(t))return this.node.style[s(t)];t=t.split(";");for(var i=0;i<t.length;i++)e=t[i].split(":"),this.style(e[0].replace(/\s+/g,""),e[1])}else this.node.style[s(t)]=null===e||g.regex.isBlank.test(e)?"":e;return this}}),g.Parent=g.invent({create:function(t){this.constructor.call(this,t)},inherit:g.Element,extend:{children:function(){return g.utils.map(g.utils.filterSVGElements(this.node.childNodes),function(t){return g.adopt(t)})},add:function(t,e){return null==e?this.node.appendChild(t.node):t.node!=this.node.childNodes[e]&&this.node.insertBefore(t.node,this.node.childNodes[e]),this},put:function(t,e){return this.add(t,e),t},has:function(t){return this.index(t)>=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return g.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,s=this.children();for(i=0,n=s.length;i<n;i++)s[i]instanceof g.Element&&t.apply(s[i],[i,s]),e&&s[i]instanceof g.Container&&s[i].each(t,e);return this},removeElement:function(t){return this.node.removeChild(t.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,this},defs:function(){return this.doc().defs()}}}),g.extend(g.Parent,{ungroup:function(t,e){return 0===e||this instanceof g.Defs?this:(t=t||(this instanceof g.Doc?this:this.parent(g.Parent)),e=e||1/0,this.each(function(){return this instanceof g.Defs?this:this instanceof g.Parent?this.ungroup(t,e-1):this.toParent(t)}),this.node.firstChild||this.remove(),this)},flatten:function(t,e){return this.ungroup(t,e)}}),g.Container=g.invent({create:function(t){this.constructor.call(this,t)},inherit:g.Parent}),g.ViewBox=g.invent({create:function(t){var e,i,n,s,r,a,o,h,u=[0,0,0,0],l=1,c=1,f=/[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi;if(t instanceof g.Element){for(o=t,h=t,a=(t.attr("viewBox")||"").match(f),r=t.bbox,n=new g.Number(t.width()),s=new g.Number(t.height());"%"==n.unit;)l*=n.value,n=new g.Number(o instanceof g.Doc?o.parent().offsetWidth:o.parent().width()),o=o.parent();for(;"%"==s.unit;)c*=s.value,s=new g.Number(h instanceof g.Doc?h.parent().offsetHeight:h.parent().height()),h=h.parent();this.x=0,this.y=0,this.width=n*l,this.height=s*c,this.zoom=1,a&&(e=parseFloat(a[0]),i=parseFloat(a[1]),n=parseFloat(a[2]),s=parseFloat(a[3]),this.zoom=this.width/this.height>n/s?this.height/s:this.width/n,this.x=e,this.y=i,this.width=n,this.height=s)}else t="string"==typeof t?t.match(f).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):u,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t){var t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments);return this.destination=new g.ViewBox(t),this},at:function(t){return this.destination?new g.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:g.Container,construct:{viewbox:function(t){return 0==arguments.length?new g.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){g.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),g.listeners=[],g.handlerMap=[],g.listenerId=0,g.on=function(t,e,i,n){var s=i.bind(n||t.instance||t),r=(g.handlerMap.indexOf(t)+1||g.handlerMap.push(t))-1,a=e.split(".")[0],o=e.split(".")[1]||"*";g.listeners[r]=g.listeners[r]||{},g.listeners[r][a]=g.listeners[r][a]||{},g.listeners[r][a][o]=g.listeners[r][a][o]||{},i._svgjsListenerId||(i._svgjsListenerId=++g.listenerId),g.listeners[r][a][o][i._svgjsListenerId]=s,t.addEventListener(a,s,!1)},g.off=function(t,e,i){var n=g.handlerMap.indexOf(t),s=e&&e.split(".")[0],r=e&&e.split(".")[1];if(n!=-1)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;g.listeners[n][s]&&g.listeners[n][s][r||"*"]&&(t.removeEventListener(s,g.listeners[n][s][r||"*"][i],!1),delete g.listeners[n][s][r||"*"][i])}else if(r&&s){if(g.listeners[n][s]&&g.listeners[n][s][r]){for(i in g.listeners[n][s][r])g.off(t,[s,r].join("."),i);delete g.listeners[n][s][r]}}else if(r)for(e in g.listeners[n])for(namespace in g.listeners[n][e])r===namespace&&g.off(t,[e,r].join("."));else if(s){if(g.listeners[n][s]){for(namespace in g.listeners[n][s])g.off(t,[s,namespace].join("."));delete g.listeners[n][s]}}else{for(e in g.listeners[n])g.off(t,e);delete g.listeners[n]}},g.extend(g.Element,{on:function(t,e,i){return g.on(this.node,t,e,i),this},off:function(t,e){return g.off(this.node,t,e),this},fire:function(t,e){return t instanceof Event?this.node.dispatchEvent(t):this.node.dispatchEvent(new b(t,{detail:e})),this}}),g.Defs=g.invent({create:"defs",inherit:g.Container}),g.G=g.invent({create:"g",inherit:g.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new g.G)}}}),g.extend(g.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof g.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof g.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),g.Mask=g.invent({create:function(){this.constructor.call(this,g.create("mask")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new g.Mask)}}}),g.extend(g.Element,{maskWith:function(t){return this.masker=t instanceof g.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),g.ClipPath=g.invent({create:function(){this.constructor.call(this,g.create("clipPath")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new g.ClipPath)}}}),g.extend(g.Element,{clipWith:function(t){return this.clipper=t instanceof g.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),g.Gradient=g.invent({create:function(t){this.constructor.call(this,g.create(t+"Gradient")),this.type=t},inherit:g.Container,extend:{at:function(t,e,i){return this.put(new g.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),g.extend(g.Gradient,g.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new g.Number(t),fy:new g.Number(e)}):this.attr({x1:new g.Number(t),y1:new g.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new g.Number(t),cy:new g.Number(e)}):this.attr({x2:new g.Number(t),y2:new g.Number(e)})}}),g.extend(g.Defs,{gradient:function(t,e){return this.put(new g.Gradient(t)).update(e)}}),g.Stop=g.invent({create:"stop",inherit:g.Element,extend:{update:function(t){return("number"==typeof t||t instanceof g.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new g.Number(t.offset)),this}}}),g.Pattern=g.invent({create:"pattern",inherit:g.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),g.extend(g.Defs,{pattern:function(t,e,i){return this.put(new g.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),g.Doc=g.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,g.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:g.Container,extend:{namespace:function(){return this.attr({xmlns:g.ns,version:"1.1"}).attr("xmlns:xlink",g.xlink,g.xmlns).attr("xmlns:svgjs",g.svgjs,g.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=g.adopt(t):this._defs=new g.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(t){var e=this.node.getScreenCTM();return e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this}}}),g.Shape=g.invent({create:function(t){this.constructor.call(this,t)},inherit:g.Element}),g.Bare=g.invent({create:function(t,e){if(this.constructor.call(this,g.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:g.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),g.extend(g.Parent,{element:function(t,e){return this.put(new g.Bare(t,e))},symbol:function(){return this.defs().element("symbol",g.Container)}}),g.Use=g.invent({create:"use",inherit:g.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,g.xlink)}},construct:{use:function(t,e){return this.put(new g.Use).element(t,e)}}}),g.Rect=g.invent({create:"rect",inherit:g.Shape,construct:{rect:function(t,e){return this.put(new g.Rect).size(t,e)}}}),g.Circle=g.invent({create:"circle",inherit:g.Shape,construct:{circle:function(t){return this.put(new g.Circle).rx(new g.Number(t).divide(2)).move(0,0)}}}),g.extend(g.Circle,g.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),g.Ellipse=g.invent({create:"ellipse",inherit:g.Shape,construct:{ellipse:function(t,e){return this.put(new g.Ellipse).size(t,e).move(0,0)}}}),g.extend(g.Ellipse,g.Rect,g.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),g.extend(g.Circle,g.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new g.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new g.Number(t).divide(2))},size:function(t,e){var i=h(this,t,e);return this.rx(new g.Number(i.width).divide(2)).ry(new g.Number(i.height).divide(2))}}),g.Line=g.invent({create:"line",inherit:g.Shape,extend:{array:function(){return new g.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return t="undefined"!=typeof e?{x1:t,y1:e,x2:i,y2:n}:new g.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=h(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return this.put(new g.Line).plot(t,e,i,n)}}}),g.Polyline=g.invent({create:"polyline",inherit:g.Shape,construct:{polyline:function(t){return this.put(new g.Polyline).plot(t)}}}),g.Polygon=g.invent({create:"polygon",inherit:g.Shape,construct:{polygon:function(t){return this.put(new g.Polygon).plot(t)}}}),g.extend(g.Polyline,g.Polygon,{array:function(){return this._array||(this._array=new g.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new g.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=h(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),g.extend(g.Line,g.Polyline,g.Polygon,{morphArray:g.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),g.Path=g.invent({create:"path",inherit:g.Shape,extend:{morphArray:g.PathArray,array:function(){return this._array||(this._array=new g.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new g.PathArray(t))},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=h(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new g.Path).plot(t)}}}),g.Image=g.invent({create:"image",inherit:g.Shape,extend:{load:function(t){if(!t)return this;var i=this,n=e.createElement("img");return n.onload=function(){var e=i.parent(g.Pattern);null!==e&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),e&&0==e.width()&&0==e.height()&&e.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:t}))},n.onerror=function(t){"function"==typeof i._error&&i._error.call(i,t)},this.attr("href",n.src=this.src=t,g.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new g.Image).load(t).size(e||0,i||e||0)}}}),g.Text=g.invent({create:function(){this.constructor.call(this,g.create("text")),this.dom.leading=new g.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",g.defaults.attrs["font-family"])},inherit:g.Shape,extend:{x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.dom.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i<n;++i)0!=i&&3!=e[i].nodeType&&1==g.adopt(e[i]).dom.newLined&&(t+="\n"),t+=e[i].textContent;return t}if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=t.split("\n");for(var i=0,s=t.length;i<s;i++)this.tspan(t[i]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this.dom.leading:(this.dom.leading=new g.Number(t),this.rebuild())},lines:function(){var t=(this.textPath&&this.textPath()||this).node,e=g.utils.map(g.utils.filterSVGElements(t.childNodes),function(t){return g.adopt(t)});return new g.Set(e)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this,i=0,n=this.dom.leading*new g.Number(this.attr("font-size"));this.lines().each(function(){this.dom.newLined&&(this.textPath||this.attr("x",e.attr("x")),"\n"==this.text()?i+=n:(this.attr("dy",n+i),i=0))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this},setData:function(t){return this.dom=t,this.dom.leading=new g.Number(t.leading||1.3),this}},construct:{text:function(t){return this.put(new g.Text).text(t)},plain:function(t){return this.put(new g.Text).plain(t)}}}),g.Tspan=g.invent({create:"tspan",inherit:g.Shape,extend:{text:function(t){return null==t?this.node.textContent+(this.dom.newLined?"\n":""):("function"==typeof t?t.call(this,this):this.plain(t),this)},dx:function(t){return this.attr("dx",t)},dy:function(t){return this.attr("dy",t)},newLine:function(){var t=this.parent(g.Text);return this.dom.newLined=!0,this.dy(t.dom.leading*t.attr("font-size")).attr("x",t.x())}}}),g.extend(g.Text,g.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(e.createTextNode(t)),this},tspan:function(t){var e=(this.textPath&&this.textPath()||this).node,i=new g.Tspan;return this._build===!1&&this.clear(),e.appendChild(i.node),i.text(t)},clear:function(){for(var t=(this.textPath&&this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this},length:function(){return this.node.getComputedTextLength()}}),g.TextPath=g.invent({create:"textPath",inherit:g.Parent,parent:g.Text,construct:{path:function(t){for(var e=new g.TextPath,i=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+i,g.xlink),this},plot:function(t){var e=this.track();return e&&e.plot(t),this},track:function(){var t=this.textPath();if(t)return t.reference("href")},textPath:function(){if(this.node.firstChild&&"textPath"==this.node.firstChild.nodeName)return g.adopt(this.node.firstChild)}}}),g.Nested=g.invent({create:function(){this.constructor.call(this,g.create("svg")),this.style("overflow","visible")},inherit:g.Container,construct:{nested:function(){return this.put(new g.Nested)}}}),g.A=g.invent({create:"a",inherit:g.Container,extend:{to:function(t){return this.attr("href",t,g.xlink)},show:function(t){return this.attr("show",t,g.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new g.A).to(t)}}}),g.extend(g.Element,{linkTo:function(t){var e=new g.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),g.Marker=g.invent({create:"marker",inherit:g.Container,extend:{width:function(t){return this.attr("markerWidth",t)},height:function(t){return this.attr("markerHeight",t)},ref:function(t,e){return this.attr("refX",t).attr("refY",e)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return"url(#"+this.id()+")"}},construct:{marker:function(t,e,i){return this.defs().marker(t,e,i)}}}),g.extend(g.Defs,{marker:function(t,e,i){return this.put(new g.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),g.extend(g.Line,g.Polyline,g.Polygon,g.Path,{marker:function(t,e,i,n){var s=["marker"];return"all"!=t&&s.push(t),s=s.join("-"),t=arguments[1]instanceof g.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(s,t)}});var y={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};["fill","stroke"].forEach(function(t){var e,i={};i[t]=function(i){if("undefined"==typeof i)return this;if("string"==typeof i||g.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=y[t].length-1;e>=0;e--)null!=i[y[t][e]]&&this.attr(y.prefix(t,y[t][e]),i[y[t][e]]);return this},g.extend(g.Element,g.FX,i)}),g.extend(g.Element,g.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return this.transform({flip:t,offset:e})},matrix:function(t){return this.attr("transform",new g.Matrix(t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x((this instanceof g.FX?0:this.x())+t,!0)},dy:function(t){return this.y((this instanceof g.FX?0:this.y())+t,!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),g.extend(g.Rect,g.Ellipse,g.Circle,g.Gradient,g.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new g.Number(t)):this.rx(t).ry(null==e?t:e)}}),g.extend(g.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),g.extend(g.Parent,g.Text,g.FX,{font:function(t){for(var e in t)"leading"==e?this.leading(t[e]):"anchor"==e?this.attr("text-anchor",t[e]):"size"==e||"family"==e||"weight"==e||"stretch"==e||"variant"==e||"style"==e?this.attr("font-"+e,t[e]):this.attr(e,t[e]);return this}}),g.Set=g.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t<e;t++)this.members.push(i[t]);return this},remove:function(t){var e=this.index(t);return e>-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e<i;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},length:function(){return this.members.length},has:function(t){return this.index(t)>=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){var t=new g.BBox;if(0==this.members.length)return t;var e=this.members[0].rbox();return t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new g.Set(t)}}}),g.FX.Set=g.invent({create:function(t){this.set=t}}),g.Set.inherit=function(){var t,e=[];for(var t in g.Shape.prototype)"function"==typeof g.Shape.prototype[t]&&"function"!=typeof g.Set.prototype[t]&&e.push(t);e.forEach(function(t){g.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e<i;e++)this.members[e]&&"function"==typeof this.members[e][t]&&this.members[e][t].apply(this.members[e],arguments);return"animate"==t?this.fx||(this.fx=new g.FX.Set(this)):this}}),e=[];for(var t in g.FX.prototype)"function"==typeof g.FX.prototype[t]&&"function"!=typeof g.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){g.FX.Set.prototype[t]=function(){for(var e=0,i=this.set.members.length;e<i;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},g.extend(g.Element,{data:function(t,e,i){if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(e){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:i===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),g.extend(g.Element,{remember:function(t,e){if("object"==typeof arguments[0])for(var e in t)this.remember(e,t[e]);else{if(1==arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0==arguments.length)this._memory={};else for(var t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),g.get=function(t){var i=e.getElementById(v(t)||t);return g.adopt(i)},g.select=function(t,i){return new g.Set(g.utils.map((i||e).querySelectorAll(t),function(t){return g.adopt(t)}))},g.extend(g.Parent,{select:function(t){return g.select(t,this.node)}});var w="abcdef".split("");if("function"!=typeof b){var b=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};b.prototype=t.Event.prototype,t.CustomEvent=b}return function(e){for(var i=0,n=["moz","webkit"],s=0;s<n.length&&!t.requestAnimationFrame;++s)e.requestAnimationFrame=e[n[s]+"RequestAnimationFrame"],e.cancelAnimationFrame=e[n[s]+"CancelAnimationFrame"]||e[n[s]+"CancelRequestAnimationFrame"];e.requestAnimationFrame=e.requestAnimationFrame||function(t){var n=(new Date).getTime(),s=Math.max(0,16-(n-i)),r=e.setTimeout(function(){t(n+s)},s);return i=n+s,r},e.cancelAnimationFrame=e.cancelAnimationFrame||e.clearTimeout;
+}(t),g});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/loader.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/loader.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7a439076d99ddb4900f9a5a7e6255d79664455d
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/loader.js
@@ -0,0 +1,281 @@
+/* loader.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.data = ysy.data || {};
+ysy.data.loader = ysy.data.loader || {};
+$.extend(ysy.data.loader, {
+  /*
+   * this object is responsible for downloading and preparing data from server
+   */
+  _name: "Loader",
+  loaded: false,
+  inited: false,
+  issueIdListMap: {},
+  _onChange: [],
+  init: function () {
+    var settings = ysy.settings;
+    var data = ysy.data;
+    if (settings.paths.rootPath.substr(-1) !== "/") {
+      settings.paths.rootPath += "/";
+    }
+    if (!settings.project) {
+      settings.global = true;
+    }
+    if (settings.project) {
+      settings.projectID = parseInt(settings.project.id);
+    }
+    settings.zoom = new ysy.data.Data();
+    settings.zoom.init({zoom: data.storage.getSavedZoom() || ysy.settings.defaultZoom || "day", _name: "Zoom"});
+    settings.controls = new ysy.data.Data();
+    settings.controls.init({controls: true, _name: "Task controls"});
+    settings.baseline = new ysy.data.Data();
+    settings.baseline.init({open: false, _name: "Baselines"});
+    settings.critical = new ysy.data.Data();
+    settings.critical.init({open: false, active: false, _name: "Critical path"});
+    settings.addTask = new ysy.data.Data();
+    settings.addTask.init({open: false, type: "issue", _name: "Add Task"});
+    settings.resource = new ysy.data.Data();
+    settings.resource.init({open: false, _name: "Resource Management"});
+    settings.scheme = new ysy.data.Data();
+    settings.scheme.init({by: ysy.settings.schemeBy, _name: "Schema switch"});
+    settings.sumRow = new ysy.data.Data();
+    settings.sumRow.init({_name: "SumRow"});
+    settings.sample = new data.Data();
+    data.limits = new data.Data();
+    data.limits.init({_name: "Limits", openings: {}});
+    data.relations = new data.Array().init({_name: "RelationArray"});
+    data.issues = new data.Array().init({_name: "IssueArray"});
+    data.milestones = new data.Array().init({_name: "MilestoneArray"});
+    data.projects = new data.Array().init({_name: "ProjectArray"});
+    data.baselines = new data.Array().init({_name: "BaselineArray"});
+    ysy.view.patch();
+    ysy.proManager.patch();
+    settings.sample.init();
+    this.inited = true;
+  },
+  load: function () {
+    // second part of program initialization
+    this.loaded = false;
+    //this.projects=new ysy.data.Array;
+    //var data=ysy.availableProjects;
+    ysy.log.debug("load()", "load");
+    if (ysy.settings.sample.active) {
+      ysy.pro.sample.loadSample(ysy.settings.sample.active);
+    } else {
+      ysy.gateway.loadGanttdata(
+          $.proxy(this._handleMainGantt, this),
+          function () {
+            ysy.log.error("Error: Unable to load data");
+          }
+      );
+    }
+  },
+  loadSubEntity: function (type, id) {
+    if (type === "project") {
+      return this.loadProject(id);
+    }
+  },
+  register: function (func, ctx) {
+    this._onChange.push({func: func, ctx: ctx});
+  },
+  /**
+   *
+   * @param {Array.<{id:int}>} array
+   * @param {Array.<int>} oldIds
+   * @return {Array.<{id:int}>}
+   */
+  reorderArray: function (array, oldIds) {
+    var newArray = [];
+    var arrayPointer = 0;
+    for (var i = 0; i < array.length; i++) {
+      if (oldIds.length === i || oldIds[i] !== array[i].id) {
+        if (i > 0) {
+          newArray = array.slice(0, i);
+          arrayPointer = i;
+          oldIds = oldIds.slice(i);
+        }
+        break;
+      }
+    }
+    var banned = [];
+    for (i = 0; i < oldIds.length; i++) {
+      for (var j = arrayPointer; j < array.length; j++) {
+        if (array[j].id === oldIds[i]) {
+          newArray.push(array[j]);
+          banned.push(array[j]);
+          break;
+        }
+      }
+    }
+    for (i = arrayPointer; i < array.length; i++) {
+      if (banned.indexOf(array[i]) > -1) continue;
+      newArray.push(array[i]);
+    }
+    return newArray;
+  },
+  _fireChanges: function (who, reason) {
+    for (var i = 0; i < this._onChange.length; i++) {
+      var ctx = this._onChange[i].ctx;
+      if (!ctx || ctx.deleted) {
+        this._onChange.splice(i, 1);
+        continue;
+      }
+      //this.onChangeNew[i].func();
+      ysy.log.log("-- changes to " + (ctx.name ? ctx.name : ctx._name) + " widget");
+      $.proxy(this._onChange[i].func, ctx)();
+    }
+  },
+  _handleMainGantt: function (data) {
+    if (!data.easy_gantt_data) return;
+    var json = data.easy_gantt_data;
+    ysy.log.debug("_handleGantt()", "load");
+    //  -- LIMITS --
+    //ysy.data.limits.set({ // TODO
+    //  start_date: moment(json.start_date, "YYYY-MM-DD"),
+    //  end_date: moment(json.end_date, "YYYY-MM-DD")
+    //});
+    //  -- COLUMNS --
+    ysy.data.columns = json.columns;
+    // ARRAY INITIALIZATION
+    //  -- RELATIONS --
+    ysy.data.relations.clear();
+    //  -- ISSUES --
+    ysy.data.issues.clear();
+    //  -- MILESTONES --
+    ysy.data.milestones.clear();
+    //  -- PROJECTS --
+    ysy.data.projects.clear();
+    // ARRAY FILLING
+    //  -- PROJECTS --
+    this._loadProjects(json.projects);
+    //  -- ISSUES --
+    this._loadIssues(json.issues, "root");
+    //  -- MILESTONES --
+    this._loadMilestones(json.versions); // after issue loading because of shared milestones
+    //  -- RELATIONS --
+    this._loadRelations(json.relations);
+    this._loadHolidays(json.holidays);
+    //  -- SCHEMES --
+    if (this._loadSchemes) this._loadSchemes(json.schemes);
+
+    ysy.log.debug("data loaded", "load");
+    ysy.log.message("JSON loaded");
+    this._fireChanges();
+    ysy.history.clear();
+    this.loaded = true;
+  },
+  _loadIssues: function (json, rootId) {
+    if (!json) return;
+    if (rootId) {
+      if (this.issueIdListMap[rootId]) {
+        json = ysy.data.loader.reorderArray(json, this.issueIdListMap[rootId]);
+      }
+      this.issueIdListMap[rootId] = json.map(function (item) {
+        return item.id;
+      });
+    }
+    var issues = ysy.data.issues;
+    for (var i = 0; i < json.length; i++) {
+      var issue = new ysy.data.Issue();
+      issue.init(json[i]);
+      issues.pushSilent(issue);
+    }
+    issues._fireChanges(this, "load");
+  },
+  _loadRelations: function (json) {
+    if (!json) return;
+    var relations = ysy.data.relations;
+    var allowedTypes = {
+      precedes: true,
+      finish_to_finish: true,
+      start_to_start: true,
+      start_to_finish: true
+    };
+    for (var i = 0; i < json.length; i++) {
+      // TODO enable other relation types
+      if (allowedTypes[json[i].type]) {
+        var rela = new ysy.data.Relation();
+      } else {
+        rela = new ysy.data.SimpleRelation();
+      }
+      rela.init(json[i]);
+      relations.pushSilent(rela);
+    }
+    relations._fireChanges(this, "load");
+  },
+  _loadMilestones: function (json) {
+    if (!json) return;
+    var milestones = ysy.data.milestones;
+    for (var i = 0; i < json.length; i++) {
+      var mile = new ysy.data.Milestone();
+      mile.init(json[i]);
+      milestones.pushSilent(mile);
+
+      var issues = mile.getIssues();
+      var projectIds = {};
+      for (var j = 0; j < issues.length; j++) {
+        projectIds[issues[j].project_id] = true;
+      }
+      delete projectIds[mile.project_id];
+      var sharedForIds = Object.getOwnPropertyNames(projectIds);
+      if (sharedForIds.length === 0) continue;
+      var realProjectId = json[i].project_id;
+      delete json[i].project_id;
+      for (j = 0; j < sharedForIds.length; j++) {
+        var sharedMile = new ysy.data.SharedMilestone();
+        $.extend(sharedMile, {
+          project_id: parseInt(sharedForIds[j]),
+          real_project_id: realProjectId,
+          real_milestone: milestones.getByID(mile.id)
+        });
+        sharedMile.init(json[i]);
+        milestones.pushSilent(sharedMile);
+      }
+    }
+    milestones._fireChanges(this, "load");
+  },
+  _loadProjects: function (json) {
+    if (!json) return;
+    var projects = ysy.data.projects;
+    //var main_id = ysy.settings.projectID;
+    for (var i = 0; i < json.length; i++) {
+      //if (json[i].id === main_id) continue;
+      var project = new ysy.data.Project();
+      project.init(json[i]);
+      projects.pushSilent(project);
+    }
+    projects._fireChanges(this, "load");
+    var openings = ysy.data.limits.openings;
+    for (var id in openings) {
+      if (!openings.hasOwnProperty(id)) continue;
+      if (ysy.main.startsWith(id, "p")) {
+        var realId = id.substring(1);
+        project = projects.getByID(realId);
+        if (!project) continue;
+        if (!project.needLoad) continue;
+        project.needLoad = false;
+        this.loadProject(realId);
+      } else if (this.openIssuesOfProject && ysy.main.startsWith(id, "s")) {
+        realId = id.substring(1);
+        if (!openings["p" + realId]) continue;
+        this.openIssuesOfProject(realId);
+      }
+    }
+  },
+  _loadHolidays: function (json) {
+    if (!json) return;
+    ysy.settings.holidays = json;
+    ysy.view.initNonworkingDays();
+  },
+  loadProject: function () {
+  }
+
+});
+if (!ysy.gateway) ysy.gateway = {};
+$.extend(ysy.gateway, {
+  loadGanttdata: function (callback, fail) {
+    $.getJSON(ysy.settings.paths.mainGantt)
+        .done(callback)
+        .fail(fail);
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/logger.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/logger.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe2c435713635790e404459c15ca91b06d6bc281
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/logger.js
@@ -0,0 +1,92 @@
+/* logger.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.log = {
+  logLevel: 2,
+  mainDebug: "",
+  debugTypes: [
+    // "refresher",
+    // "critical",
+    // "canvas_bg",
+    // "set",
+    // "baseline",
+    // "baseline_render",
+    // "inline",
+    // "print",
+    // "move_task",
+    // "add_task",
+    // "add_task_marker",
+    // "taskModal",
+    // "date_format",
+    // "date_helper",
+    // "date",
+    // "tooltip",
+    // "send",
+    // "load",
+    // "supersend",
+    // "scroll",
+    // "scrollRender",
+    // "grid_resize",
+    // "task_drag",
+    // "asc",
+    // "task_push",
+    // "link_render",
+    // "outer",
+    // "sort",
+    // "link_config",
+    // "link_drag",
+    // "empty_field",
+    // "task_drag_milestone",
+    // "widget_destroy",
+    // "resource",
+    // "summer",
+    "nothing"
+  ],
+  log: function (text) {
+    if (this.logLevel >= 4) {
+      this.print(text);
+    }
+  },
+  message: function (text) {
+    if (this.logLevel >= 3) {
+      this.print(text);
+    }
+  },
+  debug: function (text, type) {
+    if (type) {
+      if (this.mainDebug === type) {
+        this.print(text, "debug");
+        return;
+      }
+      for (var i = 0; i < this.debugTypes.length; i++) {
+        if (this.debugTypes[i] === type) {
+          this.print(text, type === this.mainDebug ? "debug" : null);
+          return;
+        }
+      }
+    } else {
+      this.print(text, "debug");
+    }
+  },
+  warning: function (text) {
+    if (this.logLevel >= 2) {
+      this.print(text, "warning");
+    }
+  },
+  error: function (text) {
+    if (this.logLevel >= 1) {
+      this.print(text, "error");
+    }
+  },
+  print: function (text, type) {
+    if (type === "error") {
+      console.error(text);
+    } else if (type === "warning") {
+      console.warn(text);
+    } else if (type === "debug") {
+      console.debug(text);
+    } else {
+      console.log(text);
+    }
+  }
+};
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/main.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..7df2f744a7945bdda1f5e571ec3d936976fca198
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/main.js
@@ -0,0 +1,15 @@
+/* main.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.main = ysy.main || {};
+ysy.initGantt = function () {
+  $("p.nodata").remove();
+  ysy.data.loader.init();
+  ysy.data.loader.load();
+  ysy.data.storage.init();
+  if (!ysy.settings.easyRedmine) {
+    moment.locale(ysy.settings.language || "en");
+  }
+  ysy.view.start();
+  //ysy.main.start();
+};
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..75498b6ac75710be6a3478f5f95ec64bb6e2668d
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/panel_widget.js
@@ -0,0 +1,445 @@
+/* panel_widget.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+
+ysy.view.Toolbars = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Toolbars, {
+  name: "ToolbarsWidget",
+  template: "",
+  childTargets: {
+    "SuperPanelWidget": "#supertop_panel",
+    "BottomPanelWidget": "#gantt_footer_buttons",
+    "BaselinePanelWidget": "#baseline_panel",
+    "CriticalPanelWidget": "#critical_panel",
+    "AddTaskPanelWidget": "#add_task_panel",
+    "LegendWidget": "#easy_gantt_footer_legend",
+    "ToolPanelWidget": "#easy_gantt_tool_panel",
+    "CollapsorsWidget": "#gantt_cont",
+    "AffixWidget": "#easy_gantt_menu"
+  },
+  _updateChildren: function () {
+    if (this.children.length > 0) {
+      return;
+    }
+    if (ysy.view.SuperPanel) {
+      var superpanel = new ysy.view.SuperPanel();
+      superpanel.init(ysy.settings.sample);
+      this.children.push(superpanel);
+    }
+
+    var toppanel = new ysy.view.AllButtons();
+    toppanel.init();
+    this.children.push(toppanel);
+
+    ysy.proManager.fireEvent("initToolbar", this);
+
+    var legend = new ysy.view.Legend();
+    legend.init(null);
+    this.children.push(legend);
+
+    var collapsors = new ysy.view.Collapsors();
+    collapsors.init(null);
+    ysy.view.collapsors = collapsors;
+    this.children.push(collapsors);
+
+    if (window.affix || !ysy.settings.easyRedmine) {
+      var affix = new ysy.view.Affix();
+      ysy.view.affix = affix;
+      affix.init();
+      this.children.push(affix);
+    } else {
+      ysy.view.affix = {
+        requestRepaint: function () {
+        }
+      };
+    }
+
+  },
+  _repaintCore: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      this.setChildTarget(child, i);
+      child.repaint(true);
+    }
+  },
+  setChildTarget: function (child/*, i*/) {
+    if (this.childTargets[child.name]) {
+      child.$target = this.$target.find(this.childTargets[child.name]);
+    }
+  }
+});
+//#############################################################################################
+ysy.view.AllButtons = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.AllButtons, {
+  name: "AllButtonsWidget",
+  templateName: "AllButtons",
+  extendees: {
+    test: {
+      func: function () {
+        ysy.test.run();
+      }, on: true,
+      hid: true
+    },
+    back: {
+      bind: function () {
+        this.model = ysy.history;
+      },
+      func: function () {
+        ysy.history.revert();
+      },
+      isDisabled: function () {
+        return ysy.history.isEmpty();
+      }
+
+    },
+    save: {
+      bind: function () {
+        this.model = ysy.history;
+        this.sample = ysy.settings.sample;
+        this._register(this.sample);
+      },
+      func: function () {
+        if (ysy.settings.sample.active) {
+          ysy.data.loader.load();
+          return;
+        }
+        var $content =$(".easy-content-page");
+        var height = $content.height();
+        $content.css({"height": height});
+        if (this.timeoutSubscription){
+          window.clearTimeout(this.timeoutSubscription);
+        }
+        this.timeoutSubscription = window.setTimeout(function(){
+          $content.css({"height": ""});
+        },5000);
+        ysy.data.save();
+      },
+      specialRepaint: function () {
+        var button_labels = ysy.view.getLabel("buttons");
+        if (ysy.settings.sample.active) {
+          var label = button_labels.button_reload;
+        } else {
+          label = button_labels.button_save;
+        }
+        this.$target.children().html(label);
+      },
+      //isHidden:function(){return ysy.settings.sample.active;},
+      isDisabled: function () {
+        return this.model.isEmpty()
+      },
+      timeoutSubscription: 0
+    },
+    day_zoom: {
+      value: "day",
+      bind: function () {
+        this.model = ysy.settings.zoom;
+      },
+      func: function () {
+        if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value);
+      },
+      isOn: function () {
+        return ysy.settings.zoom.zoom === this.value;
+      }
+    },
+    week_zoom: {
+      value: "week",
+      bind: function () {
+        this.model = ysy.settings.zoom;
+      },
+      func: function () {
+        if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value);
+      },
+      isOn: function () {
+        return ysy.settings.zoom.zoom === this.value;
+      }
+    },
+    month_zoom: {
+      value: "month",
+      bind: function () {
+        this.model = ysy.settings.zoom;
+      },
+      func: function () {
+        if (ysy.settings.zoom.setSilent("zoom", this.value)) ysy.settings.zoom._fireChanges(this, this.value);
+      },
+      isOn: function () {
+        return ysy.settings.zoom.zoom === this.value;
+      }
+    },
+    task_control: {
+      bind: function () {
+        this.model = ysy.settings.controls;
+      },
+      func: function () {
+        ysy.settings.controls.setSilent("controls", !this.isOn());
+        ysy.settings.controls._fireChanges(this, !this.isOn());
+        //this.on=!$(".gantt_bars_area").toggleClass("no_task_controls").hasClass("no_task_controls");
+        $(".gantt_bars_area").toggleClass("no_task_controls");
+        this.requestRepaint();
+      },
+      isOn: function () {
+        return ysy.settings.controls.controls;
+      },
+      isHidden: function () {
+        // return !ysy.settings.permissions.allowed("edit_easy_gantt", "edit_issues");
+        return false;
+      }
+    },
+    resource_help: {},
+    add_task_help: {},
+    baseline_help: {},
+    critical_help: {},
+    print: {
+      func: function () {
+        return ysy.pro.print.directPrint(this);
+      },
+      isOn: function () {
+        return ysy.pro.print.printPreparing;
+      },
+      forceRepaint:function () {
+        this.requestRepaint();
+        this.repaint();
+      }
+    },
+    jump_today: {
+      func: function () {
+        gantt.showDate(moment());
+      }
+    }
+  },
+  _updateChildren: function () {
+    var children = [];
+    this.$target = $("#content");
+    //var spans=this.$target.children("span");
+    for (var elid in this.extendees) {
+      if (!this.extendees.hasOwnProperty(elid)) continue;
+      var extendee = this.extendees[elid];
+      var button;
+      if (extendee.widget) {
+        button = new extendee.widget();
+      } else {
+        button = new ysy.view.Button();
+      }
+      $.extend(button, extendee, {elid: elid});
+      if (!this.getChildTarget(button, elid).length) continue;
+      button.init();
+      children.push(button);
+    }
+    this.children = children;
+  },
+  out: function () {
+    //return {buttons:this.child_array};
+  },
+  _repaintCore: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      this.setChildTarget(child, i);
+      child.repaint(true);
+    }
+  },
+  setChildTarget: function (child /*,i*/) {
+    child.$target = this.getChildTarget(child);
+  },
+  getChildTarget: function (child, elid) {
+    if (!elid) elid = child.elid;
+    return this.$target.find("#" + child.elementPrefix + elid);
+  }
+});
+//##############################################################################
+ysy.view.Button = function () {
+  ysy.view.Widget.call(this);
+  this.on = false;
+  this.disabled = false;
+  this.func = function () {
+    var div = $(this.$target).next('div');
+    var x = div.clone().attr({"id": div[0].id + "_popup"}).appendTo($("body"));
+    showModal(x[0].id);
+    //var template=ysy.view.getTemplate("easy_unimplemented");
+    //var rendered=Mustache.render(template, {modal: ysy.view.getLabel("soon_"+this.elid)});
+    //$("#ajax-modal").html(rendered); // REPAINT
+    //window.showModal("ajax-modal");
+  }
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Button, {
+  name: "ButtonWidget",
+  templateName: "Button",
+  elementPrefix: "button_",
+  _replace: true,
+  init: function () {
+    this.name = (this.elid || this.id) + this.name;
+    this.name = this.name.charAt(0).toUpperCase() + this.name.slice(1);
+    if (this.bind) {
+      this.bind();
+    }
+    if (this.model) {
+      this._register(this.model);
+    }
+    //this.tideFunctionality();
+    return this;
+  },
+  tideFunctionality: function () {
+    if (this.func && !this.isDisabled() && (!this.$target.is("a") || this.$target.attr("href") === "javascript:void(0)")) {
+      this.$target.off("click").on("click", $.proxy(this.func, this));
+    }
+  },
+  isHidden: function () {
+    return this.hid;
+  },
+  _repaintCore: function () {
+    var target = this.$target;
+    var hidden = this.isHidden();
+    target.toggle(!hidden);
+    if (hidden) {
+      if (this.specialRepaint) {
+        this.specialRepaint(hidden);
+      }
+      return;
+    }
+    if (this.isDisabled()) {
+      target.addClass("disabled");
+      target.removeClass("active");
+    } else {
+      target.removeClass("disabled");
+      if (this.isOn()) {
+        target.addClass("active");
+      } else {
+        target.removeClass("active");
+      }
+    }
+    if (this.specialRepaint) {
+      this.specialRepaint();
+    }
+    this.tideFunctionality();
+  },
+  isOn: function () {
+    return this.on;
+  },
+  isDisabled: function () {
+    return this.disabled;
+  }
+});
+//##############################################################################
+ysy.view.Select = function () {
+  ysy.view.Button.call(this);
+};
+ysy.main.extender(ysy.view.Button, ysy.view.Select, {
+  name: "SelectWidget",
+  templateName: "Select",
+  elementPrefix: "select_",
+  _repaintCore: function () {
+    var target = this.$target;
+    var hidden = this.isHidden();
+    target.toggle(!hidden);
+    if (hidden) {
+      if (this.specialRepaint) {
+        this.specialRepaint(hidden);
+      }
+      return;
+    }
+    target.prop('disabled', this.isDisabled());
+    target.val(this.modelValue());
+    if (this.specialRepaint) {
+      this.specialRepaint();
+    }
+    this.tideFunctionality();
+  },
+  tideFunctionality: function () {
+    if (this.func && !this.isDisabled()) {
+      this.$target.off("change").on("change", $.proxy(this.func, this));
+    }
+  },
+  modelValue: function () {
+    return "";
+  }
+});
+//##############################################################################
+ysy.view.CheckBox = function () {
+  ysy.view.Button.call(this);
+};
+ysy.main.extender(ysy.view.Button, ysy.view.CheckBox, {
+  name: "CheckBoxWidget",
+  elementPrefix: "checkbox_",
+  _repaintCore: function () {
+    var target = this.$target;
+    var hidden = this.isHidden();
+    target.toggle(!hidden);
+    if (hidden) {
+      if (this.specialRepaint) {
+        this.specialRepaint(hidden);
+      }
+      return;
+    }
+    target.prop('disabled', this.isDisabled());
+    target.prop('checked', this.isOn());
+    if (this.specialRepaint) {
+      this.specialRepaint();
+    }
+    this.tideFunctionality();
+  },
+  tideFunctionality: function () {
+    if (this.func && !this.isDisabled()) {
+      this.$target.off("change").on("change", $.proxy(this.func, this));
+    }
+  }
+});
+//####################################################
+ysy.view.Legend = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Legend, {
+  name: "LegendWidget",
+  templateName: "legend",
+  _postInit: function () {
+  },
+  out: function () {
+    return null;
+    //return {text: "Legend for EasyGantt"};
+  }
+});
+//###################################################
+ysy.view.Affix = function () {
+  ysy.view.Widget.call(this);
+  this.offset = 0;
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Affix, {
+  name: "AffixWidget",
+  init: function () {
+    this.$document = $(document);
+    this.$superPanel = $("#supertop_panel");
+    this.$cont = $("#gantt_cont");
+    this.$document.on("scroll", $.proxy(this.requestRepaint, this));
+    $(window).on("resize", $.proxy(this.requestRepaint, this));
+    if (ysy.settings.easyRedmine) {
+      this.offset += $("#top-menu").outerHeight();
+    }
+    this.bottomFixed = false;
+    //this._updateChildren();
+  },
+  _repaintCore: function () {
+    var top = this.$document.scrollTop() + this.offset - this.$superPanel.offset().top - this.$superPanel.outerHeight();
+    top = Math.max(Math.floor(top), 0);
+    this.setPosition(top);
+    var box = this.$cont[0].getBoundingClientRect();
+
+    if (box.bottom > window.innerHeight) {
+      if (!this.bottomFixed) {
+        // this.$cont.find(".gantt_hor_scroll").css({position: "fixed", top: (window.innerHeight - 15) + "px"});
+        this.$cont.find(".gantt_hor_scroll").css({position: "fixed", bottom: "0"});
+        this.bottomFixed = true;
+      }
+    } else {
+      if (this.bottomFixed) {
+        this.$cont.find(".gantt_hor_scroll").css({position: "relative", bottom: ""});
+        this.bottomFixed = false;
+      }
+    }
+  },
+  setPosition: function (top) {
+    this.$target.css({transform: "translate(0, " + top + "px)"});
+    this.$cont.find(".gantt_grid_scale, .gantt_task_scale").css({transform: "translate(0, " + (top - 1) + "px)"});
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/print.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/print.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee279c30ec558dc68fc9d7bdef55299ba0736f02
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/print.js
@@ -0,0 +1,277 @@
+/* print.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.print = {
+  printPrepared: false,
+  printPreparing: false,
+  patch: function () {
+    var self = this;
+    var mediaQueryList = window.matchMedia('print');
+    mediaQueryList.addListener(function (mql) {
+      if (mql.matches) {
+        // self.beforePrint();
+      } else {
+        self.afterPrint();
+      }
+    });
+    // window.onbeforeprint = $.proxy(this.beforePrint, this);
+    window.onafterprint = $.proxy(this.afterPrint, this);
+
+    window.easyModel = window.easyModel || {};
+    window.easyModel.print = window.easyModel.print || {};
+    window.easyModel.print.functions = window.easyModel.print.functions || [];
+
+    window.easyModel.print.functions.push(this.printToTemplate);
+  },
+  directPrint: function (buttonWidget) {
+    var self = this;
+    self.printPreparing = true;
+    buttonWidget.forceRepaint();
+    setTimeout(function () {
+      self.beforePrint();
+      self.printPreparing = false;
+      buttonWidget.forceRepaint();
+      window.print();
+      // self.afterPrint();
+    }, 30);
+  },
+  beforePrint: function (stripWidth) {
+    var self = ysy.pro.print;
+    if (self.printPrepared) return;
+    var $wrapper2 = $("#wrapper2");
+    var $wrapper3 = $("#wrapper3");
+    if (ysy.view.affix.setPosition) {
+      ysy.view.affix.setPosition(0);
+    }
+    $("#print_area").remove();
+    var $print = $('<div id="print_area" class="' + (ysy.settings.easyRedmine ? "easy" : "redmine") + ' gantt-print__area"></div>');
+
+    // if (ysy.settings.project) {
+    //   var headerText = '<h1 class="gantt-print__header-header gantt-print__header-project">' + ysy.settings.project.name + '</h1>'
+    //       + '<h2 class="gantt-print__header-header gantt-print__header-plugin">&nbsp;- '
+    //       + (ysy.settings.resource.open ? ysy.settings.labels.titles.title_rm : ysy.settings.labels.titles.easyGantt)
+    //       + '</h2>';
+    // } else {
+    //   headerText = '<h1 class="gantt-print__header-header gantt-print__header-plugin">'
+    //       + (ysy.settings.resource.open ? ysy.settings.labels.titles.title_rm : ysy.settings.labels.titles.easyGantt)
+    //       + '</h1>';
+    // }
+    // var $headerCont = $('<div class="gantt-print__header-cont"></div>');
+    // $headerCont.html(headerText);
+
+    var $gantt = $('<div class="gantt-print__gantt"></div>');
+    $print.append($gantt);
+    var fullWidth = gantt._tasks.full_width;
+    stripWidth = stripWidth || 490;
+    var $grid = self.cloneGrid();
+    // $print.prepend($headerCont);
+    $gantt.append($grid);
+    var gridWidth = $grid.outerWidth();
+    for (var p = -gridWidth; p < fullWidth; p += stripWidth) {
+      $gantt.append(self.createStrip(p < 0 ? 0 : p, Math.min(p + stripWidth, fullWidth)));
+      //p -= 2;
+    }
+    // $(".gantt-print__strip, .gantt-print__grid").css("margin-top", $headerCont.height() + 5);
+    $wrapper3.hide();
+    $(".gantt_hor_scroll").hide();
+    $wrapper2.append($print);
+    $("body").addClass("gantt-print__body");
+    self.printPrepared = true;
+
+  },
+  afterPrint: function () {
+    setTimeout(function () {
+      if (!ysy.pro.print.printPrepared) return;
+      $("body").removeClass("gantt-print__body");
+      $("#print_area").remove();
+
+      $("#wrapper3").show();
+      $("#content, #sidebar").removeClass("fake-responsive");
+
+      gantt._scroll_resize();
+      ysy.pro.print.printPrepared = false;
+    }, 100);
+  },
+  printToTemplate: function () {
+    var printFit = $("#easy_gantt_print_fit_checkbox").is(":checked");
+    ysy.pro.print.beforePrint(printFit ? Infinity : undefined);
+
+    var width = $(".gantt_container").width();
+    var content = $("#print_area").html() + ysy.view.templates.printIncludes;
+    // TODO  add easy_gantt_pro.css
+    // TODO  add easy_gantt_resources.css
+
+    if (printFit) {
+      content = '<div class="easy-print-page-fitting gantt-print__template--nowrap">' + content + '</div>';
+    }
+
+    // window.easyModel.print.tokens['easy_gantt_current_base64'] = $.base64.encode(content);
+    window.easyModel.print.tokens['easy_gantt_current'] = content;
+    window.easyModel.print.setWidth(width);
+    ysy.pro.print.afterPrint();
+  },
+  cloneGrid: function () {
+    var $gantt_cont = $("#gantt_cont");
+    var $grid = $gantt_cont.find(".gantt_grid").clone().addClass("gantt-print__grid");
+    $grid.find("a").each(function () {
+      var $this = $(this);
+      $this.parent().append('<span class="' + this.className + '">' + $this.text() + '</span>');
+      $this.remove();
+    });
+    var $gridScale = $grid.find(".gantt_grid_scale");
+    $gridScale.css({height: $gridScale.height() + 1 + "px", transform: "none"});
+    return $grid;
+  },
+  createStrip: function (start, end) {
+    if (end <= start) return null;
+    var $gantt_cont = $("#gantt_cont");
+    var $gantt_task = $gantt_cont.find(".gantt_task");
+    var $strip = $('<div class="gantt-print__strip" style="width:' + (end - start) + 'px"></div>');
+    // SCALE LINE
+    $strip.append(this.cloneScales($gantt_task, start, end));
+
+    // DATA AREA
+    var $gantt_data_area = $gantt_cont.find(".gantt_data_area");
+    var $data = $('<div class="gantt_data_area"></div>').css({
+      height: $gantt_data_area.height() + "px",
+      width: (end - start) + "px"
+    });
+    // BACKGROUND
+    $data.append(this.cloneSvgBackground($gantt_data_area, start, end));
+
+    // TASKS
+    $data.append(this.cloneTasks($gantt_data_area, start, end));
+    // LINKS
+    $data.append(this.cloneLinks($gantt_data_area, start, end));
+
+    $strip.append($data);
+
+    return $strip;
+  },
+  cloneScales: function ($source, start, end) {
+    var $scale = $(
+        '<div class="gantt_task_scale gantt-print__scale"></div>');
+    var lines = $source.find(".gantt_scale_line");
+    for (var l = 0; l < lines.length; l++) {
+      var oldLine = $(lines[l]);
+      var cells = oldLine.find(".gantt_scale_cell");
+      var $line = $('<div class="gantt_scale_line gantt-print__scale-line"></div>');
+      $line[0].style.height = lines[l].style.height;
+      $line[0].style.lineHeight = lines[l].style.lineHeight;
+      //$line.style.height=oldLine.style.height;
+      //$line.style.lineHeight=oldLine.style.lineHeight;
+      //$line.height(oldLine.height());
+      var leftPointer = 0;
+      var first = false;
+      for (var i = 0; i < cells.length; i++) {
+        var oldCell = $(cells[i]);
+        var width = oldCell.outerWidth();
+        if (leftPointer < end && leftPointer + width > start) {
+          var $cell = oldCell.clone();
+          $line.append($cell);
+          if (first === false) {
+            first = true;
+            $cell.css("margin-left", (leftPointer - start) + "px");
+          }
+        }
+        leftPointer += width;
+      }
+      $line.width(leftPointer);
+
+      $scale.append($line);
+    }
+    return $scale;
+  },
+  cloneSvgBackground: function ($source, startX, endX) {
+    var $background = $('<div class="gantt_task_bg gantt-print__bg" style="width:' + (endX - startX) + 'px"></div>');
+    var svg = SVG($background[0]);
+    $(svg.node).css({position: "absolute"});
+    var itemIds = gantt._order;
+    var items = itemIds.map(function (itemId) {
+      return gantt._pull[itemId];
+    });
+
+
+    var cfg = gantt._tasks;
+    var fullHeight = itemIds.length * cfg.height;
+    var colWidth = cfg.col_width;
+    var countX = cfg.count;
+    var countY = items.length;
+    var endCountX = Math.ceil(endX / colWidth);
+    var startCountX = Math.floor(startX / colWidth);
+    if (endCountX > countX) {
+      endCountX = countX;
+    }
+    gantt._backgroundRenderer._render_bg_canvas(svg, items, {
+      fromX: startCountX,
+      toX: endCountX,
+      fromY: 0,
+      toY: countY
+    });
+    svg.node.style.left = (cfg.left[startCountX] - startX) + "px";
+    return $background.height(fullHeight);
+  },
+  cloneLinks: function ($source, start, end) {
+    var $gantt_links_area = $source.find(".gantt_links_area");
+    var $links = $gantt_links_area
+        .clone()
+        .addClass("gantt-print__links-area")
+        .css('left', -start + 'px');
+    $links.find(".gantt_task_link > div").filter(function () {
+      var left = parseInt(this.style.left);
+      var width = parseInt(this.style.width || 10);
+      //ysy.log.debug("start: " + (left + width) + "<" + start +
+      //    " end: " + left + ">" + end +
+      //    "    filtered = " + (left > end || left + width < start));
+      return (left > end || left + width < start);
+    }).remove();
+    return $links;
+  },
+  cloneTasks: function ($source, start, end) {
+    var $gantt_bars_area = $source.find(".gantt_bars_area");
+    var $tasks = $($gantt_bars_area[0].cloneNode(false))
+        .addClass("gantt-print__bars-area")
+        .css('left', -start + 'px');
+    var taskArray = $gantt_bars_area.children();
+    for (var i = 0; i < taskArray.length; i++) {
+      var task = taskArray[i];
+      var left = parseInt(task.style.left) - 50;
+      var width = task.offsetWidth + 300;
+      // ysy.log.debug(JSON.stringify({text: $(task).text(), start: (left + task.offsetWidth) + "<" + start,  end: left + ">" + end}));
+      if (left >= end || left + width <= start) continue;
+      $tasks.append($(task).clone());
+    }
+    var sourceCanvases = $gantt_bars_area.find("canvas");
+    if (sourceCanvases.length > 0) {
+      var clonedCanvases = $tasks.find("canvas");
+      for (i = 0; i < clonedCanvases.length; i++) {
+        clonedCanvases[i].getContext('2d').drawImage(sourceCanvases[i], 0, 0);
+      }
+    }
+    return $tasks;
+  }
+};
+//######################################################################################################################
+ysy.pro = ysy.pro || {};
+ysy.pro.pdfPrint = ysy.pro.pdfPrint || {};
+$.extend(ysy.pro.pdfPrint, {
+  beats: 0,
+  targetBeat: 2,
+  patch: function () {
+    if (!ysy.settings.pdfPrint) return;
+    ysy.data.loader.register(function () {
+      ysy.view.onRepaint.push($.proxy(this.repaint, this));
+    }, this);
+  },
+  repaint: function () {
+    if (this.beats > this.targetBeat) return;
+    // skip first few renders
+    if (this.beats === this.targetBeat) {
+      gantt._backgroundRenderer.switchFullRender(true);
+      gantt._unset_sizes();
+      // window.print();
+    }
+    this.beats++;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js
new file mode 100644
index 0000000000000000000000000000000000000000..7cb039ac3e2b5419d6480f89720fb548715a4f73
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/pro_manager.js
@@ -0,0 +1,72 @@
+/* pro_manager.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.proManager = ysy.proManager || {};
+ysy.pro = ysy.pro || {};
+$.extend(ysy.proManager, {
+  proFunctionsMap: {},
+  name:"proManager",
+  patch: function () {
+    window.ysy = window.ysy || {};
+    ysy.settings = ysy.settings || {};
+    for (var key in ysy.pro) {
+      if (!ysy.pro.hasOwnProperty(key)) continue;
+      if (ysy.pro[key].patch) {
+        ysy.pro[key].patch();
+      }
+    }
+  },
+  forEachPro: function (wrapperFunc, event) {
+    var proFunctions = this.proFunctionsMap[event];
+    if (!proFunctions) return;
+    for (var i = 0; i < proFunctions.length; i++) {
+      wrapperFunc.call(this, proFunctions[i]);
+    }
+  },
+  fireEvent: function (event) {
+    var proFunctions = this.proFunctionsMap[event];
+    if (!proFunctions) return;
+    var slicedArgs = Array.prototype.slice.call(arguments, 1);
+    for (var i = 0; i < proFunctions.length; i++) {
+      proFunctions[i].apply(this, slicedArgs);
+    }
+  },
+  register: function (event, func) {
+    if (!func) throw "missing call function";
+    var eventList = this.proFunctionsMap[event];
+    if (!eventList) this.proFunctionsMap[event] = eventList = [];
+    for (var i = 0; i < eventList.length; i++) {
+      if (eventList[i] === func) {
+        return;
+      }
+    }
+    eventList.push(func);
+  },
+  unregister: function (event, func) {
+    var eventList = this.proFunctionsMap[event];
+    if (!eventList) return;
+    for (var i = 0; i < eventList.length; i++) {
+      if (eventList[i] === func) {
+        eventList.splice(i, 1);
+        return;
+      }
+    }
+  },
+  showHelp: function () {
+    var div = $(this).next();
+    var x = div.clone().attr({"id": div[0].id + "_popup"}).appendTo($("body"));
+    showModal(x[0].id);
+  },
+  closeAll: function (except) {
+    this.forEachPro(function (func) {
+      if (except.close !== func) func.call(this,except);
+    }, "close");
+  },
+  eventFilterTask: function (id, task) {
+    var ret = true;
+    this.forEachPro(function (func) {
+      if (ret) ret = func(id, task);
+    }, "filterTask");
+    return ret;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js
new file mode 100644
index 0000000000000000000000000000000000000000..2ad0f3677d96ca3596fd8f729d11934b5478fafb
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/problem_finder.js
@@ -0,0 +1,172 @@
+/* problem_finder.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.problemFinder = $.extend(ysy.pro.problemFinder, {
+  patch: function () {
+    var setting = new ysy.data.Data();
+    setting.init({
+      _name: "Problem finder",
+      opened: false,
+      issueProblems: [],
+      relationProblems: []
+    });
+    ysy.settings.problemFinder = setting;
+    ysy.data.issues.childRegister(function () {
+      ysy.pro.problemFinder.recalculateProblemsInIssues.call(this);
+      ysy.pro.problemFinder.recalculateProblemsInRelations.call(this);
+    }, setting);
+    ysy.data.relations.childRegister(this.recalculateProblemsInRelations, setting);
+    ysy.data.issues.register(this.recalculateProblemsInIssues, setting);
+    ysy.data.projects.register(this.recalculateProblemsInIssues, setting);
+    ysy.data.relations.register(this.recalculateProblemsInRelations, setting);
+    ysy.proManager.register("close", this.close);
+
+    ysy.view.AllButtons.prototype.extendees.problem_finder = {
+      widget: ysy.view.ProblemFinder,
+      bind: function () {
+        this.model = setting;
+      },
+      func: function () {
+        this.model.setSilent({opened: !this.model.opened});
+        this.model._fireChanges(this, "open problem list");
+      },
+      isOn: function () {
+        return this.model.opened;
+      },
+      isHidden: function () {
+        return this.problemCount() === 0;
+      }
+    };
+
+  },
+  recalculateProblemsInIssues: function () {
+    var problems = [];
+    var array = ysy.data.issues.getArray();
+    if (ysy.settings.projectProgress) {
+      array = array.concat(ysy.data.projects.getArray());
+    }
+    for (var i = 0; i < array.length; i++) {
+      var entity = array[i];
+      var entityProblems = entity.getProblems();
+      if (!entityProblems) continue;
+      for (var j = 0; j < entityProblems.length; j++) {
+        problems.push({
+          entity: entity,
+          text: entityProblems[j]
+        })
+      }
+    }
+    this.issueProblems = problems;
+    this._fireChanges(this, "issues problems recalculated");
+  },
+  recalculateProblemsInRelations: function () {
+    var problems = [];
+    var array = ysy.data.relations.getArray();
+    for (var i = 0; +i < array.length; i++) {
+      var entity = array[i];
+      var entityProblems = entity.getProblems();
+      if (!entityProblems) continue;
+      for (var j = 0; j < entityProblems.length; j++) {
+        problems.push({
+          relation: entity,
+          text: entityProblems[j]
+        })
+      }
+    }
+    this.relationProblems = problems;
+    this._fireChanges(this, "relations problems recalculated");
+  },
+  close: function (who) {
+    ysy.settings.problemFinder.setSilent({opened: false});
+    ysy.settings.problemFinder._fireChanges(who, "event close");
+  },
+  scrollToEntity: function (entityId) {
+    var task = gantt._pull[entityId];
+    if (!task) return;
+    gantt.selectTask(entityId);
+    gantt.showTask(entityId);
+  }
+});
+ysy.view.ProblemFinder = function () {
+  ysy.view.Widget.call(this);
+  this.listIsHidden = true;
+};
+ysy.main.extender(ysy.view.Button, ysy.view.ProblemFinder, {
+  templateName: "ProblemFinder",
+  elementPrefix: "button_",
+  outerClickBind: false,
+  specialRepaint: function (hidden) {
+    if (hidden && this.listIsHidden) return;
+    var $problemList = $("#gantt_problem_list");
+    if (hidden) {
+      $problemList.hide();
+      this.listIsHidden = true;
+      return;
+    }
+    var rendered = Mustache.render(ysy.view.getTemplate(this.templateName), {
+      count: this.problemCount()
+    });
+    this.$target.html(rendered);
+    if (this.model.opened) {
+      if (!$problemList.length) {
+        var $button = $("#button_problem_finder");
+        var offset = $button.position().top;
+        var viewHeight = Math.min($(window).height() - 105 - offset, $("#easy_gantt").height() - 95);
+        $problemList = $('<div id="gantt_problem_list" class="gantt-menu-problems-list"></div>').insertAfter($button)
+            .css({maxHeight: viewHeight + "px"})
+      }
+      this.bindOuterClick();
+      rendered = Mustache.render(ysy.view.getTemplate(this.templateName + "List"), this.out());
+      $problemList.html(rendered).show();
+      this.listIsHidden = false;
+    } else {
+      $problemList.hide();
+      this.listIsHidden = true;
+    }
+  },
+  out: function () {
+    var issueProblems = [];
+    var relationProblems = [];
+    var problems = this.model.issueProblems;
+    for (var i = 0; i < problems.length; i++) {
+      var problem = problems[i];
+      issueProblems.push({
+        isProject: problem.entity.isProject,
+        name: problem.entity.name,
+        text: problem.text,
+        entityId: problem.entity.getID()
+      });
+    }
+    var issues = ysy.data.issues;
+    problems = this.model.relationProblems;
+    for (i = 0; i < problems.length; i++) {
+      problem = problems[i];
+      var source = issues.getByID(problem.relation.source_id);
+      var target = issues.getByID(problem.relation.target_id);
+      relationProblems.push({
+        sourceName: (source ? source.name : "#" + problem.relation.source_id),
+        targetName: (target ? target.name : "#" + problem.relation.target_id),
+        text: problem.text,
+        entityId: problem.relation.target_id
+      });
+    }
+    return {"entities": issueProblems, "relations": relationProblems};
+
+  },
+  problemCount: function () {
+    return this.model.issueProblems.length + this.model.relationProblems.length;
+  },
+  bindOuterClick: function () {
+    if (this.outerClickBind) return;
+    var self = this;
+    $(document).on("click.problem_finder", function (e) {
+      //is inside ProblemList
+      if (e && $(e.target).closest("#gantt_problem_list").length) return;
+      $(document).off("click.problem_finder");
+      self.outerClickBind = false;
+      ysy.pro.problemFinder.close(self);
+    });
+    this.outerClickBind = true;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/sample.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/sample.js
new file mode 100644
index 0000000000000000000000000000000000000000..26fed3ac2bb1a76e65fb76b810e8b15732bba284
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/sample.js
@@ -0,0 +1,141 @@
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.sample = {
+  patch: function () {
+    var setting = ysy.settings.sample;
+    $.extend(ysy.view.AllButtons.prototype.extendees, {
+      sample: {
+        bind: function () {
+          this.model = ysy.settings.sample;
+        },
+        func: function () {
+          if (ysy.data.loader.loaded) {
+            this.model.toggle();
+            ysy.data.loader.load();
+          }
+        },
+        isOn: function () {
+          return this.model.active;
+        }
+        //icon:"zoom-in icon-day"
+      }
+    });
+
+    $.extend(setting, {
+      init: function () {
+        if (ysy.settings.easyRedmine || this.isViewed()) {
+          this.prevented = true;
+        }
+        this.active = this.getSampleVersion();
+      },
+      getSampleVersion: function (turnOn) {
+        if (ysy.settings.global) return "global";
+        if (turnOn === false) return 0;
+        if (turnOn === true) return 1;
+        return this.prevented ? 0 : 1;
+      },
+      toggle: function (turnOn) {
+        if (turnOn === undefined) {
+          turnOn = !this.active;
+        }
+        this.setSilent("active", this.getSampleVersion(turnOn));
+        this._fireChanges(this, "toggle");
+      },
+      storageKey: "sample_viewed",
+      setViewed: function () {
+        ysy.data.storage.savePersistentData(this.storageKey, true);
+      },
+      isViewed: function () {
+        return ysy.data.storage.getPersistentData(this.storageKey);
+      }
+    });
+  },
+  _loadSampleData: function (data) {
+    if (!data.easy_gantt_data) return;
+    var json = data.easy_gantt_data;
+    var projects = json.projects;
+    for (var i = 0; i < projects.length; i++) {
+      projects[i].needLoad = false;
+      projects[i].permissions = {editable: true};
+    }
+    var issues = json.issues;
+    for (i = 0; i < issues.length; i++) {
+      issues[i].permissions = {editable: true};
+    }
+    var versions = json.versions;
+    for (i = 0; i < versions.length; i++) {
+      versions[i].permissions = {editable: true};
+    }
+    ysy.data.loader._handleMainGantt(data);
+  },
+  loadSample:function (sampleVersion) {
+    ysy.gateway.polymorficGetJSON(
+        ysy.settings.paths.sample_data.replace("{{version}}", sampleVersion), null,
+        $.proxy(this._loadSampleData, this),
+        function () {
+          ysy.log.error("Error: Example data fetch failed");
+        }
+    );
+  }
+};
+//##############################################################################
+ysy.view.SuperPanel = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.SuperPanel, {
+  name: "SuperPanelWidget",
+  templateName: "SuperPanel",
+  _repaintCore: function () {
+    if (!this.template) {
+      var templ = ysy.view.getTemplate(this.templateName);
+      if (templ) {
+        this.template = templ;
+      } else {
+        return true;
+      }
+    }
+    var rendered = Mustache.render(this.template, this.out()); // REPAINT
+    var $easygantt = $("#easy_gantt");
+    $easygantt.find(".flash").remove();
+    this.$target = $(rendered);
+    $easygantt.prepend(this.$target);
+    //window.showFlashMessage("notice",rendered);
+    this.tideFunctionality();
+  },
+  out: function () {
+    var obj, label;
+    var free = !!ysy.settings.sample.getSampleVersion(false);
+    if (free) {
+      label = ysy.view.getLabel("sample_global_free_text");
+      obj = {global_free: true};
+    } else {
+      label = ysy.view.getLabel("sample_text");
+      obj = {};
+    }
+    return $.extend({}, {text: label}, {sample: this.model.active}, obj);
+  },
+  tideFunctionality: function () {
+    this.$target.find("#sample_close_button").click($.proxy(function () {
+      if (ysy.data.loader.loaded) {
+        this.model.setViewed();
+        this.model.setSilent("active", false);
+        this.model._fireChanges(this, "toggle");
+        ysy.data.loader.load();
+      }
+    }, this));
+    this.$target.find("#sample_video_button").click($.proxy(function () {
+      if (ysy.settings.global) {
+        var template = ysy.view.getTemplate("video_modal_global");
+      } else {
+        template = ysy.view.getTemplate("video_modal");
+      }
+      var $modal = ysy.main.getModal("video-modal", "850px");
+      $modal.html(template); // REPAINT
+      $modal.off("dialogclose");
+      window.showModal("video-modal", 850);
+      $modal.on("dialogclose", function () {
+        $modal.empty();
+      });
+    }));
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/saver.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/saver.js
new file mode 100644
index 0000000000000000000000000000000000000000..e41825efb9bef8dc3d6af91052a592301be81c11
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/saver.js
@@ -0,0 +1,529 @@
+/* saver.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+if (!ysy.gateway) ysy.gateway = {};
+$.extend(ysy.gateway, {
+  requestsGroups: {},
+  temp: {
+    retry: false,
+    superSuccess: null,
+    superFail: null,
+    fails: 0
+  },
+  sendIssue: function (method, issueID, data, callback) {
+    var priority = 3;
+    if (method === "REPAIR") {
+      method = "PUT";
+      priority = 9;
+    }
+    var urlTemplate = ysy.settings.paths["issue" + method];
+    if (!urlTemplate) return;
+    //var url=urlTemplate+(method==="POST"?"":"/"+issueID)+".json";
+    var url = urlTemplate.replace(":issueID", issueID);
+    //var url = Mustache.render(urlTemplate, {issueID: issueID, apiKey: this.getApiKey()});
+    this.prepare({
+      priority: priority,
+      method: method,
+      url: url,
+      data: data,
+      callback: callback,
+      issueID: issueID
+    });
+  },
+  sendRelation: function (method, rela, data, callback) {
+    var urlTemplate = ysy.settings.paths["relation" + method];
+    if (!urlTemplate) return;
+    //var url = Mustache.render(urlTemplate, $.extend(this.getBasicParams(), {
+    //  relaID: rela.id,
+    //  sourceID: rela.source_id
+    //}));
+    var url = urlTemplate.replace(":issueID", rela.source_id).replace(":projectID", rela.getSource().project_id).replace(":relaID", rela.id);
+    var priorities = {DELETE: 1, POST: 6, PUT: 6};
+    this.prepare({
+      priority: priorities[method],
+      method: method,
+      url: url,
+      data: data,
+      callback: callback,
+      relation: rela
+    });
+  },
+  sendMilestone: function (method, mile, data, callback) {
+    var urlTemplate = ysy.settings.paths["version" + method];
+    if (!urlTemplate) return;
+    //var url = Mustache.render(urlTemplate, $.extend(this.getBasicParams(), {versionID: mile.id}));
+    var url = urlTemplate.replace(":versionID", mile.id).replace(":projectID", mile.project_id);
+    this.prepare({priority: 2, method: method, url: url, data: data, callback: callback, milestone: mile});
+  },
+  sendProject: function (method, project, data, callback) {
+    var urlTemplate = ysy.settings.paths["project" + method];
+    if (!urlTemplate) return;
+    var url = urlTemplate.replace(":projectID", project.id);
+    this.prepare({priority: 10, method: method, url: url, data: data, callback: callback, project: project});
+  },
+  prepare: function (packet) {
+    if (!this.requestsGroups) {
+      this.requestsGroups = {};
+    }
+    var priority = packet.priority;
+    if (!this.requestsGroups[priority]) {
+      this.requestsGroups[priority] = [];
+    }
+    this.requestsGroups[priority].push(packet);
+    ysy.log.debug("prepared " + packet.method + " " + packet.url, "supersend");
+  },
+  send: function (request) {
+    ysy.log.debug(request.method + " " + request.url + " " + JSON.stringify(request.data), "send");
+    var xhr = $.ajax({
+      url: request.url,
+      type: request.method,
+      dataType: "text",
+      data: request.data
+    });
+    xhr.done(function (message) {
+      if (request.callback) {
+        request.callback(message);
+      }
+      var temp = ysy.gateway.temp;
+      temp.successes++;
+      request.passed = true;
+      temp.retry = true;
+      return true;
+    }).fail(function (response) {
+      var temp = ysy.gateway.temp;
+      temp.allOk = false;
+      temp.fails++;
+      if (response.responseText) {
+        try {
+          var json = JSON.parse(response.responseText);
+          if (json.errors.length) {
+            request.errorMessage = json.errors[0];
+          }
+        } catch (e) {
+          request.errorMessage = response.responseText;
+        }
+      }
+      request.errorStatus = response.statusText;
+    }).complete(ysy.gateway._process);
+    //pending.push(xhr);
+    return xhr;
+  },
+  fireSend: function (success, fail) {
+    var requestList = [];
+    var keys = Object.getOwnPropertyNames(this.requestsGroups);
+    if (keys.length === 0) {
+      success();
+      return;
+    }
+    var sortedKeys = [];
+    for (var i = 0; i < keys.length; i++) {
+      sortedKeys.push(parseFloat(keys[i]));
+    }
+    sortedKeys.sort();
+
+    for (i = 0; i < sortedKeys.length; i++) {
+      requestList = requestList.concat(this.requestsGroups[sortedKeys[i]]);
+    }
+    ysy.log.debug("_fireSend() ", "supersend");
+    this.temp = {
+      requestList: requestList,
+      superSuccess: success,
+      superFail: fail,
+      retry: true
+    };
+    this._fireRetry();
+  },
+  _fireRetry: function () {
+    var temp = ysy.gateway.temp;
+    if (temp.allOk) {
+      ysy.log.debug("superSuccess triggered", "supersend");
+      if (temp.superSuccess) {
+        temp.superSuccess();
+      }
+      this.requestsGroups = {};
+      return;
+    }
+    if (!temp.retry) {
+      ysy.log.debug("superFail triggered", "supersend");
+      if (temp.superFail) {
+        temp.superFail(this.gatherErrors());
+      }
+      this.requestsGroups = {};
+      return;
+    }
+    ysy.log.debug("_fireRetry() ", "supersend");
+    $.extend(temp, {
+      pointer: 0,
+      fails: 0,
+      successes: 0,
+      retry: false,
+      allOk: true
+    });
+    this._fireOne();
+
+  },
+  _fireOne: function () {
+    var temp = this.temp;
+    if (temp.pointer >= temp.requestList.length) {
+      this._fireRetry();
+      return;
+    }
+    var req = temp.requestList[temp.pointer];
+    temp.pointer++;
+    if (req && !req.passed) {
+      this.send(req);
+    } else {
+      this._fireOne();
+    }
+  },
+  _process: function (hrx) {
+    var temp = ysy.gateway.temp;
+    ysy.log.debug("AJAX _process (p=" + temp.pointer + ",s=" + temp.successes + ",f=" + temp.fails + ")", "supersend");
+    ysy.gateway._fireOne();
+  },
+  gatherErrors: function () {
+    var errors = [];
+    var temp = this.temp;
+    var reqList = temp.requestList;
+    if (!reqList || reqList.length === 0)return;
+    for (var j = 0; j < reqList.length; j++) {
+      var request = reqList[j];
+      if (request.passed) continue;
+      var error, name, entityType = null;
+      if (request.issueID || (request.data && request.data.issue)) {
+        entityType = "issue";
+        var issue = ysy.data.issues.getByID(request.issueID);
+        if (issue) {
+          name = '"' + issue.name + '"';
+        } else if (request.data && request.data.issue) {
+          name = '"' + request.data.issue.subject + '"';
+        } else {
+          name = "#" + request.issueID;
+        }
+      } else if (request.project) {
+        entityType = "project";
+        name = '"' + request.project.name + '"';
+      } else if (request.milestone) {
+        entityType = "milestone";
+        name = '"' + request.milestone.name + '"';
+      } else if (request.relation) {
+        entityType = "relation";
+        var source = request.relation.getSource();
+        var target = request.relation.getTarget();
+        name = '"' + source.name + '" - "' + target.name + '"';
+      }
+      if (entityType) {
+        error = ysy.settings.labels.gateway.entitySaveFailed.replace("%{entityName}", name).replace("%{entityType}", ysy.settings.labels.types[entityType]);
+      } else {
+        error = request.method + " " + request.url.substr(0, request.url.indexOf('?')) + " - " + request.errorStatus;
+      }
+      if (request.errorMessage) {
+        error += ": " + request.errorMessage;
+      } else if (entityType) {
+        error += ": " + request.errorStatus;
+      }
+      errors.push(error);
+    }
+    return errors;
+  },
+  polymorficGet: function (url, data, callback, fail) {
+    if (!url) return;
+    $.get(url, data)
+        .done(callback)
+        .fail(fail);
+  },
+  polymorficGetJSON: function (url, data, callback, fail) {
+    if (!url) return;
+    $.getJSON(url, data)
+        .done(callback)
+        .fail(fail);
+  },
+  polymorficPostJSON: function (url, data, callback, fail) {
+    if (!url) return;
+    $.ajax({
+      url: url,
+      type: "POST",
+      data: data,
+      dataType: "json"
+    }).done(callback).fail(fail);
+  },
+  polymorficPost: function (url, obj, data, callback, fail) {
+    if (!url) return;
+    $.ajax({
+      url: url,
+      type: "POST",
+      data: data,
+      dataType: "text"
+    }).done(callback).fail(fail);
+  },
+  polymorficPut: function (url, obj, data, callback, fail) {
+    if (!url) return;
+    $.ajax({
+      url: url,
+      type: "PUT",
+      data: JSON.stringify(data),
+      contentType: "application/json",
+      dataType: "text"
+    }).done(callback).fail(fail);
+  },
+  polymorficDelete: function (url, obj, callback, fail) {
+    if (!url) return;
+    $.ajax({
+      url: url,
+      type: "DELETE",
+      dataType: "json"
+    }).done(callback).fail(fail);
+  }
+
+
+});
+
+ysy.data = ysy.data || {};
+ysy.data.save = function () {
+  ysy.data.saver.sendIssues();
+  ysy.data.saver.sendRelations();
+  ysy.data.saver.sendMilestones();
+  ysy.data.saver.sendProjects();
+  ysy.gateway.fireSend(
+      ysy.data.saver.afterSaveSuccess,
+      ysy.data.saver.afterSaveFail,
+      true
+  );
+};
+ysy.data.saver = {
+  _name:"Saver",
+  sendIssues: function () {
+    var j, data;
+    var issues = ysy.data.issues.array;
+    for (j = 0; j < issues.length; j++) {
+      var issue = issues[j];
+      if (!issue._changed) continue;
+      if (issue._deleted && issue._created) continue;
+      if (issue._deleted) {
+        ysy.gateway.sendIssue("DELETE", issue.id, null, callbackBuilder(issue));
+      } else if (issue._created) {
+        data = {issue: {}};
+        for (var key in issue) {
+          if (!issue.hasOwnProperty(key)) continue;
+          if (ysy.main.startsWith(key, "_"))continue;
+          data.issue[key] = issue[key];
+        }
+        delete data.issue["name"];
+        delete data.issue["id"];
+        delete data.issue["end_date"];
+        $.extend(data.issue, {
+          subject: issue.name,
+          start_date: issue.start_date ? issue.start_date.format("YYYY-MM-DD") : undefined,
+          due_date: issue.end_date ? issue.end_date.format("YYYY-MM-DD") : undefined
+        });
+        var parents = ysy.data.saver.constructParentData(issue);
+        $.extend(data.issue, parents);
+        ysy.gateway.sendIssue("POST", null, data, this.callbackBuilder(issue));
+        //ysy.log.error("Issue "+issue.id+" cannot be created - not implemented");
+      } else {
+        data = {};
+        for (key in issue) {
+          if (!issue.hasOwnProperty(key)) continue;
+          if (ysy.main.startsWith(key, "_"))continue;
+          data[key] = issue[key];
+        }
+        delete data["end_date"];
+        delete data["columns"];
+        $.extend(data, {
+          start_date: issue.start_date ? issue.start_date.format("YYYY-MM-DD") : undefined,
+          due_date: issue.end_date ? issue.end_date.format("YYYY-MM-DD") : undefined
+        });
+        // parents = ysy.data.saver.constructParentData(issue);
+        // $.extend(data, parents);
+        ysy.proManager.fireEvent("beforeSaveIssue", data);
+        data = issue.getDiff(data);
+        if (data === null) {
+          this.callbackBuilder(issue)();
+          continue;
+        }
+        ysy.gateway.sendIssue("PUT", issue.id, {issue: data}, this.callbackBuilder(issue));
+        //console.log("Issue "+issue.id);
+      }
+    }
+  },
+  sendRelations: function () {
+    var j, data;
+    var relas = ysy.data.relations.array;
+    var repairedIssues = {};
+    for (j = 0; j < relas.length; j++) {
+      var rela = relas[j];
+      if (ysy.settings.fixedRelations) {
+        rela.makeDelayFixedForSave();
+      }
+      if (!rela._changed) continue;
+      if (rela._deleted && rela._created) continue;
+      //var callback=$.proxy(function(){this._changed=false;},rela);
+      if (rela._deleted) {
+        ysy.gateway.sendRelation("DELETE", rela, null, this.callbackBuilder(rela));
+      } else {
+        //if(rela.delay>0){data.relation.delay=rela.delay;}
+        if (rela._created) {
+          data = {
+            relation: {
+              issue_to_id: rela.target_id,
+              relation_type: rela.type,
+              delay: rela.delay
+            },
+            project_id: rela.getTarget().project_id
+          };
+          ysy.gateway.sendRelation("POST", rela, data, this.callbackBuilder(rela));
+        } else {
+          data = {delay: rela.delay};
+          ysy.gateway.sendRelation("PUT", rela, data, this.callbackBuilder(rela));
+        }
+        var targetIssue = rela.getTarget();
+        repairedIssues[targetIssue.id] = targetIssue;
+      }
+    }
+    for (var id in repairedIssues) {
+      if (!repairedIssues.hasOwnProperty(id)) continue;
+      targetIssue = repairedIssues[id];
+      var targetData = {
+        issue: {
+          start_date: targetIssue.start_date ? targetIssue.start_date.format("YYYY-MM-DD") : undefined,
+          due_date: targetIssue.end_date ? targetIssue.end_date.format("YYYY-MM-DD") : undefined
+        }
+      };
+      ysy.gateway.sendIssue("REPAIR", targetIssue.id, targetData, null);
+    }
+  },
+  sendMilestones: function () {
+    var j, data;
+    var miles = ysy.data.milestones.array;
+    for (j = 0; j < miles.length; j++) {
+      var mile = miles[j];
+      if (!mile._changed) continue;
+      if (mile._deleted && mile._created) continue;
+      //var callback=$.proxy(function(){this._changed=false;},mile);
+      if (mile._deleted) {
+        ysy.gateway.sendMilestone("DELETE", mile, null, this.callbackBuilder(mile));
+      } else if (mile._created) {
+        data = {
+          version: {
+            name: mile.name,
+            //status: the status of the version in: open (default), locked, closed
+            //sharing: the version sharing in: none (default), descendants, hierarchy, tree, system
+            description: mile.description,
+            due_date: mile.start_date.format("YYYY-MM-DD")
+          }
+        };
+        ysy.gateway.sendMilestone("POST", mile, data, this.callbackBuilder(mile));
+        //ysy.log.error("Milestone "+mile.id+" cannot be created - not implemented");
+        //ysy.gateway.sendIssue("POST",issue.id,null,callback);
+        //console.log("Issue "+issue.id+" created");
+      } else {
+        data = {
+          version: {
+            due_date: mile.start_date.format("YYYY-MM-DD")
+          }
+        };
+        ysy.gateway.sendMilestone("PUT", mile, data, this.callbackBuilder(mile));
+      }
+    }
+  },
+  sendProjects: function () {
+  },
+  callbackBuilder: function (item) {
+    return function (response) {
+      var message = (item.name || item._name);
+      //dhtmlx.message(message,"success");
+      ysy.log.debug(message + " sended", "send");
+      if (response) {
+        try {
+          var parsedResponse = JSON.parse(response);
+        } catch (e) {
+        }
+        if (parsedResponse) {
+          var model = parsedResponse.issue
+              || parsedResponse.project
+              || parsedResponse.version
+              || parsedResponse.relation;
+          if (model) {
+            item.setSilent({
+              id: model.id,
+              _created: false
+            });
+            item._fireChanges(ysy.data.saver, "afterPOST set");
+          }
+        }
+      }
+      item._changed = false;
+    }
+  },
+  constructParentData: function (issue) {
+    var data = {
+      parent_issue_id: null,
+      fixed_version_id: null,
+      project_id: issue.project_id
+    };
+    var parent;
+    if (issue.parent_issue_id) {
+      data.parent_issue_id = issue.parent_issue_id;
+      parent = ysy.data.issues.getByID(issue.parent_issue_id);
+      if (parent) {
+        data.fixed_version_id = parent.fixed_version_id;
+        //data.project_id = parent.project_id;
+      }
+    } else if (issue.fixed_version_id) {
+      data.fixed_version_id = issue.fixed_version_id;
+      //parent = ysy.data.milestones.getByID(issue.fixed_version_id);
+      //if (parent) {
+      //  data.project_id = parent.project_id;
+      //}
+    }
+    return data;
+  },
+  afterSaveSuccess: function () {
+    dhtmlx.message(ysy.view.getLabel("gateway", "allSended"), "notice", 2000);
+    ysy.data.loader.load();
+  },
+  afterSaveFail: function (errors) {
+    var string = "<p>" + ysy.view.getLabel("gateway", "sendFailed") + "</p><ul>";
+    for (var i = 0; i < errors.length; i++) {
+      string += "<li>" + errors[i] + "</li>";
+    }
+    dhtmlx.message(string + "</ul>", "error",5000);
+    //for(var i=0;i<errors.length;i++){
+    //  showFlashMessage("error",errors[i])
+    //}
+    // ysy.data.saver.openReloadModal(errors);
+    ysy.data.loader.load();
+  }
+  // openReloadModal: function (errors) {
+  //   var $target = ysy.main.getModal("form-modal", "50%");
+  //   var template = ysy.view.getTemplate("reloadModal");
+  //   //var obj = $.extend({}, ysy.view.getLabel("reloadModal"),{errors:errors});
+  //   var rendered = Mustache.render(template, {errors: errors});
+  //   $target.html(rendered);
+  //   showModal("form-modal");
+  //   $target.dialog({
+  //     buttons: [
+  //       {
+  //         id: "reload_modal_yes",
+  //         text: ysy.settings.labels.buttons.button_yes,
+  //         class: "gantt-reload-modal-button button-1",
+  //         click: function () {
+  //           $target.dialog("close");
+  //           $(".flash").remove();
+  //           ysy.data.loader.load();
+  //         }
+  //       },
+  //       {
+  //         id: "reload_modal_no",
+  //         text: ysy.settings.labels.buttons.button_no,
+  //         class: "gantt-reload-modal-button button-2",
+  //         click: function () {
+  //           $target.dialog("close");
+  //         }
+  //       }
+  //     ]
+  //   });
+  //   $("#reload_modal_yes").focus();
+  // }
+};
+
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/storage.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/storage.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ba7eecb44cd3e654be990361c23923cca96a824
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/storage.js
@@ -0,0 +1,38 @@
+/* storage.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.data = ysy.data || {};
+ysy.data.storage = ysy.data.storage || {};
+$.extend(ysy.data.storage, {
+  _scope: "easy-gantt-",
+  zoomKey: "zoom",
+  init: function () {
+
+    // ZOOM saving
+    ysy.settings.zoom.register(function () {
+      this.saveSessionData(this.zoomKey, ysy.settings.zoom.zoom);
+    }, this);
+
+  },
+  getSavedZoom: function () {
+    return this.getSessionData(this.zoomKey);
+  },
+  saveCookie: function (key, value) {
+    $.cookie(this._scope + key, value, {expires: 3650, path: '/'});
+  },
+  getCookie: function (key) {
+    return $.cookie(this._scope + key);
+  },
+  saveSessionData: function (key, value) {
+    window.sessionStorage.setItem(this._scope + key, value);
+  },
+  getSessionData: function (key) {
+    return window.sessionStorage.getItem(this._scope + key);
+  },
+  savePersistentData: function (key, value) {
+    window.localStorage.setItem(this._scope + key, value);
+  },
+  getPersistentData: function (key) {
+    return window.localStorage.getItem(this._scope + key);
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/sumrow.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/sumrow.js
new file mode 100644
index 0000000000000000000000000000000000000000..bdf4c58dffcd092f96fbf7e6b09e2a5f7b5e0b5e
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/sumrow.js
@@ -0,0 +1,201 @@
+/* sumrow.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.sumRow = ysy.pro.sumRow || {};
+$.extend(ysy.pro.sumRow, {
+  summers: {
+    count: {
+      day: function (date, issue) {
+        if (issue._start_date.isAfter(date)) return 0;
+        if (issue._end_date.isBefore(date)) return 0;
+        return 1;
+      },
+      week: function (first_date, last_date, issue) {
+        if (issue._start_date.isAfter(last_date)) return 0;
+        if (issue._end_date.isBefore(first_date)) return 0;
+        return 1;
+      },
+      //formatter:function(value){return value},
+      entities: ["issues"],
+      title: "Count"
+    }
+  },
+  patch: function () {
+    $.extend(ysy.settings.sumRow, {
+      summerArray: [],
+      setSummer: function (summer) {
+        if (!ysy.pro.sumRow.summers[summer]) return;
+        var index = this.summerArray.indexOf(summer);
+        if (index !== -1) {
+          this.summerArray.splice(index, 1);
+        }
+        this.summerArray.push(summer);
+        this.setSilent({
+          active: true,
+          summer: summer
+        });
+        this._fireChanges(this, "setSummer to " + summer);
+      },
+      removeSummer: function (summer) {
+        var index = this.summerArray.indexOf(summer);
+        if (index !== -1) {
+          this.summerArray.splice(index, 1);
+        }
+        if (this.summerArray.length === 0) {
+          this.setSilent({
+            active: false,
+            summer: false
+          });
+        } else {
+          this.setSilent({
+            active: true,
+            summer: this.summerArray[this.summerArray.length - 1]
+          });
+        }
+        this._fireChanges(this, "removeSummer " + summer);
+      }
+    });
+    // ysy.settings.sumRow.setSummer("count");
+  }
+});
+
+
+ysy.view = ysy.view || {};
+ysy.view.SumRow = function () {
+  ysy.view.Widget.call(this);
+  this.temper = {
+    values: []
+  };
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.SumRow, {
+  name: "SumRowWidget",
+  _postInit: function () {
+    ysy.data.issues.childRegister(this.invalidateIssues, this);
+    ysy.data.projects.childRegister(this.invalidateProjects, this);
+    ysy.data.issues.register(this.invalidateIssues, this);
+    ysy.data.projects.register(this.invalidateProjects, this);
+    gantt.attachEvent("onGanttRender", $.proxy(this.requestRepaint, this));
+  },
+  nopeFormatter: function () {
+    return ""
+  },
+  invalidateProjects: function () {
+    if (this.summer && this.summer.entities && this.summer.entities.indexOf("projects") > -1)
+      this.requestRepaint();
+  },
+  invalidateIssues: function () {
+    if (this.summer && (!this.summer.entities || this.summer.entities.indexOf("issues") > -1))
+      this.requestRepaint();
+  },
+  getSubScale: function (zoom) {
+    var summer = ysy.pro.sumRow.summers[ysy.settings.sumRow.summer];
+    if (!summer) return;
+    ysy.log.debug("getSubScale for " + summer.title, "summer");
+    this.summer = summer;
+    this.updateTemper();
+    var temper = this.temper;
+    // var max = Math.max.apply(null, values);
+    // var min = Math.min.apply(null, values);
+    var template = this.nopeFormatter;
+    if (temper.values) {
+      if (summer.formatter) {
+        template = function (date) {
+          var index = -temper.minDate.diff(date, zoom);
+          if (index < 0 || index > temper.valuesLength) return "";
+          return summer.formatter(temper.values[index], temper.widths[index]);
+        }
+      } else {
+        template = function (date) {
+          var index = -temper.minDate.diff(date, zoom);
+          if (index < 0 || index > temper.valuesLength) return "";
+          return temper.values[index];
+        }
+      }
+    }
+    return {
+      step: 1,
+      className: "gantt-sum-row",
+      unit: zoom,
+      template: template
+    };
+  },
+  getSummerFunction: function (zoom) {
+    return zoom === "day" ? this.summer.day : this.summer.week;
+  },
+  getEntities: function () {
+    if (!this.summer) return null;
+    var types = this.summer.entities || ["issues"];
+    var entities = [];
+    for (var i = 0; i < types.length; i++) {
+      entities = entities.concat(ysy.data[types[i]].getArray());
+    }
+    return entities;
+  },
+  _repaintCore: function () {
+    if (!ysy.settings.sumRow.active) return;
+    ysy.log.debug("sumRow repaintCore", "summer");
+    var summer = ysy.pro.sumRow.summers[ysy.settings.sumRow.summer];
+    if (!summer) return;
+    this.summer = summer;
+    this.updateTemper(this.calculateSums(ysy.settings.zoom.zoom));
+    var $target = $(".gantt_scale_line.gantt-sum-row");
+    var config = gantt.config;
+
+    var resize = gantt._get_resize_options();
+    var avail_width = resize.x ? Math.max(config.autosize_min_width, 0) : gantt.$task.offsetWidth;
+
+    var cfgs = gantt._scale_helpers.prepareConfigs([this.getSubScale(ysy.settings.zoom.zoom)], config.min_column_width, avail_width, config.scale_height - 1);
+    var html = gantt._prepare_scale_html(cfgs[0]);
+    $target.html(html);
+  },
+  updateTemper: function (values) {
+    if (values) {
+      this.temper.values = values;
+      this.temper.valuesLength = values.length;
+    }
+    //this.temper.fullRange = moment(gantt._max_date).diff(gantt._min_date, ysy.settings.zoom.zoom);
+    this.temper.minDate = moment(gantt._min_date);
+    this.temper.widths = gantt._tasks.width;
+  },
+  calculateSums: function (unit) {
+    var summerFunction = this.getSummerFunction(unit);
+    var values = [];
+    var mover = moment(gantt._min_date);
+    var maxDate = moment(gantt._max_date);
+    var issues = this.getEntities();
+    // var lastValue = 0;
+    if (unit === "day") {
+      while (mover.isBefore(maxDate)) {
+        var count = 0;
+        for (var i = 0; i < issues.length; i++) {
+          count += summerFunction(mover, issues[i]);
+        }
+        // if (summer.cumulative) {
+        //   count += lastValue;
+        // }
+        values.push(count);
+        // lastValue = count;
+        mover.add(1, unit);
+      }
+    } else {
+      var nextMover = moment(mover).add(1, unit);
+      while (mover.isBefore(maxDate)) {
+        count = 0;
+        for (i = 0; i < issues.length; i++) {
+          count += summerFunction(mover, nextMover, issues[i]);
+        }
+        // if (summer.cumulative) {
+        //   count += lastValue;
+        // }
+        values.push(count);
+        // lastValue = count;
+        mover.add(2, unit);
+        var tempMover = mover;
+        mover = nextMover;
+        nextMover = tempMover;
+      }
+    }
+    return values;
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5cd44ead4d5c15919e19e2be2c81f21197d8f6d
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/toolpanel.js
@@ -0,0 +1,95 @@
+/* toolpanel.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.pro = ysy.pro || {};
+ysy.pro.toolPanel = ysy.pro.toolPanel || {};
+$.extend(ysy.pro.toolPanel, {
+  _name: "ToolPanel",
+  extendees: [],
+  initToolbar: function (ctx) {
+    var toolPanel = new ysy.view.ToolPanel();
+    toolPanel.init(ysy.settings.toolPanel);
+    ctx.children.push(toolPanel);
+  },
+  patch: function () {
+    var toolSetting = ysy.settings.toolPanel = new ysy.data.Data();
+    toolSetting.init({
+      _name: "ToolPanel", buttonIds: [], buttons: {},
+      registerButtonSilent: function (button) {
+        if (button.id === undefined) throw("Missing id for button");
+        this.buttons[button.id] = button;
+        this.buttonIds.push(button.id);
+      }
+    });
+
+    ysy.proManager.register("initToolbar", this.initToolbar);
+
+    for (var i = 0; i < this.extendees.length; i++) {
+      var button = this.extendees[i];
+      if (button.isRemoved && button.isRemoved()) continue;
+      toolSetting.registerButtonSilent(button);
+    }
+    toolSetting._fireChanges(this, "delayed registerButton");
+    delete this.extendees;
+  },
+  registerButton: function (button) {
+    if (button.isRemoved && button.isRemoved()) return;
+    if (ysy.settings.toolPanel) {
+      ysy.settings.toolPanel.registerButtonSilent(button);
+      ysy.settings.toolPanel._fireChanges(this, "direct registerButton");
+    } else {
+      this.extendees.push(button);
+    }
+  }
+});
+
+ysy.view.ToolPanel = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.ToolPanel, {
+  name: "ToolPanelWidget",
+  templateName: "ToolButtons",
+
+  _postInit: function () {
+    var $toolPanel = $("#easy_gantt_tool_panel");
+    $toolPanel.find("a:not([href])").attr("href", "javascript:void(0)");
+    $toolPanel.find("li > *").hide();
+  },
+  _updateChildren: function () {
+    var model = this.model;
+    var children = [];
+    // this.$target = $("#content");
+    for (var i = 0; i < model.buttonIds.length; i++) {
+      var elid = model.buttonIds[i];
+      var extendee = model.buttons[elid];
+      // if (!this.getChildTarget(extendee).length) continue;
+      var button;
+      if (extendee.widget) {
+        button = new extendee.widget();
+      } else {
+        button = new ysy.view.Button();
+      }
+      $.extend(button, extendee);
+      button.init();
+      children.push(button);
+    }
+    this.children = children;
+  },
+  _repaintCore: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      this.setChildTarget(child, i);
+      child.repaint(true);
+    }
+  },
+  setChildTarget: function (child /*,i*/) {
+    var selector = "#" + child.elementPrefix + child.id;
+    if (["#select_scheme_select"].indexOf(selector) > -1) {
+      child.$target = $("<div>");
+      return;
+    }
+    var target = this.$target.find(selector);
+    if (target.length === 0) throw("element #" + child.elementPrefix + child.id + " missing");
+    child.$target = target;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/tooltip.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/tooltip.js
new file mode 100644
index 0000000000000000000000000000000000000000..50a0ab646cb013cc0a3a0611150f2bcc1f030f7f
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/tooltip.js
@@ -0,0 +1,217 @@
+/* tooltip.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.tooltip = $.extend(ysy.view.tooltip, {
+  instance: null,
+  show: function (className, event, template, out) {
+    if (!this.instance) {
+      this.instance = new ysy.view.ToolTip().init();
+    }
+    return this.instance.set(className, event, template, out);
+  },
+  hide: function () {
+    if (this.instance)
+      return this.instance.hide();
+    return false;
+  },
+  changePos: function (event) {
+    if (this.instance)
+      return this.instance.changePos(event);
+  }
+});
+ysy.view.taskTooltip = $.extend(ysy.view.taskTooltip, {
+  timeout: 0,
+  timeoutTime: 1000,
+  phase: 1,
+  /**
+   * phase 1 mouse is out
+   * phase 2 mouse on, start timer
+   * phase 3 tooltip display
+   */
+  taskTooltipInit: function () {
+    var self = this;
+    /**
+     * phase 2 mouse on, start timer 1s
+     */
+    $("#content")
+        .on("mouseenter", ".gantt_task_content, .gantt_task_progress, .gantt-task-tooltip-area", function (e) {
+          if (self.phase !== 1) return;
+          ysy.log.debug("mouseenter", "tooltip");
+          if (e.buttons !== 0) return;
+          self.phase = 2;
+          // ysy.log.debug("e.which = "+e.which+" e.button = "+ e.button+" e.buttons = "+ e.buttons);
+          self.bindHiders(e.target);
+          self.updatePos(e);
+        });
+
+  },
+  bindHiders: function (target) {
+    target.addEventListener("mouseleave", this.hideTooltip);
+    target.addEventListener("mousedown", this.hideTooltip);
+    target.addEventListener("mousemove", this.updatePos);
+  },
+  hideTooltip: function (event) {
+    var self = ysy.view.taskTooltip;
+    /**
+     * set phase 1 mouse out
+     */
+    self.phase = 1;
+    self.lastPos = null;
+    if (self.timeout) {
+      clearTimeout(self.timeout);
+    }
+    if (ysy.view.tooltip.hide()) {
+      event.target.removeEventListener("mouseleave", this.hideTooltip);
+      event.target.removeEventListener("mousedown", this.hideTooltip);
+      event.target.removeEventListener("mousemove", this.updatePos);
+    }
+  },
+  /**
+   * @param {MouseEvent} event
+   */
+  updatePos: function (event) {
+    var self = ysy.view.taskTooltip;
+    if (self.phase === 1) return;
+
+    var changed = false;
+    if (self.lastPos) {
+      if (Math.abs(self.lastPos.clientX - event.clientX) > 5) {
+        self.lastPos.clientX = event.clientX;
+        changed = true;
+      }
+      if (Math.abs(self.lastPos.clientY - event.clientY) > 5) {
+        self.lastPos.clientY = event.clientY;
+        changed = true;
+      }
+    } else {
+      self.lastPos = {clientX: event.clientX, clientY: event.clientY};
+      changed = true;
+    }
+    if (self.phase === 3 && changed) {
+      self.hideTooltip(event);
+      return;
+    }
+    self.lastPos.target = event.target;
+    if (changed) {
+      if (self.timeout) {
+        window.clearTimeout(self.timeout);
+      }
+      self.timeout = window.setTimeout(function () {
+        self.showTaskTooltip(self.lastPos)
+      }, self.timeoutTime);
+    }
+  },
+  showTaskTooltip: function (event) {
+    var self = this;
+    /**
+     * phase 3 tooltip display
+     */
+    if (self.phase !== 2) return;
+    var task = gantt._pull[gantt.locate(event)];
+    if (!task) return;
+    self.phase = 3;
+    if (event.target.parentElement.parentElement === null) {
+      self.phase = 1;
+      return;
+    }
+    var taskPos = $(event.target).offset();
+    return ysy.view.tooltip.show("gantt-task-tooltip",
+        {clientX: event.clientX, clientY: event.clientY, top: taskPos.top + gantt.config.row_height},
+        ysy.view.templates.TaskTooltip,
+        self.taskTooltipOut(task));
+  },
+
+  taskTooltipOut: function (task) {
+    var issue = task.widget.model;
+    var problemList = issue.getProblems();
+    var columns = [];
+    if (issue.milestone) {
+      if (issue.isShared) {
+        columns = [{
+          name: "shared-from",
+          label: "Shared from project",
+          value: '#' + issue.real_project_id
+        }]
+      }
+      return {
+        name: issue.name,
+        end_date: issue.start_date.format(gantt.config.date_format),
+        columns: columns
+      };
+    }
+    var columnHeads = gantt.config.columns;
+    var banned = ["subject", "start_date", "end_date", "due_date"];
+    for (var i = 0; i < columnHeads.length; i++) {
+      var columnHead = columnHeads[i];
+      if (banned.indexOf(columnHead.name) < 0) {
+        var html = columnHead.template(task);
+        if (!html) continue;
+        if (html.indexOf("<") === 0) {
+          html = $(html).html();
+        }
+        columns.push({name: columnHead.name, label: columnHead.label, value: html});
+      }
+    }
+    if (issue.fixed_version_id) {
+      var milestone = ysy.data.milestones.getByID(issue.fixed_version_id);
+    }
+    return {
+      name: issue.name,
+      start_date: issue.start_date ? issue.start_date.format(gantt.config.date_format) : moment(null),
+      end_date: issue.end_date ? issue.end_date.format(gantt.config.date_format) : moment(null),
+      milestone: milestone,
+      columns: columns,
+      problems: problemList
+    };
+  }
+});
+ysy.view.ToolTip = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.ToolTip, {
+  name: "ToolTip",
+  init: function () {
+    var $target = this.$target = $("<div id='gantt_tooltip' style='display: none'></div>").appendTo("#content");
+    $target.on("mouseleave", function () {
+      $target[0].style.display = "none";
+    });
+    ysy.view.onRepaint.push($.proxy(this.repaint, this));
+    return this;
+  },
+  set: function (className, event, template, out) {
+    this.className = className || this.className;
+    this.event = event || this.event;
+    this.template = template || this.template;
+    this.outed = out;
+    this.repaintRequested = true;
+    return this.$target;
+  },
+  hide: function () {
+    this.$target[0].style.display = "none";//.hide();
+    return true;
+  },
+  changePos: function (event) {
+    var left = event.clientX;
+    var top = event.top;
+    if (event.clientY + this.elementHeight > window.innerHeight) {
+      top -= this.elementHeight + gantt.config.row_height + 4;
+    }
+    if (event.clientX + this.elementWidth > window.innerWidth) {
+      left -= this.elementWidth;
+    }
+    this.$target[0].style.cssText = "display: block; left: " + left + "px; top: " + top + "px";
+  },
+  _repaintCore: function () {
+    this.$target.html(Mustache.render(this.template, this.outed)); // REPAINT
+    this.$target[0].style.display = "block";
+    this.$target[0].className = 'gantt-tooltip ' + this.className;
+    this.elementWidth = this.$target.outerWidth();
+    this.elementHeight = this.$target.outerHeight();
+    var event = this.event;
+    if (event) {
+      this.changePos(event);
+    }
+    return false;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/utils.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ed98e7beb520f2bf3915e3c51145eaa59d1c746
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/utils.js
@@ -0,0 +1,114 @@
+/* utils.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.main = ysy.main || {};
+$.extend(ysy.main, {
+  extender: function (parent, child, proto) {
+    function ProtoCreator() {
+    }
+    ProtoCreator.prototype = parent.prototype;
+    child.prototype = new ProtoCreator();
+    child.prototype.constructor = child;
+    $.extend(child.prototype, proto);
+  },
+  getModal: function (id, width) {
+    var $target = $("#" + id);
+    if ($target.length === 0) {
+      $target = $("<div id=" + id + ">");
+      $target.dialog({
+        width: width,
+        appendTo: document.body,
+        modal: true,
+        resizable: false,
+        dialogClass: 'modal'
+      });
+      $target.dialog("close");
+    }
+    return $target;
+  },
+  startsWith: function (text, char) {
+    if (text.startsWith) {
+      return text.startsWith(char);
+    }
+    return text.toString().charAt(0) === char;
+  },
+  isSameMoment: function (date1, date2) {
+    if (!moment.isMoment(date1)) return false;
+    if (!moment.isMoment(date2)) return false;
+    return date1.isSame(date2);
+  },
+  /**
+   * Utility function for measuring performance of some code
+   * @example
+   * var perf = createPerformanceMeter("myFunction");
+   * perf("part 1");
+   * perf("part 2");
+   * perf.whole();
+   * @param {String} groupName
+   * @return {Function}
+   */
+  createPerformanceMeter: function (groupName) {
+    var lastTime = window.performance.now();
+    var silence = false;
+    var initTime = lastTime;
+    var func = function (/** @param {String} name*/ name) {
+      if (silence) return;
+      var nowTime = window.performance.now();
+      var nameString = groupName + " " + name + ":                                  ";
+      nameString = nameString.substr(0, 30);
+      var diffString = "        " + (nowTime - lastTime).toFixed(3);
+      diffString = diffString.substr(diffString.length - 10);
+      console.debug(nameString + diffString + " ms");
+      lastTime = nowTime;
+    };
+    func.whole = function () {
+      if (silence) return;
+      var nowTime = window.performance.now();
+      var nameString = groupName + ":                                  ";
+      nameString = nameString.substr(0, 30);
+      var diffString = "        " + (nowTime - initTime).toFixed(3);
+      diffString = diffString.substr(diffString.length - 10);
+      console.debug(nameString + diffString + " ms");
+    };
+    func.silence = function (verbose) {
+      silence = !verbose;
+    };
+    return func;
+  },
+  /**
+   *
+   * @param {Array.<{name:String,value:String}>} formData
+   * @return {Object}
+   */
+  formToJson: function (formData) {
+    var result = {};
+    var prolong = function (result, split, value) {
+      var key = split.shift();
+      if (key === "") {
+        result.push(value);
+      } else {
+        if (split.length > 0) {
+          var next = split[0];
+          if (!result[key]) {
+            if (next === "") {
+              result[key] = [];
+            } else {
+              result[key] = {};
+            }
+          }
+          prolong(result[key], split, value);
+        } else {
+          result[key] = value;
+        }
+      }
+    };
+    for (var i = 0; i < formData.length; i++) {
+      var split = formData[i].name.split(/]\[|\[|]/);
+      if (split.length > 1) {
+        split.pop();
+      }
+      prolong(result, split, formData[i].value);
+    }
+    return result;
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/view.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/view.js
new file mode 100644
index 0000000000000000000000000000000000000000..97b1d77693411d401caf41cf71bcf049feadd718
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/view.js
@@ -0,0 +1,48 @@
+/* view.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+$.extend(ysy.view, {
+  onRepaint: [],
+  patch: function () {
+    this.applyGanttRewritePatch();
+    this.addGanttAddons();
+    this.applyGanttPatch();
+    if (!window.initEasyAutocomplete) {
+      window.initEasyAutocomplete = function () {
+      };
+    }
+    if (ysy.settings.easyRedmine && $("#content").children(".easy-content-page").length === 0) {
+      $("#easy_gantt").addClass("easy-content-page");
+    }
+  },
+  start: function () {
+    this.labels = ysy.settings.labels;
+    var main = new ysy.view.Main();
+    main.init(ysy.data.projects);
+    this.anim();
+  },
+  anim: function () {
+    var view = ysy.view;
+    for (var i = 0; i < view.onRepaint.length; i++) {
+      view.onRepaint[i]();
+    }
+    //requestAnimFrame($.proxy(this.anim, this));
+    requestAnimFrame(view.anim);
+  },
+  getTemplate: function (name) {
+    return this.templates[name];
+  },
+  getLabel: function () {
+    var temp = this.labels;
+    for (var i = 0; i < arguments.length; i++) {
+      var arg = arguments[i];
+      if (temp[arg]) {
+        temp = temp[arg];
+      } else {
+        return temp;
+      }
+    }
+    return temp;
+  }
+});
diff --git a/plugins/easy_gantt/assets/javascripts/easy_gantt/widget.js b/plugins/easy_gantt/assets/javascripts/easy_gantt/widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..54a6b9c8796f14e54cb47a2707b1d9713ad4e2af
--- /dev/null
+++ b/plugins/easy_gantt/assets/javascripts/easy_gantt/widget.js
@@ -0,0 +1,256 @@
+/* widget.js */
+/* global ysy */
+window.ysy = window.ysy || {};
+ysy.view = ysy.view || {};
+ysy.view.Widget = function () {
+  /*
+   *Widget class is a base class for all other Widgets.
+   *It implement basic repaint, init and _register functions, which in most cases dont have to changed.
+   */
+  //this.template = null;  // nutne zakomentovat, aby to
+  this.$target = null;
+  this.parent = null;
+  this.children = [];
+  this.repaintRequested = true;
+  this.keepPaintedState = false;
+  this.deleted = false;
+  this.regs = [null, null, null, null];
+};
+ysy.view.Widget.prototype = {
+  name: "Widget",
+  init: function (modl) {
+    if (modl instanceof Array) {
+      ysy.log.error("Array Model");
+    }
+    if (this.model) {
+      this.model.unregister(this);
+    }
+    if (arguments.length > 1) {
+      for (var i = 1; i < arguments.length; i++) {
+        if (!arguments[i]) continue;
+        this._register(arguments[i], i);
+      }
+    }
+    if (modl) {
+      this.model = modl;
+      this._register(modl);
+    }
+    this._updateChildren();
+    this._postInit();
+    return this;
+  },
+  requestRepaint: function () {
+    this.repaintRequested = true;
+  },
+  _updateChildren: function () {
+  },
+  _postInit: function () {
+  },
+  _register: function (model, pos) {
+    //if (model === undefined) {
+    //  ysy.log.error("no model for register in "+this.name);
+    //}
+    if (!model) return;
+    if (pos) {
+      if (this.regs[pos]) {
+        this.regs[pos].unregister(this);
+      }
+      this.regs[pos] = model;
+    }
+    model.register(function () {
+      this._updateChildren();
+      this.requestRepaint();
+    }, this);
+  },
+  out: function () {
+
+  },
+  repaint: function (force) {
+    if (this.hidden) {
+      this.repaintRequested = true;
+      return;
+    }
+    if (this.keepPaintedState) {
+      this.onNoRepaint();
+      return;
+    }
+    if (this.repaintRequested || force) {
+      ysy.log.log("--- RepaintCore in " + this.name);
+      this.repaintRequested = !!this._repaintCore();
+    } else {
+      this.onNoRepaint();
+      for (var i = 0; i < this.children.length; i++) {
+        this.children[i].repaint();
+      }
+    }
+  },
+  _repaintCore: function () {
+    if (!this.template) {
+      var templ = ysy.view.getTemplate(this.templateName);
+      if (templ) {
+        this.template = templ;
+      } else {
+        return true;
+      }
+    }
+    if (this.$target === null) {
+      throw "Target is null for " + this.templateName;
+    }
+    this.$target.html(Mustache.render(this.template, this.out())); // REPAINT
+    this.tideFunctionality(); //   TIDE FUNCTIONALITY
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      this.setChildTarget(child, i); //   SET CHILD TARGET
+      child.repaint(true); //  CHILD REPAINT
+    }
+    return false;
+  },
+  onNoRepaint: function () {
+
+  },
+  tideFunctionality: function () {
+
+  },
+  setChildTarget: function (child, i) {
+
+  },
+  destroy: function () {
+    this.deleted = true;
+  },
+  _getChildByID: function (id) {
+    for (var i = 0; i < this.children.length; i++) {
+      if (this.children[i].id === id) {
+        return this.children[i];
+      }
+    }
+    return null;
+  },
+  _getChildByName: function (name) {
+    for (var i = 0; i < this.children.length; i++) {
+      if (this.children[i].name === name) {
+        return this.children[i];
+      }
+    }
+    return null;
+  }
+};
+ysy.view.Main = function () {
+  ysy.view.Widget.call(this);
+  this.name = "MainWidget";
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.Main, {
+  init: function (mod) {
+    this.$target = $("#easy_gantt");
+    this.model = mod;
+    ysy.view.onRepaint.push($.proxy(this.repaint, this));
+    this._register(null);
+    this._updateChildren();
+    ysy.view.mainWidget = this;
+  },
+  _updateChildren: function () {
+    if (this.children.length > 0) {
+      return;
+    }
+    var toolbars = new ysy.view.Toolbars();
+    toolbars.init();
+    toolbars.$target = $("#content");
+    this.children.push(toolbars);
+
+    var mainGantt = new ysy.view.Gantt();
+    mainGantt.$target = $("#gantt_cont")[0];
+    mainGantt.init(/*ysy.data.limits,*//*ysy.data.loader,*/ ysy.data.baselines);
+    this.children.push(mainGantt);
+  },
+  _repaintCore: function () {
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      this.setChildTarget(child, i);
+      child.repaint(true);
+    }
+  },
+  setChildTarget: function (child/*, i*/) {
+    //if (this.childTargets[child.name]) {
+    //  child.$target = this.$target.find(this.childTargets[child.name]);
+    //}
+    //if (child.name === "AllButtonsWidget") {
+    //  child.$target = $("#content");
+    //}
+  }
+});
+//##############################################################################
+
+//##############################################################################
+
+//##############################################################################
+ysy.view.LinkPopup = function () {
+  ysy.view.Widget.call(this);
+};
+ysy.main.extender(ysy.view.Widget, ysy.view.LinkPopup, {
+  name: "PopupWidget",
+  templateName: "LinkConfigPopup",
+  init: function (model, dhtml) {
+    if (this.model) {
+      this.model.unregister(this);
+    }
+    this.model = model;
+    this.dhtml = dhtml;
+    this._register(model);
+    return this;
+  },
+  requestRepaint: function () {
+    ysy.log.debug("Popup requestRepaint()", "link_config");
+    //this.$target.hide();
+  },
+  out: function () {
+    ysy.log.debug("Popup out()", "link_config");
+    var delayLabels = ysy.view.getLabel("delay");
+    var buttonLabels = ysy.view.getLabel("buttons");
+    return {
+      readonly:this.dhtml.readonly,
+      title: delayLabels.title,
+      delay: this.dhtml.delay,
+      label_delay: delayLabels.label,
+      button_delete: buttonLabels.button_delete,
+      button_submit: buttonLabels.button_submit,
+      minimal:ysy.settings.workDayDelays?-1:""
+    };
+  },
+  tideFunctionality: function () {
+    var model = this.model;
+    var dhtml = this.dhtml;
+    var $target = this.$target;
+    if (!ysy.settings.easyRedmine) $target.addClass("redmine");
+    var close = function () {
+      ysy.log.debug("close link popup", "link_config");
+      hideModal();
+    };
+    $target.find("#link_delete").on("click", function () {
+      model.remove();
+      close();
+    });
+    $target.keyup(function (event) {
+      if (event.keyCode == 13) {
+        $("#link_close").click();
+      }
+    });
+    $target.find("#link_close").on("click", function () {
+      var delay = parseInt($target.find("#link_delay_input").val());
+      close();
+      if (isNaN(delay) || delay === dhtml.delay) return;
+      if(ysy.settings.workDayDelays && delay < -1) return;
+      dhtml.delay = delay || 0;
+      dhtml.widget.update(dhtml);
+    });
+    $target.find("#link_fix_actual").on("click", function () {
+      var delay = model.getActDelay();
+      close();
+      dhtml.delay = delay || 0;
+      dhtml.widget.update(dhtml);
+    });
+    $target.find("#link_remove_delay").on("click", function () {
+      close();
+      dhtml.delay = 0;
+      dhtml.widget.update(dhtml);
+    });
+  }
+});
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt.css b/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt.css
new file mode 100644
index 0000000000000000000000000000000000000000..2ee152a4d0c3480a76aaf8c340a56e9c60f4143d
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt.css
@@ -0,0 +1,1367 @@
+/*
+@license
+
+dhtmlxGantt v.3.2.1 Stardard
+This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
+
+(c) Dinamenta, UAB.
+*/
+
+.gridHoverStyle,
+.gridSelection,
+.timelineSelection {
+    background-color: #fff3a1
+}
+.gantt_grid_scale .gantt_grid_head_cell {
+    color: #a6a6a6;
+    border-top: none!important;
+    /*border-right: none!important*/
+}
+/*.gantt_grid_data .gantt_cell {
+    border-right: none;
+    color: #454545
+}*/
+.gantt_task_link .gantt_link_arrow_right {
+    border-width: 6px;
+    margin-top: -3px
+}
+.gantt_task_link .gantt_link_arrow_left {
+    border-width: 6px;
+    margin-left: -6px;
+    margin-top: -3px
+}
+.gantt_task_link .gantt_link_arrow_down,
+.gantt_task_link .gantt_link_arrow_top {
+    border-width: 6px
+}
+.gantt_task_line .gantt_task_progress_drag {
+    bottom: -4px;
+    height: 16px;
+    margin-left: -8px;
+    width: 16px
+}
+.chartHeaderBg {
+    background-color: #fff
+}
+.gantt_task .gantt_task_scale .gantt_scale_cell {
+    color: #a6a6a6;
+    border-right: 1px solid #ebebeb
+}
+
+/*.gantt_row.gantt_project-type,*/
+/*.gantt_row.odd.gantt_project-type {*/
+/*background-color: #edffef*/
+/*}*/
+.gantt_task_row.gantt_project-type,
+.gantt_task_row.odd.gantt_project-type {
+    background-color: #f5fff6
+}
+.gantt_task_line.gantt_project-type {
+    background-color: #65c16f;
+    border: 1px solid #3c9445
+}
+.gantt_task_line.gantt_project-type .gantt_task_progress {
+    background-color: #46ad51
+}
+.buttonBg {
+    background: #fff
+}
+.gantt_cal_light .gantt_btn_set {
+    margin: 5px 10px
+}
+.gantt_btn_set.gantt_cancel_btn_set {
+    background: #fff;
+    color: #454545;
+    border: 1px solid #cecece
+}
+.gantt_btn_set.gantt_save_btn_set {
+    background: #3db9d3;
+    text-shadow: 0 -1px 0 #248a9f;
+    color: #fff
+}
+.gantt_btn_set.gantt_delete_btn_set {
+    background: #ec8e00;
+    text-shadow: 0 -1px 0 #a60;
+    color: #fff
+}
+.gantt_cal_light_wide {
+    padding-left: 0!important;
+    padding-right: 0!important
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+    border-left: none!important;
+    border-right: none!important
+}
+.dhtmlx_popup_button.dhtmlx_ok_button {
+    background: #3db9d3;
+    text-shadow: 0 -1px 0 #248a9f;
+    color: #fff;
+    font-weight: 700;
+    border-width: 0
+}
+.dhtmlx_popup_button.dhtmlx_cancel_button {
+    font-weight: 700;
+    color: #454544
+}
+.gantt_qi_big_icon.icon_edit {
+    color: #454545;
+    background: #fff
+}
+.gantt_qi_big_icon.icon_delete {
+    text-shadow: 0 -1px 0 #a60;
+    background: #ec8e00;
+    color: #fff;
+    border-width: 0
+}
+.gantt_container {
+    font-family: Arial;
+    font-size: 13px;
+    border: 1px solid #cecece;
+    position: relative;
+    white-space: nowrap
+}
+.gantt_grid {
+    border-right: 1px solid #cecece
+}
+.gantt_task_scroll {
+    overflow-x: scroll
+}
+.gantt_task {
+    position: relative
+}
+.gantt_grid,
+.gantt_task {
+    overflow-x: hidden;
+    overflow-y: hidden;
+    display: inline-block;
+    vertical-align: top
+}
+.gantt_grid_scale,
+.gantt_task_scale {
+    color: #6b6b6b;
+    font-size: 12px;
+    border-bottom: 1px solid #cecece;
+    background-color: #fff
+}
+.gantt_scale_line {
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    border-top: 1px solid #cecece
+}
+.gantt_scale_line:first-child {
+    border-top: none
+}
+.gantt_grid_head_cell {
+    display: inline-block;
+    vertical-align: top;
+    border-right: 1px solid #cecece;
+    text-align: center;
+    position: relative;
+    cursor: default;
+    height: 100%;
+    -moz-user-select: -moz-none;
+    -webkit-user-select: none;
+    -user-select: none;
+    -ms-user-select: none;
+    overflow: hidden
+}
+.gantt_scale_line {
+    clear: both
+}
+.gantt_grid_data {
+    width: 100%;
+    overflow: hidden
+}
+.gantt_row {
+    position: relative;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    -moz-user-select: -moz-none
+}
+.gantt_add,
+.gantt_grid_head_add {
+    width: 100%;
+    height: 100%;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);
+    background-position: center center;
+    background-repeat: no-repeat;
+    cursor: pointer;
+    position: relative;
+    -moz-opacity: .3;
+    opacity: .3
+}
+.gantt_grid_head_cell.gantt_grid_head_add {
+    -moz-opacity: .6;
+    opacity: .6;
+    top: 0
+}
+.gantt_grid_head_cell.gantt_grid_head_add:hover {
+    -moz-opacity: 1;
+    opacity: 1
+}
+@media screen {
+    .gantt_grid_data .gantt_row.odd:hover,
+    .gantt_grid_data .gantt_row:hover {
+        background-color: #fff3a1
+    }
+}
+.gantt_grid_data .gantt_row.odd:hover .gantt_add,
+.gantt_grid_data .gantt_row:hover .gantt_add {
+    -moz-opacity: 1;
+    opacity: 1
+}
+.gantt_row,
+.gantt_task_row {
+    border-bottom: 1px solid #ebebeb;
+    /*background-color: #fff*/
+}
+.gantt_row.odd,
+.gantt_task_row.odd {
+    /*background-color: #fff*/
+}
+.gantt_cell,
+.gantt_grid_head_cell,
+.gantt_row,
+.gantt_scale_cell,
+.gantt_task_cell,
+.gantt_task_row {
+    box-sizing: border-box;
+    -moz-box-sizing: border-box
+}
+.gantt_grid_head_cell,
+.gantt_scale_cell {
+    line-height: inherit
+}
+.gantt_grid .gantt_grid_resize_wrap {
+    cursor: col-resize;
+    position: absolute;
+    width: 13px;
+    z-index: 1
+}
+.gantt_grid_resize_wrap .gantt_grid_resize {
+    background-color: #cecece;
+    width: 1px;
+    margin: 0 auto
+}
+.gantt_drag_marker.gantt_grid_resize_area {
+    background-color: rgba(231, 231, 231, .5);
+    border-left: 1px solid #cecece;
+    border-right: 1px solid #cecece;
+    height: 100%;
+    width: 100%;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box
+}
+.gantt_cell {
+    display: inline-block;
+    vertical-align: top;
+    border-right: 1px solid #ebebeb;
+    padding-left: 6px;
+    padding-right: 6px;
+    height: 100%;
+    overflow: hidden;
+    white-space: nowrap;
+    font-size: 13px
+}
+.gantt_grid_data .gantt_last_cell,
+.gantt_grid_scale .gantt_last_cell,
+.gantt_task_bg .gantt_last_cell,
+.gantt_task_scale .gantt_last_cell {
+    border-right-width: 0
+}
+.gantt_task_bg {
+    overflow: hidden
+}
+.gantt_scale_cell {
+    display: inline-block;
+    white-space: nowrap;
+    overflow: hidden;
+    border-right: 1px solid #cecece;
+    text-align: center;
+    height: 100%
+}
+.gantt_task_cell {
+    display: inline-block;
+    height: 100%;
+    border-right: 1px solid #ebebeb
+}
+.gantt_ver_scroll {
+    width: 0;
+    background-color: transparent;
+    height: 1px;
+    overflow-x: hidden;
+    overflow-y: scroll;
+    display: none;
+    position: absolute;
+    right: 0
+}
+.gantt_ver_scroll>div {
+    width: 1px;
+    height: 1px
+}
+.gantt_hor_scroll {
+    height: 0;
+    background-color: transparent;
+    width: 100%;
+    clear: both;
+    overflow-x: scroll;
+    overflow-y: hidden;
+    display: none
+}
+.gantt_hor_scroll>div {
+    width: 5000px;
+    height: 1px
+}
+.gantt_tree_indent {
+    width: 15px;
+    /*height: 100%;*/
+    display: inline-block
+}
+.gantt_tree_content,
+.gantt_tree_icon {
+    vertical-align: top
+}
+.gantt_tree_icon {
+    width: 28px;
+    height: 100%;
+    display: inline-block;
+    background-repeat: no-repeat;
+    background-position: center center
+}
+.gantt_tree_content {
+    height: 100%;
+    display: inline-block
+}
+.gantt_tree_icon.gantt_open {
+    background-image: url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0I5RTczQjVDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0I5RTczQjZDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQjlFNzNCM0MwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozQjlFNzNCNEMwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARJMMlJq704661B+SAIXAVhnKhBFKSZnmuLImhslXPN3ibi+6pdBXc4IIpB2YkGE1IKAoL0ICUInJNCYMDtDgJYiScUGnHO6LQkAgA7);
+    width: 18px;
+    cursor: pointer
+}
+.gantt_tree_icon.gantt_close {
+    background-image: url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39wAAAP///////wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzY0QzNGM0VDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzY0QzNGM0ZDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozNjRDM0YzQ0MwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozNjRDM0YzREMwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARDMMlJq704661B+SAIXAVhnKhBFKSZnmv7wqxVzmpd3Uff5zKEUAi0uV4xm4DAbBIEOkohMKhaB4HoxBMKjTjgsFgSAQA7);
+    width: 18px;
+    cursor: pointer
+}
+.gantt_tree_icon.gantt_blank {
+    width: 18px
+}
+.gantt_tree_icon.gantt_folder_open {
+    background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=)
+}
+.gantt_tree_icon.gantt_folder_closed {
+    background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=)
+}
+.gantt_tree_icon.gantt_file {
+    background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==)
+}
+.gantt_grid_head_cell .gantt_sort {
+    position: absolute;
+    right: 5px;
+    top: 8px;
+    width: 7px;
+    height: 13px;
+    background-repeat: no-repeat;
+    background-position: center center
+}
+.gantt_grid_head_cell .gantt_sort.gantt_asc {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)
+}
+.gantt_grid_head_cell .gantt_sort.gantt_desc {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)
+}
+.gantt_inserted,
+.gantt_updated {
+    font-weight: 700
+}
+.gantt_deleted {
+    text-decoration: line-through
+}
+.gantt_invalid {
+    background-color: #FFE0E0
+}
+.gantt_error {
+    color: red
+}
+.gantt_status {
+    right: 1px;
+    padding: 5px 10px;
+    background: rgba(155, 155, 155, .1);
+    position: absolute;
+    top: 1px;
+    -webkit-transition: opacity .2s;
+    transition: opacity .2s;
+    opacity: 0
+}
+.gantt_status.gantt_status_visible {
+    opacity: 1
+}
+#gantt_ajax_dots span {
+    -webkit-transition: opacity .2s;
+    transition: opacity .2s;
+    background-repeat: no-repeat;
+    opacity: 0
+}
+#gantt_ajax_dots span.gantt_dot_visible {
+    opacity: 1
+}
+.dhtmlx_message_area {
+    position: fixed;
+    right: 5px;
+    width: 250px;
+    z-index: 1000
+}
+.dhtmlx-message {   // HOSEK
+    min-width: 120px;
+    font-family: Arial;
+    z-index: 10000;
+    margin: 5px 5px 10px;
+    -webkit-transition: all .5s ease;
+    -moz-transition: all .5s ease;
+    -o-transition: all .5s ease;
+    transition: all .5s ease;
+    font-size: 14px;
+    color: #000;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    padding: 0;
+    background-color: #FFF;
+    border-radius: 3px;
+    border: 1px solid #fff
+}
+.dhtmlx-message.hidden {  // HOSEK
+    height: 0;
+    padding: 0;
+    border-width: 0;
+    margin: 0;
+    overflow: hidden
+}
+.dhtmlx_modal_box {
+    overflow: hidden;
+    display: inline-block;
+    min-width: 250px;
+    width: 250px;
+    text-align: center;
+    position: fixed;
+    z-index: 20000;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    font-family: Arial;
+    border-radius: 6px;
+    border: 1px solid #cecece;
+    background: #fff
+}
+.dhtmlx_popup_title {
+    border-top-left-radius: 6px;
+    border-top-right-radius: 6px;
+    border-width: 0
+}
+.dhtmlx_button,
+.dhtmlx_popup_button {
+    border: 1px solid #cecece;
+    height: 30px;
+    line-height: 30px;
+    display: inline-block;
+    margin: 0 5px;
+    border-radius: 4px;
+    background: #fff
+}
+.dhtmlx-message,   /* HOSEK*/
+.dhtmlx_button,
+.dhtmlx_popup_button {
+    user-select: none;
+    -webkit-user-select: none;
+    -moz-user-select: -moz-none;
+    -ms-user-select: none;
+    cursor: pointer
+}
+.dhtmlx_popup_text {
+    overflow: hidden
+}
+.dhtmlx_popup_controls {
+    border-radius: 6px;
+    padding: 10px
+}
+.dhtmlx_popup_button {
+    min-width: 100px
+}
+div.dhx_modal_cover {
+    background-color: #000;
+    cursor: default;
+    filter: alpha(opacity=20);
+    opacity: .2;
+    position: fixed;
+    z-index: 19999;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    border: none;
+    zoom: 1
+}
+.dhtmlx-message img,   /* HOSEK */
+.dhtmlx_modal_box img {
+    float: left;
+    margin-right: 20px
+}
+.dhtmlx-alert-error,
+.dhtmlx-confirm-error {
+    border: 1px solid red
+}
+.dhtmlx_button input,
+.dhtmlx_popup_button div {
+    border-radius: 4px;
+    font-size: 14px;
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+    padding: 0;
+    margin: 0;
+    vertical-align: top
+}
+.dhtmlx_popup_title {
+    color: #fff;
+    text-shadow: 1px 1px #000;
+    height: 40px;
+    line-height: 40px;
+    font-size: 20px
+}
+.dhtmlx_popup_text {
+    margin: 15px 15px 5px;
+    font-size: 14px;
+    color: #000;
+    min-height: 30px;
+    border-radius: 6px
+}
+
+/* HOSEK  V */
+/*.dhtmlx-error,
+.dhtmlx-info {
+    font-size: 14px;
+    color: #000;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    padding: 0;
+    background-color: #FFF;
+    border-radius: 3px;
+    border: 1px solid #fff
+}*/
+.dhtmlx-message div {
+    padding: 5px 10px;
+    border-radius: 3px;
+}
+.dhtmlx-info div {
+    background-color: #fff;
+    border: 1px solid #cecece;
+}
+.dhtmlx-error {
+    background-color: #d81b1b;
+    border: 1px solid #ff3c3c;
+}
+.dhtmlx-error div {
+    background-color: #d81b1b;
+    border: 1px solid #940000;
+    color: #FFF;
+}
+.dhtmlx-success {
+    background-color: #2dff2d;
+    border: 1px solid #6CFF6C;
+}
+.dhtmlx-success div {
+    background-color: #2dff2d;
+    border: 1px solid #1B991B;
+    /*color: #FFF*/
+}
+/* HOSEK A */
+.gantt_data_area div,
+.gantt_grid div {
+    -ms-touch-action: none;
+    -webkit-tap-highlight-color: transparent
+}
+.gantt_data_area {
+    position: relative;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    -moz-user-select: -moz-none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+.gantt_links_area {
+    position: absolute;
+    left: 0;
+    top: 0
+}
+.gantt_side_content,
+.gantt_task_content,
+.gantt_task_progress {
+    line-height: inherit;
+    overflow: hidden;
+    height: 100%
+}
+.gantt_task_content {
+    font-size: 12px;
+    /*color: #fff;*/
+    width: 100%;
+    top: 0;
+    position: absolute;
+    white-space: nowrap;
+    text-align: center
+}
+.gantt_task_progress {
+    text-align: center;
+    z-index: 0;
+    background: #299cb4
+}
+.gantt_task_line {
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+    position: absolute;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    background-color: #3db9d3;
+    border: 1px solid #2898b0;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    -moz-user-select: -moz-none
+}
+.gantt_task_line.gantt_drag_move div {
+    cursor: move
+}
+.gantt_touch_move,
+.gantt_touch_progress .gantt_touch_resize {
+    -moz-transform: scale(1.02, 1.1);
+    -o-transform: scale(1.02, 1.1);
+    -webkit-transform: scale(1.02, 1.1);
+    transform: scale(1.02, 1.1);
+    -moz-transform-origin: 50%;
+    -o-transform-origin: 50%;
+    -webkit-transform-origin: 50%;
+    transform-origin: 50%
+}
+.gantt_touch_progress .gantt_task_progress_drag,
+.gantt_touch_resize .gantt_task_drag {
+    -moz-transform: scaleY(1.3);
+    -o-transform: scaleY(1.3);
+    -webkit-transform: scaleY(1.3);
+    transform: scaleY(1.3);
+    -moz-transform-origin: 50%;
+    -o-transform-origin: 50%;
+    -webkit-transform-origin: 50%;
+    transform-origin: 50%
+}
+.gantt_side_content {
+    position: absolute;
+    white-space: nowrap;
+    /*color: #6e6e6e;*/
+    bottom: 1px;
+    /*font-size: 14px*/
+}
+.gantt_side_content.gantt_left {
+    right: 100%;
+    padding-right: 15px
+}
+.gantt_side_content.gantt_right {
+    left: 100%;
+    padding-left: 15px
+}
+.gantt_side_content.gantt_link_crossing {
+    /*bottom: 8.75px*/
+}
+.gantt_link_arrow,
+.gantt_task_link .gantt_line_wrapper {
+    position: absolute;
+    cursor: pointer
+}
+.gantt_line_wrapper div {
+    /*background-color: #ffa011;*/
+    border: 1px solid #ffa011;
+}
+.gantt_task_link:hover .gantt_line_wrapper div {
+    box-shadow: 0 0 5px 0 #ffa011
+}
+.gantt_task_link div.gantt_link_arrow {
+    background-color: transparent;
+    border-style: solid;
+    width: 0;
+    height: 0
+}
+.gantt_link_control {
+    position: absolute;
+    width: 13px;
+    top: 0
+}
+.gantt_link_control div {
+    display: none;
+    cursor: pointer;
+    box-sizing: border-box;
+    position: relative;
+    top: 50%;
+    margin-top: -7.5px;
+    vertical-align: middle;
+    border: 1px solid #929292;
+    -webkit-border-radius: 6.5px;
+    -moz-border-radius: 6.5px;
+    border-radius: 6.5px;
+    height: 13px;
+    width: 13px;
+    background-color: #f0f0f0
+}
+.gantt_link_control div:hover {
+    background-color: #fff
+}
+.gantt_link_control.task_left {
+    left: -13px
+}
+.gantt_link_control.task_right {
+    right: -13px
+}
+.gantt_link_target .gantt_link_control div,
+.gantt_task_line.gantt_selected .gantt_link_control div,
+.gantt_task_line:hover .gantt_link_control div {
+    display: block
+}
+.gantt_link_source,
+.gantt_link_target {
+    box-shadow: 0 0 3px #3db9d3
+}
+.gantt_link_target.link_finish_allow,
+.gantt_link_target.link_start_allow {
+    box-shadow: 0 0 3px #ffbf5e
+}
+.gantt_link_target.link_finish_deny,
+.gantt_link_target.link_start_deny {
+    box-shadow: 0 0 3px #e87e7b
+}
+.link_finish_allow .gantt_link_control.task_right div,
+.link_start_allow .gantt_link_control.task_left div {
+    background-color: #ffbf5e;
+    border-color: #ffa011
+}
+.link_finish_deny .gantt_link_control.task_right div,
+.link_start_deny .gantt_link_control.task_left div {
+    background-color: #e87e7b;
+    border-color: #dd3e3a
+}
+.gantt_link_arrow_right {
+    border-width: 4px 0 4px 6px;
+    border-top-color: transparent!important;
+    border-right-color: transparent!important;
+    border-bottom-color: transparent!important;
+    border-color: #ffa011;
+    margin-top: -1px
+}
+.gantt_link_arrow_left {
+    border-width: 4px 6px 4px 0;
+    margin-top: -1px;
+    border-top-color: transparent!important;
+    border-color: #ffa011;
+    border-bottom-color: transparent!important;
+    border-left-color: transparent!important
+}
+.gantt_link_arrow_top {
+    border-width: 0 4px 6px;
+    border-color: #ffa011;
+    border-top-color: transparent!important;
+    border-right-color: transparent!important;
+    border-left-color: transparent!important
+}
+.gantt_link_arrow_down {
+    border-width: 4px 6px 0 4px;
+    border-color: #ffa011;
+    border-right-color: transparent!important;
+    border-bottom-color: transparent!important;
+    border-left-color: transparent!important
+}
+.gantt_task_drag,
+.gantt_task_progress_drag {
+    cursor: w-resize;
+    height: 100%;
+    display: none;
+    position: absolute
+}
+.gantt_task_line.gantt_selected .gantt_task_drag,
+.gantt_task_line.gantt_selected .gantt_task_progress_drag,
+.gantt_task_line:hover .gantt_task_drag,
+.gantt_task_line:hover .gantt_task_progress_drag {
+    display: block
+}
+.gantt_task_drag {
+    width: 6px;
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAACCAYAAAB7Xa1eAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYDDjkw3UJvAwAAABRJREFUCNdj/P//PwM2wASl/6PTAKrrBf4+lD8LAAAAAElFTkSuQmCC);
+    z-index: 1;
+    top: 0
+}
+.gantt_task_drag.task_left {
+    left: 0
+}
+.gantt_task_drag.task_right {
+    right: 0
+}
+.gantt_task_progress_drag {
+    height: 8px;
+    width: 8px;
+    bottom: -4px;
+    margin-left: -4px;
+    background-position: bottom;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=);
+    background-repeat: no-repeat;
+    z-index: 2
+}
+.gantt_link_tooltip {
+    box-shadow: 3px 3px 3px #888;
+    background-color: #fff;
+    border-left: 1px dotted #cecece;
+    border-top: 1px dotted #cecece;
+    font-family: Tahoma;
+    font-size: 8pt;
+    color: #444;
+    padding: 6px;
+    line-height: 20px
+}
+.gantt_link_direction {
+    height: 0;
+    border: 0 #ffa011;
+    border-bottom-style: dashed;
+    border-bottom-width: 2px;
+    transform-origin: 0 0;
+    -ms-transform-origin: 0 0;
+    -webkit-transform-origin: 0 0;
+    z-index: 2;
+    margin-left: 1px;
+    position: absolute
+}
+.gantt_grid_data .gantt_row.gantt_selected,
+.gantt_grid_data .gantt_row.odd.gantt_selected,
+.gantt_task_row.gantt_selected {
+    background-color: #fff3a1
+}
+.gantt_task_row.gantt_selected .gantt_task_cell {
+    border-right-color: #ffec6e
+}
+.gantt_task_line.gantt_selected {
+    box-shadow: 0 0 5px #299cb4
+}
+.gantt_task_line.gantt_project-type.gantt_selected {
+    box-shadow: 0 0 5px #46ad51
+}
+.gantt_task_line.gantt_milestone-type {
+    visibility: hidden;
+    background-color: #d33daf;
+    border: 0 solid #61164f;
+    box-sizing: content-box;
+    -moz-box-sizing: content-box
+}
+.gantt_task_line.gantt_milestone-type div {
+    visibility: visible
+}
+.gantt_task_line.gantt_milestone-type .gantt_task_content {
+    background: inherit;
+    border: inherit;
+    border-width: 1px;
+    border-radius: inherit;
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-transform: rotate(45deg);
+    -moz-transform: rotate(45deg);
+    -ms-transform: rotate(45deg);
+    -o-transform: rotate(45deg);
+    transform: rotate(45deg)
+}
+.gantt_task_line.gantt_task_inline_color {
+    border-color: #999
+}
+.gantt_task_line.gantt_task_inline_color .gantt_task_progress {
+    background-color: #363636;
+    opacity: .2
+}
+.gantt_task_line.gantt_task_inline_color.gantt_project-type.gantt_selected,
+.gantt_task_line.gantt_task_inline_color.gantt_selected {
+    box-shadow: 0 0 5px #999
+}
+.gantt_task_link.gantt_link_inline_color:hover .gantt_line_wrapper div {
+    box-shadow: 0 0 5px 0 #999
+}
+.gantt_critical_task {
+    background-color: #e63030;
+    border-color: #9d3a3a
+}
+.gantt_critical_task .gantt_task_progress {
+    background-color: rgba(0, 0, 0, .4)
+}
+.gantt_critical_link .gantt_line_wrapper>div {
+    background-color: #e63030
+}
+.gantt_critical_link .gantt_link_arrow {
+    border-color: #e63030
+}
+.gantt_unselectable,
+.gantt_unselectable div {
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -moz-user-select: -moz-none;
+    -ms-user-select: none;
+}
+.gantt_cal_light {
+    -webkit-tap-highlight-color: transparent;
+    background: #fff;
+    border-radius: 6px;
+    font-family: Arial;
+    border: 1px solid #cecece;
+    color: #6b6b6b;
+    font-size: 12px;
+    position: absolute;
+    z-index: 10001;
+    width: 550px;
+    height: 250px;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07)
+}
+.gantt_cal_light select {
+    font-family: Arial;
+    border: 1px solid #cecece;
+    font-size: 13px;
+    padding: 2px;
+    margin: 0
+}
+.gantt_cal_ltitle {
+    padding: 7px 10px;
+    overflow: hidden;
+    white-space: nowrap;
+    -webkit-border-radius: 6px 6px 0 0;
+    -moz-border-radius-topleft: 6px;
+    -moz-border-radius-bottomleft: 0;
+    -moz-border-radius-topright: 6px;
+    -moz-border-radius-bottomright: 0;
+    border-radius: 6px 6px 0 0
+}
+.gantt_cal_ltitle span {
+    white-space: nowrap
+}
+.gantt_cal_lsection {
+    color: #727272;
+    font-weight: 700;
+    padding: 12px 0 5px 10px
+}
+.gantt_cal_lsection .gantt_fullday {
+    float: right;
+    margin-right: 5px;
+    font-size: 12px;
+    font-weight: 400;
+    line-height: 20px;
+    vertical-align: top;
+    cursor: pointer
+}
+.gantt_cal_lsection {
+    font-size: 13px
+}
+.gantt_cal_ltext {
+    padding: 2px 10px;
+    overflow: hidden
+}
+.gantt_cal_ltext textarea {
+    overflow: auto;
+    font-family: Arial;
+    font-size: 13px;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    border: 1px solid #cecece;
+    height: 100%;
+    width: 100%;
+    outline: 0!important;
+    resize: none
+}
+.gantt_time {
+    font-weight: 700
+}
+.gantt_cal_light .gantt_title {
+    padding-left: 10px
+}
+.gantt_cal_larea {
+    border: 1px solid #cecece;
+    border-left: none;
+    border-right: none;
+    background-color: #fff;
+    overflow: hidden;
+    height: 1px
+}
+.gantt_btn_set {
+    margin: 10px 7px 5px 10px;
+    padding: 5px 15px 5px 10px;
+    float: left;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    border-width: 0;
+    border-color: #cecece;
+    border-style: solid;
+    height: 32px;
+    font-weight: 700;
+    background: #fff;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    cursor: pointer
+}
+.gantt_btn_set div {
+    float: left;
+    font-size: 13px;
+    height: 22px;
+    line-height: 22px;
+    background-repeat: no-repeat;
+    vertical-align: middle
+}
+.gantt_save_btn {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=);
+    margin-top: 2px;
+    width: 21px
+}
+.gantt_cancel_btn {
+    margin-top: 2px;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);
+    width: 20px
+}
+.gantt_delete_btn {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=);
+    margin-top: 2px;
+    width: 20px
+}
+.gantt_cal_cover {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    z-index: 10000;
+    top: 0;
+    left: 0;
+    background-color: #000;
+    opacity: .1;
+    filter: alpha(opacity=10)
+}
+.gantt_custom_button {
+    padding: 0 3px;
+    font-family: Arial;
+    font-size: 13px;
+    font-weight: 400;
+    margin-right: 10px;
+    margin-top: -5px;
+    cursor: pointer;
+    float: right;
+    height: 21px;
+    width: 90px;
+    border: 1px solid #CECECE;
+    text-align: center;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    -ms-border-radius: 4px;
+    -o-border-radius: 4px;
+    border-radius: 4px
+}
+.gantt_custom_button div {
+    cursor: pointer;
+    float: none;
+    height: 21px;
+    line-height: 21px;
+    vertical-align: middle
+}
+.gantt_custom_button div:first-child {
+    display: none
+}
+.gantt_cal_light_wide {
+    width: 580px;
+    padding: 2px 4px
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    border: 1px solid #cecece
+}
+.gantt_cal_light_wide .gantt_cal_lsection {
+    border: 0;
+    float: left;
+    text-align: right;
+    width: 80px;
+    height: 20px;
+    padding: 5px 10px 0 0
+}
+.gantt_cal_light_wide .gantt_wrap_section {
+    position: relative;
+    padding: 10px 0;
+    overflow: hidden;
+    border-bottom: 1px solid #ebebeb
+}
+.gantt_cal_light_wide .gantt_section_time {
+    overflow: hidden;
+    padding-top: 2px!important;
+    padding-right: 0;
+    height: 20px!important
+}
+.gantt_cal_light_wide .gantt_cal_ltext {
+    padding-right: 0
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+    padding: 0 10px;
+    width: 100%
+}
+.gantt_cal_light_wide .gantt_section_time {
+    background: 0 0
+}
+.gantt_cal_light_wide .gantt_cal_checkbox label {
+    padding-left: 0
+}
+.gantt_cal_light_wide .gantt_cal_lsection .gantt_fullday {
+    float: none;
+    margin-right: 0;
+    font-weight: 700;
+    cursor: pointer
+}
+.gantt_cal_light_wide .gantt_custom_button {
+    position: absolute;
+    top: 0;
+    right: 0;
+    margin-top: 2px
+}
+.gantt_cal_light_wide .gantt_repeat_right {
+    margin-right: 55px
+}
+.gantt_cal_light_wide.gantt_cal_light_full {
+    width: 738px
+}
+.gantt_cal_wide_checkbox input {
+    margin-top: 8px;
+    margin-left: 14px
+}
+.gantt_cal_light input {
+    font-size: 13px
+}
+.gantt_section_time {
+    background-color: #fff;
+    white-space: nowrap;
+    padding: 5px 10px;
+    padding-top: 2px!important
+}
+.gantt_section_time .gantt_time_selects {
+    float: left;
+    height: 25px
+}
+.gantt_section_time .gantt_time_selects select {
+    height: 23px;
+    padding: 2px;
+    border: 1px solid #cecece
+}
+.gantt_duration {
+    width: 100px;
+    height: 23px;
+    float: left;
+    white-space: nowrap;
+    margin-left: 20px;
+    line-height: 23px
+}
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc,
+.gantt_duration .gantt_duration_value {
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    text-align: center;
+    vertical-align: top;
+    height: 100%;
+    border: 1px solid #cecece
+}
+.gantt_duration .gantt_duration_value {
+    width: 40px;
+    padding: 3px 4px;
+    border-left-width: 0;
+    border-right-width: 0
+}
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc {
+    width: 20px;
+    padding: 1px 1px 3px;
+    background: #fff
+}
+.gantt_duration .gantt_duration_dec {
+    -moz-border-top-left-radius: 4px;
+    -moz-border-bottom-left-radius: 4px;
+    -webkit-border-top-left-radius: 4px;
+    -webkit-border-bottom-left-radius: 4px;
+    border-top-left-radius: 4px;
+    border-bottom-left-radius: 4px
+}
+.gantt_duration .gantt_duration_inc {
+    margin-right: 4px;
+    -moz-border-top-right-radius: 4px;
+    -moz-border-bottom-right-radius: 4px;
+    -webkit-border-top-right-radius: 4px;
+    -webkit-border-bottom-right-radius: 4px;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px
+}
+.gantt_cal_quick_info {
+    border: 1px solid #cecece;
+    border-radius: 6px;
+    position: absolute;
+    z-index: 300;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    background-color: #fff;
+    width: 300px;
+    transition: left .5s ease, right .5s;
+    -moz-transition: left .5s ease, right .5s;
+    -webkit-transition: left .5s ease, right .5s;
+    -o-transition: left .5s ease, right .5s
+}
+.gantt_no_animate {
+    transition: none;
+    -moz-transition: none;
+    -webkit-transition: none;
+    -o-transition: none
+}
+.gantt_cal_quick_info.gantt_qi_left .gantt_qi_big_icon {
+    float: right
+}
+.gantt_cal_qi_title {
+    -webkit-border-radius: 6px 6px 0 0;
+    -moz-border-radius-topleft: 6px;
+    -moz-border-radius-bottomleft: 0;
+    -moz-border-radius-topright: 6px;
+    -moz-border-radius-bottomright: 0;
+    border-radius: 6px 6px 0 0;
+    padding: 5px 0 8px 12px;
+    color: #454545;
+    background-color: #fff;
+    border-bottom: 1px solid #cecece
+}
+.gantt_cal_qi_tdate {
+    font-size: 14px;
+    font-weight: 700
+}
+.gantt_cal_qi_tcontent {
+    font-size: 13px
+}
+.gantt_cal_qi_content {
+    padding: 16px 8px;
+    font-size: 13px;
+    color: #454545;
+    overflow: hidden
+}
+.gantt_cal_qi_controls {
+    -webkit-border-radius: 0 0 6px 6px;
+    -moz-border-radius-topleft: 0;
+    -moz-border-radius-bottomleft: 6px;
+    -moz-border-radius-topright: 0;
+    -moz-border-radius-bottomright: 6px;
+    border-radius: 0 0 6px 6px;
+    padding-left: 7px
+}
+.gantt_cal_qi_controls .gantt_menu_icon {
+    margin-top: 6px;
+    background-repeat: no-repeat
+}
+.gantt_cal_qi_controls .gantt_menu_icon.icon_edit {
+    width: 20px;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)
+}
+.gantt_cal_qi_controls .gantt_menu_icon.icon_delete {
+    width: 20px;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=)
+}
+.gantt_qi_big_icon {
+    font-size: 13px;
+    border-radius: 4px;
+    font-weight: 700;
+    background: #fff;
+    margin: 5px 9px 8px 0;
+    min-width: 60px;
+    line-height: 32px;
+    vertical-align: middle;
+    padding: 0 10px 0 5px;
+    cursor: pointer;
+    border: 1px solid #cecece
+}
+.gantt_cal_qi_controls div {
+    float: left;
+    height: 32px;
+    text-align: center;
+    line-height: 32px
+}
+.gantt_tooltip {
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    background-color: #fff;
+    border-left: 1px solid rgba(0, 0, 0, .07);
+    border-top: 1px solid rgba(0, 0, 0, .07);
+    font-family: Arial;
+    font-size: 8pt;
+    color: #454545;
+    padding: 10px;
+    position: absolute;
+    z-index: 50
+}
+.gantt_marker {
+    height: 100%;
+    width: 2px;
+    top: 0;
+    position: absolute;
+    text-align: center;
+    background-color: rgba(255, 0, 0, .4);
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box
+}
+.gantt_marker .gantt_marker_content {
+    padding: 5px;
+    background: inherit;
+    color: #fff;
+    position: absolute;
+    font-size: 12px;
+    line-height: 12px;
+    opacity: .8
+}
+.gantt_marker_area {
+    position: absolute;
+    top: 0;
+    left: 0
+}
+.gantt_noselect {
+    -moz-user-select: -moz-none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    user-select: none
+}
+.gantt_drag_marker {
+    position: absolute;
+    font-family: Arial;
+    font-size: 13px
+}
+.gantt_drag_marker .gantt_tree_icon.gantt_blank,
+.gantt_drag_marker .gantt_tree_icon.gantt_close,
+.gantt_drag_marker .gantt_tree_icon.gantt_open,
+.gantt_drag_marker .gantt_tree_indent {
+    display: none
+}
+.gantt_drag_marker,
+.gantt_drag_marker .gantt_row.odd {
+    background-color: #fff
+}
+.gantt_drag_marker .gantt_row {
+    border-left: 1px solid #d2d2d2;
+    border-top: 1px solid #d2d2d2
+}
+.gantt_drag_marker .gantt_cell {
+    border-color: #d2d2d2
+}
+.gantt_row.gantt_over,
+.gantt_task_row.gantt_over {
+    background-color: #0070fe
+}
+.gantt_row.gantt_transparent .gantt_cell {
+    opacity: .7
+}
+.gantt_task_row.gantt_transparent {
+    background-color: #f8fdfd
+}
+.dhtmlx_popup_button.dhtmlx_delete_button {
+    background: #3db9d3;
+    text-shadow: 0 -1px 0 #248a9f;
+    color: #fff;
+    font-weight: 700;
+    border-width: 0
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt_vanilla.css b/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt_vanilla.css
new file mode 100644
index 0000000000000000000000000000000000000000..8a6ac9335d3f0290508480147f57048808c516d8
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/dhtmlxgantt_vanilla.css
@@ -0,0 +1,9 @@
+/*
+@license
+
+dhtmlxGantt v.3.2.1 Stardard
+This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
+
+(c) Dinamenta, UAB.
+*/
+.gridHoverStyle,.gridSelection,.timelineSelection{background-color:#fff3a1}.gantt_grid_scale .gantt_grid_head_cell{color:#a6a6a6;border-top:none!important;border-right:none!important}.gantt_grid_data .gantt_cell{border-right:none;color:#454545}.gantt_task_link .gantt_link_arrow_right{border-width:6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_left{border-width:6px;margin-left:-6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_down,.gantt_task_link .gantt_link_arrow_top{border-width:6px}.gantt_task_line .gantt_task_progress_drag{bottom:-4px;height:16px;margin-left:-8px;width:16px}.chartHeaderBg{background-color:#fff}.gantt_task .gantt_task_scale .gantt_scale_cell{color:#a6a6a6;border-right:1px solid #ebebeb}.gantt_row.gantt_project,.gantt_row.odd.gantt_project{background-color:#edffef}.gantt_task_row.gantt_project,.gantt_task_row.odd.gantt_project{background-color:#f5fff6}.gantt_task_line.gantt_project{background-color:#65c16f;border:1px solid #3c9445}.gantt_task_line.gantt_project .gantt_task_progress{background-color:#46ad51}.buttonBg{background:#fff}.gantt_cal_light .gantt_btn_set{margin:5px 10px}.gantt_btn_set.gantt_cancel_btn_set{background:#fff;color:#454545;border:1px solid #cecece}.gantt_btn_set.gantt_save_btn_set{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff}.gantt_btn_set.gantt_delete_btn_set{background:#ec8e00;text-shadow:0 -1px 0 #a60;color:#fff}.gantt_cal_light_wide{padding-left:0!important;padding-right:0!important}.gantt_cal_light_wide .gantt_cal_larea{border-left:none!important;border-right:none!important}.dhtmlx_popup_button.dhtmlx_ok_button{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff;font-weight:700;border-width:0}.dhtmlx_popup_button.dhtmlx_cancel_button{font-weight:700;color:#454544}.gantt_qi_big_icon.icon_edit{color:#454545;background:#fff}.gantt_qi_big_icon.icon_delete{text-shadow:0 -1px 0 #a60;background:#ec8e00;color:#fff;border-width:0}.gantt_container{font-family:Arial;font-size:13px;border:1px solid #cecece;position:relative;white-space:nowrap}.gantt_grid{border-right:1px solid #cecece}.gantt_task_scroll{overflow-x:scroll}.gantt_task{position:relative}.gantt_grid,.gantt_task{overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#6b6b6b;font-size:12px;border-bottom:1px solid #cecece;background-color:#fff}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #cecece}.gantt_scale_line:first-child{border-top:none}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #cecece;text-align:center;position:relative;cursor:default;height:100%;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none;overflow:hidden}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);background-position:center center;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3}.gantt_grid_head_cell.gantt_grid_head_add{-moz-opacity:.6;opacity:.6;top:0}.gantt_grid_head_cell.gantt_grid_head_add:hover{-moz-opacity:1;opacity:1}.gantt_grid_data .gantt_row.odd:hover,.gantt_grid_data .gantt_row:hover{background-color:#fff3a1}.gantt_grid_data .gantt_row.odd:hover .gantt_add,.gantt_grid_data .gantt_row:hover .gantt_add{-moz-opacity:1;opacity:1}.gantt_row,.gantt_task_row{border-bottom:1px solid #ebebeb;background-color:#fff}.gantt_row.odd,.gantt_task_row.odd{background-color:#fff}.gantt_cell,.gantt_grid_head_cell,.gantt_row,.gantt_scale_cell,.gantt_task_cell,.gantt_task_row{box-sizing:border-box;-moz-box-sizing:border-box}.gantt_grid_head_cell,.gantt_scale_cell{line-height:inherit}.gantt_grid_scale .gantt_grid_column_resize_wrap{cursor:col-resize;position:absolute;width:13px}.gantt_grid_column_resize_wrap .gantt_grid_column_resize{background-color:#cecece;height:100%;width:1px;margin:0 auto}.gantt_grid .gantt_grid_resize_wrap{cursor:col-resize;position:absolute;width:13px;z-index:1}.gantt_grid_resize_wrap .gantt_grid_resize{background-color:#cecece;width:1px;margin:0 auto}.gantt_drag_marker.gantt_grid_resize_area{background-color:rgba(231,231,231,.5);border-left:1px solid #cecece;border-right:1px solid #cecece;height:100%;width:100%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.gantt_cell{display:inline-block;vertical-align:top;border-right:1px solid #ebebeb;padding-left:6px;padding-right:6px;height:100%;overflow:hidden;white-space:nowrap;font-size:13px}.gantt_grid_data .gantt_last_cell,.gantt_grid_scale .gantt_last_cell,.gantt_task_bg .gantt_last_cell,.gantt_task_scale .gantt_last_cell{border-right-width:0}.gantt_task_bg{overflow:hidden}.gantt_scale_cell{display:inline-block;white-space:nowrap;overflow:hidden;border-right:1px solid #cecece;text-align:center;height:100%}.gantt_task_cell{display:inline-block;height:100%;border-right:1px solid #ebebeb}.gantt_ver_scroll{width:0;background-color:transparent;height:1px;overflow-x:hidden;overflow-y:scroll;display:none;position:absolute;right:0}.gantt_ver_scroll>div{width:1px;height:1px}.gantt_hor_scroll{height:0;background-color:transparent;width:100%;clear:both;overflow-x:scroll;overflow-y:hidden;display:none}.gantt_hor_scroll>div{width:5000px;height:1px}.gantt_tree_indent{width:15px;height:100%;display:inline-block}.gantt_tree_content,.gantt_tree_icon{vertical-align:top}.gantt_tree_icon{width:28px;height:100%;display:inline-block;background-repeat:no-repeat;background-position:center center}.gantt_tree_content{height:100%;display:inline-block}.gantt_tree_icon.gantt_open{background-image:url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0I5RTczQjVDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0I5RTczQjZDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQjlFNzNCM0MwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozQjlFNzNCNEMwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARJMMlJq704661B+SAIXAVhnKhBFKSZnmuLImhslXPN3ibi+6pdBXc4IIpB2YkGE1IKAoL0ICUInJNCYMDtDgJYiScUGnHO6LQkAgA7);width:18px;cursor:pointer}.gantt_tree_icon.gantt_close{background-image:url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39wAAAP///////wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzY0QzNGM0VDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzY0QzNGM0ZDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozNjRDM0YzQ0MwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozNjRDM0YzREMwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARDMMlJq704661B+SAIXAVhnKhBFKSZnmv7wqxVzmpd3Uff5zKEUAi0uV4xm4DAbBIEOkohMKhaB4HoxBMKjTjgsFgSAQA7);width:18px;cursor:pointer}.gantt_tree_icon.gantt_blank{width:18px}.gantt_tree_icon.gantt_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=)}.gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=)}.gantt_tree_icon.gantt_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==)}.gantt_grid_head_cell .gantt_sort{position:absolute;right:5px;top:8px;width:7px;height:13px;background-repeat:no-repeat;background-position:center center}.gantt_grid_head_cell .gantt_sort.gantt_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}.gantt_grid_head_cell .gantt_sort.gantt_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}.gantt_inserted,.gantt_updated{font-weight:700}.gantt_deleted{text-decoration:line-through}.gantt_invalid{background-color:FFE0E0}.gantt_error{color:red}.gantt_status{right:1px;padding:5px 10px;background:rgba(155,155,155,.1);position:absolute;top:1px;-webkit-transition:opacity .2s;transition:opacity .2s;opacity:0}.gantt_status.gantt_status_visible{opacity:1}#gantt_ajax_dots span{-webkit-transition:opacity .2s;transition:opacity .2s;background-repeat:no-repeat;opacity:0}#gantt_ajax_dots span.gantt_dot_visible{opacity:1}.dhtmlx_message_area{position:fixed;right:5px;width:250px;z-index:1000}.dhtmlx-info{min-width:120px;font-family:Arial;z-index:10000;margin:5px 5px 10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}.dhtmlx-info.hidden{height:0;padding:0;border-width:0;margin:0;overflow:hidden}.dhtmlx_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;z-index:20000;box-shadow:3px 3px 3px rgba(0,0,0,.07);font-family:Arial;border-radius:6px;border:1px solid #cecece;background:#fff}.dhtmlx_popup_title{border-top-left-radius:6px;border-top-right-radius:6px;border-width:0}.dhtmlx_button,.dhtmlx_popup_button{border:1px solid #cecece;height:30px;line-height:30px;display:inline-block;margin:0 5px;border-radius:4px;background:#fff}.dhtmlx-info,.dhtmlx_button,.dhtmlx_popup_button{user-select:none;-webkit-user-select:none;-moz-user-select:-moz-none;cursor:pointer}.dhtmlx_popup_text{overflow:hidden}.dhtmlx_popup_controls{border-radius:6px;padding:10px}.dhtmlx_popup_button{min-width:100px}div.dhx_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=20);opacity:.2;position:fixed;z-index:19999;left:0;top:0;width:100%;height:100%;border:none;zoom:1}.dhtmlx-info img,.dhtmlx_modal_box img{float:left;margin-right:20px}.dhtmlx-alert-error,.dhtmlx-confirm-error{border:1px solid red}.dhtmlx_button input,.dhtmlx_popup_button div{border-radius:4px;font-size:14px;-moz-box-sizing:content-box;box-sizing:content-box;padding:0;margin:0;vertical-align:top}.dhtmlx_popup_title{color:#fff;text-shadow:1px 1px #000;height:40px;line-height:40px;font-size:20px}.dhtmlx_popup_text{margin:15px 15px 5px;font-size:14px;color:#000;min-height:30px;border-radius:6px}.dhtmlx-error,.dhtmlx-info{font-size:14px;color:#000;box-shadow:3px 3px 3px rgba(0,0,0,.07);padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}.dhtmlx-info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #cecece}.dhtmlx-error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhtmlx-error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}.gantt_data_area div,.gantt_grid div{-ms-touch-action:none;-webkit-tap-highlight-color:transparent}.gantt_data_area{position:relative;overflow-x:hidden;overflow-y:hidden;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_links_area{position:absolute;left:0;top:0}.gantt_side_content,.gantt_task_content,.gantt_task_progress{line-height:inherit;overflow:hidden;height:100%}.gantt_task_content{font-size:12px;color:#fff;width:100%;top:0;position:absolute;white-space:nowrap;text-align:center}.gantt_task_progress{text-align:center;z-index:0;background:#299cb4}.gantt_task_line{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;position:absolute;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#3db9d3;border:1px solid #2898b0;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_task_line.gantt_drag_move div{cursor:move}.gantt_touch_move,.gantt_touch_progress .gantt_touch_resize{-moz-transform:scale(1.02,1.1);-o-transform:scale(1.02,1.1);-webkit-transform:scale(1.02,1.1);transform:scale(1.02,1.1);-moz-transform-origin:50%;-o-transform-origin:50%;-webkit-transform-origin:50%;transform-origin:50%}.gantt_touch_progress .gantt_task_progress_drag,.gantt_touch_resize .gantt_task_drag{-moz-transform:scaleY(1.3);-o-transform:scaleY(1.3);-webkit-transform:scaleY(1.3);transform:scaleY(1.3);-moz-transform-origin:50%;-o-transform-origin:50%;-webkit-transform-origin:50%;transform-origin:50%}.gantt_side_content{position:absolute;white-space:nowrap;color:#6e6e6e;bottom:7px;font-size:11px}.gantt_side_content.gantt_left{right:100%;padding-right:15px}.gantt_side_content.gantt_right{left:100%;padding-left:15px}.gantt_side_content.gantt_link_crossing{bottom:8.75px}.gantt_link_arrow,.gantt_task_link .gantt_line_wrapper{position:absolute;cursor:pointer}.gantt_line_wrapper div{background-color:#ffa011}.gantt_task_link:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #ffa011}.gantt_task_link div.gantt_link_arrow{background-color:transparent;border-style:solid;width:0;height:0}.gantt_link_control{position:absolute;width:13px;top:0}.gantt_link_control div{display:none;cursor:pointer;box-sizing:border-box;position:relative;top:50%;margin-top:-7.5px;vertical-align:middle;border:1px solid #929292;-webkit-border-radius:6.5px;-moz-border-radius:6.5px;border-radius:6.5px;height:13px;width:13px;background-color:#f0f0f0}.gantt_link_control div:hover{background-color:#fff}.gantt_link_control.task_left{left:-13px}.gantt_link_control.task_right{right:-13px}.gantt_link_target .gantt_link_control div,.gantt_task_line.gantt_selected .gantt_link_control div,.gantt_task_line:hover .gantt_link_control div{display:block}.gantt_link_source,.gantt_link_target{box-shadow:0 0 3px #3db9d3}.gantt_link_target.link_finish_allow,.gantt_link_target.link_start_allow{box-shadow:0 0 3px #ffbf5e}.gantt_link_target.link_finish_deny,.gantt_link_target.link_start_deny{box-shadow:0 0 3px #e87e7b}.link_finish_allow .gantt_link_control.task_right div,.link_start_allow .gantt_link_control.task_left div{background-color:#ffbf5e;border-color:#ffa011}.link_finish_deny .gantt_link_control.task_right div,.link_start_deny .gantt_link_control.task_left div{background-color:#e87e7b;border-color:#dd3e3a}.gantt_link_arrow_right{border-width:4px 0 4px 6px;border-top-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important;border-color:#ffa011;margin-top:-1px}.gantt_link_arrow_left{border-width:4px 6px 4px 0;margin-top:-1px;border-top-color:transparent!important;border-color:#ffa011;border-bottom-color:transparent!important;border-left-color:transparent!important}.gantt_link_arrow_top{border-width:0 4px 6px;border-color:#ffa011;border-top-color:transparent!important;border-right-color:transparent!important;border-left-color:transparent!important}.gantt_link_arrow_down{border-width:4px 6px 0 4px;border-color:#ffa011;border-right-color:transparent!important;border-bottom-color:transparent!important;border-left-color:transparent!important}.gantt_task_drag,.gantt_task_progress_drag{cursor:w-resize;height:100%;display:none;position:absolute}.gantt_task_line.gantt_selected .gantt_task_drag,.gantt_task_line.gantt_selected .gantt_task_progress_drag,.gantt_task_line:hover .gantt_task_drag,.gantt_task_line:hover .gantt_task_progress_drag{display:block}.gantt_task_drag{width:6px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAACCAYAAAB7Xa1eAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYDDjkw3UJvAwAAABRJREFUCNdj/P//PwM2wASl/6PTAKrrBf4+lD8LAAAAAElFTkSuQmCC);z-index:1;top:0}.gantt_task_drag.task_left{left:0}.gantt_task_drag.task_right{right:0}.gantt_task_progress_drag{height:8px;width:8px;bottom:-4px;margin-left:-4px;background-position:bottom;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=);background-repeat:no-repeat;z-index:2}.gantt_link_tooltip{box-shadow:3px 3px 3px #888;background-color:#fff;border-left:1px dotted #cecece;border-top:1px dotted #cecece;font-family:Tahoma;font-size:8pt;color:#444;padding:6px;line-height:20px}.gantt_link_direction{height:0;border:0 #ffa011;border-bottom-style:dashed;border-bottom-width:2px;transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;z-index:2;margin-left:1px;position:absolute}.gantt_grid_data .gantt_row.gantt_selected,.gantt_grid_data .gantt_row.odd.gantt_selected,.gantt_task_row.gantt_selected{background-color:#fff3a1}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffec6e}.gantt_task_line.gantt_selected{box-shadow:0 0 5px #299cb4}.gantt_task_line.gantt_project.gantt_selected{box-shadow:0 0 5px #46ad51}.gantt_task_line.gantt_milestone{visibility:hidden;background-color:#d33daf;border:0 solid #61164f;box-sizing:content-box;-moz-box-sizing:content-box}.gantt_task_line.gantt_milestone div{visibility:visible}.gantt_task_line.gantt_milestone .gantt_task_content{background:inherit;border:inherit;border-width:1px;border-radius:inherit;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.gantt_task_line.gantt_task_inline_color{border-color:#999}.gantt_task_line.gantt_task_inline_color .gantt_task_progress{background-color:#363636;opacity:.2}.gantt_task_line.gantt_task_inline_color.gantt_project.gantt_selected,.gantt_task_line.gantt_task_inline_color.gantt_selected{box-shadow:0 0 5px #999}.gantt_task_link.gantt_link_inline_color:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #999}.gantt_critical_task{background-color:#e63030;border-color:#9d3a3a}.gantt_critical_task .gantt_task_progress{background-color:rgba(0,0,0,.4)}.gantt_critical_link .gantt_line_wrapper>div{background-color:#e63030}.gantt_critical_link .gantt_link_arrow{border-color:#e63030}.gantt_unselectable,.gantt_unselectable div{-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_cal_light{-webkit-tap-highlight-color:transparent;background:#fff;border-radius:6px;font-family:Arial;border:1px solid #cecece;color:#6b6b6b;font-size:12px;position:absolute;z-index:10001;width:550px;height:250px;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.gantt_cal_light select{font-family:Arial;border:1px solid #cecece;font-size:13px;padding:2px;margin:0}.gantt_cal_ltitle{padding:7px 10px;overflow:hidden;white-space:nowrap;-webkit-border-radius:6px 6px 0 0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-radius:6px 6px 0 0}.gantt_cal_ltitle span{white-space:nowrap}.gantt_cal_lsection{color:#727272;font-weight:700;padding:12px 0 5px 10px}.gantt_cal_lsection .gantt_fullday{float:right;margin-right:5px;font-size:12px;font-weight:400;line-height:20px;vertical-align:top;cursor:pointer}.gantt_cal_lsection{font-size:13px}.gantt_cal_ltext{padding:2px 10px;overflow:hidden}.gantt_cal_ltext textarea{overflow:auto;font-family:Arial;font-size:13px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cecece;height:100%;width:100%;outline:0!important;resize:none}.gantt_time{font-weight:700}.gantt_cal_light .gantt_title{padding-left:10px}.gantt_cal_larea{border:1px solid #cecece;border-left:none;border-right:none;background-color:#fff;overflow:hidden;height:1px}.gantt_btn_set{margin:10px 7px 5px 10px;padding:5px 15px 5px 10px;float:left;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-width:0;border-color:#cecece;border-style:solid;height:32px;font-weight:700;background:#fff;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.gantt_btn_set div{float:left;font-size:13px;height:22px;line-height:22px;background-repeat:no-repeat;vertical-align:middle}.gantt_save_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=);margin-top:2px;width:21px}.gantt_cancel_btn{margin-top:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);width:20px}.gantt_delete_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=);margin-top:2px;width:20px}.gantt_cal_cover{width:100%;height:100%;position:absolute;z-index:10000;top:0;left:0;background-color:#000;opacity:.1;filter:alpha(opacity=10)}.gantt_custom_button{padding:0 3px;font-family:Arial;font-size:13px;font-weight:400;margin-right:10px;margin-top:-5px;cursor:pointer;float:right;height:21px;width:90px;border:1px solid #CECECE;text-align:center;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.gantt_custom_button div{cursor:pointer;float:none;height:21px;line-height:21px;vertical-align:middle}.gantt_custom_button div:first-child{display:none}.gantt_cal_light_wide{width:580px;padding:2px 4px}.gantt_cal_light_wide .gantt_cal_larea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cecece}.gantt_cal_light_wide .gantt_cal_lsection{border:0;float:left;text-align:right;width:80px;height:20px;padding:5px 10px 0 0}.gantt_cal_light_wide .gantt_wrap_section{position:relative;padding:10px 0;overflow:hidden;border-bottom:1px solid #ebebeb}.gantt_cal_light_wide .gantt_section_time{overflow:hidden;padding-top:2px!important;padding-right:0;height:20px!important}.gantt_cal_light_wide .gantt_cal_ltext{padding-right:0}.gantt_cal_light_wide .gantt_cal_larea{padding:0 10px;width:100%}.gantt_cal_light_wide .gantt_section_time{background:0 0}.gantt_cal_light_wide .gantt_cal_checkbox label{padding-left:0}.gantt_cal_light_wide .gantt_cal_lsection .gantt_fullday{float:none;margin-right:0;font-weight:700;cursor:pointer}.gantt_cal_light_wide .gantt_custom_button{position:absolute;top:0;right:0;margin-top:2px}.gantt_cal_light_wide .gantt_repeat_right{margin-right:55px}.gantt_cal_light_wide.gantt_cal_light_full{width:738px}.gantt_cal_wide_checkbox input{margin-top:8px;margin-left:14px}.gantt_cal_light input{font-size:13px}.gantt_section_time{background-color:#fff;white-space:nowrap;padding:5px 10px;padding-top:2px!important}.gantt_section_time .gantt_time_selects{float:left;height:25px}.gantt_section_time .gantt_time_selects select{height:23px;padding:2px;border:1px solid #cecece}.gantt_duration{width:100px;height:23px;float:left;white-space:nowrap;margin-left:20px;line-height:23px}.gantt_duration .gantt_duration_dec,.gantt_duration .gantt_duration_inc,.gantt_duration .gantt_duration_value{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;vertical-align:top;height:100%;border:1px solid #cecece}.gantt_duration .gantt_duration_value{width:40px;padding:3px 4px;border-left-width:0;border-right-width:0}.gantt_duration .gantt_duration_dec,.gantt_duration .gantt_duration_inc{width:20px;padding:1px 1px 3px;background:#fff}.gantt_duration .gantt_duration_dec{-moz-border-top-left-radius:4px;-moz-border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-left-radius:4px}.gantt_duration .gantt_duration_inc{margin-right:4px;-moz-border-top-right-radius:4px;-moz-border-bottom-right-radius:4px;-webkit-border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px}.gantt_cal_quick_info{border:1px solid #cecece;border-radius:6px;position:absolute;z-index:300;box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;width:300px;transition:left .5s ease,right .5s;-moz-transition:left .5s ease,right .5s;-webkit-transition:left .5s ease,right .5s;-o-transition:left .5s ease,right .5s}.gantt_no_animate{transition:none;-moz-transition:none;-webkit-transition:none;-o-transition:none}.gantt_cal_quick_info.gantt_qi_left .gantt_qi_big_icon{float:right}.gantt_cal_qi_title{-webkit-border-radius:6px 6px 0 0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-radius:6px 6px 0 0;padding:5px 0 8px 12px;color:#454545;background-color:#fff;border-bottom:1px solid #cecece}.gantt_cal_qi_tdate{font-size:14px;font-weight:700}.gantt_cal_qi_tcontent{font-size:13px}.gantt_cal_qi_content{padding:16px 8px;font-size:13px;color:#454545;overflow:hidden}.gantt_cal_qi_controls{-webkit-border-radius:0 0 6px 6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:6px;border-radius:0 0 6px 6px;padding-left:7px}.gantt_cal_qi_controls .gantt_menu_icon{margin-top:6px;background-repeat:no-repeat}.gantt_cal_qi_controls .gantt_menu_icon.icon_edit{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)}.gantt_cal_qi_controls .gantt_menu_icon.icon_delete{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=)}.gantt_qi_big_icon{font-size:13px;border-radius:4px;font-weight:700;background:#fff;margin:5px 9px 8px 0;min-width:60px;line-height:32px;vertical-align:middle;padding:0 10px 0 5px;cursor:pointer;border:1px solid #cecece}.gantt_cal_qi_controls div{float:left;height:32px;text-align:center;line-height:32px}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-family:Arial;font-size:8pt;color:#454545;padding:10px;position:absolute;z-index:50}.gantt_marker{height:100%;width:2px;top:0;position:absolute;text-align:center;background-color:rgba(255,0,0,.4);-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.gantt_marker .gantt_marker_content{padding:5px;background:inherit;color:#fff;position:absolute;font-size:12px;line-height:12px;opacity:.8}.gantt_marker_area{position:absolute;top:0;left:0}.gantt_noselect{-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_drag_marker{position:absolute;font-family:Arial;font-size:13px}.gantt_drag_marker .gantt_tree_icon.gantt_blank,.gantt_drag_marker .gantt_tree_icon.gantt_close,.gantt_drag_marker .gantt_tree_icon.gantt_open,.gantt_drag_marker .gantt_tree_indent{display:none}.gantt_drag_marker,.gantt_drag_marker .gantt_row.odd{background-color:#fff}.gantt_drag_marker .gantt_row{border-left:1px solid #d2d2d2;border-top:1px solid #d2d2d2}.gantt_drag_marker .gantt_cell{border-color:#d2d2d2}.gantt_row.gantt_over,.gantt_task_row.gantt_over{background-color:#0070fe}.gantt_row.gantt_transparent .gantt_cell{opacity:.7}.gantt_task_row.gantt_transparent{background-color:#f8fdfd}.dhtmlx_popup_button.dhtmlx_delete_button{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff;font-weight:700;border-width:0}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css b/plugins/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css
new file mode 100644
index 0000000000000000000000000000000000000000..12eb8ff175c04d2c293b948b596ffd2ad8e617b2
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/easy_gantt.css
@@ -0,0 +1,656 @@
+/*
+* = require easy_gantt/dhtmlxgantt
+* = require_self
+* = require easy_gantt/sass/easy_gantt_sass.scss
+*/
+@media print {
+  .content-title {
+    display: none;
+  }
+
+  @page {
+    size: landscape
+  }
+
+  .gantt-grid-checkbox-cont {
+    display: none;
+  }
+}
+
+.gantt_container {
+  display: inline-block;
+}
+
+#gantt_cont {
+  position: relative;
+  z-index: 0;
+}
+
+#easy_gantt .weekend {
+  background-color: #F7F7F7;
+}
+
+#easy_gantt .first-date {
+  position: relative;
+}
+
+#easy_gantt .first-date:after {
+  content: "";
+  position: absolute;
+  display: block;
+  left: -1px;
+  top: 0;
+  bottom: 0;
+  border-left: 1px solid #628DB6;
+}
+
+#easy_gantt {
+  position: relative;
+}
+
+.gantt_task_content {
+  overflow: visible;
+  /*color:black;*/
+}
+
+.no_task_controls .gantt_link_control div {
+  display: none !important;
+}
+
+.gantt-fresh .gantt_link_control div {
+  display: none !important;
+}
+
+.no_task_controls .gantt_task_drag, .no_task_controls .gantt_task_progress_drag {
+  display: none !important;
+}
+
+#gantt_link_dialog {
+  border: 1px #000 solid;
+  background-color: #fff;
+  width: 200px;
+  position: absolute;
+  padding: 10px;
+  z-index: 2;
+}
+
+#gantt_link_dialog input[type="number"] {
+  width: 38px;
+}
+
+.gantt-tooltip {
+  position: absolute;
+  display: none;
+  background-color: white;
+  -webkit-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  -moz-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  padding: 20px;
+  z-index: 10;
+}
+
+.gantt-tooltip-header {
+  font-size: 1em;
+  padding-bottom: 10px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+  margin: 0 0 10px;
+}
+
+.gantt-tooltip-label {
+  font-size: 0.89em;
+  color: #a0a0a0;
+}
+
+.gantt-tooltip-problem {
+  color: #d94838;
+  font-size: smaller;
+}
+
+#easy_gantt.gantt .gantt_task_line.overdue {
+  background-color: #d94838;
+}
+
+#easy_gantt.easy .gantt_task_line, #easy_gantt.easy .gantt_row {
+  padding: 0;
+  border-width: 0 0 1px 0;
+}
+
+.gantt_task_progress {
+  /*background-color: rgba(0, 0, 0, 0.3);*/
+  margin-left: 1px;
+}
+
+#link_popup_button_cont {
+  margin-top: 35px;
+}
+
+#easy_gantt a.disabled:hover {
+  text-decoration: none;
+  cursor: not-allowed;
+}
+
+.milestone-type .gantt_cell {
+  border-right: 0;
+}
+
+.gantt-legend-color-square {
+  border: 1px solid transparent;
+  margin-left: 10px;
+}
+
+.gantt-color-square, .gantt-legend-color-square {
+  width: 15px;
+  height: 15px;
+  display: inline-block;
+  vertical-align: middle;
+  border: 1px solid transparent;
+}
+
+.easy-gantt-legend-symbol {
+  margin-left: 10px;
+}
+
+.gantt_cell {
+  padding: 0;
+}
+
+.gantt_grid {
+  border-right-width: 2px;
+}
+
+.gantt_row .gantt_drag_handle:before {
+  width: 18px;
+  height: 100%;
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.gantt_row:hover .gantt_drag_handle:before {
+  content: '\21F5';
+  opacity: 0.5;
+}
+
+.gantt_row .gantt_drag_handle:hover:before {
+  opacity: 1;
+  content: '\21F5';
+}
+
+.gantt_row.gantt_drag_to_allowed {
+  background-color: #FFEB59;
+}
+
+.gantt_row.gantt_drag_hover {
+  background-color: #eeeeee;
+}
+
+.gantt_row.gantt_drag_to_allowed.gantt_drag_hover {
+  background-color: #FBCF00;
+}
+
+/*#sample_cont.flash {*/
+/*background-image: none;*/
+/*}*/
+.gantt-supertop-panel {
+  position: relative;
+}
+
+#easy_gantt .gantt-sample-flash {
+  font-size: 14px;
+}
+
+.gantt-menu {
+  z-index: 1;
+  position: relative;
+  background-color: #ffffff;
+  padding: 5px 0;
+}
+
+.gantt-footer-menu {
+  padding: 5px 0;
+}
+
+/*.easy-gantt-menu .action-buttons-with-submenu {*/
+/*display: inline;*/
+/*}*/
+.gantt-modal-video {
+  margin-left: 10px;
+  margin-top: 5px;
+}
+
+.gantt_drag_marker {
+  z-index: 1;
+  pointer-events: none;
+}
+
+.gantt_grid_column_resize_wrap, .gantt_grid_columns_resize_wrap {
+  top: 0;
+  height: 100%;
+  cursor: col-resize;
+  position: absolute;
+  /*background-color: red;*/
+  width: 12px
+}
+
+/*.gantt_task_line.parent{*/
+/*background-color: yellow;*/
+/*}*/
+.gantt-sample-close-button {
+  background-color: #cd0a0a;
+  color: white;
+  width: auto;
+  height: auto;
+  float: none;
+  display: inline;
+  margin-left: 25px;
+  padding: 8px 10px 8px 25px;
+  background-position-x: 5px;
+}
+
+/* Easy Redmine < 2016 */
+#easy_gantt.easy .push-left {
+  float: left;
+}
+
+#easy_gantt.easy .push-right {
+  float: right;
+  text-align: right
+}
+
+/*#easy_gantt.easy .gantt-menu p[id^="button_"], #easy_gantt.easy .gantt-menu div[id^="button_"]  {*/
+/*display: inline-block;*/
+/*}*/
+.gantt_grid_data div.empty-type div {
+  display: none;
+}
+
+.gantt_bars_area div.empty-type {
+  display: none;
+}
+
+.gantt_task_line.planned {
+  z-index: 1;
+}
+
+.gantt_link_tooltip.gantt_link_deny {
+  background-color: #ff6666;
+}
+
+@media print {
+  #easy_servicebar {
+    display: none;
+  }
+
+  #easy_gantt_menu {
+    display: none;
+  }
+
+  #easy_gantt_footer {
+    display: none;
+  }
+
+  .flash {
+    display: none;
+  }
+}
+
+.gantt_grid_superitem {
+  font-weight: bold;
+  /*font-style: italic;*/
+  height: 100%;
+  width: 100%;
+  vertical-align: top;
+}
+
+.gantt_grid_scale, .gantt_task_scale {
+  background-color: #ffffff;
+  z-index: 1;
+  position: relative;
+  border-top: 1px solid #cecece;
+  transform: translate(0, -1px);
+}
+
+.gantt_grid_scale .gantt_grid_head_cell, .gantt_task .gantt_task_scale .gantt_scale_cell {
+  color: rgba(72, 72, 72, 0.8);
+}
+
+/*.gantt_scale_cell{*/
+/*overflow: visible;*/
+/*}*/
+
+.gantt-footer-legend {
+  display: inline-block;
+  float: right;
+  margin-top: 20px;
+}
+
+.gantt-legend-symbol {
+  font-size: 15px;
+}
+
+#easy_gantt input.wrong {
+  background-color: #cd0a0a;
+}
+
+.gantt-reload-model-error {
+  color: #cd0a0a;
+}
+
+.gantt_task_line {
+  background-color: inherit !important;
+  border: none !important;
+  box-shadow: none !important;
+}
+
+.gantt_task_line.gantt_selected .gantt_task_content {
+  box-shadow: none !important;
+}
+
+.gantt_task_line.closed .gantt_task_ticks,
+.gantt_task_line.closed .gantt_task_content {
+  background-color: rgba(200, 200, 200, 0.2);
+  border-color: rgba(200, 200, 200, 1);
+}
+
+.gantt_link_point {
+  border-color: rgba(217, 72, 56, 1) !important;
+  background-color: rgba(217, 72, 56, 1) !important;
+}
+
+.gantt_task_drag {
+  background: radial-gradient(rgba(217, 72, 56, 1) 25%, transparent 10%) 1px 1px;
+  background-size: 4px 4px;
+}
+
+.gantt_task_progress_drag {
+  border-bottom: 10px solid rgba(217, 72, 56, 1);
+}
+
+.gantt_line_wrapper div, .gantt_link_arrow {
+  border-color: rgba(255, 125, 30, 1);
+}
+
+.gantt-relation-simple > .gantt_line_wrapper div, .gantt-relation-simple > .gantt_link_arrow {
+  border-color: rgba(30, 120, 255, 1);
+}
+
+.gantt-relation-simple:hover > .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 rgba(30, 120, 255, 1);
+}
+.gantt-relation-unlocked > .gantt_line_wrapper div, .gantt-relation-unlocked > .gantt_link_arrow {
+  border-color: rgba(51, 141, 71, 1);
+}
+
+.gantt-relation-unlocked:hover > .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 rgba(255, 125, 30, 1);
+}
+
+#easy_gantt .wrong .gantt_line_wrapper div {
+  border-color: rgba(217, 72, 56, 1) !important;
+}
+
+#easy_gantt .wrong .gantt_link_arrow {
+  border-color: rgba(217, 72, 56, 1) !important;
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+}
+
+.gantt_task_progress_drag {
+  border-bottom: 10px solid rgba(217, 72, 56, 1);
+}
+
+#easy_gantt .gantt_link_arrow_right {
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+}
+
+.gantt_task_progress_drag {
+  width: 0 !important;
+  height: 0 !important;
+  background: none !important;
+  border-left: 6.5px solid transparent;
+  border-right: 6.5px solid transparent;
+  border-bottom-width: 10px;
+  border-bottom-style: solid;
+  margin-left: -6.5px !important;
+}
+
+.gantt_task_drag {
+  background-size: 4px 4px;
+}
+
+.gantt_link_control.task_left {
+  left: -6.5px;
+  width: 6.5px;
+  border-radius: 6.5px 0 0 6.5px;
+}
+
+.gantt_link_control.task_right {
+  right: -6.5px;
+  width: 6.5px;
+  border-radius: 0 6.5px 6.5px 0;
+}
+
+.gantt_link_control.task_left .gantt_link_point {
+  width: 6.5px;
+  border-radius: 6.5px 0 0 6.5px;
+}
+
+.gantt_link_control.task_right .gantt_link_point {
+  width: 6.5px;
+  border-radius: 0 6.5px 6.5px 0;
+}
+
+@media print {
+  body {
+    margin: 0;
+  }
+
+  .gantt_sort {
+    display: none;
+  }
+}
+
+.gantt-grid-header-collapse-buttons {
+  position: absolute;
+  left: 5px;
+  bottom: 2px;
+}
+
+.gantt-grid-header-collapse-buttons a {
+  font-size: 18px;
+  color: #a6a6a6;
+  font-weight: bold;
+  font-family: monospace;
+  display: inline-block;
+}
+
+.gantt-grid-header-collapse-buttons a.active {
+  font-size: 18px;
+  color: #d94838 !important;
+  background-color: transparent !important;
+  border: none !important;
+}
+
+.gantt-grid-header-collapse-buttons a:hover {
+  font-size: 18px;
+  color: rgba(72, 72, 72, 1);
+  text-decoration: none;
+  background-color: #eeeeee !important;
+  box-shadow: 0 0 15px 5px #eeeeee;
+  border-radius: 11px;
+}
+
+.gantt-grid-checkbox-cont br {
+  display: none;
+}
+
+@media screen {
+  .gantt-icon-parent-issue:before {
+    content: '⥐'
+  }
+
+  .gantt-icon-milestone:before {
+    content: '♦';
+  }
+}
+
+.gantt_task_line.gantt_parent_task-subtype .gantt_task_content {
+  height: 50%;
+  -moz-border-radius-bottomleft: 0;
+  -moz-border-radius-bottomright: 0;
+}
+
+.gantt_task_line.gantt_parent_task-subtype .gantt_task_progress {
+  height: 50%;
+}
+
+.gantt_task_ticks {
+  height: 0;
+  margin-top: -1px;
+  border: 10px solid rgba(51, 141, 71, 1);
+  border-top-width: 0;
+  border-bottom-color: transparent !important;
+  background-color: transparent !important;
+}
+
+.gantt-milestone-icon {
+  transform: scale(0.65) rotate(45deg);
+  width: 16px !important;
+  height: 16px !important;
+  background-color: rgba(62, 91, 118, 1);
+  border: 2px solid rgba(62, 91, 118, 1);
+}
+
+.gantt_row:hover .gantt-bullet-hover-hide {
+  display: none;
+}
+
+.gantt-sum-row-small {
+  font-size: 8px;
+}
+
+.gantt-sum-row-negative {
+  color: red;
+}
+
+#easy_gantt.easy #easy_gantt_menu {
+  z-index: 2;
+  overflow: visible;
+}
+
+#easy_gantt .contextual.settings a {
+  z-index: 1;
+  border: none;
+  padding-right: 0;
+  opacity: 0.6;
+}
+
+#easy_gantt.easy .contextual.settings a {
+  background: none;
+}
+
+#easy_gantt .contextual.settings a:hover {
+  opacity: 1;
+}
+
+#easy_gantt.easy .contextual.settings a:hover {
+  background: none;
+}
+
+.gantt-grid-header-multi {
+  line-height: normal;
+  display: inline-block;
+  white-space: normal;
+}
+
+.gantt_task_relation_stop {
+  position: absolute;
+  width: 8px;
+  top: 0;
+  height: 100%;
+  border: 2px solid rgba(255, 120, 0, 0.5);
+  box-sizing: border-box;
+}
+
+.gantt_task_relation_stop_left {
+  border-right-color: transparent;
+}
+
+.gantt_task_relation_stop_right {
+  border-left-color: transparent;
+  -webkit-transform: translate(-100%);
+  -moz-transform: translate(-100%);
+  -ms-transform: translate(-100%);
+  -o-transform: translate(-100%);
+  transform: translate(-100%);
+}
+
+.gantt-menu-sub-panel {
+  margin-top: 3px;
+}
+
+.gantt_task .gantt-sum-row .gantt_scale_cell {
+  font-weight: bold;
+  color: #260080;
+}
+
+.gantt-menu-problems-count.gantt-with-problems {
+  background-color: red;
+  padding: 1px;
+  border: 1px solid #b0b0b0;
+  border-radius: 4px;
+}
+
+.gantt-menu-problems-list {
+  overflow: auto;
+  position: absolute;
+  z-index: 2;
+  max-width: 600px;
+  margin-top: 5px;
+  border: 2px solid #cecece;
+  background-color: white;
+  right: 0;
+  text-align: left;
+}
+
+.gantt-menu-problems-list ol {
+  margin: 5px 5px 5px -5px;
+}
+
+.gantt-menu-problems-list li {
+  border-bottom: 1px solid #d9d9d9;
+  white-space: nowrap;
+  padding: 5px 25px 5px 5px;
+}
+
+.gantt-menu-problems-list ol,
+.gantt-menu-problems-list a {
+  color: #4b5561;
+}
+
+.gantt-menu-problems-reason {
+  color: red;
+}
+
+.gantt-task-bar-line {
+  position: absolute;
+  left: 0;
+  top: 0;
+  overflow: hidden
+}
+.jasmine_html-reporter .jasmine-summary ul{
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+@media print {
+  /* Because of gaps between cells in header */
+  .gantt_scale_line.gantt-print__scale-line{
+    font-size: 0;
+  }
+  .gantt_scale_line.gantt-print__scale-line .gantt_scale_cell{
+    font-size: 12px;
+  }
+}
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/generated/easy_gantt.css b/plugins/easy_gantt/assets/stylesheets/easy_gantt/generated/easy_gantt.css
new file mode 100644
index 0000000000000000000000000000000000000000..e27196c5f8ab83c20a6dccfa5517834ceef7c44b
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/generated/easy_gantt.css
@@ -0,0 +1,2956 @@
+/*
+@license
+
+dhtmlxGantt v.3.2.1 Stardard
+This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
+
+(c) Dinamenta, UAB.
+*/
+
+
+.gridHoverStyle,
+.gridSelection,
+.timelineSelection {
+  background-color: #fff3a1
+}
+.gantt_grid_scale .gantt_grid_head_cell {
+  color: #a6a6a6;
+  border-top: none!important;
+  /*border-right: none!important*/
+}
+/*.gantt_grid_data .gantt_cell {
+    border-right: none;
+    color: #454545
+}*/
+.gantt_task_link .gantt_link_arrow_right {
+  border-width: 6px;
+  margin-top: -3px
+}
+.gantt_task_link .gantt_link_arrow_left {
+  border-width: 6px;
+  margin-left: -6px;
+  margin-top: -3px
+}
+.gantt_task_link .gantt_link_arrow_down,
+.gantt_task_link .gantt_link_arrow_top {
+  border-width: 6px
+}
+.chartHeaderBg {
+  background-color: #fff
+}
+.gantt_task .gantt_task_scale .gantt_scale_cell {
+  color: #a6a6a6;
+  border-right: 1px solid #ebebeb
+}
+
+/*.gantt_row.gantt_project-type,*/
+/*.gantt_row.odd.gantt_project-type {*/
+/*background-color: #edffef*/
+/*}*/
+.gantt_task_row.gantt_project-type,
+.gantt_task_row.odd.gantt_project-type {
+  background-color: #f5fff6
+}
+.buttonBg {
+  background: #fff
+}
+.gantt_cal_light .gantt_btn_set {
+  margin: 5px 10px
+}
+.gantt_btn_set.gantt_cancel_btn_set {
+  background: #fff;
+  color: #454545;
+  border: 1px solid #cecece
+}
+.gantt_btn_set.gantt_save_btn_set {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff
+}
+.gantt_btn_set.gantt_delete_btn_set {
+  background: #ec8e00;
+  text-shadow: 0 -1px 0 #a60;
+  color: #fff
+}
+.gantt_cal_light_wide {
+  padding-left: 0!important;
+  padding-right: 0!important
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+  border-left: none!important;
+  border-right: none!important
+}
+.dhtmlx_popup_button.dhtmlx_ok_button {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff;
+  font-weight: 700;
+  border-width: 0
+}
+.dhtmlx_popup_button.dhtmlx_cancel_button {
+  font-weight: 700;
+  color: #454544
+}
+.gantt_qi_big_icon.icon_edit {
+  color: #454545;
+  background: #fff
+}
+.gantt_qi_big_icon.icon_delete {
+  text-shadow: 0 -1px 0 #a60;
+  background: #ec8e00;
+  color: #fff;
+  border-width: 0
+}
+.gantt_container {
+  font-family: Arial;
+  font-size: 13px;
+  border: 1px solid #cecece;
+  position: relative;
+  white-space: nowrap
+}
+.gantt_grid {
+  border-right: 1px solid #cecece
+}
+.gantt_task_scroll {
+  overflow-x: scroll
+}
+.gantt_task {
+  position: relative
+}
+.gantt_grid,
+.gantt_task {
+  overflow-x: hidden;
+  overflow-y: hidden;
+  display: inline-block;
+  vertical-align: top
+}
+.gantt_grid_scale,
+.gantt_task_scale {
+  color: #6b6b6b;
+  font-size: 12px;
+  border-bottom: 1px solid #cecece;
+  background-color: #fff
+}
+.gantt_scale_line {
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  border-top: 1px solid #cecece
+}
+.gantt_scale_line:first-child {
+  border-top: none
+}
+.gantt_grid_head_cell {
+  display: inline-block;
+  vertical-align: top;
+  border-right: 1px solid #cecece;
+  text-align: center;
+  position: relative;
+  cursor: default;
+  height: 100%;
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  -user-select: none;
+  -ms-user-select: none;
+  overflow: hidden
+}
+.gantt_scale_line {
+  clear: both
+}
+.gantt_grid_data {
+  width: 100%;
+  overflow: hidden
+}
+.gantt_row {
+  position: relative;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -moz-user-select: -moz-none
+}
+.gantt_add,
+.gantt_grid_head_add {
+  width: 100%;
+  height: 100%;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);
+  background-position: center center;
+  background-repeat: no-repeat;
+  cursor: pointer;
+  position: relative;
+  -moz-opacity: .3;
+  opacity: .3
+}
+.gantt_grid_head_cell.gantt_grid_head_add {
+  -moz-opacity: .6;
+  opacity: .6;
+  top: 0
+}
+.gantt_grid_head_cell.gantt_grid_head_add:hover {
+  -moz-opacity: 1;
+  opacity: 1
+}
+@media screen {
+  .gantt_grid_data .gantt_row.odd:hover,
+  .gantt_grid_data .gantt_row:hover {
+    background-color: #fff3a1
+  }
+}
+.gantt_grid_data .gantt_row.odd:hover .gantt_add,
+.gantt_grid_data .gantt_row:hover .gantt_add {
+  -moz-opacity: 1;
+  opacity: 1
+}
+.gantt_row,
+.gantt_task_row {
+  border-bottom: 1px solid #ebebeb;
+  /*background-color: #fff*/
+}
+.gantt_row.odd,
+.gantt_task_row.odd {
+  /*background-color: #fff*/
+}
+.gantt_cell,
+.gantt_grid_head_cell,
+.gantt_row,
+.gantt_scale_cell,
+.gantt_task_cell,
+.gantt_task_row {
+  box-sizing: border-box;
+  -moz-box-sizing: border-box
+}
+.gantt_grid_head_cell,
+.gantt_scale_cell {
+  line-height: inherit
+}
+.gantt_grid .gantt_grid_resize_wrap {
+  cursor: col-resize;
+  position: absolute;
+  width: 13px;
+  z-index: 1
+}
+.gantt_grid_resize_wrap .gantt_grid_resize {
+  background-color: #cecece;
+  width: 1px;
+  margin: 0 auto
+}
+.gantt_drag_marker.gantt_grid_resize_area {
+  background-color: rgba(231, 231, 231, .5);
+  border-left: 1px solid #cecece;
+  border-right: 1px solid #cecece;
+  height: 100%;
+  width: 100%;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box
+}
+.gantt_cell {
+  display: inline-block;
+  vertical-align: top;
+  border-right: 1px solid #ebebeb;
+  padding-left: 6px;
+  padding-right: 6px;
+  height: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: 13px
+}
+.gantt_grid_data .gantt_last_cell,
+.gantt_grid_scale .gantt_last_cell,
+.gantt_task_bg .gantt_last_cell,
+.gantt_task_scale .gantt_last_cell {
+  border-right-width: 0
+}
+.gantt_task_bg {
+  overflow: hidden
+}
+.gantt_scale_cell {
+  display: inline-block;
+  white-space: nowrap;
+  overflow: hidden;
+  border-right: 1px solid #cecece;
+  text-align: center;
+  height: 100%
+}
+.gantt_task_cell {
+  display: inline-block;
+  height: 100%;
+  border-right: 1px solid #ebebeb
+}
+.gantt_ver_scroll {
+  width: 0;
+  background-color: transparent;
+  height: 1px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+  display: none;
+  position: absolute;
+  right: 0
+}
+.gantt_ver_scroll>div {
+  width: 1px;
+  height: 1px
+}
+.gantt_hor_scroll {
+  height: 0;
+  background-color: transparent;
+  width: 100%;
+  clear: both;
+  overflow-x: scroll;
+  overflow-y: hidden;
+  display: none
+}
+.gantt_hor_scroll>div {
+  width: 5000px;
+  height: 1px
+}
+.gantt_tree_indent {
+  width: 15px;
+  /*height: 100%;*/
+  display: inline-block
+}
+.gantt_tree_content,
+.gantt_tree_icon {
+  vertical-align: top
+}
+.gantt_tree_icon {
+  width: 28px;
+  height: 100%;
+  display: inline-block;
+  background-repeat: no-repeat;
+  background-position: center center
+}
+.gantt_tree_content {
+  height: 100%;
+  display: inline-block
+}
+.gantt_tree_icon.gantt_blank {
+  width: 18px
+}
+.gantt_tree_icon.gantt_file {
+  background-image: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==)
+}
+.gantt_grid_head_cell .gantt_sort {
+  position: absolute;
+  right: 5px;
+  top: 8px;
+  width: 7px;
+  height: 13px;
+  background-repeat: no-repeat;
+  background-position: center center
+}
+.gantt_grid_head_cell .gantt_sort.gantt_asc {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)
+}
+.gantt_grid_head_cell .gantt_sort.gantt_desc {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)
+}
+.gantt_inserted,
+.gantt_updated {
+  font-weight: 700
+}
+.gantt_deleted {
+  text-decoration: line-through
+}
+.gantt_invalid {
+  background-color: #FFE0E0
+}
+.gantt_error {
+  color: red
+}
+.gantt_status {
+  right: 1px;
+  padding: 5px 10px;
+  background: rgba(155, 155, 155, .1);
+  position: absolute;
+  top: 1px;
+  -webkit-transition: opacity .2s;
+  transition: opacity .2s;
+  opacity: 0
+}
+.gantt_status.gantt_status_visible {
+  opacity: 1
+}
+#gantt_ajax_dots span {
+  -webkit-transition: opacity .2s;
+  transition: opacity .2s;
+  background-repeat: no-repeat;
+  opacity: 0
+}
+#gantt_ajax_dots span.gantt_dot_visible {
+  opacity: 1
+}
+.dhtmlx_message_area {
+  position: fixed;
+  right: 5px;
+  width: 250px;
+  z-index: 1000
+}
+.dhtmlx-message {   // HOSEK
+min-width: 120px;
+  font-family: Arial;
+  z-index: 10000;
+  margin: 5px 5px 10px;
+  -webkit-transition: all .5s ease;
+  -moz-transition: all .5s ease;
+  -o-transition: all .5s ease;
+  transition: all .5s ease;
+  font-size: 14px;
+  color: #000;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  padding: 0;
+  background-color: #FFF;
+  border-radius: 3px;
+  border: 1px solid #fff
+}
+.dhtmlx-message.hidden {  // HOSEK
+height: 0;
+  padding: 0;
+  border-width: 0;
+  margin: 0;
+  overflow: hidden
+}
+.dhtmlx_modal_box {
+  overflow: hidden;
+  display: inline-block;
+  min-width: 250px;
+  width: 250px;
+  text-align: center;
+  position: fixed;
+  z-index: 20000;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  font-family: Arial;
+  border-radius: 6px;
+  border: 1px solid #cecece;
+  background: #fff
+}
+.dhtmlx_popup_title {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+  border-width: 0
+}
+.dhtmlx_button,
+.dhtmlx_popup_button {
+  border: 1px solid #cecece;
+  height: 30px;
+  line-height: 30px;
+  display: inline-block;
+  margin: 0 5px;
+  border-radius: 4px;
+  background: #fff
+}
+.dhtmlx-message,   /* HOSEK*/
+.dhtmlx_button,
+.dhtmlx_popup_button {
+  user-select: none;
+  -webkit-user-select: none;
+  -moz-user-select: -moz-none;
+  -ms-user-select: none;
+  cursor: pointer
+}
+.dhtmlx_popup_text {
+  overflow: hidden
+}
+.dhtmlx_popup_controls {
+  border-radius: 6px;
+  padding: 10px
+}
+.dhtmlx_popup_button {
+  min-width: 100px
+}
+div.dhx_modal_cover {
+  background-color: #000;
+  cursor: default;
+  filter: alpha(opacity=20);
+  opacity: .2;
+  position: fixed;
+  z-index: 19999;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  border: none;
+  zoom: 1
+}
+.dhtmlx-message img,   /* HOSEK */
+.dhtmlx_modal_box img {
+  float: left;
+  margin-right: 20px
+}
+.dhtmlx-alert-error,
+.dhtmlx-confirm-error {
+  border: 1px solid red
+}
+.dhtmlx_button input,
+.dhtmlx_popup_button div {
+  border-radius: 4px;
+  font-size: 14px;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  padding: 0;
+  margin: 0;
+  vertical-align: top
+}
+.dhtmlx_popup_title {
+  color: #fff;
+  text-shadow: 1px 1px #000;
+  height: 40px;
+  line-height: 40px;
+  font-size: 20px
+}
+.dhtmlx_popup_text {
+  margin: 15px 15px 5px;
+  font-size: 14px;
+  color: #000;
+  min-height: 30px;
+  border-radius: 6px
+}
+
+/* HOSEK  V */
+/*.dhtmlx-error,
+.dhtmlx-info {
+    font-size: 14px;
+    color: #000;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+    padding: 0;
+    background-color: #FFF;
+    border-radius: 3px;
+    border: 1px solid #fff
+}*/
+.dhtmlx-message div {
+  padding: 5px 10px;
+  border-radius: 3px;
+}
+.dhtmlx-info div {
+  background-color: #fff;
+  border: 1px solid #cecece;
+}
+.dhtmlx-error {
+  background-color: #d81b1b;
+  border: 1px solid #ff3c3c;
+}
+.dhtmlx-error div {
+  background-color: #d81b1b;
+  border: 1px solid #940000;
+  color: #FFF;
+}
+.dhtmlx-success {
+  background-color: #2dff2d;
+  border: 1px solid #6CFF6C;
+}
+.dhtmlx-success div {
+  background-color: #2dff2d;
+  border: 1px solid #1B991B;
+  /*color: #FFF*/
+}
+/* HOSEK A */
+.gantt_data_area div,
+.gantt_grid div {
+  -ms-touch-action: none;
+  -webkit-tap-highlight-color: transparent
+}
+.gantt_data_area {
+  position: relative;
+  overflow-x: hidden;
+  overflow-y: hidden;
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.gantt_links_area {
+  position: absolute;
+  left: 0;
+  top: 0
+}
+.gantt_side_content,
+.gantt_task_content,
+.gantt_task_progress {
+  line-height: inherit;
+  overflow: hidden;
+  height: 100%
+}
+.gantt_task_content {
+  font-size: 12px;
+  width: 100%;
+  top: 0;
+  position: absolute;
+  white-space: nowrap;
+  text-align: center
+}
+.gantt_task_line {
+  position: absolute;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -moz-user-select: -moz-none;
+  user-select: none;
+}
+.gantt_task_line.gantt_drag_move div {
+  cursor: move
+}
+.gantt_touch_move,
+.gantt_touch_progress .gantt_touch_resize {
+  -moz-transform: scale(1.02, 1.1);
+  -o-transform: scale(1.02, 1.1);
+  -webkit-transform: scale(1.02, 1.1);
+  transform: scale(1.02, 1.1);
+  -moz-transform-origin: 50%;
+  -o-transform-origin: 50%;
+  -webkit-transform-origin: 50%;
+  transform-origin: 50%
+}
+.gantt_touch_progress .gantt_task_progress_drag,
+.gantt_touch_resize .gantt_task_drag {
+  -moz-transform: scaleY(1.3);
+  -o-transform: scaleY(1.3);
+  -webkit-transform: scaleY(1.3);
+  transform: scaleY(1.3);
+  -moz-transform-origin: 50%;
+  -o-transform-origin: 50%;
+  -webkit-transform-origin: 50%;
+  transform-origin: 50%
+}
+.gantt_side_content {
+  position: absolute;
+  white-space: nowrap;
+  /*color: #6e6e6e;*/
+  bottom: 1px;
+  /*font-size: 14px*/
+}
+.gantt_side_content.gantt_left {
+  right: 100%;
+  padding-right: 15px
+}
+.gantt_side_content.gantt_right {
+  left: 100%;
+  padding-left: 15px
+}
+.gantt_side_content.gantt_link_crossing {
+  /*bottom: 8.75px*/
+}
+.gantt_link_arrow,
+.gantt_task_link .gantt_line_wrapper {
+  position: absolute;
+  cursor: pointer
+}
+.gantt_line_wrapper div {
+  border: 1px solid;
+}
+.gantt_task_link div.gantt_link_arrow {
+  background-color: transparent;
+  border-style: solid;
+  width: 0;
+  height: 0
+}
+.gantt_link_control {
+  position: absolute;
+  width: 13px;
+  top: 0
+}
+.gantt_link_control div {
+  display: none;
+  cursor: pointer;
+  box-sizing: border-box;
+  position: relative;
+  top: 50%;
+  margin-top: -7.5px;
+  vertical-align: middle;
+  border: 1px solid #929292;
+  -webkit-border-radius: 6.5px;
+  -moz-border-radius: 6.5px;
+  border-radius: 6.5px;
+  height: 13px;
+  width: 13px;
+  background-color: #f0f0f0
+}
+.gantt_link_control div:hover {
+  background-color: #fff
+}
+.gantt_link_target .gantt_link_control div,
+.gantt_task_line.gantt_selected .gantt_link_control div,
+.gantt_task_line:hover .gantt_link_control div {
+  display: block
+}
+.gantt_link_source,
+.gantt_link_target {
+  box-shadow: 0 0 3px #3db9d3
+}
+.gantt_link_target.link_finish_allow,
+.gantt_link_target.link_start_allow {
+  box-shadow: 0 0 3px #ffbf5e
+}
+.gantt_link_target.link_finish_deny,
+.gantt_link_target.link_start_deny {
+  box-shadow: 0 0 3px #e87e7b
+}
+.link_finish_allow .gantt_link_control.task_right div,
+.link_start_allow .gantt_link_control.task_left div {
+  background-color: #ffbf5e;
+  border-color: #ffa011
+}
+.link_finish_deny .gantt_link_control.task_right div,
+.link_start_deny .gantt_link_control.task_left div {
+  background-color: #e87e7b;
+  border-color: #dd3e3a
+}
+.gantt_link_arrow_right {
+  border-width: 4px 0 4px 6px;
+  border-top-color: transparent!important;
+  border-right-color: transparent!important;
+  border-bottom-color: transparent!important;
+  border-color: #ffa011;
+  margin-top: -1px
+}
+.gantt_link_arrow_left {
+  border-width: 4px 6px 4px 0;
+  margin-top: -1px;
+  border-top-color: transparent!important;
+  border-color: #ffa011;
+  border-bottom-color: transparent!important;
+  border-left-color: transparent!important
+}
+.gantt_link_arrow_top {
+  border-width: 0 4px 6px;
+  border-color: #ffa011;
+  border-top-color: transparent!important;
+  border-right-color: transparent!important;
+  border-left-color: transparent!important
+}
+.gantt_link_arrow_down {
+  border-width: 4px 6px 0 4px;
+  border-color: #ffa011;
+  border-right-color: transparent!important;
+  border-bottom-color: transparent!important;
+  border-left-color: transparent!important
+}
+.gantt_task_line.gantt_selected .gantt_task_drag,
+.gantt_task_line.gantt_selected .gantt_task_progress_drag,
+.gantt_task_line:hover .gantt_task_drag,
+.gantt_task_line:hover .gantt_task_progress_drag {
+  display: block
+}
+.gantt_task_drag {
+  cursor: w-resize;
+  height: 100%;
+  display: none;
+  position: absolute;
+  width: 6px;
+  z-index: 1;
+  top: 0
+}
+.gantt_task_drag.task_left {
+  left: 0
+}
+.gantt_task_drag.task_right {
+  right: 0
+}
+.gantt_task_progress_drag {
+  cursor: w-resize;
+  position: absolute;
+  bottom: -4px;
+  z-index: 1;
+  display: none;
+}
+.gantt_link_tooltip {
+  box-shadow: 3px 3px 3px #888;
+  background-color: #fff;
+  border-left: 1px dotted #cecece;
+  border-top: 1px dotted #cecece;
+  font-family: Tahoma;
+  font-size: 8pt;
+  color: #444;
+  padding: 6px;
+  line-height: 20px
+}
+.gantt_link_direction {
+  height: 0;
+  border: 0 #ffa011;
+  border-bottom-style: dashed;
+  border-bottom-width: 2px;
+  transform-origin: 0 0;
+  -ms-transform-origin: 0 0;
+  -webkit-transform-origin: 0 0;
+  z-index: 2;
+  margin-left: 1px;
+  position: absolute
+}
+.gantt_grid_data .gantt_row.gantt_selected,
+.gantt_grid_data .gantt_row.odd.gantt_selected,
+.gantt_task_row.gantt_selected {
+  background-color: #fff3a1
+}
+.gantt_task_row.gantt_selected .gantt_task_cell {
+  border-right-color: #ffec6e
+}
+.gantt_task_line.gantt_milestone-type .gantt_task_content {
+  background: inherit;
+  border: inherit;
+  border-width: 1px;
+  border-radius: inherit;
+  box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  -webkit-transform: rotate(45deg);
+  -moz-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  -o-transform: rotate(45deg);
+  transform: rotate(45deg)
+}
+.gantt_task_line.gantt_task_inline_color {
+  border-color: #999
+}
+.gantt_task_line.gantt_task_inline_color .gantt_task_progress {
+  background-color: #363636;
+  opacity: .2
+}
+.gantt_task_line.gantt_task_inline_color.gantt_project-type.gantt_selected,
+.gantt_task_line.gantt_task_inline_color.gantt_selected {
+  box-shadow: 0 0 5px #999
+}
+.gantt_task_link.gantt_link_inline_color:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #999
+}
+.gantt_critical_task {
+  background-color: #e63030;
+  border-color: #9d3a3a
+}
+.gantt_critical_task .gantt_task_progress {
+  background-color: rgba(0, 0, 0, .4)
+}
+.gantt_critical_link .gantt_line_wrapper>div {
+  background-color: #e63030
+}
+.gantt_critical_link .gantt_link_arrow {
+  border-color: #e63030
+}
+.gantt_unselectable,
+.gantt_unselectable div {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -moz-user-select: -moz-none;
+  -ms-user-select: none;
+}
+.gantt_cal_light {
+  -webkit-tap-highlight-color: transparent;
+  background: #fff;
+  border-radius: 6px;
+  font-family: Arial;
+  border: 1px solid #cecece;
+  color: #6b6b6b;
+  font-size: 12px;
+  position: absolute;
+  z-index: 10001;
+  width: 550px;
+  height: 250px;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07)
+}
+.gantt_cal_light select {
+  font-family: Arial;
+  border: 1px solid #cecece;
+  font-size: 13px;
+  padding: 2px;
+  margin: 0
+}
+.gantt_cal_ltitle {
+  padding: 7px 10px;
+  overflow: hidden;
+  white-space: nowrap;
+  -webkit-border-radius: 6px 6px 0 0;
+  -moz-border-radius-topleft: 6px;
+  -moz-border-radius-bottomleft: 0;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 0;
+  border-radius: 6px 6px 0 0
+}
+.gantt_cal_ltitle span {
+  white-space: nowrap
+}
+.gantt_cal_lsection {
+  color: #727272;
+  font-weight: 700;
+  padding: 12px 0 5px 10px
+}
+.gantt_cal_lsection .gantt_fullday {
+  float: right;
+  margin-right: 5px;
+  font-size: 12px;
+  font-weight: 400;
+  line-height: 20px;
+  vertical-align: top;
+  cursor: pointer
+}
+.gantt_cal_lsection {
+  font-size: 13px
+}
+.gantt_cal_ltext {
+  padding: 2px 10px;
+  overflow: hidden
+}
+.gantt_cal_ltext textarea {
+  overflow: auto;
+  font-family: Arial;
+  font-size: 13px;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  border: 1px solid #cecece;
+  height: 100%;
+  width: 100%;
+  outline: 0!important;
+  resize: none
+}
+.gantt_time {
+  font-weight: 700
+}
+.gantt_cal_light .gantt_title {
+  padding-left: 10px
+}
+.gantt_cal_larea {
+  border: 1px solid #cecece;
+  border-left: none;
+  border-right: none;
+  background-color: #fff;
+  overflow: hidden;
+  height: 1px
+}
+.gantt_btn_set {
+  margin: 10px 7px 5px 10px;
+  padding: 5px 15px 5px 10px;
+  float: left;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  border-width: 0;
+  border-color: #cecece;
+  border-style: solid;
+  height: 32px;
+  font-weight: 700;
+  background: #fff;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  cursor: pointer
+}
+.gantt_btn_set div {
+  float: left;
+  font-size: 13px;
+  height: 22px;
+  line-height: 22px;
+  background-repeat: no-repeat;
+  vertical-align: middle
+}
+.gantt_save_btn {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=);
+  margin-top: 2px;
+  width: 21px
+}
+.gantt_cancel_btn {
+  margin-top: 2px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);
+  width: 20px
+}
+.gantt_delete_btn {
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=);
+  margin-top: 2px;
+  width: 20px
+}
+.gantt_cal_cover {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  z-index: 10000;
+  top: 0;
+  left: 0;
+  background-color: #000;
+  opacity: .1;
+  filter: alpha(opacity=10)
+}
+.gantt_custom_button {
+  padding: 0 3px;
+  font-family: Arial;
+  font-size: 13px;
+  font-weight: 400;
+  margin-right: 10px;
+  margin-top: -5px;
+  cursor: pointer;
+  float: right;
+  height: 21px;
+  width: 90px;
+  border: 1px solid #CECECE;
+  text-align: center;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  -ms-border-radius: 4px;
+  -o-border-radius: 4px;
+  border-radius: 4px
+}
+.gantt_custom_button div {
+  cursor: pointer;
+  float: none;
+  height: 21px;
+  line-height: 21px;
+  vertical-align: middle
+}
+.gantt_custom_button div:first-child {
+  display: none
+}
+.gantt_cal_light_wide {
+  width: 580px;
+  padding: 2px 4px
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  border: 1px solid #cecece
+}
+.gantt_cal_light_wide .gantt_cal_lsection {
+  border: 0;
+  float: left;
+  text-align: right;
+  width: 80px;
+  height: 20px;
+  padding: 5px 10px 0 0
+}
+.gantt_cal_light_wide .gantt_wrap_section {
+  position: relative;
+  padding: 10px 0;
+  overflow: hidden;
+  border-bottom: 1px solid #ebebeb
+}
+.gantt_cal_light_wide .gantt_section_time {
+  overflow: hidden;
+  padding-top: 2px!important;
+  padding-right: 0;
+  height: 20px!important
+}
+.gantt_cal_light_wide .gantt_cal_ltext {
+  padding-right: 0
+}
+.gantt_cal_light_wide .gantt_cal_larea {
+  padding: 0 10px;
+  width: 100%
+}
+.gantt_cal_light_wide .gantt_section_time {
+  background: 0 0
+}
+.gantt_cal_light_wide .gantt_cal_checkbox label {
+  padding-left: 0
+}
+.gantt_cal_light_wide .gantt_cal_lsection .gantt_fullday {
+  float: none;
+  margin-right: 0;
+  font-weight: 700;
+  cursor: pointer
+}
+.gantt_cal_light_wide .gantt_custom_button {
+  position: absolute;
+  top: 0;
+  right: 0;
+  margin-top: 2px
+}
+.gantt_cal_light_wide .gantt_repeat_right {
+  margin-right: 55px
+}
+.gantt_cal_light_wide.gantt_cal_light_full {
+  width: 738px
+}
+.gantt_cal_wide_checkbox input {
+  margin-top: 8px;
+  margin-left: 14px
+}
+.gantt_cal_light input {
+  font-size: 13px
+}
+.gantt_section_time {
+  background-color: #fff;
+  white-space: nowrap;
+  padding: 5px 10px;
+  padding-top: 2px!important
+}
+.gantt_section_time .gantt_time_selects {
+  float: left;
+  height: 25px
+}
+.gantt_section_time .gantt_time_selects select {
+  height: 23px;
+  padding: 2px;
+  border: 1px solid #cecece
+}
+.gantt_duration {
+  width: 100px;
+  height: 23px;
+  float: left;
+  white-space: nowrap;
+  margin-left: 20px;
+  line-height: 23px
+}
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc,
+.gantt_duration .gantt_duration_value {
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  text-align: center;
+  vertical-align: top;
+  height: 100%;
+  border: 1px solid #cecece
+}
+.gantt_duration .gantt_duration_value {
+  width: 40px;
+  padding: 3px 4px;
+  border-left-width: 0;
+  border-right-width: 0
+}
+.gantt_duration .gantt_duration_dec,
+.gantt_duration .gantt_duration_inc {
+  width: 20px;
+  padding: 1px 1px 3px;
+  background: #fff
+}
+.gantt_duration .gantt_duration_dec {
+  -moz-border-top-left-radius: 4px;
+  -moz-border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px
+}
+.gantt_duration .gantt_duration_inc {
+  margin-right: 4px;
+  -moz-border-top-right-radius: 4px;
+  -moz-border-bottom-right-radius: 4px;
+  -webkit-border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  border-top-right-radius: 4px;
+  border-bottom-right-radius: 4px
+}
+.gantt_cal_quick_info {
+  border: 1px solid #cecece;
+  border-radius: 6px;
+  position: absolute;
+  z-index: 300;
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  background-color: #fff;
+  width: 300px;
+  transition: left .5s ease, right .5s;
+  -moz-transition: left .5s ease, right .5s;
+  -webkit-transition: left .5s ease, right .5s;
+  -o-transition: left .5s ease, right .5s
+}
+.gantt_no_animate {
+  transition: none;
+  -moz-transition: none;
+  -webkit-transition: none;
+  -o-transition: none
+}
+.gantt_cal_quick_info.gantt_qi_left .gantt_qi_big_icon {
+  float: right
+}
+.gantt_cal_qi_title {
+  -webkit-border-radius: 6px 6px 0 0;
+  -moz-border-radius-topleft: 6px;
+  -moz-border-radius-bottomleft: 0;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 0;
+  border-radius: 6px 6px 0 0;
+  padding: 5px 0 8px 12px;
+  color: #454545;
+  background-color: #fff;
+  border-bottom: 1px solid #cecece
+}
+.gantt_cal_qi_tdate {
+  font-size: 14px;
+  font-weight: 700
+}
+.gantt_cal_qi_tcontent {
+  font-size: 13px
+}
+.gantt_cal_qi_content {
+  padding: 16px 8px;
+  font-size: 13px;
+  color: #454545;
+  overflow: hidden
+}
+.gantt_cal_qi_controls {
+  -webkit-border-radius: 0 0 6px 6px;
+  -moz-border-radius-topleft: 0;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topright: 0;
+  -moz-border-radius-bottomright: 6px;
+  border-radius: 0 0 6px 6px;
+  padding-left: 7px
+}
+.gantt_cal_qi_controls .gantt_menu_icon {
+  margin-top: 6px;
+  background-repeat: no-repeat
+}
+.gantt_cal_qi_controls .gantt_menu_icon.icon_edit {
+  width: 20px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)
+}
+.gantt_cal_qi_controls .gantt_menu_icon.icon_delete {
+  width: 20px;
+  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=)
+}
+.gantt_qi_big_icon {
+  font-size: 13px;
+  border-radius: 4px;
+  font-weight: 700;
+  background: #fff;
+  margin: 5px 9px 8px 0;
+  min-width: 60px;
+  line-height: 32px;
+  vertical-align: middle;
+  padding: 0 10px 0 5px;
+  cursor: pointer;
+  border: 1px solid #cecece
+}
+.gantt_cal_qi_controls div {
+  float: left;
+  height: 32px;
+  text-align: center;
+  line-height: 32px
+}
+.gantt_tooltip {
+  box-shadow: 3px 3px 3px rgba(0, 0, 0, .07);
+  background-color: #fff;
+  border-left: 1px solid rgba(0, 0, 0, .07);
+  border-top: 1px solid rgba(0, 0, 0, .07);
+  font-family: Arial;
+  font-size: 8pt;
+  color: #454545;
+  padding: 10px;
+  position: absolute;
+  z-index: 50
+}
+.gantt_marker {
+  height: 100%;
+  width: 2px;
+  top: 0;
+  position: absolute;
+  text-align: center;
+  background-color: rgba(255, 0, 0, .4);
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box
+}
+.gantt_marker .gantt_marker_content {
+  padding: 5px;
+  background: inherit;
+  color: #fff;
+  position: absolute;
+  font-size: 12px;
+  line-height: 12px;
+  opacity: .8
+}
+.gantt_marker_area {
+  position: absolute;
+  top: 0;
+  left: 0
+}
+.gantt_noselect {
+  -moz-user-select: -moz-none;
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none
+}
+.gantt_drag_marker {
+  position: absolute;
+  font-family: Arial;
+  font-size: 13px
+}
+.gantt_drag_marker .gantt_tree_icon.gantt_blank,
+.gantt_drag_marker .gantt_tree_icon.gantt_close,
+.gantt_drag_marker .gantt_tree_icon.gantt_open,
+.gantt_drag_marker .gantt_tree_indent {
+  display: none
+}
+.gantt_drag_marker,
+.gantt_drag_marker .gantt_row.odd {
+  background-color: #fff
+}
+.gantt_drag_marker .gantt_row {
+  border-left: 1px solid #d2d2d2;
+  border-top: 1px solid #d2d2d2
+}
+.gantt_drag_marker .gantt_cell {
+  border-color: #d2d2d2
+}
+.gantt_row.gantt_over,
+.gantt_task_row.gantt_over {
+  background-color: #0070fe
+}
+.gantt_row.gantt_transparent .gantt_cell {
+  opacity: .7
+}
+.gantt_task_row.gantt_transparent {
+  background-color: #f8fdfd
+}
+.dhtmlx_popup_button.dhtmlx_delete_button {
+  background: #3db9d3;
+  text-shadow: 0 -1px 0 #248a9f;
+  color: #fff;
+  font-weight: 700;
+  border-width: 0
+}
+/*
+
+
+
+*/
+
+@media print {
+  .content-title {
+    display: none;
+  }
+
+  @page {
+    size: landscape
+  }
+
+  .gantt-grid-checkbox-cont {
+    display: none;
+  }
+}
+
+.gantt_container {
+  display: inline-block;
+}
+
+#gantt_cont {
+  position: relative;
+  z-index: 0;
+}
+
+#easy_gantt .weekend {
+  background-color: #F7F7F7;
+}
+
+#easy_gantt .first-date {
+  position: relative;
+}
+
+#easy_gantt .first-date:after {
+  content: "";
+  position: absolute;
+  display: block;
+  left: -1px;
+  top: 0;
+  bottom: 0;
+  border-left: 1px solid #628DB6;
+}
+
+#easy_gantt {
+  position: relative;
+}
+
+
+.no_task_controls .gantt_link_control div {
+  display: none !important;
+}
+
+.gantt-fresh .gantt_link_control div {
+  display: none !important;
+}
+
+.no_task_controls .gantt_task_drag, .no_task_controls .gantt_task_progress_drag {
+  display: none !important;
+}
+
+#gantt_link_dialog {
+  border: 1px #000 solid;
+  background-color: #fff;
+  width: 200px;
+  position: absolute;
+  padding: 10px;
+  z-index: 2;
+}
+
+#gantt_link_dialog input[type="number"] {
+  width: 38px;
+}
+
+.gantt-tooltip {
+  position: absolute;
+  display: none;
+  background-color: white;
+  -webkit-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  -moz-box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  box-shadow: 0 0 4px 2px rgba(232, 232, 232, 1);
+  padding: 20px;
+  z-index: 10;
+}
+
+.gantt-tooltip-header {
+  font-size: 1em;
+  padding-bottom: 10px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+  margin: 0 0 10px;
+}
+
+.gantt-tooltip-label {
+  font-size: 0.89em;
+  color: #a0a0a0;
+}
+
+.gantt-tooltip-problem {
+  color: #d94838;
+  font-size: smaller;
+}
+
+#link_popup_button_cont {
+  margin-top: 35px;
+}
+
+#easy_gantt a.disabled:hover {
+  text-decoration: none;
+  cursor: not-allowed;
+}
+
+.milestone-type .gantt_cell {
+  border-right: 0;
+}
+
+.gantt-legend-color-square {
+  border: 1px solid transparent;
+  margin-left: 10px;
+}
+
+.gantt-color-square, .gantt-legend-color-square {
+  width: 15px;
+  height: 15px;
+  display: inline-block;
+  vertical-align: middle;
+  border: 1px solid transparent;
+}
+
+.easy-gantt-legend-symbol {
+  margin-left: 10px;
+}
+
+.gantt_cell {
+  padding: 0;
+}
+
+.gantt_grid {
+  border-right-width: 2px;
+}
+
+.gantt_row .gantt_drag_handle:before {
+  width: 18px;
+  height: 100%;
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.gantt_row:hover .gantt_drag_handle:before {
+  content: '\21F5';
+  opacity: 0.5;
+}
+
+.gantt_row .gantt_drag_handle:hover:before {
+  opacity: 1;
+  content: '\21F5';
+}
+
+.gantt_row.gantt_drag_to_allowed {
+  background-color: #FFEB59;
+}
+
+.gantt_row.gantt_drag_hover {
+  background-color: #eeeeee;
+}
+
+.gantt_row.gantt_drag_to_allowed.gantt_drag_hover {
+  background-color: #FBCF00;
+}
+
+/*#sample_cont.flash {*/
+/*background-image: none;*/
+/*}*/
+.gantt-supertop-panel {
+  position: relative;
+}
+
+#easy_gantt .gantt-sample-flash {
+  font-size: 14px;
+}
+
+.gantt-menu {
+  z-index: 1;
+  position: relative;
+  background-color: #ffffff;
+  padding: 5px 0;
+}
+
+.gantt-footer-menu {
+  padding: 5px 0;
+}
+
+/*.easy-gantt-menu .action-buttons-with-submenu {*/
+/*display: inline;*/
+/*}*/
+.gantt-modal-video {
+  margin-left: 10px;
+  margin-top: 5px;
+}
+
+.gantt_drag_marker {
+  z-index: 1;
+  pointer-events: none;
+}
+
+.gantt_grid_column_resize_wrap, .gantt_grid_columns_resize_wrap {
+  top: 0;
+  height: 100%;
+  cursor: col-resize;
+  position: absolute;
+  /*background-color: red;*/
+  width: 12px
+}
+
+.gantt-sample-close-button {
+  background-color: #cd0a0a;
+  color: white;
+  width: auto;
+  height: auto;
+  float: none;
+  display: inline;
+  margin-left: 25px;
+  padding: 8px 10px 8px 25px;
+  background-position-x: 5px;
+}
+
+/* Easy Redmine < 2016 */
+#easy_gantt.easy .push-left {
+  float: left;
+}
+
+#easy_gantt.easy .push-right {
+  float: right;
+  text-align: right
+}
+
+/*#easy_gantt.easy .gantt-menu p[id^="button_"], #easy_gantt.easy .gantt-menu div[id^="button_"]  {*/
+/*display: inline-block;*/
+/*}*/
+.gantt_grid_data div.empty-type div {
+  display: none;
+}
+
+.gantt_bars_area div.empty-type {
+  display: none;
+}
+
+.gantt_task_line.planned {
+  z-index: 1;
+}
+
+.gantt_link_tooltip.gantt_link_deny {
+  background-color: #ff6666;
+}
+
+@media print {
+  #easy_servicebar {
+    display: none;
+  }
+
+  #easy_gantt_menu {
+    display: none;
+  }
+
+  #easy_gantt_footer {
+    display: none;
+  }
+
+  .flash {
+    display: none;
+  }
+}
+
+.gantt_grid_superitem {
+  font-weight: bold;
+  /*font-style: italic;*/
+  height: 100%;
+  width: 100%;
+  vertical-align: top;
+}
+
+.gantt_grid_scale, .gantt_task_scale {
+  background-color: #ffffff;
+  z-index: 1;
+  position: relative;
+  border-top: 1px solid #cecece;
+  transform: translate(0, -1px);
+}
+
+.gantt_grid_scale .gantt_grid_head_cell, .gantt_task .gantt_task_scale .gantt_scale_cell {
+  color: rgba(72, 72, 72, 0.8);
+}
+
+/*.gantt_scale_cell{*/
+/*overflow: visible;*/
+/*}*/
+
+.gantt-footer-legend {
+  display: inline-block;
+  float: right;
+  margin-top: 20px;
+}
+
+.gantt-legend-symbol {
+  font-size: 15px;
+}
+
+#easy_gantt input.wrong {
+  background-color: #cd0a0a;
+}
+
+.gantt-reload-model-error {
+  color: #cd0a0a;
+}
+
+.gantt_task_line.gantt_selected {
+  box-shadow: none !important;
+}
+
+.gantt_link_point {
+  border-color: rgba(217, 72, 56, 1) !important;
+  background-color: rgba(217, 72, 56, 1) !important;
+}
+
+.gantt_task_drag {
+  background: radial-gradient(rgba(217, 72, 56, 1) 25%, transparent 10%) 1px 1px;
+  background-size: 4px 4px;
+}
+
+#easy_gantt .wrong .gantt_line_wrapper div {
+  border-color: rgba(217, 72, 56, 1) !important;
+}
+
+#easy_gantt .wrong .gantt_link_arrow {
+  border-color: rgba(217, 72, 56, 1) !important;
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+}
+
+#easy_gantt .gantt_link_arrow_right {
+  border-top-color: transparent !important;
+  border-right-color: transparent !important;
+  border-bottom-color: transparent !important;
+}
+
+.gantt_task_progress_drag {
+  width: 0;
+  height: 0;
+  border-right: 6.5px solid transparent;
+  border-bottom: 10px solid rgba(217, 72, 56, 1);
+  border-left: 6.5px solid transparent;
+  margin-left: -6.5px;
+}
+
+.gantt_milestone-type{
+  border: none !important;
+  background: none !important;
+}
+
+.gantt_link_control.task_left {
+  left: -6.5px;
+  width: 6.5px;
+  border-radius: 6.5px 0 0 6.5px;
+}
+
+.gantt_link_control.task_right {
+  right: -6.5px;
+  width: 6.5px;
+  border-radius: 0 6.5px 6.5px 0;
+}
+
+.gantt_link_control.task_left .gantt_link_point {
+  width: 6.5px;
+  border-radius: 6.5px 0 0 6.5px;
+}
+
+.gantt_link_control.task_right .gantt_link_point {
+  width: 6.5px;
+  border-radius: 0 6.5px 6.5px 0;
+}
+
+@media print {
+  body {
+    margin: 0;
+  }
+
+  .gantt_sort {
+    display: none;
+  }
+}
+
+.gantt-grid-header-collapse-buttons {
+  position: absolute;
+  left: 5px;
+  bottom: 2px;
+}
+
+.gantt-grid-header-collapse-buttons a {
+  font-size: 18px;
+  color: #a6a6a6;
+  font-weight: bold;
+  font-family: monospace;
+  display: inline-block;
+}
+
+.gantt-grid-header-collapse-buttons a.active {
+  font-size: 18px;
+  color: #d94838 !important;
+  background-color: transparent !important;
+  border: none !important;
+}
+
+.gantt-grid-header-collapse-buttons a:hover {
+  font-size: 18px;
+  color: rgba(72, 72, 72, 1);
+  text-decoration: none;
+  background-color: #eeeeee !important;
+  box-shadow: 0 0 15px 5px #eeeeee;
+  border-radius: 11px;
+}
+
+.gantt-grid-checkbox-cont br {
+  display: none;
+}
+
+@media screen {
+  .gantt-icon-parent-issue:before {
+    content: '⥐'
+  }
+
+  .gantt-icon-milestone:before {
+    content: '♦';
+  }
+}
+
+.gantt_row:hover .gantt-bullet-hover-hide {
+  display: none;
+}
+
+.gantt-sum-row-small {
+  font-size: 8px;
+}
+
+.gantt-sum-row-negative {
+  color: red;
+}
+
+#easy_gantt.easy #easy_gantt_menu {
+  z-index: 2;
+  overflow: visible;
+}
+
+#easy_gantt .contextual.settings a {
+  z-index: 1;
+  border: none;
+  background: none;
+  padding-right: 0;
+  opacity: 0.6;
+}
+
+#easy_gantt .contextual.settings a:hover {
+  background: none;
+  opacity: 1;
+}
+
+.gantt-grid-header-multi {
+  line-height: normal;
+  display: inline-block;
+  white-space: normal;
+}
+
+.gantt_task_relation_stop {
+  position: absolute;
+  width: 8px;
+  top: 0;
+  height: 100%;
+  border: 2px solid rgba(255, 120, 0, 0.5);
+  box-sizing: border-box;
+}
+
+.gantt_task_relation_stop_left {
+  border-right-color: transparent;
+}
+
+.gantt_task_relation_stop_right {
+  border-left-color: transparent;
+  -webkit-transform: translate(-100%);
+  -moz-transform: translate(-100%);
+  -ms-transform: translate(-100%);
+  -o-transform: translate(-100%);
+  transform: translate(-100%);
+}
+
+.gantt-menu-sub-panel {
+  margin-top: 3px;
+}
+
+.gantt_task .gantt-sum-row .gantt_scale_cell {
+  font-weight: bold;
+  color: #260080;
+}
+
+.gantt-menu-problems-count.gantt-with-problems {
+  background-color: red;
+  padding: 1px;
+  border: 1px solid #b0b0b0;
+  border-radius: 4px;
+}
+
+.gantt-menu-problems-list {
+  overflow: auto;
+  position: absolute;
+  z-index: 2;
+  max-width: 600px;
+  margin-top: 5px;
+  border: 2px solid #cecece;
+  background-color: white;
+  right: 0;
+  text-align: left;
+}
+
+.gantt-menu-problems-list ol {
+  margin: 5px 5px 5px -5px;
+}
+
+.gantt-menu-problems-list li {
+  border-bottom: 1px solid #d9d9d9;
+  white-space: nowrap;
+  padding: 5px 25px 5px 5px;
+}
+
+.gantt-menu-problems-list ol,
+.gantt-menu-problems-list a {
+  color: #4b5561;
+}
+
+.gantt-menu-problems-reason {
+  color: red;
+}
+
+.gantt-task-bar-line {
+  position: absolute;
+  left: 0;
+  top: 0;
+  overflow: hidden
+}
+.jasmine_html-reporter .jasmine-summary ul{
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+@media print {
+  /* Because of gaps between cells in header */
+  .gantt_scale_line.gantt-print__scale-line{
+    font-size: 0;
+  }
+  .gantt_scale_line.gantt-print__scale-line .gantt_scale_cell{
+    font-size: 12px;
+  }
+}
+.gantt__integrity-alert__subtitle{
+  font-weight: bold;
+}
+.gantt__integrity-alert__body a{
+  cursor: pointer;
+  color: #0080e6;
+}
+#context-menu.reverse-y li.folder > ul{
+  top: auto;
+  bottom:0;
+}
+#context-menu.reverse-x li.folder > ul{
+  left: auto;
+  right:99%;
+}
+/*----------------------------------------*/
+/* Vendors */
+/*----------------------------------------*/
+/*----------------------------------------*/
+/* Overides */
+/*----------------------------------------*/
+/*----------------------------------------*/
+/* Mixins & Placeholders */
+/*----------------------------------------*/
+.easy-gantt__menu .gantt-menu-problems-count.gantt-with-problems {
+  -webkit-border-radius: 5000px !important;
+  -moz-border-radius: 5000px !important;
+  border-radius: 5000px !important; }
+
+div#easy_gantt.easy .button.active, .easy .easy-gantt__menu-group--tooltiped li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul {
+  -webkit-border-radius: 0.12rem;
+  -moz-border-radius: 0.12rem;
+  border-radius: 0.12rem; }
+
+.easy-gantt__menu {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box; }
+
+.easy-gantt__menu {
+  display: -webkit-box;
+  display: -moz-box;
+  display: box;
+  display: -webkit-flex;
+  display: -moz-flex;
+  display: -ms-flexbox;
+  display: flex; }
+
+.easy-gantt__menu {
+  -webkit-justify-content: space-between;
+  -ms-justify-content: space-between;
+  justify-content: space-between; }
+
+.easy-gantt__menu {
+  -webkit-flex-wrap: wrap;
+  -ms-flex-wrap: wrap;
+  flex-wrap: wrap; }
+
+.easy .easy-gantt__menu-group--tooltiped li {
+  font-family: "Ubuntu", "Open Sans", sans-serif;
+  font-size: 15px;
+  line-height: 1.5;
+  font-weight: normal;
+  color: #324164; }
+@media only screen and (max-width: 1400px) {
+  .easy .easy-gantt__menu-group--tooltiped li {
+    font-size: 12px; } }
+@media only screen and (max-width: 1400px) and (min-resolution: 100dpi) {
+  .easy .easy-gantt__menu-group--tooltiped li {
+    font-size: 15px; } }
+
+.easy .easy-gantt__menu-group--tooltiped li a {
+  -ms-word-break: break-all;
+  word-break: break-word;
+  -webkit-hyphens: auto;
+  -moz-hyphens: auto;
+  -ms-hyphens: auto;
+  hyphens: auto; }
+
+@-webkit-keyframes icon-blink {
+  from {
+    opacity: 0.25;
+    -webkit-transform: scale(1);
+    -moz-transform: scale(1);
+    -ms-transform: scale(1);
+    -o-transform: scale(1);
+    transform: scale(1); }
+  to {
+    opacity: 1;
+    -webkit-transform: scale(1.25);
+    -moz-transform: scale(1.25);
+    -ms-transform: scale(1.25);
+    -o-transform: scale(1.25);
+    transform: scale(1.25); } }
+@-moz-keyframes icon-blink {
+  from {
+    opacity: 0.25;
+    -webkit-transform: scale(1);
+    -moz-transform: scale(1);
+    -ms-transform: scale(1);
+    -o-transform: scale(1);
+    transform: scale(1); }
+  to {
+    opacity: 1;
+    -webkit-transform: scale(1.25);
+    -moz-transform: scale(1.25);
+    -ms-transform: scale(1.25);
+    -o-transform: scale(1.25);
+    transform: scale(1.25); } }
+@-ms-keyframes icon-blink {
+  from {
+    opacity: 0.25;
+    -webkit-transform: scale(1);
+    -moz-transform: scale(1);
+    -ms-transform: scale(1);
+    -o-transform: scale(1);
+    transform: scale(1); }
+  to {
+    opacity: 1;
+    -webkit-transform: scale(1.25);
+    -moz-transform: scale(1.25);
+    -ms-transform: scale(1.25);
+    -o-transform: scale(1.25);
+    transform: scale(1.25); } }
+@keyframes icon-blink {
+  from {
+    opacity: 0.25;
+    -webkit-transform: scale(1);
+    -moz-transform: scale(1);
+    -ms-transform: scale(1);
+    -o-transform: scale(1);
+    transform: scale(1); }
+  to {
+    opacity: 1;
+    -webkit-transform: scale(1.25);
+    -moz-transform: scale(1.25);
+    -ms-transform: scale(1.25);
+    -o-transform: scale(1.25);
+    transform: scale(1.25); } }
+@-webkit-keyframes spin {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(360deg); } }
+@-moz-keyframes spin {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(360deg); } }
+@-ms-keyframes spin {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(360deg); } }
+@keyframes spin {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(360deg); } }
+@-webkit-keyframes spin-inverse {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(-360deg); } }
+@-moz-keyframes spin-inverse {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(-360deg); } }
+@-ms-keyframes spin-inverse {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(-360deg); } }
+@keyframes spin-inverse {
+  from {
+    transform: rotate(0deg); }
+  to {
+    transform: rotate(-360deg); } }
+@-webkit-keyframes translate-x {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-moz-keyframes translate-x {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-ms-keyframes translate-x {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@keyframes translate-x {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-webkit-keyframes translate-y {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-moz-keyframes translate-y {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-ms-keyframes translate-y {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@keyframes translate-y {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-webkit-keyframes translate-menu {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-moz-keyframes translate-menu {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-ms-keyframes translate-menu {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@keyframes translate-menu {
+  100% {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); } }
+@-webkit-keyframes load7 {
+  0%,
+  80%,
+  100% {
+    box-shadow: 0 2.5em 0 -1.3em; }
+  40% {
+    box-shadow: 0 2.5em 0 0; } }
+@-moz-keyframes load7 {
+  0%,
+  80%,
+  100% {
+    box-shadow: 0 2.5em 0 -1.3em; }
+  40% {
+    box-shadow: 0 2.5em 0 0; } }
+@-ms-keyframes load7 {
+  0%,
+  80%,
+  100% {
+    box-shadow: 0 2.5em 0 -1.3em; }
+  40% {
+    box-shadow: 0 2.5em 0 0; } }
+@keyframes load7 {
+  0%,
+  80%,
+  100% {
+    box-shadow: 0 2.5em 0 -1.3em; }
+  40% {
+    box-shadow: 0 2.5em 0 0; } }
+@-webkit-keyframes ripple {
+  0% {
+    -webkit-transform: scale(0);
+    -moz-transform: scale(0);
+    -ms-transform: scale(0);
+    -o-transform: scale(0);
+    transform: scale(0);
+    opacity: .2; }
+  100% {
+    -webkit-transform: scale(3);
+    -moz-transform: scale(3);
+    -ms-transform: scale(3);
+    -o-transform: scale(3);
+    transform: scale(3);
+    opacity: 0; } }
+@-moz-keyframes ripple {
+  0% {
+    -webkit-transform: scale(0);
+    -moz-transform: scale(0);
+    -ms-transform: scale(0);
+    -o-transform: scale(0);
+    transform: scale(0);
+    opacity: .2; }
+  100% {
+    -webkit-transform: scale(3);
+    -moz-transform: scale(3);
+    -ms-transform: scale(3);
+    -o-transform: scale(3);
+    transform: scale(3);
+    opacity: 0; } }
+@-ms-keyframes ripple {
+  0% {
+    -webkit-transform: scale(0);
+    -moz-transform: scale(0);
+    -ms-transform: scale(0);
+    -o-transform: scale(0);
+    transform: scale(0);
+    opacity: .2; }
+  100% {
+    -webkit-transform: scale(3);
+    -moz-transform: scale(3);
+    -ms-transform: scale(3);
+    -o-transform: scale(3);
+    transform: scale(3);
+    opacity: 0; } }
+@keyframes ripple {
+  0% {
+    -webkit-transform: scale(0);
+    -moz-transform: scale(0);
+    -ms-transform: scale(0);
+    -o-transform: scale(0);
+    transform: scale(0);
+    opacity: .2; }
+  100% {
+    -webkit-transform: scale(3);
+    -moz-transform: scale(3);
+    -ms-transform: scale(3);
+    -o-transform: scale(3);
+    transform: scale(3);
+    opacity: 0; } }
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after, .easy-gantt__icon:before {
+  speak: none;
+  font-weight: normal;
+  font-style: normal;
+  display: inline-block;
+  width: auto;
+  height: auto;
+  background-position: 0% 0%;
+  background-repeat: repeat;
+  background-image: none;
+  vertical-align: baseline;
+  text-decoration: none !important;
+  text-transform: none;
+  letter-spacing: normal;
+  word-wrap: normal;
+  white-space: nowrap;
+  direction: ltr;
+  /* Support for all WebKit browsers. */
+  -webkit-font-smoothing: antialiased;
+  /* Support for Safari and Chrome. */
+  text-rendering: optimizeLegibility;
+  /* Support for Firefox. */
+  -moz-osx-font-smoothing: grayscale;
+  /* Support for IE. */
+  font-feature-settings: 'liga'; }
+
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after, .easy-gantt__icon:before {
+  font-family: 'Material Icons','EasyIcons'; }
+
+.easy-gantt__icon {
+  position: relative;
+  background-repeat: no-repeat;
+  background-image: none !important; }
+
+.gantt_task_scale, .gantt_grid_scale {
+  -webkit-box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1); }
+
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul, .easy-gantt__menu .gantt-menu-problems-list {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1); }
+
+.easy .easy-gantt__menu-group--tooltiped ul {
+  -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1); }
+
+.contextual div#easy_gantt.easy .button.active, div#easy_gantt.easy .contextual .button.active {
+  padding: 0.2rem 0.4rem !important;
+  font-size: 0.89em; }
+.contextual div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy .contextual .icon.button.active {
+  padding-left: 2.4rem !important; }
+.contextual div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy .contextual .icon.button.active:before {
+  width: 2.4rem !important;
+  font-size: 1rem; }
+#sidebar .contextual div#easy_gantt.easy .button.active, .contextual div#easy_gantt.easy #sidebar .button.active, #sidebar div#easy_gantt.easy .contextual .button.active, div#easy_gantt.easy .contextual #sidebar .button.active, #easy_grid_sidebar .contextual div#easy_gantt.easy .button.active, .contextual div#easy_gantt.easy #easy_grid_sidebar .button.active, #easy_grid_sidebar div#easy_gantt.easy .contextual .button.active, div#easy_gantt.easy .contextual #easy_grid_sidebar .button.active {
+  display: inline-block !important; }
+
+#sidebar div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy #sidebar .icon.button.active, #easy_grid_sidebar div#easy_gantt.easy .icon.button.active, div#easy_gantt.easy #easy_grid_sidebar .icon.button.active {
+  padding-left: 3.2rem !important; }
+#sidebar div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy #sidebar .icon.button.active:before, #easy_grid_sidebar div#easy_gantt.easy .icon.button.active:before, div#easy_gantt.easy #easy_grid_sidebar .icon.button.active:before {
+  width: 3.2rem !important; }
+
+div#easy_gantt.easy .button.active {
+  font-weight: normal;
+  text-transform: uppercase;
+  display: inline-block;
+  border: 1px solid transparent;
+  padding: 0.4rem 0.8rem;
+  text-align: left;
+  cursor: pointer;
+  position: relative;
+  vertical-align: middle;
+  font-size: 0.89em; }
+div#easy_gantt.easy .button.active:focus {
+  outline: none; }
+div#easy_gantt.easy .icon.button.active {
+  padding-left: 2.4rem; }
+#sidebar div#easy_gantt.easy .button.active, div#easy_gantt.easy #sidebar .button.active, #easy_grid_sidebar div#easy_gantt.easy .button.active, div#easy_gantt.easy #easy_grid_sidebar .button.active {
+  display: block;
+  position: relative !important;
+  z-index: 2;
+  margin-bottom: 3px; }
+@media only screen and (min-width: 641px) {
+  .nosidebar #sidebar div#easy_gantt.easy .button.active:hover, div#easy_gantt.easy .nosidebar #sidebar .button.active:hover, .nosidebar #easy_grid_sidebar div#easy_gantt.easy .button.active:hover, div#easy_gantt.easy .nosidebar #easy_grid_sidebar .button.active:hover {
+    -webkit-animation: sidebar-buttons-slide-left 0.25s forwards;
+    -moz-animation: sidebar-buttons-slide-left 0.25s forwards;
+    -ms-animation: sidebar-buttons-slide-left 0.25s forwards;
+    -o-animation: sidebar-buttons-slide-left 0.25s forwards;
+    animation: sidebar-buttons-slide-left 0.25s forwards; } }
+.form-actions div#easy_gantt.easy .button.active, div#easy_gantt.easy .form-actions .button.active, .ui-dialog-buttonset div#easy_gantt.easy .button.active, div#easy_gantt.easy .ui-dialog-buttonset .button.active {
+  margin: 3px 0.4rem; }
+
+@-webkit-keyframes sidebar-buttons-slide-left {
+  from {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); }
+  to {
+    -webkit-transform: translate(-12.2666666667rem, 0);
+    -moz-transform: translate(-12.2666666667rem, 0);
+    -ms-transform: translate(-12.2666666667rem, 0);
+    -o-transform: translate(-12.2666666667rem, 0);
+    transform: translate(-12.2666666667rem, 0); } }
+@-moz-keyframes sidebar-buttons-slide-left {
+  from {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); }
+  to {
+    -webkit-transform: translate(-12.2666666667rem, 0);
+    -moz-transform: translate(-12.2666666667rem, 0);
+    -ms-transform: translate(-12.2666666667rem, 0);
+    -o-transform: translate(-12.2666666667rem, 0);
+    transform: translate(-12.2666666667rem, 0); } }
+@-ms-keyframes sidebar-buttons-slide-left {
+  from {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); }
+  to {
+    -webkit-transform: translate(-12.2666666667rem, 0);
+    -moz-transform: translate(-12.2666666667rem, 0);
+    -ms-transform: translate(-12.2666666667rem, 0);
+    -o-transform: translate(-12.2666666667rem, 0);
+    transform: translate(-12.2666666667rem, 0); } }
+@keyframes sidebar-buttons-slide-left {
+  from {
+    -webkit-transform: translate(0, 0);
+    -moz-transform: translate(0, 0);
+    -ms-transform: translate(0, 0);
+    -o-transform: translate(0, 0);
+    transform: translate(0, 0); }
+  to {
+    -webkit-transform: translate(-12.2666666667rem, 0);
+    -moz-transform: translate(-12.2666666667rem, 0);
+    -ms-transform: translate(-12.2666666667rem, 0);
+    -o-transform: translate(-12.2666666667rem, 0);
+    transform: translate(-12.2666666667rem, 0); } }
+div#easy_gantt.easy .button.active {
+  line-height: 1.4666666667rem;
+  min-height: 1.4666666667rem;
+  background: #0080e6;
+  border-color: #0080e6;
+  color: #ffffff; }
+div#easy_gantt.easy .button.active:hover {
+  color: #ffffff;
+  text-decoration: none;
+  background: #0072cd;
+  border-color: #0072cd; }
+div#easy_gantt.easy .selected.button.active {
+  background: #0072cd;
+  border-color: #0072cd;
+  color: rgba(255, 255, 255, 0.75); }
+.ui-widget-content div#easy_gantt.easy .button.active, div#easy_gantt.easy .ui-widget-content .button.active {
+  color: #ffffff; }
+
+div#easy_gantt.easy .icon.button.active:before {
+  position: absolute;
+  left: 0;
+  width: 2.4rem;
+  text-align: center;
+  font-size: 1.25em;
+  line-height: 1;
+  color: inherit;
+  top: 50%;
+  margin-top: -0.5em; }
+
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after {
+  position: absolute;
+  right: 1px;
+  top: 1px;
+  bottom: 1px;
+  font-size: 15px;
+  line-height: 2.25;
+  margin: 0;
+  padding: 0;
+  display: block;
+  background: none;
+  border: none;
+  min-width: 1.6rem;
+  text-align: center; }
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0; }
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after span {
+  display: none; }
+
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children, .easy .easy-gantt__menu-group--tooltiped ul {
+  position: absolute;
+  background-color: #ffffff;
+  color: #324164;
+  padding: 0.5em;
+  font-size: 0.89em;
+  line-height: 1;
+  margin-top: -0.25em;
+  white-space: pre; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children a, .easy .easy-gantt__menu-group--tooltiped ul a {
+  color: #324164; }
+.input-append .easy .easy-gantt__menu-group--tooltiped li > .menu-children, .easy .easy-gantt__menu-group--tooltiped .input-append li > .menu-children, .input-append .easy .easy-gantt__menu-group--tooltiped ul, .easy .easy-gantt__menu-group--tooltiped .input-append ul {
+  font-weight: normal;
+  margin-top: 1px; }
+
+.easy .easy-gantt__menu-group--tooltiped li {
+  padding: 0;
+  padding-left: 2.4rem;
+  border: 1px solid transparent;
+  border-left: none;
+  border-right: none;
+  position: relative;
+  line-height: 1.25; }
+.easy .easy-gantt__menu-group--tooltiped li:hover {
+  border-color: aliceblue !important;
+  background: aliceblue !important;
+  z-index: 1; }
+.easy .easy-gantt__menu-group--tooltiped li a {
+  padding: 0.4rem 0.8rem;
+  padding-left: 3.2rem;
+  margin-left: -2.4rem;
+  display: block;
+  text-decoration: none; }
+.easy .easy-gantt__menu-group--tooltiped li a.active {
+  color: #fe7d99;
+  background: none;
+  border: none; }
+.easy .easy-gantt__menu-group--tooltiped li a.active:before {
+  color: #fe7d99; }
+.easy .easy-gantt__menu-group--tooltiped li a:before {
+  position: absolute;
+  left: 0;
+  width: 2.4rem;
+  text-align: center;
+  padding-top: 1px;
+  color: #324164; }
+.easy .easy-gantt__menu-group--tooltiped li a.submenu {
+  padding-right: 0 !important;
+  background: none !important;
+  position: relative; }
+.easy .easy-gantt__menu-group--tooltiped li a.submenu:after {
+  content: "\005d" !important;
+  left: auto;
+  font-size: 0.6666666667rem;
+  text-align: left;
+  line-height: 1.9; }
+.easy .easy-gantt__menu-group--tooltiped li a ~ span {
+  font-size: 0.89em; }
+
+.easy .easy-gantt__menu-group--tooltiped {
+  position: relative; }
+.easy .easy-gantt__menu-group--tooltiped ul {
+  z-index: 1500;
+  margin: 0;
+  list-style: none;
+  font-size: 1.125em;
+  min-width: 200px;
+  padding: 0.4rem 0;
+  white-space: normal !important; }
+.easy .easy-gantt__menu-group--tooltiped ul:after {
+  content: '';
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  right: auto;
+  left: 2.4rem;
+  border-left: 1px solid #e5e5e5;
+  z-index: 0; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children {
+  display: none;
+  white-space: normal;
+  top: 6px;
+  left: 99%;
+  width: 150px; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children > li + li {
+  border-top: 1px dashed #e5e5e5; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children > li a {
+  text-decoration: none;
+  display: block;
+  padding: 0.5em; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children > li a:hover {
+  text-decoration: underline; }
+.easy .easy-gantt__menu-group--tooltiped li > .menu-children > li a:before {
+  color: #01c8a9;
+  text-decoration: none;
+  margin-right: 0.4rem; }
+.easy .easy-gantt__menu-group--tooltiped li:hover > .menu-children {
+  display: inline-block; }
+
+.easy-gantt__icon:before {
+  font-family: "Material Icons", sans-serif;
+  font-size: 17px;
+  color: #0080e6; }
+.easy-gantt__icon--open:before {
+  content: "\e147"; }
+.easy-gantt__icon--close:before {
+  content: "\e15c"; }
+.easy-gantt__icon--filter_1:before {
+  content: "\e3d0"; }
+.easy-gantt__icon--filter_2:before {
+  content: "\e3d1"; }
+.easy-gantt__icon--filter_3:before {
+  content: "\e3d2"; }
+.easy-gantt__icon--filter_4:before {
+  content: "\e3d4"; }
+.easy-gantt__icon--filter_5:before {
+  content: "\e3d5"; }
+.easy-gantt__icon--filter_6:before {
+  content: "\e3d6"; }
+.easy-gantt__icon--filter_7:before {
+  content: "\e3d7"; }
+.easy-gantt__icon--filter_8:before {
+  content: "\e3d8"; }
+.easy-gantt__icon--filter_9:before {
+  content: "\e3d9"; }
+.easy-gantt__icon--filter_more:before {
+  content: "\e3da"; }
+.easy-gantt__icon--filter_none:before {
+  content: "\e3e0"; }
+
+.gantt_tree_icon + .gantt_tree_icon, .gantt_tree_indent + .gantt_tree_icon {
+  margin: 0 0.4rem; }
+.gantt_tree_icon.gantt_folder_open {
+  background-image: none; }
+.gantt_tree_icon.gantt_folder_open:before {
+  content: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=); }
+.gantt_tree_icon.gantt_folder_closed {
+  background-image: none; }
+.gantt_tree_icon.gantt_folder_closed:before {
+  content: url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=); }
+.gantt_tree_icon .avatar-container {
+  float: none;
+  margin-right: 0;
+  margin-left: 0; }
+.gantt_tree_icon.gantt_folder_open:before, .gantt_tree_icon.gantt_folder_close:before {
+  vertical-align: sub; }
+
+.gantt-print__body {
+  padding: 0 !important; }
+.gantt-print__body:before {
+  display: none; }
+.gantt-print__header-logo {
+  vertical-align: middle;
+  display: inline-block; }
+.gantt-print__header-header {
+  display: inline-block;
+  margin: 0; }
+.gantt-print__header-text {
+  display: inline-block;
+  vertical-align: bottom;
+  margin-left: 20px; }
+.gantt-print__area {
+  background-color: white; }
+.gantt-print__strip {
+  border: 1px solid #cecece;
+  overflow: hidden;
+  white-space: nowrap;
+  /*page-break-before:always;*/
+  /*page-break-inside: avoid;*/
+  break-inside: avoid;
+  margin-bottom: 20px;
+  margin-left: -1px;
+  display: inline-block;
+  background-color: #ffffff;
+  border-left: 0; }
+.gantt-print__bg-canvas {
+  display: block;
+  /*page-break-before:avoid;*/ }
+.gantt-print__bg {
+  position: absolute;
+  /*page-break-before:avoid;*/ }
+.gantt-print__data-area {
+  /*page-break-before:avoid;*/ }
+.gantt-print__bars-area {
+  position: relative;
+  top: 0; }
+.gantt-print__scale {
+  transform: none;
+  border-top: 0; }
+.gantt-print__grid {
+  border: 1px solid #cecece;
+  overflow: hidden;
+  white-space: nowrap;
+  /*page-break-before:always;*/
+  /*page-break-inside: avoid;*/
+  break-inside: avoid;
+  margin-bottom: 20px;
+  margin-left: -1px;
+  display: inline-block;
+  background-color: #ffffff;
+  border-right: 2px solid #cecece;
+  margin-right: 1px; }
+.gantt-print__grid .gantt_grid_scale {
+  margin-top: -2px; }
+.gantt-print__grid .gantt-grid-header-collapse-buttons,
+.gantt-print__grid .gantt_sort {
+  display: none; }
+.gantt-print__grid .gantt-superitem-after {
+  display: none; }
+.gantt-print__template--nowrap {
+  white-space: nowrap; }
+
+div#easy_gantt.easy #easy_gantt_menu {
+  padding: 0.8rem 1.6rem; }
+div#easy_gantt.easy #easy_gantt_menu p {
+  margin-bottom: 0; }
+div#easy_gantt.easy #gantt_footer_buttons {
+  float: left;
+  margin-top: 0.8rem; }
+div#easy_gantt.easy #gantt_footer_buttons + p {
+  float: right;
+  margin-top: 0.8rem; }
+
+#button_jump_today {
+  padding-right: 0; }
+
+.easy-gantt__menu {
+  user-select: none;
+  position: relative;
+  z-index: 1;
+  background-color: #ffffff;
+  padding: 1.6rem;
+  margin: 0 -1.6rem; }
+.easy-gantt__menu:after {
+  display: none; }
+.easy-gantt__menu-item {
+  display: inline-block;
+  text-align: left; }
+.easy-gantt__menu-item .active {
+  color: #fe7d99 !important; }
+.easy-gantt__menu-item .active:before {
+  color: #fe7d99; }
+.easy-gantt__menu-group {
+  display: inline-block; }
+.easy-gantt__menu-group ul {
+  margin: 0; }
+.easy .easy-gantt__menu-group--tooltiped > ul {
+  display: none;
+  right: 0; }
+.easy .easy-gantt__menu-group--tooltiped:hover > ul {
+  display: block; }
+.easy .easy-gantt__menu .problem-finder {
+  font-size: 0.89em;
+  color: #324164; }
+.easy .easy-gantt__menu .problem-finder.active {
+  background: none;
+  border: none;
+  color: #324164; }
+.easy .easy-gantt__menu .problem-finder:hover {
+  text-decoration: none; }
+.easy-gantt__menu .gantt-menu-problems-list {
+  border-width: 1px !important; }
+.easy-gantt__menu .gantt-menu-problems-count.gantt-with-problems {
+  font-weight: bold;
+  background: #fe7d99;
+  border: 1px solid #fe6485;
+  text-align: center;
+  width: 1.2rem;
+  line-height: 1.2rem;
+  display: inline-block;
+  color: white;
+  padding: 0.2rem;
+  margin-right: 0.8rem; }
+
+.gantt-menu-sub-panel {
+  width: 100%; }
+.gantt-menu-sub-panel > span {
+  display: none !important; }
+
+#button_problem_finder.active {
+  color: #fe7d99 !important;
+  background: none !important;
+  border: none !important; }
+
+.gantt-menu-problems-reason {
+  color: #fe7d99 !important; }
+
+.gantt-grid-milestone-bullet {
+  transform: scale(0.65) rotate(45deg);
+  width: 12px;
+  height: 12px;
+  background-color: #324164;
+  border: 2px solid #324164;
+  margin-top: 4px;
+  margin-left: 5px; }
+.gantt-milestone-shared .gantt-grid-milestone-bullet {
+  background: rgba(50, 65, 100, 0.5);
+  border-color: rgba(50, 65, 100, 0.5);
+  background-clip: padding-box; }
+
+.gantt_subtask_arrow:before {
+  content: '\21B3';
+  opacity: .75; }
+
+.gantt_cell + .gantt_cell, .gantt_side_content {
+  color: #324164 !important; }
+
+.gantt_grid .gantt_grid_head_cell, .gantt_grid_scale, #gantt_grid, .gantt_scale_cel, .gantt_task_scale {
+  border-color: #e5e5e5 !important; }
+
+.gantt_grid {
+  border-right-width: 1px !important; }
+
+.gantt_container {
+  border-color: #e5e5e5; }
+
+.gantt_row {
+  padding: 0 0.4rem; }
+
+.gantt_grid_data .gantt_row.closed {
+  background-color: rgba(153, 160, 178, 0.5); }
+.gantt_grid_data .gantt_row.closed .gantt_tree_content {
+  text-decoration: line-through; }
+.gantt_grid_data .gantt_row:hover, .gantt_grid_data .gantt_row.odd:hover {
+  background: rgba(254, 125, 153, 0.25); }
+.gantt_task_link .gantt_line_wrapper div {
+  border: 1px solid #f9820a; }
+.gantt_task_link .gantt_link_arrow {
+  border-color: #f9820a; }
+.gantt_task_link:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #f9820a; }
+.gantt-relation-simple .gantt_line_wrapper div {
+  border: 1px solid #0064b3; }
+.gantt-relation-simple .gantt_link_arrow {
+  border-color: #0064b3; }
+.gantt-relation-simple:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #0064b3; }
+.gantt-relation-unlocked .gantt_line_wrapper div {
+  border: 1px solid #01957e; }
+.gantt-relation-unlocked .gantt_link_arrow {
+  border-color: #01957e; }
+.gantt-relation-unlocked:hover .gantt_line_wrapper div {
+  box-shadow: 0 0 5px 0 #01957e; }
+
+.gantt_task_scale, .gantt_grid_scale {
+  z-index: 2 !important;
+  margin-top: -1px; }
+
+#gantt_cont {
+  width: auto !important;
+  margin: 0 -1.2rem;
+  background: #ffffff; }
+
+.easy .gantt_tree_indent {
+  width: 1.6rem; }
+.easy .gantt_tree_icon {
+  width: 1.6rem;
+  text-align: center; }
+
+.gantt_task_ticks {
+  height: 0;
+  margin-top: -4px;
+  margin-left: -1px;
+  margin-right: -1px;
+  border-width: 5px 10px;
+  border-style: solid;
+  border-color: inherit;
+  border-top-color: transparent !important;
+  border-bottom-color: transparent !important;
+  background-color: transparent !important; }
+
+.gantt_task_line {
+  border-radius: 2px;
+  box-sizing: border-box;
+  border: 1px solid;
+  background-color: rgba(1, 200, 169, 0.1);
+  border-color: #01c8a9; }
+.gantt_task_line .gantt_task_progress {
+  z-index: 0 !important;
+  background-color: rgba(1, 200, 169, 0.5); }
+.gantt_task_line.wrong {
+  background-color: rgba(254, 125, 153, 0.1);
+  border-color: #fe7d99; }
+.gantt_task_line.wrong .gantt_task_progress {
+  background-color: rgba(254, 125, 153, 0.5); }
+.gantt_task_line.closed {
+  background-color: rgba(153, 160, 178, 0.25);
+  border-color: #99a0b2; }
+.gantt_task_line.closed .gantt_task_progress {
+  background-color: rgba(153, 160, 178, 0.5); }
+.gantt_task_line.gantt_project-type {
+  background-color: rgba(0, 128, 230, 0.25);
+  border-color: #0080e6; }
+.gantt_task_line.gantt_project-type .gantt_task_progress {
+  background-color: rgba(0, 128, 230, 0.5); }
+.gantt_task_line.gantt_project-type.wrong {
+  background-color: rgba(191, 126, 172, 0.25); }
+.gantt_task_line.gantt_project-type.wrong .gantt_task_progress {
+  background-color: rgba(191, 126, 172, 0.5); }
+.gantt_task_line.gantt_milestone-type .gantt_task_content {
+  -webkit-transform: scale(0.45) rotate(45deg);
+  -moz-transform: scale(0.45) rotate(45deg);
+  -ms-transform: scale(0.45) rotate(45deg);
+  -o-transform: scale(0.45) rotate(45deg);
+  transform: scale(0.45) rotate(45deg);
+  background-color: #324164;
+  border-color: #324164; }
+.gantt_task_line.gantt_milestone-type.gantt-milestone-shared .gantt_task_content {
+  -webkit-transform: scale(0.45) rotate(45deg);
+  -moz-transform: scale(0.45) rotate(45deg);
+  -ms-transform: scale(0.45) rotate(45deg);
+  -o-transform: scale(0.45) rotate(45deg);
+  transform: scale(0.45) rotate(45deg);
+  background-color: rgba(50, 65, 100, 0.5);
+  border-color: rgba(50, 65, 100, 0.5); }
+.gantt_task_line.critical {
+  background-color: rgba(200, 0, 255, 0.5);
+  border-color: #c800ff; }
+.gantt_task_line.critical .gantt_task_progress {
+  background-color: #c800ff; }
+
+.gantt_hor_scroll {
+  z-index: 3; }
+
+.redmine .button-important {
+  color: #fff; }
+.redmine .gantt-menu-button {
+  display: inline-block;
+  border-radius: 2px;
+  border: 1px solid #e4e4e4;
+  padding: 8px 16px; }
+.redmine .gantt-menu-button.button-positive {
+  background-color: #4ebf67;
+  border-color: #338d47;
+  color: #ffffff; }
+.redmine .gantt-menu-button.active, .redmine #easy_gantt.redmine .gantt_button.active {
+  background-color: #628DB6;
+  border-color: #3E5B76; }
+.redmine .problem-finder {
+  border: none;
+  padding-right: 0; }
+.redmine .gantt-menu-button.icon, .redmine .gantt_button.icon {
+  padding: 8px 10px 8px 25px;
+  background-position-x: 5px; }
+.redmine .icon-calendar-week {
+  background-image: url("../../../../../images/magnifier.png"); }
+.redmine .icon-calendar-day {
+  background-image: url("../../../../../images/zoom_in.png"); }
+.redmine .icon-calendar-month {
+  background-image: url("../../../../../images/zoom_out.png"); }
+.redmine .icon-calendar {
+  background-image: url("../../../../../images/calendar.png"); }
+.redmine .icon-print {
+  background-image: url("../../../../../images/document.png"); }
+.redmine .icon-plugin {
+  background-image: url("../../../../../images/plugin.png"); }
+.redmine .icon-back {
+  background-image: url("../../../../../images/cancel.png"); }
+.redmine .icon-unlink {
+  background-image: url("../../../../../images/link_break.png"); }
+.redmine .icon-link {
+  background-image: url("../../../../../images/link.png"); }
+.redmine .icon-settings {
+  background-image: url("../../../../../images/changeset.png"); }
+.redmine .icon-filter {
+  background-image: url("../../../../../images/task_parent_end.png"); }
+.redmine .icon-youtube {
+  background-image: url("../../../images/yt.png"); }
+.redmine .icon-youtube {
+  padding-left: 28px; }
+.redmine .icon.icon-filter {
+  background-position-x: 9px;
+  background-position-y: 7px; }
+.redmine .easy-gantt__menu {
+  margin: 0 0 1px;
+  padding: 5px 0 5px 0;
+  background-color: white; }
+.redmine #gantt_cont {
+  /* width: auto !important; */
+  margin: 0; }
+.redmine .gantt_tree_icon {
+  width: 20px; }
+.redmine .gantt_tree_icon:before {
+  vertical-align: sub; }
+.redmine a.active {
+  color: #fff; }
+.redmine a.disabled {
+  opacity: 0.25;
+  color: #484848;
+  font-weight: lighter; }
+.redmine #link_delay_input {
+  width: auto; }
+.redmine .push-left {
+  float: left; }
+.redmine .push-right {
+  float: right;
+  text-align: right; }
+.redmine .gantt-footer p {
+  text-align: center; }
+.redmine #button_close_all_projects {
+  width: 18px;
+  height: 25px;
+  padding: 0;
+  vertical-align: bottom;
+  background-position: 0 50%;
+  background-repeat: no-repeat; }
+.redmine .overdue {
+  color: #e50026; }
+.redmine .contextual {
+  text-align: right; }
+.redmine .easy-gantt__menu-group--tooltiped {
+  position: relative; }
+.redmine .easy-gantt__menu-group--tooltiped ul {
+  position: absolute;
+  right: 0;
+  background-color: white;
+  border: 1px solid #cccccc;
+  padding: 5px;
+  padding-left: 0;
+  min-width: 180px;
+  display: none; }
+.redmine .easy-gantt__menu-group--tooltiped ul:after {
+  content: '';
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  right: auto;
+  left: 25px;
+  border-left: 1px solid #eee3d4; }
+.redmine .easy-gantt__menu-group--tooltiped ul li {
+  display: block; }
+.redmine .easy-gantt__menu-group--tooltiped ul .gantt-menu-button {
+  border: none;
+  padding: 5px 5px 5px 30px;
+  background-color: transparent; }
+.redmine .easy-gantt__menu-group--tooltiped:hover ul {
+  display: block; }
+@media print {
+  .redmine .gantt_task_progress {
+    border-right: inherit; }
+  .redmine .odd {
+    background: none; } }
+
+@font-face {
+  font-family: "Material Icons";
+  src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/EasyMaterialIcons-Regular.eot);
+  src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/EasyMaterialIcons-Regular.eot?#iefix) format("embedded-opentype"), url(../../../fonts/EasyMaterialIcons-Regular.woff) format("woff"), url(../../../fonts/EasyMaterialIcons-Regular.woff2) format("woff2"), url(../../../fonts/EasyMaterialIcons-Regular.ttf) format("truetype"), url(../../../fonts/EasyMaterialIcons-Regular.svg) format("svg");
+  font-weight: normal;
+  font-style: normal; }
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/jasmine.css b/plugins/easy_gantt/assets/stylesheets/easy_gantt/jasmine.css
new file mode 100644
index 0000000000000000000000000000000000000000..631998275d5c93b1affab90180e2d2514896a33f
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/jasmine.css
@@ -0,0 +1,58 @@
+body { overflow-y: scroll; }
+
+.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; }
+.jasmine_html-reporter a { text-decoration: none; }
+.jasmine_html-reporter a:hover { text-decoration: underline; }
+.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
+.jasmine_html-reporter .jasmine-banner, .jasmine_html-reporter .jasmine-symbol-summary, .jasmine_html-reporter .jasmine-summary, .jasmine_html-reporter .jasmine-result-message, .jasmine_html-reporter .jasmine-spec .jasmine-description, .jasmine_html-reporter .jasmine-spec-detail .jasmine-description, .jasmine_html-reporter .jasmine-alert .jasmine-bar, .jasmine_html-reporter .jasmine-stack-trace { padding-left: 9px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-banner { position: relative; }
+.jasmine_html-reporter .jasmine-banner .jasmine-title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
+.jasmine_html-reporter .jasmine-banner .jasmine-version { margin-left: 14px; position: relative; top: 6px; }
+.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
+.jasmine_html-reporter .jasmine-version { color: #aaa; }
+.jasmine_html-reporter .jasmine-banner { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+.jasmine_html-reporter .jasmine-symbol-summary li { display: inline-block; height: 10px; width: 14px; font-size: 16px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before { color: #ba9d37; content: "\02022"; }
+.jasmine_html-reporter .jasmine-run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-trigger { cursor: pointer; padding: 8px 16px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; }
+.jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+.jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; }
+.jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; }
+.jasmine_html-reporter .jasmine-bar a { color: white; }
+.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list, .jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures { display: none; }
+.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list, .jasmine_html-reporter.jasmine-failure-list .jasmine-summary { display: none; }
+.jasmine_html-reporter .jasmine-results { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
+.jasmine_html-reporter .jasmine-summary ul.jasmine-suite { margin-top: 7px; margin-bottom: 7px; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-passed a { color: #007069; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; }
+.jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; }
+.jasmine_html-reporter .jasmine-suite { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-suite a { color: #333; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; }
+.jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; }
+.jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; }
+.jasmine_html-reporter .jasmine-stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss
new file mode 100644
index 0000000000000000000000000000000000000000..79dc1511215b7ee8219e8dad10fa947ba4ca326a
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_icons.scss
@@ -0,0 +1,47 @@
+//@include loadFontFace('EasyIcons','easy_icons-webfont','fontLocal','fontLocalAlt',normal,normal);
+$gantt-icons: (
+        open: \e147,
+        close: \e15c,
+);
+
+.easy-gantt {
+  &__icon {
+    @include icon-parent;
+    &:before {
+      font-family: "Material Icons", sans-serif;
+    }
+    @each $icon, $sign in $gantt-icons {
+      &--#{$icon}:before {
+        content: unicode($sign);
+      }
+    }
+  }
+}
+
+$tree_icons: (
+        folder_open:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=),
+        folder_close:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=)
+);
+.gantt_tree_icon{
+  margin-right: 7px;
+  //margin-right: 10px;
+  @each $type, $url in $tree_icons{
+    &.gantt_#{$type}{
+      background-image: none;
+      &:before{
+        content: $url;
+      }
+    }
+  }
+  &.gantt_folder{
+    &_open,&_close{
+      &:before{
+        vertical-align: sub;
+      }
+    }
+  }
+  &.gantt_open,&.gantt_close{
+    margin-right: 0;
+    margin-left: 7px;
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ec43632acd7b01385f2c6e194d2f44752f9807ba
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_main.scss
@@ -0,0 +1,281 @@
+div#easy_gantt.easy{
+  a{
+
+  }
+  #easy_gantt_menu{
+    //overflow: hidden;
+    padding-bottom: $gap - 3;
+    p{
+      margin-bottom: 0;
+    }
+  }
+  #gantt_footer_buttons{
+    float: left;
+    margin-top: $gap;
+    & + p{
+      float: right;
+      margin-top: $gap;
+    }
+  }
+  .button.active{
+    @extend %button-main !optional;
+  }
+}
+
+#button_jump_today{
+  padding-right: 0;
+}
+
+.easy-gantt{
+  &__menu{
+    @extend %box-sizing-border-box !optional;
+    //@extend %material__elevation--inline !optional;
+    @extend %flex !optional;
+    @extend %flex-wrap-wrap !optional;
+    @extend %justify-content-space-between !optional;
+    user-select: none;
+    position: relative;
+    z-index: 1;
+    background-color: $gantt-background-menu;
+    padding: $box-padding;
+    margin: 0 (-$box-padding);
+    &:after{
+      display: none;
+    }
+    &-item{
+      display: inline-block;
+      text-align: left;
+      .active{
+        color: $color-negative !important;
+        &:before{
+          color: $color-negative;
+        }
+      }
+    }
+    &-group{
+      display: inline-block;
+      ul{
+        margin: 0;
+      }
+      .easy &--tooltiped{
+        @extend %menu-tooltip !optional;
+        & > ul{
+          display: none;
+          right: 0;
+        }
+        &:hover{
+          & > ul{
+            display: block;
+          }
+        }
+      }
+    }
+    .easy & .problem-finder{
+      @include small;
+      color: $color-text;
+      &.active{
+        background: none;
+        border: none;
+        color: $color-text;
+      }
+      &:hover{
+        text-decoration: none;
+      }
+    }
+    .gantt-menu-problems-list{
+      @extend %material__elevation--depth_2 !optional;
+      border-width: 1px !important;
+    }
+    .gantt-menu-problems-count.gantt-with-problems{
+      @extend %border-radius-infinite !optional;
+      font-weight: bold;
+      background: $color-negative;
+      border: 1px solid darken($color-negative, 5%);
+      text-align: center;
+      width: 1.5*$gap;
+      line-height: 1.5*$gap;
+      display: inline-block;
+      color: white;
+      padding: .25*$gap;
+      margin-right: $gap;
+
+    }
+  }
+}
+
+.gantt-menu-sub-panel{
+  width: 100%;
+  & > span{
+    display: none !important;
+  }
+}
+
+#button_problem_finder.active{
+  color: $color-negative !important;
+  background: none !important;
+  border: none !important;
+}
+
+.gantt-menu-problems-reason{
+  color: $color-negative !important;
+}
+
+.gantt-grid-milestone-bullet{
+  margin-top: 4px !important;
+  margin-left: 5px;
+  background: $color-text !important;
+  border: none !important;
+  .gantt-milestone-shared &{
+    background: $color-important !important;
+  }
+}
+
+.gantt_subtask_arrow:before{
+  content: '\21B3';
+  opacity: .75;
+}
+
+.gantt_cell + .gantt_cell, .gantt_side_content{
+  color: $color-text !important;
+}
+
+.gantt_grid .gantt_grid_head_cell, .gantt_grid_scale, #gantt_grid, .gantt_scale_cel, .gantt_task_scale{
+  border-color: $color-border-minor !important;
+}
+
+.gantt_grid{
+  //@extend %material__elevation--depth_2 !optional;
+  border-right-width: 1px !important;
+}
+
+.gantt{
+  &_grid_data &_row{
+    &, &.odd{
+      &:hover{
+        background: rgba($color-negative, .25);
+      }
+    }
+    &.closed{
+      background-color: rgba($color-service-text, .5);
+      border-color: rgba($color-service-text, 1);
+      .gantt_tree_content{
+        text-decoration: line-through;
+      }
+    }
+  }
+}
+
+.gantt_task_scale, .gantt_grid_scale{
+  @extend %material__elevation--depth_1 !optional;
+  z-index: 2 !important;
+  margin-top: -1px;
+}
+
+#gantt_cont{
+  width: auto !important;
+  margin: 0 (-$box-padding - 1);
+  background: $color-foreground;
+}
+
+.easy{
+  & .gantt_tree{
+    &_indent{
+      width: $box-padding;
+    }
+    &_icon{
+      width: $box-padding;
+      text-align: center;
+    }
+  }
+}
+
+div.gantt_task_progress{
+  position: relative;
+  z-index: 1 !important;
+}
+
+div.gantt_task_line{
+  &.gantt_parent_task-subtype{
+    .gantt_task_progress, .gantt_task_content{
+      height: 100%;
+    }
+  }
+  .gantt_task_content{
+    border-radius: 2px;
+    border: 1px solid;
+    box-sizing: border-box;
+  }
+  .gantt_task_content,
+  .gantt_task_ticks{
+    background-color: rgba($color-positive, .1);
+    border-color: rgba($color-positive, 1);
+  }
+  .gantt_task_progress{
+    background-color: rgba($color-positive, .5);
+  }
+  &.wrong{
+    .gantt_task_ticks,
+    .gantt_task_content{
+      background-color: rgba($color-negative, .1);
+      border-color: rgba($color-negative, 1);
+    }
+    .gantt_task_progress{
+      background-color: rgba($color-negative, .5);
+    }
+  }
+  &.closed{
+    .gantt_task_ticks,
+    .gantt_task_content{
+      background-color: rgba($color-service-text, .25);
+      border-color: rgba($color-service-text, 1);
+    }
+    .gantt_task_progress{
+      background-color: rgba($color-service-text, .5);
+    }
+  }
+  &.gantt_project-type{
+    .gantt_task_content{
+      background-color: rgba($color-main, .25);
+      border-color: rgba($color-main, 1);
+    }
+    div.gantt_task_progress{
+      background-color: rgba($color-main, .5);
+    }
+    &.wrong{
+      .gantt_task_content{
+        background-color: rgba(mix($color-negative, $color-main, 75%), .25);
+      }
+      div.gantt_task_progress{
+        background-color: rgba(mix($color-negative, $color-main, 75%), .5);
+      }
+    }
+  }
+  &.gantt_milestone-type{
+    .gantt_task_content{
+      @include transform(#{scale(.65) rotate(45deg)});
+      background-color: rgba($color-text, 1);
+      border-color: rgba($color-text, 1);
+    }
+    &.gantt-milestone-shared{
+      .gantt_task_content{
+        @include transform(#{scale(.65) rotate(45deg)});
+        background-color: rgba($color-important, 1);
+        border-color: rgba($color-important, 1);
+      }
+    }
+  }
+  &.critical{
+    .gantt_task_ticks,
+    .gantt_task_content{
+      background-color: rgba($gantt-color-critical, .5);
+      border-color: rgba($gantt-color-critical, 1);
+    }
+    .gantt_task_progress{
+      background-color: rgba($gantt-color-critical, 1);
+    }
+  }
+}
+
+.gantt_hor_scroll{
+  z-index: 1;
+}
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss
new file mode 100644
index 0000000000000000000000000000000000000000..04ce749f018b89bd39acdcb9e247d9a91d269d6a
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_print.scss
@@ -0,0 +1,81 @@
+@mixin print-strip{
+  border: 1px solid #cecece;
+  overflow: hidden;
+  white-space: nowrap;
+  /*page-break-before:always;*/
+  /*page-break-inside: avoid;*/
+  break-inside: avoid;
+  margin: 10px 0;
+  margin-left: -1px;
+  display: inline-block;
+  background-color: #ffffff;
+}
+
+.gantt-print{
+  &__header{
+    &-logo{
+      vertical-align: middle;
+      display: inline-block;
+    }
+    &-header{
+      display: inline-block;
+      margin: 0;
+    }
+    &-text{
+      display: inline-block;
+      vertical-align: bottom;
+      margin-left: 20px;
+    }
+  }
+
+  &__area{
+    background-color: white;
+  }
+
+  &__strip{
+    @include print-strip;
+    border-left: 0;
+  }
+
+  &__bg-canvas{
+    display: block;
+    /*page-break-before:avoid;*/
+  }
+
+  &__bg{
+    position: absolute;
+    /*page-break-before:avoid;*/
+  }
+
+  &__data-area{
+    /*page-break-before:avoid;*/
+  }
+
+  &__bars-area{
+    position: relative;
+    top: 0;
+  }
+
+  &__scale{
+    transform: none;
+    border-top: 0;
+  }
+
+  &__grid{
+    @include print-strip;
+    border-right: 2px solid #cecece;
+    margin-right: 1px;
+    .gantt_grid_scale{
+      margin-top: -2px;
+    }
+    .gantt-grid-header-collapse-buttons,
+    .gantt_sort{
+      display: none;
+    }
+  }
+
+  &__template--nowrap{
+    white-space: nowrap;
+  }
+
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c7209e062ca47cd53aa173ad4b6b524b0514730b
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_redmine.scss
@@ -0,0 +1,164 @@
+$redmine_icons: (
+        calendar-week:magnifier,
+        calendar-day:zoom_in,
+        calendar-month:zoom_out,
+        calendar:calendar,
+        print:document,
+        plugin:plugin,
+        back:cancel,
+        unlink:link-break,
+        link:link,
+        youtube:yt,
+        settings:changeset,
+        filter:task_parent_end
+);
+.redmine{
+  .button-important{
+    color: #fff;
+  }
+
+  .gantt-menu-button{
+    display: inline-block;
+    border-radius: 2px;
+    border: 1px solid #e4e4e4;
+    padding: 8px 16px;
+  }
+
+  .gantt-menu-button.button-positive{
+    background-color: #4ebf67;
+    border-color: rgb(51, 141, 71);
+    color: #ffffff;
+  }
+
+  .gantt-menu-button.active, #easy_gantt.redmine .gantt_button.active{
+    background-color: #628DB6;
+    border-color: #3E5B76;
+  }
+  .problem-finder{
+    border: none;
+    padding-right: 0;
+  }
+
+  .gantt-menu-button.icon, .gantt_button.icon{
+    padding: 8px 10px 8px 25px;
+    background-position-x: 5px;
+  }
+  @each $icon, $image in $redmine_icons{
+    .icon-#{$icon}{
+      background-image: url('../../../../../images/#{$image}.png');
+    }
+  }
+  .icon-youtube{
+    padding-left: 28px;
+  }
+  .icon.icon-filter{
+    background-position-x: 9px;
+    background-position-y: 7px;
+  }
+  .easy-gantt__menu{
+    margin: 0 0 1px;
+    padding: 5px 0 5px 0;
+    background-color: white;
+  }
+  #gantt_cont{
+    /* width: auto !important; */
+    margin: 0;
+  }
+  .gantt_tree_icon{
+    width: 20px;
+    &:before{
+      vertical-align: sub;
+    }
+  }
+
+  a.active{
+    color: #fff;
+  }
+  a.disabled{
+    opacity: 0.25;
+    color: #484848;
+    font-weight: lighter;
+  }
+  #link_delay_input{
+    width: auto;
+  }
+  .push-left{
+    float: left;
+  }
+
+  .push-right{
+    float: right;
+    text-align: right
+  }
+
+  .gantt-footer p{
+    text-align: center;
+  }
+  #button_close_all_projects{
+    width: 18px;
+    height: 25px;
+    padding: 0;
+    vertical-align: bottom;
+    background-position: 0 50%;
+    background-repeat: no-repeat;
+  }
+
+  .overdue{
+    color: #e50026;
+  }
+  .contextual{
+    text-align: right;
+  }
+  .easy-gantt__menu-group--tooltiped{
+    position: relative;
+    ul{
+      position: absolute;
+      right: 0;
+      background-color: white;
+      border: 1px solid #cccccc;
+      padding: 5px;
+      padding-left: 0;
+      min-width: 180px;
+      display: none;
+      &:after{
+        content: '';
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        right: auto;
+        left: 25px;
+        border-left: 1px solid #eee3d4;
+      }
+      li{
+        display: block;
+      }
+      .gantt-menu-button{
+        border: none;
+        padding: 5px 5px 5px 30px;
+        background-color: transparent;
+      }
+    }
+    &:hover ul{
+      display: block;
+    }
+  }
+  @media print {
+    .gantt_task_progress{
+      border-right: 1px solid #4ebf67;
+      box-sizing: border-box;
+      margin-top: 1px;
+      margin-bottom: 1px;
+      height: calc(100% - 2px);
+    }
+    .wrong .gantt_task_progress{
+      border-color: #e50026;;
+    }
+    .gantt_project-type .gantt_task_progress{
+      border-color: #009ee0;
+    }
+    .odd{
+      background: none;
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_variables.scss b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..62c8ed25cea61c8d10b167f56fb609a57a0f941b
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/_gantt_variables.scss
@@ -0,0 +1,3 @@
+$gantt-background-menu: white !default;
+$gantt-color-critical:  #c800ff !default;
+$gap: 5px !default;
\ No newline at end of file
diff --git a/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/easy_gantt_sass.scss.erb b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/easy_gantt_sass.scss.erb
new file mode 100644
index 0000000000000000000000000000000000000000..fe22462239bf56014987ee8c91a07ce0151ac705
--- /dev/null
+++ b/plugins/easy_gantt/assets/stylesheets/easy_gantt/sass/easy_gantt_sass.scss.erb
@@ -0,0 +1,17 @@
+<% if Redmine::Plugin.installed?(:easy_project_com) %>
+@import "../../../../easyproject/easy_plugins/easy_project_com/assets/stylesheets/scss/common_assets";
+<% else %>
+@import "../../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/common_assets";
+<% end %>
+
+@import "gantt_variables";
+@import "gantt_icons";
+@import "gantt_redmine";
+@import "gantt_print";
+@import "gantt_main";
+
+<% if Redmine::Plugin.installed?(:easy_project_com) %>
+@include loadFontFace('Material Icons', '../../plugin_assets/easy_gantt/fonts/EasyMaterialIcons-Regular', 'Material Icons', 'MaterialIcons-Regular', normal, normal);
+<% else %>
+@include loadFontFace--non-rails('Material Icons', '../../plugin_assets/easy_gantt/fonts/EasyMaterialIcons-Regular', 'Material Icons', 'MaterialIcons-Regular', normal, normal);
+<% end %>
\ No newline at end of file
diff --git a/plugins/easy_gantt/config/locales/ar.yml b/plugins/easy_gantt/config/locales/ar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b93c21a986b87d70b2dfbb219cc684c7721d9891
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/ar.yml
@@ -0,0 +1,2 @@
+---
+ar: 
diff --git a/plugins/easy_gantt/config/locales/cs.yml b/plugins/easy_gantt/config/locales/cs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6999323ef6b440fe4c4bf49b853f0052953b198e
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/cs.yml
@@ -0,0 +1,184 @@
+---
+cs:
+  button_print: Tisk
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Globální Gantt
+  button_use_actual_delay: Použít skutečné zpoždění
+  easy_gantt_toolbar:
+    day: Dny
+    month: Měsíce
+    week: Týdny
+  easy_gantt:
+    button:
+      close_all: Zavřít vše
+      close_all_parent_issues: Zavřít všechny rodičovské úkoly
+      create_baseline: Baseline
+      critical_path: Kritická cesta
+      day_zoom: Dny
+      delayed_project_filter: Filtrovat zpožděné projekty
+      jump_today: Přejít na dnešek
+      load_sample_data: Nahrát zkušební data
+      month_zoom: Měsíce
+      moth_zoom: Měsíce
+      print_fit: Přizpůsobit stránce
+      problem_finder: Problémy
+      reload: Znovu načíst (uložit)
+      remove_delay: Odstranit zpoždění
+      resource_management: Vytížení zdrojů
+      tool_panel: Nástroje
+      week_zoom: Týdny
+    critical_path:
+      disabled: Vypnuto
+      last: Poslední
+      last_text: Úkoly, které by se neměly odkládat
+      longest: Nejdelší
+      longest_text: Zobrazit nejdelší sled úkolů
+    errors:
+      duplicate_link: Není možné vytvořit zdvojenou vazbu
+      fresh_milestone: Není možné přidat úkol na neuložený milník. Nejprve uložte
+        mílník.
+      link_target_new: Nelze vytvořit vazbu na neuložený úkol. Nejprve uložte změny.
+      link_target_readonly: Cílový úkol pouze pro čtení
+      loop_link: Není možné vytvořit cyklickou vazbu
+      no_rest_api: Easy Gantt potřebuje mít zapnuté REST API. Zapněte ho v Administraci
+        -> Nastavení -> API -> Zapnout službu REST
+      overdue: má skončit v budoucnu, nebo být již uzavřen
+      progress_date_overdue: se opožduje za plánem o %{days} dní
+      overmile: musí končit %{effective_date}, aby dodržel milník
+      short_delay: mělo by být delší o %{diff} dnů
+      too_short: není dost dlouhý pro zbývajících %{rest} hodin odhadovaného času
+      unsaved_parent: Nelze přidat úkol k neuloženému rodiči. Nejprve uložte změny.
+      unsupported_link_type: Nepodporovaný typ vazby
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} se nepodařilo uložit kvůli
+        chybÄ›"
+      send_failed: 'Následující požadavky se nepodařilo odeslat:'
+    label_pro_upgrade: Upgradovat na PRO verzi
+    link_dir:
+      link_end: jako předchůdce
+      link_start: jako následovník
+    popup:
+      add_task:
+        heading: Přidat úkol
+        text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Upgradovat
+          na PRO verzi</a>"
+      baseline:
+        heading: Baseline
+        text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Upgradovat
+          na PRO verzi</a>"
+      critical:
+        heading: Kritická cesta
+        text: "Tato funkce je dostupná pouze v Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Upgradovat
+          na PRO verzi</a>"
+      resource:
+        easy_text: Modul Vytížení zdrojů není nainstalován - získejte ho <a href="https://www.easyredmine.com/pricing-buy">zde!</a>
+        heading: Vytížení zdrojů
+        text: Vytížení zdrojů je dostupné v <a href="https://www.easyproject.cz/">
+          Easy Project</a>
+    reload_modal:
+      label_errors: Chyby
+      text_reload_appeal: Chcete ignorovat neuložené položky a znovu načíst data ze
+        serveru?
+      title: Gantt se nepodařilo správně uložit
+    sample_global_free:
+      text: Zkušební data nelze uzavřít. Gantt nad všemi projekty je k dispozici pouze
+        ve verzi PRO.
+      video:
+        text: Zde si můžete prohlédnout většinu funkcí Ganttu nad všemi projekty
+        title: Ukázkové video Ganttu nad všemi projekty
+        video_id: EiiqBrrY4m4
+    sample_video:
+      text: Zde můžete vidět většinu funkcí modulu Easy Gantt
+      title: Ukázkové video
+    sample:
+      active_label: Jsou načtena testovací data!
+      close_label: Zavřít toto okno a načíst skutečná data
+      header: Jsou načtena zkušební data!
+      text: Připravili jsme zkušební data, abyste <strong>vyzkoušeli všechny funkce
+        Easy Ganttu bez obav</strong>. Koukněte se také na video průvodce ukazujícího
+        užitečné triky. Zavřením tohoto okna načtete skutečná data.
+      video_id: UHgqfsrD59Q
+      video_label: Spustit video návod
+      video:
+        text: Zde můžete vidět většinu funkcí modulu Easy Gantt
+        title: Spustit video návod
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Milník blokuje posunutí úkolu za toto datum
+    text_blocker_move_pre: Úkol obsahuje vazbu, která blokuje posunutí úkolu za toto
+      datum
+    title:
+      button_test: Testovací tlačítko (pouze pro testování)
+      day_zoom: Dny
+      jump_today: Přejít na dnešek
+      month_zoom: Měsíce
+      week_zoom: Týdny
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Globální Easy Gantt
+  error_easy_gantt_view_permission: Nemáte právo vidět Easy Gantt
+  error_epm_easy_gantt_already_active: Easy gantt je na této stránce již aktivní
+  errors:
+    duplicate_link: Cannot create duplicate link
+    fresh_milestone: Cannot add task to non-saved milestone. Save the milestone first.
+    loop_link: Cannot create looped link
+    overmile: Task should end before its milestone
+    too_short: Task is too short for its estimated hours count
+  field_easy_gantt_default_zoom: Výchozí přiblížení
+  field_easy_gantt_show_holidays: Zobrazit svátky
+  field_easy_gantt_show_project_progress: Zobrazit pokrok na projektu
+  field_easy_gantt_show_task_soonest_start: Zobrazit nejdřívější začátek
+  field_keep_link_delay_in_drag: Zachovat zpoždění vazby během přesunu
+  field_relation: Vazba
+  gantt_grid_head_subject: Předmět
+  heading_delay_popup: Nastavte zpoždění úkolu ve dnech
+  heading_demo_feature_popup: Již brzy...
+  heading_easy_gantts_issues: Easy Gantt Free
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Kritická cesta
+  label_easy_gantt_settings: Nastavení Ganttu
+  label_filter_group_easy_gantt_easy_issue_query: Úkolová pole
+  label_finish_to_finish: Konec na konec
+  label_parent_issue_plural: Rodičovské úkoly
+  label_pro_upgrade: Upgrade to PRO version
+  label_start_to_finish: Začátek na konec
+  label_start_to_start: Začátek na začátek
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Upravit Easy Gantt & Vytížení zdrojů na projektu
+  permission_edit_global_easy_gantt: Upravit globální Easy Gantt & Vytížení zdrojů
+  permission_edit_personal_easy_gantt: Upravit osobní Easy Gantt & Vytížení zdrojů
+  permission_view_easy_gantt: Zobrazit Easy Gantt & Vytížení zdrojů na projektu
+  permission_view_global_easy_gantt: Zobrazit globální Easy Gantt & Vytížení zdrojů
+  permission_view_personal_easy_gantt: Zobrazit osobní Easy Gantt & Vytížení zdrojů
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  sample_global_free:
+    text: Sample data cannot be closed. Gantt over all projects is available only
+      in PRO version
+    video:
+      text: Here you can see most of features of Gantt over all projects
+      title: Sample video of Gantt over all projects
+      video_id: UHgqfsrD59Q
+  sample:
+    close_label: Close this window and load real project data
+    header: Sample data are loaded!
+    text: We have prepared some sample data for you to <strong>try all Gantt features
+      with no stress</strong>. We also want to encourage you to watch a video guide
+      showing useful tweaks. Closing this window will load real project data.
+    video_label: Watch video tutorial
+    video:
+      text: Here you can see most of features of this Gantt module
+      title: Sample video
+      video_id: UHgqfsrD59Q
+  text_demo_feature_popup: Tato funkce bude brzy dostupná.
+  text_easy_gantt_footer: Redmine Gantt powered by Easy
+  text_easy_gantt_keep_link_delay_in_drag: Zachovat zpoždění vazby, když je úkol přetažen
+    zpět
+  text_easy_gantt_show_holidays: Zobrazit aktuální dovolenou uživatelů v Ganttu (funguje
+    pouze v Easy Redmine)
+  text_easy_gantt_show_project_progress: Zobrazit procentuální dokončení na liště
+    projektu (načítání může být pomalé)
+  text_easy_gantt_show_task_soonest_start: Ukázat nejnižší platná data pro úkoly definovaná
+    vazbami nebo rodičem
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/da.yml b/plugins/easy_gantt/config/locales/da.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3dcd59d03737250a404acbf3357fe8585ca3c19
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/da.yml
@@ -0,0 +1,2 @@
+---
+da: 
diff --git a/plugins/easy_gantt/config/locales/de.yml b/plugins/easy_gantt/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5a1f854748771f975ada12a629d718a01537533a
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/de.yml
@@ -0,0 +1,196 @@
+---
+de:
+  button_print: Drucken
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projekte Gantt
+  button_use_actual_delay: Aktuelle Verzögerung übernehmen
+  easy_gantt_toolbar:
+    day: Days
+    month: Months
+    week: Weeks
+  easy_gantt:
+    buton_create_baseline: Baselines
+    button_critical_path: Critical path
+    button_resource_management: Ressourcenmanagement
+    button:
+      close_all: Alle schließen
+      close_all_parent_issues: Alle übergeordneten Aufgaben schliessen
+      create_baseline: Basispläne
+      critical_path: Kritischer Weg
+      day_zoom: Tage
+      delayed_project_filter: Verspätete Projekte filtern
+      jump_today: Zum heutigen Tag überspringen
+      load_sample_data: Beispieldaten laden
+      month_zoom: Monate
+      print_fit: Der Seite anpassen
+      problem_finder: Probleme
+      reload: Neu laden (speichern)
+      remove_delay: Die Verzögerung entfernen
+      resource_management: Ressourcenmanagement
+      tool_panel: Werkzeuge
+      week_zoom: Wochen
+    critical_path:
+      disabled: Deaktiviert
+      last: Letzte
+      last_text: Die Aufgaben, die ohne Verspätung erfüllt werden sollen
+      longest: Längste
+      longest_text: Die längste Sequenz anzeigen
+    error_overmile: Task should end before its milestone
+    error_too_short: Task is too short for its estimated hours count
+    errors:
+      duplicate_link: Das Duplikat des Links kann nicht erstellt werden
+      fresh_milestone: "Die Aufgabe kann nicht zu dem nicht gespeicherten Meilenstein
+        hinzugefügt werden.\r\nBitte speichern Sie erst den Meilenstein."
+      link_target_new: Ein Link zur nicht abgespeicherte Aufgabe kann nicht erstellt
+        werden. Bitte die Veränderungen erst speichern.
+      link_target_readonly: Nur-lese-Modus die Zielaufgabe
+      loop_link: Der geloopter Link kann nicht erstellt werden.
+      no_rest_api: Easy Gantt braucht REST API aktiviert. Bitte aktivieren Sie es
+        in der Administration -> Einstellungen-> API -> Aktivieren REST Web Service
+      overdue: Soll in der Zukunft enden oder soll bereits geschlossen werden
+      overmile: Soll am %{effective_date} enden, um den Meilenstein einzuhalten
+      short_delay: Soll länger sein als %{diff} Tage
+      too_short: enthält nicht genug Tage für %{rest} Stunden der abgeschätzten Zeit.
+      unsaved_parent: "Die Aufgabe kann nicht zur nicht gespeicherten übergeordneten
+        Aufgabe hinzugefügt werden.\r\nBitte erst die Änderungen speichern."
+      unsupported_link_type: Der Link Typ wird nicht unterstützt
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} kann nicht gespeichert werden.
+        Ein Fehler ist aufgetreten."
+      send_failed: Die Anfrage ist fehlgeschlagen
+    label_pro_upgrade: Auf die Pro Version upgraden
+    link_dir:
+      link_end: wie vorangegangen
+      link_start: als nächstes
+    popup:
+      add_task:
+        heading: Eine Funktion der Aufgabe hinzufügen
+        text: "Neue Funktion für eine/einen Aufgabe/Meilenstein ist ausschließlich
+          verfügbar für Easy Gantt PRO  Mitglieder\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      baseline:
+        heading: Funktion für Basispläne
+        text: "Funktionen für Basisplan ist ausschließlich verfügbar für Easy Gantt
+          PRO Mitglieder\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      critical:
+        heading: Funktion für den kritischen Weg
+        text: "Funktion für den kritischen Weg ist ausschließlich verfügbar für Easy
+          Gantt PRO Mitglieder\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      resource:
+        easy_text: Ressourcenmanagement Plug-in ist nicht installiert - hier herunterladen
+          <a href="https://www.easyredmine.com/pricing-buy">here!</a>
+        heading: Ressourcenmanager Funktion
+        text: Ressourcenmanagement Funktion steht Ihnen hier zur Verfügung  <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Fehler
+      text_reload_appeal: Möchten Sie nicht gespeicherte Elemente ignorieren und die
+        Daten vom Server neuladen?
+      title: Gantt wurde nicht ordnungsgemäß gespeichert
+    sample_global_free:
+      text: Beispieldaten können nicht geschlossen werden. Gantt über alle Projekte
+        ist nur in der PRO Version verfügrbar
+      video:
+        text: Hier bekommen Sie ein Übersicht über die meisten Funktionen des Gantt
+          über alle Projekte
+        title: Beispielvideo  für Gantt über alle Projekte
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Das Fenster schließen und die echten Projektdaten laden
+      header: Beispieldaten sind hochgeladen
+      text: "Wir haben einige Beispieldaten für Sie vorbereitet, um alle Easy Gantt
+        Funktionen ohne Stress testen zu können. Wir möchten Sie auch ermutigen, sich
+        die Video-Anleitung über Optimierungsmethoden anzuschauen.\r\nSchließen Sie
+        dieses Fenster, um das Laden der echten Projektdaten auszulösen."
+      video_label: Die Video-Anleitung anschauen
+      video:
+        text: Hier können Sie die meisten der Funktionen für dieses Gantt Modul sehen
+        title: Beispielvideo
+        video_id: UHgqfsrD59Q
+    soon:
+      add_task:
+        heading: Add Issue feature
+        text: This feature is comming soon. <a href="https://www.easyredmine.com/redmine-gantt-plugin">Check
+          the availability here!</a>
+      baseline:
+        heading: Baselines feature
+        text: Baselines feature is comming soon. <a href="https://www.easyredmine.com/redmine-gantt-plugin>Check
+          the availability here!</a>
+      critical:
+        heading: Critical path feature
+        text: "Critical Path feature is comming soon. \r\n<a href=\"https://www.easyredmine.com/redmine-gantt-plugin\">Check
+          the availability here!</a>"
+      resource:
+        heading: Resource manager feature
+        text: Resource Management feature is available just in <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    text_blocker_milestone: Der Meilenstein blockiert das Verschieben der Aufgabe
+      außerhalb des Datums
+    text_blocker_move_pre: Die Aufgabe hat eine Beziehung, die das Verschieben der
+      Aufgabe außerhalb des Datums verhindert.
+    title:
+      button_test: Test Taste (nur zum Testen)
+      day_zoom: In den täglichen Zeitplan zoomen
+      jump_today: springe zum heutigen Tag
+      month_zoom: In den monatlichen Zeitplan zoomen
+      print_fit: Skalieren Sie das Gantt auf eine Seite
+      week_zoom: In den wöchentlichen Zeitplan zoomen
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Globales Easy Gantt
+  error_easy_gantt_view_permission: Sie haben nicht die Berechtigung, Easy Gantt zu
+    sehen
+  error_epm_easy_gantt_already_active: Easy Gantt ist auf der Seite bereits aktiv
+  field_easy_gantt_default_zoom: Standard Zoom
+  field_easy_gantt_relation_delay_in_workdays: Relationsverzögerungen in Arbeitstagen
+  field_easy_gantt_show_holidays: Feiertage anzeigen
+  field_easy_gantt_show_project_progress: Den Projektfortschritt anzeigen
+  field_easy_gantt_show_task_soonest_start: Den frühestmöglichen Starttermin anzeigen
+  field_keep_link_delay_in_drag: Konstante Link-Verzögerung beim Ziehen (Drag)
+  field_relation: Beziehung
+  heading_delay_popup: Definition der Verzögerung in Tagen
+  heading_demo_feature_popup: Coming soon
+  heading_easy_gantts_issues: Aufgaben Gantt
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Kritischer Weg
+  label_easy_gantt_settings: Gantt Einstellungen
+  label_filter_group_easy_gantt_easy_issue_query: Aufgabenfelder
+  label_finish_to_finish: Ende zu Ende
+  label_parent_issue_plural: Probleme der übergeordneten Aufgaben
+  label_start_to_finish: Start zu Ende
+  label_start_to_start: Start zu Start
+  link_easy_gantt_footer: https://www.easyredmine.com/redmine-gantt-plugin
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Projekt bearbeiten
+  permission_edit_global_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Prokjekt
+    global bearbeiten
+  permission_edit_personal_easy_gantt: Persönliche Easy Gantt & Resource management
+    bearbeiten
+  permission_view_easy_gantt: Easy Gantt & Resourcenmanagement in dem Prokjekt sehen
+  permission_view_global_easy_gantt: Easy Gantt & Ressourcenmanagement in dem Prokjekt
+    global sehen
+  permission_view_personal_easy_gantt: Persönliche Easy Gantt & Ressourcenmanagement
+    sehen
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: This feature will be available soon.
+  text_easy_gantt_footer: Redmine Gantt powered by Easy
+  text_easy_gantt_keep_link_delay_in_drag: Die Verzögerung der Beziehung konstant
+    beibehalten während die Aufgabe nach hinten gezogen wird
+  text_easy_gantt_print_easy_gantt_current: Aktuelles Easy Gantt anzeigen (funktioniert
+    nur von einer Seite, wo Easy Gantt geladen wird)
+  text_easy_gantt_relation_delay_in_workdays: Rechnen Sie nicht Nichtarbeitstage in
+    der Relationsverzögerung mit.
+  text_easy_gantt_show_holidays: Die aktuellen Feiertage des Benutzers im Gantt- Diagram
+    anzeigen (Die Funktion ist nur in Easy Redmine verfügbar)
+  text_easy_gantt_show_project_progress: Den Fortschritt in Prozent auf dem Balken
+    des Projekts anzeigen (kann die Ladezeit beeinträchtigen)
+  text_easy_gantt_show_task_soonest_start: Die kleinsten zulässigen Daten, die durch
+    Beziehungen oder übergeordnete Aufgaben definiert sind, für die Aufgabe anzeigen
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/en-AU.yml b/plugins/easy_gantt/config/locales/en-AU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56b80b3c42087a32c3da5d38ddf1bda60788f804
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/en-AU.yml
@@ -0,0 +1,2 @@
+---
+en-AU: 
diff --git a/plugins/easy_gantt/config/locales/en-GB.yml b/plugins/easy_gantt/config/locales/en-GB.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a32c22849c6a55e573aaaa3a41b6c0aca41badc9
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/en-GB.yml
@@ -0,0 +1,2 @@
+---
+en-GB: 
diff --git a/plugins/easy_gantt/config/locales/en-US.yml b/plugins/easy_gantt/config/locales/en-US.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54b3be8e68c04f20ca86f9137ed2c6f00c987b74
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/en-US.yml
@@ -0,0 +1,2 @@
+---
+en-US: 
diff --git a/plugins/easy_gantt/config/locales/en.yml b/plugins/easy_gantt/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5bc8ea67c47139fbe545dc20061f04405e8c6d90
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/en.yml
@@ -0,0 +1,158 @@
+---
+en:
+  button_print: Print
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projects Gantt
+  button_use_actual_delay: Use actual delay
+  easy_gantt:
+    button:
+      close_all: Close all
+      close_all_parent_issues: Close all parent tasks
+      create_baseline: Baselines
+      critical_path: Critical path
+      day_zoom: Days
+      delayed_project_filter: Filter Delayed Projects
+      jump_today: Jump to today
+      load_sample_data: Load sample data
+      month_zoom: Months
+      print_fit: Fit to page
+      problem_finder: Problems
+      reload: Reload (save)
+      remove_delay: Remove delay
+      resource_management: Resource management
+      tool_panel: Tools
+      week_zoom: Weeks
+    critical_path:
+      disabled: Disabled
+      last: Last
+      last_text: Tasks which should not be delayed
+      longest: Longest
+      longest_text: Show longest sequence of tasks
+    errors:
+      duplicate_link: Cannot create duplicate link
+      fresh_milestone: Cannot add task to non-saved milestone. Save the milestone
+        first.
+      link_target_new: Cannot create link to unsaved task. Save the changes first.
+      link_target_readonly: Read-only target task
+      loop_link: Cannot create looped link
+      no_rest_api: Easy Gantt needs REST API enabled. Turn it on in Administration
+        -> Settings -> API -> Enable REST web service
+      overdue: should end in future or should be closed already
+      progress_date_overdue: is behind schedule by %{days} days
+      overmile: should end on %{effective_date} in order to keep milestone
+      short_delay: should be longer by %{diff} days
+      too_short: does not contain enough days for %{rest} hours of estimated time
+      unsaved_parent: Cannot add task to non-saved parent. Save the changes first.
+      unsupported_link_type: Unsupported link type
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} failed to save due to error"
+      send_failed: Request failed
+    label_pro_upgrade: Upgrade to PRO version
+    link_dir:
+      link_end: as preceding
+      link_start: as following
+    popup:
+      add_task:
+        heading: Add task feature
+        text: "New task/milestone feature is available only in Easy Gantt PRO \r\n<a
+          href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check for
+          update!</a>"
+      baseline:
+        heading: Baselines feature
+        text: "Baselines feature is available only in Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      critical:
+        heading: Critical path feature
+        text: "Critical path feature is available only in Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      resource:
+        easy_text: Resource management plugin is not installed - get it <a href="https://www.easyredmine.com/pricing-buy">here!</a>
+        heading: Resource manager feature
+        text: Resource management feature is available only in <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Errors
+      text_reload_appeal: Do you want to ignore unsaved items and reload data from
+        server?
+      title: Gantt failed to save properly
+    sample_global_free:
+      text: Sample data cannot be closed. Gantt over all projects is available only
+        in PRO version
+      video:
+        text: Here you can see most of features of Gantt over all projects
+        title: Sample video of Gantt over all projects
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Close this window and load real project data
+      header: Sample data are loaded!
+      text: We have prepared some sample data for you to <strong>try all Easy Gantt
+        features with no stress</strong>. We also want to encourage you to watch a
+        video guide showing useful tweaks. Closing this window will load real project
+        data.
+      video_label: Watch video tutorial
+      video:
+        text: Here you can see most of features of this Gantt module
+        title: Sample video
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Milestone is blocking task to be moved beyond this date
+    text_blocker_move_pre: Task has relation that blocks moving task beyond this date
+    text_blocker_move_end: At least one following task will be postponed if moved after this date
+    title:
+      button_test: Test button (only for testing)
+      day_zoom: Zoom to day scale
+      jump_today: Display timeline at today
+      month_zoom: Zoom to month scale
+      print_fit: Scale the gantt to one page
+      week_zoom: Zoom to week scale
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: You do not have permission to see Easy Gantt
+  error_epm_easy_gantt_already_active: Easy gantt on the page is already active
+  field_easy_gantt_default_zoom: Default zoom
+  field_easy_gantt_relation_delay_in_workdays: Relation delays in workdays
+  field_easy_gantt_show_holidays: Show holidays
+  field_easy_gantt_show_project_progress: Show project progress
+  field_easy_gantt_show_task_soonest_start: Show soonest start
+  field_relation: Relation
+  heading_delay_popup: Define delay in days
+  heading_demo_feature_popup: Coming soon
+  heading_easy_gantts_issues: Easy Gantt Free
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Critical path
+  label_easy_gantt_settings: Gantt settings
+  label_filter_group_easy_gantt_easy_issue_query: Task fields
+  label_finish_to_finish: Finish to finish
+  label_parent_issue_plural: Parent tasks
+  label_start_to_finish: Start to finish
+  label_start_to_start: Start to start
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Edit Easy Gantt & Resource management on a project
+  permission_edit_global_easy_gantt: Edit global Easy Gantt & Resource management
+  permission_edit_personal_easy_gantt: Edit personal Easy Gantt & Resource management
+  permission_view_easy_gantt: View Easy Gantt & Resource management on a project
+  permission_view_global_easy_gantt: View global Easy Gantt & Resource management
+  permission_view_personal_easy_gantt: View personal Easy Gantt & Resource management
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: This feature will be available soon.
+  text_easy_gantt_footer: Redmine Gantt powered by Easy
+  text_easy_gantt_print_easy_gantt_current: Show current Easy Gantt (works only from
+    page where is easy gantt loaded)
+  text_easy_gantt_relation_delay_in_workdays: Do not count non-working days in relation
+    delay
+  text_easy_gantt_show_holidays: Show current user holidays on gantt (works only in
+    Easy Redmine)
+  text_easy_gantt_show_project_progress: Show completed percent on project's bar (may
+    be slow to load)
+  text_easy_gantt_show_task_soonest_start: Show lowest valid dates for tasks defined
+    by relations or parent
+  title_easy_gantt_settings: Easy Gantt
+  field_easy_gantt_fixed_delay: Fixed delay
+  text_easy_gantt_fixed_delay: Keep relation delay fixed when task is dragging (works only on easy gantt)
+  label_easy_gantt_recalculate_fixed_delay: recalculate fixed delay
+  notice_easy_gantt_fixed_delay_recalculated: Fixed delay on all relations was recalculated
diff --git a/plugins/easy_gantt/config/locales/es.yml b/plugins/easy_gantt/config/locales/es.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a2b33b9888befb5610ef03a44e8d73eded554529
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/es.yml
@@ -0,0 +1,165 @@
+---
+es:
+  button_print: Imprimir
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Proyectos Gantt
+  button_use_actual_delay: Emplear retraso exacto
+  easy_gantt:
+    button:
+      close_all: Cerrar todo
+      close_all_parent_issues: Cerrar todas las tareas matrices
+      create_baseline: Referencias
+      critical_path: Ruta crítica
+      day_zoom: Días
+      delayed_project_filter: Filtrar los proyectos atrasados
+      jump_today: Saltar a hoy
+      load_sample_data: Cargar datos de muestra
+      month_zoom: Meses
+      print_fit: Ajustar a la página
+      problem_finder: Problemas
+      reload: Recargar (guardar)
+      remove_delay: Eliminar retraso
+      resource_management: Gestión de los recursos
+      tool_panel: Herramientas
+      week_zoom: Semanas
+    critical_path:
+      disabled: Deshabilitada
+      last: Última
+      last_text: Tareas que no deberían retrasarse
+      longest: Más largas
+      longest_text: Mostrar la secuencia de tareas más larga
+    errors:
+      duplicate_link: No se puede crear enlace duplicado
+      fresh_milestone: No se puede añadir tarea a un objetivo no guardado. Primero,
+        guarda el objetivo.
+      link_target_new: No se puede crear enlace a la tarea sin guardar. Primero, guarda
+        los cambios.
+      link_target_readonly: Tarea objetivo solo de lectura
+      loop_link: No se puede crear enlace con vínculo
+      no_rest_api: Easy Gantt requiere que REST API esté habilitado. Actívalo en Administración
+        -> Ajustes -> API -> Activar servicio web REST
+      overdue: debería terminar en el futuro o debería haberse cerrado ya
+      overmile: debería terminar en %{effective_date} para poder mantener el objetivo
+      short_delay: debe ser más extenso en %{diff} días
+      too_short: no contiene días suficientes para %{rest} horas de tiempo estimado
+      unsaved_parent: No se puede añadir tarea a una matriz sin guardar. Primero,
+        guarda los cambios.
+      unsupported_link_type: Tipo de enlace no compatible
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} no se ha guardado debido a
+        un error"
+      send_failed: Solicitud fallida
+    label_pro_upgrade: Actualizar a la versión PRO
+    link_dir:
+      link_end: como el anterior
+      link_start: como el siguiente
+    popup:
+      add_task:
+        heading: Añadir característica de tarea
+        text: La nueva característica de tarea/objetivo está disponible únicamente
+          en Easy Gantt PRO <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">¡Marcar
+          para actualizar!</a>
+      baseline:
+        heading: Característica de referencias
+        text: La característica de la referencia está disponible únicamente en Easy
+          Gantt PRO <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">¡Marcar
+          para actualizar!</a>
+      critical:
+        heading: Característica de ruta crítica
+        text: La característica de la ruta crítica está disponible únicamente en Easy
+          Gantt PRO <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Marcar
+          para actualizar
+      resource:
+        easy_text: El plugin de gestor de recursos no está instalado - ¡Consíguelo
+          <a href="https://www.easyredmine.com/pricing-buy">aquí!</a>
+        heading: Característica de gestor de recursos
+        text: La característica de gestor de recursos está disponible únicamente en
+          <a href="https://www.easyredmine.com/"> Easy Redmine</a>
+    reload_modal:
+      label_errors: Errores
+      text_reload_appeal: "¿Quieres ignorar los objetos no guardados y cargar de nuevo
+        los datos del servidor?"
+      title: Gantt no ha podido guardar correctamente
+    sample_global_free:
+      text: No se pueden cerrar los datos de muestra. Gantt en todos los proyectos
+        está disponible únicamente en la versión PRO
+      video:
+        text: Aquí podrás ver la mayoría de las características de Gantt en todos
+          los proyectos
+        title: Vídeo de muestra de Gant en todos los proyectos
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Cerrar esta ventana y cargar los datos reales del proyecto
+      header: "¡Se han cargado los datos de muestra!"
+      text: Hemos preparado unos datos de muestra para que <strong>pruebes todas las
+        características de Easy Gantt sin estrés</strong>. También nos gustaría animarte
+        a que visualices un vídeo de guía que muestra ajustes útiles. Al cerrar esta
+        ventana se cargarán los datos reales del proyecto.
+      video_label: Ver el vídeo tutorial
+      video:
+        text: Aquí podrás ver la mayoría de las características de este módulo de
+          Gantt
+        title: Vídeo de muestra
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: El objetivo no permite que la tarea se desplace después
+      de esta fecha
+    text_blocker_move_pre: La tarea tiene una relación que bloquea su desplazamiento
+      a una fecha posterior
+    title:
+      button_test: Botón de prueba (sólo para pruebas)
+      day_zoom: Ampliar a escala de día
+      jump_today: Mostrar calendario en hoy
+      month_zoom: Ampliar a escala de mes
+      print_fit: Ajustar gantt a una página
+      week_zoom: Ampliar a escala de semana
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: No tienes permiso para ver Easy Gantt
+  error_epm_easy_gantt_already_active: Ya está activo Easy Gantt en la página
+  field_easy_gantt_default_zoom: Zoom por defecto
+  field_easy_gantt_relation_delay_in_workdays: Retrasos de relación en días hábiles
+  field_easy_gantt_show_holidays: Mostrar vacaciones
+  field_easy_gantt_show_project_progress: Mostrar el progreso del proyecto
+  field_easy_gantt_show_task_soonest_start: Mostrar el inicio más próximo
+  field_keep_link_delay_in_drag: Retraso constante del enlace durante el arrastre
+  field_relation: Relación
+  heading_delay_popup: Definir retraso en días
+  heading_demo_feature_popup: Muy pronto
+  heading_easy_gantts_issues: Easy Gantt gratis
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Senda crítica
+  label_easy_gantt_settings: Ajustes de Gantt
+  label_filter_group_easy_gantt_easy_issue_query: Campos de tarea
+  label_finish_to_finish: Fin a fin
+  label_parent_issue_plural: Problemas de matriz
+  label_start_to_finish: Inicio a fin
+  label_start_to_start: Inicio a inicio
+  link_easy_gantt_plugin: https://www.easyredmine.com/actualizar-easy-gantt-a-pro
+  permission_edit_easy_gantt: Editar Easy Gantt y gestión de recursos en un proyecto
+  permission_edit_global_easy_gantt: Editar Easy Gantt y gestión de recursos global
+  permission_edit_personal_easy_gantt: Editar Easy Gantt y gestión de recursos personal
+  permission_view_easy_gantt: Ver Easy Gantt y gestión de recursos en un proyecto
+  permission_view_global_easy_gantt: Ver Easy Gantt y gestión de recursos global
+  permission_view_personal_easy_gantt: Ver Easy Gantt y gestión de recursos personal
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Esta característica está disponible pronto.
+  text_easy_gantt_footer: Redmine Gantt respaldado por Easy
+  text_easy_gantt_keep_link_delay_in_drag: Mantener constante la relación de retraso
+    mientras se arrastra la tarea
+  text_easy_gantt_print_easy_gantt_current: Mostrar Easy Gantt actual (funciona sola
+    desde la página en la que se carga easy gantt)
+  text_easy_gantt_relation_delay_in_workdays: No contar días no hábiles en el retraso
+    de relación
+  text_easy_gantt_show_holidays: Mostrar las vacaciones del usuario actual en gantt
+    (funciona sólo en Easy Redmine)
+  text_easy_gantt_show_project_progress: Mostrar porcentaje completado en la barra
+    del proyecto (podría tardar al cargar)
+  text_easy_gantt_show_task_soonest_start: Mostrar las fechas válidas más bajas para
+    tareas definidas por relaciones o matriz
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/fi.yml b/plugins/easy_gantt/config/locales/fi.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e173d1858f52e3f1aa745d45fd68220187ea3c51
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/fi.yml
@@ -0,0 +1,2 @@
+---
+fi: 
diff --git a/plugins/easy_gantt/config/locales/fr.yml b/plugins/easy_gantt/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..361727aecc01865619467b6e047efe65e42ab52f
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/fr.yml
@@ -0,0 +1,165 @@
+---
+fr:
+  button_print: Imprimer
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Easy Gantt
+  button_use_actual_delay: Utilisez le délai réel
+  easy_gantt_toolbar:
+    day: Jours
+    month: Mois
+    week: Semaines
+  easy_gantt:
+    button:
+      close_all: Fermer tout
+      close_all_parent_issues: Fermer toutes les tâches parents
+      create_baseline: Lignes de base
+      critical_path: Chemin critique
+      day_zoom: Jours
+      delayed_project_filter: Filtrer les projets retardés
+      jump_today: Aller à aujourd'hui
+      load_sample_data: Charger données d'échantillon
+      month_zoom: Mois
+      print_fit: Ajuster à la page
+      problem_finder: Problèmes
+      reload: Recharger (sauvegarder)
+      remove_delay: Éliminer délai
+      resource_management: Gestion des resources
+      tool_panel: Outils
+      week_zoom: Semaines
+    critical_path:
+      disabled: Désactivé
+      last: Dernier
+      last_text: Tâches qui ne doivent pas être retardées
+      longest: La plus longue
+      longest_text: Afficher la séquence de tâches la plus longue
+    errors:
+      duplicate_link: Cannot create duplicate link
+      fresh_milestone: Cannot add task to non-saved milestone. Save the milestone
+        first.
+      link_target_new: Vous ne pouvez pas créer un lien vers la tâche non-sauvegardée.
+        Commencez par enregistrer les modifications.
+      link_target_readonly: Tâche cible en lecture seule
+      loop_link: Cannot create looped link
+      no_rest_api: Easy Gantt doit activer REST API. Allumez-le dans Administration
+        - > Paramètres -> API - > Activer REST web service
+      overdue: devrait se terminer à l'avenir ou devrait être déjà fermé
+      overmile: La tâche doit être terminée avant la date d'échéance du jalon
+      short_delay: Devrait être plus long de %{diff} jours
+      too_short: Tâche est trop courte pour le nombre d'heures estimés
+      unsaved_parent: Impossible d'ajouter la tâche au parent non-enregistré. Commencez
+        par enregistrez les modifications.
+      unsupported_link_type: Type de lien non reconnu
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} n'a pas pu sauvegarder due
+        à une erreur"
+      send_failed: Request failed
+    label_pro_upgrade: Upgrade to PRO version
+    link_dir:
+      link_end: as preceding
+      link_start: as following
+    popup:
+      add_task:
+        heading: Ajouter la tâche fonctionnalité
+        text: Cette fonctionnalité est à venir bientôt. <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Check
+          the availability here!</a>
+      baseline:
+        heading: Lignes de base disposent
+        text: Baselines fonctionnalité bientôt disponible. <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Check
+          the availability here!</a>
+      critical:
+        heading: Fonctionnalité du chemin critique
+        text: "Fonctionnalité du chemin critique sera disponible bientôt.\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          the availability here!</a>"
+      resource:
+        easy_text: Le plugin de gestion des ressources n'est pas installé - faites-en
+          l'acquisition <a href="https://www.easyredmine.com/pricing-buy">here!</a>
+        heading: Fonctionnalité de gestion de ressources
+        text: Fonctionnalité de gestion de ressources est disponible <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Erreurs
+      text_reload_appeal: Voulez-vous ignorer des éléments non enregistrés et recharger
+        les données à partir du serveur ?
+      title: Gantt n'a pas réussi à enregistrer correctement
+    sample_global_free:
+      text: Sample data cannot be closed. Gantt over all projects is available only
+        in PRO version
+      video:
+        text: Here you can see most of features of Gantt over all projects
+        title: Sample video of Gantt over all projects
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Close this window and load real project data
+      header: Sample data are loaded!
+      text: We have prepared some sample data for you to <strong>try all Gantt features
+        with no stress</strong>. We also want to encourage you to watch a video guide
+        showing useful tweaks. Closing this window will load real project data.
+      video_label: Watch video tutorial
+      video:
+        text: Here you can see most of features of this Gantt module
+        title: Sample video
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Un jalon empêche la tâche d'être déplacée au-delà de cette
+      date
+    text_blocker_move_pre: La tâche a une relation qui bloque le déplacement de la
+      tâche au-delà de cette date
+    title:
+      button_test: Test button (only for testing)
+      day_zoom: Zoom to day scale
+      jump_today: Display timeline at today
+      month_zoom: Zoom to month scale
+      print_fit: Réduire le diagramme de Gantt à une page
+      week_zoom: Zoom to week scale
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: Vous n'êtes pas autorisé à voir Easy Gantt
+  error_epm_easy_gantt_already_active: Easy gantt sur la page est déjà actif
+  field_easy_gantt_default_zoom: Zoom par défaut
+  field_easy_gantt_relation_delay_in_workdays: Délais des relations en journées de
+    travail
+  field_easy_gantt_show_holidays: Afficher les vacances
+  field_easy_gantt_show_project_progress: Afficher les progrès du projet
+  field_easy_gantt_show_task_soonest_start: Afficher le démarrage le plus tôt
+  field_keep_link_delay_in_drag: Délai de lien constant  pendant glissement
+  field_relation: Relation
+  heading_delay_popup: Définir délai en jours
+  heading_demo_feature_popup: À venir
+  heading_easy_gantts_issues: Easy Gantt Free
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Chemin critique
+  label_easy_gantt_settings: Paramètres de Gantt
+  label_filter_group_easy_gantt_easy_issue_query: Champs de tâches
+  label_finish_to_finish: Terminer pour terminer
+  label_parent_issue_plural: Problèmes parents
+  label_start_to_finish: Début à la fin
+  label_start_to_start: Démarrer pour démarrer
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Éditer Easy Gantt et gestion des ressources sur un projet
+  permission_edit_global_easy_gantt: Éditer Easy Gantt & Resource management global
+  permission_edit_personal_easy_gantt: Éditer Easy Gantt & Gestion des ressources
+    personnel
+  permission_view_easy_gantt: Voir Easy Gantt et Gestion des ressources sur un projet
+  permission_view_global_easy_gantt: Voir Easy Gantt & Resource management global
+  permission_view_personal_easy_gantt: Voir Easy Gantt & Resource management personnel
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Cette fonctionnalité sera disponible bientôt.
+  text_easy_gantt_footer: Redmine Gantt powered by Easy
+  text_easy_gantt_keep_link_delay_in_drag: Maintient le délai de relation constant
+    pendant que la tâche est déplacée en arrière
+  text_easy_gantt_print_easy_gantt_current: Afficher l'actuel Easy Gantt (fonctionne
+    uniquement à partir de la page sur laquelle easy gantt est chargé)
+  text_easy_gantt_relation_delay_in_workdays: Ne pas compter les journées non-ouvrables
+    en délai de relation
+  text_easy_gantt_show_holidays: Afficher les vacances de l'utilisateur actuel sur
+    Gantt ( fonctionne uniquement dans Easy Redmine)
+  text_easy_gantt_show_project_progress: Afficher le pourcentage terminé sur la barre
+    de projet (peut être lent à charger)
+  text_easy_gantt_show_task_soonest_start: Afficher les dates de validité les plus
+    basses pour les tâches définies par des relations ou parent
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/he.yml b/plugins/easy_gantt/config/locales/he.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de35edf7f488f14ca884e77b8d36cf38a66d3e3d
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/he.yml
@@ -0,0 +1,2 @@
+---
+he: 
diff --git a/plugins/easy_gantt/config/locales/hr.yml b/plugins/easy_gantt/config/locales/hr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8ecf54ab84a5c15b21c88af98908f1ed46e902f1
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/hr.yml
@@ -0,0 +1,8 @@
+---
+hr:
+  button_print: Ispiši
+  button_use_actual_delay: Koristi stvarno kašnjeje
+  easy_gantt:
+    button:
+      close_all: Zatvori sve
+      tool_panel: Alati
diff --git a/plugins/easy_gantt/config/locales/hu.yml b/plugins/easy_gantt/config/locales/hu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b6730950ce0fb738dd1edddf225a9cd0f79b4d49
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/hu.yml
@@ -0,0 +1,161 @@
+---
+hu:
+  button_print: Nyomtatás
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projekt Gantt
+  button_use_actual_delay: Használja a jelenlegi késést
+  easy_gantt:
+    button:
+      close_all: Mind bezárása
+      close_all_parent_issues: Minden feladat bezására
+      create_baseline: Baselines
+      critical_path: Kritikus végrehajtási útvonal
+      day_zoom: Napok
+      delayed_project_filter: Elhalasztott projektek szűrése
+      jump_today: Ugrás a mai napra
+      load_sample_data: Minta adat betöltése
+      month_zoom: Hónapok
+      print_fit: Oldalhoz igazítás
+      problem_finder: Problémák
+      reload: Újratöltés (mentés)
+      remove_delay: Késés megszüntetése
+      resource_management: Erőforrások kezelése
+      tool_panel: Eszközök
+      week_zoom: Hetek
+    critical_path:
+      disabled: Letiltva
+      last: Utolsó
+      last_text: Nem halasztható feladatok
+      longest: Leghosszabb
+      longest_text: Leghosszabb feladatsorozat megjelenítése
+    errors:
+      duplicate_link: Kettős kapcsolat létrehozása nem lehetséges
+      fresh_milestone: A nem mentett mérföldkövekhez nem adható feladat. Először mentse
+        el a mérföldkövet.
+      link_target_new: A nem mentett feladatokhoz nem hozható létre link. Először
+        mentse el a változásokat.
+      link_target_readonly: Csak olvasható célfeladat
+      loop_link: Visszahivatkozó kapcsolat létrehozása nem lehetséges
+      no_rest_api: Az Easy Gantt-nek szüksége van a REST API szolgáltatásra. Engedélyezze
+        az adminisztráció => Beállítások =>  REST webes szolgáltatás bekapcsolásával
+      overdue: Hamarosan be kell fejeződnie, vagy már be kellett volna fejezni.
+      overmile: "%{effective_date} dátummal kell befejeződnie hogy tartsa a mérföldkő
+        határidejét"
+      short_delay: "%{diff} nappal hosszabbnak kell lennie"
+      too_short: Nincs elegendő nap a hátralévő %{rest} becsült óra számára
+      unsaved_parent: Nem adhat feladatot nem mentett szülőfeladathoz. Először mentse
+        a változásokat.
+      unsupported_link_type: Nem támogatott linktípus
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} hiba miatt nem sikerült menteni"
+      send_failed: Kérés nem sikerült
+    label_pro_upgrade: Frissítsen PRO verzióra
+    link_dir:
+      link_end: megelőzőként
+      link_start: következőként
+    popup:
+      add_task:
+        heading: Feladat jellemző hozzáadása
+        text: Új feladat/ mérföldkő tulajdonság csak az Easy Gantt PRO csomagban érhető
+          el <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Frissítés
+          keresése!</a>
+      baseline:
+        heading: Baselines jellemzői
+        text: Alapbeállítás jellemzői csak az Easy Gantt PRO <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">
+          csomagban érhető el. </a>
+      critical:
+        heading: Kritikus végrehajtási út funkció
+        text: Kritikus végrehajtási út funkció csak az Easy Gantt PRO csomagban elérhető
+          el.  <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro"> Kövesse
+          a frissítéseket! </a>
+      resource:
+        easy_text: Az Erőforrástervező funkció nincs telepítve. Itt megvásárolhatja
+          <a href="https://www.easyredmine.com/pricing-buy">here!</a>
+        heading: Erőforrástervező funkció
+        text: Az Erőforrástervező funkció csak az Easy Redmine csomagban érhető el.  <a
+          href="https://www.easyredmine.com/"> Easy Redmine</a>
+    reload_modal:
+      label_errors: Hibák
+      text_reload_appeal: Szeretné figyelmen kívül hagyni a nem mentett elemeket és
+        újratölteni az adatokat egy másik szerverről?
+      title: Nem sikerült a Gannt megfelelő mentése
+    sample_global_free:
+      text: A minta adatokat nem lehet bezárni.A Gantt átfogó projektjei csak a PRO
+        verzióban érhetők el
+      video:
+        text: Itt látható Gantt átfogó projektjeinek tulajdonságai
+        title: Gantt átfogó projektjeinek minta videója
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Zárja be ezt az ablakot és töltsön fel valós projekt adatokat
+      header: Minta adat betöltődött
+      text: Előkészítettünk néhány mintaadatot Önnek, hogy minden Easy Gantt funkciót
+        kipróbálhasson probléma nélkül. Emellett javasoljuk, tekintse meg a videós
+        bemutatót is. Az ablak bezárása után betöltődnek a valós projektadatok.
+      video_label: Nézze meg az oktatóvideót
+      video:
+        text: Itt látható a Gantt modul legtöbb tulajdonsága
+        title: Bemutató videó
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: A mérföldkő megakadályozza, hogy a feladatot át lehessen
+      vinni az adott dátumon túl
+    text_blocker_move_pre: A feladatnak olyan kapcsolatai vannak amelyek nem engedik
+      meg hogy az adott dátumon túl átvihető legyen.
+    title:
+      button_test: Teszt gomb (csak teszteléskor)
+      day_zoom: Váltás napi nézetre
+      jump_today: Mai napi idővonal megjelenítése
+      month_zoom: Váltás havi nézetre
+      print_fit: Gantt egész oldalas nagyítása
+      week_zoom: Váltás heti nézetre
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: Nincs felhatalazma az  Easy Gantt eléréséhez
+  error_epm_easy_gantt_already_active: Easy Gantt már aktív az oldalon
+  field_easy_gantt_default_zoom: Alapértelmezett nézet
+  field_easy_gantt_relation_delay_in_workdays: Kapcsolódó késések munkanapokban
+  field_easy_gantt_show_holidays: Szabadnapok megjelenítése
+  field_easy_gantt_show_project_progress: Projekt folyamatának mutatása
+  field_easy_gantt_show_task_soonest_start: Legkorábbi kezdés megjelenítése
+  field_keep_link_delay_in_drag: Átmozgatás során a késés és kapcsolat megőrzése
+  field_relation: Kapcsolat
+  heading_delay_popup: Adja meg a késést napokban
+  heading_demo_feature_popup: Hamarosan
+  heading_easy_gantts_issues: Ingyenes Easy Gantt
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Kritikus végrehajtási útvonal
+  label_easy_gantt_settings: Gantt beállítások
+  label_filter_group_easy_gantt_easy_issue_query: Feladat mezők
+  label_finish_to_finish: Befejezéstől a befejezésig
+  label_parent_issue_plural: Szülőfeladatok
+  label_start_to_finish: Kezdéstől a befejezésig
+  label_start_to_start: Kezdéstől kezdésig
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Easy Gantt & Erőforrástervezés szerkesztése a projekten
+  permission_edit_global_easy_gantt: Globális Easy Gantt és Erőforrástervezés szerkesztése
+  permission_edit_personal_easy_gantt: Személyes Easy Gantt és Erőforrástervező szerkesztése
+  permission_view_easy_gantt: Easy Gantt & Erőforrástervezés megtekintése a projekten
+  permission_view_global_easy_gantt: Globális Easy Gantt és Erőforrástervezés megtekintése
+  permission_view_personal_easy_gantt: Személyes Easy Gantt és Erőforrástervező megtekintése
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Ez a tulajdonság hamarosan elérhető lesz
+  text_easy_gantt_footer: Az Easy működteti a Redmine Gantt
+  text_easy_gantt_keep_link_delay_in_drag: Tartsa meg a késés mértékét a feladat korábbra
+    helyezésekor
+  text_easy_gantt_print_easy_gantt_current: Mutasd az aktív Easy Gantt-et (csak arról
+    az oldalról működik, ahonnan az Easy Gantt-et betöltötték )
+  text_easy_gantt_relation_delay_in_workdays: A kapcsolódó késésben a munkaszüneti
+    napokat ne számolja
+  text_easy_gantt_show_holidays: Mutassa a jelenlegi Gantt felhasználó szabadságát
+    (csak Easy Redmine-ban működik)
+  text_easy_gantt_show_project_progress: Jelenítse meg a százalékos készültségét a
+    projekt lapon (lassan töltődhet be)
+  text_easy_gantt_show_task_soonest_start: Jelenítse meg a legkorábbi érvényes dátumokat
+    a kapcsolatok vagy szülői függőség által meghatározott feladatoknál
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/it.yml b/plugins/easy_gantt/config/locales/it.yml
new file mode 100644
index 0000000000000000000000000000000000000000..642c04db39a9e3cf496030d352dd21303b920b6d
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/it.yml
@@ -0,0 +1,166 @@
+---
+it:
+  button_print: Stampa
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Progetti Gantt
+  button_use_actual_delay: Utilizza il ritardo effettivo
+  easy_gantt:
+    button:
+      close_all: Chiudi tutto
+      close_all_parent_issues: Chiudi tutti i task padre
+      create_baseline: Baseline
+      critical_path: Percorso critico
+      day_zoom: Giorni
+      delayed_project_filter: Filtra Progetti Rinviati
+      jump_today: Passa a oggi
+      load_sample_data: Carica dati di prova
+      month_zoom: Mesi
+      print_fit: Adatta alla pagina
+      problem_finder: Problemi
+      reload: Ricarica (salva)
+      remove_delay: Rimuovi ritardo
+      resource_management: Gestione risorse
+      tool_panel: Strumenti
+      week_zoom: Settimane
+    critical_path:
+      disabled: Disabilitato
+      last: Finale
+      last_text: Task che non dovrebbero essere posticipati
+      longest: Più lungo
+      longest_text: Mostra la più lunga sequenza di task
+    errors:
+      duplicate_link: Creazione copia collegamento non riuscita
+      fresh_milestone: Impossibile aggiungere task a una milestone non salvata. Salvare
+        prima la milestone.
+      link_target_new: Impossibile creare collegamento a un'attività non salvata.
+        Salvare prima le modifiche.
+      link_target_readonly: Task obiettivo di sola lettura
+      loop_link: Creazione collegamento ciclico non riuscita
+      no_rest_api: Easy Gantt necessita che le REST API siano attive. Per farlo andare
+        in Amministrazione -> Impostazione -> API -> Attiva servizi web REST
+      overdue: dovrebbe terminare in futuro o dovrebbe essere già chiusa
+      overmile: Dovrebbe terminare il %{effective_date} per rispettare la milestone
+      short_delay: dovrebbe essere più lunga di %{diff} giorni
+      too_short: non include giorni a sufficienza per le %{rest} ore stimate
+      unsaved_parent: Impossibile aggiungere task a un padre non salvato. Salvare
+        prima le modifiche.
+      unsupported_link_type: Tipo di collegamento non supportato
+    gateway:
+      entity_save_failed: Salvataggio di %{entityType} %{entityName} non riuscito
+        a causa di un errore
+      send_failed: Richiesta non riuscita
+    label_pro_upgrade: Effettua l'upgrade alla versione PRO
+    link_dir:
+      link_end: come precedente
+      link_start: come seguente
+    popup:
+      add_task:
+        heading: Aggiungi funzionalità al task
+        text: La funzionalità nuova task/milestone è disponibile solo in Easy Gantt
+          PRO  <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Controlla
+          aggiornamenti!</a>
+      baseline:
+        heading: Funzionalità delle baseline
+        text: La funzionalità Baseline è disponibile solo in Easy Gantt PRO <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Controlla
+          aggiornamenti!</a>
+      critical:
+        heading: Funzionalità Percorso critico
+        text: La funzionalità Percorso critico è disponibile solo in Easy Gantt PRO
+          <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Check for
+          update!</a>
+      resource:
+        easy_text: Il plugin Gestione risorse non risulta installato.  Scaricalo da
+          <a href="https://www.easyredmine.com/pricing-buy">qui!</a>
+        heading: Funzionalità Gestione risorse
+        text: La funzionalità Gestione risorse è disponibile solo in <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Errori
+      text_reload_appeal: Desideri ignorare le voci non salvate e ricaricare i dati
+        dal server?
+      title: Gantt non salvato correttamente
+    sample_global_free:
+      text: I dati di prova non possono essere chiusi. Il Gantt per tutti i progetti
+        è disponile solo nella versione PRO
+      video:
+        text: Qui puoi vedere la maggior parte delle funzionalità del Gantt per tutti
+          i progetti
+        title: Video di esempio di Gantt su tutti i progetti
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Chiudi questa finestra e carica i dati del progetto reale
+      header: I dati di esempio sono stati caricati!
+      text: Abbiamo alcuni dati di esempio per farti <strong>provare tutte le funzionalità
+        di Easy Gantt senza problemi</strong>. Ti consigliamo anche di guardare la
+        videoguida che ti mostrerà degli utili accorgimenti. Chiudendo questa finestra
+        si caricheranno i dati reali del progetto.
+      video_label: Guarda il video tutorial
+      video:
+        text: Qui puoi vedere la maggior parte delle funzionalità di questo modulo
+          Gantt
+        title: Video di esempio
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Una milestone impedisce lo spostamento del task oltre
+      questa data
+    text_blocker_move_pre: Il task ha una relazione che impedisce il suo spostamento
+      oltre questa data
+    title:
+      button_test: Pulsante Test (solo per testare)
+      day_zoom: Ingrandisci a mostrare la scaletta giornaliera
+      jump_today: Mostra la timeline aggiornata ad oggi
+      month_zoom: Ingrandisci a mostrare la scaletta mensile
+      print_fit: Ridimensiona Gantt a una singola pagina
+      week_zoom: Ingrandisci a mostrare la scaletta settimanale
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: Non sei autorizzato a vedere Easy Gantt
+  error_epm_easy_gantt_already_active: Easy Gantt risulta già attivo sulla pagina
+  field_easy_gantt_default_zoom: Ingrandimento predefinito
+  field_easy_gantt_relation_delay_in_workdays: Differimento di relazione nei giorni
+    lavorativi
+  field_easy_gantt_show_holidays: Mostra giorni festivi
+  field_easy_gantt_show_project_progress: Mostra l'avanzamento del progetto
+  field_easy_gantt_show_task_soonest_start: Mostra l'inizio più prossimo
+  field_keep_link_delay_in_drag: Ritardo di collegamento costante durante il trascinamento
+  field_relation: Relazione
+  heading_delay_popup: Definisci il ritardo in giorni
+  heading_demo_feature_popup: Disponibile a breve
+  heading_easy_gantts_issues: Easy Gantt Free
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Percorso critico
+  label_easy_gantt_settings: Impostazioni Gantt
+  label_filter_group_easy_gantt_easy_issue_query: Settori del task
+  label_finish_to_finish: Termina dalla fine
+  label_parent_issue_plural: Task padre
+  label_start_to_finish: Parti dalla fine
+  label_start_to_start: Parti dall'inizio
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Modifica Easy Gantt e Gestione risorse in un progetto
+  permission_edit_global_easy_gantt: Modifica Easy Gantt e Gestione risorse
+  permission_edit_personal_easy_gantt: Modifica Easy Gantt e Gestione risorse personali
+  permission_view_easy_gantt: Visualizza Easy Gantt e Gestione risorse applicati a
+    un progetto
+  permission_view_global_easy_gantt: Visualizza Easy Gantt e Gestione risorse
+  permission_view_personal_easy_gantt: Visualizza Easy Gantt e Gestione risorse personali
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Questa funzionalità sarà presto disponibile.
+  text_easy_gantt_footer: Redmine Gantt è offerto da Easy
+  text_easy_gantt_keep_link_delay_in_drag: Mantieni costanti i rapporti di ritardo
+    mentre il task viene spostato indietro
+  text_easy_gantt_print_easy_gantt_current: Mostra Easy Gantt attuale (funziona solo
+    da pagine in cui è caricato Easy Gantt)
+  text_easy_gantt_relation_delay_in_workdays: Non tenere conto dei giorni non lavorativi
+    nel differimento di relazione
+  text_easy_gantt_show_holidays: Mostra sul Gantt i giorni di ferie dell'utente (funziona
+    solo in Easy Redmine)
+  text_easy_gantt_show_project_progress: Mostra la percentuale di completamento sulla
+    barra del progetto (potrebbe risultare lenta da caricare)
+  text_easy_gantt_show_task_soonest_start: Mostra le date disponibili più vicine per
+    i task definiti da relazioni o dipendenza
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/ja.yml b/plugins/easy_gantt/config/locales/ja.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ee9e1410f1ab08bc14f6336bf49cba6d1abe5081
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/ja.yml
@@ -0,0 +1,147 @@
+---
+ja:
+  button_print: 印刷
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projects Gantt
+  button_use_actual_delay: 実際の遅延日数を使用
+  easy_gantt:
+    button:
+      close_all: すべて閉じる
+      close_all_parent_issues: すべての親タスクを閉じる
+      create_baseline: ベースライン
+      critical_path: クリティカルパス
+      day_zoom: 日数
+      delayed_project_filter: 遅延しているプロジェクトをフィルタする
+      jump_today: 今日に移動
+      load_sample_data: サンプルデータを読み込む
+      month_zoom: 月数
+      print_fit: ページに合わせる
+      problem_finder: 問題
+      reload: 再読み込み(保存)
+      remove_delay: 遅延を削除
+      resource_management: リソース・マネージメント
+      tool_panel: ツール
+      week_zoom: 週数
+    critical_path:
+      disabled: 無効
+      last: 最終
+      last_text: 遅延できないタスク
+      longest: 最長
+      longest_text: タスクの最長シーケンスを表示
+    errors:
+      duplicate_link: リンクのコピーを作成できません
+      fresh_milestone: 保存されていないマイルストーンにはタスクを追加できません。まず、マイルストーンを保存してください。
+      link_target_new: 保存されていないタスクにはリンクを作成できません。まず、変更を保存してください。
+      link_target_readonly: ターゲット・タスクは読み取り専用です
+      loop_link: 無限ループを作ってはなりません。
+      no_rest_api: このEasy Ganttを使うためには、REST APIを有効にする必要があります。管理⇒設定⇒APIと進み、REST Webサービスを有効にしてください。
+      overdue: あとで完了するか、すでにタスクはクローズされています
+      overmile: マイルストーンを保つには、%{effective date}に完了する必要があります。
+      short_delay: "%{diff} 日以上、必要です"
+      too_short: 予定作業時間%{rest}に対して、日数が不足しています
+      unsaved_parent: 保存されていない親タスクにタスクの追加はできません。まず、変更を保存してください
+      unsupported_link_type: サポートされていないリンクタイプです
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} はエラーのため、保存できませんでした"
+      send_failed: 要求は失敗しました
+    label_pro_upgrade: PROにアップグレード
+    link_dir:
+      link_end: 前の通り
+      link_start: 以下の通り
+    popup:
+      add_task:
+        heading: タスクの機能追加
+        text: 今回新たに追加されたタスク/マイルストーン機能は、Easy Gantt PROでのみ使用できます。\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">PROのアップデートされた機能については、こちらからご確認ください!</a>"
+      baseline:
+        heading: ベースライン機能
+        text: "ベースライン機能は、Easy Gantt PROでのみ使用できます。\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">PROのアップデートされた機能については、こちらからご確認ください!</a>"
+      critical:
+        heading: クリティカルパス機能
+        text: "クリティカルパス機能は、Easy Gantt PROでのみ使用できます。\r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">PROのアップデートされた機能については、こちらからご確認ください!</a>"
+      resource:
+        easy_text: Resource Management プラグインがインストールされていません。- <a href="https://www.easyredmine.com/pricing-buy">こちらから購入してください!</a>
+        heading: リソース・マネージャー機能
+        text: リソースマネージメント機能は<a href="https://www.easyredmine.com/">Easy Redmine</a>でのみ使用できます。
+    reload_modal:
+      label_errors: エラー
+      text_reload_appeal: 保存されていない変更を破棄して、サーバのデータを再読み込みしますか?
+      title: Ganttが正しく保存されませんでした。
+    sample_global_free:
+      text: 全プロジェクト横断管理ガントチャートはPROバージョンのみで使用可能です。
+      video:
+        text: 全プロジェクト横断管理ガントチャートの大半の機能はこちらから見ることができます。
+        title: 全プロジェクト横断管理ガントチャートのデモビデオ
+        video_id: zd3M0KYxcXM
+    sample:
+      close_label: このウィンドウを閉じて、実際のプロジェクトデータを読み込む
+      header: サンプルデータが読み込まれました!
+      text: "<strong>Easy Ganttの機能をストレスなく試していただくため</strong>、サンプルデータを用意しました。また、便利な使い方を説明したビデオガイドもご用意しています。このウィンドウを閉じると、実際のプロジェクトデータが読み込まれます。"
+      video_label: ビデオチュートリアルを見る
+      video:
+        text: このGanttモジュールの機能はこちらから見ることができます
+        title: サンプルビデオ
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: マイルストーンによってタスクの延期がブロックされています。
+    text_blocker_move_end: このタスクをこの日付以降に延期すると、以下のタスクのうち一つ以上のタスクが延期されます。
+    text_blocker_move_pre: このタスクはリレーションにより拘束されているため、このタスクをこの日付以降にずらすことはできません。
+    title:
+      button_test: テスト ボタン(テスト専用)
+      day_zoom: 日単位で表示
+      jump_today: 今日のチャートを表示する
+      month_zoom: 月単位で表示
+      print_fit: Ganttのチャートを1ページの大きさにする
+      week_zoom: 週単位で表示
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: Easy Ganttを見る権限がありません。
+  error_epm_easy_gantt_already_active: 既に有効になっています	このページ上のEasy Ganttはすでに有効になっています。
+  error_small_screen: ガントチャートを表示するには端末の画面が小さすぎます
+  field_easy_gantt_default_zoom: デフォルト・タイムライン
+  field_easy_gantt_fixed_delay: タスク間の固定ラグタイム
+  field_easy_gantt_relation_delay_in_workdays: 作業日数におけるリレーション全体のデレイ
+  field_easy_gantt_show_holidays: 休日の表示
+  field_easy_gantt_show_project_progress: プロジェクト進捗の表示
+  field_easy_gantt_show_task_soonest_start: 最も早い開始日を表示
+  field_keep_link_delay_in_drag: ドラッグ作業中、タスク間ラグタイムは変わりません
+  field_relation: リレーション
+  heading_delay_popup: 遅延を日数で確認
+  heading_demo_feature_popup: Coming soon
+  heading_easy_gantts_issues: Easy Gantt無料版
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: クリティカルパス
+  label_easy_gantt_critical_path: Critical path
+  label_easy_gantt_global: グローバル
+  label_easy_gantt_issue_loaded: 全タスク読み込み完了
+  label_easy_gantt_load: 読み込む
+  label_easy_gantt_recalculate_fixed_delay: タスク間の固定ラグタイムの再計算
+  label_easy_gantt_settings: Gantt設定
+  label_filter_group_easy_gantt_easy_issue_query: タスクフィールド
+  label_finish_to_finish: 終了‐終了
+  label_parent_issue_plural: 親タスク
+  label_start_to_finish: 開始‐終了
+  label_start_to_start: 開始‐開始
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  notice_easy_gantt_fixed_delay_recalculated: 関連付けられた全タスク間の固定ラグタイムが再計算完了
+  permission_edit_easy_gantt: 任意のプロジェクト上でEasy Gantt とResource Managementを編集する
+  permission_edit_global_easy_gantt: Easy Gantt(全体)とリソース・マネージメントを編集
+  permission_edit_personal_easy_gantt: Easy Gantt(個別)とリソース・マネージメントを編集
+  permission_view_easy_gantt: プロジェクトのEasy Ganttとリソース・マネージメントを閲覧
+  permission_view_global_easy_gantt: Easy Ganttチャート(全体)とリソース・マネージメントを閲覧
+  permission_view_personal_easy_gantt: Easy Ganttチャート(個別)とリソース・マネージメントを閲覧
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: 本機能はもうじき利用可能になります
+  text_easy_gantt_fixed_delay: ドラッグバック作業中、タスク間ラグタイムは変わりません
+  text_easy_gantt_footer: Easyにより運営されるRedmine Gantt
+  text_easy_gantt_keep_link_delay_in_drag: タスクがドラッグされてもタスク間の日程は維持
+  text_easy_gantt_print_easy_gantt_current: 現在のEasy Gantt を表示する(Easy Gantt が読み込まれたページからのみ可能です)
+  text_easy_gantt_relation_delay_in_workdays: リレーション遅延による非稼働日をカウントしない
+  text_easy_gantt_show_holidays: ユーザーの休日をGanttに表示する(Easy Redmineでのみ有効)
+  text_easy_gantt_show_project_progress: プロジェクトバー上に進捗率を表示する(読み込み速度が遅い可能性があり)
+  text_easy_gantt_show_task_soonest_start: 親タスク、またはリレーションから規定されるもっとも妥当性の低い日程を表示
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/ko.yml b/plugins/easy_gantt/config/locales/ko.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a3e3aa0502ed5c9c9f79c01f4abb4090be204538
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/ko.yml
@@ -0,0 +1,145 @@
+---
+ko:
+  button_print: 프린트
+  button_project_menu_easy_gantt: 쉬운 간트
+  button_top_menu_easy_gantt: 프로젝트 간트
+  button_use_actual_delay: 실제 지연일 사용
+  easy_gantt:
+    button:
+      close_all: 모두 닫기
+      close_all_parent_issues: 모든 상위작업 닫기
+      create_baseline: 베이스라인
+      critical_path: 중요한 경로
+      day_zoom: 일
+      delayed_project_filter: 지연된 프로젝트 필터
+      jump_today: 오늘로 이동하기
+      load_sample_data: 샘플 데이터 로딩
+      month_zoom: ì›”
+      print_fit: 페이지에 맞추기
+      problem_finder: 문제
+      reload: 다시 불러오기 (저장)
+      remove_delay: 지연 삭제
+      resource_management: 리소스 관리
+      tool_panel: 도구
+      week_zoom: 주
+    critical_path:
+      disabled: 비활성화
+      last: 지난
+      last_text: 지연되서는 안되는 작업들
+      longest: 가장 긴
+      longest_text: 가장 긴 작업 보이기
+    errors:
+      duplicate_link: 복제된 링크를 생성할 수 없습니다
+      fresh_milestone: 저장되지 않은 마일스톤에 작업을 추가 할 수 없습니다. 먼저 마일스톤을 저장하십시오.
+      link_target_new: 저장되지 않은 작업에 대한 링크를 만들 수 없습니다. 변경 사항을 먼저 저장하십시오.
+      link_target_readonly: 읽기전용 타겟 작업
+      loop_link: 루프된 링크를 생성할 수 없음
+      no_rest_api: Easy Gantt는 REST API를 활성화해야합니다. 관리 -> 설정 -> API -> REST 웹 서비스 순서로
+        활성화 하세요.
+      overdue: 이후에 종료하고 이미 종료했어야 합니다
+      overmile: 마일스톤을 유지하려면 %{effective_date}에 작업을 끝내야합니다.
+      short_delay: "%{diff}보다 길어야 합니다"
+      too_short: 예상 시간의 %{rest} 시간 동안 충분한 일 수가 없습니다.
+      unsaved_parent: 저장되지 ㅇ낳은 상위 작업을 작업에 추가할 수 없습니다. 변경사항을 먼저 저장하세요
+      unsupported_link_type: 지원하지 않는 링크 종류
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName}는 에러로 인해 저장하지 못했습니다"
+      send_failed: 요청 반려됨
+    label_pro_upgrade: PRO버전으로 업그레이드하기
+    link_dir:
+      link_end: 진행하는 동안
+      link_start: 다음과 같이
+    popup:
+      add_task:
+        heading: 작업 특성 추가
+        text: 새로운 작업 / 마일스톤 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">
+          업데이트를 확인하세요! </a>
+      baseline:
+        heading: 베이스라인 특징
+        text: 베이스 라인 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">
+          업데이트를 확인하세요! </a>
+      critical:
+        heading: 중요한 경로 특징
+        text: 중요 경로 기능은 Easy Gantt PRO에서만 사용할 수 있습니다. <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">
+          업데이트를 확인하세요! </a>
+      resource:
+        easy_text: 리소스 관리 플러그인이 설치되지 않았습니다. <a href="https://www.easyredmine.com/pricing-buy">
+          이곳을 클릭하고 설치하세요 </a>!
+        heading: 리소스 관리 특징
+        text: 리소스 관리 기능은 <a href="https://www.easyredmine.com/"> Easy Redmine </a>에서
+          사용하실 수 있습니다</a>
+    reload_modal:
+      label_errors: 에러
+      text_reload_appeal: 저장하지 않은 항목을 무시하고 서버에서 데이터를 다시 불러올까요?
+      title: 간트는 올바르게 저장할 수 없습니다
+    sample_global_free:
+      text: 샘플 데이터를 닫을 수 없습니다. 모든 프로젝트의 Gantt는 PRO 버전에서만 사용이 가능합니다.
+      video:
+        text: 여기서 모든 프로젝트에 대해 Gantt의 대부분 기능을 볼 수 있습니다.
+        title: 모든 프로젝트에 관한 간트 비디오 샘플
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: 이 창을 닫고 실제 프로젝트 데이터 불러오기
+      header: 샘플데이터가 로딩되었습니다!
+      text: "<strong> 스트레스 받지 않고 모든 Easy Gantt 기능을 사용해보세요 </ strong>를 위한 샘플 데이터가 준비되어
+        있습니다. 또한 유용한 조정을 보여주는 비디오 가이드를 보는 것을 추천하여 드립니다. 이 창을 닫으면 실제 프로젝트 데이터가 로딩됩니다."
+      video_label: 비디오 튜토리얼 보기
+      video:
+        text: 이 간트 모듈의 가장 최근 기능들을 확인하실 수 있습니다
+        title: 샘플 비디오
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: 마일스톤이 날짜 이후에 작업을 하지 못하도록 차단하게 되어 있습니다
+    text_blocker_move_pre: 작업이날짜 이후에 작업을 이동하지 못하도록 차단하는 상태로 있습니다.
+    title:
+      button_test: 테스트 버튼 (테스트용)
+      day_zoom: 요일별 확대
+      jump_today: 금일 타임라인 보기
+      month_zoom: 월별로 확대
+      print_fit: 간트 차트 한 페이지로 보이기
+      week_zoom: 주별로 확대
+  easy_printable_templates_categories:
+    easy_gantt: 쉬운 간트
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: 쉬운 간트
+      easy_gantt_easy_project_query: 글로벌 Easy Gantt
+  error_easy_gantt_view_permission: 이지간트 보기 권한을 가지고 있지 않습니다.
+  error_epm_easy_gantt_already_active: 페이지의 Easy Gantt는 이미 활성화 되어 있습니다
+  field_easy_gantt_default_zoom: 영구설정 확대
+  field_easy_gantt_relation_delay_in_workdays: 작업 날짜에서 관련 지연일
+  field_easy_gantt_show_holidays: 휴일 보이기
+  field_easy_gantt_show_project_progress: 프로젝트 과정 보이기
+  field_easy_gantt_show_task_soonest_start: 가장 빨리 시작하는 것 보이기
+  field_keep_link_delay_in_drag: 드래그 동안 지속적인 링크 지연
+  field_relation: 관련
+  heading_delay_popup: 지연 날짜 설정
+  heading_demo_feature_popup: 곧 출시됩니다
+  heading_easy_gantts_issues: 무료 쉬운 간트
+  label_easy_gantt: 쉬운 간트
+  label_easy_gantt_critical_path: 중요한 경로
+  label_easy_gantt_settings: 간트 설정
+  label_filter_group_easy_gantt_easy_issue_query: 작업 필드
+  label_finish_to_finish: 종료하기 종료
+  label_parent_issue_plural: 상위 작업
+  label_start_to_finish: 종료하기 시작
+  label_start_to_start: 시작하기 시작
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: 쉬운 간트 수정 & 프로젝트 리소스 관리
+  permission_edit_global_easy_gantt: 글로벌 쉬운 간트 수정 & 리소스 관리
+  permission_edit_personal_easy_gantt: 개별 쉬운 간트 수정 & 리소스 관리
+  permission_view_easy_gantt: 쉬운 간트 확인 & 프로젝트 리소스 관리
+  permission_view_global_easy_gantt: 글로벌 쉬운 간트 보기 & 리소스 관리
+  permission_view_personal_easy_gantt: 개별 쉬운 간트 확인 & 리소스 관리
+  project_default_page:
+    easy_gantt: 쉬운 간트
+  project_module_easy_gantt: 쉬운 간트
+  text_demo_feature_popup: 이 기능은 곧 사용가능합니다.
+  text_easy_gantt_footer: Easy가 지원하는 Redmine Gantt
+  text_easy_gantt_keep_link_delay_in_drag: 작업이 지연되는 동안 연장시간을 일관적으로 유지하십시오
+  text_easy_gantt_print_easy_gantt_current: 현재 쉬운 간트 표 보기 (쉬운 간트가 표시된 페이지에만 작업하실 수
+    있습니다)
+  text_easy_gantt_relation_delay_in_workdays: 관련 지연에서 일하지 않은 날은 세지 않기
+  text_easy_gantt_show_holidays: 간트에 있는 현재 사용자 휴일 보기 (Easy Redmine에서만 확인할 수 있습니다)
+  text_easy_gantt_show_project_progress: 프로젝트 바에서 완료율 보기 (로딩하는데 시간이 걸릴 수 있습니다)
+  text_easy_gantt_show_task_soonest_start: 관련 혹은 상위그룹으로 정렬된 작업의 가장 적은 유효날짜 표시
+  title_easy_gantt_settings: 쉬운 간트
diff --git a/plugins/easy_gantt/config/locales/mk.yml b/plugins/easy_gantt/config/locales/mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0ae10927d4e23c4b03326d1b5d63b519da6d0cae
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/mk.yml
@@ -0,0 +1,2 @@
+---
+mk: 
diff --git a/plugins/easy_gantt/config/locales/nl.yml b/plugins/easy_gantt/config/locales/nl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..14f7affba2b05e9f160152749e9e10bf354a1ed6
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/nl.yml
@@ -0,0 +1,161 @@
+---
+nl:
+  button_print: Afdrukken
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projecten Gantt
+  button_use_actual_delay: Gebruik actuele vertraging
+  easy_gantt:
+    button:
+      close_all: Sluit alle
+      close_all_parent_issues: Sluit alle overkoepelende taken
+      create_baseline: Baselines
+      critical_path: Kritiek pad
+      day_zoom: Dagen
+      delayed_project_filter: Filter Vertraagde  Projecten
+      jump_today: Ga naar vandaag
+      load_sample_data: Laad sample gegevens
+      month_zoom: Maanden
+      print_fit: Aangepast aan pagina
+      problem_finder: Problemen
+      reload: Herladen (opslaan)
+      remove_delay: Verwijder vertraging
+      resource_management: Resource management
+      tool_panel: Tools
+      week_zoom: Weken
+    critical_path:
+      disabled: Uitgeschakeld
+      last: Laatste
+      last_text: Taken die niet vertraagd mogen worden
+      longest: Langste
+      longest_text: Toon langste sequentie taken
+    errors:
+      duplicate_link: Kan link niet dupliceren
+      fresh_milestone: Kan geen taak toevoegen aan niet-opgeslagen mijlpaal. Sla eerst
+        de mijlpaal op.
+      link_target_new: Kan geen link maken naar niet-opgeslagen taak. Sla eerst de
+        wijzigingen op.
+      link_target_readonly: Alleen lezen doeltaak
+      loop_link: Kan geloopte link niet aanmaken
+      no_rest_api: Voor Easy Gantt moet REST API ingeschakeld zijn. Zet het aan bij  Administratie
+        -> Instellingen -> API -> REST web service inschakelen
+      overdue: zou in de toekomst moeten eindigen of al gesloten moeten zijn
+      overmile: moet eindigen op %{effective_date} om mijlpaal te houden
+      short_delay: Moet %{diff} dagen langer zijn
+      too_short: bevat niet genoeg dagen voor %{rest} uren geschatte tijd
+      unsaved_parent: Kan geen taak toevoegen aan niet opgeslagen overkoepeling. Sla
+        de wijzigingen eerst op.
+      unsupported_link_type: Niet ondersteund type link
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} niet opgeslagen door fout"
+      send_failed: Verzoek mislukt
+    label_pro_upgrade: Upgraden naar PRO versie
+    link_dir:
+      link_end: als voorgaande
+      link_start: als volgt
+    popup:
+      add_task:
+        heading: Voeg eigenschap taak toe
+        text: "Nieuwe taak/mijlpaal eigenschap is alleen beschikbaar in Easy Gantt
+          PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Controleer
+          voor een update!</a>"
+      baseline:
+        heading: Baselines functie
+        text: "De Baselines functie is alleen beschikbaar in Easy Gantt PRO \r\n<a
+          href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Controleer
+          voor een update!</a>"
+      critical:
+        heading: Kritiek pad functie
+        text: "Kritiek pad functie is alleen beschikbaar in Easy Gantt PRO \r\n<a
+          href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Kijk hier
+          voor een update!</a>"
+      resource:
+        easy_text: Resource management plugin is niet geïnstalleerd - krijg hem <a
+          href="https://www.easyredmine.com/pricing-buy">hier!</a>
+        heading: Resource manager functie
+        text: Resource management functie is alleen beschikbaar in <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Fouten
+      text_reload_appeal: Wilt u niet opgeslagen items negeren en data opnieuw laden
+        van de server?
+      title: Gantt niet juist opgeslagen
+    sample_global_free:
+      text: Sample data kunnen niet gesloten worden. Gantt over alle projecten is
+        alleen beschikbaar in de PRO versie
+      video:
+        text: Hier zie je de meeste functies van Gantt in alle projecten
+        title: Sample video van Gantt over alle projecten
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Sluit dit venster en laad echte projectgegevens
+      header: Sample data zijn geladen!
+      text: We hebben wat sample gegevens voorbereid om <strong>alle Easy Gantt functies
+        stress-vrij te proberen</strong>. We adviseren ook om een video met wat nuttige
+        tips te bekijken. Het sluiten van dit venster laadt echte project gegevens.
+      video_label: Bekijk video tutorial
+      video:
+        text: Hier ziet u de meeste functies van de Gantt module
+        title: Sample video
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Mijlpaal blokkeert taak om verplaatst te worden na deze
+      datum
+    text_blocker_move_pre: Taak heeft een relatie die het verschuiven van deze taak
+      na deze datum blokkeert
+    title:
+      button_test: Test button (alleen voor testen)
+      day_zoom: Zoom naar dagweergave
+      jump_today: Geef tijdlijn voor vandaag weer
+      month_zoom: Zoom naar maandelijkse weergave
+      print_fit: Schaal gantt tot een pagina
+      week_zoom: Zoom naar wekelijkse weergave
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Global Easy Gantt
+  error_easy_gantt_view_permission: U heeft geen toestemming om Easy Gantt te bekijken
+  error_epm_easy_gantt_already_active: Easy gantt is al actief op de pagina
+  field_easy_gantt_default_zoom: Standaard zoom
+  field_easy_gantt_relation_delay_in_workdays: Relatie vertragingen in werkdagen
+  field_easy_gantt_show_holidays: Toon vakanties
+  field_easy_gantt_show_project_progress: Toon projectvoortgang
+  field_easy_gantt_show_task_soonest_start: Toon eerste start
+  field_keep_link_delay_in_drag: Constante link vertraging tijdens slepen
+  field_relation: Relatie
+  heading_delay_popup: Definieer vertraging in dagen
+  heading_demo_feature_popup: Binnenkort
+  heading_easy_gantts_issues: Easy Gantt Gratis
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Kritiek pad
+  label_easy_gantt_settings: Gantt instellingen
+  label_filter_group_easy_gantt_easy_issue_query: Taken velden
+  label_finish_to_finish: Finish om af te ronden
+  label_parent_issue_plural: Ouder kwesties
+  label_start_to_finish: Start tot finish
+  label_start_to_start: Start om te beginnen
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-naar-pro
+  permission_edit_easy_gantt: Bewerk Easy Gantt & Resource management op een project
+  permission_edit_global_easy_gantt: Bewerk globaal Easy Gantt & Resource management
+  permission_edit_personal_easy_gantt: Bewerk persoonlijk Easy Gantt & Resource management
+  permission_view_easy_gantt: Bekijk Easy Gantt & Resource management op een project
+  permission_view_global_easy_gantt: Bekijk globaal Easy Gantt & Resource management
+  permission_view_personal_easy_gantt: Bekijk persoonlijk Easy Gantt & Resource management
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Deze functie is binnenkort beschikbaar
+  text_easy_gantt_footer: Redmine Gantt mogelijk gemaakt door Easy
+  text_easy_gantt_keep_link_delay_in_drag: Hou relatievertraging constant terwijl
+    taak teruggedraaid wordt
+  text_easy_gantt_print_easy_gantt_current: Toon huidige Easy Gantt (werkt alleen
+    op pagina's waar easy gantt is geladen)
+  text_easy_gantt_relation_delay_in_workdays: Tel niet-werkdagen niet mee in relatie
+    vertraging
+  text_easy_gantt_show_holidays: Toon vakantie van huidige gebruiker op gantt (werkt
+    alleen in Easy Redmine)
+  text_easy_gantt_show_project_progress: Bekijk voortgangspercentage op de balk van
+    het project (laadt wellicht langzaam)
+  text_easy_gantt_show_task_soonest_start: Toon laagst geldige data voor taken gedefinieerd
+    door relaties of ouder
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/no.yml b/plugins/easy_gantt/config/locales/no.yml
new file mode 100644
index 0000000000000000000000000000000000000000..38901c6667cbd0aed63c1f7b5bcb733803675b36
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/no.yml
@@ -0,0 +1,2 @@
+---
+'no': 
diff --git a/plugins/easy_gantt/config/locales/pl.yml b/plugins/easy_gantt/config/locales/pl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..40003b02eb0034f913f6e4d5cc357590f4742c6d
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/pl.yml
@@ -0,0 +1,162 @@
+---
+pl:
+  button_print: Drukuj
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: Projekty Gantt
+  button_use_actual_delay: Użyj aktualnego opóźnienia
+  easy_gantt:
+    button:
+      close_all: Zamknij wszystko
+      close_all_parent_issues: Zamknij wszystkie zadania nadrzędne
+      create_baseline: Bazy
+      critical_path: Ścieżka krytyczna
+      day_zoom: Dni
+      delayed_project_filter: Filtruj opóźnione projekty
+      jump_today: Skok do dzisiaj
+      load_sample_data: Załaduj proste dane
+      month_zoom: MiesiÄ…ce
+      print_fit: Dopasuj do strony
+      problem_finder: Problemy
+      reload: Przeładuj (zapisz)
+      remove_delay: Usuń opóźnienie
+      resource_management: ZarzÄ…dzanie zasobami
+      tool_panel: Narzędzia
+      week_zoom: Tygodnie
+    critical_path:
+      disabled: Wyłączony
+      last: Ostatni
+      last_text: Zadania, które nie powinny być opóźnione
+      longest: Najdłuższy
+      longest_text: Pokaż najdłuższą sekwencję zadań
+    errors:
+      duplicate_link: Nie można utworzyć zduplikowanego linku
+      fresh_milestone: Nie można dodać zadania do niezapisanych kroków milowych. Najpierw
+        zapisz krok milowy.
+      link_target_new: Nie można utworzyć linku do niezapisanego zadania. Najpierw
+        zapisz zmiany.
+      link_target_readonly: Docelowe zadanie jedynie do odczytu
+      loop_link: Nie można utworzyć zapętlonego linku
+      no_rest_api: Easy Gantt potrzebuje włączonego API REST. Włącz go w Administracja
+        -> Ustawienia -> API -> Włącz usługę sieciową REST
+      overdue: powinno się zakończyć w przyszłości lub być już zamknięte
+      overmile: powinno zakończyć się %{effective_date}, aby zachować krok milowy
+      short_delay: powinno być dłuższe niż %{diff}  dni
+      too_short: nie zawiera wystarczajÄ…cej liczby dni dla %{rest} godzin oszacowanego
+        czasu
+      unsaved_parent: Nie można dodać zadania do niezapisanego zadania nadrzędnego.
+        Najpierw zapisz zmiany
+      unsupported_link_type: Nieobsługiwany typ linku
+    gateway:
+      entity_save_failed: Nie powiodło się zapisanie %{entityType} %{entityName} z
+        powodu błędu
+      send_failed: Wymagane pole
+    label_pro_upgrade: Zaktualizuj do wersji PRO
+    link_dir:
+      link_end: jak poprzedni
+      link_start: jak następujący
+    popup:
+      add_task:
+        heading: Dodaj funkcjÄ™ zadania
+        text: "Funkcja nowego zadania/kroku milowego jest dostępna jedynie w Easy
+          Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Sprawdź
+          aktualizacjÄ™!</a>"
+      baseline:
+        heading: Funkcja baz
+        text: "Funkcja baz jest dostępna jedynie w Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Sprawdź
+          aktualizacjÄ™!</a>"
+      critical:
+        heading: Funkcja ścieżki krytycznej
+        text: "Funkcja ścieżki krytycznej jest dostępna jedynie w Easy Gantt PRO \r\n<a
+          href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Sprawdź aktualizację!</a>"
+      resource:
+        easy_text: Wtyczka zarzÄ…dzania zasobami nie jest zainstalowana - pobierz jÄ…
+          <a href="https://www.easyredmine.com/pricing-buy">tutaj!</a>
+        heading: Funkcja menedżera zasobów
+        text: Funkcja ścieżki krytycznej jest dostępna jedynie w <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Błędy
+      text_reload_appeal: Czy chcesz zignorować niezapisane pozycje i ponownie załadować
+        dane z serwera?
+      title: Poprawne zapisanie Gantta nie powiodło się
+    sample_global_free:
+      text: Przykładowe dane nie mogą zostać zamknięte. Gantt w stosunku do innych
+        projektów jest dostępny jedynie w wersji PRO
+      video:
+        text: Tutaj możesz zobaczyć większość funkcji Gantta w stosunku do wszystkich
+          projektów
+        title: Przykładowe wideo Gantta w stosunku do wszystkich projektów
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Zamknij to okno i załaduj prawdziwe dane projektowe
+      header: Przykładowe dane są załadowane!
+      text: Przygotowaliśmy dla ciebie pewne przykładowe dane w celu <strong>wypróbowania
+        wszystkich  funkcji Easy Gantt bez stresu</strong>.
+      video_label: Zobacz samouczek filmowy
+      video:
+        text: Tutaj możesz zobaczyć większość funkcji tego modułu Gantta
+        title: Przykładowy film
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Krok milowy blokuje przeniesienie zadania poza tÄ™ datÄ™
+    text_blocker_move_pre: Zadanie posiada relację, która blokuje przesunięcie go
+      poza tÄ™ datÄ™
+    title:
+      button_test: Przycisk testowy (tylko dla testowania)
+      day_zoom: Powiększ do skali dni
+      jump_today: Wyświetl linię czasu dla dzisiaj
+      month_zoom: Powiększ do skali miesiąca
+      print_fit: Skaluj Gantta na jednÄ… stronÄ™
+      week_zoom: Powiększ do skali tygodnia
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Globalny Easy Gantt
+  error_easy_gantt_view_permission: Nie masz uprawnień do zobaczenia Easy Gantt
+  error_epm_easy_gantt_already_active: Easy Gantt jest już aktywny na stronie
+  field_easy_gantt_default_zoom: Domyślne powiększenie
+  field_easy_gantt_relation_delay_in_workdays: Opóźnienie relacji w dniach roboczych
+  field_easy_gantt_show_holidays: Pokaż urlopy
+  field_easy_gantt_show_project_progress: Pokaż postęp projektu
+  field_easy_gantt_show_task_soonest_start: Pokaż najwcześniejsze rozpoczęcie
+  field_keep_link_delay_in_drag: Stałe opóźnienie linku podczas przeciągania
+  field_relation: Relacja
+  heading_delay_popup: Określ opóźnienie w dniach
+  heading_demo_feature_popup: Wkrótce
+  heading_easy_gantts_issues: Bezpłatny Easy Gantt
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Ścieżka krytyczna
+  label_easy_gantt_settings: Ustawienia Gantta
+  label_filter_group_easy_gantt_easy_issue_query: Pole zadania
+  label_finish_to_finish: Zakończ aby zakończyć
+  label_parent_issue_plural: Zadania nadrzędne
+  label_start_to_finish: Rozpocznij aby zakończyć
+  label_start_to_start: Zacznij aby zacząć
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Edytuj Easy Gantt i ZarzÄ…dzanie zasobami w projekcie
+  permission_edit_global_easy_gantt: Edytuj globalne Easy Gantt i ZarzÄ…dzanie zasobami
+  permission_edit_personal_easy_gantt: Edytuj personalne Easy Gantt i ZarzÄ…dzanie
+    zasobami
+  permission_view_easy_gantt: Zobacz Easy Gantt i ZarzÄ…dzanie zasobami w projekcie
+  permission_view_global_easy_gantt: Zobacz globalne Easy Gantt i ZarzÄ…dzanie zasobami
+  permission_view_personal_easy_gantt: Zobacz personalne Easy Gantt i ZarzÄ…dzanie
+    zasobami
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Ta funkcja będzie dostępna wkrótce.
+  text_easy_gantt_footer: Redmine Gantt powered by Easy
+  text_easy_gantt_keep_link_delay_in_drag: Utrzymaj stałe opóźnienie relacji gdy zadanie
+    jest przeciÄ…gane z powrotem
+  text_easy_gantt_print_easy_gantt_current: Pokaż obecny Easy Gantt (działa tylko
+    na stronie, na której jest załadowany Easy Gantt)
+  text_easy_gantt_relation_delay_in_workdays: Nie bierz pod uwagÄ™ nieroboczych dni
+    w opóźnieniu relacji
+  text_easy_gantt_show_holidays: Pokaż urlop obecnego użytkownika na Gantt (działa
+    jedynie w Easy Redmine)
+  text_easy_gantt_show_project_progress: Pokaż zakończony procent na pasku projektu
+    (pobranie może być wolne)
+  text_easy_gantt_show_task_soonest_start: Pokaż najbliższe ważne daty dla zadań określonych
+    przez relacje lub zadanie nadrzędne
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/pt-BR.yml b/plugins/easy_gantt/config/locales/pt-BR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c63a3d9b18e76464464f319a944f1fae450ae79
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/pt-BR.yml
@@ -0,0 +1,163 @@
+---
+pt-BR:
+  button_print: Imprimir
+  button_project_menu_easy_gantt: Diagrama de Easy Gantt
+  button_top_menu_easy_gantt: Projetos diagrama de Gantt
+  button_use_actual_delay: Use atraso real
+  easy_gantt:
+    button:
+      close_all: Fechar todos
+      close_all_parent_issues: Fechar todas tarefas principais
+      create_baseline: Linhas de base
+      critical_path: Trajeto crítico
+      day_zoom: Dias
+      delayed_project_filter: Filtrar Projetos Adiados
+      jump_today: Saltar para hoje
+      load_sample_data: Carregar dados de amostra
+      month_zoom: Meses
+      print_fit: Ajustar à página
+      problem_finder: Problemas
+      reload: Recarregar (salvar)
+      remove_delay: Retirar atraso
+      resource_management: Gestão de recursos
+      tool_panel: Ferramentas
+      week_zoom: Semanas
+    critical_path:
+      disabled: Desativado
+      last: Último
+      last_text: Tarefas que não devem ser adiada
+      longest: Mais longo
+      longest_text: Mostrar sequência mais longa
+    errors:
+      duplicate_link: Não é possível duplicar link
+      fresh_milestone: Não é possível adicionar tarefa a marcos não-salvos. Primeiro
+        salve o marco.
+      link_target_new: Não é possível criar tarefas não salvas. Salve as mudanças
+        primeiro.
+      link_target_readonly: Tarefa alvo somente para leitura
+      loop_link: Não é possível criar link em repetição
+      no_rest_api: Diagrama de Easy Gantt precisa de REST API ativado. Ligar em Administração
+        ->Configurações -> API -> Habilitar serviço web REST
+      overdue: deve terminar no futuro ou já deve estar fechado
+      overmile: Deve terminar em %{effective_date} a fim de manter o marco
+      short_delay: deve ser mais longo que %{diff} dias
+      too_short: Não contém suficiente dias para %{rest} horas de tempo estimado
+      unsaved_parent: Não é possível adicionar tarefa  ao principal não-salvos. Primeiro
+        salvar as alterações.
+      unsupported_link_type: Tipo de link não assistido
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} falha ao salvar devido a erro"
+      send_failed: Falha na solicitação
+    label_pro_upgrade: Atualizar para a versão PRO
+    link_dir:
+      link_end: como precedendo
+      link_start: como a seguir
+    popup:
+      add_task:
+        heading: Adicionar recurso tarefa
+        text: Novo recurso tarefa/marco está disponível apenas em Easy Gantt PRO <a
+          href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Check for update!</a>
+      baseline:
+        heading: Caracteristica de linhas de base
+        text: Caracteristica de linhas de base está disponível em Easy Gantt PRO <a
+          href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Verifique se
+          há atualização!</a>
+      critical:
+        heading: Caracteristica de trajeto crítico
+        text: Recurso de trajeto crítico somente está disponível em Easy Gantt PRO
+          <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">Verificar
+          se há atualização!</a>
+      resource:
+        easy_text: Plugin gerente de recursos não está instalado - obter plugin <a
+          href="https://www.easyredmine.com/pricing-buy">aqui!</a>
+        heading: Recurso gerente de recursos
+        text: Recurso gerente de recursos só está disponível em <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Erros
+      text_reload_appeal: Você quer ignorar itens que não foram salvos e recarregar
+        os dados do servidor?
+      title: Gantt não salvou corretamente
+    sample_global_free:
+      text: Dados de amostra não podem ser fechados. Projetos gerais Gantt só estão
+        disponíveis na versão PRO
+      video:
+        text: Aqui você pode ver a maioria das características de projetos gerais
+          Gantt
+        title: Video de amostra de projetos gerais Gantt
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Fechar a janela e carregar os dados reais de projeto
+      header: Os dados da amostra estão carregados!
+      text: Nós preparamos alguns exemplos de dados para você <strong> experimentar
+        todos os recursos Easy Gantt sem estresse </strong>. Nós também queremos encorajá-lo
+        a assistir a um vídeo gia mostrando ajustes úteis. Ao fechar esta janela irá
+        carregar os dados de projeto real.
+      video_label: Assistir video tutorial
+      video:
+        text: Aqui você pode ver a maioria das funcionalidades deste módulo Gantt
+        title: Video exemplo
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: Marco está bloqueando tarefa a ser deslocado para além
+      desta data
+    text_blocker_move_pre: Tarefa tem relação que bloqueia mover tarefa para além
+      desta data
+    title:
+      button_test: Botão de teste (somente para teste)
+      day_zoom: Aumentar a escala de dia
+      jump_today: Exibir linha do tempo em hoje
+      month_zoom: Fazer zoom a escala mês
+      print_fit: Escale o Gantt para uma página
+      week_zoom: Fazer zoom a escala semana
+  easy_printable_templates_categories:
+    easy_gantt: Easy Gantt
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: Easy Gantt Global
+  error_easy_gantt_view_permission: Você não tem permissão para visualizar o Easy
+    Gantt
+  error_epm_easy_gantt_already_active: Easy Gantt na página já está ativado.
+  field_easy_gantt_default_zoom: Padrão de zoom
+  field_easy_gantt_relation_delay_in_workdays: Relação atrasos nos dias úteis
+  field_easy_gantt_show_holidays: Mostrar férias
+  field_easy_gantt_show_project_progress: Mostrar progresso do projeto
+  field_easy_gantt_show_task_soonest_start: Mostrar início mais breve
+  field_keep_link_delay_in_drag: Constante atraso de conexão durante o arrasto
+  field_relation: Relação
+  heading_delay_popup: Definir atraso em dias
+  heading_demo_feature_popup: Em breve
+  heading_easy_gantts_issues: Easy Gantt grátis
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: Trajeto crítico
+  label_easy_gantt_settings: Configurações Gantt
+  label_filter_group_easy_gantt_easy_issue_query: Campos de tarefas
+  label_finish_to_finish: Final ao final
+  label_parent_issue_plural: Problemas principais
+  label_start_to_finish: Início ao final
+  label_start_to_start: Início ao início
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Editar Easy Gantt & Gestão de recursos em um projeto
+  permission_edit_global_easy_gantt: Editar Easy Gantt & Gestão de recursos
+  permission_edit_personal_easy_gantt: Editar Easy Gant pessoal & Gestão de recursos
+  permission_view_easy_gantt: Visualizar Easy Gantt & Gestão de recursos em um projeto
+  permission_view_global_easy_gantt: Visualizar Easy Gantt global & Gestão de recursos
+  permission_view_personal_easy_gantt: Visualizar Easy Gant geral & Gestão de recursos
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: Esta funcionalidade estará disponível em breve.
+  text_easy_gantt_footer: Redmine Gantt distribuído por Easy
+  text_easy_gantt_keep_link_delay_in_drag: Manter atraso de relação constante enquanto
+    a tarefa é arrastado de volta
+  text_easy_gantt_print_easy_gantt_current: Mostrar Easy Gantt atual (somente funciona
+    a partir da página onde o Easy Gantt for carregado)
+  text_easy_gantt_relation_delay_in_workdays: Não contar os dias não úteis em relação
+    ao atraso
+  text_easy_gantt_show_holidays: Mostrar férias atuais de usuário em Gantt (funciona
+    em Easy Redmine)
+  text_easy_gantt_show_project_progress: Mostrar porcentagem completa na barra do
+    projeto (pode ser lento para carregar)
+  text_easy_gantt_show_task_soonest_start: Mostrar datas válidas mais baixas para
+    as tarefas definidas por equações ou principal
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/locales/pt.yml b/plugins/easy_gantt/config/locales/pt.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e40c4851af3aac216594db949527057b830ec1cf
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/pt.yml
@@ -0,0 +1,2 @@
+---
+pt: 
diff --git a/plugins/easy_gantt/config/locales/ro.yml b/plugins/easy_gantt/config/locales/ro.yml
new file mode 100644
index 0000000000000000000000000000000000000000..583d9e1cebe676ea3c2bc4686b372a9e27014081
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/ro.yml
@@ -0,0 +1,19 @@
+---
+ro:
+  button_print: Imprimare
+  button_project_menu_easy_gantt: Gantt Simplu
+  button_top_menu_easy_gantt: Proiecte Gantt
+  easy_gantt:
+    button:
+      close_all_parent_issues: Închide toate sarcinile sursă
+      day_zoom: Zile
+      jump_today: Sari la azi
+      month_zoom: Luni
+      reload: Reincarca (salveaza)
+      tool_panel: Instrumente
+      week_zoom: Săptămani
+  field_easy_gantt_default_zoom: Zoom implicit
+  field_easy_gantt_show_holidays: Arată vacanţe
+  field_easy_gantt_show_project_progress: Arată progresul proiectului
+  field_relation: Relaţie
+  label_easy_gantt: Gantt Simplu
diff --git a/plugins/easy_gantt/config/locales/ru.yml b/plugins/easy_gantt/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..894d4b7009aa7c41499bd158ca9ab534e1e63227
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/ru.yml
@@ -0,0 +1,186 @@
+---
+ru:
+  button_print: Печать
+  button_project_menu_easy_gantt: Диаграмма Ганта Easy
+  button_top_menu_easy_gantt: Диаграмма Ганта
+  button_use_actual_delay: Использовать фактическую задержку
+  easy_gantt_toolbar:
+    day: Дни
+    month: Месяцы
+    week: Недели
+  easy_gantt:
+    buton_create_baseline: Базовый план
+    button_critical_path: Критический путь
+    button_resource_management: Управление персоналом
+    button:
+      close_all: Закрыть все
+      close_all_parent_issues: Закрыть все родительские задачи
+      create_baseline: Базовый план
+      critical_path: Критический путь
+      day_zoom: Дни
+      delayed_project_filter: Фильтр Просроченные Проекты
+      jump_today: Сегодня
+      load_sample_data: Загрузить демо-данные
+      month_zoom: Месяцы
+      print_fit: Подогнать
+      problem_finder: Ошибки
+      reload: Перезагрузить (сохранить)
+      remove_delay: Удалить задержку
+      resource_management: Управление персоналом
+      tool_panel: Инструменты
+      week_zoom: Недели
+    critical_path:
+      disabled: Отключен
+      last: Короткий
+      last_text: Первоочередные задачи
+      longest: Длинный
+      longest_text: Самая длинная последовательность задач
+    error_overmile: Дата выполнения задачи должна находиться до даты Вехи (Версии)
+    error_too_short: Залача слишком короткая для рассчета запланированного времени
+    errors:
+      duplicate_link: Нельзя создать двойную ссылку
+      fresh_milestone: Нельзя добавить задачу к несохраненной вехе. Сначала сохраните
+        веху.
+      link_target_new: Нельзя добавить связь к несохраненной задаче. Сохраните изменения.
+      link_target_readonly: Задача доступна только для чтения
+      loop_link: Невозможно создать цикличную связь
+      no_rest_api: Для работы Easy Gantt требуется REST API. Перейдите в Администрирование->
+        Настройки -> API -> Включить веб-сервис REST
+      overdue: должна заканчиваться в будущем или должна быть уже закрыта
+      overmile: Дата выполнения задачи должна находиться до даты Вехи (Версии)
+      short_delay: должна быть длиннее на  %{diff} дней
+      too_short: недостаточно дней для  %{rest}  часов запланированного времени
+      unsaved_parent: Невозможно добавить задачу к несохраненной родительской задаче.
+        Пожалуйста, сохраните сначала родительскую задачу.
+      unsupported_link_type: Данный тип связи не поддерживатеся
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName} не удалось сохранить из-за
+        ошибки"
+      send_failed: Ошибка запроса
+    label_pro_upgrade: Обновление до PRO-версии
+    link_dir:
+      link_end: предшествует
+      link_start: следует за
+    popup:
+      add_task:
+        heading: Функция "Добавить задачу"
+        text: "Новая задача/веха доступна только в Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      baseline:
+        heading: Функция "Базовый план"
+        text: "Базовый план доступен только в Easy Gantt PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Check
+          for update!</a>"
+      critical:
+        heading: Фунция - Критический путь
+        text: "Критический путь доступен только в Easy Гант PRO \r\n<a href=\"https://www.easyredmine.com/easy-gantt-upgrade-to-pro\">Проверьте
+          обновления!</a>"
+      resource:
+        easy_text: Плагин Resource management не установлен - <a href="https://www.easyredmine.com/pricing-buy">Перейдите!</a>
+        heading: Функция  управления  персоналом
+        text: Resource management доступен только в <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    reload_modal:
+      label_errors: Ошибки
+      text_reload_appeal: Вы действительно хотите не сохранить элементы и перезагрузить
+        данные с сервера?
+      title: Не удалось правильно сохранить Гант
+    sample_global_free:
+      text: Демо-данные не могут быть удалены. Гант для всех проектов доступен только
+        в PRO версии.
+      video:
+        text: Здесть вы можете увидеть большинство возможностей Ганта для всех проектов
+        title: Видео-демонстрация возможностей Ганта для всех проектов.
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: Закройте данное окно и загрузите реальные данные по проекту.
+      header: Демо-данные загружены!
+      text: Мы подготовили специально демо-данные - <strong>попробуйте все возможности
+        Easy Гант без ущерба для Вас!</strong>. Мы также предлагаем вам просмотреть
+        видео-инструкцию, показывающее особенности работы. При закрытии данного окна
+        будут загружены реальные данные по проекту.
+      video_label: Смотреть видео-инструкцию
+      video:
+        text: Здесь вы можете ознакомиться с большинством функций модуля Ганта.
+        title: Демо-видео
+        video_id: UHgqfsrD59Q
+    soon:
+      add_task:
+        heading: Функция Дабавить задачу
+        text: Эта функция скоро появится в продукции. <a href="https://www.easyredmine.com/redmine-gantt-plugin">Проверить
+          доступность!</a>
+      baseline:
+        heading: Функция Базовый План
+        text: Функция "Базовый План" скоро появится в продукции. <a href="https://www.easyredmine.com/redmine-gantt-plugin>Проверить
+          доступность!</a>
+      critical:
+        heading: Функция Критический Путь
+        text: "Функция Критический путь скоро появится в продукции.\r\n<a href=\"https://www.easyredmine.com/redmine-gantt-plugin\">Проверить
+          доступность!</a>"
+      resource:
+        heading: Функция Управление Персоналом
+        text: Функция Управление Персоналом доступна только в <a href="https://www.easyredmine.com/">
+          Easy Redmine</a>
+    text_blocker_milestone: Веха блокирует перемещение задачи вне данной даты
+    text_blocker_move_pre: У данной задачи есть связь, которая не позволяет передвинуть
+      ее вне этой даты
+    title:
+      button_test: Тест (только для тестирования)
+      day_zoom: Отображение временной шкалы по дням
+      jump_today: Показать на графике сегодня
+      month_zoom: Отображение временной шкалы по месяцам
+      print_fit: Вписать диаграмму в страницу
+      week_zoom: Отображение временной шкалы по неделям
+  easy_printable_templates_categories:
+    easy_gantt: Диаграмма Ганта Easy
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Гант
+      easy_gantt_easy_project_query: Глобальный Easy Гант
+  error_easy_gantt_view_permission: У вас нет доступа для просмотра Easy Gantt
+  error_epm_easy_gantt_already_active: Easy Гант уже присутствует на этой странице
+  field_easy_gantt_default_zoom: Масштаб по умолчанию
+  field_easy_gantt_relation_delay_in_workdays: Задержка в рабочих днях
+  field_easy_gantt_show_holidays: Показать нерабочие дни
+  field_easy_gantt_show_project_progress: Показать прогресс проекта
+  field_easy_gantt_show_task_soonest_start: Показать скорейшее начало
+  field_keep_link_delay_in_drag: Сохранять задержку при перемещении
+  field_relation: Связь
+  heading_delay_popup: Задать задержку (в днях)
+  heading_demo_feature_popup: Скоро в продукции
+  heading_easy_gantts_issues: Гант по задачам
+  label_easy_gantt: Easy Гант
+  label_easy_gantt_critical_path: Критический путь
+  label_easy_gantt_settings: Настройки Ганта
+  label_filter_group_easy_gantt_easy_issue_query: Поля задачи
+  label_finish_to_finish: Финиш --> Финиш
+  label_parent_issue_plural: Родительские задачи
+  label_start_to_finish: Начало-Окончание
+  label_start_to_start: Старт --> Старт
+  link_easy_gantt_footer: https://www.easyredmine.com/redmine-gantt-plugin
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: Редактировать Easy Гант & Resource management на проекте
+  permission_edit_global_easy_gantt: Редактировать глобальный Easy Гант & Resource
+    management
+  permission_edit_personal_easy_gantt: Реадактировать персональный Easy Gantt & Resource
+    management
+  permission_view_easy_gantt: Отображать Easy Гант & Resource management на проекте
+  permission_view_global_easy_gantt: Отображать глобальный Easy Гант & Resource management
+  permission_view_personal_easy_gantt: Отображать персональный Easy Гант & Resource
+    management
+  project_default_page:
+    easy_gantt: Easy Гант
+  project_module_easy_gantt: Easy Гант
+  text_demo_feature_popup: Эта функия будет скоро доступна
+  text_easy_gantt_footer: Redmine Гант от Easy
+  text_easy_gantt_keep_link_delay_in_drag: Сохранять постоянную задержку при перетаскивании
+    задачи назад
+  text_easy_gantt_print_easy_gantt_current: Показать текущую диаграмму (только для
+    страниц с модулем Easy Gantt)
+  text_easy_gantt_relation_delay_in_workdays: Не учитывать нерабочие дни при запаздывании
+  text_easy_gantt_show_holidays: Показать выходные на Ганте (работают только в Easy
+    Redmine)
+  text_easy_gantt_show_project_progress: Показать процент выполнения на диаграмме
+    (может потребовать дополнительного времени)
+  text_easy_gantt_show_task_soonest_start: Показать минимально допустимые даты для
+    связанных задач или подзадач
+  title_easy_gantt_settings: Easy Гант
diff --git a/plugins/easy_gantt/config/locales/sk.yml b/plugins/easy_gantt/config/locales/sk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..354e579f4c849b3d5e6f02dbb1142251de534b0f
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sk.yml
@@ -0,0 +1,2 @@
+---
+sk: 
diff --git a/plugins/easy_gantt/config/locales/sl.yml b/plugins/easy_gantt/config/locales/sl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31bb26cc3f5eecb9e78c89d8ee46f4f7c6d93bd3
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sl.yml
@@ -0,0 +1,2 @@
+---
+sl: 
diff --git a/plugins/easy_gantt/config/locales/sq.yml b/plugins/easy_gantt/config/locales/sq.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c092f0a65e3c87d0e93a80f699335ac29e94852
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sq.yml
@@ -0,0 +1,2 @@
+---
+sq: 
diff --git a/plugins/easy_gantt/config/locales/sr-YU.yml b/plugins/easy_gantt/config/locales/sr-YU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..24e179627aca2606c215e215a21b980ce59dfb96
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sr-YU.yml
@@ -0,0 +1,2 @@
+---
+sr-YU: 
diff --git a/plugins/easy_gantt/config/locales/sr.yml b/plugins/easy_gantt/config/locales/sr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..43a10141ba3e960e043e9dda2aa7785a8ef3da4b
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sr.yml
@@ -0,0 +1,2 @@
+---
+sr: 
diff --git a/plugins/easy_gantt/config/locales/sv.yml b/plugins/easy_gantt/config/locales/sv.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed425eabde8cdb1194378aac15a95f05cb156df7
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/sv.yml
@@ -0,0 +1,2 @@
+---
+sv: 
diff --git a/plugins/easy_gantt/config/locales/th.yml b/plugins/easy_gantt/config/locales/th.yml
new file mode 100644
index 0000000000000000000000000000000000000000..35969140c57bbc57ca621c97098aa6ba6fff96dc
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/th.yml
@@ -0,0 +1,2 @@
+---
+th: 
diff --git a/plugins/easy_gantt/config/locales/tr.yml b/plugins/easy_gantt/config/locales/tr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3be79e79e8e62a69dcbe1e94764bb4f52a409ff4
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/tr.yml
@@ -0,0 +1,2 @@
+---
+tr: 
diff --git a/plugins/easy_gantt/config/locales/zh-TW.yml b/plugins/easy_gantt/config/locales/zh-TW.yml
new file mode 100644
index 0000000000000000000000000000000000000000..44cf0f8d49e34cec16d3ed63ef44e1699d8b9b7f
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/zh-TW.yml
@@ -0,0 +1,2 @@
+---
+zh-TW: 
diff --git a/plugins/easy_gantt/config/locales/zh.yml b/plugins/easy_gantt/config/locales/zh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..80524439b6a0221bdb7fa42e858baa379b9b83cc
--- /dev/null
+++ b/plugins/easy_gantt/config/locales/zh.yml
@@ -0,0 +1,140 @@
+---
+zh:
+  button_print: 打印
+  button_project_menu_easy_gantt: Easy Gantt
+  button_top_menu_easy_gantt: 项目甘特图
+  button_use_actual_delay: 采用实际延迟时间
+  easy_gantt:
+    button:
+      close_all: 关闭全部
+      close_all_parent_issues: 关闭所有上一级任务
+      create_baseline: 原始时间线
+      critical_path: 关键路径
+      day_zoom: 天
+      delayed_project_filter: 筛选延误的项目
+      jump_today: 转到今天
+      load_sample_data: 载入示例数据
+      month_zoom: 月
+      print_fit: 适应页面大小
+      problem_finder: 问题
+      reload: 保存
+      remove_delay: 删除延误
+      resource_management: 资源管理
+      tool_panel: 工具栏
+      week_zoom: 周
+    critical_path:
+      disabled: 禁用
+      last: 最近的
+      last_text: 任务不能推迟
+      longest: 最长
+      longest_text: 显示最长的任务队列
+    errors:
+      duplicate_link: 不能创建重复的连接
+      fresh_milestone: 不能给未保存的里程碑添加任务, 请先保存里程碑
+      link_target_new: 不能为未保存任务创建连接, 请先保存任务
+      link_target_readonly: 只读的目标任务
+      loop_link: 不能创建循环的连接
+      no_rest_api: Easy Gantt需要将REST API开启. 请在管理(Administration)->设置 -> API-> 开启REST网络服务中进行设置.
+      overdue: 结束日该是将来的日期, 或者应该是已经完成了.
+      overmile: 必须在 %{effective_date}结束, 以达到里程碑进度要求
+      short_delay: 应当增加 %{diff} 天
+      too_short: 预计要%{rest} 小时, 所给天数不够.
+      unsaved_parent: 上一级任务未保存, 不能在其下添加任务. 请先保存
+      unsupported_link_type: 不支持的连接类型
+    gateway:
+      entity_save_failed: "%{entityType} %{entityName}保存失败"
+      send_failed: 请求失败
+    label_pro_upgrade: 升级到专业版
+    link_dir:
+      link_end: 同上一次
+      link_start: 同下一个
+    popup:
+      add_task:
+        heading: 添加任务属性
+        text: 新任务/里程碑的特性只有有 Easy Gantt PRO(专业版)内可用. 访问 <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">进行更新!</a>
+      baseline:
+        heading: Baseline的属性
+        text: 原始时间线(Baseline)功能仅在Easy Gantt 专业版中可用 <a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">检查更新!</a>
+      critical:
+        heading: 关键路径(Path)的属性
+        text: 关键路径的属性只有在Easy Gantt 专业版内可用. 访问<a href="https://www.easyredmine.com/easy-gantt-upgrade-to-pro">进行更新!</a>
+          ï…‰
+      resource:
+        easy_text: 没有安装资源管理插件- 请从<a href="https://www.easyredmine.com/pricing-buy">这里!</a>
+          下载
+        heading: 资源管理器的属性
+        text: 资源管理器的属性只在 <a href="https://www.easyredmine.com/"> Easy Redmine</a>
+          中可用
+    reload_modal:
+      label_errors: 错误
+      text_reload_appeal: 忽略未保存数据,  重新从服务器下载数据?
+      title: Gantt图没有保存成功
+    sample_global_free:
+      text: 不能关闭示例数据. 所有项目的Gantt只有在专业版中可用.
+      video:
+        text: 在这里可以查看所有项目的甘特图Gantt的属性
+        title: 所有项目的Gantt示例视频
+        video_id: EiiqBrrY4m4
+    sample:
+      close_label: 关闭窗口, 并下载真正的项目数据
+      header: 示例数据下载完成
+      text: 查看一些示例数据来<strong>轻松了解Easy Gantt的特性</strong>. 也可以观看演示视频来进一步提高, 关闭窗口将下载真正的项目数据.
+      video_label: 观看视频教程
+      video:
+        text: 在这里可以了解Gantt模块的大部份特性
+        title: 示例视频
+        video_id: UHgqfsrD59Q
+    text_blocker_milestone: 里程碑不允许该任务移到此日期之后
+    text_blocker_move_pre: 由于一些关系的限制, 该任务不能移到此日期之后
+    title:
+      button_test: 测试按钮(仅作测试用)
+      day_zoom: 放大到按天显示
+      jump_today: 显示今天的时间安排
+      month_zoom: 按月度显示
+      print_fit: 缩放甘特图到一页大小
+      week_zoom: 按周显示
+  easy_printable_templates_categories:
+    easy_gantt: Easy 甘特图
+  easy_query:
+    name:
+      easy_gantt_easy_issue_query: Easy Gantt
+      easy_gantt_easy_project_query: ''
+  error_easy_gantt_view_permission: 你没有查看Easy 甘特图的权限
+  error_epm_easy_gantt_already_active: 本页内的Easy gantt 已经生效!
+  field_easy_gantt_default_zoom: 默认时间单位
+  field_easy_gantt_relation_delay_in_workdays: 工作日内延期关系
+  field_easy_gantt_show_holidays: 显示假日
+  field_easy_gantt_show_project_progress: 显示项目进度
+  field_easy_gantt_show_task_soonest_start: 显示最近的开始时间
+  field_keep_link_delay_in_drag: 拖放时使用固定时间延迟
+  field_relation: 关系
+  heading_delay_popup: 设置延迟天数
+  heading_demo_feature_popup: 马上就绪
+  heading_easy_gantts_issues: Easy Gantt Free
+  label_easy_gantt: Easy Gantt
+  label_easy_gantt_critical_path: 关键路径
+  label_easy_gantt_settings: Easy Gantt 设置
+  label_filter_group_easy_gantt_easy_issue_query: 任务
+  label_finish_to_finish: 以最后结束
+  label_parent_issue_plural: 上一级的问题
+  label_start_to_finish: 从结尾开始
+  label_start_to_start: 从头开始
+  link_easy_gantt_plugin: https://www.easyredmine.com/easy-gantt-upgrade-to-pro
+  permission_edit_easy_gantt: 编辑项目中的Easy Gantt和资源管理
+  permission_edit_global_easy_gantt: 编辑通用Easy Gantt 和资源管理
+  permission_edit_personal_easy_gantt: 编辑个人定制 Easy Gantt & Resource management
+  permission_view_easy_gantt: 查看项目中的Easy Gantt和资源管理
+  permission_view_global_easy_gantt: 查看通用的 Easy Gantt 和资源管理
+  permission_view_personal_easy_gantt: 查看个人定制Easy Gantt & Resource management
+  project_default_page:
+    easy_gantt: Easy Gantt
+  project_module_easy_gantt: Easy Gantt
+  text_demo_feature_popup: 该特性将很快生效
+  text_easy_gantt_footer: 由Easy开发的 Redmine Gantt
+  text_easy_gantt_keep_link_delay_in_drag: 将任务拖放回来时, 保持原有的关系和延误情况
+  text_easy_gantt_print_easy_gantt_current: 显示当前的Easy 甘特图(只有在已载入Easy 甘特图的页面有效)
+  text_easy_gantt_relation_delay_in_workdays: 在相关延误天数计算时, 不要计入非工作日
+  text_easy_gantt_show_holidays: 在Gantt上显示当前用户的假日安排(仅在Easy Redmine上可用)
+  text_easy_gantt_show_project_progress: 在项目进度条上显示完成比例(可能导致加载较慢)
+  text_easy_gantt_show_task_soonest_start: 显示由关系或其前提决定的任务的最近有效日期
+  title_easy_gantt_settings: Easy Gantt
diff --git a/plugins/easy_gantt/config/routes.rb b/plugins/easy_gantt/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9f1d9f154b78a87633e5318f3995ee1cfa9fae2e
--- /dev/null
+++ b/plugins/easy_gantt/config/routes.rb
@@ -0,0 +1,13 @@
+# Because of plugin deactivations
+if Redmine::Plugin.installed?(:easy_gantt)
+  get '(projects/:project_id)/easy_gantt' => 'easy_gantt#index', as: 'easy_gantt'
+
+  scope format: true, defaults: { format: 'json' }, constraints: { format: 'json' } do
+    scope 'projects/:project_id' do
+      get 'easy_gantt/issues' => 'easy_gantt#issues', as: 'issues_easy_gantt'
+      put 'easy_gantt/relation/:id' => 'easy_gantt#change_issue_relation_delay', as: 'relation_easy_gantt'
+      get 'easy_gantt/project_issues' => 'easy_gantt#project_issues', as: 'project_issues_easy_gantt'
+    end
+    get 'easy_gantt/projects' => 'easy_gantt#projects', as: 'projects_easy_gantt'
+  end
+end
diff --git a/plugins/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb b/plugins/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fcbc0f14bb0128edf0de11fbff1a5431ff0d9b0b
--- /dev/null
+++ b/plugins/easy_gantt/db/migrate/20170213152215_add_default_printable_template.rb
@@ -0,0 +1,27 @@
+require 'easy_gantt/easy_gantt'
+
+class AddDefaultPrintableTemplate < ActiveRecord::Migration
+
+  def up
+    return unless EasyGantt.easy_printable_templates?
+
+    plugin = Redmine::Plugin.find('easy_gantt')
+    path = File.join(plugin.directory, 'app', 'views', 'easy_gantt', 'printable_templates', 'default.html')
+
+    EasyPrintableTemplate.create_from_view!(
+      {
+        'name' => 'Easy Gantt (default)' ,
+        'pages_orientation' => 'landscape',
+        'pages_size' => 'a4',
+        'category' => 'easy_gantt',
+      },
+      { template_path: path })
+  end
+
+  def down
+    return unless EasyGantt.easy_printable_templates?
+
+    EasyPrintableTemplate.where(category: 'easy_gantt').destroy_all
+  end
+
+end
diff --git a/plugins/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb b/plugins/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..808fe4f5e2232719988151cf939697ef20b6c16a
--- /dev/null
+++ b/plugins/easy_gantt/db/migrate/20170224134615_update_rest_api_settings.rb
@@ -0,0 +1,10 @@
+class UpdateRestApiSettings < ActiveRecord::Migration
+
+  def up
+    Setting.where(name: 'rest_api_enabled').update_all(value: '1')
+  end
+
+  def down
+  end
+
+end
diff --git a/plugins/easy_gantt/init.rb b/plugins/easy_gantt/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d5367a5b48b09e4f983af1f4caa8d235953d60b0
--- /dev/null
+++ b/plugins/easy_gantt/init.rb
@@ -0,0 +1,25 @@
+Redmine::Plugin.register :easy_gantt do
+  name 'Easy Gantt plugin'
+  author 'Easy Software Ltd'
+  description 'Cool gantt for redmine'
+  version '1.12'
+  url 'www.easyredmine.com'
+  author_url 'www.easysoftware.cz'
+
+  requires_redmine version_or_higher: '3.2'
+
+  settings partial: 'easy_gantt_nil', only_easy: true, easy_settings: {
+    show_holidays: false,
+    show_project_progress: true,
+    critical_path: 'last',
+    default_zoom: 'day',
+    show_lowest_progress_tasks: false,
+    show_task_soonest_start: false,
+    relation_delay_in_workdays: false,
+    fixed_delay: false
+  }
+end
+
+unless Redmine::Plugin.installed?(:easy_extensions)
+  require_relative 'after_init'
+end
diff --git a/plugins/easy_gantt/lib/easy_gantt/easy_gantt.rb b/plugins/easy_gantt/lib/easy_gantt/easy_gantt.rb
new file mode 100644
index 0000000000000000000000000000000000000000..478e99429fd5eb4c46b637fda453b52eec41eb15
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/easy_gantt.rb
@@ -0,0 +1,78 @@
+module EasyGantt
+
+  def self.non_working_week_days(user=nil)
+    if user.is_a?(Integer)
+      user = Principal.find_by(id: user)
+    elsif user.nil?
+      user = User.current
+    end
+
+    working_days = user.try(:current_working_time_calendar).try(:working_week_days)
+    working_days = Array(working_days).map(&:to_i)
+
+    if working_days.any?
+      (1..7).to_a - working_days
+    else
+      Array(Setting.non_working_week_days).map(&:to_i)
+    end
+  end
+
+  # Experimental function
+  def self.load_fixed_delay?
+    false
+  end
+
+  def self.easy_extensions?
+    Redmine::Plugin.installed?(:easy_extensions)
+  end
+
+  def self.easy_project_com?
+    Redmine::Plugin.installed?(:easy_project_com)
+  end
+
+  def self.easy_calendar?
+    Redmine::Plugin.installed?(:easy_calendar)
+  end
+
+  def self.easy_attendances?
+    easy_extensions? && Redmine::Plugin.installed?(:easy_attendances) && EasyAttendance.enabled?
+  end
+
+  def self.easy_money?
+    Redmine::Plugin.installed?(:easy_money)
+  end
+
+  def self.easy_gantt_pro?
+    Redmine::Plugin.installed?(:easy_gantt_pro)
+  end
+
+  def self.easy_gantt_resources?
+    Redmine::Plugin.installed?(:easy_gantt_resources)
+  end
+
+  def self.easy_baseline?
+    Redmine::Plugin.installed?(:easy_baseline)
+  end
+
+  def self.easy_printable_templates?
+    Redmine::Plugin.installed?(:easy_printable_templates)
+  end
+
+  def self.combine_by_pipeline?(params)
+    return false unless easy_extensions?
+    return params[:combine_by_pipeline].to_s.to_boolean if params.key?(:combine_by_pipeline)
+    Rails.env.production?
+  end
+
+  def self.platform
+    case
+    when easy_project_com?
+      'easyproject'
+    when easy_extensions?
+      'easyredmine'
+    else
+      'redmine'
+    end
+  end
+
+end
diff --git a/plugins/easy_gantt/lib/easy_gantt/hooks.rb b/plugins/easy_gantt/lib/easy_gantt/hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..529daa970a3926b010250d64991a670ee9270c9a
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/hooks.rb
@@ -0,0 +1,15 @@
+module EasyGantt
+  class Hooks < Redmine::Hook::ViewListener
+
+    def helper_options_for_default_project_page(context={})
+      context[:default_pages] << 'easy_gantt' if context[:enabled_modules].include?('easy_gantt')
+    end
+
+    def view_easy_printable_templates_token_list_bottom(**context)
+      if context[:section] == :plugins
+        context[:hook_caller].render('easy_gantt/printable_templates/token_list', context)
+      end
+    end
+
+  end
+end
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/controllers/queries_controller_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/controllers/queries_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84779a7b971fdeae4746d7c51725942cb4bfdbfd
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/controllers/queries_controller_patch.rb
@@ -0,0 +1,32 @@
+module EasyGantt
+  module QueriesControllerPatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+        if method_defined?(:query_class) || private_method_defined?(:query_class)
+          alias_method_chain :query_class, :easy_gantt
+        end
+      end
+    end
+
+    module InstanceMethods
+
+      # Redmine return only direct sublasses but
+      # Gantt query inherit from IssueQuery
+      def query_class_with_easy_gantt
+        case params[:type]
+        when 'EasyGantt::EasyGanttIssueQuery'
+          EasyGantt::EasyGanttIssueQuery
+        else
+          query_class_without_easy_gantt
+        end
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_controller_patch 'QueriesController', 'EasyGantt::QueriesControllerPatch'
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/helpers/application_helper_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/helpers/application_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7903ad678e4785d7a1f1b937fd46194f0e18866e
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/helpers/application_helper_patch.rb
@@ -0,0 +1,26 @@
+module EasyGantt
+  module ApplicationHelperPatch
+
+    def self.included(base)
+      base.extend(ClassMethods)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+
+        def link_to_project_with_easy_gantt(project, options = {})
+          { controller: 'easy_gantt', action: 'index', project_id: project }
+        end
+
+      end
+    end
+
+    module InstanceMethods
+    end
+
+    module ClassMethods
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_helper_patch 'ApplicationHelper', 'EasyGantt::ApplicationHelperPatch'
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..283b0354ef668ca3ee7469acb7c607a38f3db8b7
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_patch.rb
@@ -0,0 +1,49 @@
+module EasyGantt
+  module IssuePatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+
+        scope :gantt_opened, lambda {
+          joins(:status).where(IssueStatus.table_name => { is_closed: false })
+        }
+
+      end
+    end
+
+    module InstanceMethods
+
+      def gantt_editable?(user=nil)
+        user ||= User.current
+
+        (user.allowed_to?(:edit_easy_gantt, project) ||
+         user.allowed_to_globally?(:edit_global_easy_gantt) ||
+         (assigned_to_id == user.id &&
+          user.allowed_to_globally?(:edit_personal_easy_gantt))) &&
+        user.allowed_to?(:manage_issue_relations, project) &&
+        user.allowed_to?(:edit_issues, project)
+      end
+
+      def gantt_latest_due
+        if @gantt_latest_due.nil?
+          dates = relations_from.map{|relation| relation.gantt_previous_latest_start }
+
+          p = @parent_issue || parent
+          if p && Setting.parent_issue_dates == 'derived'
+            dates << p.gantt_latest_due
+          end
+
+          @gantt_latest_due = dates.compact.max
+        end
+
+        @gantt_latest_due
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_model_patch 'Issue', 'EasyGantt::IssuePatch'
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_relation_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_relation_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..25d123b19e90298bed2dcaa80260c97b2cfb9a44
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/issue_relation_patch.rb
@@ -0,0 +1,70 @@
+#
+# THIS FILE MUST BE LOADED BEFORE IssueQuery OR EasyIssueQuery !!!
+#
+module EasyGantt
+  module IssueRelationPatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods)
+
+      start_to_start = 'start_to_start'
+      finish_to_finish = 'finish_to_finish'
+      start_to_finish = 'start_to_finish'
+
+      new_types = base::TYPES.merge(
+
+          start_to_start => {
+            name: :label_start_to_start,
+            sym_name: :label_start_to_start,
+            order: 20,
+            sym: start_to_start
+          },
+
+          finish_to_finish => {
+            name: :label_finish_to_finish,
+            sym_name: :label_finish_to_finish,
+            order: 21,
+            sym: finish_to_finish
+          },
+
+          start_to_finish => {
+            name: :label_start_to_finish,
+            sym_name: :label_start_to_finish,
+            order: 22,
+            sym: start_to_finish
+          }
+
+        )
+
+      base.class_eval do
+        const_set :TYPE_START_TO_START, start_to_start
+        const_set :TYPE_FINISH_TO_FINISH, finish_to_finish
+        const_set :TYPE_START_TO_FINISH, start_to_finish
+
+        remove_const :TYPES
+        const_set :TYPES, new_types.freeze
+
+        inclusion_validator = _validators[:relation_type].find{|v| v.kind == :inclusion}
+        inclusion_validator.instance_variable_set(:@delimiter, new_types.keys)
+      end
+    end
+
+    module InstanceMethods
+
+      # +---------+   (5)    +---------+
+      # | Issue 1 |--------->| Issue 2 |
+      # +---------+          +---------+
+      # (issue_from)         (issue_to)
+      #
+      def gantt_previous_latest_start
+        if (IssueRelation::TYPE_PRECEDES == relation_type) && delay && issue_to && (issue_to.start_date || issue_to.due_date)
+          (issue_to.start_date || issue_to.due_date) - 1 - delay
+        end
+      end
+
+    end
+
+  end
+end
+
+IssueRelation.send(:include, EasyGantt::IssueRelationPatch)
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/project_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/project_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98e4e6d084ce3d59577de2ec6f6f6c9edafcd74a
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/project_patch.rb
@@ -0,0 +1,192 @@
+module EasyGantt
+  module ProjectPatch
+
+    def self.included(base)
+      base.extend(ClassMethods)
+      base.send(:include, InstanceMethods)
+
+      # Patch is also applied before db:migrate so you cannot call
+      # column_names on non-existed table
+      if Project.table_exists? && Project.column_names.include?('easy_start_date') && Project.column_names.include?('easy_due_date')
+        base.send(:include, DatesWithNativeColumns)
+      end
+
+      base.class_eval do
+      end
+    end
+
+    module InstanceMethods
+
+      def gantt_editable?(user=nil)
+        user ||= User.current
+
+        (user.allowed_to?(:edit_easy_gantt, self) ||
+         user.allowed_to_globally?(:edit_global_easy_gantt)) &&
+        user.allowed_to?(:edit_project, self)
+      end
+
+      def gantt_reschedule(days)
+        transaction do
+          all_issues = Issue.joins(:project).where(gantt_subprojects_conditions)
+          all_issues.update_all("start_date = start_date + INTERVAL '#{days}' DAY," +
+                                "due_date = due_date + INTERVAL '#{days}' DAY")
+
+          all_versions = Version.joins(:project).where(gantt_subprojects_conditions)
+          all_versions.update_all("effective_date = effective_date + INTERVAL '#{days}' DAY")
+
+          Redmine::Hook.call_hook(:model_project_gantt_reschedule, project: project, days: days, all_issues: all_issues)
+        end
+      end
+
+      # Weighted completed percent including subprojects
+      def gantt_completed_percent
+        return @gantt_completed_percent if @gantt_completed_percent || @gantt_completed_percent_added
+
+        i_table = Issue.table_name
+
+        scope = Issue.where("#{i_table}.estimated_hours IS NOT NULL").
+                      where("#{i_table}.estimated_hours > 0").
+                      joins(:project).
+                      where(gantt_subprojects_conditions)
+
+        if scope.exists?
+          sum = scope.select('(SUM(done_ratio / 100.0 * estimated_hours) / SUM(estimated_hours) * 100) AS sum_alias').reorder(nil).first
+          @gantt_completed_percent = sum ? sum.sum_alias.to_f : 0.0
+        else
+          @gantt_completed_percent = 100.0
+        end
+
+        @gantt_completed_percent
+      end
+
+      def gantt_start_date
+        return @gantt_start_date if @gantt_start_date || @gantt_start_date_added
+
+        @gantt_start_date = [
+          Issue.joins(:project).where(gantt_subprojects_conditions).minimum('start_date'),
+          Version.joins(:project).where(gantt_subprojects_conditions).minimum('effective_date')
+        ].compact.min
+      end
+
+      def gantt_due_date
+        return @gantt_due_date if @gantt_due_date || @gantt_due_date_added
+
+        @gantt_due_date = [
+          Issue.joins(:project).where(gantt_subprojects_conditions).maximum('due_date'),
+          Version.joins(:project).where(gantt_subprojects_conditions).maximum('effective_date')
+        ].compact.max
+      end
+
+      def gantt_subprojects_conditions
+        p_table = Project.table_name
+        "#{p_table}.status <> #{Project::STATUS_ARCHIVED} AND #{p_table}.lft >= #{lft} AND #{p_table}.rgt <= #{rgt}"
+      end
+
+    end
+
+    module DatesWithNativeColumns
+
+      def gantt_reschedule(days)
+        all_projects = Project.where(gantt_subprojects_conditions)
+        all_projects.update_all("easy_start_date = easy_start_date + INTERVAL '#{days}' DAY," +
+                                "easy_due_date = easy_due_date + INTERVAL '#{days}' DAY")
+
+        super
+      end
+
+      def gantt_start_date
+        if EasySetting.value(:project_calculate_start_date) || easy_start_date.nil?
+          super
+        else
+          easy_start_date
+        end
+      end
+
+      def gantt_due_date
+        if EasySetting.value(:project_calculate_due_date) || easy_due_date.nil?
+          super
+        else
+          easy_due_date
+        end
+      end
+
+    end
+
+    module ClassMethods
+
+      def load_gantt_dates(projects)
+        p_table = Project.table_name
+        i_table = Issue.table_name
+        v_table = Version.table_name
+
+        project_ids = projects.map(&:id)
+
+        data = []
+        data.concat Project.where(id: project_ids).
+                            joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt").
+                            joins("JOIN #{i_table} i ON i.project_id = p2.id").
+                            group("#{p_table}.id").
+                            pluck("#{p_table}.id, MIN(i.start_date), MAX(i.due_date)")
+
+        data.concat Project.where(id: project_ids).
+                            joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt").
+                            joins("JOIN #{v_table} v ON v.project_id = p2.id").
+                            group("#{p_table}.id").
+                            pluck("#{p_table}.id, MIN(v.effective_date), MAX(v.effective_date)")
+
+        result = {}
+        data.each do |id, min, max|
+          if result.has_key?(id)
+            result[id][0] = [result[id][0], min].compact.min
+            result[id][1] = [result[id][1], max].compact.max
+          else
+            result[id] = [min, max]
+          end
+        end
+
+        projects.each do |project|
+          project_data = result[project.id]
+
+          if project_data
+            project.instance_variable_set :@gantt_start_date, project_data[0]
+            project.instance_variable_set :@gantt_due_date, project_data[1]
+          end
+
+          project.instance_variable_set :@gantt_start_date_added, true
+          project.instance_variable_set :@gantt_due_date_added, true
+        end
+      end
+
+      def load_gantt_completed_percent(projects)
+        p_table = Project.table_name
+        i_table = Issue.table_name
+
+        project_ids = projects.map(&:id)
+        result = Project.where(id: project_ids).
+                         joins("JOIN #{p_table} p2 ON p2.lft >= #{p_table}.lft AND p2.rgt <= #{p_table}.rgt").
+                         joins("JOIN #{i_table} i ON i.project_id = p2.id").
+                         where("i.estimated_hours IS NOT NULL AND i.estimated_hours > 0").
+                         where("p2.status <> ?", Project::STATUS_ARCHIVED).
+                         group("#{p_table}.id").
+                         pluck("#{p_table}.id, (SUM(i.done_ratio / 100.0 * i.estimated_hours) / SUM(i.estimated_hours) * 100)").
+                         to_h
+
+        projects.each do |project|
+          done_ratio = result[project.id]
+
+          if done_ratio
+            project.instance_variable_set :@gantt_completed_percent, done_ratio
+          else
+            project.instance_variable_set :@gantt_completed_percent, 100.0
+          end
+
+          project.instance_variable_set :@gantt_completed_percent_added, true
+        end
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_model_patch 'Project', 'EasyGantt::ProjectPatch'
diff --git a/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/version_patch.rb b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/version_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98b3b5992d1ec83a30b509a0adffe7fa9f55d620
--- /dev/null
+++ b/plugins/easy_gantt/lib/easy_gantt/redmine_patch/models/version_patch.rb
@@ -0,0 +1,26 @@
+module EasyGantt
+  module VersionPatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+      end
+    end
+
+    module InstanceMethods
+
+      def gantt_editable?(user=nil)
+        user ||= User.current
+
+        (user.allowed_to?(:edit_easy_gantt, project) ||
+         user.allowed_to_globally?(:edit_global_easy_gantt)) &&
+        user.allowed_to?(:manage_versions, project)
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_model_patch 'Version', 'EasyGantt::VersionPatch'
diff --git a/plugins/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb b/plugins/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e305916354e0a6eec7c10a9b6e801cda7d5574e
--- /dev/null
+++ b/plugins/easy_gantt/spec/controllers/easy_gantt_controller_spec.rb
@@ -0,0 +1,28 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.describe EasyGanttController, logged: :admin do
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  context 'rest api' do
+    let(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) }
+
+    it 'disabled' do
+      with_settings(rest_api_enabled: 0) do
+        get :index, project_id: project
+        expect(response).to be_error
+
+        get :index
+        expect(response).to be_error
+      end
+    end
+
+    it 'enabled' do
+      get :index
+      expect(response).to be_success
+    end
+  end
+
+end
diff --git a/plugins/easy_gantt/spec/features/add_task_spec.rb b/plugins/easy_gantt/spec/features/add_task_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a9d7afd238999dcde9e63275993b4eb01538d5cd
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/add_task_spec.rb
@@ -0,0 +1,24 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Add task', logged: :admin, js: true do
+  let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  describe 'toolbar' do
+
+    unless Redmine::Plugin.installed?(:easy_gantt_pro)
+      scenario 'should open help' do
+        visit easy_gantt_path(project)
+        wait_for_ajax
+        page.find('.easy-gantt__menu-tools').hover
+        expect(page).to have_selector('#button_add_task_help', text: I18n.t(:label_new))
+        page.find('#button_add_task_help').click
+        expect(page).to have_selector('#add_task_help_modal_popup')
+      end
+    end
+
+  end
+end
diff --git a/plugins/easy_gantt/spec/features/correct_tree_spec.rb b/plugins/easy_gantt/spec/features/correct_tree_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9709303902a517d005dfefaa3e65fea64c0de016
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/correct_tree_spec.rb
@@ -0,0 +1,73 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Correct tree', logged: :admin, js: true do
+  let(:project) {
+    FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 0)
+  }
+  let(:sub_project) {
+    FactoryGirl.create(:project, parent_id: project.id, number_of_issues: 1)
+  }
+  let(:project_issues) {
+    FactoryGirl.create_list(:issue, 3, :project_id => project.id)
+  }
+  let(:milestone) {
+    FactoryGirl.create(:version, project_id: project.id, due_date: Date.today + 7 )
+  }
+  let(:milestone_issues) {
+    FactoryGirl.create_list(:issue, 3, :fixed_version_id => milestone.id, :project_id => project.id)
+  }
+  let(:sub_issues) {
+    FactoryGirl.create_list(:issue, 3, :parent_issue_id => milestone_issues[0].id, :project_id => project.id)
+  }
+  let(:sub_sub_issues) {
+    FactoryGirl.create_list(:issue, 3, :parent_issue_id => sub_issues[0].id, :project_id => project.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+  it 'should show project items in correct order' do
+    sub_sub_issues
+    project_issues
+    sub_project
+    visit easy_gantt_path(project)
+    wait_for_ajax
+    bars_area = page.find('.gantt_grid_data')
+    # bars_area = page.find('.gantt_bars_area')
+    order_list = [
+      project,
+      project_issues[0],
+      project_issues[1],
+      project_issues[2],
+      milestone,
+      milestone_issues[0],
+      sub_issues[0],
+      sub_sub_issues[0],
+      sub_sub_issues[1],
+      sub_sub_issues[2],
+      sub_issues[1],
+      sub_issues[2],
+      milestone_issues[1],
+      milestone_issues[2],
+      sub_project
+    ]
+    prev_id = nil
+    order_list.each do |issue|
+      if issue.is_a? Project
+        id = 'p' + issue.id.to_s
+        name = issue.name
+      elsif issue.is_a? Version
+        id = 'm' + issue.id.to_s
+        name = issue.name
+      else
+        id = issue.id
+        name = issue.subject
+      end
+      expect(bars_area).to have_text(name)
+      unless prev_id.nil?
+        expect(bars_area.find("div[task_id='#{prev_id}']+div[task_id='#{id}']")).not_to be_nil
+      end
+      prev_id = id
+    end
+  end
+end
\ No newline at end of file
diff --git a/plugins/easy_gantt/spec/features/easy_gantt_spec.rb b/plugins/easy_gantt/spec/features/easy_gantt_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c4c4ec2c102ce0ea6d5e9a7315db8393fef7e4b
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/easy_gantt_spec.rb
@@ -0,0 +1,25 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Easy gantt', js: true, logged: :admin do
+  let!(:project) { FactoryGirl.create(:project, add_modules: ['easy_gantt']) }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  describe 'show gantt' do
+
+    scenario 'show easy gantt on project' do
+      visit easy_gantt_path(project)
+      wait_for_ajax
+      # unless Redmine::Plugin.installed?(:easy_gantt_pro)
+      #   page.find('#sample_close_button').click
+      #   wait_for_ajax
+      # end
+      expect(page).to have_css('#easy_gantt')
+      expect(page.find('.gantt_grid_data')).to have_content(project.name)
+      expect(page.find('#header')).to have_content(project.name)
+    end
+
+  end
+end
diff --git a/plugins/easy_gantt/spec/features/gantt_save_spec.rb b/plugins/easy_gantt/spec/features/gantt_save_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8cfdca1afad7cdfea2f3cc7eae7987413a434df8
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/gantt_save_spec.rb
@@ -0,0 +1,52 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Gantt save', logged: :admin, js: true, slow: true do
+  let(:subproject) {
+    FactoryGirl.create(:project, :parent_id => superproject.id, add_modules: ['easy_gantt'], number_of_issues: 3)
+  }
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3)
+  }
+  let(:superproject_milestone_issues) {
+    FactoryGirl.create_list(:issue, 3, :fixed_version_id => superproject_milestone.id, :project_id => superproject.id)
+  }
+  let(:subproject_milestone_issues) {
+    FactoryGirl.create_list(:issue, 3, :fixed_version_id => subproject_milestone.id, :project_id => subproject.id)
+  }
+  let(:subproject_milestone) {
+    FactoryGirl.create(:version, project_id: subproject.id)
+  }
+  let(:superproject_milestone) {
+    FactoryGirl.create(:version, project_id: superproject.id)
+  }
+  let(:subissues) {
+    FactoryGirl.create_list(:issue, 3, :parent_issue_id => superproject.issues[0].id, :project_id => superproject.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+
+  it 'should save issue' do
+    issue = superproject.issues[0]
+    old_start_date = issue.start_date
+    visit easy_gantt_path(superproject)
+    wait_for_ajax
+    expect(page).to have_text(issue.subject)
+    move_script= <<-EOF
+      (function(){var issue = ysy.data.issues.getByID(#{issue.id});
+      issue.set({start_date:moment('#{Date.today + 2.days}'),end_date:moment('#{Date.today + 4.days}')});
+      return "success";})()
+    EOF
+    save_button = page.find('#button_save')
+    expect(page).to have_css('#button_save.disabled')
+    expect(page.evaluate_script(move_script)).to eq('success')
+    expect(page).not_to have_css('#button_save.disabled')
+    save_button.click
+    wait_for_ajax
+    expect(page).to have_css('#button_save.disabled')
+    issue.reload
+    expect(issue.start_date).to eq(old_start_date + 2.days)
+  end
+end
\ No newline at end of file
diff --git a/plugins/easy_gantt/spec/features/global_gantt_spec.rb b/plugins/easy_gantt/spec/features/global_gantt_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..63e9820be72925a15721c002cf4cb989c8d70736
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/global_gantt_spec.rb
@@ -0,0 +1,30 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Global gantt', logged: :admin, js: true do
+  let!(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3)
+  }
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+  unless Redmine::Plugin.installed?(:easy_gantt_pro) then
+    it 'should load sample Data' do
+      visit easy_gantt_path
+      wait_for_ajax
+      expect(page).to have_css('#sample_cont')
+      expect(page).to have_text('1. Administrative Projects')
+      expect(page).to have_text('2. HR Projects')
+      expect(page).to have_text('3. IT Projects')
+      expect(page).to have_text('4. Product Development')
+    end
+    it 'should open sample project' do
+      visit easy_gantt_path
+      wait_for_ajax
+      expect(page).to have_text('3. IT Projects')
+      page.find('[task_id="p3"] .gantt_open').click
+      expect(page).to have_text('Client Project')
+      expect(page).to have_text('Implementation of IS')
+      expect(page).to have_text('Managing projects')
+    end
+  end
+end
\ No newline at end of file
diff --git a/plugins/easy_gantt/spec/features/jasmine_spec.rb b/plugins/easy_gantt/spec/features/jasmine_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ac6132d199e28b6658823bb1082ecbef762384d8
--- /dev/null
+++ b/plugins/easy_gantt/spec/features/jasmine_spec.rb
@@ -0,0 +1,51 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Jasmine', logged: :admin, js: true, js_wait: true do
+
+  let(:subproject) {
+    FactoryGirl.create(:project, parent_id: superproject.id, add_modules: ['easy_gantt'], number_of_issues: 3)
+  }
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_gantt'], number_of_issues: 3)
+  }
+  let(:superproject_milestone_issues) {
+    FactoryGirl.create_list(:issue, 3, fixed_version_id: superproject_milestone.id, project_id: superproject.id)
+  }
+  let(:subproject_milestone_issues) {
+    FactoryGirl.create_list(:issue, 3, fixed_version_id: subproject_milestone.id, project_id: subproject.id)
+  }
+  let(:subproject_milestone) {
+    FactoryGirl.create(:version, project_id: subproject.id)
+  }
+  let(:superproject_milestone) {
+    FactoryGirl.create(:version, project_id: superproject.id)
+  }
+  let(:subissues) {
+    FactoryGirl.create_list(:issue, 3, parent_issue_id: superproject.issues[0].id, project_id: superproject.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  describe 'Project gantt' do
+    it 'should not fail' do
+      visit easy_gantt_path(superproject, run_jasmine_tests: true)
+      wait_for_ajax
+      expect(page).to have_css('.jasmine-bar')
+      result = page.evaluate_script('ysy.pro.test.parseResult();')
+      expect(result).to eq('success')
+    end
+  end
+
+  describe 'Global gantt' do
+    it 'should not fail' do
+      visit easy_gantt_path(run_jasmine_tests: ['global_gantt'])
+      wait_for_ajax
+      expect(page).to have_css('.jasmine-bar')
+      result = page.evaluate_script('ysy.pro.test.parseResult();')
+      expect(result).to eq('success')
+    end
+  end
+
+end
diff --git a/plugins/easy_gantt/spec/requests/permissions_spec.rb b/plugins/easy_gantt/spec/requests/permissions_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a27b49ea96567c0e7192eacc9421c1915782cc59
--- /dev/null
+++ b/plugins/easy_gantt/spec/requests/permissions_spec.rb
@@ -0,0 +1,71 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.describe 'Permission', type: :request do
+
+  let!(:user) { FactoryGirl.create(:user) }
+
+  let!(:project1) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) }
+  let!(:project2) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) }
+  let!(:project3) { FactoryGirl.create(:project, number_of_issues: 3, add_modules: ['easy_gantt']) }
+
+  let!(:role_nothing) { FactoryGirl.create(:role, permissions: []) }
+  let!(:role_project_view) { FactoryGirl.create(:role, permissions: [:view_issues, :view_easy_gantt]) }
+  let!(:role_project_edit) { FactoryGirl.create(:role, permissions: [:view_issues, :view_easy_gantt, :edit_easy_gantt, :manage_issue_relations, :edit_issues]) }
+
+  let(:query) do
+    _query = EasyIssueGanttQuery.new(name: '_')
+    _query.filters = {}
+    _query.add_filter('status_id', 'o', nil)
+    _query
+  end
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  before(:each) do
+    FactoryGirl.create(:member, project: project1, user: user, roles: [role_nothing])
+    FactoryGirl.create(:member, project: project2, user: user, roles: [role_project_view])
+    FactoryGirl.create(:member, project: project3, user: user, roles: [role_project_edit])
+
+    logged_user(user)
+  end
+
+  # context 'Project level' do
+  # end
+
+  context 'Global level' do
+
+    def get_issues(project)
+      params = query.to_params.merge(opened_project_id: project.id, key: User.current.api_key, format: 'json')
+      get projects_easy_gantt_path, params
+
+      expect(response).to be_ok
+
+      json = JSON.parse(body)
+      json['easy_gantt_data']['issues']
+    end
+
+    # TODO: Global gantt now works with projects
+
+    # it 'should see nothing' do
+    #   binding.pry unless $__binding
+    #   issues = get_issues(project1)
+    #   expect(issues).to be_empty
+    # end
+
+    # it 'only view' do
+    #   issues = get_issues(project2)
+    #   expect(issues).to be_any
+    #   expect(issues.map{|i| i['permissions']['editable']}).to all(be_nil)
+    # end
+
+    # it 'editable' do
+    #   issues = get_issues(project3)
+    #   expect(issues).to be_any
+    #   expect(issues.map{|i| i['permissions']['editable']}).to all(be_truthy)
+    # end
+
+  end
+
+end
diff --git a/plugins/easy_mindmup/Gemfile b/plugins/easy_mindmup/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..0ab92e69d9d2aff829b9394aa35edd1ea101ce23
--- /dev/null
+++ b/plugins/easy_mindmup/Gemfile
@@ -0,0 +1,3 @@
+unless %w(easyproject easy_gantt).any? { |plugin| Dir.exist?(File.expand_path("../../#{plugin}", __FILE__)) }
+  gem 'redmine_extensions', '~> 0.2.4'
+end
\ No newline at end of file
diff --git a/plugins/easy_mindmup/README.md b/plugins/easy_mindmup/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a89d93d690f68b256ce0d2339a58888c2e36563d
--- /dev/null
+++ b/plugins/easy_mindmup/README.md
@@ -0,0 +1 @@
+# Easy Mindmup
diff --git a/plugins/easy_mindmup/after_init.rb b/plugins/easy_mindmup/after_init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..837c915a6b9b1e1840a22634263ca323d20fd21d
--- /dev/null
+++ b/plugins/easy_mindmup/after_init.rb
@@ -0,0 +1,6 @@
+app_dir = File.join(File.dirname(__FILE__), 'app')
+lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_mindmup')
+
+ActionDispatch::Reloader.to_prepare do
+  require 'easy_mindmup/easy_mindmup'
+end
diff --git a/plugins/easy_mindmup/app/controllers/easy_mindmup_controller.rb b/plugins/easy_mindmup/app/controllers/easy_mindmup_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..64933b69d273488d2c44d9274370b4fc4fb712e2
--- /dev/null
+++ b/plugins/easy_mindmup/app/controllers/easy_mindmup_controller.rb
@@ -0,0 +1,44 @@
+class EasyMindmupController < ApplicationController
+  accept_api_auth :update_layout
+
+  before_action :find_project_by_project_id
+  before_action :authorize
+
+  # Save mindmup layout
+  #
+  #   PUT update_layout
+  #   {
+  #     easy_setting: {
+  #       *_layout: { ... }
+  #     }
+  #   }
+  #
+  def update_layout
+    if params[:easy_setting].is_a?(Hash)
+      params[:easy_setting].each do |name, value|
+        next unless name.end_with?('_layout')
+        next unless value.is_a?(Hash)
+
+        # Convert `ActionController::Parameters` to `Hash`
+        value = value.to_hash
+
+        setting = EasySetting.find_or_initialize_by(name: name, project_id: @project.id)
+        setting.value = value
+        setting.save
+      end
+    end
+
+    respond_to do |format|
+      format.api { render_api_ok }
+    end
+  end
+
+  private
+
+    def authorize
+      unless User.current.allowed_to?(:edit_project, @project)
+        return render_403
+      end
+    end
+
+end
diff --git a/plugins/easy_mindmup/app/helpers/easy_mindmup_helper.rb b/plugins/easy_mindmup/app/helpers/easy_mindmup_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4bebec3c2f42b604add8811a31e4835b9e556954
--- /dev/null
+++ b/plugins/easy_mindmup/app/helpers/easy_mindmup_helper.rb
@@ -0,0 +1,35 @@
+module EasyMindmupHelper
+
+  # def avatar_url_from_avatar(avatar_html)
+  #   start_index = avatar_html.index('src="')
+  #   return '' if start_index.nil?
+  #   start_index += 5
+  #   end_index = avatar_html.index('"', start_index)
+  #   avatar_html[start_index, end_index - start_index]
+  # end
+
+  def mindmup_avatar_url(user)
+    if defined?(avatar_url)
+      avatar_url(user)
+    elsif Setting.gravatar_enabled?
+      gravatar_url(user.mail.to_s.downcase, size: 64, default: Setting.gravatar_default)
+    else
+      ''
+    end
+  end
+
+  def prepare_test_includes(tests)
+    includes = []
+    tests.each do |test_file|
+      plugin = 'easy_mindmup'
+      if test_file.include?('/')
+        split = test_file.split('/')
+        plugin = split[0]
+        test_file = split[1]
+      end
+      includes << [test_file, plugin]
+    end
+    includes
+  end
+
+end
diff --git a/plugins/easy_mindmup/app/views/easy_mindmup/_hotkeys.html.erb b/plugins/easy_mindmup/app/views/easy_mindmup/_hotkeys.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..2e12624a68b60bf3b79d5d1b8c36ca80ceccc252
--- /dev/null
+++ b/plugins/easy_mindmup/app/views/easy_mindmup/_hotkeys.html.erb
@@ -0,0 +1,31 @@
+  <div class="mindmup-hotkeys-source" style="display:none">
+    <h3 class="title"><%= l(:title_shortcuts, :scope => [:easy_mindmup, :hotkeys]) %></h3>
+    <h1><%= l(:title_key_shortcuts, :scope => [:easy_mindmup, :hotkeys]) %></h1>
+    <p><%= l(:info_mac_metakey, :scope => [:easy_mindmup, :hotkeys]) %></p>
+    <table class="table table-striped">
+    <colgroup><col width="30%"></col><col width="70%"></col></colgroup>
+    <tbody>
+      <% l(:keyboard, :scope => [:easy_mindmup, :hotkeys]).each do |group| %>
+        <tr>
+          <td colspan="2"><h2><%= group[:title] %></h2></td>
+        </tr>
+        <% group[:hotkeys].each do |item| %>
+          <tr>
+            <td><%= item[:hotkey] %></td>
+            <td><%= item[:info] %></td>
+          </tr>
+        <% end
+      end %>
+    </tbody>
+    </table>
+    <h1><%= l(:title_mouse_shortcuts, :scope => [:easy_mindmup, :hotkeys]) %></h1>
+    <table class="table table-striped">
+    <colgroup><col width="20%"></col><col width="80%"></col></colgroup>
+      <% l(:mouse, :scope => [:easy_mindmup, :hotkeys]).each do |item| %>
+        <tr>
+          <td><%= item[:action] %></td>
+          <td><%= item[:gesture] %></td>
+        </tr>
+      <% end %>
+    </table>
+  </div>
\ No newline at end of file
diff --git a/plugins/easy_mindmup/app/views/easy_mindmup/_includes.html.erb b/plugins/easy_mindmup/app/views/easy_mindmup/_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3a2407551b43ec65390cd4062325f5d33d24afee
--- /dev/null
+++ b/plugins/easy_mindmup/app/views/easy_mindmup/_includes.html.erb
@@ -0,0 +1,71 @@
+<% include_calendar_headers_tags %>
+<% heads_for_wiki_formatter %>
+<% if EasyMindmup.easy_extensions? %>
+  <%= stylesheet_link_tag('easy_mindmup', :media => 'all') %>
+<% else %>
+  <%= stylesheet_link_tag('context_menu', :media => 'all') %>
+<% end %>
+<% if EasyMindmup.combine_by_pipeline?(params) %>
+  <%= javascript_include_tag('easy_mindmup') %>
+<% else %>
+  <%= javascript_include_tag(
+          'external',
+
+          'mindmup/mapjs',
+          'mindmup/clipboard',
+          'mindmup/content',
+          'mindmup/dom-map-view',
+          'mindmup/dom-map-widget',
+          'mindmup/hammer-draggable',
+          # 'mindmup/image-drop-widget',
+          'mindmup/layout',
+          #'mindmup/link-edit-widget',
+          'mindmup/map-model',
+          'mindmup/map-toolbar-widget',
+          'mindmup/observable',
+          # 'mindmup/url-helper',
+
+          'polyfill',
+          'utils',
+          'main',
+          'mapjs_init',
+          'event_bus',
+          'redrawer',
+          'toolbar',
+          'history',
+          'data',
+          'links',
+          'loader',
+          # 'last_state',
+          'saver',
+          'autosave',
+          'save_progress',
+          'save_info',
+          'filter',
+          'node_patch',
+          'dom_patch',
+          'map_model_patch',
+          'model_classes',
+          'after_change',
+          'styles',
+          'logger',
+          # 'gateway',
+          # 'modals',
+          'validator',
+          'storage',
+          'legend',
+          'legend_events',
+          'expand_all',
+          'print',
+          'mm_context_menu',
+          'layout_patch',
+          'content_patch',
+          'links',
+          'link_edit_widget', :plugin => 'easy_mindmup')
+  %>
+<% end %>
+
+
+<% if EasyMindmup.easy_extensions?
+     include_front_end_commons if respond_to? :include_front_end_commons
+   end %>
diff --git a/plugins/easy_mindmup/app/views/easy_mindmup/_js_prepare.html.erb b/plugins/easy_mindmup/app/views/easy_mindmup/_js_prepare.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0aaff397c7cda4695d0986e04d9f536364437c02
--- /dev/null
+++ b/plugins/easy_mindmup/app/views/easy_mindmup/_js_prepare.html.erb
@@ -0,0 +1,141 @@
+<script type="text/javascript">
+  window.easyMindMupSetting = $.extend(true, window.easyMindMupSetting, <%= {
+    easyRedmine: EasyMindmup.easy_extensions?,
+    rootID: @project.id,
+    apiKey: User.current.api_key,
+    noSave: params[:nosave].present?,
+    allIcons: false,
+    paths: {
+      root: home_path
+    },
+    labels: {
+      free: {
+        headerNotAvailable: l('easy_mindmup.free.header_not_available'),
+        buttonUpgrade: l('easy_mindmup.free.button_upgrade')
+      },
+      errors: {
+        warning_delete_node: l('easy_mindmup.warning_delete_node'),
+        warning_delete_nodes: l('easy_mindmup.warning_delete_nodes')
+      },
+      types: {},
+      gateway: {
+        multiSuccess: l('easy_mindmup.info_all_saved'),
+        multiFail: l('easy_mindmup.info_any_failed'),
+        PUTfail: l('easy_mindmup.error_update'),
+        POSTfail: l('easy_mindmup.error_create'),
+        DELETEfail: l('easy_mindmup.error_delete'),
+        response_403: l('easy_mindmup.info_no_permission'),
+        notProperlySaved: l('easy_mindmup.warning_not_saved')
+      },
+      legend: {},
+      buttons: {
+        close: l(:button_close),
+        button_yes: l(:general_text_Yes),
+        button_no: l(:general_text_No)
+      },
+      context: {
+        expand: l('easy_mindmup.button_expand'),
+        collapse: l('easy_mindmup.button_collapse'),
+        goto: l('easy_mindmup.label_go_to'),
+        rename: l(:button_rename),
+        editData: l('easy_mindmup.button_edit_data'),
+        add: l(:button_add),
+        addChild: l('easy_mindmup.button_add_child'),
+        addSibling: l('easy_mindmup.button_add_sibling'),
+        addParent: l('easy_mindmup.button_add_parent'),
+        remove: l(:button_delete),
+      },
+      links: {
+      },
+      save_info: {
+        initial: l('easy_mindmup.save_info.loaded'),
+        saved: l('easy_mindmup.save_info.saved'),
+        autosaved: l('easy_mindmup.save_info.autosaved'),
+        at: l('easy_mindmup.save_info.at')
+      },
+      titleNodeChanged: l('easy_mindmup.title_node_changed')
+    },
+    templates: {
+      legendTemplate: %{
+        {{#filter}}
+        <div class="mindmup-legend__filter_cont">
+          <a class="easy-mindmup__icon easy-mindmup__icon--cancel_filter mindmup-legend__filter_cancel_icon"></a>
+          <a class="easy-mindmup__icon easy-mindmup__icon--filter mindmup-legend__filter_icon"></a>
+        </div>
+        {{/filter}}
+        {{#items}}
+          {{{.}}}
+        {{/items}}
+        <a href="javascript:void(0)" class="mindmup-legend-used mindmup-legend-used-used--{{active}}">#{l('easy_mindmup.label_show_unused')}</a>
+        <a href="javascript:void(0)" class="mindmup-legend-used mindmup-legend-used-all--{{active}}">#{l('easy_mindmup.label_hide_unused')}</a>
+        <div class="hotkey_link"><a href="javascript:void(0)">#{l('easy_mindmup.button_hotkeys')}</a></div>
+      },
+      upgrade: %{
+        <h3 class="title">#{l('easy_mindmup.free.header_not_available')}</h3>
+        <p>
+          {{#filtering}}
+            #{l('easy_mindmup.free.feature_filtering')}
+          {{/filtering}}
+          {{#coloring}}
+            #{l('easy_mindmup.free.feature_coloring')}
+          {{/coloring}}
+          {{#context_menu}}
+            #{l('easy_mindmup.free.feature_context_menu')}
+          {{/context_menu}}
+          {{#dnd_property}}
+            #{l('easy_mindmup.free.feature_dnd_property')}
+          {{/dnd_property}}
+          {{text}}
+        </p>
+        <a id="upgrade_link" style="display:none" href="{{href}}">link</a>
+      },
+      reloadErrors: %{
+        {{#.}}
+          <li>
+            {{#isProject}}#{l(:label_project)}{{/isProject}}
+            {{^isProject}}#{l(:label_issue)}{{/isProject}}
+            {{#changed}}
+              "{{name}}" #{l('easy_mindmup.last_state_modal.message_changed')}
+            {{/changed}}
+            {{#moved}}
+              "{{name}}" #{l('easy_mindmup.last_state_modal.message_moved')}
+            {{/moved}}
+            {{#present}}
+              "{{name}}" #{l('easy_mindmup.last_state_modal.message_present')}
+            {{/present}}
+            {{#missing}}
+              "{{name}}" #{l('easy_mindmup.last_state_modal.message_missing')}
+            {{/missing}}
+          </li>
+        {{/.}}
+      },
+      lastStateModal: %{
+        <h3 class="title">#{l('easy_mindmup.last_state_modal.title')}</h3>
+        <h4>#{l('easy_mindmup.last_state_modal.label_differencies')}:</h4>
+        <ul class="mindmup-last-modal-diffs">
+          {{{differences}}}
+        </ul>
+        <p>#{l('easy_mindmup.last_state_modal.text_reload_appeal')}</p>
+      },
+      storedModal: %{
+        <h3 class="title">#{l('easy_mindmup.stored_modal.title')}</h3>
+        <p>#{l('easy_mindmup.stored_modal.text_load_appeal')}</p>
+        <button id="stored_state_modal_local">#{l('easy_mindmup.stored_modal.button_local')}</button>
+        <button id="stored_state_modal_server">#{l('easy_mindmup.stored_modal.button_server')}</button>
+      },
+      saveProgressModal: %{
+        <div class="mindmup-progress-modal">
+          <div class="mindmup-progress-title"><h3>#{l('easy_mindmup.label_save_progress')}</h3></div>
+          <div class="mindmup-progress-cont">
+            <div class="mindmup-progress-bar">
+            </div>
+          </div>
+        </div>
+      }
+    }
+  }.to_json.html_safe %>);
+
+  $(document).ready(function(){
+    $("p.nodata").remove()
+  })
+</script>
diff --git a/plugins/easy_mindmup/app/views/easy_mindmup/_test_includes.html.erb b/plugins/easy_mindmup/app/views/easy_mindmup/_test_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..55bcea0e5231a6ba8dc6b6737fceaaf8330e5b62
--- /dev/null
+++ b/plugins/easy_mindmup/app/views/easy_mindmup/_test_includes.html.erb
@@ -0,0 +1,28 @@
+<%=
+  javascript_include_tag(
+      # test framework
+      'jasmine/helpers/test',
+      'jasmine/jasmine_lib/jasmine',
+      'jasmine/jasmine_lib/jasmine-html',
+      'jasmine/jasmine_lib/boot',
+
+      # common tests
+      'jasmine/main',
+      'jasmine/parse_form',
+      'jasmine/saver_linearize',
+      plugin: :easy_mindmup)
+%>
+<% extra_test_names = params[:run_jasmine_tests]
+   if extra_test_names != 'true'
+     if extra_test_names.is_a?(String)
+       extra_tests = prepare_test_includes([extra_test_names])
+     elsif extra_test_names.is_a?(Array)
+       extra_tests = prepare_test_includes(extra_test_names)
+     else
+       extra_tests = []
+     end
+     extra_tests.each do |test, plugin| %>
+    <%= javascript_include_tag("jasmine/#{test}", plugin: plugin) %>
+  <% end %>
+<% end %>
+<%= stylesheet_link_tag('jasmine', media: 'all', plugin: :easy_mindmup) %>
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.eot b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.eot
new file mode 100644
index 0000000000000000000000000000000000000000..c1cc0f7fd9a9233ce0235a17d8761f224c1b2aea
Binary files /dev/null and b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.eot differ
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.otf b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.otf
new file mode 100644
index 0000000000000000000000000000000000000000..f8500754524df47597aace5f694a24f3125e129b
Binary files /dev/null and b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.otf differ
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.svg b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b46f165d8b9fb8f172ddcc20456e072546e4188a
--- /dev/null
+++ b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.svg
@@ -0,0 +1,2411 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
+<metadata>
+Created by FontForge 20161004 at Mon Feb 06 14:01:27 2017
+ By milos
+Copyright 2015 Google, Inc. All Rights Reserved.
+</metadata>
+<defs>
+<font id="MaterialIcons-Regular" horiz-adv-x="512" >
+  <font-face 
+    font-family="Material Icons"
+    font-weight="400"
+    font-stretch="normal"
+    units-per-em="512"
+    panose-1="2 0 5 3 0 0 0 0 0 0"
+    ascent="512"
+    descent="0"
+    bbox="0 0 512 512.5"
+    underline-thickness="50"
+    underline-position="-200"
+    unicode-range="U+0030-10FFFD"
+  />
+<missing-glyph 
+d="M17 0v341h136v-341h-136zM34 17h102v307h-102v-307z" />
+    <glyph glyph-name="uniE000" unicode="error" 
+d="M277 235v128h-42v-128h42zM277 149v43h-42v-43h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE001" unicode="error_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 363h42v-128h-42v128z
+M235 192h42v-43h-42v43z" />
+    <glyph glyph-name="uniE002" unicode="warning" 
+d="M277 213v86h-42v-86h42zM277 128v43h-42v-43h42zM21 64l235 405l235 -405h-470z" />
+    <glyph glyph-name="uniE003" unicode="add_alert" 
+d="M341 234v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM403 153l45 -45v-23h-384v23l45 45v124q0 51 32 91.5t81 51.5v15q0 14 10 24t24 10t24 -10t10 -24v-15q49 -11 81 -51.5t32 -91.5v-124zM214 64h84q0 -17 -12.5 -30t-29.5 -13t-29.5 13t-12.5 30z" />
+    <glyph glyph-name="uniE019" unicode="album" 
+d="M256 277q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM256 160q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE01B" unicode="av_timer" 
+d="M128 256q0 9 6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15zM384 256q0 -9 -6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6t15 -6t6 -15zM235 448h21q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136q0 96 77 153v1l145 -145l-30 -30l-116 115
+q-33 -41 -33 -94q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5t43.5 105.5q0 56 -37 98t-91 50v-41h-42v85zM235 149q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15t-15 -6t-15 6t-6 15z" />
+    <glyph glyph-name="uniE01C" unicode="closed_caption" 
+d="M384 277v22q0 9 -6 15t-15 6h-64q-9 0 -15.5 -6t-6.5 -15v-86q0 -9 6.5 -15t15.5 -6h64q9 0 15 6t6 15v22h-32v-11h-43v64h43v-11h32zM235 277v22q0 9 -6.5 15t-15.5 6h-64q-9 0 -15 -6t-6 -15v-86q0 -9 6 -15t15 -6h64q9 0 15.5 6t6.5 15v22h-32v-11h-43v64h43v-11h32z
+M405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE01D" unicode="equalizer" 
+d="M341 320h86v-235h-86v235zM85 85v171h86v-171h-86zM213 85v342h86v-342h-86z" />
+    <glyph glyph-name="uniE01E" unicode="explicit" 
+d="M320 320v43h-128v-214h128v43h-85v43h85v42h-85v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE01F" unicode="fast_forward" 
+d="M277 384l182 -128l-182 -128v256zM85 128v256l182 -128z" />
+    <glyph glyph-name="uniE020" unicode="fast_rewind" 
+d="M245 256l182 128v-256zM235 128l-182 128l182 128v-256z" />
+    <glyph glyph-name="uniE021" unicode="games" 
+d="M352 320h117v-128h-117l-64 64zM192 160l64 64l64 -64v-117h-128v117zM160 320l64 -64l-64 -64h-117v128h117zM320 352l-64 -64l-64 64v117h128v-117z" />
+    <glyph glyph-name="uniE023" unicode="hearing" 
+d="M245 320q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5zM163 456q-56 -56 -56 -136t56 -136l-30 -30q-69 69 -69 166t69 166zM363 85q17 0 29.5 13t12.5 30h43q0 -35 -25 -60t-60 -25q-20 0 -35 7q-41 21 -59 76q-7 22 -36 44
+q-41 30 -61 67q-23 41 -23 83q0 63 43.5 106t106.5 43t106 -43t43 -106h-43q0 45 -30.5 76t-75.5 31t-76 -31t-31 -76q0 -32 17 -63q14 -27 50 -54q40 -30 51 -64q13 -38 36 -50q8 -4 17 -4z" />
+    <glyph glyph-name="uniE024" unicode="high_quality" 
+d="M309 224v64h43v-64h-43zM384 213v86q0 9 -6 15t-15 6h-64q-9 0 -15.5 -6t-6.5 -15v-86q0 -9 6.5 -15t15.5 -6h16v-32h32v32h16q9 0 15 6t6 15zM235 192v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298
+q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE028" unicode="loop" 
+d="M256 128v64l85 -85l-85 -86v64q-70 0 -120.5 50.5t-50.5 120.5q0 50 27 91l31 -31q-15 -27 -15 -60q0 -53 37.5 -90.5t90.5 -37.5zM256 427q70 0 120.5 -50.5t50.5 -120.5q0 -50 -27 -91l-31 31q15 27 15 60q0 53 -37.5 90.5t-90.5 37.5v-64l-85 85l85 86v-64z" />
+    <glyph glyph-name="uniE029" unicode="mic" 
+d="M369 277h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5t79.5 30.5t33.5 77.5zM256 213q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE02A" unicode="mic_none" 
+d="M369 277h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5t79.5 30.5t33.5 77.5zM230 407v-132q0 -10 7.5 -17.5t18.5 -7.5q10 0 17.5 7t7.5 18l1 132q0 11 -8 18.5t-18 7.5t-18 -7.5t-8 -18.5zM256 213
+q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE02B" unicode="mic_off" 
+d="M91 448l357 -357l-27 -27l-89 89q-22 -14 -55 -19v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -77.5t79.5 -30.5q25 0 49 11l-35 35q-8 -2 -14 -2q-26 0 -45 19t-19 45v16l-128 128zM320 274l-128 127v4q0 26 19 45t45 19t45 -19t19 -45v-131zM405 277
+q0 -37 -19 -70l-26 27q9 20 9 43h36z" />
+    <glyph glyph-name="uniE02C" unicode="movie" 
+d="M384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -86h64l-42 86h42l43 -86h64l-43 86h43l43 -86h64z" />
+    <glyph glyph-name="uniE02E" unicode="library_add" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE02F" unicode="library_books" 
+d="M405 363v42h-213v-42h213zM320 192v43h-128v-43h128zM405 277v43h-213v-43h213zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5
+v299h42z" />
+    <glyph glyph-name="uniE030" unicode="library_music" 
+d="M85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42zM384 363v42h-85v-117q-14 11 -32 11q-22 0 -38 -16t-16 -38t16 -37.5t38 -15.5t37.5 15.5t15.5 37.5v118h64zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13
+t-13 30v256q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE031" unicode="new_releases" 
+d="M277 235v128h-42v-128h42zM277 149v43h-42v-43h42zM491 256l-52 -59l7 -79l-77 -17l-40 -68l-73 31l-73 -31l-40 67l-77 18l7 79l-52 59l52 60l-7 78l77 17l40 68l73 -31l73 31l40 -68l77 -17l-7 -79z" />
+    <glyph glyph-name="uniE033" unicode="not_interested" 
+d="M391 151q36 45 36 105q0 70 -50.5 120.5t-120.5 50.5q-60 0 -105 -36zM256 85q60 0 105 36l-240 240q-36 -45 -36 -105q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5
+z" />
+    <glyph glyph-name="uniE034" unicode="pause" 
+d="M299 405h85v-298h-85v298zM128 107v298h85v-298h-85z" />
+    <glyph glyph-name="uniE035" unicode="pause_circle_filled" 
+d="M320 171v170h-43v-170h43zM235 171v170h-43v-170h43zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE036" unicode="pause_circle_outline" 
+d="M277 171v170h43v-170h-43zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z
+M192 171v170h43v-170h-43z" />
+    <glyph glyph-name="uniE037" unicode="play_arrow" 
+d="M171 405l234 -149l-234 -149v298z" />
+    <glyph glyph-name="uniE038" unicode="play_circle_filled" 
+d="M213 160l128 96l-128 96v-192zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE039" unicode="play_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM213 160v192l128 -96z" />
+    <glyph glyph-name="uniE03B" unicode="playlist_add" 
+d="M43 171v42h170v-42h-170zM384 213h85v-42h-85v-86h-43v86h-85v42h85v86h43v-86zM299 384v-43h-256v43h256zM299 299v-43h-256v43h256z" />
+    <glyph glyph-name="uniE03C" unicode="queue" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE03D" unicode="queue_music" 
+d="M363 384h106v-43h-64v-192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19q8 0 22 -4v175zM64 171v42h171v-42h-171zM320 299v-43h-256v43h256zM320 384v-43h-256v43h256z" />
+    <glyph glyph-name="uniE03E" unicode="radio" 
+d="M427 256v85h-342v-85h256v43h43v-43h43zM149 85q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM69 381l270 110l14 -36l-176 -71h250q18 0 30 -12.5t12 -30.5v-256q0 -17 -12 -29.5t-30 -12.5h-342q-18 0 -30 12.5t-12 29.5v256q0 30 26 40z" />
+    <glyph glyph-name="uniE03F" unicode="recent_actors" 
+d="M267 149v16q0 22 -33 35t-63 13t-63 -13t-33 -35v-16h192zM171 347q-19 0 -33.5 -14.5t-14.5 -33.5t14.5 -33.5t33.5 -14.5t33.5 14.5t14.5 33.5t-14.5 33.5t-33.5 14.5zM299 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-256q-9 0 -15.5 6t-6.5 15v256q0 9 6.5 15
+t15.5 6h256zM363 107v298h42v-298h-42zM448 405h43v-298h-43v298z" />
+    <glyph glyph-name="uniE040" unicode="repeat" 
+d="M363 149v86h42v-128h-256v-64l-85 85l85 85v-64h214zM149 363v-86h-42v128h256v64l85 -85l-85 -85v64h-214z" />
+    <glyph glyph-name="uniE041" unicode="repeat_one" 
+d="M277 192h-32v85h-32v22l43 21h21v-128zM363 149v86h42v-128h-256v-64l-85 85l85 85v-64h214zM149 363v-86h-42v128h256v64l85 -85l-85 -85v64h-214z" />
+    <glyph glyph-name="uniE042" unicode="replay" 
+d="M256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE043" unicode="shuffle" 
+d="M316 226l67 -67l44 44v-118h-118l44 44l-67 67zM309 427h118v-118l-44 44l-268 -268l-30 30l268 268zM226 316l-30 -30l-111 111l30 30z" />
+    <glyph glyph-name="uniE044" unicode="skip_next" 
+d="M341 384h43v-256h-43v256zM128 128v256l181 -128z" />
+    <glyph glyph-name="uniE045" unicode="skip_previous" 
+d="M203 256l181 128v-256zM128 384h43v-256h-43v256z" />
+    <glyph glyph-name="uniE046" unicode="snooze" 
+d="M192 277v43h128v-38l-77 -90h77v-43h-128v39l77 89h-77zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE047" unicode="stop" 
+d="M128 384h256v-256h-256v256z" />
+    <glyph glyph-name="uniE048" unicode="subtitles" 
+d="M427 213v43h-214v-43h214zM427 128v43h-86v-43h86zM299 128v43h-214v-43h214zM85 256v-43h86v43h-86zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE049" unicode="surround_sound" 
+d="M256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM377 135q50 50 50 121t-50 121l-31 -31q38 -38 38 -90q0 -54 -37 -91zM256 171q35 0 60 25t25 60t-25 60t-60 25t-60 -25t-25 -60t25 -60t60 -25zM166 166q-38 38 -38 90q0 54 37 91l-30 30
+q-50 -50 -50 -121t50 -121zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE04A" unicode="video_collection" 
+d="M256 203l128 96l-128 96v-192zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE04B" unicode="videocam" 
+d="M363 288l85 85v-234l-85 85v-75q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h256q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE04C" unicode="videocam_off" 
+d="M70 469l378 -378l-27 -27l-68 68q-6 -4 -12 -4h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h16l-58 58zM448 373v-228l-239 239h132q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE04D" unicode="volume_down" 
+d="M107 320h85l107 107v-342l-107 107h-85v128zM395 256q0 -59 -54 -86v172q54 -27 54 -86z" />
+    <glyph glyph-name="uniE04E" unicode="volume_mute" 
+d="M149 320h86l106 107v-342l-106 107h-86v128z" />
+    <glyph glyph-name="uniE04F" unicode="volume_off" 
+d="M256 427v-90l-45 45zM91 448l357 -357l-27 -27l-44 44q-37 -29 -78 -39v44q25 7 48 25l-91 91v-144l-107 107h-85v128h101l-101 101zM405 256q0 51 -29.5 90t-76.5 53v44q65 -14 107 -66.5t42 -120.5q0 -48 -22 -89l-32 33q11 27 11 56zM352 256q0 -9 -1 -13l-52 52v47
+q53 -26 53 -86z" />
+    <glyph glyph-name="uniE050" unicode="volume_up" 
+d="M299 443q65 -14 107 -66.5t42 -120.5t-42 -120.5t-107 -66.5v44q47 14 76.5 53t29.5 90t-29.5 90t-76.5 53v44zM352 256q0 -60 -53 -86v172q53 -26 53 -86zM64 320h85l107 107v-342l-107 107h-85v128z" />
+    <glyph glyph-name="uniE051" unicode="web" 
+d="M427 128v192h-86v-192h86zM320 235v85h-235v-85h235zM320 128v85h-235v-85h235zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE052" unicode="hd" 
+d="M309 224v64h43v-64h-43zM277 320v-128h86q9 0 15 6t6 15v86q0 9 -6 15t-15 6h-86zM235 192v128h-32v-53h-43v53h-32v-128h32v43h43v-43h32zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE053" unicode="sort_by_alpha" 
+d="M336 168h130v-34h-182v27l126 183h-125v34h177v-27zM106 221h83l-42 111zM130 378h35l96 -244h-39l-20 52h-109l-20 -52h-39zM219 99h99l-50 -50zM319 413h-101l50 50z" />
+    <glyph glyph-name="uniE055" unicode="airplay" 
+d="M448 448q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5t-30 -12.5h-85v42h85v256h-384v-256h85v-42h-85q-17 0 -30 12.5t-13 29.5v256q0 17 13 30t30 13h384zM128 43l128 128l128 -128h-256z" />
+    <glyph glyph-name="uniE056" unicode="forward_&#x31;&#x30;" 
+d="M282 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4q-3 -4 -3 -6v-43q3 -4 3 -6zM322 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-9 0 -13 2q-2 1 -5 3t-5 3q-9 5 -9 30v15q0 13 2 17l7 13q6 6 10 6q2 0 6.5 1
+t6.5 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM233 171h-20v70l-21 -6v15l38 12h3v-91zM85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50
+t-50.5 121z" />
+    <glyph glyph-name="uniE057" unicode="forward_&#x33;&#x30;" 
+d="M85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121zM284 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4
+q-2 -4 -2 -6v-43q2 -4 2 -6zM326 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-7 0 -23 8q-2 1 -6 13q-3 9 -3 17v15q0 11 3 17l6 13q7 6 11 6q2 0 6 1t6 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM213 224q15 0 15 13v4q-2 2 -2 4t-4 2h-11
+q-2 -2 -4 -2t-2 -4v-4h-22q0 8 5.5 15.5t12.5 7.5q1 0 5 1t5 1q12 0 24 -6q8 -4 8 -19v-7q-2 -4 -2 -6q0 -4 -4 -4q-2 0 -7 -5q9 -5 11 -8q4 -8 4 -13q0 -9 -2 -11q-1 -1 -3 -4t-3 -4q-4 -4 -11 -4q-2 0 -6.5 -1t-6.5 -1q-8 0 -10 2q-1 1 -5 2t-6 2q-9 5 -9 21h18v-4
+q2 -2 2 -4t4 -2h11q2 2 4 2t2 4v11q-2 2 -2 4t-4 2h-13v15h8z" />
+    <glyph glyph-name="uniE058" unicode="forward_&#x35;" 
+d="M250 222q-7 -3 -7 -4l-2 -3h-13l5 47h51v-15h-37l-2 -19q2 0 2 2q0 1 1.5 1.5t1.5 1.5h4h4q8 0 11 -3q1 -1 4 -3t4 -3q9 -9 9 -23q0 -9 -2 -11q-1 -1 -3 -5t-4 -6q-8 -8 -23 -8q-9 0 -11 2q-1 1 -4.5 2t-5.5 2q-9 5 -9 19h17q0 -10 13 -10q4 0 6 2l5 4q2 4 2 6v13l-2 4
+l-5 5q-4 2 -6 2h-4zM85 235q0 70 50 120t121 50v86l107 -107l-107 -107v86q-52 0 -90 -37.5t-38 -90.5t38 -90.5t90 -37.5t90 37.5t38 90.5h43q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121z" />
+    <glyph glyph-name="uniE059" unicode="replay_&#x31;&#x30;" 
+d="M282 188q0 -7 10 -7q5 0 7 2l4 5q2 4 2 6v43q-2 4 -2 6t-4.5 4.5t-6.5 2.5q-3 0 -6 -3l-4 -4q-3 -4 -3 -6v-43q3 -4 3 -6zM324 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-9 0 -13 2q-2 1 -5 3t-5 3q-9 5 -9 30v15q0 13 2 17l7 13q6 6 10 6q2 0 6.5 1
+t6.5 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM233 171h-20v70l-21 -6v15l38 12h3v-91zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107
+v-86z" />
+    <glyph glyph-name="uniE05A" unicode="replay_&#x33;&#x30;" 
+d="M286 188q0 -7 11 -7q4 0 6 2l4 5q2 4 2 6v43q0 1 -1 3t-1 3q0 2 -4 4.5t-6 2.5q-4 0 -7 -3l-4 -4q-2 -4 -2 -6v-43q2 -4 2 -6zM326 209q0 -13 -2 -17l-6 -13q-7 -6 -11 -6q-2 0 -6.5 -1t-6.5 -1q-7 0 -23 8q-2 1 -6 13q-3 9 -3 17v15q0 11 3 17l6 13q7 6 11 6q2 0 6 1
+t6 1q9 0 13 -2q2 -1 5.5 -3t5.5 -3t6 -13q2 -6 2 -17v-15zM213 224q15 0 15 13v4q-2 2 -2 4t-4 2h-11q-2 -2 -4 -2t-2 -4v-4h-22q0 8 5.5 15.5t12.5 7.5q1 0 5 1t5 1q12 0 24 -6q8 -4 8 -19v-7q-2 -4 -2 -6q0 -4 -4 -4q-2 0 -7 -5q9 -5 11 -8q4 -8 4 -13q0 -9 -2 -11
+q-1 -1 -3 -4t-3 -4q-4 -4 -11 -4q-2 0 -6.5 -1t-6.5 -1q-8 0 -10 2q-1 1 -5 2t-6 2q-9 5 -9 21h18v-4q2 -2 2 -4t4 -2h11q2 2 4 2t2 4v11q-2 2 -2 4t-4 2h-13v15h8zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5
+t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE05B" unicode="replay_&#x35;" 
+d="M252 222q-7 -3 -7 -4l-2 -3h-15l5 47h51v-15h-37l-2 -19q2 0 2 2q0 1 1.5 1.5t1.5 1.5h4h4q8 0 11 -3q1 -1 4 -3t4 -3q9 -9 9 -23q0 -9 -2 -11q-1 -1 -3 -5t-4 -6t-4.5 -3.5t-3.5 -2.5q-2 -2 -13 -2q-9 0 -11 2q-1 1 -4.5 2t-5.5 2q-9 5 -9 19h17q0 -10 13 -10q4 0 6 2
+l5 4q2 4 2 6v13l-2 4l-5 5q-4 2 -6 2h-4zM256 405q71 0 121 -50t50 -120q0 -71 -50.5 -121t-120.5 -50t-120.5 50t-50.5 121h43q0 -53 38 -90.5t90 -37.5t90 37.5t38 90.5t-38 90.5t-90 37.5v-86l-107 107l107 107v-86z" />
+    <glyph glyph-name="uniE05C" unicode="add_to_queue" 
+d="M341 299v-43h-64v-64h-42v64h-64v43h64v64h42v-64h64zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE05D" unicode="fiber_dvr" 
+d="M448 267v21q0 14 -9 23t-23 9h-75v-128h32v43h25l18 -43h32l-19 45q19 9 19 30zM269 192l38 128h-32l-22 -73l-21 73h-32l37 -128h32zM171 224v64q0 14 -9.5 23t-22.5 9h-75v-128h75q13 0 22.5 9t9.5 23zM448 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -17 -12.5 -30
+t-30.5 -13h-384q-18 0 -30.5 13t-12.5 30v298q0 18 12.5 30.5t30.5 12.5h384zM96 288h43v-64h-43v64zM373 288h43v-21h-43v21z" />
+    <glyph glyph-name="uniE05E" unicode="fiber_new" 
+d="M437 213v107h-26v-96h-24v75h-27v-75h-24v96h-27v-107q0 -9 6.5 -15t15.5 -6h85q9 0 15 6t6 15zM288 293v27h-85v-128h85v27h-53v23h53v27h-53v24h53zM181 192v128h-26v-75l-54 75h-26v-128h26v75l55 -75h25zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5
+t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE05F" unicode="playlist_play" 
+d="M363 235l106 -64l-106 -64v128zM43 192v43h277v-43h-277zM405 405v-42h-362v42h362zM405 320v-43h-362v43h362z" />
+    <glyph glyph-name="uniE060" unicode="art_track" 
+d="M224 192l-48 64l-37 -48l-27 32l-37 -48h149zM256 320v-128q0 -17 -13 -30t-30 -13h-128q-17 0 -29.5 13t-12.5 30v128q0 17 12.5 30t29.5 13h128q17 0 30 -13t13 -30zM299 149v43h170v-43h-170zM469 363v-43h-170v43h170zM469 235h-170v42h170v-42z" />
+    <glyph glyph-name="uniE061" unicode="fiber_manual_record" 
+d="M85 256q0 70 50.5 120.5t120.5 50.5t120.5 -50.5t50.5 -120.5t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5z" />
+    <glyph glyph-name="uniE062" unicode="fiber_smart_record" 
+d="M363 421q56 -14 92 -60t36 -105t-36 -105t-92 -60v44q38 13 61.5 46t23.5 75t-23.5 75t-61.5 46v44zM21 256q0 70 50.5 120.5t120.5 50.5t120.5 -50.5t50.5 -120.5t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5z" />
+    <glyph glyph-name="uniE063" unicode="music_video" 
+d="M171 192q0 26 19 45t45 19q7 0 21 -4v132h107v-43h-64v-150q0 -26 -19 -44.5t-45 -18.5t-45 19t-19 45zM448 107v298h-384v-298h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE064" unicode="subscriptions" 
+d="M341 171l-128 69v-139zM469 256v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v171q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30zM384 469v-42h-256v42h256zM427 341h-342v43h342v-43z" />
+    <glyph glyph-name="uniE065" unicode="playlist_add_check" 
+d="M459 267l32 -32l-149 -150l-97 96l32 32l65 -64zM43 171v42h170v-42h-170zM299 384v-43h-256v43h256zM299 299v-43h-256v43h256z" />
+    <glyph glyph-name="uniE066" unicode="queue_play_next" 
+d="M512 128l-96 -96l-32 32l64 64l-64 64l32 32zM277 299h64v-43h-64v-64h-42v64h-64v43h64v64h42v-64zM448 448q17 0 30 -12.5t13 -30.5v-170h-43v170h-384v-256h320v-42h-43v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE067" unicode="remove_from_queue" 
+d="M341 299v-43h-170v43h170zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE068" unicode="slow_motion_video" 
+d="M469 256q0 -82 -55 -143t-136 -69v43q63 8 106 56.5t43 112.5t-43 112.5t-106 56.5v43q81 -8 136 -69t55 -143zM121 91l30 30q37 -28 84 -34v-43q-63 6 -114 47zM87 235q6 -47 34 -83l-30 -31q-41 51 -47 114h43zM121 361q-28 -37 -34 -84h-43q6 63 47 114zM235 425
+q-47 -6 -84 -34l-30 30q51 41 114 47v-43zM278 303l63 -47q-63 -47 -128 -96z" />
+    <glyph glyph-name="uniE069" unicode="web_asset" 
+d="M405 128v213h-298v-213h298zM405 427q18 0 30.5 -13t12.5 -30v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE06A" unicode="fiber_pin" 
+d="M427 192v128h-27v-75l-53 75h-27v-128h27v75l54 -75h26zM267 192v128h-32v-128h32zM192 267v21q0 14 -9 23t-23 9h-75v-128h32v43h43q14 0 23 9.5t9 22.5zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256
+q0 18 12 30.5t30 12.5h342zM117 288h43v-21h-43v21z" />
+    <glyph glyph-name="uniE06B" unicode="branding_watermark" 
+d="M448 107v128h-192v-128h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06C" unicode="call_to_action" 
+d="M448 107v64h-384v-64h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06D" unicode="featured_play_list" 
+d="M256 363v42h-192v-42h192zM256 277v43h-192v-43h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06E" unicode="featured_video" 
+d="M256 256v149h-192v-149h192zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE06F" unicode="note" 
+d="M320 395v-118h117zM469 299v-171q0 -17 -12.5 -29.5t-29.5 -12.5l-342 -1q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h256z" />
+    <glyph glyph-name="uniE070" unicode="video_call" 
+d="M299 235v42h-64v64h-43v-64h-64v-42h64v-64h43v64h64zM363 288l85 85v-234l-85 85v-75q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v214q0 9 6 15t15 6h256q9 0 15.5 -6t6.5 -15v-75z" />
+    <glyph glyph-name="uniE071" unicode="video_label" 
+d="M448 171v234h-384v-234h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE0AF" unicode="business" 
+d="M384 192v-43h-43v43h43zM384 277v-42h-43v42h43zM427 107v213h-171v-43h43v-42h-43v-43h43v-43h-43v-42h171zM213 363v42h-42v-42h42zM213 277v43h-42v-43h42zM213 192v43h-42v-43h42zM213 107v42h-42v-42h42zM128 363v42h-43v-42h43zM128 277v43h-43v-43h43zM128 192v43
+h-43v-43h43zM128 107v42h-43v-42h43zM256 363h213v-299h-426v384h213v-85z" />
+    <glyph glyph-name="uniE0B0" unicode="call" 
+d="M141 282q48 -93 141 -141l47 47q6.66667 6.66667 14.2222 6.66667q3.77778 0 7.77778 -1.66667q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q1.23077 -4 1.23077 -7.6213
+q0 -8.14793 -6.23077 -14.3787z" />
+    <glyph glyph-name="uniE0B1" unicode="call_end" 
+d="M256 320q-52 0 -98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-25 24 -57 39q-12 5 -12 19v66q-50 16 -98 16z" />
+    <glyph glyph-name="uniE0B2" unicode="call_made" 
+d="M192 405h213v-213h-42v141l-248 -248l-30 30l248 248h-141v42z" />
+    <glyph glyph-name="uniE0B3" unicode="call_merge" 
+d="M160 341l96 96l96 -96h-75v-136l-128 -128l-30 30l116 115v119h-75zM363 77l-73 72l30 30l73 -72z" />
+    <glyph glyph-name="uniE0B4" unicode="call_missed" 
+d="M418 363l30 -30l-192 -192l-149 149v-98h-43v171h171v-43h-98l119 -119z" />
+    <glyph glyph-name="uniE0B5" unicode="call_received" 
+d="M427 397l-248 -248h141v-42h-213v213h42v-141l248 248z" />
+    <glyph glyph-name="uniE0B6" unicode="call_split" 
+d="M213 427l-49 -49l113 -113v-180h-42v162l-101 101l-49 -49v128h128zM299 427h128v-128l-49 49l-62 -62l-30 30l62 62z" />
+    <glyph glyph-name="uniE0B7" unicode="chat" 
+d="M384 341v43h-256v-43h256zM299 213v43h-171v-43h171zM128 320v-43h256v43h-256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0B8" unicode="clear_all" 
+d="M149 363h299v-43h-299v43zM64 149v43h299v-43h-299zM107 235v42h298v-42h-298z" />
+    <glyph glyph-name="uniE0B9" unicode="comment" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM469 427v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -29.5z" />
+    <glyph glyph-name="uniE0BA" unicode="contacts" 
+d="M363 149v32q0 24 -36.5 39t-70.5 15t-70.5 -15t-36.5 -39v-32h214zM256 368q-20 0 -34 -14t-14 -34t14 -34t34 -14t34 14t14 34t-14 34t-34 14zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13
+h342zM85 0v43h342v-43h-342zM427 512v-43h-342v43h342z" />
+    <glyph glyph-name="uniE0BB" unicode="dialer_sip" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM427 405v22h-22v-22h22zM384 448h64v-64h-43v-43h-21v107z
+M320 405v-64h-64v22h43v21h-43v64h64v-21h-43v-22h43zM363 448v-107h-22v107h22z" />
+    <glyph glyph-name="uniE0BC" unicode="dialpad" 
+d="M256 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 363q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 363q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 235q17 0 30 -13t13 -30t-13 -30
+t-30 -13t-30 13t-13 30t13 30t30 13zM256 235q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 405q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13zM128 235q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 363
+q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 107q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE0BE" unicode="email" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE0BF" unicode="forum" 
+d="M363 256q0 -9 -6.5 -15t-15.5 -6h-213l-85 -86v299q0 9 6 15t15 6h277q9 0 15.5 -6t6.5 -15v-192zM448 384q9 0 15 -6t6 -15v-320l-85 85h-235q-9 0 -15 6t-6 15v43h277v192h43z" />
+    <glyph glyph-name="uniE0C3" unicode="import_export" 
+d="M341 149h64l-85 -85l-85 85h64v150h42v-150zM192 448l85 -85h-64v-150h-42v150h-64z" />
+    <glyph glyph-name="uniE0C4" unicode="invert_colors_off" 
+d="M256 403l-49 -48l-30 30l79 79l121 -121q38 -38 47 -91.5t-13 -100.5l-155 154v98zM256 94v103l-102 102q-26 -34 -26 -77q0 -52 38 -90t90 -38zM441 67l7 -8l-27 -27l-58 58q-47 -38 -107 -38q-71 0 -121 50q-46 47 -49.5 112.5t37.5 115.5l-59 59l27 27
+q299 -299 350 -349z" />
+    <glyph glyph-name="uniE0C6" unicode="live_help" 
+d="M321 293q20 20 20 48q0 35 -25 60.5t-60 25.5t-60 -25.5t-25 -60.5h42q0 17 13 30t30 13t30 -13t13 -30t-13 -30l-26 -27q-25 -27 -25 -60v-11h42q0 34 25 61zM277 128v43h-42v-43h42zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64
+h-85q-18 0 -30.5 13t-12.5 30v299q0 17 12.5 29.5t30.5 12.5h298z" />
+    <glyph glyph-name="uniE0C7" unicode="location_off" 
+d="M250 267q109 -108 177 -176l-27 -27l-72 71q-16 -24 -34 -47t-28 -34l-10 -11q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 11 4 33l-68 68l27 27l178 -178zM256 373q-23 0 -39 -18l-69 68q44 46 108 46q62 0 105.5 -43.5t43.5 -105.5q0 -48 -36 -117l-77 78
+q17 15 17 39q0 22 -15.5 37.5t-37.5 15.5z" />
+    <glyph glyph-name="uniE0C8" unicode="location_on" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE0C9" unicode="message" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CA" unicode="chat_bubble" 
+d="M427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CB" unicode="chat_bubble_outline" 
+d="M427 171v256h-342v-299l43 43h299zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0CC" unicode="no_sim" 
+d="M78 429l373 -372l-28 -28l-40 41q-12 -6 -20 -6h-214q-17 0 -29.5 13t-12.5 30v239l-56 56zM405 405v-249l-242 242l50 50h150q17 0 29.5 -13t12.5 -30z" />
+    <glyph glyph-name="uniE0CD" unicode="phone" 
+d="M141 282q48 -93 141 -141l47 47q10 10 22 5q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22z" />
+    <glyph glyph-name="uniE0CE" unicode="portable_wifi_off" 
+d="M70 459l21 -22l357 -357l-27 -27l-160 161h-1l-4 -1q-17 0 -30 13t-13 30l1 4l-34 34q-9 -18 -9 -38q0 -49 42 -74l-21 -37q-29 17 -46.5 46.5t-17.5 64.5q0 38 20 69l-30 31q-33 -46 -33 -100q0 -46 23 -85.5t62 -62.5l-21 -37q-49 28 -77.5 77.5t-28.5 107.5
+q0 74 44 131l-44 45zM256 427q-43 0 -80 -20l-31 31q50 31 111 31q88 0 150.5 -62.5t62.5 -150.5q0 -61 -31 -111l-32 31q21 39 21 80q0 70 -50.5 120.5t-120.5 50.5zM375 208l-35 35q1 4 1 13q0 35 -25 60t-60 25q-9 0 -13 -1l-35 35q22 9 48 9q53 0 90.5 -37.5t37.5 -90.5
+q0 -26 -9 -48z" />
+    <glyph glyph-name="uniE0CF" unicode="contact_phone" 
+d="M381 213q-8 21 -8 43t8 43h35l32 42l-42 43q-44 -33 -59 -85q-6 -21 -6 -43t6 -43q15 -52 59 -85l42 43l-32 42h-35zM299 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM171 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM469 448
+q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h426z" />
+    <glyph glyph-name="uniE0D0" unicode="contact_mail" 
+d="M469 256v128h-170v-128h170zM299 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM171 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM469 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30v298
+q0 17 13 30t30 13h426zM448 341l-64 -42l-64 42v22l64 -43l64 43v-22z" />
+    <glyph glyph-name="uniE0D1" unicode="ring_volume" 
+d="M137 303q-74 74 -76 75l30 31l76 -76zM277 469v-106h-42v106h42zM451 378l-76 -75l-30 30l76 76zM506 156q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-28 26 -57 40q-12 5 -12 19v66q-46 15 -98 15t-98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6
+l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100z" />
+    <glyph glyph-name="uniE0D2" unicode="speaker_phone" 
+d="M320 85v171h-128v-171h128zM317 298q10 0 17 -7t7 -17v-207q0 -10 -7 -17t-17 -7h-122q-10 0 -17 7t-7 17v207q0 10 7 17.5t17 7.5zM256 491q96 0 165 -69l-30 -30q-56 56 -135 56t-135 -56l-30 30q69 69 165 69zM149 361q44 44 107 44t107 -44l-31 -30q-31 31 -76 31
+t-76 -31z" />
+    <glyph glyph-name="uniE0D3" unicode="stay_current_landscape" 
+d="M405 363h-298v-214h298v214zM22 363q0 17 12.5 29.5t29.5 12.5h384q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5z" />
+    <glyph glyph-name="uniE0D4" unicode="stay_current_portrait" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE0D5" unicode="stay_primary_landscape" 
+d="M405 363h-298v-214h298v214zM22 363q0 17 12.5 29.5t29.5 12.5h384q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5z" />
+    <glyph glyph-name="uniE0D6" unicode="stay_primary_portrait" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE0D7" unicode="swap_calls" 
+d="M384 427l85 -86h-64v-149q0 -35 -25 -60t-60 -25t-60 25t-25 60v149q0 17 -13 30t-30 13t-30 -13t-13 -30v-149h64l-85 -85l-85 85h64v149q0 35 25 60.5t60 25.5t60 -25.5t25 -60.5v-149q0 -17 13 -30t30 -13t30 13t13 30v149h-64z" />
+    <glyph glyph-name="uniE0D8" unicode="textsms" 
+d="M363 277v43h-43v-43h43zM277 277v43h-42v-43h42zM192 277v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE0D9" unicode="voicemail" 
+d="M395 192q31 0 52.5 22t21.5 53t-21.5 52.5t-52.5 21.5t-53 -21.5t-22 -52.5t22 -53t53 -22zM117 192q31 0 53 22t22 53t-22 52.5t-53 21.5t-52.5 -21.5t-21.5 -52.5t21.5 -53t52.5 -22zM395 384q49 0 83 -34t34 -83t-34 -83.5t-83 -34.5h-278q-49 0 -83 34.5t-34 83.5
+t34 83t83 34t83.5 -34t34.5 -83q0 -43 -27 -75h96q-27 32 -27 75q0 49 34.5 83t83.5 34z" />
+    <glyph glyph-name="uniE0DA" unicode="vpn_key" 
+d="M149 213q17 0 30 13t13 30t-13 30t-30 13t-29.5 -13t-12.5 -30t12.5 -30t29.5 -13zM270 299h221v-86h-43v-85h-85v85h-93q-13 -38 -46 -61.5t-75 -23.5q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q42 0 75 -23.5t46 -61.5z" />
+    <glyph glyph-name="uniE0DB" unicode="phonelink_erase" 
+d="M405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213zM277 337l-85 -85l85 -86l-21 -21l-85 85l-86 -85l-21 21l85 86l-85 85l21 21l86 -85l85 85z" />
+    <glyph glyph-name="uniE0DC" unicode="phonelink_lock" 
+d="M203 277v32q0 12 -9.5 20t-22.5 8t-22.5 -8t-9.5 -20v-32h64zM230 277q10 0 18 -8t8 -19v-75q0 -10 -8.5 -18t-19.5 -8h-117q-10 0 -18 8.5t-8 19.5v75q0 10 8 17.5t18 7.5v32q0 22 18.5 38t41.5 16t41 -16t18 -38v-32zM405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30
+t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213z" />
+    <glyph glyph-name="uniE0DD" unicode="phonelink_ring" 
+d="M299 85v342h-214v-342h214zM299 491q17 0 29.5 -13t12.5 -30v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13h214zM384 303q20 -21 20 -47t-20 -45l-21 22q18 25 0 49zM429 348q40 -38 40 -91.5t-40 -90.5l-22 22q29 31 29 70.5
+t-29 67.5z" />
+    <glyph glyph-name="uniE0DE" unicode="phonelink_setup" 
+d="M405 491q17 0 30 -13t13 -30v-384q0 -17 -13 -30t-30 -13h-213q-17 0 -30 13t-13 30v64h43v-43h213v342h-213v-43h-43v64q0 17 13 30t30 13h213zM171 213q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13t-30 -13t-13 -30t13 -30t30 -13zM252 245l23 -19q4 -4 2 -6l-21 -37
+q-2 -2 -6 -2l-28 11q-14 -9 -19 -11l-5 -27q-5 -5 -6 -5h-43q-2 0 -3.5 2t-0.5 3l-4 27q-5 2 -19 11l-30 -9q-2 -2 -7 3l-21 36q0 4 2 8l24 17v22l-24 17q-4 4 -2 6l21 37q2 2 7 2l27 -11q13 9 20 11l4 27q5 5 6 5h43q6 0 6 -5l5 -27q5 -2 19 -11l28 9q2 1 6 -3l21 -36
+q0 -4 -2 -6l-23 -17v-22z" />
+    <glyph glyph-name="uniE0DF" unicode="present_to_all" 
+d="M213 256h-42l85 85l85 -85h-42v-85h-86v85zM448 106v300h-384v-300h384zM448 448q18 0 30.5 -12.5t12.5 -30.5v-298q0 -18 -12.5 -30.5t-30.5 -12.5h-384q-18 0 -30.5 12.5t-12.5 30.5v298q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE0E0" unicode="import_contacts" 
+d="M448 117v246q-33 10 -75 10q-65 0 -117 -32v-245q52 32 117 32q39 0 75 -11zM373 416q76 0 118 -32v-311q0 -4 -3.5 -7.5t-7.5 -3.5q-3 0 -5 1q-41 22 -102 22q-65 0 -117 -32q-43 32 -117 32q-54 0 -102 -23q-1 0 -2.5 -0.5t-2.5 -0.5q-4 0 -7.5 3t-3.5 7v313
+q43 32 118 32q74 0 117 -32q43 32 117 32z" />
+    <glyph glyph-name="uniE0E1" unicode="mail_outline" 
+d="M256 277l171 107h-342zM427 128v213l-171 -106l-171 106v-213h342zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE0E2" unicode="screen_share" 
+d="M277 203l86 80l-86 80v-46q-105 -15 -128 -125q43 58 128 58v-47zM427 128h85v-43h-512v43h85q-18 0 -30 12.5t-12 30.5v213q0 18 12 30.5t30 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE0E3" unicode="stop_screen_share" 
+d="M149 192q29 40 78 52l-34 34q-32 -31 -44 -86zM51 475l421 -421l-27 -27l-58 58h-387v43h85q-18 0 -30 12.5t-12 29.5v214q0 19 14 31l-33 33zM469 170q0 -25 -22 -37l-118 118l34 32l-86 79v-45q-2 -1 -5.5 -1t-5.5 -1l-112 111h273q17 0 29.5 -12t12.5 -30v-214z
+M453 128h59v-43h-17z" />
+    <glyph glyph-name="uniE0E4" unicode="call_missed_outgoing" 
+d="M64 333l30 30l162 -162l119 119h-98v43h171v-171h-43v98l-149 -149z" />
+    <glyph glyph-name="uniE0E5" unicode="rss_feed" 
+d="M85 297q88 0 150 -62t62 -150h-61q0 62 -44.5 106.5t-106.5 44.5v61zM85 417q137 0 234.5 -97.5t97.5 -234.5h-60q0 113 -79.5 192.5t-192.5 79.5v60zM85 132q0 19 13.5 32.5t33.5 13.5t33 -13t13 -33t-13.5 -33.5t-32.5 -13.5q-20 0 -33.5 13.5t-13.5 33.5z" />
+    <glyph glyph-name="uniE145" unicode="add" 
+d="M405 235h-128v-128h-42v128h-128v42h128v128h42v-128h128v-42z" />
+    <glyph glyph-name="uniE146" unicode="add_box" 
+d="M363 235v42h-86v86h-42v-86h-86v-42h86v-86h42v86h86zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE147" unicode="add_circle" 
+d="M363 235v42h-86v86h-42v-86h-86v-42h86v-86h42v86h86zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE148" unicode="add_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86h86v-42h-86v-86h-42
+v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE149" unicode="archive" 
+d="M109 405h294l-20 22h-256zM256 139l117 117h-74v43h-86v-43h-74zM438 400q10 -12 10 -27v-266q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v266q0 15 10 27l29 36q10 12 25 12h256q15 0 25 -12z" />
+    <glyph glyph-name="uniE14A" unicode="backspace" 
+d="M405 179l-76 77l76 77l-30 30l-76 -77l-77 77l-30 -30l77 -77l-77 -77l30 -30l77 77l76 -77zM469 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-320q-20 0 -34 19l-115 173l115 173q14 19 34 19h320z" />
+    <glyph glyph-name="uniE14B" unicode="block" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 60 -36 105l-240 -240q45 -36 105 -36zM85 256q0 -60 36 -105l240 240q-45 36 -105 36q-70 0 -120.5 -50.5t-50.5 -120.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE14C" unicode="clear" 
+d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" />
+    <glyph glyph-name="uniE14D" unicode="content_copy" 
+d="M405 64v299h-234v-299h234zM405 405q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-234q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h234zM341 491v-43h-256v-299h-42v299q0 17 12.5 30t29.5 13h256z" />
+    <glyph glyph-name="uniE14E" unicode="content_cut" 
+d="M405 448h64v-21l-149 -150l-43 43zM256 245q11 0 11 11t-11 11t-11 -11t11 -11zM128 85q17 0 30 12.5t13 30.5t-13 30.5t-30 12.5t-30 -12.5t-13 -30.5t13 -30.5t30 -12.5zM128 341q17 0 30 12.5t13 30.5t-13 30.5t-30 12.5t-30 -12.5t-13 -30.5t13 -30.5t30 -12.5z
+M206 349l263 -264v-21h-64l-149 149l-50 -50q7 -15 7 -35q0 -35 -25 -60t-60 -25t-60 25t-25 60t25 60t60 25q20 0 35 -7l50 50l-50 50q-15 -7 -35 -7q-35 0 -60 25t-25 60t25 60t60 25t60 -25t25 -60q0 -20 -7 -35z" />
+    <glyph glyph-name="uniE14F" unicode="content_paste" 
+d="M405 85v342h-42v-64h-214v64h-42v-342h298zM256 469q-9 0 -15 -6t-6 -15t6 -15t15 -6t15 6t6 15t-6 15t-15 6zM405 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h89q7 19 23 31t37 12t37 -12
+t23 -31h89z" />
+    <glyph glyph-name="uniE150" unicode="create" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE151" unicode="drafts" 
+d="M256 235l176 110l-176 103l-176 -103zM469 341v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v213q0 25 20 37l193 113l193 -113q20 -12 20 -37z" />
+    <glyph glyph-name="uniE152" unicode="filter_list" 
+d="M128 235v42h256v-42h-256zM64 384h384v-43h-384v43zM213 128v43h86v-43h-86z" />
+    <glyph glyph-name="uniE153" unicode="flag" 
+d="M307 384h120v-213h-150l-8 42h-120v-149h-42v363h192z" />
+    <glyph glyph-name="uniE154" unicode="forward" 
+d="M256 341v86l171 -171l-171 -171v86h-171v170h171z" />
+    <glyph glyph-name="uniE155" unicode="gesture" 
+d="M296 116q14 0 28 18.5t18 56.5q-30 -8 -46 -27t-16 -32q0 -7 5 -11.5t11 -4.5zM98 365l-37 36q10 12 18 20q27 27 58 27q19 0 36.5 -15t17.5 -46q0 -30 -28 -70q-28 -39 -39 -75q-6 -17 -3.5 -29t10.5 -12q9 0 24 18q22 22 49 58q48 60 105 60q42 0 62.5 -27.5
+t23.5 -61.5h53v-53h-52q-6 -69 -37 -100t-64 -31q-28 0 -48 19.5t-20 46.5q0 33 30 69t85 46q-1 8 -1.5 11t-3 9.5t-6 9.5t-10.5 5.5t-17 2.5q-28 0 -87 -73q-17 -21 -23.5 -28.5t-18.5 -17.5t-23 -13q-35 -11 -60 13t-25 60q0 15 5.5 33t14.5 35t17 30.5t15.5 24.5t8.5 12
+q17 28 6 32q-7 3 -36 -26z" />
+    <glyph glyph-name="uniE156" unicode="inbox" 
+d="M405 192v213h-299v-213h86q0 -26 19 -45t45 -19t45 19t19 45h85zM405 448q17 0 30 -12.5t13 -30.5v-298q0 -17 -13 -30t-30 -13h-299q-18 0 -30 12.5t-12 30.5v298q0 18 12 30.5t30 12.5h299z" />
+    <glyph glyph-name="uniE157" unicode="link" 
+d="M363 363q44 0 75 -31.5t31 -75.5t-31 -75.5t-75 -31.5h-86v41h86q27 0 46.5 19.5t19.5 46.5t-19.5 46.5t-46.5 19.5h-86v41h86zM171 235v42h170v-42h-170zM83 256q0 -27 19.5 -46.5t46.5 -19.5h86v-41h-86q-44 0 -75 31.5t-31 75.5t31 75.5t75 31.5h86v-41h-86
+q-27 0 -46.5 -19.5t-19.5 -46.5z" />
+    <glyph glyph-name="uniE158" unicode="mail" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE159" unicode="markunread" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE15A" unicode="redo" 
+d="M393 286l76 77v-192h-192l78 77q-48 40 -110 40q-56 0 -100.5 -32.5t-61.5 -84.5l-50 16q22 68 80.5 111t131.5 43q85 0 148 -55z" />
+    <glyph glyph-name="uniE15B" unicode="remove" 
+d="M405 235h-298v42h298v-42z" />
+    <glyph glyph-name="uniE15C" unicode="remove_circle" 
+d="M363 235v42h-214v-42h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE15D" unicode="remove_circle_outline" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 277h214v-42h-214v42z" />
+    <glyph glyph-name="uniE15E" unicode="reply" 
+d="M213 320q104 -15 160.5 -79.5t74.5 -155.5q-77 109 -235 109v-87l-149 149l149 149v-85z" />
+    <glyph glyph-name="uniE15F" unicode="reply_all" 
+d="M277 320q104 -15 160.5 -79.5t74.5 -155.5q-77 109 -235 109v-87l-149 149l149 149v-85zM149 341l-85 -85l85 -85v-64l-149 149l149 149v-64z" />
+    <glyph glyph-name="uniE160" unicode="report" 
+d="M277 235v128h-42v-128h42zM256 143q11 0 19.5 8.5t8.5 19.5t-8.5 19t-19.5 8t-19.5 -8t-8.5 -19t8.5 -19.5t19.5 -8.5zM336 448l112 -112v-160l-112 -112h-160l-112 112v160l112 112h160z" />
+    <glyph glyph-name="uniE161" unicode="save" 
+d="M320 320v85h-213v-85h213zM256 107q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM363 448l85 -85v-256q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h256z" />
+    <glyph glyph-name="uniE162" unicode="select_all" 
+d="M192 320v-128h128v128h-128zM149 149v214h214v-214h-214zM320 405v43h43v-43h-43zM320 64v43h43v-43h-43zM405 149v43h43v-43h-43zM405 320v43h43v-43h-43zM405 64v43h43q0 -17 -13 -30t-30 -13zM405 235v42h43v-42h-43zM235 64v43h42v-43h-42zM192 448v-43h-43v43h43z
+M64 149v43h43v-43h-43zM107 64q-17 0 -30 13t-13 30h43v-43zM405 448q17 0 30 -13t13 -30h-43v43zM277 448v-43h-42v43h42zM64 320v43h43v-43h-43zM149 64v43h43v-43h-43zM64 235v42h43v-42h-43zM64 405q0 17 13 30t30 13v-43h-43z" />
+    <glyph glyph-name="uniE163" unicode="send" 
+d="M43 64v149l320 43l-320 43v149l448 -192z" />
+    <glyph glyph-name="uniE164" unicode="sort" 
+d="M64 235v42h256v-42h-256zM64 384h384v-43h-384v43zM64 128v43h128v-43h-128z" />
+    <glyph glyph-name="uniE165" unicode="text_format" 
+d="M256 384l-40 -107h80zM203 239l-20 -47h-44l101 235h32l101 -235h-44l-20 47h-106zM107 149h298v-42h-298v42z" />
+    <glyph glyph-name="uniE166" unicode="undo" 
+d="M267 341q73 0 131 -43t81 -111l-50 -16q-17 52 -61.5 84.5t-100.5 32.5q-62 0 -110 -40l78 -77h-192v192l76 -77q63 55 148 55z" />
+    <glyph glyph-name="uniE167" unicode="font_download" 
+d="M340 117h45l-109 278h-40l-109 -278h45l24 64h120zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342zM212 224l44 118l44 -118h-88z" />
+    <glyph glyph-name="uniE168" unicode="move_to_inbox" 
+d="M341 299l-85 -86l-85 86h42v64h86v-64h42zM405 192v213h-299v-213h86q0 -26 19 -45t45 -19t45 19t19 45h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-299q-18 0 -30 12.5t-12 30.5v298q0 18 12 30.5t30 12.5h299z" />
+    <glyph glyph-name="uniE169" unicode="unarchive" 
+d="M109 405h294l-20 22h-256zM256 309l-117 -117h74v-43h86v43h74zM438 401q10 -12 10 -28v-266q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v266q0 16 10 28l29 35q10 12 25 12h256q15 0 25 -12z" />
+    <glyph glyph-name="uniE16A" unicode="next_week" 
+d="M235 117l85 86l-85 85l-22 -21l64 -64l-64 -64zM213 405v-42h86v42h-86zM299 448q17 0 29.5 -12.5t12.5 -30.5v-42h86q17 0 29.5 -13t12.5 -30v-235q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v235q0 17 12.5 30t29.5 13h86v42q0 17 12.5 30
+t29.5 13h86z" />
+    <glyph glyph-name="uniE16B" unicode="weekend" 
+d="M384 405q17 0 30 -12.5t13 -29.5v-46q-19 -7 -31 -23t-12 -37v-44h-256v44q0 21 -12 37t-31 23v46q0 17 13 29.5t30 12.5h256zM448 299q17 0 30 -13t13 -30v-107q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v107q0 17 13 30t30 13t30 -13t13 -30v-64h298v64
+q0 17 13 30t30 13z" />
+    <glyph glyph-name="uniE16C" unicode="delete_sweep" 
+d="M299 405v-42h-256v42h64l21 22h85l22 -22h64zM64 128v213h213v-213q0 -17 -12.5 -30t-29.5 -13h-128q-17 0 -30 13t-13 30zM320 256h128v-43h-128v43zM320 341h149v-42h-149v42zM320 171h85v-43h-85v43z" />
+    <glyph glyph-name="uniE16D" unicode="low_priority" 
+d="M43 267q0 57 40.5 97.5t97.5 40.5h75v-42h-75q-40 0 -68 -28t-28 -68t28 -68t68 -28h11v42l64 -64l-64 -64v43h-11q-57 0 -97.5 41t-40.5 98zM299 171h170v-43h-170v43zM299 288h170v-43h-170v43zM299 405h170v-42h-170v42z" />
+    <glyph glyph-name="uniE190" unicode="access_alarm" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM267 341v-112l85 -50l-16 -26l-101 60v128h32zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE191" unicode="access_alarms" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136t56 136t136 56zM267 341v-113l85 -51l-17 -26l-100 62v128h32zM169 439l-99 -81l-27 32
+l98 81zM469 390l-27 -32l-99 84l28 32z" />
+    <glyph glyph-name="uniE192" unicode="access_time" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE193" unicode="add_alarm" 
+d="M277 320v-64h64v-43h-64v-64h-42v64h-64v43h64v64h42zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE194" unicode="airplanemode_inactive" 
+d="M64 400l27 27l336 -336l-27 -27l-123 122v-79l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l127 80zM277 320l171 -107v-42l-68 21l-167 167v78q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117z" />
+    <glyph glyph-name="uniE195" unicode="airplanemode_active" 
+d="M217 320zM448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE19C" unicode="battery_alert" 
+d="M277 213v107h-42v-107h42zM277 128v43h-42v-43h42zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A3" unicode="battery_charging_full" 
+d="M235 85l85 160h-43v118l-85 -160h43v-118zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A4" unicode="battery_full" 
+d="M334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A5" unicode="battery_std" 
+d="M334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A6" unicode="battery_unknown" 
+d="M305 241q15 15 15 36q0 26 -19 45t-45 19t-45 -19t-19 -45h32q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5t-9 -22l-20 -20q-20 -20 -20 -43h34q0 16 18 34zM276 129v41h-40v-41h40zM334 427q12 0 20.5 -8.5t8.5 -20.5v-327q0 -12 -8.5 -20t-20.5 -8h-156q-12 0 -20.5 8t-8.5 20
+v327q0 12 8.5 20.5t20.5 8.5h35v42h86v-42h35z" />
+    <glyph glyph-name="uniE1A7" unicode="bluetooth" 
+d="M317 164l-40 41v-81zM277 388v-81l40 41zM378 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21z" />
+    <glyph glyph-name="uniE1A8" unicode="bluetooth_connected" 
+d="M405 299l43 -43l-43 -43l-42 43zM317 164l-40 41v-81zM277 388v-81l40 41zM378 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM149 256l-42 -43l-43 43l43 43z" />
+    <glyph glyph-name="uniE1A9" unicode="bluetooth_disabled" 
+d="M277 124l40 40l-40 41v-81zM115 427l312 -312l-30 -30l-49 49l-92 -91h-21v162l-98 -98l-30 30l119 119l-141 141zM277 388v-69l-42 43v107h21l122 -121l-65 -65l-30 30l34 35z" />
+    <glyph glyph-name="uniE1AA" unicode="bluetooth_searching" 
+d="M275 164l-40 41v-81zM235 388v-81l40 41zM335 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM417 369q31 -50 31 -111t-33 -113l-25 25q21 42 21 86t-21 86zM304 256l49 49q10 -25 10 -49q0 -25 -10 -50z" />
+    <glyph glyph-name="uniE1AB" unicode="brightness_auto" 
+d="M305 171h41l-69 192h-42l-69 -192h41l15 42h68zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100zM231 242l25 78l25 -78h-50z" />
+    <glyph glyph-name="uniE1AC" unicode="brightness_high" 
+d="M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100
+l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE1AD" unicode="brightness_low" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE1AE" unicode="brightness_medium" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-256zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE1AF" unicode="data_usage" 
+d="M256 107q72 0 116 56l56 -33q-64 -87 -172 -87q-88 0 -150.5 62.5t-62.5 150.5q0 83 55.5 143.5t136.5 68.5v-64q-54 -8 -91 -50t-37 -98q0 -62 43.5 -105.5t105.5 -43.5zM277 468q81 -8 136.5 -68.5t55.5 -143.5q0 -48 -18 -87l-56 33q10 28 10 54q0 56 -37 98t-91 50
+v64z" />
+    <glyph glyph-name="uniE1B0" unicode="developer_mode" 
+d="M363 107v42h42v-85q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v85h42v-42h214zM213 188l-30 -30l-98 98l98 98l30 -30l-67 -68zM329 158l-30 30l67 68l-67 68l30 30l98 -98zM149 405v-42h-42v85q0 17 12.5 30t29.5 13l214 -1q17 0 29.5 -12.5t12.5 -29.5
+v-85h-42v42h-214z" />
+    <glyph glyph-name="uniE1B1" unicode="devices" 
+d="M469 149v150h-85v-150h85zM491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15.5 6.5t-6.5 15.5v213q0 9 6.5 15t15.5 6h128zM85 384v-235h214v-64h-299v64h43v235q0 17 12.5 30t29.5 13h384v-43h-384z" />
+    <glyph glyph-name="uniE1B2" unicode="dvr" 
+d="M149 256v-43h-42v43h42zM149 341v-42h-42v42h42zM405 256v-43h-234v43h234zM405 341v-42h-234v42h234zM448 149v256h-384v-256h384zM448 448q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-17 0 -30 12.5t-13 29.5v256q0 17 13 30
+t30 13h384z" />
+    <glyph glyph-name="uniE1B3" unicode="gps_fixed" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE1B4" unicode="gps_not_fixed" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+" />
+    <glyph glyph-name="uniE1B5" unicode="gps_off" 
+d="M347 138l-209 209q-31 -41 -31 -91q0 -62 43.5 -105.5t105.5 -43.5q50 0 91 31zM64 421l27 27l357 -357l-27 -27l-44 44q-45 -37 -100 -43v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q6 55 43 100zM447 277h44v-42h-44q-5 -39 -21 -68l-32 32q11 27 11 57
+q0 62 -43.5 105.5t-105.5 43.5q-30 0 -57 -11l-32 32q31 16 68 21v44h42v-44q67 -7 115 -55t55 -115z" />
+    <glyph glyph-name="uniE1B6" unicode="location_disabled" 
+d="M347 138l-209 209q-31 -41 -31 -91q0 -62 43.5 -105.5t105.5 -43.5q50 0 91 31zM64 421l27 27l357 -357l-27 -27l-44 44q-45 -37 -100 -43v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q6 55 43 100zM447 277h44v-42h-44q-5 -39 -21 -68l-32 32q11 27 11 57
+q0 62 -43.5 105.5t-105.5 43.5q-30 0 -57 -11l-32 32q31 16 68 21v44h42v-44q67 -7 115 -55t55 -115z" />
+    <glyph glyph-name="uniE1B7" unicode="location_searching" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+" />
+    <glyph glyph-name="uniE1B8" unicode="graphic_eq" 
+d="M405 299h43v-86h-43v86zM320 128v256h43v-256h-43zM64 213v86h43v-86h-43zM235 43v426h42v-426h-42zM149 128v256h43v-256h-43z" />
+    <glyph glyph-name="uniE1B9" unicode="network_cell" 
+d="M469 43h-426l426 426v-426z" />
+    <glyph glyph-name="uniE1BA" unicode="network_wifi" 
+d="M436 279l1 -1l-181 -224l-181 224l1 1l-68 84q121 85 248 85t248 -85z" />
+    <glyph glyph-name="uniE1BB" unicode="nfc" 
+d="M384 384v-256h-256v256h85v-43h-42v-170h170v170h-64v-48q22 -12 22 -37q0 -17 -13 -30t-30 -13t-30 13t-13 30q0 25 22 37v48q0 17 12.5 30t29.5 13h107zM427 85v342h-342v-342h342zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342
+q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE1BC" unicode="now_wallpaper" 
+d="M85 235v-150h150v-42h-150q-17 0 -29.5 12.5t-12.5 29.5v150h42zM427 85v150h42v-150q0 -17 -12.5 -29.5t-29.5 -12.5h-150v42h150zM427 469q17 0 29.5 -12.5t12.5 -29.5v-150h-42v150h-150v42h150zM363 331q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5
+t22.5 9.5t22.5 -9.5t9.5 -22.5zM213 235l64 -79l43 57l64 -85h-256zM85 427v-150h-42v150q0 17 12.5 29.5t29.5 12.5h150v-42h-150z" />
+    <glyph glyph-name="uniE1BD" unicode="now_widgets" 
+d="M355 476l121 -121l-121 -120h93v-171h-171v171h78l-120 120v-78h-171v171h171v-93zM64 64v171h171v-171h-171z" />
+    <glyph glyph-name="uniE1BE" unicode="screen_lock_landscape" 
+d="M230 299v-22h52v22q0 10 -7.5 17.5t-18.5 7.5t-18.5 -7.5t-7.5 -17.5zM213 171q-9 0 -15 6t-6 15v64q0 9 6 15t15 6v22q0 17 12.5 29.5t30.5 12.5t30.5 -12t12.5 -30v-22q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-86zM405 149v214h-298v-214h298zM448 405q17 0 30 -12.5
+t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE1BF" unicode="screen_lock_portrait" 
+d="M363 107v298h-214v-298h214zM363 491q17 0 29.5 -13t12.5 -30v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13h214zM230 299v-22h52v22q0 10 -7.5 17.5t-18.5 7.5t-18.5 -7.5t-7.5 -17.5zM213 171q-9 0 -15 6t-6 15v64q0 9 6 15
+t15 6v22q0 17 12.5 29.5t30.5 12.5t30.5 -12t12.5 -30v-22q9 0 15 -6t6 -15v-64q0 -9 -6 -15t-15 -6h-86z" />
+    <glyph glyph-name="uniE1C0" unicode="screen_lock_rotation" 
+d="M358 459v-11h73v11q0 15 -10.5 25.5t-25.5 10.5t-26 -10.5t-11 -25.5zM341 320q-9 0 -15 6t-6 15v86q0 9 6 15t15 6v11q0 22 16 37.5t38 15.5t37.5 -15.5t15.5 -37.5v-11q9 0 15 -6t6 -15v-86q0 -9 -6 -15t-15 -6h-107zM181 75l28 28l81 -81l-14 -1q-100 0 -173.5 68
+t-81.5 167h32q6 -59 40.5 -107.5t87.5 -73.5zM496 240q10 -9 10 -22.5t-10 -23.5l-136 -135q-9 -10 -22 -10t-23 10l-256 256q-10 9 -10 22t10 23l135 136q9 10 22.5 10t23.5 -10l52 -52l-30 -30l-45 44l-121 -120l242 -242l120 121l-47 47l30 30z" />
+    <glyph glyph-name="uniE1C1" unicode="screen_rotation" 
+d="M160 54l29 28l81 -81l-14 -1q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73zM316 60l136 136l-256 256l-136 -136zM218 475l257 -257q10 -9 10 -22t-10 -23l-136 -136q-9 -10 -22 -10t-23 10l-257 257q-10 9 -10 22t10 23l136 136q9 10 22 10t23 -10zM352 458
+l-29 -28l-81 81l14 1q100 0 173.5 -68t81.5 -167h-32q-6 60 -40 108t-87 73z" />
+    <glyph glyph-name="uniE1C2" unicode="sd_storage" 
+d="M384 341v86h-43v-86h43zM320 341v86h-43v-86h43zM256 341v86h-43v-86h43zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE1C3" unicode="settings_system_daydream" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM192 171q-26 0 -45 19t-19 45q0 25 16.5 43t40.5 20h4q21 43 67 43q28 0 49 -18.5t25 -45.5h1q22 0 37.5 -15.5t15.5 -37.5
+t-15.5 -37.5t-37.5 -15.5h-139z" />
+    <glyph glyph-name="uniE1C8" unicode="signal_cellular_&#x34;_bar" 
+d="M43 43l426 426v-426h-426z" />
+    <glyph glyph-name="uniE1CD" unicode="signal_cellular_connected_no_internet_&#x34;_bar" 
+d="M43 43l426 426v-128h-85v-298h-341zM427 43v42h42v-42h-42zM427 128v171h42v-171h-42z" />
+    <glyph glyph-name="uniE1CE" unicode="signal_cellular_no_sim" 
+d="M78 429l373 -372l-28 -28l-40 41q-12 -6 -20 -6h-214q-17 0 -29.5 13t-12.5 30v239l-56 56zM405 405v-249l-242 242l50 50h150q17 0 29.5 -13t12.5 -30z" />
+    <glyph glyph-name="uniE1CF" unicode="signal_cellular_null" 
+d="M469 469v-426h-426zM427 366l-281 -281h281v281z" />
+    <glyph glyph-name="uniE1D0" unicode="signal_cellular_off" 
+d="M102 416l367 -368l-27 -27l-42 43h-379l189 189l-135 136zM448 491v-367l-183 183z" />
+    <glyph glyph-name="uniE1D8" unicode="signal_wifi_&#x34;_bar" 
+d="M256 54l-248 309q121 85 248 85t248 -85z" />
+    <glyph glyph-name="uniE1D9" unicode="signal_wifi_&#x34;_bar_lock" 
+d="M331 203v-56l-75 -94l-247 310l6 5q6 4 12.5 8.5t17.5 11.5t23.5 13t28.5 13.5t34 13t37.5 10.5t42 7.5t45.5 2.5t45.5 -2.5t42 -7.5t37.5 -10.5t34 -13t28.5 -13.5t23.5 -13t17.5 -11.5t12.5 -8.5l6 -5l-44 -56q-6 2 -22 2q-45 0 -75.5 -30.5t-30.5 -75.5zM469 171v32
+q0 13 -9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5v-32h64zM491 171q8 0 14.5 -7t6.5 -15v-85q0 -8 -6.5 -14.5t-14.5 -6.5h-107q-8 0 -14.5 6.5t-6.5 14.5v85q0 8 6.5 15t14.5 7v32q0 23 15 38t38 15t38.5 -15.5t15.5 -37.5v-32z" />
+    <glyph glyph-name="uniE1DA" unicode="signal_wifi_off" 
+d="M70 481q12 -12 153.5 -153t213.5 -215l-27 -27l-71 71l-83 -103l-248 309q33 27 78 47l-43 44zM504 363l-116 -145l-221 220q45 10 89 10q127 0 248 -85z" />
+    <glyph glyph-name="uniE1DB" unicode="storage" 
+d="M85 277v-42h43v42h-43zM43 213v86h426v-86h-426zM128 363v42h-43v-42h43zM43 427h426v-86h-426v86zM85 149v-42h43v42h-43zM43 85v86h426v-86h-426z" />
+    <glyph glyph-name="uniE1E0" unicode="usb" 
+d="M320 363h85v-86h-21v-42q0 -18 -12.5 -30.5t-30.5 -12.5h-64v-65q26 -14 26 -42q0 -19 -13.5 -33t-33.5 -14t-33.5 14t-13.5 33q0 28 26 42v65h-64q-18 0 -30.5 12.5t-12.5 30.5v44q-26 14 -26 41q0 20 14 33.5t33 13.5t33 -13.5t14 -33.5q0 -28 -25 -41v-44h64v170h-43
+l64 86l64 -86h-43v-170h64v42h-21v86z" />
+    <glyph glyph-name="uniE1E1" unicode="wifi_lock" 
+d="M469 171v32q0 13 -9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5v-32h64zM491 171q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v85q0 9 6 15.5t15 6.5v32q0 22 15.5 37.5t37.5 15.5t38 -15.5t16 -37.5v-32zM437 309q-44 0 -75 -31t-31 -75v-61
+l-75 -99l-256 341q112 85 256 85t256 -85l-57 -76q-6 1 -18 1z" />
+    <glyph glyph-name="uniE1E2" unicode="wifi_tethering" 
+d="M256 448q88 0 150.5 -62t62.5 -151q0 -58 -28.5 -107.5t-77.5 -77.5l-22 37q39 23 62.5 62.5t23.5 85.5q0 70 -50 120t-121 50t-121 -50t-50 -120q0 -47 23 -86t62 -62l-21 -37q-49 28 -77.5 77.5t-28.5 107.5q0 89 62.5 151t150.5 62zM384 235q0 -35 -17.5 -64.5
+t-46.5 -46.5l-21 37q42 25 42 74q0 35 -25 60t-60 25t-60 -25t-25 -60q0 -49 42 -74l-21 -37q-29 17 -46.5 46.5t-17.5 64.5q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM256 277q17 0 30 -12.5t13 -29.5t-13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5z" />
+    <glyph glyph-name="uniE226" unicode="attach_file" 
+d="M352 384h32v-245q0 -49 -34 -83.5t-83 -34.5t-83.5 34.5t-34.5 83.5v266q0 35 25.5 60.5t60.5 25.5t60 -25.5t25 -60.5v-224q0 -22 -15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5v203h32v-203q0 -9 6.5 -15t15.5 -6t15 6t6 15v224q0 22 -15.5 38t-37.5 16t-38 -16t-16 -38
+v-266q0 -35 25.5 -60.5t60.5 -25.5t60 25.5t25 60.5v245z" />
+    <glyph glyph-name="uniE227" unicode="attach_money" 
+d="M252 279q22 -6 36.5 -12t30.5 -16.5t24.5 -26.5t8.5 -38q0 -31 -20.5 -50.5t-54.5 -25.5v-46h-64v46q-33 7 -54.5 28t-23.5 54h47q4 -45 63 -45q31 0 44.5 11.5t13.5 26.5q0 36 -64 52q-100 23 -100 88q0 29 21 49.5t53 27.5v46h64v-47q33 -8 50.5 -30t18.5 -51h-47
+q-2 45 -54 45q-26 0 -41.5 -11t-15.5 -29q0 -29 64 -46z" />
+    <glyph glyph-name="uniE228" unicode="border_all" 
+d="M405 277v128h-128v-128h128zM405 107v128h-128v-128h128zM235 277v128h-128v-128h128zM235 107v128h-128v-128h128zM64 448h384v-384h-384v384z" />
+    <glyph glyph-name="uniE229" unicode="border_bottom" 
+d="M107 192v-43h-43v43h43zM64 64v43h384v-43h-384zM107 277v-42h-43v42h43zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM107 363v-43h-43v43h43zM405 149v43h43v-43h-43zM405 235v42h43v-42h-43zM363 448v-43h-43v43h43zM277 448v-43h-42v43h42zM363 277v-42h-43v42
+h43zM277 363v-43h-42v43h42zM107 448v-43h-43v43h43zM277 277v-42h-42v42h42zM192 448v-43h-43v43h43zM277 192v-43h-42v43h42zM192 277v-42h-43v42h43z" />
+    <glyph glyph-name="uniE22A" unicode="border_clear" 
+d="M320 405v43h43v-43h-43zM320 235v42h43v-42h-43zM320 64v43h43v-43h-43zM235 405v43h42v-43h-42zM405 448h43v-43h-43v43zM235 320v43h42v-43h-42zM405 320v43h43v-43h-43zM405 64v43h43v-43h-43zM405 235v42h43v-42h-43zM405 149v43h43v-43h-43zM235 235v42h42v-42h-42z
+M64 405v43h43v-43h-43zM64 320v43h43v-43h-43zM64 235v42h43v-42h-43zM64 149v43h43v-43h-43zM64 64v43h43v-43h-43zM235 64v43h42v-43h-42zM235 149v43h42v-43h-42zM149 64v43h43v-43h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22B" unicode="border_color" 
+d="M0 85h512v-85h-512v85zM442 426l-42 -42l-80 80l42 42q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM379 363l-214 -214h-80v80l214 214z" />
+    <glyph glyph-name="uniE22C" unicode="border_horizontal" 
+d="M405 64v43h43v-43h-43zM320 64v43h43v-43h-43zM235 149v43h42v-43h-42zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM64 235v42h384v-42h-384zM235 64v43h42v-43h-42zM405 149v43h43v-43h-43zM277 448v-43h-42v43h42zM277 363v-43h-42v43h42zM363 448v-43h-43v43h43z
+M192 448v-43h-43v43h43zM107 448v-43h-43v43h43zM149 64v43h43v-43h-43zM64 149v43h43v-43h-43zM107 363v-43h-43v43h43zM64 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22D" unicode="border_inner" 
+d="M405 149v43h43v-43h-43zM405 64v43h43v-43h-43zM277 448v-171h171v-42h-171v-171h-42v171h-171v42h171v171h42zM320 64v43h43v-43h-43zM405 448h43v-43h-43v43zM405 320v43h43v-43h-43zM363 448v-43h-43v43h43zM107 448v-43h-43v43h43zM192 448v-43h-43v43h43zM64 149v43
+h43v-43h-43zM107 363v-43h-43v43h43zM149 64v43h43v-43h-43zM64 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE22E" unicode="border_left" 
+d="M320 405v43h43v-43h-43zM320 235v42h43v-42h-43zM405 64v43h43v-43h-43zM405 235v42h43v-42h-43zM405 448h43v-43h-43v43zM405 149v43h43v-43h-43zM320 64v43h43v-43h-43zM405 320v43h43v-43h-43zM64 64v384h43v-384h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43z
+M149 64v43h43v-43h-43zM235 235v42h42v-42h-42zM235 320v43h42v-43h-42zM235 405v43h42v-43h-42zM235 149v43h42v-43h-42zM235 64v43h42v-43h-42z" />
+    <glyph glyph-name="uniE22F" unicode="border_outer" 
+d="M192 277v-42h-43v42h43zM277 192v-43h-42v43h42zM405 107v298h-298v-298h298zM64 448h384v-384h-384v384zM363 277v-42h-43v42h43zM277 277v-42h-42v42h42zM277 363v-43h-42v43h42z" />
+    <glyph glyph-name="uniE230" unicode="border_right" 
+d="M235 320v43h42v-43h-42zM235 405v43h42v-43h-42zM235 235v42h42v-42h-42zM320 405v43h43v-43h-43zM320 64v43h43v-43h-43zM405 448h43v-384h-43v384zM320 235v42h43v-42h-43zM235 149v43h42v-43h-42zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z
+M235 64v43h42v-43h-42zM64 64v43h43v-43h-43zM149 235v42h43v-42h-43zM149 405v43h43v-43h-43zM64 405v43h43v-43h-43zM149 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE231" unicode="border_style" 
+d="M405 320v43h43v-43h-43zM64 448h384v-43h-341v-341h-43v384zM405 235v42h43v-42h-43zM405 149v43h43v-43h-43zM235 64v43h42v-43h-42zM149 64v43h43v-43h-43zM405 64v43h43v-43h-43zM320 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE232" unicode="border_top" 
+d="M320 235v42h43v-42h-43zM405 64v43h43v-43h-43zM235 320v43h42v-43h-42zM320 64v43h43v-43h-43zM405 149v43h43v-43h-43zM64 448h384v-43h-384v43zM405 235v42h43v-42h-43zM405 320v43h43v-43h-43zM235 149v43h42v-43h-42zM64 320v43h43v-43h-43zM64 235v42h43v-42h-43z
+M64 64v43h43v-43h-43zM64 149v43h43v-43h-43zM235 64v43h42v-43h-42zM235 235v42h42v-42h-42zM149 235v42h43v-42h-43zM149 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE233" unicode="border_vertical" 
+d="M320 235v42h43v-42h-43zM320 64v43h43v-43h-43zM320 405v43h43v-43h-43zM405 320v43h43v-43h-43zM405 448h43v-43h-43v43zM405 235v42h43v-42h-43zM405 64v43h43v-43h-43zM235 64v384h42v-384h-42zM405 149v43h43v-43h-43zM149 405v43h43v-43h-43zM64 149v43h43v-43h-43z
+M64 64v43h43v-43h-43zM64 235v42h43v-42h-43zM149 235v42h43v-42h-43zM149 64v43h43v-43h-43zM64 405v43h43v-43h-43zM64 320v43h43v-43h-43z" />
+    <glyph glyph-name="uniE234" unicode="format_align_center" 
+d="M64 448h384v-43h-384v43zM149 363h214v-43h-214v43zM64 235v42h384v-42h-384zM64 64v43h384v-43h-384zM149 192h214v-43h-214v43z" />
+    <glyph glyph-name="uniE235" unicode="format_align_justify" 
+d="M64 448h384v-43h-384v43zM64 320v43h384v-43h-384zM64 235v42h384v-42h-384zM64 149v43h384v-43h-384zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE236" unicode="format_align_left" 
+d="M64 448h384v-43h-384v43zM64 64v43h384v-43h-384zM64 235v42h384v-42h-384zM320 363v-43h-256v43h256zM320 192v-43h-256v43h256z" />
+    <glyph glyph-name="uniE237" unicode="format_align_right" 
+d="M64 448h384v-43h-384v43zM192 320v43h256v-43h-256zM64 235v42h384v-42h-384zM192 149v43h256v-43h-256zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE238" unicode="format_bold" 
+d="M288 181q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5h-75v-64h75zM213 373v-64h64q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5h-64zM333 282q46 -21 46 -73q0 -34 -22.5 -57.5t-56.5 -23.5h-151v299h134q36 0 60.5 -25t24.5 -61t-35 -59z" />
+    <glyph glyph-name="uniE239" unicode="format_clear" 
+d="M128 405h299v-64h-124l-34 -80l-45 44l15 36h-51l-60 60v4zM70 405l6 -5l308 -309l-27 -27l-121 121l-33 -78h-64l52 123l-148 148z" />
+    <glyph glyph-name="uniE23A" unicode="format_color_fill" 
+d="M0 85h512v-85h-512v85zM405 267q43 -47 43 -75q0 -17 -13 -30t-30 -13t-29.5 13t-12.5 30q0 12 10.5 31t20.5 31zM111 299h205l-103 102zM353 321q10 -10 10 -23t-10 -22l-117 -117q-10 -10 -23 -10q-12 0 -22 10l-118 117q-10 9 -10 22t10 23l110 110l-51 51l31 30z" />
+    <glyph glyph-name="uniE23B" unicode="format_color_reset" 
+d="M112 400l312 -312l-27 -27l-57 56q-37 -32 -84 -32q-53 0 -90.5 37.5t-37.5 90.5q0 34 28 88l-71 71zM384 213q0 -15 -3 -28l-183 184l58 75q14 -16 35.5 -43t57 -88.5t35.5 -99.5z" />
+    <glyph glyph-name="uniE23C" unicode="format_color_text" 
+d="M205 256h102l-51 135zM235 448h42l117 -299h-48l-23 64h-134l-24 -64h-48zM0 85h512v-85h-512v85z" />
+    <glyph glyph-name="uniE23D" unicode="format_indent_decrease" 
+d="M235 235v42h213v-42h-213zM235 320v43h213v-43h-213zM64 448h384v-43h-384v43zM64 64v43h384v-43h-384zM64 256l85 85v-170zM235 149v43h213v-43h-213z" />
+    <glyph glyph-name="uniE23E" unicode="format_indent_increase" 
+d="M235 235v42h213v-42h-213zM235 320v43h213v-43h-213zM64 448h384v-43h-384v43zM235 149v43h213v-43h-213zM64 341l85 -85l-85 -85v170zM64 64v43h384v-43h-384z" />
+    <glyph glyph-name="uniE23F" unicode="format_italic" 
+d="M213 427h171v-64h-60l-72 -171h47v-64h-171v64h60l72 171h-47v64z" />
+    <glyph glyph-name="uniE240" unicode="format_line_spacing" 
+d="M213 235v42h256v-42h-256zM213 107v42h256v-42h-256zM213 405h256v-42h-256v42zM128 363v-214h53l-74 -74l-75 74h53v214h-53l75 74l74 -74h-53z" />
+    <glyph glyph-name="uniE241" unicode="format_list_bulleted" 
+d="M149 405h299v-42h-299v42zM149 235v42h299v-42h-299zM149 107v42h299v-42h-299zM85 160q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM85 416q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9z
+M85 288q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9z" />
+    <glyph glyph-name="uniE242" unicode="format_list_numbered" 
+d="M149 235v42h299v-42h-299zM149 107v42h299v-42h-299zM149 405h299v-42h-299v42zM43 277v22h64v-20l-39 -44h39v-22h-64v20l38 44h-38zM64 341v64h-21v22h42v-86h-21zM43 149v22h64v-86h-64v22h42v10h-21v22h21v10h-42z" />
+    <glyph glyph-name="uniE243" unicode="format_paint" 
+d="M384 427h64v-171h-171v-192q0 -9 -6 -15t-15 -6h-43q-9 0 -15 6t-6 15v235h213v85h-21v-21q0 -9 -6 -15.5t-15 -6.5h-256q-9 0 -15.5 6.5t-6.5 15.5v85q0 9 6.5 15t15.5 6h256q9 0 15 -6t6 -15v-21z" />
+    <glyph glyph-name="uniE244" unicode="format_quote" 
+d="M299 149l42 86h-64v128h128v-128l-42 -86h-64zM128 149l43 86h-64v128h128v-128l-43 -86h-64z" />
+    <glyph glyph-name="uniE245" unicode="format_size" 
+d="M64 256v64h192v-64h-64v-149h-64v149h-64zM192 427h277v-64h-106v-256h-64v256h-107v64z" />
+    <glyph glyph-name="uniE246" unicode="format_strikethrough" 
+d="M64 213v43h384v-43h-384zM107 427h298v-64h-106v-64h-86v64h-106v64zM213 107v64h86v-64h-86z" />
+    <glyph glyph-name="uniE247" unicode="format_textdirection_l_to_r" 
+d="M448 128l-85 -85v64h-256v42h256v64zM192 299q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-43v235h-42v-235h-43v107z" />
+    <glyph glyph-name="uniE248" unicode="format_textdirection_r_to_l" 
+d="M171 149h256v-42h-256v-64l-86 85l86 85v-64zM213 299q-35 0 -60 25t-25 60t25 60t60 25h171v-42h-43v-235h-42v235h-43v-235h-43v107z" />
+    <glyph glyph-name="uniE249" unicode="format_underlined" 
+d="M107 107h298v-43h-298v43zM256 149q-53 0 -90.5 37.5t-37.5 90.5v171h53v-171q0 -31 22 -52.5t53 -21.5t53 21.5t22 52.5v171h53v-171q0 -53 -37.5 -90.5t-90.5 -37.5z" />
+    <glyph glyph-name="uniE24A" unicode="functions" 
+d="M384 427v-64h-149l106 -107l-106 -107h149v-64h-256v43l139 128l-139 128v43h256z" />
+    <glyph glyph-name="uniE24B" unicode="insert_chart" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE24C" unicode="insert_comment" 
+d="M384 341v43h-256v-43h256zM384 277v43h-256v-43h256zM384 213v43h-256v-43h256zM427 469q17 0 29.5 -12.5t12.5 -29.5v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE24D" unicode="insert_drive_file" 
+d="M277 320h118l-118 117v-117zM128 469h171l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5z" />
+    <glyph glyph-name="uniE24E" unicode="insert_emoticon" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE24F" unicode="insert_invitation" 
+d="M405 107v234h-298v-234h298zM341 491h43v-43h21q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43zM363 256v-107h-107v107h107z" />
+    <glyph glyph-name="uniE250" unicode="insert_link" 
+d="M363 363q44 0 75 -31.5t31 -75.5t-31 -75.5t-75 -31.5h-86v41h86q27 0 46.5 19.5t19.5 46.5t-19.5 46.5t-46.5 19.5h-86v41h86zM171 235v42h170v-42h-170zM83 256q0 -27 19.5 -46.5t46.5 -19.5h86v-41h-86q-44 0 -75 31.5t-31 75.5t31 75.5t75 31.5h86v-41h-86
+q-27 0 -46.5 -19.5t-19.5 -46.5z" />
+    <glyph glyph-name="uniE251" unicode="insert_photo" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE252" unicode="merge_type" 
+d="M160 341l96 96l96 -96h-75v-136l-128 -128l-30 30l116 115v119h-75zM363 77l-73 72l30 30l73 -72z" />
+    <glyph glyph-name="uniE253" unicode="mode_comment" 
+d="M469 427v-384l-85 85h-299q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h342q17 0 29.5 -12.5t12.5 -29.5z" />
+    <glyph glyph-name="uniE254" unicode="mode_edit" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE255" unicode="publish" 
+d="M107 213l149 150l149 -150h-85v-128h-128v128h-85zM107 427h298v-43h-298v43z" />
+    <glyph glyph-name="uniE256" unicode="space_bar" 
+d="M384 320h43v-128h-342v128h43v-85h256v85z" />
+    <glyph glyph-name="uniE257" unicode="strikethrough_s" 
+d="M200 232q0 -54 62 -54q49 0 49 36q0 16 -6.5 23t-23.5 15q-1 0 -3.5 1t-4.5 2t-4 1h-205v43h384v-43h-83q0 -1 1.5 -3.5t2.5 -3.5q7 -17 7 -35q0 -61 -67 -80q-21 -6 -46 -6q-16 0 -31 3q-36 7 -56 22q-39 29 -39 79h63zM311 352q0 45 -51 45q-36 0 -47 -22q-3 -6 -3 -14
+q0 -15 16 -26q16 -10 30 -15h-98q0 1 -2 2.5t-2 2.5q-8 13 -8 36q0 37 32 63q34 24 83 24q53 0 83 -27q31 -28 31 -69h-64z" />
+    <glyph glyph-name="uniE258" unicode="vertical_align_bottom" 
+d="M85 107h342v-43h-342v43zM341 235l-85 -86l-85 86h64v213h42v-213h64z" />
+    <glyph glyph-name="uniE259" unicode="vertical_align_center" 
+d="M85 277h342v-42h-342v42zM341 405l-85 -85l-85 85h64v86h42v-86h64zM171 107l85 85l85 -85h-64v-86h-42v86h-64z" />
+    <glyph glyph-name="uniE25A" unicode="vertical_align_top" 
+d="M85 448h342v-43h-342v43zM171 277l85 86l85 -86h-64v-213h-42v213h-64z" />
+    <glyph glyph-name="uniE25B" unicode="wrap_text" 
+d="M363 277q35 0 60 -25t25 -60t-25 -60t-60 -25h-43v-43l-64 64l64 64v-43h48q17 0 30 13t13 30t-13 30t-30 13h-283v42h278zM427 405v-42h-342v42h342zM85 107v42h128v-42h-128z" />
+    <glyph glyph-name="uniE25C" unicode="money_off" 
+d="M114 425l311 -312l-27 -27l-47 48q-19 -17 -52 -24v-46h-64v46q-33 7 -55 28t-24 54h47q4 -45 64 -45q37 0 51 20l-75 74q-83 25 -83 84l-73 73zM267 365q-19 0 -33 -6l-31 31q14 7 32 12v46h64v-47q32 -8 49.5 -30t18.5 -51h-47q-2 45 -53 45z" />
+    <glyph glyph-name="uniE25D" unicode="drag_handle" 
+d="M85 192v43h342v-43h-342zM427 320v-43h-342v43h342z" />
+    <glyph glyph-name="uniE25E" unicode="format_shapes" 
+d="M228 240h56l-28 82zM293 213h-75l-15 -42h-35l73 192h30l72 -192h-34zM405 405h43v43h-43v-43zM448 64v43h-43v-43h43zM363 107v42h42v214h-42v42h-214v-42h-42v-214h42v-42h214zM107 64v43h-43v-43h43zM64 448v-43h43v43h-43zM491 363h-43v-214h43v-128h-128v43h-214
+v-43h-128v128h43v214h-43v128h128v-43h214v43h128v-128z" />
+    <glyph glyph-name="uniE25F" unicode="highlight" 
+d="M362 372l45 45l30 -30l-45 -46zM75 387l30 30l45 -45l-30 -31zM235 469h42v-64h-42v64zM128 213v107h256v-107l-64 -64v-106h-128v106z" />
+    <glyph glyph-name="uniE260" unicode="linear_scale" 
+d="M416 309q22 0 37.5 -15.5t15.5 -37.5t-15.5 -37.5t-37.5 -15.5q-36 0 -49 32h-62q-13 -32 -49 -32t-49 32h-62q-13 -32 -49 -32q-22 0 -37.5 15.5t-15.5 37.5t15.5 37.5t37.5 15.5q36 0 49 -32h62q13 32 49 32t49 -32h62q13 32 49 32z" />
+    <glyph glyph-name="uniE261" unicode="short_text" 
+d="M85 235h214v-43h-214v43zM85 320h342v-43h-342v43z" />
+    <glyph glyph-name="uniE262" unicode="text_fields" 
+d="M459 320v-64h-64v-149h-64v149h-64v64h192zM53 427h278v-64h-107v-256h-64v256h-107v64z" />
+    <glyph glyph-name="uniE263" unicode="monetization_on" 
+d="M286 126q67 13 67 67q0 61 -90 84q-56 14 -56 41q0 15 13.5 25t37.5 10q45 0 47 -40h42q-2 57 -61 72v42h-57v-42q-29 -6 -47.5 -23.5t-18.5 -44.5q0 -56 89 -78q57 -13 57 -46q0 -13 -11.5 -23.5t-39.5 -10.5q-53 0 -57 40h-42q3 -57 70 -72v-42h57v41zM256 469
+q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE264" unicode="title" 
+d="M107 427h298v-64h-117v-256h-64v256h-117v64z" />
+    <glyph glyph-name="uniE2BC" unicode="attachment" 
+d="M43 245q0 49 34 83.5t83 34.5h224q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25h-181q-22 0 -38 15.5t-16 37.5t16 38t38 16h160v-43h-162q-9 0 -9 -10.5t9 -10.5h183q17 0 30 12.5t13 29.5t-13 30t-30 13h-224q-31 0 -53 -22t-22 -53t22 -52.5t53 -21.5h203v-43h-203
+q-49 0 -83 34t-34 83z" />
+    <glyph glyph-name="uniE2BD" unicode="cloud" 
+d="M413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2BE" unicode="cloud_circle" 
+d="M352 171q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5h-11q0 35 -25 60.5t-60 25.5q-30 0 -52.5 -18.5t-29.5 -46.5l-3 1q-26 0 -45 -19t-19 -45t19 -45t45 -19h181zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5
+t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE2BF" unicode="cloud_done" 
+d="M213 149l141 141l-30 30l-111 -110l-44 44l-30 -30zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C0" unicode="cloud_download" 
+d="M363 235h-64v85h-86v-85h-64l107 -107zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C1" unicode="cloud_off" 
+d="M165 299h-37q-35 0 -60 -25.5t-25 -60.5t25 -60t60 -25h208zM64 400l27 27l357 -357l-27 -27l-43 42h-250q-53 0 -90.5 37.5t-37.5 90.5q0 52 36 89t87 39zM413 298q41 -3 70 -33.5t29 -72.5q0 -55 -45 -87l-31 31q33 18 33 56q0 26 -19 45t-45 19h-32v11q0 49 -34 83
+t-83 34q-31 0 -54 -13l-32 31q39 25 86 25q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C2" unicode="cloud_queue" 
+d="M405 128q26 0 45 19t19 45t-19 45t-45 19h-32v11q0 49 -34 83t-83 34q-40 0 -71 -24t-42 -61h-15q-35 0 -60 -25.5t-25 -60.5t25 -60t60 -25h277zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5
+t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C3" unicode="cloud_upload" 
+d="M299 235h64l-107 106l-107 -106h64v-86h86v86zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE2C4" unicode="file_download" 
+d="M107 128h298v-43h-298v43zM405 320l-149 -149l-149 149h85v128h128v-128h85z" />
+    <glyph glyph-name="uniE2C6" unicode="file_upload" 
+d="M107 128h298v-43h-298v43zM192 171v128h-85l149 149l149 -149h-85v-128h-128z" />
+    <glyph glyph-name="uniE2C7" unicode="folder" 
+d="M213 427l43 -43h171q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128z" />
+    <glyph glyph-name="uniE2C8" unicode="folder_open" 
+d="M427 128v213h-342v-213h342zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE2C9" unicode="folder_shared" 
+d="M405 149v22q0 19 -29.5 30.5t-55.5 11.5t-55.5 -11.5t-29.5 -30.5v-22h170zM320 320q-17 0 -30 -13t-13 -30t13 -29.5t30 -12.5t30 12.5t13 29.5t-13 30t-30 13zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256
+q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE2CC" unicode="create_new_folder" 
+d="M405 213v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM427 384q18 0 30 -12.5t12 -30.5v-213q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h128l43 -43h171z" />
+    <glyph glyph-name="uniE307" unicode="cast" 
+d="M21 299q97 0 166 -69t69 -166h-43q0 80 -56.5 136t-135.5 56v43zM21 213q62 0 106 -43.5t44 -105.5h-43q0 44 -31.5 75.5t-75.5 31.5v42zM21 128q26 0 45 -19t19 -45h-64v64zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-149v43h149v298h-384v-64h-43v64
+q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE308" unicode="cast_connected" 
+d="M448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-149v43h149v298h-384v-64h-43v64q0 17 13 30t30 13h384zM21 299q97 0 166 -69t69 -166h-43q0 80 -56.5 136t-135.5 56v43zM405 363v-214h-120q-20 63 -67.5 111t-110.5 68v35h298zM21 213q62 0 106 -43.5
+t44 -105.5h-43q0 44 -31.5 75.5t-75.5 31.5v42zM21 128q26 0 45 -19t19 -45h-64v64z" />
+    <glyph glyph-name="uniE30A" unicode="computer" 
+d="M85 384v-213h342v213h-342zM427 128h85v-43h-512v43h85q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE30B" unicode="desktop_mac" 
+d="M448 213v214h-384v-214h384zM448 469q17 0 30 -12.5t13 -29.5v-256q0 -17 -13 -30t-30 -13h-149l42 -64v-21h-170v21l42 64h-149q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE30C" unicode="desktop_windows" 
+d="M448 171v256h-384v-256h384zM448 469q17 0 30 -12.5t13 -29.5v-256q0 -17 -13 -30t-30 -13h-149v-43h42v-42h-170v42h42v43h-149q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h384z" />
+    <glyph glyph-name="uniE30D" unicode="developer_board" 
+d="M256 277h85v-128h-85v128zM128 363h107v-107h-107v107zM256 363h85v-64h-85v64zM128 235h107v-86h-107v86zM384 107v298h-299v-298h299zM469 320h-42v-43h42v-42h-42v-43h42v-43h-42v-42q0 -17 -13 -30t-30 -13h-299q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13
+h299q17 0 30 -13t13 -30v-42h42v-43z" />
+    <glyph glyph-name="uniE30E" unicode="dock" 
+d="M341 192v213h-170v-213h170zM341 490q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -29.5t-30 -12.5h-170q-17 0 -30 12.5t-13 29.5v299q0 17 13 30t30 13zM171 21v43h170v-43h-170z" />
+    <glyph glyph-name="uniE30F" unicode="gamepad" 
+d="M352 320h117v-128h-117l-64 64zM192 160l64 64l64 -64v-117h-128v117zM160 320l64 -64l-64 -64h-117v128h117zM320 352l-64 -64l-64 64v117h128v-117z" />
+    <glyph glyph-name="uniE310" unicode="headset" 
+d="M256 491q80 0 136 -56.5t56 -135.5v-150q0 -26 -19 -45t-45 -19h-64v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-26 0 -45 19t-19 45v150q0 79 56 135.5t136 56.5z" />
+    <glyph glyph-name="uniE311" unicode="headset_mic" 
+d="M256 491q80 0 136 -56.5t56 -135.5v-214q0 -26 -19 -45t-45 -19h-128v43h149v21h-85v171h85v43q0 62 -43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5v-43h85v-171h-64q-26 0 -45 19t-19 45v150q0 79 56 135.5t136 56.5z" />
+    <glyph glyph-name="uniE312" unicode="keyboard" 
+d="M405 299v42h-42v-42h42zM405 235v42h-42v-42h42zM341 299v42h-42v-42h42zM341 235v42h-42v-42h42zM341 149v43h-170v-43h170zM149 299v42h-42v-42h42zM149 235v42h-42v-42h42zM171 277v-42h42v42h-42zM171 341v-42h42v42h-42zM235 277v-42h42v42h-42zM235 341v-42h42v42
+h-42zM427 405q17 0 29.5 -12.5t12.5 -29.5v-214q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v214q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE313" unicode="keyboard_arrow_down" 
+d="M158 345l98 -98l98 98l30 -30l-128 -128l-128 128z" />
+    <glyph glyph-name="uniE314" unicode="keyboard_arrow_left" 
+d="M329 169l-30 -30l-128 128l128 128l30 -30l-98 -98z" />
+    <glyph glyph-name="uniE315" unicode="keyboard_arrow_right" 
+d="M183 163l98 98l-98 98l30 30l128 -128l-128 -128z" />
+    <glyph glyph-name="uniE316" unicode="keyboard_arrow_up" 
+d="M158 183l-30 30l128 128l128 -128l-30 -30l-98 98z" />
+    <glyph glyph-name="uniE317" unicode="keyboard_backspace" 
+d="M448 277v-42h-302l76 -77l-30 -30l-128 128l128 128l30 -30l-76 -77h302z" />
+    <glyph glyph-name="uniE318" unicode="keyboard_capslock" 
+d="M128 128v43h256v-43h-256zM256 333l-98 -98l-30 30l128 128l128 -128l-30 -30z" />
+    <glyph glyph-name="uniE31A" unicode="keyboard_hide" 
+d="M256 21l-85 86h170zM405 341v43h-42v-43h42zM405 277v43h-42v-43h42zM341 341v43h-42v-43h42zM341 277v43h-42v-43h42zM341 192v43h-170v-43h170zM149 341v43h-42v-43h42zM149 277v43h-42v-43h42zM171 320v-43h42v43h-42zM171 384v-43h42v43h-42zM235 320v-43h42v43h-42z
+M235 384v-43h42v43h-42zM427 448q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE31B" unicode="keyboard_return" 
+d="M405 363h43v-128h-324l77 -77l-30 -30l-128 128l128 128l30 -30l-77 -77h281v86z" />
+    <glyph glyph-name="uniE31C" unicode="keyboard_tab" 
+d="M427 384h42v-256h-42v256zM247 354l30 30l128 -128l-128 -128l-30 30l77 77h-303v42h303z" />
+    <glyph glyph-name="uniE31D" unicode="keyboard_voice" 
+d="M369 256h36q0 -54 -37.5 -94.5t-90.5 -48.5v-70h-42v70q-53 8 -90.5 48.5t-37.5 94.5h36q0 -47 33.5 -78t79.5 -31t79.5 31t33.5 78zM256 192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z" />
+    <glyph glyph-name="uniE31E" unicode="laptop" 
+d="M85 384v-213h342v213h-342zM427 128h85v-43h-512v43h85q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13z" />
+    <glyph glyph-name="uniE31F" unicode="laptop_chromebook" 
+d="M427 192v213h-342v-213h342zM299 128v21h-86v-21h86zM469 128h43v-43h-512v43h43v320h426v-320z" />
+    <glyph glyph-name="uniE320" unicode="laptop_mac" 
+d="M256 107q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM85 405v-234h342v234h-342zM427 128h85q0 -17 -13 -30t-30 -13h-426q-17 0 -30 13t-13 30h85q-17 0 -29.5 13t-12.5 30v234q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-234q0 -17 -12.5 -30
+t-29.5 -13z" />
+    <glyph glyph-name="uniE321" unicode="laptop_windows" 
+d="M85 405v-213h342v213h-342zM427 128h85v-43h-512v43h85v21q-17 0 -29.5 13t-12.5 30v213q0 17 12.5 30t29.5 13h342q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13v-21z" />
+    <glyph glyph-name="uniE322" unicode="memory" 
+d="M363 149v214h-214v-214h214zM448 277h-43v-42h43v-43h-43v-43q0 -17 -12.5 -29.5t-29.5 -12.5h-43v-43h-43v43h-42v-43h-43v43h-43q-17 0 -29.5 12.5t-12.5 29.5v43h-43v43h43v42h-43v43h43v43q0 17 12.5 29.5t29.5 12.5h43v43h43v-43h42v43h43v-43h43q17 0 29.5 -12.5
+t12.5 -29.5v-43h43v-43zM277 235v42h-42v-42h42zM320 320v-128h-128v128h128z" />
+    <glyph glyph-name="uniE323" unicode="mouse" 
+d="M235 489v-169h-150q0 65 43.5 113t106.5 56zM85 192v85h342v-85q0 -70 -50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5zM277 489q63 -8 106.5 -56t43.5 -113h-150v169z" />
+    <glyph glyph-name="uniE324" unicode="phone_android" 
+d="M368 128v299h-224v-299h224zM299 64v21h-86v-21h86zM341 491q26 0 45 -19t19 -45v-342q0 -26 -19 -45t-45 -19h-170q-26 0 -45 19t-19 45v342q0 26 19 45t45 19h170z" />
+    <glyph glyph-name="uniE325" unicode="phone_iphone" 
+d="M341 128v299h-192v-299h192zM245 43q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM331 491q22 0 37.5 -16t15.5 -38v-362q0 -22 -15.5 -38t-37.5 -16h-171q-22 0 -37.5 16t-15.5 38v362q0 22 15.5 38t37.5 16h171z" />
+    <glyph glyph-name="uniE326" unicode="phonelink" 
+d="M469 149v150h-85v-150h85zM491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15.5 6.5t-6.5 15.5v213q0 9 6.5 15t15.5 6h128zM85 384v-235h214v-64h-299v64h43v235q0 17 12.5 30t29.5 13h384v-43h-384z" />
+    <glyph glyph-name="uniE327" unicode="phonelink_off" 
+d="M491 341q9 0 15 -6t6 -15v-213q0 -9 -6 -15.5t-15 -6.5h-4l-64 64h46v150h-85v-111l-43 43v89q0 9 6.5 15t15.5 6h128zM85 378v-229h229zM41 477q81 -81 230.5 -231t183.5 -184l-27 -27l-50 50h-378v64h43v235q0 15 10 27l-39 39zM469 384h-281l-43 43h324v-43z" />
+    <glyph glyph-name="uniE328" unicode="router" 
+d="M320 128v43h-43v-43h43zM245 128v43h-42v-43h42zM171 128v43h-43v-43h43zM405 235q17 0 30 -13t13 -30v-85q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v85q0 17 13 30t30 13h213v85h43v-85h42zM412 369l-17 -17q-21 21 -54 21q-32 0 -53 -21l-17 17q30 30 70 30
+q41 0 71 -30zM431 386q-41 36 -90 36q-48 0 -89 -36l-17 17q45 45 106 45q62 0 107 -45z" />
+    <glyph glyph-name="uniE329" unicode="scanner" 
+d="M405 149v43h-213v-43h213zM149 149v43h-42v-43h42zM422 284q11 -3 18.5 -14.5t7.5 -24.5v-117q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v85q0 17 13 30t30 13h268l-300 109l15 40z" />
+    <glyph glyph-name="uniE32A" unicode="security" 
+d="M256 491l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128zM256 256v-191q59 19 100 71.5t49 119.5h-149zM256 256v188l-149 -66v-122h149z" />
+    <glyph glyph-name="uniE32B" unicode="sim_card" 
+d="M363 192v85h-43v-85h43zM277 235v42h-42v-42h42zM277 107v85h-42v-85h42zM192 192v85h-43v-85h43zM363 107v42h-43v-42h43zM192 107v42h-43v-42h43zM426 427l1 -342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v256l128 128h171q17 0 29.5 -12.5t12.5 -29.5
+z" />
+    <glyph glyph-name="uniE32C" unicode="smartphone" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE32D" unicode="speaker" 
+d="M256 256q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 85q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 427q-18 0 -30.5 -13t-12.5 -30t12.5 -30t30.5 -13q17 0 30 13t13 30t-13 30
+t-30 13zM363 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-214q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h214z" />
+    <glyph glyph-name="uniE32E" unicode="speaker_group" 
+d="M128 405v-341h213v-43h-213q-18 0 -30.5 13t-12.5 30v341h43zM245 245q0 22 16 38t38 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-38 15.5t-16 37.5zM299 160q35 0 60 25t25 60t-25 60.5t-60 25.5t-60.5 -25.5t-25.5 -60.5t25.5 -60t60.5 -25zM299 448
+q-17 0 -30 -12.5t-13 -30.5t12.5 -30t30.5 -12q17 0 29.5 12t12.5 30t-12.5 30.5t-29.5 12.5zM388 491q16 0 27.5 -11.5t11.5 -27.5v-307q0 -16 -11.5 -27t-27.5 -11h-179q-16 0 -27 11t-11 27v307q0 16 11 27.5t27 11.5h179z" />
+    <glyph glyph-name="uniE32F" unicode="tablet" 
+d="M405 128v256h-298v-256h298zM448 427q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -30t-29.5 -13h-384q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE330" unicode="tablet_android" 
+d="M411 107v341h-310v-341h310zM299 43v21h-86v-21h86zM384 512q26 0 45 -19t19 -45v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h256z" />
+    <glyph glyph-name="uniE331" unicode="tablet_mac" 
+d="M405 107v341h-320v-341h320zM245 21q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM395 512q22 0 37.5 -15.5t15.5 -37.5v-406q0 -22 -15.5 -37.5t-37.5 -15.5h-299q-22 0 -37.5 15.5t-15.5 37.5v406q0 22 15.5 37.5
+t37.5 15.5h299z" />
+    <glyph glyph-name="uniE332" unicode="toys" 
+d="M256 256q0 -48 -34.5 -82.5t-82.5 -34.5t-83 34.5t-35 82.5h235zM256 256q-48 0 -82.5 34.5t-34.5 82.5t34.5 83t82.5 35v-235zM256 256q48 0 82.5 -34.5t34.5 -82.5t-34.5 -83t-82.5 -35v235zM256 256q0 48 34.5 82.5t82.5 34.5t83 -34.5t35 -82.5h-235z" />
+    <glyph glyph-name="uniE333" unicode="tv" 
+d="M448 149v256h-384v-256h384zM448 448q17 0 30 -13t13 -30l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-17 0 -30 12.5t-13 29.5v256q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE334" unicode="watch" 
+d="M128 256q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5zM427 256q0 -82 -65 -134l-21 -122h-170l-21 122q-65 50 -65 134t65 134l21 122h170l21 -122q65 -52 65 -134z" />
+    <glyph glyph-name="uniE335" unicode="device_hub" 
+d="M363 171h85v-107h-107v65l-85 90l-85 -90v-65h-107v107h85l86 85v68q-19 7 -31 23t-12 37q0 26 19 45t45 19t45 -19t19 -45q0 -21 -12 -37t-31 -23v-68z" />
+    <glyph glyph-name="uniE336" unicode="power_input" 
+d="M341 192v43h107v-43h-107zM192 192v43h107v-43h-107zM43 192v43h106v-43h-106zM43 320h405v-43h-405v43z" />
+    <glyph glyph-name="uniE337" unicode="devices_other" 
+d="M448 128v171h-85v-171h85zM469 341q8 0 15 -6.5t7 -14.5v-213q0 -8 -7 -15t-15 -7h-128q-8 0 -14.5 7t-6.5 15v213q0 8 6.5 14.5t14.5 6.5h128zM235 139q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM277 256v-38
+q22 -20 22 -47q0 -28 -22 -48v-38h-85v38q-21 19 -21 48q0 28 21 47v38h85zM64 384v-256h85v-43h-85q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384v-43h-384z" />
+    <glyph glyph-name="uniE338" unicode="videogame_asset" 
+d="M416 256q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM331 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM235 235v42h-64v64h-43v-64h-64v-42h64v-64h43v64h64zM448 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-384
+q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE39D" unicode="add_to_photos" 
+d="M405 277v43h-85v85h-43v-85h-85v-43h85v-85h43v85h85zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE39E" unicode="adjust" 
+d="M320 256q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5
+t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE39F" unicode="assistant" 
+d="M296 237l88 40l-88 40l-40 88l-40 -88l-88 -40l88 -40l40 -88zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64h-85q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3A0" unicode="assistant_photo" 
+d="M307 384h120v-213h-150l-8 42h-120v-149h-42v363h192z" />
+    <glyph glyph-name="uniE3A1" unicode="audiotrack" 
+d="M256 448h149v-64h-85v-235h-1q-4 -36 -31 -60.5t-64 -24.5q-40 0 -68 28t-28 68t28 68t68 28q17 0 32 -6v198z" />
+    <glyph glyph-name="uniE3A2" unicode="blur_circular" 
+d="M299 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15.5t15.5 6.5zM299 160q10 0 10 -11q0 -10 -10 -10q-11 0 -11 10q0 11 11 11zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5z
+M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM363 309q10 0 10 -10q0 -11 -10 -11q-11 0 -11 11q0 10 11 10zM363 224q10 0 10 -11q0 -10 -10 -10q-11 0 -11 10q0 11 11 11zM299 352q-11 0 -11 11
+q0 10 11 10q10 0 10 -10q0 -11 -10 -11zM299 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15.5 6.5t-6.5 15.5t6.5 15t15.5 6zM213 352q-10 0 -10 11q0 10 10 10q11 0 11 -10q0 -11 -11 -11zM149 224q11 0 11 -11q0 -10 -11 -10q-10 0 -10 10q0 11 10 11zM213 160q11 0 11 -11
+q0 -10 -11 -10q-10 0 -10 10q0 11 10 11zM149 309q11 0 11 -10q0 -11 -11 -11q-10 0 -10 11q0 10 10 10zM213 235q9 0 15.5 -6.5t6.5 -15.5t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15.5t15 6.5zM213 320q9 0 15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5t-15 6.5t-6 15.5t6 15t15 6z
+" />
+    <glyph glyph-name="uniE3A3" unicode="blur_linear" 
+d="M277 149q-9 0 -15 6.5t-6 15.5t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5zM277 235q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM277 320q-9 0 -15 6t-6 15t6 15.5t15 6.5t15.5 -6.5t6.5 -15.5t-6.5 -15t-15.5 -6zM363 245q-11 0 -11 11
+t11 11q10 0 10 -11t-10 -11zM363 331q-11 0 -11 10q0 11 11 11q10 0 10 -11q0 -10 -10 -10zM64 448h384v-43h-384v43zM363 160q-11 0 -11 11q0 10 11 10q10 0 10 -10q0 -11 -10 -11zM192 149q-9 0 -15 6.5t-6 15.5t6 15t15 6t15 -6t6 -15t-6 -15.5t-15 -6.5zM107 224
+q-13 0 -22.5 9t-9.5 23t9.5 23t22.5 9t22.5 -9t9.5 -23t-9.5 -23t-22.5 -9zM107 309q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM64 64v43h384v-43h-384zM192 320q-9 0 -15 6t-6 15t6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15
+t-15 -6zM192 235q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15t-15 -6zM107 139q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5z" />
+    <glyph glyph-name="uniE3A4" unicode="blur_off" 
+d="M64 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM128 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM213 75q11 0 11 -11t-11 -11q-10 0 -10 11t10 11zM64 309q11 0 11 -10q0 -11 -11 -11t-11 11q0 10 11 10zM128 235q9 0 15 -6.5t6 -15.5t-6 -15
+t-15 -6t-15 6t-6 15t6 15.5t15 6.5zM448 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM213 149q9 0 15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15t15 6zM53 400l27 27l347 -347l-28 -27l-80 81q1 -2 1 -6q0 -9 -6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6
+q4 0 6 -1l-60 60q-2 -11 -11 -19t-21 -8q-13 0 -22.5 9.5t-9.5 22.5q0 12 8 21t19 11l-60 60q1 -2 1 -6q0 -9 -6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6l6 -1zM299 75q10 0 10 -11t-10 -11q-11 0 -11 11t11 11zM384 363q-9 0 -15 6t-6 15t6 15t15 6t15 -6t6 -15t-6 -15
+t-15 -6zM384 277q-9 0 -15 6.5t-6 15.5t6 15t15 6t15 -6t6 -15t-6 -15.5t-15 -6.5zM384 192q-9 0 -15 6t-6 15t6 15.5t15 6.5t15 -6.5t6 -15.5t-6 -15t-15 -6zM213 363q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM448 288q-11 0 -11 11q0 10 11 10
+t11 -10q0 -11 -11 -11zM213 437q-10 0 -10 11t10 11q11 0 11 -11t-11 -11zM299 437q-11 0 -11 11t11 11q10 0 10 -11t-10 -11zM294 267q-10 1 -18 9t-9 18v5q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5h-5zM299 363q-9 0 -15.5 6t-6.5 15t6.5 15
+t15.5 6t15 -6t6 -15t-6 -15t-15 -6z" />
+    <glyph glyph-name="uniE3A5" unicode="blur_on" 
+d="M299 331q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM299 245q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM213 149q9 0 15.5 -6t6.5 -15t-6.5 -15t-15.5 -6t-15 6t-6 15t6 15
+t15 6zM213 331q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM299 75q10 0 10 -11t-10 -11q-11 0 -11 11t11 11zM299 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15.5 6t-6.5 15t6.5 15t15.5 6zM448 224q11 0 11 -11q0 -10 -11 -10
+t-11 10q0 11 11 11zM384 405q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM384 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6zM384 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM384 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6
+t-15 6t-6 15t6 15.5t15 6.5zM213 245q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM213 363q-9 0 -15 6t-6 15t6 15t15 6t15.5 -6t6.5 -15t-6.5 -15t-15.5 -6zM213 437q-10 0 -10 11t10 11q11 0 11 -11t-11 -11zM213 75
+q11 0 11 -11t-11 -11q-10 0 -10 11t10 11zM64 224q11 0 11 -11q0 -10 -11 -10t-11 10q0 11 11 11zM299 437q-11 0 -11 11t11 11q10 0 10 -11t-10 -11zM299 363q-9 0 -15.5 6t-6.5 15t6.5 15t15.5 6t15 -6t6 -15t-6 -15t-15 -6zM448 288q-11 0 -11 11q0 10 11 10t11 -10
+q0 -11 -11 -11zM128 405q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15t15 6zM64 309q11 0 11 -10q0 -11 -11 -11t-11 11q0 10 11 10zM128 320q9 0 15 -6t6 -15t-6 -15.5t-15 -6.5t-15 6.5t-6 15.5t6 15t15 6zM128 149q9 0 15 -6t6 -15t-6 -15t-15 -6t-15 6t-6 15t6 15
+t15 6zM128 235q9 0 15 -6.5t6 -15.5t-6 -15t-15 -6t-15 6t-6 15t6 15.5t15 6.5z" />
+    <glyph glyph-name="uniE3A6" unicode="brightness_&#x31;" 
+d="M43 256q0 88 62.5 150.5t150.5 62.5t150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5z" />
+    <glyph glyph-name="uniE3A7" unicode="brightness_&#x32;" 
+d="M213 469q89 0 151.5 -62.5t62.5 -150.5t-62.5 -150.5t-151.5 -62.5q-58 0 -106 28q49 28 77.5 77.5t28.5 107.5t-28.5 107.5t-77.5 77.5q48 28 106 28z" />
+    <glyph glyph-name="uniE3A8" unicode="brightness_&#x33;" 
+d="M192 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5q-34 0 -64 9q66 20 107.5 76.5t41.5 127.5t-41.5 127.5t-107.5 76.5q30 9 64 9z" />
+    <glyph glyph-name="uniE3A9" unicode="brightness_&#x34;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5q-26 0 -53 -12q33 -15 53.5 -46.5t20.5 -69.5t-20.5 -69.5t-53.5 -46.5q27 -12 53 -12zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE3AA" unicode="brightness_&#x35;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE3AB" unicode="brightness_&#x36;" 
+d="M256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5v-256zM427 185v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100l71 70l71 -70h100v-100l70 -71z" />
+    <glyph glyph-name="uniE3AC" unicode="brightness_&#x37;" 
+d="M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM256 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM427 327l70 -71l-70 -71v-100h-100l-71 -70l-71 70h-100v100l-70 71l70 71v100h100
+l71 70l71 -70h100v-100z" />
+    <glyph glyph-name="uniE3AD" unicode="broken_image" 
+d="M384 268l64 -64v-97q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v140l64 -64l85 86l86 -86zM448 405v-140l-64 64l-85 -86l-86 86l-85 -86l-64 65v97q0 17 13 30t30 13h298q17 0 30 -13t13 -30z" />
+    <glyph glyph-name="uniE3AE" unicode="brush" 
+d="M442 413q6 -6 6 -15t-6 -15l-191 -191l-59 59l191 191q6 6 15 6t15 -6zM149 213q26 0 45 -19t19 -45q0 -35 -25 -60t-60 -25q-52 0 -85 43q15 0 28.5 11.5t13.5 30.5q0 26 19 45t45 19z" />
+    <glyph glyph-name="uniE3AF" unicode="camera" 
+d="M210 48q4 7 43 75t60 103l78 -135q-59 -48 -135 -48q-21 0 -46 5zM52 192h207l-79 -135q-46 17 -79.5 52.5t-48.5 82.5zM99 400l108 -187h-160q-4 19 -4 43q0 83 56 144zM465 299q4 -19 4 -43q0 -83 -56 -144l-102 176l-6 11h160zM460 320h-207l79 135q46 -17 79.5 -52.5
+t48.5 -82.5zM201 288l-2 -2l-78 135q59 48 135 48q21 0 46 -5z" />
+    <glyph glyph-name="uniE3B0" unicode="camera_alt" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE3B1" unicode="camera_front" 
+d="M149 469v-224q0 24 36.5 39t70.5 15t70.5 -15t36.5 -39v224h-214zM363 512q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-150l64 -64l-64 -64v43h-106v42h106v43h-64q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214zM256 341q-17 0 -29.5 13
+t-12.5 30t12.5 30t29.5 13t30 -13t13 -30t-13 -30t-30 -13zM299 85h106v-42h-106v42z" />
+    <glyph glyph-name="uniE3B2" unicode="camera_rear" 
+d="M256 384q17 0 29.5 13t12.5 30t-12.5 29.5t-29.5 12.5t-30 -12.5t-13 -29.5t12.5 -30t30.5 -13zM363 512q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-150l64 -64l-64 -64v43h-106v42h106v43h-64q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214z
+M299 85h106v-42h-106v42z" />
+    <glyph glyph-name="uniE3B3" unicode="camera_roll" 
+d="M427 320v43h-43v-43h43zM427 128v43h-43v-43h43zM341 320v43h-42v-43h42zM341 128v43h-42v-43h42zM256 320v43h-43v-43h43zM256 128v43h-43v-43h43zM299 405h170v-320h-170q0 -17 -13 -29.5t-30 -12.5h-171q-17 0 -29.5 12.5t-12.5 29.5v320q0 17 12.5 30t29.5 13h22v21
+q0 9 6 15.5t15 6.5h85q9 0 15.5 -6.5t6.5 -15.5v-21h21q17 0 30 -13t13 -30z" />
+    <glyph glyph-name="uniE3B4" unicode="center_focus_strong" 
+d="M405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43zM256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25
+t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE3B5" unicode="center_focus_weak" 
+d="M256 213q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405
+v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43z" />
+    <glyph glyph-name="uniE3B6" unicode="collections" 
+d="M43 384h42v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299zM235 256l-64 -85h256l-86 106l-63 -79zM469 171q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256q17 0 29.5 -12.5t12.5 -29.5v-256z" />
+    <glyph glyph-name="uniE3B7" unicode="color_lens" 
+d="M373 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM309 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM203 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5
+t9.5 -22.5t22.5 -9.5zM139 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 448q79 0 135.5 -50t56.5 -121q0 -44 -31.5 -75t-75.5 -31h-37q-14 0 -23 -9.5t-9 -22.5q0 -11 8 -21t8 -22q0 -14 -9 -23t-23 -9q-80 0 -136 56t-56 136t56 136
+t136 56z" />
+    <glyph glyph-name="uniE3B8" unicode="colorize" 
+d="M148 107l172 172l-41 41l-172 -172zM442 392q15 -15 0 -30l-67 -67l41 -41l-30 -30l-30 30l-191 -190h-101v101l190 191l-30 30l30 30l41 -41l67 67q6 6 15 6t15 -6z" />
+    <glyph glyph-name="uniE3B9" unicode="compare" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-106v192l106 -128v277h-106v43h106zM213 128v128l-106 -128h106zM213 448v43h43v-470h-43v43h-106q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h106z" />
+    <glyph glyph-name="uniE3BA" unicode="control_point" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86h86v-42h-86v-86h-42
+v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE3BB" unicode="control_point_duplicate" 
+d="M320 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM320 448q80 0 136 -56t56 -136t-56 -136t-136 -56t-136 56t-56 136t56 136t136 56zM43 256q0 -44 23 -80.5t62 -54.5v-46q-56 20 -92 70t-36 111
+t36 111t92 70v-46q-39 -18 -62 -54.5t-23 -80.5zM341 341v-64h64v-42h-64v-64h-42v64h-64v42h64v64h42z" />
+    <glyph glyph-name="uniE3BC" unicode="crop_&#x31;&#x36;_&#x39;" 
+d="M405 171v170h-298v-170h298zM405 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3BD" unicode="crop_&#x33;_&#x32;" 
+d="M405 128v256h-298v-256h298zM405 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3BE" unicode="crop" 
+d="M149 149h342v-42h-86v-86h-42v86h-214q-17 0 -29.5 12.5t-12.5 29.5v214h-86v42h86v86h42v-342zM363 192v171h-171v42h171q17 0 29.5 -12.5t12.5 -29.5v-171h-42z" />
+    <glyph glyph-name="uniE3BF" unicode="crop_&#x35;_&#x34;" 
+d="M405 149v214h-298v-214h298zM405 405q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3C0" unicode="crop_&#x37;_&#x35;" 
+d="M405 192v128h-298v-128h298zM405 363q17 0 30 -13t13 -30v-128q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v128q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C1" unicode="crop_din" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C2" unicode="crop_free" 
+d="M405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM107 192v-85h85v-43h-85q-17 0 -30 13t-13 30v85h43zM64 405q0 17 13 30t30 13h85v-43h-85v-85h-43v85z" />
+    <glyph glyph-name="uniE3C3" unicode="crop_landscape" 
+d="M405 149v214h-298v-214h298zM405 405q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h298z" />
+    <glyph glyph-name="uniE3C4" unicode="crop_original" 
+d="M298 250l75 -101h-234l58 76l42 -51zM405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3C5" unicode="crop_portrait" 
+d="M363 107v298h-214v-298h214zM363 448q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v298q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE3C6" unicode="crop_square" 
+d="M384 128v256h-256v-256h256zM384 427q17 0 30 -13t13 -30v-256q0 -17 -13 -30t-30 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h256z" />
+    <glyph glyph-name="uniE3C7" unicode="dehaze" 
+d="M43 395h426v-43h-426v43zM43 288h426v-43h-426v43zM43 181h426v-42h-426v42z" />
+    <glyph glyph-name="uniE3C8" unicode="details" 
+d="M136 384l120 -213l120 213h-240zM64 427h384l-192 -342z" />
+    <glyph glyph-name="uniE3C9" unicode="edit" 
+d="M442 362l-39 -39l-80 80l39 39q6 6 15 6t15 -6l50 -50q6 -6 6 -15t-6 -15zM64 144l236 236l80 -80l-236 -236h-80v80z" />
+    <glyph glyph-name="uniE3CA" unicode="exposure" 
+d="M427 85v342l-342 -342h342zM107 405v-42h128v42h-128zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342zM320 149h-43v43h43v43h43v-43h42v-43h-42v-42h-43v42z" />
+    <glyph glyph-name="uniE3CB" unicode="exposure_minus_&#x31;" 
+d="M405 128h-42v227l-64 -22v36l100 36h6v-277zM85 277h171v-42h-171v42z" />
+    <glyph glyph-name="uniE3CC" unicode="exposure_minus_&#x32;" 
+d="M43 277h170v-42h-170v42zM351 405q86 0 86 -75q0 -14 -4 -25q-7 -19 -11 -25q-17 -27 -40 -50l-61 -66h127v-36h-184v32l89 97q18 18 31 40q7 12 7 28q0 13 -2 18q-10 26 -38 26q-46 0 -46 -49h-46q0 36 24 60q25 25 68 25z" />
+    <glyph glyph-name="uniE3CD" unicode="exposure_plus_&#x31;" 
+d="M427 128h-43v227l-64 -22v36l100 36h7v-277zM213 363v-86h86v-42h-86v-86h-42v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE3CE" unicode="exposure_plus_&#x32;" 
+d="M171 363v-86h85v-42h-85v-86h-43v86h-85v42h85v86h43zM342 164h127v-36h-184v32l89 97q18 18 31 40q8 13 8 28q0 16 -11 31q-10 13 -30 13q-21 0 -35 -14q-11 -11 -11 -35h-46q0 36 24 60q14 14 29 19q18 6 40 6q18 0 36 -5q20 -8 27 -15q23 -20 23 -55q0 -24 -16 -50
+q-12 -20 -17 -25q-14 -16 -23 -25z" />
+    <glyph glyph-name="uniE3CF" unicode="exposure_zero" 
+d="M299 296q0 40 -11 57q-4 6 -14 12q-7 4 -18 4t-18 -4q-10 -6 -14 -12q-11 -17 -11 -57v-57q0 -56 25 -71q7 -4 18 -4q21 0 32 17q12 18 12 58v57h-1zM168 289q0 116 88 116q65 0 82 -62q7 -26 7 -54v-44h-1q0 -59 -24 -90q-14 -16 -28 -21q-16 -6 -36 -6t-36 6
+q-14 5 -28 21q-24 27 -24 90v44z" />
+    <glyph glyph-name="uniE3D0" unicode="filter_&#x31;" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM299 192v171h-43v42h85v-213h-42zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D1" unicode="filter_&#x32;" 
+d="M363 235v-43h-128v85q0 18 12.5 30.5t29.5 12.5h43v43h-85v42h85q18 0 30.5 -12t12.5 -30v-43q0 -18 -13 -30.5t-30 -12.5h-43v-42h86zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299
+q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D2" unicode="filter_&#x33;" 
+d="M363 235q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-43v43h43v43h-85v42h85q18 0 30.5 -12t12.5 -30v-32q0 -13 -9.5 -22.5t-22.5 -9.5q13 0 22.5 -9.5t9.5 -22.5v-32zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM448 149v299h-299v-299h299zM448 491
+q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299z" />
+    <glyph glyph-name="uniE3D3" unicode="filter" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM340 292l76 -100h-235l59 75l42 -50z" />
+    <glyph glyph-name="uniE3D4" unicode="filter_&#x34;" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM320 192v85h-85v128h42v-85h43v85h43v-213h-43zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D5" unicode="filter_&#x35;" 
+d="M363 235q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-85v128h128v-42h-86v-43h43q17 0 30 -12.5t13 -30.5v-42zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299z" />
+    <glyph glyph-name="uniE3D6" unicode="filter_&#x36;" 
+d="M277 277v-42h43v42h-43zM277 192q-17 0 -29.5 12.5t-12.5 30.5v128q0 18 12.5 30t29.5 12h86v-42h-86v-43h43q17 0 30 -12.5t13 -30.5v-42q0 -18 -13 -30.5t-30 -12.5h-43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D7" unicode="filter_&#x37;" 
+d="M277 192h-42l85 171h-85v42h128v-42zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D8" unicode="filter_&#x38;" 
+d="M277 277v-42h43v42h-43zM277 363v-43h43v43h-43zM277 192q-17 0 -29.5 12.5t-12.5 30.5v32q0 13 9.5 22.5t22.5 9.5q-13 0 -22.5 9.5t-9.5 22.5v32q0 18 12.5 30t29.5 12h43q18 0 30.5 -12t12.5 -30v-32q0 -13 -9.5 -22.5t-22.5 -9.5q13 0 22.5 -9.5t9.5 -22.5v-32
+q0 -18 -13 -30.5t-30 -12.5h-43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3D9" unicode="filter_&#x39;" 
+d="M320 320v43h-43v-43h43zM320 405q18 0 30.5 -12t12.5 -30v-128q0 -18 -13 -30.5t-30 -12.5h-85v43h85v42h-43q-17 0 -29.5 12.5t-12.5 30.5v43q0 18 12.5 30t29.5 12h43zM448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299
+q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3DA" unicode="filter_&#x39;_plus" 
+d="M448 320v128h-299v-299h299v128h-43v-42h-42v42h-43v43h43v43h42v-43h43zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM235 320h21v21h-21v-21zM299 256q0 -18 -13 -30.5t-30 -12.5h-64
+v43h64v21h-21q-17 0 -30 12.5t-13 30.5v21q0 18 13 30.5t30 12.5h21q17 0 30 -12.5t13 -30.5v-85zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3DB" unicode="filter_b_and_w" 
+d="M405 107v298h-149v-128zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM256 277l-149 -170h149v170z" />
+    <glyph glyph-name="uniE3DC" unicode="filter_center_focus" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM405 107v85h43v-85q0 -17 -13 -30t-30 -13h-85v43h85zM405 448q17 0 30 -13t13 -30v-85h-43v85h-85v43h85zM107 405v-85h-43v85q0 17 13 30t30 13h85v-43h-85zM107 192v-85h85v-43h-85
+q-17 0 -30 13t-13 30v85h43z" />
+    <glyph glyph-name="uniE3DD" unicode="filter_drama" 
+d="M405 128q26 0 45 19t19 45t-19 45t-45 19h-32v11q0 49 -34 83t-83 34q-58 0 -94 -47q41 -11 67.5 -45.5t26.5 -78.5h-43q0 35 -25 60.5t-60 25.5t-60 -25.5t-25 -60.5t25 -60t60 -25h277zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277
+q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE3DE" unicode="filter_frames" 
+d="M384 341h-256v-213h256v213zM427 85v299h-96l-74 75l-75 -75h-97v-299h342zM427 427q17 0 29.5 -13t12.5 -30v-299q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h86l85 85l85 -85h86z" />
+    <glyph glyph-name="uniE3DF" unicode="filter_hdr" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE3E0" unicode="filter_none" 
+d="M448 149v299h-299v-299h299zM448 491q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-299q-17 0 -29.5 12.5t-12.5 29.5v299q0 17 12.5 30t29.5 13h299zM64 405v-341h341v-43h-341q-17 0 -30 13t-13 30v341h43z" />
+    <glyph glyph-name="uniE3E2" unicode="filter_tilt_shift" 
+d="M121 91l30 30q37 -28 84 -34v-43q-63 6 -114 47zM277 87q47 6 83 34l31 -30q-51 -41 -114 -47v43zM391 151q28 38 34 83h43q-6 -62 -47 -113zM320 256q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM87 235q6 -47 34 -83l-30 -31q-41 51 -47 114h43z
+M121 361q-28 -37 -34 -84h-43q6 63 47 114zM425 277q-6 47 -34 84l30 30q41 -51 47 -114h-43zM391 421l-30 -30q-37 28 -84 34v43q63 -6 114 -47zM235 425q-47 -6 -84 -34l-30 30q51 41 114 47v-43z" />
+    <glyph glyph-name="uniE3E3" unicode="filter_vintage" 
+d="M256 171q35 0 60 25t25 60t-25 60t-60 25t-60 -25t-25 -60t25 -60t60 -25zM49 137q0 38 22.5 72.5t59.5 46.5l-18 9q-30 18 -47 47.5t-17 62.5q64 37 128 0q9 -4 17 -11q-2 14 -2 20q0 35 17.5 64.5t46.5 46.5q29 -17 46.5 -46.5t17.5 -64.5q0 -6 -2 -20q8 7 17 11
+q64 37 128 0q0 -33 -17 -62.5t-47 -47.5q-2 -1 -5.5 -3t-6.5 -3.5t-6 -2.5q3 -1 6 -2.5t6.5 -3.5t5.5 -3q30 -18 47 -47.5t17 -62.5q-64 -37 -128 0q-9 4 -17 11q2 -14 2 -20q0 -35 -17.5 -64.5t-46.5 -46.5q-29 17 -46.5 46.5t-17.5 64.5q0 6 2 20q-8 -7 -17 -11
+q-64 -37 -128 0z" />
+    <glyph glyph-name="uniE3E4" unicode="flare" 
+d="M235 21v128h42v-128h-42zM120 150l46 46l30 -30l-46 -46zM316 166l30 30l46 -46l-30 -30zM256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM363 277h128v-42h-128v42zM392 362l-46 -46l-30 30l46 46zM277 491v-128h-42v128h42zM196 346l-30 -30
+l-46 46l30 30zM149 277v-42h-128v42h128z" />
+    <glyph glyph-name="uniE3E5" unicode="flash_auto" 
+d="M359 349h50l-25 78zM405 469l69 -192h-41l-15 43h-68l-15 -43h-41l69 192h42zM64 469h213l-85 -192h85l-149 -256v192h-64v256z" />
+    <glyph glyph-name="uniE3E6" unicode="flash_off" 
+d="M363 299l-33 -57l-181 181v46h214l-86 -170h86zM70 448l335 -336l-27 -27l-88 89l-77 -131v192h-64v79l-106 107z" />
+    <glyph glyph-name="uniE3E7" unicode="flash_on" 
+d="M149 469h214l-86 -170h86l-150 -256v192h-64v234z" />
+    <glyph glyph-name="uniE3E8" unicode="flip" 
+d="M405 64v43h43q0 -17 -13 -30t-30 -13zM405 235v42h43v-42h-43zM320 405v43h43v-43h-43zM405 149v43h43v-43h-43zM235 21v470h42v-470h-42zM405 448q17 0 30 -13t13 -30h-43v43zM64 405q0 17 13 30t30 13h85v-43h-85v-298h85v-43h-85q-17 0 -30 13t-13 30v298zM405 320v43
+h43v-43h-43zM320 64v43h43v-43h-43z" />
+    <glyph glyph-name="uniE3E9" unicode="gradient" 
+d="M405 277v128h-298v-128h42v-42h43v-43h43v43h42v-43h43v43h43v42h42zM363 128v43h-43v-43h43zM277 128v43h-42v-43h42zM192 128v43h-43v-43h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM149 320h43
+v-43h-43v43zM320 320h43v-43h-43v43zM235 320h42v-43h43v-42h-43v42h-42v-42h-43v42h43v43zM363 235v-43h42v43h-42zM149 235h-42v-43h42v43z" />
+    <glyph glyph-name="uniE3EA" unicode="grain" 
+d="M213 427q17 0 30 -13t13 -30t-13 -30t-30 -13t-29.5 13t-12.5 30t12.5 30t29.5 13zM299 341q17 0 29.5 -12.5t12.5 -29.5t-12.5 -30t-29.5 -13t-30 13t-13 30t13 29.5t30 12.5zM384 256q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-30 12.5t-13 29.5t13 30t30 13zM299 171
+q17 0 29.5 -13t12.5 -30t-12.5 -30t-29.5 -13t-30 13t-13 30t13 30t30 13zM384 341q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13zM128 171q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 341q17 0 30 -12.5t13 -29.5t-13 -30
+t-30 -13t-30 13t-13 30t13 29.5t30 12.5zM213 256q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE3EB" unicode="grid_off" 
+d="M341 85h31l-31 31v-31zM299 85v74l-12 12h-74v-86h86zM171 213v74l-12 12h-74v-86h86zM171 85v86h-86v-86h86zM85 372v-31h31zM213 244v-31h31zM27 485l458 -458l-27 -27l-43 43h-330q-17 0 -29.5 12.5t-12.5 29.5v330l-43 43zM341 427v-86h86v86h-86zM171 427h-31
+l-43 42h330q17 0 29.5 -12.5t12.5 -29.5v-330l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31h-31l-43 42h74v86h-86v-74l-42 43v31z" />
+    <glyph glyph-name="uniE3EC" unicode="grid_on" 
+d="M427 341v86h-86v-86h86zM427 213v86h-86v-86h86zM427 85v86h-86v-86h86zM299 341v86h-86v-86h86zM299 213v86h-86v-86h86zM299 85v86h-86v-86h86zM171 341v86h-86v-86h86zM171 213v86h-86v-86h86zM171 85v86h-86v-86h86zM427 469q17 0 29.5 -12.5t12.5 -29.5v-342
+q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v342q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE3ED" unicode="hdr_off" 
+d="M53 459q98 -99 408 -406l-24 -23l-162 162h-72v73l-32 32v-105h-32v53h-43v-53h-32v128h32v-43h43v43h8l-117 117zM277 288h-8l-32 32h40q13 0 22.5 -9.5t9.5 -22.5v-41l-32 32v9zM373 288v-21h43v21h-43zM373 192h-8l-24 23v105h75q13 0 22.5 -9.5t9.5 -22.5v-21
+q0 -23 -19 -30l19 -45h-32l-19 43h-24v-43z" />
+    <glyph glyph-name="uniE3EE" unicode="hdr_on" 
+d="M277 224v64h-42v-64h42zM277 320q13 0 22.5 -9.5t9.5 -22.5v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-74v128h74zM139 277v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43zM416 267v21h-43v-21h43zM448 267q0 -20 -19 -30l19 -45h-32l-19 43h-24v-43h-32v128h75q13 0 22.5 -9.5
+t9.5 -22.5v-21z" />
+    <glyph glyph-name="uniE3F1" unicode="hdr_strong" 
+d="M107 213q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13t-30 -13t-13 -30t13 -30t30 -13zM107 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60.5 25t-25.5 60t25.5 60t60.5 25zM363 384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5
+t90.5 37.5z" />
+    <glyph glyph-name="uniE3F2" unicode="hdr_weak" 
+d="M363 171q35 0 60 25t25 60t-25 60t-60 25t-60.5 -25t-25.5 -60t25.5 -60t60.5 -25zM363 384q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM107 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60.5 25t-25.5 60t25.5 60
+t60.5 25z" />
+    <glyph glyph-name="uniE3F3" unicode="healing" 
+d="M355 78l78 78l-78 77l-77 -78zM299 277q-9 0 -15.5 -6t-6.5 -15t6.5 -15t15.5 -6t15 6t6 15t-6 15t-15 6zM256 192q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM213 235q9 0 15.5 6t6.5 15t-6.5 15t-15.5 6t-15 -6t-6 -15t6 -15t15 -6zM156 278l77 78
+l-77 77l-78 -78zM256 320q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM378 256l85 -85q6 -6 6 -15t-6 -15l-92 -93q-6 -6 -15 -6q-10 0 -16 6l-84 85l-85 -85q-6 -6 -15 -6t-15 6l-93 93q-6 6 -6 15t6 15l85 85l-85 84q-6 6 -6 15.5t6 15.5l93 92
+q6 6 15 6t15 -6l85 -85l84 85q6 6 15.5 6t15.5 -6l92 -92q6 -6 6 -15.5t-6 -15.5z" />
+    <glyph glyph-name="uniE3F4" unicode="image" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE3F5" unicode="image_aspect_ratio" 
+d="M427 128v256h-342v-256h342zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342zM256 299v-43h-43v43h43zM171 299v-43h-43v43h43zM341 213v-42h-42v42h42zM341 299v-43h-42v43h42z" />
+    <glyph glyph-name="uniE3F6" unicode="iso" 
+d="M363 149h-107v32h107v-32zM405 107v298l-298 -298h298zM117 352v-32h43v-43h32v43h43v32h-43v43h-32v-43h-43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3F7" unicode="landscape" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE3F8" unicode="leak_add" 
+d="M299 64q0 62 43.5 105.5t105.5 43.5v-42q-44 0 -75.5 -31.5t-31.5 -75.5h-42zM384 64q0 26 19 45t45 19v-64h-64zM213 64q0 97 69 166t166 69v-43q-80 0 -136 -56t-56 -136h-43zM213 448q0 -62 -43.5 -105.5t-105.5 -43.5v42q44 0 75.5 31.5t31.5 75.5h42zM299 448
+q0 -97 -69 -166t-166 -69v43q80 0 136 56t56 136h43zM128 448q0 -26 -19 -45t-45 -19v64h64z" />
+    <glyph glyph-name="uniE3F9" unicode="leak_remove" 
+d="M328 265q57 34 120 34v-43q-47 0 -89 -22zM425 168l-34 34q27 11 57 11v-42q-12 0 -23 -3zM299 448q0 -63 -34 -120l-31 31q22 42 22 89h43zM64 421l27 27l357 -357l-27 -27l-61 61q-19 -27 -19 -61h-42q0 50 31 91l-31 30q-43 -53 -43 -121h-43q0 86 56 152l-53 53
+q-66 -56 -152 -56v43q69 0 122 43l-31 31q-41 -31 -91 -31v42q34 0 61 19zM213 448q0 -30 -11 -57l-34 34q3 11 3 23h42z" />
+    <glyph glyph-name="uniE3FA" unicode="lens" 
+d="M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE3FB" unicode="looks_&#x33;" 
+d="M320 288v32q0 18 -12.5 30.5t-29.5 12.5h-86v-43h86v-43h-43v-42h43v-43h-86v-43h86q17 0 29.5 12.5t12.5 30.5v32q0 14 -9 23t-23 9q14 0 23 9t9 23zM406 448q17 0 29.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-29.5 -13h-299q-17 0 -30 13t-13 30v298q0 17 13 30t30 13
+h299z" />
+    <glyph glyph-name="uniE3FC" unicode="looks" 
+d="M256 384q97 0 166 -69t69 -166h-43q0 79 -56 135.5t-136 56.5t-136 -56.5t-56 -135.5h-43q0 97 69 166t166 69zM256 299q61 0 105 -44t44 -106h-42q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5h-42q0 62 44 106t105 44z" />
+    <glyph glyph-name="uniE3FD" unicode="looks_&#x34;" 
+d="M320 149v214h-43v-86h-42v86h-43v-128h85v-86h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3FE" unicode="looks_&#x35;" 
+d="M320 320v43h-128v-128h85v-43h-85v-43h85q17 0 30 12.5t13 30.5v43q0 18 -12.5 30t-30.5 12h-42v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE3FF" unicode="looks_&#x36;" 
+d="M320 320v43h-85q-17 0 -30 -12.5t-13 -30.5v-128q0 -18 13 -30.5t30 -12.5h42q17 0 30 12.5t13 30.5v43q0 18 -12.5 30t-30.5 12h-42v43h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM235 192v43h42
+v-43h-42z" />
+    <glyph glyph-name="uniE400" unicode="looks_one" 
+d="M299 149v214h-86v-43h43v-171h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE401" unicode="looks_two" 
+d="M320 277v43q0 18 -13 30.5t-30 12.5h-85v-43h85v-43h-42q-18 0 -30.5 -12t-12.5 -30v-86h128v43h-85v43h42q18 0 30.5 12t12.5 30zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE402" unicode="loupe" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-171q-88 0 -150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM277 363v-86
+h86v-42h-86v-86h-42v86h-86v42h86v86h42z" />
+    <glyph glyph-name="uniE403" unicode="monochrome_photos" 
+d="M427 107v256h-171v-22q45 0 76 -30.5t31 -75.5t-31 -76t-76 -31v38q-28 0 -48 20t-20 49t20 48.5t48 19.5v-137q28 0 48 20t20 49t-20 48.5t-48 19.5v38q-45 0 -76 -30.5t-31 -75.5t31 -76t76 -31v-21h171zM427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30
+t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h69l38 43h128l38 -43h69z" />
+    <glyph glyph-name="uniE404" unicode="movie_creation" 
+d="M384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -86h64l-42 86h42l43 -86h64l-43 86h43l43 -86h64z" />
+    <glyph glyph-name="uniE405" unicode="music_note" 
+d="M256 448h128v-85h-85v-214q0 -35 -25.5 -60t-60.5 -25t-60 25t-25 60t25 60.5t60 25.5q21 0 43 -12v225z" />
+    <glyph glyph-name="uniE406" unicode="nature" 
+d="M277 168v-83h128v-42h-298v42h128v84q-54 9 -89.5 50.5t-35.5 96.5q0 62 44 106t106 44t105.5 -44t43.5 -106q0 -57 -38 -99t-94 -49z" />
+    <glyph glyph-name="uniE407" unicode="nature_people" 
+d="M96 277q-14 0 -23 9.5t-9 22.5t9 22.5t23 9.5t23 -9.5t9 -22.5t-9 -22.5t-23 -9.5zM473 316q0 -57 -38 -99t-94 -49v-83h64v-42h-341v106h-21v86q0 9 6 15t15 6h64q9 0 15 -6t6 -15v-86h-21v-64h171v84q-54 9 -89.5 50.5t-35.5 96.5q0 62 44 106t106 44t105.5 -44
+t43.5 -106z" />
+    <glyph glyph-name="uniE408" unicode="navigate_before" 
+d="M329 354l-98 -98l98 -98l-30 -30l-128 128l128 128z" />
+    <glyph glyph-name="uniE409" unicode="navigate_next" 
+d="M213 384l128 -128l-128 -128l-30 30l98 98l-98 98z" />
+    <glyph glyph-name="uniE40A" unicode="palette" 
+d="M373 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM309 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM203 341q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5
+t9.5 -22.5t22.5 -9.5zM139 256q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 448q79 0 135.5 -50t56.5 -121q0 -44 -31.5 -75t-75.5 -31h-37q-14 0 -23 -9.5t-9 -22.5q0 -11 8 -21t8 -22q0 -14 -9 -23t-23 -9q-80 0 -136 56t-56 136t56 136
+t136 56z" />
+    <glyph glyph-name="uniE40B" unicode="panorama" 
+d="M181 245l-74 -96h298l-96 128l-74 -96zM491 128q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v256q0 17 13 30t30 13h384q17 0 30 -13t13 -30v-256z" />
+    <glyph glyph-name="uniE40C" unicode="panorama_fish_eye" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE40D" unicode="panorama_horizontal" 
+d="M457 427q12 0 12 -14v-314q0 -14 -12 -14q-2 0 -6 2q-96 35 -195 35t-195 -35q-4 -2 -6 -2q-12 0 -12 14v314q0 14 12 14q2 0 6 -2q96 -35 195 -35t195 35q4 2 6 2zM427 372q-78 -24 -171 -24q-88 0 -171 24v-232q82 24 171 24q88 0 171 -24v232z" />
+    <glyph glyph-name="uniE40E" unicode="panorama_vertical" 
+d="M140 85h232q-24 83 -24 171t24 171h-232q24 -82 24 -171q0 -88 -24 -171zM425 61q2 -4 2 -6q0 -12 -14 -12h-314q-14 0 -14 12q0 2 2 6q35 96 35 195t-35 195q-2 4 -2 6q0 12 14 12h314q14 0 14 -12q0 -2 -2 -6q-35 -96 -35 -195t35 -195z" />
+    <glyph glyph-name="uniE40F" unicode="panorama_wide_angle" 
+d="M256 427q77 0 170 -16l19 -3l6 -19q18 -66 18 -133t-18 -133l-6 -19l-19 -3q-93 -16 -170 -16t-170 16l-19 3l-6 19q-18 66 -18 133t18 133l6 19l19 3q93 16 170 16zM256 384q-70 0 -156 -14q-15 -57 -15 -114t15 -114q86 -14 156 -14t156 14q15 57 15 114t-15 114
+q-86 14 -156 14z" />
+    <glyph glyph-name="uniE410" unicode="photo" 
+d="M181 224l-74 -96h298l-96 128l-74 -96zM448 107q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298q17 0 30 -13t13 -30v-298z" />
+    <glyph glyph-name="uniE411" unicode="photo_album" 
+d="M128 107h256l-82 109l-64 -82l-46 55zM128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE412" unicode="photo_camera" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE413" unicode="photo_library" 
+d="M43 384h42v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299zM235 256l-64 -85h256l-86 106l-63 -79zM469 171q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256q17 0 29.5 -12.5t12.5 -29.5v-256z" />
+    <glyph glyph-name="uniE415" unicode="picture_as_pdf" 
+d="M299 267v64h21v-64h-21zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42zM192 309v22h21v-22h-21zM437 331v32h-64v-128h32v42h32v32h-32v22h32zM352 267v64q0 13 -9 22.5t-23 9.5h-53v-128h53q14 0 23 9.5t9 22.5zM245 309v22q0 13 -9.5 22.5t-22.5 9.5
+h-53v-128h32v42h21q13 0 22.5 9.5t9.5 22.5zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE416" unicode="portrait" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM352 165v-16h-192v16q0 22 33 35t63 13t63 -13t33 -35zM256 251q-20 0 -34 14.5t-14 33.5t14 33.5t34 14.5t34 -14.5t14 -33.5
+t-14 -33.5t-34 -14.5z" />
+    <glyph glyph-name="uniE417" unicode="remove_red_eye" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 416q79 0 143 -44t92 -116q-28 -72 -92 -116t-143 -44t-143 44t-92 116
+q28 72 92 116t143 44z" />
+    <glyph glyph-name="uniE418" unicode="rotate_&#x39;&#x30;_degrees_ccw" 
+d="M413 370q56 -56 56 -135.5t-56 -135.5q-55 -56 -136 -56q-49 0 -92 24l32 31q27 -13 60 -13q62 0 106 44q43 43 43 105t-43 106q-44 44 -106 44v-69l-90 90l90 91v-69q81 0 136 -57zM79 237l78 -78l78 78l-78 78zM157 375l138 -138l-138 -138l-139 138z" />
+    <glyph glyph-name="uniE419" unicode="rotate_left" 
+d="M277 425q63 -8 106.5 -56t43.5 -113t-43.5 -113t-106.5 -56v43q46 8 76.5 43.5t30.5 82.5t-30.5 82.5t-76.5 43.5v-83l-97 95l97 97v-66zM151 121l31 31q23 -17 53 -22v-43q-48 6 -84 34zM130 235q5 -29 21 -53l-30 -30q-28 37 -34 83h43zM152 330q-18 -26 -22 -53h-43
+q6 45 35 83z" />
+    <glyph glyph-name="uniE41A" unicode="rotate_right" 
+d="M360 182q17 23 22 53h43q-6 -46 -34 -83zM277 130q30 5 53 22l31 -31q-36 -28 -84 -34v43zM425 277h-43q-5 30 -22 53l31 30q28 -37 34 -83zM332 394l-97 -95v83q-46 -8 -76.5 -43.5t-30.5 -82.5t30.5 -82.5t76.5 -43.5v-43q-63 8 -106.5 56t-43.5 113t43.5 113t106.5 56
+v66z" />
+    <glyph glyph-name="uniE41B" unicode="slideshow" 
+d="M405 107v298h-298v-298h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM213 341l107 -85l-107 -85v170z" />
+    <glyph glyph-name="uniE41C" unicode="straighten" 
+d="M448 171v170h-43v-85h-42v85h-43v-85h-43v85h-42v-85h-43v85h-43v-85h-42v85h-43v-170h384zM448 384q17 0 30 -13t13 -30v-170q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v170q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE41D" unicode="style" 
+d="M125 91v135l74 -178h-31q-17 0 -30 13t-13 30zM168 325q9 0 15 6.5t6 15.5t-6 15t-15 6t-15 -6t-6 -15t6 -15.5t15 -6.5zM470 172q7 -16 0 -32.5t-23 -23.5l-157 -65q-8 -3 -17 -3q-28 0 -39 26l-106 256q-3 9 -3 17q0 27 26 38l158 65q9 3 17 3q27 0 38 -26zM54 93
+q-16 7 -23 23t0 32l52 125v-192z" />
+    <glyph glyph-name="uniE41E" unicode="switch_camera" 
+d="M320 181l75 75l-75 75v-54h-128v54l-75 -75l75 -75v54h128v-54zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68l39 42h128l39 -42h68z" />
+    <glyph glyph-name="uniE41F" unicode="switch_video" 
+d="M277 181l75 75l-75 75v-54h-128v54l-74 -75l74 -75v54h128v-54zM384 309l85 86v-278l-85 86v-75q0 -9 -6 -15t-15 -6h-299q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h299q9 0 15 -6t6 -15v-75z" />
+    <glyph glyph-name="uniE420" unicode="tag_faces" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE421" unicode="texture" 
+d="M198 64l250 250v-61l-189 -189h-61zM448 107q0 -17 -13 -30t-30 -13h-42l85 85v-42zM107 448h42l-85 -85v42q0 17 13 30t30 13zM253 448h61l-250 -250v61zM416 446q25 -7 31 -30l-351 -350q-23 7 -30 30z" />
+    <glyph glyph-name="uniE422" unicode="timelapse" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM346 346q38 -38 38 -90t-37 -90
+t-90 -38t-91 38l90 90v128q52 0 90 -38z" />
+    <glyph glyph-name="uniE423" unicode="timer_&#x31;&#x30;" 
+d="M275 230v54q0 36 -10 53q-7 15 -30 15q-10 0 -18 -3q-23 -14 -23 -66v-53q0 -24 2 -33q1 -5 9 -21t30 -16t30 16q6 12 8 21t2 33zM152 277q0 109 83 109q41 0 60 -24q22 -28 22 -85v-41q0 -110 -82 -110q-83 0 -83 110v41zM436 289q-30 0 -30 -23q0 -3 0.5 -5.5t1.5 -4
+l2 -3t3 -2.5l3 -1.5t4.5 -2t4.5 -1.5q9 -4 19 -5q9 -2 28 -8q8 -3 22 -12q8 -5 13 -16q5 -10 5 -21q0 -37 -44 -52q-12 -4 -30 -4q-57 0 -74 39q-5 12 -5 22h41q0 -30 38 -30q33 0 33 23q0 6 -3 10.5t-6 6t-10 4.5t-24.5 7.5t-23.5 6.5q-8 3 -20 11q-19 13 -19 36
+q0 38 42 52q12 4 29 4q33 0 54 -15t21 -44h-42q0 21 -18 25q-12 3 -15 3zM0 347l101 37h6v-256h-43v205l-64 -22v36z" />
+    <glyph glyph-name="uniE424" unicode="timer_&#x33;" 
+d="M374 289q-31 0 -31 -23q0 -3 0.5 -5t1 -3.5t2 -3t2.5 -2t3 -2t3 -1.5t4 -1.5t4 -1.5q9 -4 18 -5q11 -2 29 -8q8 -3 22 -12q7 -5 13 -16q5 -10 5 -21q0 -37 -44 -52q-12 -4 -31 -4q-56 0 -73 39q-5 12 -5 22h40q0 -15 11 -22.5t28 -7.5q33 0 33 23q0 6 -3 10.5t-6 6
+t-10 4.5q-5 2 -21 6q-28 6 -48 19q-18 12 -18 36q0 38 42 52q12 4 29 4q33 0 54 -15t21 -44h-42q0 20 -19 25q-12 3 -14 3zM215 259q42 -16 42 -58q0 -37 -24 -55q-23 -20 -60 -20t-60.5 19t-23.5 53h43q0 -20 11 -28q13 -10 30 -10q42 0 42 41t-46 41h-26v33h25q32 0 41 23
+q2 5 2 16q0 38 -38 38q-26 0 -36 -20q-3 -6 -3 -15h-42q0 45 48 64q17 5 33 5q38 0 59 -18t21 -54t-38 -55z" />
+    <glyph glyph-name="uniE425" unicode="timer" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM406 354q42 -53 42 -119q0 -79 -56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5q65 0 120 -43l30 31q16 -13 30 -30zM235 213v128h42v-128h-42z
+M320 491v-43h-128v43h128z" />
+    <glyph glyph-name="uniE426" unicode="timer_off" 
+d="M256 85q39 0 75 21l-204 204q-20 -34 -20 -75q0 -62 43.5 -106t105.5 -44zM64 427l379 -379l-27 -27l-54 54q-50 -32 -106 -32q-80 0 -136 56.5t-56 135.5q0 58 32 106l-59 59zM235 311v30h42v-73zM320 491v-43h-128v43h128zM406 415l30 -30l-30 -31q42 -53 42 -119
+q0 -58 -32 -106l-31 31q20 34 20 75q0 62 -43.5 105.5t-105.5 43.5q-40 0 -74 -20l-32 31q48 32 106 32q67 0 120 -42z" />
+    <glyph glyph-name="uniE427" unicode="tonality" 
+d="M421 213q2 6 4 22h-148v-22h144zM389 149q12 17 15 22h-127v-22h112zM277 87q32 4 62 20h-62v-20zM277 299v-22h148q-2 16 -4 22h-144zM277 363v-22h127q-3 5 -15 22h-112zM277 425v-20h62q-30 16 -62 20zM235 87v338q-63 -8 -106.5 -56t-43.5 -113t43.5 -113t106.5 -56z
+M256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE428" unicode="transform" 
+d="M213 341v43h128q17 0 30 -13t13 -30v-128h-43v128h-128zM469 128h-85v-43h43l-64 -64l-64 64h42v43h-170q-17 0 -30 13t-13 30v170h-85v43h85v43h-43l64 64l64 -64h-42v-256h298v-43z" />
+    <glyph glyph-name="uniE429" unicode="tune" 
+d="M320 320v128h43v-43h85v-42h-85v-43h-43zM448 235h-213v42h213v-42zM149 320h43v-128h-43v43h-85v42h85v43zM277 64h-42v128h42v-43h171v-42h-171v-43zM64 405h213v-42h-213v42zM64 149h128v-42h-128v42z" />
+    <glyph glyph-name="uniE42A" unicode="view_comfortable" 
+d="M384 405h85v-85h-85v85zM384 107v85h85v-85h-85zM277 107v85h86v-85h-86zM171 107v85h85v-85h-85zM64 107v85h85v-85h-85zM384 213v86h85v-86h-85zM277 405h86v-85h-86v85zM171 320v85h85v-85h-85zM277 213v86h86v-86h-86zM171 213v86h85v-86h-85zM64 213v86h85v-86h-85z
+M64 320v85h85v-85h-85z" />
+    <glyph glyph-name="uniE42B" unicode="view_compact" 
+d="M64 405h405v-128h-405v128zM213 107v149h256v-149h-256zM64 107v149h128v-149h-128z" />
+    <glyph glyph-name="uniE42C" unicode="wb_auto" 
+d="M220 171h40l-68 192h-43l-68 -192h41l15 42h68zM469 363h39l-44 -192h-37l-32 130l-32 -130h-38l-2 9q-21 -43 -62 -69t-90 -26q-71 0 -121 50.5t-50 120.5t50 120.5t121 50.5q82 0 133 -64h16l26 -135l32 135h34l32 -135zM146 242l25 78l24 -78h-49z" />
+    <glyph glyph-name="uniE42D" unicode="wb_cloudy" 
+d="M413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE42E" unicode="wb_incandescent" 
+d="M368 125l30 29l38 -38l-30 -30zM427 288h64v-43h-64v43zM320 377q29 -17 46.5 -46t17.5 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 35 17.5 64t46.5 46v103h128v-103zM85 288v-43h-64v43h64zM235 33v63h42v-63h-42zM76 116l38 39l30 -30l-38 -39z" />
+    <glyph glyph-name="uniE430" unicode="wb_sunny" 
+d="M76 116l38 39l30 -30l-38 -39zM235 33v63h42v-63h-42zM256 395q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5zM427 288h64v-43h-64v43zM368 125l30 29l38 -38l-30 -30zM436 417l-38 -38l-30 30l38 38zM277 500v-63
+h-42v63h42zM85 288v-43h-64v43h64zM144 409l-30 -30l-38 38l30 30z" />
+    <glyph glyph-name="uniE431" unicode="collections_bookmark" 
+d="M427 256v171h-107v-171l53 32zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-256q-17 0 -30 13t-13 30v256q0 17 13 29.5t30 12.5h256zM85 384v-299h299v-42h-299q-17 0 -29.5 12.5t-12.5 29.5v299h42z" />
+    <glyph glyph-name="uniE432" unicode="photo_size_select_actual" 
+d="M107 149h298l-96 128l-74 -96l-54 64zM448 448q16 0 29.5 -13.5t13.5 -29.5v-298q0 -16 -13.5 -29.5t-29.5 -13.5h-384q-17 0 -30 13t-13 30v298q0 16 13.5 29.5t29.5 13.5h384z" />
+    <glyph glyph-name="uniE433" unicode="photo_size_select_large" 
+d="M64 107h213l-68 91l-53 -69l-39 46zM21 277h299v-213h-256q-17 0 -30 13t-13 30v170zM107 448h42v-43h-42v43zM192 448h43v-43h-43v43zM64 448v-43h-43q0 16 13.5 29.5t29.5 13.5zM363 107h42v-43h-42v43zM363 448h42v-43h-42v43zM21 363h43v-43h-43v43zM448 448
+q16 0 29.5 -13.5t13.5 -29.5h-43v43zM448 363h43v-43h-43v43zM277 448h43v-43h-43v43zM491 107q0 -16 -13.5 -29.5t-29.5 -13.5v43h43zM448 277h43v-42h-43v42zM448 192h43v-43h-43v43z" />
+    <glyph glyph-name="uniE434" unicode="photo_size_select_small" 
+d="M149 448v-43h-42v43h42zM235 448v-43h-43v43h43zM64 277v-42h-43v42h43zM64 448v-43h-43q0 16 13.5 29.5t29.5 13.5zM405 107v-43h-42v43h42zM405 448v-43h-42v43h42zM320 107v-43h-43v43h43zM64 363v-43h-43v43h43zM64 64q-17 0 -30 13t-13 30v85h214v-128h-171z
+M448 448q16 0 29.5 -13.5t13.5 -29.5h-43v43zM491 363v-43h-43v43h43zM320 448v-43h-43v43h43zM491 107q0 -16 -13.5 -29.5t-29.5 -13.5v43h43zM491 277v-42h-43v42h43zM491 192v-43h-43v43h43z" />
+    <glyph glyph-name="uniE435" unicode="vignette" 
+d="M256 128q71 0 121 37.5t50 90.5t-50 90.5t-121 37.5t-121 -37.5t-50 -90.5t50 -90.5t121 -37.5zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE436" unicode="wb_iridescent" 
+d="M106 86l-30 31l38 38l30 -30zM76 417l30 30l38 -38l-30 -30zM436 116l-30 -30l-38 39l30 30zM277 33h-42v63h42v-63zM406 447l30 -30l-38 -38l-30 30zM235 500h42v-63h-42v63zM107 203v128h298v-128h-298z" />
+    <glyph glyph-name="uniE437" unicode="crop_rotate" 
+d="M171 171h256v-43h-43v-43h-43v43h-170q-18 0 -30.5 13t-12.5 30v170h-43v43h43v43h43v-256zM341 213v128h-128v43h128q17 0 30 -12.5t13 -30.5v-128h-43zM257 512q100 0 173.5 -68t81.5 -167h-32q-6 60 -40 108t-87 73l-29 -28l-81 81q3 0 7 0.5t7 0.5zM159 54l29 28
+l81 -81q-3 0 -7 -0.5t-7 -0.5q-100 0 -173.5 68t-81.5 167h32q6 -60 40 -108t87 -73z" />
+    <glyph glyph-name="uniE438" unicode="linked_camera" 
+d="M341 384q18 0 30.5 -12.5t12.5 -30.5h28q0 29 -21 50t-50 21v-28v0zM256 107q44 0 75.5 31t31.5 75t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75t75.5 -31zM363 320h106v-235q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v256
+q0 17 12.5 30t29.5 13h68l39 43h128v-64q18 0 30.5 -12.5t12.5 -30.5zM341 441v28q53 0 90.5 -37.5t37.5 -90.5h-28q0 41 -29.5 70.5t-70.5 29.5zM188 213q0 29 20 49t48 20t48 -20t20 -49t-20 -48.5t-48 -19.5t-48 19.5t-20 48.5z" />
+    <glyph glyph-name="uniE439" unicode="add_a_photo" 
+d="M209 213q0 29 20 49t48 20q29 0 49 -20t20 -49q0 -28 -20 -48t-49 -20t-48.5 19.5t-19.5 48.5zM277 107q44 0 75.5 31t31.5 75t-31.5 75.5t-75.5 31.5t-75 -31.5t-31 -75.5t31 -75t75 -31zM128 299v64h64v64h149l39 -43h68q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5
+t-30 -12.5h-341q-17 0 -30 12.5t-13 29.5v214h64zM64 427v64h43v-64h64v-43h-64v-64h-43v64h-64v43h64z" />
+    <glyph glyph-name="uniE43A" unicode="movie_filter" 
+d="M361 257l44 20l-44 20l-20 44l-20 -44l-44 -20l44 -20l20 -44zM240 187l59 26l-59 27l-27 59l-26 -59l-59 -27l59 -26l26 -59zM384 427h85v-299q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h22l42 -64h64l-42 64h42l43 -64h64
+l-43 64h43l43 -64h64z" />
+    <glyph glyph-name="uniE43B" unicode="photo_filter" 
+d="M283 283l58 -27l-58 -27l-27 -58l-27 58l-58 27l58 27l27 58zM363 299l-20 44l-44 20l44 20l20 44l20 -44l44 -20l-44 -20zM406 299h42v-192q0 -17 -12.5 -30t-29.5 -13h-299q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h192v-43h-192v-298h299v192z" />
+    <glyph glyph-name="uniE43C" unicode="burst_mode" 
+d="M235 149h213l-68 90l-54 -68l-38 46zM469 405q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-256q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h256zM107 405h42v-298h-42v298zM21 405h43v-298h-43v298z" />
+    <glyph glyph-name="uniE52D" unicode="beenhere" 
+d="M213 171l192 192l-30 30l-162 -162l-76 76l-30 -30zM405 491q17 0 30 -13t13 -30v-276q0 -21 -19 -35l-173 -116l-173 116q-19 14 -19 35v276q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE52E" unicode="directions" 
+d="M299 203l74 74l-74 75v-53h-107q-9 0 -15 -6.5t-6 -15.5v-85h42v64h86v-53zM463 271q14 -16 0 -30l-192 -192q-6 -6 -15 -6t-15 6l-192 192q-6 6 -6 15t6 15l192 192q6 6 15 6t15 -6z" />
+    <glyph glyph-name="uniE52F" unicode="directions_bike" 
+d="M405 75q31 0 53 21.5t22 52.5t-22 53t-53 22t-52.5 -22t-21.5 -53t21.5 -52.5t52.5 -21.5zM405 256q45 0 76 -31t31 -76t-31 -75.5t-76 -30.5t-75.5 30.5t-30.5 75.5t30.5 76t75.5 31zM230 288l47 -49v-132h-42v106l-69 60q-12 8 -12 30q0 18 12 30l60 60q8 12 30 12
+q19 0 34 -12l41 -41q32 -32 76 -32v-43q-63 0 -108 45l-17 17zM107 75q31 0 52.5 21.5t21.5 52.5t-21.5 53t-52.5 22t-53 -22t-22 -53t22 -52.5t53 -21.5zM107 256q45 0 75.5 -31t30.5 -76t-30.5 -75.5t-75.5 -30.5t-76 30.5t-31 75.5t31 76t76 31zM331 395q-17 0 -30 12.5
+t-13 29.5t13 30t30 13t29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5z" />
+    <glyph glyph-name="uniE530" unicode="directions_bus" 
+d="M384 277v107h-256v-107h256zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM85 171v213q0 51 44 68t127 17t127 -17t44 -68v-213q0 -28 -22 -48v-38
+q0 -9 -6 -15t-15 -6h-21q-9 0 -15.5 6t-6.5 15v22h-170v-22q0 -9 -6.5 -15t-15.5 -6h-21q-9 0 -15 6t-6 15v38q-22 20 -22 48z" />
+    <glyph glyph-name="uniE531" unicode="directions_car" 
+d="M107 277h298l-32 96h-234zM373 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM139 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM404 384l44 -128v-171q0 -9 -6 -15
+t-15 -6h-22q-9 0 -15 6t-6 15v22h-256v-22q0 -9 -6 -15t-15 -6h-22q-9 0 -15 6t-6 15v171l44 128q6 21 31 21h234q25 0 31 -21z" />
+    <glyph glyph-name="uniE532" unicode="directions_ferry" 
+d="M128 384v-85l128 42l128 -42v85h-256zM84 107l-40 142q-7 20 14 27l27 9v99q0 17 13 30t30 13h64v64h128v-64h64q17 0 30 -13t13 -30v-99l27 -9q21 -7 14 -27l-40 -142h-1q-49 0 -86 42q-37 -42 -85 -42t-85 42q-37 -42 -86 -42h-1zM427 64h42v-43h-42q-45 0 -86 21
+q-85 -44 -170 0q-41 -21 -86 -21h-42v43h42q46 0 86 28q39 -27 85 -27t85 27q40 -28 86 -28z" />
+    <glyph glyph-name="uniE533" unicode="directions_subway" 
+d="M384 277v107h-107v-107h107zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM235 277v107h-107v-107h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 51 44 68t127 17z" />
+    <glyph glyph-name="uniE534" unicode="directions_railway" 
+d="M384 299v106h-256v-106h256zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM85 181v224q0 51 44 68.5t127 17.5t127 -17.5t44 -68.5v-224q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5z" />
+    <glyph glyph-name="uniE535" unicode="directions_transit" 
+d="M384 277v107h-107v-107h107zM352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM235 277v107h-107v-107h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-256v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 51 44 68t127 17z" />
+    <glyph glyph-name="uniE536" unicode="directions_walk" 
+d="M209 322l-60 -301h45l39 171l44 -43v-128h43v160l-45 43l13 64q46 -53 117 -53v42q-62 0 -91 52l-22 34q-15 21 -36 21q-3 0 -8.5 -1t-8.5 -1l-111 -47v-100h43v72l38 15v0zM288 395q-17 0 -30 12.5t-13 29.5t13 30t30 13t30 -13t13 -30t-13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE539" unicode="flight" 
+d="M217 320zM448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE53A" unicode="hotel" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-192h-43v64h-384v-64h-43v320h43v-192h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE53B" unicode="layers" 
+d="M256 171q-9 7 -81.5 63.5t-110.5 85.5l192 149l192 -149q-38 -29 -110 -85t-82 -64zM256 116l157 123l35 -27l-192 -149l-192 149l35 27z" />
+    <glyph glyph-name="uniE53C" unicode="layers_clear" 
+d="M70 491l399 -400l-27 -27l-80 81l-106 -82l-192 149l35 27l157 -123l75 59l-30 30l-45 -34q-9 7 -81.5 63.5t-110.5 85.5l69 54l-90 90zM448 320q-27 -21 -86 -67l-168 168l62 48zM423 192l-31 31l25 19l31 -30z" />
+    <glyph glyph-name="uniE53D" unicode="local_airport" 
+d="M448 171l-171 53v-117l43 -32v-32l-75 21l-74 -21v32l42 32v117l-170 -53v42l170 107v117q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-117l171 -107v-42z" />
+    <glyph glyph-name="uniE53E" unicode="local_atm" 
+d="M427 128v256h-342v-256h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342zM235 149v22h-43v42h85v22h-64q-9 0 -15 6t-6 15v64q0 9 6 15t15 6h22v22h42v-22h43v-42h-85v-22h64q9 0 15 -6
+t6 -15v-64q0 -9 -6 -15t-15 -6h-22v-22h-42z" />
+    <glyph glyph-name="uniE53F" unicode="local_attraction" 
+d="M332 154l-23 87l70 58l-90 5l-33 84l-33 -84l-91 -5l71 -58l-23 -87l76 49zM427 256q0 -17 12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 17 12.5 30t29.5 13h342q17 0 29.5 -13
+t12.5 -30v-85q-17 0 -29.5 -13t-12.5 -30z" />
+    <glyph glyph-name="uniE540" unicode="local_bar" 
+d="M159 363h194l38 42h-270zM448 405l-171 -192v-106h107v-43h-256v43h107v106l-171 192v43h384v-43z" />
+    <glyph glyph-name="uniE541" unicode="local_cafe" 
+d="M43 64v43h384v-43h-384zM427 341v64h-43v-64h43zM427 448q18 0 30 -12.5t12 -30.5v-64q0 -18 -12 -30t-30 -12h-43v-64q0 -35 -25 -60.5t-60 -25.5h-128q-35 0 -60.5 25.5t-25.5 60.5v213h342z" />
+    <glyph glyph-name="uniE542" unicode="local_car_wash" 
+d="M107 235h298l-32 96h-234zM373 128q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 128q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 341l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22zM149 405q-13 0 -22.5 9.5t-9.5 22.5q0 9 8 23.5t16 24.5l8 10q32 -37 32 -58q0 -13 -9.5 -22.5t-22.5 -9.5zM256 405q-14 0 -23 9.5t-9 22.5q0 9 8 23.5t16 24.5l8 10
+q32 -37 32 -58q0 -13 -9 -22.5t-23 -9.5zM363 405q-13 0 -22.5 9.5t-9.5 22.5q0 9 8 23.5t16 24.5l8 10q32 -37 32 -58q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+    <glyph glyph-name="uniE543" unicode="local_convenience_store" 
+d="M341 256v107h-21v-43h-21v43h-22v-64h43v-43h21zM235 299v64h-64v-22h42v-21h-42v-64h64v21h-43v22h43zM405 363h64v-278h-170v86h-86v-86h-170v278h64v64h298v-64z" />
+    <glyph glyph-name="uniE544" unicode="local_drink" 
+d="M391 341l9 86h-288l9 -86h270zM256 107q26 0 45 19t19 45q0 19 -16 48t-32 48l-16 19q-64 -72 -64 -115q0 -26 19 -45t45 -19zM64 469h384l-43 -389q-2 -16 -14 -26.5t-28 -10.5h-214q-16 0 -28 10.5t-14 26.5z" />
+    <glyph glyph-name="uniE545" unicode="local_florist" 
+d="M256 395q-22 0 -37.5 -16t-15.5 -38t15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 38t-37.5 16zM119 293q0 33 31 48q-31 15 -31 48q0 22 16 38t38 16q15 0 30 -10v4q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-4q15 10 30 10q22 0 38 -16t16 -38q0 -33 -31 -48
+q31 -15 31 -48q0 -22 -16 -37.5t-38 -15.5q-17 0 -30 9v-4q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v4q-13 -9 -30 -9q-22 0 -38 15.5t-16 37.5zM256 43q-80 0 -136 56.5t-56 135.5q80 0 136 -56.5t56 -135.5zM256 43q0 79 56 135.5t136 56.5q0 -79 -56 -135.5
+t-136 -56.5z" />
+    <glyph glyph-name="uniE546" unicode="local_gas_station" 
+d="M384 299q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM256 299v106h-128v-106h128zM422 358q15 -15 15 -38v-203q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v107h-32v-160h-214v341q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-149h21q17 0 30 -13
+t13 -30v-96q0 -9 6 -15t15 -6t15 6t6 15v154q-9 -4 -21 -4q-22 0 -37.5 15.5t-15.5 37.5q0 36 34 50l-45 45l23 22z" />
+    <glyph glyph-name="uniE547" unicode="local_grocery_store" 
+d="M363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5t-30 12.5t-13 29.5t13 30t30 13zM21 469h70l20 -42h316q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22h-159l-19 -35l-1 -3q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53
+l-77 162h-43v42zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE548" unicode="local_hospital" 
+d="M384 213v86h-85v85h-86v-85h-85v-86h85v-85h86v85h85zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE549" unicode="local_hotel" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-192h-43v64h-384v-64h-43v320h43v-192h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE54A" unicode="local_laundry_service" 
+d="M256 85q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM149 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15.5 6t6.5 15t-6.5 15.5t-15.5 6.5zM213 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15.5 6t6.5 15t-6.5 15.5
+t-15.5 6.5zM384 469q18 0 30.5 -12t12.5 -30v-342q0 -18 -12.5 -30t-30.5 -12h-256q-18 0 -30.5 12t-12.5 30v342q0 18 12.5 30t30.5 12h256zM196 153l120 121q25 -25 25 -60.5t-25 -60.5t-60 -25t-60 25z" />
+    <glyph glyph-name="uniE54B" unicode="local_library" 
+d="M256 341q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM256 266q80 75 192 75v-234q-111 0 -192 -76q-81 76 -192 76v234q112 0 192 -75z" />
+    <glyph glyph-name="uniE54C" unicode="local_mall" 
+d="M256 235q44 0 75.5 31t31.5 75h-43q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43q0 -44 31.5 -75t75.5 -31zM256 448q-26 0 -45 -19t-19 -45h128q0 26 -19 45t-45 19zM405 384q17 0 30 -13t13 -30v-256q0 -17 -13 -29.5t-30 -12.5h-298q-17 0 -30 12.5t-13 29.5v256
+q0 17 13 30t30 13h42q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5h42z" />
+    <glyph glyph-name="uniE54D" unicode="local_movies" 
+d="M384 320v43h-43v-43h43zM384 235v42h-43v-42h43zM384 149v43h-43v-43h43zM171 320v43h-43v-43h43zM171 235v42h-43v-42h43zM171 149v43h-43v-43h43zM384 448h43v-384h-43v43h-43v-43h-170v43h-43v-43h-43v384h43v-43h43v43h170v-43h43v43z" />
+    <glyph glyph-name="uniE54E" unicode="local_offer" 
+d="M117 363q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM457 265q12 -12 12 -30t-12 -30l-150 -150q-12 -12 -30 -12t-30 12l-192 192q-12 12 -12 30v150q0 17 12.5 29.5t29.5 12.5h150q18 0 30 -12z" />
+    <glyph glyph-name="uniE54F" unicode="local_parking" 
+d="M282 277q17 0 29.5 13t12.5 30t-12.5 30t-29.5 13h-69v-86h69zM277 448q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-64v-128h-85v384h149z" />
+    <glyph glyph-name="uniE550" unicode="local_pharmacy" 
+d="M341 213v43h-64v64h-42v-64h-64v-43h64v-64h42v64h64zM448 405v-42l-43 -128l43 -128v-43h-384v43l43 128l-43 128v42h271l31 86l50 -19l-24 -67h56z" />
+    <glyph glyph-name="uniE551" unicode="local_phone" 
+d="M141 282q48 -93 141 -141l47 47q10 10 22 5q36 -12 76 -12q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22z" />
+    <glyph glyph-name="uniE552" unicode="local_pizza" 
+d="M256 192q17 0 30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5t13 -30t30 -13zM149 363q0 -17 13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5zM256 469q115 0 192 -85l-192 -341l-192 341q77 85 192 85z" />
+    <glyph glyph-name="uniE553" unicode="local_play" 
+d="M332 154l-23 87l70 58l-90 5l-33 84l-33 -84l-91 -5l71 -58l-23 -87l76 49zM427 256q0 -17 12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 17 12.5 30t29.5 13h342q17 0 29.5 -13
+t12.5 -30v-85q-17 0 -29.5 -13t-12.5 -30z" />
+    <glyph glyph-name="uniE554" unicode="local_post_office" 
+d="M427 341v43l-171 -107l-171 107v-43l171 -106zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342z" />
+    <glyph glyph-name="uniE555" unicode="local_print_shop" 
+d="M384 448v-85h-256v85h256zM405 256q9 0 15.5 6t6.5 15t-6.5 15.5t-15.5 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM341 107v106h-170v-106h170zM405 341q26 0 45 -19t19 -45v-128h-85v-85h-256v85h-85v128q0 26 19 45t45 19h298z" />
+    <glyph glyph-name="uniE556" unicode="local_restaurant" 
+d="M317 266l-31 -31l147 -147l-30 -30l-147 147l-147 -147l-30 30l208 208q-12 24 -3.5 56t33.5 57q31 31 69 35.5t61 -18.5t18.5 -61.5t-35.5 -69.5q-25 -25 -57 -33t-56 4zM173 227l-90 90q-25 25 -25 60t25 60l150 -149z" />
+    <glyph glyph-name="uniE557" unicode="local_see" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM192 469h128l39 -42h68q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68zM188 256
+q0 28 20 48t48 20t48 -20t20 -48t-20 -48t-48 -20t-48 20t-20 48z" />
+    <glyph glyph-name="uniE558" unicode="local_shipping" 
+d="M384 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM416 309h-53v-53h95zM128 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM427 341l64 -85v-107h-43q0 -26 -19 -45t-45 -19t-45 19t-19 45h-128
+q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43v235q0 17 13 30t30 13h299v-86h64z" />
+    <glyph glyph-name="uniE559" unicode="local_taxi" 
+d="M107 277h298l-32 96h-234zM373 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM139 171q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM404 384l44 -128v-171q0 -9 -6 -15
+t-15 -6h-22q-9 0 -15 6t-6 15v22h-256v-22q0 -9 -6 -15t-15 -6h-22q-9 0 -15 6t-6 15v171l44 128q6 21 31 21h53v43h128v-43h53q25 0 31 -21z" />
+    <glyph glyph-name="uniE55A" unicode="location_history" 
+d="M384 171v19q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-19h256zM256 399q-24 0 -41 -17t-17 -41t17 -40.5t41 -16.5t41 16.5t17 40.5t-17 41t-41 17zM405 469q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-85l-64 -64l-64 64h-85q-18 0 -30.5 13t-12.5 30v299
+q0 17 12.5 29.5t30.5 12.5h298z" />
+    <glyph glyph-name="uniE55B" unicode="map" 
+d="M320 107v253l-128 45v-253zM437 448q11 0 11 -11v-322q0 -8 -8 -10l-120 -41l-128 45l-114 -44l-3 -1q-11 0 -11 11v322q0 8 8 10l120 41l128 -45l114 44z" />
+    <glyph glyph-name="uniE55C" unicode="my_location" 
+d="M256 107q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -105.5t105.5 -43.5zM447 277h44v-42h-44q-7 -67 -55 -115t-115 -55v-44h-42v44q-67 7 -115 55t-55 115h-44v42h44q7 67 55 115t115 55v44h42v-44q67 -7 115 -55t55 -115z
+M256 341q35 0 60 -25t25 -60t-25 -60t-60 -25t-60 25t-25 60t25 60t60 25z" />
+    <glyph glyph-name="uniE55D" unicode="navigation" 
+d="M256 469l160 -390l-15 -15l-145 64l-145 -64l-15 15z" />
+    <glyph glyph-name="uniE55E" unicode="pin_drop" 
+d="M107 85h298v-42h-298v42zM213 341q0 -17 13 -29.5t30 -12.5q18 0 30.5 12.5t12.5 29.5t-13 30t-30 13t-30 -13t-13 -30zM384 341q0 -43 -32 -101.5t-64 -95.5l-32 -37q-14 15 -35.5 41t-57 88t-35.5 105q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5z" />
+    <glyph glyph-name="uniE55F" unicode="place" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE560" unicode="rate_review" 
+d="M384 213v43h-117l-43 -43h160zM128 213h53l147 147q8 8 0 15l-38 38q-8 7 -15 0l-147 -147v-53zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE561" unicode="restaurant_menu" 
+d="M317 266l-31 -31l147 -147l-30 -30l-147 147l-147 -147l-30 30l208 208q-12 24 -3.5 56t33.5 57q31 31 69 35.5t61 -18.5t18.5 -61.5t-35.5 -69.5q-25 -25 -57 -33t-56 4zM173 227l-90 90q-25 25 -25 60t25 60l150 -149z" />
+    <glyph glyph-name="uniE562" unicode="satellite" 
+d="M107 128h298l-96 128l-74 -96l-54 64zM107 256q62 0 105.5 44t43.5 106h-43q0 -44 -31 -75.5t-75 -31.5v-43zM107 406v-65q26 0 45 19.5t19 45.5h-64zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE563" unicode="store_mall_directory" 
+d="M256 128v85h-128v-85h128zM448 213h-21v-128h-43v128h-85v-128h-214v128h-21v43l21 107h342l21 -107v-43zM427 427v-43h-342v43h342z" />
+    <glyph glyph-name="uniE564" unicode="terrain" 
+d="M299 384l192 -256h-470l128 171l96 -128l34 25l-60 81z" />
+    <glyph glyph-name="uniE565" unicode="traffic" 
+d="M256 320q18 0 30.5 13t12.5 30q0 18 -12.5 30t-30.5 12t-30.5 -12t-12.5 -30q0 -17 12.5 -30t30.5 -13zM256 213q18 0 30.5 13t12.5 30t-13 30t-30 13q-18 0 -30.5 -13t-12.5 -30t12.5 -30t30.5 -13zM256 107q18 0 30.5 12.5t12.5 29.5t-13 30t-30 13q-18 0 -30.5 -13
+t-12.5 -30t12.5 -29.5t30.5 -12.5zM427 299q0 -30 -18 -52.5t-46 -30.5v-24h64q0 -30 -18 -52t-46 -30v-25q0 -9 -6.5 -15t-15.5 -6h-170q-9 0 -15.5 6t-6.5 15v25q-28 8 -46 30t-18 52h64v24q-28 8 -46 30.5t-18 52.5h64v24q-28 8 -46 30t-18 52h64v22q0 9 6.5 15t15.5 6
+h170q9 0 15.5 -6t6.5 -15v-22h64q0 -30 -18 -52t-46 -30v-24h64z" />
+    <glyph glyph-name="uniE566" unicode="directions_run" 
+d="M211 99l-149 29l8 43l105 -21l34 173l-39 -15v-73h-42v100l111 47q3 0 8.5 1t8.5 1q21 0 36 -21l21 -34q29 -51 92 -51v-43q-71 0 -117 53l-13 -64l45 -42v-160h-43v128l-45 42zM288 395q-17 0 -30 13t-13 30t13 29.5t30 12.5t29.5 -12.5t12.5 -29.5t-12.5 -30t-29.5 -13
+z" />
+    <glyph glyph-name="uniE567" unicode="add_location" 
+d="M341 299v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE568" unicode="edit_location" 
+d="M318 351q6 5 0 11l-20 20q-6 6 -11 0l-15 -15l31 -31zM223 256l71 71l-31 31l-71 -71v-31h31zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5
+t105.5 43.5z" />
+    <glyph glyph-name="uniE569" unicode="near_me" 
+d="M448 448l-161 -384h-21l-56 146l-146 56v21z" />
+    <glyph glyph-name="uniE56A" unicode="person_pin_circle" 
+d="M256 213q55 0 85 46q0 19 -29.5 31.5t-55.5 12.5t-55.5 -12.5t-29.5 -31.5q30 -46 85 -46zM256 427q-17 0 -30 -13t-13 -30q0 -18 13 -30.5t30 -12.5t30 12.5t13 30.5q0 17 -13 30t-30 13zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5
+t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE56B" unicode="zoom_out_map" 
+d="M448 192v-128h-128l49 49l-62 61l31 31l61 -62zM192 64h-128v128l49 -49l61 62l31 -31l-62 -61zM64 320v128h128l-49 -49l62 -61l-31 -31l-61 62zM320 448h128v-128l-49 49l-61 -62l-31 31l62 61z" />
+    <glyph glyph-name="uniE56C" unicode="restaurant" 
+d="M341 384q0 30 32.5 57.5t74.5 27.5v-426h-53v170h-54v171zM235 320v149h42v-149q0 -34 -23 -58.5t-57 -26.5v-192h-53v192q-34 2 -57 26.5t-23 58.5v149h43v-149h42v149h43v-149h43z" />
+    <glyph glyph-name="uniE56D" unicode="ev_station" 
+d="M171 128l85 149h-43v107l-85 -160h43v-96zM384 299q9 0 15 6t6 15t-6 15t-15 6t-15 -6t-6 -15t6 -15t15 -6zM422 358q15 -15 15 -38v-203q0 -22 -15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5v107h-32v-160h-214v341q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-149h21
+q17 0 30 -13t13 -30v-96q0 -9 6 -15t15 -6t15 6t6 15v154q-9 -4 -21 -4q-22 0 -37.5 15.5t-15.5 37.5q0 36 34 50l-45 45l23 22z" />
+    <glyph glyph-name="uniE56E" unicode="streetview" 
+d="M245 384q0 -57 41 -98l-209 -209q-13 13 -13 30v298q0 17 13 30t30 13h154q-16 -30 -16 -64zM277 384q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5t-31.5 -75.5t-75.5 -31.5t-75.5 31.5t-31.5 75.5zM268 206q52 39 116 39q33 0 64 -11v-127q0 -17 -13 -30t-30 -13
+h-149v117q0 15 12 25z" />
+    <glyph glyph-name="uniE56F" unicode="subway" 
+d="M384 173v147q0 38 -33 51t-95 13q-59 0 -93.5 -13t-34.5 -51v-147q0 -23 16.5 -39.5t39.5 -16.5l-24 -24v-8h36l32 32h60l32 -32h32v8l-24 24q23 0 39.5 16.5t16.5 39.5zM380 452q43 -16 66 -50t23 -79v-280h-426v280q0 45 23 79t66 50q43 17 124 17t124 -17zM150 320
+h213v-107h-213v107zM160 171q0 9 6 15t15 6t15.5 -6t6.5 -15t-6.5 -15.5t-15.5 -6.5t-15 6.5t-6 15.5zM309 171q0 9 6.5 15t15.5 6t15 -6t6 -15t-6 -15.5t-15 -6.5t-15.5 6.5t-6.5 15.5z" />
+    <glyph glyph-name="uniE570" unicode="train" 
+d="M352 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM277 299h107v85h-107v-85zM235 299v85h-107v-85h107zM160 149q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM256 469q83 0 127 -17t44 -68v-203
+q0 -31 -22 -52.5t-53 -21.5l32 -32v-11h-43l-42 43h-81l-42 -43h-48v11l32 32q-31 0 -53 21.5t-22 52.5v203q0 27 14 45t40.5 26t53 11t63.5 3z" />
+    <glyph glyph-name="uniE571" unicode="tram" 
+d="M363 213v107h-214v-107h214zM256 117q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM405 151q0 -28 -14.5 -47t-40.5 -19h2l32 -32v-10h-43l-42 42h-81l-42 -42h-48v10l34 34q-23 5 -39 23t-16 41v180q0 41 34 56.5t93 17.5l17 32h-102v32h214
+v-32h-70l-16 -32q63 -2 95.5 -17t32.5 -57v-180z" />
+    <glyph glyph-name="uniE572" unicode="transfer_within_a_station" 
+d="M123 322l-59 -301h45l37 171l46 -43v-128h43v161l-44 44l13 64q45 -55 116 -55v42q-29 0 -53.5 14.5t-39.5 38.5l-20 34q-11 20 -36 20q-9 0 -16 -3l-112 -46v-100h42v71l38 16v0zM203 395q-17 0 -30 12.5t-13 29.5t13 30t30 13t29.5 -13t12.5 -30t-12.5 -29.5
+t-29.5 -12.5zM416 91v37l53 -53l-53 -54v38h-117v32h117zM352 181h117v-32h-117v-37l-53 53l53 54v-38z" />
+    <glyph glyph-name="uniE5C3" unicode="apps" 
+d="M341 85v86h86v-86h-86zM341 213v86h86v-86h-86zM213 341v86h86v-86h-86zM341 427h86v-86h-86v86zM213 213v86h86v-86h-86zM85 213v86h86v-86h-86zM85 85v86h86v-86h-86zM213 85v86h86v-86h-86zM85 341v86h86v-86h-86z" />
+    <glyph glyph-name="uniE5C4" unicode="arrow_back" 
+d="M427 277v-42h-260l119 -120l-30 -30l-171 171l171 171l30 -30l-119 -120h260z" />
+    <glyph glyph-name="uniE5C5" unicode="arrow_drop_down" 
+d="M149 299h214l-107 -107z" />
+    <glyph glyph-name="uniE5C6" unicode="arrow_drop_down_circle" 
+d="M256 213l85 86h-170zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE5C7" unicode="arrow_drop_up" 
+d="M149 213l107 107l107 -107h-214z" />
+    <glyph glyph-name="uniE5C8" unicode="arrow_forward" 
+d="M256 427l171 -171l-171 -171l-30 30l119 120h-260v42h260l-119 120z" />
+    <glyph glyph-name="uniE5C9" unicode="cancel" 
+d="M363 179l-77 77l77 77l-30 30l-77 -77l-77 77l-30 -30l77 -77l-77 -77l30 -30l77 77l77 -77zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE5CA" unicode="check" 
+d="M192 167l226 226l30 -30l-256 -256l-119 119l30 30z" />
+    <glyph glyph-name="uniE5CB" unicode="chevron_left" 
+d="M329 354l-98 -98l98 -98l-30 -30l-128 128l128 128z" />
+    <glyph glyph-name="uniE5CC" unicode="chevron_right" 
+d="M213 384l128 -128l-128 -128l-30 30l98 98l-98 98z" />
+    <glyph glyph-name="uniE5CD" unicode="close" 
+d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" />
+    <glyph glyph-name="uniE5CE" unicode="expand_less" 
+d="M256 341l128 -128l-30 -30l-98 98l-98 -98l-30 30z" />
+    <glyph glyph-name="uniE5CF" unicode="expand_more" 
+d="M354 329l30 -30l-128 -128l-128 128l30 30l98 -98z" />
+    <glyph glyph-name="uniE5D0" unicode="fullscreen" 
+d="M299 405h106v-106h-42v64h-64v42zM363 149v64h42v-106h-106v42h64zM107 299v106h106v-42h-64v-64h-42zM149 213v-64h64v-42h-106v106h42z" />
+    <glyph glyph-name="uniE5D1" unicode="fullscreen_exit" 
+d="M341 341h64v-42h-106v106h42v-64zM299 107v106h106v-42h-64v-64h-42zM171 341v64h42v-106h-106v42h64zM107 171v42h106v-106h-42v64h-64z" />
+    <glyph glyph-name="uniE5D2" unicode="menu" 
+d="M64 384h384v-43h-384v43zM64 235v42h384v-42h-384zM64 128v43h384v-43h-384z" />
+    <glyph glyph-name="uniE5D3" unicode="keyboard_control" 
+d="M256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM384 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM128 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE5D4" unicode="more_vert" 
+d="M256 171q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 299q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM256 341q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE5D5" unicode="refresh" 
+d="M377 377l50 50v-150h-150l69 69q-38 38 -90 38q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5q42 0 75 23.5t46 61.5h44q-14 -56 -60 -92t-105 -36q-70 0 -120 50t-50 121t50 121t120 50q71 0 121 -50z" />
+    <glyph glyph-name="uniE5D6" unicode="unfold_less" 
+d="M354 397l-98 -98l-98 98l30 30l68 -68l68 68zM158 115l98 98l98 -98l-30 -30l-68 68l-68 -68z" />
+    <glyph glyph-name="uniE5D7" unicode="unfold_more" 
+d="M256 124l68 68l30 -30l-98 -98l-98 98l30 30zM256 388l-68 -68l-30 30l98 98l98 -98l-30 -30z" />
+    <glyph glyph-name="uniE5D8" unicode="arrow_upward" 
+d="M85 256l171 171l171 -171l-31 -30l-119 119v-260h-42v260l-120 -119z" />
+    <glyph glyph-name="uniE5D9" unicode="subdirectory_arrow_left" 
+d="M235 320l30 -30l-77 -77h196v214h43v-256h-239l77 -77l-30 -30l-128 128z" />
+    <glyph glyph-name="uniE5DA" unicode="subdirectory_arrow_right" 
+d="M405 192l-128 -128l-30 30l77 77h-239v256h43v-214h196l-77 77l30 30z" />
+    <glyph glyph-name="uniE5DB" unicode="arrow_downward" 
+d="M427 256l-171 -171l-171 171l31 30l119 -119v260h42v-260l120 119z" />
+    <glyph glyph-name="uniE5DC" unicode="first_page" 
+d="M128 384h43v-256h-43v256zM393 158l-30 -30l-128 128l128 128l30 -30l-98 -98z" />
+    <glyph glyph-name="uniE5DD" unicode="last_page" 
+d="M341 384h43v-256h-43v256zM119 354l30 30l128 -128l-128 -128l-30 30l98 98z" />
+    <glyph glyph-name="uniE60E" unicode="adb" 
+d="M320 320q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM192 320q9 0 15 6t6 15t-6 15.5t-15 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM344 419q61 -45 61 -120v-22h-298v22q0 75 61 120l-45 45l18 17l49 -49q32 16 66 16t66 -16l49 49l18 -17zM107 171v85
+h298v-85q0 -62 -43.5 -106t-105.5 -44t-105.5 44t-43.5 106z" />
+    <glyph glyph-name="uniE60F" unicode="bluetooth_audio" 
+d="M275 164l-40 41v-81zM235 388v-81l40 41zM335 348l-92 -92l92 -92l-122 -121h-21v162l-98 -98l-30 30l119 119l-119 119l30 30l98 -98v162h21zM417 369q31 -50 31 -111t-33 -113l-25 25q21 42 21 86t-21 86zM304 256l49 49q10 -25 10 -49q0 -25 -10 -50z" />
+    <glyph glyph-name="uniE610" unicode="disc_full" 
+d="M213 213q17 0 30 13t13 30t-13 30t-30 13t-29.5 -13t-12.5 -30t12.5 -30t29.5 -13zM213 427q71 0 121 -50.5t50 -120.5t-50 -120.5t-121 -50.5q-70 0 -120 50t-50 121t50 121t120 50zM427 363h42v-107h-42v107zM427 171v42h42v-42h-42z" />
+    <glyph glyph-name="uniE611" unicode="do_not_disturb_alt" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 57 -37 105l-239 -239q48 -37 105 -37zM85 256q0 -57 37 -105l239 239q-48 37 -105 37q-70 0 -120.5 -50.5t-50.5 -120.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE612" unicode="do_not_disturb" 
+d="M391 151q36 45 36 105q0 70 -50.5 120.5t-120.5 50.5q-60 0 -105 -36zM256 85q60 0 105 36l-240 240q-36 -45 -36 -105q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5
+z" />
+    <glyph glyph-name="uniE613" unicode="drive_eta" 
+d="M107 299h298l-32 96h-234zM373 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 405l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22z" />
+    <glyph glyph-name="uniE614" unicode="event_available" 
+d="M405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM353 276l-127 -127l-68 68l23 23l45 -45l104 104z" />
+    <glyph glyph-name="uniE615" unicode="event_busy" 
+d="M405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM199 149l-23 23l52 52l-52 52l23 23l52 -52l52 52l22 -23l-52 -52l52 -52l-22 -23l-52 52z
+" />
+    <glyph glyph-name="uniE616" unicode="event_note" 
+d="M299 213v-42h-150v42h150zM405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21zM363 299v-43h-214v43h214z" />
+    <glyph glyph-name="uniE617" unicode="folder_special" 
+d="M383 149l-17 71l55 48l-72 6l-29 67l-29 -67l-72 -6l55 -48l-17 -71l63 37zM427 384q17 0 29.5 -13t12.5 -30v-213q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h128l43 -43h171z" />
+    <glyph glyph-name="uniE618" unicode="mms" 
+d="M107 213h298l-96 128l-74 -96l-54 64zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE619" unicode="more" 
+d="M405 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM299 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM192 224q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM469 448q17 0 30 -13t13 -30v-298
+q0 -17 -13 -30t-30 -13h-318q-22 0 -36 19l-115 173l115 173q14 19 34 19h320z" />
+    <glyph glyph-name="uniE61A" unicode="network_locked" 
+d="M448 171v32q0 13 -9 22.5t-23 9.5t-23 -9.5t-9 -22.5v-32h64zM469 171q9 0 15.5 -6.5t6.5 -15.5v-85q0 -9 -6.5 -15t-15.5 -6h-106q-9 0 -15.5 6t-6.5 15v85q0 9 6.5 15.5t15.5 6.5v32q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5v-32zM416 299q-40 0 -68 -28
+t-28 -68v-6q-21 -19 -21 -48v-64h-278l406 406v-193q-2 0 -5.5 0.5t-5.5 0.5z" />
+    <glyph glyph-name="uniE61B" unicode="phone_bluetooth_speaker" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM384 358v-40l20 20zM384 450v-40l20 20zM314 309l-15 15
+l59 60l-59 60l15 15l49 -49v81h10l61 -61l-46 -46l46 -46l-61 -61h-10v81z" />
+    <glyph glyph-name="uniE61C" unicode="phone_forwarded" 
+d="M427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM384 277v64h-85v86h85v64l107 -107z" />
+    <glyph glyph-name="uniE61D" unicode="phone_in_talk" 
+d="M320 256q0 26 -19 45t-45 19v43q44 0 75.5 -31.5t31.5 -75.5h-43zM405 256q0 62 -43.5 105.5t-105.5 43.5v43q80 0 136 -56t56 -136h-43zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15
+q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE61E" unicode="phone_locked" 
+d="M410 427v10q0 15 -11 26t-26 11t-25.5 -11t-10.5 -26v-10h73zM427 427q9 0 15 -6.5t6 -15.5v-85q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v85q0 9 6 15.5t15 6.5v10q0 22 15.5 38t37.5 16t38 -16t16 -38v-10zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6
+q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE61F" unicode="phone_missed" 
+d="M506 156q6 -6 6 -15t-6 -15l-53 -53q-6 -6 -15 -6t-15 6q-28 26 -57 40q-12 5 -12 19v66q-46 15 -98 15t-98 -15v-66q0 -15 -12 -20q-32 -15 -57 -39q-6 -6 -15 -6t-15 6l-53 53q-6 6 -6 15t6 15q105 100 250 100t250 -100zM139 395v-75h-32v128h128v-32h-75l96 -96
+l128 128l21 -21l-149 -150z" />
+    <glyph glyph-name="uniE620" unicode="phone_paused" 
+d="M405 448h43v-149h-43v149zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q47 -92 141 -141l47 47q9 9 22 5q36 -12 76 -12zM363 448v-149h-43v149h43z" />
+    <glyph glyph-name="uniE623" unicode="sd_card" 
+d="M384 341v86h-43v-86h43zM320 341v86h-43v-86h43zM256 341v86h-43v-86h43zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE624" unicode="sim_card_alert" 
+d="M277 235v106h-42v-106h42zM277 149v43h-42v-43h42zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 256l127 128h171z" />
+    <glyph glyph-name="uniE625" unicode="sms" 
+d="M363 277v43h-43v-43h43zM277 277v43h-42v-43h42zM192 277v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE626" unicode="sms_failed" 
+d="M277 299v85h-42v-85h42zM277 213v43h-42v-43h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE627" unicode="sync" 
+d="M256 128v64l85 -85l-85 -86v64q-70 0 -120.5 50.5t-50.5 120.5q0 50 27 91l31 -31q-15 -27 -15 -60q0 -53 37.5 -90.5t90.5 -37.5zM256 427q70 0 120.5 -50.5t50.5 -120.5q0 -50 -27 -91l-31 31q15 27 15 60q0 53 -37.5 90.5t-90.5 37.5v-64l-85 85l85 86v-64z" />
+    <glyph glyph-name="uniE628" unicode="sync_disabled" 
+d="M427 427l-51 -51q51 -51 51 -120q0 -48 -26 -90l-32 31q15 30 15 59q0 52 -38 90l-47 -47v128h128zM61 397l27 27l335 -336l-27 -27l-50 50q-24 -14 -48 -20v44q8 3 17 8l-172 172q-15 -30 -15 -59q0 -52 38 -90l47 47v-128h-128l51 51q-51 51 -51 120q0 48 26 90z
+M213 377q-6 -2 -16 -8l-31 32q25 15 47 20v-44z" />
+    <glyph glyph-name="uniE629" unicode="sync_problem" 
+d="M235 235v128h42v-128h-42zM448 427l-50 -51q50 -50 50 -120q0 -59 -36 -105t-92 -60v44q38 13 61.5 46t23.5 75q0 53 -37 90l-48 -47v128h128zM235 149v43h42v-43h-42zM64 256q0 59 36 105t92 60v-44q-38 -13 -61.5 -46t-23.5 -75q0 -53 37 -90l48 47v-128h-128l50 51
+q-50 50 -50 120z" />
+    <glyph glyph-name="uniE62A" unicode="system_update" 
+d="M341 235l-85 -86l-85 86h64v106h42v-106h64zM363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE62B" unicode="tap_and_play" 
+d="M363 490q17 0 29.5 -12.5t12.5 -29.5v-363q0 -17 -12.5 -29.5t-29.5 -12.5h-45q-3 43 -20 85h65v277h-214v-128q-25 11 -42 14v157q0 17 12.5 30t29.5 13zM43 256q97 0 165.5 -68.5t68.5 -166.5h-42q0 79 -56.5 135.5t-135.5 56.5v43zM43 85q26 0 45 -19t19 -45h-64v64z
+M43 171q62 0 105.5 -44t43.5 -106h-43q0 44 -31 75.5t-75 31.5v43z" />
+    <glyph glyph-name="uniE62C" unicode="time_to_leave" 
+d="M107 299h298l-32 96h-234zM373 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM139 192q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM404 405l44 -128v-170q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5
+v21h-256v-21q0 -9 -6 -15.5t-15 -6.5h-22q-9 0 -15 6.5t-6 15.5v170l44 128q7 22 31 22h234q24 0 31 -22z" />
+    <glyph glyph-name="uniE62D" unicode="vibration" 
+d="M341 107v298h-170v-298h170zM352 448q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192zM405 149v214h43v-214h-43zM469 320h43v-128h-43v128zM64 149v214h43v-214h-43zM0 192v128h43v-128h-43z" />
+    <glyph glyph-name="uniE62E" unicode="voice_chat" 
+d="M384 213v171l-85 -68v68h-171v-171h171v69zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE62F" unicode="vpn_lock" 
+d="M213 65v42q-17 0 -29.5 12.5t-12.5 29.5v22l-103 102q-4 -16 -4 -38q0 -65 43 -113.5t106 -56.5zM404 256h43q1 -7 1 -21q0 -89 -62.5 -151.5t-150.5 -62.5q-89 0 -151.5 62.5t-62.5 151.5q0 88 62.5 150.5t151.5 62.5q31 0 64 -10v-54q0 -17 -13 -30t-30 -13h-43v-42
+q0 -9 -6 -15.5t-15 -6.5h-43v-42h128q9 0 15.5 -6.5t6.5 -15.5v-64h21q31 0 41 -29q44 47 44 115q0 14 -1 21zM452 427v10q0 15 -10.5 26t-25.5 11t-25.5 -11t-10.5 -26v-10h72zM469 427q9 0 15.5 -6.5t6.5 -15.5v-85q0 -9 -6.5 -15t-15.5 -6h-106q-9 0 -15.5 6t-6.5 15v85
+q0 9 6.5 15.5t15.5 6.5v10q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-10z" />
+    <glyph glyph-name="uniE630" unicode="airline_seat_flat" 
+d="M152 254q-19 -19 -45 -19.5t-45 18.5t-19.5 45t18.5 45t45 19.5t45 -18.5t19.5 -45t-18.5 -45zM43 213h426v-42h-128v-43h-170v43h-128v42zM469 277v-42h-277v128h192q35 0 60 -25.5t25 -60.5z" />
+    <glyph glyph-name="uniE631" unicode="airline_seat_flat_angled" 
+d="M156 294q-24 -11 -49.5 -2.5t-36.5 32.5t-2.5 49.5t32.5 36.5t49.5 2.5t36.5 -32.5t2.5 -49.5t-32.5 -36.5zM32 253l15 40l405 -146l-14 -40l-97 34v-34h-170v96zM475 207l-15 -40l-264 95l45 121l182 -66q34 -12 49 -44t3 -66z" />
+    <glyph glyph-name="uniE632" unicode="airline_seat_individual_suite" 
+d="M405 363q35 0 60.5 -25.5t25.5 -60.5v-128h-470v214h43v-150h171v150h170zM149 235q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE633" unicode="airline_seat_legroom_extra" 
+d="M487 144q7 -12 2 -25t-17 -19l-79 -36l-73 149h-149q-26 0 -45 19t-19 45v171h128v-128h75q27 0 38 -24l72 -149l24 11q12 5 24.5 1t18.5 -15zM85 256q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75 31.5t-31 75.5v192h42v-192z" />
+    <glyph glyph-name="uniE634" unicode="airline_seat_legroom_normal" 
+d="M437 128q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9h-96v149h-149q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -13t12.5 -30v-149h32zM107 256q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192z" />
+    <glyph glyph-name="uniE635" unicode="airline_seat_legroom_reduced" 
+d="M107 256q0 -26 19 -45t45 -19h85v-43h-85q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM426 102q3 -15 -6.5 -26.5t-24.5 -11.5h-96v64l21 85h-128q-26 0 -45 19t-19 45v171h128v-128h107q17 0 29.5 -13t12.5 -30l-42 -149h30q12 0 21.5 -7.5t11.5 -18.5z" />
+    <glyph glyph-name="uniE636" unicode="airline_seat_recline_extra" 
+d="M346 192l123 -96l-32 -32l-81 64h-146q-23 0 -40.5 14.5t-22.5 37.5l-29 126q-3 20 8.5 36t30.5 20h1q22 3 37 -9l35 -27q48 -37 100 -27v-46q-48 -8 -110 26l22 -87h104zM341 107v-43h-150q-40 0 -70 25.5t-36 64.5l-42 209h42l42 -202q4 -23 22 -38.5t42 -15.5h150z
+M114 392q-14 10 -17 27.5t7 31.5t27.5 17.5t31.5 -6.5q14 -11 17.5 -28.5t-6.5 -31.5t-28 -17t-32 7z" />
+    <glyph glyph-name="uniE637" unicode="airline_seat_recline_normal" 
+d="M427 84l-31 -31l-75 75h-108q-26 0 -45 19t-19 45v123q0 19 14.5 33.5t33.5 14.5h1q19 0 35 -16l30 -33q17 -19 45.5 -31t54.5 -12v-47q-62 0 -118 47v-79h74zM128 171q0 -26 19 -45t45 -19h128v-43h-128q-44 0 -75.5 31.5t-31.5 75.5v192h43v-192zM162 397
+q-13 13 -13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13t-30 13z" />
+    <glyph glyph-name="uniE638" unicode="confirmation_number" 
+d="M277 331v42h-42v-42h42zM277 235v42h-42v-42h42zM277 139v42h-42v-42h42zM469 299q-17 0 -29.5 -13t-12.5 -30t12.5 -30t29.5 -13v-85q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v85q18 0 30 12.5t12 30.5q0 17 -12.5 30t-29.5 13v85q0 18 12.5 30.5
+t29.5 12.5h342q17 0 29.5 -12.5t12.5 -30.5v-85z" />
+    <glyph glyph-name="uniE639" unicode="live_tv" 
+d="M192 299l149 -86l-149 -85v171zM448 85v256h-384v-256h384zM448 384q17 0 30 -12.5t13 -30.5v-256q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v256q0 18 13 30.5t30 12.5h162l-70 70l15 15l85 -85l85 85l15 -15l-70 -70h162z" />
+    <glyph glyph-name="uniE63A" unicode="ondemand_video" 
+d="M341 277l-149 -85v171zM448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE63B" unicode="personal_video" 
+d="M448 149v256h-384v-256h384zM448 448q17 0 30 -12.5t13 -30.5l-1 -256q0 -17 -12.5 -29.5t-29.5 -12.5h-107v-43h-170v43h-107q-18 0 -30.5 12.5t-12.5 29.5v256q0 18 12.5 30.5t30.5 12.5h384z" />
+    <glyph glyph-name="uniE63C" unicode="power" 
+d="M342 363q16 0 29 -13.5t13 -29.5v-117l-75 -75v-64h-106v64l-75 75v117q0 16 13 29.5t29 13.5h1v85h42v-85h86v85h42z" />
+    <glyph glyph-name="uniE63D" unicode="wc" 
+d="M352 384q-18 0 -30.5 12.5t-12.5 30.5t12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5zM160 384q-18 0 -30.5 12.5t-12.5 30.5t12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5zM384 43h-64v128h-64l54 162q11 30 41 30h2q30 0 41 -30l54 -162
+h-64v-128zM117 43v160h-32v117q0 17 13 30t30 13h64q17 0 30 -13t13 -30v-117h-32v-160h-86z" />
+    <glyph glyph-name="uniE63E" unicode="wifi" 
+d="M107 235q62 61 149.5 61t148.5 -61l-42 -43q-44 44 -107 44t-107 -44zM192 149q26 26 64 26t64 -26l-64 -64zM21 320q98 97 235.5 97t234.5 -97l-43 -43q-80 79 -192 79t-192 -79z" />
+    <glyph glyph-name="uniE63F" unicode="enhanced_encryption" 
+d="M341 171v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM190 384v-43h132v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h21v43
+q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE640" unicode="network_check" 
+d="M107 235q64 64 156 61l-28 -61q-49 -6 -86 -43zM363 192q-11 11 -26 21l12 62q32 -16 56 -40zM448 277q-39 39 -88 59l11 60q69 -25 120 -76zM21 320q59 59 136.5 82.5t156.5 9.5l-25 -57q-61 8 -120.5 -12.5t-104.5 -65.5zM339 405q11 0 11 -10l-52 -275v-1
+q-3 -14 -15 -24t-27 -10q-18 0 -30.5 12.5t-12.5 30.5q0 11 5 21l111 248q3 8 10 8z" />
+    <glyph glyph-name="uniE641" unicode="no_encryption" 
+d="M190 384v-26l-39 39q5 40 34.5 67t70.5 27q44 0 75.5 -31.5t31.5 -75.5v-43h21q17 0 30 -12.5t13 -29.5v-178l-221 220h116v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM448 47l-26 -26l-24 24q-8 -2 -14 -2h-256q-17 0 -30 12.5t-13 29.5v214q0 25 23 37
+l-44 43l26 26z" />
+    <glyph glyph-name="uniE642" unicode="rv_hookup" 
+d="M363 469l64 -64l-64 -64v43h-171v43h171v42zM384 213v64h-85v-64h85zM235 85q9 0 15 6.5t6 15.5t-6 15t-15 6t-15.5 -6t-6.5 -15t6.5 -15.5t15.5 -6.5zM427 149h42v-42h-170q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43q-17 0 -30 12.5t-13 29.5v64h150v64h-86v-42l-64 64
+l64 64v-43h235q17 0 30 -13t13 -30v-128z" />
+    <glyph glyph-name="uniE643" unicode="do_not_disturb_off" 
+d="M149 235h74l-43 42h-31v-42zM48 464l416 -416l-28 -27l-59 60q-54 -38 -121 -38q-88 0 -150.5 62.5t-62.5 150.5q0 67 38 121l-60 59zM363 277h-74l-154 154q54 38 121 38q88 0 150.5 -62.5t62.5 -150.5q0 -67 -38 -121l-99 100h31v42z" />
+    <glyph glyph-name="uniE644" unicode="do_not_disturb_on" 
+d="M363 235v42h-214v-42h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE645" unicode="priority_high" 
+d="M213 448h86v-256h-86v256zM213 107q0 18 12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE6C4" unicode="pie_chart" 
+d="M278 234h191q-7 -76 -61 -130t-130 -61v191zM278 469q76 -7 130 -61t61 -130h-191v191zM235 469v-426q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69z" />
+    <glyph glyph-name="uniE6C5" unicode="pie_chart_outlined" 
+d="M277 87q58 7 99.5 48.5t48.5 99.5h-148v-148zM85 256q0 -65 43 -113t107 -56v338q-63 -8 -106.5 -56t-43.5 -113zM277 425v-148h148q-7 58 -48.5 99.5t-99.5 48.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE6DD" unicode="bubble_chart" 
+d="M222 324q0 43 29.5 73t72.5 30t73 -30t30 -73t-30 -72.5t-73 -29.5t-72.5 29.5t-29.5 72.5zM273 128q0 18 12.5 30.5t30.5 12.5t30 -12.5t12 -30.5t-12 -30.5t-30 -12.5t-30.5 12.5t-12.5 30.5zM85 205q0 28 20 48t49 20q28 0 48 -19.5t20 -48.5t-20 -48.5t-48 -19.5
+q-29 0 -49 20t-20 48z" />
+    <glyph glyph-name="uniE6DF" unicode="multiline_chart" 
+d="M469 364l-65 -73q37 -59 44 -131h-43q-6 51 -31 97l-86 -97l-85 86l-128 -128l-32 32l160 160l85 -86l61 70q-59 69 -144 69q-73 0 -132 -52l-30 30q72 64 162 64q102 0 173 -79l61 68z" />
+    <glyph glyph-name="uniE6E1" unicode="show_chart" 
+d="M75 118l-32 32l160 160l85 -86l151 170l30 -30l-181 -204l-85 86z" />
+    <glyph glyph-name="uniE7E9" unicode="cake" 
+d="M384 320q26 0 45 -19t19 -45v-33q0 -17 -12.5 -29.5t-29.5 -12.5t-29 12l-46 46l-46 -46q-12 -12 -29.5 -12t-29.5 12l-45 46l-46 -46q-12 -12 -29 -12t-29.5 12.5t-12.5 29.5v33q0 26 19 45t45 19h107v43h42v-43h107zM354 171q22 -22 52 -22q22 0 42 13v-98q0 -9 -6 -15
+t-15 -6h-342q-9 0 -15 6t-6 15v98q19 -13 42 -13q30 0 52 22l23 23l23 -23q21 -21 52 -21t52 21l23 23zM256 384q-17 0 -30 13t-13 30q0 11 7 22l36 63l36 -63q7 -11 7 -22q0 -17 -12.5 -30t-30.5 -13z" />
+    <glyph glyph-name="uniE7EE" unicode="domain" 
+d="M384 192v-43h-43v43h43zM384 277v-42h-43v42h43zM427 107v213h-171v-43h43v-42h-43v-43h43v-43h-43v-42h171zM213 363v42h-42v-42h42zM213 277v43h-42v-43h42zM213 192v43h-42v-43h42zM213 107v42h-42v-42h42zM128 363v42h-43v-42h43zM128 277v43h-43v-43h43zM128 192v43
+h-43v-43h43zM128 107v42h-43v-42h43zM256 363h213v-299h-426v384h213v-85z" />
+    <glyph glyph-name="uniE7EF" unicode="group" 
+d="M341 235q28 0 61 -8t61 -26t28 -41v-53h-128v53q0 44 -42 74q7 1 20 1zM171 235q28 0 61 -8t60.5 -26t27.5 -41v-53h-299v53q0 23 28 41t61 26t61 8zM171 277q-26 0 -45 19t-19 45t19 45t45 19t44.5 -19t18.5 -45t-18.5 -45t-44.5 -19zM341 277q-26 0 -45 19t-19 45
+t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE7F0" unicode="group_add" 
+d="M277 235q40 0 84 -17.5t44 -46.5v-43h-256v43q0 29 44 46.5t84 17.5zM419 231q37 -6 65 -21.5t28 -38.5v-43h-64v43q0 34 -29 60zM277 277q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM384 277q-10 0 -19 3q19 27 19 61t-19 61q9 3 19 3
+q26 0 45 -19t19 -45t-19 -45t-45 -19zM171 299v-43h-64v-64h-43v64h-64v43h64v64h43v-64h64z" />
+    <glyph glyph-name="uniE7F1" unicode="location_city" 
+d="M405 192v43h-42v-43h42zM405 107v42h-42v-42h42zM277 363v42h-42v-42h42zM277 277v43h-42v-43h42zM277 192v43h-42v-43h42zM277 107v42h-42v-42h42zM149 277v43h-42v-43h42zM149 192v43h-42v-43h42zM149 107v42h-42v-42h42zM320 277h128v-213h-384v299h128v42l64 64
+l64 -64v-128z" />
+    <glyph glyph-name="uniE7F2" unicode="mood" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE7F3" unicode="mood_bad" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-218q13 33 42.5 53.5t66.5 20.5zM181 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5zM331 277q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5
+t-22.5 -9.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE7F4" unicode="notifications" 
+d="M384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-18 0 -30.5 12.5t-12.5 29.5h86q0 -17 -13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE7F5" unicode="notifications_none" 
+d="M341 149v128q0 41 -23 68.5t-62 27.5t-62 -27.5t-23 -68.5v-128h170zM384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-17 0 -30 12.5t-13 29.5h86q0 -17 -13 -29.5
+t-30 -12.5z" />
+    <glyph glyph-name="uniE7F6" unicode="notifications_off" 
+d="M384 199l-191 201q3 1 8 3.5l7 3.5h1l6 3q1 0 4 1t5 1v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-78zM256 43q-18 0 -30.5 12t-12.5 30h86q0 -18 -12.5 -30t-30.5 -12zM167 381q24 -25 125.5 -129t155.5 -161l-27 -27l-43 43h-293v21l43 43
+v107q0 41 17 73l-60 59l27 28z" />
+    <glyph glyph-name="uniE7F7" unicode="notifications_active" 
+d="M256 43q-18 0 -30.5 12.5t-12.5 29.5h85q0 -18 -12 -30t-30 -12zM384 277v-106l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87zM426 288q-5 86 -75 137l30 30q83 -64 88 -167h-43zM162 425
+q-71 -50 -76 -137h-43q5 103 88 167z" />
+    <glyph glyph-name="uniE7F8" unicode="notifications_paused" 
+d="M309 303v38h-106v-38h59l-59 -73v-38h106v38h-59zM384 171l43 -43v-21h-342v21l43 43v106q0 50 25.5 87t70.5 48v15q0 13 9 22.5t23 9.5t23 -9.5t9 -22.5v-15q45 -11 70.5 -48t25.5 -87v-106zM256 43q-18 0 -30.5 12.5t-12.5 29.5h86q0 -17 -13 -29.5t-30 -12.5z" />
+    <glyph glyph-name="uniE7F9" unicode="pages" 
+d="M405 448q17 0 30 -13t13 -30v-128h-107l22 86l-86 -22v107h128zM363 149l-22 86h107v-128q0 -17 -13 -30t-30 -13h-128v107zM171 235l-22 -86l86 22v-107h-128q-17 0 -30 13t-13 30v128h107zM64 405q0 17 13 30t30 13h128v-107l-86 22l22 -86h-107v128z" />
+    <glyph glyph-name="uniE7FA" unicode="party_mode" 
+d="M256 149q44 0 75.5 31.5t31.5 75.5q0 7 -2 21h-45q4 -14 4 -21q0 -26 -19 -45t-45 -19h-85q33 -43 85 -43zM256 363q-44 0 -75.5 -31.5t-31.5 -75.5q0 -7 2 -21h45q-4 14 -4 21q0 26 19 45t45 19h85q-33 43 -85 43zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30
+t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h68l39 42h128l39 -42h68z" />
+    <glyph glyph-name="uniE7FB" unicode="people" 
+d="M341 235q28 0 61 -8t61 -26t28 -41v-53h-128v53q0 44 -42 74q7 1 20 1zM171 235q28 0 61 -8t60.5 -26t27.5 -41v-53h-299v53q0 23 28 41t61 26t61 8zM171 277q-26 0 -45 19t-19 45t19 45t45 19t44.5 -19t18.5 -45t-18.5 -45t-44.5 -19zM341 277q-26 0 -45 19t-19 45
+t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19z" />
+    <glyph glyph-name="uniE7FC" unicode="people_outline" 
+d="M352 373q-17 0 -30 -12.5t-13 -29.5t13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5zM352 256q-31 0 -53 22t-22 53t22 52.5t53 21.5t53 -21.5t22 -52.5t-22 -53t-53 -22zM160 373q-17 0 -30 -12.5t-13 -29.5t13 -30t30 -13t30 13t13 30t-13 29.5t-30 12.5zM160 256
+q-31 0 -53 22t-22 53t22 52.5t53 21.5t53 -21.5t22 -52.5t-22 -53t-53 -22zM459 139v26q0 10 -35.5 24t-71.5 14q-26 0 -64 -12q11 -13 11 -26v-26h160zM267 139v26q0 10 -35.5 24t-71.5 14t-71.5 -14t-35.5 -24v-26h214zM352 235q43 0 91 -19.5t48 -50.5v-58h-470v58
+q0 31 48 50.5t91 19.5q47 0 96 -22q49 22 96 22z" />
+    <glyph glyph-name="uniE7FD" unicode="person" 
+d="M256 213q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM256 256q-35 0 -60 25t-25 60t25 60.5t60 25.5t60 -25.5t25 -60.5t-25 -60t-60 -25z" />
+    <glyph glyph-name="uniE7FE" unicode="person_add" 
+d="M320 213q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM128 299h64v-43h-64v-64h-43v64h-64v43h64v64h43v-64zM320 256q-35 0 -60 25t-25 60t25 60.5t60 25.5t60 -25.5t25 -60.5t-25 -60t-60 -25z" />
+    <glyph glyph-name="uniE7FF" unicode="person_outline" 
+d="M256 235q32 0 70 -9t69.5 -30t31.5 -47v-64h-342v64q0 26 31.5 47t69.5 30t70 9zM256 427q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25t-60 25t-25 60t25 60.5t60 25.5zM256 194q-44 0 -87 -16.5t-43 -28.5v-23h260v23q0 12 -43 28.5t-87 16.5zM256 386q-19 0 -32 -13
+t-13 -32t13 -31.5t32 -12.5t32 12.5t13 31.5t-13 32t-32 13z" />
+    <glyph glyph-name="uniE800" unicode="plus_one" 
+d="M309 382l96 23v-277h-42v226l-54 -11v39zM213 341v-85h86v-43h-86v-85h-42v85h-86v43h86v85h42z" />
+    <glyph glyph-name="uniE801" unicode="poll" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE80B" unicode="public" 
+d="M382 141q45 48 45 115q0 53 -29.5 96t-77.5 62v-9q0 -17 -13 -29.5t-30 -12.5h-42v-43q0 -9 -6.5 -15t-15.5 -6h-42v-43h128q9 0 15 -6t6 -15v-64h21q30 0 41 -30zM235 87v41q-17 0 -30 13t-13 30v21l-102 102q-5 -20 -5 -38q0 -65 43.5 -113t106.5 -56zM256 469
+q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE80C" unicode="school" 
+d="M256 448l235 -128v-171h-43v148l-192 -105l-235 128zM107 231l149 -82l149 82v-86l-149 -81l-149 81v86z" />
+    <glyph glyph-name="uniE80D" unicode="share" 
+d="M384 169q26 0 44 -18.5t18 -43.5q0 -26 -18.5 -44.5t-43.5 -18.5t-43.5 18.5t-18.5 44.5q0 10 1 14l-151 88q-19 -17 -44 -17q-26 0 -45 19t-19 45t19 45t45 19q25 0 44 -17l150 87q-2 10 -2 15q0 26 19 45t45 19t45 -19t19 -45t-19 -45t-45 -19q-24 0 -44 18l-150 -88
+q2 -10 2 -15t-2 -15l152 -88q18 16 42 16z" />
+    <glyph glyph-name="uniE80E" unicode="whatshot" 
+d="M250 107q43 0 72.5 29.5t29.5 72.5q0 44 -12 86q-31 -41 -99 -55q-60 -13 -60 -66q0 -28 20 -47.5t49 -19.5zM288 498q64 -52 101.5 -126.5t37.5 -158.5q0 -70 -50 -120t-121 -50t-121 50t-50 120q0 108 69 190v-8q0 -33 22 -56t55 -23q32 0 52.5 22.5t20.5 56.5
+q0 20 -4 46t-8 41z" />
+    <glyph glyph-name="uniE811" unicode="sentiment_dissatisfied" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-35q-25 42 -74 42t-74 -42h-35q13 33 42.5 53.5t66.5 20.5zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5
+t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5z" />
+    <glyph glyph-name="uniE812" unicode="sentiment_neutral" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5
+t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM192 213h128v-32h-128v32z" />
+    <glyph glyph-name="uniE813" unicode="sentiment_satisfied" 
+d="M256 171q49 0 74 42h35q-13 -33 -42.5 -53.5t-66.5 -20.5t-66.5 20.5t-42.5 53.5h35q25 -42 74 -42zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5
+t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM149 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5zM299 309q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5
+t-9.5 22.5z" />
+    <glyph glyph-name="uniE814" unicode="sentiment_very_dissatisfied" 
+d="M256 213q37 0 66.5 -20.5t42.5 -53.5h-218q13 33 42.5 53.5t66.5 20.5zM167 256l-23 23l23 22l-23 23l23 22l22 -22l23 22l23 -22l-23 -23l23 -22l-23 -23l-23 23zM345 346l23 -22l-23 -23l23 -22l-23 -23l-22 23l-23 -23l-23 23l23 22l-23 23l23 22l23 -22zM256 85
+q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE815" unicode="sentiment_very_satisfied" 
+d="M256 139q-37 0 -66.5 20.5t-42.5 53.5h218q-13 -33 -42.5 -53.5t-66.5 -20.5zM189 300l-22 -23l-23 23l45 45l46 -45l-23 -23zM277 300l46 45l45 -45l-23 -23l-22 23l-23 -23zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5
+t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE834" unicode="check_box" 
+d="M213 149l192 192l-30 31l-162 -162l-76 76l-30 -30zM405 448q18 0 30.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-30.5 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE835" unicode="check_box_outline_blank" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298zM405 405h-298v-298h298v298z" />
+    <glyph glyph-name="uniE836" unicode="radio_button_unchecked" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE837" unicode="radio_button_checked" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM256 363q44 0 75.5 -31.5
+t31.5 -75.5t-31.5 -75.5t-75.5 -31.5t-75.5 31.5t-31.5 75.5t31.5 75.5t75.5 31.5z" />
+    <glyph glyph-name="uniE838" unicode="star" 
+d="M256 144l-132 -80l35 150l-116 101l153 13l60 141l60 -141l153 -13l-116 -101l35 -150z" />
+    <glyph glyph-name="uniE839" unicode="star_half" 
+d="M256 183l80 -48l-21 91l71 62l-94 8l-36 86v-199zM469 315l-116 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141z" />
+    <glyph glyph-name="uniE83A" unicode="star_outline" 
+d="M256 183l80 -48l-21 91l71 62l-94 8l-36 86l-36 -86l-94 -8l71 -62l-21 -91zM469 315l-116 -101l35 -150l-132 80l-132 -80l35 150l-116 101l153 13l60 141l60 -141z" />
+    <glyph glyph-name="uniE84D" unicode="&#x33;d_rotation" 
+d="M256 512q100 0 173.5 -67.5t81.5 -166.5h-32q-5 59 -39.5 107t-87.5 73l-29 -28l-81 81zM353 260q0 57 -47 57h-20v-123h19q36 0 46 34q2 7 2 24v8zM306 341q53 0 72 -47q5 -12 5 -34v-8q0 -38 -21 -59q-22 -22 -57 -22h-49v170h50zM207 258q28 -11 28 -39q0 -10 -5 -20
+q-6 -12 -11 -16q-15 -12 -40 -12q-24 0 -39 12t-15 35h27q0 -11 7.5 -18t19.5 -7q28 0 28 27t-31 27h-16v22h16q29 0 29 25t-26 25q-25 0 -25 -23h-28q0 17 15 32q16 13 38 13q35 0 49 -27q4 -8 4 -20q0 -23 -25 -36zM160 54l29 28l81 -81l-14 -1q-100 0 -173.5 68
+t-81.5 167h32q6 -60 40 -108t87 -73z" />
+    <glyph glyph-name="uniE84E" unicode="accessibility" 
+d="M448 320h-128v-277h-43v128h-42v-128h-43v277h-128v43h384v-43zM256 469q17 0 30 -12.5t13 -29.5t-13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5z" />
+    <glyph glyph-name="uniE84F" unicode="account_balance" 
+d="M245 491l203 -107v-43h-405v43zM341 299h64v-150h-64v150zM43 43v64h405v-64h-405zM213 299h64v-150h-64v150zM85 299h64v-150h-64v150z" />
+    <glyph glyph-name="uniE850" unicode="account_balance_wallet" 
+d="M341 224q13 0 22.5 9t9.5 23t-9.5 23t-22.5 9t-22.5 -9t-9.5 -23t9.5 -23t22.5 -9zM256 171v170h213v-170h-213zM448 128v-21q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298q17 0 30 -13t13 -30v-21h-192q-18 0 -30.5 -13t-12.5 -30
+v-170q0 -17 12.5 -30t30.5 -13h192z" />
+    <glyph glyph-name="uniE851" unicode="account_box" 
+d="M128 149v-21h256v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5zM320 320q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM64 405q0 17 12.5 30t30.5 13h298q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298z" />
+    <glyph glyph-name="uniE853" unicode="account_circle" 
+d="M256 102q81 0 128 69q-1 28 -45 47t-83 19t-83 -18.5t-45 -47.5q47 -69 128 -69zM256 405q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE854" unicode="add_shopping_cart" 
+d="M153 197q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53l-77 162h-43v42h70q20 -42 40 -85q5 -9 23 -47.5t28 -59.5h150q75 136 82 150l37 -21l-82 -149q-12 -22 -37 -22h-159l-19 -35zM363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5
+t-30 12.5t-13 29.5t13 30t30 13zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13zM235 320v64h-64v43h64v64h42v-64h64v-43h-64v-64h-42z" />
+    <glyph glyph-name="uniE855" unicode="alarm" 
+d="M256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM267 341v-112l85 -50l-16 -26l-101 60v128h32zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE856" unicode="alarm_add" 
+d="M277 320v-64h64v-43h-64v-64h-42v64h-64v43h64v64h42zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5z
+M469 390l-27 -33l-98 83l27 32zM168 440l-98 -82l-27 32l98 82z" />
+    <glyph glyph-name="uniE857" unicode="alarm_off" 
+d="M171 442l-18 -15l-31 30l19 15zM351 120l-210 210q-34 -42 -34 -95q0 -62 43.5 -106t105.5 -44q52 0 95 35zM62 463q76 -76 216.5 -216t177.5 -177l-27 -27l-47 47q-55 -47 -126 -47q-80 0 -136 56.5t-56 135.5q0 70 47 125l-17 17l-24 -20l-30 31l24 19l-29 29zM469 390
+l-27 -33l-98 83l27 32zM256 384q-27 0 -51 -9l-33 32q42 20 84 20q80 0 136 -56.5t56 -135.5q0 -44 -19 -84l-33 32q9 24 9 52q0 62 -43.5 105.5t-105.5 43.5z" />
+    <glyph glyph-name="uniE858" unicode="alarm_on" 
+d="M225 202l105 106l23 -23l-128 -128l-68 68l22 22zM256 85q62 0 105.5 44t43.5 106t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5t43.5 -106t105.5 -44zM256 427q80 0 136 -56.5t56 -135.5t-56 -135.5t-136 -56.5t-136 56.5t-56 135.5t56 135.5t136 56.5zM168 440
+l-98 -82l-27 32l98 82zM469 390l-27 -33l-98 83l27 32z" />
+    <glyph glyph-name="uniE859" unicode="android" 
+d="M320 405v22h-21v-22h21zM213 405v22h-21v-22h21zM331 466q53 -38 53 -103h-256q0 66 52 103l-28 28q-8 8 0 15t15 0l32 -32q26 14 57 14q30 0 56 -14l32 32q8 7 15 0t0 -15zM437 341q13 0 22.5 -9.5t9.5 -22.5v-149q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v149
+q0 13 9.5 22.5t22.5 9.5zM75 341q13 0 22.5 -9.5t9.5 -22.5v-149q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v149q0 13 9.5 22.5t22.5 9.5zM128 128v213h256v-213q0 -9 -6 -15t-15 -6h-22v-75q0 -14 -9.5 -23t-22.5 -9t-22.5 9t-9.5 23v75h-42v-75q0 -14 -9.5 -23t-22.5 -9
+t-22.5 9t-9.5 23v75h-22q-9 0 -15 6t-6 15z" />
+    <glyph glyph-name="uniE85A" unicode="announcement" 
+d="M277 192v43h-42v-43h42zM277 277v128h-42v-128h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE85B" unicode="aspect_ratio" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM149 320v-64h-42v107h106v-43h-64zM405 256v-107h-106v43h64v64h42z" />
+    <glyph glyph-name="uniE85C" unicode="assessment" 
+d="M363 149v86h-43v-86h43zM277 149v214h-42v-214h42zM192 149v150h-43v-150h43zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE85D" unicode="assignment" 
+d="M363 320v43h-214v-43h214zM363 235v42h-214v-42h214zM299 149v43h-150v-43h150zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89
+q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE85E" unicode="assignment_ind" 
+d="M384 107v30q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-30h256zM256 363q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30
+t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE85F" unicode="assignment_late" 
+d="M256 405q9 0 15 6.5t6 15.5t-6 15t-15 6t-15 -6t-6 -15t6 -15.5t15 -6.5zM277 213v128h-42v-128h42zM277 128v43h-42v-43h42zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31
+h89z" />
+    <glyph glyph-name="uniE860" unicode="assignment_return" 
+d="M341 192v85h-85v64l-107 -106l107 -107v64h85zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89
+z" />
+    <glyph glyph-name="uniE861" unicode="assignment_returned" 
+d="M256 128l107 107h-64v85h-86v-85h-64zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12t23 -31h89z" />
+    <glyph glyph-name="uniE862" unicode="assignment_turned_in" 
+d="M213 149l171 171l-30 30l-141 -140l-55 55l-30 -30zM256 448q-9 0 -15 -6t-6 -15t6 -15.5t15 -6.5t15 6.5t6 15.5t-6 15t-15 6zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h89q7 19 23 31t37 12t37 -12
+t23 -31h89z" />
+    <glyph glyph-name="uniE863" unicode="autorenew" 
+d="M400 347q27 -41 27 -91q0 -70 -50.5 -120.5t-120.5 -50.5v-64l-85 86l85 85v-64q53 0 90.5 37.5t37.5 90.5q0 30 -15 60zM256 384q-53 0 -90.5 -37.5t-37.5 -90.5q0 -33 15 -60l-31 -31q-27 41 -27 91q0 70 50.5 120.5t120.5 50.5v64l85 -86l-85 -85v64z" />
+    <glyph glyph-name="uniE864" unicode="backup" 
+d="M299 235h64l-107 106l-107 -106h64v-86h86v86zM413 298q41 -3 70 -33.5t29 -72.5q0 -44 -31.5 -75.5t-75.5 -31.5h-277q-53 0 -90.5 37.5t-37.5 90.5q0 49 33 85.5t81 41.5q21 39 59 63t83 24q58 0 102 -36.5t55 -92.5z" />
+    <glyph glyph-name="uniE865" unicode="book" 
+d="M128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE866" unicode="bookmark" 
+d="M363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE867" unicode="bookmark_outline" 
+d="M363 128v277h-214v-277l107 47zM363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE868" unicode="bug_report" 
+d="M299 256v43h-86v-43h86zM299 171v42h-86v-42h86zM427 341v-42h-45q2 -14 2 -22v-21h43v-43h-43v-21q0 -7 -2 -21h45v-43h-60q-17 -29 -46.5 -46.5t-64.5 -17.5t-64.5 17.5t-46.5 46.5h-60v43h45q-2 14 -2 21v21h-43v43h43v21q0 8 2 22h-45v42h60q15 25 39 42l-35 35
+l30 30l47 -46q15 3 30 3t30 -3l47 46l30 -30l-35 -35q24 -17 39 -42h60z" />
+    <glyph glyph-name="uniE869" unicode="build" 
+d="M484 107q7 -4 6.5 -13.5t-8.5 -16.5l-49 -49q-15 -15 -30 0l-194 194q-36 -15 -76.5 -6.5t-70.5 38.5q-32 32 -40 76t12 82l94 -92l64 64l-92 92q38 18 82 11t76 -39q30 -30 38.5 -70.5t-6.5 -76.5z" />
+    <glyph glyph-name="uniE86A" unicode="cached" 
+d="M128 256h64l-85 -85l-86 85h64q0 70 50.5 120.5t120.5 50.5q50 0 91 -27l-31 -31q-27 15 -60 15q-53 0 -90.5 -37.5t-37.5 -90.5zM405 341l86 -85h-64q0 -70 -50.5 -120.5t-120.5 -50.5q-50 0 -91 27l31 31q27 -15 60 -15q53 0 90.5 37.5t37.5 90.5h-64z" />
+    <glyph glyph-name="uniE86B" unicode="change_history" 
+d="M256 427l213 -342h-426zM256 346l-136 -218h272z" />
+    <glyph glyph-name="uniE86C" unicode="check_circle" 
+d="M213 149l192 192l-30 31l-162 -162l-76 76l-30 -30zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE86D" unicode="chrome_reader_mode" 
+d="M448 107v277h-192v-277h192zM448 427q17 0 30 -13t13 -30v-277q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v277q0 17 13 30t30 13h384zM277 203h150v-32h-150v32zM277 309h150v-32h-150v32zM277 256h150v-32h-150v32z" />
+    <glyph glyph-name="uniE86E" unicode="class" 
+d="M128 427v-171l53 32l54 -32v171h-107zM384 469q17 0 30 -12.5t13 -29.5v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 17 13 29.5t30 12.5h256z" />
+    <glyph glyph-name="uniE86F" unicode="code" 
+d="M311 158l99 98l-99 98l30 30l128 -128l-128 -128zM201 158l-30 -30l-128 128l128 128l30 -30l-99 -98z" />
+    <glyph glyph-name="uniE870" unicode="credit_card" 
+d="M427 341v43h-342v-43h342zM427 128v128h-342v-128h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE871" unicode="dashboard" 
+d="M277 448h171v-128h-171v128zM277 64v213h171v-213h-171zM64 64v128h171v-128h-171zM64 235v213h171v-213h-171z" />
+    <glyph glyph-name="uniE872" unicode="delete" 
+d="M405 427v-43h-298v43h74l22 21h106l22 -21h74zM128 107v256h256v-256q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30z" />
+    <glyph glyph-name="uniE873" unicode="description" 
+d="M277 320h118l-118 117v-117zM341 213v43h-170v-43h170zM341 128v43h-170v-43h170zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE875" unicode="dns" 
+d="M149 320q17 0 30 13t13 30t-13 29.5t-30 12.5t-29.5 -12.5t-12.5 -29.5t12.5 -30t29.5 -13zM427 448q9 0 15 -6t6 -15v-128q0 -9 -6 -15.5t-15 -6.5h-342q-9 0 -15 6.5t-6 15.5v128q0 9 6 15t15 6h342zM149 107q17 0 30 12.5t13 29.5t-13 30t-30 13t-29.5 -13t-12.5 -30
+t12.5 -29.5t29.5 -12.5zM427 235q9 0 15 -6.5t6 -15.5v-128q0 -9 -6 -15t-15 -6h-342q-9 0 -15 6t-6 15v128q0 9 6 15.5t15 6.5h342z" />
+    <glyph glyph-name="uniE876" unicode="done" 
+d="M192 166l226 227l30 -30l-256 -256l-119 119l29 30z" />
+    <glyph glyph-name="uniE877" unicode="done_all" 
+d="M9 226l30 30l119 -119l-30 -30zM474 393l31 -30l-256 -256l-120 119l31 30l89 -89zM384 363l-135 -136l-30 30l135 136z" />
+    <glyph glyph-name="uniE878" unicode="event" 
+d="M405 107v234h-298v-234h298zM341 491h43v-43h21q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43zM363 256v-107h-107v107h107z" />
+    <glyph glyph-name="uniE879" unicode="exit_to_app" 
+d="M405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v85h43v-85h298v298h-298v-85h-43v85q0 17 12.5 30t30.5 13h298zM215 179l55 56h-206v42h206l-55 56l30 30l107 -107l-107 -107z" />
+    <glyph glyph-name="uniE87A" unicode="explore" 
+d="M303 209l81 175l-175 -81l-81 -175zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM256 279q10 0 16.5 -6.5t6.5 -16.5t-6.5 -16.5t-16.5 -6.5t-16.5 6.5t-6.5 16.5t6.5 16.5t16.5 6.5z" />
+    <glyph glyph-name="uniE87B" unicode="extension" 
+d="M437 277q22 0 38 -15.5t16 -37.5t-16 -37.5t-38 -15.5h-32v-86q0 -17 -12.5 -29.5t-29.5 -12.5h-81v32q0 24 -17 40.5t-41 16.5t-41 -16.5t-17 -40.5v-32h-81q-17 0 -29.5 12.5t-12.5 29.5v81h32q24 0 40.5 17t16.5 41t-16.5 41t-40.5 17h-32v81q0 17 12.5 29.5
+t29.5 12.5h86v32q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38v-32h86q17 0 29.5 -12.5t12.5 -29.5v-86h32z" />
+    <glyph glyph-name="uniE87C" unicode="face" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5q0 22 -7 48q-19 -5 -48 -5q-110 0 -174 90q-33 -80 -112 -115q-1 -6 -1 -18q0 -70 50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z
+M320 261q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-19 8t-8 19t8 18.5t19 7.5zM192 261q11 0 19 -7.5t8 -18.5t-8 -19t-19 -8t-19 8t-8 19t8 18.5t19 7.5z" />
+    <glyph glyph-name="uniE87D" unicode="favorite" 
+d="M256 57l-31 28q-53 48 -77 71t-53.5 57t-40.5 61.5t-11 56.5q0 49 33.5 83t83.5 34q58 0 96 -45q38 45 96 45q50 0 83.5 -34t33.5 -83q0 -39 -26 -81t-56.5 -73t-99.5 -93z" />
+    <glyph glyph-name="uniE87E" unicode="favorite_outline" 
+d="M258 116q48 43 71 65t50 52t37.5 53t10.5 45q0 32 -21.5 53t-53.5 21q-25 0 -46.5 -14t-29.5 -36h-40q-8 22 -29.5 36t-46.5 14q-32 0 -53.5 -21t-21.5 -53q0 -22 10.5 -45t37.5 -53t50 -52t71 -65l2 -2zM352 448q50 0 83.5 -34t33.5 -83q0 -29 -11 -56.5t-40.5 -61.5
+t-53.5 -57t-77 -71l-31 -28l-31 27q-69 62 -99.5 93t-56.5 73t-26 81q0 49 33.5 83t83.5 34q58 0 96 -45q38 45 96 45z" />
+    <glyph glyph-name="uniE87F" unicode="feedback" 
+d="M277 299v85h-42v-85h42zM277 213v43h-42v-43h42zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5t29.5 12.5h342z" />
+    <glyph glyph-name="uniE880" unicode="find_in_page" 
+d="M192 235q0 26 19 45t45 19t45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45zM427 94l-82 82q18 28 18 59q0 44 -31.5 75t-75.5 31t-75.5 -31t-31.5 -75t31.5 -75.5t75.5 -31.5q31 0 59 18l94 -95q-11 -8 -25 -8h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5
+t29.5 12.5h171l128 -128v-247z" />
+    <glyph glyph-name="uniE881" unicode="find_replace" 
+d="M355 189l103 -104l-31 -31l-104 103q-40 -29 -88 -29q-62 0 -106 44l-44 -44v128h128l-54 -54q31 -31 76 -31q39 0 67.5 24t36.5 61h43q-4 -36 -27 -67zM235 384q-39 0 -68 -24t-37 -61h-43q8 54 50 91t98 37q61 0 105 -44l44 44v-128h-128l54 54q-31 31 -75 31z" />
+    <glyph glyph-name="uniE882" unicode="flip_to_back" 
+d="M320 149v43h43v-43h-43zM320 405v43h43v-43h-43zM107 363v-256h256v-43h-256q-18 0 -30.5 13t-12.5 30v256h43zM405 149v43h43q0 -17 -13 -30t-30 -13zM405 320v43h43v-43h-43zM405 235v42h43v-42h-43zM192 149q-18 0 -30.5 13t-12.5 30h43v-43zM277 448v-43h-42v43h42z
+M405 448q17 0 30 -13t13 -30h-43v43zM277 192v-43h-42v43h42zM192 448v-43h-43q0 17 12.5 30t30.5 13zM192 277v-42h-43v42h43zM192 363v-43h-43v43h43z" />
+    <glyph glyph-name="uniE883" unicode="flip_to_front" 
+d="M149 64v43h43v-43h-43zM235 64v43h42v-43h-42zM405 192v213h-213v-213h213zM405 448q17 0 30 -13t13 -30v-213q0 -17 -13 -30t-30 -13h-213q-18 0 -30.5 13t-12.5 30v213q0 17 12.5 30t30.5 13h213zM320 64v43h43v-43h-43zM64 320v43h43v-43h-43zM107 64q-18 0 -30.5 13
+t-12.5 30h43v-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z" />
+    <glyph glyph-name="uniE884" unicode="get_app" 
+d="M107 128h298v-43h-298v43zM405 320l-149 -149l-149 149h85v128h128v-128h85z" />
+    <glyph glyph-name="uniE885" unicode="grade" 
+d="M256 144l-132 -80l35 150l-116 101l153 13l60 141l60 -141l153 -13l-116 -101l35 -150z" />
+    <glyph glyph-name="uniE886" unicode="group_work" 
+d="M341 139q22 0 38 15.5t16 37.5t-16 37.5t-38 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM203 341q0 -22 15.5 -37.5t37.5 -15.5t37.5 15.5t15.5 37.5t-15.5 38t-37.5 16t-37.5 -16t-15.5 -38zM171 139q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5
+t-38 -15.5t-16 -37.5t16 -37.5t38 -15.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE887" unicode="help" 
+d="M321 272q20 20 20 48q0 35 -25 60t-60 25t-60 -25t-25 -60h42q0 17 13 30t30 13t30 -13t13 -30t-13 -30l-26 -27q-25 -27 -25 -60v-11h42q0 33 25 60zM277 107v42h-42v-42h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5
+t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE888" unicode="highlight_remove" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM311 341l30 -30l-55 -55l55 -55
+l-30 -30l-55 55l-55 -55l-30 30l55 55l-55 55l30 30l55 -55z" />
+    <glyph glyph-name="uniE889" unicode="history" 
+d="M256 341h32v-90l75 -45l-16 -26l-91 55v106zM277 448q79 0 135.5 -56t56.5 -136t-56.5 -136t-135.5 -56t-135 56l30 31q44 -44 105 -44q62 0 106 43.5t44 105.5t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -86l-2 3l-83 83h64q0 80 56.5 136t135.5 56z" />
+    <glyph glyph-name="uniE88A" unicode="home" 
+d="M213 85h-106v171h-64l213 192l213 -192h-64v-171h-106v128h-86v-128z" />
+    <glyph glyph-name="uniE88B" unicode="hourglass_empty" 
+d="M256 267l85 85v75h-170v-75zM341 160l-85 85l-85 -85v-75h170v75zM128 469h256v-128l-85 -85l85 -85v-128h-256v128l85 85l-85 85v128z" />
+    <glyph glyph-name="uniE88C" unicode="hourglass_full" 
+d="M128 469h256v-128l-85 -85l85 -85v-128h-256v128l85 85l-85 85v128z" />
+    <glyph glyph-name="uniE88D" unicode="https" 
+d="M322 341v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5v-43h132zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5
+t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE88E" unicode="info" 
+d="M277 320v43h-42v-43h42zM277 149v128h-42v-128h42zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE88F" unicode="info_outline" 
+d="M235 320v43h42v-43h-42zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 149
+v128h42v-128h-42z" />
+    <glyph glyph-name="uniE890" unicode="input" 
+d="M235 171v64h-214v42h214v64l85 -85zM448 448q17 0 30 -12.5t13 -30.5v-299q0 -17 -13 -29.5t-30 -12.5h-384q-17 0 -30 12.5t-13 29.5v86h43v-86h384v300h-384v-86h-43v85q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE891" unicode="invert_colors_on" 
+d="M256 94v309l-90 -90q-38 -38 -38 -91q0 -52 38 -90t90 -38zM377 343q50 -50 50 -120.5t-50 -120.5t-121 -50t-121 50t-50 120.5t50 120.5l121 121z" />
+    <glyph glyph-name="uniE892" unicode="label" 
+d="M376 387l93 -131l-93 -131q-13 -18 -35 -18h-234q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h234q22 0 35 -18z" />
+    <glyph glyph-name="uniE893" unicode="label_outline" 
+d="M341 149l76 107l-76 107h-234v-214h234zM376 387l93 -131l-93 -131q-13 -18 -35 -18h-234q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h234q22 0 35 -18z" />
+    <glyph glyph-name="uniE894" unicode="language" 
+d="M349 213h72q6 28 6 43t-6 43h-72q3 -21 3 -43t-3 -43zM311 95q61 20 93 76h-63q-10 -40 -30 -76zM306 213q3 21 3 43t-3 43h-100q-3 -21 -3 -43t3 -43h100zM256 86q28 41 41 85h-82q13 -44 41 -85zM171 341q10 40 30 76q-61 -20 -93 -76h63zM108 171q32 -56 93 -76
+q-20 36 -30 76h-63zM91 213h72q-3 21 -3 43t3 43h-72q-6 -28 -6 -43t6 -43zM256 426q-28 -41 -41 -85h82q-13 44 -41 85zM404 341q-32 56 -93 76q20 -36 30 -76h63zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE895" unicode="launch" 
+d="M299 448h149v-149h-43v76l-209 -209l-30 30l209 209h-76v43zM405 107v149h43v-149q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h149v-43h-149v-298h298z" />
+    <glyph glyph-name="uniE896" unicode="list" 
+d="M149 363h299v-43h-299v43zM149 149v43h299v-43h-299zM149 235v42h299v-42h-299zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42h-43z" />
+    <glyph glyph-name="uniE897" unicode="lock" 
+d="M322 341v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5v-43h132zM256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5
+t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5v-43h21z" />
+    <glyph glyph-name="uniE898" unicode="lock_open" 
+d="M384 85v214h-256v-214h256zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h194v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5h-41q0 44 31.5 75.5t75.5 31.5t75.5 -31.5t31.5 -75.5
+v-43h21zM256 149q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE899" unicode="lock_outline" 
+d="M384 85v214h-256v-214h256zM190 384v-43h132v43q0 27 -19.5 46.5t-46.5 19.5t-46.5 -19.5t-19.5 -46.5zM384 341q17 0 30 -12.5t13 -29.5v-214q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v214q0 17 13 29.5t30 12.5h21v43q0 44 31.5 75.5t75.5 31.5
+t75.5 -31.5t31.5 -75.5v-43h21zM256 149q-17 0 -30 13t-13 30t13 30t30 13t30 -13t13 -30t-13 -30t-30 -13z" />
+    <glyph glyph-name="uniE89A" unicode="loyalty" 
+d="M368 186q16 16 16 38t-15.5 37.5t-37.5 15.5q-23 0 -38 -15l-16 -16l-15 16q-15 15 -38 15q-22 0 -37.5 -15.5t-15.5 -37.5q0 -23 15 -38l91 -91zM117 363q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM457 265
+q12 -12 12 -30t-12 -30l-150 -150q-12 -12 -30 -12t-30 12l-192 192q-12 12 -12 30v150q0 17 12.5 29.5t29.5 12.5h150q18 0 30 -12z" />
+    <glyph glyph-name="uniE89B" unicode="markunread_mailbox" 
+d="M427 384q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v256q0 17 12.5 30t29.5 13h43v128h171v-85h-128v-171h42v128h214z" />
+    <glyph glyph-name="uniE89C" unicode="note_add" 
+d="M277 320h118l-118 117v-117zM341 171v42h-64v64h-42v-64h-64v-42h64v-64h42v64h64zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE89D" unicode="open_in_browser" 
+d="M256 299l85 -86h-64v-128h-42v128h-64zM405 427q18 0 30.5 -13t12.5 -30v-256q0 -17 -13 -30t-30 -13h-85v43h85v213h-298v-213h85v-43h-85q-18 0 -30.5 13t-12.5 30v256q0 17 12.5 30t30.5 13h298z" />
+    <glyph glyph-name="uniE89E" unicode="open_in_new" 
+d="M299 448h149v-149h-43v76l-209 -209l-30 30l209 209h-76v43zM405 107v149h43v-149q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h149v-43h-149v-298h298z" />
+    <glyph glyph-name="uniE89F" unicode="open_with" 
+d="M299 192v-64h64l-107 -107l-107 107h64v64h86zM491 256l-107 -107v64h-64v86h64v64zM192 299v-86h-64v-64l-107 107l107 107v-64h64zM213 320v64h-64l107 107l107 -107h-64v-64h-86z" />
+    <glyph glyph-name="uniE8A0" unicode="pageview" 
+d="M358 124l30 30l-62 62q15 25 15 51q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28q26 0 51 15zM427 427q17 0 29.5 -13t12.5 -30v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 30t29.5 13h342zM245 320q22 0 38 -15.5t16 -37.5
+t-16 -38t-38 -16t-37.5 16t-15.5 38t15.5 37.5t37.5 15.5z" />
+    <glyph glyph-name="uniE8A1" unicode="payment" 
+d="M427 341v43h-342v-43h342zM427 128v128h-342v-128h342zM427 427q18 0 30 -12.5t12 -30.5v-256q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v256q0 18 12 30.5t30 12.5h342z" />
+    <glyph glyph-name="uniE8A2" unicode="perm_camera_mic" 
+d="M299 235v85q0 17 -13 30t-30 13t-30 -13t-13 -30v-85q0 -17 13 -30t30 -13t30 13t13 30zM427 405q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-150v45q45 8 76 43.5t31 82.5h-43q0 -35 -25 -60.5t-60 -25.5t-60 25.5t-25 60.5h-43q0 -47 31 -82.5
+t76 -43.5v-45h-150q-17 0 -29.5 13t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h68l39 43h128l39 -43h68z" />
+    <glyph glyph-name="uniE8A3" unicode="perm_contact_calendar" 
+d="M384 128v21q0 29 -44 47.5t-84 18.5t-84 -18.5t-44 -47.5v-21h256zM256 384q-26 0 -45 -19t-19 -45t19 -45t45 -19t45 19t19 45t-19 45t-45 19zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43
+v-43h170v43h43v-43h21z" />
+    <glyph glyph-name="uniE8A4" unicode="perm_data_setting" 
+d="M405 75q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM484 96l23 -17q3 -3 1 -7l-21 -37q-3 -5 -7 -3l-26 11q-3 -3 -18 -10l-4 -29q0 -4 -6 -4h-42q-6 0 -6 4l-4 29q-8 4 -18 10l-26 -11q-5 -2 -7 3l-21 37q-2 4 1 7l23 17
+q0 1 -0.5 5t-0.5 6t0.5 5.5t0.5 4.5l-23 18q-3 3 -1 7l21 37q2 4 7 2l26 -11q12 8 18 11l4 28q0 4 6 4h42q6 0 6 -4l4 -28q4 -2 18 -11l26 11q5 2 7 -2l21 -37q2 -4 -1 -7l-23 -18q1 -3 1 -10q0 -2 -0.5 -6t-0.5 -5zM405 267q-66 0 -113 -47t-47 -113q0 -8 2 -22h-247
+l427 427l-1 -247q-14 2 -21 2z" />
+    <glyph glyph-name="uniE8A5" unicode="perm_device_information" 
+d="M363 107v298h-214v-298h214zM363 490q17 0 29.5 -12.5t12.5 -29.5v-384q0 -17 -12.5 -30t-29.5 -13h-214q-17 0 -29.5 13t-12.5 30v384q0 17 12.5 30t29.5 13zM277 277v-128h-42v128h42zM277 363v-43h-42v43h42z" />
+    <glyph glyph-name="uniE8A6" unicode="perm_identity" 
+d="M256 235q32 0 70 -9t69.5 -30t31.5 -47v-64h-342v64q0 26 31.5 47t69.5 30t70 9zM256 427q35 0 60 -25.5t25 -60.5t-25 -60t-60 -25t-60 25t-25 60t25 60.5t60 25.5zM256 194q-44 0 -87 -16.5t-43 -28.5v-23h260v23q0 12 -43 28.5t-87 16.5zM256 386q-19 0 -32 -13
+t-13 -32t13 -31.5t32 -12.5t32 12.5t13 31.5t-13 32t-32 13z" />
+    <glyph glyph-name="uniE8A7" unicode="perm_media" 
+d="M149 192h299l-75 96l-53 -64l-75 96zM469 427q17 0 30 -13t13 -30v-213q0 -17 -13 -30t-30 -13h-341q-17 0 -30 13t-13 30l1 256q0 17 12.5 29.5t29.5 12.5h128l43 -42h170zM43 384v-299h384v-42h-384q-17 0 -30 12.5t-13 29.5v299h43z" />
+    <glyph glyph-name="uniE8A8" unicode="perm_phone_msg" 
+d="M256 448h192v-149h-128l-64 -64v213zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q48 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12z" />
+    <glyph glyph-name="uniE8A9" unicode="perm_scan_wifi" 
+d="M235 341h42v43h-42v-43zM277 171v128h-42v-128h42zM256 448q136 0 256 -91l-256 -314l-256 315q118 90 256 90z" />
+    <glyph glyph-name="uniE8AA" unicode="picture_in_picture" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM405 363v-128h-170v128h170z" />
+    <glyph glyph-name="uniE8AB" unicode="polymer" 
+d="M405 427l96 -171l-96 -171h-85l96 171l-56 99l-168 -270h-85l-96 171l96 171h85l-96 -171l56 -99l168 270h85z" />
+    <glyph glyph-name="uniE8AC" unicode="power_settings_new" 
+d="M380 402q68 -58 68 -146q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 88 68 146l30 -30q-55 -45 -55 -116q0 -62 43.5 -105.5t105.5 -43.5t105.5 43.5t43.5 105.5q0 71 -55 115zM277 448v-213h-42v213h42z" />
+    <glyph glyph-name="uniE8AD" unicode="print" 
+d="M384 448v-85h-256v85h256zM405 256q9 0 15.5 6t6.5 15t-6.5 15.5t-15.5 6.5t-15 -6.5t-6 -15.5t6 -15t15 -6zM341 107v106h-170v-106h170zM405 341q26 0 45 -19t19 -45v-128h-85v-85h-256v85h-85v128q0 26 19 45t45 19h298z" />
+    <glyph glyph-name="uniE8AE" unicode="query_builder" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE8AF" unicode="question_answer" 
+d="M363 256q0 -9 -6.5 -15t-15.5 -6h-213l-85 -86v299q0 9 6 15t15 6h277q9 0 15.5 -6t6.5 -15v-192zM448 384q9 0 15 -6t6 -15v-320l-85 85h-235q-9 0 -15 6t-6 15v43h277v192h43z" />
+    <glyph glyph-name="uniE8B0" unicode="receipt" 
+d="M64 43v426l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32l32 -32l32 32v-426l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32l-32 -32l-32 32zM384 320v43h-256v-43h256zM384 235v42h-256v-42h256zM384 149v43h-256v-43h256z
+" />
+    <glyph glyph-name="uniE8B1" unicode="redeem" 
+d="M427 213v128h-109l45 -60l-35 -25q-64 87 -72 98q-8 -11 -72 -98l-35 25l45 60h-109v-128h342zM427 107v42h-342v-42h342zM192 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5t-15 6.5zM320 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5
+t-15 6.5zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h47q-4 14 -4 21q0 26 19 45t45 19q33 0 53 -28l11 -15l11 15q20 28 53 28q26 0 45 -19t19 -45q0 -7 -4 -21h47z" />
+    <glyph glyph-name="uniE8B2" unicode="report_problem" 
+d="M277 213v86h-42v-86h42zM277 128v43h-42v-43h42zM21 64l235 405l235 -405h-470z" />
+    <glyph glyph-name="uniE8B3" unicode="restore" 
+d="M256 341h32v-90l75 -45l-16 -26l-91 55v106zM277 448q79 0 135.5 -56t56.5 -136t-56.5 -136t-135.5 -56t-135 56l30 31q44 -44 105 -44q62 0 106 43.5t44 105.5t-44 105.5t-106 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -86l-2 3l-83 83h64q0 80 56.5 136t135.5 56z" />
+    <glyph glyph-name="uniE8B4" unicode="room" 
+d="M256 267q22 0 37.5 15.5t15.5 37.5t-15.5 37.5t-37.5 15.5t-37.5 -15.5t-15.5 -37.5t15.5 -37.5t37.5 -15.5zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -31 -15.5 -71t-37.5 -75t-43.5 -65.5t-36.5 -48.5l-16 -17q-6 7 -16 18.5t-36 46t-45.5 67t-35.5 73.5t-16 72
+q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE8B5" unicode="schedule" 
+d="M267 363v-112l96 -57l-16 -27l-112 68v128h32zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5
+t150.5 62.5z" />
+    <glyph glyph-name="uniE8B6" unicode="search" 
+d="M203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE8B8" unicode="settings" 
+d="M256 181q31 0 53 22t22 53t-22 53t-53 22t-53 -22t-22 -53t22 -53t53 -22zM415 235l45 -35q7 -5 2 -14l-43 -74q-4 -7 -13 -4l-53 21q-21 -15 -36 -21l-8 -56q-2 -9 -10 -9h-86q-8 0 -10 9l-8 56q-19 8 -36 21l-53 -21q-9 -3 -13 4l-43 74q-5 9 2 14l45 35q-1 7 -1 21
+t1 21l-45 35q-7 5 -2 14l43 74q4 7 13 4l53 -21q21 15 36 21l8 56q2 9 10 9h86q8 0 10 -9l8 -56q19 -8 36 -21l53 21q9 3 13 -4l43 -74q5 -9 -2 -14l-45 -35q1 -7 1 -21t-1 -21z" />
+    <glyph glyph-name="uniE8B9" unicode="settings_applications" 
+d="M368 256q0 10 -1 15l32 24q5 4 1 10l-30 52q-3 5 -9 3l-37 -15q-13 10 -25 15l-6 39q-2 6 -7 6h-60q-6 0 -7 -6l-6 -40q-16 -7 -25 -14l-37 15q-5 2 -9 -4l-30 -51q-4 -6 1 -10l32 -24q-1 -5 -1 -15t1 -15l-32 -24q-5 -4 -1 -10l30 -52q3 -5 9 -3l37 15q13 -10 25 -15
+l6 -39q2 -6 7 -6h60q6 0 7 6l6 40q16 7 25 14l37 -15q5 -2 9 4l30 51q4 6 -1 10l-32 24q1 5 1 15zM405 448q18 0 30.5 -13t12.5 -30v-298q0 -17 -12.5 -30t-30.5 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h298zM256 299q17 0 30 -13t13 -30t-13 -30
+t-30 -13t-30 13t-13 30t13 30t30 13z" />
+    <glyph glyph-name="uniE8BA" unicode="settings_backup_restore" 
+d="M256 448q80 0 136 -56t56 -136t-56 -136t-136 -56q-66 0 -117 40l30 30q40 -27 87 -27q62 0 105.5 43.5t43.5 105.5t-43.5 105.5t-105.5 43.5t-105.5 -43.5t-43.5 -105.5h64l-86 -85l-85 85h64q0 80 56 136t136 56zM299 256q0 -17 -13 -30t-30 -13t-30 13t-13 30t13 30
+t30 13t30 -13t13 -30z" />
+    <glyph glyph-name="uniE8BB" unicode="settings_bluetooth" 
+d="M317 207l-40 40v-80zM277 430v-80l40 40zM378 390l-92 -91l92 -92l-122 -122h-21v162l-98 -98l-30 30l119 120l-119 119l30 30l98 -98v162h21zM320 0v43h43v-43h-43zM149 0v43h43v-43h-43zM235 0v43h42v-43h-42z" />
+    <glyph glyph-name="uniE8BC" unicode="settings_cell" 
+d="M341 171v256h-170v-256h170zM341 512q17 0 30 -13t13 -30v-341q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30v341q0 17 13 30t30 13h170zM320 0v43h43v-43h-43zM235 0v43h42v-43h-42zM149 0v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8BD" unicode="settings_brightness" 
+d="M256 320v-128q26 0 45 19t19 45t-19 45t-45 19zM171 171v53l-32 32l32 32v53h53l32 32l32 -32h53v-53l32 -32l-32 -32v-53h-53l-32 -32l-32 32h-53zM448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298
+q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE8BE" unicode="settings_ethernet" 
+d="M379 395l116 -139l-116 -139l-33 27l93 112l-93 112zM235 235v42h42v-42h-42zM363 277v-42h-43v42h43zM149 235v42h43v-42h-43zM166 368l-93 -112l93 -112l-33 -27l-116 139l116 139z" />
+    <glyph glyph-name="uniE8BF" unicode="settings_input_antenna" 
+d="M256 491q97 0 166 -69t69 -166h-43q0 80 -56 136t-136 56t-136 -56t-56 -136h-43q0 97 69 166t166 69zM277 207v-70l73 -73l-30 -30l-64 64l-64 -64l-30 30l73 73v70q-32 13 -32 49q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5q0 -36 -32 -49zM256 405
+q62 0 105.5 -43.5t43.5 -105.5h-42q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5h-42q0 62 43.5 105.5t105.5 43.5z" />
+    <glyph glyph-name="uniE8C0" unicode="settings_input_component" 
+d="M363 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-43v90q-42 15 -42 60zM277 469v-85h43v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5zM448 384h43v-128h-128v128h42v85q0 9 6.5 15.5t15.5 6.5t15 -6.5t6 -15.5v-85zM21 171v42h128v-42q0 -45 -42 -60v-90h-43
+v90q-19 7 -31 23t-12 37zM192 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-42v90q-19 7 -31 23t-12 37zM107 469v-85h42v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15.5 -6.5t6.5 -15.5z" />
+    <glyph glyph-name="uniE8C1" unicode="settings_input_composite" 
+d="M363 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-43v90q-42 15 -42 60zM277 469v-85h43v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15 -6.5t6 -15.5zM448 384h43v-128h-128v128h42v85q0 9 6.5 15.5t15.5 6.5t15 -6.5t6 -15.5v-85zM21 171v42h128v-42q0 -45 -42 -60v-90h-43
+v90q-19 7 -31 23t-12 37zM192 171v42h128v-42q0 -21 -12 -37t-31 -23v-90h-42v90q-19 7 -31 23t-12 37zM107 469v-85h42v-128h-128v128h43v85q0 9 6 15.5t15 6.5t15.5 -6.5t6.5 -15.5z" />
+    <glyph glyph-name="uniE8C2" unicode="settings_input_hdmi" 
+d="M171 427v-64h42v42h22v-42h42v42h22v-42h42v64h-170zM384 363h21v-128l-64 -128v-64h-170v64l-64 128v128h21v64q0 17 13 29.5t30 12.5h170q17 0 30 -12.5t13 -29.5v-64z" />
+    <glyph glyph-name="uniE8C3" unicode="settings_input_svideo" 
+d="M331 192q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9zM373 299q13 0 22.5 -9.5t9.5 -22.5t-9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5zM256 64q80 0 136 56t56 136t-56 136t-136 56t-136 -56t-56 -136t56 -136t136 -56z
+M256 491q97 0 166 -69t69 -166t-69 -166t-166 -69t-166 69t-69 166t69 166t166 69zM181 192q13 0 22.5 -9t9.5 -23t-9.5 -23t-22.5 -9t-22.5 9t-9.5 23t9.5 23t22.5 9zM320 373q0 -13 -9 -22.5t-23 -9.5h-64q-14 0 -23 9.5t-9 22.5t9 22.5t23 9.5h64q14 0 23 -9.5t9 -22.5z
+M171 267q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5z" />
+    <glyph glyph-name="uniE8C4" unicode="settings_overscan" 
+d="M448 106v300h-384v-300h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384zM299 171l-43 -54l-43 54h86zM128 299v-86l-53 43zM384 299l53 -43l-53 -43v86zM256 395l43 -54h-86z" />
+    <glyph glyph-name="uniE8C5" unicode="settings_phone" 
+d="M405 320h43v-43h-43v43zM427 181q9 0 15 -6t6 -15v-75q0 -9 -6 -15t-15 -6q-150 0 -256.5 106.5t-106.5 256.5q0 9 6 15t15 6h75q9 0 15 -6t6 -15q0 -40 12 -76q4 -13 -5 -22l-47 -47q48 -93 141 -141l47 47q9 9 22 5q36 -12 76 -12zM363 320v-43h-43v43h43zM277 320v-43
+h-42v43h42z" />
+    <glyph glyph-name="uniE8C6" unicode="settings_power" 
+d="M320 0v43h43v-43h-43zM353 417q74 -52 74 -140q0 -70 -50 -120t-121 -50t-121 50t-50 120q0 88 74 140l30 -30q-61 -38 -61 -110q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5q0 72 -62 109zM277 469v-213h-42v213h42zM235 0v43h42v-43h-42zM149 0v43h43v-43h-43z
+" />
+    <glyph glyph-name="uniE8C7" unicode="settings_remote" 
+d="M256 512q99 0 166 -69l-30 -30q-56 56 -136 56t-136 -56l-30 30q69 69 166 69zM150 383q44 44 106 44t106 -44l-30 -30q-31 31 -76 31t-76 -31zM256 192q17 0 30 13t13 30t-13 29.5t-30 12.5t-30 -12.5t-13 -29.5t13 -30t30 -13zM320 320q9 0 15 -6t6 -15v-256
+q0 -9 -6 -15.5t-15 -6.5h-128q-9 0 -15 6.5t-6 15.5v256q0 9 6 15t15 6h128z" />
+    <glyph glyph-name="uniE8C8" unicode="settings_voice" 
+d="M405 299q0 -54 -37.5 -95t-90.5 -49v-70h-42v70q-53 8 -90.5 49t-37.5 95h36q0 -47 33.5 -78t79.5 -31t79.5 31t33.5 78h36zM320 0v43h43v-43h-43zM235 0v43h42v-43h-42zM256 235q-26 0 -45 19t-19 45v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -26 -19 -45t-45 -19z
+M149 0v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8C9" unicode="shop" 
+d="M192 128l160 107l-160 85v-192zM213 427v-43h86v43h-86zM341 384h128v-277q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v277h128v43q0 18 12 30t30 12h86q18 0 30 -12t12 -30v-43z" />
+    <glyph glyph-name="uniE8CA" unicode="shop_two" 
+d="M256 192l117 85l-117 64v-149zM256 448v-43h85v43h-85zM384 405h107v-234q0 -18 -12.5 -30.5t-30.5 -12.5h-299q-18 0 -30 12.5t-12 30.5v234h106v43q0 18 12.5 30.5t30.5 12.5h85q18 0 30.5 -12.5t12.5 -30.5v-43zM64 320v-235h341q0 -18 -12 -30t-30 -12h-299
+q-18 0 -30.5 12t-12.5 30v235h43z" />
+    <glyph glyph-name="uniE8CB" unicode="shopping_basket" 
+d="M256 149q17 0 30 13t13 30t-13 30t-30 13t-30 -13t-13 -30t13 -30t30 -13zM192 320h128l-64 94zM367 320h102q9 0 15.5 -6t6.5 -15q-10 -40 -30 -112.5t-25 -91.5q-9 -31 -41 -31h-278q-32 0 -41 31l-54 198q-1 2 -1 6q0 9 6.5 15t15.5 6h102l93 140q6 9 18 9t18 -9z" />
+    <glyph glyph-name="uniE8CC" unicode="shopping_cart" 
+d="M363 128q17 0 29.5 -13t12.5 -30t-12.5 -29.5t-29.5 -12.5t-30 12.5t-13 29.5t13 30t30 13zM21 469h70l20 -42h316q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22h-159l-19 -35l-1 -3q0 -5 5 -5h247v-43h-256q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53
+l-77 162h-43v42zM149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13z" />
+    <glyph glyph-name="uniE8CD" unicode="speaker_notes" 
+d="M384 341v43h-171v-43h171zM384 277v43h-171v-43h171zM320 213v43h-107v-43h107zM171 341v43h-43v-43h43zM171 277v43h-43v-43h43zM171 213v43h-43v-43h43zM427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-299l-85 -85v384q0 17 12.5 29.5
+t29.5 12.5h342z" />
+    <glyph glyph-name="uniE8CE" unicode="spellcheck" 
+d="M461 265l30 -30l-203 -203l-108 109l30 30l78 -79zM137 277h88l-44 118zM266 171l-25 64h-120l-24 -64h-45l109 277h40l109 -277h-44z" />
+    <glyph glyph-name="uniE8D0" unicode="stars" 
+d="M346 128l-24 103l80 69l-105 9l-41 96l-41 -97l-105 -8l80 -69l-24 -103l90 54zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE8D1" unicode="store" 
+d="M256 128v85h-128v-85h128zM448 213h-21v-128h-43v128h-85v-128h-214v128h-21v43l21 107h342l21 -107v-43zM427 427v-43h-342v43h342z" />
+    <glyph glyph-name="uniE8D2" unicode="subject" 
+d="M85 405h342v-42h-342v42zM85 192v43h342v-43h-342zM427 320v-43h-342v43h342zM299 149v-42h-214v42h214z" />
+    <glyph glyph-name="uniE8D3" unicode="supervisor_account" 
+d="M192 235q22 0 51 -6q-51 -28 -51 -74v-48h-149v53q0 23 27.5 41t60.5 26t61 8zM352 213q37 0 77 -16t40 -42v-48h-234v48q0 26 40 42t77 16zM192 277q-26 0 -45 19t-19 45t19 45t45 19t45 -19t19 -45t-19 -45t-45 -19zM352 256q-22 0 -37.5 15.5t-15.5 37.5t15.5 38
+t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5z" />
+    <glyph glyph-name="uniE8D4" unicode="swap_horiz" 
+d="M448 320l-85 -85v64h-150v42h150v64zM149 277v-64h150v-42h-150v-64l-85 85z" />
+    <glyph glyph-name="uniE8D5" unicode="swap_vert" 
+d="M192 448l85 -85h-64v-150h-42v150h-64zM341 149h64l-85 -85l-85 85h64v150h42v-150z" />
+    <glyph glyph-name="uniE8D6" unicode="swap_vertical_circle" 
+d="M373 192h-53v85h-43v-85h-53l75 -75zM139 320h53v-85h43v85h53l-75 75zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE8D7" unicode="system_update_alt" 
+d="M448 437q17 0 30 -12.5t13 -29.5v-299q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h128v-42h-128v-299h384v299h-128v42h128zM256 160l-85 85h64v192h42v-192h64z" />
+    <glyph glyph-name="uniE8D8" unicode="tab" 
+d="M448 107v213h-171v85h-213v-298h384zM448 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h384z" />
+    <glyph glyph-name="uniE8D9" unicode="tab_unselected" 
+d="M363 64v43h42v-43h-42zM277 64v43h43v-43h-43zM448 235v42h43v-42h-43zM448 64v43h43q0 -17 -13 -30t-30 -13zM107 405v43h42v-43h-42zM107 64v43h42v-43h-42zM192 405v43h43v-43h-43zM448 149v43h43v-43h-43zM448 448q17 0 30 -13t13 -30v-85h-214v128h171zM64 64
+q-17 0 -30 13t-13 30h43v-43zM21 149v43h43v-43h-43zM192 64v43h43v-43h-43zM21 405q0 17 13 30t30 13v-43h-43zM21 235v42h43v-42h-43zM21 320v43h43v-43h-43z" />
+    <glyph glyph-name="uniE8DA" unicode="theaters" 
+d="M384 320v43h-43v-43h43zM384 235v42h-43v-42h43zM384 149v43h-43v-43h43zM171 320v43h-43v-43h43zM171 235v42h-43v-42h43zM171 149v43h-43v-43h43zM384 448h43v-384h-43v43h-43v-43h-170v43h-43v-43h-43v384h43v-43h43v43h170v-43h43v43z" />
+    <glyph glyph-name="uniE8DB" unicode="thumb_down" 
+d="M405 448h86v-256h-86v256zM320 448q17 0 30 -13t13 -30v-213q0 -17 -13 -30l-140 -141l-23 23q-9 9 -9 22v7l21 98h-135q-17 0 -30 12.5t-13 29.5l1 2h-1v41q0 8 3 16l65 150q10 26 39 26h192z" />
+    <glyph glyph-name="uniE8DC" unicode="thumb_up" 
+d="M491 299l-1 -2h1v-41q0 -8 -3 -16l-65 -150q-10 -26 -39 -26h-192q-17 0 -30 13t-13 30v213q0 17 13 30l140 141l23 -23q9 -9 9 -22v-7l-21 -98h135q17 0 30 -12.5t13 -29.5zM21 64v256h86v-256h-86z" />
+    <glyph glyph-name="uniE8DD" unicode="thumbs_up_down" 
+d="M480 299q14 0 23 -9.5t9 -22.5v-139q0 -14 -9 -23l-106 -105l-17 17q-7 7 -7 17q3 14 8.5 40t6.5 33h-111q-9 0 -15 6t-6 15v27q0 3 2 11l49 113q9 20 29 20h144zM256 384v-27q0 -3 -2 -11l-49 -113q-9 -20 -29 -20h-144q-14 0 -23 9.5t-9 22.5v139q0 14 9 23l106 105
+l17 -17q7 -7 7 -17q-3 -14 -8.5 -40t-6.5 -33h111q9 0 15 -6t6 -15z" />
+    <glyph glyph-name="uniE8DE" unicode="toc" 
+d="M405 235v42h43v-42h-43zM405 363h43v-43h-43v43zM405 149v43h43v-43h-43zM64 149v43h299v-43h-299zM64 235v42h299v-42h-299zM64 320v43h299v-43h-299z" />
+    <glyph glyph-name="uniE8DF" unicode="today" 
+d="M149 299h107v-107h-107v107zM405 107v234h-298v-234h298zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-18 0 -30.5 13t-12.5 30v298q0 17 12.5 30t30.5 13h21v43h43v-43h170v43h43v-43h21z" />
+    <glyph glyph-name="uniE8E0" unicode="toll" 
+d="M64 256q0 -42 23.5 -75t61.5 -46v-44q-56 14 -92 60t-36 105t36 105t92 60v-44q-38 -13 -61.5 -46t-23.5 -75zM320 128q53 0 90.5 37.5t37.5 90.5t-37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5zM320 427q70 0 120.5 -50.5t50.5 -120.5
+t-50.5 -120.5t-120.5 -50.5t-120.5 50.5t-50.5 120.5t50.5 120.5t120.5 50.5z" />
+    <glyph glyph-name="uniE8E1" unicode="track_changes" 
+d="M407 407q62 -62 62 -151q0 -88 -62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5h21v-176q22 -12 22 -37q0 -17 -13 -30t-30 -13t-30 13t-13 30q0 25 22 37v45q-28 -8 -46 -30t-18 -52q0 -35 25 -60t60 -25t60 25t25 60q0 33 -25 60l30 30
+q38 -38 38 -90q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5q0 47 30.5 82.5t76.5 43.5v43q-63 -8 -106.5 -56t-43.5 -113q0 -70 50.5 -120.5t120.5 -50.5t120.5 50.5t50.5 120.5q0 71 -50 121z" />
+    <glyph glyph-name="uniE8E2" unicode="translate" 
+d="M339 149h69l-35 93zM395 299l96 -256h-43l-24 64h-101l-24 -64h-43l96 256h43zM275 191l-17 -44l-66 66l-107 -106l-30 30l109 107q-40 44 -64 97h43q21 -40 49 -71q46 51 68 114h-239v43h150v42h42v-42h150v-43h-63q-26 -80 -79 -139l-1 -1z" />
+    <glyph glyph-name="uniE8E3" unicode="trending_down" 
+d="M341 128l49 49l-104 104l-85 -85l-158 158l30 30l128 -128l85 85l134 -134l49 49v-128h-128z" />
+    <glyph glyph-name="uniE8E4" unicode="trending_neutral" 
+d="M469 256l-85 -85v64h-320v42h320v64z" />
+    <glyph glyph-name="uniE8E5" unicode="trending_up" 
+d="M341 384h128v-128l-49 49l-134 -134l-85 85l-128 -128l-30 30l158 158l85 -85l104 104z" />
+    <glyph glyph-name="uniE8E6" unicode="turned_in" 
+d="M363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE8E7" unicode="turned_in_not" 
+d="M363 128v277h-214v-277l107 47zM363 448q17 0 29.5 -13t12.5 -30v-341l-149 64l-149 -64v341q0 17 12.5 30t29.5 13h214z" />
+    <glyph glyph-name="uniE8E8" unicode="verified_user" 
+d="M213 149l171 171l-30 30l-141 -140l-55 55l-30 -30zM256 491l192 -86v-128q0 -89 -55 -162.5t-137 -93.5q-82 20 -137 93.5t-55 162.5v128z" />
+    <glyph glyph-name="uniE8E9" unicode="view_agenda" 
+d="M427 448q9 0 15 -6t6 -15v-128q0 -9 -6 -15.5t-15 -6.5h-363q-9 0 -15 6.5t-6 15.5v128q0 9 6 15t15 6h363zM427 235q9 0 15 -6.5t6 -15.5v-128q0 -9 -6 -15t-15 -6h-363q-9 0 -15 6t-6 15v128q0 9 6 15.5t15 6.5h363z" />
+    <glyph glyph-name="uniE8EA" unicode="view_array" 
+d="M171 128v277h192v-277h-192zM384 405h64v-277h-64v277zM85 128v277h64v-277h-64z" />
+    <glyph glyph-name="uniE8EB" unicode="view_carousel" 
+d="M384 384h85v-235h-85v235zM43 149v235h85v-235h-85zM149 107v320h214v-320h-214z" />
+    <glyph glyph-name="uniE8EC" unicode="view_column" 
+d="M341 405h107v-277h-107v277zM85 128v277h107v-277h-107zM213 128v277h107v-277h-107z" />
+    <glyph glyph-name="uniE8ED" unicode="view_day" 
+d="M43 448h405v-64h-405v64zM427 341q9 0 15 -6t6 -15v-128q0 -9 -6 -15t-15 -6h-363q-9 0 -15 6t-6 15v128q0 9 6 15t15 6h363zM43 64v64h405v-64h-405z" />
+    <glyph glyph-name="uniE8EE" unicode="view_headline" 
+d="M85 405h342v-42h-342v42zM85 277v43h342v-43h-342zM85 107v42h342v-42h-342zM85 192v43h342v-43h-342z" />
+    <glyph glyph-name="uniE8EF" unicode="view_list" 
+d="M192 405h256v-85h-256v85zM192 107v85h256v-85h-256zM192 213v86h256v-86h-256zM85 320v85h86v-85h-86zM85 107v85h86v-85h-86zM85 213v86h86v-86h-86z" />
+    <glyph glyph-name="uniE8F0" unicode="view_module" 
+d="M341 405h107v-128h-107v128zM213 277v128h107v-128h-107zM341 128v128h107v-128h-107zM213 128v128h107v-128h-107zM85 128v128h107v-128h-107zM85 277v128h107v-128h-107z" />
+    <glyph glyph-name="uniE8F1" unicode="view_quilt" 
+d="M213 405h235v-128h-235v128zM341 128v128h107v-128h-107zM85 128v277h107v-277h-107zM213 128v128h107v-128h-107z" />
+    <glyph glyph-name="uniE8F2" unicode="view_stream" 
+d="M85 405h363v-128h-363v128zM85 128v128h363v-128h-363z" />
+    <glyph glyph-name="uniE8F3" unicode="view_week" 
+d="M277 405q9 0 15.5 -6t6.5 -15v-256q0 -9 -6.5 -15t-15.5 -6h-64q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h64zM427 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-64q-9 0 -15.5 6t-6.5 15v256q0 9 6.5 15t15.5 6h64zM128 405q9 0 15 -6t6 -15v-256q0 -9 -6 -15t-15 -6h-64
+q-9 0 -15 6t-6 15v256q0 9 6 15t15 6h64z" />
+    <glyph glyph-name="uniE8F4" unicode="visibility" 
+d="M256 320q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM256 149q44 0 75.5 31.5t31.5 75.5t-31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5t31.5 -75.5t75.5 -31.5zM256 416q79 0 143 -44t92 -116q-28 -72 -92 -116t-143 -44t-143 44t-92 116
+q28 72 92 116t143 44z" />
+    <glyph glyph-name="uniE8F5" unicode="visibility_off" 
+d="M253 320h3q26 0 45 -19t19 -45v-4zM161 303q-12 -24 -12 -47q0 -44 31.5 -75.5t75.5 -31.5q23 0 47 12l-33 33q-8 -2 -14 -2q-26 0 -45 19t-19 45q0 6 2 14zM43 421l27 27l378 -378l-27 -27q-5 5 -31.5 31t-40.5 40q-43 -18 -93 -18q-79 0 -143 44t-92 116q25 62 80 106
+q-12 12 -33.5 34t-24.5 25zM256 363q-20 0 -39 -8l-46 46q39 15 85 15q79 0 142.5 -44t91.5 -116q-24 -59 -73 -101l-62 62q8 19 8 39q0 44 -31.5 75.5t-75.5 31.5z" />
+    <glyph glyph-name="uniE8F6" unicode="card_giftcard" 
+d="M427 213v128h-109l45 -60l-35 -25q-64 87 -72 98q-8 -11 -72 -98l-35 25l45 60h-109v-128h342zM427 107v42h-342v-42h342zM192 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5t-15 6.5zM320 427q-9 0 -15 -6.5t-6 -15.5t6 -15t15 -6t15 6t6 15t-6 15.5
+t-15 6.5zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h47q-4 14 -4 21q0 26 19 45t45 19q33 0 53 -28l11 -15l11 15q20 28 53 28q26 0 45 -19t19 -45q0 -7 -4 -21h47z" />
+    <glyph glyph-name="uniE8F7" unicode="card_membership" 
+d="M427 299v128h-342v-128h342zM427 192v43h-342v-43h342zM427 469q18 0 30 -12t12 -30v-235q0 -18 -12 -30.5t-30 -12.5h-86v-106l-85 42l-85 -42v106h-86q-18 0 -30 12.5t-12 30.5v235q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniE8F8" unicode="card_travel" 
+d="M427 213v128h-64v-42h-43v42h-128v-42h-43v42h-64v-128h342zM427 107v42h-342v-42h342zM192 427v-43h128v43h-128zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h64v43q0 18 12.5 30t30.5 12
+h128q18 0 30.5 -12t12.5 -30v-43h64z" />
+    <glyph glyph-name="uniE8F9" unicode="work" 
+d="M299 384v43h-86v-43h86zM427 384q18 0 30 -12.5t12 -30.5v-234q0 -18 -12 -30.5t-30 -12.5h-342q-18 0 -30 12.5t-12 30.5v234q0 18 12 30.5t30 12.5h86v43q0 18 12 30t30 12h86q18 0 30 -12t12 -30v-43h86z" />
+    <glyph glyph-name="uniE8FA" unicode="youtube_searched_for" 
+d="M363 213l106 -106l-31 -32l-107 107v16l-6 6q-38 -33 -90 -33q-38 0 -71 19l32 31q19 -8 39 -8q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68h74l-88 -85l-82 85h53q0 57 41 98t98 41q58 0 98.5 -40.5t40.5 -98.5q0 -51 -34 -90l6 -6h17z" />
+    <glyph glyph-name="uniE8FB" unicode="eject" 
+d="M256 405l142 -213h-284zM107 149h298v-42h-298v42z" />
+    <glyph glyph-name="uniE8FC" unicode="camera_enhance" 
+d="M256 149l-27 59l-58 27l58 26l27 59l27 -59l58 -26l-58 -27zM256 128q44 0 75.5 31.5t31.5 75.5t-31.5 75t-75.5 31t-75.5 -31t-31.5 -75t31.5 -75.5t75.5 -31.5zM192 448h128l39 -43h68q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12.5 -30t-29.5 -13h-342q-17 0 -29.5 13
+t-12.5 30v256q0 17 12.5 29.5t29.5 12.5h68z" />
+    <glyph glyph-name="uniE8FD" unicode="help_outline" 
+d="M256 384q35 0 60 -25t25 -60q0 -27 -32 -55.5t-32 -51.5h-42q0 23 10 39.5t22 24t22 18.5t10 25q0 17 -13 29.5t-30 12.5t-30 -12.5t-13 -29.5h-42q0 35 25 60t60 25zM256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5
+t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM235 128v43h42v-43h-42z" />
+    <glyph glyph-name="uniE8FE" unicode="reorder" 
+d="M64 405h384v-42h-384v42zM64 277v43h384v-43h-384zM64 107v42h384v-42h-384zM64 192v43h384v-43h-384z" />
+    <glyph glyph-name="uniE8FF" unicode="zoom_in" 
+d="M256 299h-43v-43h-21v43h-43v21h43v43h21v-43h43v-21zM203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5
+q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE900" unicode="zoom_out" 
+d="M149 320h107v-21h-107v21zM203 213q40 0 68 28t28 68t-28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28zM331 213l106 -106l-32 -32l-106 106v17l-6 6q-38 -33 -90 -33q-58 0 -98.5 40t-40.5 98t40.5 98.5t98.5 40.5t98 -40.5t40 -98.5q0 -52 -33 -90l6 -6h17z" />
+    <glyph glyph-name="uniE902" unicode="http" 
+d="M459 267v21h-43v-21h43zM459 320q13 0 22.5 -9.5t9.5 -22.5v-21q0 -13 -9.5 -22.5t-22.5 -9.5h-43v-43h-32v128h75zM267 288v32h96v-32h-32v-96h-32v96h-32zM149 288v32h96v-32h-32v-96h-32v96h-32zM96 277v43h32v-128h-32v53h-43v-53h-32v128h32v-43h43z" />
+    <glyph glyph-name="uniE903" unicode="event_seat" 
+d="M363 235h-214v170q0 17 13 30t30 13h128q17 0 30 -13t13 -30v-170zM43 299h64v-64h-64v64zM405 299h64v-64h-64v64zM85 64v128h342v-128h-64v64h-214v-64h-64z" />
+    <glyph glyph-name="uniE904" unicode="flight_land" 
+d="M299 204q-82 23 -206 55l-34 10v110l31 -8l20 -50l106 -28v176l41 -11l59 -192l113 -30q13 -4 19.5 -15.5t3.5 -24.5q-4 -13 -15 -19t-24 -3zM53 107h406v-43h-406v43z" />
+    <glyph glyph-name="uniE905" unicode="flight_takeoff" 
+d="M471 306q3 -13 -3.5 -24t-19.5 -15q-124 -33 -206 -55l-113 -30l-34 -10l-56 96l31 8l42 -32l106 28l-88 153l41 11l147 -137l114 30q13 4 24.5 -3t14.5 -20zM53 107h406v-43h-406v43z" />
+    <glyph glyph-name="uniE906" unicode="play_for_work" 
+d="M128 213h43q0 -35 25 -60t60 -25t60 25t25 60h43q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5zM235 405h42v-119h75l-96 -96l-96 96h75v119z" />
+    <glyph glyph-name="uniE908" unicode="gif" 
+d="M405 288h-64v-21h43v-32h-43v-43h-32v128h96v-32zM192 320q9 0 15 -6t6 -15v-11h-74v-64h42v32h32v-43q0 -9 -6 -15t-15 -6h-64q-9 0 -15 6t-6 15v86q0 9 6 15t15 6h64zM245 320h32v-128h-32v128z" />
+    <glyph glyph-name="uniE909" unicode="indeterminate_check_box" 
+d="M363 235v42h-214v-42h214zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniE90A" unicode="offline_pin" 
+d="M220 213l143 143l-30 30l-113 -113l-41 41l-30 -30zM363 128v43h-214v-43h214zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE90B" unicode="all_out" 
+d="M343 170q34 34 34 82.5t-34 82.5t-82.5 34t-82.5 -34t-34 -82.5t34 -82.5t82.5 -34t82.5 34zM366 358q44 -44 44 -106t-44 -105t-106 -43t-105 43t-43 105t43 106t105 44t106 -44zM90 338v85h85zM175 82h-85v85zM431 167v-85h-85zM346 423h85v-85z" />
+    <glyph glyph-name="uniE90C" unicode="copyright" 
+d="M256 85q70 0 120.5 50.5t50.5 120.5t-50.5 120.5t-120.5 50.5t-120.5 -50.5t-50.5 -120.5t50.5 -120.5t120.5 -50.5zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5zM253 317q-40 0 -40 -58v-6
+q0 -58 40 -58q15 0 25 8.5t10 21.5h38q0 -25 -22 -44q-21 -18 -51 -18q-40 0 -61 24t-21 66v6q0 41 20 64q24 27 62 27q33 0 52 -19q21 -21 21 -49h-38q0 7 -3 13q-5 10 -7 12q-10 10 -25 10z" />
+    <glyph glyph-name="uniE90D" unicode="fingerprint" 
+d="M318 43h-3q-46 12 -79 45q-46 46 -46 111q0 26 19 44.5t46 18.5t46.5 -18.5t19.5 -44.5q0 -17 12.5 -29t31.5 -12t32 12t13 29q0 60 -45.5 103t-109.5 43q-46 0 -84 -23.5t-57 -62.5q-12 -25 -12 -60q0 -40 14 -77q3 -10 -7 -13t-13 6q-16 45 -16 84t15 69q21 45 64.5 72
+t95.5 27q73 0 124.5 -49.5t51.5 -118.5q0 -26 -19.5 -44t-46.5 -18t-46 18t-19 44q0 17 -13 29.5t-32 12.5t-31.5 -12.5t-12.5 -29.5q0 -56 40 -96q28 -28 70 -39q9 -1 7 -13q-2 -8 -10 -8zM265 199q0 -37 27.5 -64t68.5 -27q2 0 9 1t11.5 1t9 -1.5t5.5 -6.5q2 -11 -9 -13
+q-12 -2 -26 -2q-40 0 -66 19q-51 35 -51 93q0 11 11 11q10 0 10 -11zM208 47q-4 0 -7 3q-27 27 -43 57q-23 40 -23 92q0 47 35.5 81t85.5 34t85.5 -34t35.5 -81q0 -10 -11 -10t-11 10q0 39 -29 66.5t-70 27.5t-70 -27.5t-29 -66.5q0 -48 19 -82q13 -23 40 -52q8 -7 0 -15
+q-3 -3 -8 -3zM75 305q-7 0 -10 5t1 11q32 46 80 70q50 26 110 26t110 -26q49 -24 80 -69q6 -10 -3 -15q-10 -6 -15 3q-27 39 -72 62q-46 23 -100 23.5t-100 -23.5q-45 -23 -73 -63q-3 -4 -8 -4zM380 417q-4 0 -5 1q-59 30 -119 30q-65 0 -119 -30q-6 -3 -10.5 0.5t-4.5 9
+t5 9.5q58 32 129 32q65 0 129 -32q7 -4 5 -12t-10 -8z" />
+    <glyph glyph-name="uniE90E" unicode="gavel" 
+d="M82 310l120 -121l-60 -60l-121 120zM263 491l120 -121l-60 -60l-121 120zM112 340l60 60l302 -302l-60 -60zM21 64h256v-43h-256v43z" />
+    <glyph glyph-name="uniE90F" unicode="lightbulb_outline" 
+d="M317 233q46 32 46 87q0 44 -31.5 75.5t-75.5 31.5t-75.5 -31.5t-31.5 -75.5q0 -55 46 -87l18 -13v-49h86v49zM256 469q62 0 105.5 -43.5t43.5 -105.5q0 -78 -64 -122v-49q0 -9 -6 -15t-15 -6h-128q-9 0 -15 6t-6 15v49q-64 44 -64 122q0 62 43.5 105.5t105.5 43.5z
+M192 64v21h128v-21q0 -9 -6 -15t-15 -6h-86q-9 0 -15 6t-6 15z" />
+    <glyph glyph-name="uniE911" unicode="picture_in_picture_alt" 
+d="M448 106v300h-384v-300h384zM491 107q0 -17 -13 -30t-30 -13h-384q-17 0 -30 13t-13 30v299q0 17 13 29.5t30 12.5h384q17 0 30 -12.5t13 -29.5v-299zM405 277v-128h-170v128h170z" />
+    <glyph glyph-name="uniE912" unicode="important_devices" 
+d="M255 320h65l-53 -38l20 -62l-52 39l-53 -39l20 62l-53 38h65l21 64zM427 469q17 0 29.5 -12t12.5 -30v-107h-42v107h-384v-256h277v-43h-43v-43h43v-42h-171v42h43v43h-149q-18 0 -30.5 13t-12.5 30v256q0 18 12.5 30t30.5 12h384zM491 85v150h-107v-150h107zM491 277
+q9 0 15 -6t6 -15v-192q0 -9 -6 -15t-15 -6h-107q-9 0 -15 6t-6 15v192q0 9 6 15t15 6h107z" />
+    <glyph glyph-name="uniE913" unicode="touch_app" 
+d="M402 173q19 -9 19 -29v-4l-16 -113q-1 -12 -10 -19.5t-21 -7.5h-145q-13 0 -22 9l-106 106l17 17q7 7 17 7q1 0 2.5 -0.5t2.5 -0.5l73 -15v229q0 14 9.5 23t22.5 9t22.5 -9t9.5 -23v-128h17q3 0 11 -2zM192 272q-43 28 -43 80q0 40 28 68t68 28t68 -28t28 -68
+q0 -53 -42 -80v80q0 22 -16 37.5t-38 15.5t-37.5 -15.5t-15.5 -37.5v-80z" />
+    <glyph glyph-name="uniE914" unicode="accessible" 
+d="M274 128h44q-8 -37 -37 -61t-68 -24q-44 0 -75 31t-31 75q0 39 24 68t61 37v-44q-19 -7 -31 -23.5t-12 -37.5q0 -26 19 -45t45 -19q21 0 37.5 12t23.5 31zM213 318q0 24 21 38t43 1h1v-1q7 -3 13 -9l28 -31q36 -39 86 -39v-42q-56 0 -106 41v-73h64q17 0 29.5 -13
+t12.5 -30v-117h-42v106h-107q-17 0 -30 13t-13 30v126zM213 427q0 18 12.5 30t30.5 12t30.5 -12t12.5 -30t-12.5 -30.5t-30.5 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE915" unicode="compare_arrows" 
+d="M320 235l-85 85l85 85v-64h149v-42h-149v-64zM192 213v64l85 -85l-85 -85v64h-149v42h149z" />
+    <glyph glyph-name="uniE916" unicode="date_range" 
+d="M405 85v235h-298v-235h298zM405 427q17 0 30 -13t13 -30v-299q0 -17 -13 -29.5t-30 -12.5h-298q-18 0 -30.5 12.5t-12.5 29.5v299q0 17 12.5 30t30.5 13h21v42h43v-42h170v42h43v-42h21zM363 277v-42h-43v42h43zM277 277v-42h-42v42h42zM192 277v-42h-43v42h43z" />
+    <glyph glyph-name="uniE917" unicode="donut_large" 
+d="M277 108q48 7 84.5 43t43.5 84h64q-8 -80 -60 -132.5t-132 -59.5v65zM405 277q-7 48 -43.5 84t-84.5 43v65q80 -8 132 -60t60 -132h-64zM235 404q-50 -8 -89 -51t-39 -97t39 -97t89 -51v-65q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69v-65z" />
+    <glyph glyph-name="uniE918" unicode="donut_small" 
+d="M277 195q27 8 40 40h152q-8 -77 -60.5 -131t-131.5 -61v152zM317 277q-12 32 -40 40v152q79 -7 131.5 -61t60.5 -131h-152zM235 317q-17 -7 -30 -24t-13 -37t13 -37t30 -24v-152q-81 8 -136.5 69t-55.5 144t55.5 144t136.5 69v-152z" />
+    <glyph glyph-name="uniE919" unicode="line_style" 
+d="M64 427h384v-86h-384v86zM277 256v43h171v-43h-171zM64 256v43h171v-43h-171zM405 85v43h43v-43h-43zM320 85v43h43v-43h-43zM235 85v43h42v-43h-42zM149 85v43h43v-43h-43zM64 85v43h43v-43h-43zM341 171v42h107v-42h-107zM203 171v42h106v-42h-106zM64 171v42h107v-42
+h-107z" />
+    <glyph glyph-name="uniE91A" unicode="line_weight" 
+d="M64 427h384v-86h-384v86zM64 235v64h384v-64h-384zM64 85v22h384v-22h-384zM64 149v43h384v-43h-384z" />
+    <glyph glyph-name="uniE91B" unicode="motorcycle" 
+d="M405 149q26 0 45 19t19 45t-19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19zM167 192h-60v43h60q-7 19 -23.5 30.5t-36.5 11.5q-26 0 -45 -19t-19 -45t19 -45t45 -19q20 0 36.5 12t23.5 31zM415 319q41 -3 69 -33t28 -73q0 -45 -31 -75.5t-76 -30.5t-75.5 30.5t-30.5 75.5
+q0 20 6 38l-59 -59h-35q-8 -37 -36.5 -61t-67.5 -24q-45 0 -76 30.5t-31 75.5t31 76t76 31h247l-43 43h-76v42h94z" />
+    <glyph glyph-name="uniE91C" unicode="opacity" 
+d="M128 213h256q0 55 -38 93l-90 94l-90 -93q-38 -38 -38 -94zM377 341q50 -50 50 -120q0 -71 -50 -121t-121 -50t-121 50t-50 121q0 70 50 120l121 121z" />
+    <glyph glyph-name="uniE91D" unicode="pets" 
+d="M370 195l8.5 -8.5l8.5 -8.5t8.5 -9t8 -9.5t7.5 -9.5t6.5 -10t5 -11t3 -11.5t1 -12t-0.5 -12.5q-11 -42 -50 -50q-7 -1 -48.5 4t-69.5 5h-4q-28 0 -69.5 -5t-48.5 -4q-39 8 -50 50q-3 20 7 41t18.5 30t30.5 31q10 11 26.5 31t26.5 31q18 22 37 28q4 2 7 2q6 1 17 1
+q12 0 17 -1q3 0 7 -2q19 -6 37 -28q9 -11 26 -31t27 -31zM363 309q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5zM267 395q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM139 395
+q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM43 309q0 22 15.5 38t37.5 16t37.5 -16t15.5 -38t-15.5 -37.5t-37.5 -15.5t-37.5 15.5t-15.5 37.5z" />
+    <glyph glyph-name="uniE91E" unicode="pregnant_woman" 
+d="M341 235v-86h-64v-106h-64v106h-42v150q0 26 19 45t45 19t45 -19t19 -45q42 -17 42 -64zM192 427q0 18 12.5 30t30.5 12t30 -12t12 -30t-12 -30.5t-30 -12.5t-30.5 12.5t-12.5 30.5z" />
+    <glyph glyph-name="uniE91F" unicode="record_voice_over" 
+d="M428 469q62 -65 62 -150.5t-62 -147.5l-35 34q44 51 44 116.5t-44 113.5zM358 398q32 -35 32 -79t-32 -76l-36 36q14 19 14 41.5t-14 41.5zM192 192q54 0 112.5 -23.5t58.5 -61.5v-43h-342v43q0 38 58.5 61.5t112.5 23.5zM107 320q0 35 25 60t60 25t60 -25t25 -60
+t-25 -60t-60 -25t-60 25t-25 60z" />
+    <glyph glyph-name="uniE920" unicode="rounded_corner" 
+d="M448 341v-106h-43v106q0 26 -19 45t-45 19h-106v43h106q44 0 75.5 -31.5t31.5 -75.5zM64 64v43h43v-43h-43zM149 64v43h43v-43h-43zM235 64v43h42v-43h-42zM149 405v43h43v-43h-43zM64 405v43h43v-43h-43zM64 320v43h43v-43h-43zM64 149v43h43v-43h-43zM64 235v42h43v-42
+h-43zM405 149v43h43v-43h-43zM405 107h43v-43h-43v43z" />
+    <glyph glyph-name="uniE921" unicode="rowing" 
+d="M448 64l-64 -64l-64 64v32l-151 151q-7 -1 -20 -1v46q26 -1 54 11.5t46 31.5l30 33q16 16 35 16h1q19 0 33.5 -14t14.5 -34v-123q0 -26 -20 -46l-76 76v49q-20 -17 -49 -30l134 -134h32zM320 491q17 0 30 -13t13 -30t-13 -30t-30 -13t-30 13t-13 30t13 30t30 13zM181 203
+l54 -54h-43l-75 -74l-32 32z" />
+    <glyph glyph-name="uniE922" unicode="timeline" 
+d="M491 341q0 -17 -13 -29.5t-30 -12.5q-8 0 -11 1l-76 -76q2 -6 2 -11q0 -17 -13 -29.5t-30 -12.5t-30 12.5t-13 29.5q0 5 2 11l-55 55q-6 -2 -11 -2t-11 2l-97 -97q2 -6 2 -11q0 -17 -13 -30t-30 -13t-30 13t-13 30t13 29.5t30 12.5q8 0 11 -1l97 97q-1 3 -1 11
+q0 17 12.5 30t29.5 13t30 -13t13 -30q0 -8 -1 -11l54 -54q3 1 11 1t11 -1l76 75q-2 6 -2 11q0 17 13 30t30 13t30 -13t13 -30z" />
+    <glyph glyph-name="uniE923" unicode="update" 
+d="M267 341v-90l74 -45l-15 -26l-91 55v106h32zM448 296h-145l59 60q-44 44 -105.5 44.5t-105.5 -42.5q-43 -44 -43 -104t43 -104t105 -44t106 44q43 43 43 104h43q0 -79 -56 -134q-56 -56 -136 -56t-136 56q-56 55 -56 133.5t56 134.5t135 56t135 -56l58 60v-152z" />
+    <glyph glyph-name="uniE924" unicode="watch_later" 
+d="M346 166l17 28l-96 58v111h-32v-128zM256 469q88 0 150.5 -62.5t62.5 -150.5t-62.5 -150.5t-150.5 -62.5t-150.5 62.5t-62.5 150.5t62.5 150.5t150.5 62.5z" />
+    <glyph glyph-name="uniE925" unicode="pan_tool" 
+d="M491 395v-310q0 -35 -25.5 -60t-60.5 -25h-155q-36 0 -61 25l-168 171q27 26 28 26q7 6 17 6q7 0 13 -3l92 -52v254q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-150h21v203q0 14 9 23t23 9t23 -9t9 -23v-203h21v182q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5v-182
+h22v118q0 13 9.5 22.5t22.5 9.5t22.5 -9.5t9.5 -22.5z" />
+    <glyph glyph-name="uniE926" unicode="euro_symbol" 
+d="M320 117q51 0 90 34l38 -38q-54 -49 -128 -49q-62 0 -111.5 36t-69.5 92h-75v43h65q-1 7 -1 21t1 21h-65v43h75q20 56 69.5 92t111.5 36q74 0 128 -49l-38 -38q-39 34 -90 34q-39 0 -72 -20.5t-51 -54.5h123v-43h-137q-2 -14 -2 -21t2 -21h137v-43h-123
+q18 -34 50.5 -54.5t72.5 -20.5z" />
+    <glyph glyph-name="uniE927" unicode="g_translate" 
+d="M448 85v278q0 9 -6 15t-15 6h-188l25 -86h41v22h23v-22h77v-22h-27q-10 -39 -41 -75l58 -57l-15 -16l-58 57l-19 -19l17 -59l-43 -43h150q9 0 15 6t6 15zM298 253q9 -19 24 -36q12 14 20 28.5t10 22.5l3 8h-85l7 -23h21zM282 237l13 -47l12 11q-14 15 -25 36zM237 286
+q0 11 -2 15h-84v-33h47q-3 -12 -14 -22t-31 -10q-21 0 -36 15.5t-15 36.5t15 36.5t36 15.5q19 0 32 -13l2 -1l26 25l-2 1q-25 23 -58 23q-36 0 -61.5 -25.5t-25.5 -61.5t25.5 -61.5t61.5 -25.5q37 0 60.5 24t23.5 61zM427 405q17 0 29.5 -12.5t12.5 -29.5v-278
+q0 -17 -12.5 -29.5t-29.5 -12.5h-171l-21 64h-150q-17 0 -29.5 12.5t-12.5 29.5v278q0 17 12.5 29.5t29.5 12.5h128l19 -64h195z" />
+    <glyph glyph-name="uniE928" unicode="remove_shopping_cart" 
+d="M149 128q17 0 30 -13t13 -30t-13 -29.5t-30 -12.5t-29.5 12.5t-12.5 29.5t12.5 30t29.5 13zM332 235l-192 192h287q9 0 15 -6.5t6 -15.5q0 -5 -3 -10l-76 -138q-12 -22 -37 -22zM158 192h108l-43 43h-50l-19 -35l-1 -3q0 -5 5 -5zM485 27l-27 -27l-61 61q-13 -18 -34 -18
+q-17 0 -30 12.5t-13 29.5q0 22 18 35l-30 29h-159q-17 0 -29.5 13t-12.5 30q0 10 5 20l29 53l-47 99l-94 94l27 27z" />
+    <glyph glyph-name="uniE929" unicode="restore_page" 
+d="M256 128q44 0 75.5 31.5t31.5 75.5t-31.5 75t-75.5 31q-58 0 -90 -49l-27 28v-85h85l-34 34q20 40 66 40q31 0 53 -21.5t22 -52.5t-22 -53t-53 -22q-39 0 -61 32h-37q12 -29 38.5 -46.5t59.5 -17.5zM299 469l128 -128v-256q0 -17 -13 -29.5t-30 -12.5h-256
+q-17 0 -30 12.5t-13 29.5l1 342q0 17 12.5 29.5t29.5 12.5h171z" />
+    <glyph glyph-name="uniE92A" unicode="speaker_notes_off" 
+d="M427 469q17 0 29.5 -12.5t12.5 -29.5v-256q0 -17 -12 -29.5t-29 -13.5l-149 149h105v43h-148l-21 21h169v43h-171v-41l-126 126h340zM128 277h43l-43 43v-43zM171 213v43h-43v-43h43zM27 475l442 -442l-27 -27l-122 122h-192l-85 -85v362l-43 43z" />
+    <glyph glyph-name="uniE92B" unicode="delete_forever" 
+d="M331 427h74v-43h-298v43h74l22 21h106zM180 259l46 -46l-45 -45l30 -30l45 45l45 -45l30 30l-45 45l45 46l-30 30l-45 -46l-45 46zM128 107v256h256v-256q0 -17 -13 -30t-30 -13h-170q-17 0 -30 13t-13 30z" />
+    <glyph glyph-name="uniEB3B" unicode="ac_unit" 
+d="M469 277v-42h-89l69 -69l-30 -31l-99 100h-43v-43l100 -99l-31 -30l-69 69v-89h-42v89l-69 -69l-31 30l100 99v43h-43l-99 -100l-30 31l69 69h-89v42h89l-69 69l30 31l99 -100h43v43l-100 99l31 30l69 -69v89h42v-89l69 69l31 -30l-100 -99v-43h43l99 100l30 -31l-69 -69
+h89z" />
+    <glyph glyph-name="uniEB3C" unicode="airport_shuttle" 
+d="M320 277h107l-86 86h-21v-86zM373 139q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM277 277v86h-85v-86h85zM128 139q14 0 23 9.5t9 22.5t-9 22.5t-23 9.5t-23 -9.5t-9 -22.5t9 -22.5t23 -9.5zM64 277h85v86h-85v-86z
+M363 405l128 -128v-106h-54q0 -26 -19 -45t-45 -19t-45 19t-19 45h-117q0 -26 -19 -45t-45 -19t-45 19t-19 45h-43v192q0 18 12.5 30t30.5 12h299z" />
+    <glyph glyph-name="uniEB3D" unicode="all_inclusive" 
+d="M397 371q48 0 81.5 -34t33.5 -81t-33.5 -81t-81.5 -34t-82 34l-27 24l32 28l25 -21q22 -22 52 -22t51 21t21 51t-21 51t-51 21t-51 -21q-71 -62 -90 -80l-60 -53q-33 -33 -81 -33t-81.5 34t-33.5 81t33.5 81t81.5 34t82 -34l27 -24l-33 -28l-24 21q-22 22 -52 22t-51 -21
+t-21 -51t21 -51t51 -21t51 21q71 62 90 80l60 53q33 33 81 33z" />
+    <glyph glyph-name="uniEB3E" unicode="beach_access" 
+d="M371 324q-50 50 -115.5 66t-127.5 -5q48 6 105.5 -18.5t106.5 -73.5l-122 -122q-49 49 -73.5 106.5t-18.5 105.5q-21 -62 -5 -127.5t66 -115.5l-61 -61q-63 63 -63 152.5t63 152.5q0 1 1 1q70 70 168 62q81 -6 137 -62zM279 201l31 31l137 -138l-31 -30z" />
+    <glyph glyph-name="uniEB3F" unicode="business_center" 
+d="M299 363v42h-86v-42h86zM427 363q17 0 29.5 -13t12.5 -30v-64q0 -17 -12.5 -30t-29.5 -13h-128v43h-86v-43h-128q-18 0 -30 12.5t-12 30.5v64q0 17 12.5 30t29.5 13h85v42l43 43h85l43 -43v-42h86zM213 171h86v21h149v-85q0 -18 -12.5 -30.5t-30.5 -12.5h-298
+q-18 0 -30.5 12.5t-12.5 30.5v85h149v-21z" />
+    <glyph glyph-name="uniEB40" unicode="casino" 
+d="M352 320q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM352 128q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM256 224q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM160 320q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9z
+M160 128q14 0 23 9t9 23t-9 23t-23 9t-23 -9t-9 -23t9 -23t23 -9zM405 448q17 0 30 -13t13 -30v-298q0 -17 -13 -30t-30 -13h-298q-17 0 -30 13t-13 30v298q0 17 13 30t30 13h298z" />
+    <glyph glyph-name="uniEB41" unicode="child_care" 
+d="M160 213h192q-12 -29 -38 -46.5t-58 -17.5t-58 17.5t-38 46.5zM256 107q51 0 90.5 30t52.5 77q1 0 3 -0.5t3 -0.5q17 0 30 13t13 30t-13 30t-30 13q-1 0 -3 -0.5t-3 -0.5q-13 47 -52.5 77t-90.5 30t-90.5 -30t-52.5 -77q-1 0 -3 0.5t-3 0.5q-17 0 -30 -13t-13 -30t13 -30
+t30 -13q1 0 3 0.5t3 0.5q13 -47 52.5 -77t90.5 -30zM489 242q-4 -24 -20 -42.5t-39 -25.5q-22 -47 -69.5 -78.5t-104.5 -31.5t-104 31.5t-69 78.5q-23 7 -39.5 25.5t-20.5 42.5q-2 8 -2 14t2 14q4 24 20.5 42.5t39.5 25.5q17 38 46 62q54 48 127 48q58 0 104.5 -31t68.5 -79
+q23 -7 39.5 -25.5t20.5 -42.5q2 -8 2 -14t-2 -14zM176 288q0 11 8 19t19 8t18.5 -8t7.5 -19t-7.5 -19t-18.5 -8t-19 8t-8 19zM283 288q0 11 7.5 19t18.5 8t19 -8t8 -19t-8 -19t-19 -8t-18.5 8t-7.5 19z" />
+    <glyph glyph-name="uniEB42" unicode="child_friendly" 
+d="M363 85q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM171 85q13 0 22.5 9.5t9.5 22.5t-9.5 22.5t-22.5 9.5t-22.5 -9.5t-9.5 -22.5t9.5 -22.5t22.5 -9.5zM412 173q25 -23 25 -56q0 -31 -21.5 -52.5t-52.5 -21.5
+q-28 0 -49 18.5t-25 45.5h-45q-4 -27 -24.5 -45.5t-48.5 -18.5q-31 0 -53 21.5t-22 52.5q0 44 39 66l-45 94h-47v43h74l20 -43h311q0 -57 -36 -104zM277 469q71 0 121 -50t50 -120h-171v170z" />
+    <glyph glyph-name="uniEB43" unicode="fitness_center" 
+d="M439 195l30 -31l-45 -45l30 -31l-30 -30l-31 30l-45 -45l-31 30l-30 -30l-31 30l76 76l-183 183l-76 -76l-30 31l30 30l-30 31l45 45l-30 31l30 30l31 -30l45 45l31 -30l30 30l31 -30l-76 -76l183 -183l76 76l30 -31z" />
+    <glyph glyph-name="uniEB44" unicode="free_breakfast" 
+d="M85 107h342v-43h-342v43zM427 341v64h-43v-64h43zM427 448q18 0 30 -12.5t12 -30.5v-64q0 -17 -12 -29.5t-30 -12.5h-43v-64q0 -35 -25 -60.5t-60 -25.5h-128q-35 0 -60.5 25.5t-25.5 60.5v213h342z" />
+    <glyph glyph-name="uniEB45" unicode="golf_course" 
+d="M363 386l-128 -66v-193q46 -2 76 -14t30 -28q0 -17 -37.5 -29.5t-90.5 -12.5t-90.5 12.5t-37.5 29.5q0 25 64 37v-37h43v384zM384 96q0 14 9 23t23 9t23 -9t9 -23t-9 -23t-23 -9t-23 9t-9 23z" />
+    <glyph glyph-name="uniEB46" unicode="hot_tub" 
+d="M313 387q33 -32 27 -79l-1 -9h-41l3 12q5 27 -16 48q-34 34 -28 80l1 9h41l-2 -13q-5 -27 14 -47zM398 387q34 -33 28 -79l-2 -9h-40l2 12q5 27 -14 47l-2 1q-34 34 -28 80l2 9h40l-2 -13q-5 -27 14 -47zM405 85v128h-42v-128h42zM320 85v128h-43v-128h43zM235 85v128
+h-43v-128h43zM149 85v128h-42v-128h42zM238 256h231v-171q0 -17 -12.5 -29.5t-29.5 -12.5h-342q-17 0 -29.5 12.5t-12.5 29.5v171h64v16q0 20 14.5 34t33.5 14q20 0 36 -16l29 -33q4 -5 18 -15zM107 384q0 18 12 30.5t30 12.5t30.5 -12.5t12.5 -30.5t-12.5 -30.5
+t-30.5 -12.5t-30 12.5t-12 30.5z" />
+    <glyph glyph-name="uniEB47" unicode="kitchen" 
+d="M171 256h42v-107h-42v107zM171 405h42v-64h-42v64zM384 320v107h-256v-107h256zM384 85v193h-256v-193h256zM384 469q18 0 30.5 -12t12.5 -30v-342q0 -17 -13 -29.5t-30 -12.5h-256q-17 0 -30 12.5t-13 29.5v342q0 18 12.5 30t30.5 12h256z" />
+    <glyph glyph-name="uniEB48" unicode="pool" 
+d="M299 395q0 22 15.5 37.5t37.5 15.5t37.5 -15.5t15.5 -37.5t-15.5 -38t-37.5 -16t-37.5 16t-15.5 38zM185 256q-12 0 -25 8q-4 3 -16 8l69 69l-21 22q-32 32 -85 32v53q41 0 67 -9.5t50 -33.5l137 -136q-6 -4 -9 -5q-13 -8 -25 -8q-11 0 -24 8q-22 13 -47 13t-47 -13
+q-13 -8 -24 -8zM469 160q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14v43q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7
+q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7v-43zM469 64q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14q-23 0 -46 14q-11 7 -25 7q-13 0 -24 -7q-23 -14 -47 -14t-47 14q-11 7 -24 7q-14 0 -25 -7q-23 -14 -46 -14v43
+q13 0 24 7q23 14 47 14q23 0 46 -14q11 -7 25 -7q13 0 24 7q23 14 47 14t47 -14q11 -7 24 -7q14 0 25 7q23 14 46 14q24 0 47 -14q11 -7 24 -7v-43z" />
+    <glyph glyph-name="uniEB49" unicode="room_service" 
+d="M295 346q63 -13 106 -61.5t47 -113.5h-384q4 65 47 113.5t106 61.5q-4 10 -4 17q0 17 13 29.5t30 12.5t30 -12.5t13 -29.5q0 -7 -4 -17zM43 149h426v-42h-426v42z" />
+    <glyph glyph-name="uniEB4A" unicode="smoke_free" 
+d="M363 172l-63 63h63v-63zM309 326q-29 0 -50 21.5t-21 50.5t21 50t50 21v-32q-17 0 -28 -10.5t-11 -26.5q0 -17 11.5 -30t27.5 -13h33q30 0 52 -19.5t22 -47.5v-34h-32v27q0 20 -12.5 31.5t-29.5 11.5h-33zM402 408q30 -14 48.5 -43t18.5 -65v-44h-32v44q0 36 -24.5 61.5
+t-60.5 25.5v32q16 0 27.5 11.5t11.5 28.5h32q0 -30 -21 -51zM384 235h32v-64h-32v64zM437 235h32v-64h-32v64zM43 384l26 27l363 -363l-27 -27l-149 150h-213v64h149z" />
+    <glyph glyph-name="uniEB4B" unicode="smoking_rooms" 
+d="M342 294q31 0 52.5 -19t21.5 -48v-35h-32v28q0 20 -12.5 31.5t-29.5 11.5h-33q-29 0 -50 21.5t-21 50.5t21 50t50 21v-32q-17 0 -28 -10.5t-11 -26.5q0 -17 11.5 -30t27.5 -13h33zM402 347q30 -14 48.5 -43t18.5 -64v-48h-32v48q0 36 -24.5 61t-60.5 25v32
+q16 0 27.5 11.5t11.5 28.5q0 16 -11.5 27.5t-27.5 11.5v32q29 0 50 -21t21 -50q0 -30 -21 -51zM384 171h32v-64h-32v64zM437 171h32v-64h-32v64zM43 171h320v-64h-320v64z" />
+    <glyph glyph-name="uniEB4C" unicode="spa" 
+d="M330 307q-42 -23 -74 -57q-32 34 -74 57q8 95 75 162q67 -67 73 -162zM43 299q68 0 124 -33t89 -84q33 51 89 84t124 33q0 -84 -47.5 -151t-123.5 -94q-17 -6 -42 -11q-21 3 -42 11q-76 27 -123.5 94t-47.5 151z" />
+    <glyph glyph-name="u10FFFD" unicode="goat" 
+d="M511 318q1 -1 1 -2.5t-1 -2.5l-25 -32q-2 -2 -4 -2l-15 3l-7 -22q-2 -4 -6.5 -4t-6.5 4l-14 30l-23 5l-47 -112l17 -136q0 -4 -4 -4h-20q-2 0 -4 3l-20 81l-10 17l-25 -98q0 -3 -4 -3h-21q-4 0 -4 4l23 135h-135l-35 -66l8 -68q2 -5 -4 -5h-20q-3 0 -4 2l-28 102l-34 -39
+l6 -60q2 -5 -4 -5h-22q-4 0 -4 2l-13 56l21 82v144q-23 9 -23 30h274q48 -1 95 33q-8 22 6 36q28 -20 36 -25q7 -4 11.5 1.5t2.5 12.5q-9 28 -45 42q-1 0 -3 1q-12 3 -10 8q0 3 4 3q29 -4 49.5 -22.5t27.5 -35.5l4 -3q3 -4 6.5 -8t6 -11.5t1.5 -15.5q0 -5 2 -7z" />
+    <glyph glyph-name=".notdef" 
+d="M17 0v341h136v-341h-136zM34 17h102v307h-102v-307z" />
+    <glyph glyph-name="glyph1" horiz-adv-x="0" 
+ />
+    <glyph glyph-name="glyph2" 
+ />
+    <glyph glyph-name="zero" unicode="0" 
+d="M0 0z" />
+    <glyph glyph-name="one" unicode="1" 
+d="M0 0z" />
+    <glyph glyph-name="two" unicode="2" 
+d="M0 0z" />
+    <glyph glyph-name="three" unicode="3" 
+d="M0 0z" />
+    <glyph glyph-name="four" unicode="4" 
+d="M0 0z" />
+    <glyph glyph-name="five" unicode="5" 
+d="M0 0z" />
+    <glyph glyph-name="six" unicode="6" 
+d="M0 0z" />
+    <glyph glyph-name="seven" unicode="7" 
+d="M0 0z" />
+    <glyph glyph-name="eight" unicode="8" 
+d="M0 0z" />
+    <glyph glyph-name="nine" unicode="9" 
+d="M0 0z" />
+    <glyph glyph-name="underscore" unicode="_" 
+d="M0 0z" />
+    <glyph glyph-name="a" unicode="a" 
+d="M0 0z" />
+    <glyph glyph-name="b" unicode="b" 
+d="M0 0z" />
+    <glyph glyph-name="c" unicode="c" 
+d="M0 0z" />
+    <glyph glyph-name="d" unicode="d" 
+d="M0 0z" />
+    <glyph glyph-name="e" unicode="e" 
+d="M0 0z" />
+    <glyph glyph-name="f" unicode="f" 
+d="M0 0z" />
+    <glyph glyph-name="g" unicode="g" 
+d="M0 0z" />
+    <glyph glyph-name="h" unicode="h" 
+d="M0 0z" />
+    <glyph glyph-name="i" unicode="i" 
+d="M0 0z" />
+    <glyph glyph-name="j" unicode="j" 
+d="M0 0z" />
+    <glyph glyph-name="k" unicode="k" 
+d="M0 0z" />
+    <glyph glyph-name="l" unicode="l" 
+d="M0 0z" />
+    <glyph glyph-name="m" unicode="m" 
+d="M0 0z" />
+    <glyph glyph-name="n" unicode="n" 
+d="M0 0z" />
+    <glyph glyph-name="o" unicode="o" 
+d="M0 0z" />
+    <glyph glyph-name="p" unicode="p" 
+d="M0 0z" />
+    <glyph glyph-name="q" unicode="q" 
+d="M0 0z" />
+    <glyph glyph-name="r" unicode="r" 
+d="M0 0z" />
+    <glyph glyph-name="s" unicode="s" 
+d="M0 0z" />
+    <glyph glyph-name="t" unicode="t" 
+d="M0 0z" />
+    <glyph glyph-name="u" unicode="u" 
+d="M0 0z" />
+    <glyph glyph-name="v" unicode="v" 
+d="M0 0z" />
+    <glyph glyph-name="w" unicode="w" 
+d="M0 0z" />
+    <glyph glyph-name="x" unicode="x" 
+d="M0 0z" />
+    <glyph glyph-name="y" unicode="y" 
+d="M0 0z" />
+    <glyph glyph-name="z" unicode="z" 
+d="M0 0z" />
+    <glyph glyph-name="uniEC2E" unicode="&#xec2e;" 
+d="M142 313zM323 220q41 -17 68.5 -53t33.5 -82h-41q-8 47 -44 78t-84 31t-84.5 -31t-43.5 -78h-42q6 45 33.5 81.5t68.5 54.5q-21 16 -33.5 40t-12.5 52q0 47 33 80t80.5 33t80.5 -33t33 -81q0 -27 -12.5 -51.5t-33.5 -40.5zM183 313q0 -30 21.5 -51.5t51.5 -21.5
+t51.5 21.5t21.5 51.5t-21.5 51.5t-51.5 21.5t-51.5 -21.5t-21.5 -51.5v0z" />
+    <glyph glyph-name="uniEDDE" unicode="&#xedde;" 
+d="M405 427h-53q-31.999 -1.00098 -53 -22q-21 -21 -22 -53v-53h-42v-64h42v-150h64v150h64v64h-64v42q1.00098 9 7 15q6 6 15 7h42v64zM427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDDF" unicode="&#xeddf;" 
+d="M405 107v121q0 31.999 -21.5 53q-21.499 21 -52.5 22q-15 0 -30 -8q-15 -7.99902 -24 -22v26h-64v-192h64v113q1.00098 13.001 10 22q9 9 22.5 9t22.5 -9t9 -22v-113h64zM139 335q16.001 0 27 11q10.999 10.999 11 27.5q0 16.5 -11 27.5q-10.999 10.999 -27.5 11
+q-16.5 0 -27.5 -11q-10.999 -10.999 -11 -27.5q0 -16.5 11 -27.5q10.999 -10.999 28 -11zM171 107v192h-64v-192h64zM427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDE0" unicode="&#xede0;" 
+d="M427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342zM427 256h-43v43h-21v-43h-43v-21h43v-43h21v43h43v21zM192 271v-36h61q-1.99902 -13.001 -15.5 -29q-13.5 -16.001 -45.5 -17q-28.001 1.00098 -46.5 20
+q-18.499 19.001 -18.5 47q0 28.001 18.5 47q18.499 19.001 46.5 20q16.001 0 26.5 -5.5q10.501 -5.50098 16.5 -11.5l29 28q-28.001 28.001 -72 29q-46.001 -1.00098 -76 -31q-30 -30 -30 -76q0 -46.001 30 -76q30 -30 76 -30q46.001 0 74 28q28.001 28.001 28 75
+q1.00098 10.001 -2 18h-100z" />
+    <glyph glyph-name="uniEDE1" unicode="&#xede1;" 
+d="M378 309q7.99902 6 15.5 14q7.49805 7.99707 11.5 16q-13.001 -7.00098 -30 -9q16.001 10.999 24 32q-16.001 -10.001 -37 -14q-28.999 28.999 -66.5 11.5q-37.5 -17.501 -31.5 -63.5q-40.999 3 -69 19.5t-49 39.5q-10.999 -21 -4.5 -44q6.49902 -22.999 21.5 -32
+q-13.999 1.00098 -24 7q1.00098 -24 13 -37.5q12 -13.5 31 -19.5q-12 -3 -24 -1q10.999 -34.999 52 -40q-15 -13.001 -38 -19.5q-22.999 -6.49902 -45 -3.5q18 -12 40 -19.5q22.001 -7.5 51 -6.5q72 4 114.5 49.5q42.499 45.499 44.5 120.5zM427 465q18 0 30 -12t12 -30
+v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342z" />
+    <glyph glyph-name="uniEDE0" unicode="&#xede0;" 
+d="M427 469q18 0 30 -12t12 -30v-342q0 -18 -12 -30t-30 -12h-342q-18 0 -30 12t-12 30v342q0 18 12 30t30 12h342zM427 256h-43v43h-21v-43h-43v-21h43v-43h21v43h43v21zM192 271v-36h61q-2 -13 -15 -29q-14 -16 -46 -17q-1.13636 -0.0454545 -2.26446 -0.0454545
+q-23.6901 0 -43.7355 20.0455q-19 19 -19 47t18 47q18 18 47 20q2.14286 0.142857 4.20408 0.142857q12.3673 0 21.7959 -5.14286q11 -6 17 -12l29 28q-28 28 -72 29q-1.33333 0.030303 -2.65565 0.030303q-42.314 0 -73.3444 -31.0303q-30 -30 -30 -76t30 -76t76 -30t74 28
+t28 75q0 13 -2 18h-100z" />
+  </font>
+</defs></svg>
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.ttf b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..567bc7822e146735ca48da310f6839b1f39ce3ce
Binary files /dev/null and b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.ttf differ
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff
new file mode 100644
index 0000000000000000000000000000000000000000..ceb41a11158b2497932b035bc1c46258beaa8130
Binary files /dev/null and b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff differ
diff --git a/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff2 b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..6fbc47406ef0bcbe8b57b6da13670dcb60fd0f2d
Binary files /dev/null and b/plugins/easy_mindmup/assets/fonts/EasyMaterialIcons-Regular.woff2 differ
diff --git a/plugins/easy_mindmup/assets/images/person.gif b/plugins/easy_mindmup/assets/images/person.gif
new file mode 100644
index 0000000000000000000000000000000000000000..a0e72aff4e540308d048fd10a07f48a64d32b654
Binary files /dev/null and b/plugins/easy_mindmup/assets/images/person.gif differ
diff --git a/plugins/easy_mindmup/assets/javascripts/after_change.js b/plugins/easy_mindmup/assets/javascripts/after_change.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a653fa12d94c32a070b1af128c4eb50c00dbbd7
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/after_change.js
@@ -0,0 +1,70 @@
+(function () {
+  /**
+   * Class responsible for all callbacks after any change in model
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function AfterChange(ysy) {
+    this.ysy = ysy;
+    this.init();
+  }
+
+  AfterChange.prototype.init = function () {
+    var self = this;
+    var executeByEvent = function (method, args) {
+      if (!self[method]) return;
+      return self[method].apply(self, args);
+    };
+    this.ysy.eventBus.register("TreeLoaded", /** @param {RootIdea} idea*/function (idea) {
+      idea.addEventListener('changed', function (method, args) {
+        if (method === "batch") {
+          if (args && args.length) {
+            for (var i = 0; i < args.length; i++) {
+              var arg = args[i];
+              if (arg && arg.length) {
+                executeByEvent(arg[0], arg.slice(1));
+              }
+            }
+          }
+        } else {
+          executeByEvent(method, args);
+        }
+      }, 5);
+    });
+  };
+  AfterChange.prototype.removeSubIdea = function (id) {
+    var node = this.ysy.getLayoutNode(id);
+    if (!node) return;
+    var saver = this.ysy.saver;
+    if (saver.deleteStack) {
+      saver.deleteStack.push(new window.easyMindMupClasses.ModelEntity().extend(node));
+    }
+  };
+  AfterChange.prototype.paste = function (parentIdeaId, jsonToPaste, newIdeaId) {
+    this._upgradeToModelEntity(newIdeaId);
+  };
+  AfterChange.prototype.insertIntermediate = function (inFrontOfIdeaId, title, newIdeaId) {
+    var idea = this._upgradeToModelEntity(newIdeaId);
+    idea.attr.isFresh = true;
+  };
+  AfterChange.prototype.addSubIdea = function (parentId, ideaTitle, ideaId) {
+    var idea = this._upgradeToModelEntity(ideaId);
+    idea.attr.isFresh = true;
+  };
+  /**
+   * Function, which converts Object generated by MindMup component into ModelEntity
+   * @param {number} ideaId
+   * @private
+   */
+  AfterChange.prototype._upgradeToModelEntity = function (ideaId) {
+    /** @type {MindMup} */
+    var ysy = this.ysy;
+    // var rootIdea = ysy.idea;
+    var idea = ysy.util.findWhere(ysy.idea, function (/** @type {ModelEntity} */ node) {
+      return node.id === ideaId;
+    });
+    return ysy.upgradeToModelEntity(idea);
+  };
+
+  window.easyMindMupClasses.AfterChange = AfterChange;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/autosave.js b/plugins/easy_mindmup/assets/javascripts/autosave.js
new file mode 100644
index 0000000000000000000000000000000000000000..30771e190701a25663e0b47584e3fa9c625fca67
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/autosave.js
@@ -0,0 +1,81 @@
+(function () {
+  /**
+   * Autosave feature - there are three phases - long, render and short
+   * 'hidden' period is there for off-screen detection to prevent saving in background
+   * In 'short' period, any user action triggers Save.
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Autosave(ysy) {
+    this.phase = null;
+    this.ysy = ysy;
+    this.shortTimeout = 0;
+    this.longTimeout = 0;
+    this.init();
+  }
+
+  Autosave.prototype.longPeriod = 10 * 60 * 1000;
+  Autosave.prototype.shortPeriod = 2 * 60 * 1000;
+  // Autosave.prototype.longPeriod = 3 * 1000;
+  // Autosave.prototype.shortPeriod = 2 * 1000;
+
+  Autosave.prototype.init = function () {
+    var self = this;
+    var ysy = this.ysy;
+    var testing = false;
+    /** called when change in mindMap is detected and at the end of short period */
+    var changeWatcher = function () {
+      if (self.phase !== 'short') return;
+      // probably unnecessary, but can prevent multiple firing
+      ysy.idea.removeEventListener('changed', self.changeWatcher);
+      if (testing) {
+        ysy.log.debug(new Date().toISOString() + " saving", "autosave");
+        startLongPhase(); // "TreeLoaded" event already calls startLongPhase()
+      } else {
+        ysy.saver.save(true);
+      }
+    };
+    self.changeWatcher = changeWatcher;
+
+    var startShortPhase = function () {
+      if (self.phase === 'short') return;
+      if (self.longTimeout) {
+        window.clearTimeout(self.longTimeout);
+        self.longTimeout = 0;
+      }
+      self.phase = 'short';
+      ysy.log.debug("short period", "autosave");
+      ysy.idea.removeEventListener('changed', changeWatcher);
+      ysy.idea.addEventListener('changed', changeWatcher);
+      self.shortTimeout = setTimeout(changeWatcher, self.shortPeriod);
+    };
+    var userReturned = function () {
+      document.removeEventListener("visibilitychange", userReturned);
+      startShortPhase();
+    };
+    var startHiddenPhase = function () {
+      if (document.hidden !== true) return startShortPhase();
+      self.phase = 'hidden';
+      ysy.log.debug("hidden period", "autosave");
+      document.addEventListener("visibilitychange", userReturned);
+    };
+    var startLongPhase = function () {
+      if (self.shortTimeout) {
+        window.clearTimeout(self.shortTimeout);
+        self.shortTimeout = 0;
+      }
+      if (self.longTimeout) {
+        window.clearTimeout(self.longTimeout);
+      }
+      self.phase = 'long';
+      ysy.log.debug("long period", "autosave");
+      self.longTimeout = setTimeout(startHiddenPhase, self.longPeriod);
+    };
+
+    ysy.eventBus.register("IdeaConstructed", function () {
+      startLongPhase();
+    });
+  };
+  window.easyMindMupClasses.Autosave = Autosave;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/backup/last_state.js b/plugins/easy_mindmup/assets/javascripts/backup/last_state.js
new file mode 100644
index 0000000000000000000000000000000000000000..4fb36864113eda2f6d86e4a1cfd3e72445d82169
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/backup/last_state.js
@@ -0,0 +1,170 @@
+/**
+ * Created by hosekp on 11/8/16.
+ */
+
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function LastStateChecker(ysy) {
+    this.ysy = ysy;
+  }
+
+  /** @type {Array.<String>} */
+  LastStateChecker.prototype.lastStateKeys = null;
+  LastStateChecker.prototype.getDiffMessages = function (diff, idea, isOld, constructs) {
+    if (diff && diff.attr && diff.attr.data) {
+      var dataDiff = diff.attr.data;
+      var dataKeys = _.intersection(_.keys(dataDiff), this.lastStateKeys);
+      if (dataKeys.length) {
+        var changedKeys = [];
+        var changedValues = {};
+        for (var i = 0; i < dataKeys.length; i++) {
+          var key = dataKeys[i];
+          changedKeys.push(key);
+          changedValues[key] = dataDiff[key];
+          // subMessages.push(key + ": " + oldDataDiff[key] + " => " + newDataDiff[key]);
+        }
+        var id = idea.attr.data.id;
+        if (!constructs[id]) {
+          constructs[id] = {
+            isProject: idea.attr.isProject,
+            name: idea.title,
+            changed: true
+          }
+        }
+        if (constructs[id].changedKeys) {
+          constructs[id].changedKeys = _.uniq(constructs[id].changedKeys.concat(changedKeys));
+        } else {
+          constructs[id].changedKeys = changedKeys;
+        }
+        if (isOld) {
+          constructs[id].oldValues = changedValues;
+        } else {
+          constructs[id].newValues = changedValues;
+        }
+      }
+    }
+    if (diff.ideas) {
+      var ideasDiff = diff.ideas;
+      dataKeys = _.keys(ideasDiff);
+      for (i = 0; i < dataKeys.length; i++) {
+        var rank = dataKeys[i];
+        var child = ideasDiff[rank];
+        if (child.id) {
+          id = this.ysy.getData(child).id;
+          if (!constructs[id]) {
+            constructs[id] = {
+              entityType: child.attr.entityType,
+              name: child.title
+            }
+          }
+          if (isOld) {
+            constructs[id].fromId = idea.attr.data.id;
+            constructs[id].from = idea.title;
+          } else {
+            constructs[id].toId = idea.attr.data.id;
+            constructs[id].to = idea.title;
+          }
+        } else {
+          this.getDiffMessages(ideasDiff[rank], idea.ideas[rank], isOld, constructs);
+        }
+      }
+    }
+  };
+  LastStateChecker.prototype.processLastStateMessages = function (constructs) {
+    var ids = _.keys(constructs);
+    var messages = [];
+    for (var i = 0; i < ids.length; i++) {
+      var id = ids[i];
+      var construct = constructs[id];
+      var message;
+      if (construct.changedKeys) {
+        if (construct.from || construct.to) {
+          message = _.extend({}, construct);
+        } else {
+          message = construct;
+        }
+        if (!construct.oldValues) construct.oldValues = {};
+        if (!construct.newValues) construct.newValues = {};
+        var changes = [];
+        for (var j = 0; j < construct.changedKeys.length; j++) {
+          var key = construct.changedKeys[j];
+          changes.push(key + ": " + construct.oldValues[key] + " => " + construct.newValues[key]);
+        }
+        message.changes = changes;
+        messages.push(message);
+      }
+      if (construct.from || construct.to) {
+        construct.missing = construct.from && !construct.to;
+        construct.present = !construct.from && construct.to;
+        construct.moved = construct.from && construct.to;
+        messages.push(construct);
+      }
+    }
+    return messages;
+  };
+  LastStateChecker.prototype.prepareLastStateMessages = function (diff, last, initedData) {
+    if (this.lastStateKeys == null)throw "lastStateKeys is not defined";
+    if (!diff) return false;
+    if (!last) return false;
+    var constructs = {};
+    this.getDiffMessages(diff.oldDiff, last, true, constructs);
+    this.getDiffMessages(diff.newDiff, initedData, false, constructs);
+    var messagePacks = this.processLastStateMessages(constructs);
+    if (messagePacks.length === 0) return false;
+    var template = this.ysy.settings.templates.reloadErrors;
+    var errorsHtml = Mustache.render(template, messagePacks);
+    this.ysy.util.showMessage(errorsHtml, "warning");
+    // this.openLastStateModal(errorsHtml, initedData);
+    return true;
+  };
+  /**
+   *
+   * @param {RootIdea} last
+   * @param {RootIdea} serverState
+   */
+  LastStateChecker.prototype.openStoredModal = function (last, serverState) {
+    var $target = this.ysy.util.getModal("form-modal", "50%");
+    //var template = ysy.settings.templates.lastStateModal;
+    var self = this;
+    var template = self.ysy.settings.templates.storedModal;
+    //var obj = $.extend({}, ysy.view.getLabel("reloadModal"),{errors:errors});
+    //var rendered = Mustache.render(template, {});
+    $target.html(template);
+    var labels = {};
+    $target.find("button").each(function(){
+      labels[this.id]=$(this).text();
+    }).remove();
+    var loadServerIdea = function () {
+      self.ysy.setIdea(serverState);
+      $target.dialog("close");
+    };
+    showModal("form-modal");
+    $target.dialog({
+      buttons: [
+        {
+          id: "last_state_modal_local",
+          text: labels["last_state_modal_local"],
+          class: "wbs-last-modal-button button-1",
+          click: function () {
+            $target.dialog("close");
+            self.ysy.setIdea(MAPJS.content(last));
+          }
+        },
+        {
+          id: "last_state_modal_server",
+          text: labels["last_state_modal_server"],
+          class: "wbs-last-modal-button button-2",
+          click: loadServerIdea
+        }
+      ]
+    })
+        .on('dialogclose', loadServerIdea);
+    $("#last_state_modal_yes").focus();
+  };
+
+  window.easyMindMupClasses.LastStateChecker = LastStateChecker;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/content_patch.js b/plugins/easy_mindmup/assets/javascripts/content_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..f80f77b87254bc826ea28a315c1ab28ea265ef13
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/content_patch.js
@@ -0,0 +1,84 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @constructor
+   */
+  function ContentPatch(ysy) {
+    this.ysy = ysy;
+    this.patch(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  ContentPatch.prototype.patch = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("TreeLoaded", /** @param {RootIdea} contentAggregate*/ function (contentAggregate) {
+      var commandProcessors = contentAggregate.getCommandProcessors();
+      contentAggregate.clone = function (subIdeaId) {
+        var toClone = (subIdeaId && subIdeaId != contentAggregate.id && contentAggregate.findSubIdeaById(subIdeaId)) || contentAggregate;
+        var cloned = JSON.parse(JSON.stringify(toClone));
+        ysy.util.traverse(cloned, function (node) {
+          ysy.getData(node).id = null;
+        });
+        return cloned;
+      };
+      commandProcessors.setData = function (originId, idea, obj) {
+        ysy.setData(idea, obj);
+      };
+      commandProcessors.setCustomData = function (originId, idea, obj) {
+        ysy.setCustomData(idea, obj);
+      };
+      contentAggregate.updateOneSide = $.proxy(self.updateOneSide, self);
+    });
+  };
+  /**
+   *
+   * @param {RootIdea} idea
+   */
+  ContentPatch.prototype.updateOneSide = function (idea) {
+    var i;
+    var oneSideOn = this.ysy.settings.oneSideOn;
+    if (oneSideOn === idea.oneSideOn) return;
+    var ranks = Object.getOwnPropertyNames(idea.ideas).map(function (i) {
+      return parseFloat(i);
+    }).sort(function (a, b) {
+      return a - b
+    });
+    if (ranks.length === 0) return;
+    var constructed = {};
+    if (oneSideOn) {
+      var firstPositive = _.findIndex(ranks, function (rank) {
+        return rank > 0;
+      });
+      var pointer = 1;
+      for (i = firstPositive; i < ranks.length; i++) {
+        constructed[pointer++] = idea.ideas[ranks[i]];
+      }
+      for (i = 0; i < firstPositive; i++) {
+        constructed[pointer++] = idea.ideas[ranks[i]];
+      }
+    } else {
+      if (ranks[0] < 0) {
+        return;
+      }
+      var middlePoint = Math.ceil(ranks.length / 2);
+      //index = i % 2 !== 0 ? -i / 2 - 0.5 : i / 2 + 1;
+      for (i = 0; i < middlePoint; i++) {
+        constructed[i + 1] = idea.ideas[ranks[i]];
+      }
+      for (i = middlePoint; i < ranks.length; i++) {
+        constructed[i - ranks.length] = idea.ideas[ranks[i]];
+      }
+    }
+    idea.oneSideOn = oneSideOn;
+    idea.ideas = constructed;
+    if (idea.dispatchEvent) {
+      idea.dispatchEvent("changed");
+    }
+  };
+  window.easyMindMupClasses.ContentPatch = ContentPatch;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/data.js b/plugins/easy_mindmup/assets/javascripts/data.js
new file mode 100644
index 0000000000000000000000000000000000000000..ebaa06052a43bebc3134b9d920aa11f2a0310a9b
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/data.js
@@ -0,0 +1,36 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function DataStorage(ysy) {
+    /**
+     *
+     * @type {MindMup}
+     */
+    this.ysy = ysy;
+    this.dataArrays = {};
+  }
+
+  /**
+   *
+   * @param {String} key
+   * @param {Array} array
+   */
+  DataStorage.prototype.save = function (key, array) {
+    array = array || [];
+    this.dataArrays[key] = array;
+    this.ysy.eventBus.fireEvent("dataFilled", key, array);
+  };
+  /**
+   *
+   * @param {String} key
+   * @return {Array}
+   */
+  DataStorage.prototype.get = function (key) {
+    return this.dataArrays[key];
+  };
+
+  window.easyMindMupClasses.DataStorage = DataStorage;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/dom_patch.js b/plugins/easy_mindmup/assets/javascripts/dom_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..e61fafa9377c7f5076532bc03f3265f4b70bd1c3
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/dom_patch.js
@@ -0,0 +1,197 @@
+/**
+ * Created by hosekp on 11/8/16.
+ */
+
+/**
+ * Hammer-drag Event
+ * @typedef {Object} HammerEvent
+ * @property {{left:number,top:number}} finalPosition
+ * @property {{deltaX:number,deltaY:number,center:{}}} gesture
+ * @property {boolean} fakedEvent
+ */
+
+(function () {
+  /**
+   * Patch class for mindmup/dom-map-view.js
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function DomPatch(ysy) {
+    this.ysy = ysy;
+    this.patch(ysy);
+    this.deltaX = 0;
+    this.deltaY = 0;
+    this.init(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  DomPatch.prototype.patch = function (ysy) {
+    jQuery.fn.scrollWhenDragging = function (scrollPredicate) {
+      'use strict';
+      return this.each(function () {
+        var element = $(this),
+            dragOrigin,
+            lastGesture,
+            isOut;
+        var dragStartFunc = function (withNode, event) {
+          if (scrollPredicate()) {
+            withNode = withNode === true;
+            $("body").addClass("mindmup-noselect");
+            dragOrigin = {
+              top: $(window).scrollTop(),
+              left: element.scrollLeft(),
+              withNode: withNode
+            };
+            if (event) {
+              dragOrigin.top += event.gesture.deltaY;
+              dragOrigin.left += event.gesture.deltaX;
+            }
+            isOut = false;
+            element.on('mouseleave.bg_drag', function () {
+              isOut = true;
+              // console.log("OUT");
+            }).on('mouseenter.bg_drag', function () {
+              isOut = false;
+              // console.log("IN");
+              dragOrigin = {
+                top: $(window).scrollTop() + lastGesture.deltaY,
+                left: element.scrollLeft() + lastGesture.deltaX,
+                withNode: withNode
+              };
+            });
+          }
+        };
+        var dragFunc = function (e) {
+          if (e.gesture && e.gesture.srcEvent) {
+            if (isOut) {
+              lastGesture = e.gesture;
+              return;
+            }
+            if (dragOrigin && dragOrigin.withNode && !e.gesture.srcEvent.ctrlKey) {
+              return dragEndFunc();
+            }
+            if (!dragOrigin && e.gesture.srcEvent.ctrlKey) {
+              dragStartFunc(true, e);
+            }
+            if (dragOrigin) {
+              $("html, body").scrollTop(dragOrigin.top - e.gesture.deltaY);
+              element.scrollLeft(dragOrigin.left - e.gesture.deltaX);
+            }
+          }
+        };
+        var dragEndFunc = function () {
+          isOut = false;
+          $("body").removeClass("mindmup-noselect");
+          element.off('mouseleave.bg_drag').off("mouseenter.bg_drag");
+          dragOrigin = undefined;
+        };
+        element
+            .on("dragstart", dragStartFunc)
+            .on('drag', dragFunc)
+            .on('dragend', dragEndFunc);
+      });
+    };
+    jQuery.fn.updateStage = function () {
+      'use strict';
+      var data = this.data(),
+          size = {
+            'min-width': Math.floor(data.width - data.offsetX),
+            'min-height': Math.floor(data.height - data.offsetY),
+            'width': Math.floor(data.width - data.offsetX),
+            'height': Math.floor(data.height - data.offsetY),
+            'transform-origin': 'top left',
+            'transform': 'translate3d(' + Math.floor(data.offsetX) + 'px, ' + Math.floor(data.offsetY) + 'px, 0)'
+          };
+      if (data.scale && data.scale !== 1) {
+        size.transform = 'scale(' + data.scale + ') translate(' + Math.floor(data.offsetX) + 'px, ' + Math.floor(data.offsetY) + 'px)';
+      }
+      this.css(size);
+      var viewHeight = Math.floor(data.height) * data.scale;
+      ysy.$container.height(viewHeight);
+      return this;
+    };
+  };
+
+  /** @param {MindMup} ysy */
+  DomPatch.prototype.init = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("MapInited", function (mapModel) {
+      mapModel.addEventListener('nodeCreated', function (node) {
+        var $element = ysy.getNodeElement(node);
+        if (node.level !== 1) return;
+        $element.on('mm:stop-dragging', function (/** @type {HammerEvent} */evt) {
+          if (!evt.fakedEvent && evt.gesture && !evt.isPropagationStopped()) {
+            self.deltaX -= evt.gesture.deltaX;
+            self.deltaY -= evt.gesture.deltaY;
+            ysy.storage.extra.save(ysy.idea);
+          }
+        });
+      }, -1);
+    });
+    ysy.eventBus.register("TreeLoaded", function () {
+      if (self.deltaX !== 0 || self.deltaY !== 0) {
+        self.focusOnCenter();
+      }
+    });
+  };
+  /**
+   *
+   * @param {{deltaX:number,deltaY:number}} data
+   */
+  DomPatch.prototype.loadRootPosition = function (data) {
+    if (!data) return;
+    if (data.deltaX) this.deltaX = parseFloat(data.deltaX);
+    if (data.deltaY) this.deltaY = parseFloat(data.deltaY);
+  };
+  DomPatch.prototype.resetRootPosition = function () {
+    this.deltaX = 0;
+    this.deltaY = 0;
+    this.ysy.storage.extra.save(this.ysy.idea);
+  };
+  DomPatch.prototype.focusOnCenter = function () {
+    var $stage = this.ysy.$container.find('[data-mapjs-role="stage"]');
+    var $central = $stage.find("#node_central");
+    if ($central.length === 0) {
+      $central = $('<div id="node_central"></div>').appendTo($stage);
+    }
+    $central.css({left: this.deltaX, top: this.deltaY});
+    $central.data({x: this.deltaX, y: this.deltaY, height: 40, width: 100});
+    this.ysy.mapModel.dispatchEvent('nodeFocusRequested', ["central"]);
+  };
+
+  DomPatch.prototype.curvedPath = function (parent, child) {
+    'use strict';
+    var calculateConnector = function (parent, child) {
+          var childHorizontalOffset = parent.left < child.left ? 0 : 1;
+          return {
+            from: {
+              x: parent.left + 0.5 * parent.width,
+              y: parent.top + 0.5 * parent.height
+            },
+            to: {
+              x: child.left + childHorizontalOffset * child.width,
+              y: child.top + 0.5 * child.height
+            }
+          };
+        },
+        position = {
+          left: Math.min(parent.left, child.left),
+          top: Math.min(parent.top, child.top)
+        },
+        calculatedConnector;
+    position.width = Math.max(parent.left + parent.width, child.left + child.width, position.left + 1) - position.left;
+    position.height = Math.max(parent.top + parent.height, child.top + child.height, position.top + 1) - position.top;
+
+    calculatedConnector = calculateConnector(parent, child);
+    return {
+      'd': 'M' + Math.round(calculatedConnector.from.x - position.left) + ',' + Math.round(calculatedConnector.from.y - position.top) +
+      'Q' + Math.round(calculatedConnector.from.x - position.left) + ',' + Math.round(calculatedConnector.to.y - position.top) + ' ' + Math.round(calculatedConnector.to.x - position.left) + ',' + Math.round(calculatedConnector.to.y - position.top),
+      // 'conn': calculatedConnector,
+      'position': position
+    };
+  };
+  window.easyMindMupClasses.DomPatch = DomPatch;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/easy_mindmup.js b/plugins/easy_mindmup/assets/javascripts/easy_mindmup.js
new file mode 100644
index 0000000000000000000000000000000000000000..ed30119392e65c4bb50bfc06c48c63513bd4c2a7
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/easy_mindmup.js
@@ -0,0 +1,11 @@
+/*
+ * = require external
+ * = require mindmup/mapjs
+ * = require_directory ./mindmup
+ * = require libs
+ * = require polyfill
+ * = require utils
+ * = require_directory .
+ * = stub mindmup/link-edit-widget
+ * = stub jsdocs_external
+ */
diff --git a/plugins/easy_mindmup/assets/javascripts/event_bus.js b/plugins/easy_mindmup/assets/javascripts/event_bus.js
new file mode 100644
index 0000000000000000000000000000000000000000..b27d0f32a1948040b0bed9cb67957f56c289d0ea
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/event_bus.js
@@ -0,0 +1,58 @@
+/**
+ * Created by hosekp on 11/10/16.
+ */
+(function () {
+  /**
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function EventBus(ysy) {
+    this.ysy = ysy;
+    this.registeredMap = {};
+  }
+
+  /**
+   * send event to all of the listeners of specified event
+   * @param {String} event
+   * @param {...any}
+   */
+  EventBus.prototype.fireEvent = function (event) {
+    var proFunctions = this.registeredMap[event];
+    if (!proFunctions) return;
+    var slicedArgs = Array.prototype.slice.call(arguments, 1);
+    for (var i = 0; i < proFunctions.length; i++) {
+      proFunctions[i].apply(this, slicedArgs);
+    }
+  };
+  /**
+   * Register listener of specified event
+   * @param {String} event
+   * @param {Function} func
+   */
+  EventBus.prototype.register = function (event, func) {
+    var eventList = this.registeredMap[event];
+    if (!eventList) this.registeredMap[event] = eventList = [];
+    for (var i = 0; i < eventList.length; i++) {
+      if (eventList[i] === func) {
+        return;
+      }
+    }
+    eventList.push(func);
+  };
+  /**
+   *
+   * @param {String} event
+   * @param {Function} func
+   */
+  EventBus.prototype.unregister = function (event, func) {
+    var eventList = this.registeredMap[event];
+    if (!eventList) return;
+    for (var i = 0; i < eventList.length; i++) {
+      if (eventList[i] === func) {
+        eventList.splice(i, 1);
+        return;
+      }
+    }
+  };
+  window.easyMindMupClasses.EventBus = EventBus;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/expand_all.js b/plugins/easy_mindmup/assets/javascripts/expand_all.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee8d25f9e68b55c9613a1598ab2e4cff53d3d1ba
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/expand_all.js
@@ -0,0 +1,63 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function ExpandAllButton(ysy,$parent) {
+    /**
+     *
+     * @type {boolean}
+     */
+    this.expanded=false;
+    this.ysy = ysy;
+    this.init(ysy,$parent);
+  }
+
+  ExpandAllButton.prototype.id = "expandAllButton";
+
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @return {ExpandAllButton}
+   */
+  ExpandAllButton.prototype.init= function (ysy,$parent) {
+    var self = this;
+    ysy.repainter.redrawMe(this);
+    this.$element = $parent.find(".mindmup-expand-all");
+    this.$element.click(function () {
+      self.expanded = !self.expanded;
+      if(self.expanded){
+        self.collapseAll();
+      }else{
+        self.expandAll();
+      }
+      // self.toggle(self.expanded);
+      ysy.repainter.redrawMe(self);
+    });
+    return this;
+  };
+  ExpandAllButton.prototype.expandAll= function () {
+    this.ysy.util.traverse(this.ysy.idea, function (node) {
+      if(_.isEmpty(node.ideas)) return;
+      node.attr.collapsed = false;
+    });
+    this.ysy.idea.dispatchEvent("changed");
+  };
+  ExpandAllButton.prototype.collapseAll= function () {
+    var rootIdea = this.ysy.idea;
+    this.ysy.util.traverse(rootIdea, function (node) {
+      if(_.isEmpty(node.ideas)) return;
+      node.attr.collapsed = true;
+    });
+    rootIdea.attr.collapsed = false;
+    rootIdea.dispatchEvent("changed");
+  };
+  ExpandAllButton.prototype._render= function () {
+    this.$element.find(".mindmup-button-expand-all").toggle(this.expanded);
+    this.$element.find(".mindmup-button-collapse-all").toggle(!this.expanded);
+  };
+  window.easyMindMupClasses.ExpandAllButton = ExpandAllButton;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/external.js b/plugins/easy_mindmup/assets/javascripts/external.js
new file mode 100644
index 0000000000000000000000000000000000000000..b267393d3343a23e3b33a2bc5a2089019568a6db
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/external.js
@@ -0,0 +1,1692 @@
+(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.10.0",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r<i.length;r++)e.options[i[r][0]]&&i[r][1].apply(e.element,n)}},hasScroll:function(t,n){if(e(t).css("overflow")==="hidden")return!1;var r=n&&n==="left"?"scrollLeft":"scrollTop",i=!1;return t[r]>0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)}})})(jQuery);(function(e,t){var n=0,r=Array.prototype.slice,i=e.cleanData;e.cleanData=function(t){for(var n=0,r;(r=t[n])!=null;n++)try{e(r).triggerHandler("remove")}catch(s){}i(t)},e.widget=function(t,n,r){var i,s,o,u,a={},f=t.split(".")[0];t=t.split(".")[1],i=f+"-"+t,r||(r=n,n=e.Widget),e.expr[":"][i.toLowerCase()]=function(t){return!!e.data(t,i)},e[f]=e[f]||{},s=e[f][t],o=e[f][t]=function(e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)},e.extend(o,s,{version:r.version,_proto:e.extend({},r),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(r,function(t,r){if(!e.isFunction(r)){a[t]=r;return}a[t]=function(){var e=function(){return n.prototype[t].apply(this,arguments)},i=function(e){return n.prototype[t].apply(this,e)};return function(){var t=this._super,n=this._superApply,s;return this._super=e,this._superApply=i,s=r.apply(this,arguments),this._super=t,this._superApply=n,s}}()}),o.prototype=e.widget.extend(u,{widgetEventPrefix:s?u.widgetEventPrefix:t},a,{constructor:o,namespace:f,widgetName:t,widgetFullName:i}),s?(e.each(s._childConstructors,function(t,n){var r=n.prototype;e.widget(r.namespace+"."+r.widgetName,o,n._proto)}),delete s._childConstructors):n._childConstructors.push(o),e.widget.bridge(t,o)},e.widget.extend=function(n){var i=r.call(arguments,1),s=0,o=i.length,u,a;for(;s<o;s++)for(u in i[s])a=i[s][u],i[s].hasOwnProperty(u)&&a!==t&&(e.isPlainObject(a)?n[u]=e.isPlainObject(n[u])?e.widget.extend({},n[u],a):e.widget.extend({},a):n[u]=a);return n},e.widget.bridge=function(n,i){var s=i.prototype.widgetFullName||n;e.fn[n]=function(o){var u=typeof o=="string",a=r.call(arguments,1),f=this;return o=!u&&a.length?e.widget.extend.apply(null,[o].concat(a)):o,u?this.each(function(){var r,i=e.data(this,s);if(!i)return e.error("cannot call methods on "+n+" prior to initialization; "+"attempted to call method '"+o+"'");if(!e.isFunction(i[o])||o.charAt(0)==="_")return e.error("no such method '"+o+"' for "+n+" widget instance");r=i[o].apply(i,a);if(r!==i&&r!==t)return f=r&&r.jquery?f.pushStack(r.get()):r,!1}):this.each(function(){var t=e.data(this,s);t?t.option(o||{})._init():e.data(this,s,new i(o,this))}),f}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u<s.length-1;u++)o[s[u]]=o[s[u]]||{},o=o[s[u]];n=s.pop();if(r===t)return o[n]===t?null:o[n];o[n]=r}else{if(r===t)return this.options[n]===t?null:this.options[n];i[n]=r}}return this._setOptions(i),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,e==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(t,n,r){var i,s=this;typeof t!="boolean"&&(r=n,n=t,t=!1),r?(n=i=e(n),this.bindings=this.bindings.add(n)):(r=n,n=this.element,i=this.widget()),e.each(r,function(r,o){function u(){if(!t&&(s.options.disabled===!0||e(this).hasClass("ui-state-disabled")))return;return(typeof o=="string"?s[o]:o).apply(s,arguments)}typeof o!="string"&&(u.guid=o.guid=o.guid||u.guid||e.guid++);var a=r.match(/^(\w+)\s*(.*)$/),f=a[1]+s.eventNamespace,l=a[2];l?i.delegate(l,f,u):n.bind(f,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function n(){return(typeof e=="string"?r[e]:e).apply(r,arguments)}var r=this;return setTimeout(n,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,n,r){var i,s,o=this.options[t];r=r||{},n=e.Event(n),n.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),n.target=this.element[0],s=n.originalEvent;if(s)for(i in s)i in n||(n[i]=s[i]);return this.element.trigger(n,r),!(e.isFunction(o)&&o.apply(this.element[0],[n].concat(r))===!1||n.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,n){e.Widget.prototype["_"+t]=function(r,i,s){typeof i=="string"&&(i={effect:i});var o,u=i?i===!0||typeof i=="number"?n:i.effect||n:t;i=i||{},typeof i=="number"&&(i={duration:i}),o=!e.isEmptyObject(i),i.complete=s,i.delay&&r.delay(i.delay),o&&e.effects&&e.effects.effect[u]?r[t](i):u!==t&&r[u]?r[u](i.duration,i.easing,s):r.queue(function(n){e(this)[t](),s&&s.call(r[0]),n()})}})})(jQuery);(function(e,t){var n=!1;e(document).mouseup(function(){n=!1}),e.widget("ui.mouse",{version:"1.10.0",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(n){if(!0===e.data(n.target,t.widgetName+".preventClickEvent"))return e.removeData(n.target,t.widgetName+".preventClickEvent"),n.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(n)return;this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var r=this,i=t.which===1,s=typeof this.options.cancel=="string"&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;if(!i||s||!this._mouseCapture(t))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){r.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)){this._mouseStarted=this._mouseStart(t)!==!1;if(!this._mouseStarted)return t.preventDefault(),!0}return!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return r._mouseMove(e)},this._mouseUpDelegate=function(e){return r._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),n=!0,!0},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||document.documentMode<9)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}function d(t){var n=t[0];return n.nodeType===9?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(n)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:n.preventDefault?{width:0,height:0,offset:{top:n.pageY,left:n.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width<t.element[0].scrollWidth,s=r==="scroll"||r==="auto"&&t.height<t.element[0].scrollHeight;return{width:i?e.position.scrollbarWidth():0,height:s?e.position.scrollbarWidth():0}},getWithinInfo:function(t){var n=e(t||window),r=e.isWindow(n[0]);return{element:n,isWindow:r,offset:n.offset()||{left:0,top:0},scrollLeft:n.scrollLeft(),scrollTop:n.scrollTop(),width:r?n.width():n.outerWidth(),height:r?n.height():n.outerHeight()}}},e.fn.position=function(t){if(!t||!t.of)return c.apply(this,arguments);t=e.extend({},t);var n,l,v,m,g,y,b=e(t.of),w=e.position.getWithinInfo(t.within),E=e.position.getScrollInfo(w),S=(t.collision||"flip").split(" "),x={};return y=d(b),b[0].preventDefault&&(t.at="left top"),l=y.width,v=y.height,m=y.offset,g=e.extend({},m),e.each(["my","at"],function(){var e=(t[this]||"").split(" "),n,r;e.length===1&&(e=o.test(e[0])?e.concat(["center"]):u.test(e[0])?["center"].concat(e):["center","center"]),e[0]=o.test(e[0])?e[0]:"center",e[1]=u.test(e[1])?e[1]:"center",n=a.exec(e[0]),r=a.exec(e[1]),x[this]=[n?n[0]:0,r?r[0]:0],t[this]=[f.exec(e[0])[0],f.exec(e[1])[0]]}),S.length===1&&(S[1]=S[0]),t.at[0]==="right"?g.left+=l:t.at[0]==="center"&&(g.left+=l/2),t.at[1]==="bottom"?g.top+=v:t.at[1]==="center"&&(g.top+=v/2),n=h(x.at,l,v),g.left+=n[0],g.top+=n[1],this.each(function(){var o,u,a=e(this),f=a.outerWidth(),c=a.outerHeight(),d=p(this,"marginLeft"),y=p(this,"marginTop"),T=f+d+p(this,"marginRight")+E.width,N=c+y+p(this,"marginBottom")+E.height,C=e.extend({},g),k=h(x.my,a.outerWidth(),a.outerHeight());t.my[0]==="right"?C.left-=f:t.my[0]==="center"&&(C.left-=f/2),t.my[1]==="bottom"?C.top-=c:t.my[1]==="center"&&(C.top-=c/2),C.left+=k[0],C.top+=k[1],e.support.offsetFractions||(C.left=s(C.left),C.top=s(C.top)),o={marginLeft:d,marginTop:y},e.each(["left","top"],function(r,i){e.ui.position[S[r]]&&e.ui.position[S[r]][i](C,{targetWidth:l,targetHeight:v,elemWidth:f,elemHeight:c,collisionPosition:o,collisionWidth:T,collisionHeight:N,offset:[n[0]+k[0],n[1]+k[1]],my:t.my,at:t.at,within:w,elem:a})}),t.using&&(u=function(e){var n=m.left-C.left,s=n+l-f,o=m.top-C.top,u=o+v-c,h={target:{element:b,left:m.left,top:m.top,width:l,height:v},element:{element:a,left:C.left,top:C.top,width:f,height:c},horizontal:s<0?"left":n>0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};l<f&&i(n+s)<l&&(h.horizontal="center"),v<c&&i(o+u)<v&&(h.vertical="middle"),r(i(n),i(s))>r(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p<i(a))e.left+=l+c+h}else if(f>0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)<f)e.left+=l+c+h}},top:function(e,t){var n=t.within,r=n.offset.top+n.scrollTop,s=n.height,o=n.isWindow?n.scrollTop:n.offset.top,u=e.top-t.collisionPosition.marginTop,a=u-o,f=u+t.collisionHeight-s-o,l=t.my[1]==="top",c=l?-t.elemHeight:t.my[1]==="bottom"?t.elemHeight:0,h=t.at[1]==="top"?t.targetHeight:t.at[1]==="bottom"?-t.targetHeight:0,p=-2*t.offset[1],d,v;a<0?(v=e.top+c+h+p+t.collisionHeight-s-r,e.top+c+h+p>a&&(v<0||v<i(a))&&(e.top+=c+h+p)):f>0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)<f)&&(e.top+=c+h+p))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,n,r,i,s,o=document.getElementsByTagName("body")[0],u=document.createElement("div");t=document.createElement(o?"div":"body"),r={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&e.extend(r,{position:"absolute",left:"-1000px",top:"-1000px"});for(s in r)t.style[s]=r[s];t.appendChild(u),n=o||document.documentElement,n.insertBefore(t,n.firstChild),u.style.cssText="position: absolute; left: 10.7432222px;",i=e(u).offset().left,e.support.offsetFractions=i>10&&i<11,t.innerHTML="",n.removeChild(t)}()})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.10.0",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){this.options.helper==="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!=="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!=="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n,r=this,i=!1,s=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),n=this.element[0];while(n&&(n=n.parentNode))n===document&&(i=!0);return!i&&this.options.helper==="original"?!1:(this.options.revert==="invalid"&&!s||this.options.revert==="valid"&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){r._trigger("stop",t)!==!1&&r._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1)},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").addBack().each(function(){this===t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper==="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo==="parent"?this.element[0].parentNode:n.appendTo),r[0]!==this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition==="absolute"&&this.scrollParent[0]!==document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]===document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()==="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition==="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,n,r,i=this.options;i.containment==="parent"&&(i.containment=this.helper[0].parentNode);if(i.containment==="document"||i.containment==="window")this.containment=[i.containment==="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,i.containment==="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(i.containment==="document"?0:e(window).scrollLeft())+e(i.containment==="document"?document:window).width()-this.helperProportions.width-this.margins.left,(i.containment==="document"?0:e(window).scrollTop())+(e(i.containment==="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(i.containment)&&i.containment.constructor!==Array){n=e(i.containment),r=n[0];if(!r)return;t=e(r).css("overflow")!=="hidden",this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(t?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else i.containment.constructor===Array&&(this.containment=i.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t==="absolute"?1:-1,i=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,s=/(html|body)/i.test(i[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():s?0:i.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():s?0:i.scrollLeft())*r}},_generatePosition:function(t){var n,r,i,s,o=this.options,u=this.cssPosition!=="absolute"||this.scrollParent[0]!==document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(u[0].tagName),f=t.pageX,l=t.pageY;return this.originalPosition&&(this.containment&&(this.relative_container?(r=this.relative_container.offset(),n=[this.containment[0]+r.left,this.containment[1]+r.top,this.containment[2]+r.left,this.containment[3]+r.top]):n=this.containment,t.pageX-this.offset.click.left<n[0]&&(f=n[0]+this.offset.click.left),t.pageY-this.offset.click.top<n[1]&&(l=n[1]+this.offset.click.top),t.pageX-this.offset.click.left>n[2]&&(f=n[2]+this.offset.click.left),t.pageY-this.offset.click.top>n[3]&&(l=n[3]+this.offset.click.top)),o.grid&&(i=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=n?i-this.offset.click.top>=n[1]||i-this.offset.click.top>n[3]?i:i-this.offset.click.top>=n[1]?i-o.grid[1]:i+o.grid[1]:i,s=o.grid[0]?this.originalPageX+Math.round((f-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,f=n?s-this.offset.click.left>=n[0]||s-this.offset.click.left>n[2]?s:s-this.offset.click.left>=n[0]?s-o.grid[0]:s+o.grid[0]:s)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():a?0:u.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():a?0:u.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!==this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(t,n,r){return r=r||this._uiHash(),e.ui.plugin.call(this,t,[n,r]),t==="drag"&&(this.positionAbs=this._convertPositionTo("absolute")),e.Widget.prototype._trigger.call(this,t,n,r)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,n){var r=e(this).data("ui-draggable"),i=r.options,s=e.extend({},n,{item:r.element});r.sortables=[],e(i.connectToSortable).each(function(){var n=e.data(this,"ui-sortable");n&&!n.options.disabled&&(r.sortables.push({instance:n,shouldRevert:n.options.revert}),n.refreshPositions(),n._trigger("activate",t,s))})},stop:function(t,n){var r=e(this).data("ui-draggable"),i=e.extend({},n,{item:r.element});e.each(r.sortables,function(){this.instance.isOver?(this.instance.isOver=0,r.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(t),this.instance.options.helper=this.instance.options._helper,r.options.helper==="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",t,i))})},drag:function(t,n){var r=e(this).data("ui-draggable"),i=this;e.each(r.sortables,function(){var s=!1,o=this;this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this.instance._intersectsWith(this.instance.containerCache)&&(s=!0,e.each(r.sortables,function(){return this.instance.positionAbs=r.positionAbs,this.instance.helperProportions=r.helperProportions,this.instance.offset.click=r.offset.click,this!==o&&this.instance._intersectsWith(this.instance.containerCache)&&e.ui.contains(o.instance.element[0],this.instance.element[0])&&(s=!1),s})),s?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=e(i).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return n.helper[0]},t.target=this.instance.currentItem[0],this.instance._mouseCapture(t,!0),this.instance._mouseStart(t,!0,!0),this.instance.offset.click.top=r.offset.click.top,this.instance.offset.click.left=r.offset.click.left,this.instance.offset.parent.left-=r.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=r.offset.parent.top-this.instance.offset.parent.top,r._trigger("toSortable",t),r.dropped=this.instance.element,r.currentItem=r.element,this.instance.fromOutside=r),this.instance.currentItem&&this.instance._mouseDrag(t)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",t,this.instance._uiHash(this.instance)),this.instance._mouseStop(t,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),r._trigger("fromSortable",t),r.dropped=!1)})}}),e.ui.plugin.add("draggable","cursor",{start:function(){var t=e("body"),n=e(this).data("ui-draggable").options;t.css("cursor")&&(n._cursor=t.css("cursor")),t.css("cursor",n.cursor)},stop:function(){var t=e(this).data("ui-draggable").options;t._cursor&&e("body").css("cursor",t._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,n){var r=e(n.helper),i=e(this).data("ui-draggable").options;r.css("opacity")&&(i._opacity=r.css("opacity")),r.css("opacity",i.opacity)},stop:function(t,n){var r=e(this).data("ui-draggable").options;r._opacity&&e(n.helper).css("opacity",r._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(){var t=e(this).data("ui-draggable");t.scrollParent[0]!==document&&t.scrollParent[0].tagName!=="HTML"&&(t.overflowOffset=t.scrollParent.offset())},drag:function(t){var n=e(this).data("ui-draggable"),r=n.options,i=!1;if(n.scrollParent[0]!==document&&n.scrollParent[0].tagName!=="HTML"){if(!r.axis||r.axis!=="x")n.overflowOffset.top+n.scrollParent[0].offsetHeight-t.pageY<r.scrollSensitivity?n.scrollParent[0].scrollTop=i=n.scrollParent[0].scrollTop+r.scrollSpeed:t.pageY-n.overflowOffset.top<r.scrollSensitivity&&(n.scrollParent[0].scrollTop=i=n.scrollParent[0].scrollTop-r.scrollSpeed);if(!r.axis||r.axis!=="y")n.overflowOffset.left+n.scrollParent[0].offsetWidth-t.pageX<r.scrollSensitivity?n.scrollParent[0].scrollLeft=i=n.scrollParent[0].scrollLeft+r.scrollSpeed:t.pageX-n.overflowOffset.left<r.scrollSensitivity&&(n.scrollParent[0].scrollLeft=i=n.scrollParent[0].scrollLeft-r.scrollSpeed)}else{if(!r.axis||r.axis!=="x")t.pageY-e(document).scrollTop()<r.scrollSensitivity?i=e(document).scrollTop(e(document).scrollTop()-r.scrollSpeed):e(window).height()-(t.pageY-e(document).scrollTop())<r.scrollSensitivity&&(i=e(document).scrollTop(e(document).scrollTop()+r.scrollSpeed));if(!r.axis||r.axis!=="y")t.pageX-e(document).scrollLeft()<r.scrollSensitivity?i=e(document).scrollLeft(e(document).scrollLeft()-r.scrollSpeed):e(window).width()-(t.pageX-e(document).scrollLeft())<r.scrollSensitivity&&(i=e(document).scrollLeft(e(document).scrollLeft()+r.scrollSpeed))}i!==!1&&e.ui.ddmanager&&!r.dropBehaviour&&e.ui.ddmanager.prepareOffsets(n,t)}}),e.ui.plugin.add("draggable","snap",{start:function(){var t=e(this).data("ui-draggable"),n=t.options;t.snapElements=[],e(n.snap.constructor!==String?n.snap.items||":data(ui-draggable)":n.snap).each(function(){var n=e(this),r=n.offset();this!==t.element[0]&&t.snapElements.push({item:this,width:n.outerWidth(),height:n.outerHeight(),top:r.top,left:r.left})})},drag:function(t,n){var r,i,s,o,u,a,f,l,c,h,p=e(this).data("ui-draggable"),d=p.options,v=d.snapTolerance,m=n.offset.left,g=m+p.helperProportions.width,y=n.offset.top,b=y+p.helperProportions.height;for(c=p.snapElements.length-1;c>=0;c--){u=p.snapElements[c].left,a=u+p.snapElements[c].width,f=p.snapElements[c].top,l=f+p.snapElements[c].height;if(!(u-v<m&&m<a+v&&f-v<y&&y<l+v||u-v<m&&m<a+v&&f-v<b&&b<l+v||u-v<g&&g<a+v&&f-v<y&&y<l+v||u-v<g&&g<a+v&&f-v<b&&b<l+v)){p.snapElements[c].snapping&&p.options.snap.release&&p.options.snap.release.call(p.element,t,e.extend(p._uiHash(),{snapItem:p.snapElements[c].item})),p.snapElements[c].snapping=!1;continue}d.snapMode!=="inner"&&(r=Math.abs(f-b)<=v,i=Math.abs(l-y)<=v,s=Math.abs(u-g)<=v,o=Math.abs(a-m)<=v,r&&(n.position.top=p._convertPositionTo("relative",{top:f-p.helperProportions.height,left:0}).top-p.margins.top),i&&(n.position.top=p._convertPositionTo("relative",{top:l,left:0}).top-p.margins.top),s&&(n.position.left=p._convertPositionTo("relative",{top:0,left:u-p.helperProportions.width}).left-p.margins.left),o&&(n.position.left=p._convertPositionTo("relative",{top:0,left:a}).left-p.margins.left)),h=r||i||s||o,d.snapMode!=="outer"&&(r=Math.abs(f-y)<=v,i=Math.abs(l-b)<=v,s=Math.abs(u-m)<=v,o=Math.abs(a-g)<=v,r&&(n.position.top=p._convertPositionTo("relative",{top:f,left:0}).top-p.margins.top),i&&(n.position.top=p._convertPositionTo("relative",{top:l-p.helperProportions.height,left:0}).top-p.margins.top),s&&(n.position.left=p._convertPositionTo("relative",{top:0,left:u}).left-p.margins.left),o&&(n.position.left=p._convertPositionTo("relative",{top:0,left:a-p.helperProportions.width}).left-p.margins.left)),!p.snapElements[c].snapping&&(r||i||s||o||h)&&p.options.snap.snap&&p.options.snap.snap.call(p.element,t,e.extend(p._uiHash(),{snapItem:p.snapElements[c].item})),p.snapElements[c].snapping=r||i||s||o||h}}}),e.ui.plugin.add("draggable","stack",{start:function(){var t,n=e(this).data("ui-draggable").options,r=e.makeArray(e(n.stack)).sort(function(t,n){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(n).css("zIndex"),10)||0)});if(!r.length)return;t=parseInt(r[0].style.zIndex,10)||0,e(r).each(function(e){this.style.zIndex=t+e}),this[0].style.zIndex=t+r.length}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,n){var r=e(n.helper),i=e(this).data("ui-draggable").options;r.css("zIndex")&&(i._zIndex=r.css("zIndex")),r.css("zIndex",i.zIndex)},stop:function(t,n){var r=e(this).data("ui-draggable").options;r._zIndex&&e(n.helper).css("zIndex",r._zIndex)}})})(jQuery);jQuery.effects||function(e,t){var n="ui-effects-";e.effects={effect:{}},function(e,t){function h(e,t,n){var r=u[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max<e?r.max:e)}function p(t){var n=s(),r=n._rgba=[];return t=t.toLowerCase(),c(i,function(e,i){var s,u=i.re.exec(t),a=u&&i.parse(u),f=i.space||"rgba";if(a)return s=n[f](a),n[o[f].cache]=s[o[f].cache],r=n._rgba=s._rgba,!1}),r.length?(r.join()==="0,0,0,0"&&e.extend(r,l.transparent),n):l[t]}function d(e,t,n){return n=(n+1)%1,n*6<1?e+(t-e)*n*6:n*2<1?t:n*3<2?e+(t-e)*(2/3-n)*6:e}var n="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,i=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1]*2.55,e[2]*2.55,e[3]*2.55,e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],s=e.Color=function(t,n,r,i){return new e.Color.fn.parse(t,n,r,i)},o={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},a=s.support={},f=e("<p>")[0],l,c=e.each;f.style.cssText="background-color:rgba(1,1,1,.5)",a.rgba=f.style.backgroundColor.indexOf("rgba")>-1,c(o,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),s.fn=e.extend(s.prototype,{parse:function(n,r,i,u){if(n===t)return this._rgba=[null,null,null,null],this;if(n.jquery||n.nodeType)n=e(n).css(r),r=t;var a=this,f=e.type(n),d=this._rgba=[];r!==t&&(n=[n,r,i,u],f="array");if(f==="string")return this.parse(p(n)||l._default);if(f==="array")return c(o.rgba.props,function(e,t){d[t.idx]=h(n[t.idx],t)}),this;if(f==="object")return n instanceof s?c(o,function(e,t){n[t.cache]&&(a[t.cache]=n[t.cache].slice())}):c(o,function(t,r){var i=r.cache;c(r.props,function(e,t){if(!a[i]&&r.to){if(e==="alpha"||n[e]==null)return;a[i]=r.to(a._rgba)}a[i][t.idx]=h(n[e],t,!0)}),a[i]&&e.inArray(null,a[i].slice(0,3))<0&&(a[i][3]=1,r.from&&(a._rgba=r.from(a[i])))}),this},is:function(e){var t=s(e),n=!0,r=this;return c(o,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],c(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return c(o,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=s(e),r=n._space(),i=o[r],a=this.alpha()===0?s("transparent"):this,f=a[i.cache]||i.to(a._rgba),l=f.slice();return n=n[i.cache],c(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],a=u[r.type]||{};if(o===null)return;s===null?l[i]=o:(a.mod&&(o-s>a.mod/2?s+=a.mod:s-o>a.mod/2&&(s-=a.mod)),l[i]=h((o-s)*t+s,r))}),this[r](l)},blend:function(t){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=s(t)._rgba;return s(e.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var t="rgba(",n=e.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),t="rgb("),t+n.join()+")"},toHslaString:function(){var t="hsla(",n=e.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),t="hsl("),t+n.join()+")"},toHexString:function(t){var n=this._rgba.slice(),r=n.pop();return t&&n.push(~~(r*255)),"#"+e.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),s.fn.parse.prototype=s.fn,o.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,u===0?c=0:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},o.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(d(o,s,t+1/3)*255),Math.round(d(o,s,t)*255),Math.round(d(o,s,t-1/3)*255),i]},c(o,function(n,i){var o=i.props,u=i.cache,a=i.to,f=i.from;s.fn[n]=function(n){a&&!this[u]&&(this[u]=a(this._rgba));if(n===t)return this[u].slice();var r,i=e.type(n),l=i==="array"||i==="object"?n:arguments,p=this[u].slice();return c(o,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=p[t.idx]),p[t.idx]=h(n,t)}),f?(r=s(f(p)),r[u]=p,r):s(p)},c(o,function(t,i){if(s.fn[t])return;s.fn[t]=function(s){var o=e.type(s),u=t==="alpha"?this._hsla?"hsla":"rgba":n,a=this[u](),f=a[i.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=e.type(s)),s==null&&i.empty?this:(o==="string"&&(l=r.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[i.idx]=s,this[u](a)))}})}),s.hook=function(t){var n=t.split(" ");c(n,function(t,n){e.cssHooks[n]={set:function(t,r){var i,o,u="";if(r!=="transparent"&&(e.type(r)!=="string"||(i=p(r)))){r=s(i||r);if(!a.rgba&&r._rgba[3]!==1){o=n==="backgroundColor"?t.parentNode:t;while((u===""||u==="transparent")&&o&&o.style)try{u=e.css(o,"backgroundColor"),o=o.parentNode}catch(f){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{t.style[n]=r}catch(f){}}},e.fx.step[n]=function(t){t.colorInit||(t.start=s(t.elem,n),t.end=s(t.end),t.colorInit=!0),e.cssHooks[n].set(t.elem,t.start.transition(t.end,t.pos))}})},s.hook(n),e.cssHooks.borderColor={expand:function(e){var t={};return c(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},l=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(t){var n,r,i=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,s={};if(i&&i.length&&i[0]&&i[i[0]]){r=i.length;while(r--)n=i[r],typeof i[n]=="string"&&(s[e.camelCase(n)]=i[n])}else for(n in i)typeof i[n]=="string"&&(s[n]=i[n]);return s}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").addBack():r;f=f.map(function(){var t=e(this);return{el:t,start:i(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=e.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function r(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function i(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]}e.extend(e.effects,{version:"1.10.0",save:function(e,t){for(var r=0;r<t.length;r++)t[r]!==null&&e.data(n+t[r],e[0].style[t[r]])},restore:function(e,r){var i,s;for(s=0;s<r.length;s++)r[s]!==null&&(i=e.data(n+r[s]),i===t&&(i=""),e.css(r[s],i))},setMode:function(e,t){return t==="toggle"&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var n,r;switch(e[0]){case"top":n=0;break;case"middle":n=.5;break;case"bottom":n=1;break;default:n=e[0]/t.height}switch(e[1]){case"left":r=0;break;case"center":r=.5;break;case"right":r=1;break;default:r=e[1]/t.width}return{x:r,y:n}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var n={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},r=e("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function o(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,o=t.mode;(r.is(":hidden")?o==="hide":o==="show")?u():s.call(r[0],t,u)}var t=r.apply(this,arguments),n=t.mode,i=t.queue,s=e.effects.effect[t.effect];return e.fx.off||!s?n?this[n](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):i===!1?this.each(o):this.queue(i||"fx",o)},_show:e.fn.show,show:function(e){if(i(e))return this._show.apply(this,arguments);var t=r.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(i(e))return this._hide.apply(this,arguments);var t=r.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(i(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=r.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}})(jQuery);(function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m<l;m++)g={},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p).animate(y,h,p),f=o?f*2:f/2;o&&(g={opacity:0},g[d]=(v?"-=":"+=")+f,r.animate(g,h,p)),r.queue(function(){o&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),w>1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h<r;h++){v=a.top+h*l,g=h-(r-1)/2;for(p=0;p<i;p++)d=a.left+p*f,m=p-(i-1)/2,s.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}})(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);(function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p<a;p++)r.animate({opacity:l},f,t.easing),l=1-l;r.animate({opacity:l},f,t.easing),r.queue(function(){o&&r.hide(),n()}),h>1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}})(jQuery);(function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m<a;m++)r.animate(d,l,t.easing).animate(v,l,t.easing);r.animate(d,l,t.easing).animate(p,l/2,t.easing).queue(function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}),y>1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e("<div class='ui-effects-transfer'></div>").appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}})(jQuery);(function(){function b(){return b}function c(b,d){var e=c.resolve(b),f=c.modules[e];if(!f)throw new Error('failed to require "'+b+'" from '+d);return f.exports||(f.exports={},f.call(f.exports,f,f.exports,c.relative(e),a)),f.exports}var a=this;c.modules={},c.resolve=function(a){var b=a,d=a+".js",e=a+"/index.js";return c.modules[d]&&d||c.modules[e]&&e||b},c.register=function(a,b){c.modules[a]=b},c.relative=function(a){return function(d){if("debug"==d)return b;if("."!=d.charAt(0))return c(d);var e=a.split("/"),f=d.split("/");e.pop();for(var g=0;g<f.length;g++){var h=f[g];".."==h?e.pop():"."!=h&&e.push(h)}return c(e.join("/"),a)}},c.register("color.js",function(a,b,c,d){var e=c("color-convert"),f=c("color-string");a.exports=function(a){return new g(a)};var g=function(a){this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],cmyk:[0,0,0,0],alpha:1};if(typeof a=="string"){var b=f.getRgba(a);b?this.setValues("rgb",b):(b=f.getHsla(a))&&this.setValues("hsl",b)}else if(typeof a=="object"){var b=a;b.r!==undefined||b.red!==undefined?this.setValues("rgb",b):b.l!==undefined||b.lightness!==undefined?this.setValues("hsl",b):b.v!==undefined||b.value!==undefined?this.setValues("hsv",b):(b.c!==undefined||b.cyan!==undefined)&&this.setValues("cmyk",b)}};g.prototype={rgb:function(a){return this.setSpace("rgb",arguments)},hsl:function(a){return this.setSpace("hsl",arguments)},hsv:function(a){return this.setSpace("hsv",arguments)},cmyk:function(a){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var a=this.values.rgb;return a.push(this.values.alpha),a},hslaArray:function(){var a=this.values.hsl;return a.push(this.values.alpha),a},alpha:function(a){return a===undefined?this.values.alpha:(this.setValues("alpha",a),this)},red:function(a){return this.setChannel("rgb",0,a)},green:function(a){return this.setChannel("rgb",1,a)},blue:function(a){return this.setChannel("rgb",2,a)},hue:function(a){return this.setChannel("hsl",0,a)},saturation:function(a){return this.setChannel("hsl",1,a)},lightness:function(a){return this.setChannel("hsl",2,a)},saturationv:function(a){return this.setChannel("hsv",1,a)},value:function(a){return this.setChannel("hsv",2,a)},cyan:function(a){return this.setChannel("cmyk",0,a)},magenta:function(a){return this.setChannel("cmyk",1,a)},yellow:function(a){return this.setChannel("cmyk",2,a)},black:function(a){return this.setChannel("cmyk",3,a)},hexString:function(){return f.hexString(this.values.rgb)},rgbString:function(){return f.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return f.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return f.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return f.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return f.hslaString(this.values.hsl,this.values.alpha)},keyword:function(){return f.keyword(this.values.rgb,this.values.alpha)},luminosity:function(){var a=this.values.rgb;for(var b=0;b<a.length;b++){var c=a[b]/255;a[b]=c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4)}return.2126*a[0]+.7152*a[1]+.0722*a[2]},contrast:function(a){var b=this.luminosity(),c=a.luminosity();return b>c?(b+.05)/(c+.05):(c+.05)/(b+.05)},dark:function(){var a=this.values.rgb,b=(a[0]*299+a[1]*587+a[2]*114)/1e3;return b<128},light:function(){return!this.dark()},negate:function(){var a=[];for(var b=0;b<3;b++)a[b]=255-this.values.rgb[b];return this.setValues("rgb",a),this},lighten:function(a){return this.values.hsl[2]+=this.values.hsl[2]*a,this.setValues("hsl",this.values.hsl),this},darken:function(a){return this.values.hsl[2]-=this.values.hsl[2]*a,this.setValues("hsl",this.values.hsl),this},saturate:function(a){return this.values.hsl[1]+=this.values.hsl[1]*a,this.setValues("hsl",this.values.hsl),this},desaturate:function(a){return this.values.hsl[1]-=this.values.hsl[1]*a,this.setValues("hsl",this.values.hsl),this},greyscale:function(){var a=this.values.rgb,b=a[0]*.3+a[1]*.59+a[2]*.11;return this.setValues("rgb",[b,b,b]),this},clearer:function(a){return this.setValues("alpha",this.values.alpha-this.values.alpha*a),this},opaquer:function(a){return this.setValues("alpha",this.values.alpha+this.values.alpha*a),this},rotate:function(a){var b=this.values.hsl[0];return b=(b+a)%360,b=b<0?360+b:b,this.values.hsl[0]=b,this.setValues("hsl",this.values.hsl),this},mix:function(a,b){b=1-(b||.5);var c=b*2-1,d=this.alpha()-a.alpha(),e=((c*d==-1?c:(c+d)/(1+c*d))+1)/2,f=1-e,g=this.rgbArray(),h=a.rgbArray();for(var i=0;i<g.length;i++)g[i]=g[i]*e+h[i]*f;this.setValues("rgb",g);var j=this.alpha()*b+a.alpha()*(1-b);return this.setValues("alpha",j),this},toJSON:function(){return this.rgb()}},g.prototype.getValues=function(a){var b={};for(var c=0;c<a.length;c++)b[a[c]]=this.values[a][c];return this.values.alpha!=1&&(b.a=this.values.alpha),b},g.prototype.setValues=function(a,b){var c={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],cmyk:["cyan","magenta","yellow","black"]},d={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],cmyk:[100,100,100,100]},f=1;if(a=="alpha")f=b;else if(b.length)this.values[a]=b.slice(0,a.length),f=b[a.length];else if(b[a[0]]!==undefined){for(var g=0;g<a.length;g++)this.values[a][g]=b[a[g]];f=b.a}else if(b[c[a][0]]!==undefined){var h=c[a];for(var g=0;g<a.length;g++)this.values[a][g]=b[h[g]];f=b.alpha}this.values.alpha=Math.max(0,Math.min(1,f||this.values.alpha));if(a=="alpha")return;for(var i in c){i!=a&&(this.values[i]=e[a][i](this.values[a]));for(var g=0;g<i.length;g++){var j=Math.max(0,Math.min(d[i][g],this.values[i][g]));this.values[i][g]=Math.round(j)}}return!0},g.prototype.setSpace=function(a,b){var c=b[0];return c===undefined?this.getValues(a):(typeof c=="number"&&(c=Array.prototype.slice.call(b)),this.setValues(a,c),this)},g.prototype.setChannel=function(a,b,c){return c===undefined?this.values[a][b]:(this.values[a][b]=c,this.setValues(a,this.values[a]),this)}}),c.register("color-string",function(a,b,c,d){function f(a){if(!a)return;var b=/^#([a-fA-F0-9]{3})$/,c=/^#([a-fA-F0-9]{6})$/,d=/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d\.]+)\s*)?\)$/,f=/^rgba?\(\s*([\d\.]+)\%\s*,\s*([\d\.]+)\%\s*,\s*([\d\.]+)\%\s*(?:,\s*([\d\.]+)\s*)?\)$/,g=/(\D+)/,h=[0,0,0],i=1,j=a.match(b);if(j){j=j[1];for(var k=0;k<h.length;k++)h[k]=parseInt(j[k]+j[k],16)}else if(j=a.match(c)){j=j[1];for(var k=0;k<h.length;k++)h[k]=parseInt(j.slice(k*2,k*2+2),16)}else if(j=a.match(d)){for(var k=0;k<h.length;k++)h[k]=parseInt(j[k+1]);i=parseFloat(j[4])}else if(j=a.match(f)){for(var k=0;k<h.length;k++)h[k]=Math.round(parseFloat(j[k+1])*2.55);i=parseFloat(j[4])}else if(j=a.match(g)){if(j[1]=="transparent")return[0,0,0,0];h=e.keyword2rgb(j[1]);if(!h)return}for(var k=0;k<h.length;k++)h[k]=s(h[k],0,255);return i?i=s(i,0,1):i=1,h.push(i),h}function g(a){if(!a)return;var b=/^hsla?\(\s*(\d+)\s*,\s*([\d\.]+)%\s*,\s*([\d\.]+)%\s*(?:,\s*([\d\.]+)\s*)?\)/,c=a.match(b);if(c){var d=s(parseInt(c[1]),0,360),e=s(parseFloat(c[2]),0,100),f=s(parseFloat(c[3]),0,100),g=s(parseFloat(c[4])||1,0,1);return[d,e,f,g]}}function h(a){var b=f(a);return b&&b.slice(0,3)}function i(a){var b=g(a);return b&&b.slice(0,3)}function j(a){var b=f(a);if(b)return b[3];if(b=g(a))return b[3]}function k(a){return"#"+t(a[0])+t(a[1])+t(a[2])}function l(a,b){return b<1||a[3]&&a[3]<1?m(a,b):"rgb("+a[0]+", "+a[1]+", "+a[2]+")"}function m(a,b){return"rgba("+a[0]+", "+a[1]+", "+a[2]+", "+(b||a[3]||1)+")"}function n(a,b){if(b<1||a[3]&&a[3]<1)return o(a,b);var c=Math.round(a[0]/255*100),d=Math.round(a[1]/255*100),e=Math.round(a[2]/255*100);return"rgb("+c+"%, "+d+"%, "+e+"%)"}function o(a,b){var c=Math.round(a[0]/255*100),d=Math.round(a[1]/255*100),e=Math.round(a[2]/255*100);return"rgba("+c+"%, "+d+"%, "+e+"%, "+(b||a[3]||1)+")"}function p(a,b){return b<1||a[3]&&a[3]<1?q(a,b):"hsl("+a[0]+", "+a[1]+"%, "+a[2]+"%)"}function q(a,b){return"hsla("+a[0]+", "+a[1]+"%, "+a[2]+"%, "+(b||a[3]||1)+")"}function r(a){return e.rgb2keyword(a.slice(0,3))}function s(a,b,c){return Math.min(Math.max(b,a),c)}function t(a){var b=a.toString(16).toUpperCase();return b.length<2?"0"+b:b}var e=c("color-convert");a.exports={getRgba:f,getHsla:g,getRgb:h,getHsl:i,getAlpha:j,hexString:k,rgbString:l,rgbaString:m,percentString:n,percentaString:o,hslString:p,hslaString:q,keyword:r}}),c.register("color-convert",function(a,b,c,d){function l(a){for(var b=0;b<a.length;b++)a[b]=Math.round(a[b])}var e=c("conversions"),f=function(){return new k};for(var g in e){f[g+"Raw"]=function(a){return function(b){return typeof b=="number"&&(b=Array.prototype.slice.call(arguments)),e[a](b)}}(g);var h=/(\w+)2(\w+)/.exec(g),i=h[1],j=h[2];f[i]=f[i]||{},f[i][j]=f[g]=function(a){return function(b){typeof b=="number"&&(b=Array.prototype.slice.call(arguments));var c=e[a](b);return typeof c=="string"||c===undefined?c:(l(c),c)}}(g)}var k=function(){this.space="rgb",this.convs={rgb:[0,0,0]}};k.prototype.routeSpace=function(a,b){var c=b[0];return c===undefined?this.getValues(a):(typeof c=="number"&&(c=Array.prototype.slice.call(b)),this.setValues(a,c))},k.prototype.setValues=function(a,b){return this.space=a,this.convs={},this.convs[a]=b,this},k.prototype.getValues=function(a){var b=this.convs[a];if(!b){var c=this.space,d=this.convs[c];b=f[c][a](d),this.convs[a]=b}else l(b);return b},["rgb","hsl","hsv","cmyk","keyword"].forEach(function(a){k.prototype[a]=function(b){return this.routeSpace(a,arguments)}}),a.exports=f}),c.register("conversions",function(a,b,c,d){function e(a){var b=a[0]/255,c=a[1]/255,d=a[2]/255,e=Math.min(b,c,d),f=Math.max(b,c,d),g=f-e,h,i,j;return f==e?h=0:b==f?h=(c-d)/g:c==f?h=2+(d-b)/g:d==f&&(h=4+(b-c)/g),h=Math.min(h*60,360),h<0&&(h+=360),j=(e+f)/2,f==e?i=0:j<=.5?i=g/(f+e):i=g/(2-f-e),[h,i*100,j*100]}function f(a){var b=a[0],c=a[1],d=a[2],e=Math.min(b,c,d),f=Math.max(b,c,d),g=f-e,h,i,j;return f==0?i=0:i=g/f*1e3/10,f==e?h=0:b==f?h=(c-d)/g:c==f?h=2+(d-b)/g:d==f&&(h=4+(b-c)/g),h=Math.min(h*60,360),h<0&&(h+=360),j=f/255*1e3/10,[h,i,j]}function g(a){var b=a[0]/255,c=a[1]/255,d=a[2]/255,e,f,g,h;return h=Math.min(1-b,1-c,1-d),e=(1-b-h)/(1-h),f=(1-c-h)/(1-h),g=(1-d-h)/(1-h),[e*100,f*100,g*100,h*100]}function h(a){return G[JSON.stringify(a)]}function i(a){var b=a[0]/255,c=a[1]/255,d=a[2]/255;b=b>.04045?Math.pow((b+.055)/1.055,2.4):b/12.92,c=c>.04045?Math.pow((c+.055)/1.055,2.4):c/12.92,d=d>.04045?Math.pow((d+.055)/1.055,2.4):d/12.92;var e=b*.4124+c*.3576+d*.1805,f=b*.2126+c*.7152+d*.0722,g=b*.0193+c*.1192+d*.9505;return[e*100,f*100,g*100]}function j(a){var b=i(a),c=b[0],d=b[1],e=b[2],f,g,h;return c/=95.047,d/=100,e/=108.883,c=c>.008856?Math.pow(c,1/3):7.787*c+16/116,d=d>.008856?Math.pow(d,1/3):7.787*d+16/116,e=e>.008856?Math.pow(e,1/3):7.787*e+16/116,f=116*d-16,g=500*(c-d),h=200*(d-e),[f,g,h]}function k(a){var b=a[0]/360,c=a[1]/100,d=a[2]/100,e,f,g,h,i;if(c==0)return i=d*255,[i,i,i];d<.5?f=d*(1+c):f=d+c-d*c,e=2*d-f,h=[0,0,0];for(var j=0;j<3;j++)g=b+1/3*-(j-1),g<0&&g++,g>1&&g--,6*g<1?i=e+(f-e)*6*g:2*g<1?i=f:3*g<2?i=e+(f-e)*(2/3-g)*6:i=e,h[j]=i*255;return h}function l(a){var b=a[0],c=a[1]/100,d=a[2]/100,e,f;return d*=2,c*=d<=1?d:2-d,f=(d+c)/2,e=2*c/(d+c),[b,e*100,f*100]}function m(a){return g(k(a))}function n(a){return h(k(a))}function o(a){var b=a[0]/60,c=a[1]/100,d=a[2]/100,e=Math.floor(b)%6,f=b-Math.floor(b),g=255*d*(1-c),h=255*d*(1-c*f),i=255*d*(1-c*(1-f)),d=255*d;switch(e){case 0:return[d,i,g];case 1:return[h,d,g];case 2:return[g,d,i];case 3:return[g,h,d];case 4:return[i,g,d];case 5:return[d,g,h]}}function p(a){var b=a[0],c=a[1]/100,d=a[2]/100,e,f;return f=(2-c)*d,e=c*d,e/=f<=1?f:2-f,f/=2,[b,e*100,f*100]}function q(a){return g(o(a))}function r(a){return h(o(a))}function s(a){var b=a[0]/100,c=a[1]/100,d=a[2]/100,e=a[3]/100,f,g,h;return f=1-Math.min(1,b*(1-e)+e),g=1-Math.min(1,c*(1-e)+e),h=1-Math.min(1,d*(1-e)+e),[f*255,g*255,h*255]}function t(a){return e(s(a))}function u(a){return f(s(a))}function v(a){return h(s(a))}function w(a){var b=a[0]/100,c=a[1]/100,d=a[2]/100,e,f,g;return e=b*3.2406+c*-1.5372+d*-0.4986,f=b*-0.9689+c*1.8758+d*.0415,g=b*.0557+c*-0.204+d*1.057,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,f=f>.0031308?1.055*Math.pow(f,1/2.4)-.055:f*=12.92,g=g>.0031308?1.055*Math.pow(g,1/2.4)-.055:g*=12.92,e=e<0?0:e,f=f<0?0:f,g=g<0?0:g,[e*255,f*255,g*255]}function x(a){var b=a[0],c=a[1],d=a[2],e,f,g;return b/=95.047,c/=100,d/=108.883,b=b>.008856?Math.pow(b,1/3):7.787*b+16/116,c=c>.008856?Math.pow(c,1/3):7.787*c+16/116,d=d>.008856?Math.pow(d,1/3):7.787*d+16/116,e=116*c-16,f=500*(b-c),g=200*(c-d),[e,f,g]}function y(a){var b=a[0],c=a[1],d=a[2],e,f,g,h;return b<=8?(f=b*100/903.3,h=7.787*(f/100)+16/116):(f=100*Math.pow((b+16)/116,3),h=Math.pow(f/100,1/3)),e=e/95.047<=.008856?e=95.047*(c/500+h-16/116)/7.787:95.047*Math.pow(c/500+h,3),g=g/108.883<=.008859?g=108.883*(h-d/200-16/116)/7.787:108.883*Math.pow(h-d/200,3),[e,f,g]}function z(a){return F[a]}function A(a){return e(z(a))}function B(a){return f(z(a))}function C(a){return g(z(a))}function D(a){return j(z(a))}function E(a){return i(z(a))}a.exports={rgb2hsl:e,rgb2hsv:f,rgb2cmyk:g,rgb2keyword:h,rgb2xyz:i,rgb2lab:j,hsl2rgb:k,hsl2hsv:l,hsl2cmyk:m,hsl2keyword:n,hsv2rgb:o,hsv2hsl:p,hsv2cmyk:q,hsv2keyword:r,cmyk2rgb:s,cmyk2hsl:t,cmyk2hsv:u,cmyk2keyword:v,keyword2rgb:z,keyword2hsl:A,keyword2hsv:B,keyword2cmyk:C,keyword2lab:D,keyword2xyz:E,xyz2rgb:w,xyz2lab:x,lab2xyz:y};var F={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},G={};for(var H in F)G[JSON.stringify(F[H])]=H}),Color=c("color.js")})();/**
+ * Really Simple Color Picker in jQuery
+ *
+ * Licensed under the MIT (MIT-LICENSE.txt) licenses.
+ *
+ * Copyright (c) 2008-2012
+ * Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */(function(a){var b,c,d=0,e={control:a('<div class="colorPicker-picker">&nbsp;</div>'),palette:a('<div id="colorPicker_palette" class="colorPicker-palette" />'),swatch:a('<div class="colorPicker-swatch">&nbsp;</div>'),hexLabel:a('<label for="colorPicker_hex">Hex</label>'),hexField:a('<input type="text" id="colorPicker_hex" />')},f="transparent",g;a.fn.colorPicker=function(b){return this.each(function(){var c=a(this),g=a.extend({},a.fn.colorPicker.defaults,b),h=a.fn.colorPicker.toHex(c.val().length>0?c.val():g.pickerDefault),i=e.control.clone(),j=e.palette.clone().attr("id","colorPicker_palette-"+d),k=e.hexLabel.clone(),l=e.hexField.clone(),m=j[0].id,n;a.each(g.colors,function(b){n=e.swatch.clone(),g.colors[b]===f?(n.addClass(f).text("X"),a.fn.colorPicker.bindPalette(l,n,f)):(n.css("background-color","#"+this),a.fn.colorPicker.bindPalette(l,n)),n.appendTo(j)}),k.attr("for","colorPicker_hex-"+d),l.attr({id:"colorPicker_hex-"+d,value:h}),l.bind("keydown",function(b){if(b.keyCode===13){var d=a.fn.colorPicker.toHex(a(this).val());a.fn.colorPicker.changeColor(d?d:c.val())}b.keyCode===27&&a.fn.colorPicker.hidePalette()}),l.bind("keyup",function(b){var d=a.fn.colorPicker.toHex(a(b.target).val());a.fn.colorPicker.previewColor(d?d:c.val())}),a('<div class="colorPicker_hexWrap" />').append(k).appendTo(j),j.find(".colorPicker_hexWrap").append(l),a("body").append(j),j.hide(),i.css("background-color",h),i.bind("click",function(){c.is(":not(:disabled)")&&a.fn.colorPicker.togglePalette(a("#"+m),a(this))}),b&&b.onColorChange?i.data("onColorChange",b.onColorChange):i.data("onColorChange",function(){}),c.after(i),c.bind("change",function(){c.next(".colorPicker-picker").css("background-color",a.fn.colorPicker.toHex(a(this).val()))}),c.val(h).hide(),d++})},a.extend(!0,a.fn.colorPicker,{toHex:function(a){if(a.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i))return a.charAt(0)==="#"?a:"#"+a;if(!a.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/))return!1;var b=[parseInt(RegExp.$1,10),parseInt(RegExp.$2,10),parseInt(RegExp.$3,10)],c=function(a){if(a.length<2)for(var b=0,c=2-a.length;b<c;b++)a="0"+a;return a};if(b.length===3){var d=c(b[0].toString(16)),e=c(b[1].toString(16)),f=c(b[2].toString(16));return"#"+d+e+f}},checkMouse:function(d,e){var f=c,g=a(d.target).parents("#"+f.attr("id")).length;if(d.target===a(f)[0]||d.target===b[0]||g>0)return;a.fn.colorPicker.hidePalette()},hidePalette:function(){a(document).unbind("mousedown",a.fn.colorPicker.checkMouse),a(".colorPicker-palette").hide()},showPalette:function(c){var d=b.prev("input").val();c.css({top:b.offset().top+b.outerHeight(),left:b.offset().left}),a("#color_value").val(d),c.show(),a(document).bind("mousedown",a.fn.colorPicker.checkMouse)},togglePalette:function(d,e){e&&(b=e),c=d,c.is(":visible")?a.fn.colorPicker.hidePalette():a.fn.colorPicker.showPalette(d)},changeColor:function(c){b.css("background-color",c),b.prev("input").val(c).change(),a.fn.colorPicker.hidePalette(),b.data("onColorChange").call(b,a(b).prev("input").attr("id"),c)},previewColor:function(a){b.css("background-color",a)},bindPalette:function(c,d,e){e=e?e:a.fn.colorPicker.toHex(d.css("background-color")),d.bind({click:function(b){g=e,a.fn.colorPicker.changeColor(e)},mouseover:function(b){g=c.val(),a(this).css("border-color","#598FEF"),c.val(e),a.fn.colorPicker.previewColor(e)},mouseout:function(d){a(this).css("border-color","#000"),c.val(b.css("background-color")),c.val(g),a.fn.colorPicker.previewColor(g)}})}}),a.fn.colorPicker.defaults={pickerDefault:"FFFFFF",colors:["000000","993300","333300","000080","333399","333333","800000","FF6600","808000","008000","008080","0000FF","666699","808080","FF0000","FF9900","99CC00","339966","33CCCC","3366FF","800080","999999","FF00FF","FFCC00","FFFF00","00FF00","00FFFF","00CCFF","993366","C0C0C0","FF99CC","FFCC99","FFFF99","CCFFFF","99CCFF","FFFFFF"],addColors:[]}})(jQuery);/*! Hammer.JS - v1.1.3 - 2014-05-20
+ * http://eightmedia.github.io/hammer.js
+ *
+ * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
+ * Licensed under the MIT license */
+
+
+!function(a,b){"use strict";function c(){d.READY||(s.determineEventTypes(),r.each(d.gestures,function(a){u.register(a)}),s.onTouch(d.DOCUMENT,n,u.detect),s.onTouch(d.DOCUMENT,o,u.detect),d.READY=!0)}var d=function v(a,b){return new v.Instance(a,b||{})};d.VERSION="1.1.3",d.defaults={behavior:{userSelect:"none",touchAction:"pan-y",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},d.DOCUMENT=document,d.HAS_POINTEREVENTS=navigator.pointerEnabled||navigator.msPointerEnabled,d.HAS_TOUCHEVENTS="ontouchstart"in a,d.IS_MOBILE=/mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent),d.NO_MOUSEEVENTS=d.HAS_TOUCHEVENTS&&d.IS_MOBILE||d.HAS_POINTEREVENTS,d.CALCULATE_INTERVAL=25;var e={},f=d.DIRECTION_DOWN="down",g=d.DIRECTION_LEFT="left",h=d.DIRECTION_UP="up",i=d.DIRECTION_RIGHT="right",j=d.POINTER_MOUSE="mouse",k=d.POINTER_TOUCH="touch",l=d.POINTER_PEN="pen",m=d.EVENT_START="start",n=d.EVENT_MOVE="move",o=d.EVENT_END="end",p=d.EVENT_RELEASE="release",q=d.EVENT_TOUCH="touch";d.READY=!1,d.plugins=d.plugins||{},d.gestures=d.gestures||{};var r=d.utils={extend:function(a,c,d){for(var e in c)!c.hasOwnProperty(e)||a[e]!==b&&d||(a[e]=c[e]);return a},on:function(a,b,c){a.addEventListener(b,c,!1)},off:function(a,b,c){a.removeEventListener(b,c,!1)},each:function(a,c,d){var e,f;if("forEach"in a)a.forEach(c,d);else if(a.length!==b){for(e=0,f=a.length;f>e;e++)if(c.call(d,a[e],e,a)===!1)return}else for(e in a)if(a.hasOwnProperty(e)&&c.call(d,a[e],e,a)===!1)return},inStr:function(a,b){return a.indexOf(b)>-1},inArray:function(a,b){if(a.indexOf){var c=a.indexOf(b);return-1===c?!1:c}for(var d=0,e=a.length;e>d;d++)if(a[d]===b)return d;return!1},toArray:function(a){return Array.prototype.slice.call(a,0)},hasParent:function(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1},getCenter:function(a){var b=[],c=[],d=[],e=[],f=Math.min,g=Math.max;return 1===a.length?{pageX:a[0].pageX,pageY:a[0].pageY,clientX:a[0].clientX,clientY:a[0].clientY}:(r.each(a,function(a){b.push(a.pageX),c.push(a.pageY),d.push(a.clientX),e.push(a.clientY)}),{pageX:(f.apply(Math,b)+g.apply(Math,b))/2,pageY:(f.apply(Math,c)+g.apply(Math,c))/2,clientX:(f.apply(Math,d)+g.apply(Math,d))/2,clientY:(f.apply(Math,e)+g.apply(Math,e))/2})},getVelocity:function(a,b,c){return{x:Math.abs(b/a)||0,y:Math.abs(c/a)||0}},getAngle:function(a,b){var c=b.clientX-a.clientX,d=b.clientY-a.clientY;return 180*Math.atan2(d,c)/Math.PI},getDirection:function(a,b){var c=Math.abs(a.clientX-b.clientX),d=Math.abs(a.clientY-b.clientY);return c>=d?a.clientX-b.clientX>0?g:i:a.clientY-b.clientY>0?h:f},getDistance:function(a,b){var c=b.clientX-a.clientX,d=b.clientY-a.clientY;return Math.sqrt(c*c+d*d)},getScale:function(a,b){return a.length>=2&&b.length>=2?this.getDistance(b[0],b[1])/this.getDistance(a[0],a[1]):1},getRotation:function(a,b){return a.length>=2&&b.length>=2?this.getAngle(b[1],b[0])-this.getAngle(a[1],a[0]):0},isVertical:function(a){return a==h||a==f},setPrefixedCss:function(a,b,c,d){var e=["","Webkit","Moz","O","ms"];b=r.toCamelCase(b);for(var f=0;f<e.length;f++){var g=b;if(e[f]&&(g=e[f]+g.slice(0,1).toUpperCase()+g.slice(1)),g in a.style){a.style[g]=(null==d||d)&&c||"";break}}},toggleBehavior:function(a,b,c){if(b&&a&&a.style){r.each(b,function(b,d){r.setPrefixedCss(a,d,b,c)});var d=c&&function(){return!1};"none"==b.userSelect&&(a.onselectstart=d),"none"==b.userDrag&&(a.ondragstart=d)}},toCamelCase:function(a){return a.replace(/[_-]([a-z])/g,function(a){return a[1].toUpperCase()})}},s=d.event={preventMouseEvents:!1,started:!1,shouldDetect:!1,on:function(a,b,c,d){var e=b.split(" ");r.each(e,function(b){r.on(a,b,c),d&&d(b)})},off:function(a,b,c,d){var e=b.split(" ");r.each(e,function(b){r.off(a,b,c),d&&d(b)})},onTouch:function(a,b,c){var f=this,g=function(e){var g,h=e.type.toLowerCase(),i=d.HAS_POINTEREVENTS,j=r.inStr(h,"mouse");j&&f.preventMouseEvents||(j&&b==m&&0===e.button?(f.preventMouseEvents=!1,f.shouldDetect=!0):i&&b==m?f.shouldDetect=1===e.buttons||t.matchType(k,e):j||b!=m||(f.preventMouseEvents=!0,f.shouldDetect=!0),i&&b!=o&&t.updatePointer(b,e),f.shouldDetect&&(g=f.doDetect.call(f,e,b,a,c)),g==o&&(f.preventMouseEvents=!1,f.shouldDetect=!1,t.reset()),i&&b==o&&t.updatePointer(b,e))};return this.on(a,e[b],g),g},doDetect:function(a,b,c,d){var e=this.getTouchList(a,b),f=e.length,g=b,h=e.trigger,i=f;b==m?h=q:b==o&&(h=p,i=e.length-(a.changedTouches?a.changedTouches.length:1)),i>0&&this.started&&(g=n),this.started=!0;var j=this.collectEventData(c,g,e,a);return b!=o&&d.call(u,j),h&&(j.changedLength=i,j.eventType=h,d.call(u,j),j.eventType=g,delete j.changedLength),g==o&&(d.call(u,j),this.started=!1),g},determineEventTypes:function(){var b;return b=d.HAS_POINTEREVENTS?a.PointerEvent?["pointerdown","pointermove","pointerup pointercancel lostpointercapture"]:["MSPointerDown","MSPointerMove","MSPointerUp MSPointerCancel MSLostPointerCapture"]:d.NO_MOUSEEVENTS?["touchstart","touchmove","touchend touchcancel"]:["touchstart mousedown","touchmove mousemove","touchend touchcancel mouseup"],e[m]=b[0],e[n]=b[1],e[o]=b[2],e},getTouchList:function(a,b){if(d.HAS_POINTEREVENTS)return t.getTouchList();if(a.touches){if(b==n)return a.touches;var c=[],e=[].concat(r.toArray(a.touches),r.toArray(a.changedTouches)),f=[];return r.each(e,function(a){r.inArray(c,a.identifier)===!1&&f.push(a),c.push(a.identifier)}),f}return a.identifier=1,[a]},collectEventData:function(a,b,c,d){var e=k;return r.inStr(d.type,"mouse")||t.matchType(j,d)?e=j:t.matchType(l,d)&&(e=l),{center:r.getCenter(c),timeStamp:Date.now(),target:d.target,touches:c,eventType:b,pointerType:e,srcEvent:d,preventDefault:function(){var a=this.srcEvent;a.preventManipulation&&a.preventManipulation(),a.preventDefault&&a.preventDefault()},stopPropagation:function(){this.srcEvent.stopPropagation()},stopDetect:function(){return u.stopDetect()}}}},t=d.PointerEvent={pointers:{},getTouchList:function(){var a=[];return r.each(this.pointers,function(b){a.push(b)}),a},updatePointer:function(a,b){a==o||a!=o&&1!==b.buttons?delete this.pointers[b.pointerId]:(b.identifier=b.pointerId,this.pointers[b.pointerId]=b)},matchType:function(a,b){if(!b.pointerType)return!1;var c=b.pointerType,d={};return d[j]=c===(b.MSPOINTER_TYPE_MOUSE||j),d[k]=c===(b.MSPOINTER_TYPE_TOUCH||k),d[l]=c===(b.MSPOINTER_TYPE_PEN||l),d[a]},reset:function(){this.pointers={}}},u=d.detection={gestures:[],current:null,previous:null,stopped:!1,startDetect:function(a,b){this.current||(this.stopped=!1,this.current={inst:a,startEvent:r.extend({},b),lastEvent:!1,lastCalcEvent:!1,futureCalcEvent:!1,lastCalcData:{},name:""},this.detect(b))},detect:function(a){if(this.current&&!this.stopped){a=this.extendEventData(a);var b=this.current.inst,c=b.options;return r.each(this.gestures,function(d){!this.stopped&&b.enabled&&c[d.name]&&d.handler.call(d,a,b)},this),this.current&&(this.current.lastEvent=a),a.eventType==o&&this.stopDetect(),a}},stopDetect:function(){this.previous=r.extend({},this.current),this.current=null,this.stopped=!0},getCalculatedData:function(a,b,c,e,f){var g=this.current,h=!1,i=g.lastCalcEvent,j=g.lastCalcData;i&&a.timeStamp-i.timeStamp>d.CALCULATE_INTERVAL&&(b=i.center,c=a.timeStamp-i.timeStamp,e=a.center.clientX-i.center.clientX,f=a.center.clientY-i.center.clientY,h=!0),(a.eventType==q||a.eventType==p)&&(g.futureCalcEvent=a),(!g.lastCalcEvent||h)&&(j.velocity=r.getVelocity(c,e,f),j.angle=r.getAngle(b,a.center),j.direction=r.getDirection(b,a.center),g.lastCalcEvent=g.futureCalcEvent||a,g.futureCalcEvent=a),a.velocityX=j.velocity.x,a.velocityY=j.velocity.y,a.interimAngle=j.angle,a.interimDirection=j.direction},extendEventData:function(a){var b=this.current,c=b.startEvent,d=b.lastEvent||c;(a.eventType==q||a.eventType==p)&&(c.touches=[],r.each(a.touches,function(a){c.touches.push({clientX:a.clientX,clientY:a.clientY})}));var e=a.timeStamp-c.timeStamp,f=a.center.clientX-c.center.clientX,g=a.center.clientY-c.center.clientY;return this.getCalculatedData(a,d.center,e,f,g),r.extend(a,{startEvent:c,deltaTime:e,deltaX:f,deltaY:g,distance:r.getDistance(c.center,a.center),angle:r.getAngle(c.center,a.center),direction:r.getDirection(c.center,a.center),scale:r.getScale(c.touches,a.touches),rotation:r.getRotation(c.touches,a.touches)}),a},register:function(a){var c=a.defaults||{};return c[a.name]===b&&(c[a.name]=!0),r.extend(d.defaults,c,!0),a.index=a.index||1e3,this.gestures.push(a),this.gestures.sort(function(a,b){return a.index<b.index?-1:a.index>b.index?1:0}),this.gestures}};d.Instance=function(a,b){var e=this;c(),this.element=a,this.enabled=!0,r.each(b,function(a,c){delete b[c],b[r.toCamelCase(c)]=a}),this.options=r.extend(r.extend({},d.defaults),b||{}),this.options.behavior&&r.toggleBehavior(this.element,this.options.behavior,!0),this.eventStartHandler=s.onTouch(a,m,function(a){e.enabled&&a.eventType==m?u.startDetect(e,a):a.eventType==q&&u.detect(a)}),this.eventHandlers=[]},d.Instance.prototype={on:function(a,b){var c=this;return s.on(c.element,a,b,function(a){c.eventHandlers.push({gesture:a,handler:b})}),c},off:function(a,b){var c=this;return s.off(c.element,a,b,function(a){var d=r.inArray({gesture:a,handler:b});d!==!1&&c.eventHandlers.splice(d,1)}),c},trigger:function(a,b){b||(b={});var c=d.DOCUMENT.createEvent("Event");c.initEvent(a,!0,!0),c.gesture=b;var e=this.element;return r.hasParent(b.target,e)&&(e=b.target),e.dispatchEvent(c),this},enable:function(a){return this.enabled=a,this},dispose:function(){var a,b;for(r.toggleBehavior(this.element,this.options.behavior,!1),a=-1;b=this.eventHandlers[++a];)r.off(this.element,b.gesture,b.handler);return this.eventHandlers=[],s.off(this.element,e[m],this.eventStartHandler),null}},function(a){function b(b,d){var e=u.current;if(!(d.options.dragMaxTouches>0&&b.touches.length>d.options.dragMaxTouches))switch(b.eventType){case m:c=!1;break;case n:if(b.distance<d.options.dragMinDistance&&e.name!=a)return;var j=e.startEvent.center;if(e.name!=a&&(e.name=a,d.options.dragDistanceCorrection&&b.distance>0)){var k=Math.abs(d.options.dragMinDistance/b.distance);j.pageX+=b.deltaX*k,j.pageY+=b.deltaY*k,j.clientX+=b.deltaX*k,j.clientY+=b.deltaY*k,b=u.extendEventData(b)}(e.lastEvent.dragLockToAxis||d.options.dragLockToAxis&&d.options.dragLockMinDistance<=b.distance)&&(b.dragLockToAxis=!0);var l=e.lastEvent.direction;b.dragLockToAxis&&l!==b.direction&&(b.direction=r.isVertical(l)?b.deltaY<0?h:f:b.deltaX<0?g:i),c||(d.trigger(a+"start",b),c=!0),d.trigger(a,b),d.trigger(a+b.direction,b);var q=r.isVertical(b.direction);(d.options.dragBlockVertical&&q||d.options.dragBlockHorizontal&&!q)&&b.preventDefault();break;case p:c&&b.changedLength<=d.options.dragMaxTouches&&(d.trigger(a+"end",b),c=!1);break;case o:c=!1}}var c=!1;d.gestures.Drag={name:a,index:50,handler:b,defaults:{dragMinDistance:10,dragDistanceCorrection:!0,dragMaxTouches:1,dragBlockHorizontal:!1,dragBlockVertical:!1,dragLockToAxis:!1,dragLockMinDistance:25}}}("drag"),d.gestures.Gesture={name:"gesture",index:1337,handler:function(a,b){b.trigger(this.name,a)}},function(a){function b(b,d){var e=d.options,f=u.current;switch(b.eventType){case m:clearTimeout(c),f.name=a,c=setTimeout(function(){f&&f.name==a&&d.trigger(a,b)},e.holdTimeout);break;case n:b.distance>e.holdThreshold&&clearTimeout(c);break;case p:clearTimeout(c)}}var c;d.gestures.Hold={name:a,index:10,defaults:{holdTimeout:500,holdThreshold:2},handler:b}}("hold"),d.gestures.Release={name:"release",index:1/0,handler:function(a,b){a.eventType==p&&b.trigger(this.name,a)}},d.gestures.Swipe={name:"swipe",index:40,defaults:{swipeMinTouches:1,swipeMaxTouches:1,swipeVelocityX:.6,swipeVelocityY:.6},handler:function(a,b){if(a.eventType==p){var c=a.touches.length,d=b.options;if(c<d.swipeMinTouches||c>d.swipeMaxTouches)return;(a.velocityX>d.swipeVelocityX||a.velocityY>d.swipeVelocityY)&&(b.trigger(this.name,a),b.trigger(this.name+a.direction,a))}}},function(a){function b(b,d){var e,f,g=d.options,h=u.current,i=u.previous;switch(b.eventType){case m:c=!1;break;case n:c=c||b.distance>g.tapMaxDistance;break;case o:!r.inStr(b.srcEvent.type,"cancel")&&b.deltaTime<g.tapMaxTime&&!c&&(e=i&&i.lastEvent&&b.timeStamp-i.lastEvent.timeStamp,f=!1,i&&i.name==a&&e&&e<g.doubleTapInterval&&b.distance<g.doubleTapDistance&&(d.trigger("doubletap",b),f=!0),(!f||g.tapAlways)&&(h.name=a,d.trigger(h.name,b)))}}var c=!1;d.gestures.Tap={name:a,index:100,handler:b,defaults:{tapMaxTime:250,tapMaxDistance:10,tapAlways:!0,doubleTapDistance:20,doubleTapInterval:300}}}("tap"),d.gestures.Touch={name:"touch",index:-1/0,defaults:{preventDefault:!1,preventMouse:!1},handler:function(a,b){return b.options.preventMouse&&a.pointerType==j?void a.stopDetect():(b.options.preventDefault&&a.preventDefault(),void(a.eventType==q&&b.trigger("touch",a)))}},function(a){function b(b,d){switch(b.eventType){case m:c=!1;break;case n:if(b.touches.length<2)return;var e=Math.abs(1-b.scale),f=Math.abs(b.rotation);if(e<d.options.transformMinScale&&f<d.options.transformMinRotation)return;u.current.name=a,c||(d.trigger(a+"start",b),c=!0),d.trigger(a,b),f>d.options.transformMinRotation&&d.trigger("rotate",b),e>d.options.transformMinScale&&(d.trigger("pinch",b),d.trigger("pinch"+(b.scale<1?"in":"out"),b));break;case p:c&&b.changedLength<2&&(d.trigger(a+"end",b),c=!1)}}var c=!1;d.gestures.Transform={name:a,index:45,defaults:{transformMinScale:.01,transformMinRotation:1},handler:b}}("transform"),"function"==typeof define&&define.amd?define(function(){return d}):"undefined"!=typeof module&&module.exports?module.exports=d:a.Hammer=d}(window);
+/*! jQuery plugin for Hammer.JS - v1.1.3 - 2014-05-20
+ * http://eightmedia.github.com/hammer.js
+ *
+ * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
+ * Licensed under the MIT license */
+
+!function(a,b){"use strict";function c(a,c){Date.now||(Date.now=function(){return(new Date).getTime()}),a.utils.each(["on","off"],function(d){a.utils[d]=function(a,e,f){c(a)[d](e,function(a){var d=c.extend({},a.originalEvent,a);d.button===b&&(d.button=a.which-1),f.call(this,d)})}}),a.Instance.prototype.trigger=function(a,b){var d=c(this.element);return d.has(b.target).length&&(d=c(b.target)),d.trigger({type:a,gesture:b})},c.fn.hammer=function(b){return this.each(function(){var d=c(this),e=d.data("hammer");e?e&&b&&a.utils.extend(e.options,b):d.data("hammer",new a(this,b||{}))})}}"function"==typeof define&&define.amd?define(["hammerjs","jquery"],c):c(a.Hammer,a.jQuery||a.Zepto)}(window);
+/* Mustache.js */
+(function defineMustache(global,factory){if(typeof exports==="object"&&exports&&typeof exports.nodeName!=="string"){factory(exports)}else if(typeof define==="function"&&define.amd){define(["exports"],factory)}else{global.Mustache={};factory(global.Mustache)}})(this,function mustacheFactory(mustache){var objectToString=Object.prototype.toString;var isArray=Array.isArray||function isArrayPolyfill(object){return objectToString.call(object)==="[object Array]"};function isFunction(object){return typeof object==="function"}function typeStr(obj){return isArray(obj)?"array":typeof obj}function escapeRegExp(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function hasProperty(obj,propName){return obj!=null&&typeof obj==="object"&&propName in obj}var regExpTest=RegExp.prototype.test;function testRegExp(re,string){return regExpTest.call(re,string)}var nonSpaceRe=/\S/;function isWhitespace(string){return!testRegExp(nonSpaceRe,string)}var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};function escapeHtml(string){return String(string).replace(/[&<>"'`=\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i<valueLength;++i){chr=value.charAt(i);if(isWhitespace(chr)){spaces.push(tokens.length)}else{nonSpace=true}tokens.push(["text",chr,start,start+1]);start+=1;if(chr==="\n")stripSpace()}}if(!scanner.scan(openingTagRe))break;hasTag=true;type=scanner.scan(tagRe)||"name";scanner.scan(whiteRe);if(type==="="){value=scanner.scanUntil(equalsRe);scanner.scan(equalsRe);scanner.scanUntil(closingTagRe)}else if(type==="{"){value=scanner.scanUntil(closingCurlyRe);scanner.scan(curlyRe);scanner.scanUntil(closingTagRe);type="&"}else{value=scanner.scanUntil(closingTagRe)}if(!scanner.scan(closingTagRe))throw new Error("Unclosed tag at "+scanner.pos);token=[type,value,start,scanner.pos];tokens.push(token);if(type==="#"||type==="^"){sections.push(token)}else if(type==="/"){openSection=sections.pop();if(!openSection)throw new Error('Unopened section "'+value+'" at '+start);if(openSection[1]!==value)throw new Error('Unclosed section "'+openSection[1]+'" at '+start)}else if(type==="name"||type==="{"||type==="&"){nonSpace=true}else if(type==="="){compileTags(value)}}openSection=sections.pop();if(openSection)throw new Error('Unclosed section "'+openSection[1]+'" at '+scanner.pos);return nestTokens(squashTokens(tokens))}function squashTokens(tokens){var squashedTokens=[];var token,lastToken;for(var i=0,numTokens=tokens.length;i<numTokens;++i){token=tokens[i];if(token){if(token[0]==="text"&&lastToken&&lastToken[0]==="text"){lastToken[1]+=token[1];lastToken[3]=token[3]}else{squashedTokens.push(token);lastToken=token}}}return squashedTokens}function nestTokens(tokens){var nestedTokens=[];var collector=nestedTokens;var sections=[];var token,section;for(var i=0,numTokens=tokens.length;i<numTokens;++i){token=tokens[i];switch(token[0]){case"#":case"^":collector.push(token);sections.push(token);collector=token[4]=[];break;case"/":section=sections.pop();section[5]=token[2];collector=sections.length>0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index<names.length){if(index===names.length-1)lookupHit=hasProperty(value,names[index]);value=value[names[index++]]}}else{value=context.view[name];lookupHit=hasProperty(context.view,name)}if(lookupHit)break;context=context.parent}cache[name]=value}if(isFunction(value))value=value.call(this.view);return value};function Writer(){this.cache={}}Writer.prototype.clearCache=function clearCache(){this.cache={}};Writer.prototype.parse=function parse(template,tags){var cache=this.cache;var tokens=cache[template];if(tokens==null)tokens=cache[template]=parseTemplate(template,tags);return tokens};Writer.prototype.render=function render(template,view,partials){var tokens=this.parse(template);var context=view instanceof Context?view:new Context(view);return this.renderTokens(tokens,context,partials,template)};Writer.prototype.renderTokens=function renderTokens(tokens,context,partials,originalTemplate){var buffer="";var token,symbol,value;for(var i=0,numTokens=tokens.length;i<numTokens;++i){value=undefined;token=tokens[i];symbol=token[0];if(symbol==="#")value=this.renderSection(token,context,partials,originalTemplate);else if(symbol==="^")value=this.renderInverted(token,context,partials,originalTemplate);else if(symbol===">")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j<valueLength;++j){buffer+=this.renderTokens(token[4],context.push(value[j]),partials,originalTemplate)}}else if(typeof value==="object"||typeof value==="string"||typeof value==="number"){buffer+=this.renderTokens(token[4],context.push(value),partials,originalTemplate)}else if(isFunction(value)){if(typeof originalTemplate!=="string")throw new Error("Cannot use higher-order sections without the original template");value=value.call(context.view,originalTemplate.slice(token[3],token[5]),subRender);if(value!=null)buffer+=value}else{buffer+=this.renderTokens(token[4],context,partials,originalTemplate)}return buffer};Writer.prototype.renderInverted=function renderInverted(token,context,partials,originalTemplate){var value=context.lookup(token[1]);if(!value||isArray(value)&&value.length===0)return this.renderTokens(token[4],context,partials,originalTemplate)};Writer.prototype.renderPartial=function renderPartial(token,context,partials){if(!partials)return;var value=isFunction(partials)?partials(token[1]):partials[token[1]];if(value!=null)return this.renderTokens(this.parse(value),context,partials,value)};Writer.prototype.unescapedValue=function unescapedValue(token,context){var value=context.lookup(token[1]);if(value!=null)return value};Writer.prototype.escapedValue=function escapedValue(token,context){var value=context.lookup(token[1]);if(value!=null)return mustache.escape(value)};Writer.prototype.rawValue=function rawValue(token){return token[1]};mustache.name="mustache.js";mustache.version="2.2.1";mustache.tags=["{{","}}"];var defaultWriter=new Writer;mustache.clearCache=function clearCache(){return defaultWriter.clearCache()};mustache.parse=function parse(template,tags){return defaultWriter.parse(template,tags)};mustache.render=function render(template,view,partials){if(typeof template!=="string"){throw new TypeError('Invalid template! Template should be a "string" '+'but "'+typeStr(template)+'" was given as the first '+"argument for mustache#render(template, view, partials)")}return defaultWriter.render(template,view,partials)};mustache.to_html=function to_html(template,view,partials,send){var result=mustache.render(template,view,partials);if(isFunction(send)){send(result)}else{return result}};mustache.escape=escapeHtml;mustache.Scanner=Scanner;mustache.Context=Context;mustache.Writer=Writer});
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+ */
+
+(function(jQuery){
+
+  jQuery.hotkeys = {
+    version: "0.8",
+
+    specialKeys: {
+      8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+      20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+      37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
+      96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+      104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
+      112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+      120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+    },
+
+    shiftNums: {
+      "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+      "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+      ".": ">",  "/": "?",  "\\": "|"
+    }
+  };
+
+  function keyHandler( handleObj ) {
+    // Only care when a possible input has been specified
+    if ( typeof handleObj.data !== "string" ) {
+      return;
+    }
+
+    var origHandler = handleObj.handler,
+        keys = handleObj.data.toLowerCase().split(" "),
+        textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"];
+
+    handleObj.handler = function( event ) {
+      // Don't fire in text-accepting inputs that we didn't directly bind to
+      if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+          jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
+        return;
+      }
+
+      // Keypress represents characters, not special keys
+      var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+          character = String.fromCharCode( event.which ).toLowerCase(),
+          key, modif = "", possible = {};
+
+      // check combinations (alt|ctrl|shift+anything)
+      if ( event.altKey && special !== "alt" ) {
+        modif += "alt+";
+      }
+
+      if ( event.ctrlKey && special !== "ctrl" ) {
+        modif += "ctrl+";
+      }
+
+      // TODO: Need to make sure this works consistently across platforms
+      if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+        modif += "meta+";
+      }
+
+      if ( event.shiftKey && special !== "shift" ) {
+        modif += "shift+";
+      }
+
+      if ( special ) {
+        possible[ modif + special ] = true;
+
+      } else {
+        possible[ modif + character ] = true;
+        possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+        // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+        if ( modif === "shift+" ) {
+          possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+        }
+      }
+
+      for ( var i = 0, l = keys.length; i < l; i++ ) {
+        if ( possible[ keys[i] ] ) {
+          return origHandler.apply( this, arguments );
+        }
+      }
+    };
+  }
+
+  jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+    jQuery.event.special[ this ] = { add: keyHandler };
+  });
+
+})( jQuery );//https://github.com/googledrive/cors-upload-sample/commits/master/upload.js
+
+
+//     Underscore.js 1.8.3
+//     http://underscorejs.org
+//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+      push             = ArrayProto.push,
+      slice            = ArrayProto.slice,
+      toString         = ObjProto.toString,
+      hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+      nativeIsArray      = Array.isArray,
+      nativeKeys         = Object.keys,
+      nativeBind         = FuncProto.bind,
+      nativeCreate       = Object.create;
+
+  // Naked function reference for surrogate-prototype-swapping.
+  var Ctor = function(){};
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.8.3';
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var optimizeCb = function(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      case 2: return function(value, other) {
+        return func.call(context, value, other);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // A mostly-internal function to generate callbacks that can be applied
+  // to each element in a collection, returning the desired result — either
+  // identity, an arbitrary callback, a property matcher, or a property accessor.
+  var cb = function(value, context, argCount) {
+    if (value == null) return _.identity;
+    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+    if (_.isObject(value)) return _.matcher(value);
+    return _.property(value);
+  };
+  _.iteratee = function(value, context) {
+    return cb(value, context, Infinity);
+  };
+
+  // An internal function for creating assigner functions.
+  var createAssigner = function(keysFunc, undefinedOnly) {
+    return function(obj) {
+      var length = arguments.length;
+      if (length < 2 || obj == null) return obj;
+      for (var index = 1; index < length; index++) {
+        var source = arguments[index],
+            keys = keysFunc(source),
+            l = keys.length;
+        for (var i = 0; i < l; i++) {
+          var key = keys[i];
+          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
+        }
+      }
+      return obj;
+    };
+  };
+
+  // An internal function for creating a new object that inherits from another.
+  var baseCreate = function(prototype) {
+    if (!_.isObject(prototype)) return {};
+    if (nativeCreate) return nativeCreate(prototype);
+    Ctor.prototype = prototype;
+    var result = new Ctor;
+    Ctor.prototype = null;
+    return result;
+  };
+
+  var property = function(key) {
+    return function(obj) {
+      return obj == null ? void 0 : obj[key];
+    };
+  };
+
+  // Helper for collection methods to determine whether a collection
+  // should be iterated as an array or as an object
+  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+  var getLength = property('length');
+  var isArrayLike = function(collection) {
+    var length = getLength(collection);
+    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+  };
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  _.each = _.forEach = function(obj, iteratee, context) {
+    iteratee = optimizeCb(iteratee, context);
+    var i, length;
+    if (isArrayLike(obj)) {
+      for (i = 0, length = obj.length; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (i = 0, length = keys.length; i < length; i++) {
+        iteratee(obj[keys[i]], keys[i], obj);
+      }
+    }
+    return obj;
+  };
+
+  // Return the results of applying the iteratee to each element.
+  _.map = _.collect = function(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length,
+        results = Array(length);
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  // Create a reducing function iterating left or right.
+  function createReduce(dir) {
+    // Optimized iterator function as using arguments.length
+    // in the main function will deoptimize the, see #1991.
+    function iterator(obj, iteratee, memo, keys, index, length) {
+      for (; index >= 0 && index < length; index += dir) {
+        var currentKey = keys ? keys[index] : index;
+        memo = iteratee(memo, obj[currentKey], currentKey, obj);
+      }
+      return memo;
+    }
+
+    return function(obj, iteratee, memo, context) {
+      iteratee = optimizeCb(iteratee, context, 4);
+      var keys = !isArrayLike(obj) && _.keys(obj),
+          length = (keys || obj).length,
+          index = dir > 0 ? 0 : length - 1;
+      // Determine the initial value if none is provided.
+      if (arguments.length < 3) {
+        memo = obj[keys ? keys[index] : index];
+        index += dir;
+      }
+      return iterator(obj, iteratee, memo, keys, index, length);
+    };
+  }
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  _.reduce = _.foldl = _.inject = createReduce(1);
+
+  // The right-associative version of reduce, also known as `foldr`.
+  _.reduceRight = _.foldr = createReduce(-1);
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, predicate, context) {
+    var key;
+    if (isArrayLike(obj)) {
+      key = _.findIndex(obj, predicate, context);
+    } else {
+      key = _.findKey(obj, predicate, context);
+    }
+    if (key !== void 0 && key !== -1) return obj[key];
+  };
+
+  // Return all the elements that pass a truth test.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, predicate, context) {
+    var results = [];
+    predicate = cb(predicate, context);
+    _.each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, predicate, context) {
+    return _.filter(obj, _.negate(cb(predicate)), context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Aliased as `any`.
+  _.some = _.any = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  };
+
+  // Determine if the array or object contains a given item (using `===`).
+  // Aliased as `includes` and `include`.
+  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
+    if (!isArrayLike(obj)) obj = _.values(obj);
+    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+    return _.indexOf(obj, item, fromIndex) >= 0;
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      var func = isFunc ? method : value[method];
+      return func == null ? func : func.apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, _.property(key));
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs) {
+    return _.filter(obj, _.matcher(attrs));
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.find(obj, _.matcher(attrs));
+  };
+
+  // Return the maximum element (or element-based computation).
+  _.max = function(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = isArrayLike(obj) ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = isArrayLike(obj) ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Shuffle a collection, using the modern version of the
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  _.shuffle = function(obj) {
+    var set = isArrayLike(obj) ? obj : _.values(obj);
+    var length = set.length;
+    var shuffled = Array(length);
+    for (var index = 0, rand; index < length; index++) {
+      rand = _.random(0, index);
+      if (rand !== index) shuffled[index] = shuffled[rand];
+      shuffled[rand] = set[index];
+    }
+    return shuffled;
+  };
+
+  // Sample **n** random values from a collection.
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (n == null || guard) {
+      if (!isArrayLike(obj)) obj = _.values(obj);
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  _.sortBy = function(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iteratee(value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, iteratee, context) {
+      var result = {};
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (isArrayLike(obj)) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
+  };
+
+  // Split a collection into two arrays: one whose elements all satisfy the given
+  // predicate, and one whose elements all do not satisfy the predicate.
+  _.partition = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var pass = [], fail = [];
+    _.each(obj, function(value, key, obj) {
+      (predicate(value, key, obj) ? pass : fail).push(value);
+    });
+    return [pass, fail];
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[0];
+    return _.initial(array, array.length - n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[array.length - 1];
+    return _.rest(array, Math.max(0, array.length - n));
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, strict, startIndex) {
+    var output = [], idx = 0;
+    for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
+      var value = input[i];
+      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
+        //flatten current level of array or arguments object
+        if (!shallow) value = flatten(value, shallow, strict);
+        var j = 0, len = value.length;
+        output.length += len;
+        while (j < len) {
+          output[idx++] = value[j++];
+        }
+      } else if (!strict) {
+        output[idx++] = value;
+      }
+    }
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, false);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+    if (!_.isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = cb(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var value = array[i],
+          computed = iteratee ? iteratee(value, i, array) : value;
+      if (isSorted) {
+        if (!i || seen !== computed) result.push(value);
+        seen = computed;
+      } else if (iteratee) {
+        if (!_.contains(seen, computed)) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (!_.contains(result, value)) {
+        result.push(value);
+      }
+    }
+    return result;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(flatten(arguments, true, true));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var item = array[i];
+      if (_.contains(result, item)) continue;
+      for (var j = 1; j < argsLength; j++) {
+        if (!_.contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = flatten(arguments, true, true, 1);
+    return _.filter(array, function(value){
+      return !_.contains(rest, value);
+    });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function() {
+    return _.unzip(arguments);
+  };
+
+  // Complement of _.zip. Unzip accepts an array of arrays and groups
+  // each array's elements on shared indices
+  _.unzip = function(array) {
+    var length = array && _.max(array, getLength).length || 0;
+    var result = Array(length);
+
+    for (var index = 0; index < length; index++) {
+      result[index] = _.pluck(array, index);
+    }
+    return result;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    var result = {};
+    for (var i = 0, length = getLength(list); i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // Generator function to create the findIndex and findLastIndex functions
+  function createPredicateIndexFinder(dir) {
+    return function(array, predicate, context) {
+      predicate = cb(predicate, context);
+      var length = getLength(array);
+      var index = dir > 0 ? 0 : length - 1;
+      for (; index >= 0 && index < length; index += dir) {
+        if (predicate(array[index], index, array)) return index;
+      }
+      return -1;
+    };
+  }
+
+  // Returns the first index on an array-like that passes a predicate test
+  _.findIndex = createPredicateIndexFinder(1);
+  _.findLastIndex = createPredicateIndexFinder(-1);
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iteratee, context) {
+    iteratee = cb(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = getLength(array);
+    while (low < high) {
+      var mid = Math.floor((low + high) / 2);
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  };
+
+  // Generator function to create the indexOf and lastIndexOf functions
+  function createIndexFinder(dir, predicateFind, sortedIndex) {
+    return function(array, item, idx) {
+      var i = 0, length = getLength(array);
+      if (typeof idx == 'number') {
+        if (dir > 0) {
+          i = idx >= 0 ? idx : Math.max(idx + length, i);
+        } else {
+          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+        }
+      } else if (sortedIndex && idx && length) {
+        idx = sortedIndex(array, item);
+        return array[idx] === item ? idx : -1;
+      }
+      if (item !== item) {
+        idx = predicateFind(slice.call(array, i, length), _.isNaN);
+        return idx >= 0 ? idx + i : -1;
+      }
+      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+        if (array[idx] === item) return idx;
+      }
+      return -1;
+    };
+  }
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
+  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (stop == null) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = step || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Determines whether to execute a function as a constructor
+  // or a normal function with the provided arguments
+  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
+    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+    var self = baseCreate(sourceFunc.prototype);
+    var result = sourceFunc.apply(self, args);
+    if (_.isObject(result)) return result;
+    return self;
+  };
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+    var args = slice.call(arguments, 2);
+    var bound = function() {
+      return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
+    };
+    return bound;
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. _ acts
+  // as a placeholder, allowing any combination of arguments to be pre-filled.
+  _.partial = function(func) {
+    var boundArgs = slice.call(arguments, 1);
+    var bound = function() {
+      var position = 0, length = boundArgs.length;
+      var args = Array(length);
+      for (var i = 0; i < length; i++) {
+        args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return executeBound(func, bound, this, this, args);
+    };
+    return bound;
+  };
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var i, length = arguments.length, key;
+    if (length <= 1) throw new Error('bindAll must be passed function names');
+    for (i = 1; i < length; i++) {
+      key = arguments[i];
+      obj[key] = _.bind(obj[key], obj);
+    }
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){
+      return func.apply(null, args);
+    }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = _.partial(_.delay, _, 1);
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    if (!options) options = {};
+    var later = function() {
+      previous = options.leading === false ? 0 : _.now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+    return function() {
+      var now = _.now();
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        if (timeout) {
+          clearTimeout(timeout);
+          timeout = null;
+        }
+        previous = now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+
+    var later = function() {
+      var last = _.now() - timestamp;
+
+      if (last < wait && last >= 0) {
+        timeout = setTimeout(later, wait - last);
+      } else {
+        timeout = null;
+        if (!immediate) {
+          result = func.apply(context, args);
+          if (!timeout) context = args = null;
+        }
+      }
+    };
+
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = _.now();
+      var callNow = immediate && !timeout;
+      if (!timeout) timeout = setTimeout(later, wait);
+      if (callNow) {
+        result = func.apply(context, args);
+        context = args = null;
+      }
+
+      return result;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return _.partial(wrapper, func);
+  };
+
+  // Returns a negated version of the passed-in predicate.
+  _.negate = function(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  };
+
+  // Returns a function that will only be executed on and after the Nth call.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Returns a function that will only be executed up to (but not including) the Nth call.
+  _.before = function(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      }
+      if (times <= 1) func = null;
+      return memo;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = _.partial(_.before, 2);
+
+  // Object Functions
+  // ----------------
+
+  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+  function collectNonEnumProps(obj, keys) {
+    var nonEnumIdx = nonEnumerableProps.length;
+    var constructor = obj.constructor;
+    var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
+
+    // Constructor is a special case.
+    var prop = 'constructor';
+    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+    while (nonEnumIdx--) {
+      prop = nonEnumerableProps[nonEnumIdx];
+      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+        keys.push(prop);
+      }
+    }
+  }
+
+  // Retrieve the names of an object's own properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  };
+
+  // Retrieve all the property names of an object.
+  _.allKeys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    var keys = [];
+    for (var key in obj) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Returns the results of applying the iteratee to each element of the object
+  // In contrast to _.map it returns an object
+  _.mapObject = function(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var keys =  _.keys(obj),
+        length = keys.length,
+        results = {},
+        currentKey;
+    for (var index = 0; index < length; index++) {
+      currentKey = keys[index];
+      results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = createAssigner(_.allKeys);
+
+  // Assigns a given object with all the own properties in the passed-in object(s)
+  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+  _.extendOwn = _.assign = createAssigner(_.keys);
+
+  // Returns the first key on an object that passes a predicate test
+  _.findKey = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = _.keys(obj), key;
+    for (var i = 0, length = keys.length; i < length; i++) {
+      key = keys[i];
+      if (predicate(obj[key], key, obj)) return key;
+    }
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(object, oiteratee, context) {
+    var result = {}, obj = object, iteratee, keys;
+    if (obj == null) return result;
+    if (_.isFunction(oiteratee)) {
+      keys = _.allKeys(obj);
+      iteratee = optimizeCb(oiteratee, context);
+    } else {
+      keys = flatten(arguments, false, false, 1);
+      iteratee = function(value, key, obj) { return key in obj; };
+      obj = Object(obj);
+    }
+    for (var i = 0, length = keys.length; i < length; i++) {
+      var key = keys[i];
+      var value = obj[key];
+      if (iteratee(value, key, obj)) result[key] = value;
+    }
+    return result;
+  };
+
+  // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj, iteratee, context) {
+    if (_.isFunction(iteratee)) {
+      iteratee = _.negate(iteratee);
+    } else {
+      var keys = _.map(flatten(arguments, false, false, 1), String);
+      iteratee = function(value, key) {
+        return !_.contains(keys, key);
+      };
+    }
+    return _.pick(obj, iteratee, context);
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = createAssigner(_.allKeys, true);
+
+  // Creates an object that inherits from the given prototype object.
+  // If additional properties are provided then they will be added to the
+  // created object.
+  _.create = function(prototype, props) {
+    var result = baseCreate(prototype);
+    if (props) _.extendOwn(result, props);
+    return result;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Returns whether an object has a given set of `key:value` pairs.
+  _.isMatch = function(object, attrs) {
+    var keys = _.keys(attrs), length = keys.length;
+    if (object == null) return !length;
+    var obj = Object(object);
+    for (var i = 0; i < length; i++) {
+      var key = keys[i];
+      if (attrs[key] !== obj[key] || !(key in obj)) return false;
+    }
+    return true;
+  };
+
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+      case '[object RegExp]':
+      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+    }
+
+    var areArrays = className === '[object Array]';
+    if (!areArrays) {
+      if (typeof a != 'object' || typeof b != 'object') return false;
+
+      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+      // from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+          _.isFunction(bCtor) && bCtor instanceof bCtor)
+          && ('constructor' in a && 'constructor' in b)) {
+        return false;
+      }
+    }
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+    // Initializing stack of traversed objects.
+    // It's done here since we only need them for objects and arrays comparison.
+    aStack = aStack || [];
+    bStack = bStack || [];
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+
+    // Recursively compare objects and arrays.
+    if (areArrays) {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      length = a.length;
+      if (length !== b.length) return false;
+      // Deep compare the contents, ignoring non-numeric properties.
+      while (length--) {
+        if (!eq(a[length], b[length], aStack, bStack)) return false;
+      }
+    } else {
+      // Deep compare objects.
+      var keys = _.keys(a), key;
+      length = keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      if (_.keys(b).length !== length) return false;
+      while (length--) {
+        // Deep compare each member
+        key = keys[length];
+        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return true;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+    return _.keys(obj).length === 0;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+        return toString.call(obj) === '[object Array]';
+      };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
+  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) === '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE < 9), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return _.has(obj, 'callee');
+    };
+  }
+
+  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
+  // IE 11 (#1621), and in Safari 8 (#1929).
+  if (typeof /./ != 'function' && typeof Int8Array != 'object') {
+    _.isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj !== +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iteratees.
+  _.identity = function(value) {
+    return value;
+  };
+
+  // Predicate-generating functions. Often useful outside of Underscore.
+  _.constant = function(value) {
+    return function() {
+      return value;
+    };
+  };
+
+  _.noop = function(){};
+
+  _.property = property;
+
+  // Generates a function for a given object that returns a given property.
+  _.propertyOf = function(obj) {
+    return obj == null ? function(){} : function(key) {
+      return obj[key];
+    };
+  };
+
+  // Returns a predicate for checking whether an object has a given set of
+  // `key:value` pairs.
+  _.matcher = _.matches = function(attrs) {
+    attrs = _.extendOwn({}, attrs);
+    return function(obj) {
+      return _.isMatch(obj, attrs);
+    };
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = optimizeCb(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  _.now = Date.now || function() {
+        return new Date().getTime();
+      };
+
+  // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+  var unescapeMap = _.invert(escapeMap);
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped
+    var source = '(?:' + _.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+  _.escape = createEscaper(escapeMap);
+  _.unescape = createEscaper(unescapeMap);
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property, fallback) {
+    var value = object == null ? void 0 : object[property];
+    if (value === void 0) {
+      value = fallback;
+    }
+    return _.isFunction(value) ? value.call(object) : value;
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  var escapeChar = function(match) {
+    return '\\' + escapes[match];
+  };
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  _.template = function(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+          (settings.escape || noMatch).source,
+          (settings.interpolate || noMatch).source,
+          (settings.evaluate || noMatch).source
+        ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escaper, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offest.
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+        "print=function(){__p+=__j.call(arguments,'');};\n" +
+        source + 'return __p;\n';
+
+    try {
+      var render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    var argument = settings.variable || 'obj';
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function. Start chaining a wrapped Underscore object.
+  _.chain = function(obj) {
+    var instance = _(obj);
+    instance._chain = true;
+    return instance;
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(instance, obj) {
+    return instance._chain ? _(obj).chain() : obj;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    _.each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+      return result(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  _.each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  // Extracts the result from a wrapped and chained object.
+  _.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // Provide unwrapping proxy for some methods used in engine operations
+  // such as arithmetic and JSON stringification.
+  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
+
+  _.prototype.toString = function() {
+    return '' + this._wrapped;
+  };
+
+  // AMD registration happens at the end for compatibility with AMD loaders
+  // that may not enforce next-turn semantics on modules. Even though general
+  // practice for AMD registration is to be anonymous, underscore registers
+  // as a named module because, like jQuery, it is a base library that is
+  // popular enough to be bundled in a third party lib, but not be part of
+  // an AMD load request. Those cases could generate an error when an
+  // anonymous define() is called outside of a loader request.
+  if (typeof define === 'function' && define.amd) {
+    define('underscore', [], function() {
+      return _;
+    });
+  }
+}.call(this));
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/filter.js b/plugins/easy_mindmup/assets/javascripts/filter.js
new file mode 100644
index 0000000000000000000000000000000000000000..506933407572e0accd606570c27772f9675c6834
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/filter.js
@@ -0,0 +1,80 @@
+(function () {
+  /**
+   * Class responsible for filtering nodes from tree and options from legend
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Filter(ysy) {
+    this.ysy = ysy;
+    this.allowedValues = [];
+    this.init(ysy);
+  }
+
+  Filter.prototype.init = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("nodeStyleChanged", function () {
+      self.reset();
+    });
+  };
+
+  Filter.prototype.className = "mindmup-node-filtered";
+  Filter.prototype.pushAllowed = function (value) {
+    this.allowedValues.push(value || 0);
+    this.sweepNodes();
+  };
+  Filter.prototype.isOn = function(){
+    return !!this.allowedValues.length;
+  };
+  Filter.prototype.removeAllowed = function (value) {
+    this.allowedValues = _.without(this.allowedValues, value);
+    this.sweepNodes();
+  };
+  Filter.prototype.toggleAllowed = function (value) {
+    var store = this.ysy.styles.getCurrentStyle();
+    if (store) this.store = store;
+    if (_.contains(this.allowedValues, value)) {
+      this.removeAllowed(value);
+    } else {
+      this.pushAllowed(value);
+    }
+  };
+  Filter.prototype.cssByBannedValue = function (value) {
+    if (!this.allowedValues.length) return "";
+    return _.contains(this.allowedValues, value) ? "" : " " + this.className;
+  };
+  Filter.prototype.reset = function () {
+    this.ysy.$container.find("." + this.className).removeClass(this.className);
+    this.allowedValues = [];
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @return {boolean}
+   */
+  Filter.prototype.isBanned = function (idea) {
+    if (!this.allowedValues.length) return false;
+    if (idea.attr.isFresh) return false;
+    var data = this.ysy.getData(idea);
+    var value = this.store.value(data);
+    return this.allowedValues.indexOf(value) === -1;
+  };
+  Filter.prototype.sweepNodes = function () {
+    this.recursiveBanner(this.ysy.idea);
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   */
+  Filter.prototype.recursiveBanner = function (idea) {
+    var banned = this.isBanned(idea);
+    var node = this.ysy.getNodeElement(idea);
+    if (node.length) {
+      node.toggleClass(this.className, banned);
+    }
+    if (idea.ideas) {
+      _.each(idea.ideas, this.recursiveBanner, this)
+    }
+  };
+
+  window.easyMindMupClasses.Filter = Filter;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/history.js b/plugins/easy_mindmup/assets/javascripts/history.js
new file mode 100644
index 0000000000000000000000000000000000000000..b6a74891004aee63d9701cf7fe4bf705fd93079f
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/history.js
@@ -0,0 +1,174 @@
+/**
+ * Created by merta on 27.4.17.
+ */
+(function () {
+  /**
+   * @param {MindMup} ysy
+   * @property {boolean} historyIsPushed
+   * @property {boolean} redoIsPushed
+   * @property {SaveButton} saveButton
+   * @property {UndoButton} undoButton
+   * @property {RedoButton} redoButton
+   * @constructor
+   */
+  function History(ysy) {
+    this.ysy = ysy;
+    this.init();
+    this.buttonsInited = false;
+    this.saveButton = null;
+    this.undoButton = null;
+    this.redoButton = null;
+    this.historyIsPushed = true;
+    this.redoIsPushed = true;
+  }
+
+  History.prototype.init = function () {
+    var self = this;
+    this.ysy.eventBus.register("TreeLoaded", /** @param {RootIdea} idea*/function (idea) {
+      idea.addEventListener('changed', $.proxy(self.historyChanged, self));
+      self.historyChanged();
+    });
+    this.ysy.eventBus.register("BeforeServerClassInit", function () {
+      self.saveButton = self.ysy.toolbar.children["save"];
+      self.undoButton = self.ysy.toolbar.children["undo"];
+      self.redoButton = self.ysy.toolbar.children["redo"];
+      self.buttonsInited = true;
+    });
+  };
+  History.prototype.historyChanged = function () {
+    var idea = this.ysy.idea;
+    if (idea.canUndo()) {
+      this.historyPushed();
+    } else {
+      this.historyEmpty();
+    }
+    if (idea.canRedo()) {
+      this.redoPushed();
+    } else {
+      this.redoEmpty();
+    }
+  };
+  History.prototype.historyEmpty = function () {
+    if (!this.historyIsPushed) return;
+    this.historyIsPushed = false;
+    $(window).unbind('beforeunload');
+    $(window).unbind('unload');
+    if (this.buttonsInited) {
+      this.saveButton.setDisabled(true);
+      this.undoButton.setDisabled(true);
+    }
+    this.ysy.log.debug("History Empty", "history");
+  };
+  History.prototype.historyPushed = function () {
+    if (this.historyIsPushed) return;
+    this.historyIsPushed = true;
+    $(window).bind('beforeunload', function (e) {
+      var message = "Some changes are not saved!";
+      e.returnValue = message;
+      return message;
+    });
+    //     .unbind('unload').bind('unload', function () {
+    //   storage.lastState.remove();
+    // });
+    if (this.buttonsInited) {
+      this.saveButton.setDisabled(false);
+      this.undoButton.setDisabled(false);
+    }
+    this.ysy.log.debug("History Pushed", "history");
+  };
+  History.prototype.redoEmpty = function () {
+    if (!this.redoIsPushed) return;
+    this.redoIsPushed = false;
+    if (this.buttonsInited) {
+      this.redoButton.setDisabled(true);
+    }
+    this.ysy.log.debug("Redo Empty", "history");
+  };
+  History.prototype.redoPushed = function () {
+    if (this.redoIsPushed) return;
+    this.redoIsPushed = true;
+    if (this.buttonsInited) {
+      this.redoButton.setDisabled(false);
+    }
+    this.ysy.log.debug("Redo Pushed", "history");
+  };
+
+
+  window.easyMindMupClasses.History = History;
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function HistoryButton(ysy, $parent) {
+    this.ysy = ysy;
+    this.isDisabled = false;
+    this.$element = $parent.find(this.elementClass);
+  }
+
+  HistoryButton.prototype.elementClass = "";
+  /**
+   * Set disable state of button
+   * @param {boolean} isDisabled
+   */
+  HistoryButton.prototype.setDisabled = function (isDisabled) {
+    this.isDisabled = isDisabled;
+    this.ysy.repainter.redrawMe(this);
+  };
+  HistoryButton.prototype._render = function () {
+    this.$element.toggleClass("mindmup__button--disabled", this.isDisabled);
+  };
+
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * @extends {HistoryButton}
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function SaveButton(ysy, $parent) {
+    HistoryButton.call(this, ysy, $parent);
+  }
+
+  classes.extendClass(SaveButton, HistoryButton);
+  SaveButton.prototype.elementClass = ".mindmup__menu-save";
+  SaveButton.prototype.id = "save";
+
+  window.easyMindMupClasses.SaveButton = SaveButton;
+
+  /**
+   * @extends {HistoryButton}
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function UndoButton(ysy, $parent) {
+    HistoryButton.call(this, ysy, $parent);
+  }
+
+  classes.extendClass(UndoButton, HistoryButton);
+  UndoButton.prototype.id = "undo";
+  UndoButton.prototype.elementClass = ".mindmup-button-undo";
+
+  window.easyMindMupClasses.UndoButton = UndoButton;
+
+  /**
+   * @extends {HistoryButton}
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function RedoButton(ysy, $parent) {
+    HistoryButton.call(this, ysy, $parent);
+  }
+
+  classes.extendClass(RedoButton, HistoryButton);
+  RedoButton.prototype.id = "redo";
+  RedoButton.prototype.elementClass = ".mindmup-button-redo";
+
+  window.easyMindMupClasses.RedoButton = RedoButton;
+
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/helpers/test.js b/plugins/easy_mindmup/assets/javascripts/jasmine/helpers/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..e3357e9a78e1b85512d0d332ef670d354c617d1c
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/helpers/test.js
@@ -0,0 +1,107 @@
+(function () {
+  /**
+   * Class responsible for Jasmine testing
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @constructor
+   */
+  function JasmineTests(ysy) {
+    this.ysy = ysy;
+    this._redrawRequested = true;
+    this.jasmineStarted = false;
+    this.startCounter = 0;
+    this.beatCallbacks = [];
+    this.extraTestNames = [];
+    this.extraTestFunctions = [];
+    this.init(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  JasmineTests.prototype.init = function (ysy) {
+    ysy.eventBus.register("TreeLoaded",function () {
+      if (!this.jasmineStarted) {
+        this.jasmineStarted = true;
+        window.jasmine.jasmineStart(ysy);
+      }
+    });
+    ysy.repainter.redrawMe(this);
+    this.loadExtraTests();
+  };
+  JasmineTests.prototype._render = function () {
+    if (this.beatCallbacks.length) {
+      var newCallbacks = [];
+      for (var i = 0; i < this.beatCallbacks.length; i++) {
+        var callPack = this.beatCallbacks[i];
+        if (callPack.rounds === 1) {
+          callPack.callback();
+        } else {
+          callPack.rounds--;
+          newCallbacks.push(callPack);
+        }
+      }
+      this.beatCallbacks = newCallbacks;
+      if(newCallbacks.length){
+        this.ysy.repainter.redrawMe(this);
+      }
+    }
+  };
+  JasmineTests.prototype.fewBeatsAfter = function (callback, count) {
+    if (count === undefined) count = 2;
+    this.beatCallbacks.push({callback: callback, rounds: count});
+    this.ysy.repainter.redrawMe(this);
+  };
+  JasmineTests.prototype.loadExtraTests = function () {
+    var self = this;
+    describe("(EXTRA)", function () {
+      for (var i = 0; i < self.extraTestFunctions.length; i++) {
+        self.extraTestFunctions[i]();
+      }
+    });
+  };
+  JasmineTests.prototype.parseResult = function () {
+    var specs = window.jsApiReporter.specs();
+    var shortReport = "";
+    var report = "";
+    var allPassed = true;
+    var result = "";
+    for (var i = 0; i < specs.length; i++) {
+      var spec = specs[i];
+      if (spec.status === "passed") {
+        shortReport += ".";
+      } else {
+        allPassed = false;
+        shortReport += "X";
+        report += "__TEST " + spec.fullName + "______\n";
+        for (var j = 0; j < spec.failedExpectations.length; j++) {
+          var fail = spec.failedExpectations[j];
+          var split = fail.stack.split("\n");
+          result += window.location + "\n";
+          report += "   " + fail.message + "\n";
+          for (var k = 1; k < split.length; k++) {
+            if (split[k].indexOf("/jasmine_lib/") > -1) break;
+            report += split[k] + "\n";
+          }
+        }
+      }
+    }
+    if (allPassed) {
+      return "success";
+    }
+    result += " RESULTS: " + shortReport + "\n" + report;
+    $("#content").text(result.replace("\n", "<br>"));
+    return result;
+  };
+  easyMindMupClasses.JasmineTests = JasmineTests;
+})();
+window.describeExtra = function (file, func) {
+  if (file.indexOf("/") === -1) {
+    file = "easy_mindmup/" + file
+  }
+  jasmine.ysyInstance.tests.extraTestNames.push(file);
+  jasmine.ysyInstance.tests.extraTestFunctions.push(function () {
+    describe(file, func);
+  });
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/boot.js b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/boot.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1fd8022951c66b4d83bef88b7f1f9c01ae72187
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/boot.js
@@ -0,0 +1,135 @@
+/**
+ Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
+
+ If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
+
+ The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
+
+ [jasmine-gem]: http://github.com/pivotal/jasmine-gem
+ */
+
+(function() {
+
+  /**
+   * ## Require &amp; Instantiate
+   *
+   * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
+   */
+  window.jasmine = jasmineRequire.core(jasmineRequire);
+
+  /**
+   * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
+   */
+  jasmineRequire.html(jasmine);
+
+  /**
+   * Create the Jasmine environment. This is used to run all specs in a project.
+   */
+  var env = jasmine.getEnv();
+
+  /**
+   * ## The Global Interface
+   *
+   * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
+   */
+  var jasmineInterface = jasmineRequire.interface(jasmine, env);
+
+  /**
+   * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
+   */
+  extend(window, jasmineInterface);
+
+  /**
+   * ## Runner Parameters
+   *
+   * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
+   */
+
+  var queryString = new jasmine.QueryString({
+    getWindowLocation: function() { return window.location; }
+  });
+
+  var catchingExceptions = queryString.getParam("catch");
+  env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
+
+  var throwingExpectationFailures = queryString.getParam("throwFailures");
+  env.throwOnExpectationFailure(throwingExpectationFailures);
+
+  var random = queryString.getParam("random");
+  env.randomizeTests(random);
+
+  var seed = queryString.getParam("seed");
+  if (seed) {
+    env.seed(seed);
+  }
+
+  /**
+   * ## Reporters
+   * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
+   */
+  var htmlReporter = new jasmine.HtmlReporter({
+    env: env,
+    onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
+    onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
+    onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); },
+    addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
+    getContainer: function() { return document.body; },
+    createElement: function() { return document.createElement.apply(document, arguments); },
+    createTextNode: function() { return document.createTextNode.apply(document, arguments); },
+    timer: new jasmine.Timer()
+  });
+
+  /**
+   * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results  from JavaScript.
+   */
+  env.addReporter(jasmineInterface.jsApiReporter);
+  env.addReporter(htmlReporter);
+
+  /**
+   * Filter which specs will be run by matching the start of the full name against the `spec` query param.
+   */
+  var specFilter = new jasmine.HtmlSpecFilter({
+    filterString: function() { return queryString.getParam("spec"); }
+  });
+
+  env.specFilter = function(spec) {
+    return specFilter.matches(spec.getFullName());
+  };
+
+  /**
+   * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
+   */
+  window.setTimeout = window.setTimeout;
+  window.setInterval = window.setInterval;
+  window.clearTimeout = window.clearTimeout;
+  window.clearInterval = window.clearInterval;
+
+  /**
+   * ## Execution
+   *
+   * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
+   */
+  var currentWindowOnload = window.onload;
+  /**
+   * @type {MindMup}
+   */
+  jasmine.ysyInstance = null;
+  jasmine.jasmineStart = function(ysyInstance) {
+    jasmine.ysyInstance = ysyInstance;
+  // window.onload = function() {   // HOSEKP
+    // if (currentWindowOnload) {
+    //   currentWindowOnload();
+    // }                            // HOSEKP
+    htmlReporter.initialize();
+    env.execute();
+  };
+
+  /**
+   * Helper function for readability above.
+   */
+  function extend(destination, source) {
+    for (var property in source) destination[property] = source[property];
+    return destination;
+  }
+
+}());
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine-html.js b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine-html.js
new file mode 100644
index 0000000000000000000000000000000000000000..da23532e9b661f71eb9b667f52a78e0cfa92e583
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine-html.js
@@ -0,0 +1,473 @@
+/*
+Copyright (c) 2008-2015 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+jasmineRequire.html = function(j$) {
+  j$.ResultsNode = jasmineRequire.ResultsNode();
+  j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
+  j$.QueryString = jasmineRequire.QueryString();
+  j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
+};
+
+jasmineRequire.HtmlReporter = function(j$) {
+
+  var noopTimer = {
+    start: function() {},
+    elapsed: function() { return 0; }
+  };
+
+  function HtmlReporter(options) {
+    var env = options.env || {},
+      getContainer = options.getContainer,
+      createElement = options.createElement,
+      createTextNode = options.createTextNode,
+      onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
+      onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
+      onRandomClick = options.onRandomClick || function() {},
+      addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
+      timer = options.timer || noopTimer,
+      results = [],
+      specsExecuted = 0,
+      failureCount = 0,
+      pendingSpecCount = 0,
+      htmlReporterMain,
+      symbols,
+      failedSuites = [];
+
+    this.initialize = function() {
+      clearPrior();
+      htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
+        createDom('div', {className: 'jasmine-banner'},
+          createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
+          createDom('span', {className: 'jasmine-version'}, j$.version)
+        ),
+        createDom('ul', {className: 'jasmine-symbol-summary'}),
+        createDom('div', {className: 'jasmine-alert'}),
+        createDom('div', {className: 'jasmine-results'},
+          createDom('div', {className: 'jasmine-failures'})
+        )
+      );
+      getContainer().appendChild(htmlReporterMain);
+    };
+
+    var totalSpecsDefined;
+    this.jasmineStarted = function(options) {
+      totalSpecsDefined = options.totalSpecsDefined || 0;
+      timer.start();
+    };
+
+    var summary = createDom('div', {className: 'jasmine-summary'});
+
+    var topResults = new j$.ResultsNode({}, '', null),
+      currentParent = topResults;
+
+    this.suiteStarted = function(result) {
+      currentParent.addChild(result, 'suite');
+      currentParent = currentParent.last();
+    };
+
+    this.suiteDone = function(result) {
+      if (result.status == 'failed') {
+        failedSuites.push(result);
+      }
+
+      if (currentParent == topResults) {
+        return;
+      }
+
+      currentParent = currentParent.parent;
+    };
+
+    this.specStarted = function(result) {
+      currentParent.addChild(result, 'spec');
+    };
+
+    var failures = [];
+    this.specDone = function(result) {
+      if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
+        console.error('Spec \'' + result.fullName + '\' has no expectations.');
+      }
+
+      if (result.status != 'disabled') {
+        specsExecuted++;
+      }
+
+      if (!symbols){
+        symbols = find('.jasmine-symbol-summary');
+      }
+
+      symbols.appendChild(createDom('li', {
+          className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status,
+          id: 'spec_' + result.id,
+          title: result.fullName
+        }
+      ));
+
+      if (result.status == 'failed') {
+        failureCount++;
+
+        var failure =
+          createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
+            createDom('div', {className: 'jasmine-description'},
+              createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
+            ),
+            createDom('div', {className: 'jasmine-messages'})
+          );
+        var messages = failure.childNodes[1];
+
+        for (var i = 0; i < result.failedExpectations.length; i++) {
+          var expectation = result.failedExpectations[i];
+          messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
+          messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
+        }
+
+        failures.push(failure);
+      }
+
+      if (result.status == 'pending') {
+        pendingSpecCount++;
+      }
+    };
+
+    this.jasmineDone = function(doneResult) {
+      var banner = find('.jasmine-banner');
+      var alert = find('.jasmine-alert');
+      var order = doneResult && doneResult.order;
+      alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
+
+      banner.appendChild(
+        createDom('div', { className: 'jasmine-run-options' },
+          createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+          createDom('div', { className: 'jasmine-payload' },
+            createDom('div', { className: 'jasmine-exceptions' },
+              createDom('input', {
+                className: 'jasmine-raise',
+                id: 'jasmine-raise-exceptions',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')),
+            createDom('div', { className: 'jasmine-throw-failures' },
+              createDom('input', {
+                className: 'jasmine-throw',
+                id: 'jasmine-throw-failures',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
+            createDom('div', { className: 'jasmine-random-order' },
+              createDom('input', {
+                className: 'jasmine-random',
+                id: 'jasmine-random-order',
+                type: 'checkbox'
+              }),
+              createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order'))
+          )
+        ));
+
+      var raiseCheckbox = find('#jasmine-raise-exceptions');
+
+      raiseCheckbox.checked = !env.catchingExceptions();
+      raiseCheckbox.onclick = onRaiseExceptionsClick;
+
+      var throwCheckbox = find('#jasmine-throw-failures');
+      throwCheckbox.checked = env.throwingExpectationFailures();
+      throwCheckbox.onclick = onThrowExpectationsClick;
+
+      var randomCheckbox = find('#jasmine-random-order');
+      randomCheckbox.checked = env.randomTests();
+      randomCheckbox.onclick = onRandomClick;
+
+      var optionsMenu = find('.jasmine-run-options'),
+          optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'),
+          optionsPayload = optionsMenu.querySelector('.jasmine-payload'),
+          isOpen = /\bjasmine-open\b/;
+
+      optionsTrigger.onclick = function() {
+        if (isOpen.test(optionsPayload.className)) {
+          optionsPayload.className = optionsPayload.className.replace(isOpen, '');
+        } else {
+          optionsPayload.className += ' jasmine-open';
+        }
+      };
+
+      if (specsExecuted < totalSpecsDefined) {
+        var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-bar jasmine-skipped'},
+            createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
+          )
+        );
+      }
+      var statusBarMessage = '';
+      var statusBarClassName = 'jasmine-bar ';
+
+      if (totalSpecsDefined > 0) {
+        statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
+        if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
+        statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed';
+      } else {
+        statusBarClassName += 'jasmine-skipped';
+        statusBarMessage += 'No specs found';
+      }
+
+      var seedBar;
+      if (order && order.random) {
+        seedBar = createDom('span', {className: 'jasmine-seed-bar'},
+          ', randomized with seed ',
+          createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
+        );
+      }
+
+      alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
+
+      for(i = 0; i < failedSuites.length; i++) {
+        var failedSuite = failedSuites[i];
+        for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
+          var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
+          var errorBarClassName = 'jasmine-bar jasmine-errored';
+          alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
+        }
+      }
+
+      var results = find('.jasmine-results');
+      results.appendChild(summary);
+
+      summaryList(topResults, summary);
+
+      function summaryList(resultsTree, domParent) {
+        var specListNode;
+        for (var i = 0; i < resultsTree.children.length; i++) {
+          var resultNode = resultsTree.children[i];
+          if (resultNode.type == 'suite') {
+            var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
+              createDom('li', {className: 'jasmine-suite-detail'},
+                createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
+              )
+            );
+
+            summaryList(resultNode, suiteListNode);
+            domParent.appendChild(suiteListNode);
+          }
+          if (resultNode.type == 'spec') {
+            if (domParent.getAttribute('class') != 'jasmine-specs') {
+              specListNode = createDom('ul', {className: 'jasmine-specs'});
+              domParent.appendChild(specListNode);
+            }
+            var specDescription = resultNode.result.description;
+            if(noExpectations(resultNode.result)) {
+              specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+            }
+            if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
+              specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+            }
+            specListNode.appendChild(
+              createDom('li', {
+                  className: 'jasmine-' + resultNode.result.status,
+                  id: 'spec-' + resultNode.result.id
+                },
+                createDom('a', {href: specHref(resultNode.result)}, specDescription)
+              )
+            );
+          }
+        }
+      }
+
+      if (failures.length) {
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
+            createDom('span', {}, 'Spec List | '),
+            createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
+        alert.appendChild(
+          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
+            createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
+            createDom('span', {}, ' | Failures ')));
+
+        find('.jasmine-failures-menu').onclick = function() {
+          setMenuModeTo('jasmine-failure-list');
+        };
+        find('.jasmine-spec-list-menu').onclick = function() {
+          setMenuModeTo('jasmine-spec-list');
+        };
+
+        setMenuModeTo('jasmine-failure-list');
+
+        var failureNode = find('.jasmine-failures');
+        for (var i = 0; i < failures.length; i++) {
+          failureNode.appendChild(failures[i]);
+        }
+      }
+    };
+
+    return this;
+
+    function find(selector) {
+      return getContainer().querySelector('.jasmine_html-reporter ' + selector);
+    }
+
+    function clearPrior() {
+      // return the reporter
+      var oldReporter = find('');
+
+      if(oldReporter) {
+        getContainer().removeChild(oldReporter);
+      }
+    }
+
+    function createDom(type, attrs, childrenVarArgs) {
+      var el = createElement(type);
+
+      for (var i = 2; i < arguments.length; i++) {
+        var child = arguments[i];
+
+        if (typeof child === 'string') {
+          el.appendChild(createTextNode(child));
+        } else {
+          if (child) {
+            el.appendChild(child);
+          }
+        }
+      }
+
+      for (var attr in attrs) {
+        if (attr == 'className') {
+          el[attr] = attrs[attr];
+        } else {
+          el.setAttribute(attr, attrs[attr]);
+        }
+      }
+
+      return el;
+    }
+
+    function pluralize(singular, count) {
+      var word = (count == 1 ? singular : singular + 's');
+
+      return '' + count + ' ' + word;
+    }
+
+    function specHref(result) {
+      return addToExistingQueryString('spec', result.fullName);
+    }
+
+    function seedHref(seed) {
+      return addToExistingQueryString('seed', seed);
+    }
+
+    function defaultQueryString(key, value) {
+      return '?' + key + '=' + value;
+    }
+
+    function setMenuModeTo(mode) {
+      htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
+    }
+
+    function noExpectations(result) {
+      return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
+        result.status === 'passed';
+    }
+  }
+
+  return HtmlReporter;
+};
+
+jasmineRequire.HtmlSpecFilter = function() {
+  function HtmlSpecFilter(options) {
+    var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+    var filterPattern = new RegExp(filterString);
+
+    this.matches = function(specName) {
+      return filterPattern.test(specName);
+    };
+  }
+
+  return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsNode = function() {
+  function ResultsNode(result, type, parent) {
+    this.result = result;
+    this.type = type;
+    this.parent = parent;
+
+    this.children = [];
+
+    this.addChild = function(result, type) {
+      this.children.push(new ResultsNode(result, type, this));
+    };
+
+    this.last = function() {
+      return this.children[this.children.length - 1];
+    };
+  }
+
+  return ResultsNode;
+};
+
+jasmineRequire.QueryString = function() {
+  function QueryString(options) {
+
+    this.navigateWithNewParam = function(key, value) {
+      options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
+    };
+
+    this.fullStringWithNewParam = function(key, value) {
+      var paramMap = queryStringToParamMap();
+      paramMap[key] = value;
+      return toQueryString(paramMap);
+    };
+
+    this.getParam = function(key) {
+      return queryStringToParamMap()[key];
+    };
+
+    return this;
+
+    function toQueryString(paramMap) {
+      var qStrPairs = [];
+      for (var prop in paramMap) {
+        qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
+      }
+      return '?' + qStrPairs.join('&');
+    }
+
+    function queryStringToParamMap() {
+      var paramStr = options.getWindowLocation().search.substring(1),
+        params = [],
+        paramMap = {};
+
+      if (paramStr.length > 0) {
+        params = paramStr.split('&');
+        for (var i = 0; i < params.length; i++) {
+          var p = params[i].split('=');
+          var value = decodeURIComponent(p[1]);
+          if (value === 'true' || value === 'false') {
+            value = JSON.parse(value);
+          }
+          paramMap[decodeURIComponent(p[0])] = value;
+        }
+      }
+
+      return paramMap;
+    }
+
+  }
+
+  return QueryString;
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine.js b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine.js
new file mode 100644
index 0000000000000000000000000000000000000000..7156e6672e7ec6aabbd2a8a8321661c338deb299
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/jasmine_lib/jasmine.js
@@ -0,0 +1,4941 @@
+/*
+ Copyright (c) 2008-2017 Pivotal Labs
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+var getJasmineRequireObj = (function (jasmineGlobal) {
+  var jasmineRequire;
+
+  if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') {
+    if (typeof global !== 'undefined') {
+      jasmineGlobal = global;
+    } else {
+      jasmineGlobal = {};
+    }
+    jasmineRequire = exports;
+  } else {
+    if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') {
+      jasmineGlobal = window;
+    }
+    jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+  }
+
+  function getJasmineRequire() {
+    return jasmineRequire;
+  }
+
+  getJasmineRequire().core = function(jRequire) {
+    var j$ = {};
+
+    jRequire.base(j$, jasmineGlobal);
+    j$.util = jRequire.util();
+    j$.errors = jRequire.errors();
+    j$.formatErrorMsg = jRequire.formatErrorMsg();
+    j$.Any = jRequire.Any(j$);
+    j$.Anything = jRequire.Anything(j$);
+    j$.CallTracker = jRequire.CallTracker(j$);
+    j$.MockDate = jRequire.MockDate();
+    j$.getClearStack = jRequire.clearStack(j$);
+    j$.Clock = jRequire.Clock();
+    j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+    j$.Env = jRequire.Env(j$);
+    j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+    j$.Expectation = jRequire.Expectation();
+    j$.buildExpectationResult = jRequire.buildExpectationResult();
+    j$.JsApiReporter = jRequire.JsApiReporter();
+    j$.matchersUtil = jRequire.matchersUtil(j$);
+    j$.ObjectContaining = jRequire.ObjectContaining(j$);
+    j$.ArrayContaining = jRequire.ArrayContaining(j$);
+    j$.pp = jRequire.pp(j$);
+    j$.QueueRunner = jRequire.QueueRunner(j$);
+    j$.ReportDispatcher = jRequire.ReportDispatcher();
+    j$.Spec = jRequire.Spec(j$);
+    j$.Spy = jRequire.Spy(j$);
+    j$.SpyRegistry = jRequire.SpyRegistry(j$);
+    j$.SpyStrategy = jRequire.SpyStrategy(j$);
+    j$.StringMatching = jRequire.StringMatching(j$);
+    j$.Suite = jRequire.Suite(j$);
+    j$.Timer = jRequire.Timer();
+    j$.TreeProcessor = jRequire.TreeProcessor();
+    j$.version = jRequire.version();
+    j$.Order = jRequire.Order();
+    j$.DiffBuilder = jRequire.DiffBuilder(j$);
+    j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
+    j$.ObjectPath = jRequire.ObjectPath(j$);
+    j$.GlobalErrors = jRequire.GlobalErrors(j$);
+
+    j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+    return j$;
+  };
+
+  return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+  var availableMatchers = [
+        'toBe',
+        'toBeCloseTo',
+        'toBeDefined',
+        'toBeFalsy',
+        'toBeGreaterThan',
+        'toBeGreaterThanOrEqual',
+        'toBeLessThan',
+        'toBeLessThanOrEqual',
+        'toBeNaN',
+        'toBeNegativeInfinity',
+        'toBeNull',
+        'toBePositiveInfinity',
+        'toBeTruthy',
+        'toBeUndefined',
+        'toContain',
+        'toEqual',
+        'toHaveBeenCalled',
+        'toHaveBeenCalledBefore',
+        'toHaveBeenCalledTimes',
+        'toHaveBeenCalledWith',
+        'toMatch',
+        'toThrow',
+        'toThrowError'
+      ],
+      matchers = {};
+
+  for (var i = 0; i < availableMatchers.length; i++) {
+    var name = availableMatchers[i];
+    matchers[name] = jRequire[name](j$);
+  }
+
+  return matchers;
+};
+
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+  j$.unimplementedMethod_ = function() {
+    throw new Error('unimplemented method');
+  };
+
+  /**
+   * Maximum object depth the pretty printer will print to.
+   * Set this to a lower value to speed up pretty printing if you have large objects.
+   * @name jasmine.MAX_PRETTY_PRINT_DEPTH
+   */
+  j$.MAX_PRETTY_PRINT_DEPTH = 40;
+  /**
+   * Maximum number of array elements to display when pretty printing objects.
+   * Elements past this number will be ellipised.
+   * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH
+   */
+  j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+  /**
+   * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete.
+   * @name jasmine.DEFAULT_TIMEOUT_INTERVAL
+   */
+  j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+  j$.getGlobal = function() {
+    return jasmineGlobal;
+  };
+
+  /**
+   * Get the currently booted Jasmine Environment.
+   *
+   * @name jasmine.getEnv
+   * @function
+   * @return {Env}
+   */
+  j$.getEnv = function(options) {
+    var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+    //jasmine. singletons in here (setTimeout blah blah).
+    return env;
+  };
+
+  j$.isArray_ = function(value) {
+    return j$.isA_('Array', value);
+  };
+
+  j$.isObject_ = function(value) {
+    return !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value);
+  };
+
+  j$.isString_ = function(value) {
+    return j$.isA_('String', value);
+  };
+
+  j$.isNumber_ = function(value) {
+    return j$.isA_('Number', value);
+  };
+
+  j$.isFunction_ = function(value) {
+    return j$.isA_('Function', value);
+  };
+
+  j$.isA_ = function(typeName, value) {
+    return j$.getType_(value) === '[object ' + typeName + ']';
+  };
+
+  j$.getType_ = function(value) {
+    return Object.prototype.toString.apply(value);
+  };
+
+  j$.isDomNode = function(obj) {
+    return obj.nodeType > 0;
+  };
+
+  j$.fnNameFor = function(func) {
+    if (func.name) {
+      return func.name;
+    }
+
+    var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/);
+    return matches ? matches[1] : '<anonymous>';
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared is an instance of the specified class/constructor.
+   * @name jasmine.any
+   * @function
+   * @param {Constructor} clazz - The constructor to check against.
+   */
+  j$.any = function(clazz) {
+    return new j$.Any(clazz);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared is not `null` and not `undefined`.
+   * @name jasmine.anything
+   * @function
+   */
+  j$.anything = function() {
+    return new j$.Anything();
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value being compared contains at least the keys and values.
+   * @name jasmine.objectContaining
+   * @function
+   * @param {Object} sample - The subset of properties that _must_ be in the actual.
+   */
+  j$.objectContaining = function(sample) {
+    return new j$.ObjectContaining(sample);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`.
+   * @name jasmine.stringMatching
+   * @function
+   * @param {RegExp|String} expected
+   */
+  j$.stringMatching = function(expected) {
+    return new j$.StringMatching(expected);
+  };
+
+  /**
+   * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+   * that will succeed if the actual value is an `Array` that contains at least the elements in the sample.
+   * @name jasmine.arrayContaining
+   * @function
+   * @param {Array} sample
+   */
+  j$.arrayContaining = function(sample) {
+    return new j$.ArrayContaining(sample);
+  };
+
+  /**
+   * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it.
+   * @name jasmine.createSpy
+   * @function
+   * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
+   * @param {Function} [originalFn] - Function to act as the real implementation.
+   * @return {Spy}
+   */
+  j$.createSpy = function(name, originalFn) {
+    return j$.Spy(name, originalFn);
+  };
+
+  j$.isSpy = function(putativeSpy) {
+    if (!putativeSpy) {
+      return false;
+    }
+    return putativeSpy.and instanceof j$.SpyStrategy &&
+        putativeSpy.calls instanceof j$.CallTracker;
+  };
+
+  /**
+   * Create an object with multiple {@link Spy}s as its members.
+   * @name jasmine.createSpyObj
+   * @function
+   * @param {String} [baseName] - Base name for the spies in the object.
+   * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}.
+   * @return {Object}
+   */
+  j$.createSpyObj = function(baseName, methodNames) {
+    var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName);
+
+    if (baseNameIsCollection && j$.util.isUndefined(methodNames)) {
+      methodNames = baseName;
+      baseName = 'unknown';
+    }
+
+    var obj = {};
+    var spiesWereSet = false;
+
+    if (j$.isArray_(methodNames)) {
+      for (var i = 0; i < methodNames.length; i++) {
+        obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+        spiesWereSet = true;
+      }
+    } else if (j$.isObject_(methodNames)) {
+      for (var key in methodNames) {
+        if (methodNames.hasOwnProperty(key)) {
+          obj[key] = j$.createSpy(baseName + '.' + key);
+          obj[key].and.returnValue(methodNames[key]);
+          spiesWereSet = true;
+        }
+      }
+    }
+
+    if (!spiesWereSet) {
+      throw 'createSpyObj requires a non-empty array or object of method names to create spies for';
+    }
+
+    return obj;
+  };
+};
+
+getJasmineRequireObj().util = function() {
+
+  var util = {};
+
+  util.inherit = function(childClass, parentClass) {
+    var Subclass = function() {
+    };
+    Subclass.prototype = parentClass.prototype;
+    childClass.prototype = new Subclass();
+  };
+
+  util.htmlEscape = function(str) {
+    if (!str) {
+      return str;
+    }
+    return str.replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;');
+  };
+
+  util.argsToArray = function(args) {
+    var arrayOfArgs = [];
+    for (var i = 0; i < args.length; i++) {
+      arrayOfArgs.push(args[i]);
+    }
+    return arrayOfArgs;
+  };
+
+  util.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  util.arrayContains = function(array, search) {
+    var i = array.length;
+    while (i--) {
+      if (array[i] === search) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  util.clone = function(obj) {
+    if (Object.prototype.toString.apply(obj) === '[object Array]') {
+      return obj.slice();
+    }
+
+    var cloned = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        cloned[prop] = obj[prop];
+      }
+    }
+
+    return cloned;
+  };
+
+  util.getPropertyDescriptor = function(obj, methodName) {
+    var descriptor,
+        proto = obj;
+
+    do {
+      descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
+      proto = Object.getPrototypeOf(proto);
+    } while (!descriptor && proto);
+
+    return descriptor;
+  };
+
+  util.objectDifference = function(obj, toRemove) {
+    var diff = {};
+
+    for (var key in obj) {
+      if (util.has(obj, key) && !util.has(toRemove, key)) {
+        diff[key] = obj[key];
+      }
+    }
+
+    return diff;
+  };
+
+  util.has = function(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  };
+
+  return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+  function Spec(attrs) {
+    this.expectationFactory = attrs.expectationFactory;
+    this.resultCallback = attrs.resultCallback || function() {};
+    this.id = attrs.id;
+    this.description = attrs.description || '';
+    this.queueableFn = attrs.queueableFn;
+    this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+    this.userContext = attrs.userContext || function() { return {}; };
+    this.onStart = attrs.onStart || function() {};
+    this.getSpecName = attrs.getSpecName || function() { return ''; };
+    this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+    this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+    this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    if (!this.queueableFn.fn) {
+      this.pend();
+    }
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: [],
+      passedExpectations: [],
+      pendingReason: ''
+    };
+  }
+
+  Spec.prototype.addExpectationResult = function(passed, data, isError) {
+    var expectationResult = this.expectationResultFactory(data);
+    if (passed) {
+      this.result.passedExpectations.push(expectationResult);
+    } else {
+      this.result.failedExpectations.push(expectationResult);
+
+      if (this.throwOnExpectationFailure && !isError) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    }
+  };
+
+  Spec.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Spec.prototype.execute = function(onComplete, enabled) {
+    var self = this;
+
+    this.onStart(this);
+
+    if (!this.isExecutable() || this.markedPending || enabled === false) {
+      complete(enabled);
+      return;
+    }
+
+    var fns = this.beforeAndAfterFns();
+    var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
+
+    this.queueRunnerFactory({
+      queueableFns: allFns,
+      onException: function() { self.onException.apply(self, arguments); },
+      onComplete: complete,
+      userContext: this.userContext()
+    });
+
+    function complete(enabledAgain) {
+      self.result.status = self.status(enabledAgain);
+      self.resultCallback(self.result);
+
+      if (onComplete) {
+        onComplete();
+      }
+    }
+  };
+
+  Spec.prototype.onException = function onException(e) {
+    if (Spec.isPendingSpecException(e)) {
+      this.pend(extractCustomPendingMessage(e));
+      return;
+    }
+
+    if (e instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    this.addExpectationResult(false, {
+      matcherName: '',
+      passed: false,
+      expected: '',
+      actual: '',
+      error: e
+    }, true);
+  };
+
+  Spec.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Spec.prototype.pend = function(message) {
+    this.markedPending = true;
+    if (message) {
+      this.result.pendingReason = message;
+    }
+  };
+
+  Spec.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Spec.prototype.status = function(enabled) {
+    if (this.disabled || enabled === false) {
+      return 'disabled';
+    }
+
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'passed';
+    }
+  };
+
+  Spec.prototype.isExecutable = function() {
+    return !this.disabled;
+  };
+
+  Spec.prototype.getFullName = function() {
+    return this.getSpecName(this);
+  };
+
+  var extractCustomPendingMessage = function(e) {
+    var fullMessage = e.toString(),
+        boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+        boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+
+    return fullMessage.substr(boilerplateEnd);
+  };
+
+  Spec.pendingSpecExceptionMessage = '=> marked Pending';
+
+  Spec.isPendingSpecException = function(e) {
+    return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+  };
+
+  return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Spec = jasmineRequire.Spec;
+}
+
+/*jshint bitwise: false*/
+
+getJasmineRequireObj().Order = function() {
+  function Order(options) {
+    this.random = 'random' in options ? options.random : true;
+    var seed = this.seed = options.seed || generateSeed();
+    this.sort = this.random ? randomOrder : naturalOrder;
+
+    function naturalOrder(items) {
+      return items;
+    }
+
+    function randomOrder(items) {
+      var copy = items.slice();
+      copy.sort(function(a, b) {
+        return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id);
+      });
+      return copy;
+    }
+
+    function generateSeed() {
+      return String(Math.random()).slice(-5);
+    }
+
+    // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function
+    // used to get a different output when the key changes slighly.
+    // We use your return to sort the children randomly in a consistent way when
+    // used in conjunction with a seed
+
+    function jenkinsHash(key) {
+      var hash, i;
+      for(hash = i = 0; i < key.length; ++i) {
+        hash += key.charCodeAt(i);
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+      }
+      hash += (hash << 3);
+      hash ^= (hash >> 11);
+      hash += (hash << 15);
+      return hash;
+    }
+
+  }
+
+  return Order;
+};
+
+getJasmineRequireObj().Env = function(j$) {
+  /**
+   * _Note:_ Do not construct this directly, Jasmine will make one during booting.
+   * @name Env
+   * @classdesc The Jasmine environment
+   * @constructor
+   */
+  function Env(options) {
+    options = options || {};
+
+    var self = this;
+    var global = options.global || j$.getGlobal();
+
+    var totalSpecsDefined = 0;
+
+    var catchExceptions = true;
+
+    var realSetTimeout = j$.getGlobal().setTimeout;
+    var realClearTimeout = j$.getGlobal().clearTimeout;
+    var clearStack = j$.getClearStack(j$.getGlobal());
+    this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
+
+    var runnableResources = {};
+
+    var currentSpec = null;
+    var currentlyExecutingSuites = [];
+    var currentDeclarationSuite = null;
+    var throwOnExpectationFailure = false;
+    var random = false;
+    var seed = null;
+
+    var currentSuite = function() {
+      return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+    };
+
+    var currentRunnable = function() {
+      return currentSpec || currentSuite();
+    };
+
+    var reporter = new j$.ReportDispatcher([
+      'jasmineStarted',
+      'jasmineDone',
+      'suiteStarted',
+      'suiteDone',
+      'specStarted',
+      'specDone'
+    ]);
+
+    var globalErrors = new j$.GlobalErrors();
+
+    this.specFilter = function() {
+      return true;
+    };
+
+    this.addCustomEqualityTester = function(tester) {
+      if(!currentRunnable()) {
+        throw new Error('Custom Equalities must be added in a before function or a spec');
+      }
+      runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+    };
+
+    this.addMatchers = function(matchersToAdd) {
+      if(!currentRunnable()) {
+        throw new Error('Matchers must be added in a before function or a spec');
+      }
+      var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+      for (var matcherName in matchersToAdd) {
+        customMatchers[matcherName] = matchersToAdd[matcherName];
+      }
+    };
+
+    j$.Expectation.addCoreMatchers(j$.matchers);
+
+    var nextSpecId = 0;
+    var getNextSpecId = function() {
+      return 'spec' + nextSpecId++;
+    };
+
+    var nextSuiteId = 0;
+    var getNextSuiteId = function() {
+      return 'suite' + nextSuiteId++;
+    };
+
+    var expectationFactory = function(actual, spec) {
+      return j$.Expectation.Factory({
+        util: j$.matchersUtil,
+        customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+        customMatchers: runnableResources[spec.id].customMatchers,
+        actual: actual,
+        addExpectationResult: addExpectationResult
+      });
+
+      function addExpectationResult(passed, result) {
+        return spec.addExpectationResult(passed, result);
+      }
+    };
+
+    var defaultResourcesForRunnable = function(id, parentRunnableId) {
+      var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+      if(runnableResources[parentRunnableId]){
+        resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+        resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+      }
+
+      runnableResources[id] = resources;
+    };
+
+    var clearResourcesForRunnable = function(id) {
+      spyRegistry.clearSpies();
+      delete runnableResources[id];
+    };
+
+    var beforeAndAfterFns = function(suite) {
+      return function() {
+        var befores = [],
+            afters = [];
+
+        while(suite) {
+          befores = befores.concat(suite.beforeFns);
+          afters = afters.concat(suite.afterFns);
+
+          suite = suite.parentSuite;
+        }
+
+        return {
+          befores: befores.reverse(),
+          afters: afters
+        };
+      };
+    };
+
+    var getSpecName = function(spec, suite) {
+      var fullName = [spec.description],
+          suiteFullName = suite.getFullName();
+
+      if (suiteFullName !== '') {
+        fullName.unshift(suiteFullName);
+      }
+      return fullName.join(' ');
+    };
+
+    // TODO: we may just be able to pass in the fn instead of wrapping here
+    var buildExpectationResult = j$.buildExpectationResult,
+        exceptionFormatter = new j$.ExceptionFormatter(),
+        expectationResultFactory = function(attrs) {
+          attrs.messageFormatter = exceptionFormatter.message;
+          attrs.stackFormatter = exceptionFormatter.stack;
+
+          return buildExpectationResult(attrs);
+        };
+
+    // TODO: fix this naming, and here's where the value comes in
+    this.catchExceptions = function(value) {
+      catchExceptions = !!value;
+      return catchExceptions;
+    };
+
+    this.catchingExceptions = function() {
+      return catchExceptions;
+    };
+
+    var maximumSpecCallbackDepth = 20;
+    var currentSpecCallbackDepth = 0;
+
+    var catchException = function(e) {
+      return j$.Spec.isPendingSpecException(e) || catchExceptions;
+    };
+
+    this.throwOnExpectationFailure = function(value) {
+      throwOnExpectationFailure = !!value;
+    };
+
+    this.throwingExpectationFailures = function() {
+      return throwOnExpectationFailure;
+    };
+
+    this.randomizeTests = function(value) {
+      random = !!value;
+    };
+
+    this.randomTests = function() {
+      return random;
+    };
+
+    this.seed = function(value) {
+      if (value) {
+        seed = value;
+      }
+      return seed;
+    };
+
+    var queueRunnerFactory = function(options) {
+      options.catchException = catchException;
+      options.clearStack = options.clearStack || clearStack;
+      options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+      options.fail = self.fail;
+      options.globalErrors = globalErrors;
+
+      new j$.QueueRunner(options).execute();
+    };
+
+    var topSuite = new j$.Suite({
+      env: this,
+      id: getNextSuiteId(),
+      description: 'Jasmine__TopLevel__Suite',
+      expectationFactory: expectationFactory,
+      expectationResultFactory: expectationResultFactory
+    });
+    defaultResourcesForRunnable(topSuite.id);
+    currentDeclarationSuite = topSuite;
+
+    this.topSuite = function() {
+      return topSuite;
+    };
+
+    this.execute = function(runnablesToRun) {
+      if(!runnablesToRun) {
+        if (focusedRunnables.length) {
+          runnablesToRun = focusedRunnables;
+        } else {
+          runnablesToRun = [topSuite.id];
+        }
+      }
+
+      var order = new j$.Order({
+        random: random,
+        seed: seed
+      });
+
+      var processor = new j$.TreeProcessor({
+        tree: topSuite,
+        runnableIds: runnablesToRun,
+        queueRunnerFactory: queueRunnerFactory,
+        nodeStart: function(suite) {
+          currentlyExecutingSuites.push(suite);
+          defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+          reporter.suiteStarted(suite.result);
+        },
+        nodeComplete: function(suite, result) {
+          if (!suite.markedPending) {
+            clearResourcesForRunnable(suite.id);
+          }
+          currentlyExecutingSuites.pop();
+          reporter.suiteDone(result);
+        },
+        orderChildren: function(node) {
+          return order.sort(node.children);
+        }
+      });
+
+      if(!processor.processTree().valid) {
+        throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
+      }
+
+      reporter.jasmineStarted({
+        totalSpecsDefined: totalSpecsDefined
+      });
+
+      currentlyExecutingSuites.push(topSuite);
+
+      globalErrors.install();
+      processor.execute(function() {
+        clearResourcesForRunnable(topSuite.id);
+        currentlyExecutingSuites.pop();
+        globalErrors.uninstall();
+
+        reporter.jasmineDone({
+          order: order,
+          failedExpectations: topSuite.result.failedExpectations
+        });
+      });
+    };
+
+    /**
+     * Add a custom reporter to the Jasmine environment.
+     * @name Env#addReporter
+     * @function
+     * @see custom_reporter
+     */
+    this.addReporter = function(reporterToAdd) {
+      reporter.addReporter(reporterToAdd);
+    };
+
+    this.provideFallbackReporter = function(reporterToAdd) {
+      reporter.provideFallbackReporter(reporterToAdd);
+    };
+
+    this.clearReporters = function() {
+      reporter.clearReporters();
+    };
+
+    var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+      if(!currentRunnable()) {
+        throw new Error('Spies must be created in a before function or a spec');
+      }
+      return runnableResources[currentRunnable().id].spies;
+    }});
+
+    this.allowRespy = function(allow){
+      spyRegistry.allowRespy(allow);
+    };
+
+    this.spyOn = function() {
+      return spyRegistry.spyOn.apply(spyRegistry, arguments);
+    };
+
+    this.spyOnProperty = function() {
+      return spyRegistry.spyOnProperty.apply(spyRegistry, arguments);
+    };
+
+    var ensureIsFunction = function(fn, caller) {
+      if (!j$.isFunction_(fn)) {
+        throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn));
+      }
+    };
+
+    var suiteFactory = function(description) {
+      var suite = new j$.Suite({
+        env: self,
+        id: getNextSuiteId(),
+        description: description,
+        parentSuite: currentDeclarationSuite,
+        expectationFactory: expectationFactory,
+        expectationResultFactory: expectationResultFactory,
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      return suite;
+    };
+
+    this.describe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'describe');
+      var suite = suiteFactory(description);
+      if (specDefinitions.length > 0) {
+        throw new Error('describe does not expect any arguments');
+      }
+      if (currentDeclarationSuite.markedPending) {
+        suite.pend();
+      }
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    this.xdescribe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'xdescribe');
+      var suite = suiteFactory(description);
+      suite.pend();
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    var focusedRunnables = [];
+
+    this.fdescribe = function(description, specDefinitions) {
+      ensureIsFunction(specDefinitions, 'fdescribe');
+      var suite = suiteFactory(description);
+      suite.isFocused = true;
+
+      focusedRunnables.push(suite.id);
+      unfocusAncestor();
+      addSpecsToSuite(suite, specDefinitions);
+
+      return suite;
+    };
+
+    function addSpecsToSuite(suite, specDefinitions) {
+      var parentSuite = currentDeclarationSuite;
+      parentSuite.addChild(suite);
+      currentDeclarationSuite = suite;
+
+      var declarationError = null;
+      try {
+        specDefinitions.call(suite);
+      } catch (e) {
+        declarationError = e;
+      }
+
+      if (declarationError) {
+        self.it('encountered a declaration exception', function() {
+          throw declarationError;
+        });
+      }
+
+      currentDeclarationSuite = parentSuite;
+    }
+
+    function findFocusedAncestor(suite) {
+      while (suite) {
+        if (suite.isFocused) {
+          return suite.id;
+        }
+        suite = suite.parentSuite;
+      }
+
+      return null;
+    }
+
+    function unfocusAncestor() {
+      var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+      if (focusedAncestor) {
+        for (var i = 0; i < focusedRunnables.length; i++) {
+          if (focusedRunnables[i] === focusedAncestor) {
+            focusedRunnables.splice(i, 1);
+            break;
+          }
+        }
+      }
+    }
+
+    var specFactory = function(description, fn, suite, timeout) {
+      totalSpecsDefined++;
+      var spec = new j$.Spec({
+        id: getNextSpecId(),
+        beforeAndAfterFns: beforeAndAfterFns(suite),
+        expectationFactory: expectationFactory,
+        resultCallback: specResultCallback,
+        getSpecName: function(spec) {
+          return getSpecName(spec, suite);
+        },
+        onStart: specStarted,
+        description: description,
+        expectationResultFactory: expectationResultFactory,
+        queueRunnerFactory: queueRunnerFactory,
+        userContext: function() { return suite.clonedSharedUserContext(); },
+        queueableFn: {
+          fn: fn,
+          timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+        },
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      if (!self.specFilter(spec)) {
+        spec.disable();
+      }
+
+      return spec;
+
+      function specResultCallback(result) {
+        clearResourcesForRunnable(spec.id);
+        currentSpec = null;
+        reporter.specDone(result);
+      }
+
+      function specStarted(spec) {
+        currentSpec = spec;
+        defaultResourcesForRunnable(spec.id, suite.id);
+        reporter.specStarted(spec.result);
+      }
+    };
+
+    this.it = function(description, fn, timeout) {
+      // it() sometimes doesn't have a fn argument, so only check the type if
+      // it's given.
+      if (arguments.length > 1 && typeof fn !== 'undefined') {
+        ensureIsFunction(fn, 'it');
+      }
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      if (currentDeclarationSuite.markedPending) {
+        spec.pend();
+      }
+      currentDeclarationSuite.addChild(spec);
+      return spec;
+    };
+
+    this.xit = function(description, fn, timeout) {
+      // xit(), like it(), doesn't always have a fn argument, so only check the
+      // type when needed.
+      if (arguments.length > 1 && typeof fn !== 'undefined') {
+        ensureIsFunction(fn, 'xit');
+      }
+      var spec = this.it.apply(this, arguments);
+      spec.pend('Temporarily disabled with xit');
+      return spec;
+    };
+
+    this.fit = function(description, fn, timeout){
+      ensureIsFunction(fn, 'fit');
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      currentDeclarationSuite.addChild(spec);
+      focusedRunnables.push(spec.id);
+      unfocusAncestor();
+      return spec;
+    };
+
+    this.expect = function(actual) {
+      if (!currentRunnable()) {
+        throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
+      }
+
+      return currentRunnable().expect(actual);
+    };
+
+    this.beforeEach = function(beforeEachFunction, timeout) {
+      ensureIsFunction(beforeEachFunction, 'beforeEach');
+      currentDeclarationSuite.beforeEach({
+        fn: beforeEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.beforeAll = function(beforeAllFunction, timeout) {
+      ensureIsFunction(beforeAllFunction, 'beforeAll');
+      currentDeclarationSuite.beforeAll({
+        fn: beforeAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterEach = function(afterEachFunction, timeout) {
+      ensureIsFunction(afterEachFunction, 'afterEach');
+      currentDeclarationSuite.afterEach({
+        fn: afterEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterAll = function(afterAllFunction, timeout) {
+      ensureIsFunction(afterAllFunction, 'afterAll');
+      currentDeclarationSuite.afterAll({
+        fn: afterAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.pending = function(message) {
+      var fullMessage = j$.Spec.pendingSpecExceptionMessage;
+      if(message) {
+        fullMessage += message;
+      }
+      throw fullMessage;
+    };
+
+    this.fail = function(error) {
+      if (!currentRunnable()) {
+        throw new Error('\'fail\' was used when there was no current spec, this could be because an asynchronous test timed out');
+      }
+
+      var message = 'Failed';
+      if (error) {
+        message += ': ';
+        if (error.message) {
+          message += error.message;
+        } else if (jasmine.isString_(error)) {
+          message += error;
+        } else {
+          // pretty print all kind of objects. This includes arrays.
+          message += jasmine.pp(error);
+        }
+      }
+
+      currentRunnable().addExpectationResult(false, {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        message: message,
+        error: error && error.message ? error : null
+      });
+    };
+  }
+
+  return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+  var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  /**
+   * _Note:_ Do not construct this directly, use the global `jsApiReporter` to retrieve the instantiated object.
+   *
+   * @name jsApiReporter
+   * @classdesc Reporter added by default in `boot.js` to record results for retrieval in javascript code.
+   * @class
+   */
+  function JsApiReporter(options) {
+    var timer = options.timer || noopTimer,
+        status = 'loaded';
+
+    this.started = false;
+    this.finished = false;
+    this.runDetails = {};
+
+    this.jasmineStarted = function() {
+      this.started = true;
+      status = 'started';
+      timer.start();
+    };
+
+    var executionTime;
+
+    this.jasmineDone = function(runDetails) {
+      this.finished = true;
+      this.runDetails = runDetails;
+      executionTime = timer.elapsed();
+      status = 'done';
+    };
+
+    /**
+     * Get the current status for the Jasmine environment.
+     * @name jsApiReporter#status
+     * @function
+     * @return {String} - One of `loaded`, `started`, or `done`
+     */
+    this.status = function() {
+      return status;
+    };
+
+    var suites = [],
+        suites_hash = {};
+
+    this.suiteStarted = function(result) {
+      suites_hash[result.id] = result;
+    };
+
+    this.suiteDone = function(result) {
+      storeSuite(result);
+    };
+
+    /**
+     * Get the results for a set of suites.
+     *
+     * Retrievable in slices for easier serialization.
+     * @name jsApiReporter#suiteResults
+     * @function
+     * @param {Number} index - The position in the suites list to start from.
+     * @param {Number} length - Maximum number of suite results to return.
+     * @return {Object[]}
+     */
+    this.suiteResults = function(index, length) {
+      return suites.slice(index, index + length);
+    };
+
+    function storeSuite(result) {
+      suites.push(result);
+      suites_hash[result.id] = result;
+    }
+
+    /**
+     * Get all of the suites in a single object, with their `id` as the key.
+     * @name jsApiReporter#suites
+     * @function
+     * @return {Object}
+     */
+    this.suites = function() {
+      return suites_hash;
+    };
+
+    var specs = [];
+
+    this.specDone = function(result) {
+      specs.push(result);
+    };
+
+    /**
+     * Get the results for a set of specs.
+     *
+     * Retrievable in slices for easier serialization.
+     * @name jsApiReporter#specResults
+     * @function
+     * @param {Number} index - The position in the specs list to start from.
+     * @param {Number} length - Maximum number of specs results to return.
+     * @return {Object[]}
+     */
+    this.specResults = function(index, length) {
+      return specs.slice(index, index + length);
+    };
+
+    /**
+     * Get all spec results.
+     * @name jsApiReporter#specs
+     * @function
+     * @return {Object[]}
+     */
+    this.specs = function() {
+      return specs;
+    };
+
+    /**
+     * Get the number of milliseconds it took for the full Jasmine suite to run.
+     * @name jsApiReporter#executionTime
+     * @function
+     * @return {Number}
+     */
+    this.executionTime = function() {
+      return executionTime;
+    };
+
+  }
+
+  return JsApiReporter;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+
+  function Any(expectedObject) {
+    if (typeof expectedObject === 'undefined') {
+      throw new TypeError(
+          'jasmine.any() expects to be passed a constructor function. ' +
+          'Please pass one or use jasmine.anything() to match any object.'
+      );
+    }
+    this.expectedObject = expectedObject;
+  }
+
+  Any.prototype.asymmetricMatch = function(other) {
+    if (this.expectedObject == String) {
+      return typeof other == 'string' || other instanceof String;
+    }
+
+    if (this.expectedObject == Number) {
+      return typeof other == 'number' || other instanceof Number;
+    }
+
+    if (this.expectedObject == Function) {
+      return typeof other == 'function' || other instanceof Function;
+    }
+
+    if (this.expectedObject == Object) {
+      return typeof other == 'object';
+    }
+
+    if (this.expectedObject == Boolean) {
+      return typeof other == 'boolean';
+    }
+
+    return other instanceof this.expectedObject;
+  };
+
+  Any.prototype.jasmineToString = function() {
+    return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+  };
+
+  return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+
+  function Anything() {}
+
+  Anything.prototype.asymmetricMatch = function(other) {
+    return !j$.util.isUndefined(other) && other !== null;
+  };
+
+  Anything.prototype.jasmineToString = function() {
+    return '<jasmine.anything>';
+  };
+
+  return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+  function ArrayContaining(sample) {
+    this.sample = sample;
+  }
+
+  ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) {
+    var className = Object.prototype.toString.call(this.sample);
+    if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); }
+
+    for (var i = 0; i < this.sample.length; i++) {
+      var item = this.sample[i];
+      if (!j$.matchersUtil.contains(other, item, customTesters)) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ArrayContaining.prototype.jasmineToString = function () {
+    return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>';
+  };
+
+  return ArrayContaining;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+  function ObjectContaining(sample) {
+    this.sample = sample;
+  }
+
+  function getPrototype(obj) {
+    if (Object.getPrototypeOf) {
+      return Object.getPrototypeOf(obj);
+    }
+
+    if (obj.constructor.prototype == obj) {
+      return null;
+    }
+
+    return obj.constructor.prototype;
+  }
+
+  function hasProperty(obj, property) {
+    if (!obj) {
+      return false;
+    }
+
+    if (Object.prototype.hasOwnProperty.call(obj, property)) {
+      return true;
+    }
+
+    return hasProperty(getPrototype(obj), property);
+  }
+
+  ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) {
+    if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
+
+    for (var property in this.sample) {
+      if (!hasProperty(other, property) ||
+          !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ObjectContaining.prototype.jasmineToString = function() {
+    return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
+  };
+
+  return ObjectContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+
+  function StringMatching(expected) {
+    if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+      throw new Error('Expected is not a String or a RegExp');
+    }
+
+    this.regexp = new RegExp(expected);
+  }
+
+  StringMatching.prototype.asymmetricMatch = function(other) {
+    return this.regexp.test(other);
+  };
+
+  StringMatching.prototype.jasmineToString = function() {
+    return '<jasmine.stringMatching(' + this.regexp + ')>';
+  };
+
+  return StringMatching;
+};
+
+getJasmineRequireObj().CallTracker = function(j$) {
+
+  /**
+   * @namespace Spy#calls
+   */
+  function CallTracker() {
+    var calls = [];
+    var opts = {};
+
+    function argCloner(context) {
+      var clonedArgs = [];
+      var argsAsArray = j$.util.argsToArray(context.args);
+      for(var i = 0; i < argsAsArray.length; i++) {
+        if(Object.prototype.toString.apply(argsAsArray[i]).match(/^\[object/)) {
+          clonedArgs.push(j$.util.clone(argsAsArray[i]));
+        } else {
+          clonedArgs.push(argsAsArray[i]);
+        }
+      }
+      context.args = clonedArgs;
+    }
+
+    this.track = function(context) {
+      if(opts.cloneArgs) {
+        argCloner(context);
+      }
+      calls.push(context);
+    };
+
+    /**
+     * Check whether this spy has been invoked.
+     * @name Spy#calls#any
+     * @function
+     * @return {Boolean}
+     */
+    this.any = function() {
+      return !!calls.length;
+    };
+
+    /**
+     * Get the number of invocations of this spy.
+     * @name Spy#calls#count
+     * @function
+     * @return {Integer}
+     */
+    this.count = function() {
+      return calls.length;
+    };
+
+    /**
+     * Get the arguments that were passed to a specific invocation of this spy.
+     * @name Spy#calls#argsFor
+     * @function
+     * @param {Integer} index The 0-based invocation index.
+     * @return {Array}
+     */
+    this.argsFor = function(index) {
+      var call = calls[index];
+      return call ? call.args : [];
+    };
+
+    /**
+     * Get the raw calls array for this spy.
+     * @name Spy#calls#all
+     * @function
+     * @return {Spy.callData[]}
+     */
+    this.all = function() {
+      return calls;
+    };
+
+    /**
+     * Get all of the arguments for each invocation of this spy in the order they were received.
+     * @name Spy#calls#allArgs
+     * @function
+     * @return {Array}
+     */
+    this.allArgs = function() {
+      var callArgs = [];
+      for(var i = 0; i < calls.length; i++){
+        callArgs.push(calls[i].args);
+      }
+
+      return callArgs;
+    };
+
+    /**
+     * Get the first invocation of this spy.
+     * @name Spy#calls#first
+     * @function
+     * @return {ObjecSpy.callData}
+     */
+    this.first = function() {
+      return calls[0];
+    };
+
+    /**
+     * Get the most recent invocation of this spy.
+     * @name Spy#calls#mostRecent
+     * @function
+     * @return {ObjecSpy.callData}
+     */
+    this.mostRecent = function() {
+      return calls[calls.length - 1];
+    };
+
+    /**
+     * Reset this spy as if it has never been called.
+     * @name Spy#calls#reset
+     * @function
+     */
+    this.reset = function() {
+      calls = [];
+    };
+
+    /**
+     * Set this spy to do a shallow clone of arguments passed to each invocation.
+     * @name Spy#calls#saveArgumentsByValue
+     * @function
+     */
+    this.saveArgumentsByValue = function() {
+      opts.cloneArgs = true;
+    };
+
+  }
+
+  return CallTracker;
+};
+
+getJasmineRequireObj().clearStack = function(j$) {
+  function messageChannelImpl(global) {
+    var channel = new global.MessageChannel(),
+        head = {},
+        tail = head;
+
+    channel.port1.onmessage = function() {
+      head = head.next;
+      var task = head.task;
+      delete head.task;
+      task();
+    };
+
+    return function clearStack(fn) {
+      tail = tail.next = { task: fn };
+      channel.port2.postMessage(0);
+    };
+  }
+
+  function getClearStack(global) {
+    if (j$.isFunction_(global.setImmediate)) {
+      var realSetImmediate = global.setImmediate;
+      return function(fn) {
+        realSetImmediate(fn);
+      };
+    } else if (!j$.util.isUndefined(global.MessageChannel)) {
+      return messageChannelImpl(global);
+    } else {
+      var realSetTimeout = global.setTimeout;
+      return function clearStack(fn) {
+        Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
+      };
+    }
+  }
+
+  return getClearStack;
+};
+
+getJasmineRequireObj().Clock = function() {
+  /**
+   * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}.
+   * @class Clock
+   * @classdesc Jasmine's mock clock is used when testing time dependent code.
+   */
+  function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
+    var self = this,
+        realTimingFunctions = {
+          setTimeout: global.setTimeout,
+          clearTimeout: global.clearTimeout,
+          setInterval: global.setInterval,
+          clearInterval: global.clearInterval
+        },
+        fakeTimingFunctions = {
+          setTimeout: setTimeout,
+          clearTimeout: clearTimeout,
+          setInterval: setInterval,
+          clearInterval: clearInterval
+        },
+        installed = false,
+        delayedFunctionScheduler,
+        timer;
+
+
+    /**
+     * Install the mock clock over the built-in methods.
+     * @name Clock#install
+     * @function
+     * @return {Clock}
+     */
+    self.install = function() {
+      if(!originalTimingFunctionsIntact()) {
+        throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
+      }
+      replace(global, fakeTimingFunctions);
+      timer = fakeTimingFunctions;
+      delayedFunctionScheduler = delayedFunctionSchedulerFactory();
+      installed = true;
+
+      return self;
+    };
+
+    /**
+     * Uninstall the mock clock, returning the built-in methods to their places.
+     * @name Clock#uninstall
+     * @function
+     */
+    self.uninstall = function() {
+      delayedFunctionScheduler = null;
+      mockDate.uninstall();
+      replace(global, realTimingFunctions);
+
+      timer = realTimingFunctions;
+      installed = false;
+    };
+
+    /**
+     * Execute a function with a mocked Clock
+     *
+     * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes.
+     * @name Clock#withMock
+     * @function
+     * @param {closure} Function The function to be called.
+     */
+    self.withMock = function(closure) {
+      this.install();
+      try {
+        closure();
+      } finally {
+        this.uninstall();
+      }
+    };
+
+    /**
+     * Instruct the installed Clock to also mock the date returned by `new Date()`
+     * @name Clock#mockDate
+     * @function
+     * @param {Date} [initialDate=now] The `Date` to provide.
+     */
+    self.mockDate = function(initialDate) {
+      mockDate.install(initialDate);
+    };
+
+    self.setTimeout = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
+        }
+        return timer.setTimeout(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+    };
+
+    self.setInterval = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
+        }
+        return timer.setInterval(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+    };
+
+    self.clearTimeout = function(id) {
+      return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+    };
+
+    self.clearInterval = function(id) {
+      return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+    };
+
+    /**
+     * Tick the Clock forward, running any enqueued timeouts along the way
+     * @name Clock#tick
+     * @function
+     * @param {int} millis The number of milliseconds to tick.
+     */
+    self.tick = function(millis) {
+      if (installed) {
+        delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); });
+      } else {
+        throw new Error('Mock clock is not installed, use jasmine.clock().install()');
+      }
+    };
+
+    return self;
+
+    function originalTimingFunctionsIntact() {
+      return global.setTimeout === realTimingFunctions.setTimeout &&
+          global.clearTimeout === realTimingFunctions.clearTimeout &&
+          global.setInterval === realTimingFunctions.setInterval &&
+          global.clearInterval === realTimingFunctions.clearInterval;
+    }
+
+    function legacyIE() {
+      //if these methods are polyfilled, apply will be present
+      return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+    }
+
+    function replace(dest, source) {
+      for (var prop in source) {
+        dest[prop] = source[prop];
+      }
+    }
+
+    function setTimeout(fn, delay) {
+      return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+    }
+
+    function clearTimeout(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function setInterval(fn, interval) {
+      return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+    }
+
+    function clearInterval(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function argSlice(argsObj, n) {
+      return Array.prototype.slice.call(argsObj, n);
+    }
+  }
+
+  return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+  function DelayedFunctionScheduler() {
+    var self = this;
+    var scheduledLookup = [];
+    var scheduledFunctions = {};
+    var currentTime = 0;
+    var delayedFnCount = 0;
+
+    self.tick = function(millis, tickDate) {
+      millis = millis || 0;
+      var endTime = currentTime + millis;
+
+      runScheduledFunctions(endTime, tickDate);
+      currentTime = endTime;
+    };
+
+    self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+      var f;
+      if (typeof(funcToCall) === 'string') {
+        /* jshint evil: true */
+        f = function() { return eval(funcToCall); };
+        /* jshint evil: false */
+      } else {
+        f = funcToCall;
+      }
+
+      millis = millis || 0;
+      timeoutKey = timeoutKey || ++delayedFnCount;
+      runAtMillis = runAtMillis || (currentTime + millis);
+
+      var funcToSchedule = {
+        runAtMillis: runAtMillis,
+        funcToCall: f,
+        recurring: recurring,
+        params: params,
+        timeoutKey: timeoutKey,
+        millis: millis
+      };
+
+      if (runAtMillis in scheduledFunctions) {
+        scheduledFunctions[runAtMillis].push(funcToSchedule);
+      } else {
+        scheduledFunctions[runAtMillis] = [funcToSchedule];
+        scheduledLookup.push(runAtMillis);
+        scheduledLookup.sort(function (a, b) {
+          return a - b;
+        });
+      }
+
+      return timeoutKey;
+    };
+
+    self.removeFunctionWithId = function(timeoutKey) {
+      for (var runAtMillis in scheduledFunctions) {
+        var funcs = scheduledFunctions[runAtMillis];
+        var i = indexOfFirstToPass(funcs, function (func) {
+          return func.timeoutKey === timeoutKey;
+        });
+
+        if (i > -1) {
+          if (funcs.length === 1) {
+            delete scheduledFunctions[runAtMillis];
+            deleteFromLookup(runAtMillis);
+          } else {
+            funcs.splice(i, 1);
+          }
+
+          // intervals get rescheduled when executed, so there's never more
+          // than a single scheduled function with a given timeoutKey
+          break;
+        }
+      }
+    };
+
+    return self;
+
+    function indexOfFirstToPass(array, testFn) {
+      var index = -1;
+
+      for (var i = 0; i < array.length; ++i) {
+        if (testFn(array[i])) {
+          index = i;
+          break;
+        }
+      }
+
+      return index;
+    }
+
+    function deleteFromLookup(key) {
+      var value = Number(key);
+      var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+        return millis === value;
+      });
+
+      if (i > -1) {
+        scheduledLookup.splice(i, 1);
+      }
+    }
+
+    function reschedule(scheduledFn) {
+      self.scheduleFunction(scheduledFn.funcToCall,
+          scheduledFn.millis,
+          scheduledFn.params,
+          true,
+          scheduledFn.timeoutKey,
+          scheduledFn.runAtMillis + scheduledFn.millis);
+    }
+
+    function forEachFunction(funcsToRun, callback) {
+      for (var i = 0; i < funcsToRun.length; ++i) {
+        callback(funcsToRun[i]);
+      }
+    }
+
+    function runScheduledFunctions(endTime, tickDate) {
+      tickDate = tickDate || function() {};
+      if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+        tickDate(endTime - currentTime);
+        return;
+      }
+
+      do {
+        var newCurrentTime = scheduledLookup.shift();
+        tickDate(newCurrentTime - currentTime);
+
+        currentTime = newCurrentTime;
+
+        var funcsToRun = scheduledFunctions[currentTime];
+        delete scheduledFunctions[currentTime];
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          if (funcToRun.recurring) {
+            reschedule(funcToRun);
+          }
+        });
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          funcToRun.funcToCall.apply(null, funcToRun.params || []);
+        });
+      } while (scheduledLookup.length > 0 &&
+      // checking first if we're out of time prevents setTimeout(0)
+      // scheduled in a funcToRun from forcing an extra iteration
+      currentTime !== endTime  &&
+      scheduledLookup[0] <= endTime);
+
+      // ran out of functions to call, but still time left on the clock
+      if (currentTime !== endTime) {
+        tickDate(endTime - currentTime);
+      }
+    }
+  }
+
+  return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().errors = function() {
+  function ExpectationFailed() {}
+
+  ExpectationFailed.prototype = new Error();
+  ExpectationFailed.prototype.constructor = ExpectationFailed;
+
+  return {
+    ExpectationFailed: ExpectationFailed
+  };
+};
+getJasmineRequireObj().ExceptionFormatter = function() {
+  function ExceptionFormatter() {
+    this.message = function(error) {
+      var message = '';
+
+      if (error.name && error.message) {
+        message += error.name + ': ' + error.message;
+      } else {
+        message += error.toString() + ' thrown';
+      }
+
+      if (error.fileName || error.sourceURL) {
+        message += ' in ' + (error.fileName || error.sourceURL);
+      }
+
+      if (error.line || error.lineNumber) {
+        message += ' (line ' + (error.line || error.lineNumber) + ')';
+      }
+
+      return message;
+    };
+
+    this.stack = function(error) {
+      return error ? error.stack : null;
+    };
+  }
+
+  return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+  /**
+   * Matchers that come with Jasmine out of the box.
+   * @namespace matchers
+   */
+  function Expectation(options) {
+    this.util = options.util || { buildFailureMessage: function() {} };
+    this.customEqualityTesters = options.customEqualityTesters || [];
+    this.actual = options.actual;
+    this.addExpectationResult = options.addExpectationResult || function(){};
+    this.isNot = options.isNot;
+
+    var customMatchers = options.customMatchers || {};
+    for (var matcherName in customMatchers) {
+      this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
+    }
+  }
+
+  Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+    return function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+          expected = args.slice(0),
+          message = '';
+
+      args.unshift(this.actual);
+
+      var matcher = matcherFactory(this.util, this.customEqualityTesters),
+          matcherCompare = matcher.compare;
+
+      function defaultNegativeCompare() {
+        var result = matcher.compare.apply(null, args);
+        result.pass = !result.pass;
+        return result;
+      }
+
+      if (this.isNot) {
+        matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+      }
+
+      var result = matcherCompare.apply(null, args);
+
+      if (!result.pass) {
+        if (!result.message) {
+          args.unshift(this.isNot);
+          args.unshift(name);
+          message = this.util.buildFailureMessage.apply(null, args);
+        } else {
+          if (Object.prototype.toString.apply(result.message) === '[object Function]') {
+            message = result.message();
+          } else {
+            message = result.message;
+          }
+        }
+      }
+
+      if (expected.length == 1) {
+        expected = expected[0];
+      }
+
+      // TODO: how many of these params are needed?
+      this.addExpectationResult(
+          result.pass,
+          {
+            matcherName: name,
+            passed: result.pass,
+            message: message,
+            error: result.error,
+            actual: this.actual,
+            expected: expected // TODO: this may need to be arrayified/sliced
+          }
+      );
+    };
+  };
+
+  Expectation.addCoreMatchers = function(matchers) {
+    var prototype = Expectation.prototype;
+    for (var matcherName in matchers) {
+      var matcher = matchers[matcherName];
+      prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+    }
+  };
+
+  Expectation.Factory = function(options) {
+    options = options || {};
+
+    var expect = new Expectation(options);
+
+    // TODO: this would be nice as its own Object - NegativeExpectation
+    // TODO: copy instead of mutate options
+    options.isNot = true;
+    expect.not = new Expectation(options);
+
+    return expect;
+  };
+
+  return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+  function buildExpectationResult(options) {
+    var messageFormatter = options.messageFormatter || function() {},
+        stackFormatter = options.stackFormatter || function() {};
+
+    var result = {
+      matcherName: options.matcherName,
+      message: message(),
+      stack: stack(),
+      passed: options.passed
+    };
+
+    if(!result.passed) {
+      result.expected = options.expected;
+      result.actual = options.actual;
+    }
+
+    return result;
+
+    function message() {
+      if (options.passed) {
+        return 'Passed.';
+      } else if (options.message) {
+        return options.message;
+      } else if (options.error) {
+        return messageFormatter(options.error);
+      }
+      return '';
+    }
+
+    function stack() {
+      if (options.passed) {
+        return '';
+      }
+
+      var error = options.error;
+      if (!error) {
+        try {
+          throw new Error(message());
+        } catch (e) {
+          error = e;
+        }
+      }
+      return stackFormatter(error);
+    }
+  }
+
+  return buildExpectationResult;
+};
+
+getJasmineRequireObj().formatErrorMsg = function() {
+  function generateErrorMsg(domain, usage) {
+    var usageDefinition = usage ? '\nUsage: ' + usage : '';
+
+    return function errorMsg(msg) {
+      return domain + ' : ' + msg + usageDefinition;
+    };
+  }
+
+  return generateErrorMsg;
+};
+
+getJasmineRequireObj().GlobalErrors = function(j$) {
+  function GlobalErrors(global) {
+    var handlers = [];
+    global = global || j$.getGlobal();
+
+    var onerror = function onerror() {
+      var handler = handlers[handlers.length - 1];
+      handler.apply(null, Array.prototype.slice.call(arguments, 0));
+    };
+
+    this.uninstall = function noop() {};
+
+    this.install = function install() {
+      if (global.process && global.process.listeners && j$.isFunction_(global.process.on)) {
+        var originalHandlers = global.process.listeners('uncaughtException');
+        global.process.removeAllListeners('uncaughtException');
+        global.process.on('uncaughtException', onerror);
+
+        this.uninstall = function uninstall() {
+          global.process.removeListener('uncaughtException', onerror);
+          for (var i = 0; i < originalHandlers.length; i++) {
+            global.process.on('uncaughtException', originalHandlers[i]);
+          }
+        };
+      } else {
+        var originalHandler = global.onerror;
+        global.onerror = onerror;
+
+        this.uninstall = function uninstall() {
+          global.onerror = originalHandler;
+        };
+      }
+    };
+
+    this.pushListener = function pushListener(listener) {
+      handlers.push(listener);
+    };
+
+    this.popListener = function popListener() {
+      handlers.pop();
+    };
+  }
+
+  return GlobalErrors;
+};
+
+getJasmineRequireObj().DiffBuilder = function(j$) {
+  return function DiffBuilder() {
+    var path = new j$.ObjectPath(),
+        mismatches = [];
+
+    return {
+      record: function (actual, expected, formatter) {
+        formatter = formatter || defaultFormatter;
+        mismatches.push(formatter(actual, expected, path));
+      },
+
+      getMessage: function () {
+        return mismatches.join('\n');
+      },
+
+      withPath: function (pathComponent, block) {
+        var oldPath = path;
+        path = path.add(pathComponent);
+        block();
+        path = oldPath;
+      }
+    };
+
+    function defaultFormatter (actual, expected, path) {
+      return 'Expected ' +
+          path + (path.depth() ? ' = ' : '') +
+          j$.pp(actual) +
+          ' to equal ' +
+          j$.pp(expected) +
+          '.';
+    }
+  };
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+  // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+  return {
+    equals: equals,
+
+    contains: function(haystack, needle, customTesters) {
+      customTesters = customTesters || [];
+
+      if ((Object.prototype.toString.apply(haystack) === '[object Set]')) {
+        return haystack.has(needle);
+      }
+
+      if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+          (!!haystack && !haystack.indexOf))
+      {
+        for (var i = 0; i < haystack.length; i++) {
+          if (equals(haystack[i], needle, customTesters)) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      return !!haystack && haystack.indexOf(needle) >= 0;
+    },
+
+    buildFailureMessage: function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+          matcherName = args[0],
+          isNot = args[1],
+          actual = args[2],
+          expected = args.slice(3),
+          englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+      var message = 'Expected ' +
+          j$.pp(actual) +
+          (isNot ? ' not ' : ' ') +
+          englishyPredicate;
+
+      if (expected.length > 0) {
+        for (var i = 0; i < expected.length; i++) {
+          if (i > 0) {
+            message += ',';
+          }
+          message += ' ' + j$.pp(expected[i]);
+        }
+      }
+
+      return message + '.';
+    }
+  };
+
+  function isAsymmetric(obj) {
+    return obj && j$.isA_('Function', obj.asymmetricMatch);
+  }
+
+  function asymmetricMatch(a, b, customTesters, diffBuilder) {
+    var asymmetricA = isAsymmetric(a),
+        asymmetricB = isAsymmetric(b),
+        result;
+
+    if (asymmetricA && asymmetricB) {
+      return undefined;
+    }
+
+    if (asymmetricA) {
+      result = a.asymmetricMatch(b, customTesters);
+      diffBuilder.record(a, b);
+      return result;
+    }
+
+    if (asymmetricB) {
+      result = b.asymmetricMatch(a, customTesters);
+      diffBuilder.record(a, b);
+      return result;
+    }
+  }
+
+  function equals(a, b, customTesters, diffBuilder) {
+    customTesters = customTesters || [];
+    diffBuilder = diffBuilder || j$.NullDiffBuilder();
+
+    return eq(a, b, [], [], customTesters, diffBuilder);
+  }
+
+  // Equality function lovingly adapted from isEqual in
+  //   [Underscore](http://underscorejs.org)
+  function eq(a, b, aStack, bStack, customTesters, diffBuilder) {
+    var result = true, i;
+
+    var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder);
+    if (!j$.util.isUndefined(asymmetricResult)) {
+      return asymmetricResult;
+    }
+
+    for (i = 0; i < customTesters.length; i++) {
+      var customTesterResult = customTesters[i](a, b);
+      if (!j$.util.isUndefined(customTesterResult)) {
+        if (!customTesterResult) {
+          diffBuilder.record(a, b);
+        }
+        return customTesterResult;
+      }
+    }
+
+    if (a instanceof Error && b instanceof Error) {
+      result = a.message == b.message;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) {
+      result = a !== 0 || 1 / a == 1 / b;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    // A strict comparison is necessary because `null == undefined`.
+    if (a === null || b === null) {
+      result = a === b;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    var className = Object.prototype.toString.call(a);
+    if (className != Object.prototype.toString.call(b)) {
+      diffBuilder.record(a, b);
+      return false;
+    }
+    switch (className) {
+        // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        result = a == String(b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        result = a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        result = +a == +b;
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+        // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+            a.global == b.global &&
+            a.multiline == b.multiline &&
+            a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') {
+      diffBuilder.record(a, b);
+      return false;
+    }
+
+    var aIsDomNode = j$.isDomNode(a);
+    var bIsDomNode = j$.isDomNode(b);
+    if (aIsDomNode && bIsDomNode) {
+      // At first try to use DOM3 method isEqualNode
+      if (a.isEqualNode) {
+        result = a.isEqualNode(b);
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      }
+      // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
+      var aIsElement = a instanceof Element;
+      var bIsElement = b instanceof Element;
+      if (aIsElement && bIsElement) {
+        result = a.outerHTML == b.outerHTML;
+        if (!result) {
+          diffBuilder.record(a, b);
+        }
+        return result;
+      }
+      if (aIsElement || bIsElement) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+      result = a.innerText == b.innerText && a.textContent == b.textContent;
+      if (!result) {
+        diffBuilder.record(a, b);
+      }
+      return result;
+    }
+    if (aIsDomNode || bIsDomNode) {
+      diffBuilder.record(a, b);
+      return false;
+    }
+
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) { return bStack[length] == b; }
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0;
+    // Recursively compare objects and arrays.
+    // Compare array lengths to determine if a deep comparison is necessary.
+    if (className == '[object Array]') {
+      size = a.length;
+      if (size !== b.length) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+
+      for (i = 0; i < size; i++) {
+        diffBuilder.withPath(i, function() {
+          result = eq(a[i], b[i], aStack, bStack, customTesters, diffBuilder) && result;
+        });
+      }
+      if (!result) {
+        return false;
+      }
+    } else if (className == '[object Set]') {
+      if (a.size != b.size) {
+        diffBuilder.record(a, b);
+        return false;
+      }
+      var iterA = a.values(), iterB = b.values();
+      var valA, valB;
+      do {
+        valA = iterA.next();
+        valB = iterB.next();
+        if (!eq(valA.value, valB.value, aStack, bStack, customTesters, j$.NullDiffBuilder())) {
+          diffBuilder.record(a, b);
+          return false;
+        }
+      } while (!valA.done && !valB.done);
+    } else {
+
+      // Objects with different constructors are not equivalent, but `Object`s
+      // or `Array`s from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor &&
+          isFunction(aCtor) && isFunction(bCtor) &&
+          a instanceof aCtor && b instanceof bCtor &&
+          !(aCtor instanceof aCtor && bCtor instanceof bCtor)) {
+
+        diffBuilder.record(a, b, constructorsAreDifferentFormatter);
+        return false;
+      }
+    }
+
+    // Deep compare objects.
+    var aKeys = keys(a, className == '[object Array]'), key;
+    size = aKeys.length;
+
+    // Ensure that both objects contain the same number of properties before comparing deep equality.
+    if (keys(b, className == '[object Array]').length !== size) {
+      diffBuilder.record(a, b, objectKeysAreDifferentFormatter);
+      return false;
+    }
+
+    for (i = 0; i < size; i++) {
+      key = aKeys[i];
+      // Deep compare each member
+      if (!j$.util.has(b, key)) {
+        diffBuilder.record(a, b, objectKeysAreDifferentFormatter);
+        result = false;
+        continue;
+      }
+
+      diffBuilder.withPath(key, function() {
+        if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) {
+          result = false;
+        }
+      });
+    }
+
+    if (!result) {
+      return false;
+    }
+
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+
+    return result;
+  }
+
+  function keys(obj, isArray) {
+    var allKeys = Object.keys ? Object.keys(obj) :
+        (function(o) {
+          var keys = [];
+          for (var key in o) {
+            if (j$.util.has(o, key)) {
+              keys.push(key);
+            }
+          }
+          return keys;
+        })(obj);
+
+    if (!isArray) {
+      return allKeys;
+    }
+
+    if (allKeys.length === 0) {
+      return allKeys;
+    }
+
+    var extraKeys = [];
+    for (var i = 0; i < allKeys.length; i++) {
+      if (!/^[0-9]+$/.test(allKeys[i])) {
+        extraKeys.push(allKeys[i]);
+      }
+    }
+
+    return extraKeys;
+  }
+
+  function has(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  }
+
+  function isFunction(obj) {
+    return typeof obj === 'function';
+  }
+
+  function objectKeysAreDifferentFormatter(actual, expected, path) {
+    var missingProperties = j$.util.objectDifference(expected, actual),
+        extraProperties = j$.util.objectDifference(actual, expected),
+        missingPropertiesMessage = formatKeyValuePairs(missingProperties),
+        extraPropertiesMessage = formatKeyValuePairs(extraProperties),
+        messages = [];
+
+    if (!path.depth()) {
+      path = 'object';
+    }
+
+    if (missingPropertiesMessage.length) {
+      messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage);
+    }
+
+    if (extraPropertiesMessage.length) {
+      messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage);
+    }
+
+    return messages.join('\n');
+  }
+
+  function constructorsAreDifferentFormatter(actual, expected, path) {
+    if (!path.depth()) {
+      path = 'object';
+    }
+
+    return 'Expected ' +
+        path + ' to be a kind of ' +
+        j$.fnNameFor(expected.constructor) +
+        ', but was ' + j$.pp(actual) + '.';
+  }
+
+  function formatKeyValuePairs(obj) {
+    var formatted = '';
+    for (var key in obj) {
+      formatted += '\n    ' + key + ': ' + j$.pp(obj[key]);
+    }
+    return formatted;
+  }
+};
+
+getJasmineRequireObj().NullDiffBuilder = function(j$) {
+  return function() {
+    return {
+      withPath: function(_, block) {
+        block();
+      },
+      record: function() {}
+    };
+  };
+};
+
+getJasmineRequireObj().ObjectPath = function(j$) {
+  function ObjectPath(components) {
+    this.components = components || [];
+  }
+
+  ObjectPath.prototype.toString = function() {
+    if (this.components.length) {
+      return '$' + map(this.components, formatPropertyAccess).join('');
+    } else {
+      return '';
+    }
+  };
+
+  ObjectPath.prototype.add = function(component) {
+    return new ObjectPath(this.components.concat([component]));
+  };
+
+  ObjectPath.prototype.depth = function() {
+    return this.components.length;
+  };
+
+  function formatPropertyAccess(prop) {
+    if (typeof prop === 'number') {
+      return '[' + prop + ']';
+    }
+
+    if (isValidIdentifier(prop)) {
+      return '.' + prop;
+    }
+
+    return '[\'' + prop + '\']';
+  }
+
+  function map(array, fn) {
+    var results = [];
+    for (var i = 0; i < array.length; i++) {
+      results.push(fn(array[i]));
+    }
+    return results;
+  }
+
+  function isValidIdentifier(string) {
+    return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string);
+  }
+
+  return ObjectPath;
+};
+
+getJasmineRequireObj().toBe = function() {
+  /**
+   * {@link expect} the actual value to be `===` to the expected value.
+   * @function
+   * @name matchers#toBe
+   * @param {Object} expected - The expected value to compare against.
+   * @example
+   * expect(thing).toBe(realThing);
+   */
+  function toBe() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual === expected
+        };
+      }
+    };
+  }
+
+  return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+  /**
+   * {@link expect} the actual value to be within a specified precision of the expected value.
+   * @function
+   * @name matchers#toBeCloseTo
+   * @param {Object} expected - The expected value to compare against.
+   * @param {Number} [precision=2] - The number of decimal points to check.
+   * @example
+   * expect(number).toBeCloseTo(42.2, 3);
+   */
+  function toBeCloseTo() {
+    return {
+      compare: function(actual, expected, precision) {
+        if (precision !== 0) {
+          precision = precision || 2;
+        }
+
+        return {
+          pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+        };
+      }
+    };
+  }
+
+  return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+  /**
+   * {@link expect} the actual value to be defined. (Not `undefined`)
+   * @function
+   * @name matchers#toBeDefined
+   * @example
+   * expect(result).toBeDefined();
+   */
+  function toBeDefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: (void 0 !== actual)
+        };
+      }
+    };
+  }
+
+  return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+  /**
+   * {@link expect} the actual value to be falsy
+   * @function
+   * @name matchers#toBeFalsy
+   * @example
+   * expect(result).toBeFalsy();
+   */
+  function toBeFalsy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!!actual
+        };
+      }
+    };
+  }
+
+  return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+  /**
+   * {@link expect} the actual value to be greater than the expected value.
+   * @function
+   * @name matchers#toBeGreaterThan
+   * @param {Number} expected - The value to compare against.
+   * @example
+   * expect(result).toBeGreaterThan(3);
+   */
+  function toBeGreaterThan() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual > expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeGreaterThanOrEqual = function() {
+  /**
+   * {@link expect} the actual value to be greater than or equal to the expected value.
+   * @function
+   * @name matchers#toBeGreaterThanOrEqual
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeGreaterThanOrEqual(25);
+   */
+  function toBeGreaterThanOrEqual() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual >= expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThanOrEqual;
+};
+
+getJasmineRequireObj().toBeLessThan = function() {
+  /**
+   * {@link expect} the actual value to be less than the expected value.
+   * @function
+   * @name matchers#toBeLessThan
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeLessThan(0);
+   */
+  function toBeLessThan() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual < expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThan;
+};
+
+getJasmineRequireObj().toBeLessThanOrEqual = function() {
+  /**
+   * {@link expect} the actual value to be less than or equal to the expected value.
+   * @function
+   * @name matchers#toBeLessThanOrEqual
+   * @param {Number} expected - The expected value to compare against.
+   * @example
+   * expect(result).toBeLessThanOrEqual(123);
+   */
+  function toBeLessThanOrEqual() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual <= expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThanOrEqual;
+};
+
+getJasmineRequireObj().toBeNaN = function(j$) {
+  /**
+   * {@link expect} the actual value to be `NaN` (Not a Number).
+   * @function
+   * @name matchers#toBeNaN
+   * @example
+   * expect(thing).toBeNaN();
+   */
+  function toBeNaN() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual !== actual)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual not to be NaN.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNegativeInfinity = function(j$) {
+  /**
+   * {@link expect} the actual value to be `-Infinity` (-infinity).
+   * @function
+   * @name matchers#toBeNegativeInfinity
+   * @example
+   * expect(thing).toBeNegativeInfinity();
+   */
+  function toBeNegativeInfinity() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual === Number.NEGATIVE_INFINITY)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual to be -Infinity.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be -Infinity.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNegativeInfinity;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+  /**
+   * {@link expect} the actual value to be `null`.
+   * @function
+   * @name matchers#toBeNull
+   * @example
+   * expect(result).toBeNull();
+   */
+  function toBeNull() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: actual === null
+        };
+      }
+    };
+  }
+
+  return toBeNull;
+};
+
+getJasmineRequireObj().toBePositiveInfinity = function(j$) {
+  /**
+   * {@link expect} the actual value to be `Infinity` (infinity).
+   * @function
+   * @name matchers#toBePositiveInfinity
+   * @example
+   * expect(thing).toBePositiveInfinity();
+   */
+  function toBePositiveInfinity() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual === Number.POSITIVE_INFINITY)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual to be Infinity.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be Infinity.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBePositiveInfinity;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+  /**
+   * {@link expect} the actual value to be truthy.
+   * @function
+   * @name matchers#toBeTruthy
+   * @example
+   * expect(thing).toBeTruthy();
+   */
+  function toBeTruthy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!actual
+        };
+      }
+    };
+  }
+
+  return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+  /**
+   * {@link expect} the actual value to be `undefined`.
+   * @function
+   * @name matchers#toBeUndefined
+   * @example
+   * expect(result).toBeUndefined():
+   */
+  function toBeUndefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: void 0 === actual
+        };
+      }
+    };
+  }
+
+  return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+  /**
+   * {@link expect} the actual value to contain a specific value.
+   * @function
+   * @name matchers#toContain
+   * @param {Object} expected - The value to look for.
+   * @example
+   * expect(array).toContain(anElement);
+   * expect(string).toContain(substring);
+   */
+  function toContain(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+
+        return {
+          pass: util.contains(actual, expected, customEqualityTesters)
+        };
+      }
+    };
+  }
+
+  return toContain;
+};
+
+getJasmineRequireObj().toEqual = function(j$) {
+  /**
+   * {@link expect} the actual value to be equal to the expected, using deep equality comparison.
+   * @function
+   * @name matchers#toEqual
+   * @param {Object} expected - Expected value
+   * @example
+   * expect(bigObject).toEqual({"foo": ['bar', 'baz']});
+   */
+  function toEqual(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+        var result = {
+              pass: false
+            },
+            diffBuilder = j$.DiffBuilder();
+
+        result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder);
+
+        // TODO: only set error message if test fails
+        result.message = diffBuilder.getMessage();
+
+        return result;
+      }
+    };
+  }
+
+  return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalled>', 'expect(<spyObj>).toHaveBeenCalled()');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called.
+   * @function
+   * @name matchers#toHaveBeenCalled
+   * @example
+   * expect(mySpy).toHaveBeenCalled();
+   * expect(mySpy).not.toHaveBeenCalled();
+   */
+  function toHaveBeenCalled() {
+    return {
+      compare: function(actual) {
+        var result = {};
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (arguments.length > 1) {
+          throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith'));
+        }
+
+        result.pass = actual.calls.any();
+
+        result.message = result.pass ?
+            'Expected spy ' + actual.and.identity() + ' not to have been called.' :
+            'Expected spy ' + actual.and.identity() + ' to have been called.';
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledBefore>', 'expect(<spyObj>).toHaveBeenCalledBefore(<spyObj>)');
+
+  /**
+   * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}.
+   * @function
+   * @name matchers#toHaveBeenCalledBefore
+   * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}.
+   * @example
+   * expect(mySpy).toHaveBeenCalledBefore(otherSpy);
+   */
+  function toHaveBeenCalledBefore() {
+    return {
+      compare: function(firstSpy, latterSpy) {
+        if (!j$.isSpy(firstSpy)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.'));
+        }
+        if (!j$.isSpy(latterSpy)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.'));
+        }
+
+        var result = { pass: false };
+
+        if (!firstSpy.calls.count()) {
+          result.message = 'Expected spy ' +  firstSpy.and.identity() + ' to have been called.';
+          return result;
+        }
+        if (!latterSpy.calls.count()) {
+          result.message = 'Expected spy ' +  latterSpy.and.identity() + ' to have been called.';
+          return result;
+        }
+
+        var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder;
+        var first2ndSpyCall = latterSpy.calls.first().invocationOrder;
+
+        result.pass = latest1stSpyCall < first2ndSpyCall;
+
+        if (result.pass) {
+          result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was';
+        } else {
+          var first1stSpyCall = firstSpy.calls.first().invocationOrder;
+          var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder;
+
+          if(first1stSpyCall < first2ndSpyCall) {
+            result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)';
+          } else if (latest2ndSpyCall > latest1stSpyCall) {
+            result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)';
+          } else {
+            result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity();
+          }
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledBefore;
+};
+
+getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledTimes>', 'expect(<spyObj>).toHaveBeenCalledTimes(<Number>)');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times.
+   * @function
+   * @name matchers#toHaveBeenCalledTimes
+   * @param {Number} expected - The number of invocations to look for.
+   * @example
+   * expect(mySpy).toHaveBeenCalledTimes(3);
+   */
+  function toHaveBeenCalledTimes() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        var args = Array.prototype.slice.call(arguments, 0),
+            result = { pass: false };
+
+        if (!j$.isNumber_(expected)){
+          throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.'));
+        }
+
+        actual = args[0];
+        var calls = actual.calls.count();
+        var timesMessage = expected === 1 ? 'once' : expected + ' times';
+        result.pass = calls === expected;
+        result.message = result.pass ?
+            'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' +  calls + ' times.' :
+            'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' +  calls + ' times.';
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledTimes;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledWith>', 'expect(<spyObj>).toHaveBeenCalledWith(...arguments)');
+
+  /**
+   * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once.
+   * @function
+   * @name matchers#toHaveBeenCalledWith
+   * @param {...Object} - The arguments to look for
+   * @example
+   * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2);
+   */
+  function toHaveBeenCalledWith(util, customEqualityTesters) {
+    return {
+      compare: function() {
+        var args = Array.prototype.slice.call(arguments, 0),
+            actual = args[0],
+            expectedArgs = args.slice(1),
+            result = { pass: false };
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (!actual.calls.any()) {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
+          return result;
+        }
+
+        if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+          result.pass = true;
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+        } else {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toMatch>', 'expect(<expectation>).toMatch(<string> || <regexp>)');
+
+  /**
+   * {@link expect} the actual value to match a regular expression
+   * @function
+   * @name matchers#toMatch
+   * @param {RegExp|String} expected - Value to look for in the string.
+   * @example
+   * expect("my string").toMatch(/string$/);
+   * expect("other string").toMatch("her");
+   */
+  function toMatch() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+          throw new Error(getErrorMsg('Expected is not a String or a RegExp'));
+        }
+
+        var regexp = new RegExp(expected);
+
+        return {
+          pass: regexp.test(actual)
+        };
+      }
+    };
+  }
+
+  return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toThrow>', 'expect(function() {<expectation>}).toThrow()');
+
+  /**
+   * {@link expect} a function to `throw` something.
+   * @function
+   * @name matchers#toThrow
+   * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that something was thrown will be checked.
+   * @example
+   * expect(function() { return 'things'; }).toThrow('foo');
+   * expect(function() { return 'stuff'; }).toThrow();
+   */
+  function toThrow(util) {
+    return {
+      compare: function(actual, expected) {
+        var result = { pass: false },
+            threw = false,
+            thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          result.message = 'Expected function to throw an exception.';
+          return result;
+        }
+
+        if (arguments.length == 1) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; };
+
+          return result;
+        }
+
+        if (util.equals(thrown, expected)) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+        } else {
+          result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' +  j$.pp(thrown) + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+
+  var getErrorMsg =  j$.formatErrorMsg('<toThrowError>', 'expect(function() {<expectation>}).toThrowError(<ErrorConstructor>, <message>)');
+
+  /**
+   * {@link expect} a function to `throw` an `Error`.
+   * @function
+   * @name matchers#toThrowError
+   * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used.
+   * @param {RegExp|String} [message] - The message that should be set on the thrown `Error`
+   * @example
+   * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message');
+   * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/);
+   * expect(function() { return 'stuff'; }).toThrowError(MyCustomError);
+   * expect(function() { return 'other'; }).toThrowError(/foo/);
+   * expect(function() { return 'other'; }).toThrowError();
+   */
+  function toThrowError () {
+    return {
+      compare: function(actual) {
+        var threw = false,
+            pass = {pass: true},
+            fail = {pass: false},
+            thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        var errorMatcher = getMatcher.apply(null, arguments);
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          fail.message = 'Expected function to throw an Error.';
+          return fail;
+        }
+
+        // Get Error constructor of thrown
+        if (!isErrorObject(thrown)) {
+          fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; };
+          return fail;
+        }
+
+        if (errorMatcher.hasNoSpecifics()) {
+          pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.';
+          return pass;
+        }
+
+        if (errorMatcher.matches(thrown)) {
+          pass.message = function() {
+            return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
+          };
+          return pass;
+        } else {
+          fail.message = function() {
+            return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
+                ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+          };
+          return fail;
+        }
+      }
+    };
+
+    function getMatcher() {
+      var expected = null,
+          errorType = null;
+
+      if (arguments.length == 2) {
+        expected = arguments[1];
+        if (isAnErrorType(expected)) {
+          errorType = expected;
+          expected = null;
+        }
+      } else if (arguments.length > 2) {
+        errorType = arguments[1];
+        expected = arguments[2];
+        if (!isAnErrorType(errorType)) {
+          throw new Error(getErrorMsg('Expected error type is not an Error.'));
+        }
+      }
+
+      if (expected && !isStringOrRegExp(expected)) {
+        if (errorType) {
+          throw new Error(getErrorMsg('Expected error message is not a string or RegExp.'));
+        } else {
+          throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.'));
+        }
+      }
+
+      function messageMatch(message) {
+        if (typeof expected == 'string') {
+          return expected == message;
+        } else {
+          return expected.test(message);
+        }
+      }
+
+      return {
+        errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
+        thrownDescription: function(thrown) {
+          var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+              thrownMessage = '';
+
+          if (expected) {
+            thrownMessage = ' with message ' + j$.pp(thrown.message);
+          }
+
+          return thrownName + thrownMessage;
+        },
+        messageDescription: function() {
+          if (expected === null) {
+            return '';
+          } else if (expected instanceof RegExp) {
+            return ' with a message matching ' + j$.pp(expected);
+          } else {
+            return ' with message ' + j$.pp(expected);
+          }
+        },
+        hasNoSpecifics: function() {
+          return expected === null && errorType === null;
+        },
+        matches: function(error) {
+          return (errorType === null || error instanceof errorType) &&
+              (expected === null || messageMatch(error.message));
+        }
+      };
+    }
+
+    function isStringOrRegExp(potential) {
+      return potential instanceof RegExp || (typeof potential == 'string');
+    }
+
+    function isAnErrorType(type) {
+      if (typeof type !== 'function') {
+        return false;
+      }
+
+      var Surrogate = function() {};
+      Surrogate.prototype = type.prototype;
+      return isErrorObject(new Surrogate());
+    }
+
+    function isErrorObject(thrown) {
+      if (thrown instanceof Error) {
+        return true;
+      }
+      if (thrown && thrown.constructor && thrown.constructor.constructor &&
+          (thrown instanceof (thrown.constructor.constructor('return this')()).Error)) {
+        return true;
+      }
+      return false;
+    }
+  }
+
+  return toThrowError;
+};
+
+getJasmineRequireObj().MockDate = function() {
+  function MockDate(global) {
+    var self = this;
+    var currentTime = 0;
+
+    if (!global || !global.Date) {
+      self.install = function() {};
+      self.tick = function() {};
+      self.uninstall = function() {};
+      return self;
+    }
+
+    var GlobalDate = global.Date;
+
+    self.install = function(mockDate) {
+      if (mockDate instanceof GlobalDate) {
+        currentTime = mockDate.getTime();
+      } else {
+        currentTime = new GlobalDate().getTime();
+      }
+
+      global.Date = FakeDate;
+    };
+
+    self.tick = function(millis) {
+      millis = millis || 0;
+      currentTime = currentTime + millis;
+    };
+
+    self.uninstall = function() {
+      currentTime = 0;
+      global.Date = GlobalDate;
+    };
+
+    createDateProperties();
+
+    return self;
+
+    function FakeDate() {
+      switch(arguments.length) {
+        case 0:
+          return new GlobalDate(currentTime);
+        case 1:
+          return new GlobalDate(arguments[0]);
+        case 2:
+          return new GlobalDate(arguments[0], arguments[1]);
+        case 3:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+        case 4:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+        case 5:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4]);
+        case 6:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4], arguments[5]);
+        default:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+              arguments[4], arguments[5], arguments[6]);
+      }
+    }
+
+    function createDateProperties() {
+      FakeDate.prototype = GlobalDate.prototype;
+
+      FakeDate.now = function() {
+        if (GlobalDate.now) {
+          return currentTime;
+        } else {
+          throw new Error('Browser does not support Date.now()');
+        }
+      };
+
+      FakeDate.toSource = GlobalDate.toSource;
+      FakeDate.toString = GlobalDate.toString;
+      FakeDate.parse = GlobalDate.parse;
+      FakeDate.UTC = GlobalDate.UTC;
+    }
+  }
+
+  return MockDate;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+  function PrettyPrinter() {
+    this.ppNestLevel_ = 0;
+    this.seen = [];
+  }
+
+  function hasCustomToString(value) {
+    // value.toString !== Object.prototype.toString if value has no custom toString but is from another context (e.g.
+    // iframe, web worker)
+    return value.toString !== Object.prototype.toString && (value.toString() !== Object.prototype.toString.call(value));
+  }
+
+  PrettyPrinter.prototype.format = function(value) {
+    this.ppNestLevel_++;
+    try {
+      if (j$.util.isUndefined(value)) {
+        this.emitScalar('undefined');
+      } else if (value === null) {
+        this.emitScalar('null');
+      } else if (value === 0 && 1/value === -Infinity) {
+        this.emitScalar('-0');
+      } else if (value === j$.getGlobal()) {
+        this.emitScalar('<global>');
+      } else if (value.jasmineToString) {
+        this.emitScalar(value.jasmineToString());
+      } else if (typeof value === 'string') {
+        this.emitString(value);
+      } else if (j$.isSpy(value)) {
+        this.emitScalar('spy on ' + value.and.identity());
+      } else if (value instanceof RegExp) {
+        this.emitScalar(value.toString());
+      } else if (typeof value === 'function') {
+        this.emitScalar('Function');
+      } else if (typeof value.nodeType === 'number') {
+        this.emitScalar('HTMLNode');
+      } else if (value instanceof Date) {
+        this.emitScalar('Date(' + value + ')');
+      } else if (value.toString && value.toString() == '[object Set]') {
+        this.emitSet(value);
+      } else if (value.toString && typeof value === 'object' && !j$.isArray_(value) && hasCustomToString(value)) {
+        this.emitScalar(value.toString());
+      } else if (j$.util.arrayContains(this.seen, value)) {
+        this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
+      } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+        this.seen.push(value);
+        if (j$.isArray_(value)) {
+          this.emitArray(value);
+        } else {
+          this.emitObject(value);
+        }
+        this.seen.pop();
+      } else {
+        this.emitScalar(value.toString());
+      }
+    } finally {
+      this.ppNestLevel_--;
+    }
+  };
+
+  PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+    for (var property in obj) {
+      if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
+      fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+      obj.__lookupGetter__(property) !== null) : false);
+    }
+  };
+
+  PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitSet = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+  function StringPrettyPrinter() {
+    PrettyPrinter.call(this);
+
+    this.string = '';
+  }
+
+  j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+  StringPrettyPrinter.prototype.emitScalar = function(value) {
+    this.append(value);
+  };
+
+  StringPrettyPrinter.prototype.emitString = function(value) {
+    this.append('\'' + value + '\'');
+  };
+
+  StringPrettyPrinter.prototype.emitArray = function(array) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append('Array');
+      return;
+    }
+    var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+    this.append('[ ');
+    for (var i = 0; i < length; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(array[i]);
+    }
+    if(array.length > length){
+      this.append(', ...');
+    }
+
+    var self = this;
+    var first = array.length === 0;
+    this.iterateObject(array, function(property, isGetter) {
+      if (property.match(/^\d+$/)) {
+        return;
+      }
+
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(array, property, isGetter);
+    });
+
+    this.append(' ]');
+  };
+
+  StringPrettyPrinter.prototype.emitSet = function(set) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append('Set');
+      return;
+    }
+    this.append('Set( ');
+    var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+    var iter = set.values();
+    for (var i = 0; i < size; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(iter.next().value);
+    }
+    if (set.size > size){
+      this.append(', ...');
+    }
+    this.append(' )');
+  };
+
+  StringPrettyPrinter.prototype.emitObject = function(obj) {
+    var ctor = obj.constructor,
+        constructorName;
+
+    constructorName = typeof ctor === 'function' && obj instanceof ctor ?
+        j$.fnNameFor(obj.constructor) :
+        'null';
+
+    this.append(constructorName);
+
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      return;
+    }
+
+    var self = this;
+    this.append('({ ');
+    var first = true;
+
+    this.iterateObject(obj, function(property, isGetter) {
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(obj, property, isGetter);
+    });
+
+    this.append(' })');
+  };
+
+  StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
+    this.append(property);
+    this.append(': ');
+    if (isGetter) {
+      this.append('<getter>');
+    } else {
+      this.format(obj[property]);
+    }
+  };
+
+  StringPrettyPrinter.prototype.append = function(value) {
+    this.string += value;
+  };
+
+  return function(value) {
+    var stringPrettyPrinter = new StringPrettyPrinter();
+    stringPrettyPrinter.format(value);
+    return stringPrettyPrinter.string;
+  };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+
+  function once(fn) {
+    var called = false;
+    return function() {
+      if (!called) {
+        called = true;
+        fn();
+      }
+      return null;
+    };
+  }
+
+  function QueueRunner(attrs) {
+    this.queueableFns = attrs.queueableFns || [];
+    this.onComplete = attrs.onComplete || function() {};
+    this.clearStack = attrs.clearStack || function(fn) {fn();};
+    this.onException = attrs.onException || function() {};
+    this.catchException = attrs.catchException || function() { return true; };
+    this.userContext = attrs.userContext || {};
+    this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+    this.fail = attrs.fail || function() {};
+    this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} };
+  }
+
+  QueueRunner.prototype.execute = function() {
+    this.run(this.queueableFns, 0);
+  };
+
+  QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+    var length = queueableFns.length,
+        self = this,
+        iterativeIndex;
+
+
+    for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+      var queueableFn = queueableFns[iterativeIndex];
+      if (queueableFn.fn.length > 0) {
+        attemptAsync(queueableFn);
+        return;
+      } else {
+        attemptSync(queueableFn);
+      }
+    }
+
+    this.clearStack(this.onComplete);
+
+    function attemptSync(queueableFn) {
+      try {
+        queueableFn.fn.call(self.userContext);
+      } catch (e) {
+        handleException(e, queueableFn);
+      }
+    }
+
+    function attemptAsync(queueableFn) {
+      var clearTimeout = function () {
+            Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+          },
+          handleError = function(error) {
+            onException(error);
+            next();
+          },
+          next = once(function () {
+            clearTimeout(timeoutId);
+            self.globalErrors.popListener(handleError);
+            self.run(queueableFns, iterativeIndex + 1);
+          }),
+          timeoutId;
+
+      next.fail = function() {
+        self.fail.apply(null, arguments);
+        next();
+      };
+
+      self.globalErrors.pushListener(handleError);
+
+      if (queueableFn.timeout) {
+        timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
+          var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+          onException(error);
+          next();
+        }, queueableFn.timeout()]]);
+      }
+
+      try {
+        queueableFn.fn.call(self.userContext, next);
+      } catch (e) {
+        handleException(e, queueableFn);
+        next();
+      }
+    }
+
+    function onException(e) {
+      self.onException(e);
+    }
+
+    function handleException(e, queueableFn) {
+      onException(e);
+      if (!self.catchException(e)) {
+        //TODO: set a var when we catch an exception and
+        //use a finally block to close the loop in a nice way..
+        throw e;
+      }
+    }
+  };
+
+  return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+  function ReportDispatcher(methods) {
+
+    var dispatchedMethods = methods || [];
+
+    for (var i = 0; i < dispatchedMethods.length; i++) {
+      var method = dispatchedMethods[i];
+      this[method] = (function(m) {
+        return function() {
+          dispatch(m, arguments);
+        };
+      }(method));
+    }
+
+    var reporters = [];
+    var fallbackReporter = null;
+
+    this.addReporter = function(reporter) {
+      reporters.push(reporter);
+    };
+
+    this.provideFallbackReporter = function(reporter) {
+      fallbackReporter = reporter;
+    };
+
+    this.clearReporters = function() {
+      reporters = [];
+    };
+
+    return this;
+
+    function dispatch(method, args) {
+      if (reporters.length === 0 && fallbackReporter !== null) {
+        reporters.push(fallbackReporter);
+      }
+      for (var i = 0; i < reporters.length; i++) {
+        var reporter = reporters[i];
+        if (reporter[method]) {
+          reporter[method].apply(reporter, args);
+        }
+      }
+    }
+  }
+
+  return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+  var jasmineInterface = {
+    /**
+     * Create a group of specs (often called a suite).
+     *
+     * Calls to `describe` can be nested within other calls to compose your suite as a tree.
+     * @name describe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    describe: function(description, specDefinitions) {
+      return env.describe(description, specDefinitions);
+    },
+
+    /**
+     * A temporarily disabled [`describe`]{@link describe}
+     *
+     * Specs within an `xdescribe` will be marked pending and not executed
+     * @name xdescribe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    xdescribe: function(description, specDefinitions) {
+      return env.xdescribe(description, specDefinitions);
+    },
+
+    /**
+     * A focused [`describe`]{@link describe}
+     *
+     * If suites or specs are focused, only those that are focused will be executed
+     * @see fit
+     * @name fdescribe
+     * @function
+     * @global
+     * @param {String} description Textual description of the group
+     * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs
+     */
+    fdescribe: function(description, specDefinitions) {
+      return env.fdescribe(description, specDefinitions);
+    },
+
+    /**
+     * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the state of the code.
+     *
+     * A spec whose expectations all succeed will be passing and a spec with any failures will fail.
+     * @name it
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking
+     * @param {Function} [testFunction] Function that contains the code of your test. If not provided the test will be `pending`.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+     */
+    it: function() {
+      return env.it.apply(env, arguments);
+    },
+
+    /**
+     * A temporarily disabled [`it`]{@link it}
+     *
+     * The spec will report as `pending` and will not be executed.
+     * @name xit
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking.
+     * @param {Function} [testFunction] Function that contains the code of your test. Will not be executed.
+     */
+    xit: function() {
+      return env.xit.apply(env, arguments);
+    },
+
+    /**
+     * A focused [`it`]{@link it}
+     *
+     * If suites or specs are focused, only those that are focused will be executed.
+     * @name fit
+     * @function
+     * @global
+     * @param {String} description Textual description of what this spec is checking.
+     * @param {Function} testFunction Function that contains the code of your test.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+     */
+    fit: function() {
+      return env.fit.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared setup before each of the specs in the {@link describe} in which it is called.
+     * @name beforeEach
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to setup your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeEach.
+     */
+    beforeEach: function() {
+      return env.beforeEach.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared teardown after each of the specs in the {@link describe} in which it is called.
+     * @name afterEach
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to teardown your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach.
+     */
+    afterEach: function() {
+      return env.afterEach.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared setup once before all of the specs in the {@link describe} are run.
+     *
+     * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail.
+     * @name beforeAll
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to setup your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll.
+     */
+    beforeAll: function() {
+      return env.beforeAll.apply(env, arguments);
+    },
+
+    /**
+     * Run some shared teardown once before all of the specs in the {@link describe} are run.
+     *
+     * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail.
+     * @name afterAll
+     * @function
+     * @global
+     * @param {Function} [function] Function that contains the code to teardown your specs.
+     * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll.
+     */
+    afterAll: function() {
+      return env.afterAll.apply(env, arguments);
+    },
+
+    /**
+     * Create an expectation for a spec.
+     * @name expect
+     * @function
+     * @global
+     * @param {Object} actual - Actual computed value to test expectations against.
+     * @return {matchers}
+     */
+    expect: function(actual) {
+      return env.expect(actual);
+    },
+
+    /**
+     * Mark a spec as pending, expectation results will be ignored.
+     * @name pending
+     * @function
+     * @global
+     * @param {String} [message] - Reason the spec is pending.
+     */
+    pending: function() {
+      return env.pending.apply(env, arguments);
+    },
+
+    /**
+     * Explicitly mark a spec as failed.
+     * @name fail
+     * @function
+     * @global
+     * @param {String|Error} [error] - Reason for the failure.
+     */
+    fail: function() {
+      return env.fail.apply(env, arguments);
+    },
+
+    /**
+     * Install a spy onto an existing object.
+     * @name spyOn
+     * @function
+     * @global
+     * @param {Object} obj - The object upon which to install the {@link Spy}.
+     * @param {String} methodName - The name of the method to replace with a {@link Spy}.
+     * @returns {Spy}
+     */
+    spyOn: function(obj, methodName) {
+      return env.spyOn(obj, methodName);
+    },
+
+    /**
+     * Install a spy on a property onto an existing object.
+     * @name spyOnProperty
+     * @function
+     * @global
+     * @param {Object} obj - The object upon which to install the {@link Spy}
+     * @param {String} propertyName - The name of the property to replace with a {@link Spy}.
+     * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on.
+     * @returns {Spy}
+     */
+    spyOnProperty: function(obj, methodName, accessType) {
+      return env.spyOnProperty(obj, methodName, accessType);
+    },
+
+    jsApiReporter: new jasmine.JsApiReporter({
+      timer: new jasmine.Timer()
+    }),
+
+    /**
+     * @namespace jasmine
+     */
+    jasmine: jasmine
+  };
+
+  /**
+   * Add a custom equality tester for the current scope of specs.
+   *
+   * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+   * @name jasmine.addCustomEqualityTester
+   * @function
+   * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or `false` comparison result if it knows how to compare them, and `undefined` otherwise.
+   * @see custom_equality
+   */
+  jasmine.addCustomEqualityTester = function(tester) {
+    env.addCustomEqualityTester(tester);
+  };
+
+  /**
+   * Add custom matchers for the current scope of specs.
+   *
+   * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+   * @name jasmine.addMatchers
+   * @function
+   * @param {Object} matchers - Keys from this object will be the new matcher names.
+   * @see custom_matcher
+   */
+  jasmine.addMatchers = function(matchers) {
+    return env.addMatchers(matchers);
+  };
+
+  /**
+   * Get the currently booted mock {Clock} for this Jasmine environment.
+   * @name jasmine.clock
+   * @function
+   * @returns {Clock}
+   */
+  jasmine.clock = function() {
+    return env.clock;
+  };
+
+  return jasmineInterface;
+};
+
+getJasmineRequireObj().Spy = function (j$) {
+
+  var nextOrder = (function() {
+    var order = 0;
+
+    return function() {
+      return order++;
+    };
+  })();
+
+  /**
+   * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj}
+   * @constructor
+   * @name Spy
+   */
+  function Spy(name, originalFn) {
+    var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0),
+        wrapper = makeFunc(numArgs, function () {
+          return spy.apply(this, Array.prototype.slice.call(arguments));
+        }),
+        spyStrategy = new j$.SpyStrategy({
+          name: name,
+          fn: originalFn,
+          getSpy: function () {
+            return wrapper;
+          }
+        }),
+        callTracker = new j$.CallTracker(),
+        spy = function () {
+          /**
+           * @name Spy.callData
+           * @property {object} object - `this` context for the invocation.
+           * @property {number} invocationOrder - Order of the invocation.
+           * @property {Array} args - The arguments passed for this invocation.
+           */
+          var callData = {
+            object: this,
+            invocationOrder: nextOrder(),
+            args: Array.prototype.slice.apply(arguments)
+          };
+
+          callTracker.track(callData);
+          var returnValue = spyStrategy.exec.apply(this, arguments);
+          callData.returnValue = returnValue;
+
+          return returnValue;
+        };
+
+    function makeFunc(length, fn) {
+      switch (length) {
+        case 1 : return function (a) { return fn.apply(this, arguments); };
+        case 2 : return function (a,b) { return fn.apply(this, arguments); };
+        case 3 : return function (a,b,c) { return fn.apply(this, arguments); };
+        case 4 : return function (a,b,c,d) { return fn.apply(this, arguments); };
+        case 5 : return function (a,b,c,d,e) { return fn.apply(this, arguments); };
+        case 6 : return function (a,b,c,d,e,f) { return fn.apply(this, arguments); };
+        case 7 : return function (a,b,c,d,e,f,g) { return fn.apply(this, arguments); };
+        case 8 : return function (a,b,c,d,e,f,g,h) { return fn.apply(this, arguments); };
+        case 9 : return function (a,b,c,d,e,f,g,h,i) { return fn.apply(this, arguments); };
+        default : return function () { return fn.apply(this, arguments); };
+      }
+    }
+
+    for (var prop in originalFn) {
+      if (prop === 'and' || prop === 'calls') {
+        throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
+      }
+
+      wrapper[prop] = originalFn[prop];
+    }
+
+    wrapper.and = spyStrategy;
+    wrapper.calls = callTracker;
+
+    return wrapper;
+  }
+
+  return Spy;
+};
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
+
+  function SpyRegistry(options) {
+    options = options || {};
+    var currentSpies = options.currentSpies || function() { return []; };
+
+    this.allowRespy = function(allow){
+      this.respy = allow;
+    };
+
+    this.spyOn = function(obj, methodName) {
+
+      if (j$.util.isUndefined(obj) || obj === null) {
+        throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()'));
+      }
+
+      if (j$.util.isUndefined(methodName) || methodName === null) {
+        throw new Error(getErrorMsg('No method name supplied'));
+      }
+
+      if (j$.util.isUndefined(obj[methodName])) {
+        throw new Error(getErrorMsg(methodName + '() method does not exist'));
+      }
+
+      if (obj[methodName] && j$.isSpy(obj[methodName])  ) {
+        if ( !!this.respy ){
+          return obj[methodName];
+        }else {
+          throw new Error(getErrorMsg(methodName + ' has already been spied upon'));
+        }
+      }
+
+      var descriptor;
+      try {
+        descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
+      } catch(e) {
+        // IE 8 doesn't support `definePropery` on non-DOM nodes
+      }
+
+      if (descriptor && !(descriptor.writable || descriptor.set)) {
+        throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter'));
+      }
+
+      var originalMethod = obj[methodName],
+          spiedMethod = j$.createSpy(methodName, originalMethod),
+          restoreStrategy;
+
+      if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
+        restoreStrategy = function() {
+          obj[methodName] = originalMethod;
+        };
+      } else {
+        restoreStrategy = function() {
+          if (!delete obj[methodName]) {
+            obj[methodName] = originalMethod;
+          }
+        };
+      }
+
+      currentSpies().push({
+        restoreObjectToOriginalState: restoreStrategy
+      });
+
+      obj[methodName] = spiedMethod;
+
+      return spiedMethod;
+    };
+
+    this.spyOnProperty = function (obj, propertyName, accessType) {
+      accessType = accessType || 'get';
+
+      if (j$.util.isUndefined(obj)) {
+        throw new Error('spyOn could not find an object to spy upon for ' + propertyName + '');
+      }
+
+      if (j$.util.isUndefined(propertyName)) {
+        throw new Error('No property name supplied');
+      }
+
+      var descriptor;
+      try {
+        descriptor = j$.util.getPropertyDescriptor(obj, propertyName);
+      } catch(e) {
+        // IE 8 doesn't support `definePropery` on non-DOM nodes
+      }
+
+      if (!descriptor) {
+        throw new Error(propertyName + ' property does not exist');
+      }
+
+      if (!descriptor.configurable) {
+        throw new Error(propertyName + ' is not declared configurable');
+      }
+
+      if(!descriptor[accessType]) {
+        throw new Error('Property ' + propertyName + ' does not have access type ' + accessType);
+      }
+
+      if (j$.isSpy(descriptor[accessType])) {
+        //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+        throw new Error(propertyName + ' has already been spied upon');
+      }
+
+      var originalDescriptor = j$.util.clone(descriptor),
+          spy = j$.createSpy(propertyName, descriptor[accessType]),
+          restoreStrategy;
+
+      if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
+        restoreStrategy = function() {
+          Object.defineProperty(obj, propertyName, originalDescriptor);
+        };
+      } else {
+        restoreStrategy = function() {
+          delete obj[propertyName];
+        };
+      }
+
+      currentSpies().push({
+        restoreObjectToOriginalState: restoreStrategy
+      });
+
+      descriptor[accessType] = spy;
+
+      Object.defineProperty(obj, propertyName, descriptor);
+
+      return spy;
+    };
+
+    this.clearSpies = function() {
+      var spies = currentSpies();
+      for (var i = spies.length - 1; i >= 0; i--) {
+        var spyEntry = spies[i];
+        spyEntry.restoreObjectToOriginalState();
+      }
+    };
+  }
+
+  return SpyRegistry;
+};
+
+getJasmineRequireObj().SpyStrategy = function(j$) {
+
+  /**
+   * @namespace Spy#and
+   */
+  function SpyStrategy(options) {
+    options = options || {};
+
+    var identity = options.name || 'unknown',
+        originalFn = options.fn || function() {},
+        getSpy = options.getSpy || function() {},
+        plan = function() {};
+
+    /**
+     * Return the identifying information for the spy.
+     * @name Spy#and#identity
+     * @function
+     * @returns {String}
+     */
+    this.identity = function() {
+      return identity;
+    };
+
+    /**
+     * Execute the current spy strategy.
+     * @name Spy#and#exec
+     * @function
+     */
+    this.exec = function() {
+      return plan.apply(this, arguments);
+    };
+
+    /**
+     * Tell the spy to call through to the real implementation when invoked.
+     * @name Spy#and#callThrough
+     * @function
+     */
+    this.callThrough = function() {
+      plan = originalFn;
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to return the value when invoked.
+     * @name Spy#and#returnValue
+     * @function
+     * @param {*} value The value to return.
+     */
+    this.returnValue = function(value) {
+      plan = function() {
+        return value;
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked.
+     * @name Spy#and#returnValues
+     * @function
+     * @param {...*} values - Values to be returned on subsequent calls to the spy.
+     */
+    this.returnValues = function() {
+      var values = Array.prototype.slice.call(arguments);
+      plan = function () {
+        return values.shift();
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to throw an error when invoked.
+     * @name Spy#and#throwError
+     * @function
+     * @param {Error|String} something Thing to throw
+     */
+    this.throwError = function(something) {
+      var error = (something instanceof Error) ? something : new Error(something);
+      plan = function() {
+        throw error;
+      };
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to call a fake implementation when invoked.
+     * @name Spy#and#callFake
+     * @function
+     * @param {Function} fn The function to invoke with the passed parameters.
+     */
+    this.callFake = function(fn) {
+      if(!j$.isFunction_(fn)) {
+        throw new Error('Argument passed to callFake should be a function, got ' + fn);
+      }
+      plan = fn;
+      return getSpy();
+    };
+
+    /**
+     * Tell the spy to do nothing when invoked. This is the default.
+     * @name Spy#and#stub
+     * @function
+     */
+    this.stub = function(fn) {
+      plan = function() {};
+      return getSpy();
+    };
+  }
+
+  return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function(j$) {
+  function Suite(attrs) {
+    this.env = attrs.env;
+    this.id = attrs.id;
+    this.parentSuite = attrs.parentSuite;
+    this.description = attrs.description;
+    this.expectationFactory = attrs.expectationFactory;
+    this.expectationResultFactory = attrs.expectationResultFactory;
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    this.beforeFns = [];
+    this.afterFns = [];
+    this.beforeAllFns = [];
+    this.afterAllFns = [];
+
+    this.children = [];
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: []
+    };
+  }
+
+  Suite.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Suite.prototype.getFullName = function() {
+    var fullName = [];
+    for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) {
+      if (parentSuite.parentSuite) {
+        fullName.unshift(parentSuite.description);
+      }
+    }
+    return fullName.join(' ');
+  };
+
+  Suite.prototype.pend = function() {
+    this.markedPending = true;
+  };
+
+  Suite.prototype.beforeEach = function(fn) {
+    this.beforeFns.unshift(fn);
+  };
+
+  Suite.prototype.beforeAll = function(fn) {
+    this.beforeAllFns.push(fn);
+  };
+
+  Suite.prototype.afterEach = function(fn) {
+    this.afterFns.unshift(fn);
+  };
+
+  Suite.prototype.afterAll = function(fn) {
+    this.afterAllFns.unshift(fn);
+  };
+
+  Suite.prototype.addChild = function(child) {
+    this.children.push(child);
+  };
+
+  Suite.prototype.status = function() {
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'finished';
+    }
+  };
+
+  Suite.prototype.isExecutable = function() {
+    return !this.markedPending;
+  };
+
+  Suite.prototype.canBeReentered = function() {
+    return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
+  };
+
+  Suite.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Suite.prototype.sharedUserContext = function() {
+    if (!this.sharedContext) {
+      this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+    }
+
+    return this.sharedContext;
+  };
+
+  Suite.prototype.clonedSharedUserContext = function() {
+    return clone(this.sharedUserContext());
+  };
+
+  Suite.prototype.onException = function() {
+    if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    if(isAfterAll(this.children)) {
+      var data = {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        error: arguments[0]
+      };
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        child.onException.apply(child, arguments);
+      }
+    }
+  };
+
+  Suite.prototype.addExpectationResult = function () {
+    if(isAfterAll(this.children) && isFailure(arguments)){
+      var data = arguments[1];
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+      if(this.throwOnExpectationFailure) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        try {
+          child.addExpectationResult.apply(child, arguments);
+        } catch(e) {
+          // keep going
+        }
+      }
+    }
+  };
+
+  function isAfterAll(children) {
+    return children && children[0].result.status;
+  }
+
+  function isFailure(args) {
+    return !args[0];
+  }
+
+  function clone(obj) {
+    var clonedObj = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        clonedObj[prop] = obj[prop];
+      }
+    }
+
+    return clonedObj;
+  }
+
+  return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+  var defaultNow = (function(Date) {
+    return function() { return new Date().getTime(); };
+  })(Date);
+
+  function Timer(options) {
+    options = options || {};
+
+    var now = options.now || defaultNow,
+        startTime;
+
+    this.start = function() {
+      startTime = now();
+    };
+
+    this.elapsed = function() {
+      return now() - startTime;
+    };
+  }
+
+  return Timer;
+};
+
+getJasmineRequireObj().TreeProcessor = function() {
+  function TreeProcessor(attrs) {
+    var tree = attrs.tree,
+        runnableIds = attrs.runnableIds,
+        queueRunnerFactory = attrs.queueRunnerFactory,
+        nodeStart = attrs.nodeStart || function() {},
+        nodeComplete = attrs.nodeComplete || function() {},
+        orderChildren = attrs.orderChildren || function(node) { return node.children; },
+        stats = { valid: true },
+        processed = false,
+        defaultMin = Infinity,
+        defaultMax = 1 - Infinity;
+
+    this.processTree = function() {
+      processNode(tree, false);
+      processed = true;
+      return stats;
+    };
+
+    this.execute = function(done) {
+      if (!processed) {
+        this.processTree();
+      }
+
+      if (!stats.valid) {
+        throw 'invalid order';
+      }
+
+      var childFns = wrapChildren(tree, 0);
+
+      queueRunnerFactory({
+        queueableFns: childFns,
+        userContext: tree.sharedUserContext(),
+        onException: function() {
+          tree.onException.apply(tree, arguments);
+        },
+        onComplete: done
+      });
+    };
+
+    function runnableIndex(id) {
+      for (var i = 0; i < runnableIds.length; i++) {
+        if (runnableIds[i] === id) {
+          return i;
+        }
+      }
+    }
+
+    function processNode(node, parentEnabled) {
+      var executableIndex = runnableIndex(node.id);
+
+      if (executableIndex !== undefined) {
+        parentEnabled = true;
+      }
+
+      parentEnabled = parentEnabled && node.isExecutable();
+
+      if (!node.children) {
+        stats[node.id] = {
+          executable: parentEnabled && node.isExecutable(),
+          segments: [{
+            index: 0,
+            owner: node,
+            nodes: [node],
+            min: startingMin(executableIndex),
+            max: startingMax(executableIndex)
+          }]
+        };
+      } else {
+        var hasExecutableChild = false;
+
+        var orderedChildren = orderChildren(node);
+
+        for (var i = 0; i < orderedChildren.length; i++) {
+          var child = orderedChildren[i];
+
+          processNode(child, parentEnabled);
+
+          if (!stats.valid) {
+            return;
+          }
+
+          var childStats = stats[child.id];
+
+          hasExecutableChild = hasExecutableChild || childStats.executable;
+        }
+
+        stats[node.id] = {
+          executable: hasExecutableChild
+        };
+
+        segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
+
+        if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
+          stats = { valid: false };
+        }
+      }
+    }
+
+    function startingMin(executableIndex) {
+      return executableIndex === undefined ? defaultMin : executableIndex;
+    }
+
+    function startingMax(executableIndex) {
+      return executableIndex === undefined ? defaultMax : executableIndex;
+    }
+
+    function segmentChildren(node, orderedChildren, nodeStats, executableIndex) {
+      var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
+          result = [currentSegment],
+          lastMax = defaultMax,
+          orderedChildSegments = orderChildSegments(orderedChildren);
+
+      function isSegmentBoundary(minIndex) {
+        return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+      }
+
+      for (var i = 0; i < orderedChildSegments.length; i++) {
+        var childSegment = orderedChildSegments[i],
+            maxIndex = childSegment.max,
+            minIndex = childSegment.min;
+
+        if (isSegmentBoundary(minIndex)) {
+          currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
+          result.push(currentSegment);
+        }
+
+        currentSegment.nodes.push(childSegment);
+        currentSegment.min = Math.min(currentSegment.min, minIndex);
+        currentSegment.max = Math.max(currentSegment.max, maxIndex);
+        lastMax = maxIndex;
+      }
+
+      nodeStats.segments = result;
+    }
+
+    function orderChildSegments(children) {
+      var specifiedOrder = [],
+          unspecifiedOrder = [];
+
+      for (var i = 0; i < children.length; i++) {
+        var child = children[i],
+            segments = stats[child.id].segments;
+
+        for (var j = 0; j < segments.length; j++) {
+          var seg = segments[j];
+
+          if (seg.min === defaultMin) {
+            unspecifiedOrder.push(seg);
+          } else {
+            specifiedOrder.push(seg);
+          }
+        }
+      }
+
+      specifiedOrder.sort(function(a, b) {
+        return a.min - b.min;
+      });
+
+      return specifiedOrder.concat(unspecifiedOrder);
+    }
+
+    function executeNode(node, segmentNumber) {
+      if (node.children) {
+        return {
+          fn: function(done) {
+            nodeStart(node);
+
+            queueRunnerFactory({
+              onComplete: function() {
+                nodeComplete(node, node.getResult());
+                done();
+              },
+              queueableFns: wrapChildren(node, segmentNumber),
+              userContext: node.sharedUserContext(),
+              onException: function() {
+                node.onException.apply(node, arguments);
+              }
+            });
+          }
+        };
+      } else {
+        return {
+          fn: function(done) { node.execute(done, stats[node.id].executable); }
+        };
+      }
+    }
+
+    function wrapChildren(node, segmentNumber) {
+      var result = [],
+          segmentChildren = stats[node.id].segments[segmentNumber].nodes;
+
+      for (var i = 0; i < segmentChildren.length; i++) {
+        result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
+      }
+
+      if (!stats[node.id].executable) {
+        return result;
+      }
+
+      return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+    }
+  }
+
+  return TreeProcessor;
+};
+
+getJasmineRequireObj().version = function() {
+  return '2.6.1';
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/main.js b/plugins/easy_mindmup/assets/javascripts/jasmine/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..db2263744ba19e0eaf5cce95b909bd8b5a651ca6
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/main.js
@@ -0,0 +1,66 @@
+describe("Test framework", function () {
+  it("should load", function () {
+    expect(true).toBe(true);
+    // expect(false).toBe(true);
+  });
+
+  it("should start after mindmup is loaded", function () {
+    expect($("#node_1").length).toBe(1);
+  });
+  it("should handle long tests", function (done) {
+    setTimeout(function () {
+      expect(true).toBe(true);
+      done();
+    }, 100);
+  });
+  var getQueryString = function () {
+    var query_string = {};
+    var query = window.location.search.substring(1);
+    var vars = query.split("&");
+    for (var i = 0; i < vars.length; i++) {
+      var pair = vars[i].split("=");
+      // If first entry with this name
+      if (typeof query_string[pair[0]] === "undefined") {
+        query_string[pair[0]] = decodeURIComponent(pair[1]);
+        // If second entry with this name
+      } else if (typeof query_string[pair[0]] === "string") {
+        query_string[pair[0]] = [query_string[pair[0]], decodeURIComponent(pair[1])];
+        // If third or later entry with this name
+      } else {
+        query_string[pair[0]].push(decodeURIComponent(pair[1]));
+      }
+    }
+    return query_string;
+  };
+  var prefixTestFile = function (file) {
+    if (file.indexOf("/") > -1) return file;
+    return "easy_gantt/" + file;
+  };
+  it("should load extra tests if any", function () {
+    var params = getQueryString();
+    var requestedTests = params["run_jasmine_tests"] || params["run_jasmine_tests[]"] || params["run_jasmine_tests%5B%5D"];
+    if (requestedTests === "true") {
+      requestedTests = [];
+    } else if (typeof(requestedTests) === "string") {
+      requestedTests = [prefixTestFile(requestedTests)];
+    } else if (typeof(requestedTests) === "object" && requestedTests.length !== undefined) {
+      requestedTests = requestedTests.map(function (requestedTest) {
+        return prefixTestFile(requestedTest);
+      })
+    } else {
+      throw "Wrong type of run_jasmine_tests - \""+requestedTests+"\" is not true|string|Array<String>";
+    }
+    var extraTests = jasmine.ysyInstance.tests.extraTestNames;
+    if (requestedTests.length > extraTests.length) {
+      for (var i = 0; i < requestedTests.length; i++) {
+        expect(extraTests).toContain(requestedTests[i], "extraTests missing " + requestedTests[i]);
+      }
+      return;
+    }
+    expect(requestedTests.length).toBe(extraTests.length, "requested tests !== loaded tests");
+    for (i = 0; i < extraTests.length; i++) {
+      expect(requestedTests).toContain(extraTests[i]);
+    }
+
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/parse_form.js b/plugins/easy_mindmup/assets/javascripts/jasmine/parse_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..3693e5feff69d5e44521349cd41e447c489447f4
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/parse_form.js
@@ -0,0 +1,87 @@
+describe("parseForm", function () {
+  it("should convert FormArray to json", function () {
+    var formData = [{"name": "utf8", "value": "✓"}, {
+      "name": "_method",
+      "value": "patch"
+    }, {
+      "name": "authenticity_token",
+      "value": "AyUhWxdKyCnB5V5FgkMZecRtDucWOsFRAq+RmxhPjclTrjNy3VngNdHp5tiS+iVqOqp4+7PXyrJNrDAX2rWGmA=="
+    }, {"name": "form_update_triggered_by", "value": ""}, {
+      "name": "issue[subject]",
+      "value": "level 1d"
+    }, {"name": "issue[tracker_id]", "value": "15"}, {
+      "name": "issue[author_id]",
+      "value": "5"
+    }, {"name": "issue[fixed_version_id]", "value": ""}, {
+      "name": "issue[old_fixed_version_id]",
+      "value": ""
+    }, {"name": "issue[parent_issue_id]", "value": ""}, {
+      "name": "issue[parent_issue_id]",
+      "value": ""
+    }, {"name": "issue[start_date]", "value": "2016-12-08"}, {
+      "name": "issue[easy_repeat_settings][simple_period]",
+      "value": ""
+    }, {
+      "name": "issue[easy_repeat_settings][end_date]",
+      "value": ""
+    }, {
+      "name": "issue[easy_repeat_settings][endtype_count_x]",
+      "value": ""
+    }, {"name": "issue[custom_field_values][29][]", "value": "material2"}, {
+      "name": "issue[custom_field_values][29][]",
+      "value": "material4"
+    }, {"name": "issue[custom_field_values][29][]", "value": ""}, {
+      "name": "issue[custom_field_values][83]",
+      "value": ""
+    }, {"name": "issue[status_id]", "value": "1"}, {
+      "name": "issue[done_ratio]",
+      "value": "0"
+    }, {"name": "issue[priority_id]", "value": "9"}, {"name": "issue[due_date]", "value": ""}, {
+      "name": "issue[notes]",
+      "value": ""
+    }, {"name": "version[project_id]", "value": "118"}, {
+      "name": "issue[private_notes]",
+      "value": "0"
+    }, {"name": "issue[private_notes]", "value": "1"}, {
+      "name": "issue[update_repeat_entity_attributes]",
+      "value": "1"
+    }, {"name": "issue[lock_version]", "value": "4"}];
+    var json = {
+      "utf8": "✓",
+      "_method": "patch",
+      "authenticity_token": "AyUhWxdKyCnB5V5FgkMZecRtDucWOsFRAq+RmxhPjclTrjNy3VngNdHp5tiS+iVqOqp4+7PXyrJNrDAX2rWGmA==",
+      "form_update_triggered_by": "",
+      "issue": {
+        "subject": "level 1d",
+        "tracker_id": "15",
+        "author_id": "5",
+        "fixed_version_id": "",
+        "old_fixed_version_id": "",
+        "parent_issue_id": "",
+        "start_date": "2016-12-08",
+        "easy_repeat_settings": {
+          "simple_period": "",
+          "end_date": "",
+          "endtype_count_x": ""
+        },
+        "custom_field_values": {
+          "29": ["material2", "material4", ""],
+          "83": ""
+        },
+        "status_id": "1",
+        "done_ratio": "0",
+        "priority_id": "9",
+        "due_date": "",
+        "notes": "",
+        "private_notes": "1",
+        "update_repeat_entity_attributes": "1",
+        "lock_version": "4"
+      },
+      "version": {
+        "project_id": "118"
+      }
+    };
+    var result = jasmine.ysyInstance.util.formToJson(formData);
+    expect(result).toEqual(json);
+  });
+});
diff --git a/plugins/easy_mindmup/assets/javascripts/jasmine/saver_linearize.js b/plugins/easy_mindmup/assets/javascripts/jasmine/saver_linearize.js
new file mode 100644
index 0000000000000000000000000000000000000000..92c1073755bcbb35d4e6f8f6ab65568a27bb97ae
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jasmine/saver_linearize.js
@@ -0,0 +1,43 @@
+describe("Saver linearize", function () {
+  it("should return [] for only project", function () {
+    var ysy = jasmine.ysyInstance;
+    var project = new window.easyMindMupClasses.RootIdea(ysy);
+    project.fromServer(5, "Project A", "project", false, {id: 5});
+    var result = [];
+    ysy.saver.linearizeTree(project, null, null, result, true);
+    expect(result).toEqual([]);
+  });
+  it("should return 1 unsafe pack for 1 issue if unsafe", function () {
+    var ysy = jasmine.ysyInstance;
+    var project = new window.easyMindMupClasses.RootIdea(ysy);
+    project.fromServer(5, "Project A", "project", false, {id: 5});
+    var issue = new window.easyMindMupClasses.ModelEntity(ysy);
+    issue.fromServer(6, "Issue A", "issue", true, {
+      id: 6, project_id: 5,
+      subject: "Issue A"
+    });
+    project.ideas = {5: issue};
+    var result = [];
+    ysy.saver.linearizeTree(project, null, null, result, true);
+    expect(result.length).toEqual(1);
+    var pack = result[0];
+    expect(pack.node).toBe(issue);
+    expect(pack.parent).toBe(project);
+    expect(pack.isSame).toBe(false);
+    expect(pack.isSafe).toBe(false);
+  });
+  it("should return 0 packs for 1 unchanged issue if safe", function () {
+    var ysy = jasmine.ysyInstance;
+    var project = new window.easyMindMupClasses.RootIdea(ysy);
+    project.fromServer(5, "Project A", "project", false, {id: 5});
+    var issue = new window.easyMindMupClasses.ModelEntity(ysy);
+    issue.fromServer(6, "Issue A", "issue", true, {
+      id: 6, project_id: 5,
+      subject: "Issue A"
+    });
+    project.ideas = {5: issue};
+    var result = [];
+    ysy.saver.linearizeTree(project, null, null, result, false);
+    expect(result.length).toEqual(0);
+  });
+});
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/jsdocs_external.js b/plugins/easy_mindmup/assets/javascripts/jsdocs_external.js
new file mode 100644
index 0000000000000000000000000000000000000000..53b22355153016cc56231d6c559954943fca8cca
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/jsdocs_external.js
@@ -0,0 +1,27 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  /**
+   * Do not include this file - it is just for suppressing JSDocs warnings
+   */
+  window.showModal =function(){};
+  jQuery.fn.dialog=function(){};
+  /**
+   *
+   * @param {String} type
+   * @param {String} message
+   * @param {number} delay
+   */
+  window.showFlashMessage=function(type, message, delay){};
+  /**
+   *
+   * @param {String} htmlId
+   */
+  window.fillFormTextAreaFromCKEditor = function(htmlId){};
+
+  jQuery.Deferred.prototype.done = function (func) {
+
+  };
+  window._ = window._ || {};
+});
diff --git a/plugins/easy_mindmup/assets/javascripts/layout_patch.js b/plugins/easy_mindmup/assets/javascripts/layout_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f694f0a4cda951ad440ad3c83154b4d6d4d6bb3
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/layout_patch.js
@@ -0,0 +1,74 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function LayoutPatch(ysy) {
+    this.nodeCacheMarks = {};
+    this.ysy = ysy;
+    this.patch(ysy);
+  }
+
+  LayoutPatch.prototype.patch = function (ysy) {
+    var self = this;
+    self.layoutCalculator = /** @param {RootIdea} contentAggregate */function (contentAggregate) {
+      self.preComputeDimensions(contentAggregate, self, ysy);
+      return MAPJS.calculateLayout(contentAggregate, function (idea) {
+        return self.nodeCacheMarks[idea.id]
+      });
+    };
+    jQuery.fn.queueFadeOut = function (options) {
+      var element = this;
+      return element.animate({opacity: 0}, _.extend({
+        complete: function () {
+          element.remove();
+        }
+      }, options));
+    }
+  };
+  /**
+   *
+   * @param {RootIdea} superIdea
+   * @param {LayoutPatch} self
+   * @param {MindMup} ysy
+   */
+  LayoutPatch.prototype.preComputeDimensions = function (superIdea, self, ysy) {
+    var nodeCacheMarks = self.nodeCacheMarks;
+    var nodes = [];
+    ysy.util.traverse(superIdea, function (idea) {
+      if (!idea.attr || !idea.attr.entityType) {
+        ysy.upgradeToModelEntity(idea);
+      }
+      if (nodeCacheMarks[idea.id]) {
+        if (nodeCacheMarks[idea.id].title === idea.title && nodeCacheMarks[idea.id].collapsed === idea.attr.collapsed) return;
+      }
+      nodes.push(idea);
+    });
+    var translateToPixel = function () {
+      return MAPJS.DOMRender.svgPixel;
+    };
+    var bigHtml = '<div id="dimension_compute_cont">';
+    for (var i = 0; i < nodes.length; i++) {
+      var idea = nodes[i];
+      var text = ysy.util.escapeHtml(ysy.nodePatch.getNodeText(idea));
+      bigHtml += '<div id="compute_node_' + idea.id + '" class="mapjs-node" style="visibility: hidden;position: absolute"><span>' + text + '</span></div>'
+    }
+    bigHtml += '</div>';
+    $(bigHtml).appendTo('body');
+
+    for (i = 0; i < nodes.length; i++) {
+      idea = nodes[i];
+      var textBox = $("#compute_node_" + idea.id);
+      nodeCacheMarks[idea.id] = {
+        title: idea.title,
+        collapsed: idea.attr.collapsed,
+        width: textBox.outerWidth(true),
+        height: textBox.outerHeight(true)
+      };
+      // textBox.detach();
+    }
+    $("#dimension_compute_cont").remove();
+  };
+  window.easyMindMupClasses.LayoutPatch = LayoutPatch;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/legend.js b/plugins/easy_mindmup/assets/javascripts/legend.js
new file mode 100644
index 0000000000000000000000000000000000000000..64018a415512041d34e6bcd4a74a151b4aff2fd6
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/legend.js
@@ -0,0 +1,220 @@
+(function () {
+  /**
+   *
+   * @property {jQuery} $element
+   * @property {jQuery} $titleElement
+   * @property {Array.<String>} otherBuilders
+   * @property {MindMup} ysy
+   * @property {LegendBuilders} itemBuilders
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Legend(ysy) {
+    this.opened = true;
+    this.headerHidden = false;
+    this.lastHeaderHidden = true;
+    this.otherBuilders = [
+      "project"
+    ];
+    this.usedActive = false;
+    this.ysy = ysy;
+    this.itemBuilders = new LegendBuilders(ysy);
+    this.init(ysy);
+  }
+
+  /**
+   * @param {MindMup} ysy
+   */
+  Legend.prototype.init = function (ysy) {
+    var self = this;
+    var $menu = ysy.$menu;
+    ysy.eventBus.register("TreeLoaded", function (idea) {
+      idea.addEventListener('changed', function () {
+        self.draw();
+      });
+      self.draw();
+    });
+    ysy.eventBus.register("nodeStyleChanged", $.proxy(this.draw, this));
+    this.$container = $menu.find(".mindmup__legend-container");
+    this.$element = $menu.find(".mindmup-legend");
+    this.$header = $menu.find(".mindmup__legend-header");
+    this.$usedToggle = this.$header.find(".mindmup__legend-used-toggle");
+    this.$openToggle = this.$header.find(".mindmup__legend-toggler");
+    this.opened = !ysy.storage.settings.loadLegendHidden();
+    this.$headerToggle = $menu.find(".mindmup__legend-cont-toggler");
+    this.opened = !ysy.storage.settings.loadLegendHidden();
+    this.headerHidden = ysy.storage.settings.loadLegendHeaderHidden();
+    this.lastHeaderHidden = !this.headerHidden;
+  };
+  Legend.prototype.headerToggle = function () {
+    this.headerHidden = !this.headerHidden;
+    this.ysy.repainter.redrawMe(this);
+    this.ysy.eventBus.fireEvent("legendHeaderToggled", this.headerHidden);
+  };
+  Legend.prototype.toggle = function () {
+    this.opened = !this.opened;
+    this.ysy.repainter.redrawMe(this);
+    this.ysy.eventBus.fireEvent("legendToggled", this.opened);
+  };
+  /** @param {Style} style */
+  Legend.prototype.getItemBuilder = function (style) {
+    return this.itemBuilders[style.builderType];
+  };
+  Legend.prototype.hotkeysBuilder = function ($element) {
+    var ysy = this.ysy;
+    $element.find(".hotkey_link").click(function () {
+      var modal = ysy.util.getModal("info-modal", "90%");
+      modal.html(ysy.$container.find(".mindmup-hotkeys-source").html());
+      showModal("info-modal");
+      modal.dialog({
+        buttons: [
+          {
+            class: "button-2 button",
+            text: ysy.settings.labels.buttons.close,
+            click: function () {
+              modal.dialog("close")
+            }
+          }
+        ]
+      });
+    });
+  };
+  Legend.prototype.draw = function () {
+    this.ysy.repainter.redrawMe(this);
+  };
+  Legend.prototype._render = function () {
+    var ysy = this.ysy;
+    var self = this;
+    if (this.headerHidden) {
+      if (!this.lastHeaderHidden) {
+        this.$container.addClass("mindmup__legend-container--hidden");
+        this.$headerToggle
+            .toggleClass("active", false)
+            .find("a").toggleClass("active", false);
+        this.lastHeaderHidden = true;
+      }
+      return;
+    } else {
+      if (this.lastHeaderHidden) {
+        this.$container.removeClass("mindmup__legend-container--hidden");
+        this.$headerToggle
+            .find("a").toggleClass("active", true);
+        this.lastHeaderHidden = false;
+      }
+    }
+    this.$openToggle.toggleClass("active", this.opened);
+    if (!this.opened) {
+      this.$element.hide();
+      return;
+    }
+    this.resize();
+    this.$openToggle.toggleClass("active", this.opened);
+    var $element = this.$element.show();
+    var setting = ysy.styles.setting;
+    var store = ysy.styles.getCurrentStyle();
+    var itemElement;
+    var array = store.options(this.usedActive);
+    var itemBuilder = this.getItemBuilder(store) || _.noop;
+    var obj = {
+      active: this.usedActive ? "used" : "all",
+      filter: ysy.filter.isOn()
+    };
+    var items = [];
+    for (var i = 0; i < this.otherBuilders.length; i++) {
+      var builder = this.itemBuilders[this.otherBuilders[i]];
+      if (!builder) continue;
+      itemElement = builder.call(this.itemBuilders);
+      if (itemElement) items.push(itemElement);
+    }
+    obj.items = items;
+    for (i = 0; i < array.length; i++) {
+      itemElement = itemBuilder.call(this.itemBuilders, array[i], setting);
+      items.push(itemElement);
+    }
+    $element.html(Mustache.render(ysy.settings.templates.legendTemplate, obj));
+    $element.find(".mindmup-legend__filter_cont").click(function () {
+      ysy.filter.reset();
+      self.draw();
+    });
+    this.hotkeysBuilder($element);
+  };
+  Legend.prototype.resize = function () {
+    if (!this.opened) return;
+    var height = window.innerHeight
+        || document.documentElement.clientHeight
+        || document.body.clientHeight;
+    var offset = this.$element.offset().top;
+    var scroll = $(document).scrollTop();
+    this.$element.css("max-height", (height + scroll - offset - 25) + "px");
+  };
+  window.easyMindMupClasses.Legend = Legend;
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @property {Filter} filter
+   * @property {Object.<String,Style>} styles
+   * @property {String} projectLabel
+   * @constructor
+   */
+  function LegendBuilders(ysy) {
+    this.ysy = ysy;
+    this.filter = ysy.filter;
+    this.styles = ysy.styles.styles;
+    this.projectLabel = ysy.settings.labels.types.project;
+  }
+
+  LegendBuilders.prototype.assigneeTemplate = '\
+          <div data-item_id="{{item.id}}" class="mindmup-legend-item-cont">\
+            <div class="mindmup-legend-color-box{{banned}}{{scheme}}"></div>\
+            {{#avatar}}\
+             <span class="avatar-container">\
+              <img width="64" height="64" alt="{{item.name}}" class="gravatar" src="{{{avatarUrl}}}">\
+              </span>\
+            {{/avatar}}\
+            {{item.name}}\
+          </div>';
+  LegendBuilders.prototype.percentTemplate = '<div data-item_id="{{percent}}" class="mindmup-legend-item-cont">\
+            <div class="mindmup-legend-color-box{{banned}}{{scheme}}"></div>\
+            {{percent}} %\
+          </div>';
+  LegendBuilders.prototype.dataBasedTemplate = '<div data-item_id="{{item.id}}" class="mindmup-legend-item-cont">\
+            <div class="mindmup-legend-color-box{{banned}}{{scheme}}"></div>\
+            {{item.name}}\
+          </div>';
+  LegendBuilders.prototype.projectTemplate = '<div data-item_id="project" class="mindmup-legend-item-cont">\
+            <div class="mindmup-legend-color-box mindmup-scheme-project{{banned}}"></div>\
+            {{label}}\
+            </div>';
+
+  LegendBuilders.prototype.assignee = function assigneeBuilder(item, type) {
+    return Mustache.render(this.assigneeTemplate, {
+      item: item,
+      banned: this.filter.cssByBannedValue(item.id),
+      scheme: this.styles[type].addSchemeClass(item.id),
+      avatar: item.id !== 0,
+      avatarUrl: item.avatar_url ? item.avatar_url : "/plugin_assets/easy_extensions/images/avatar.jpg"
+    });
+  };
+  LegendBuilders.prototype.percent = function percentBuilder(item, type) {
+    return Mustache.render(this.percentTemplate, {
+      percent: item,
+      banned: this.filter.cssByBannedValue(item),
+      scheme: this.styles[type].addSchemeClass(item)
+    });
+  };
+  LegendBuilders.prototype.dataBased = function dataBasedBuilder(item, type) {
+    return Mustache.render(this.dataBasedTemplate, {
+      item: item,
+      banned: this.filter.cssByBannedValue(item.id),
+      scheme: this.styles[type].addSchemeClass(item.id)
+    });
+  };
+  LegendBuilders.prototype.project = function projectBuilder() {
+    return Mustache.render(this.projectTemplate, {
+      label: this.projectLabel,
+      banned: this.filter.cssByBannedValue("project")
+    });
+  };
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/legend_events.js b/plugins/easy_mindmup/assets/javascripts/legend_events.js
new file mode 100644
index 0000000000000000000000000000000000000000..11ef9c1c7574352705401a1912245873fbdd08ea
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/legend_events.js
@@ -0,0 +1,302 @@
+/**
+ * Created by hosekp on 12/1/16.
+ */
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @property {jQuery} $element
+   * @property {Legend} legend
+   * @constructor
+   */
+  function LegendEvents(ysy) {
+    this.legend = ysy.legends;
+    this.$element = this.legend.$element;
+    this.ysy = ysy;
+    this.init(ysy);
+    this.possibleTargets = [];
+  }
+
+  LegendEvents.prototype.maxDistanceToClick = 30;
+  LegendEvents.prototype.domain = "easy-mindmup-legend";
+  LegendEvents.prototype.draggedSelector = ".mindmup-legend-item-cont:not([data-item_id='project'])";
+  LegendEvents.prototype.filterSelector = ".mindmup-legend-item-cont[data-item_id='project']";
+  LegendEvents.prototype.usedToggleSelector = ".mindmup-legend-used";
+  LegendEvents.prototype.legendHeaderTogglerSelector = ".mindmup__legend-cont-toggler";
+  LegendEvents.prototype.hoverClass = "mindmup-legend-drag-hover droppable";
+
+  /**
+   * @param {MindMup} ysy
+   */
+  LegendEvents.prototype.init = function (ysy) {
+    var _self = this;
+    var legend = this.legend;
+    ysy.$menu.find(".mindmup__legend-trigger").on("click", function (e) {
+      legend.toggle();
+    });
+    ysy.$menu.find(this.legendHeaderTogglerSelector).on("click", function () {
+      legend.headerToggle();
+    });
+    this.$element
+        .off("click.legend-used")
+        .on("click.legend-used", this.usedToggleSelector, function () {
+          legend.usedActive = !legend.usedActive;
+          ysy.repainter.redrawMe(legend);
+        })
+        .off("mousedown." + this.domain)
+        .on("mousedown." + this.domain, this.draggedSelector, function (e) {
+          _self.shiftKey = e.shiftKey;
+          if (e.target.parentElement.tagName === "A") {
+            _self.aTouched = true;
+          }
+          e.preventDefault();
+          if (e.which === 3) {
+            return false;
+            // _self.actualX = e.pageX - window.scrollX;
+            // _self.actualY = e.pageY - window.scrollY;
+            // _self._contextMenu();
+          } else {
+            _self.actualX = e.pageX - window.scrollX;
+            _self.actualY = e.pageY - window.scrollY;
+            _self.down($(this), e.pageX, e.pageY);
+          }
+          return false;
+        })
+        .off("touchstart." + this.domain)
+        .on("touchstart." + this.domain, this.draggedSelector, function (e) {
+          e.preventDefault();
+          var touch = e.originalEvent.touches[0];
+          if (!touch) return false;
+          _self.actualX = touch.pageX - window.scrollX;
+          _self.actualY = touch.pageY - window.scrollY;
+          _self.down($(this), touch.pageX, touch.pageY);
+          return false;
+        })
+        .off("touchend." + this.domain)
+        .on("touchend." + this.domain, function (e) {
+          e.preventDefault();
+          _self.up();
+          return false;
+
+        })
+        .off("touchmove." + this.domain)
+        .on("touchmove." + this.domain, this.draggedSelector, function (e) {
+          e.preventDefault();
+          var touch = e.originalEvent.touches[0];
+          if (!touch) return false;
+          _self.move(touch.pageX, touch.pageY);
+          return false;
+        })
+        .off("click." + this.domain)
+        .on("click." + this.domain, this.filterSelector, function () {
+          var id = $(this).data("item_id");
+          ysy.filter.toggleAllowed(id);
+          ysy.repainter.redrawMe(ysy.legends);
+        });
+    ysy.eventBus.register('resize', $.proxy(legend.resize, legend));
+  };
+  /**
+   * main mouseDown function (handles also click events)
+   * @param {jQuery} $sourceElement
+   * @param {number} x
+   * @param {number} y
+   */
+  LegendEvents.prototype.down = function ($sourceElement, x, y) {
+    this.maxDistance = 0;
+    var $stageElement = this.ysy.$container.children("[data-mapjs-role=\"stage\"]");
+    this.stageData = $stageElement.data();
+    this.stageOffset = $stageElement.offset();
+    this.startX = x - window.scrollX;
+    this.startY = y - window.scrollY;
+    // this.startTime = Date.now();
+    this.actionActive = true;
+    var _self = this;
+    this.$sourceElement = $sourceElement;   // window.easyView.root.dragStartOnDomain(this.domain, $draggedElement);
+
+    // create overlay div
+    this.$overlay = $('<div class="mindmup-legend-drag-overlay">').css({
+      width: window.innerWidth,
+      height: window.innerHeight
+    });
+    var colorClass = this.$element[0].className.replace("mindmup-legend ", "");
+    this.$overlay.addClass(colorClass);
+    $(document.body).append(this.$overlay);
+
+
+    this.$overlay.on("mouseup." + this.domain, function (e) {
+      e.preventDefault();
+      _self.up();
+
+    });
+    this.$overlay.on("mousemove." + this.domain, function (e) {
+      e.preventDefault();
+      _self.move(e.pageX, e.pageY);
+    });
+  };
+  /**
+   * main mouseMove function
+   * @param {number} x
+   * @param {number} y
+   */
+  LegendEvents.prototype.move = function (x, y) {
+    if (!this.actionActive) return;
+
+    // if ((y - window.scrollY) < 80) {
+    //   this.doScrollUp = true;
+    //   this.doScroll();
+    // } else {
+    //   this.doScrollUp = false;
+    // }
+    //
+    // if ((window.innerHeight - y + window.scrollY) < 80) {
+    //   this.doScrollDown = true;
+    //   this.doScroll();
+    // } else {
+    //   this.doScrollDown = false;
+    // }
+
+    this.actualX = x - window.scrollX;
+    this.actualY = y - window.scrollY;
+    if (this.moveDistance() > this.maxDistanceToClick && !this.avatar) {
+      this.avatar = new DragAvatar(this.ysy, this.$sourceElement);
+      this.changeObject = this.avatar.getChangeObject();
+      if (!this.changeObject) return this.up();
+      this.possibleTargets = this.findPossibleTargets();
+      this.$overlay.append(this.avatar.$cont);
+    }
+    if (this.avatar) {
+      this.avatar.moveAvatar(this.actualX, this.actualY);
+      var oldDropTarget = this.currentDropTarget;
+      this.currentDropTarget = this.getCurrentTarget(x, y);
+      if (this.currentDropTarget !== oldDropTarget) {
+        if (oldDropTarget) {
+          oldDropTarget.removeClass(this.hoverClass);
+        }
+        if (this.currentDropTarget) {
+          this.currentDropTarget.addClass(this.hoverClass);
+        }
+      }
+    }
+  };
+  /**
+   * main mouseUp function
+   */
+  LegendEvents.prototype.up = function () {
+    // this.doScrollDown = false;
+    // this.doScrollUp = false;
+    this.possibleTargets = null;
+    this.actionActive = false;
+
+    if (this.moveDistance() < this.maxDistanceToClick) {
+      var id = this.$sourceElement.data("item_id");
+      this.ysy.filter.toggleAllowed(id);
+      this.ysy.repainter.redrawMe(this.legend);
+    } else {
+      this._drop();
+    }
+    if (this.$overlay) {
+      if (this.avatar) {
+        this.avatar.destroy();
+      }
+      this.avatar = null;
+      this.$overlay.remove();
+    }
+    if (this.currentDropTarget) {
+      this.currentDropTarget.removeClass(this.hoverClass);
+    }
+    this.currentDropTarget = null;
+  };
+  /**
+   *
+   * @return {Array.<jQuery>}
+   */
+  LegendEvents.prototype.findPossibleTargets = function () {
+    var possibles = this.ysy.$container.find(".mapjs-node");
+    var $possibles = [];
+    for (var i = 0; i < possibles.length; i++) {
+      $possibles.push($(possibles[i]));
+    }
+    return $possibles;
+  };
+  /**
+   *
+   * @param {number} x
+   * @param {number} y
+   * @return {jQuery}
+   */
+  LegendEvents.prototype.getCurrentTarget = function (x, y) {
+    var $possibles = this.possibleTargets;
+    for (var i = 0; i < $possibles.length; i++) {
+      var $possible = $possibles[i];
+      var data = $possible.data();
+      var transformedX = (x - this.stageOffset.left) / this.stageData.scale;
+      if (transformedX < data.x) continue;
+      if (transformedX > data.x + data.width) continue;
+      var transformedY = (y - this.stageOffset.top) / this.stageData.scale;
+      if (transformedY < data.y) continue;
+      if (transformedY > data.y + data.height) continue;
+      return $possible;
+    }
+    return null;
+  };
+  /**
+   * run at the end of drag event
+   * @private
+   */
+  LegendEvents.prototype._drop = function () {
+    if (!this.currentDropTarget) return;
+    var ysy = this.ysy;
+    var idea = ysy.mapModel.findIdeaById(this.currentDropTarget.attr("id").split("_")[1]);
+    if (ysy.setData(idea, this.changeObject)) {
+      ysy.mapModel.selectNode(idea.id);
+      ysy.idea.dispatchEvent('changed');
+    }
+  };
+  LegendEvents.prototype.moveDistance = function () {
+    if (this.actualX == null) {
+      return 0;
+    }
+    var distance = Math.sqrt(Math.pow(this.actualX - this.startX, 2) + Math.pow(this.actualY - this.startY, 2));
+    if (distance > this.maxDistance) {
+      this.maxDistance = distance;
+    }
+    return distance;
+  };
+
+  window.easyMindMupClasses.LegendEvents = LegendEvents;
+
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $source - element with class ".mindmup-legend-item-cont", where dragging started
+   * @constructor
+   */
+  function DragAvatar(ysy, $source) {
+    this.ysy = ysy;
+    this.init($source);
+    this.legendItemId = $source.data("item_id");
+  }
+
+  DragAvatar.prototype.init = function ($source) {
+    var $boxElement = $source.children(".mindmup-legend-color-box");
+    var colorClass = $boxElement[0].className;//.replace("mindmup-legend-color-box ","");
+    this.$cont = $('<div class="mindmup-legend-drag-avatar ' + colorClass + '"></div>');
+  };
+  /**
+   * moves avatar to chosen coordinates
+   * @param {number} x
+   * @param {number} y
+   */
+  DragAvatar.prototype.moveAvatar = function (x, y) {
+    this.$cont.css({left: x, top: y});
+  };
+  DragAvatar.prototype.destroy = function () {
+    this.$cont.remove();
+  };
+  DragAvatar.prototype.getChangeObject = function () {
+    var store = this.ysy.styles.getCurrentStyle();
+    return store.changeObject(this.legendItemId);
+  };
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/link_edit_widget.js b/plugins/easy_mindmup/assets/javascripts/link_edit_widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..75fea73a37c87d522a8623651b118283b1e777f6
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/link_edit_widget.js
@@ -0,0 +1,70 @@
+(function () {
+  /**
+   * Class for handle of link edit modal
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function LinkEdit(ysy) {
+    this.ysy = ysy;
+    this.init(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  LinkEdit.prototype.init = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("MapInited", function (mapModel) {
+      self._prepareWidget(mapModel);
+    });
+  };
+  /** @private */
+  LinkEdit.prototype._prepareWidget = function (mapModel) {
+    /** @type {MindMup} */
+    var ysy = this.ysy;
+    /** @type {jQuery} */
+    var element = ysy.$container.find('.link-edit-widget');//.linkEditWidget(mapModel);
+    element.hide();
+    var currentLink, width, height;//, colorElement, lineStyleElement, arrowElement;
+    var typeElement;
+    // colorElement = element.find('.color');
+    // lineStyleElement = element.find('.lineStyle');
+    // arrowElement = element.find('.arrow');
+    typeElement = element.find('.link-edit-type-actual');
+    mapModel.addEventListener('linkSelected', function (link, selectionPoint) {
+      currentLink = link;
+      typeElement.text(ysy.settings.labels.links[link.attr.data.type]);
+      element.show();
+      var cont_off = ysy.$container.offset();
+      width = width || element.width();
+      height = height || element.height();
+      element.css({
+        top: (selectionPoint.y - cont_off.top - 5) + 'px',
+        left: (selectionPoint.x - cont_off.left - 5) + 'px'
+      });
+      // colorElement.val(linkStyle.color).change();
+      // lineStyleElement.val(linkStyle.lineStyle);
+      // arrowElement[linkStyle.arrow ? 'addClass' : 'removeClass']('active');
+    });
+    mapModel.addEventListener('mapMoveRequested', function () {
+      element.hide();
+    });
+    element.find('.delete').click(function () {
+      mapModel.removeLink('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo);
+      element.hide();
+    });
+    // colorElement.change(function () {
+    //   mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'color', jQuery(this).val());
+    // });
+    // lineStyleElement.find('a').click(function () {
+    //   mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'lineStyle', jQuery(this).text());
+    // });
+    // arrowElement.click(function () {
+    //   mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'arrow', !arrowElement.hasClass('active'));
+    // });
+    element.mouseleave(element.hide.bind(element));
+  };
+
+  window.easyMindMupClasses.LinkEdit = LinkEdit;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/links.js b/plugins/easy_mindmup/assets/javascripts/links.js
new file mode 100644
index 0000000000000000000000000000000000000000..953c7ff7835f5ec8c1a4ea83eb88c1090ed55468
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/links.js
@@ -0,0 +1,198 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Links(ysy) {
+    this.ysy = ysy;
+    this.linkArray=[];
+    this.disabled = false;
+    this.showLinks = false;
+  }
+
+  Links.prototype.commonLinkStyle = {
+    "color": "#0000FF",
+    "lineStyle": "dashed",
+    "arrow": false
+  };
+  Links.prototype.precedesLinkStyle = {
+    "color": "#00FF00",
+    "lineStyle": "solid",
+    "arrow": true
+  };
+  /**
+   *
+   * @param data
+   * @param {Array} convertedEntities
+   * @return {Array}
+   */
+  Links.prototype.convertRelations = function (data, convertedEntities) {
+    if (!data.relations){
+      this.disabled=true;
+      return null;
+    }
+    var relations = data.relations;
+    var issueMap = {};
+    for (var i = 1; i < convertedEntities.length; i++) {
+      var entity = convertedEntities[i];
+      if (entity.attr.entityType!=="issue") continue;
+      issueMap[entity.attr.data.id] = entity;
+    }
+    var links = [];
+    for (i = 0; i < relations.length; i++) {
+      var relation = relations[i];
+      var source = issueMap[relation.source_id];
+      var target = issueMap[relation.target_id];
+      if (!source || !target) continue;
+      var style = this.getLinkStyleFromType(relation.type);
+      var link = {
+        "ideaIdFrom": source.id,
+        "ideaIdTo": target.id,
+        "attr": {
+          "style": style,
+          "data": relation
+        }
+      };
+      links.push(link);
+    }
+    return links;
+  };
+  Links.prototype.getLinkStyleFromType = function (type) {
+    var style = this.commonLinkStyle;
+    if (type === "precedes" || type === "start_to_start" || type === "finish_to_finish" || type === "start_to_finish") {
+      style = this.precedesLinkStyle;
+    }
+    return $.extend({}, style);
+  };
+  /**
+   *
+   * @param {RootIdea} idea
+   * @param {Array} links
+   */
+  Links.prototype.attachLinks = function (idea, links) {
+    if(this.disabled) return;
+    this.linkArray = links;
+    idea.links = null;
+    if (this.showLinks)
+      idea.links = links;
+  };
+
+
+  Links.prototype.outerPath = function (parent, child) {
+    'use strict';
+    var xControl = 125;
+    var leftRightXControl = 250;
+    var yControl = 75;
+    var parentIsLeft = parent.left < 0;
+    var childIsLeft = child.left < 0;
+
+    var position = {
+      left: Math.min(parent.left, child.left),
+      top: Math.min(parent.top, child.top)
+    };
+    position.width = Math.max(parent.left + parent.width, child.left + child.width, position.left + 1) - position.left;
+    position.height = Math.max(parent.top + parent.height, child.top + child.height, position.top + 1) - position.top;
+
+    var parentMount = {
+      x: parent.left - position.left + (parentIsLeft ? 0 : parent.width),
+      y: parent.top - position.top + 0.5 * parent.height
+    };
+    var childMount = {
+      x: child.left - position.left + (childIsLeft ? 0 : child.width),
+      y: child.top - position.top + 0.5 * child.height
+    };
+
+    if (parentIsLeft === childIsLeft && Math.abs(parentMount.y - childMount.y) > 50) {
+      // simpler quadratic bezier
+      var commonControl = {
+        y: (parentMount.y + childMount.y) / 2,
+        x: (Math.max(Math.abs(parentMount.x), Math.abs(childMount.x)) + xControl) * (parentIsLeft ? -1 : 1)
+      };
+      return {
+        'd': 'M' + Math.round(parentMount.x) + ',' + Math.round(parentMount.y)
+        + 'Q' + Math.round(commonControl.x) + ',' + Math.round(commonControl.y) + ',' + Math.round(childMount.x) + ',' + Math.round(childMount.y),
+        'conn': {
+          from: {x: commonControl.x + position.left, y: commonControl.y + position.top},
+          to: {x: childMount.x + position.left, y: childMount.y + position.top}
+        },
+        'position': position
+      }
+    }
+    if (parentIsLeft !== childIsLeft) {
+      xControl = leftRightXControl;
+    }
+
+    var parentControl = {
+      x: parentMount.x + xControl * (parentIsLeft ? -1 : 1)
+    };
+    var childControl = {
+      x: childMount.x + xControl * (childIsLeft ? -1 : 1)
+    };
+    if (Math.abs(parentMount.y - childMount.y) < 50) {
+      parentControl.y = parentMount.y + yControl * (parentMount.y > -position.top ? 1 : -1);
+      childControl.y = childMount.y + yControl * (childMount.y > -position.top ? 1 : -1);
+    } else {
+      var diffY = childMount.y - parentMount.y;
+      parentControl.y = parentMount.y + diffY / 4;
+      childControl.y = childMount.y - diffY / 4;
+    }
+    return {
+      'd': 'M' + Math.round(parentMount.x) + ',' + Math.round(parentMount.y)
+      + 'C' + Math.round(parentControl.x) + ',' + Math.round(parentControl.y) + ',' + Math.round(childControl.x) + ',' + Math.round(childControl.y) + ',' + Math.round(childMount.x) + ',' + Math.round(childMount.y),
+      'conn': {
+        from: {x: childControl.x + position.left, y: childControl.y + position.top},
+        to: {x: childMount.x + position.left, y: childMount.y + position.top}
+      },
+      'position': position
+    }
+  };
+  window.easyMindMupClasses.Links = Links;
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function ShowLinksButton(ysy, $parent) {
+    this.ysy = ysy;
+    this.init(ysy, $parent);
+  }
+
+  ShowLinksButton.prototype.id = "ShowLinksButton";
+
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @return {ShowLinksButton}
+   */
+  ShowLinksButton.prototype.init = function (ysy, $parent) {
+    this.$element = $parent.find(".show-links-toggler");
+    /** @type {Links} */
+    var linkClass = this.ysy.links;
+    this.$element.click($.proxy(function () {
+      linkClass.showLinks = !linkClass.showLinks;
+      if (linkClass.showLinks) {
+        ysy.idea.links = ysy.links.linkArray;
+      } else {
+        delete ysy.idea.links;
+      }
+      ysy.idea.dispatchEvent('changed');
+      ysy.repainter.redrawMe(this);
+    }, this));
+    return this;
+  };
+  ShowLinksButton.prototype._render = function () {
+    /** @type {Links} */
+    var linkClass = this.ysy.links;
+    if(linkClass.disabled){
+      this.$element.hide();
+    }
+    var isActive = linkClass.showLinks || false;
+    this.$element.find("a").toggleClass("active", isActive);
+  };
+
+  window.easyMindMupClasses.ShowLinksButton = ShowLinksButton;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/loader.js b/plugins/easy_mindmup/assets/javascripts/loader.js
new file mode 100644
index 0000000000000000000000000000000000000000..0920166af5241e67276e4eb8d788a9de140b4bec
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/loader.js
@@ -0,0 +1,441 @@
+(function () {
+  /**
+   * Class responsible for loading data from database and generating tree
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Loader(ysy) {
+    this.ysy = ysy;
+    // this.lastStateChecker = this.lastStateChecker || new window.easyMindMupClasses.LastStateChecker(ysy);
+    /**
+     * mindMup ID of rootNode - usually 1
+     * @type {number}
+     * @private
+     */
+    this._rootId = 1;
+  }
+
+  /**
+   * main load function
+   * beware - it is not sync or callback capable. Use "AfterLoad" event
+   * @return {Loader}
+   */
+  Loader.prototype.load = function () {
+    // var self = this;
+    this.ysy.storage.clear();
+    if (this.ysy.idea) {
+      this.ysy.idea.resetHistory();
+    }
+    // var last = this.ysy.storage.lastState.getSavedIdea();
+    // if (last) {
+    //   var storedDeferred = this.openStoredModal(last);
+    // }
+    $.getJSON(this.ysy.settings.paths.data, $.proxy(this._handleData, this));
+    // $.getJSON(this.ysy.settings.paths.data, function (rawData) {
+    // if (storedDeferred) {
+    //   storedDeferred.done(function (type) {
+    //     if (type === "server") {
+    //       self._handleData(rawData);
+    //     } else {
+    //       self.ysy.storage.clear();
+    //       var data = self.extractData(rawData);
+    //       self.loadSideData(data);
+    //       var idea = MAPJS.content(last);
+    //       self.ysy.storage.extra.save(idea);
+    //       self.setIdea(idea);
+    //     }
+    //   });
+    //   return;
+    // }
+    // self._handleData(rawData);
+    // });
+    return this;
+  };
+  /**
+   * main data processor
+   * @param {Object} rawData
+   * @return {*}
+   */
+  Loader.prototype._handleData = function (rawData) {
+    var data = this.extractData(rawData);
+    // this.sourceData = data;
+    this.loadSideData(data);
+    if (this.ysy.idea) {
+      return this._updateIdeaByData(this.ysy.idea, data);
+    }
+    var convertedData = this.convertData(data);
+    this.ysy.storage.extra.setLayout(data["layout"]);  // position of all nodes
+    var enhancedData = this.ysy.storage.extra.enhanceData(convertedData, convertedData[this._rootId]);
+    var links = this.ysy.links.convertRelations(data, enhancedData);
+    var rearranged = this.rearrangeData(enhancedData);
+    this.ysy.links.attachLinks(rearranged, links);
+    /** @type {RootIdea} */
+    var initedData = MAPJS.content(rearranged);
+    // var diff = ysy.storage.lastState.compareIdea(initedData, 'server');
+    // this.prepareLastStateMessages(diff, last, initedData);
+    this.ysy.eventBus.fireEvent("IdeaConstructed", initedData);
+    this.setIdea(initedData);
+  };
+  /**
+   * update data processor
+   * @param {RootIdea} idea
+   * @param {Object} data
+   * @return {*}
+   */
+  Loader.prototype._updateIdeaByData = function (idea, data) {
+    var convertedData = this.convertData(data);
+    this.ysy.storage.extra.setLayout(data["layout"]);  // position of all nodes
+    var enhancedData = this.ysy.storage.extra.enhanceData(convertedData, convertedData[this._rootId]);
+    var links = this.ysy.links.convertRelations(data, enhancedData);
+    var rearranged = this.rearrangeData(enhancedData, idea);
+    var initedData = MAPJS.content(rearranged);
+    idea.ideas = initedData.ideas;
+    this.ysy.links.attachLinks(idea, links);
+    this.ysy.eventBus.fireEvent("IdeaConstructed", idea);
+    this.ysy.eventBus.fireEvent("TreeUpdated", idea);
+    this.ysy.fireChangedEvent("TreeUpdated", "");
+  };
+  /**
+   * extract Object containing actual data from container coming from server
+   * @abstract
+   * @param {Object} rawData
+   * @return {Object} data
+   * @example return rawData["easy_wbs_data"];
+   */
+  Loader.prototype.extractData = function (rawData) {
+    rawData.cosi = true;
+    throw "extractData is not defined";
+  };
+  /**
+   * load additional data from JSON such as arrays containing trackers, users, etc.
+   * @abstract
+   * @param {Object} data
+   */
+  Loader.prototype.loadSideData = function (data) {
+    data.dom = true;
+    throw "loadSideData is not defined";
+    // if (!data) return;
+    // ysy.data.trackers = data.trackers;
+    // ysy.proManager.fireEvent("dataFilled", "trackers", ysy.data.trackers);
+  };
+  Loader.prototype.convertData = function (data) {
+    var projectsSource = data["projects"];
+    var issuesSource = data["issues"];
+    var convertedEntities = [{}];
+    var groupedConverted = {root: null, project: {}, issue: {}};
+    var i;
+    var projectGenerator = this.nodeGenerator("project", groupedConverted, convertedEntities);
+    for (i = 0; i < projectsSource.length; i++) {
+      var projectSource = projectsSource[i];
+      if (projectSource.id === this.ysy.settings.rootID) {
+        this._rootId = convertedEntities.length;
+      }
+      projectSource.isProject = true;
+      projectGenerator(projectSource, projectSource.name, false);
+    }
+    var issueGenerator = this.nodeGenerator("issue", groupedConverted, convertedEntities);
+    for (i = 0; i < issuesSource.length; i++) {
+      var issueSource = issuesSource[i];
+      issueGenerator(issueSource, issueSource.subject, !issueSource.filtered_out);
+    }
+    var root = new easyMindMupClasses.RootIdea(this.ysy).upgrade(convertedEntities[this._rootId]);
+    groupedConverted.root = root;
+    convertedEntities[this._rootId] = root;
+    groupedConverted[root.attr.entityType][this.ysy.settings.rootID] = root;
+    this.assignParents(convertedEntities, groupedConverted);
+    return convertedEntities;
+  };
+  /**
+   * Prepare generator function for creating specific entity
+   * @param {String} entityType
+   * @param {Object} groupedConverted
+   * @param {Array} convertedList
+   * @return {Function}
+   */
+  Loader.prototype.nodeGenerator = function (entityType, groupedConverted, convertedList) {
+    return function (source, name, editable) {
+      var entity = new window.easyMindMupClasses.ModelEntity().fromServer(convertedList.length, name, entityType, editable, source);
+      groupedConverted[entityType][source.id] = entity;
+      convertedList.push(entity);
+    };
+  };
+  /**
+   * @abstract
+   * @param {ModelEntity} entity
+   * @param {boolean} next
+   * @return {ParentPack}
+   */
+  Loader.prototype.getParentFromSource = function (entity, next) {
+    // Override this - extract direct parent from entity
+    entity.title = next.toString();
+    throw "getParentFromSource is not defined";
+    // if (!next) {
+    //   if (entityData.parent_issue_id) return new ParentPack("issue", entityData.parent_issue_id);
+    //   if (entityData.parent_id) return new ParentPack("project", entityData.parent_id);
+    //   if (entityData.project_id) return new ParentPack("project", entityData.project_id);
+    // } else {
+    //   if (entityData.parent_issue_id) return new ParentPack("project", entityData.project_id);
+    // }
+    // return null;
+  };
+  /**
+   *
+   * @param {Array.<ModelEntity>} convertedEntities
+   * @param {Object} grouped
+   */
+  Loader.prototype.assignParents = function (convertedEntities, grouped) {
+    for (var id = 1; id < convertedEntities.length; id++) {
+      if (id === this._rootId) continue;
+      var entity = convertedEntities[id];
+      var parentGroup = this.getParentFromSource(entity, false);
+      if (parentGroup !== null) {
+        var parent = grouped[parentGroup.type][parentGroup.id];
+        if (!parent) {
+          parentGroup = this.getParentFromSource(entity, true);
+          if (parentGroup !== null) {
+            parent = grouped[parentGroup.type][parentGroup.id];
+          }
+        }
+      }
+      if (parent) {
+        entity.parent = parent;
+      } else {
+        entity.parent = grouped.root;
+      }
+    }
+  };
+  /**
+   *
+   * @param {Array.<ModelEntity>} convertedEntities
+   * @param {RootIdea} [oldIdea]
+   * @return {RootIdea}
+   */
+  Loader.prototype.rearrangeData = function (convertedEntities, oldIdea) {
+    /** @type {Array.<ModelEntity>} rootChildren */
+    var rootChildren = [];
+    /** @type {ModelEntity} entity */
+    var entity;
+    /** @type {ModelEntity} */
+    var parent;
+    /** @type {RootIdea}*/
+    var root = /** @type {RootIdea}*/ convertedEntities[this._rootId];
+    var entitiesToProcess;
+    if (oldIdea) {
+      var idLookup = {};
+      for (var id = 1; id < convertedEntities.length; id++) {
+        if (id === this._rootId) continue;
+        entity = convertedEntities[id];
+        idLookup[entity.attr.data.id] = entity;
+      }
+      this.rearrangeByIdea(oldIdea, convertedEntities[this._rootId], idLookup);
+      entitiesToProcess = Object.getOwnPropertyNames(idLookup).map(function (serverId) {
+        return idLookup[serverId];
+      });
+    } else {
+      entitiesToProcess = convertedEntities.slice(1);
+    }
+
+    for (var i = 0; i < entitiesToProcess.length; i++) {
+      entity = entitiesToProcess[i];
+      parent = entity.parent;
+      if (!parent) continue;
+      var parentIdeas = parent.ideas;
+      if (entity.rank) {
+        if (parentIdeas[entity.rank]) {
+          var detachedEntity = parentIdeas[entity.rank];
+          parentIdeas[entity.rank] = entity;
+          while (parent.nextChildIndex === 0 || parentIdeas[parent.nextChildIndex]) {
+            parent.nextChildIndex++;
+          }
+          parentIdeas[parent.nextChildIndex++] = detachedEntity;
+          delete entity.rank;
+          continue;
+        } else {
+          if (parent.nextChildIndex === 0) parent.nextChildIndex = 1;
+          parentIdeas[entity.rank] = entity;
+        }
+        delete entity.rank;
+      } else {
+        if (parent.id === this._rootId) {
+          rootChildren.push(entity);
+        } else {
+          while (parent.nextChildIndex === 0 || parentIdeas[parent.nextChildIndex]) {
+            parent.nextChildIndex++;
+          }
+          parentIdeas[parent.nextChildIndex++] = entity;
+        }
+      }
+    }
+
+    this._fillRootChildren(root, rootChildren);
+
+    for (id = 1; id < convertedEntities.length; id++) {
+      entity = convertedEntities[id];
+      delete entity.parent;
+      if (entity.attr.collapsed === undefined) {
+        if (id === this._rootId) continue;
+        if (entity.nextChildIndex) {
+          entity.attr.collapsed = true;
+        }
+      }
+      delete entity.nextChildIndex;
+    }
+    return root;
+  };
+  /**
+   *
+   * @param {RootIdea} idea
+   * @param {Array.<ModelEntity>} children
+   * @private
+   */
+  Loader.prototype._fillRootChildren = function (idea, children) {
+    if (children.length === 0){
+      this.ysy.contentPatch.updateOneSide(idea);
+      return;
+    }
+    var parentIdeas = idea.ideas;
+    var ranks = Object.getOwnPropertyNames(idea.ideas).map(function (i) {
+      return parseInt(i);
+    }).sort(function (a, b) {
+      return a - b
+    });
+    var positiveIndex = 1;
+    var nextPositive = function () {
+      while (parentIdeas[positiveIndex]) {
+        positiveIndex++;
+      }
+      return positiveIndex;
+    };
+    if(this.ysy.settings.oneSideOn){
+      var firstPositive = _.findIndex(ranks, function (rank) {
+        return rank > 0;
+      });
+      for (i = 0; i < children.length; i++) {
+        parentIdeas[nextPositive()]=children[i];
+      }
+      for (i = 0; i < firstPositive; i++) {
+        parentIdeas[nextPositive()] = parentIdeas[ranks[i]];
+        delete parentIdeas[ranks[i]];
+      }
+    }
+    var halfCount = Math.ceil((ranks.length + children.length) / 2);
+    var positives = 0;
+    var negativeIndex = -1;
+    for (var i = 0; i < ranks.length; i++) {
+      if (ranks[i] > 0) {
+        positives++;
+      }
+    }
+    for (i = 0; i < children.length; i++) {
+      if (halfCount >= positives) {
+        parentIdeas[nextPositive()] = children[i];
+        positives++;
+      } else {
+        while (parentIdeas[negativeIndex]) {
+          negativeIndex--;
+        }
+        parentIdeas[negativeIndex] = children[i];
+        negativeIndex--;
+      }
+    }
+  };
+  /**
+   *
+   * @param {ModelEntity} oldIdea
+   * @param {ModelEntity} newIdea
+   * @param {Object.<string,ModelEntity>} idLookup
+   */
+  Loader.prototype.rearrangeByIdea = function (oldIdea, newIdea, idLookup) {
+    if (!_.isEmpty(oldIdea.ideas)) {
+      var oldRanks = Object.getOwnPropertyNames(oldIdea.ideas);
+      var oldParentId = this.ysy.getData(oldIdea).id;
+      for (var i = 0; i < oldRanks.length; i++) {
+        var rank = oldRanks[i];
+        var oldChild = oldIdea.ideas[rank];
+        var oldChildId = this.ysy.getData(oldChild).id;
+        if (!oldChildId) continue;
+        var newChild = idLookup[oldChildId];
+        if (!newChild) continue;
+        var newParentId = this.ysy.getData(newChild.parent).id;
+        if (oldParentId !== newParentId) continue;
+        if (!newIdea.ideas) {
+          newIdea.ideas = {};
+        }
+        newIdea.ideas[rank] = newChild;
+        if (!newIdea.nextChildIndex) newIdea.nextChildIndex++;
+        newIdea.nextChildIndex++;
+        this.rearrangeByIdea(oldChild, newChild, idLookup);
+      }
+    }
+    delete idLookup[newIdea.attr.data.id];
+  };
+  /**
+   * push generated idea into MindMup component
+   * @param {RootIdea} idea
+   */
+  Loader.prototype.setIdea = function (idea) {
+    this.ysy.idea = idea;
+    this.ysy.mapModel.setIdea(idea);
+    this.ysy.eventBus.fireEvent("TreeLoaded", idea);
+  };
+  // /**
+  //  *
+  //  * @param last
+  //  // * @param {jQuery.Deferred} serverDeferred
+  //  */
+  // Loader.prototype.openStoredModal = function (last) {
+  //   var $target = this.ysy.util.getModal("form-modal", "50%");
+  //   var self = this;
+  //   var deferred = $.Deferred();
+  //   var template = self.ysy.settings.templates.storedModal;
+  //   //var obj = $.extend({}, ysy.view.getLabel("reloadModal"),{errors:errors});
+  //   //var rendered = Mustache.render(template, {});
+  //   $target.html(template);
+  //   var labels = {};
+  //   $target.find("button").each(function () {
+  //     labels[this.id] = $(this).text();
+  //   }).remove();
+  //   showModal("form-modal");
+  //   $target.dialog({
+  //     buttons: [
+  //       {
+  //         id: "stored_state_modal_local",
+  //         text: labels["stored_state_modal_local"],
+  //         class: "mindmup-stored-modal-button button-1",
+  //         click: function () {
+  //           deferred.resolve("local");
+  //           $target.dialog("close");
+  //         }
+  //       },
+  //       {
+  //         id: "stored_state_modal_server",
+  //         text: labels["stored_state_modal_server"],
+  //         class: "mindmup-stored-modal-button button-2",
+  //         click: function () {
+  //           $target.dialog("close");
+  //         }
+  //       }
+  //     ]
+  //   })
+  //       .on('dialogclose', function () {
+  //         deferred.resolve("server");
+  //       });
+  //   $("#last_state_modal_yes").focus();
+  //   return deferred;
+  // };
+
+  window.easyMindMupClasses.Loader = Loader;
+  //####################################################################################################################
+  /**
+   *
+   * @param {String} entityType
+   * @param {number} id
+   * @constructor
+   */
+  function ParentPack(entityType, id) {
+    this.type = entityType;
+    this.id = id;
+  }
+
+  window.easyMindMupClasses.ParentPack = ParentPack;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/logger.js b/plugins/easy_mindmup/assets/javascripts/logger.js
new file mode 100644
index 0000000000000000000000000000000000000000..eebb98fab23a4a279713e62f6ca9b3ba72a90816
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/logger.js
@@ -0,0 +1,70 @@
+(function () {
+  function Logger(ysy) {
+    this.logLevel = 2;
+    this.mainDebug = "";
+    this.debugTypes = [
+      // "keys",
+      // "diff",
+      // "send",
+      // "storage",
+      // "events",
+      // "changedEvent",
+      // "redraw",
+      // "history",
+      // "validator",
+      // "multisave",
+      // "autosave",
+      // "mm:drag",
+      "nothing"
+    ];
+  }
+
+  Logger.prototype.log = function (text) {
+    if (this.logLevel >= 4) {
+      this.print(text);
+    }
+  };
+  Logger.prototype.message = function (text) {
+    if (this.logLevel >= 3) {
+      this.print(text);
+    }
+  };
+  Logger.prototype.debug = function (text, type) {
+    if (type) {
+      if (this.mainDebug === type) {
+        this.print(text, "debug");
+        return;
+      }
+      for (var i = 0; i < this.debugTypes.length; i++) {
+        if (this.debugTypes[i] === type) {
+          this.print(text, type === this.mainDebug ? "debug" : null);
+          return;
+        }
+      }
+    } else {
+      this.print(text, "debug");
+    }
+  };
+  Logger.prototype.warning = function (text) {
+    if (this.logLevel >= 2) {
+      this.print(text, "warning");
+    }
+  };
+  Logger.prototype.error = function (text) {
+    if (this.logLevel >= 1) {
+      this.print(text, "error");
+    }
+  };
+  Logger.prototype.print = function (text, type) {
+    if (type === "error") {
+      console.error(text);
+    } else if (type === "warning") {
+      console.warn(text);
+    } else if (type === "debug") {
+      console.debug(text);
+    } else {
+      console.log(text);
+    }
+  };
+  easyMindMupClasses.Logger = Logger;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/main.js b/plugins/easy_mindmup/assets/javascripts/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddcdeef3e4b56d075ed8abc6a6c1cac879d7875f
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/main.js
@@ -0,0 +1,268 @@
+(function () {
+  function MindMup(settings) {
+    this.settings = settings;
+    this.id = settings.mindMupId || "mindMup";
+    this.containerDiv = settings.containerDiv || "#container";
+    this.menuDiv = settings.menuDiv || "#mindmup_menu";
+    this.$container = $(this.containerDiv);
+    this.$menu = $(this.menuDiv);
+    MindMup.allMindMups[this.id] = this;
+    /** @type {boolean} */
+    this.helperInited = false;
+    // this.init();
+  }
+
+  MindMup.allMindMups = {};
+  MindMup.prototype.helperInit = function () {
+    this.helperInited = true;
+    var settings = this.settings;
+    if (settings.easyRedmine && $("#content").children(".easy-content-page").length === 0) {
+      $("#easy_wbs").addClass("easy-content-page");
+    }
+    // $(this.containerDiv + " p.nodata").remove();
+
+    /** HelperClassInit PHASE */
+    /** @type {Util} */
+    this.util = new easyMindMupClasses.Util(this);
+    /** @type {EventBus} */
+    this.eventBus = new easyMindMupClasses.EventBus(this);
+    /** @type {Logger} */
+    this.log = new easyMindMupClasses.Logger(this);
+    /** @type {Repainter} */
+    this.repainter = new easyMindMupClasses.Repainter(this);
+    /** @type {JasmineTests} */
+    this.tests = easyMindMupClasses.JasmineTests && new easyMindMupClasses.JasmineTests(this);
+  };
+  MindMup.prototype.init = function () {
+    if (!this.helperInited) {
+      this.helperInit();
+    }
+
+    /** PatchClassInit PHASE */
+    this.eventBus.fireEvent("BeforePatchClassInit");
+    /** @type {LayoutPatch} */
+    this.layoutPatch = this.layoutPatch || new easyMindMupClasses.LayoutPatch(this);
+    /** @type {ContentPatch} */
+    this.contentPatch = this.contentPatch || new easyMindMupClasses.ContentPatch(this);
+    /** @type {NodePatch} */
+    this.nodePatch = this.nodePatch || new easyMindMupClasses.NodePatch(this);
+    /** @type {MapModelPatch} */
+    this.mapModelPatch = this.mapModelPatch || new easyMindMupClasses.MapModelPatch(this);
+
+    /** DataClassInit PHASE */
+    this.eventBus.fireEvent("BeforeDataClassInit");
+    /** @type {DomPatch} */
+    this.domPatch = this.domPatch || new easyMindMupClasses.DomPatch(this);
+    /** @type {AfterChange} */
+    this.afterChange = this.afterChange || new easyMindMupClasses.AfterChange(this);
+    /** @type {Storage} */
+    this.storage = this.storage || new easyMindMupClasses.Storage(this);
+    /** @type {History} */
+    this.history = this.history || new easyMindMupClasses.History(this);
+    /** @type {Validator} */
+    this.validator = this.validator || new easyMindMupClasses.Validator(this);
+    /** @type {Links} */
+    this.links = this.links || new easyMindMupClasses.Links(this);
+    /** @type {DataStorage} */
+    this.dataStorage = this.dataStorage || new easyMindMupClasses.DataStorage(this);
+
+    /** ViewClassInit PHASE */
+    this.eventBus.fireEvent("BeforeViewClassInit");
+    /** @type {MMInitiator} */
+    this.mmInitiator = this.mmInitiator || new easyMindMupClasses.MMInitiator(this);
+    /** @type {Filter} */
+    this.filter = this.filter || new easyMindMupClasses.Filter(this);
+    /** @type {Styles} */
+    this.styles = this.styles || new easyMindMupClasses.Styles(this);
+    /** @type {Legend} */
+    this.legends = this.legends || new easyMindMupClasses.Legend(this);
+    /** @type {Toolbar} */
+    this.toolbar = this.toolbar || new easyMindMupClasses.Toolbar(this);
+    /** @type {LinkEdit} */
+    this.linksEdit = this.linksEdit || new easyMindMupClasses.LinkEdit(this);
+    /** @type {Print} */
+    this.print = this.print || new easyMindMupClasses.Print(this);
+    /** @type {ContextMenu} */
+    this.contextMenu = this.contextMenu || new easyMindMupClasses.ContextMenu(this);
+    /** @type {LegendEvents} */
+    this.legendEvents = this.legendEvents || new easyMindMupClasses.LegendEvents(this);
+
+    /** ServerClassInit PHASE */
+    this.eventBus.fireEvent("BeforeServerClassInit");
+    /** @type {Loader} */
+    this.loader = this.loader || new easyMindMupClasses.Loader(this);
+    /** @type {Saver} */
+    this.saver = this.saver || new easyMindMupClasses.Saver(this);
+    /** @type {Autosave} */
+    this.autosave = this.autosave || new easyMindMupClasses.Autosave(this);
+    /** @type {SaveProgress} */
+    this.saveProgress = this.saveProgress || new easyMindMupClasses.SaveProgress(this);
+    /** @type {SaveInfo} */
+    this.saveInfo = this.saveInfo || new easyMindMupClasses.SaveInfo(this);
+
+    /** BeforeMapInit PHASE */
+    this.eventBus.fireEvent("beforeMapInit");
+    this.mmInitiator.init(this); // initialize MindMup component
+
+    /** MapInited PHASE */
+    this.eventBus.fireEvent("MapInited", this.mapModel);
+    this.repainter.start();
+    this.loader.load();
+    /** TreeLoaded PHASE (after load) */
+  };
+  MindMup.prototype.idea = null;
+  MindMup.prototype.mapModel = null;
+  /**
+   * entityType of primary entity to create by Add functions
+   * @type {String}
+   */
+  MindMup.prototype.creatingEntity = null;
+  /**
+   * Get Node from Layout. Node is view counterpart of an idea
+   * @param {number} nodeId
+   * @return {ModelEntity} - very similar to ModelEntity, mainly [attr] property
+   */
+  MindMup.prototype.getLayoutNode = function (nodeId) {
+    if (!this.mapModel) return null;
+    var layout = this.mapModel.getCurrentLayout();
+    if (!layout) return null;
+    return layout.nodes[nodeId];
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @return {ModelEntityData}
+   */
+  MindMup.prototype.getData = function (idea) {
+    if (!idea) return null;
+    if (!idea.attr.data) idea.attr.data = new window.easyMindMupClasses.ModelEntityData;
+    return idea.attr.data;
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @param {Object|String} obj
+   * @param {boolean} [silent]
+   * @return {boolean}
+   */
+  MindMup.prototype.setData = function (idea, obj, silent) {
+    if (!idea) return false;
+    if (!idea.attr.data) idea.attr.data = new window.easyMindMupClasses.ModelEntityData;
+    var data = idea.attr.data;
+    if (typeof(obj) === 'string') {
+      obj = JSON.parse(obj);
+    }
+    var props = Object.getOwnPropertyNames(obj);
+    var changed = false;
+    var rev = {};
+    for (var i = 0; i < props.length; i++) {
+      var key = props[i];
+      if (obj[key] !== data[key]) {
+        if (!data[key] && !obj[key]) continue;
+        if (!data._old) data._old = {};
+        data._old[key] = data[key];
+        rev[key] = data[key];
+        data[key] = obj[key];
+        if (this.afterChange[key + 'Func']) {
+          this.afterChange[key + 'Func'](idea, obj[key], data._old[key]);
+        }
+        changed = true;
+      }
+    }
+    if (changed && !silent) {
+      var self = this;
+      this.idea.logChange("setData", [idea, obj], function () {
+        self.setData(idea, rev, true);
+      });
+    }
+    return changed;
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @param {Object|String} obj - should be with index of customField as key
+   * @param {boolean} [silent]
+   * @return {boolean}
+   */
+  MindMup.prototype.setCustomData = function (idea, obj, silent) {
+    if (!idea) return false;
+    if (!idea.attr.data) idea.attr.data = new window.easyMindMupClasses.ModelEntityData;
+    var data = idea.attr.data;
+    var customFields = data.custom_fields;
+    if (typeof(obj) === 'string') {
+      obj = JSON.parse(obj);
+    }
+    var props = Object.getOwnPropertyNames(obj);
+    if (!customFields) {
+      data.custom_fields = customFields = [];
+      for (var k = 0; k < props.length; k++) {
+        customFields.push({"id": props[k], "value": obj[props[k]]});
+      }
+      if (!silent) {
+        this.idea.logChange("initCustomData", [idea, obj], function () {
+          delete idea.attr.data.custom_fields;
+        });
+      }
+      return true;
+    }
+    var changed = false;
+    var rev = {};
+    for (var i = 0; i < props.length; i++) {
+      var stringId = props[i];
+      var id = parseInt(stringId);
+      for (var j = 0; j < customFields.length; j++) {
+        var customField = customFields[j];
+        if (customField.id !== id) continue;
+        if (obj[stringId] !== customField.value) {
+          if (!data._old) data._old = {};
+          if (!data._old.custom_fields) data._old.custom_fields = {};
+          data._old.custom_fields[j] = {value: customField.value};
+          rev[stringId] = customField.value;
+          customField.value = obj[stringId];
+          // if (this.afterChange['customFieldFunc']) {
+          //   this.afterChange['customFieldFunc'](idea, obj[index], data._old.custom_fields[index]);
+          // }
+          changed = true;
+        }
+        break;
+      }
+    }
+    if (changed && !silent) {
+      var self = this;
+      this.idea.logChange("setCustomData", [idea, obj], function () {
+        self.setCustomData(idea, rev, true);
+      })
+    }
+    return changed;
+  };
+  /**
+   * @param {String} eventSubName
+   * @param {String} json
+   */
+  MindMup.prototype.fireChangedEvent = function (eventSubName, json) {
+    if (!this.mapModel.getInputEnabled()) return;
+    this.idea.dispatchEvent('changed', eventSubName, json);
+  };
+  /**
+   * @param {ModelEntity} idea
+   * @return {jQuery}
+   */
+  MindMup.prototype.getNodeElement = function (idea) {
+    return this.$container.find("#node_" + idea.id);
+  };
+  /**
+   *
+   * @param {*} idea
+   * @return {ModelEntity}
+   */
+  MindMup.prototype.upgradeToModelEntity = function (idea) {
+    idea.__proto__ = window.easyMindMupClasses.ModelEntity.prototype;
+    idea.attr = new window.easyMindMupClasses.ModelEntityAttr().fromJson(idea.attr);
+    idea.attr.entityType = this.creatingEntity;
+    return idea;
+  };
+
+  window.easyMindMupClasses = window.easyMindMupClasses || {};
+  window.easyMindMupClasses.MindMup = MindMup;
+})();
+
diff --git a/plugins/easy_mindmup/assets/javascripts/map_model_patch.js b/plugins/easy_mindmup/assets/javascripts/map_model_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..c36cb5bfe1e458f5e10b9e89fdc1c03ce691ac5e
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/map_model_patch.js
@@ -0,0 +1,108 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  /**
+   * Class responsible for modification of mapModel code
+   * @param {MindMup} ysy
+   */
+  function MapModelPatch(ysy) {
+    this.ysy = ysy;
+    this.init(ysy);
+    this.selectPersist = new SelectionPersist(ysy);
+  }
+
+  /**
+   * @param {MindMup} ysy
+   */
+  MapModelPatch.prototype.init = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("MapInited", function (mapModel) {
+      self.selectPersist.init(mapModel);
+      mapModel.getYsy = function () {
+        return ysy;
+      };
+      mapModel.save = function (origin) {
+        ysy.saver.save();
+      };
+      /**
+       * Open new page with entity detail.
+       * Expect pathUrl with key [entityPage] and value [.../:entityID/...]
+       * @param {number|string} id
+       * @return {boolean}
+       */
+      mapModel.followURL = function (id) {
+        if (id === 'toolbar') {
+          id = mapModel.getCurrentlySelectedIdeaId();
+        }
+        /** @type {ModelEntity} */
+        var idea = ysy.mapModel.findIdeaById(id);
+        var data = ysy.getData(idea);
+        if (!data.id) return false;
+        if(data.default_url){
+          window.open(data.default_url);
+        } else{
+          var templateUrl = ysy.settings.paths[idea.attr.entityType + "Page"];
+          if (templateUrl === undefined) throw "entityPage URL is not defined";
+          window.open(templateUrl.replace(":" + idea.attr.entityType + "ID", data.id));
+        }
+        return true;
+      };
+      mapModel.editNodeData = function (source) {
+        if (!mapModel.getEditingEnabled() || !mapModel.getInputEnabled()) {
+          return false;
+        }
+        ysy.eventBus.fireEvent('nodeEditDataRequested', mapModel.getCurrentlySelectedIdeaId());
+      };
+      mapModel.toggleOneSide = function (source) {
+        var idea = ysy.idea;
+        var targetState = !self.ysy.settings.oneSideOn;
+        self.ysy.settings.oneSideOn = targetState;
+        idea.updateOneSide(idea);
+        self.ysy.eventBus.fireEvent("saveOneSideOn", targetState);
+      };
+      /** prevent scroll jumping after deselecting node while editing */
+      mapModel.addEventListener('inputEnabledChanged', function (canInput, holdFocus) {
+        if (canInput && !holdFocus) {
+          // console.log("inputEnabledChanged without focus");
+          mapModel.dispatchEvent('inputEnabledChanged', true, true);
+          return false;
+        }
+      }, 5);
+
+      var oldResetView = mapModel.resetView;
+      mapModel.resetView = function (source) {
+        ysy.domPatch.resetRootPosition();
+        oldResetView(source);
+      };
+    });
+  };
+  window.easyMindMupClasses.MapModelPatch = MapModelPatch;
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function SelectionPersist(ysy) {
+    this.ysy = ysy;
+    this.preLastSelectedNode = null;
+    this.lastSelectedNode = null;
+  }
+
+  SelectionPersist.prototype.init = function (mapModel) {
+    var self = this;
+    var selectHandler = function (id, added) {
+      if (!added) return;
+      self.preLastSelectedNode = self.lastSelectedNode;
+      self.lastSelectedNode = id;
+    };
+    var resetViewHandler = function () {
+      self.preLastSelectedNode = self.preLastSelectedNode || self.ysy.idea.id;
+      self.lastSelectedNode = self.preLastSelectedNode;
+      mapModel.selectNode(self.preLastSelectedNode);
+    };
+    mapModel.addEventListener('nodeSelectionChanged', selectHandler);
+    mapModel.addEventListener('mapViewResetRequested', resetViewHandler, 5);
+  }
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/mapjs_init.js b/plugins/easy_mindmup/assets/javascripts/mapjs_init.js
new file mode 100644
index 0000000000000000000000000000000000000000..3984ac674e4b6643a5efc09798f276fe5babedd5
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mapjs_init.js
@@ -0,0 +1,33 @@
+(function () {
+  /**
+   * Class responsible for initiation of MindMup mind map component
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function MMInitiator(ysy) {
+    this.ysy = ysy;
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  MMInitiator.prototype.init = function (ysy) {
+    // var imageInsertController = new MAPJS.ImageInsertController("http://localhost:4999?u=");
+    var mapModel = new MAPJS.MapModel(ysy.layoutPatch.layoutCalculator, []);
+    jQuery(ysy.$container).domMapWidget(console, mapModel, false, null, undefined, ysy);
+    jQuery(ysy.$menu).mapToolbarWidget(mapModel, ysy);
+    MAPJS.DOMRender.stageMargin = {top: 300, left: 300, bottom: 300, right: 300};
+    MAPJS.DOMRender.linkConnectorPath = ysy.links.outerPath;
+    MAPJS.DOMRender.nodeConnectorPath = ysy.domPatch.curvedPath;
+    ysy.mapModel = mapModel;
+    $(window).resize(function (event) {
+      ysy.eventBus.fireEvent("resize", event)
+    });
+    // imageInsertController.addEventListener('imageInsertError', function (reason) {
+    //   ysy.log.error('image insert error', reason);
+    // });
+  };
+
+  window.easyMindMupClasses.MMInitiator = MMInitiator;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/clipboard.js b/plugins/easy_mindmup/assets/javascripts/mindmup/clipboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..19f5a3b573092b891e60f78bf448f97162344e60
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/clipboard.js
@@ -0,0 +1,18 @@
+/*global MAPJS*/
+MAPJS.MemoryClipboard = function () {
+  'use strict';
+  var self = this,
+      clone = function (something) {
+        if (!something) {
+          return undefined;
+        }
+        return JSON.parse(JSON.stringify(something));
+      },
+      contents;
+  self.get = function () {
+    return clone(contents);
+  };
+  self.put = function (c) {
+    contents = clone(c);
+  };
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/content.js b/plugins/easy_mindmup/assets/javascripts/mindmup/content.js
new file mode 100644
index 0000000000000000000000000000000000000000..d2eb7d17fcb61737f7d1557a450874f053c9a85a
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/content.js
@@ -0,0 +1,979 @@
+/*jslint eqeq: true, forin: true, nomen: true*/
+/*jshint unused:false, loopfunc:true */
+/*global _, MAPJS, observable*/
+MAPJS.content = function (contentAggregate, sessionKey) {
+  'use strict';
+  var cachedId,
+      invalidateIdCache = function () {
+        cachedId = undefined;
+      },
+      maxId = function maxId(idea) {
+        idea = idea || contentAggregate;
+        if (!idea.ideas) {
+          return parseInt(idea.id, 10) || 0;
+        }
+        return _.reduce(
+            idea.ideas,
+            function (result, subidea) {
+              return Math.max(result, maxId(subidea));
+            },
+            parseInt(idea.id, 10) || 0
+        );
+      },
+      nextId = function nextId(originSession) {
+        originSession = originSession || sessionKey;
+        if (!cachedId) {
+          cachedId = maxId();
+        }
+        cachedId += 1;
+        if (originSession) {
+          return cachedId + '.' + originSession;
+        }
+        return cachedId;
+      },
+      init = function (contentIdea, originSession) {
+        if (!contentIdea.id) {
+          contentIdea.id = nextId(originSession);
+        } else {
+          invalidateIdCache();
+        }
+        if (contentIdea.ideas) {
+          _.each(contentIdea.ideas, function (value, key) {
+            contentIdea.ideas[parseFloat(key)] = init(value, originSession);
+          });
+        }
+        if (!contentIdea.title) {
+          contentIdea.title = '';
+        }
+        contentIdea.containsDirectChild = contentIdea.findChildRankById = function (childIdeaId) {
+          return parseFloat(
+              _.reduce(
+                  contentIdea.ideas,
+                  function (res, value, key) {
+                    return value.id == childIdeaId ? key : res;
+                  },
+                  undefined
+              )
+          );
+        };
+        contentIdea.findSubIdeaById = function (childIdeaId) {
+          var myChild = _.find(contentIdea.ideas, function (idea) {
+            return idea.id == childIdeaId;
+          });
+          return myChild || _.reduce(contentIdea.ideas, function (result, idea) {
+                return result || idea.findSubIdeaById(childIdeaId);
+              }, undefined);
+        };
+        contentIdea.find = function (predicate) {
+          var current = predicate(contentIdea) ? [_.pick(contentIdea, 'id', 'title')] : [];
+          if (_.size(contentIdea.ideas) === 0) {
+            return current;
+          }
+          return _.reduce(contentIdea.ideas, function (result, idea) {
+            return _.union(result, idea.find(predicate));
+          }, current);
+        };
+        contentIdea.getAttr = function (name) {
+          if (contentIdea.attr && contentIdea.attr[name]) {
+            return _.clone(contentIdea.attr[name]);
+          }
+          return false;
+        };
+        contentIdea.sortedSubIdeas = function () {
+          if (!contentIdea.ideas) {
+            return [];
+          }
+          var result = [],
+              childKeys = _.groupBy(_.map(_.keys(contentIdea.ideas), parseFloat), function (key) {
+                return key > 0;
+              }),
+              sortedChildKeys = _.sortBy(childKeys[true], Math.abs).concat(_.sortBy(childKeys[false], Math.abs));
+          _.each(sortedChildKeys, function (key) {
+            result.push(contentIdea.ideas[key]);
+          });
+          return result;
+        };
+        contentIdea.traverse = function (iterator, postOrder) {
+          if (!postOrder) {
+            iterator(contentIdea);
+          }
+          _.each(contentIdea.sortedSubIdeas(), function (subIdea) {
+            subIdea.traverse(iterator, postOrder);
+          });
+          if (postOrder) {
+            iterator(contentIdea);
+          }
+        };
+        return contentIdea;
+      },
+      maxKey = function (kvMap, sign) {
+        sign = sign || 1;
+        if (_.size(kvMap) === 0) {
+          return 0;
+        }
+        var currentKeys = _.keys(kvMap);
+        currentKeys.push(0);
+        /* ensure at least 0 is there for negative ranks */
+        return _.max(_.map(currentKeys, parseFloat), function (x) {
+          return x * sign;
+        });
+      },
+      nextChildRank = function (parentIdea) {
+        var newRank, counts, childRankSign = 1;
+        if (parentIdea.id == contentAggregate.id) {
+          counts = _.countBy(parentIdea.ideas, function (v, k) {
+            return k < 0;
+          });
+          if ((counts['true'] || 0) < counts['false']) {
+            childRankSign = -1;
+          }
+        }
+        newRank = maxKey(parentIdea.ideas, childRankSign) + childRankSign;
+        return newRank;
+      },
+      appendSubIdea = function (parentIdea, subIdea) {
+        var rank;
+        parentIdea.ideas = parentIdea.ideas || {};
+        rank = nextChildRank(parentIdea);
+        parentIdea.ideas[rank] = subIdea;
+        return rank;
+      },
+      findIdeaById = function (ideaId) {
+        return contentAggregate.id == ideaId ? contentAggregate : contentAggregate.findSubIdeaById(ideaId);
+      },
+      sameSideSiblingRanks = function (parentIdea, ideaRank) {
+        return _(_.map(_.keys(parentIdea.ideas), parseFloat)).reject(function (k) {
+          return k * ideaRank < 0;
+        });
+      },
+      sign = function (number) {
+        /* intentionally not returning 0 case, to help with split sorting into 2 groups */
+        return number < 0 ? -1 : 1;
+      },
+      eventStacks = {},
+      redoStacks = {},
+      isRedoInProgress = false,
+      batches = {},
+      notifyChange = function (method, args, originSession) {
+        if (originSession) {
+          contentAggregate.dispatchEvent('changed', method, args, originSession);
+        } else {
+          contentAggregate.dispatchEvent('changed', method, args);
+        }
+      },
+      appendChange = function (method, args, undofunc, originSession) {
+        var prev;
+        if (method === 'batch' || batches[originSession] || !eventStacks || !eventStacks[originSession] || eventStacks[originSession].length === 0) {
+          logChange(method, args, undofunc, originSession);
+          return;
+        } else {
+          prev = eventStacks[originSession].pop();
+          if (prev.eventMethod === 'batch') {
+            eventStacks[originSession].push({
+              eventMethod: 'batch',
+              eventArgs: prev.eventArgs.concat([[method].concat(args)]),
+              undoFunction: function () {
+                undofunc();
+                prev.undoFunction();
+              }
+            });
+          } else {
+            eventStacks[originSession].push({
+              eventMethod: 'batch',
+              eventArgs: [[prev.eventMethod].concat(prev.eventArgs)].concat([[method].concat(args)]),
+              undoFunction: function () {
+                undofunc();
+                prev.undoFunction();
+              }
+            });
+          }
+        }
+        if (isRedoInProgress) {
+          contentAggregate.dispatchEvent('changed', 'redo', undefined, originSession);
+        } else {
+          notifyChange(method, args, originSession);
+          redoStacks[originSession] = [];
+        }
+      },
+      logChange = function (method, args, undofunc, originSession) {
+        var event = {eventMethod: method, eventArgs: args, undoFunction: undofunc};
+        if (batches[originSession]) {
+          batches[originSession].push(event);
+          return;
+        }
+        if (!eventStacks[originSession]) {
+          eventStacks[originSession] = [];
+        }
+        eventStacks[originSession].push(event);
+
+        if (isRedoInProgress) {
+          contentAggregate.dispatchEvent('changed', 'redo', undefined, originSession);
+        } else {
+          notifyChange(method, args, originSession);
+          redoStacks[originSession] = [];
+        }
+      },
+      reorderChild = function (parentIdea, newRank, oldRank) {
+        parentIdea.ideas[newRank] = parentIdea.ideas[oldRank];
+        delete parentIdea.ideas[oldRank];
+      },
+      upgrade = function (idea) {
+        if (idea.style) {
+          idea.attr = {};
+          var collapsed = idea.style.collapsed;
+          delete idea.style.collapsed;
+          idea.attr.style = idea.style;
+          if (collapsed) {
+            idea.attr.collapsed = collapsed;
+          }
+          delete idea.style;
+        }
+        if (idea.ideas) {
+          _.each(idea.ideas, upgrade);
+        }
+      },
+      sessionFromId = function (id) {
+        var dotIndex = String(id).indexOf('.');
+        return dotIndex > 0 && id.substr(dotIndex + 1);
+      },
+      commandProcessors = {},
+      configuration = {},
+      uniqueResourcePostfix = '/xxxxxxxx-yxxx-yxxx-yxxx-xxxxxxxxxxxx/'.replace(/[xy]/g, function (c) {
+            /*jshint bitwise: false*/
+            // jscs:disable
+            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
+            // jscs:enable
+            return v.toString(16);
+          }) + (sessionKey || ''),
+      updateAttr = function (object, attrName, attrValue) {
+        var oldAttr;
+        if (!object) {
+          return false;
+        }
+        oldAttr = _.extend({}, object.attr);
+        object.attr = _.extend({}, object.attr);
+        if (!attrValue || attrValue === 'false' || (_.isObject(attrValue) && _.isEmpty(attrValue))) {
+          if (!object.attr[attrName]) {
+            return false;
+          }
+          delete object.attr[attrName];
+        } else {
+          if (_.isEqual(object.attr[attrName], attrValue)) {
+            return false;
+          }
+          object.attr[attrName] = JSON.parse(JSON.stringify(attrValue));
+        }
+        if (_.size(object.attr) === 0) {
+          delete object.attr;
+        }
+        return function () {
+          object.attr = oldAttr;
+        };
+      };
+
+  contentAggregate.logChange = logChange;
+  contentAggregate.setConfiguration = function (config) {
+    configuration = config || {};
+  };
+  contentAggregate.getSessionKey = function () {
+    return sessionKey;
+  };
+  contentAggregate.nextSiblingId = function (subIdeaId) {
+    var parentIdea = contentAggregate.findParent(subIdeaId),
+        currentRank,
+        candidateSiblingRanks,
+        siblingsAfter;
+    if (!parentIdea) {
+      return false;
+    }
+    currentRank = parentIdea.findChildRankById(subIdeaId);
+    candidateSiblingRanks = sameSideSiblingRanks(parentIdea, currentRank);
+    siblingsAfter = _.reject(candidateSiblingRanks, function (k) {
+      return Math.abs(k) <= Math.abs(currentRank);
+    });
+    if (siblingsAfter.length === 0) {
+      return false;
+    }
+    return parentIdea.ideas[_.min(siblingsAfter, Math.abs)].id;
+  };
+  contentAggregate.sameSideSiblingIds = function (subIdeaId) {
+    var parentIdea = contentAggregate.findParent(subIdeaId),
+        currentRank = parentIdea.findChildRankById(subIdeaId);
+    return _.without(_.map(_.pick(parentIdea.ideas, sameSideSiblingRanks(parentIdea, currentRank)), function (i) {
+      return i.id;
+    }), subIdeaId);
+  };
+  contentAggregate.getAttrById = function (ideaId, attrName) {
+    var idea = findIdeaById(ideaId);
+    return idea && idea.getAttr(attrName);
+  };
+  contentAggregate.previousSiblingId = function (subIdeaId) {
+    var parentIdea = contentAggregate.findParent(subIdeaId),
+        currentRank,
+        candidateSiblingRanks,
+        siblingsBefore;
+    if (!parentIdea) {
+      return false;
+    }
+    currentRank = parentIdea.findChildRankById(subIdeaId);
+    candidateSiblingRanks = sameSideSiblingRanks(parentIdea, currentRank);
+    siblingsBefore = _.reject(candidateSiblingRanks, function (k) {
+      return Math.abs(k) >= Math.abs(currentRank);
+    });
+    if (siblingsBefore.length === 0) {
+      return false;
+    }
+    return parentIdea.ideas[_.max(siblingsBefore, Math.abs)].id;
+  };
+  contentAggregate.clone = function (subIdeaId) {
+    var toClone = (subIdeaId && subIdeaId != contentAggregate.id && contentAggregate.findSubIdeaById(subIdeaId)) || contentAggregate;
+    return JSON.parse(JSON.stringify(toClone));
+  };
+  contentAggregate.cloneMultiple = function (subIdeaIdArray) {
+    return _.map(subIdeaIdArray, contentAggregate.clone);
+  };
+  contentAggregate.calculatePath = function (ideaId, currentPath, potentialParent) {
+    if (contentAggregate.id == ideaId) {
+      return [];
+    }
+    currentPath = currentPath || [contentAggregate];
+    potentialParent = potentialParent || contentAggregate;
+    if (potentialParent.containsDirectChild(ideaId)) {
+      return currentPath;
+    }
+    return _.reduce(
+        potentialParent.ideas,
+        function (result, child) {
+          return result || contentAggregate.calculatePath(ideaId, [child].concat(currentPath), child);
+        },
+        false
+    );
+  };
+  contentAggregate.getSubTreeIds = function (rootIdeaId) {
+    var result = [],
+        collectIds = function (idea) {
+          if (_.isEmpty(idea.ideas)) {
+            return [];
+          }
+          _.each(idea.sortedSubIdeas(), function (child) {
+            collectIds(child);
+            result.push(child.id);
+          });
+        };
+    collectIds(contentAggregate.findSubIdeaById(rootIdeaId) || contentAggregate);
+    return result;
+  };
+  contentAggregate.findParent = function (subIdeaId, parentIdea) {
+    parentIdea = parentIdea || contentAggregate;
+    if (parentIdea.containsDirectChild(subIdeaId)) {
+      return parentIdea;
+    }
+    return _.reduce(
+        parentIdea.ideas,
+        function (result, child) {
+          return result || contentAggregate.findParent(subIdeaId, child);
+        },
+        false
+    );
+  };
+
+  /**** aggregate command processing methods ****/
+  contentAggregate.getCommandProcessors = function () {
+    return commandProcessors;
+  };
+  contentAggregate.startBatch = function (originSession) {
+    var activeSession = originSession || sessionKey;
+    contentAggregate.endBatch(originSession);
+    batches[activeSession] = [];
+  };
+  contentAggregate.endBatch = function (originSession) {
+    var activeSession = originSession || sessionKey,
+        inBatch = batches[activeSession],
+        batchArgs,
+        batchUndoFunctions,
+        undo;
+    batches[activeSession] = undefined;
+    if (_.isEmpty(inBatch)) {
+      return;
+    }
+    if (_.size(inBatch) === 1) {
+      logChange(inBatch[0].eventMethod, inBatch[0].eventArgs, inBatch[0].undoFunction, activeSession);
+    } else {
+      batchArgs = _.map(inBatch, function (event) {
+        return [event.eventMethod].concat(event.eventArgs);
+      });
+      batchUndoFunctions = _.sortBy(
+          _.map(inBatch, function (event) {
+            return event.undoFunction;
+          }),
+          function (f, idx) {
+            return -1 * idx;
+          }
+      );
+      undo = function () {
+        _.each(batchUndoFunctions, function (eventUndo) {
+          eventUndo();
+        });
+      };
+      logChange('batch', batchArgs, undo, activeSession);
+    }
+  };
+  contentAggregate.execCommand = function (cmd, args, originSession) {
+    if (!commandProcessors[cmd]) {
+      return false;
+    }
+    return commandProcessors[cmd].apply(contentAggregate, [originSession || sessionKey].concat(_.toArray(args)));
+  };
+
+  contentAggregate.batch = function (batchOp) {
+    contentAggregate.startBatch();
+    try {
+      batchOp();
+    }
+    finally {
+      contentAggregate.endBatch();
+    }
+  };
+
+  commandProcessors.batch = function (originSession) {
+    contentAggregate.startBatch(originSession);
+    try {
+      _.each(_.toArray(arguments).slice(1), function (event) {
+        contentAggregate.execCommand(event[0], event.slice(1), originSession);
+      });
+    }
+    finally {
+      contentAggregate.endBatch(originSession);
+    }
+  };
+  contentAggregate.pasteMultiple = function (parentIdeaId, jsonArrayToPaste) {
+    contentAggregate.startBatch();
+    var results = _.map(jsonArrayToPaste, function (json) {
+      return contentAggregate.paste(parentIdeaId, json);
+    });
+    contentAggregate.endBatch();
+    return results;
+  };
+
+  contentAggregate.paste = function (parentIdeaId, jsonToPaste, initialId) {
+    return contentAggregate.execCommand('paste', arguments);
+  };
+  commandProcessors.paste = function (originSession, parentIdeaId, jsonToPaste, initialId) {
+    var pasteParent = (parentIdeaId == contentAggregate.id) ? contentAggregate : contentAggregate.findSubIdeaById(parentIdeaId),
+        cleanUp = function (json) {
+          var result = _.omit(json, 'ideas', 'id', 'attr'), index = 1, childKeys, sortedChildKeys;
+          result.attr = _.omit(json.attr, configuration.nonClonedAttributes);
+          if (_.isEmpty(result.attr)) {
+            delete result.attr;
+          }
+          if (json.ideas) {
+            childKeys = _.groupBy(_.map(_.keys(json.ideas), parseFloat), function (key) {
+              return key > 0;
+            });
+            sortedChildKeys = _.sortBy(childKeys[true], Math.abs).concat(_.sortBy(childKeys[false], Math.abs));
+            result.ideas = {};
+            _.each(sortedChildKeys, function (key) {
+              result.ideas[index++] = cleanUp(json.ideas[key]);
+            });
+          }
+          return result;
+        },
+        newIdea,
+        newRank,
+        oldPosition;
+    if (initialId) {
+      cachedId = parseInt(initialId, 10) - 1;
+    }
+    newIdea = jsonToPaste && (jsonToPaste.title || jsonToPaste.attr) && init(cleanUp(jsonToPaste), sessionFromId(initialId));
+    if (!pasteParent || !newIdea) {
+      return false;
+    }
+    if (!contentAggregate.getYsy().validator.validate("paste", pasteParent, newIdea)) return false;
+    newRank = appendSubIdea(pasteParent, newIdea);
+    if (initialId) {
+      invalidateIdCache();
+    }
+    updateAttr(newIdea, 'position');
+    logChange('paste', [parentIdeaId, jsonToPaste, newIdea.id], function () {
+      delete pasteParent.ideas[newRank];
+    }, originSession);
+    return newIdea.id;
+  };
+  contentAggregate.flip = function (ideaId) {
+    return contentAggregate.execCommand('flip', arguments);
+  };
+  commandProcessors.flip = function (originSession, ideaId) {
+    var newRank, maxRank, currentRank = contentAggregate.findChildRankById(ideaId);
+    if (!currentRank) {
+      return false;
+    }
+    maxRank = maxKey(contentAggregate.ideas, -1 * sign(currentRank));
+    newRank = maxRank - 10 * sign(currentRank);
+    reorderChild(contentAggregate, newRank, currentRank);
+    logChange('flip', [ideaId], function () {
+      reorderChild(contentAggregate, currentRank, newRank);
+    }, originSession);
+    return true;
+  };
+  contentAggregate.initialiseTitle = function (ideaId, title) {
+    return contentAggregate.execCommand('initialiseTitle', arguments);
+  };
+  commandProcessors.initialiseTitle = function (originSession, ideaId, title) {
+    var idea = findIdeaById(ideaId), originalTitle;
+    if (!idea) {
+      return false;
+    }
+    originalTitle = idea.title;
+    if (originalTitle == title) {
+      return false;
+    }
+    idea.title = title;
+    appendChange('initialiseTitle', [ideaId, title], function () {
+      idea.title = originalTitle;
+    }, originSession);
+    return true;
+  };
+  contentAggregate.updateTitle = function (ideaId, title) {
+    return contentAggregate.execCommand('updateTitle', arguments);
+  };
+  commandProcessors.updateTitle = function (originSession, ideaId, title) {
+    var idea = findIdeaById(ideaId), originalTitle;
+    if (!idea) {
+      return false;
+    }
+    originalTitle = idea.title;
+    if (originalTitle == title) {
+      return false;
+    }
+    idea.title = title;
+    logChange('updateTitle', [ideaId, title], function () {
+      idea.title = originalTitle;
+    }, originSession);
+    return true;
+  };
+  contentAggregate.addSubIdea = function (parentId, ideaTitle, optionalNewId) {
+    return contentAggregate.execCommand('addSubIdea', arguments);
+  };
+  commandProcessors.addSubIdea = function (originSession, parentId, ideaTitle, optionalNewId) {
+    var idea, parent = findIdeaById(parentId), newRank;
+    if (!parent) {
+      return false;
+    }
+    if (optionalNewId && findIdeaById(optionalNewId)) {
+      return false;
+    }
+    idea = init({
+      title: ideaTitle,
+      id: optionalNewId
+    });
+    if (!contentAggregate.getYsy().validator.validate("addSubIdea", parent, idea)) return false;
+    newRank = appendSubIdea(parent, idea);
+    logChange('addSubIdea', [parentId, ideaTitle, idea.id], function () {
+      delete parent.ideas[newRank];
+    }, originSession);
+    return idea.id;
+  };
+  contentAggregate.removeMultiple = function (subIdeaIdArray) {
+    contentAggregate.startBatch();
+    var results = _.map(subIdeaIdArray, contentAggregate.removeSubIdea);
+    contentAggregate.endBatch();
+    return results;
+  };
+  contentAggregate.removeSubIdea = function (subIdeaId) {
+    return contentAggregate.execCommand('removeSubIdea', arguments);
+  };
+  commandProcessors.removeSubIdea = function (originSession, subIdeaId) {
+    if (!contentAggregate.getYsy().validator.validate("removeSubIdea", subIdeaId, originSession)) return false;
+    var parent = contentAggregate.findParent(subIdeaId), oldRank, oldIdea, oldLinks;
+    if (parent) {
+      oldRank = parent.findChildRankById(subIdeaId);
+      oldIdea = parent.ideas[oldRank];
+      delete parent.ideas[oldRank];
+      oldLinks = contentAggregate.links;
+      contentAggregate.links = _.reject(contentAggregate.links, function (link) {
+        return link.ideaIdFrom == subIdeaId || link.ideaIdTo == subIdeaId;
+      });
+      logChange('removeSubIdea', [subIdeaId], function () {
+        parent.ideas[oldRank] = oldIdea;
+        contentAggregate.links = oldLinks;
+      }, originSession);
+      return true;
+    }
+    return false;
+  };
+  contentAggregate.insertIntermediateMultiple = function (idArray) {
+    contentAggregate.startBatch();
+    var newId = contentAggregate.insertIntermediate(idArray[0]);
+    _.each(idArray.slice(1), function (id) {
+      contentAggregate.changeParent(id, newId);
+    });
+    contentAggregate.endBatch();
+    return newId;
+  };
+  contentAggregate.insertIntermediate = function (inFrontOfIdeaId, title, optionalNewId) {
+    return contentAggregate.execCommand('insertIntermediate', arguments);
+  };
+  commandProcessors.insertIntermediate = function (originSession, inFrontOfIdeaId, title, optionalNewId) {
+    if (contentAggregate.id == inFrontOfIdeaId) {
+      return false;
+    }
+    var childRank, oldIdea, newIdea, parentIdea = contentAggregate.findParent(inFrontOfIdeaId);
+    if (!parentIdea) {
+      return false;
+    }
+    if (optionalNewId && findIdeaById(optionalNewId)) {
+      return false;
+    }
+    childRank = parentIdea.findChildRankById(inFrontOfIdeaId);
+    if (!childRank) {
+      return false;
+    }
+    oldIdea = parentIdea.ideas[childRank];
+    newIdea = init({
+      title: title,
+      id: optionalNewId
+    });
+    if (!contentAggregate.getYsy().validator.validate("insertIntermediate", parentIdea, oldIdea, newIdea)) return false;
+    parentIdea.ideas[childRank] = newIdea;
+    newIdea.ideas = {
+      1: oldIdea
+    };
+    logChange('insertIntermediate', [inFrontOfIdeaId, title, newIdea.id], function () {
+      parentIdea.ideas[childRank] = oldIdea;
+    }, originSession);
+    return newIdea.id;
+  };
+  contentAggregate.changeParent = function (ideaId, newParentId) {
+    return contentAggregate.execCommand('changeParent', arguments);
+  };
+  commandProcessors.changeParent = function (originSession, ideaId, newParentId) {
+    var oldParent, oldRank, newRank, idea, parent = findIdeaById(newParentId), oldPosition;
+    if (ideaId == newParentId) {
+      return false;
+    }
+    if (!parent) {
+      return false;
+    }
+    idea = contentAggregate.findSubIdeaById(ideaId);
+    if (!idea) {
+      return false;
+    }
+    if (idea.findSubIdeaById(newParentId)) {
+      return false;
+    }
+    if (parent.containsDirectChild(ideaId)) {
+      return false;
+    }
+    if (!contentAggregate.getYsy().validator.changeParent(idea, parent)) return false;
+    oldParent = contentAggregate.findParent(ideaId);
+    if (!oldParent) {
+      return false;
+    }
+    oldRank = oldParent.findChildRankById(ideaId);
+    newRank = appendSubIdea(parent, idea);
+    oldPosition = idea.getAttr('position');
+    updateAttr(idea, 'position');
+    delete oldParent.ideas[oldRank];
+    logChange('changeParent', [ideaId, newParentId, oldParent.id], function () {
+      updateAttr(idea, 'position', oldPosition);
+      oldParent.ideas[oldRank] = idea;
+      delete parent.ideas[newRank];
+    }, originSession);
+    return true;
+  };
+  contentAggregate.mergeAttrProperty = function (ideaId, attrName, attrPropertyName, attrPropertyValue) {
+    var val = contentAggregate.getAttrById(ideaId, attrName) || {};
+    if (attrPropertyValue) {
+      val[attrPropertyName] = attrPropertyValue;
+    } else {
+      delete val[attrPropertyName];
+    }
+    if (_.isEmpty(val)) {
+      val = false;
+    }
+    return contentAggregate.updateAttr(ideaId, attrName, val);
+  };
+  contentAggregate.updateAttr = function (ideaId, attrName, attrValue) {
+    return contentAggregate.execCommand('updateAttr', arguments);
+  };
+  commandProcessors.updateAttr = function (originSession, ideaId, attrName, attrValue) {
+    var idea = findIdeaById(ideaId), undoAction;
+    undoAction = updateAttr(idea, attrName, attrValue);
+    if (undoAction) {
+      logChange('updateAttr', [ideaId, attrName, attrValue], undoAction, originSession);
+    }
+    return !!undoAction;
+  };
+  contentAggregate.moveRelative = function (ideaId, relativeMovement) {
+    var parentIdea = contentAggregate.findParent(ideaId),
+        currentRank = parentIdea && parentIdea.findChildRankById(ideaId),
+        siblingRanks = currentRank && _.sortBy(sameSideSiblingRanks(parentIdea, currentRank), Math.abs),
+        currentIndex = siblingRanks && siblingRanks.indexOf(currentRank),
+        /* we call positionBefore, so movement down is actually 2 spaces, not 1 */
+        newIndex = currentIndex + (relativeMovement > 0 ? relativeMovement + 1 : relativeMovement),
+        beforeSibling = (newIndex >= 0) && parentIdea && siblingRanks && parentIdea.ideas[siblingRanks[newIndex]];
+    if (newIndex < 0 || !parentIdea) {
+      return false;
+    }
+    return contentAggregate.positionBefore(ideaId, beforeSibling && beforeSibling.id, parentIdea);
+  };
+  contentAggregate.positionBefore = function (ideaId, positionBeforeIdeaId, parentIdea) {
+    return contentAggregate.execCommand('positionBefore', arguments);
+  };
+  commandProcessors.positionBefore = function (originSession, ideaId, positionBeforeIdeaId, parentIdea) {
+    parentIdea = parentIdea || contentAggregate;
+    var newRank, afterRank, siblingRanks, candidateSiblings, beforeRank, maxRank, currentRank;
+    currentRank = parentIdea.findChildRankById(ideaId);
+    if (!currentRank) {
+      return _.reduce(
+          parentIdea.ideas,
+          function (result, idea) {
+            return result || commandProcessors.positionBefore(originSession, ideaId, positionBeforeIdeaId, idea);
+          },
+          false
+      );
+    }
+    if (ideaId == positionBeforeIdeaId) {
+      return false;
+    }
+    newRank = 0;
+    if (positionBeforeIdeaId) {
+      afterRank = parentIdea.findChildRankById(positionBeforeIdeaId);
+      if (!afterRank) {
+        return false;
+      }
+      siblingRanks = sameSideSiblingRanks(parentIdea, currentRank);
+      candidateSiblings = _.reject(_.sortBy(siblingRanks, Math.abs), function (k) {
+        return Math.abs(k) >= Math.abs(afterRank);
+      });
+      beforeRank = candidateSiblings.length > 0 ? _.max(candidateSiblings, Math.abs) : 0;
+      if (beforeRank == currentRank) {
+        return false;
+      }
+      newRank = beforeRank + (afterRank - beforeRank) / 2;
+    } else {
+      maxRank = maxKey(parentIdea.ideas, currentRank < 0 ? -1 : 1);
+      if (maxRank == currentRank) {
+        return false;
+      }
+      newRank = maxRank + 10 * (currentRank < 0 ? -1 : 1);
+    }
+    if (newRank == currentRank) {
+      return false;
+    }
+    reorderChild(parentIdea, newRank, currentRank);
+    logChange('positionBefore', [ideaId, positionBeforeIdeaId], function () {
+      reorderChild(parentIdea, currentRank, newRank);
+    }, originSession);
+    return true;
+  };
+  observable(contentAggregate);
+  (function () {
+    var isLinkValid = function (ideaIdFrom, ideaIdTo) {
+      var isParentChild, ideaFrom, ideaTo;
+      if (ideaIdFrom === ideaIdTo) {
+        return false;
+      }
+      ideaFrom = findIdeaById(ideaIdFrom);
+      if (!ideaFrom) {
+        return false;
+      }
+      ideaTo = findIdeaById(ideaIdTo);
+      if (!ideaTo) {
+        return false;
+      }
+      isParentChild = _.find(
+              ideaFrom.ideas,
+              function (node) {
+                return node.id === ideaIdTo;
+              }
+          ) || _.find(
+              ideaTo.ideas,
+              function (node) {
+                return node.id === ideaIdFrom;
+              }
+          );
+      if (isParentChild) {
+        return false;
+      }
+      return true;
+    };
+    contentAggregate.addLink = function (ideaIdFrom, ideaIdTo) {
+      return contentAggregate.execCommand('addLink', arguments);
+    };
+    commandProcessors.addLink = function (originSession, ideaIdFrom, ideaIdTo) {
+      var alreadyExists, link;
+      if (!isLinkValid(ideaIdFrom, ideaIdTo)) {
+        return false;
+      }
+      alreadyExists = _.find(
+          contentAggregate.links,
+          function (link) {
+            return (link.ideaIdFrom === ideaIdFrom && link.ideaIdTo === ideaIdTo) || (link.ideaIdFrom === ideaIdTo && link.ideaIdTo === ideaIdFrom);
+          }
+      );
+      if (alreadyExists) {
+        return false;
+      }
+      contentAggregate.links = contentAggregate.links || [];
+      link = {
+        ideaIdFrom: ideaIdFrom,
+        ideaIdTo: ideaIdTo,
+        attr: {
+          style: {
+            color: '#FF0000',
+            lineStyle: 'dashed'
+          }
+        }
+      };
+      contentAggregate.links.push(link);
+      logChange('addLink', [ideaIdFrom, ideaIdTo], function () {
+        contentAggregate.links.pop();
+      }, originSession);
+      return true;
+    };
+    contentAggregate.removeLink = function (ideaIdOne, ideaIdTwo) {
+      return contentAggregate.execCommand('removeLink', arguments);
+    };
+    commandProcessors.removeLink = function (originSession, ideaIdOne, ideaIdTwo) {
+      var i = 0, link;
+
+      while (contentAggregate.links && i < contentAggregate.links.length) {
+        link = contentAggregate.links[i];
+        if (String(link.ideaIdFrom) === String(ideaIdOne) && String(link.ideaIdTo) === String(ideaIdTwo)) {
+          contentAggregate.links.splice(i, 1);
+          logChange('removeLink', [ideaIdOne, ideaIdTwo], function () {
+            contentAggregate.links.push(_.clone(link));
+          }, originSession);
+          return true;
+        }
+        i += 1;
+      }
+      return false;
+    };
+    contentAggregate.getLinkAttr = function (ideaIdFrom, ideaIdTo, name) {
+      var link = _.find(
+          contentAggregate.links,
+          function (link) {
+            return link.ideaIdFrom == ideaIdFrom && link.ideaIdTo == ideaIdTo;
+          }
+      );
+      if (link && link.attr && link.attr[name]) {
+        return link.attr[name];
+      }
+      return false;
+    };
+    contentAggregate.updateLinkAttr = function (ideaIdFrom, ideaIdTo, attrName, attrValue) {
+      return contentAggregate.execCommand('updateLinkAttr', arguments);
+    };
+    commandProcessors.updateLinkAttr = function (originSession, ideaIdFrom, ideaIdTo, attrName, attrValue) {
+      var link = _.find(
+          contentAggregate.links,
+          function (link) {
+            return link.ideaIdFrom == ideaIdFrom && link.ideaIdTo == ideaIdTo;
+          }
+      ), undoAction;
+      undoAction = updateAttr(link, attrName, attrValue);
+      if (undoAction) {
+        logChange('updateLinkAttr', [ideaIdFrom, ideaIdTo, attrName, attrValue], undoAction, originSession);
+      }
+      return !!undoAction;
+    };
+  }());
+  /* undo/redo */
+  contentAggregate.resetHistory = function () {
+    eventStacks = {};
+    redoStacks = {};
+  };
+  contentAggregate.canUndo = function () {
+    return !!(eventStacks[sessionKey] && eventStacks[sessionKey].length > 0);
+  };
+  contentAggregate.canRedo = function () {
+    return !!(redoStacks[sessionKey] && redoStacks[sessionKey].length > 0);
+  };
+  contentAggregate.undo = function () {
+    return contentAggregate.execCommand('undo', arguments);
+  };
+  commandProcessors.undo = function (originSession) {
+    contentAggregate.endBatch();
+    var topEvent;
+    topEvent = eventStacks[originSession] && eventStacks[originSession].pop();
+    if (topEvent && topEvent.undoFunction) {
+      topEvent.undoFunction();
+      if (!redoStacks[originSession]) {
+        redoStacks[originSession] = [];
+      }
+      redoStacks[originSession].push(topEvent);
+      contentAggregate.dispatchEvent('changed', 'undo', [], originSession);
+      return true;
+    }
+    return false;
+  };
+  contentAggregate.redo = function () {
+    return contentAggregate.execCommand('redo', arguments);
+  };
+  commandProcessors.redo = function (originSession) {
+    contentAggregate.endBatch();
+    var topEvent;
+    topEvent = redoStacks[originSession] && redoStacks[originSession].pop();
+    if (topEvent) {
+      isRedoInProgress = true;
+      contentAggregate.execCommand(topEvent.eventMethod, topEvent.eventArgs, originSession);
+      isRedoInProgress = false;
+      return true;
+    }
+    return false;
+  };
+  contentAggregate.storeResource = function (/*resourceBody, optionalKey*/) {
+    return contentAggregate.execCommand('storeResource', arguments);
+  };
+  commandProcessors.storeResource = function (originSession, resourceBody, optionalKey) {
+    var existingId, id,
+        maxIdForSession = function () {
+          if (_.isEmpty(contentAggregate.resources)) {
+            return 0;
+          }
+          var toInt = function (string) {
+                return parseInt(string, 10);
+              },
+              keys = _.keys(contentAggregate.resources),
+              filteredKeys = sessionKey ? _.filter(keys, RegExp.prototype.test.bind(new RegExp('\\/' + sessionKey + '$'))) : keys,
+              intKeys = _.map(filteredKeys, toInt);
+          return _.isEmpty(intKeys) ? 0 : _.max(intKeys);
+        },
+        nextResourceId = function () {
+          var intId = maxIdForSession() + 1;
+          return intId + uniqueResourcePostfix;
+        };
+
+    if (!optionalKey && contentAggregate.resources) {
+      existingId = _.find(_.keys(contentAggregate.resources), function (key) {
+        return contentAggregate.resources[key] === resourceBody;
+      });
+      if (existingId) {
+        return existingId;
+      }
+    }
+    id = optionalKey || nextResourceId();
+    contentAggregate.resources = contentAggregate.resources || {};
+    contentAggregate.resources[id] = resourceBody;
+    contentAggregate.dispatchEvent('resourceStored', resourceBody, id, originSession);
+    return id;
+  };
+  contentAggregate.getResource = function (id) {
+    return contentAggregate.resources && contentAggregate.resources[id];
+  };
+  contentAggregate.hasSiblings = function (id) {
+    if (id === contentAggregate.id) {
+      return false;
+    }
+    var parent = contentAggregate.findParent(id);
+    return parent && _.size(parent.ideas) > 1;
+  };
+  if (contentAggregate.formatVersion != 2) {
+    upgrade(contentAggregate);
+    contentAggregate.formatVersion = 2;
+  }
+  init(contentAggregate);
+  return contentAggregate;
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-view.js b/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-view.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5d8b0c031dfed01b6aad2e29b695f62ae61b2d9
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-view.js
@@ -0,0 +1,1340 @@
+/*global jQuery, Color, _, MAPJS, document, window*/
+MAPJS.DOMRender = {
+  svgPixel: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"></svg>',
+  nodeCacheMark: function (idea, levelOverride) {
+    'use strict';
+    return {
+      title: idea.title,
+      icon: idea.attr && idea.attr.icon && _.pick(idea.attr.icon, 'width', 'height', 'position'),
+      collapsed: idea.attr && idea.attr.collapsed,
+      level: idea.level || levelOverride
+    };
+  },
+  dummyTextBox: jQuery('<div>').addClass('mapjs-node').css({position: 'absolute', visibility: 'hidden'}),
+  dimensionProvider: function (idea, level) {
+    'use strict';
+    /* support multiple stages? */
+    var textBox = jQuery(document).nodeWithId(idea.id),
+        translateToPixel = function () {
+          return MAPJS.DOMRender.svgPixel;
+        },
+        result;
+    if (textBox && textBox.length > 0) {
+      if (_.isEqual(textBox.data('nodeCacheMark'), MAPJS.DOMRender.nodeCacheMark(idea, level))) {
+        return _.pick(textBox.data(), 'width', 'height');
+      }
+    }
+    textBox = MAPJS.DOMRender.dummyTextBox;
+    textBox.attr('mapjs-level', level).appendTo('body').updateNodeContent(idea, translateToPixel);
+    result = {
+      width: textBox.outerWidth(true),
+      height: textBox.outerHeight(true)
+    };
+    textBox.detach();
+    return result;
+  },
+  layoutCalculator: function (contentAggregate) {
+    'use strict';
+    return MAPJS.calculateLayout(contentAggregate, MAPJS.DOMRender.dimensionProvider);
+  },
+  fixedLayout: false
+};
+MAPJS.createSVG = function (tag) {
+  'use strict';
+  return jQuery(document.createElementNS('http://www.w3.org/2000/svg', tag || 'svg'));
+};
+jQuery.fn.getBox = function () {
+  'use strict';
+  var domShape = this && this[0];
+  if (!domShape) {
+    return false;
+  }
+  return {
+    top: domShape.offsetTop,
+    left: domShape.offsetLeft,
+    width: domShape.offsetWidth,
+    height: domShape.offsetHeight
+  };
+};
+jQuery.fn.getDataBox = function () {
+  'use strict';
+  var domShapeData = this.data();
+  if (domShapeData && domShapeData.width && domShapeData.height) {
+    return {
+      top: domShapeData.y,
+      left: domShapeData.x,
+      width: domShapeData.width,
+      height: domShapeData.height
+    };
+  }
+  return this.getBox();
+};
+
+
+jQuery.fn.animateConnectorToPosition = function (animationOptions, tolerance) {
+  'use strict';
+  var element = jQuery(this),
+      shapeFrom = element.data('nodeFrom'),
+      shapeTo = element.data('nodeTo'),
+      fromBox = shapeFrom && shapeFrom.getDataBox(),
+      toBox = shapeTo && shapeTo.getDataBox(),
+      oldBox = {
+        from: shapeFrom && shapeFrom.getBox(),
+        to: shapeTo && shapeTo.getBox()
+      };
+  tolerance = tolerance || 1;
+  if (fromBox && toBox && oldBox && oldBox.from.width === fromBox.width &&
+      oldBox.to.width === toBox.width &&
+      oldBox.from.height === fromBox.height &&
+      oldBox.to.height === toBox.height &&
+      Math.abs(oldBox.from.top - oldBox.to.top - (fromBox.top - toBox.top)) < tolerance &&
+      Math.abs(oldBox.from.left - oldBox.to.left - (fromBox.left - toBox.left)) < tolerance) {
+
+    element.animate({
+      left: Math.round(Math.min(fromBox.left, toBox.left)),
+      top: Math.round(Math.min(fromBox.top, toBox.top))
+    }, animationOptions);
+    return true;
+  }
+  return false;
+};
+jQuery.fn.queueFadeOut = function (options) {
+  'use strict';
+  var element = this;
+  return element.fadeOut(_.extend({
+    complete: function () {
+      if (element.is(':focus')) {
+        element.parents('[tabindex]').focus();
+      }
+      element.remove();
+    }
+  }, options));
+};
+jQuery.fn.queueFadeIn = function (options) {
+  'use strict';
+  var element = this;
+  return element
+      .css('opacity', 0)
+      .animate(
+          {'opacity': 1},
+          _.extend({
+            complete: function () {
+              element.css('opacity', '');
+            }
+          }, options)
+      );
+};
+
+jQuery.fn.updateStage = function () {
+  'use strict';
+  var data = this.data(),
+      size = {
+        'min-width': Math.round(data.width - data.offsetX),
+        'min-height': Math.round(data.height - data.offsetY),
+        'width': Math.round(data.width - data.offsetX),
+        'height': Math.round(data.height - data.offsetY),
+        'transform-origin': 'top left',
+        'transform': 'translate3d(' + Math.round(data.offsetX) + 'px, ' + Math.round(data.offsetY) + 'px, 0)'
+      };
+  if (data.scale && data.scale !== 1) {
+    size.transform = 'scale(' + data.scale + ') translate(' + Math.round(data.offsetX) + 'px, ' + Math.round(data.offsetY) + 'px)';
+  }
+  this.css(size);
+  return this;
+};
+
+MAPJS.DOMRender.curvedPath = function (parent, child) {
+  'use strict';
+  var horizontalConnector = function (parentX, parentY, parentWidth, parentHeight, childX, childY, childWidth, childHeight) {
+        var childHorizontalOffset = parentX < childX ? 0.1 : 0.9,
+            parentHorizontalOffset = 1 - childHorizontalOffset;
+        return {
+          from: {
+            x: parentX + parentHorizontalOffset * parentWidth,
+            y: parentY + 0.5 * parentHeight
+          },
+          to: {
+            x: childX + childHorizontalOffset * childWidth,
+            y: childY + 0.5 * childHeight
+          },
+          controlPointOffset: 0
+        };
+      },
+      calculateConnector = function (parent, child) {
+        var tolerance = 10,
+            childHorizontalOffset,
+            childMid = child.top + child.height * 0.5,
+            parentMid = parent.top + parent.height * 0.5;
+        if (Math.abs(parentMid - childMid) + tolerance < Math.max(child.height, parent.height * 0.75)) {
+          return horizontalConnector(parent.left, parent.top, parent.width, parent.height, child.left, child.top, child.width, child.height);
+        }
+        childHorizontalOffset = parent.left < child.left ? 0 : 1;
+        return {
+          from: {
+            x: parent.left + 0.5 * parent.width,
+            y: parent.top + 0.5 * parent.height
+          },
+          to: {
+            x: child.left + childHorizontalOffset * child.width,
+            y: child.top + 0.5 * child.height
+          },
+          controlPointOffset: 0.75
+        };
+      },
+      position = {
+        left: Math.min(parent.left, child.left),
+        top: Math.min(parent.top, child.top)
+      },
+      calculatedConnector, offset, maxOffset;
+  position.width = Math.max(parent.left + parent.width, child.left + child.width, position.left + 1) - position.left;
+  position.height = Math.max(parent.top + parent.height, child.top + child.height, position.top + 1) - position.top;
+
+  calculatedConnector = calculateConnector(parent, child);
+  offset = calculatedConnector.controlPointOffset * (calculatedConnector.from.y - calculatedConnector.to.y);
+  maxOffset = Math.min(child.height, parent.height) * 1.5;
+  offset = Math.max(-maxOffset, Math.min(maxOffset, offset));
+  return {
+    'd': 'M' + Math.round(calculatedConnector.from.x - position.left) + ',' + Math.round(calculatedConnector.from.y - position.top) +
+    'Q' + Math.round(calculatedConnector.from.x - position.left) + ',' + Math.round(calculatedConnector.to.y - offset - position.top) + ' ' + Math.round(calculatedConnector.to.x - position.left) + ',' + Math.round(calculatedConnector.to.y - position.top),
+    // 'conn': calculatedConnector,
+    'position': position
+  };
+};
+MAPJS.DOMRender.straightPath = function (parent, child) {
+  'use strict';
+  var calculateConnector = function (parent, child) {
+        var parentPoints = [
+          {
+            x: parent.left + 0.5 * parent.width,
+            y: parent.top
+          },
+          {
+            x: parent.left + parent.width,
+            y: parent.top + 0.5 * parent.height
+          },
+          {
+            x: parent.left + 0.5 * parent.width,
+            y: parent.top + parent.height
+          },
+          {
+            x: parent.left,
+            y: parent.top + 0.5 * parent.height
+          }
+        ], childPoints = [
+          {
+            x: child.left + 0.5 * child.width,
+            y: child.top
+          },
+          {
+            x: child.left + child.width,
+            y: child.top + 0.5 * child.height
+          },
+          {
+            x: child.left + 0.5 * child.width,
+            y: child.top + child.height
+          },
+          {
+            x: child.left,
+            y: child.top + 0.5 * child.height
+          }
+        ], i, j, min = Infinity, bestParent, bestChild, dx, dy, current;
+        for (i = 0; i < parentPoints.length; i += 1) {
+          for (j = 0; j < childPoints.length; j += 1) {
+            dx = parentPoints[i].x - childPoints[j].x;
+            dy = parentPoints[i].y - childPoints[j].y;
+            current = dx * dx + dy * dy;
+            if (current < min) {
+              bestParent = i;
+              bestChild = j;
+              min = current;
+            }
+          }
+        }
+        return {
+          from: parentPoints[bestParent],
+          to: childPoints[bestChild]
+        };
+      },
+      position = {
+        left: Math.min(parent.left, child.left),
+        top: Math.min(parent.top, child.top)
+      },
+      conn = calculateConnector(parent, child);
+  position.width = Math.max(parent.left + parent.width, child.left + child.width, position.left + 1) - position.left;
+  position.height = Math.max(parent.top + parent.height, child.top + child.height, position.top + 1) - position.top;
+
+  return {
+    'd': 'M' + Math.round(conn.from.x - position.left) + ',' + Math.round(conn.from.y - position.top) + 'L' + Math.round(conn.to.x - position.left) + ',' + Math.round(conn.to.y - position.top),
+    'conn': conn,
+    'position': position
+  };
+};
+
+MAPJS.DOMRender.nodeConnectorPath = MAPJS.DOMRender.curvedPath;
+MAPJS.DOMRender.linkConnectorPath = MAPJS.DOMRender.straightPath;
+
+jQuery.fn.updateConnector = function (canUseData) {
+  'use strict';
+  return jQuery.each(this, function () {
+    var element = jQuery(this),
+        shapeFrom = element.data('nodeFrom'),
+        shapeTo = element.data('nodeTo'),
+        connection, pathElement, fromBox, toBox, changeCheck;
+    if (!shapeFrom || !shapeTo || shapeFrom.length === 0 || shapeTo.length === 0) {
+      element.hide();
+      return;
+    }
+    if (canUseData) {
+      fromBox = shapeFrom.getDataBox();
+      toBox = shapeTo.getDataBox();
+    } else {
+      fromBox = shapeFrom.getBox();
+      toBox = shapeTo.getBox();
+    }
+    changeCheck = {from: fromBox, to: toBox};
+    if (_.isEqual(changeCheck, element.data('changeCheck'))) {
+      return;
+    }
+
+    element.data('changeCheck', changeCheck);
+    connection = MAPJS.DOMRender.nodeConnectorPath(fromBox, toBox);
+    pathElement = element.find('path');
+    element.css(connection.position);
+    if (pathElement.length === 0) {
+      pathElement = MAPJS.createSVG('path').attr('class', 'mapjs-connector').appendTo(element);
+    }
+    // if only the relative position changed, do not re-update the curve!!!!
+    pathElement.attr('d',
+        connection.d
+    );
+  });
+};
+
+jQuery.fn.updateLink = function () {
+  'use strict';
+  return jQuery.each(this, function () {
+    var element = jQuery(this),
+        shapeFrom = element.data('nodeFrom'),
+        shapeTo = element.data('nodeTo'),
+        connection,
+        pathElement = element.find('path.mapjs-link'),
+        hitElement = element.find('path.mapjs-link-hit'),
+        arrowElement = element.find('path.mapjs-arrow'),
+        n = Math.tan(Math.PI / 9),
+        dashes = {
+          dashed: '8, 8',
+          solid: ''
+        },
+        attrs = _.pick(element.data(), 'lineStyle', 'arrow', 'color'),
+        fromBox, toBox, changeCheck,
+        a1x, a1y, a2x, a2y, len, iy, m, dx, dy;
+    if (!shapeFrom || !shapeTo || shapeFrom.length === 0 || shapeTo.length === 0) {
+      element.hide();
+      return;
+    }
+    fromBox = shapeFrom.getBox();
+    toBox = shapeTo.getBox();
+
+    changeCheck = {from: fromBox, to: toBox, attrs: attrs};
+    if (_.isEqual(changeCheck, element.data('changeCheck'))) {
+      return;
+    }
+
+    element.data('changeCheck', changeCheck);
+
+    connection = MAPJS.DOMRender.linkConnectorPath(fromBox, toBox);
+    element.css(connection.position);
+
+    if (pathElement.length === 0) {
+      pathElement = MAPJS.createSVG('path').attr('class', 'mapjs-link').appendTo(element);
+    }
+    pathElement.attr({
+      'd': connection.d,
+      'stroke-dasharray': dashes[attrs.lineStyle]
+    }).css('stroke', attrs.color);
+
+    if (hitElement.length === 0) {
+      hitElement = MAPJS.createSVG('path').attr('class', 'mapjs-link-hit').appendTo(element);
+    }
+    hitElement.attr({
+      'd': connection.d
+    });
+
+    if (attrs.arrow) {
+      if (arrowElement.length === 0) {
+        arrowElement = MAPJS.createSVG('path').attr('class', 'mapjs-arrow').appendTo(element);
+      }
+      len = 14;
+      dx = connection.conn.to.x - connection.conn.from.x;
+      dy = connection.conn.to.y - connection.conn.from.y;
+      if (dx === 0) {
+        iy = dy < 0 ? -1 : 1;
+        a1x = connection.conn.to.x + len * Math.sin(n) * iy;
+        a2x = connection.conn.to.x - len * Math.sin(n) * iy;
+        a1y = connection.conn.to.y - len * Math.cos(n) * iy;
+        a2y = connection.conn.to.y - len * Math.cos(n) * iy;
+      } else {
+        m = dy / dx;
+        if (connection.conn.from.x < connection.conn.to.x) {
+          len = -len;
+        }
+        a1x = connection.conn.to.x + (1 - m * n) * len / Math.sqrt((1 + m * m) * (1 + n * n));
+        a1y = connection.conn.to.y + (m + n) * len / Math.sqrt((1 + m * m) * (1 + n * n));
+        a2x = connection.conn.to.x + (1 + m * n) * len / Math.sqrt((1 + m * m) * (1 + n * n));
+        a2y = connection.conn.to.y + (m - n) * len / Math.sqrt((1 + m * m) * (1 + n * n));
+      }
+      arrowElement.attr('d',
+          'M' + Math.round(a1x - connection.position.left) + ',' + Math.round(a1y - connection.position.top) +
+          'L' + Math.round(connection.conn.to.x - connection.position.left) + ',' + Math.round(connection.conn.to.y - connection.position.top) +
+          'L' + Math.round(a2x - connection.position.left) + ',' + Math.round(a2y - connection.position.top) +
+          'Z')
+          .css('fill', attrs.color)
+          .show();
+    } else {
+      arrowElement.hide();
+    }
+
+  });
+};
+
+jQuery.fn.addNodeCacheMark = function (idea) {
+  'use strict';
+  this.data('nodeCacheMark', MAPJS.DOMRender.nodeCacheMark(idea));
+};
+
+jQuery.fn.updateNodeContent = function (nodeContent, resourceTranslator) {
+  'use strict';
+  var MAX_URL_LENGTH = 25,
+      self = jQuery(this),
+      textSpan = function () {
+        var span = self.find('[data-mapjs-role=title]');
+        if (span.length === 0) {
+          span = jQuery('<span>').attr('data-mapjs-role', 'title').appendTo(self);
+        }
+        return span;
+      },
+      applyLinkUrl = function (title) {
+        var url = MAPJS.URLHelper.getLink(title),
+            element = self.find('a.mapjs-hyperlink');
+        if (!url) {
+          element.hide();
+          return;
+        }
+        if (element.length === 0) {
+          element = jQuery('<a target="_blank" class="mapjs-hyperlink"></a>').appendTo(self);
+        }
+        element.attr('href', url).show();
+      },
+      applyLabel = function (label) {
+        var element = self.find('.mapjs-label');
+        if (!label && label !== 0) {
+          element.hide();
+          return;
+        }
+        if (element.length === 0) {
+          element = jQuery('<span class="mapjs-label"></span>').appendTo(self);
+        }
+        element.text(label).show();
+      },
+      applyAttachment = function () {
+        var attachment = nodeContent.attr && nodeContent.attr.attachment,
+            element = self.find('a.mapjs-attachment');
+        if (!attachment) {
+          element.hide();
+          return;
+        }
+        if (element.length === 0) {
+          element = jQuery('<a href="#" class="mapjs-attachment"></a>').appendTo(self).click(function () {
+            self.trigger('attachment-click');
+          });
+        }
+        element.show();
+      },
+      updateText = function (title) {
+        var text = MAPJS.URLHelper.stripLink(title) ||
+                (title.length < MAX_URL_LENGTH ? title : (title.substring(0, MAX_URL_LENGTH) + '...')),
+            nodeTextPadding = MAPJS.DOMRender.nodeTextPadding || 11,
+            element = textSpan(),
+            domElement = element[0],
+            height;
+
+        element.text(text.trim());
+        self.data('title', title);
+        element.css({'max-width': '', 'min-width': ''});
+        if ((domElement.scrollWidth - nodeTextPadding) > domElement.offsetWidth) {
+          element.css('max-width', domElement.scrollWidth + 'px');
+        } else {
+          height = domElement.offsetHeight;
+          element.css('min-width', element.css('max-width'));
+          if (domElement.offsetHeight === height) {
+            element.css('min-width', '');
+          }
+        }
+      },
+      setCollapseClass = function () {
+        if (nodeContent.attr && nodeContent.attr.collapsed) {
+          self.addClass('collapsed');
+        } else {
+          self.removeClass('collapsed');
+        }
+      },
+      foregroundClass = function (backgroundColor) {
+        /*jslint newcap:true*/
+        var luminosity = Color(backgroundColor).mix(Color('#EEEEEE')).luminosity();
+        if (luminosity < 0.5) {
+          return 'mapjs-node-dark';
+        } else if (luminosity < 0.9) {
+          return 'mapjs-node-light';
+        }
+        return 'mapjs-node-white';
+      },
+      setColors = function () {
+        var fromStyle = nodeContent.attr && nodeContent.attr.style && nodeContent.attr.style.background;
+        if (fromStyle === 'false' || fromStyle === 'transparent') {
+          fromStyle = false;
+        }
+        self.removeClass('mapjs-node-dark mapjs-node-white mapjs-node-light');
+        if (fromStyle) {
+          self.css('background-color', fromStyle);
+          self.addClass(foregroundClass(fromStyle));
+        } else {
+          self.css('background-color', '');
+        }
+      },
+      setIcon = function (icon) {
+        var textBox = textSpan(),
+            textHeight,
+            textWidth,
+            maxTextWidth,
+            padding,
+            selfProps = {
+              'min-height': '',
+              'min-width': '',
+              'background-image': '',
+              'background-repeat': '',
+              'background-size': '',
+              'background-position': ''
+            },
+            textProps = {
+              'margin-top': '',
+              'margin-left': ''
+            };
+        self.css({padding: ''});
+        if (icon) {
+          padding = parseInt(self.css('padding-left'), 10);
+          textHeight = textBox.outerHeight();
+          textWidth = textBox.outerWidth();
+          maxTextWidth = parseInt(textBox.css('max-width'), 10);
+          _.extend(selfProps, {
+            'background-image': 'url("' + (resourceTranslator ? resourceTranslator(icon.url) : icon.url) + '")',
+            'background-repeat': 'no-repeat',
+            'background-size': icon.width + 'px ' + icon.height + 'px',
+            'background-position': 'center center'
+          });
+          if (icon.position === 'top' || icon.position === 'bottom') {
+            if (icon.position === 'top') {
+              selfProps['background-position'] = 'center ' + padding + 'px';
+            } else if (MAPJS.DOMRender.fixedLayout) {
+              selfProps['background-position'] = 'center ' + (padding + textHeight) + 'px';
+            } else {
+              selfProps['background-position'] = 'center ' + icon.position + ' ' + padding + 'px';
+            }
+
+            selfProps['padding-' + icon.position] = icon.height + (padding * 2);
+            selfProps['min-width'] = icon.width;
+            if (icon.width > maxTextWidth) {
+              textProps['margin-left'] = (icon.width - maxTextWidth) / 2;
+            }
+          } else if (icon.position === 'left' || icon.position === 'right') {
+            if (icon.position === 'left') {
+              selfProps['background-position'] = padding + 'px center';
+            } else if (MAPJS.DOMRender.fixedLayout) {
+              selfProps['background-position'] = (textWidth + (2 * padding)) + 'px center ';
+            } else {
+              selfProps['background-position'] = icon.position + ' ' + padding + 'px center';
+            }
+
+            selfProps['padding-' + icon.position] = icon.width + (padding * 2);
+            if (icon.height > textHeight) {
+              textProps['margin-top'] = (icon.height - textHeight) / 2;
+              selfProps['min-height'] = icon.height;
+            }
+          } else {
+            if (icon.height > textHeight) {
+              textProps['margin-top'] = (icon.height - textHeight) / 2;
+              selfProps['min-height'] = icon.height;
+            }
+            selfProps['min-width'] = icon.width;
+            if (icon.width > maxTextWidth) {
+              textProps['margin-left'] = (icon.width - maxTextWidth) / 2;
+            }
+          }
+        }
+        self.css(selfProps);
+        textBox.css(textProps);
+      };
+  self.attr('mapjs-level', nodeContent.level);
+  updateText(nodeContent.title);
+  applyLinkUrl(nodeContent.title);
+  applyLabel(nodeContent.label);
+  applyAttachment();
+  self.data({
+    'x': Math.round(nodeContent.x),
+    'y': Math.round(nodeContent.y),
+    'width': Math.round(nodeContent.width),
+    'height': Math.round(nodeContent.height),
+    'nodeId': nodeContent.id
+  })
+      .addNodeCacheMark(nodeContent);
+  setColors();
+  setIcon(nodeContent.attr && nodeContent.attr.icon);
+  setCollapseClass();
+  return self;
+};
+jQuery.fn.placeCaretAtEnd = function () {
+  'use strict';
+  var el = this[0],
+      range, sel, textRange;
+  if (window.getSelection && document.createRange) {
+    range = document.createRange();
+    range.selectNodeContents(el);
+    range.collapse(false);
+    sel = window.getSelection();
+    sel.removeAllRanges();
+    sel.addRange(range);
+  } else if (document.body.createTextRange) {
+    textRange = document.body.createTextRange();
+    textRange.moveToElementText(el);
+    textRange.collapse(false);
+    textRange.select();
+  }
+};
+jQuery.fn.selectAll = function () {
+  'use strict';
+  var el = this[0],
+      range, sel, textRange;
+  if (window.getSelection && document.createRange) {
+    range = document.createRange();
+    range.selectNodeContents(el);
+    sel = window.getSelection();
+    sel.removeAllRanges();
+    sel.addRange(range);
+  } else if (document.body.createTextRange) {
+    textRange = document.body.createTextRange();
+    textRange.moveToElementText(el);
+    textRange.select();
+  }
+};
+jQuery.fn.innerText = function () {
+  'use strict';
+  var htmlContent = this.html(),
+      containsBr = /<br\/?>/.test(htmlContent),
+      containsDiv = /<div>/.test(htmlContent);
+  if (containsDiv && this[0].innerText) { /* broken safari jquery text */
+    return this[0].innerText.trim();
+  } else if (containsBr) { /*broken firefox innerText */
+    return htmlContent.replace(/<br\/?>/gi, '\n').replace(/(<([^>]+)>)/gi, '');
+  }
+  return this.text();
+};
+jQuery.fn.editNode = function (shouldSelectAll) {
+  'use strict';
+  var node = this,
+      textBox = this.find('[data-mapjs-role=title]'),
+      unformattedText = this.data('title'),
+      originalText = textBox.text(),
+      result = jQuery.Deferred(),
+      clear = function () {
+        detachListeners();
+        textBox.css('word-break', '');
+        textBox.removeAttr('contenteditable');
+        node.shadowDraggable();
+      },
+      finishEditing = function () {
+        var content = textBox.innerText();
+        if (content === unformattedText) {
+          return cancelEditing();
+        }
+        clear();
+        result.resolve(content);
+      },
+      cancelEditing = function () {
+        clear();
+        textBox.text(originalText);
+        result.reject();
+      },
+      keyboardEvents = function (e) {
+        var ENTER_KEY_CODE = 13,
+            ESC_KEY_CODE = 27,
+            TAB_KEY_CODE = 9,
+            S_KEY_CODE = 83,
+            Z_KEY_CODE = 90;
+        if (e.shiftKey && e.which === ENTER_KEY_CODE) {
+          return; // allow shift+enter to break lines
+        } else if (e.which === ENTER_KEY_CODE) {
+          finishEditing();
+          e.stopPropagation();
+        } else if (e.which === ESC_KEY_CODE) {
+          cancelEditing();
+          e.stopPropagation();
+        } else if (e.which === TAB_KEY_CODE || (e.which === S_KEY_CODE && (e.metaKey || e.ctrlKey) && !e.altKey)) {
+          finishEditing();
+          e.preventDefault();
+          /* stop focus on another object */
+        } else if (!e.shiftKey && e.which === Z_KEY_CODE && (e.metaKey || e.ctrlKey) && !e.altKey) { /* undo node edit on ctrl+z if text was not changed */
+          if (textBox.text() === unformattedText) {
+            cancelEditing();
+          }
+          e.stopPropagation();
+        }
+      },
+      attachListeners = function () {
+        textBox.on('blur', finishEditing).on('keydown', keyboardEvents);
+      },
+      detachListeners = function () {
+        textBox.off('blur', finishEditing).off('keydown', keyboardEvents);
+      };
+  attachListeners();
+  if (unformattedText !== originalText) { /* links or some other potential formatting issues */
+    textBox.css('word-break', 'break-all');
+  }
+  textBox.text(unformattedText).attr('contenteditable', true).focus();
+  if (shouldSelectAll) {
+    textBox.selectAll();
+  } else if (unformattedText) {
+    textBox.placeCaretAtEnd();
+  }
+  node.shadowDraggable({disable: true});
+  return result.promise();
+};
+jQuery.fn.updateReorderBounds = function (border, box) {
+  'use strict';
+  var element = this;
+  if (!border) {
+    element.hide();
+    return;
+  }
+  element.show();
+  element.attr('mapjs-edge', border.edge);
+  element.css({
+    top: box.y + box.height / 2 - element.height() / 2,
+    left: border.x - (border.edge === 'left' ? element.width() : 0)
+  });
+
+};
+
+(function () {
+  'use strict';
+  var cleanDOMId = function (s) {
+        return s.replace(/[^A-Za-z0-9_-]/g, '_');
+      },
+      connectorKey = function (connectorObj) {
+        return cleanDOMId('connector_' + connectorObj.from + '_' + connectorObj.to);
+      },
+      linkKey = function (linkObj) {
+        return cleanDOMId('link_' + linkObj.ideaIdFrom + '_' + linkObj.ideaIdTo);
+      },
+      nodeKey = function (id) {
+        return cleanDOMId('node_' + id);
+      };
+
+  jQuery.fn.createNode = function (node) {
+    return jQuery('<div>')
+        .attr({'id': nodeKey(node.id), 'tabindex': 0, 'data-mapjs-role': 'node'})
+        .css({display: 'block', position: 'absolute'})
+        .addClass('mapjs-node')
+        .appendTo(this);
+  };
+  jQuery.fn.createConnector = function (connector) {
+    return MAPJS.createSVG()
+        .attr({'id': connectorKey(connector), 'data-mapjs-role': 'connector', 'class': 'mapjs-draw-container'})
+        .data({'nodeFrom': this.nodeWithId(connector.from), 'nodeTo': this.nodeWithId(connector.to)})
+        .appendTo(this);
+  };
+  jQuery.fn.createLink = function (l) {
+    var defaults = _.extend({color: 'red', lineStyle: 'dashed'}, l.attr && l.attr.style);
+    return MAPJS.createSVG()
+        .attr({
+          'id': linkKey(l),
+          'data-mapjs-role': 'link',
+          'class': 'mapjs-draw-container'
+        })
+        .data({'nodeFrom': this.nodeWithId(l.ideaIdFrom), 'nodeTo': this.nodeWithId(l.ideaIdTo)})
+        .data(defaults)
+        .appendTo(this);
+  };
+  jQuery.fn.nodeWithId = function (id) {
+    return this.find('#' + nodeKey(id));
+  };
+  jQuery.fn.findConnector = function (connectorObj) {
+    return this.find('#' + connectorKey(connectorObj));
+  };
+  jQuery.fn.findLink = function (linkObj) {
+    return this.find('#' + linkKey(linkObj));
+  };
+  jQuery.fn.createReorderBounds = function () {
+    var result = jQuery('<div>').attr({
+      'data-mapjs-role': 'reorder-bounds',
+      'class': 'mapjs-reorder-bounds'
+    }).hide().css('position', 'absolute').appendTo(this);
+    return result;
+  };
+})();
+
+MAPJS.DOMRender.viewController = function (mapModel, stageElement, touchEnabled, imageInsertController, resourceTranslator, options) {
+  'use strict';
+  var viewPort = stageElement.parent(),
+      connectorsForAnimation = [],
+      linksForAnimation = [],
+      nodeAnimOptions = {duration: 400, queue: 'nodeQueue', easing: 'linear'},
+      reorderBounds = mapModel.isEditingEnabled() ? stageElement.createReorderBounds() : jQuery('<div>'),
+      toolbarHeight = resourceTranslator.$menu.outerHeight(),
+      getViewPortDimensions = function () {
+        if (viewPortDimensions) {
+          return viewPortDimensions;
+        }
+        viewPortDimensions = {
+          left: viewPort.scrollLeft(),
+          top: viewPort.scrollTop(),
+          innerWidth: viewPort.innerWidth(),
+          innerHeight: viewPort.innerHeight()
+        };
+        return viewPortDimensions;
+      },
+      stageToViewCoordinates = function (x, y) {
+        var stage = stageElement.data(),
+            scrollPosition = getViewPortDimensions();
+        return {
+          x: stage.scale * (x + stage.offsetX) - scrollPosition.left,
+          y: stage.scale * (y + stage.offsetY) - scrollPosition.top
+        };
+      },
+      stageToWindowTop = function (y) {
+        var stage = stageElement.data();
+        return stage.scale * (y + stage.offsetY) - $(document).scrollTop() + viewPort.offset().top;
+      },
+      viewToStageCoordinates = function (x, y) {
+        var stage = stageElement.data(),
+            scrollPosition = getViewPortDimensions();
+        return {
+          x: (scrollPosition.left + x) / stage.scale - stage.offsetX,
+          y: (scrollPosition.top + y) / stage.scale - stage.offsetY
+        };
+      },
+      dirtyNodesToUpdate=[],
+      updateScreenCoordinates = function () {
+        var element = jQuery(this);
+        if(stageElement.data('dirty')){
+          dirtyNodesToUpdate.push(element);
+          return;
+        }
+        element.css({
+          'left': element.data('x'),
+          'top': element.data('y')
+        }).trigger('mapjs:move');
+      },
+      updateDirtyStage = function(){
+        if(stageElement.data('dirty')){
+          stageElement.updateStage();
+          stageElement.data('dirty',false);
+        }
+        _.each(dirtyNodesToUpdate,function(element){
+          element.css({
+            'left': element.data('x'),
+            'top': element.data('y')
+          }).trigger('mapjs:move');
+        });
+        dirtyNodesToUpdate=[];
+      },
+      animateToPositionCoordinates = function () {
+        var element = jQuery(this);
+        element.clearQueue(nodeAnimOptions.queue).animate({
+          'left': element.data('x'),
+          'top': element.data('y'),
+          'opacity': 1 /* previous animation can be cancelled with clearqueue, so ensure it gets visible */
+        }, _.extend({
+          complete: function () {
+            element.css('opacity', '');
+            element.each(updateScreenCoordinates);
+          }
+        }, nodeAnimOptions)).trigger('mapjs:animatemove');
+      },
+      ensureSpaceForPoint = function (x, y) {/* in stage coordinates */
+        var stage = stageElement.data();
+        if (x < -1 * stage.offsetX) {
+          stage.width = stage.width - stage.offsetX - x;
+          stage.offsetX = -1 * x;
+          stage.dirty = true;
+        }
+        if (y < -1 * stage.offsetY) {
+          stage.height = stage.height - stage.offsetY - y;
+          stage.offsetY = -1 * y;
+          stage.dirty = true;
+        }
+        if (x > stage.width - stage.offsetX) {
+          stage.width = stage.offsetX + x;
+          stage.dirty = true;
+        }
+        if (y > stage.height - stage.offsetY) {
+          stage.height = stage.offsetY + y;
+          stage.dirty = true;
+        }
+      },
+      ensureSpaceForNode = function () {
+        return jQuery(this).each(function () {
+          var node = jQuery(this).data(),
+              margin = MAPJS.DOMRender.stageMargin || {top: 0, left: 0, bottom: 0, right: 0};
+          /* sequence of calculations is important because maxX and maxY take into consideration the new offsetX snd offsetY */
+          ensureSpaceForPoint(node.x - margin.left, node.y - margin.top);
+          ensureSpaceForPoint(node.x + node.width + margin.right, node.y + node.height + margin.bottom);
+        });
+      },
+      centerViewOn = function (x, y, animate) { /*in the stage coordinate system*/
+        var stage = stageElement.data(),
+            windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
+            viewPortCenter = {
+              x: viewPort.innerWidth() / 2,
+              y: windowHeight / 2 - viewPort.offset().top
+            },
+            newLeftScroll, newTopScroll,
+            margin = MAPJS.DOMRender.stageVisibilityMargin || {top: 0, left: 0, bottom: 0, right: 0};
+        ensureSpaceForPoint(x - viewPortCenter.x / stage.scale, y - viewPortCenter.y / stage.scale);
+        ensureSpaceForPoint(x + viewPortCenter.x / stage.scale - margin.left, y + viewPortCenter.y / stage.scale - margin.top);
+        updateDirtyStage();
+        newLeftScroll = stage.scale * (x + stage.offsetX) - viewPortCenter.x;
+        newTopScroll = stage.scale * (y + stage.offsetY) - viewPortCenter.y;
+        viewPort.finish();
+        if (animate) {
+          viewPort.animate({
+            scrollLeft: newLeftScroll
+          }, {
+            duration: 400
+          });
+          $("html, body").animate({
+            scrollTop: newTopScroll
+          }, {
+            duration: 400
+          });
+        } else {
+          viewPort.scrollLeft(newLeftScroll);
+          $(document).scrollTop(newTopScroll);
+        }
+      },
+      stagePointAtViewportCenter = function () {
+        return viewToStageCoordinates(viewPort.innerWidth() / 2, viewPort.innerHeight() / 2);
+      },
+      ensureNodeVisible = function (domElement) {
+        if (!domElement || domElement.length === 0) {
+          return;
+        }
+        viewPort.finish();
+        var node = domElement.data(),
+            nodeTopLeft = stageToViewCoordinates(node.x, node.y),
+            nodeBottomRight = stageToViewCoordinates(node.x + node.width, node.y + node.height),
+            windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
+            scrollLeft = null,
+            scrollTop = null,
+            margin = MAPJS.DOMRender.stageVisibilityMargin || {top: 10, left: 10, bottom: 10, right: 10};
+        nodeTopLeft.y = stageToWindowTop(node.y);
+        nodeBottomRight.y = stageToWindowTop(node.y + node.height);
+        if ((nodeTopLeft.x - margin.left) < 0) {
+          scrollLeft = viewPort.scrollLeft() + nodeTopLeft.x - margin.left;
+        } else if ((nodeBottomRight.x + margin.right) > viewPort.innerWidth()) {
+          scrollLeft = viewPort.scrollLeft() + nodeBottomRight.x - viewPort.innerWidth() + margin.right;
+        }
+        if ((nodeTopLeft.y - margin.top) < toolbarHeight) {
+          scrollTop = $(document).scrollTop() + nodeTopLeft.y - margin.top - toolbarHeight;
+        } else if ((nodeBottomRight.y + margin.bottom) > windowHeight) {
+          scrollTop = $(document).scrollTop() + nodeBottomRight.y - windowHeight + margin.bottom;
+        }
+        if (scrollLeft !== null) {
+          viewPort[0].scrollLeft = scrollLeft;
+          //viewPort.animate({scrollLeft: scrollLeft}, {duration: 100});
+        }
+        if (scrollTop !== null) {
+          $("html, body").scrollTop(scrollTop);
+          // $("html, body").animate({scrollTop: scrollTop}, {duration: 100});
+
+        }
+      },
+      viewportCoordinatesForPointEvent = function (evt) {
+        var dropPosition = (evt && evt.gesture && evt.gesture.center) || evt,
+            vpOffset = viewPort.offset(),
+            result;
+        if (dropPosition) {
+          result = {
+            x: dropPosition.pageX - vpOffset.left,
+            y: dropPosition.pageY - vpOffset.top
+          };
+          if (result.x >= 0 && result.x <= viewPort.innerWidth() && result.y >= 0 && result.y <= viewPort.innerHeight()) {
+            return result;
+          }
+        }
+      },
+      stagePositionForPointEvent = function (evt) {
+        var viewportDropCoordinates = viewportCoordinatesForPointEvent(evt);
+        if (viewportDropCoordinates) {
+          return viewToStageCoordinates(viewportDropCoordinates.x, viewportDropCoordinates.y);
+        }
+      },
+      clearCurrentDroppable = function () {
+        if (currentDroppable || currentDroppable === false) {
+          jQuery('.mapjs-node').removeClass('droppable');
+          currentDroppable = undefined;
+        }
+      },
+      showDroppable = function (nodeId) {
+        stageElement.nodeWithId(nodeId).addClass('droppable');
+        currentDroppable = nodeId;
+      },
+      currentDroppable = false,
+      viewPortDimensions,
+      withinReorderBoundary = function (boundaries, box) {
+        if (_.isEmpty(boundaries)) {
+          return false;
+        }
+        if (!box) {
+          return false;
+        }
+        var closeTo = function (reorderBoundary) {
+          var nodeX = box.x;
+          if (reorderBoundary.edge === 'right') {
+            nodeX += box.width;
+          }
+          return Math.abs(nodeX - reorderBoundary.x) < reorderBoundary.margin * 2 &&
+              box.y < reorderBoundary.maxY &&
+              box.y > reorderBoundary.minY;
+        };
+        return _.find(boundaries, closeTo);
+      };
+
+
+  viewPort.on('scroll', function () {
+    viewPortDimensions = undefined;
+  });
+  if (imageInsertController) {
+    imageInsertController.addEventListener('imageInserted', function (dataUrl, imgWidth, imgHeight, evt) {
+      var point = stagePositionForPointEvent(evt);
+      mapModel.dropImage(dataUrl, imgWidth, imgHeight, point && point.x, point && point.y);
+    });
+  }
+  mapModel.addEventListener('nodeCreated', function (node) {
+    var currentReorderBoundary,
+        element = stageElement.createNode(node)
+            .queueFadeIn(nodeAnimOptions)
+            .updateNodeContent(node, resourceTranslator)
+            .on('tap', function (evt) {
+
+              var realEvent = (evt.gesture && evt.gesture.srcEvent) || evt;
+              if (realEvent.button && realEvent.button !== -1) {
+                return;
+              }
+              mapModel.clickNode(node.id, realEvent);
+              if (evt) {
+                evt.stopPropagation();
+              }
+              if (evt && evt.gesture) {
+                evt.gesture.stopPropagation();
+              }
+
+            })
+            .on('doubletap', function (event) {
+              if (event) {
+                event.stopPropagation();
+                if (event.gesture) {
+                  event.gesture.stopPropagation();
+                }
+              }
+              if (!mapModel.isEditingEnabled()) {
+                mapModel.toggleCollapse('mouse');
+                return;
+              }
+              mapModel.editNode('mouse');
+            })
+            .on('attachment-click', function () {
+              mapModel.openAttachment('mouse', node.id);
+            })
+            .each(ensureSpaceForNode)
+            .each(updateScreenCoordinates)
+            .on('mm:start-dragging mm:start-dragging-shadow', function () {
+              mapModel.selectNode(node.id);
+              currentReorderBoundary = mapModel.getReorderBoundary(node.id);
+              element.addClass('dragging');
+            })
+            .on('mm:drag', function (evt) {
+              var dropCoords = stagePositionForPointEvent(evt),
+                  currentPosition = evt.currentPosition && stagePositionForPointEvent({
+                        pageX: evt.currentPosition.left,
+                        pageY: evt.currentPosition.top
+                      }),
+                  nodeId,
+                  hasShift = evt && evt.gesture && evt.gesture.srcEvent && evt.gesture.srcEvent.shiftKey,
+                  border;
+              if (!dropCoords) {
+                clearCurrentDroppable();
+                return;
+              }
+              //ysy.log.debug("Diff={x:"+(currentPosition.x-dropCoords.x)+",y:"+(currentPosition.y-dropCoords.y)+"} \
+              //evtCurr="+JSON.stringify(evt.currentPosition)+"\
+              //curr="+JSON.stringify(currentPosition)+"\
+              //drop="+JSON.stringify(dropCoords),"mm:drag");
+              nodeId = mapModel.getNodeIdAtPosition(dropCoords.x, dropCoords.y);
+              if (!hasShift && !nodeId && currentPosition) {
+                currentPosition.width = element.outerWidth();
+                currentPosition.height = element.outerHeight();
+                border = withinReorderBoundary(currentReorderBoundary, currentPosition);
+                reorderBounds.updateReorderBounds(border, currentPosition);
+              } else {
+                reorderBounds.hide();
+              }
+              if (!nodeId || nodeId === node.id) {
+                clearCurrentDroppable();
+              } else if (nodeId !== currentDroppable) {
+                clearCurrentDroppable();
+                if (nodeId) {
+                  showDroppable(nodeId);
+                }
+              }
+            })
+            .on('contextmenu', function (event) {
+              mapModel.selectNode(node.id, false, event.shiftKey || event.ctrlKey);
+              if (mapModel.requestContextMenu(event.pageX, event.pageY)) {
+                event.preventDefault();
+                return false;
+              }
+            })
+            .on('mm:stop-dragging', function (evt) {
+              element.removeClass('dragging');
+              reorderBounds.hide();
+              var isShift = evt && evt.gesture && evt.gesture.srcEvent && evt.gesture.srcEvent.shiftKey,
+                  stageDropCoordinates = stagePositionForPointEvent(evt),
+                  nodeAtDrop, finalPosition, dropResult, manualPosition, vpCenter;
+              clearCurrentDroppable();
+              if (!stageDropCoordinates) {
+                return;
+              }
+              nodeAtDrop = mapModel.getNodeIdAtPosition(stageDropCoordinates.x, stageDropCoordinates.y);
+              finalPosition = stagePositionForPointEvent({pageX: evt.finalPosition.left, pageY: evt.finalPosition.top});
+              if (!finalPosition) return false;
+              if (nodeAtDrop && nodeAtDrop !== node.id) {
+                dropResult = mapModel.dropNode(node.id, nodeAtDrop, !!isShift);
+              } else if (node.level > 1) {
+                finalPosition.width = element.outerWidth();
+                finalPosition.height = element.outerHeight();
+                manualPosition = (!!isShift) || !withinReorderBoundary(currentReorderBoundary, finalPosition);
+                dropResult = mapModel.positionNodeAt(node.id, finalPosition.x, finalPosition.y, manualPosition);
+              } else if (node.level === 1 && evt.gesture) {
+                vpCenter = stagePointAtViewportCenter();
+                vpCenter.x -= evt.gesture.deltaX || 0;
+                vpCenter.y -= evt.gesture.deltaY || 0;
+                centerViewOn(vpCenter.x, vpCenter.y, true);
+                dropResult = true;
+              } else {
+                dropResult = false;
+              }
+              return dropResult;
+            })
+            .on('mm:cancel-dragging', function () {
+              clearCurrentDroppable();
+              element.removeClass('dragging');
+              reorderBounds.hide();
+            });
+    if (touchEnabled) {
+      element.on('hold', function (evt) {
+        var realEvent = (evt.gesture && evt.gesture.srcEvent) || evt;
+        mapModel.clickNode(node.id, realEvent);
+        if (mapModel.requestContextMenu(evt.gesture.center.pageX, evt.gesture.center.pageY)) {
+          evt.preventDefault();
+          if (evt.gesture) {
+            evt.gesture.preventDefault();
+            evt.gesture.stopPropagation();
+          }
+          return false;
+        }
+      });
+    }
+    // element.css('min-width', element.css('width'));
+    if (mapModel.isEditingEnabled()) {
+      element.shadowDraggable();
+    }
+  });
+  mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
+    var node = stageElement.nodeWithId(ideaId);
+    if (isSelected) {
+      node.addClass('selected');
+      ensureNodeVisible(node);
+    } else {
+      node.removeClass('selected');
+    }
+  });
+  mapModel.addEventListener('nodeRemoved', function (node) {
+    stageElement.nodeWithId(node.id).queueFadeOut(nodeAnimOptions);
+  });
+  mapModel.addEventListener('nodeMoved', function (node /*, reason*/) {
+    var currentViewPortDimensions = getViewPortDimensions(),
+        //nodeDomData = {x:stageElement.nodeWithId(node.id).data('x'),y:stageElement.nodeWithId(node.id).data('y')},
+        nodeDom = stageElement.nodeWithId(node.id).data({
+          'x': Math.round(node.x),
+          'y': Math.round(node.y)
+        }).each(ensureSpaceForNode),
+        screenTopLeft = stageToViewCoordinates(Math.round(node.x), Math.round(node.y)),
+        screenBottomRight = stageToViewCoordinates(Math.round(node.x + node.width), Math.round(node.y + node.height));
+    //ysy.log.debug("VIEW: "+node.title+" old=["+nodeDomData.x+","+nodeDomData.y+"] new=["+nodeDom.data('x')+","+nodeDom.data('y')+"]");
+    if (screenBottomRight.x < 0 || screenBottomRight.y < 0 || screenTopLeft.x > currentViewPortDimensions.innerWidth || screenTopLeft.y > currentViewPortDimensions.innerHeight) {
+      nodeDom.each(updateScreenCoordinates);
+    } else {
+      nodeDom.each(animateToPositionCoordinates);
+    }
+  });
+  mapModel.addEventListener('nodeTitleChanged nodeAttrChanged nodeLabelChanged', function (n) {
+    stageElement.nodeWithId(n.id).updateNodeContent(n, resourceTranslator);
+  });
+  mapModel.addEventListener('connectorCreated', function (connector) {
+    var element = stageElement.createConnector(connector).queueFadeIn(nodeAnimOptions).updateConnector(true);
+    stageElement.nodeWithId(connector.from).add(stageElement.nodeWithId(connector.to))
+        .on('mapjs:move', function () {
+          element.updateConnector(true);
+        })
+        .on('mm:drag', function () {
+          element.updateConnector();
+        })
+        .on('mapjs:animatemove', function () {
+          connectorsForAnimation.push(element);
+        });
+  });
+  mapModel.addEventListener('connectorRemoved', function (connector) {
+    stageElement.findConnector(connector).queueFadeOut(nodeAnimOptions);
+  });
+  mapModel.addEventListener('linkCreated', function (l) {
+    var link = stageElement.createLink(l).queueFadeIn(nodeAnimOptions).updateLink();
+    link.find('.mapjs-link-hit').on('tap', function (event) {
+      mapModel.selectLink('mouse', l, {x: event.gesture.center.pageX, y: event.gesture.center.pageY});
+      event.stopPropagation();
+      event.gesture.stopPropagation();
+    });
+    stageElement.nodeWithId(l.ideaIdFrom).add(stageElement.nodeWithId(l.ideaIdTo))
+        .on('mapjs:move mm:drag', function () {
+          link.updateLink();
+        })
+        .on('mapjs:animatemove', function () {
+          linksForAnimation.push(link);
+        });
+
+  });
+  mapModel.addEventListener('linkRemoved', function (l) {
+    stageElement.findLink(l).queueFadeOut(nodeAnimOptions);
+  });
+  mapModel.addEventListener('mapScaleChanged', function (scaleMultiplier /*, zoomPoint */) {
+    var currentScale = stageElement.data('scale'),
+        targetScale = Math.max(Math.min(currentScale * scaleMultiplier, 5), 0.2),
+        currentCenter = stagePointAtViewportCenter();
+    if (currentScale === targetScale) {
+      return;
+    }
+    stageElement.data('scale', targetScale).updateStage();
+    centerViewOn(currentCenter.x, currentCenter.y);
+  });
+  mapModel.addEventListener('nodeVisibilityRequested', function (ideaId) {
+    var id = ideaId || mapModel.getCurrentlySelectedIdeaId(),
+        node = stageElement.nodeWithId(id);
+    if (node) {
+      ensureNodeVisible(node);
+      viewPort.finish();
+    }
+
+  });
+  mapModel.addEventListener('nodeFocusRequested', function (ideaId) {
+    var node = stageElement.nodeWithId(ideaId).data(),
+        nodeCenterX = node.x + node.width / 2,
+        nodeCenterY = node.y + node.height / 2;
+    if (stageElement.data('scale') !== 1) {
+      stageElement.data('scale', 1).updateStage();
+    }
+    centerViewOn(nodeCenterX, nodeCenterY, true);
+  });
+  mapModel.addEventListener('mapViewResetRequested', function () {
+    stageElement.data({'scale': 1, 'height': 0, 'width': 0, 'offsetX': 0, 'offsetY': 0}).updateStage();
+    stageElement.children().andSelf().finish(nodeAnimOptions.queue);
+    jQuery(stageElement).find('.mapjs-node').each(ensureSpaceForNode);
+    updateDirtyStage();
+    jQuery(stageElement).find('[data-mapjs-role=connector]').updateConnector(true);
+    jQuery(stageElement).find('[data-mapjs-role=link]').updateLink();
+    centerViewOn(0, 0);
+    viewPort.focus();
+  });
+  mapModel.addEventListener('layoutChangeStarting', function () {
+    viewPortDimensions = undefined;
+    stageElement.children().finish(nodeAnimOptions.queue);
+    stageElement.finish(nodeAnimOptions.queue);
+  });
+  mapModel.addEventListener('layoutChangeComplete', function () {
+    var connectorGroupClone = [], linkGroupClone = [];
+
+    _.each(connectorsForAnimation,function (connector) {
+      if (!jQuery(connector).animateConnectorToPosition(nodeAnimOptions, 2)) {
+        connectorGroupClone.push(connector);
+      }
+    });
+    _.each(linksForAnimation, function (link) {
+      if (!jQuery(link).animateConnectorToPosition(nodeAnimOptions, 2)) {
+        linkGroupClone.push(link);
+      }
+    });
+    connectorsForAnimation = [];
+    linksForAnimation = [];
+    stageElement.animate({'opacity': 1}, _.extend({
+      progress: function () {
+        $(connectorGroupClone).updateConnector();
+        $(linkGroupClone).updateLink();
+      }
+    }, nodeAnimOptions));
+    ensureNodeVisible(stageElement.nodeWithId(mapModel.getCurrentlySelectedIdeaId()));
+    updateDirtyStage();
+    stageElement.children().dequeue(nodeAnimOptions.queue);
+    stageElement.dequeue(nodeAnimOptions.queue);
+  });
+
+  /* editing */
+  if (!options || !options.inlineEditingDisabled) {
+    mapModel.addEventListener('nodeEditRequested', function (nodeId, shouldSelectAll, editingNew) {
+      var editingElement = stageElement.nodeWithId(nodeId);
+      mapModel.setInputEnabled(false);
+      viewPort.finish();
+      /* close any pending animations */
+      editingElement.editNode(shouldSelectAll).done(
+          function (newText) {
+            mapModel.setInputEnabled(true);
+            mapModel.updateTitle(nodeId, newText, editingNew);
+            editingElement.focus();
+
+          }).fail(function () {
+        mapModel.setInputEnabled(true);
+        if (editingNew) {
+          mapModel.undo('internal');
+        }
+        editingElement.focus();
+      });
+    });
+  }
+  mapModel.addEventListener('addLinkModeToggled', function (isOn) {
+    if (isOn) {
+      stageElement.addClass('mapjs-add-link');
+    } else {
+      stageElement.removeClass('mapjs-add-link');
+    }
+  });
+  mapModel.addEventListener('linkAttrChanged', function (l) {
+    var attr = _.extend({arrow: false}, l.attr && l.attr.style);
+    stageElement.findLink(l).data(attr).updateLink();
+  });
+
+  mapModel.addEventListener('activatedNodesChanged', function (activatedNodes, deactivatedNodes) {
+    _.each(activatedNodes, function (nodeId) {
+      stageElement.nodeWithId(nodeId).addClass('activated');
+    });
+    _.each(deactivatedNodes, function (nodeId) {
+      stageElement.nodeWithId(nodeId).removeClass('activated');
+    });
+  });
+};
+
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-widget.js b/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f19274f5906f6bf7b906baebbaf4031ed8db429
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/dom-map-widget.js
@@ -0,0 +1,218 @@
+/*jslint nomen: true, newcap: true, browser: true*/
+/*global MAPJS, $, _, jQuery*/
+
+jQuery.fn.scrollWhenDragging = function (scrollPredicate) {
+  /*jslint newcap:true*/
+  'use strict';
+  return this.each(function () {
+    var element = $(this),
+        dragOrigin;
+    element.on('dragstart', function () {
+      if (scrollPredicate()) {
+        dragOrigin = {
+          top: element.scrollTop(),
+          left: element.scrollLeft()
+        };
+      }
+    }).on('drag', function (e) {
+      if (e.gesture && dragOrigin) {
+        element.scrollTop(dragOrigin.top - e.gesture.deltaY);
+        element.scrollLeft(dragOrigin.left - e.gesture.deltaX);
+      }
+    }).on('dragend', function () {
+      dragOrigin = undefined;
+    });
+  });
+};
+$.fn.domMapWidget = function (activityLog, mapModel, touchEnabled, imageInsertController, dragContainer, resourceTranslator, centerSelectedNodeOnOrientationChange, options) {
+  'use strict';
+  var hotkeyEventHandlers = {
+        'return': 'addSiblingIdea',
+        'shift+return': 'addSiblingIdeaBefore',
+        'del backspace': 'removeSubIdea',
+        'tab insert': 'addSubIdea',
+        'left': 'selectNodeLeft',
+        'up': 'selectNodeUp',
+        'right': 'selectNodeRight',
+        'shift+right': 'activateNodeRight',
+        'shift+left': 'activateNodeLeft',
+        'meta+right ctrl+right meta+left ctrl+left': 'flip',
+        'shift+up': 'activateNodeUp',
+        'shift+down': 'activateNodeDown',
+        'down': 'selectNodeDown',
+        'space f2': 'editNode',
+        'shift+space': 'editNodeData',
+        'f': 'toggleCollapse',
+        'c meta+x ctrl+x': 'cut',
+        'p meta+v ctrl+v': 'paste',
+        'y meta+c ctrl+c': 'copy',
+        'u meta+z ctrl+z': 'undo',
+        'shift+tab': 'insertIntermediate',
+        'Esc 0 meta+0 ctrl+0': 'resetView',
+        'r meta+shift+z ctrl+shift+z meta+y ctrl+y': 'redo',
+        'meta++ ctrl++ z': 'scaleUp',
+        'meta+- ctrl+- shift+z': 'scaleDown',
+        'meta+up ctrl+up': 'moveUp',
+        'meta+down ctrl+down': 'moveDown',
+        //'ctrl+shift+v meta+shift+v': 'pasteStyle',
+        'Esc': 'cancelCurrentAction',
+        'ctrl+s meta+s': 'save',
+        'k': 'requestContextMenu'
+      },
+      charEventHandlers = {
+        '[': 'activateChildren',
+        '{': 'activateNodeAndChildren',
+        '=': 'activateSiblingNodes',
+        '.': 'activateSelectedNode',
+        '/': 'toggleCollapse'
+        //'a': 'openAttachment',
+        //'i': 'editIcon'
+      },
+      actOnKeys = true,
+      self = this;
+  mapModel.addEventListener('inputEnabledChanged', function (canInput, holdFocus) {
+    actOnKeys = canInput;
+    if (canInput && !holdFocus) {
+      self.focus();
+    }
+  });
+
+  return this.each(function () {
+    var element = $(this),
+        stage = $('<div>').css({
+          position: 'relative'
+        }).attr('data-mapjs-role', 'stage').appendTo(element).data({
+          'offsetX': element.innerWidth() / 2,
+          'offsetY': element.innerHeight() / 2,
+          'width': element.innerWidth() - 20,
+          'height': element.innerHeight() - 20,
+          'scale': 1
+        }).updateStage(),
+        previousPinchScale = false;
+    element.css('overflow', 'auto').attr('tabindex', 1);
+    if (mapModel.isEditingEnabled()) {
+      (dragContainer || element).simpleDraggableContainer();
+    }
+
+    if (!touchEnabled) {
+      element.scrollWhenDragging(mapModel.getInputEnabled); //no need to do this for touch, this is native
+      element.on('mousedown', function (e) {
+        if (e.target !== element[0]) {
+          element.css('overflow', 'hidden');
+        }
+      });
+      jQuery(document).on('mouseup', function () {
+        if (element.css('overflow') !== 'auto') {
+          element.css('overflow', 'auto');
+        }
+      });
+      //element.imageDropWidget(imageInsertController);
+    } else {
+      element.on('doubletap', function (event) {
+        if (mapModel.requestContextMenu(event.gesture.center.pageX, event.gesture.center.pageY)) {
+          event.preventDefault();
+          event.gesture.preventDefault();
+          return false;
+        }
+      }).on('pinch', function (event) {
+        if (!event || !event.gesture || !event.gesture.scale) {
+          return;
+        }
+        event.preventDefault();
+        event.gesture.preventDefault();
+
+        var scale = event.gesture.scale;
+        if (previousPinchScale) {
+          scale = scale / previousPinchScale;
+        }
+        if (Math.abs(scale - 1) < 0.05) {
+          return;
+        }
+        previousPinchScale = event.gesture.scale;
+
+        mapModel.scale('touch', scale, {
+          x: event.gesture.center.pageX - stage.data('offsetX'),
+          y: event.gesture.center.pageY - stage.data('offsetY')
+        });
+      }).on('gestureend', function () {
+        previousPinchScale = false;
+      });
+
+    }
+    MAPJS.DOMRender.viewController(mapModel, stage, touchEnabled, imageInsertController, resourceTranslator, options);
+    _.each(hotkeyEventHandlers, function (mappedFunction, keysPressed) {
+      element.keydown(keysPressed, function (event) {
+        mapModel.getYsy().log.debug("keydown " + keysPressed + " => " + mappedFunction, "keys");
+        if (actOnKeys) {
+          event.stopImmediatePropagation();
+          event.preventDefault();
+          mapModel[mappedFunction]('keyboard');
+        }
+      });
+    });
+    if (!touchEnabled) {
+      jQuery(window).on('resize', function () {
+        mapModel.resetView();
+      });
+    }
+
+    jQuery(window).on('orientationchange', function () {
+      if (centerSelectedNodeOnOrientationChange) {
+        mapModel.centerOnNode(mapModel.getSelectedNodeId());
+      } else {
+        mapModel.resetView();
+      }
+
+    });
+    jQuery("#container").on('keydown', function (e) {
+      var functions = {
+        'U+003D': 'scaleUp',
+        'U+002D': 'scaleDown',
+        61: 'scaleUp',
+        173: 'scaleDown'
+      }, mappedFunction;
+      if (e && !e.altKey && (e.ctrlKey || e.metaKey)) {
+        if (e.originalEvent && e.originalEvent.keyIdentifier) { /* webkit */
+          mappedFunction = functions[e.originalEvent.keyIdentifier];
+        } else if (e.key === 'MozPrintableKey') {
+          mappedFunction = functions[e.which];
+        }
+        if (mappedFunction) {
+          mapModel.getYsy().log.debug("keydown => " + mappedFunction, "keys");
+          if (actOnKeys) {
+            e.preventDefault();
+            mapModel[mappedFunction]('keyboard');
+          }
+        }
+      }
+    }).on('wheel mousewheel', function (e) {
+      var scroll = e.originalEvent.deltaX || (-1 * e.originalEvent.wheelDeltaX);
+      if (scroll < 0 && element.scrollLeft() === 0) {
+        e.preventDefault();
+      }
+      if (scroll > 0 && (element[0].scrollWidth - element.width() - element.scrollLeft() === 0)) {
+        e.preventDefault();
+      }
+    });
+
+    element.on('keypress', function (evt) {
+      if (!actOnKeys) {
+        return;
+      }
+      if (/INPUT|TEXTAREA/.test(evt && evt.target && evt.target.tagName)) {
+        return;
+      }
+      var unicode = evt.charCode || evt.keyCode,
+          actualkey = String.fromCharCode(unicode),
+          mappedFunction = charEventHandlers[actualkey];
+      mapModel.getYsy().log.debug("keydown " + actualkey + " => " + mappedFunction, "keys");
+      if (mappedFunction) {
+        evt.preventDefault();
+        mapModel[mappedFunction]('keyboard');
+      } else if (Number(actualkey) <= 9 && Number(actualkey) >= 1) {
+        evt.preventDefault();
+        mapModel.activateLevel('keyboard', Number(actualkey) + 1);
+      }
+    });
+  });
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/hammer-draggable.js b/plugins/easy_mindmup/assets/javascripts/mindmup/hammer-draggable.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a3678f28c3a360e6d83a0919ae67cf09750f2bb
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/hammer-draggable.js
@@ -0,0 +1,153 @@
+/*global $, Hammer*/
+/*jslint newcap:true*/
+(function () {
+  'use strict';
+  $.fn.simpleDraggableContainer = function () {
+    var currentDragObject,
+        originalDragObjectPosition,
+        originalScroll,
+        container = this,
+        drag = function (event) {
+
+          if (currentDragObject && event.gesture) {
+            var currentScroll = $(document).scrollTop();
+            var newpos = {
+              top: Math.round(originalDragObjectPosition.top + event.gesture.deltaY - originalScroll + currentScroll),
+              left: Math.round(originalDragObjectPosition.left + event.gesture.deltaX)
+            };
+            currentDragObject.offset(newpos).trigger($.Event('mm:drag', {
+              currentPosition: newpos,
+              gesture: event.gesture
+            }));
+            if (event.gesture) {
+              event.gesture.preventDefault();
+            }
+            return false;
+          }
+        },
+        rollback = function (e) {
+          var target = currentDragObject; // allow it to be cleared while animating
+          if (target.attr('mapjs-drag-role') !== 'shadow') {
+            target.animate(originalDragObjectPosition, {
+              complete: function () {
+                target.trigger($.Event('mm:cancel-dragging', {gesture: e.gesture}));
+              },
+              progress: function () {
+                target.trigger('mm:drag');
+              }
+            });
+          } else {
+            target.trigger($.Event('mm:cancel-dragging', {gesture: e.gesture}));
+          }
+        };
+    Hammer(this, {'drag_min_distance': 2});
+    return this.on('mm:start-dragging', function (event) {
+      if (!currentDragObject) {
+        currentDragObject = $(event.relatedTarget);
+        //originalDragObjectPosition = {
+        //  top: currentDragObject.css('top'),
+        //  left: currentDragObject.css('left')
+        //};
+        originalScroll = $(document).scrollTop();
+        originalDragObjectPosition = currentDragObject.offset();
+        $(this).on('drag', drag);
+      }
+    }).on('mm:start-dragging-shadow', function (event) {
+      var target = $(event.relatedTarget),
+          clone = function () {
+            var result = target.clone().addClass('drag-shadow').appendTo(container).offset(target.offset()).data(target.data()).attr('mapjs-drag-role', 'shadow'),
+                scale = target.parent().data('scale') || 1;
+            if (scale !== 0) {
+              result.css({
+                'transform': 'scale(' + scale + ')',
+                'transform-origin': 'top left'
+              });
+            }
+            return result;
+          };
+      if (!currentDragObject) {
+        currentDragObject = clone();
+        //originalDragObjectPosition = {
+        //  top: currentDragObject.css('top'),
+        //  left: currentDragObject.css('left')
+        //};
+        originalScroll = $(document).scrollTop();
+        originalDragObjectPosition = currentDragObject.offset();
+        currentDragObject.on('mm:stop-dragging mm:cancel-dragging', function (e) {
+          $(this).remove();
+          e.stopPropagation();
+          e.stopImmediatePropagation();
+          var evt = $.Event(e.type, {
+            gesture: e.gesture,
+            finalPosition: e.finalPosition
+          });
+          target.trigger(evt);
+        }).on('mm:drag', function (e) {
+          target.trigger(e);
+        });
+        $(this).on('drag', drag);
+      }
+    }).on('dragend', function (e) {
+      $(this).off('drag', drag);
+      if (currentDragObject) {
+        var evt = $.Event('mm:stop-dragging', {
+          gesture: e.gesture,
+          finalPosition: currentDragObject.offset()
+        });
+        currentDragObject.trigger(evt);
+        if (evt.result === false) {
+          rollback(e);
+        }
+        currentDragObject = undefined;
+      }
+    }).on('mouseleave', function (e) {
+      if (currentDragObject) {
+        $(this).off('drag', drag);
+        rollback(e);
+        currentDragObject = undefined;
+      }
+    }).attr('data-drag-role', 'container');
+  };
+
+  var onDrag = function (e) {
+    $(this).trigger(
+        $.Event('mm:start-dragging', {
+          relatedTarget: this,
+          gesture: e.gesture
+        })
+    );
+    e.stopPropagation();
+    e.preventDefault();
+    if (e.gesture) {
+      e.gesture.stopPropagation();
+      e.gesture.preventDefault();
+    }
+  }, onShadowDrag = function (e) {
+    $(this).trigger(
+        $.Event('mm:start-dragging-shadow', {
+          relatedTarget: this,
+          gesture: e.gesture
+        })
+    );
+    e.stopPropagation();
+    e.preventDefault();
+    if (e.gesture) {
+      e.gesture.stopPropagation();
+      e.gesture.preventDefault();
+    }
+  };
+  $.fn.simpleDraggable = function (options) {
+    if (!options || !options.disable) {
+      return $(this).on('dragstart', onDrag);
+    } else {
+      return $(this).off('dragstart', onDrag);
+    }
+  };
+  $.fn.shadowDraggable = function (options) {
+    if (!options || !options.disable) {
+      return $(this).on('dragstart', onShadowDrag);
+    } else {
+      return $(this).off('dragstart', onShadowDrag);
+    }
+  };
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/image-drop-widget.js b/plugins/easy_mindmup/assets/javascripts/mindmup/image-drop-widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..4d139e7740083ca004f3293f01598381c6dbd373
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/image-drop-widget.js
@@ -0,0 +1,109 @@
+/*global observable, jQuery, FileReader, Image, MAPJS, document, _ */
+MAPJS.getDataURIAndDimensions = function (src, corsProxyUrl) {
+  'use strict';
+  var isDataUri = function (string) {
+        return (/^data:image/).test(string);
+      },
+      convertSrcToDataUri = function (img) {
+        if (isDataUri(img.src)) {
+          return img.src;
+        }
+        var canvas = document.createElement('canvas'),
+            ctx;
+        canvas.width = img.width;
+        canvas.height = img.height;
+        ctx = canvas.getContext('2d');
+        ctx.drawImage(img, 0, 0);
+        return canvas.toDataURL('image/png');
+      },
+      deferred = jQuery.Deferred(),
+      domImg = new Image();
+
+  domImg.onload = function () {
+    try {
+      deferred.resolve({dataUri: convertSrcToDataUri(domImg), width: domImg.width, height: domImg.height});
+    } catch (e) {
+      deferred.reject();
+    }
+  };
+  domImg.onerror = function () {
+    deferred.reject();
+  };
+  if (!isDataUri(src)) {
+    if (corsProxyUrl) {
+      domImg.crossOrigin = 'Anonymous';
+      src = corsProxyUrl + encodeURIComponent(src);
+    } else {
+      deferred.reject('no-cors');
+    }
+  }
+  domImg.src = src;
+  return deferred.promise();
+};
+MAPJS.ImageInsertController = function (corsProxyUrl, resourceConverter) {
+  'use strict';
+  var self = observable(this),
+      readFileIntoDataUrl = function (fileInfo) {
+        var loader = jQuery.Deferred(),
+            fReader = new FileReader();
+        fReader.onload = function (e) {
+          loader.resolve(e.target.result);
+        };
+        fReader.onerror = loader.reject;
+        fReader.onprogress = loader.notify;
+        fReader.readAsDataURL(fileInfo);
+        return loader.promise();
+      };
+  self.insertDataUrl = function (dataUrl, evt) {
+    self.dispatchEvent('imageLoadStarted');
+    MAPJS.getDataURIAndDimensions(dataUrl, corsProxyUrl).then(
+        function (result) {
+          var storeUrl = result.dataUri;
+          if (resourceConverter) {
+            storeUrl = resourceConverter(storeUrl);
+          }
+          self.dispatchEvent('imageInserted', storeUrl, result.width, result.height, evt);
+        },
+        function (reason) {
+          self.dispatchEvent('imageInsertError', reason);
+        }
+    );
+  };
+  self.insertFiles = function (files, evt) {
+    jQuery.each(files, function (idx, fileInfo) {
+      if (/^image\//.test(fileInfo.type)) {
+        jQuery.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
+          self.insertDataUrl(dataUrl, evt);
+        });
+      }
+    });
+  };
+  self.insertHtmlContent = function (htmlContent, evt) {
+    var images = htmlContent.match(/img[^>]*src="([^"]*)"/);
+    if (images && images.length > 0) {
+      _.each(images.slice(1), function (dataUrl) {
+        self.insertDataUrl(dataUrl, evt);
+      });
+    }
+  };
+};
+jQuery.fn.imageDropWidget = function (imageInsertController) {
+  'use strict';
+  this.on('dragenter dragover', function (e) {
+    if (e.originalEvent.dataTransfer) {
+      return false;
+    }
+  }).on('drop', function (e) {
+    var dataTransfer = e.originalEvent.dataTransfer,
+        htmlContent;
+    e.stopPropagation();
+    e.preventDefault();
+    if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
+      imageInsertController.insertFiles(dataTransfer.files, e.originalEvent);
+    } else if (dataTransfer) {
+      htmlContent = dataTransfer.getData('text/html');
+      imageInsertController.insertHtmlContent(htmlContent, e.originalEvent);
+    }
+  });
+  return this;
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/layout.js b/plugins/easy_mindmup/assets/javascripts/mindmup/layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..eca20aa4879b984a6e113d23d5ee2ac57838b3ed
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/layout.js
@@ -0,0 +1,353 @@
+/*jslint nomen: true*/
+/*global _, Color, MAPJS*/
+MAPJS.defaultStyles = {};
+MAPJS.layoutLinks = function (idea, visibleNodes) {
+  'use strict';
+  var result = {};
+  _.each(idea.links, function (link) {
+    if (visibleNodes[link.ideaIdFrom] && visibleNodes[link.ideaIdTo]) {
+      result[link.ideaIdFrom + '_' + link.ideaIdTo] = {
+        ideaIdFrom: link.ideaIdFrom,
+        ideaIdTo: link.ideaIdTo,
+        attr: _.clone(link.attr)
+      };
+      //todo - clone
+    }
+  });
+  return result;
+};
+MAPJS.calculateFrame = function (nodes, margin) {
+  'use strict';
+  margin = margin || 0;
+  var result = {
+    top: _.min(nodes, function (node) {
+      return node.y;
+    }).y - margin,
+    left: _.min(nodes, function (node) {
+      return node.x;
+    }).x - margin
+  };
+  result.width = margin + _.max(_.map(nodes, function (node) {
+        return node.x + node.width;
+      })) - result.left;
+  result.height = margin + _.max(_.map(nodes, function (node) {
+        return node.y + node.height;
+      })) - result.top;
+  return result;
+};
+MAPJS.contrastForeground = function (background) {
+  'use strict';
+  /*jslint newcap:true*/
+  var luminosity = Color(background).luminosity();
+  if (luminosity < 0.5) {
+    return '#EEEEEE';
+  }
+  if (luminosity < 0.9) {
+    return '#4F4F4F';
+  }
+  return '#000000';
+};
+MAPJS.Outline = function (topBorder, bottomBorder) {
+  'use strict';
+  var shiftBorder = function (border, deltaH) {
+    return _.map(border, function (segment) {
+      return {
+        l: segment.l,
+        h: segment.h + deltaH
+      };
+    });
+  };
+  this.initialHeight = function () {
+    return this.bottom[0].h - this.top[0].h;
+  };
+  this.borders = function () {
+    return _.pick(this, 'top', 'bottom');
+  };
+  this.spacingAbove = function (outline) {
+    var i = 0, j = 0, result = 0, li = 0, lj = 0;
+    while (i < this.bottom.length && j < outline.top.length) {
+      result = Math.max(result, this.bottom[i].h - outline.top[j].h);
+      if (li + this.bottom[i].l < lj + outline.top[j].l) {
+        li += this.bottom[i].l;
+        i += 1;
+      } else if (li + this.bottom[i].l === lj + outline.top[j].l) {
+        li += this.bottom[i].l;
+        i += 1;
+        lj += outline.top[j].l;
+        j += 1;
+      } else {
+        lj += outline.top[j].l;
+        j += 1;
+      }
+    }
+    return result;
+  };
+  this.indent = function (horizontalIndent, margin) {
+    if (!horizontalIndent) {
+      return this;
+    }
+    var top = this.top.slice(),
+        bottom = this.bottom.slice(),
+        vertCenter = (bottom[0].h + top[0].h) / 2;
+    top.unshift({h: vertCenter - margin / 2, l: horizontalIndent});
+    bottom.unshift({h: vertCenter + margin / 2, l: horizontalIndent});
+    return new MAPJS.Outline(top, bottom);
+  };
+  this.stackBelow = function (outline, margin) {
+    var spacing = outline.spacingAbove(this),
+        top = MAPJS.Outline.extendBorder(outline.top, shiftBorder(this.top, spacing + margin)),
+        bottom = MAPJS.Outline.extendBorder(shiftBorder(this.bottom, spacing + margin), outline.bottom);
+    return new MAPJS.Outline(
+        top,
+        bottom
+    );
+  };
+  this.expand = function (initialTopHeight, initialBottomHeight) {
+    var topAlignment = initialTopHeight - this.top[0].h,
+        bottomAlignment = initialBottomHeight - this.bottom[0].h,
+        top = shiftBorder(this.top, topAlignment),
+        bottom = shiftBorder(this.bottom, bottomAlignment);
+    return new MAPJS.Outline(
+        top,
+        bottom
+    );
+  };
+  this.insertAtStart = function (dimensions, margin) {
+    var alignment = 0, //-1 * this.top[0].h - suboutlineHeight * 0.5,
+        topBorder = shiftBorder(this.top, alignment),
+        bottomBorder = shiftBorder(this.bottom, alignment),
+        easeIn = function (border) {
+          border[0].l *= 0.5;
+          border[1].l += border[0].l;
+        };
+    topBorder[0].l += margin;
+    bottomBorder[0].l += margin;
+    topBorder.unshift({h: -0.5 * dimensions.height, l: dimensions.width});
+    bottomBorder.unshift({h: 0.5 * dimensions.height, l: dimensions.width});
+    if (topBorder[0].h > topBorder[1].h) {
+      easeIn(topBorder);
+    }
+    if (bottomBorder[0].h < bottomBorder[1].h) {
+      easeIn(bottomBorder);
+    }
+    return new MAPJS.Outline(topBorder, bottomBorder);
+  };
+  this.top = topBorder.slice();
+  this.bottom = bottomBorder.slice();
+};
+MAPJS.Outline.borderLength = function (border) {
+  'use strict';
+  return _.reduce(border, function (seed, el) {
+    return seed + el.l;
+  }, 0);
+};
+MAPJS.Outline.borderSegmentIndexAt = function (border, length) {
+  'use strict';
+  var l = 0, i = -1;
+  while (l <= length) {
+    i += 1;
+    if (i >= border.length) {
+      return -1;
+    }
+    l += border[i].l;
+  }
+  return i;
+};
+MAPJS.Outline.extendBorder = function (originalBorder, extension) {
+  'use strict';
+  var result = originalBorder.slice(),
+      origLength = MAPJS.Outline.borderLength(originalBorder),
+      i = MAPJS.Outline.borderSegmentIndexAt(extension, origLength),
+      lengthToCut;
+  if (i >= 0) {
+    lengthToCut = MAPJS.Outline.borderLength(extension.slice(0, i + 1));
+    result.push({h: extension[i].h, l: lengthToCut - origLength});
+    result = result.concat(extension.slice(i + 1));
+  }
+  return result;
+};
+MAPJS.Tree = function (options) {
+  'use strict';
+  _.extend(this, options);
+  this.toLayout = function (x, y, parentId) {
+    x = x || 0;
+    y = y || 0;
+    var result = {
+      nodes: {},
+      connectors: {}
+    }, self;
+    self = _.pick(this, 'id', 'title', 'attr', 'width', 'height', 'level');
+    self.attr.data = _.extend({}, self.attr.data);
+    if (self.level === 1) {
+      self.x = -0.5 * this.width;
+      self.y = -0.5 * this.height;
+    } else {
+      self.x = x + this.deltaX || 0;
+      self.y = y + this.deltaY || 0;
+    }
+    if (!self.attr.style) {
+      self.attr.style = {};
+    }
+    result.nodes[this.id] = self;
+    if (parentId !== undefined) {
+      result.connectors[self.id] = {
+        from: parentId,
+        to: self.id
+      };
+    }
+    if (this.subtrees) {
+      this.subtrees.forEach(function (t) {
+        var subLayout = t.toLayout(self.x, self.y, self.id);
+        _.extend(result.nodes, subLayout.nodes);
+        _.extend(result.connectors, subLayout.connectors);
+      });
+    }
+    return result;
+  };
+};
+MAPJS.Outline.fromDimensions = function (dimensions) {
+  'use strict';
+  return new MAPJS.Outline([{
+    h: -0.5 * dimensions.height,
+    l: dimensions.width
+  }], [{
+    h: 0.5 * dimensions.height,
+    l: dimensions.width
+  }]);
+};
+MAPJS.calculateTree = function (content, dimensionProvider, margin, rankAndParentPredicate, level) {
+  'use strict';
+  var options = {
+        id: content.id,
+        title: content.title,
+        attr: $.extend({}, content.attr, {hasChildren: !_.isEmpty(content.ideas)}),
+        deltaY: 0,
+        deltaX: 0,
+        level: level || 1
+      },
+      setVerticalSpacing = function (treeArray, dy) {
+        var i,
+            tree,
+            oldSpacing,
+            newSpacing,
+            oldPositions = _.map(treeArray, function (t) {
+              return _.pick(t, 'deltaX', 'deltaY');
+            }),
+            referenceTree,
+            alignment;
+        for (i = 0; i < treeArray.length; i += 1) {
+          tree = treeArray[i];
+          if (tree.attr && tree.attr.position) {
+            tree.deltaY = tree.attr.position[1];
+            if (referenceTree === undefined || tree.attr.position[2] > treeArray[referenceTree].attr.position[2]) {
+              referenceTree = i;
+            }
+          } else {
+            tree.deltaY += dy;
+          }
+          if (i > 0) {
+            oldSpacing = oldPositions[i].deltaY - oldPositions[i - 1].deltaY;
+            newSpacing = treeArray[i].deltaY - treeArray[i - 1].deltaY;
+            if (newSpacing < oldSpacing) {
+              tree.deltaY += oldSpacing - newSpacing;
+            }
+          }
+        }
+        alignment = referenceTree && (treeArray[referenceTree].attr.position[1] - treeArray[referenceTree].deltaY);
+        if (alignment) {
+          for (i = 0; i < treeArray.length; i += 1) {
+            treeArray[i].deltaY += alignment;
+          }
+        }
+      },
+      shouldIncludeSubIdeas = function () {
+        return !(_.isEmpty(content.ideas) || (content.attr && content.attr.collapsed));
+      },
+      includedSubIdeaKeys = function () {
+        var allRanks = _.map(_.keys(content.ideas), parseFloat),
+            includedRanks = rankAndParentPredicate ? _.filter(allRanks, function (rank) {
+              return rankAndParentPredicate(rank, content.id);
+            }) : allRanks;
+        return _.sortBy(includedRanks, Math.abs);
+      },
+      includedSubIdeas = function () {
+        var result = [];
+        _.each(includedSubIdeaKeys(), function (key) {
+          result.push(content.ideas[key]);
+        });
+        return result;
+      },
+      nodeDimensions = dimensionProvider(content, options.level),
+      appendSubtrees = function (subtrees) {
+        var suboutline, deltaHeight, subtreePosition, horizontal, treeOutline;
+        _.each(subtrees, function (subtree) {
+          subtree.deltaX = nodeDimensions.width + margin;
+          subtreePosition = subtree.attr && subtree.attr.position && subtree.attr.position[0];
+          if (subtreePosition && subtreePosition > subtree.deltaX) {
+            horizontal = subtreePosition - subtree.deltaX;
+            subtree.deltaX = subtreePosition;
+          } else {
+            horizontal = 0;
+          }
+          if (!suboutline) {
+            suboutline = subtree.outline.indent(horizontal, margin);
+          } else {
+            treeOutline = subtree.outline.indent(horizontal, margin);
+            deltaHeight = treeOutline.initialHeight();
+            suboutline = treeOutline.stackBelow(suboutline, margin);
+            subtree.deltaY = suboutline.initialHeight() - deltaHeight / 2 - subtree.height / 2;
+          }
+        });
+        if (subtrees && subtrees.length) {
+          setVerticalSpacing(subtrees, 0.5 * (nodeDimensions.height - suboutline.initialHeight()));
+          suboutline = suboutline.expand(
+              subtrees[0].deltaY - nodeDimensions.height * 0.5,
+              subtrees[subtrees.length - 1].deltaY + subtrees[subtrees.length - 1].height - nodeDimensions.height * 0.5
+          );
+        }
+        options.outline = suboutline.insertAtStart(nodeDimensions, margin);
+      };
+  _.extend(options, nodeDimensions);
+  options.outline = new MAPJS.Outline.fromDimensions(nodeDimensions);
+  if (shouldIncludeSubIdeas()) {
+    options.subtrees = _.map(includedSubIdeas(), function (i) {
+      return MAPJS.calculateTree(i, dimensionProvider, margin, rankAndParentPredicate, options.level + 1);
+    });
+    if (!_.isEmpty(options.subtrees)) {
+      appendSubtrees(options.subtrees);
+    }
+  }
+  return new MAPJS.Tree(options);
+};
+
+MAPJS.calculateLayout = function (idea, dimensionProvider, margin) {
+  'use strict';
+  var positiveTree, negativeTree, layout, negativeLayout,
+      setDefaultStyles = function (nodes) {
+        _.each(nodes, function (node) {
+          node.attr = node.attr || {};
+          node.attr.style = _.extend({}, MAPJS.defaultStyles[(node.level === 1) ? 'root' : 'nonRoot'], node.attr.style);
+        });
+      },
+      positive = function (rank, parentId) {
+        return parentId !== idea.id || rank > 0;
+      },
+      negative = function (rank, parentId) {
+        return parentId !== idea.id || rank < 0;
+      };
+  margin = margin || 20;
+  positiveTree = MAPJS.calculateTree(idea, dimensionProvider, margin, positive);
+  negativeTree = MAPJS.calculateTree(idea, dimensionProvider, margin, negative);
+  layout = positiveTree.toLayout();
+  negativeLayout = negativeTree.toLayout();
+  _.each(negativeLayout.nodes, function (n) {
+    n.x = -1 * n.x - n.width;
+  });
+  _.extend(negativeLayout.nodes, layout.nodes);
+  _.extend(negativeLayout.connectors, layout.connectors);
+  setDefaultStyles(negativeLayout.nodes);
+  negativeLayout.links = MAPJS.layoutLinks(idea, negativeLayout.nodes);
+  negativeLayout.rootNodeId = idea.id;
+  return negativeLayout;
+};
+
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/link-edit-widget.js b/plugins/easy_mindmup/assets/javascripts/mindmup/link-edit-widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..d70576576051c04f810f201ad90e9220e7265ec3
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/link-edit-widget.js
@@ -0,0 +1,40 @@
+/*global jQuery*/
+jQuery.fn.linkEditWidget = function (mapModel) {
+  'use strict';
+  return this.each(function () {
+    var element = jQuery(this), currentLink, width, height, colorElement, lineStyleElement, arrowElement;
+    colorElement = element.find('.color');
+    lineStyleElement = element.find('.lineStyle');
+    arrowElement = element.find('.arrow');
+    mapModel.addEventListener('linkSelected', function (link, selectionPoint, linkStyle) {
+      currentLink = link;
+      element.show();
+      width = width || element.width();
+      height = height || element.height();
+      element.css({
+        top: (selectionPoint.y - 0.5 * height - 15) + 'px',
+        left: (selectionPoint.x - 0.5 * width - 15) + 'px'
+      });
+      colorElement.val(linkStyle.color).change();
+      lineStyleElement.val(linkStyle.lineStyle);
+      arrowElement[linkStyle.arrow ? 'addClass' : 'removeClass']('active');
+    });
+    mapModel.addEventListener('mapMoveRequested', function () {
+      element.hide();
+    });
+    element.find('.delete').click(function () {
+      mapModel.removeLink('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo);
+      element.hide();
+    });
+    colorElement.change(function () {
+      mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'color', jQuery(this).val());
+    });
+    lineStyleElement.find('a').click(function () {
+      mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'lineStyle', jQuery(this).text());
+    });
+    arrowElement.click(function () {
+      mapModel.updateLinkStyle('mouse', currentLink.ideaIdFrom, currentLink.ideaIdTo, 'arrow', !arrowElement.hasClass('active'));
+    });
+    element.mouseleave(element.hide.bind(element));
+  });
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/map-model.js b/plugins/easy_mindmup/assets/javascripts/mindmup/map-model.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e41758b42120a75423922d9843233e4d56b8498
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/map-model.js
@@ -0,0 +1,1230 @@
+/*jslint forin: true, nomen: true*/
+/*global _, MAPJS, observable*/
+MAPJS.MapModel = function (layoutCalculatorArg, selectAllTitles, clipboardProvider, defaultReorderMargin) {
+  'use strict';
+  var self = this,
+      layoutCalculator = layoutCalculatorArg,
+      reorderMargin = defaultReorderMargin || 20,
+      clipboard = clipboardProvider || new MAPJS.MemoryClipboard(),
+      analytic,
+      currentLayout = {
+        nodes: {},
+        connectors: {}
+      },
+      idea,
+      currentLabelGenerator,
+      isInputEnabled = true,
+      isEditingEnabled = true,
+      currentlySelectedIdeaId,
+      activatedNodes = [],
+      setActiveNodes = function (activated) {
+        var wasActivated = _.clone(activatedNodes);
+        if (activated.length === 0) {
+          activatedNodes = [currentlySelectedIdeaId];
+        } else {
+          activatedNodes = activated;
+        }
+        self.dispatchEvent('activatedNodesChanged', _.difference(activatedNodes, wasActivated), _.difference(wasActivated, activatedNodes));
+      },
+      horizontalSelectionThreshold = 300,
+      isAddLinkMode,
+      applyLabels = function (newLayout) {
+        var labelMap;
+        if (!currentLabelGenerator) {
+          return;
+        }
+        labelMap = currentLabelGenerator(idea);
+        _.each(newLayout.nodes, function (node, id) {
+          if (labelMap[id] || labelMap[id] === 0) {
+            node.label = labelMap[id];
+          }
+        });
+      },
+      updateCurrentLayout = function (newLayout, sessionId) {
+        self.getYsy().log.debug("updateCurrentLayout", "redraw");
+        self.dispatchEvent('layoutChangeStarting', _.size(newLayout.nodes) - _.size(currentLayout.nodes));
+        applyLabels(newLayout);
+
+        _.each(currentLayout.connectors, function (oldConnector, connectorId) {
+          var newConnector = newLayout.connectors[connectorId];
+          if (!newConnector || newConnector.from !== oldConnector.from || newConnector.to !== oldConnector.to) {
+            self.dispatchEvent('connectorRemoved', oldConnector);
+          }
+        });
+        _.each(currentLayout.links, function (oldLink, linkId) {
+          var newLink = newLayout.links && newLayout.links[linkId];
+          if (!newLink) {
+            self.dispatchEvent('linkRemoved', oldLink);
+          }
+        });
+        _.each(currentLayout.nodes, function (oldNode, nodeId) {
+          var newNode = newLayout.nodes[nodeId],
+              newActive;
+          if (!newNode) {
+            /*jslint eqeq: true*/
+            if (nodeId == currentlySelectedIdeaId) {
+              self.selectNode(idea.id);
+            }
+            newActive = _.reject(activatedNodes, function (e) {
+              return e == nodeId;
+            });
+            if (newActive.length !== activatedNodes.length) {
+              setActiveNodes(newActive);
+            }
+            self.dispatchEvent('nodeRemoved', oldNode, nodeId, sessionId);
+          }
+        });
+
+        _.each(newLayout.nodes, function (newNode, nodeId) {
+          var oldNode = currentLayout.nodes[nodeId];
+          if (!oldNode) {
+            self.dispatchEvent('nodeCreated', newNode, sessionId);
+          } else {
+            if (newNode.x !== oldNode.x || newNode.y !== oldNode.y) {
+              //ysy.log.debug("CONTENT: "+newNode.title+" old=["+oldNode.x+","+oldNode.y+"] new=["+newNode.x+","+newNode.y+"]");
+              self.dispatchEvent('nodeMoved', newNode, sessionId);
+            }
+            if (newNode.title !== oldNode.title) {
+              self.dispatchEvent('nodeTitleChanged', newNode, sessionId);
+            }
+            if (!_.isEqual(newNode.attr || {}, oldNode.attr || {})) {
+              self.dispatchEvent('nodeAttrChanged', newNode, sessionId);
+            }
+            if (newNode.label !== oldNode.label) {
+              self.dispatchEvent('nodeLabelChanged', newNode, sessionId);
+            }
+          }
+        });
+        _.each(newLayout.connectors, function (newConnector, connectorId) {
+          var oldConnector = currentLayout.connectors[connectorId];
+          if (!oldConnector || newConnector.from !== oldConnector.from || newConnector.to !== oldConnector.to) {
+            self.dispatchEvent('connectorCreated', newConnector, sessionId);
+          }
+        });
+        _.each(newLayout.links, function (newLink, linkId) {
+          var oldLink = currentLayout.links && currentLayout.links[linkId];
+          if (oldLink) {
+            if (!_.isEqual(newLink.attr || {}, (oldLink && oldLink.attr) || {})) {
+              self.dispatchEvent('linkAttrChanged', newLink, sessionId);
+            }
+          } else {
+            self.dispatchEvent('linkCreated', newLink, sessionId);
+          }
+        });
+        currentLayout = newLayout;
+        if (!self.isInCollapse) {
+          self.dispatchEvent('layoutChangeComplete');
+        }
+      },
+      revertSelectionForUndo,
+      revertActivatedForUndo,
+      selectNewIdea = function (newIdeaId) {
+        revertSelectionForUndo = currentlySelectedIdeaId;
+        revertActivatedForUndo = activatedNodes.slice(0);
+        self.selectNode(newIdeaId);
+      },
+      editNewIdea = function (newIdeaId) {
+        selectNewIdea(newIdeaId);
+        self.editNode(false, true, true);
+      },
+      getCurrentlySelectedIdeaId = function () {
+        return currentlySelectedIdeaId || idea.id;
+      },
+      paused = false,
+      onIdeaChanged = function (action, args, sessionId) {
+        if (paused) {
+          return;
+        }
+        revertSelectionForUndo = false;
+        revertActivatedForUndo = false;
+        self.rebuildRequired(sessionId);
+      },
+      currentlySelectedIdea = function () {
+        return (idea.findSubIdeaById(currentlySelectedIdeaId) || idea);
+      },
+      ensureNodeIsExpanded = function (source, nodeId) {
+        var node = idea.findSubIdeaById(nodeId) || idea;
+        if (node.getAttr('collapsed')) {
+          idea.updateAttr(nodeId, 'collapsed', false);
+        }
+      };
+  observable(this);
+  analytic = self.dispatchEvent.bind(self, 'analytic', 'mapModel');
+  self.pause = function () {
+    paused = true;
+  };
+  self.resume = function () {
+    paused = false;
+    self.rebuildRequired();
+  };
+  self.getIdea = function () {
+    return idea;
+  };
+  self.isEditingEnabled = function () {
+    return isEditingEnabled;
+  };
+  self.getCurrentLayout = function () {
+    return currentLayout;
+  };
+  self.analytic = analytic;
+  self.getCurrentlySelectedIdeaId = getCurrentlySelectedIdeaId;
+  self.rebuildRequired = function (sessionId) {
+    if (!idea) {
+      return;
+    }
+    updateCurrentLayout(self.reactivate(layoutCalculator(idea)), sessionId);
+  };
+  this.setIdea = function (anIdea) {
+    if (idea) {
+      idea.removeEventListener('changed', onIdeaChanged);
+      paused = false;
+      setActiveNodes([]);
+      self.dispatchEvent('nodeSelectionChanged', currentlySelectedIdeaId, false);
+      currentlySelectedIdeaId = undefined;
+    }
+    idea = anIdea;
+    idea.addEventListener('changed', onIdeaChanged);
+    onIdeaChanged();
+    self.selectNode(idea.id, true);
+    self.dispatchEvent('mapViewResetRequested');
+  };
+  this.setEditingEnabled = function (value) {
+    isEditingEnabled = value;
+  };
+  this.getEditingEnabled = function () {
+    return isEditingEnabled;
+  };
+  this.setInputEnabled = function (value, holdFocus) {
+    if (isInputEnabled !== value) {
+      isInputEnabled = value;
+      self.dispatchEvent('inputEnabledChanged', value, !!holdFocus);
+    }
+  };
+  this.getInputEnabled = function () {
+    return isInputEnabled;
+  };
+  this.selectNode = function (id, force, appendToActive) {
+    if (force || (isInputEnabled && (id !== currentlySelectedIdeaId || !self.isActivated(id)))) {
+      if (currentlySelectedIdeaId) {
+        self.dispatchEvent('nodeSelectionChanged', currentlySelectedIdeaId, false);
+      }
+      currentlySelectedIdeaId = id;
+      if (appendToActive) {
+        self.activateNode('internal', id);
+      } else {
+        setActiveNodes([id]);
+      }
+
+      self.dispatchEvent('nodeSelectionChanged', id, true);
+    }
+  };
+  this.clickNode = function (id, event) {
+    var button = event && event.button && event.button !== -1;
+    if (event && event.altKey) {
+      self.followURL(id);
+      //self.toggleLink('mouse', id); // HOSEK
+    } else if (event && (event.shiftKey || event.ctrlKey)) {
+      /*don't stop propagation, this is needed for drop targets*/
+      self.toggleActivationOnNode('mouse', id);
+    } else if (isAddLinkMode && !button) {
+      this.toggleLink('mouse', id);
+      this.toggleAddLinkMode();
+    } else {
+      this.selectNode(id);
+      if (button && button !== -1 && isInputEnabled) {
+        self.dispatchEvent('contextMenuRequested', id, event.layerX, event.layerY);
+      }
+    }
+  };
+  this.findIdeaById = function (id) {
+    /*jslint eqeq:true */
+    if (idea.id == id) {
+      return idea;
+    }
+    return idea.findSubIdeaById(id);
+  };
+  this.getSelectedStyle = function (prop) {
+    return this.getStyleForId(currentlySelectedIdeaId, prop);
+  };
+  this.getStyleForId = function (id, prop) {
+    var node = currentLayout.nodes && currentLayout.nodes[id];
+    return node && node.attr && node.attr.style && node.attr.style[prop];
+  };
+  this.toggleCollapse = function (source) {
+    var selectedIdea = currentlySelectedIdea(),
+        isCollapsed;
+    if (self.isActivated(selectedIdea.id) && _.size(selectedIdea.ideas) > 0) {
+      isCollapsed = selectedIdea.getAttr('collapsed');
+    } else {
+      isCollapsed = self.everyActivatedIs(function (id) {
+        var node = self.findIdeaById(id);
+        if (node && _.size(node.ideas) > 0) {
+          return node.getAttr('collapsed');
+        }
+        return true;
+      });
+    }
+    this.collapse(source, !isCollapsed);
+  };
+  this.collapse = function (source, doCollapse) {
+
+    var contextNodeId = getCurrentlySelectedIdeaId(),
+        contextNode = function () {
+          return contextNodeId && currentLayout && currentLayout.nodes && currentLayout.nodes[contextNodeId];
+        },
+        moveNodes = function (nodes, deltaX, deltaY) {
+          if (deltaX || deltaY) {
+            _.each(nodes, function (node) {
+              node.x += deltaX;
+              node.y += deltaY;
+              self.dispatchEvent('nodeMoved', node, 'scroll');
+            });
+          }
+        },
+        oldContext,
+        newContext;
+    analytic('collapse:' + doCollapse, source);
+    self.isInCollapse = true;
+    oldContext = contextNode();
+    if (isInputEnabled) {
+      self.applyToActivated(function (id) {
+        var node = self.findIdeaById(id);
+        if (node && (!doCollapse || (node.ideas && _.size(node.ideas) > 0))) {
+          idea.updateAttr(id, 'collapsed', doCollapse);
+        }
+      });
+    }
+    newContext = contextNode();
+    if (oldContext && newContext) {
+      moveNodes(
+          currentLayout.nodes,
+          oldContext.x - newContext.x,
+          oldContext.y - newContext.y
+      );
+    }
+    self.isInCollapse = false;
+    self.dispatchEvent('layoutChangeComplete');
+  };
+  this.updateStyle = function (source, prop, value) {
+    /*jslint eqeq:true */
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (isInputEnabled) {
+      analytic('updateStyle:' + prop, source);
+      self.applyToActivated(function (id) {
+        if (self.getStyleForId(id, prop) != value) {
+          idea.mergeAttrProperty(id, 'style', prop, value);
+        }
+      });
+    }
+  };
+  this.updateLinkStyle = function (source, ideaIdFrom, ideaIdTo, prop, value) {
+    var merged = _.extend({}, idea.getLinkAttr(ideaIdFrom, ideaIdTo, 'style'));
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (isInputEnabled) {
+      analytic('updateLinkStyle:' + prop, source);
+      merged[prop] = value;
+      idea.updateLinkAttr(ideaIdFrom, ideaIdTo, 'style', merged);
+    }
+  };
+  this.addSubIdea = function (source, parentId, initialTitle) {
+    var target = parentId || currentlySelectedIdeaId, newId;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('addSubIdea', source);
+    if (isInputEnabled) {
+      idea.batch(function () {
+        ensureNodeIsExpanded(source, target);
+        if (initialTitle) {
+          newId = idea.addSubIdea(target, initialTitle);
+        } else {
+          newId = idea.addSubIdea(target);
+        }
+      });
+      if (newId) {
+        if (initialTitle) {
+          selectNewIdea(newId);
+        } else {
+          editNewIdea(newId);
+        }
+      }
+    }
+
+  };
+  this.insertIntermediate = function (source) {
+    var activeNodes = [], newId;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (!isInputEnabled || currentlySelectedIdeaId === idea.id) {
+      return false;
+    }
+    analytic('insertIntermediate', source);
+    self.applyToActivated(function (i) {
+      activeNodes.push(i);
+    });
+    newId = idea.insertIntermediateMultiple(activeNodes);
+    if (newId) {
+      editNewIdea(newId);
+    }
+  };
+  this.flip = function (source) {
+    var node = currentLayout && currentLayout.nodes && currentLayout.nodes[currentlySelectedIdeaId];
+
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('flip', source);
+    if (!isInputEnabled || currentlySelectedIdeaId === idea.id) {
+      return false;
+    }
+    if (!node || node.level !== 2) {
+      return false;
+    }
+
+    return idea.flip(currentlySelectedIdeaId);
+  };
+  this.addSiblingIdeaBefore = function (source) {
+    var newId, parent, contextRank, newRank;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('addSiblingIdeaBefore', source);
+    if (!isInputEnabled) {
+      return false;
+    }
+    parent = idea.findParent(currentlySelectedIdeaId) || idea;
+    idea.batch(function () {
+      ensureNodeIsExpanded(source, parent.id);
+      newId = idea.addSubIdea(parent.id);
+      if (newId && currentlySelectedIdeaId !== idea.id) {
+        contextRank = parent.findChildRankById(currentlySelectedIdeaId);
+        newRank = parent.findChildRankById(newId);
+        if (contextRank * newRank < 0) {
+          idea.flip(newId);
+        }
+        idea.positionBefore(newId, currentlySelectedIdeaId);
+      }
+    });
+    if (newId) {
+      editNewIdea(newId);
+    }
+  };
+  this.addSiblingIdea = function (source, optionalNodeId, optionalInitialText) {
+    var newId, nextId, parent, contextRank, newRank, currentId;
+    currentId = optionalNodeId || currentlySelectedIdeaId;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('addSiblingIdea', source);
+    if (isInputEnabled) {
+      parent = idea.findParent(currentId) || idea;
+      idea.batch(function () {
+        ensureNodeIsExpanded(source, parent.id);
+        if (optionalInitialText) {
+          newId = idea.addSubIdea(parent.id, optionalInitialText);
+        } else {
+          newId = idea.addSubIdea(parent.id);
+        }
+        if (newId && currentId !== idea.id) {
+          nextId = idea.nextSiblingId(currentId);
+          contextRank = parent.findChildRankById(currentId);
+          newRank = parent.findChildRankById(newId);
+          if (contextRank * newRank < 0) {
+            idea.flip(newId);
+          }
+          if (nextId) {
+            idea.positionBefore(newId, nextId);
+          }
+        }
+      });
+      if (newId) {
+        if (optionalInitialText) {
+          selectNewIdea(newId);
+        } else {
+          editNewIdea(newId);
+        }
+      }
+    }
+  };
+  this.removeSubIdea = function (source) {
+    var removed;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('removeSubIdea', source);
+    if (isInputEnabled) {
+      self.applyToActivated(function (id) {
+        /*jslint eqeq:true */
+        var parent;
+        if (currentlySelectedIdeaId == id) {
+          parent = idea.findParent(currentlySelectedIdeaId);
+          if (parent) {
+            self.selectNode(parent.id);
+          }
+        }
+        removed = idea.removeSubIdea(id);
+      });
+    }
+    return removed;
+  };
+  this.updateTitle = function (ideaId, title, isNew) {
+    if (isNew) {
+      idea.initialiseTitle(ideaId, title);
+    } else {
+      idea.updateTitle(ideaId, title);
+    }
+  };
+  this.editNode = function (source, shouldSelectAll, editingNew) {
+    var title;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (source) {
+      analytic('editNode', source);
+    }
+    if (!isInputEnabled) {
+      return false;
+    }
+    var idea = currentlySelectedIdea();
+    if (!self.getYsy().validator.validate("nodeRename", idea)) return false;
+
+    title = idea.title;
+    if (_.include(selectAllTitles, title)) { // === 'Press Space or double-click to edit') {
+      shouldSelectAll = true;
+    }
+    self.dispatchEvent('nodeEditRequested', currentlySelectedIdeaId, shouldSelectAll, !!editingNew);
+  };
+  this.editIcon = function (source) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (source) {
+      analytic('editIcon', source);
+    }
+    if (!isInputEnabled) {
+      return false;
+    }
+    self.dispatchEvent('nodeIconEditRequested', currentlySelectedIdeaId);
+  };
+  this.scaleUp = function (source) {
+    self.scale(source, 1.25);
+  };
+  this.scaleDown = function (source) {
+    self.scale(source, 0.8);
+  };
+  this.scale = function (source, scaleMultiplier, zoomPoint) {
+    if (isInputEnabled) {
+      self.dispatchEvent('mapScaleChanged', scaleMultiplier, zoomPoint);
+      analytic(scaleMultiplier < 1 ? 'scaleDown' : 'scaleUp', source);
+    }
+  };
+  this.move = function (source, deltaX, deltaY) {
+    if (isInputEnabled) {
+      self.dispatchEvent('mapMoveRequested', deltaX, deltaY);
+      analytic('move', source);
+    }
+  };
+  this.resetView = function (source) {
+    if (isInputEnabled && idea) {
+      self.selectNode(idea.id);
+      self.dispatchEvent('mapViewResetRequested');
+      analytic('resetView', source);
+    }
+
+  };
+  this.openAttachment = function (source, nodeId) {
+    var node, attachment;
+    analytic('openAttachment', source);
+    nodeId = nodeId || currentlySelectedIdeaId;
+    node = currentLayout && currentLayout.nodes && currentLayout.nodes[nodeId];
+    attachment = node && node.attr && node.attr.attachment;
+    if (node) {
+      self.dispatchEvent('attachmentOpened', nodeId, attachment);
+    }
+  };
+  this.setAttachment = function (source, nodeId, attachment) {
+    var hasAttachment = !!(attachment && attachment.content);
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('setAttachment', source);
+    idea.updateAttr(nodeId, 'attachment', hasAttachment && attachment);
+  };
+  this.toggleLink = function (source, nodeIdTo) {
+    var exists = _.find(idea.links, function (link) {
+      return (String(link.ideaIdFrom) === String(nodeIdTo) && String(link.ideaIdTo) === String(currentlySelectedIdeaId)) || (String(link.ideaIdTo) === String(nodeIdTo) && String(link.ideaIdFrom) === String(currentlySelectedIdeaId));
+    });
+    if (exists) {
+      self.removeLink(source, exists.ideaIdFrom, exists.ideaIdTo);
+    } else {
+      self.addLink(source, nodeIdTo);
+    }
+  };
+  this.addLink = function (source, nodeIdTo) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('addLink', source);
+    idea.addLink(currentlySelectedIdeaId, nodeIdTo);
+  };
+  this.selectLink = function (source, link, selectionPoint) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('selectLink', source);
+    if (!link) {
+      return false;
+    }
+    self.dispatchEvent('linkSelected', link, selectionPoint, idea.getLinkAttr(link.ideaIdFrom, link.ideaIdTo, 'style'));
+  };
+  this.removeLink = function (source, nodeIdFrom, nodeIdTo) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('removeLink', source);
+    idea.removeLink(nodeIdFrom, nodeIdTo);
+  };
+
+  this.toggleAddLinkMode = function (source) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (!isInputEnabled) {
+      return false;
+    }
+    analytic('toggleAddLinkMode', source);
+    isAddLinkMode = !isAddLinkMode;
+    self.dispatchEvent('addLinkModeToggled', isAddLinkMode);
+  };
+  this.cancelCurrentAction = function (source) {
+    if (!isInputEnabled) {
+      return false;
+    }
+    if (!isEditingEnabled) {
+      return false;
+    }
+    if (isAddLinkMode) {
+      this.toggleAddLinkMode(source);
+    }
+  };
+  self.undo = function (source) {
+    var undoSelectionClone = revertSelectionForUndo,
+        undoActivationClone = revertActivatedForUndo;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('undo', source);
+    if (isInputEnabled) {
+      idea.undo();
+      if (undoSelectionClone) {
+        self.selectNode(undoSelectionClone);
+      }
+      if (undoActivationClone) {
+        setActiveNodes(undoActivationClone);
+      }
+
+    }
+  };
+  self.redo = function (source) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+
+    analytic('redo', source);
+    if (isInputEnabled) {
+      idea.redo();
+    }
+  };
+  self.moveRelative = function (source, relativeMovement) {
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('moveRelative', source);
+    if (isInputEnabled) {
+      idea.moveRelative(currentlySelectedIdeaId, relativeMovement);
+    }
+  };
+  self.cut = function (source) {
+    var activeNodeIds = [], parents = [], firstLiveParent;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('cut', source);
+    if (isInputEnabled) {
+      self.applyToActivated(function (nodeId) {
+        activeNodeIds.push(nodeId);
+        parents.push(idea.findParent(nodeId).id);
+      });
+      clipboard.put(idea.cloneMultiple(activeNodeIds));
+      idea.removeMultiple(activeNodeIds);
+      firstLiveParent = _.find(parents, idea.findSubIdeaById);
+      self.selectNode(firstLiveParent || idea.id);
+    }
+  };
+  self.contextForNode = function (nodeId) {
+    var node = self.findIdeaById(nodeId),
+        hasChildren = node && node.ideas && _.size(node.ideas) > 0,
+        hasSiblings = idea.hasSiblings(nodeId),
+        isCollapsed = node && node.getAttr('collapsed'),
+        canPaste = node && isEditingEnabled && clipboard && clipboard.get();
+    if (node) {
+      return {
+        hasChildren: !!hasChildren,
+        hasSiblings: !!hasSiblings,
+        canPaste: !!canPaste,
+        notRoot: idea.id != nodeId,
+        canUndo: idea.canUndo(),
+        canRedo: idea.canRedo(),
+        canCollapse: hasChildren && !isCollapsed,
+        canExpand: hasChildren && isCollapsed
+      };
+    }
+
+  };
+  self.copy = function (source) {
+    var activeNodeIds = [];
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('copy', source);
+    if (isInputEnabled) {
+      self.applyToActivated(function (node) {
+        activeNodeIds.push(node);
+      });
+      clipboard.put(idea.cloneMultiple(activeNodeIds));
+    }
+  };
+  self.paste = function (source) {
+    var result;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('paste', source);
+    if (isInputEnabled) {
+      result = idea.pasteMultiple(currentlySelectedIdeaId, clipboard.get());
+      if (result && result[0]) {
+        self.selectNode(result[0]);
+      }
+    }
+  };
+  self.pasteStyle = function (source) {
+    var clipContents = clipboard.get(),
+        pastingStyle;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('pasteStyle', source);
+    if (isInputEnabled && clipContents && clipContents[0]) {
+      pastingStyle = clipContents[0].attr && clipContents[0].attr.style;
+      self.applyToActivated(function (id) {
+        idea.updateAttr(id, 'style', pastingStyle);
+      });
+    }
+  };
+  self.getIcon = function (nodeId) {
+    var node = currentLayout.nodes[nodeId || currentlySelectedIdeaId];
+    if (!node) {
+      return false;
+    }
+    return node.attr && node.attr.icon;
+  };
+  self.setIcon = function (source, url, imgWidth, imgHeight, position, nodeId, metaData) {
+    var nodeIdea, iconObject;
+    if (!isEditingEnabled) {
+      return false;
+    }
+    analytic('setIcon', source);
+    nodeId = nodeId || currentlySelectedIdeaId;
+    nodeIdea = self.findIdeaById(nodeId);
+    if (!nodeIdea) {
+      return false;
+    }
+    if (url) {
+      iconObject = {
+        url: url,
+        width: imgWidth,
+        height: imgHeight,
+        position: position
+      };
+      if (metaData) {
+        iconObject.metaData = metaData;
+      }
+      idea.updateAttr(nodeId, 'icon', iconObject);
+    } else if (nodeIdea.title || nodeId === idea.id) {
+      idea.updateAttr(nodeId, 'icon', false);
+    } else {
+      idea.removeSubIdea(nodeId);
+    }
+  };
+  self.moveUp = function (source) {
+    self.moveRelative(source, -1);
+  };
+  self.moveDown = function (source) {
+    self.moveRelative(source, 1);
+  };
+  self.getSelectedNodeId = function () {
+    return getCurrentlySelectedIdeaId();
+  };
+  self.centerOnNode = function (nodeId) {
+    if (!currentLayout.nodes[nodeId]) {
+      idea.startBatch();
+      _.each(idea.calculatePath(nodeId), function (parent) {
+        idea.updateAttr(parent.id, 'collapsed', false);
+      });
+      idea.endBatch();
+    }
+    self.dispatchEvent('nodeFocusRequested', nodeId);
+    self.selectNode(nodeId);
+  };
+  self.search = function (query) {
+    var result = [];
+    query = query.toLocaleLowerCase();
+    idea.traverse(function (contentIdea) {
+      if (contentIdea.title && contentIdea.title.toLocaleLowerCase().indexOf(query) >= 0) {
+        result.push({id: contentIdea.id, title: contentIdea.title});
+      }
+    });
+    return result;
+  };
+  //node activation and selection
+  (function () {
+    var isRootOrRightHalf = function (id) {
+          return currentLayout.nodes[id].x >= currentLayout.nodes[idea.id].x;
+        },
+        isRootOrLeftHalf = function (id) {
+          return currentLayout.nodes[id].x <= currentLayout.nodes[idea.id].x;
+        },
+        nodesWithIDs = function () {
+          return _.map(currentLayout.nodes,
+              function (n, nodeId) {
+                return _.extend({id: parseInt(nodeId, 10)}, n);
+              });
+        },
+        applyToNodeLeft = function (source, analyticTag, method) {
+          var node,
+              rank,
+              isRoot = currentlySelectedIdeaId === idea.id,
+              targetRank = isRoot ? -Infinity : Infinity;
+          if (!isInputEnabled) {
+            return;
+          }
+          analytic(analyticTag, source);
+          if (isRootOrLeftHalf(currentlySelectedIdeaId)) {
+            node = idea.id === currentlySelectedIdeaId ? idea : idea.findSubIdeaById(currentlySelectedIdeaId);
+            ensureNodeIsExpanded(source, node.id);
+            for (rank in node.ideas) {
+              rank = parseFloat(rank);
+              if ((isRoot && rank < 0 && rank > targetRank) || (!isRoot && rank > 0 && rank < targetRank)) {
+                targetRank = rank;
+              }
+            }
+            if (targetRank !== Infinity && targetRank !== -Infinity) {
+              method.apply(self, [node.ideas[targetRank].id]);
+            }
+          } else {
+            method.apply(self, [idea.findParent(currentlySelectedIdeaId).id]);
+          }
+        },
+        applyToNodeRight = function (source, analyticTag, method) {
+          var node, rank, minimumPositiveRank = Infinity;
+          if (!isInputEnabled) {
+            return;
+          }
+          analytic(analyticTag, source);
+          if (isRootOrRightHalf(currentlySelectedIdeaId)) {
+            node = idea.id === currentlySelectedIdeaId ? idea : idea.findSubIdeaById(currentlySelectedIdeaId);
+            ensureNodeIsExpanded(source, node.id);
+            for (rank in node.ideas) {
+              rank = parseFloat(rank);
+              if (rank > 0 && rank < minimumPositiveRank) {
+                minimumPositiveRank = rank;
+              }
+            }
+            if (minimumPositiveRank !== Infinity) {
+              method.apply(self, [node.ideas[minimumPositiveRank].id]);
+            }
+          } else {
+            method.apply(self, [idea.findParent(currentlySelectedIdeaId).id]);
+          }
+        },
+        applyToNodeUp = function (source, analyticTag, method) {
+          var previousSibling = idea.previousSiblingId(currentlySelectedIdeaId),
+              nodesAbove,
+              closestNode,
+              currentNode = currentLayout.nodes[currentlySelectedIdeaId];
+          if (!isInputEnabled) {
+            return;
+          }
+          analytic(analyticTag, source);
+          if (previousSibling) {
+            method.apply(self, [previousSibling]);
+          } else {
+            if (!currentNode) {
+              return;
+            }
+            nodesAbove = _.reject(nodesWithIDs(), function (node) {
+              return node.y >= currentNode.y || Math.abs(node.x - currentNode.x) > horizontalSelectionThreshold;
+            });
+            if (_.size(nodesAbove) === 0) {
+              return;
+            }
+            closestNode = _.min(nodesAbove, function (node) {
+              return Math.pow(node.x - currentNode.x, 2) + Math.pow(node.y - currentNode.y, 2);
+            });
+            method.apply(self, [closestNode.id]);
+          }
+        },
+        applyToNodeDown = function (source, analyticTag, method) {
+          var nextSibling = idea.nextSiblingId(currentlySelectedIdeaId),
+              nodesBelow,
+              closestNode,
+              currentNode = currentLayout.nodes[currentlySelectedIdeaId];
+          if (!isInputEnabled) {
+            return;
+          }
+          analytic(analyticTag, source);
+          if (nextSibling) {
+            method.apply(self, [nextSibling]);
+          } else {
+            if (!currentNode) {
+              return;
+            }
+            nodesBelow = _.reject(nodesWithIDs(), function (node) {
+              return node.y <= currentNode.y || Math.abs(node.x - currentNode.x) > horizontalSelectionThreshold;
+            });
+            if (_.size(nodesBelow) === 0) {
+              return;
+            }
+            closestNode = _.min(nodesBelow, function (node) {
+              return Math.pow(node.x - currentNode.x, 2) + Math.pow(node.y - currentNode.y, 2);
+            });
+            method.apply(self, [closestNode.id]);
+          }
+        },
+        applyFuncs = {'Left': applyToNodeLeft, 'Up': applyToNodeUp, 'Down': applyToNodeDown, 'Right': applyToNodeRight};
+    self.getActivatedNodeIds = function () {
+      return activatedNodes.slice(0);
+    };
+    self.activateSiblingNodes = function (source) {
+      var parent = idea.findParent(currentlySelectedIdeaId),
+          siblingIds;
+      analytic('activateSiblingNodes', source);
+      if (!parent || !parent.ideas) {
+        return;
+      }
+      siblingIds = _.map(parent.ideas, function (child) {
+        return child.id;
+      });
+      setActiveNodes(siblingIds);
+    };
+    self.activateNodeAndChildren = function (source) {
+      var contextId = getCurrentlySelectedIdeaId(),
+          subtree = idea.getSubTreeIds(contextId);
+      analytic('activateNodeAndChildren', source);
+      subtree.push(contextId);
+      setActiveNodes(subtree);
+    };
+    _.each(['Left', 'Right', 'Up', 'Down'], function (position) {
+      self['activateNode' + position] = function (source) {
+        applyFuncs[position](source, 'activateNode' + position, function (nodeId) {
+          self.selectNode(nodeId, false, true);
+        });
+      };
+      self['selectNode' + position] = function (source) {
+        applyFuncs[position](source, 'selectNode' + position, self.selectNode);
+      };
+    });
+    self.toggleActivationOnNode = function (source, nodeId) {
+      analytic('toggleActivated', source);
+      if (!self.isActivated(nodeId)) {
+        setActiveNodes([nodeId].concat(activatedNodes));
+      } else {
+        setActiveNodes(_.without(activatedNodes, nodeId));
+      }
+    };
+    self.activateNode = function (source, nodeId) {
+      analytic('activateNode', source);
+      if (!self.isActivated(nodeId)) {
+        activatedNodes.push(nodeId);
+        self.dispatchEvent('activatedNodesChanged', [nodeId], []);
+      }
+    };
+    self.activateChildren = function (source) {
+      var context = currentlySelectedIdea();
+      analytic('activateChildren', source);
+      if (!context || _.isEmpty(context.ideas) || context.getAttr('collapsed')) {
+        return;
+      }
+      setActiveNodes(idea.getSubTreeIds(context.id));
+    };
+    self.activateSelectedNode = function (source) {
+      analytic('activateSelectedNode', source);
+      setActiveNodes([getCurrentlySelectedIdeaId()]);
+    };
+    self.isActivated = function (id) {
+      /*jslint eqeq:true*/
+      return _.find(activatedNodes, function (activeId) {
+        return id == activeId;
+      });
+    };
+    self.applyToActivated = function (toApply) {
+      idea.batch(function () {
+        _.each(activatedNodes, toApply);
+      });
+    };
+    self.everyActivatedIs = function (predicate) {
+      return _.every(activatedNodes, predicate);
+    };
+    self.activateLevel = function (source, level) {
+      var toActivate = _.map(
+          _.filter(
+              currentLayout.nodes,
+              function (node) {
+                /*jslint eqeq:true*/
+                return node.level == level;
+              }
+          ),
+          function (node) {
+            return node.id;
+          }
+      );
+      analytic('activateLevel', source);
+      if (!_.isEmpty(toActivate)) {
+        setActiveNodes(toActivate);
+      }
+    };
+    self.reactivate = function (layout) {
+      _.each(layout.nodes, function (node) {
+        if (_.contains(activatedNodes, node.id)) {
+          node.activated = true;
+        }
+      });
+      return layout;
+    };
+  }());
+
+  self.getNodeIdAtPosition = function (x, y) {
+    var isPointOverNode = function (node) { //move to mapModel candidate
+          /*jslint eqeq: true*/
+          return x >= node.x &&
+              y >= node.y &&
+              x <= node.x + node.width &&
+              y <= node.y + node.height;
+        },
+        node = _.find(currentLayout.nodes, isPointOverNode);
+    return node && node.id;
+  };
+  self.autoPosition = function (nodeId) {
+    return idea.updateAttr(nodeId, 'position', false);
+  };
+  self.positionNodeAt = function (nodeId, x, y, manualPosition) {
+    var rootNode = currentLayout.nodes[idea.id],
+        verticallyClosestNode = {
+          id: null,
+          y: Infinity
+        },
+        parentIdea = idea.findParent(nodeId),
+        parentNode = currentLayout.nodes[parentIdea.id],
+        nodeBeingDragged = currentLayout.nodes[nodeId],
+        tryFlip = function (rootNode, nodeBeingDragged, nodeDragEndX) {
+          var flipRightToLeft = rootNode.x < nodeBeingDragged.x && nodeDragEndX < rootNode.x,
+              flipLeftToRight = rootNode.x > nodeBeingDragged.x && rootNode.x < nodeDragEndX;
+          if (flipRightToLeft || flipLeftToRight) {
+            return idea.flip(nodeId);
+          }
+          return false;
+        },
+        maxSequence = 1,
+        validReposition = function () {
+          return nodeBeingDragged.level === 2 ||
+              ((nodeBeingDragged.x - parentNode.x) * (x - parentNode.x) > 0);
+        },
+        result = false,
+        xOffset;
+    idea.startBatch();
+    if (currentLayout.nodes[nodeId].level === 2) {
+      result = tryFlip(rootNode, nodeBeingDragged, x);
+    }
+    _.each(idea.sameSideSiblingIds(nodeId), function (id) {
+      var node = currentLayout.nodes[id];
+      if (y < node.y && node.y < verticallyClosestNode.y) {
+        verticallyClosestNode = node;
+      }
+    });
+    if (!manualPosition && validReposition()) {
+      self.autoPosition(nodeId);
+    }
+    result = idea.positionBefore(nodeId, verticallyClosestNode.id) || result;
+    if (manualPosition && validReposition()) {
+      if (x < parentNode.x) {
+        xOffset = parentNode.x - x - nodeBeingDragged.width + parentNode.width;
+        /* negative nodes will get flipped so distance is not correct out of the box */
+      } else {
+        xOffset = x - parentNode.x;
+      }
+      analytic('nodeManuallyPositioned');
+      maxSequence = _.max(_.map(parentIdea.ideas, function (i) {
+        return (i.id !== nodeId && i.attr && i.attr.position && i.attr.position[2]) || 0;
+      }));
+      result = idea.updateAttr(
+              nodeId,
+              'position',
+              [xOffset, y - parentNode.y, maxSequence + 1]
+          ) || result;
+    }
+    idea.endBatch();
+    return result;
+  };
+  self.dropNode = function (nodeId, dropTargetId, shiftKey) {
+    var clone,
+        parentIdea = idea.findParent(nodeId);
+    if (dropTargetId === nodeId) {
+      return false;
+    }
+    if (shiftKey) {
+      clone = idea.clone(nodeId);
+      if (clone) {
+        idea.paste(dropTargetId, clone);
+      }
+      return false;
+    }
+    if (dropTargetId === parentIdea.id) {
+      return self.autoPosition(nodeId);
+    } else {
+      ensureNodeIsExpanded(nodeId, dropTargetId);
+      return idea.changeParent(nodeId, dropTargetId);
+    }
+  };
+  self.setLayoutCalculator = function (newCalculator) {
+    layoutCalculator = newCalculator;
+  };
+  self.dropImage = function (dataUrl, imgWidth, imgHeight, x, y, metaData) {
+    var nodeId,
+        dropOn = function (ideaId, position) {
+          var scaleX = Math.min(imgWidth, 300) / imgWidth,
+              scaleY = Math.min(imgHeight, 300) / imgHeight,
+              scale = Math.min(scaleX, scaleY),
+              existing = idea.getAttrById(ideaId, 'icon');
+          self.setIcon('drag and drop', dataUrl, Math.round(imgWidth * scale), Math.round(imgHeight * scale), (existing && existing.position) || position, ideaId, metaData);
+        },
+        addNew = function () {
+          var newId;
+          idea.startBatch();
+          newId = idea.addSubIdea(currentlySelectedIdeaId);
+          dropOn(newId, 'center');
+          idea.endBatch();
+          self.selectNode(newId);
+        };
+    nodeId = self.getNodeIdAtPosition(x, y);
+    if (nodeId) {
+      return dropOn(nodeId, 'left');
+    }
+    addNew();
+  };
+  self.setLabelGenerator = function (labelGenerator) {
+    currentLabelGenerator = labelGenerator;
+    self.rebuildRequired();
+  };
+  self.getReorderBoundary = function (nodeId) {
+    var isRoot = function () {
+          /*jslint eqeq: true*/
+          return nodeId == idea.id;
+        },
+        isFirstLevel = function () {
+          return parentIdea.id === idea.id;
+        },
+        isRightHalf = function (nodeId) {
+          return currentLayout.nodes[nodeId].x >= currentLayout.nodes[idea.id].x;
+        },
+        siblingBoundary = function (siblings, side) {
+          var tops = _.map(siblings, function (node) {
+                return node.y;
+              }),
+              bottoms = _.map(siblings, function (node) {
+                return node.y + node.height;
+              }),
+              result = {
+                'minY': _.min(tops) - reorderMargin - currentLayout.nodes[nodeId].height,
+                'maxY': _.max(bottoms) + reorderMargin,
+                'margin': reorderMargin
+              };
+          result.edge = side;
+          if (side === 'left') {
+            result.x = parentNode.x + parentNode.width + reorderMargin;
+          } else {
+            result.x = parentNode.x - reorderMargin;
+          }
+          return result;
+        },
+        parentBoundary = function (side) {
+          var result = {
+            'minY': parentNode.y - reorderMargin - currentLayout.nodes[nodeId].height,
+            'maxY': parentNode.y + parentNode.height + reorderMargin,
+            'margin': reorderMargin
+          };
+          result.edge = side;
+          if (side === 'left') {
+            result.x = parentNode.x + parentNode.width + reorderMargin;
+          } else {
+            result.x = parentNode.x - reorderMargin;
+          }
+
+          return result;
+        },
+        otherSideSiblings = function () {
+          var otherSide = _.map(parentIdea.ideas, function (subIdea) {
+            return currentLayout.nodes[subIdea.id];
+          });
+          otherSide = _.without(otherSide, currentLayout.nodes[nodeId]);
+          if (!_.isEmpty(sameSide)) {
+            otherSide = _.difference(otherSide, sameSide);
+          }
+          return otherSide;
+        },
+        parentIdea,
+        parentNode,
+        boundaries = [],
+        sameSide,
+        opposite,
+        primaryEdge,
+        secondaryEdge;
+    if (isRoot(nodeId)) {
+      return false;
+    }
+    parentIdea = idea.findParent(nodeId);
+    parentNode = currentLayout.nodes[parentIdea.id];
+    primaryEdge = isRightHalf(nodeId) ? 'left' : 'right';
+    secondaryEdge = isRightHalf(nodeId) ? 'right' : 'left';
+    sameSide = _.map(idea.sameSideSiblingIds(nodeId), function (id) {
+      return currentLayout.nodes[id];
+    });
+    if (!_.isEmpty(sameSide)) {
+      boundaries.push(siblingBoundary(sameSide, primaryEdge));
+    }
+    boundaries.push(parentBoundary(primaryEdge));
+    if (isFirstLevel()) {
+      opposite = otherSideSiblings();
+      if (!_.isEmpty(opposite)) {
+        boundaries.push(siblingBoundary(opposite, secondaryEdge));
+      }
+      boundaries.push(parentBoundary(secondaryEdge));
+    }
+    return boundaries;
+  };
+  self.focusAndSelect = function (nodeId) {
+    self.selectNode(nodeId);
+    self.dispatchEvent('nodeFocusRequested', nodeId);
+  };
+  self.requestContextMenu = function (eventPointX, eventPointY) {
+    if (isInputEnabled && isEditingEnabled) {
+      self.dispatchEvent('contextMenuRequested', currentlySelectedIdeaId, eventPointX, eventPointY);
+      return true;
+    }
+    return false;
+  };
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/map-toolbar-widget.js b/plugins/easy_mindmup/assets/javascripts/mindmup/map-toolbar-widget.js
new file mode 100644
index 0000000000000000000000000000000000000000..30ab46dc4270fcb32716da21fe532c5b14e875da
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/map-toolbar-widget.js
@@ -0,0 +1,40 @@
+/*global jQuery*/
+jQuery.fn.mapToolbarWidget = function (mapModel, ysy) {
+  'use strict';
+  var clickMethodNames = ['insertIntermediate', 'scaleUp', 'scaleDown', 'addSubIdea', 'followURL', 'editNode', 'editNodeData', 'removeSubIdea', 'toggleCollapse', 'addSiblingIdea', 'undo', 'redo',
+        'copy', 'cut', 'paste', 'resetView', 'openAttachment', 'toggleAddLinkMode', 'activateChildren', 'activateNodeAndChildren', 'activateSiblingNodes', 'editIcon', 'toggleOneSide', 'save'],
+      changeMethodNames = ['updateStyle'];
+  return this.each(function () {
+    var element = jQuery(this), preventRoundtrip = false;
+    //mapModel.addEventListener('nodeSelectionChanged', function () {
+    //  preventRoundtrip = true;
+    //  element.find('.updateStyle[data-mm-target-property]').val(function () {
+    //    return mapModel.getSelectedStyle(jQuery(this).data('mm-target-property'));
+    //  }).change();
+    //  preventRoundtrip = false;
+    //});
+    //mapModel.addEventListener('addLinkModeToggled', function () {
+    //  element.find('.toggleAddLinkMode').toggleClass('active');
+    //});
+    clickMethodNames.forEach(function (methodName) {
+      element.find('.' + methodName).click(function () {
+        if ($(this).hasClass("mindmup__button--disabled")) return;
+        if (mapModel[methodName]) {
+          mapModel[methodName]('toolbar');
+        }
+        ysy.toolbar.redraw(methodName);
+      });
+    });
+    changeMethodNames.forEach(function (methodName) {
+      element.find('.' + methodName).change(function () {
+        if (preventRoundtrip) {
+          return;
+        }
+        var tool = jQuery(this);
+        if (tool.data('mm-target-property')) {
+          mapModel[methodName]('toolbar', tool.data('mm-target-property'), tool.val());
+        }
+      });
+    });
+  });
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/mapjs.js b/plugins/easy_mindmup/assets/javascripts/mindmup/mapjs.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ee4e0b34eae2cb62738c8b48655ca7ab34734a4
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/mapjs.js
@@ -0,0 +1 @@
+var MAPJS = MAPJS || {};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/observable.js b/plugins/easy_mindmup/assets/javascripts/mindmup/observable.js
new file mode 100644
index 0000000000000000000000000000000000000000..21d8fbd6e7a980992f96fa09c782531dcae95252
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/observable.js
@@ -0,0 +1,52 @@
+/*global console*/
+/*jshint unused:false */
+var observable = function (base) {
+  'use strict';
+  var listeners = [], x;
+  base.addEventListener = function (types, listener, priority) {
+    types.split(' ').forEach(function (type) {
+      if (type) {
+        listeners.push({
+          type: type,
+          listener: listener,
+          priority: priority || 0
+        });
+      }
+    });
+  };
+  base.listeners = function (type) {
+    return listeners.filter(function (listenerDetails) {
+      return listenerDetails.type === type;
+    }).map(function (listenerDetails) {
+      return listenerDetails.listener;
+    });
+  };
+  base.removeEventListener = function (type, listener) {
+    listeners = listeners.filter(function (details) {
+      return details.listener !== listener;
+    });
+  };
+  base.dispatchEvent = function (type) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    base.getYsy().log.debug("Event " + type + " " + JSON.stringify(args), "events");
+    if (type === 'changed') {
+      base.getYsy().log.debug("Event " + type + " " + JSON.stringify(args), "changedEvent");
+    }
+    listeners
+        .filter(function (listenerDetails) {
+          return listenerDetails.type === type;
+        })
+        .sort(function (firstListenerDetails, secondListenerDetails) {
+          return secondListenerDetails.priority - firstListenerDetails.priority;
+        })
+        .some(function (listenerDetails) {
+          //try {
+          return listenerDetails.listener.apply(undefined, args) === false;
+          //} catch (e) {
+          //  console.log('dispatchEvent failed', e, listenerDetails);
+          //}
+
+        });
+  };
+  return base;
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mindmup/url-helper.js b/plugins/easy_mindmup/assets/javascripts/mindmup/url-helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..4733c3094d56ee460f62ba6e35e19cd5d9cde03c
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mindmup/url-helper.js
@@ -0,0 +1,23 @@
+/*global MAPJS */
+MAPJS.URLHelper = {
+  urlPattern: /(https?:\/\/|www\.)[\w-]+(\.[\w-]+)+([\w.,!@?^=%&amp;:\/~+#-]*[\w!@?^=%&amp;\/~+#-])?/i,
+  containsLink: function (text) {
+    'use strict';
+    return MAPJS.URLHelper.urlPattern.test(text);
+  },
+  getLink: function (text) {
+    'use strict';
+    var url = text.match(MAPJS.URLHelper.urlPattern);
+    if (url && url[0]) {
+      url = url[0];
+      if (!/https?:\/\//i.test(url)) {
+        url = 'http://' + url;
+      }
+    }
+    return url;
+  },
+  stripLink: function (text) {
+    'use strict';
+    return text.replace(MAPJS.URLHelper.urlPattern, '');
+  }
+};
diff --git a/plugins/easy_mindmup/assets/javascripts/mm_context_menu.js b/plugins/easy_mindmup/assets/javascripts/mm_context_menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..66d1a8c11314d8038b305c96c9a3c24bfc255d5c
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/mm_context_menu.js
@@ -0,0 +1,247 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @property {boolean} hidden
+   * @property {jQuery} $element
+   * @constructor
+   */
+  function ContextMenu(ysy) {
+    this.ysy = ysy;
+    this.$element = null;
+    this.hidden = true;
+    this.init(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  ContextMenu.prototype.init = function (ysy) {
+    ysy.eventBus.register("MapInited", $.proxy(this.prepare, this));
+  };
+  ContextMenu.prototype.prepare = function (mapModel) {
+    var $element = $("#context-menu");
+    if ($element.length === 0) {
+      $element =
+          $('<div id="context-menu" class="mindmup__context_menu ' + (this.ysy.settings.easyRedmine ? "easy" : "redmine") + '"></div>')
+          .appendTo('#content').hide();
+    }
+    this.$element = $element;
+    var self = this;
+    mapModel.addEventListener('mapMoveRequested mapScaleChanged nodeSelectionChanged nodeEditRequested mapViewResetRequested', function () {
+      self.innerHide()
+    });
+    mapModel.addEventListener('contextMenuRequested', function (id, x, y) {
+      self.innerShow(id, x, y);
+    });
+    $element.on('contextmenu', function (e) {
+      e.preventDefault();
+      e.stopPropagation();
+      return false;
+    });
+  };
+  /**
+   * Hide context menu (but have to have correct [this])
+   */
+  ContextMenu.prototype.innerHide = function () {
+    if (this.hidden) return;
+    this.hidden = true;
+    $("body").off("click.context_out");
+    this.$element.hide();
+  };
+  /**
+   *
+   * @param {number} nodeId
+   * @param {number} x
+   * @param {number} y
+   */
+  ContextMenu.prototype.innerShow = function (nodeId, x, y) {
+    this.hidden = false;
+    var self = this;
+    var ysy = this.ysy;
+    var hide = function () {
+      self.innerHide();
+    };
+    var $element = this.$element;
+    if (nodeId === 1) {
+      var idea = ysy.idea;
+    } else {
+      idea = ysy.idea.findSubIdeaById(nodeId);
+    }
+    var rendered = Mustache.render(this.template, this.getStructure(idea));
+    $element.html(rendered);
+    if (x === undefined) {
+      var offset = ysy.$container.find("#node_" + nodeId).position();
+      x = offset.left + 30;
+      y = offset.top + 20;
+    }
+    var maxLeft = x + 2 * $element.width();
+    var maxTop = y + $element.height();
+    var ws = this.window_size();
+    if (maxLeft > ws.width) {
+      x -= $element.width();
+      $element.addClass('reverse-x');
+    } else {
+      $element.removeClass('reverse-x');
+    }
+    if (maxTop > ws.height + $(window).scrollTop()) {
+      y -= $element.height();
+      $element.addClass('reverse-y');
+    } else {
+      $element.removeClass('reverse-y');
+    }
+    if (x <= 0) x = 1;
+    if (y <= 0) y = 1;
+    this.bindEvents(idea, $element);
+    $element.css({left: x + 'px', top: y + 'px'});
+    $element.show().focus();
+    $("body").off("click.context_out").on("click.context_out", hide);
+    // $element.off("mouseleave").on('mouseleave', hide);
+    $element.off('tap click', hide).on('tap click', hide);
+  };
+  /**
+   *
+   * @param {ModelEntity} primaryNode
+   * @param {jQuery} $element
+   */
+  ContextMenu.prototype.bindEvents = function (primaryNode, $element) {
+    var ysy = this.ysy;
+    var self = this;
+    var mapModel = ysy.mapModel;
+    $element.mapToolbarWidget(mapModel, ysy);
+    $element.find(".mindmup-data-value-changer:not(.disabled)").on('tap click', function () {
+      var $this = $(this);
+      var obj = {};
+      obj[$this.data("key")] = $this.data("value");
+      var activatedIds = ysy.mapModel.getActivatedNodeIds();
+      for (var i = 0; i < activatedIds.length; i++) {
+        var node = ysy.idea;
+        if (activatedIds[i] !== 1) {
+          node = node.findSubIdeaById(activatedIds[i]);
+        }
+        if (node.attr.entityType !== primaryNode.attr.entityType) continue;
+        ysy.setData(node, obj);
+      }
+      //ysy.mapModel.setData(primaryNode, obj);
+      ysy.idea.dispatchEvent('changed');
+    });
+    $element.find(".mindmup-data-value-input-link:not(.disabled)").on('tap click', function () {
+      var $this = $(this);
+      $this.hide();
+      var $input = $this.parent().find(".mindmup-data-value-input");
+      if ($input.length === 0) return;
+      $input.show().focus();
+      $input.on("change", function () {
+        var obj = {};
+        var newValue = $input.val();
+        if ($input.attr("type") === "number") {
+          newValue = parseFloat(newValue);
+        }
+        obj[$input.data("key")] = newValue;
+        var activatedIds = ysy.mapModel.getActivatedNodeIds();
+        for (var i = 0; i < activatedIds.length; i++) {
+          var node = ysy.mapModel.findIdeaById(activatedIds[i]);
+          if (node.attr.entityType !== primaryNode.attr.entityType) continue;
+          ysy.setData(node, obj);
+        }
+        self.innerHide();
+        ysy.idea.dispatchEvent('changed');
+
+      });
+      return false;
+      // var obj = {};
+      // obj[$this.data("key")] = $this.data("value");
+      //ysy.mapModel.setData(primaryNode, obj);
+    });
+  };
+  ContextMenu.prototype.window_size = function () {
+    var w;
+    var h;
+    if (window.innerWidth) {
+      w = window.innerWidth;
+      h = window.innerHeight;
+    } else if (document.documentElement) {
+      w = document.documentElement.clientWidth;
+      h = document.documentElement.clientHeight;
+    } else {
+      w = document.body.clientWidth;
+      h = document.body.clientHeight;
+    }
+    return {width: w, height: h};
+  };
+  ContextMenu.prototype.template = '\
+    <ul>\
+      {{#.}}\
+      {{^skip}}\
+        <li class="{{folderClass}}{{className}}">\
+          <a href="javascript:void(0)" class="{{submenuClass}}{{aClassName}}">{{name}}</a>\
+          {{{subMenu}}}\
+          {{{changer}}}\
+          {{{input}}}\
+        </li>\
+      {{/skip}}\
+      {{/.}}\
+    </ul>';
+  ContextMenu.prototype.subMenuTemplate = '\
+    <ul>\
+      {{#subMenu}}\
+        <li><a href="javascript:void(0)" class="{{className}} mindmup-context-submenu-item" >{{name}}</a></li>\
+      {{/subMenu}}\
+    </ul>\
+  ';
+  ContextMenu.prototype.changerTemplate = '\
+    <ul>\
+      {{#changer}}\
+        <li><a href="javascript:void(0)" class="mindmup-data-value-changer {{className}} mindmup-context-submenu-item {{#previous}}icon-checked easy-mindmup__icon--checked disabled{{/previous}}" data-key="{{key}}" data-value="{{value}}">{{name}}</a></li>\
+      {{/changer}}\
+    </ul>\
+  ';
+  ContextMenu.prototype.inputTemplate = '\
+    <input type="{{input.inputType}}" class="mindmup-data-value-input {{input.className}}" data-key="{{key}}" value="{{input.value}}" style="display: none">\
+  ';
+  /**
+   * @type {ModelEntity} node
+   */
+  ContextMenu.prototype.getStructure = function (node) {
+    node.attr.force = true;
+    // Override this - structure is inserted into Mustache template
+    throw "getStructure is not defined!";
+  };
+  var skipOption = {skip: true};
+  /**
+   * Executes [func] if [skip]==false
+   * Otherwise it returns null to be filtered later
+   * @param {Function} func
+   * @param {boolean} skip
+   */
+  ContextMenu.prototype.prepareOption = function (func, skip) {
+    if (skip) return skipOption;
+    var result = func.call(this);
+    if (result.subMenu) {
+      result.subMenu = Mustache.render(this.subMenuTemplate, result);
+    }
+    if (result.changer) {
+      result.changer = Mustache.render(this.changerTemplate, result);
+    }
+    if (result.input) {
+      result.input = Mustache.render(this.inputTemplate, result);
+    }
+    if (result.subMenu || result.changer) {
+      result.submenuClass = "submenu ";
+      result.folderClass = "folder ";
+    }
+    return result;
+  };
+
+  // /**
+  //  * Filter out results
+  //  * @param {Array.<{skip:boolean}>} array
+  //  */
+  // ContextMenu.prototype.filterSkipped = function (array) {
+  //
+  // };
+
+  window.easyMindMupClasses.ContextMenu = ContextMenu;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/model_classes.js b/plugins/easy_mindmup/assets/javascripts/model_classes.js
new file mode 100644
index 0000000000000000000000000000000000000000..25525d7a49153736b847ca4a04a4eec49182f6d7
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/model_classes.js
@@ -0,0 +1,214 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  /**
+   * Inner part of ModelEntity - contains data from server
+   * @property {number|null} id - server ID
+   * @property {Object} _old
+   * @property {boolean} filtered_out - entity do not satisfy query filter
+   * @property {Array} custom_fields
+   * @property {Object} custom_field_values
+   * @constructor
+   */
+  function ModelEntityData() {
+    this.id = null;
+    this._old = null;
+    this.filtered_out = false;
+  }
+
+  /**
+   *
+   * @param {Object} source
+   * @return {ModelEntityData}
+   */
+  ModelEntityData.prototype.fromServer = function (source) {
+    $.extend(this, source);
+    return this;
+  };
+  /**
+   * Accepts JSON and do deep cleaning and extending
+   * @param {String} json
+   * @param {boolean} doNotOverwrite
+   */
+  ModelEntityData.prototype.fromJsonString = function (json, doNotOverwrite) {
+    var copied = JSON.parse(json);
+    if (doNotOverwrite) {
+      _.defaults(this, copied);
+    } else {
+      this.extend(copied);
+    }
+  };
+  /**
+   *
+   * @param {Object} source
+   * @return {ModelEntityData}
+   */
+  ModelEntityData.prototype.extend = function (source) {
+    $.extend(this, source);
+    return this;
+  };
+  window.easyMindMupClasses.ModelEntityData = ModelEntityData;
+  //####################################################################################################################
+  /**
+   * specially selected Attributes - it is accessible from [Idea] and from [Node]
+   * @property {boolean} nonEditable
+   * @property {String} entityType
+   * @property {ModelEntityData} data
+   * @property {boolean} isFromServer
+   * @constructor
+   */
+  function ModelEntityAttr() {
+    this.nonEditable = false;
+    this.entityType = "";
+    this.data = new ModelEntityData();
+    this.isFromServer = false;
+    this.isFresh = false;
+  }
+
+  /**
+   *
+   * @param {String} entityType
+   * @param {boolean} editable
+   * @param {Object} source
+   * @return {ModelEntityAttr}
+   */
+  ModelEntityAttr.prototype.fromServer = function (entityType, editable, source) {
+    this.entityType = entityType;
+    this.isFromServer = true;
+    this.nonEditable = !editable;
+    this.data.fromServer(source);
+    return this;
+  };
+  /**
+   *
+   * @param {Object} json
+   * @return {ModelEntityAttr}
+   */
+  ModelEntityAttr.prototype.fromJson = function (json) {
+    if (!json) return this;
+    $.extend(this, _.omit(json, ["data"]));
+    this.data.extend(json.data);
+    return this;
+  };
+  window.easyMindMupClasses.ModelEntityAttr = ModelEntityAttr;
+  //####################################################################################################################
+  /**
+   * Universal entity from which model tree is generated
+   * @property {number} id
+   * @property {String} title
+   * @property {number} nextChildIndex
+   * @property {Object.<String, ModelEntity>} ideas
+   * @property {ModelEntityAttr} attr
+   * @property {ModelEntity} parent - temporary = used only for generating of model tree and saving
+   * @property {number} rank - relative position of node among its siblings
+   *    temporary = it is used as key in [ideas] of parent ModelEntity
+   * @constructor
+   */
+  function ModelEntity() {
+    this.id = 0;
+    this.title = "No title";
+    this.nextChildIndex = 0;
+    this.ideas = {};
+    this.attr = new ModelEntityAttr();
+    this.parent = null;
+    this.rank = 0;
+  }
+
+  /**
+   * "constructor" of ModelEntity with data from server
+   * @param {number} id
+   * @param {String} name
+   * @param {String} entityType
+   * @param {boolean} editable
+   * @param {object} source
+   */
+  ModelEntity.prototype.fromServer = function (id, name, entityType, editable, source) {
+    this.id = id;
+    this.title = name;
+    this.attr.fromServer(entityType, editable, source);
+    return this;
+  };
+  /**
+   * simple extending of the ModelEntity - do not use this pls
+   * @param {Object} obj
+   * @return {ModelEntity}
+   */
+  ModelEntity.prototype.extend = function (obj) {
+    $.extend(true, this, obj);
+    return this;
+  };
+  /**
+   * Create whole ModelEntity tree from JSON
+   * @param {object} json
+   * @return {ModelEntity}
+   */
+  ModelEntity.prototype.fromJson = function (json) {
+    $.extend(this, _.omit(json, ["ideas", "attr"]));
+    this.attr.fromJson(json.attr);
+    for (var rank in json.ideas) {
+      if (!json.ideas.hasOwnProperty(rank)) continue;
+      this.ideas[rank] = new ModelEntity().fromJson(json.ideas[rank]);
+    }
+    return this;
+  };
+
+  window.easyMindMupClasses.ModelEntity = ModelEntity;
+//####################################################################################################################
+  /**
+   * Root idea (=ModelEntity) is enhanced by MAPJS.Content mixin (which I cannot modify),
+   * so several functions from Content I have to declare here.
+   * @extends ModelEntity
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function RootIdea(ysy) {
+    ModelEntity.prototype.constructor.call(this, ysy);
+    /** @return {MindMup} */
+    this.getYsy = function () {
+      return ysy;
+    };
+  }
+
+  window.easyMindMupClasses.extendClass(RootIdea, ModelEntity);
+  /**
+   * Create RootIdea instance with same attributes as source ModelEntity, so it can replace it.
+   * @param {ModelEntity} idea
+   * @return {RootIdea}
+   */
+  RootIdea.prototype.upgrade = function (idea) {
+    $.extend(this, idea);
+    return this;
+  };
+
+  /** @type {Function} */
+  RootIdea.prototype.removeEventListener = null;
+  /** @type {Function} */
+  RootIdea.prototype.addEventListener = null;
+  /** @type {Function} */
+  RootIdea.prototype.dispatchEvent = null;
+  /** @type {Function} */
+  RootIdea.prototype.findSubIdeaById = null;
+  /** @type {Function} */
+  RootIdea.prototype.findParent = null;
+  /** @type {Array} */
+  RootIdea.prototype.links = null;
+  /** @type {boolean} */
+  RootIdea.prototype.oneSideOn = false;
+  /**
+   *
+   * @param {String} method
+   * @param {Array} args
+   * @param {Function} undofunc
+   * @param {String} [originSession]
+   */
+  RootIdea.prototype.logChange = function (method, args, undofunc, originSession) {
+  };
+  RootIdea.prototype.resetHistory = function () {
+  };
+  RootIdea.prototype.updateOneSide = function () {
+  };
+
+
+  window.easyMindMupClasses.RootIdea = RootIdea;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/node_patch.js b/plugins/easy_mindmup/assets/javascripts/node_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2b0aa3d3b23d8add08784ce0cc1d1b415d7b26f
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/node_patch.js
@@ -0,0 +1,209 @@
+(function () {
+  /**
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function NodePatch(ysy) {
+    this.ysy = ysy;
+    this.patch();
+    this.initCoreIcons();
+  }
+
+  /**
+   * Choose icons from [icons] attribute and use it to render.
+   * It use the order specified in Array
+   * @type {Object.<String,Array.<String>>}
+   */
+  NodePatch.prototype.iconsForEntity = {
+    // issue:["avatar"]
+  };
+  /**
+   * @callback IconBuilder
+   * @param {ModelEntity} nodeContent - it is different from ModelEntity, but it share [attr] reference
+   */
+  /**
+   * Object filled with IconBuilders
+   * @type {Object.<String,IconBuilder>}
+   */
+  NodePatch.prototype._icons = {};
+
+  /**
+   * @param {String} key
+   * @param {IconBuilder} builder
+   */
+  NodePatch.prototype.addIconBuilder = function (key, builder) {
+    this._icons[key] = builder;
+  };
+
+  NodePatch.prototype.initCoreIcons = function () {
+    /** @type {WbsMain} */
+    var ysy = this.ysy;
+    this.addIconBuilder("avatar", function (nodeContent) {
+      var users = ysy.dataStorage.get("users");
+      var assigneeIndex = _.findIndex(users, {
+        id: ysy.getData(nodeContent).assigned_to_id
+      });
+      if (assigneeIndex > -1) {
+        var user = users[assigneeIndex];
+        var avatarUrl = user.avatar_url ? user.avatar_url : "/plugin_assets/easy_extensions/images/avatar.jpg";
+        return '<span class="avatar-container"><img width="64" height="64" alt="' + user.name + '" class="gravatar" src="' + avatarUrl + '"></span>';
+      }
+    });
+  };
+
+  /**
+   * Add extra CSS classes to node element
+   * @param {ModelEntity} nodeContent
+   * @example return nodeContent.attr.isProject ? " wbs-project" : " wbs-issue";
+   * @return {String}
+   */
+  NodePatch.prototype.nodeBonusCss = function (nodeContent) {
+    nodeContent.title = "true";
+    // Override this for adding special data-specific classes to node
+    throw "nodeBonusCss is not defined!";
+  };
+  /**
+   * extract and transform name property from node
+   * @param {ModelEntity} nodeContent
+   * @return {string}
+   */
+  var getNodeText = function (nodeContent) {
+    var MAX_URL_LENGTH = 25;
+    var title = nodeContent.title;
+    var text = title.length < MAX_URL_LENGTH ? title : (title.substring(0, MAX_URL_LENGTH) + '...');
+    return text.trim();
+  };
+  NodePatch.prototype.getNodeText = getNodeText;
+  NodePatch.prototype.patch = function () {
+    jQuery.fn.updateNodeContent = function (nodeContent, ysy) {
+      'use strict';
+      // var MAX_URL_LENGTH = 25,
+      var self = jQuery(this),
+          title = getNodeText(nodeContent),
+          updateText = function (title) {
+            // var text = MAPJS.URLHelper.stripLink(title) ||
+            //         (title.length < MAX_URL_LENGTH ? title : (title.substring(0, MAX_URL_LENGTH) + '...')),
+            var span = self.find('[data-mapjs-role=title]');
+            if (span.length === 0) {
+              span = jQuery('<span>').attr('data-mapjs-role', 'title').appendTo(self);
+            }
+            span.text(title);
+          },
+          setStyles = function () {
+            var element = self.find('.mapjs-collapsor');
+            var oldClassName = self[0].className;
+            self[0].className = "mapjs-node"
+                + (oldClassName.indexOf("activated") > -1 ? " activated" : "")
+                + (oldClassName.indexOf("selected") > -1 ? " selected" : "")
+                + ysy.styles.cssClasses(nodeContent)
+                + (nodeContent.x && nodeContent.x + nodeContent.width < 0 ? ' mindmup-node-left' : '')
+                + (nodeContent.attr.collapsed && nodeContent.attr.hasChildren ? ' collapsed' : '')
+                + ysy.nodePatch.nodeBonusCss(nodeContent)
+                + (nodeContent.attr.data.filtered_out ? " mindmup__node--filtered_out" : "")
+                + (ysy.filter.isBanned(nodeContent) ? " " + ysy.filter.className : "");
+            if (nodeContent.attr.hasChildren) {
+              if (element.length === 0) {
+                jQuery('<div class="mapjs-collapsor"></div>').on("tap", function () {
+                  var model = ysy.mapModel;
+                  model.selectNode(nodeContent.id);
+                  model.toggleCollapse();
+                  return false;
+                }).appendTo(self);
+              }
+            } else if (element.length > 0) {
+              element.remove();
+            }
+          },
+          // needChangeIcon = function () {
+          //   return !!ysy.getData(nodeContent)._old;
+          // },
+          // setChangeIcon = function ($iconsCont) {
+          //   var isEdited = !!ysy.getData(nodeContent)._old;
+          //   if (isEdited) {
+          //     var $icon = $iconsCont.find(".mindmup-node-icon-is_edited");
+          //     if ($icon.length === 0) {
+          //       $iconsCont.prepend('<div class="mindmup-node-icon-is_edited" title="' + ysy.settings.labels.titleNodeChanged + '"></div>');
+          //     }
+          //   } else {
+          //     $iconsCont.find(".mindmup-node-icon-is_edited").remove();
+          //   }
+          // },
+          needIcons = function () {
+            return ysy.settings.allIcons;
+          },
+          setIcons = function ($iconsCont) {
+            var creatorName, creator, icon;
+            var iconCreators = ysy.nodePatch.iconsForEntity[nodeContent.attr.entityType];
+            if (!iconCreators) return;
+            var nodePatch = ysy.nodePatch;
+            if (!ysy.settings.allIcons) return;
+            var $icons = $iconsCont.find(".mindmup-node-icons-all");
+            if ($icons.length === 0) {
+              $icons = $('<div class="mindmup-node-icons-all"></div>');
+              $iconsCont.append($icons);
+            }
+            var lastIconCreatorName = null;
+            for (var i = 0; i < iconCreators.length; i++) {
+              creatorName = iconCreators[i];
+              creator = nodePatch._icons[creatorName];
+              if (!creator) continue;
+              var newIconContent = creator(nodeContent);
+              icon = $icons.find(".mindmup-node-icon-" + creatorName);
+              if (newIconContent) {
+                if (!icon.length) {
+                  icon = $('<div class="mindmup-node-icon mindmup-node-icon-' + creatorName + '"></div>');
+                  if (!lastIconCreatorName) {
+                    $icons.prepend(icon);
+                  } else {
+                    $icons.find(".mindmup-node-icon-" + lastIconCreatorName).after(icon);
+                  }
+                }
+                if (typeof newIconContent === "string") {
+                  icon.html(newIconContent);
+                } else {
+                  icon.clear().append(newIconContent);
+                }
+                lastIconCreatorName = creatorName;
+              } else {
+                icon.remove();
+              }
+            }
+          };
+      if (!self.is(":focus")) {
+        var parent = self.parent();
+        self.detach();
+        var detached = true;
+      }
+      updateText(title);
+      self.data({
+        'x': Math.round(nodeContent.x),
+        'y': Math.round(nodeContent.y),
+        'width': Math.round(nodeContent.width),
+        'height': Math.round(nodeContent.height),
+        'nodeId': nodeContent.id,
+        'title': title
+      });
+      setStyles();
+      var $element = self.find('.mindmup-node-icons');
+      if (needIcons()) {
+        if ($element.length === 0) {
+          $element = $('<div class="mindmup-node-icons"></div>');
+          var haveToBeAppended = true;
+        }
+        //setChangeIcon($element);
+        setIcons($element);
+        if (haveToBeAppended) {
+          $element.appendTo(self);
+        }
+      } else if ($element.length > 0) {
+        $element.remove();
+      }
+      if (nodeContent.attr.force) delete nodeContent.attr.force;  // remove force flag if present
+      if (detached) {
+        parent.append(self);
+      }
+      return self;
+    };
+  };
+  window.easyMindMupClasses.NodePatch = NodePatch;
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/polyfill.js b/plugins/easy_mindmup/assets/javascripts/polyfill.js
new file mode 100644
index 0000000000000000000000000000000000000000..af247c72fe5762d4114e70cead2f285cee7d7cda
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/polyfill.js
@@ -0,0 +1,27 @@
+if (!Function.prototype.bind) {
+  Function.prototype.bind = function(oThis) {
+    if (typeof this !== 'function') {
+      // closest thing possible to the ECMAScript 5
+      // internal IsCallable function
+      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
+    }
+
+    var aArgs   = Array.prototype.slice.call(arguments, 1),
+        fToBind = this,
+        fNOP    = function() {},
+        fBound  = function() {
+          return fToBind.apply(this instanceof fNOP
+                  ? this
+                  : oThis,
+              aArgs.concat(Array.prototype.slice.call(arguments)));
+        };
+
+    if (this.prototype) {
+      // Function.prototype doesn't have a prototype property
+      fNOP.prototype = this.prototype;
+    }
+    fBound.prototype = new fNOP();
+
+    return fBound;
+  };
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/print.js b/plugins/easy_mindmup/assets/javascripts/print.js
new file mode 100644
index 0000000000000000000000000000000000000000..43572705c85c1b4aa22aba4d96e8a68e302dc82d
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/print.js
@@ -0,0 +1,146 @@
+(function () {
+  function Print(ysy) {
+    this.printReady = false;
+    this.$area = null;
+    this.ysy = ysy;
+    this.patch(ysy);
+  }
+
+  Print.prototype.margins = {
+    left: 10,
+    right: 10,
+    top: 20,
+    bottom: 10
+  };
+  Print.prototype.patch = function (ysy) {
+    var self = this;
+    var mediaQueryList = window.matchMedia('print');
+    mediaQueryList.addListener(function (mql) {
+      if (mql.matches) {
+        self.beforePrint();
+      } else {
+        self.afterPrint();
+      }
+    });
+    window.onbeforeprint = $.proxy(this.beforePrint, this);
+    window.onafterprint = $.proxy(this.afterPrint, this);
+  };
+  Print.prototype.directPrint = function () {
+    this.beforePrint();
+    window.print();
+    this.afterPrint();
+  };
+  Print.prototype.beforePrint = function () {
+    if (this.printReady) return;
+    this.ysy.mapModel.resetView();
+    this.$area = this.createPrintArea();
+    $("body").append(this.$area);
+    $("#wrapper").hide();
+    this.printReady = true;
+    return this.$area;
+  };
+  Print.prototype.afterPrint = function () {
+    if (!this.printReady) return;
+    this.$area.remove();
+    $("#wrapper").show();
+    this.ysy.mapModel.resetView();
+    this.printReady = false;
+  };
+  Print.prototype.createPrintArea = function () {
+    var $stage = this.ysy.$container.children();
+    // var width = $stage.width();
+    // var height = $stage.height();
+    var stripWidth = 330;
+    var children = $stage.children(":not(:hidden)");
+    var dims = this.getStageDims(children);
+    var $area = $('<div id="mindmup__print-area" class="mindmup__print-area mindmup__print-area--stripped scheme-by-' + this.ysy.styles.setting + '"></div>');
+    for (var p = dims.left - this.margins.left; p < dims.right + this.margins.right; p += stripWidth) {
+      $area.append(this.createStrip(children, dims, p, p + stripWidth));
+    }
+    return $area;
+  };
+  Print.prototype.createStrip = function (children, dims, start, end) {
+    /* start can be negative*/
+    if (end <= start) return null;
+    // var stageOffset = $stage.height();
+    var $strip = $('<div class="mindmup__print-strip" style="height:' + (dims.bottom - dims.top + this.margins.top + this.margins.bottom) + 'px;width:' + (end - start) + 'px"></div>');
+    // var children = $stage.children(":not(:hidden)");
+    var added = 0;
+    var topEdge = dims.top - this.margins.top;
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      var left = parseInt(child.style.left);
+      var width = child.offsetWidth;
+      if (left > end + 5) continue;
+      if (left + width < start - 5) continue;
+      added++;
+      $strip.append(
+          $(child)
+              .clone()
+              .css({
+                left: left - start,
+                top: parseInt(child.style.top) - topEdge,
+                width: width
+              })
+      );
+    }
+    if (!added) return null;
+    return $strip;
+  };
+  Print.prototype.getStageDims = function (children) {
+    var dims = {
+      left: Infinity,
+      top: Infinity,
+      right: -Infinity,
+      bottom: -Infinity
+    };
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      var left = parseInt(child.style.left);
+      var top = parseInt(child.style.top);
+      var width = child.offsetWidth;
+      var height = child.offsetHeight;
+      if (left < dims.left) dims.left = left;
+      if (top < dims.top) dims.top = top;
+      if (left + width > dims.right) dims.right = left + width;
+      if (top + height > dims.bottom) dims.bottom = top + height;
+    }
+    return dims;
+  };
+
+  window.easyMindMupClasses.Print = Print;
+  //####################################################################################################################
+  /**
+   * Button, which prepare Mind Map into printable version
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function PrintButton(ysy, $parent) {
+    this.$element = null;
+    this.ysy = ysy;
+    this.init(ysy, $parent);
+  }
+
+  PrintButton.prototype.id = "PrintButton";
+
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @return {PrintButton}
+   */
+  PrintButton.prototype.init = function (ysy, $parent) {
+    this.$element = $parent.find(".mindmup-button-print");
+    var self = this;
+    this.$element.click(function () {
+      self.ysy.print.directPrint();
+    });
+    return this;
+  };
+  PrintButton.prototype._render = function () {
+  };
+
+  window.easyMindMupClasses.PrintButton = PrintButton;
+
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/redrawer.js b/plugins/easy_mindmup/assets/javascripts/redrawer.js
new file mode 100644
index 0000000000000000000000000000000000000000..0fcd9a3907f898802946de0a9f117ce23beeab1f
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/redrawer.js
@@ -0,0 +1,52 @@
+(function () {
+  /**
+   * Asynchronic redrawer
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Repainter(ysy) {
+    this.ysy = ysy;
+    this.onRepaint = [];
+    var self = this;
+    var animationLoop = function () {
+      var queue = self.onRepaint;
+      if (queue.length > 0) {
+        self.onRepaint = [];
+        for (var i = 0; i < queue.length; i++) {
+          var widget = queue[i];
+          widget._redrawRequested = false;
+          widget._render();
+        }
+      }
+      requestAnimationFrame(animationLoop);
+    };
+    this.animationLoop = animationLoop;
+  }
+
+  Repainter.prototype.start = function () {
+    this.animationLoop();
+  };
+  /**
+   * Main function - insert widget into repaint queue (if not present there)
+   * @param {Object} widget
+   */
+  Repainter.prototype.redrawMe = function (widget) {
+    if (widget._redrawRequested) return;
+    widget._redrawRequested = true;
+    this.onRepaint.push(widget);
+  };
+  /**
+   * Whole node tree will be repainted. Repaint can be delayed to next change by [noRender] parameter
+   * @param {boolean} [noRender]
+   */
+  Repainter.prototype.forceRedraw = function (noRender) {
+    var idea = this.ysy.idea;
+    this.ysy.util.traverse(idea, function (node) {
+      node.attr.force = true;
+    });
+    if (!noRender) {
+      idea.dispatchEvent('changed');
+    }
+  };
+  window.easyMindMupClasses.Repainter = Repainter;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/save_info.js b/plugins/easy_mindmup/assets/javascripts/save_info.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9cfcad791a30e6a8b3feacfe1339d80ae3685cf
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/save_info.js
@@ -0,0 +1,56 @@
+/**
+ * Created by hosekp on 1/23/17.
+ */
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @property {jQuery} $parent
+   * @property {jQuery} $element
+   * @property {Date} lastTime
+   * @property {string} state
+   * @constructor
+   */
+  function SaveInfo(ysy) {
+    this.ysy = ysy;
+    this.init(ysy);
+    this.state = 'initial';
+    this.lastTime = new Date();
+  }
+
+  /** @param {MindMup} ysy */
+  SaveInfo.prototype.init = function (ysy) {
+    this.$parent = ysy.toolbar.$menu;
+    ysy.toolbar.addChild(this);
+    this.$element = this.$parent.find(".mindmup__menu-save");
+    if (window.moment) {
+      var self = this;
+      this.interval = window.setInterval(function () {
+        ysy.repainter.redrawMe(self);
+      }, 60 * 1000);
+    }
+  };
+
+  SaveInfo.prototype.isSaved = function (isAutosave) {
+    if (!this.$element.length) return;
+    this.lastTime = new Date();
+    this.state = isAutosave ? "autosaved" : "saved";
+    this.ysy.repainter.redrawMe(this);
+  };
+
+  SaveInfo.prototype._render = function () {
+    if (!this.$element.length) return;
+    var rendered;
+    var labels = this.ysy.settings.labels.save_info;
+    if (window.moment) {
+      rendered = labels[this.state] + " " + moment(this.lastTime).fromNow();
+    } else {
+      rendered = labels[this.state] + " " + labels["at"] + " " + this.lastTime.toTimeString().split(" ")[0];
+    }
+    this.$element.attr("title", rendered);
+  };
+
+  window.easyMindMupClasses.SaveInfo = SaveInfo;
+  //####################################################################################################################
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/save_progress.js b/plugins/easy_mindmup/assets/javascripts/save_progress.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab6ac505254229ec66962c0c750aa348c8599b2e
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/save_progress.js
@@ -0,0 +1,80 @@
+/**
+ * Created by hosekp on 11/15/16.
+ */
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function SaveProgress(ysy) {
+    this.ysy = ysy;
+    this.currentScore = 0;
+    this.fullScore = 0;
+    this.$element = $(ysy.settings.templates.saveProgressModal);
+    this.$element.hide();
+    this.$element.appendTo("body");
+    this.$progressBar = this.$element.find(".mindmup-progress-bar");
+    this.hidden = true;
+
+  }
+
+  SaveProgress.prototype.scoreForSequential = 1;
+  SaveProgress.prototype.scoreForParallel = 1 / 4.0;
+  SaveProgress.prototype.scoreForLayout = 1 / 4.0;
+  /**
+   *
+   * @param {Array.<SendPack>} list
+   * return {number}
+   */
+  SaveProgress.prototype.estimateProgress = function (list) {
+    var short = 0;
+    for (var i = 0; i < list.length; i++) {
+      if (list[i].isSafe) short++;
+    }
+    return list.length - short * (this.scoreForSequential - this.scoreForParallel);//+this.scoreForLayout;
+    // return {short:short,long:list.length-short,average:list.length-short*5/6};
+  };
+  /**
+   * Entry function - it shows progress bar and estimate its score (difficulty to fill)
+   * @param {Array.<SendPack>} list
+   */
+  SaveProgress.prototype.startProgress = function (list) {
+    this.fullScore = this.estimateProgress(list);
+    if (this.fullScore <= 1) return;
+    this.currentScore = 0;
+    this.hidden = false;
+    this.$element.show();
+    this.ysy.repainter.redrawMe(this);
+  };
+  /**
+   *
+   * @param {SendPack} sendPack
+   */
+  SaveProgress.prototype.requestFinished = function (sendPack) {
+    if (this.hidden) return;
+    if (!sendPack) {
+      this.hide();
+      return;
+    }
+    if (sendPack.isSafe) {
+      this.currentScore += this.scoreForParallel;
+    } else {
+      this.currentScore += this.scoreForSequential;
+    }
+    if (this.currentScore >= this.fullScore) {
+      this.hide();
+    } else {
+      this.ysy.repainter.redrawMe(this);
+    }
+  };
+  SaveProgress.prototype._render = function () {
+    this.$progressBar.width((100 * this.currentScore / (this.fullScore + 0.0001)) + "%");
+  };
+  SaveProgress.prototype.hide = function () {
+    this.$element.hide();
+  };
+
+
+  window.easyMindMupClasses.SaveProgress = SaveProgress;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/saver.js b/plugins/easy_mindmup/assets/javascripts/saver.js
new file mode 100644
index 0000000000000000000000000000000000000000..81222a746727430f5d6aba9df83d02534bca3df2
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/saver.js
@@ -0,0 +1,610 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {{
+   *   fails: Array,
+   *   layoutSend: boolean,
+   *   onWay: number,
+   *   unsafeOnWay: boolean,
+   *   updatesDone: boolean,
+   *   deletesDone: boolean,
+   *   deletesStarted: boolean,
+   *   sendPacks: Array.<SendPack>,
+   *   doneCounter: number,
+   *   pointer: number
+   * }|null} temp
+   * @property {boolean} isAutosave
+   * @property {Array} deleteStack
+   * @constructor
+   */
+  function Saver(ysy) {
+    /** @type {MindMup} */
+    this.ysy = ysy;
+    this.temp = null;
+    /** @type {Array.<ModelEntity>} */
+    this.deleteStack = [];
+    this.delaying = 500;
+    this.isAutosave = false;
+  }
+
+  /**
+   * @example "easy_wbs_layout";
+   * @type {String}
+   */
+  Saver.prototype.layoutKey = null;
+
+  Saver.prototype.save = function (isAutosave) {
+    var idea = this.ysy.idea;
+    this.isAutosave = !!isAutosave;
+    /**@type{Array.<SendPack>} */
+    var list = [];
+    this.linearizeTree(idea, null, null, list, false);
+    this.temp = {
+      fails: [],
+      layoutSend: false,
+      onWay: 0,
+      unsafeOnWay: false,
+      updatesDone: false,
+      deletesDone: false,
+      deletesStarted: false,
+      sendPacks: list,
+      doneCounter: 0,
+      pointer: 0
+    };
+    this.ysy.saveProgress.startProgress(list);
+    this.saveLayout();
+    this.sendNextNode();
+  };
+  /**
+   *
+   * @param {ModelEntity} node
+   * @param {ModelEntity} parent
+   * @param {ModelEntity} project
+   * @param {Array.<SendPack>} list
+   * @param {boolean} unsafe
+   */
+  Saver.prototype.linearizeTree = function (node, parent, project, list, unsafe) {
+    if (!node) return;
+    if (!node.attr.nonEditable) {
+      var pack = this.createSendPack(node, parent, project);
+      if (!unsafe) {
+        pack.evaluate(this.ysy);
+        if (!pack.isSame) {
+          list.push(pack);
+          if (!pack.isSafe) {
+            unsafe = true;
+          }
+        }
+
+      } else {
+        list.push(pack);
+      }
+    }
+    if (!node.ideas) return;
+    if (node && node.attr.entityType === "project") {
+      project = node;
+    }
+    var ideas = _.values(node.ideas);
+    for (var i = 0; i < ideas.length; i++) {
+      this.linearizeTree(ideas[i], node, project, list, unsafe);
+    }
+  };
+
+  Saver.prototype.sendNextNode = function (async) {
+    var temp = this.temp;
+    var index = temp.pointer;
+    if (async && index % 10 === 9) {
+      setTimeout($.proxy(this.sendNextNode, this), 0);
+      return;
+    }
+    var list = this.temp.sendPacks;
+    // while(temp.laggingPointer<index){
+    //   if(list[temp.laggingPointer].response){
+    //     temp.laggingPointer++;
+    //   }else{
+    //     break;
+    //   }
+    // }
+    if (temp.doneCounter === list.length) {
+      this.temp.updatesDone = true;
+      this.finishCheck();
+      return;
+    }
+    var pack = list[index];
+    if (!pack) {
+      return;
+    }
+    pack.evaluate(this.ysy);
+    // if(!pack.isSame) this.ysy.log.debug("__ " + pack.print() + " __");
+    if (pack.isSame) {
+      temp.pointer++;
+      temp.doneCounter++;
+      this.sendNextNode(true);
+    } else if (pack.isSafe) {
+      pack.sendRequest();
+      temp.pointer++;
+      this.sendNextNode(true);
+    } else if (!pack.needInclusion) {
+      if (temp.unsafeOnWay) return;
+      temp.pointer++;
+      pack.sendRequest();
+    } else {
+      if (temp.unsafeOnWay) return;
+      pack.sendInclusion();
+      temp.doneCounter--;
+    }
+  };
+
+  Saver.prototype.sendDeletes = function () {
+    var ysy = this.ysy;
+    ysy.log.debug("sendDeletes", "send");
+    var temp = this.temp;
+    temp.deletesStarted = true;
+    temp.pointer = 0;
+    temp.doneCounter = 0;
+    temp.sendPacks = [];
+    for (var i = 0; i < this.deleteStack.length; i++) {
+      /** @type {ModelEntity} */
+      var deletedEntity = this.deleteStack[i];
+      if (!deletedEntity.attr.isFromServer) continue;
+      var id = ysy.getData(deletedEntity).id;
+      if (!id) continue;
+      var nodeInTree = ysy.util.findWhere(ysy.idea, function (node) {
+        //noinspection JSReferencingMutableVariableFromClosure
+        return ysy.getData(node).id === id;
+      });
+      if (nodeInTree) continue;
+      var sendPack = this.createSendPack(deletedEntity);
+      sendPack.makeDelete(this.ysy);
+      temp.sendPacks.push(sendPack);
+    }
+    this.ysy.saveProgress.startProgress(temp.sendPacks);
+    this.sendNextNode();
+    this.deleteStack = [];
+  };
+  /**
+   *
+   * @param {SendPack} sendPack
+   */
+  Saver.prototype.requestFinished = function (sendPack) {
+    this.temp.doneCounter++;
+    this.ysy.saveProgress.requestFinished(sendPack);
+    this.sendNextNode();
+  };
+
+  Saver.prototype.finishCheck = function () {
+    if (this.temp.unsafeOnWay) return;
+    // if (this.temp.onWay) return;
+    if (this.temp.sendPacks.length !== this.temp.doneCounter) return;
+    if (!this.temp.layoutSend) return;
+    if (!this.temp.updatesDone) return;
+    if (!this.temp.deletesStarted) {
+      this.sendDeletes();
+      return;
+    }
+    this.ysy.saveProgress.requestFinished(null);
+    this.afterSave();
+  };
+  Saver.prototype.afterSave = function () {
+    this.ysy.log.debug("afterSave", "send");
+    /** @type {MindMup} */
+    var ysy = this.ysy;
+    var fails = this.temp.fails;
+    var self = this;
+
+    this.ysy.saveInfo.isSaved(this.isAutosave);
+    if (fails.length > 0) {
+      var errors = _.map(fails, function (fail) {
+        return self.createErrorNotice(fail)
+      });
+      ysy.util.showMessage(ysy.settings.labels.gateway.multiFail + "<br>" + errors.join("<br>"), "error", 5000);
+    } else {
+      ysy.util.showMessage(ysy.settings.labels.gateway.multiSuccess, "notice", 1000);
+    }
+    ysy.storage.clear();
+    ysy.loader.load();
+
+  };
+  /**
+   *
+   * @param {SendPack} sendPack
+   * @return {string}
+   */
+  Saver.prototype.createErrorNotice = function (sendPack) {
+    var method = sendPack.method;
+    var name = sendPack.node.title;
+    var reason = null;
+    var status = sendPack.response.status;
+    if (status === 403) {
+      reason = this.ysy.settings.labels.gateway.response_403;
+    } else {
+      try {
+        var responseJson = JSON.parse(sendPack.response.responseText);
+        if (responseJson.errors) {
+          reason = responseJson.errors.join(", ");
+        }
+      } catch (e) {
+      }
+    }
+    //if(method === "DELETE") {
+    //
+    //}else{
+    //}
+    var labels = this.ysy.settings.labels;
+    return labels.types[sendPack.node.attr.entityType] + " " + name + " " + labels.gateway[method + "fail"] + ": " + (reason || sendPack.response.statusText);
+  };
+
+  Saver.prototype.saveLayout = function () {
+    if (!this.layoutKey) throw "Missing layoutKey";
+    var self = this;
+    var layout = this.ysy.storage.extra.getLayout();
+    var requestData = {easy_setting: {}};
+    requestData.easy_setting[this.layoutKey] = layout;
+    var xhr = $.ajax({
+      method: "PUT",
+      url: this.ysy.settings.paths.updateLayout,
+      // type: request.type,
+      dataType: "json",
+      data: requestData
+    });
+    xhr.complete(function () {
+      self.temp.layoutSend = true;
+      self.finishCheck();
+    });
+  };
+
+  window.easyMindMupClasses.Saver = Saver;
+  //####################################################################################################################
+  /**
+   * Contains all information needed for sending of proper request.
+   * It also contains [node], which is ModelEntity so its children are accessible to be send next
+   * @constructor
+   * @param {ModelEntity} node
+   * @param {ModelEntity} [parent]
+   * @param {ModelEntity} [project]
+   * @property {String} method
+   * @property {String} url
+   * @property {ModelEntity} node
+   * @property {ModelEntity} parent
+   * @property {ModelEntityData} nodeData
+   * @property {String} response
+   * @property {Saver} saver
+   * @property {boolean} isSame
+   * @property {boolean} isSafe
+   * @property {boolean} needInclusion
+   * @property {boolean} evaluated
+   */
+  function SendPack(node, parent,project) {
+    this.method = null;
+    this.url = "";
+    this.node = node;
+    this.parent = parent;
+    this.project = project;
+    this.nodeData = null;
+    /** generated data for request - it is filled just before actual send */
+    this.data = {};
+    this.response = null;
+    this.saver = null;
+
+    this.isSame = false;
+    this.isSafe = false;
+    this.needInclusion = false;
+    this.evaluated = false;
+  }
+
+  /**
+   * Simple way how to create SendPack for deleting entities without complicated [evaluate]
+   * @param {MindMup} ysy
+   */
+  SendPack.prototype.makeDelete = function (ysy) {
+    this.ysy = ysy;
+    this.method = "DELETE";
+    this.nodeData = this.node.attr.data;
+    this.evaluated = true;
+  };
+
+  /**
+   * Check ModelEntity and decide:
+   * - if it is same as entity on server - in that case the entity can be skipped
+   * - if changed attributes are safe - can be send on server in parallel with other safe requests
+   * - if changed attribute is not safe - have to be send in alone (or with safe requests)
+   * - if inclusion is needed - some unsafe attribute changed in such way that two requests are needed,
+   *      first to clear old value and the second to set a new value of the attribute
+   * @param {MindMup} ysy
+   */
+  SendPack.prototype.evaluate = function (ysy) {
+    if (this.evaluated) return;
+    this.evaluated = true;
+    this.ysy = ysy;
+    var node = this.node;
+    this.updateNodeData();
+    this.nodeData = this.node.attr.data;
+    if (node.attr.nonEditable) {
+      this.isSame = true;
+      return;
+    }
+    if (node.attr.isFromServer && this.nodeData.id) {
+      if (!this.nodeData._old) {
+        this.isSame = true;
+        return;
+      }
+      this.method = "PUT";
+      if (this.isSafeCheck()) {
+        this.isSafe = true;
+        return;
+      }
+      if (this.needInclusionCheck()) {
+        this.needInclusion = true;
+        // return;
+      }
+    } else {
+      this.method = "POST";
+      // this.isSafe = false;
+    }
+  };
+  SendPack.prototype.generateUrl = function () {
+    var type = this.node.attr.entityType;
+    var url = this.ysy.settings.paths[type + this.method];
+    if (!url) url = "";
+    this.url = url.replace(new RegExp("(:|%3A)" + type + "ID", ""), this.nodeData.id);
+  };
+  SendPack.prototype.needInclusionCheck = function () {
+    throw "needInclusionCheck not implemented";
+    // var entityData = this.nodeData;
+    // return entityData._old.parent_issue_id !== entityData.parent_issue_id
+    //     && entityData.parent_issue_id
+    //     && entityData._old.parent_issue_id
+  };
+  SendPack.prototype.isSafeCheck = function () {
+    throw "isSafeCheck not implemented";
+    // var data = this.nodeData;
+    // if (this.node.attr.entityType === "project") {
+    //   return !data._old.hasOwnProperty("parent_id") || data._old.parent_id === data.parent_id;
+    // }
+    // if (data._old.hasOwnProperty("project_id") && data._old.project_id !== data.project_id) return false;
+    // return !data._old.hasOwnProperty("parent_issue_id") || data._old.parent_issue_id === data.parent_issue_id;
+  };
+  SendPack.prototype.updateNodeData = function () {
+    if (0 == "0") throw "updateNodeData not implemented";
+    var idea = this.node;
+    var parent = this.parent;
+    var updateObj;
+    if (idea.attr.entityType === "project") {
+      if (parent) {
+        var parentData = this.ysy.getData(parent);
+        updateObj = {parent_id: parentData.id, name: idea.title};
+      } else {
+        updateObj = {name: idea.title};
+      }
+    } else {
+      if (parent) {
+        parentData = this.ysy.getData(parent);
+        if (parent.attr.entityType === "project") {
+          updateObj = {
+            project_id: parentData.id,
+            parent_issue_id: null,
+            subject: idea.title
+          }
+        } else {
+          updateObj = {
+            parent_issue_id: parentData.id,
+            subject: idea.title,
+            project_id: this.ysy.util.getEntityProjectId(parent)
+          };
+        }
+      }
+    }
+    this.ysy.setData(idea, updateObj);
+  };
+  /**
+   * Create String representation of this. Only for debugging purposes
+   * @return {String}
+   */
+  SendPack.prototype.print = function () {
+    return '{"node":' + this.node.title + ',"flags":"' + (this.isSame ? "isSame" : "") + (this.isSafe ? "isSafe" : "") + (this.needInclusion ? "Inclusion" : "") + '"}';
+  };
+  SendPack.prototype.sendRequest = function () {
+    var temp = this.saver.temp;
+    this.generateUrl();
+    this.ysy.log.debug("send for " + (this.nodeData.subject || this.nodeData.name), "send");
+    if (this.method === "POST") {
+      // delete this.nodeData._old;
+      // delete this.nodeData.id;
+      this.data[this.node.attr.entityType] = this.filterPostData(this.nodeData);
+      // this.data[this.node.attr.entityType] = this.nodeData;
+    } else if (this.method === "PUT") {
+      this.data[this.node.attr.entityType] = this.filterPutData(this.nodeData, this.nodeData._old);
+    }
+    var self = this;
+    if (!this.isSafe) {
+      temp.unsafeOnWay = true;
+    }
+    if (this.ysy.settings.noSave) {
+      self.saver.delaying += 150;
+      self.ysy.log.debug(self.method + " " + self.url + " " + JSON.stringify(self.data));
+      setTimeout(function () {
+        if (!self.isSafe) {
+          temp.unsafeOnWay = false;
+        }
+        self.response = '{"' + self.node.attr.entityType + '":{"id":' + self.node.id + '}}';
+        if (self.method === "POST") {
+          self.updateByPOST();
+        }
+        self.ysy.log.debug("DONE " + self.method + " " + self.url + " " + JSON.stringify(self.data));
+        self.saver.requestFinished(self);
+      }, self.saver.delaying);
+      return;
+    }
+    var xhr = $.ajax({
+      method: this.method,
+      url: this.url,
+      dataType: "text",
+      data: this.data
+    });
+    xhr.done(function (response) {
+      self.response = response;
+      if (self.method === "POST") {
+        self.updateByPOST();
+      }
+    });
+    xhr.fail(function (response) {
+      self.response = response;
+      temp.fails.push(self);
+    });
+    xhr.complete(function () {
+      if (!self.isSafe) {
+        temp.unsafeOnWay = false;
+      }
+      self.saver.requestFinished(self);
+    });
+  };
+  /**
+   * Update ID of entity from the response, so its children can use obtained ID for their requests
+   * @protected
+   */
+  SendPack.prototype.updateByPOST = function () {
+    var source = JSON.parse(this.response)[this.node.attr.entityType];
+    if (!source) return;
+    //UPDATE ID
+    this.nodeData.id = source.id;
+    this.updateByPOSTAdditional(source);
+  };
+  /**
+   * Enables to update more attributes from POST - just override this function
+   * @param {Object} source
+   */
+  SendPack.prototype.updateByPOSTAdditional = function (source) {
+    if (0 == "0") throw "updateByPOSTAdditional not implemented";
+    var keysToTransform = ["tracker", "status", "priority"];
+    var wantedKeys = ["tracker_id", "status_id", "priority_id", "done_ratio"];
+    for (var i = 0; i < keysToTransform.length; i++) {
+      var key = keysToTransform[i];
+      if (_.isObject(source[key])) {
+        source[key + "_id"] = source[key].id;
+        delete source[key];
+      }
+    }
+    $.extend(this.nodeData, _.pick(source, wantedKeys));
+  };
+  /**
+   * @protected
+   */
+  SendPack.prototype.sendInclusion = function () {
+    var node = this.node;
+    /** @type {ModelEntity} */
+    var inclusion = new window.easyMindMupClasses.ModelEntity()
+        .fromServer(node.id, node.title + " inclusion", node.attr.entityType, true, this.getInclusionData());
+    inclusion.ideas = {1: node};
+    /** @type {SendPack} */
+    var pack = this.saver.createSendPack(inclusion);
+    pack.ysy = this.ysy;
+    pack.nodeData = pack.node.attr.data;
+    pack.method = "PUT";
+    this.needInclusion = false;
+    pack.sendRequest();
+  };
+  SendPack.prototype.getInclusionData = function () {
+    // the code may be marked as error by IDE, dead code is left for overrides
+    if (0 == "0") throw "getInclusionData";
+    if (this.node.attr.entityType === "issue") {
+      return {id: this.nodeData.id, _old: {parent_issue_id: 5}, parent_issue_id: null};
+    }
+  };
+  /**
+   * Returns only changed values from nodeData
+   * @param {ModelEntityData} nodeData
+   * @param {Object} oldNodeData
+   * @return {Object}
+   */
+  SendPack.prototype.filterPutData = function (nodeData, oldNodeData) {
+    var filtered = {};
+    var keys = Object.getOwnPropertyNames(oldNodeData);
+    for (var i = 0; i < keys.length; i++) {
+      var key = keys[i];
+      if (key.substring(0, 1) === "_") continue;
+      // if (nodeData[key] === oldNodeData[key]) continue;
+      if (key === "custom_fields") {
+        filtered.custom_field_values = this.transformCustomValues(nodeData.custom_fields, oldNodeData.custom_fields);
+        continue;
+      }
+      filtered[key] = nodeData[key];
+    }
+    return filtered;
+  };
+  /**
+   * Returns only attributes which are safe to send to server
+   * @param {ModelEntityData} nodeData
+   * @return {Object}
+   */
+  SendPack.prototype.filterPostData = function (nodeData) {
+    var filtered = {};
+    var util = this.ysy.util;
+    var keys = Object.getOwnPropertyNames(nodeData);
+    for (var i = 0; i < keys.length; i++) {
+      var key = keys[i];
+      if (key === "id") continue;
+      if (util.startsWith(key, "_")) continue;
+      if (typeof(nodeData[key]) === "function") continue;
+      if (key === "custom_fields") {
+        filtered.custom_field_values = this.transformCustomValues(nodeData.custom_fields, null);
+        continue;
+      }
+      filtered[key] = nodeData[key];
+    }
+    return filtered;
+  };
+  /**
+   *
+   * @param {Array.<{id:String,value:*}>} customFields
+   * @param {Array.<{id:String,value:*}>} oldCustomFields
+   * @return {Object.<int,*>}
+   */
+  SendPack.prototype.transformCustomValues = function (customFields, oldCustomFields) {
+    var customValues = {};
+    if (oldCustomFields) {
+      var customIndices = Object.getOwnPropertyNames(oldCustomFields);
+      for (var j = 0; j < customIndices.length; j++) {
+        if (customIndices[j].startsWith("_"))continue;
+        var customIndex = parseInt(customIndices[j]);
+        var customField = customFields[customIndex];
+        if (customField.field_format === "easy_lookup" && typeof customField.value.length !== "undefined") {
+          var out = [];
+          for (var k = 0; k < customField.value.length - 2; k = k + 3) {
+            out.push(customField.value[k]);
+          }
+          if (customField.multiple) {
+            customValues[customField.id] = out;
+          } else {
+            if (out.length === 0) {
+              customValues[customField.id] = null;
+            } else {
+              customValues[customField.id] = out[0];
+            }
+          }
+        } else {
+          customValues[customField.id] = customField.value;
+        }
+      }
+    } else {
+      for (j = 0; j < customFields.length; j++) {
+        customField = customFields[j];
+        customValues[customField.id] = customField.value;
+      }
+    }
+    return customValues;
+  };
+  window.easyMindMupClasses.SendPack = SendPack;
+  /**
+   * @param {ModelEntity} node
+   * @param {ModelEntity} [parent]
+   * @param {ModelEntity} [project]
+   * @return {SendPack}
+   */
+  Saver.prototype.createSendPack = function (node, parent, project) {
+    var pack = new SendPack(node, parent, project);
+    pack.saver = this;
+    return pack;
+  };
+  //####################################################################################################
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/storage.js b/plugins/easy_mindmup/assets/javascripts/storage.js
new file mode 100644
index 0000000000000000000000000000000000000000..41624659854d4d5fcf5f3abefb599b1ab40b03ba
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/storage.js
@@ -0,0 +1,552 @@
+(function () {
+  'use strict';
+  /**
+   *
+   * @param {MindMup} ysy
+   * @property {StorageExtra} extra
+   * @property {StorageLastState} lastState
+   * @property {StorageSettings} settings
+   * @constructor
+   */
+  function Storage(ysy) {
+    this._scope = ysy.id + "-";
+    this.ysy = ysy;
+    this.extra = new StorageExtra(this);
+    var lastState = new StorageLastState(this);
+    var settings = new StorageSettings(this);
+    this.lastState = lastState;
+    this.settings = settings;
+    var self = this;
+    //if (this._scope == null) throw "_scope is not defined! Scope is used for separation of xBS products in localStorage";
+
+    ysy.eventBus.register("TreeLoaded", function (idea) {
+      idea.addEventListener('changed', function () {
+        lastState.remove();
+        self.save(idea);
+      });
+    });
+
+  }
+
+  Storage.prototype.getSessionData = function (key) {
+    return window.sessionStorage.getItem(this._scope + key);
+  };
+  Storage.prototype.saveSessionData = function (key, value) {
+    window.sessionStorage.setItem(this._scope + key, value);
+  };
+  Storage.prototype.resetSessionData = function (key) {
+    window.sessionStorage.removeItem(this._scope + key);
+  };
+  Storage.prototype.getPersistentData = function (key) {
+    return window.localStorage.getItem(this._scope + key);
+  };
+  Storage.prototype.savePersistentData = function (key, value) {
+    window.localStorage.setItem(this._scope + key, value);
+  };
+  Storage.prototype.resetPersistentData = function (key) {
+    window.localStorage.removeItem(this._scope + key);
+  };
+  Storage.prototype.save = function (idea) {
+    this.extra.save(idea);
+    this.lastState.save(idea);
+  };
+  Storage.prototype.clear = function () {
+    this.lastState.remove();
+    this.extra.positionExtract = null;
+  };
+  window.easyMindMupClasses = window.easyMindMupClasses || {};
+  window.easyMindMupClasses.Storage = Storage;
+
+//###################################################################################################
+  /**
+   *
+   * @param {Storage} storage
+   * @constructor
+   */
+  function StorageExtra(storage) {
+    this.positionExtract = null;
+    this.collapseExtract = null;
+    this.storage = storage;
+    /** @type {MindMup} ysy */
+    this.ysy = storage.ysy;
+    /** @param {ModelEntity} idea */
+    this._getIdOfIdea = function (idea) {
+      // Override this for proper entity type prefixing
+      // (to prevent having same id for different entities)
+      return this.ysy.getData(idea).id;
+    };
+  }
+
+  StorageExtra.prototype._key = "extra-";
+  /**
+   *
+   * @param {RootIdea} idea
+   */
+  StorageExtra.prototype.save = function (idea) {
+    if (!idea) return;
+    var extract = this._extractFromNode(idea);
+    this.positionExtract = extract.positions;
+    this.collapseExtract = extract.collapses;
+    var toSave = {
+      collapses: this.collapseExtract,
+      rootPos: {
+        deltaX: this.ysy.domPatch.deltaX,
+        deltaY: this.ysy.domPatch.deltaY
+      }
+    };
+
+    this.storage.savePersistentData(this._key + this._getIdOfIdea(idea), JSON.stringify(toSave));
+  };
+  /**
+   * @param {RootIdea} idea
+   * return {{collapses:Object, rootPos: Object}}
+   */
+  StorageExtra.prototype.getLocalProjectData = function (idea) {
+    var json = this.storage.getPersistentData(this._key + this._getIdOfIdea(idea));
+    if (json === null || json === "") return {};
+    var result = JSON.parse(json) || {};
+    if (!result.collapses) return {collapses: result};
+    return result;
+  };
+  /**
+   *
+   * @param {Array.<ModelEntity>} data
+   * @param {RootIdea} root
+   * @return {Array.<ModelEntity>}
+   */
+  StorageExtra.prototype.enhanceData = function (data, root) {
+    /** @type {Object.<string,{position:Object,rank:number}>} */
+    var positions = this.positionExtract;
+    var projectData = this.getLocalProjectData(root);
+    var collapses = projectData.collapses;
+    this.ysy.domPatch.loadRootPosition(projectData.rootPos);
+    if (positions) {
+      for (var i = 1; i < data.length; i++) {
+        var nodeExtract = positions[this._getIdOfIdea(data[i])];
+        if (!nodeExtract) continue;
+        if (nodeExtract.position) {
+          var position = [];
+          for (var j = 0; j < nodeExtract.position.length; j++) {
+            position.push(parseFloat(nodeExtract.position[j]));
+          }
+          data[i].attr.position = position;
+        }
+        data[i].rank = nodeExtract.rank;
+        // data[i]._parentTitle = nodeExtract.parentTitle;
+      }
+    }
+    if (collapses) {
+      for (i = 1; i < data.length; i++) {
+        data[i].attr.collapsed = !!collapses[this._getIdOfIdea(data[i])];
+      }
+    }
+    return data;
+  };
+  /**
+   * @param {{code?:String}} layout
+   */
+  StorageExtra.prototype.setLayout = function (layout) {
+    this.positionExtract = this._decodeLayout(layout);
+  };
+  /**
+   * @return {{code?: String}}
+   */
+  StorageExtra.prototype.getLayout = function () {
+    return this._encodeLayout(this.positionExtract);
+  };
+  /**
+   * encode layout if it contains more than 50 keys.
+   * result is capped if it reaches 60000 characters
+   * @param {{}} decodedLayout
+   * @return {{code?:String}|null}
+   */
+  StorageExtra.prototype._encodeLayout = function (decodedLayout) {
+    if(!decodedLayout) return null;
+    var keys = Object.getOwnPropertyNames(decodedLayout);
+    if (keys.length <= 50) return decodedLayout;
+    var i, key, pos, result = "";
+    if (keys.length > 2000) {
+      for (i = 0; i < keys.length; i++) {
+        key = keys[i];
+        pos = decodedLayout[key].position ? ("[" + decodedLayout[key].position + "]") : "";
+        result += key + "{" + (decodedLayout[key].rank || "") + pos + "}";
+        if (result.length > 60000) return {code: result};
+      }
+    } else {
+      for (i = 0; i < keys.length; i++) {
+        key = keys[i];
+        pos = decodedLayout[key].position ? ("[" + decodedLayout[key].position + "]") : "";
+        result += key + "{" + (decodedLayout[key].rank || "") + pos + "}";
+      }
+
+    }
+    return {code: result};
+  };
+  /**
+   * decode layout if contains "code" key
+   * @param {{code?:String}} encodedLayout
+   * @return {{}|null}
+   */
+  StorageExtra.prototype._decodeLayout = function (encodedLayout) {
+    if (!encodedLayout) return null;
+    if (!encodedLayout.code) return encodedLayout;
+    var issueStrings = encodedLayout.code.split("}");
+    var result = {};
+    for (var i = 0; i < issueStrings.length; i++) {
+      var issueString = issueStrings[i];
+      var p = issueString.indexOf("{");
+      if (p === -1) continue;
+      var issueResult = {};
+      var key = issueString.substring(0, p);
+      result[key] = issueResult;
+      var k = issueString.indexOf("[");
+      var issueRank;
+      if (k > -1) {
+        var posString = issueString.substring(k);
+        issueResult.position = JSON.parse(posString);
+        issueRank = issueString.substring(p + 1, k);
+      } else {
+        issueRank = issueString.substring(p + 1);
+      }
+      if (issueRank) {
+        issueResult.rank = parseInt(issueRank);
+      }
+    }
+    return result;
+  };
+
+  /**
+   *
+   * @param {ModelEntity} node
+   * @param {string|number} [rank]
+   * @param {{positions:Object.<string,{position:Object,rank:number}>,collapses:Object.<string,boolean>}} [extract]
+   * @return {{positions:Object.<string,{position:Object,rank:number}>,collapses:Object.<string,boolean>}}
+   * @private
+   */
+  StorageExtra.prototype._extractFromNode = function (node, rank, extract) {
+    if (extract === undefined) extract = {positions: {}, collapses: {}};
+    var positionExtract = {};
+    positionExtract.rank = rank;
+    // positionExtract.parentTitle = parentTitle;
+    // var data = this.ysy.getData(node);
+    // if (!data.id) {
+    //   positionExtract.title = node.title;
+    // }
+    if (node.attr.position) {
+      positionExtract.position = node.attr.position;
+    }
+    if (node.attr.collapsed && !_.isEmpty(node.ideas)) {
+      extract.collapses[this._getIdOfIdea(node)] = true;
+    }
+    if (node.ideas) {
+      var sortedKeys = this.ysy.util.getSortedRanks(node.ideas);
+      var correctedRanks = this.ysy.util.correctRanks(sortedKeys);
+      for (var i = 0; i < correctedRanks.length; i++) {
+        this._extractFromNode(node.ideas[sortedKeys[i]], correctedRanks[i], extract);
+      }
+    }
+    extract.positions[this._getIdOfIdea(node)] = positionExtract;
+    return extract;
+  };
+//#######################################################################################
+  /**
+   *
+   * @param {Storage} storage
+   * @constructor
+   */
+  function StorageLastState(storage) {
+    this.storage = storage;
+    this._binded = false;
+    this.ysy = storage.ysy;
+  }
+
+  StorageLastState.prototype._dataKey = "last-data";
+  StorageLastState.prototype._idKey = "last-id";
+  StorageLastState.prototype.save = function (idea) {
+    // this.storage.savePersistentData(this._dataKey, JSON.stringify(idea));
+    // this.storage.savePersistentData(this._idKey, this.ysy.settings.rootID);
+    // if (this._binded)return;
+    // this._binded = true;
+    // var storage = this.storage;
+    // $(window).unbind('beforeunload').bind('beforeunload', function (e) {
+    //   var message = "Some changes are not saved!";
+    //   e.returnValue = message;
+    //   return message;
+    // }).unbind('unload').bind('unload', function () {
+    //   storage.lastState.remove();
+    // });
+  };
+  /**
+   *
+   * @return {RootIdea}
+   */
+  StorageLastState.prototype.getSavedIdea = function () {
+    var oldId = this.storage.getPersistentData(this._idKey);
+    if (oldId && parseInt(oldId) !== this.ysy.settings.rootID) {
+      this.remove();
+    }
+    var ideaJson = this.storage.getPersistentData(this._dataKey);
+    if (!ideaJson) return null;
+
+    var idea = new window.easyMindMupClasses.RootIdea(this.ysy).fromJson(JSON.parse(ideaJson));
+    return MAPJS.content(idea);
+  };
+  StorageLastState.prototype.remove = function () {
+    this.storage.resetPersistentData(this._dataKey);
+    this.storage.resetPersistentData(this._idKey);
+    if (this._binded) {
+      $(window).unbind('beforeunload');
+      $(window).unbind('unload');
+      this._binded = false;
+    }
+  };
+  StorageLastState.prototype.compareIdea = function (idea, diffType, savedIdea) {
+    if (!savedIdea) {
+      savedIdea = this.getSavedIdea();
+      if (savedIdea === null) {
+        return null;
+      }
+    }
+    return recursiveDiff(savedIdea, idea, {
+      softParams: diffType === 'all' || diffType === 'soft',
+      data: diffType === 'all' || diffType === 'data' || diffType === 'server',
+      structure: diffType === 'all' || diffType === 'structure' || diffType === 'server'
+    });
+  };
+  var recursiveDiff = function (oldIdea, newIdea, diffTypes) {
+    var isDifferent = false;
+    var oldDiff = {};
+    var newDiff = {};
+    var keys = ["title"];
+    for (var i = 0; i < keys.length; i++) {
+      var key = keys[i];
+      if (oldIdea[key] !== newIdea[key]) {
+        oldDiff[key] = oldIdea[key];
+        newDiff[key] = newIdea[key];
+        isDifferent = true;
+      }
+    }
+    if (diffTypes.data) {
+      var oldData = this.ysy.getData(oldIdea);
+      var newData = this.ysy.getData(newIdea);
+      keys = _.union(Object.getOwnPropertyNames(oldData), Object.getOwnPropertyNames(newData));
+      for (i = 0; i < keys.length; i++) {
+        key = keys[i];
+        if (typeof oldData[key] === "object" || typeof newData[key] === "object") {
+          continue;
+        }
+        if (oldData[key] !== newData[key]) {
+          if (oldDiff.attr === undefined) {
+            oldDiff.attr = {data: {}};
+            newDiff.attr = {data: {}};
+          }
+          oldDiff.attr.data[key] = oldData[key];
+          newDiff.attr.data[key] = newData[key];
+          isDifferent = true;
+        }
+      }
+    }
+    if (diffTypes.softParams) {
+      keys = ["collapsed", "position"];
+      for (i = 0; i < keys.length; i++) {
+        key = keys[i];
+        if (oldIdea.attr[key] !== newIdea.attr[key]) {
+          if (oldDiff.attr === undefined) {
+            oldDiff.attr = {};
+            newDiff.attr = {};
+          }
+          oldDiff.attr[key] = oldIdea.attr[key];
+          newDiff.attr[key] = newIdea.attr[key];
+          isDifferent = true;
+        }
+      }
+    }
+    if (_.isEmpty(oldIdea.ideas) && !_.isEmpty(newIdea.ideas)) {
+      newDiff.ideas = newIdea.ideas;
+      isDifferent = true;
+    } else if (_.isEmpty(newIdea.ideas) && !_.isEmpty(oldIdea.ideas)) {
+      oldDiff.ideas = oldIdea.ideas;
+      isDifferent = true;
+    } else {
+      var oldIdeas = $.extend({}, oldIdea.ideas);
+      var newIdeas = $.extend({}, newIdea.ideas);
+      var oldKeys = Object.getOwnPropertyNames(oldIdeas);
+      var newKeys = Object.getOwnPropertyNames(newIdeas);
+      for (var j = 0; j < oldKeys.length; j++) {
+        var oldKey = oldKeys[j];
+        var oldSubIdea = oldIdea.ideas[oldKey];
+        var oldSubIdeaId = this.ysy.getData(oldSubIdea).id;
+        for (var k = 0; k < newKeys.length; k++) {
+          var newKey = newKeys[k];
+          if (!newIdeas.hasOwnProperty(newKey)) continue;
+          var newSubIdea = newIdea.ideas[newKey];
+          var newSubIdeaId = this.ysy.getData(newSubIdea).id;
+          if (oldSubIdeaId !== newSubIdeaId) continue;
+          var diff = recursiveDiff(oldSubIdea, newSubIdea, diffTypes);
+          if (diff && diff.oldDiff) {
+            if (oldDiff.ideas === undefined) {
+              oldDiff.ideas = {};
+            }
+            oldDiff.ideas[oldKey] = diff.oldDiff;
+            isDifferent = true;
+          }
+          if (diff && diff.newDiff) {
+            if (newDiff.ideas === undefined) {
+              newDiff.ideas = {};
+            }
+            newDiff.ideas[newKey] = diff.newDiff;
+            isDifferent = true;
+          }
+          delete oldIdeas[oldKey];
+          delete newIdeas[newKey];
+        }
+        if (oldIdeas[oldKey]) {
+          if (!oldDiff.ideas) oldDiff.ideas = {};
+          oldDiff.ideas[oldKey] = oldIdeas[oldKey];
+          isDifferent = true;
+        }
+      }
+      for (newKey in newIdeas) {
+        if (!newIdeas.hasOwnProperty(newKey)) continue;
+        if (!newDiff.ideas) newDiff.ideas = {};
+        newDiff.ideas[newKey] = newIdeas[newKey];
+        isDifferent = true;
+      }
+    }
+    if (!isDifferent) return null;
+    var ret = {};
+    if (!_.isEmpty(oldDiff)) ret.oldDiff = oldDiff;
+    if (!_.isEmpty(newDiff)) ret.newDiff = newDiff;
+    return ret;
+  };
+//######################################################################################
+  /**
+   *
+   * @param {Storage} storage
+   * @property {MindMup} ysy
+   * @constructor
+   */
+  function StorageSettings(storage) {
+    this.storage = storage;
+    this.ysy = storage.ysy;
+    this.init(storage.ysy);
+  }
+
+  StorageSettings.prototype._key = "settings";
+  /**
+   * @param {MindMup} ysy
+   */
+  StorageSettings.prototype.init = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("saveOneSideOn",function(targetState){
+      self.saveOneSide(targetState);
+    });
+    ysy.eventBus.register("nodeStyleChanged", function () {
+      self.saveStyle();
+    });
+    ysy.eventBus.register("legendToggled", function (opened) {
+      self._save({legendHidden: !opened}, false, null);
+    });
+    ysy.eventBus.register("legendHeaderToggled", function (hidden) {
+      self._save({legendHeaderHidden: hidden}, false, null);
+    });
+    ysy.eventBus.register("BeforeServerClassInit",function () {
+      self.load();
+    });
+  };
+  StorageSettings.prototype.load = function () {
+    this.ysy.settings.oneSideOn = this._load("oneSideOn", null);
+    this.ysy.toolbar.redraw("toggleOneSide");
+  };
+  StorageSettings.prototype.loadStyle = function () {
+    return this._load("defaultStyle", this.ysy.settings.rootID);
+  };
+  StorageSettings.prototype.loadLegendHidden = function () {
+    return this._load("legendHidden", null);
+  };
+  StorageSettings.prototype.loadLegendHeaderHidden = function () {
+    return this._load("legendHeaderHidden", null);
+  };
+  /**
+   *
+   * @param {String|Array.<String>} keys
+   * @param {String|null} rootKey
+   * @private
+   */
+  StorageSettings.prototype._load = function (keys, rootKey) {
+    if (!rootKey) {
+      rootKey = this._key;
+    } else {
+      rootKey = this._key + '-' + rootKey;
+    }
+    var extractString = this.storage.getPersistentData(rootKey);
+    if (!extractString) return;
+    var extract = JSON.parse(extractString);
+    if (typeof keys === "string") {
+      return extract[keys];
+    }
+    return _.pick(extract, keys);
+  };
+  /**
+   * @param {boolean} oneSideOn
+   */
+  StorageSettings.prototype.saveOneSide = function (oneSideOn) {
+    this._save({oneSideOn: oneSideOn}, false, null);
+  };
+  StorageSettings.prototype.saveStyle = function () {
+    var stylesClass = this.ysy.styles;
+    this._save(
+        {defaultStyle: stylesClass.setting === stylesClass.defaultStyle ? undefined : stylesClass.setting},
+        false,
+        this.ysy.settings.rootID
+    );
+  };
+  /**
+   * @param {Object} map
+   * @param {boolean} keepFalse
+   * @param {(string|null)} rootKey
+   * @param {boolean} [map.oneSideOn]
+   * @param {String} [map.defaultStyle]
+   * @param {boolean} [map.legendToggle]
+   */
+  StorageSettings.prototype._save = function (map, keepFalse, rootKey) {
+    if (!rootKey) {
+      rootKey = this._key;
+    } else {
+      rootKey = this._key + '-' + rootKey;
+    }
+    var extractString = this.storage.getPersistentData(rootKey);
+    if (!extractString) {
+      var updatedExtract = {};
+    } else {
+      var extract = JSON.parse(extractString);
+      updatedExtract = _.clone(extract);
+    }
+    var updatingKeys = _.keys(map);
+    if (keepFalse) {
+      for (var i = 0; i < updatingKeys.length; i++) {
+        var key = updatingKeys[i];
+        if (map[key] === undefined) {
+          delete updatedExtract[key];
+        } else {
+          updatedExtract[key] = map[key];
+        }
+      }
+    } else {
+      for (i = 0; i < updatingKeys.length; i++) {
+        key = updatingKeys[i];
+        if (!map[key]) {
+          delete updatedExtract[key];
+        } else {
+          updatedExtract[key] = map[key];
+        }
+      }
+    }
+    if (_.isEqual(extract, updatedExtract)) return;
+    if (_.isEmpty(updatedExtract)) {
+      this.storage.resetPersistentData(rootKey);
+      return;
+    }
+    this.storage.savePersistentData(rootKey, JSON.stringify(updatedExtract));
+  };
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/styles.js b/plugins/easy_mindmup/assets/javascripts/styles.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc93c4bd962a2644d2dd8a0cb48b84c2dbc4b14b
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/styles.js
@@ -0,0 +1,234 @@
+(function () {
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Styles(ysy) {
+    this.ysy = ysy;
+    /**
+     *
+     * @type {String}
+     */
+    this.setting = null;
+    /** @type Object.<String,Style> */
+    this.styles = {};
+    this.init();
+  }
+
+  /**
+   *
+   * @type {String}
+   */
+  Styles.prototype.defaultStyle = null;
+  /**
+   *@param {String} key
+   * @param {Style} style
+   */
+  Styles.prototype.addStyle = function (key, style) {
+    style.key = key;
+    style.init();
+    this.styles[key] = style;
+  };
+  Styles.prototype.init = function () {
+    if (!this.defaultStyle) throw "default style is not defined";
+    var self = this;
+    var $select = this.ysy.$menu.find(".mindmup-color-select");
+    this.ysy.eventBus.register("BeforeServerClassInit", function () {
+      var defaultStyle = self.ysy.storage.settings.loadStyle();
+      if (!defaultStyle) defaultStyle = self.defaultStyle;
+      $select.val(defaultStyle);
+      self.setColor(defaultStyle);
+    });
+
+    $select.change(function () {
+      var value = $(this).val();
+      self.setColor(value);
+    });
+  };
+  /**
+   *
+   * @param {String} setting
+   */
+  Styles.prototype.setColor = function (setting) {
+    var cssPrefix = "scheme-by-";
+    $(this.ysy.containerDiv + "," + this.ysy.menuDiv + " .mindmup-legend").removeClass(cssPrefix + this.setting).addClass(cssPrefix + setting);
+    this.setting = setting;
+    this.ysy.eventBus.fireEvent("nodeStyleChanged", setting);
+  };
+  /**
+   *
+   * @returns {Style}
+   */
+  Styles.prototype.getCurrentStyle = function () {
+    return this.styles[this.setting];
+  };
+  /**
+   * @param {ModelEntity} node
+   * @return {String}
+   */
+  Styles.prototype.cssClasses = function (node) {
+    node.title = "";
+    // Override this for proper coloring of nodes and legend items
+    throw "cssClasses is not defined";
+    // var data = ysy.mapModel.getData(node);
+    // if (node.attr && node.attr.isProject) return " wbs-scheme-project";
+    // return ""
+    //     + this.styles["tracker"].addSchemeClassFromData(data)
+    //     + this.styles["assignee"].addSchemeClassFromData(data)
+    //     + this.styles["assignee"].addSchemeClassFromData(data)
+    //     + this.styles["progress"].addSchemeClassFromData(data)
+    //     + this.styles["milestone"].addSchemeClassFromData(data)
+    //     + this.styles["priority"].addSchemeClassFromData(data);
+  };
+  /**
+   * Create [Style] instances from source Objects and put then into [styles] attribute in [Styles]
+   * @param {object.<String, {dataArray:String,value:Function,options:Function}>} styleSources
+   */
+  Styles.prototype.createStyles = function (styleSources) {
+    for (var key in styleSources) {
+      if (!styleSources.hasOwnProperty(key)) continue;
+      var style = new Style(this.ysy).fromSource(styleSources[key]);
+      this.addStyle(key, style);
+    }
+  };
+  window.easyMindMupClasses.Styles = Styles;
+  //####################################################################################################################
+  var nope = function () {
+  };
+
+  /**
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Style(ysy) {
+    this.ysy = ysy;
+    this.key = null;
+    var self = this;
+    ysy.eventBus.register("dataFilled", function (name, array) {
+      if (self.dataArray === name) {
+        self.initAttribute(array);
+      }
+    });
+  }
+
+  /**
+   * Fill Style attributes from Style source Object
+   * @param {{dataArray:String,value:Function,options:Function}} source
+   * @return {Style}
+   */
+  Style.prototype.fromSource = function (source) {
+    $.extend(this, source);
+    // if (source.init) this.init = source.init;
+    // if (source.dataArray) this.dataArray = source.dataArray;
+    // this.value = source.value;
+    // if (source.options) this.options = source.options;
+    return this;
+  };
+
+  Style.prototype.init = nope;
+  Style.prototype.dataArray = "";
+  Style.prototype.value = nope;
+  Style.prototype.changeObject = function (value) {
+    var result = {};
+    result[this.key + "_id"] = value;
+    return result;
+  };
+  Style.prototype.builderType = "dataBased";
+
+
+  /**
+   * create function, which creates oneKeyObjects
+   * @static
+   * @param {String} key
+   * @return {Function}
+   */
+  Style.oneKeyObjectConstructorBuilder = function (key) {
+    return function (value) {
+      var result = {};
+      result[key] = value;
+      return result;
+    };
+  };
+  /**
+   * simple constructor for generating Object with one key:value pair
+   * @static
+   * @param {String} key
+   * @param {*} value
+   * @return {Object.<String,*>}
+   */
+  Style.oneKeyObjectConstructor = function (key, value) {
+    var result = {};
+    result[key] = value;
+    return result;
+  };
+  /**
+   * returns Array with items for Legend
+   * @param onlyUsed
+   * @return {Array}
+   */
+  Style.prototype.options = function (onlyUsed) {
+    if (!this.data) return [];
+    if (onlyUsed) return this.findUsed();
+    if (!this.nullAllowed) return this.data;
+    return [{id: 0, name: "---"}].concat(this.data);
+  };
+  /**
+   * prepare Style after data (trackers, categories, ...) are loaded
+   * @param {Array} list
+   */
+  Style.prototype.initAttribute = function (list) {
+    this.count = 1;
+    this.colors = {};
+    for (var i = 0; i < list.length; i++) {
+      this.colors[list[i].id] = this.count++;
+      if (this.count > 12) this.count = 1;
+    }
+    this.data = list;
+  };
+  /**
+   * Generate CSS class from data of node
+   * @param {ModelEntityData} data
+   * @return {String}
+   */
+  Style.prototype.addSchemeClassFromData = function (data) {
+    var value = this.value(data);
+    return this.addSchemeClass(value);
+  };
+  /**
+   * create CSS class from value generated by [value] function
+   * @param value
+   * @return {String}
+   */
+  Style.prototype.addSchemeClass = function (value) {
+    if (!this.colors || this.colors[value] === undefined) return "";
+    return " scheme-" + this.key + "-" + this.colors[value];
+  };
+  Style.prototype.findUsed = function () {
+    if (!this.ysy.idea) return [];
+    var values = {};
+    this.recursiveUsed(this.ysy.idea, values);
+    var filtered = [];
+    if (values[0]) {
+      filtered.push({id: 0, name: "---"});
+    }
+    var array = this.data;
+    for (var i = 0; i < array.length; i++) {
+      if (values[array[i].id]) {
+        filtered.push(array[i]);
+      }
+    }
+    return filtered;
+  };
+  Style.prototype.recursiveUsed = function (idea, values) {
+    var value = this.value(this.ysy.getData(idea));
+    values[value] = true;
+    var ideaIdeas = idea.ideas;
+    for (var rank in ideaIdeas) {
+      if (!ideaIdeas.hasOwnProperty(rank)) continue;
+      var child = ideaIdeas[rank];
+      this.recursiveUsed(child, values);
+    }
+  };
+  window.easyMindMupClasses.Style = Style;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/toolbar.js b/plugins/easy_mindmup/assets/javascripts/toolbar.js
new file mode 100644
index 0000000000000000000000000000000000000000..161d7632d2314fc0fb9aa6fdc196bb5a2ee2f5ac
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/toolbar.js
@@ -0,0 +1,192 @@
+(function () {
+  /**
+   * This Widget contains many buttons and other widgets located in top mindMup menu
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Toolbar(ysy) {
+    this.$menu = ysy.$menu;
+    this.ysy = ysy;
+    this.children = {};
+    this.init(ysy);
+  }
+
+  /**
+   *
+   * @param {MindMup} ysy
+   */
+  Toolbar.prototype.init = function (ysy) {
+    ysy.eventBus.register("TreeLoaded", $.proxy(this.redraw, this));
+    // this.initChildren(ysy);
+    // for (var id in this.subClasses) {
+    //   if (!this.subClasses.hasOwnProperty(id)) continue;
+    //   var child = new this.subClasses[id](ysy, this.$menu);
+    //   this.children[child.triggerName || id] = child;
+    // }
+    this.initChildren(ysy);
+    // _.each(ysy.view.toolbarChildren, function (child, id) {
+    //   child.init(this.$menu);
+    //   this.children[child.triggerName || id] = child;
+    // }, this);
+
+  };
+  Toolbar.prototype.initChildren = function (ysy) {
+    this.addChild(new OneSideButton(ysy, this.$menu));
+    this.addChild(new AllIconButton(ysy, this.$menu));
+    this.addChild(new StickyMenu(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.ExpandAllButton(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.ShowLinksButton(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.PrintButton(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.SaveButton(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.UndoButton(ysy, this.$menu));
+    this.addChild(new window.easyMindMupClasses.RedoButton(ysy, this.$menu))
+  };
+  Toolbar.prototype.addChild = function (button) {
+    /** @type {String} */
+    var id = button.triggerName || button.id;
+    this.children[id] = button;
+    this.redraw(id);
+  };
+  Toolbar.prototype._render = function () {
+    for (var key in this.children) {
+      if (!this.children.hasOwnProperty(key)) continue;
+      this.children[key]._render();
+    }
+  };
+  /**
+   *
+   * @param {String} [itemName]
+   */
+  Toolbar.prototype.redraw = function (itemName) {
+    if (!itemName) {
+      return this.ysy.repainter.redrawMe(this);
+    }
+    if (this.children[itemName]) {
+      var child = this.children[itemName];
+      this.ysy.repainter.redrawMe(child);
+    }
+  };
+  window.easyMindMupClasses.Toolbar = Toolbar;
+
+  //####################################################################################################################
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function OneSideButton(ysy, $parent) {
+    this.ysy = ysy;
+    this.$element = $parent.find(".toggleOneSide");
+  }
+
+  OneSideButton.prototype.triggerName = "toggleOneSide";
+  OneSideButton.prototype._render = function () {
+    var isActive = this.ysy.settings.oneSideOn;
+    if (!isActive) isActive = false;
+    this.$element.find("a").toggleClass("active", isActive);
+  };
+  //####################################################################################################################
+  /**
+   * Button, which shows and hides icons onto nodes
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function AllIconButton(ysy, $parent) {
+    this.$element = null;
+    this.ysy = ysy;
+    this.init(ysy, $parent);
+  }
+
+  AllIconButton.prototype.id = "allIconButton";
+
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @return {AllIconButton}
+   */
+  AllIconButton.prototype.init = function (ysy, $parent) {
+    this.$element = $parent.find(".all-icon-toggler");
+    var self = this;
+    this.$element.click(function () {
+      ysy.settings.allIcons = !ysy.settings.allIcons;
+      if (ysy.settings.allIcons) {
+        ysy.$container.addClass("mindmup-node-icons--with_icons");
+        ysy.repainter.forceRedraw();
+      } else {
+        ysy.$container.find(".mindmup-node-icons-all").remove();
+        ysy.$container.removeClass("mindmup-node-icons--with_icons");
+      }
+      ysy.repainter.redrawMe(self);
+    });
+    return this;
+  };
+  AllIconButton.prototype._render = function () {
+    var isActive = this.ysy.settings.allIcons;
+    this.$element.find("a").toggleClass("active", isActive);
+  };
+  //####################################################################################################################
+  /**
+   * Makes top menu sticky
+   * @param {MindMup} ysy
+   * @param {jQuery} $parent
+   * @constructor
+   */
+  function StickyMenu(ysy, $parent) {
+    this.$element = null;
+    this.ysy = ysy;
+    this.isFixed = false;
+    this.init(ysy, $parent);
+  }
+
+  StickyMenu.prototype.id = "StickyMenu";
+
+  /**
+   *
+   * @param {MindMup} ysy
+   * @param {jQuery} $element
+   * @return {StickyMenu}
+   */
+  StickyMenu.prototype.init = function (ysy, $element) {
+    this.$element = $element;
+    this.$cont = $element.parent();
+    this.$placeholder = $("<div id='mindmup_menu_placeholder' style='height:0'></div>");
+    this.$cont.prepend(this.$placeholder);
+    this.$document = $(document);
+    this.offset = 0;
+    if (ysy.settings.easyRedmine) {
+      this.offset += $("#top-menu").outerHeight();
+    }
+    var self = this;
+    $(document).on("scroll", function () {
+      ysy.repainter.redrawMe(self);
+    });
+    ysy.eventBus.register("resize", function () {
+      ysy.repainter.redrawMe(self);
+    });
+    ysy.repainter.redrawMe(self);
+    return this;
+  };
+  StickyMenu.prototype._render = function () {
+    //ysy.log.debug("stickyMenu rendered");
+    var top = this.$document.scrollTop() + this.offset - this.$cont.offset().top;
+    if (top > 0) {
+      this.$element.css("width", this.$cont.width());
+      if (!this.isFixed) {
+        this.$element.css({position: "fixed", top: this.offset + "px"});
+        this.$placeholder.height(this.$element.outerHeight());
+        this.isFixed = true;
+      }
+    } else {
+      if (this.isFixed) {
+        this.$element.css({position: "relative", top: "0", width: ""});
+        this.$placeholder.height(0);
+        this.isFixed = false;
+      }
+    }
+    //var top = Math.max(this.$document.scrollTop() + this.offset - this.$cont.offset().top, 0);
+    //this.$element.css({transform: "translate(0," + Math.round(top) + "px)"});
+  };
+})();
diff --git a/plugins/easy_mindmup/assets/javascripts/utils.js b/plugins/easy_mindmup/assets/javascripts/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..67255e6534d04211892a4e29d02e19181cf9c219
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/utils.js
@@ -0,0 +1,332 @@
+(function () {
+  window.easyMindMupClasses = window.easyMindMupClasses || {};
+  /**
+   * Makes [Child] class ascendant of the [Parent] class
+   * @type {Function}
+   * @param {Object} Child
+   * @param {Object} Parent
+   * @return {Object}
+   */
+  window.easyMindMupClasses.extendClass = function (Child, Parent) {
+    var F = new Function();
+    F.prototype = Parent.prototype;
+    Child.prototype = new F();
+    Child.prototype.constructor = Child;
+    Child.superclass = Parent.prototype;
+    return Child;
+  };
+  /**
+   *
+   * @param {MindMup} ysy
+   * @constructor
+   */
+  function Util(ysy) {
+    this.ysy = ysy;
+    this._messages = [];
+    this._messageType = "notice";
+    this._lastMessageTime = 0;
+  }
+
+  /**
+   *
+   * @param {String} message
+   * @param {String} type - ["error","warning","notice"]
+   * @param {number} [delay] - in milliseconds - time to disappear
+   */
+  Util.prototype.showMessage = function (message, type, delay) {
+    var flash = $("#content").children(".flash");
+    var now = new Date().valueOf();
+    if (!flash.length || this._lastMessageTime + 60 * 1000 < now || this._messageType === "notice") {
+      window.showFlashMessage(type, message, delay);
+      this._lastMessageTime = now;
+      this._messages = [message];
+      this._messageType = type;
+      return;
+    }
+    if (type === "notice") return;
+    this._lastMessageTime = now;
+    this._messages.push(message);
+    if (type === "error" && this._messageType === "warning") {
+      window.showFlashMessage(type, this._messages.join("<br>"), delay);
+      this._messageType = type;
+    } else {
+      flash.find("span").html(this._messages.join("<br>"));
+    }
+  };
+  /**
+   *
+   * @param {String} id - HTML id of newly created modal
+   * @param {String} width - in percent "%" or in pixels "px"
+   * @return {jQuery}
+   */
+  Util.prototype.getModal = function (id, width) {
+    var $target = $("#" + id);
+    if ($target.length === 0) {
+      $target = $("<div id=" + id + ">");
+      $target.dialog({
+        width: width,
+        appendTo: document.body,
+        modal: true,
+        resizable: false,
+        dialogClass: 'modal'
+      });
+      $target.dialog("close");
+    }
+    return $target;
+  };
+  /**
+   * show modal with feature specific text and upgrade button
+   * @param {string} feature
+   */
+  Util.prototype.showUpgradeModal= function (feature) {
+    var ysy = this.ysy;
+    var $target = ysy.util.getModal("form-modal", "auto");
+    var template = ysy.settings.templates.upgrade;
+    var freeLabels = ysy.settings.labels.free;
+    var obj = {
+      text: freeLabels.textNotAvailable,
+      href: freeLabels.buttonUpgradeHref
+    };
+    obj[feature] = true;
+    var rendered = Mustache.render(template, obj);
+    $target.html(rendered);
+    showModal("form-modal");
+    $target.dialog({
+      buttons: [
+        {
+          id: "upgrade_button",
+          class: "button-1 button-positive",
+          text: freeLabels.buttonUpgrade,
+          click: function () {
+            var $link = $target.find("#upgrade_link");
+            //$link.show().click();
+            window.open($link.attr("href"), '_blank');
+            $target.dialog("close");
+          }
+        },
+        {
+          id: "close_button",
+          class: "button-2 button",
+          text: ysy.settings.labels.buttons.close,
+          click: function () {
+            $target.dialog("close");
+          }
+        }
+      ]
+    });
+    $target.parent().find("#upgrade_button").focus();
+  };
+  /**
+   *
+   * @param {String} text
+   * @param {String} char
+   * @return {boolean}
+   */
+  Util.prototype.startsWith = function (text, char) {
+    if (text.startsWith) {
+      return text.startsWith(char);
+    }
+    return text.charAt(0) === char;
+  };
+  /**
+   * Convert CamelCase to snake_case
+   * @param {String} text
+   * @return {string}
+   */
+  Util.prototype.toUnderscore = function (text) {
+    return text.replace(/([A-Z])/g, function ($1) {
+      return "_" + $1.toLowerCase();
+    });
+  };
+  Util.prototype.isEquivalent = function (a, b) {
+    var aProps = Object.getOwnPropertyNames(a);
+    var bProps = Object.getOwnPropertyNames(b);
+    if (aProps.length != bProps.length) {
+      return false;
+    }
+    for (var i = 0; i < aProps.length; i++) {
+      var propName = aProps[i];
+      if (a[propName] !== b[propName]) {
+        return false;
+      }
+    }
+    return true;
+  };
+  /**
+   * @callback TraverseCallback
+   * @param {ModelEntity} node
+   * @param {ModelEntity} [parent]
+   */
+  /**
+   * Execute [func] for every node in tree
+   * @param {ModelEntity} node
+   * @param {TraverseCallback} func
+   * @param {ModelEntity} [parent]
+   */
+  Util.prototype.traverse = function (node, func, parent) {
+    if (!node) return;
+    func(node, parent);
+    if (!node.ideas) return;
+    var ideas = _.values(node.ideas);
+    for (var i = 0; i < ideas.length; i++) {
+      this.traverse(ideas[i], func, node);
+    }
+  };
+  /**
+   * Similar to [traverse] but [func] is executed for children and then for the parent
+   * @param {ModelEntity} node
+   * @param {TraverseCallback} func
+   */
+  Util.prototype.backTraverse = function (node, func) {
+    if (!node) return;
+    if (node.ideas) {
+      var ideas = _.values(node.ideas);
+      for (var i = 0; i < ideas.length; i++) {
+        this.backTraverse(ideas[i], func);
+      }
+    }
+    func(node);
+  };
+  /**
+   *
+   * @param {ModelEntity} parent - regularly RootIdea
+   * @param {ModelEntity} target - node which parents have to be transformed by [func]
+   * @param {Function} func
+   * @return {boolean}
+   */
+  Util.prototype.forAllParents = function (parent, target, func) {
+    if (parent === target) return true;
+    if (!parent.ideas) return false;
+    var ideas = _.values(parent.ideas);
+    for (var i = 0; i < ideas.length; i++) {
+      var found = this.forAllParents(ideas[i], target, func);
+      if (found) {
+        func(parent);
+        return true;
+      }
+    }
+  };
+  /**
+   * Finds one node which satisfy [func], so func(node)===true
+   * @param {ModelEntity} node - usually RootIdea, but it can be called in subtrees
+   * @param {Function} func
+   * @return {ModelEntity}
+   */
+  Util.prototype.findWhere = function (node, func) {
+    if (func(node)) {
+      return node;
+    }
+    if (!node.ideas) return null;
+    var ideas = _.values(node.ideas);
+    for (var i = 0; i < ideas.length; i++) {
+      var result = this.findWhere(ideas[i], func);
+      if (result) return result;
+    }
+    return null;
+  };
+  /**
+   *
+   * @param {Object.<String, ModelEntity>} ideas
+   * @return {Array.<number>}
+   */
+  Util.prototype.getSortedRanks = function (ideas) {
+    var keys = _.chain(ideas).keys().map(parseFloat).value();
+    keys.sort(function (a, b) {
+      return a - b;
+    });
+    return keys;
+  };
+  /**
+   * Transforms the ranks by removing decimal ones and closes gaps between ranks. Order is not corrupted.
+   * @param {Array.<number>} ranks
+   * @return {Array.<number>}
+   */
+  Util.prototype.correctRanks = function (ranks) {
+    var firstPositive = _.findIndex(ranks, function (key) {
+      return key > 0;
+    });
+    var correctedRanks = [];
+    if (firstPositive < 0) firstPositive = 0;
+    // NEGATIVE
+    for (var i = 0; i < firstPositive; i++) {
+      correctedRanks.push(i - firstPositive);
+    }
+    // POSITIVE
+    for (i = firstPositive; i < ranks.length; i++) {
+      correctedRanks.push(i - firstPositive + 1);
+    }
+    return correctedRanks;
+  };
+  /**
+   *
+   * @param {Array.<{name:String,value:String}>} formData
+   * @return {Object}
+   */
+  Util.prototype.formToJson = function (formData) {
+    var result = {};
+    var prolong = function (result, split, value) {
+      var key = split.shift();
+      if (key === "") {
+        result.push(value);
+      } else {
+        if (split.length > 0) {
+          var next = split[0];
+          if (!result[key]) {
+            if (next === "") {
+              result[key] = [];
+            } else {
+              result[key] = {};
+            }
+          }
+          prolong(result[key], split, value);
+        } else {
+          result[key] = value;
+        }
+      }
+    };
+    for (var i = 0; i < formData.length; i++) {
+      var split = formData[i].name.split(/]\[|\[|]/);
+      if (split.length > 1) {
+        split.pop();
+      }
+      prolong(result, split, formData[i].value);
+    }
+    return result;
+  };
+  /**
+   * return project_id of entity
+   * @param {ModelEntity} parent
+   * @param {int} [ideaId]
+   */
+  Util.prototype.getEntityProjectId = function (parent, ideaId) {
+    if(!parent){
+      parent = this.ysy.idea.findParent(ideaId);
+    }
+    while (parent && parent.attr.entityType !== "project") {
+      parent = this.ysy.idea.findParent(parent.id);
+    }
+    if (parent) {
+      return this.ysy.getData(parent).id;
+    }
+    return null;
+  };
+
+  var entityMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;',
+    '/': '&#x2F;',
+    '`': '&#x60;',
+    '=': '&#x3D;'
+  };
+
+  Util.prototype.escapeHtml = function(string) {
+    return String(string).replace(/[&<>"'`=\/]/g, function (s) {
+      return entityMap[s];
+    });
+  };
+
+  window.easyMindMupClasses.Util = Util;
+})();
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/javascripts/validator.js b/plugins/easy_mindmup/assets/javascripts/validator.js
new file mode 100644
index 0000000000000000000000000000000000000000..8563b8e24c99d47f45f2f1de6e28b4ee21cf28e6
--- /dev/null
+++ b/plugins/easy_mindmup/assets/javascripts/validator.js
@@ -0,0 +1,107 @@
+(function () {
+  /**
+   * contains methods, which is called by the code from mindmup/content.js and could prevent completion of this code
+   * by not returning true
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @constructor
+   */
+  function Validator(ysy) {
+    this.ysy = ysy;
+    this._removeConfirm = new RemoveConfirm(ysy);
+  }
+
+  Validator.prototype.changeParent = function (child, newParent) {
+    return !child.attr.nonEditable;
+  };
+  Validator.prototype.removeSubIdea = function (ideaId, eventOrigin, idea) {
+    if (!idea) {
+      var mainIdea = this.ysy.idea;
+      if (!mainIdea) return false;
+      idea = mainIdea.findSubIdeaById(ideaId);
+    }
+    if (!idea) return false;
+    if (idea.attr.nonEditable) return false;
+    return this._removeConfirm.removeIdea(idea);
+  };
+  Validator.prototype.paste = function (parent, newIdea) {
+    return !newIdea.attr.nonEditable;
+  };
+  Validator.prototype.nodeRename = function (idea) {
+    return !idea.attr.nonEditable;
+  };
+  Validator.prototype.validate = function (name /*, arg1, arg2, ... */) {
+    if (!this[name]) return true;
+    return this[name].apply(this, Array.prototype.slice.call(arguments, 1));
+  };
+  window.easyMindMupClasses.Validator = Validator;
+
+  //####################################################################################################################
+  /**
+   * @param {MindMup} ysy
+   * @property {MindMup} ysy
+   * @property {Array.<ModelEntity>} _removeStack
+   * @property {number} _timeout
+   * @property {boolean} _leaking - removes are not prevented (for short moment after passed confirm)
+   * @constructor
+   */
+  function RemoveConfirm(ysy) {
+    this.ysy = ysy;
+    this._removeStack = [];
+    this._timeout = 0;
+    this._leaking = false;
+  }
+
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @return {boolean} - return boolean to know if removing idea should proceed or not
+   */
+  RemoveConfirm.prototype.removeIdea = function (idea) {
+    if (this._leaking) return true;
+    this._removeStack.push(idea);
+    var self = this;
+    if (!this._timeout) {
+      this._timeout = window.setTimeout(function () {
+        if (self._showConfirm()) {
+          self._deleteStacked();
+        } else {
+          self._removeStack = [];
+        }
+        self._timeout = 0;
+      }, 0);
+    }
+    return false;
+  };
+  /**
+   * Confirm is passed so now all stacked ideas has to be deleted
+   * @private
+   */
+  RemoveConfirm.prototype._deleteStacked = function () {
+    this._leaking = true;
+    var rootIdea = this.ysy.idea;
+    _.each(this._removeStack, /** @param {ModelEntity} idea*/ function (idea) {
+      rootIdea.removeSubIdea(idea.id);
+    });
+    this._leaking = false;
+    this._removeStack = [];
+  };
+  /**
+   * @return {boolean}
+   * @private
+   */
+  RemoveConfirm.prototype._showConfirm = function () {
+    if (this._removeStack.length === 0) return true;
+    if (this._removeStack.length === 1) {
+      var idea = this._removeStack[0];
+      return window.confirm(this.ysy.settings.labels.errors.warning_delete_node.replace("{{name}}", '"' + idea.title + '"'));
+    } else {
+      var list = "\n";
+      for (var i = 0; i < this._removeStack.length; i++) {
+        idea = this._removeStack[i];
+        list += "\u2022 " + idea.title + "\n";
+      }
+      return window.confirm(this.ysy.settings.labels.errors.warning_delete_nodes + list);
+    }
+  }
+})();
diff --git a/plugins/easy_mindmup/assets/stylesheets/easy_mindmup.css b/plugins/easy_mindmup/assets/stylesheets/easy_mindmup.css
new file mode 100644
index 0000000000000000000000000000000000000000..506686d0fe6322bd7d5dfbe2a5633ece08154402
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/easy_mindmup.css
@@ -0,0 +1,163 @@
+/*
+* = require mindmup
+* = require_self
+* = require easy_mindmup_sass.scss
+*/
+
+.mindmup_context_menu_item {
+  cursor: pointer;
+}
+
+.mindmup-reload-modal-errors li {
+  color: #cd0a0a;
+}
+
+.mindmup-last-modal-diffs li {
+  color: #bf993f;
+}
+
+.mindmup-node-icons {
+  position: absolute;
+  left: 0;
+  top: -10px;
+  white-space: nowrap;
+}
+
+.mindmup-node-icons--with_icons .mindmup-node-icons {
+  top: -18px;
+}
+
+.mindmup-node-icon {
+  height: 20px;
+  display: inline-block;
+  margin-right: 3px;
+  vertical-align: middle;
+}
+
+.mindmup-node-icon-avatar, .mindmup-legend-item-cont .gravatar {
+  height: 24px;
+  width: 24px;
+}
+
+.mindmup-node-icon-avatar .gravatar {
+  vertical-align: sub;
+}
+
+.mindmup-node-icon-avatar .gravatar:hover,
+.mindmup-legend-item-cont .gravatar:hover {
+  transform: scale(2);
+  position: relative;
+  z-index: 5;
+}
+
+.mindmup-node-icon-milestone-diamond {
+  border-width: 4px;
+  border-style: solid;
+}
+
+#context-menu li:not(.folder) > ul {
+  display: none;
+}
+
+.controller-easy_wbs .easy-content-page {
+  transform: none !important;
+}
+
+.link-edit-widget {
+  display: none;
+  z-index: 999;
+  position: absolute;
+  padding: 10px;
+  background-color: white;
+  border: 1px solid #dd3e3a;
+  cursor: default;
+}
+
+.mindmup-legend-used {
+  margin-top: 10px;
+  display: inline-block;
+  color: rgba(66, 50, 26, 0.5);
+}
+
+.mindmup-legend-used-used--all, .mindmup-legend-used-all--used {
+  display: none;
+}
+
+.mindmup-legend-drag-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 10000;
+  cursor: move;
+}
+
+.mindmup-legend-drag-avatar {
+  position: absolute;
+  z-index: 5;
+  height: 20px;
+  width: 20px;
+  margin-left: -12px;
+  margin-top: -12px;
+  border: 1px solid;
+}
+
+/*.mindmup__menu-save--tooltip {*/
+  /*display: inline-block;*/
+  /*color: green;*/
+  /*padding: 0 5px;*/
+/*}*/
+
+.mindmup__legend-container {
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14), 0 2px 2px rgba(0, 0, 0, 0.05);
+}
+
+.mindmup__menu {
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14), 0 2px 2px rgba(0, 0, 0, 0.05);
+}
+
+.mindmup-node-icon-is_edited {
+  margin-right: 1px;
+  margin-left: -10px;
+  vertical-align: middle;
+  border-radius: 5000px;
+  width: 20px;
+  height: 20px;
+  color: #ffffff;
+  background-color: #e50026;
+  line-height: 1.2;
+  font-size: 18px;
+  text-align: center;
+  display: inline-block;
+}
+
+.mindmup-node-icon-is_edited::before {
+  content: "!";
+  font-family: inherit;
+}
+
+.mindmup-node-icons-all {
+  display: inline-block;
+}
+
+.mindmup__node--filtered_out {
+  opacity: 0.2;
+}
+
+mindmup-sidebar {
+  display: block;
+}
+
+.date-picker__date-wrap .input-append {
+  width: auto;
+  float: right;
+}
+
+.date-picker__date-wrap {
+  margin-bottom: 5px;
+  overflow: hidden;
+}
+
+easy-lookup .easy-lookup-values-wrapper {
+  height: auto;
+}
+
diff --git a/plugins/easy_mindmup/assets/stylesheets/easy_mindmup_sass.scss.erb b/plugins/easy_mindmup/assets/stylesheets/easy_mindmup_sass.scss.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5c00743fb8de00bb88e0e75f93ce3c54dc7de68b
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/easy_mindmup_sass.scss.erb
@@ -0,0 +1,38 @@
+<% redmine_sass = false
+   redmine_sass = true
+   ep_com = Redmine::Plugin.installed?(:easy_project_com)
+ %>
+<% if ep_com %>
+@import "../../../easyproject/easy_plugins/easy_project_com/assets/stylesheets/scss/common_assets";
+<% else %>
+@import "../../../easyproject/easy_plugins/easy_extensions/assets/stylesheets/scss/common_assets";
+<% end %>
+@import "sass/mindmup_default_variables";
+@import "sass/mindmup_variables";
+@import "sass/mindmup_mixins";
+@import "sass/mindmup_frame";
+@import "sass/mindmup_icons";
+<% if redmine_sass %>
+@import "sass/mindmup_redmine";
+<% end %>
+@import "sass/mindmup_progress";
+@import "sass/mindmup_legend";
+@import "sass/mindmup_mapjs";
+@import "sass/mindmup_nodes";
+@import "sass/mindmup_schemes";
+@import "sass/mindmup_print";
+@import "sass/mindmup";
+
+.mindmup {
+  @import "sass/mindmup_sidebar";
+}
+
+<%if redmine_sass %>
+@include loadFontFace--non-rails('Material Icons', '../../fonts/EasyMaterialIcons-Regular', 'Material Icons', 'MaterialIcons-Regular', normal, normal);
+<% else %>
+<% if ep_com %>
+@include loadFontFace('Material Icons', '../plugin_assets/easy_mindmup/fonts/EasyMaterialIcons-Regular', 'Material Icons', 'MaterialIcons-Regular', normal, normal);
+<% else %>
+@include loadFontFace--non-rails('Material Icons', '../plugin_assets/easy_mindmup/fonts/EasyMaterialIcons-Regular', 'Material Icons', 'MaterialIcons-Regular', normal, normal);
+<% end %>
+<% end %>
diff --git a/plugins/easy_mindmup/assets/stylesheets/jasmine.css b/plugins/easy_mindmup/assets/stylesheets/jasmine.css
new file mode 100644
index 0000000000000000000000000000000000000000..631998275d5c93b1affab90180e2d2514896a33f
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/jasmine.css
@@ -0,0 +1,58 @@
+body { overflow-y: scroll; }
+
+.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; }
+.jasmine_html-reporter a { text-decoration: none; }
+.jasmine_html-reporter a:hover { text-decoration: underline; }
+.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
+.jasmine_html-reporter .jasmine-banner, .jasmine_html-reporter .jasmine-symbol-summary, .jasmine_html-reporter .jasmine-summary, .jasmine_html-reporter .jasmine-result-message, .jasmine_html-reporter .jasmine-spec .jasmine-description, .jasmine_html-reporter .jasmine-spec-detail .jasmine-description, .jasmine_html-reporter .jasmine-alert .jasmine-bar, .jasmine_html-reporter .jasmine-stack-trace { padding-left: 9px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-banner { position: relative; }
+.jasmine_html-reporter .jasmine-banner .jasmine-title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
+.jasmine_html-reporter .jasmine-banner .jasmine-version { margin-left: 14px; position: relative; top: 6px; }
+.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
+.jasmine_html-reporter .jasmine-version { color: #aaa; }
+.jasmine_html-reporter .jasmine-banner { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+.jasmine_html-reporter .jasmine-symbol-summary li { display: inline-block; height: 10px; width: 14px; font-size: 16px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before { color: #ba9d37; content: "\02022"; }
+.jasmine_html-reporter .jasmine-run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-trigger { cursor: pointer; padding: 8px 16px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; }
+.jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+.jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; }
+.jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; }
+.jasmine_html-reporter .jasmine-bar a { color: white; }
+.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list, .jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures { display: none; }
+.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list, .jasmine_html-reporter.jasmine-failure-list .jasmine-summary { display: none; }
+.jasmine_html-reporter .jasmine-results { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
+.jasmine_html-reporter .jasmine-summary ul.jasmine-suite { margin-top: 7px; margin-bottom: 7px; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-passed a { color: #007069; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; }
+.jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; }
+.jasmine_html-reporter .jasmine-suite { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-suite a { color: #333; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; }
+.jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; }
+.jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; }
+.jasmine_html-reporter .jasmine-stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/plugins/easy_mindmup/assets/stylesheets/mindmup.css b/plugins/easy_mindmup/assets/stylesheets/mindmup.css
new file mode 100644
index 0000000000000000000000000000000000000000..e4fd88cb5f54ce1148f761234e87dbc695791cf2
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/mindmup.css
@@ -0,0 +1,1434 @@
+body, html {
+    height: 100%;
+    /*overflow: hidden*/
+}
+
+#container, #floating-toolbar, #topbar, .dropdown-menu, .floating {
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none
+}
+
+#toolbarSocial {
+    margin-right: 20px
+}
+
+.logo {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_32.png);
+    background-position: left center;
+    background-repeat: no-repeat;
+    width: 0;
+    height: 40px;
+    padding-left: 40px;
+    padding-top: 5px;
+    padding-bottom: 5px
+}
+
+#logo-img {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_32.png);
+    background-position: left center;
+    background-repeat: no-repeat;
+    width: 40px;
+    height: 40px;
+    padding: 0;
+    margin-left: 50px
+}
+
+@media (max-width: 979px) {
+    .navbar-fixed-bottom, .navbar-fixed-top {
+        position: fixed;
+        margin-bottom: 0
+    }
+}
+
+@media (max-width: 767px) {
+    .navbar {
+        padding-left: 20px;
+        padding-right: 20px
+    }
+
+    body {
+        padding: 0
+    }
+}
+
+.span-toolbar {
+    width: 120px;
+    cursor: move
+}
+
+@media (max-width: 1024px) {
+    .hidden-tablet-landscape {
+        display: none !important
+    }
+}
+
+@media (max-width: 768px) {
+    .hidden-tablet-portrait {
+        display: none !important
+    }
+}
+
+@media (max-width: 320px) {
+    .hidden-phone-portrait {
+        display: none !important
+    }
+}
+
+@media (max-height: 320px) {
+    .form-horizontal .control-group {
+        margin-bottom: 5px
+    }
+
+    .hidden-phone-landscape {
+        display: none
+    }
+}
+
+@media (max-width: 400px) {
+    .map-changed .changed-phone-hidden {
+        display: none !important
+    }
+}
+
+@media (max-width: 1024px) {
+    .map-changed .changed-tablet-hidden {
+        display: none !important
+    }
+}
+
+@media screen {
+    #topbar .nav > li > a {
+        font-weight: 700
+    }
+}
+
+#floating-toolbar {
+    position: absolute;
+    top: 100px;
+    right: 10px;
+    z-index: 999;
+    border: 1px solid #08c;
+    border-radius: 9px
+}
+
+.android .visible-touch, .ios .visible-touch {
+    display: block !important
+}
+
+.android .hidden-touch, .ios .hidden-touch {
+    display: none !important
+}
+
+div.topbar-color-picker {
+    width: 127px
+}
+
+.topbar-color-picker .colorPicker-swatch {
+    width: 15px;
+    height: 15px
+}
+
+.android .colorPicker-swatch, .ios .colorPicker-swatch {
+    width: 30px;
+    height: 30px
+}
+
+.android .colorPicker-palette, .ios .colorPicker-palette {
+    width: 217px;
+    top: 55px;
+    left: 5px
+}
+
+.map-changed .hidden-map-changed, .map-unchanged .visible-map-changed {
+    display: none !important
+}
+
+#toolbarEdit .btn-group {
+    margin-left: 0
+}
+
+#toolbarEdit button {
+    margin-top: 5px;
+    width: 40px;
+    height: 32px
+}
+
+#toolbarEdit button.two {
+    width: 80px
+}
+
+#toolbarEdit button.three {
+    width: 120px
+}
+
+.ideaInput {
+    position: absolute;
+    z-index: 999;
+    background-color: #5FBF5F;
+    color: #FFF;
+    font-family: Helvetica, "Arial Unicode MS", sans-serif;
+    font-weight: 700;
+    font-size: 13px;
+    line-height: 13px;
+    text-align: center;
+    padding: 0
+}
+
+.center {
+    text-align: center
+}
+
+.ideaInput:focus {
+    outline: 0
+}
+
+.menulink {
+    margin-right: 20px;
+    margin-top: 10px
+}
+
+#gplus-share {
+    border: 0;
+    height: 16px;
+    background-image: url(//ssl.gstatic.com/images/icons/gplus-16.png);
+    background-position: left center;
+    background-repeat: no-repeat;
+    margin-left: 6px;
+    margin-right: 20px;
+    margin-top: 2px;
+    padding-right: 0
+}
+
+#modalDownload .modal-body {
+    text-align: center
+}
+
+#modalDownload .modal-body p {
+    text-align: left
+}
+
+#modalDownload img {
+    max-height: 300px;
+    max-width: 450px
+}
+
+.share {
+    width: 32px;
+    height: 32px;
+    display: inline-block;
+    margin: 5px;
+    cursor: pointer
+}
+
+.btn-inline {
+    border: 0;
+    background: 0 0;
+    margin-left: 5px;
+    margin-right: 5px
+}
+
+.colorPicker_hexWrap {
+    display: none
+}
+
+div.colorPicker-picker {
+    display: inline;
+    float: left;
+    background: 0 0
+}
+
+#modalImport input[type=file] {
+    opacity: 0;
+    position: absolute;
+    width: 0;
+    height: 0
+}
+
+.repo {
+    background-repeat: no-repeat;
+    background-position: left center;
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_16.png)
+}
+
+.repo-a {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_16.png)
+}
+
+.repo-b {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_gold_16.png)
+}
+
+.repo-p {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/logo_gold_private_16.png)
+}
+
+.repo-g {
+    background-image: url(//d1g6a398qq2djm.cloudfront.net/img/google_drive.png)
+}
+
+.repo-menuitem {
+    background-position: 14px center;
+    padding-left: 35px !important
+}
+
+a.repo {
+    margin-left: 5px
+}
+
+span.repo-a, span.repo-b, span.repo-g, span.repo-p {
+    padding-left: 16px
+}
+
+.dropdown-menu li a {
+    cursor: pointer
+}
+
+.visible-collapsed-toolbar {
+    display: none
+}
+
+.collapsed-toolbar .visible-collapsed-toolbar {
+    display: block !important
+}
+
+.collapsed-toolbar span.visible-collapsed-toolbar {
+    display: inline !important
+}
+
+.collapsed-toolbar .hidden-collapsed-toolbar {
+    display: none !important
+}
+
+.visible-row-split {
+    display: none
+}
+
+.row-split .visible-row-split {
+    display: block !important
+}
+
+.row-split span.visible-row-split {
+    display: inline !important
+}
+
+.row-split .hidden-row-split {
+    display: none !important
+}
+
+.visible-column-split {
+    display: none
+}
+
+.column-split .visible-column-split {
+    display: block !important
+}
+
+.column-split span.visible-column-split {
+    display: inline !important
+}
+
+.column-split .hidden-column-split {
+    display: none !important
+}
+
+#listBookmarks a .btn-inline {
+    margin-left: 15px
+}
+
+.gecko #listBookmarks a .btn-inline {
+    position: relative;
+    top: -17px;
+    left: 15px
+}
+
+.hidden-topbar #topbar .navbar-inner {
+    visibility: hidden
+}
+
+.column-split.hidden-topbar #topbar .navbar-inner, .hidden-topbar #topbar:hover .navbar-inner {
+    visibility: visible
+}
+
+.topbar-autohide {
+    display: inline
+}
+
+.hidden-topbar .topbar-autohide, span.topbar-fixed {
+    display: none
+}
+
+.hidden-topbar span.topbar-fixed {
+    display: inline
+}
+
+body.ipad {
+    margin: 1px
+}
+
+body.ios-wkwebview {
+    margin: 0;
+    padding: 0;
+    width: 100%
+}
+
+.image-render-visible {
+    display: none
+}
+
+.image-render .image-render-visible {
+    display: block
+}
+
+.image-render .image-render-hidden, .offline-disabled-hidden {
+    display: none
+}
+
+body.offline-enabled .offline-disabled-hidden {
+    display: block
+}
+
+#attachEditArea {
+    overflow: scroll;
+    outline: 0;
+    border: 1px solid #ccc;
+    padding: 4px;
+    margin: 10px;
+    box-sizing: content-box;
+    -webkit-box-shadow: rgba(0, 0, 0, .0745098) 0 1px 1px 0 inset;
+    box-shadow: rgba(0, 0, 0, .0745098) 0 1px 1px 0 inset;
+    border-radius: 3px;
+    background-color: #fff;
+    display: block;
+    clear: both
+}
+
+#modalAttachmentEditor .editor-topbar {
+    padding-right: 10px;
+    padding-left: 10px;
+    display: none
+}
+
+#modalAttachmentEditor.mm-editable .editor-topbar {
+    display: block
+}
+
+#modalAttachmentEditor .viewer-topbar {
+    margin-top: 5px;
+    margin-bottom: 5px;
+    padding-right: 10px;
+    padding-left: 10px;
+    height: 30px
+}
+
+#modalAttachmentEditor.mm-editable .viewer-topbar {
+    display: none
+}
+
+#modalAttachmentEditor button.close {
+    margin-left: 5px
+}
+
+#modalAttachmentEditor .editor-topbar > .btn-toolbar {
+    display: inline-block;
+    background-color: #eee;
+    width: 100%
+}
+
+#modalAttachmentEditor {
+    position: absolute;
+    height: 80%;
+    width: 80%;
+    left: 10%;
+    top: 10%;
+    padding: 5px;
+    z-index: 9999;
+    background-color: #eee;
+    -moz-border-radius: 6px;
+    border-radius: 6px;
+    margin: 0
+}
+
+@media (max-width: 1200px) {
+    #modalAttachmentEditor {
+        width: 90%;
+        left: 5%;
+        top: 5%
+    }
+}
+
+#modalAttachmentEditor .dropdown-menu .btn-toolbar {
+    display: block
+}
+
+#modalAttachmentEditor .dropdown-menu .btn-toolbar .btn-group {
+    margin-top: 5px;
+    margin-left: 5px;
+    margin-right: 5px
+}
+
+.android #modalAttachmentEditor .btn-toolbar, .ios #modalAttachmentEditor .btn-toolbar {
+    margin-top: 5px;
+    margin-bottom: 5px;
+    padding-bottom: 10px
+}
+
+.android #modalAttachmentEditor, .ios #modalAttachmentEditor {
+    top: 0;
+    height: 60%
+}
+
+@media (max-height: 768px) {
+    .android #modalAttachmentEditor, .ios #modalAttachmentEditor {
+        height: 37%
+    }
+}
+
+.non-group .btn {
+    margin-left: 5px
+}
+
+#linkEditWidget {
+    display: none;
+    z-index: 999;
+    position: absolute;
+    padding: 10px
+}
+
+.alert a {
+    text-decoration: underline;
+    cursor: pointer
+}
+
+.alert a.btn {
+    text-decoration: none
+}
+
+input.noglow {
+    outline: 0;
+    border: none !important;
+    box-shadow: none !important;
+    cursor: pointer
+}
+
+.visible-map-source-a, .visible-map-source-b, .visible-map-source-g, .visible-map-source-n, .visible-map-source-p {
+    display: none
+}
+
+.map-source-a .visible-map-source-a, .map-source-b .visible-map-source-b, .map-source-g .visible-map-source-g, .map-source-n .visible-map-source-n, .map-source-p .visible-map-source-p {
+    display: inherit
+}
+
+@media (max-width: 1200px) {
+    .hidden-narrow-screen {
+        display: none
+    }
+}
+
+@media (max-width: 1600px) {
+    #optionalPane .hidden-narrow-screen {
+        display: none
+    }
+}
+
+.btn-xlarge {
+    padding: 18px 28px;
+    font-size: 40px;
+    line-height: normal;
+    -webkit-border-radius: 8px;
+    -moz-border-radius: 8px;
+    border-radius: 8px;
+    width: 250px;
+    height: 180px;
+    margin-left: 10px;
+    margin-bottom: 10px
+}
+
+@media (max-height: 320px) {
+    .btn-xlarge {
+        padding: 9px 14px;
+        font-size: 20px;
+        line-height: normal;
+        -webkit-border-radius: 4px;
+        -moz-border-radius: 4px;
+        border-radius: 4px;
+        width: 230px;
+        height: 70px;
+        margin-left: 5px;
+        margin-bottom: 5px
+    }
+}
+
+@media (max-width: 320px) {
+    .btn-xlarge {
+        padding: 9px 14px;
+        font-size: 20px;
+        line-height: normal;
+        -webkit-border-radius: 4px;
+        -moz-border-radius: 4px;
+        border-radius: 4px;
+        width: 230px;
+        height: 70px;
+        margin-left: 5px;
+        margin-bottom: 5px
+    }
+
+    #mainMenu > li > a {
+        padding-left: 0;
+        padding-right: 15px
+    }
+
+    #logo-img {
+        margin: 0;
+        padding-left: 5px;
+        padding-right: 0
+    }
+}
+
+@media (min-height: 640px) {
+    #modalKeyActions .modal-body {
+        min-height: 400px
+    }
+}
+
+#modalKeyActions .item a span {
+    padding-right: 10px
+}
+
+#modalKeyActions a {
+    cursor: pointer;
+    color: #000
+}
+
+#modalKeyActions div.item > a {
+    text-decoration: underline
+}
+
+.social {
+    display: inline-block !important;
+    padding-left: 2px !important;
+    padding-right: 2px !important;
+    margin-right: 2px !important;
+    cursor: pointer
+}
+
+#modalGoldLicense textarea {
+    width: 95%;
+    height: 200px
+}
+
+#modalIconEdit .file-drop-zone {
+    width: 150px;
+    height: 150px;
+    border: 1px dashed #000;
+    color: gray;
+    font-size: 7px;
+    text-align: center
+}
+
+#modalIconEdit .file-drop-zone img {
+    max-width: 150px;
+    max-height: 150px
+}
+
+#modalIconEdit {
+    z-index: 9999
+}
+
+.btn .export {
+    max-width: 24px
+}
+
+.btn .landscape {
+    transform: rotate(90deg);
+    -moz-transform: rotate(90deg);
+    -webkit-transform: rotate(90deg)
+}
+
+#modalPDFExport form {
+    margin-top: 20px
+}
+
+.alert-error input {
+    background: 0 0;
+    margin: 0 0 2px;
+    color: #b94a48;
+    border: none !important;
+    outline: 0;
+    box-shadow: none !important;
+    width: 100%;
+    cursor: pointer
+}
+
+body .modal.huge {
+    width: 90%;
+    height: 80%;
+    left: 5%;
+    margin-left: auto;
+    margin-right: auto
+}
+
+#measuresSheet table {
+    background-color: #fff;
+    border: 1px solid #d4d4d4
+}
+
+#measuresSheet td {
+    text-align: right;
+    width: 20%
+}
+
+#measuresSheet th + th {
+    text-align: right
+}
+
+#measuresSheet table thead tr th {
+    border-bottom: 3px gray solid !important
+}
+
+#measuresSheet table tfoot tr th {
+    border-top: 3px gray solid !important
+}
+
+#measuresSheet table tbody th {
+    width: 30%
+}
+
+#measuresSheet form {
+    margin: 0 !important
+}
+
+#measuresSheet td:focus {
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    outline: #5b9dd9 auto
+}
+
+.measures-editor.error {
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px red !important;
+    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px red !important;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px red !important;
+    outline: red auto thin !important
+}
+
+#measuresSheet .modal-footer a {
+    margin-right: 20px;
+    margin-left: 20px;
+    line-height: 30px
+}
+
+#optionalPane .input-append.navbar-form button {
+    margin-top: 5px !important
+}
+
+#measuresSheet {
+    margin: 5px;
+    border-left: 1px;
+    user-select: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none
+}
+
+.table-container {
+    margin-right: 5px
+}
+
+.column-split .table-container {
+    margin-bottom: 45px
+}
+
+.show-active {
+    display: none
+}
+
+.mm-active .show-active {
+    display: initial
+}
+
+.black {
+    color: #000 !important
+}
+
+.activated-scene {
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #7ab5d3;
+    outline: #5b9dd9 auto
+}
+
+#storyboard {
+    user-select: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none
+}
+
+.storyboard-container {
+    padding-left: 10px;
+    padding-right: 10px
+}
+
+.storyboard-scene {
+    display: inline-block;
+    max-height: 120px;
+    max-width: 160px;
+    min-height: 120px;
+    min-width: 160px;
+    overflow: hidden;
+    background-color: #fff;
+    border: 1px solid silver;
+    border-radius: 4px;
+    color: #000;
+    margin: 10px;
+    text-align: center;
+    cursor: move
+}
+
+.storyboard-scene-title {
+    display: table-cell;
+    vertical-align: middle;
+    white-space: pre-wrap;
+    font-weight: 700
+}
+
+.drag-shadow {
+    opacity: .5;
+    box-shadow: none;
+    outline: 0;
+    cursor: move;
+    z-index: 10;
+    position: relative
+}
+
+.potential-drop-left {
+    margin-left: 0;
+    margin-right: 20px
+}
+
+.potential-drop-right {
+    margin-left: 20px;
+    margin-right: 0
+}
+
+#splittable {
+    width: 100%;
+    height: calc(100% - 41px);
+    margin-top: 41px;
+    -webkit-tap-highlight-color: transparent
+}
+
+.hidden-topbar:not(.column-split) > #splittable {
+    height: 100%;
+    margin-top: 0
+}
+
+.ios-wkwebview > #splittable {
+    width: 100%;
+    height: calc(100%);
+    margin-top: 0
+}
+
+.ios-wkwebview {
+    -webkit-tap-highlight-color: transparent
+}
+
+.splittable-optional .navbar {
+    width: 100%;
+    margin-bottom: 0
+}
+
+.splittable-optional .content {
+    overflow: scroll;
+    height: calc(100% - 41px)
+}
+
+.ios-wkwebview .splittable-optional .content {
+    overflow: scroll;
+    height: 100%
+}
+
+.splittable-primary {
+    outline: #d4d4d4 solid 1px
+}
+
+.splittable-optional {
+    display: none;
+    outline: #d4d4d4 solid 1px
+}
+
+.column-split .splittable-primary {
+    float: left;
+    height: 100%;
+    overflow: scroll;
+    width: 50%
+}
+
+.column-split .splittable-optional {
+    float: right;
+    height: 100%;
+    width: 50%;
+    display: block
+}
+
+.splittable-primary {
+    height: 100%;
+    width: 100%
+}
+
+.row-split .splittable-primary {
+    height: 50%;
+    overflow: scroll;
+    width: 100%
+}
+
+.row-split .splittable-optional {
+    height: 50%;
+    width: 100%;
+    display: block
+}
+
+#optionalPane {
+    background-color: #f5f5f5;
+    -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1);
+    -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1);
+    box-shadow: 0 1px 10px rgba(0, 0, 0, .1)
+}
+
+.splittable-optional .content-container {
+    height: 100%
+}
+
+.pull-middle {
+    display: table;
+    height: 100%
+}
+
+.pull-middle > div {
+    display: table-cell;
+    vertical-align: middle;
+    padding-left: 20%;
+    padding-right: 20%
+}
+
+.ios-menu-toggle-container {
+    position: absolute;
+    height: 90px;
+    width: 75px;
+    top: calc(100% - 90px);
+    left: 0;
+    overflow: hidden;
+    z-index: 999
+}
+
+.ios-corner-icon-container {
+    position: absolute;
+    height: 100px;
+    width: 75px;
+    top: 5;
+    left: -11px;
+    border: 1px solid #22AAE0;
+    background-color: #FFF;
+    border-radius: 5px;
+    box-shadow: 2px 2px 1px rgba(104, 104, 104, .8)
+}
+
+.ios-menu-toggle {
+    position: absolute;
+    top: 5;
+    left: 12;
+    height: 73px;
+    width: 65px
+}
+
+.ios-toolbar {
+    position: absolute;
+    box-shadow: 0 -1px 0 #22AAE0;
+    background-color: rgba(255, 255, 255, .85);
+    top: calc(100% - 80px);
+    left: 65px;
+    width: calc(100% - 65px);
+    height: 80px;
+    z-index: 998;
+    overflow: scroll
+}
+
+.ios-icon-label {
+    position: absolute;
+    font-family: Helvetica, "Arial Unicode MS", sans-serif;
+    font-size: 8pt;
+    color: #22AAE0;
+    font-weight: 400;
+    text-align: center;
+    line-height: 1.2;
+    top: calc(100% - 22px);
+    width: 50px;
+    left: 5px;
+    height: 22px
+}
+
+.ios-toolbar-item {
+    display: inline-block;
+    position: relative;
+    top: 0;
+    height: 75px;
+    width: 60px
+}
+
+.ios-toolbar-icon {
+    position: absolute;
+    left: 10px;
+    top: 8px;
+    height: 40px;
+    width: 40px
+}
+
+.ios-modal {
+    z-index: 1090;
+    position: absolute;
+    overflow: hidden;
+    left: 0;
+    top: 0;
+    height: 100%;
+    width: 100%;
+    background-color: rgba(255, 255, 255, .85)
+}
+
+.ios-modal-close {
+    position: absolute;
+    top: 20;
+    left: calc(100% - 45px);
+    height: 40px;
+    width: 40px
+}
+
+.ios-modal-title {
+    position: absolute;
+    left: 45px;
+    top: 30px;
+    height: 40px;
+    width: calc(100% - 90px);
+    text-align: center;
+    font-family: Helvetica, "Arial Unicode MS", sans-serif;
+    font-size: 12pt;
+    color: #22AAE0;
+    font-weight: 700;
+    line-height: 1.2
+}
+
+.ios-modal-content {
+    z-index: 1095;
+    position: absolute;
+    top: 80px;
+    height: calc(100% - 75px);
+    overflow-x: hidden;
+    overflow-y: auto;
+    background-color: #FFF;
+    border: 1px solid #22AAE0;
+    border-radius: 5px;
+    box-shadow: 2px 2px 1px rgba(104, 104, 104, .8)
+}
+
+.ios-color-selector {
+    display: inline-block;
+    box-shadow: 1px 1px 3px rgba(104, 104, 104, .7)
+}
+
+.ios-color-palette {
+    display: block;
+    text-align: center
+}
+
+@media (min-width: 601px) {
+    .ios-modal-content {
+        left: calc(50% - 300px);
+        width: 600px
+    }
+
+    .ios-color-palette {
+        margin: 20px 50px
+    }
+
+    .ios-color-selector {
+        margin: 20px;
+        height: 80px;
+        width: 80px;
+        border-radius: 40px
+    }
+}
+
+@media (max-width: 600px) {
+    .ios-modal-content {
+        left: calc(50% - 150px);
+        width: 300px
+    }
+
+    .ios-color-palette {
+        margin: 10px 20px
+    }
+
+    .ios-color-selector {
+        margin: 10px;
+        height: 40px;
+        width: 40px;
+        border-radius: 20px
+    }
+}
+
+.collaborator-list-container {
+    max-height: 200px;
+    overflow-y: scroll;
+    padding-top: 10px
+}
+
+.collaborator-list-container td {
+    cursor: pointer
+}
+
+.collaborator-list-container table {
+    margin-bottom: 0
+}
+
+.mm-collaborator {
+    position: absolute;
+    width: 32px;
+    height: 32px;
+    border: 3px solid transparent;
+    border-radius: 20px;
+    transform: translate(5px, 5px);
+    min-width: 30px
+}
+
+.collab-name {
+    overflow-x: hidden;
+    max-width: 50px;
+    white-space: nowrap
+}
+
+.collab-name a {
+    line-height: 30px
+}
+
+.collab-photo {
+    width: 30px
+}
+
+.collab-photo img {
+    width: 30px;
+    height: 30px;
+    float: left;
+    margin-right: 10px;
+    border: 2px solid transparent;
+    border-radius: 20px
+}
+
+#floating-collaborators {
+    position: absolute;
+    top: 100px;
+    left: 10px;
+    z-index: 999;
+    border: 1px solid #08c;
+    border-radius: 9px
+}
+
+.visible-collaboration-toolbar {
+    display: none
+}
+
+.map-source-c.collaboration-toolbar .visible-collaboration-toolbar {
+    display: initial !important
+}
+
+.collaboration-toolbar .hidden-collaboration-toolbar {
+    display: none !important
+}
+
+.mm-has-collaborators .hidden-has-collaborators, .visible-has-collaborators {
+    display: none
+}
+
+.mm-has-collaborators .visible-has-collaborators {
+    display: block
+}
+
+.visible-collaboration-mute-speech {
+    display: none
+}
+
+.collaboration-mute-speech .visible-collaboration-mute-speech {
+    display: initial !important
+}
+
+.collaboration-mute-speech .hidden-collaboration-mute-speech {
+    display: none !important
+}
+
+.btn-share {
+    margin-left: 10px;
+    margin-right: 10px
+}
+
+.mm-icon-gmail {
+    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNDU1NTUgMzMwOTciPg0KIDxnPg0KICA8cGF0aCBmaWxsPSIjRTc1QTREIiBkPSJNNDE2MDggMjUwYzIwMzAsMCAzNjkxLDE2NjEgMzY5MSwzNjkxbDAgMTA3MCAtNTEyOCAzNjYzIC0xNzQ1OCAxMjAyNiAtMTc0NTggLTEyMTQ4IDAgMjQyOTUgLTEzMTQgMGMtMjAzMCwwIC0zNjkxLC0xNjYxIC0zNjkxLC0zNjkxbDAgLTI1MjE0YzAsLTIwMzAgMTY2MSwtMzY5MSAzNjkxLC0zNjkxbDE4NzcyIDEzOTE4IDE4ODk0IC0xMzkxOHoiLz4NCiAgPHBvbHlnb24gZmlsbD0iI0U3RTRENyIgcG9pbnRzPSI1MjU1LDg1NTIgNTI1NSwzMjg0NyA0MDE3MiwzMjg0NyA0MDE3Miw4Njc0IDIyNzEzLDIwNzAwICIvPg0KICA8cG9seWdvbiBmaWxsPSIjQjhCN0FFIiBwb2ludHM9IjUyNTUsMzI4NDcgMjI3MTMsMjA3MDAgMjI2MzQsMjA2NDQgNTI1NSwzMjU4MCAiLz4NCiAgPHBvbHlnb24gZmlsbD0iI0I3QjZBRCIgcG9pbnRzPSI0MDE3Miw4Njc0IDQwMTgyLDMyODQ3IDIyNzEzLDIwNzAwICIvPg0KICA8cGF0aCBmaWxsPSIjQjIzOTJGIiBkPSJNNDUyOTkgNTAxMWw2IDI0MTQ4Yy04OCwyNjg0IC0xMjUxLDM2NDIgLTUxMjMsMzY4N2wtMTAgLTI0MTczIDUxMjggLTM2NjN6Ii8+DQogIDxwb2x5Z29uIGZpbGw9IiNGN0Y1RUQiIHBvaW50cz0iMzk0MSwyNTAgMjI3MTMsMTQxNjggNDE2MDgsMjUwICIvPg0KIDwvZz4NCjwvc3ZnPg0K);
+    padding: 15px 9px;
+    background-position: center center;
+    background-repeat: no-repeat
+}
+
+.mm-icon-gdrive {
+    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iNTA5LjA4bW0iIGhlaWdodD0iNDQwLjk3bW0iIHZlcnNpb249IjEuMSIgc3R5bGU9InNoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247IHRleHQtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbjsgaW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTsgZmlsbC1ydWxlOmV2ZW5vZGQ7IGNsaXAtcnVsZTpldmVub2RkIg0Kdmlld0JveD0iMCAwIDEzMzE1NiAxMTUzNDEiDQogeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPg0KIDxkZWZzPg0KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KICAgPCFbQ0RBVEFbDQogICAgLmZpbDIge2ZpbGw6IzExQTg2MX0NCiAgICAuZmlsMCB7ZmlsbDojMzc3N0UzfQ0KICAgIC5maWwxIHtmaWxsOiNGRkNGNjN9DQogICBdXT4NCiAgPC9zdHlsZT4NCiA8L2RlZnM+DQogPGcgaWQ9IkxpdmVsbG9feDAwMjBfMSI+DQogIDxtZXRhZGF0YSBpZD0iQ29yZWxDb3JwSURfMENvcmVsLUxheWVyIi8+DQogIDxwb2x5Z29uIGNsYXNzPSJmaWwwIiBwb2ludHM9IjIyMTk0LDExNTM0MSA0NDM4NSw3Njg5NCAxMzMxNTYsNzY4OTQgMTEwOTYzLDExNTM0MSAiLz4NCiAgPHBvbHlnb24gY2xhc3M9ImZpbDEiIHBvaW50cz0iODg3NzIsNzY4OTQgMTMzMTU2LDc2ODk0IDg4NzcyLDAgNDQzODUsMCAiLz4NCiAgPHBvbHlnb24gY2xhc3M9ImZpbDIiIHBvaW50cz0iMCw3Njg5NCAyMjE5NCwxMTUzNDEgNjY1NzgsMzg0NDcgNDQzODUsMCAiLz4NCiA8L2c+DQo8L3N2Zz4=);
+    padding: 15px 9px;
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: 100% 100%
+}
+
+#collaboratorSpeechBubble {
+    position: absolute;
+    z-index: 2;
+    top: 100px;
+    left: 20px
+}
+
+#collaboratorSpeechBubble img {
+    width: 60px;
+    height: 60px;
+    border-radius: 60px;
+    border: 2px solid transparent;
+    box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+    cursor: pointer
+}
+
+#collaboratorSpeechBubble img:hover {
+    box-shadow: 0 5px 10px rgba(0, 0, 0, .5)
+}
+
+.speech-bubble-inner {
+    max-width: 300px;
+    max-height: 100px;
+    min-width: 150px;
+    overflow: hidden
+}
+
+.speech-bubble-title {
+    max-width: 300px;
+    white-space: nowrap;
+    overflow: hidden
+}
+
+.icon-volume-off {
+    margin-left: 3px;
+    margin-right: 5px
+}
+
+.hidden-topbar #mainMenu .dropdown-menu {
+    margin-top: 0
+}
+
+.mapjs-node {
+    margin: 0;
+    padding: 7px;
+    border-radius: 10px;
+    border: 1px solid #777;
+    /*box-shadow: 5px 5px 5px rgba(204, 204, 204, .8);*/
+    z-index: 3;
+    user-select: none;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    background-color: #E0E0E0;
+    color: #4F4F4F;
+    cursor: pointer;
+    font-family: Helvetica, "Arial Unicode MS", sans-serif;
+    font-weight: 700;
+    font-size: 12px
+}
+
+.mapjs-add-link {
+    cursor: crosshair
+}
+
+.mapjs-add-link .mapjs-node {
+    cursor: alias
+}
+
+.mapjs-node span {
+    white-space: pre-wrap;
+    text-align: center;
+    line-height: 150%;
+    display: block;
+    max-width: 146px;
+    min-height: 1.5em;
+    min-width: 1em;
+    outline: 0;
+    cursor: pointer
+}
+
+.mapjs-node span[contenteditable=true] {
+    user-select: text;
+    -moz-user-select: text;
+    -webkit-user-select: text;
+    -ms-user-select: text;
+    cursor: auto
+}
+
+.mapjs-node.activated {
+    outline: 0;
+    border: 3px dotted #2E9AFE;
+    margin: -2px
+}
+
+.mapjs-node:focus {
+    outline: 0
+}
+
+.mapjs-node.selected {
+    outline: 0;
+    /*box-shadow: 5px 5px 5px #000;*/
+    z-index: 4
+}
+
+.mapjs-node.dragging {
+    opacity: .4;
+    z-index: 5
+}
+
+/*.mapjs-node.collapsed {*/
+    /*box-shadow: 3px 3px 0 #A0A0A0, 4px 4px 0 #333, 6px 6px 0 #707070, 7px 7px 0 #222*/
+/*}*/
+
+/*.mapjs-node.collapsed.selected {*/
+    /*box-shadow: 4px 4px 0 #A0A0A0, 5px 5px 0 #333, 8px 8px 0 #707070, 9px 9px 0 #222, 12px 12px 5px #000*/
+/*}*/
+
+/*.mapjs-node[mapjs-level="1"] {*/
+    /*background-color: #22AAE0*/
+/*}*/
+
+/*.mapjs-node[mapjs-level="1"].activated {*/
+    /*border: 3px dotted #E0E0E0*/
+/*}*/
+
+.mapjs-node.droppable {
+    outline: 0;
+    border: 3px dashed #EF6F6F;
+    margin: -2px
+}
+
+.mapjs-node-light {
+    color: #4F4F4F
+}
+
+.mapjs-node-dark {
+    color: #EEE
+}
+
+.mapjs-node-white {
+    color: #000
+}
+
+.mapjs-label {
+    left: -.75em;
+    position: absolute;
+    bottom: -1em;
+    opacity: .8;
+    background-color: #f13333;
+    padding: 1px 2px;
+    border: 1px solid #fff;
+    -webkit-border-radius: 9px;
+    -moz-border-radius: 9px;
+    border-radius: 9px;
+    display: inline-block;
+    font-size: 10px;
+    font-weight: 700;
+    line-height: 14px;
+    color: #fff;
+    text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
+    white-space: nowrap;
+    vertical-align: baseline
+}
+
+.mapjs-hyperlink {
+    right: -.75em;
+    position: absolute;
+    bottom: -.75em;
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoNCgkgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQoNCjxwYXRoIHN0eWxlPSJmaWxsOmJsYWNrIiBkPSJNMTU2LjIyNiwxOTkuNjc5YzcuNTQxLTcuNTQsMTUuOTAyLTEzLjc1NywyNC43OTQtMTguNjU5YzQ5LjU1Ni0yNy4zMTgsMTEzLjExNy0xMi43ODgsMTQ0Ljk3LDM1LjUxOA0KDQoJbC0zOC41NDcsMzguNTQ3Yy0xMS4wNTktMjUuMjI3LTM4LjUtMzkuNTY1LTY1LjgxMy0zMy40NTZjLTEwLjI4MiwyLjMtMjAuMDU0LDcuNDI3LTI4LjAzOSwxNS40MTNsLTczLjg5OCw3My44OTYNCg0KCWMtMjIuNDMzLDIyLjQzMy0yMi40MzIsNTguOTM2LDAuMDAyLDgxLjM2OWMyMi40MzMsMjIuNDMzLDU4LjkzNSwyMi40MzMsODEuMzY4LDBsMjIuNzgtMjIuNzc5DQoNCgljMjAuNzEsOC4yMTcsNDIuOTM4LDExLjUwOCw2NC44NjIsOS44NjNsLTUwLjI3OCw1MC4yNzhjLTQzLjEwNSw0My4xMDUtMTEyLjk5MSw0My4xMDUtMTU2LjA5NiwwDQoNCgljLTQzLjEwNS00My4xMDQtNDMuMTA2LTExMi45OTEtMC4wMDEtMTU2LjA5NkwxNTYuMjI2LDE5OS42Nzl6IE0yNzMuNTc0LDgyLjMzbC01MC4yNzgsNTAuMjc4DQoNCgljMjEuOTI4LTEuNjQzLDQ0LjE1MiwxLjY0OCw2NC44NjMsOS44NjVsMjIuNzc5LTIyLjc4YzIyLjQzNC0yMi40MzQsNTguOTM2LTIyLjQzNCw4MS4zNywwYzIyLjQzNCwyMi40MzQsMjIuNDM0LDU4LjkzNiwwLDgxLjM3DQoNCglsLTczLjg5Nyw3My44OTVjLTIyLjUwMSwyMi41MDEtNTkuMDYxLDIyLjMxMS04MS4zNjgsMGMtNS4yMDItNS4yMDEtOS42OTQtMTEuNjc4LTEyLjQ4NC0xOC4wNGwtMzguNTQ2LDM4LjU0Ng0KDQoJYzQuMDQ5LDYuMTQyLDguMjYxLDExLjQ1MywxMy42NjYsMTYuODU4YzEzLjk0OSwxMy45NSwzMS42OTgsMjQuMzM5LDUyLjExNywyOS4yNTFjMjYuNDY2LDYuMzcsNTQuODIzLDIuODM5LDc5LjE4NS0xMC41OTINCg0KCWM4Ljg5Mi00LjkwMywxNy4yNTQtMTEuMTE5LDI0Ljc5NC0xOC42NTlsNzMuODk2LTczLjg5NWM0My4xMDUtNDMuMTA1LDQzLjEwNS0xMTIuOTkxLDAuMDAxLTE1Ni4wOTcNCg0KCUMzODYuNTY2LDM5LjIyNSwzMTYuNjgsMzkuMjI1LDI3My41NzQsODIuMzN6Ii8+DQo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAsLTIwKSIgc3R5bGU9ImZpbGw6IzAwOUFDRCI+DQo8cGF0aCAgZD0iTTE1Ni4yMjYsMTk5LjY3OWM3LjU0MS03LjU0LDE1LjkwMi0xMy43NTcsMjQuNzk0LTE4LjY1OWM0OS41NTYtMjcuMzE4LDExMy4xMTctMTIuNzg4LDE0NC45NywzNS41MTgNCg0KCWwtMzguNTQ3LDM4LjU0N2MtMTEuMDU5LTI1LjIyNy0zOC41LTM5LjU2NS02NS44MTMtMzMuNDU2Yy0xMC4yODIsMi4zLTIwLjA1NCw3LjQyNy0yOC4wMzksMTUuNDEzbC03My44OTgsNzMuODk2DQoNCgljLTIyLjQzMywyMi40MzMtMjIuNDMyLDU4LjkzNiwwLjAwMiw4MS4zNjljMjIuNDMzLDIyLjQzMyw1OC45MzUsMjIuNDMzLDgxLjM2OCwwbDIyLjc4LTIyLjc3OQ0KDQoJYzIwLjcxLDguMjE3LDQyLjkzOCwxMS41MDgsNjQuODYyLDkuODYzbC01MC4yNzgsNTAuMjc4Yy00My4xMDUsNDMuMTA1LTExMi45OTEsNDMuMTA1LTE1Ni4wOTYsMA0KDQoJYy00My4xMDUtNDMuMTA0LTQzLjEwNi0xMTIuOTkxLTAuMDAxLTE1Ni4wOTZMMTU2LjIyNiwxOTkuNjc5eiBNMjczLjU3NCw4Mi4zM2wtNTAuMjc4LDUwLjI3OA0KDQoJYzIxLjkyOC0xLjY0Myw0NC4xNTIsMS42NDgsNjQuODYzLDkuODY1bDIyLjc3OS0yMi43OGMyMi40MzQtMjIuNDM0LDU4LjkzNi0yMi40MzQsODEuMzcsMGMyMi40MzQsMjIuNDM0LDIyLjQzNCw1OC45MzYsMCw4MS4zNw0KDQoJbC03My44OTcsNzMuODk1Yy0yMi41MDEsMjIuNTAxLTU5LjA2MSwyMi4zMTEtODEuMzY4LDBjLTUuMjAyLTUuMjAxLTkuNjk0LTExLjY3OC0xMi40ODQtMTguMDRsLTM4LjU0NiwzOC41NDYNCg0KCWM0LjA0OSw2LjE0Miw4LjI2MSwxMS40NTMsMTMuNjY2LDE2Ljg1OGMxMy45NDksMTMuOTUsMzEuNjk4LDI0LjMzOSw1Mi4xMTcsMjkuMjUxYzI2LjQ2Niw2LjM3LDU0LjgyMywyLjgzOSw3OS4xODUtMTAuNTkyDQoNCgljOC44OTItNC45MDMsMTcuMjU0LTExLjExOSwyNC43OTQtMTguNjU5bDczLjg5Ni03My44OTVjNDMuMTA1LTQzLjEwNSw0My4xMDUtMTEyLjk5MSwwLjAwMS0xNTYuMDk3DQoNCglDMzg2LjU2NiwzOS4yMjUsMzE2LjY4LDM5LjIyNSwyNzMuNTc0LDgyLjMzeiIvPg0KPC9nPg0KDQo8L3N2Zz4NCg0K);
+    width: 2em;
+    height: 2em;
+    background-size: 2em;
+    background-repeat: no-repeat no-repeat
+}
+
+.mapjs-hyperlink:hover {
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoNCgkgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQoNCjxwYXRoIHN0eWxlPSJmaWxsOmJsYWNrIiBkPSJNMTU2LjIyNiwxOTkuNjc5YzcuNTQxLTcuNTQsMTUuOTAyLTEzLjc1NywyNC43OTQtMTguNjU5YzQ5LjU1Ni0yNy4zMTgsMTEzLjExNy0xMi43ODgsMTQ0Ljk3LDM1LjUxOA0KDQoJbC0zOC41NDcsMzguNTQ3Yy0xMS4wNTktMjUuMjI3LTM4LjUtMzkuNTY1LTY1LjgxMy0zMy40NTZjLTEwLjI4MiwyLjMtMjAuMDU0LDcuNDI3LTI4LjAzOSwxNS40MTNsLTczLjg5OCw3My44OTYNCg0KCWMtMjIuNDMzLDIyLjQzMy0yMi40MzIsNTguOTM2LDAuMDAyLDgxLjM2OWMyMi40MzMsMjIuNDMzLDU4LjkzNSwyMi40MzMsODEuMzY4LDBsMjIuNzgtMjIuNzc5DQoNCgljMjAuNzEsOC4yMTcsNDIuOTM4LDExLjUwOCw2NC44NjIsOS44NjNsLTUwLjI3OCw1MC4yNzhjLTQzLjEwNSw0My4xMDUtMTEyLjk5MSw0My4xMDUtMTU2LjA5NiwwDQoNCgljLTQzLjEwNS00My4xMDQtNDMuMTA2LTExMi45OTEtMC4wMDEtMTU2LjA5NkwxNTYuMjI2LDE5OS42Nzl6IE0yNzMuNTc0LDgyLjMzbC01MC4yNzgsNTAuMjc4DQoNCgljMjEuOTI4LTEuNjQzLDQ0LjE1MiwxLjY0OCw2NC44NjMsOS44NjVsMjIuNzc5LTIyLjc4YzIyLjQzNC0yMi40MzQsNTguOTM2LTIyLjQzNCw4MS4zNywwYzIyLjQzNCwyMi40MzQsMjIuNDM0LDU4LjkzNiwwLDgxLjM3DQoNCglsLTczLjg5Nyw3My44OTVjLTIyLjUwMSwyMi41MDEtNTkuMDYxLDIyLjMxMS04MS4zNjgsMGMtNS4yMDItNS4yMDEtOS42OTQtMTEuNjc4LTEyLjQ4NC0xOC4wNGwtMzguNTQ2LDM4LjU0Ng0KDQoJYzQuMDQ5LDYuMTQyLDguMjYxLDExLjQ1MywxMy42NjYsMTYuODU4YzEzLjk0OSwxMy45NSwzMS42OTgsMjQuMzM5LDUyLjExNywyOS4yNTFjMjYuNDY2LDYuMzcsNTQuODIzLDIuODM5LDc5LjE4NS0xMC41OTINCg0KCWM4Ljg5Mi00LjkwMywxNy4yNTQtMTEuMTE5LDI0Ljc5NC0xOC42NTlsNzMuODk2LTczLjg5NWM0My4xMDUtNDMuMTA1LDQzLjEwNS0xMTIuOTkxLDAuMDAxLTE1Ni4wOTcNCg0KCUMzODYuNTY2LDM5LjIyNSwzMTYuNjgsMzkuMjI1LDI3My41NzQsODIuMzN6Ii8+DQo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAsLTIwKSIgc3R5bGU9ImZpbGw6IzAwQkZGRiI+DQo8cGF0aCAgZD0iTTE1Ni4yMjYsMTk5LjY3OWM3LjU0MS03LjU0LDE1LjkwMi0xMy43NTcsMjQuNzk0LTE4LjY1OWM0OS41NTYtMjcuMzE4LDExMy4xMTctMTIuNzg4LDE0NC45NywzNS41MTgNCg0KCWwtMzguNTQ3LDM4LjU0N2MtMTEuMDU5LTI1LjIyNy0zOC41LTM5LjU2NS02NS44MTMtMzMuNDU2Yy0xMC4yODIsMi4zLTIwLjA1NCw3LjQyNy0yOC4wMzksMTUuNDEzbC03My44OTgsNzMuODk2DQoNCgljLTIyLjQzMywyMi40MzMtMjIuNDMyLDU4LjkzNiwwLjAwMiw4MS4zNjljMjIuNDMzLDIyLjQzMyw1OC45MzUsMjIuNDMzLDgxLjM2OCwwbDIyLjc4LTIyLjc3OQ0KDQoJYzIwLjcxLDguMjE3LDQyLjkzOCwxMS41MDgsNjQuODYyLDkuODYzbC01MC4yNzgsNTAuMjc4Yy00My4xMDUsNDMuMTA1LTExMi45OTEsNDMuMTA1LTE1Ni4wOTYsMA0KDQoJYy00My4xMDUtNDMuMTA0LTQzLjEwNi0xMTIuOTkxLTAuMDAxLTE1Ni4wOTZMMTU2LjIyNiwxOTkuNjc5eiBNMjczLjU3NCw4Mi4zM2wtNTAuMjc4LDUwLjI3OA0KDQoJYzIxLjkyOC0xLjY0Myw0NC4xNTIsMS42NDgsNjQuODYzLDkuODY1bDIyLjc3OS0yMi43OGMyMi40MzQtMjIuNDM0LDU4LjkzNi0yMi40MzQsODEuMzcsMGMyMi40MzQsMjIuNDM0LDIyLjQzNCw1OC45MzYsMCw4MS4zNw0KDQoJbC03My44OTcsNzMuODk1Yy0yMi41MDEsMjIuNTAxLTU5LjA2MSwyMi4zMTEtODEuMzY4LDBjLTUuMjAyLTUuMjAxLTkuNjk0LTExLjY3OC0xMi40ODQtMTguMDRsLTM4LjU0NiwzOC41NDYNCg0KCWM0LjA0OSw2LjE0Miw4LjI2MSwxMS40NTMsMTMuNjY2LDE2Ljg1OGMxMy45NDksMTMuOTUsMzEuNjk4LDI0LjMzOSw1Mi4xMTcsMjkuMjUxYzI2LjQ2Niw2LjM3LDU0LjgyMywyLjgzOSw3OS4xODUtMTAuNTkyDQoNCgljOC44OTItNC45MDMsMTcuMjU0LTExLjExOSwyNC43OTQtMTguNjU5bDczLjg5Ni03My44OTVjNDMuMTA1LTQzLjEwNSw0My4xMDUtMTEyLjk5MSwwLjAwMS0xNTYuMDk3DQoNCglDMzg2LjU2NiwzOS4yMjUsMzE2LjY4LDM5LjIyNSwyNzMuNTc0LDgyLjMzeiIvPg0KPC9nPg0KDQo8L3N2Zz4NCg==)
+}
+
+.mapjs-attachment {
+    right: -5px;
+    position: absolute;
+    top: -15px;
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoJIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0eWxlPSJ0cmFuc2Zvcm06cm90YXRlKDEwKSI+DQogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiwtMikgcm90YXRlKDEwKSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MiI+DQogICA8cGF0aCBkPSJNNCwxNiBMNCwxMSBDNCwxIDE1LDEgMTUsMTEgTDE1LDI2IEMxNSwzMSA4LDMxIDgsMjUgTDgsMTAiDQogICAgIHN0eWxlPSJzdHJva2U6YmxhY2siLz4NCiAgIDxwYXRoIGQ9Ik0zLDE2IEwzLDEwIEMzLDAgMTQsMCAxNCwxMCBMMTQsMjUgQzE0LDMwIDcsMzAgNywyNSBMNywxMCINCiAgICAgc3R5bGU9InN0cm9rZTojMDA5QUNEIi8+DQogIDwvZz4NCjwvc3ZnPg0K);
+    width: 16px;
+    height: 32px;
+    background-size: 16px 32px;
+    background-repeat: no-repeat no-repeat
+}
+
+.mapjs-attachment:hover {
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoJIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0eWxlPSJ0cmFuc2Zvcm06cm90YXRlKDEwKSI+DQogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiwtMikgcm90YXRlKDEwKSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MiI+DQogICA8cGF0aCBkPSJNNCwxNiBMNCwxMSBDNCwxIDE1LDEgMTUsMTEgTDE1LDI2IEMxNSwzMSA4LDMxIDgsMjUgTDgsMTAiDQogICAgIHN0eWxlPSJzdHJva2U6YmxhY2siLz4NCiAgIDxwYXRoIGQ9Ik0zLDE2IEwzLDEwIEMzLDAgMTQsMCAxNCwxMCBMMTQsMjUgQzE0LDMwIDcsMzAgNywyNSBMNywxMCINCiAgICAgc3R5bGU9InN0cm9rZTojMDBCRkZGIi8+DQogIDwvZz4NCjwvc3ZnPg0K)
+}
+
+.mapjs-draw-container {
+    position: absolute;
+    margin: 0;
+    padding: 0
+}
+
+.mapjs-draw-container[data-mapjs-role=connector] {
+    z-index: 1
+}
+
+.mapjs-draw-container[data-mapjs-role=link] {
+    z-index: 2
+}
+
+.mapjs-connector {
+    stroke-width: 1px;
+    fill: none;
+    stroke: #888
+}
+
+.mapjs-link {
+    stroke-width: 1.5px;
+    fill: none
+}
+
+.mapjs-link-hit {
+    stroke: transparent;
+    stroke-width: 15;
+    cursor: crosshair
+}
+
+#container {
+    background-color: #FFF;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    margin: 0;
+    padding: 0
+}
+
+.mapjs-reorder-bounds {
+    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjExcHgiIGhlaWdodD0iMjAuNjM0cHgiPg0KPHBhdGggZD0iTTAuMTk5LDEwLjc5bDkuNjQ2LDkuNjQ2YzAuMTMsMC4xMzMsMC4zMDIsMC4xOTcsMC40NzEsMC4xOTdjMC4xNzMsMCwwLjM0NC0wLjA2NCwwLjQ3My0wLjE5N2wwLjAxMy0wLjAxMQ0KCUMxMC45MjksMjAuMzA1LDExLDIwLjEzMiwxMSwxOS45NTJ2LTQuNjhjMC0wLjE4Mi0wLjA3LTAuMzQ3LTAuMTk4LTAuNDc1TDYuMzIzLDEwLjMybDQuNDc5LTQuNDgzQzEwLjkyOSw1LjcxNCwxMSw1LjU0MywxMSw1LjM2NA0KCVYwLjY4MmMwLTAuMTc2LTAuMDctMC4zNDUtMC4xOTgtMC40NzRsLTAuMDEzLTAuMDEyQzEwLjY2NywwLjA3MSwxMC40OTcsMCwxMC4zMTYsMEMxMC4xNDMsMCw5Ljk3LDAuMDcxLDkuODQ1LDAuMTk2TDAuMTk5LDkuODQ1DQoJQy0wLjA2NiwxMC4xMDQtMC4wNjYsMTAuNTMyLDAuMTk5LDEwLjc5eiIvPg0KPC9zdmc+);
+    background-height: 100%;
+    background-width: 100%;
+    height: 20px;
+    width: 11px;
+    z-index: 999;
+    background-repeat: no-repeat
+}
+
+.mapjs-reorder-bounds[mapjs-edge=left] {
+    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjExcHgiIGhlaWdodD0iMjAuNjM0cHgiPg0KICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTEsIDApIHNjYWxlKC0xLCAxKSI+DQo8cGF0aCBkPSJNMC4xOTksMTAuNzlsOS42NDYsOS42NDZjMC4xMywwLjEzMywwLjMwMiwwLjE5NywwLjQ3MSwwLjE5N2MwLjE3MywwLDAuMzQ0LTAuMDY0LDAuNDczLTAuMTk3bDAuMDEzLTAuMDExDQoJQzEwLjkyOSwyMC4zMDUsMTEsMjAuMTMyLDExLDE5Ljk1MnYtNC42OGMwLTAuMTgyLTAuMDctMC4zNDctMC4xOTgtMC40NzVMNi4zMjMsMTAuMzJsNC40NzktNC40ODNDMTAuOTI5LDUuNzE0LDExLDUuNTQzLDExLDUuMzY0DQoJVjAuNjgyYzAtMC4xNzYtMC4wNy0wLjM0NS0wLjE5OC0wLjQ3NGwtMC4wMTMtMC4wMTJDMTAuNjY3LDAuMDcxLDEwLjQ5NywwLDEwLjMxNiwwQzEwLjE0MywwLDkuOTcsMC4wNzEsOS44NDUsMC4xOTZMMC4xOTksOS44NDUNCglDLTAuMDY2LDEwLjEwNC0wLjA2NiwxMC41MzIsMC4xOTksMTAuNzl6Ii8+DQogIDwvZz4NCjwvc3ZnPg==)
+}
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_default_variables.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_default_variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..16f15194905bda8b1a02378f6617e55e78dca671
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_default_variables.scss
@@ -0,0 +1,6 @@
+$form-input-height: 25px !default;
+$mup-background-menu: white !default;
+$mindmup-node-shadow: 0px 3px 3px 0px rgba(0,0,0,0.3) !default;
+
+$retrace: .75 * $box-padding !default;
+$retrace-mod: (0.25)*$box-padding !default;
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_frame.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_frame.scss
new file mode 100644
index 0000000000000000000000000000000000000000..da89cc8ca1551db6fe5ef5001342bbb3935151ff
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_frame.scss
@@ -0,0 +1,217 @@
+$legend-width: 260px;
+.mindmup {
+  &-cont {
+    margin: 0 (-$retrace) $retrace-mod;
+    width: auto;
+    overflow: hidden;
+    @include respond-to(max-small-screen) {
+      margin-top: -$box-padding;
+    }
+  }
+  &-container {
+    @extend %user-select-none !optional;
+    position: relative;
+    cursor: all-scroll;
+    border-top: none;
+    box-sizing: border-box;
+    background-color: mix($color-background, $color-foreground);
+    margin: 0;
+    padding: 0;
+    outline: none;
+    overflow-y: hidden !important;
+    @extend %pattern-grid-background !optional;
+  }
+  &__legend {
+    &-container {
+      @extend %box-shadow !optional;
+      @extend %material__elevation--depth_2 !optional;
+      background-color: $color-foreground;
+      position: absolute;
+      left: $gap;
+      top: $gap;
+      width: $legend-width;
+      @include respond-to(max-small-screen) {
+        top: 110%;
+      }
+      //@include respond-to(max-small-screen) {
+      //  display: none;
+      //}
+      &--hidden {
+        display: none;
+      }
+    }
+    &-header {
+      background: mix($color-foreground, $color-background);
+      padding: $gap;
+      @extend %flex;
+      & > label {
+        @extend %flex-grow-1;
+      }
+    }
+    &-toggler {
+      a {
+        color: rgba($color-text, .5);
+        font-size: 1.5em;
+        line-height: $form-input-height;
+      }
+      .tip {
+        display: none;
+      }
+      &.active {
+        a {
+          @if map-has-key($icons, 'expand-less') {
+            @include icon-parent('expand-less');
+          }
+        }
+      }
+    }
+  }
+  &-noselect {
+    @extend %user-select-none !optional;
+  }
+  &__menu {
+    @extend %box-sizing-border-box !optional;
+    //@extend %material__elevation--inline !optional;
+    @extend %box-shadow-none !optional;
+    @extend %flex !optional;
+    @extend %justify-content-space-between !optional;
+    user-select: none;
+    position: relative;
+    z-index: 1;
+    background-color: $mup-background-menu;
+    //overflow-x: hidden;
+    //border-top: 1px solid $color-border-minor;
+    border-bottom: 1px solid $color-border-minor;
+
+    //margin: 0 (-$retrace-mod);
+    padding: $box-padding;
+    //@include respond-to(min-medium-screen){
+    //  padding-left: $gap + $box-padding + $legend-width;
+    //}
+    &_addons{
+      position: absolute;
+      right: 0;
+      top: 2 * $box-padding + 31;
+      z-index: 5;
+    }
+    &-item {
+      display: inline-block;
+      text-align: left;
+
+      a.active {
+        //* TODO - WILL BE REMOVED after changes in @menu-tooltip
+        // 035b00e84609610ee12cb93294ea823bd2f130ad
+        // 62002a072a0997a8b275a528c8e4aefa41a48412
+        color: #d94838 !important;
+        &:before {
+          color: #d94838 !important;
+        }
+      }
+
+      @include respond-to(max-medium-screen) {
+        & > a {
+          padding-right: 0;
+          & > span {
+            display: none;
+          }
+        }
+      }
+      @include respond-to(max-xlarge-screen) {
+        & > a {
+          &.easy-mindmup__icon--settings, &.easy-mindmup__icon--display {
+            padding-right: 0;
+            & > span {
+              display: none;
+            }
+          }
+        }
+      }
+    }
+    .right-menu {
+      float: right;
+    }
+    &-group {
+      ul {
+        margin: 0;
+      }
+      &--tooltiped {
+        .easy & {
+          @extend %menu-tooltip !optional;
+        }
+        & > ul {
+          display: none;
+        }
+        &:hover {
+          & > ul {
+            display: block;
+          }
+        }
+      }
+      &--sizing {
+        @extend %flex-grow-1 !optional;
+        text-align: center;
+        font-size: 1.5em;
+        position: absolute;
+        top: $box-padding;
+        left: -$box-padding - 90;
+        line-height: $box-padding;
+        @include respond-to(max-small-screen) {
+          display: none;
+        }
+        a {
+          color: rgba($color-text, .5);
+          text-decoration: none;
+        }
+        li {
+          list-style: none;
+          display: inline-block;
+        }
+      }
+      &-display {
+        @include respond-to(min-small-screen) {
+          margin-left: $gap + $legend-width
+        }
+
+      }
+      .mindmup__legend-container--hidden + &-display {
+        margin-left: 0
+      }
+    }
+    &-save {
+    }
+  }
+  &_hover_menu {
+    display: block;
+    position: absolute;
+    z-index: 99;
+    background-color: white;
+    min-width: 160px;
+    padding: 5px 0;
+    margin: 2px 0 0;
+    border: 1px solid rgba(0, 0, 0, 0.2);
+    @extend %border-radius !optional;
+    @include box-shadow(0 5px 10px rgba(0, 0, 0, 0.2));
+    @extend %background-clip-padding-box !optional;
+  }
+  &-reload-modal-errors li {
+    color: $color-negative;
+  }
+
+  &-last-modal-diffs li {
+    color: $color-important;
+  }
+  &__button {
+    &--disabled {
+      opacity: 0.4;
+    }
+  }
+  &_modal {
+    &__flash_close {
+      position: absolute;
+      right: $gap;
+      font-size: 2*$gap;
+      line-height: 2*$gap;
+    }
+  }
+}
+
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_icons.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_icons.scss
new file mode 100644
index 0000000000000000000000000000000000000000..09a1d230d149446ecd09e3bc2af35616bc95576e
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_icons.scss
@@ -0,0 +1,50 @@
+$mindmup-icons: (
+        add: \e3ba,
+        add_sibling: \e3ba,
+        insert_between: \e3bb,
+        remove: \e92b,
+        rename: \e22b,
+        edit_data: \e880,
+        follow_url: \e0e2,
+        zoom_in: \e8ff,
+        zoom_out:\e900,
+        refresh_view: \e881,
+        filter: \e152,
+        cancel: \e5c9,
+        cancel_filter: \e5c9,
+        close: \e5cd,
+        display: \e417,
+        links: \e157,
+        icons: \e24e,
+        collapse: \e909,
+        expand: \e146,
+        one_side: \e86d,
+        settings: \e8b8,
+        undo: \e166,
+        redo: \e15a,
+        print: \e16b,
+        cut: \e14e,
+        copy: \e14d,
+        paste: \e14f,
+        save: \e161,
+        legend: \e5d2,
+        legend_hide: \e06d,
+);
+
+.easy-mindmup {
+  &__icon {
+    @include icon-parent;
+    &:before {
+      font-family: "Material Icons", sans-serif;
+    }
+    @each $icon, $sign in $mindmup-icons {
+      &--#{$icon}:before {
+        content: unicode($sign);
+      }
+    }
+    &.button {
+      @extend %button-with-icon;
+      padding-left: 3*$gap;
+    }
+  }
+}
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_legend.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_legend.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8a4ac23396469326e37dc07d2149dd33fb9a3b90
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_legend.scss
@@ -0,0 +1,70 @@
+.mindmup{
+  &-legend {
+    @extend %background-clip-padding-box !optional;
+    overflow-y: auto;
+    padding: $gap $box-padding;
+    overflow-x: hidden;
+    &-color-box {
+      //@include mindmup-scheme($color-border-minor);
+      background-color: #E0E0E0;
+      border-color: #E0E0E0;
+      @extend %border-radius-infinite !optional;
+      //&:before{
+      //  content: 'B';
+      //  font-weight: bold;
+      //  display: block;
+      //  text-align: center;
+      //}
+      width: $box-padding;
+      height: $box-padding;
+      display: inline-block;
+      border-width: 1px;
+      border-style: solid;
+      vertical-align: middle;
+      margin-right: 5px;
+    }
+    &-used-toggle{
+      float:right;
+      margin: 5px 0;
+    }
+    &-item{
+      &-cont{
+        cursor: pointer;
+        margin-top: 0.5*$gap;
+        .avatar-container{
+          float: none;
+        }
+      }
+    }
+    .hotkey_link {
+      margin: $gap (-$gap) 0;
+      padding-top: $gap;
+      border-top: 1px solid $color-border-minor;
+      .easy & a{
+        color: rgba($color-text, .5);
+      }
+    }
+    &__filter{
+      &_cont{
+        position: absolute;
+        right: $gap;
+      }
+      &_icon{
+        color: rgba($color-text, .4);;
+        font-size: 2em;
+        padding: 5px;
+      }
+      &_cancel_icon{
+        position: absolute;
+        display: none;
+        color: lighten($color-negative,10);
+        //color: $color-negative;
+        font-size: 2em;
+        padding: 0 5px 5px;
+      }
+      &_cont:hover &_cancel_icon{
+        display: inline;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mapjs.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mapjs.scss
new file mode 100644
index 0000000000000000000000000000000000000000..4cc10c6aebb46a5b84438bde5eac765516ed2005
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mapjs.scss
@@ -0,0 +1,30 @@
+.mapjs{
+  &-connector{
+    stroke: darken($color-border,5%);
+    //stroke-width: 2px;
+    //stroke-dasharray: 2, 2;
+  }
+  &-draw{
+    &-container{
+      pointer-events: none;
+      overflow: visible;
+      &[data-mapjs-role=connector]{
+
+      }
+    }
+  }
+  &-link-hit {
+    pointer-events: all;
+    fill: none;
+  }
+  &-exclamation {
+    right: -.9em;
+    position: absolute;
+    top: -.9em;
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAIWUlEQVR42p2XCWzT1x3Hv3/fTmI7jhPHuUpuxznpAgmES4GGjUFa1LWdWNWBRlXGpHZbN7Wr6DRNazWoqhY2rUDXlkOjbAgqVo5xqEk3Etqy0hyE3OQgd5zDsZP49n+/97e92mlMaZ/0Evkdv/d5v+v9/hzusx1amqXOUMielvD8dhH4YvCBCS7wh+Pg47hmL7hjvQ7nO7sauq33I5f7uvmTy4yZejF3ViaTFiY9WARNWgpiUw0QiSTgXW74PG7wHg/c9nlYBkZgHTNjoncAHq+3Zczt3brtv+09JIf/NgDiqytMbykk4mdyq78HfUkh4PXSsIhNCTcGT3J9fOA/zc3NwmWdgcsyhfHefgy3dcPp499+qL7lZ7TJ+00ApDXlef36vOykgscfofNkdK4UrvYWzLW3wn63Fx7LdED1EAAkGi2UaUsQk50HaXomnKOkjb4eDHbcwYzFNlJZd2sJrXTfD4D0o+XGPtPDG5OTViynm4ng6b+L8Q9PwWebBSRicCLqXPhWniB4n0/QhDg6BvqHNkMcG4upW80Y7x/A6Ih5uLK+JX0hBPeVw8vy7pqqqwxJ5ctIqhTWKxdgvfkZRDKZcDCCBzO1h3piyDjP+8C7PVAXlUBVthqTn32K8bsDGJuYHq2sa3kgFCIUQHyxPO9wmjF7Z+FTT5DFJLBcOIv5thZwUmng8MBKrwc+pxNwOf2/ZXKI5HKSIAlAwA9BzhmdlQvNyjUYr61F/8AQJucd7266fntX0CeCIrl9Bel5K+NiWte8+HNSsxK2miuYbfyCbi4l+4u/xCQ18w471LteQNRjO4Sh+dNHYT38GjiFktaKwtb6CEKVl4+YnAL0Xb6EQYsNn0zN5r/Q0tPOUIMAsqtleY3FP9hs0hcXwzs4DPOZk+DkAbWHNrKxjzzdUNMZNjy6PhcitSYclp3g82sisfK7mB03o7+hEeN2d9uGuualNO0SAB5N1iX/Mit5aPWeX9EOCSaOHAJPKuYk4q+6CYWib2YK+pqusOHx9TkQaeLIDAuAGQTtYSbSVW1B35nTGJx1Yn/PcMqZoYlhJl18alnuS9+pWPaHrI3r4ekbxPTl84LThakzFMA6jYSrbWHD5ioTaUC7KIDgmOQ32uWrMN3Vjf6uO+jx8L994tPWPzIA5aUy483Sx6tN8aZ8zH1cC0dPNzmeZPEoDZgg/uwNgNmcNfKJia1li5ogVAvK5DSINfHouHoFY1JF28ZrzaXsBG1tuXFq9S+egUSlg+Xvf6MU61r8JkHHmrUi9s8nIc4w+pXS2wHLs9sgilEvrrXAPhH5lKZ8HVrf+SsmKHGt+7gxjgEk1JYZx9c+v5sEUOI48japX35PQTylXNWrByEpLBWGPC03YduzGxwloIj7gmbYsBmt+9/EhN7AAPQMQE8AY2uefZoyVzymjx/xxzTHRQagh0f55C7IH/2xMOT84DjsJw6DU0bdG4DMF7uuCrdffwOTKSkMINEPUG4cq9i1AzJdIqwnj4OT3QOAJRiHA/Infwr5w9v8AB+ehPPEIcoDCtoXGYDBq1ZXonnfa5hJz8Da2oYgQN5Yxc4fQaZPge3U++SAsnvehGVAUW4+on73J2Fo/vfPwdfZKmTEyOB+gJiKtWjeuw8zGZkhACtMY8u3bUV0WgbmLv2LjOqODMCa2w2xqRiKF/cKPx37fgNvWzO9JNLIewiAk0gRlV+E5v0HwjSQcKHcVF+ytiwnuWgpPJ0dcI8MRY6CgB8wLfCBt0Aw2b0cN2A6mTYeLg+PtkuXYI5L6NpU17xKCMO/FGe9XFlifD5zZRlkJNtBb4DwsERSJytMXA5/uAoAZDKZ4muhlRlZGLr+CYaGh/GFT/rG7obOV4REVKCOMh0syb65dFMlVBm5cNZfo/wdwQx0E1CalvxwJyQbtwpDnitn4fnHu4AQPYvt4amekUJGL2PrWwcxaUjC7obu0tvWuTYhFVM3XFyR9x9jkSkzpbCQ5Cjgamzw32ihFpj6KQ/Ijl4IG3bt2AxEygNM/elZmGxqQl9TM8wabc+multraWY0KD32+wbt6pdyUs8Zl5cgvqAIGBmBh7ogMBRCALBB+t75cL/8yRYCUC0A8NeLEq0OPjJpy9FjsD6wBHs7Bqovjk7V0QLL/59j6knHS3MPFBp0jywxZiGupBS+3h74pqb8AKGVkNMB0WPbIdqwxc/00Xn4Th8jEyjCYWmtWK0Gp4tH+3tHME1vxx2I//nUjTYqOjCC4HMM/6tD+kPGlYqCmtRkvS4hyYD4BynVjgzDazZ/CRGIZ7hdQjgKjYVfMHeEgIpj6XXUaNB14n3MON2waGMnq/7dtJ5me6nPIqQgQcAXdNQzL1cUnEvVx8XrqKjUl5b6C9/BQeFFC71dWAvREke+w+kT4LXNoevMB5glx5xWx05svNZUTdPsO2ESC0qyYGOZJJ5p4vKqgnNapSIuIUqOuFwj1NnZgN1B3DZKfXbSgid8OwtbBUVBVLTgvJOff47B+uuw6xNhkyunAoezm08gQlG6EGLJ0VLjy1nR8s0amQQqqo6iDYlQpaRBYTBQ3Sjxa4F1dnMKzXmqfKe7OjHT2Q07Pb3zcTr02p0Xtt9of4Xk9S88PBJAEELLwrMyIbbw1zmpr6rFonQl74WU8oOEnBBOl1B6e5kfMAiyv1uphJceJKdKDZuX73u94+6eWrOlhYUb9Wnc54dJqE9EBfwiIVUpT3wuJ6U6XxVdpZKKM0JFBP3O6vL2ttnmrh7oGjw3aHeO0ZA5YO95fMNPs9B5aQCEjAsqeUB1F1gttjDjUGiAnAMzjIX6XOBgdutv9XG6mEZYvqBgx2IFIzuEeSbZB65IN17Y/gfqSW4KhD7b/AAAAABJRU5ErkJggg==);
+    width: 2em;
+    height: 2em;
+    background-size: 2em;
+    background-repeat: no-repeat no-repeat
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mixins.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mixins.scss
new file mode 100644
index 0000000000000000000000000000000000000000..953d642834c798a6e9106bbfe34d82329ec87e68
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_mixins.scss
@@ -0,0 +1,6 @@
+@mixin mindmup-scheme($color){
+  background-color: $color;
+  &.activated {
+    background-color: darken($color, 10%);
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_nodes.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_nodes.scss
new file mode 100644
index 0000000000000000000000000000000000000000..08726a76c09a6a2e005dfe6e2c9a0f2e6a6ee84a
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_nodes.scss
@@ -0,0 +1,130 @@
+.mapjs {
+  &-node {
+    border: 1px solid transparent; // !important;
+    @extend %border-radius-small !optional;
+    //@include mindmup-scheme($color-border-minor);
+    //@include box-shadow(0 0 3px 0px rgba($black, .25));
+    @include box-shadow(none);
+    @include mindmup-scheme($mindmup-default-node-color);
+    color: $color-text;
+    padding: 0.75*$gap 1.5*$gap;
+    margin: 0;
+    &.activated, &.selected, &.droppable {
+      @include box-shadow($mindmup-node-shadow);
+      @extend %material__elevation--depth_4 !optional;
+      margin-left: -2px;
+    }
+    &.activated {
+      border: 1px solid transparent;
+    }
+    &.droppable {
+      border-color: $color-important;
+    }
+    span {
+      word-wrap: break-word;
+    }
+  }
+  &-collapsor {
+    @extend %border-radius-infinite !optional;
+    position: absolute;
+    left: auto;
+    top: 50%;
+    right: -$gap - 2;
+    margin-top: -$gap;
+    background-color: $white;
+    color: $button-negative-background;
+    line-height: 1;
+    font-size: 2*$gap;
+    font-weight: normal;
+
+    font-family: "Material Icons", sans-serif;
+    @extend %material-icon !optional;
+    &:before {
+      content: '\e15c'
+    }
+    .collapsed & {
+      color: $button-positive-background;
+      &:before {
+        content: '\e147'
+      }
+    }
+    .mindmup-node-left & {
+      right: auto;
+      left: -$gap - 2;
+    }
+    .mapjs-node:hover & {
+      font-size: 2.5*$gap;
+      margin-top: -$gap - 2;
+      right: -$gap - 5;
+    }
+    .mindmup-node-left:hover & {
+      left: -$gap - 5;
+      right: auto;
+    }
+  }
+}
+
+.mindmup {
+  &-node {
+    &-filtered {
+      opacity: 0.2;
+    }
+    &-icon {
+      height: 20px;
+      display: inline-block;
+      margin-right: 3px;
+      vertical-align: middle;
+      &-progress {
+        width: 4px;
+        background-color: $color-border;
+        @extend %rotate-90cw;
+        margin: 0 $gap;
+      }
+      &-progress-bar {
+        background-color: $color-positive;
+        position: relative;
+      }
+      &-milestone-shell {
+        border: 1px solid $color-border;
+        @extend %rotate-45cw;
+        vertical-align: middle;
+        display: inline-block;
+        margin: 1px;
+      }
+      &-status {
+        font-weight: normal;
+        opacity: .5;
+        @include small;
+      }
+
+      &s {
+        position: absolute;
+        left: 0;
+        top: -10px;
+        white-space: nowrap;
+        &--with_icons & {
+          top: -18px;
+        }
+      }
+    }
+
+    &-filtered {
+      opacity: 0.2;
+    }
+    &-avatar {
+      position: absolute;
+      top: 50%;
+      margin-top: -11px;
+      .mindmup-node-left & {
+        left: auto;
+        right: -31px;
+      }
+    }
+  }
+}
+
+.mindmup-scheme-project {
+  @include mindmup-scheme($button-main-background);
+  color: $button-main-color;
+}
+
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_print.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_print.scss
new file mode 100644
index 0000000000000000000000000000000000000000..aa8844a173277af2cb1ae5227b5a8dadc06eb819
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_print.scss
@@ -0,0 +1,32 @@
+@media print {
+  .gravatar {
+    max-width: 100%;
+    height: auto;
+    -webkit-border-radius: 5000px;
+    -moz-border-radius: 5000px;
+    border-radius: 5000px;
+  }
+}
+.mindmup-print{
+  &-area{
+    & .mapjs-collapsor{
+      display: none;
+    }
+    & .mindmup-node-add-button{
+      display: none;
+    }
+  }
+  &-strip{
+    position: relative;
+    /*border: 1px solid red;*/
+    border: 1px solid #cecece;
+    overflow: hidden;
+    white-space: nowrap;
+    break-inside: avoid;
+    margin: 10px 0;
+    margin-left: -1px;
+    display: inline-block;
+    background-color: #ffffff;
+    border-left: 0;
+  }
+}
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_progress.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_progress.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e20723542c681b1af228e0dd03f6a4492c64fc5d
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_progress.scss
@@ -0,0 +1,30 @@
+.mindmup-progress {
+  &-modal {
+    position: fixed;
+    z-index: 10000;
+    width: 40%;
+    height: 150px;
+    top: 150px;
+    left: 30%;
+    right: 30%;
+    padding-top: 30px;
+    background-color: $color-foreground;
+    border: 1px solid $color-border;
+    box-shadow: 6px 6px 42px 7px transparentize($color-border, 0.35);
+    h3 {
+      text-align: center;
+    }
+  }
+  &-cont {
+    height: 8px;
+    width: 80%;
+    margin-left: 10%;
+    margin-right: 10%;
+    border: 1px solid $color-border;
+  }
+  &-bar {
+    width: 50%;
+    height: 100%;
+    background-color: green;
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_redmine.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_redmine.scss
new file mode 100644
index 0000000000000000000000000000000000000000..905200fbde879f8a9f09a74fddd41e1b0bf6c13b
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_redmine.scss
@@ -0,0 +1,143 @@
+%icon_before_list_item{
+  position: absolute;
+  left: 0;
+  text-align: center;
+  font-size: 1.2em;
+  line-height: 1;
+  color: inherit;
+  top: 50%;
+  margin-top: -0.5em;
+}
+
+.redmine {
+  .mindmup {
+    &-cont {
+      margin: 0 -10px -10px -10px;
+    }
+    &-menu {
+      padding: 5px 1px 0 1px;
+      .menu-item {
+        padding: 8px 10px 8px 10px;
+      }
+    }
+    &__menu {
+      padding: 14px;
+      &-group {
+        &--tooltiped {
+          & > a {
+            padding-top: 10px;
+            padding-bottom: 10px;
+          }
+          & > ul {
+            position: absolute;
+            background-color: #ffffff;
+            border: 1px solid #dfccaf;
+            color: #42321a;
+            padding: 5px 5px 5px 0;
+            margin-top: 10px;
+            margin-left: -10px;
+            min-width: 150px;
+            li {
+              display: block;
+              padding: 3px;
+              a {
+                display: block;
+                padding-left: 20px;
+                &:before {
+                  position: absolute;
+                  left: 0;
+                  width: 20px;
+                }
+              }
+            }
+          }
+          &:after {
+            display: none;
+          }
+          .icon {
+            background-image: none;
+          }
+        }
+        &--sizing {
+          a {
+            padding: 0;
+          }
+        }
+      }
+      &-item {
+        padding-left: 15px;
+        padding-right: 15px;
+      }
+      &-save {
+        a {
+          padding-left: 30px !important;
+          &:before {
+            text-align: center;
+            width: 36px;
+            position: absolute;
+          }
+        }
+      }
+    }
+    &-legend {
+      margin-top: 1px;
+      &__filter {
+        &_cancel_icon {
+          position: absolute;
+        }
+      }
+    }
+    &__legend {
+      &-container {
+        top: 0;
+      }
+      &-toggler {
+        height: 25px;
+        a {
+          line-height: 1.5em;
+        }
+      }
+      &-header {
+        padding: 8px;
+      }
+    }
+  }
+  a.button-positive {
+    background-color: #4ebf67;
+    border-radius: 2px;
+    border: 1.3px solid rgb(51, 141, 71);
+    color: #ffffff;
+    padding: 8px 16px;
+  }
+  .menu-item.active {
+    background-color: #9DB9D5;
+  }
+  .menu-item.active a.button {
+    color: #fff;
+  }
+  .gravatar {
+    max-width: 100%;
+    height: auto;
+    border-radius: 5000px;
+    overflow: hidden;
+    box-sizing: border-box;
+  }
+  .button-2.active {
+    background-color: transparent !important;
+  }
+  &.mindmup__context_menu{
+    .easy-mindmup__icon:before{
+      @extend %icon_before_list_item
+    }
+  }
+  .button{
+    &.easy-mindmup__icon:before{
+      @extend %icon_before_list_item
+    }
+  }
+  .mindmup_modal__flash_close{
+    color: #880000;
+    right: 15px;
+    line-height: 18px;
+  }
+}
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_schemes.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_schemes.scss
new file mode 100644
index 0000000000000000000000000000000000000000..19601c5b567dbec27dec32a3b918c93bbcd5c2f8
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_schemes.scss
@@ -0,0 +1,12 @@
+.scheme{
+  @each $scheme, $map in $mindmup-schemes {
+    &-by-#{$scheme} &-#{$scheme}{
+      @for $i from 1 through length($map) {
+        $color: nth($map, $i);
+        &-#{$i} {
+          @include mindmup-scheme($color);
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_sidebar.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_sidebar.scss
new file mode 100644
index 0000000000000000000000000000000000000000..61e39eb735ed4f3ce5138acdec92960851579b2e
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_sidebar.scss
@@ -0,0 +1,219 @@
+&-sidebar {
+  %info-block {
+    background-color: #ffffaa;
+    border: 1px solid #bfb23f;
+    padding: 5px;
+    text-align: center;
+  }
+  &__empty-title {
+    @extend %info-block;
+    display: block;
+    margin-top: 10px;
+    box-sizing: border-box;
+  }
+  &__resize {
+    position: fixed;
+    width: 10px;
+    text-align: center;
+    cursor: col-resize;
+    display: block;
+    z-index: 101;
+    background-color: silver;
+    box-sizing: border-box;
+    padding-top: 8px;
+    font-size: 20px;
+    border-radius: 5px;
+  }
+  &__root {
+    display: block;
+  }
+  &__toggler {
+    position: absolute;
+    top: 1px;
+    right: 1px;
+    padding: 0;
+    z-index: 1;
+    &.active {
+      background: #d94838;
+      color: #fff;
+    }
+    &.easy-mindmup__icon {
+      padding-left: 15px;
+      &:before{
+        width: auto;
+      }
+    }
+  }
+  &__container {
+    border-left: 1px solid #dfccaf;
+    background: mix($color-foreground, $color-background);
+    padding-left: 8px;
+    overflow-y: scroll;
+  }
+  &__long-text {
+    position: relative;
+    max-height: 100px;
+    overflow: hidden;
+    min-height:30px;
+    border: 1px solid #dfccaf;
+    &__curtain {
+      height: 100%;
+      position: absolute;
+      width: 100%;
+      top: 0;
+      left: 1px;
+      border-bottom: 1px solid #dfccaf;
+      background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.01) 0%, rgba(255, 255, 255, 0.01) 65%, white 100%);
+      background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.01) 0%, rgba(255, 255, 255, 0.01) 65%, white 100%);
+      background: linear-gradient(to bottom, rgba(255, 255, 255, 0.01) 0%, rgba(255, 255, 255, 0.01) 65%, white 100%);
+      filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#03ffffff', endColorstr='#ffffff', GradientType=0);
+    }
+    &__content{
+      padding: 3px;
+      min-height: 25px;
+      width: 100%;
+      box-sizing: border-box;
+      max-height: 100px;
+    }
+  }
+  &__attribute {
+    display: block;
+    overflow: hidden;
+    margin-top: 3px;
+    position: relative;
+    & .invalid{
+      border: 1px solid red;
+      color: red;
+    }
+    &-form-field {
+      max-width: 200px !important;
+      padding: 1px 1px 1px 5px !important;
+      line-height: normal;
+      float: right;
+      overflow-x: hidden !important;
+    }
+    &-value {
+      max-width: 197px !important;
+      width: 100%;
+      padding: 1px 1px 1px 5px !important;
+      line-height: normal;
+      float: right;
+      margin-top: 6px;
+    }
+    &-label-disabled {
+      color: rgba(0, 0, 0, 0.38);
+    }
+    &-label {
+      display: inline-block;
+      width: 100px;
+      margin-top: 5px;
+    }
+    .spaceholder {
+      display: none;
+    }
+    .top-section {
+      margin-bottom: 0;
+    }
+    &-full-screen-icon {
+      display: block;
+      float: right;
+      cursor: pointer;
+      margin-top: 6px;
+      position: absolute;
+      top: 0;
+      right: 0;
+    }
+    & .icon-edit{
+      position: absolute;
+      top: 0;
+      right: 0;
+      cursor: pointer;
+    }
+  }
+  &__attributes {
+
+  }
+  &__add-comment-button {
+    a {
+      width: 100%;
+      box-sizing: border-box;
+    }
+    margin: 3px 0 12px 0;
+  }
+  &__journal {
+    &-avatar-container {
+      margin-top: 6px
+    }
+    &-header {
+      margin-top: -6px
+    }
+    &-notes {
+      margin-top: 0;
+      background: #ffffff;
+      border: 1px solid #dfccaf;
+      margin-bottom: 0 !important;
+      box-sizing: border-box;
+      padding: 3px;
+      p:last-child {
+        margin-bottom: 0;
+      }
+    }
+    &-full-screen-icon {
+      display: block;
+      float: right;
+      cursor: pointer;
+      margin-top: 25px;
+      position: absolute;
+      top: 0;
+      right: 20px;
+    }
+    &-timestamp {
+      font-weight: bold;
+      font-size: smaller;
+    }
+  }
+  &__coworkers--no_id {
+    @extend %info-block
+  }
+  &__tab {
+    margin: 6px 6px;
+    header {
+      min-height: 36px !important;
+      padding: 0 12px 8px !important;
+    }
+    header.open {
+      min-height: 44px;
+    }
+    header:not(:hover) {
+      color: inherit !important;
+      background-color: unset !important;
+    }
+  }
+  &__input {
+    &__name {
+      color: black;
+      font-family: "Open Sans", sans-serif;
+      width: 100% !important;
+      .input {
+        max-width: none;
+        text-align: center;
+        font-weight: bold;
+        opacity: 1;
+        color: black !important;
+      }
+      .bottom-section {
+        display: none;
+      }
+    }
+  }
+  .gravatar {
+    max-height: 32px;
+    max-width: 32px;
+  }
+  .content-wrapper {
+    margin: 0 12px 8px !important;
+  }
+  .material-tab {
+    padding: 0 !important;
+  }
+}
diff --git a/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_variables.scss b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ad8db5d74202128c6e2a3f1384b2df3f1dd867a8
--- /dev/null
+++ b/plugins/easy_mindmup/assets/stylesheets/sass/_mindmup_variables.scss
@@ -0,0 +1,44 @@
+$mindmup-default-node-color: #e5e5e5;
+$mindmup-default-schemes: (
+        #f9f6a8,
+        #fbe3bd,
+        #f3cccf,
+        #eabdec,
+        #d0b8ec,
+        #c9c9f9,
+        #c9dcff,
+        #d2f4f0,
+        #d2ecc9,
+        #e1ee9e,
+        #dadace,
+        #dbd1c7
+);
+$progress-0: white;
+$progress-1: $color-positive;
+$mindmup-schemes: (
+        priority: (
+                hsl(hue($color-negative), 80%, 90%),
+                hsl(hue($color-negative), 80%, 80%),
+                hsl(hue($color-positive), 80%, 90%),
+                hsl(hue($color-positive), 80%, 80%),
+                hsl(hue($color-main), 80%, 90%),
+                hsl(hue($color-main), 80%, 80%),
+                hsl(hue($color-important), 80%, 90%),
+                hsl(hue($color-important), 80%, 80%),
+                #add7f3, // redmine low
+                #e5e5e5, // redmine normal
+                #fcc,    // redmine high
+                #ffb4b4  // redmine urgent
+        ),
+        status: join((), $mindmup-default-schemes),
+        tracker: join((), $mindmup-default-schemes),
+        assignee: join((), $mindmup-default-schemes),
+        milestone: join((), $mindmup-default-schemes),
+        progress: (
+                $mindmup-default-node-color,
+                mix($progress-1, $progress-0, 10%),
+                mix($progress-1, $progress-0, 25%),
+                mix($progress-1, $progress-0, 45%),
+                mix($progress-1, $progress-0, 70%),
+                $progress-1)
+) !default;
diff --git a/plugins/easy_mindmup/config/locales/cs.yml b/plugins/easy_mindmup/config/locales/cs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cfa1ca6317e193fb20c23ffe9d5b7071897348ad
--- /dev/null
+++ b/plugins/easy_mindmup/config/locales/cs.yml
@@ -0,0 +1,163 @@
+---
+cs:
+  easy_mindmup:
+    button_add_child: Přidat dítě
+    button_add_parent: Přidat rodiče
+    button_add_sibling: Přidat sourozence
+    button_all_icons: Ikony
+    button_collapse: Sbalit
+    button_collapse_all: Sbalit vše
+    button_cut: Vyjmout
+    button_edit_data: Upravit údaje
+    button_expand: Rozbalit
+    button_expand_all: Rozbalit vše
+    button_expand_collapse: Rozbalit/sbalit
+    button_legend: Legenda
+    button_one_side: Jedna strana
+    button_paste: Vložit
+    button_project_menu: Easy MindMup
+    button_redo: Vpřed
+    button_remove_node: Odstranit uzel
+    button_show_links: Zobrazit odkazy
+    button_undo: Zpět
+    edit_issue: Upravit úkol
+    error_create: Nelze vytvořit
+    error_delete: Nelze smazat
+    error_update: Nelze aktualizovat
+    errors:
+      not_subtaskable: Úkol "%{task_name}" nemůže být podúkolem kvůli nastavení jeho
+        trackeru
+    button_hotkeys: Klávesové zkratky
+    hotkeys:
+      info_mac_metakey: Na Mac OS X klávesy Ctrl a Cmd mohou být použity pro zkratky
+        níže - některé prohlížeče mohou určitým klávesovým zkratkám bránit. Například
+        pokud Cmd + Space nefunguje v prohlížeči, zkuste Ctrl + Space.
+      keyboard:
+      - název: Manipulace s uzly
+        hotkeys:
+        - hotkey: Enter
+          info: Přidat sourozence
+        - hotkey: Shift+Enter
+          info: Přidat sourozence nad, nebo zalomení řádku (při přejmenování uzlu)
+        - hotkey: Tab nebo Insert
+          info: Přidat dítě
+        - hotkey: Shift+Tab
+          info: Přidat rodiče
+        - hotkey: Mezerník
+          info: Přejmenovat uzel
+        - hotkey: Shift+mezerník
+          info: Upravit údaje uzlu
+        - hotkey: Backspace nebo Delete
+          info: Odstranit uzel
+        - hotkey: Ctrl+nahoru/dolů
+          info: Posunout uzel nahoru/dolů
+      - název: "Úpravy"
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Uložit
+        - hotkey: Ctrl+X nebo C
+          info: Vyjmout
+        - hotkey: Ctrl+C nebo Y
+          info: Kopírovat
+        - hotkey: Ctrl+V nebo P
+          info: Vložit
+        - hotkey: U nebo Ctrl+Z
+          info: Zpět
+        - hotkey: R nebo Ctrl+Y nebo Ctrl+Shift+Z
+          info: Vpřed
+      - název: Vybrat
+        hotkeys:
+        - hotkey: "Å¡ipky"
+          info: Vyberte uzel nahoře/dole/vlevo/vpravo nebo aktuálně vybraný
+        - hotkey: Shift+Å¡ipky
+          info: Přidat uzel nahoru/dolů/doleva/doprava k výběru (užitečné pro vícenásobné
+            označení sourozenců)
+        - hotkey: "{"
+          info: Vícenásobný výběr aktuálního uzlu a celého podstromu pod ním
+        - hotkey: "["
+          info: Vícenásobný výběr pouze podstromu pod aktuálním uzlem (nikoli uzel
+            sám)
+        - hotkey: "="
+          info: Vícenásobný výběr všech sourozenců aktuálního uzlu (které mají stejného
+            rodiče)
+        - hotkey: "."
+          info: Zrušit vícenásobný výběr a vybrat znovu pouze aktuální uzel
+        - hotkey: 1 - 9
+          info: Vybrat všechny uzly na určité úrovni (např. 1 vybere všechny uzly
+            první úrovně)
+      - název: Navigace a obrazovka
+        hotkeys:
+        - hotkey: "/ nebo F"
+          info: Rozbalit nebo sbalit uzel (složit nebo rozložit děti)
+        - hotkey: Ctrl + nebo Z
+          info: Přiblížit
+        - hotkey: Ctrl - nebo Shift Z
+          info: Oddálit
+        - hotkey: Esc, 0, Ctrl+0
+          info: Resetovat zobrazení mapy - vyberte kořenový uzel a přetáhněte jej
+            do středu obrazovky
+      mouse:
+      - action: Přesunutí mapy
+        gesture: klikněte a přetáhněte středový uzel, klikněte a přetáhněte pozadí nebo
+          skrolujte na trackpadu/touchpadu.
+      - action: Vybrat uzel
+        gesture: klikněte nebo klepněte na něj
+      - action: Vybrat více uzlů
+        gesture: Shift+klik
+      - action: Změna pořadí uzlů
+        gesture: přetáhněte uzel mezi svými sourozenci, horizontálně blízko k místu
+          změny pořadí. Při změně pořadí se zobrazí bod s černou šipkou.
+      - action: Ručně umístit uzel
+        gesture: stačí přetáhnout uzel, dokud šipka pro změnu pořadí nezmizí. Chcete-li
+          vynutit ruční postavení, i když se šipka pro změnu pořadí ukazuje, podržte
+          klávesu Shift při tažení. Upozorňujeme, že děti z kořenového uzlu je možné
+          přetáhnout v libovolném směru, ale uzly nižší úrovně mohou být umístěny
+          pouze ve směru svého rodiče ke kořeni.
+      - action: Přejmenování uzlu
+        gesture: Dvojitý klik nebo dvojité ťuknutí
+      - action: Zobrazit kontextové menu s operacemi
+        gesture: 'Klikněte pravým tlačítkem na uzel (myší) nebo poklepejte na pozadí,
+          nebo dlouze stiskněte uzel: zobrazit (na dotykových zařízeních)'
+      - action: Změna rodičovského uzlu
+        gesture: přetáhněte uzel na jiný uzel (kruhové upuštění není povoleno, takže
+          nelze upustit uzel na jednom ze svých dětí nebo potomků)
+      - action: Otevřít úkol nebo projekt v samostatném okně
+        gesture: Alt+klik
+      title_key_shortcuts: Klávesové operace
+      title_mouse_shortcuts: Operace s myší a dotykem
+      title_shortcuts: Zkratky
+    info_all_saved: Vše úspěšně uloženo
+    info_any_failed: 'Některé z neúspěšných požadavků:'
+    info_no_permission: Nemáte potřebná oprávnění k této akci
+    warning_delete_node: Opravdu chcete smazat uzel {{name}} a všechny jeho poduzly?
+    warning_delete_nodes: Opravdu chcete smazat tyto uzly a všechny jejich poduzly?
+    label_color_by: Barva dle
+    label_go_to: Jít do
+    label_or: nebo
+    last_state_modal:
+      label_differencies: Rozdíly na serveru
+      message_changed: mají rozdílné atributy (browser => server) ({{changes}})
+      message_missing: nemá nadřazený úkol "{{from}}"
+      message_moved: je podúkolem "{{to}}", ne "{{from}}"
+      message_present: je přítomen jako podúkol "{{to}}"
+      text_reload_appeal: Přejete si znovu načíst stav ze serveru?
+      title: Poslední klientský stav Myšlenkové mapy je odlišný od stavu serveru
+    reload_modal:
+      label_errors: Chyby
+      text_reload_appeal: Chcete ignorovat neuložené položky a znovu načíst data ze
+        serveru?
+      title: Myšlenkovou mapu se nepodařilo správně uložit
+    stored_modal:
+      text_load_appeal: Chcete jej načíst místo aktuálního stavu ze serveru?
+      title: Byl nalezen neuložený stav
+      button_local: Neuložená verze
+      button_server: Serverová verze
+    save_info:
+      loaded: Staženo
+      saved: Uloženo
+      autosaved: Automaticky uloženo
+      at: v
+    label_save_progress: Prosím počkejte, probíhá ukládání
+    warning_not_saved: není správně uloženo
+    title_node_changed: Tento uzel byl změněn a bude uložen
+    label_less: Méně
diff --git a/plugins/easy_mindmup/config/locales/en.yml b/plugins/easy_mindmup/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5149d56dfbe62404a2c0cf0e9ee34cdd57082ca
--- /dev/null
+++ b/plugins/easy_mindmup/config/locales/en.yml
@@ -0,0 +1,171 @@
+---
+en:
+  easy_mindmup:
+    button_add_child: Add child
+    button_add_parent: Add parent
+    button_add_sibling: Add sibling
+    button_all_icons: Icons
+    button_collapse: Collapse
+    button_collapse_all: Collapse all
+    button_cut: Cut
+    button_edit_data: Edit data
+    button_expand: Expand
+    button_expand_all: Expand all
+    button_expand_collapse: Expand/Collapse
+    button_legend: Legend
+    button_one_side: One side
+    button_paste: Paste
+    button_project_menu: Easy MindMup
+    button_redo: Redo
+    button_remove_node: Remove node
+    button_show_links: Show links
+    button_undo: Undo
+    error_create: could not be created
+    error_delete: could not be deleted
+    error_update: could not be updated
+    errors:
+      not_subtaskable: Task "%{task_name}" cannot be subtask because of the setting
+        of its tracker
+      no_rest_api: REST API is disabled. Please enable it on Administration.
+    free:
+      button_upgrade: Get Full version
+      feature_coloring: Coloring nodes by property
+      feature_context_menu: Changing node's properties by context menu
+      feature_dnd_property: Changing node's properties by drag & drop
+      feature_filtering: Filtering nodes by property
+      header_not_available: Available only in Full version
+    button_hotkeys: Hotkeys
+    hotkeys:
+      info_mac_metakey: On Mac OSX, Ctrl and Cmd keys can be used for shortcuts marked
+        Ctrl below - some browsers prevent certain key bindings. So, for example,
+        if Cmd+Space does not work in your browser, try Ctrl+Space.
+      keyboard:
+      - title: Node manipulation
+        hotkeys:
+        - hotkey: Enter
+          info: Add Sibling
+        - hotkey: Shift+Enter
+          info: Add Sibling above or line break (when renaming node)
+        - hotkey: Tab or Insert
+          info: Add child
+        - hotkey: Shift+Tab
+          info: Insert parent
+        - hotkey: Space
+          info: Rename node
+        - hotkey: Shift+Space
+          info: Edit node data
+        - hotkey: Backspace or Delete
+          info: Remove node
+        - hotkey: Ctrl+Up/Down
+          info: Move node up/down
+      - title: Editing
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Save
+        - hotkey: Ctrl+X or C
+          info: Cut
+        - hotkey: Ctrl+C or Y
+          info: Copy
+        - hotkey: Ctrl+V or P
+          info: Paste
+        - hotkey: U or Ctrl+Z
+          info: Undo
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: Redo
+      - title: Selection
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: Select the node up/down/left/right of the currently selected one
+        - hotkey: Shift + Arrow keys
+          info: Add node up/down/left/right to selection (useful to multi-select siblings)
+        - hotkey: "{"
+          info: Multi-select the current node and the entire subtree under it
+        - hotkey: "["
+          info: Multi-select only the subtree under the current node (not the node
+            itself)
+        - hotkey: "="
+          info: Multi-select all the siblings of the current node (that have the same
+            parent)
+        - hotkey: "."
+          info: Cancel multi-selection and select only the current node again
+        - hotkey: 1 - 9
+          info: Select all nodes of a particular level (eg 1 selects all first level
+            nodes)
+      - title: Navigation and screen
+        hotkeys:
+        - hotkey: "/ or F"
+          info: Expand or collapse node (fold or unfold children)
+        - hotkey: Ctrl + or Z
+          info: Zoom in
+        - hotkey: Ctrl - or Shift Z
+          info: Zoom out
+        - hotkey: Esc, 0, Ctrl+0
+          info: Reset map view - select root node and bring it to the center of the
+            screen
+      mouse:
+      - action: Move the map
+        gesture: click and drag the center node, click and drag the background or
+          scroll with trackpad/touchpad.
+      - action: Select a node
+        gesture: tap or click on it
+      - action: Select multiple nodes
+        gesture: Shift+click
+      - action: Reorder nodes
+        gesture: drag a node between its siblings, horizontally close to the position
+          of reordering. A black arrow point will show when reordering.
+      - action: Manually position a node
+        gesture: just drag a node until the arrow point for reordering isn't showing.
+          To force manual position even when the reorder arrow point is showing, hold
+          Shift while dragging. Please note that children of the root node can be
+          pulled in any direction, but lower level nodes can only be positioned in
+          the direction of its parent relative to the root.
+      - action: Rename a node
+        gesture: Double-click or double-tap it
+      - action: Show context menu with operations
+        gesture: 'Right click on a node (by mouse) or double-tap the background, or
+          long press a node: show (on touch devices)'
+      - action: Change node parent
+        gesture: drag and drop a node on another node (circular drops are not allowed,
+          so you can't drop a node on one of it's children or descendants)
+      - action: Open issue or project in separate window
+        gesture: Alt+click
+      title_key_shortcuts: Keyboard operations
+      title_mouse_shortcuts: Mouse and touch operations
+      title_shortcuts: Shortcuts
+    info_all_saved: Everything saved successfully
+    info_any_failed: 'Some of the requests failed:'
+    info_no_permission: You do not have necessary permissions to do that
+    warning_delete_node: Do you really want to delete node {{name}} and every of its descendants?
+    warning_delete_nodes: Do you really want to delete these nodes and every of their descendants?
+    label_color_by: Color by
+    label_go_to: Go to
+    label_hide_unused: Hide unused
+    label_or: or
+    last_state_modal:
+      label_differencies: Differencies on server
+      message_changed: have different attributes (browser => server) ({{changes}})
+      message_missing: is missing from parent "{{from}}"
+      message_moved: is child of "{{to}}", not "{{from}}"
+      message_present: is present as child of "{{to}}"
+      text_reload_appeal: Do you want reload state from server?
+      title: Last client state of Mind map is different from server state
+    reload_modal:
+      label_errors: Errors
+      text_reload_appeal: Do you want to ignore unsaved items and reload data from
+        server?
+      title: Mind map failed to save properly
+    stored_modal:
+      text_load_appeal: Do you want to loaded it? Or you want to load fresh state from the server?
+      title: Unsaved local version have been found
+      button_local: Local version
+      button_server: Server version
+    save_info:
+      loaded: Loaded
+      saved: Saved
+      autosaved: Autosaved
+      at: at
+    label_save_progress: Please wait, all entities are being saved
+    label_show_unused: Show unused
+    warning_not_saved: is not properly saved
+    title_node_changed: This node have been changed and will be saved
+    label_less: Less
diff --git a/plugins/easy_mindmup/config/locales/ja.yml b/plugins/easy_mindmup/config/locales/ja.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2f9986150b1f728663cedc5559a45c395ab0b4ec
--- /dev/null
+++ b/plugins/easy_mindmup/config/locales/ja.yml
@@ -0,0 +1,155 @@
+---
+ja:
+  easy_mindmup:
+    button_add_child: 子ノードの追加
+    button_add_parent: 親ノードの追加
+    button_add_sibling: 兄弟ノードの追加
+    button_all_icons: アイコン
+    button_collapse: フォルダツリー折りたたみ
+    button_collapse_all: すべて折りたたみ
+    button_cut: 切り取り
+    button_edit_data: データ編集
+    button_expand: フォルダツリー展開
+    button_expand_all: すべて展開する
+    button_expand_collapse: 展開/折りたたみ
+    button_hotkeys: ホットキー
+    button_legend: 凡例
+    button_one_side: 片側
+    button_paste: 貼り付け
+    button_project_menu: Easy MindMup
+    button_redo: 元に戻す
+    button_remove_node: ノードを取り除く
+    button_show_links: リンクを表示する
+    button_undo: 取り消し
+    error_create: 作成できませんでした
+    error_delete: 削除できませんでした
+    error_update: 更新できませんでした
+    errors:
+      no_rest_api: REST APIが無効になっています。管理でこれを有効にしてください。
+      not_subtaskable: チケット種別の設定のせいで、タスク “%{task_name}”をサブタスクできませんでした
+    free:
+      button_upgrade: 完全版を取得する
+      feature_coloring: プロパティによるノードの色付け
+      feature_context_menu: コンテキスト・メニューによるノード・プロパティの変更
+      feature_dnd_property: ドラッグ・アンド・ドロップによるノード・プロパティの変更
+      feature_filtering: プロパティによるノードのフィルタリング
+      header_not_available: 完全版でのみ提供可能
+    hotkeys:
+      info_mac_metakey: Mac OS Xでは、下記Ctrlと記載されたショートカットは、Ctrl + Cmdキーとなります。一部のブラウザーは、一部のキーバインドを使用することができません。よって、例えば、お客様のブラウザーでCmd + Spaceが機能しない場合は、Ctrl + Spaceを試してみてください。
+      keyboard:
+      - title: ノード操作
+        hotkeys:
+        - hotkey: Enter
+          info: 兄弟ノードの追加
+        - hotkey: Shift + Enter
+          info: 上記の兄弟ノード追加または改行(ノードの名前を変更する場合)
+        - hotkey: Tabまたはインサート・キー
+          info: 子ノードの追加
+        - hotkey: Shift + Tab
+          info: 親ノードの挿入
+        - hotkey: Space  
+          info: ノードのリネーム
+        - hotkey: Shift + Space
+          info: ノード・データの編集
+        - hotkey: バックスペースまたは削除
+          info: ノードを取り除く
+        - hotkey: Ctrl + 上/下キー
+          info: 上/下キーでノードの移動
+      - title: 編集
+        hotkeys:
+        - hotkey: Ctrl + S
+          info: 保存
+        - hotkey: Ctrl + X またはC
+          info: 切り取り
+        - hotkey: Ctrl + CまたはY
+          info: コピー
+        - hotkey: Ctrl + VまたはP
+          info: 貼り付け
+        - hotkey: UまたはCtrl+Z
+          info: 取り消し
+        - hotkey: RまたはCtrl+YまたはCtrl+Shift+Z
+          info: 元に戻す
+      - title: 選択
+        hotkeys:
+        - hotkey: 矢印キー
+          info: 現在選択しているノードの上/下/左/右を選択する
+        - hotkey: Shift + 矢印キー
+          info: 選択しているノードに上/下/左/右のノードを追加する(兄弟ノード複数選択には便利です)
+        - hotkey: "{"
+          info: カレント・ノードとその下のサブツリー全部の選択
+        - hotkey: "["
+          info: カレント・ノードの下のサブツリーのみ選択(そのノードそのものではなく)
+        - hotkey: "="
+          info: カレント・ノードの兄弟ノード全部を複数選択(同じ親を持つもの)
+        - hotkey: .
+          info: 複数選択を解除し、再びカレント・ノードのみ選択する
+        - hotkey: 1 - 9
+          info: 特定の改装のノードすべてを選択する(例:1で第一階層すべてを選ぶ)
+      - title: ナビゲーションと画面
+        hotkeys:
+        - hotkey: /またはF
+          info: ノードの展開または折りたたみ(子ノードを含める、または含めない)
+        - hotkey: Ctrl + またはZ
+          info: ズームイン
+        - hotkey: Ctrl − またはShift Z
+          info: ズームアウト
+        - hotkey: Esc、0、Ctrl + 0
+          info: "マップ表示のリセット - ルート・ノードを選択し、画面の中央に移動します"
+      mouse:
+      - action: マップを移動する
+        gesture: センター・ノードのクリック・アンド・ドラッグする、背景のクリック・アンド・ドラッグ、またはトラックパッド/タッチパッドでスクロールする
+      - action: ノードの選択
+        gesture: ノードをタップまたはクリックする
+      - action: 複数のノードの選択
+        gesture: Shift + クリック
+      - action: ノードの並べ替え
+        gesture: ノードをドラッグし、兄弟ノード間で水平に並べ替えに近い位置に移動する並べ替えでは、黒いアローポイントが表示される
+      - action: 手動でノードの位置を決める
+        gesture: ノードを並べ替えのためのアローポイントが消えるところ迄ドラッグする並べ替えアローポイントが表示されている場合に手動で位置を強制するには、ドラッグ中にShiftキーを押し続けるルート・ノードの子ノードはどの方向にもドラッグすることが可能ですが、それより下の階層のノードは、ルートに対して、それらの親ノードの方向にのみ配置することが可能だということに留意ください。
+      - action: ノード・リネーム
+        gesture: ノードをダブルクリックまたはダブルタップする
+      - action: 操作のコンテキスト・メニューの表示
+        gesture: ノードを右クリック(マウスで)、または背景をダブルタップ、またはノードを押し続けて、表示する(」タッチスクリーン上で)
+      - action: 親ノードの変更
+        gesture: ノードを他のノードの上にドラッグ・アンド・ドロップする(無限循環となるドロップはできません。したがって、子ノードまたは子孫ノード上にノードをドロップすることはできません)
+      - action: 課題やプロジェクトを別ウィンドウで開く
+        gesture: Alt + クリックする
+      title_key_shortcuts: キーボード操作
+      title_mouse_shortcuts: マウスとタッチによる操作
+      title_shortcuts: ショートカット
+    info_all_saved: すべての保存に成功しました
+    info_any_failed: リクエストの一部に失敗しました
+    info_no_permission: これを行うための必要な権限を持っていません
+    label_color_by: による色付け
+    label_go_to: に進む
+    label_hide_unused: 未使用のものを隠す
+    label_less: 未満
+    label_or: または
+    label_save_progress: しばらくお待ちください。全エンティティーの保存中です
+    label_show_unused: 未使用の項目を表示
+    last_state_modal:
+      label_differencies: サーバー上の相違は、
+      message_changed: "異なる属性を持つ(ブラウザー =>サーバー({{changes}})"
+      message_missing: "親から欠落しています “{{from}}"
+      message_moved: "の子供です “{{to}}”、ではない “{{from}}”"
+      message_present: "の子供としてあります “{{to}}”"
+      text_reload_appeal: サーバーからステートを再読込しますか?
+      title: Mind mapの最新のクライアント・ステートがサーバー・ステートと異なっています。
+    reload_modal:
+      label_errors: エラー
+      text_reload_appeal: 保存されていない項目を無視して、サーバーからデータの再読込をしますか?
+      title: Mind mapが正しく保存されませんでした
+    save_info:
+      at: に
+      autosaved: 自動保存
+      loaded: ロード終了
+      saved: 保存終了
+    stored_modal:
+      button_local: ローカルバージョン
+      button_server: サーバーバージョン
+      text_load_appeal: ロードしますか?それとも、サーバーから新しいステートをロードしますか?
+      title: 保存されていないロカールバージョンが見つかりました
+    title_node_changed: このノードが変更され、保存されます。
+    warning_delete_node: ノード{{name}}とそのすべての子孫を削除しますか?
+    warning_delete_nodes: これらのノードとそのすべての子孫を削除しますか?
+    warning_not_saved: は正しく保存されていません
\ No newline at end of file
diff --git a/plugins/easy_mindmup/config/routes.rb b/plugins/easy_mindmup/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..69ec9c2ef3cd5fb94fb0bbb1667f480fb549b2fd
--- /dev/null
+++ b/plugins/easy_mindmup/config/routes.rb
@@ -0,0 +1,3 @@
+resources :projects do
+  put 'easy_mindmup_update_layout', to: 'easy_mindmup#update_layout', as: 'easy_mindmup_update_layout'
+end
diff --git a/plugins/easy_mindmup/init.rb b/plugins/easy_mindmup/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f9ba9b34654e96b5d14a82da7be4d95806b60dad
--- /dev/null
+++ b/plugins/easy_mindmup/init.rb
@@ -0,0 +1,17 @@
+Redmine::Plugin.register :easy_mindmup do
+  name 'Easy MindMup plugin'
+  author 'Easy Software Ltd'
+  description 'Mind map view based on MindMup'
+  version '1.4'
+  url 'www.easyredmine.com'
+  author_url 'www.easysoftware.cz'
+
+  if Redmine::Plugin.installed?(:easy_extensions)
+    visible false
+    should_be_disabled false
+  end
+end
+
+unless Redmine::Plugin.installed?(:easy_extensions)
+  require_relative 'after_init'
+end
diff --git a/plugins/easy_mindmup/lib/easy_mindmup/easy_mindmup.rb b/plugins/easy_mindmup/lib/easy_mindmup/easy_mindmup.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17f7c5371d772705569e3cf84205b4d8f302f77a
--- /dev/null
+++ b/plugins/easy_mindmup/lib/easy_mindmup/easy_mindmup.rb
@@ -0,0 +1,13 @@
+module EasyMindmup
+
+  def self.easy_extensions?
+    Redmine::Plugin.installed?(:easy_extensions)
+  end
+
+  def self.combine_by_pipeline?(params)
+    return false unless easy_extensions?
+    return params[:combine_by_pipeline].to_s.to_boolean if params.key?(:combine_by_pipeline)
+    Rails.env.production?
+  end
+
+end
diff --git a/plugins/easy_mindmup/spec/controllers/easy_wbs_controller_spec.rb b/plugins/easy_mindmup/spec/controllers/easy_wbs_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e09343cd80da3e04092d2765ce420877bf1e9e48
--- /dev/null
+++ b/plugins/easy_mindmup/spec/controllers/easy_wbs_controller_spec.rb
@@ -0,0 +1,19 @@
+# require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+# RSpec.describe EasyWbsController, logged: :admin do
+
+#   around(:each) do |example|
+#     with_settings(rest_api_enabled: 1) { example.run }
+#   end
+
+#   context 'rest api' do
+#     let(:project) { FactoryGirl.create(:project, add_modules: ['easy_wbs']) }
+
+#     it 'enabled' do
+#       get :index, project_id: project
+#       expect(response).to be_success
+#     end
+#   end
+
+# end
+
diff --git a/plugins/easy_mindmup/spec/features/wbs_tree_spec.rb b/plugins/easy_mindmup/spec/features/wbs_tree_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9e95f9696920d552df11711bf2f7aae2140856e8
--- /dev/null
+++ b/plugins/easy_mindmup/spec/features/wbs_tree_spec.rb
@@ -0,0 +1,74 @@
+# require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+# RSpec.feature 'tree', logged: :admin, js: true do
+#   let(:superproject) {
+#     FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 3)
+#   }
+#   let(:subproject) {
+#     FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 0,parent_id:superproject.id)
+#   }
+#   let(:project_issues) {
+#     FactoryGirl.create_list(:issue, 3, :project_id => superproject.id)
+#   }
+#   let(:subproject_issues) {
+#     FactoryGirl.create_list(:issue, 3, :project_id => subproject.id)
+#   }
+#   let(:sub_issues) {
+#     FactoryGirl.create_list(:issue, 3, :parent_issue_id => subproject_issues[0].id, :project_id => subproject.id)
+#   }
+#   let(:sub_sub_issues) {
+#     FactoryGirl.create_list(:issue, 3, :parent_issue_id => sub_issues[0].id, :project_id => subproject.id)
+#   }
+
+#   around(:each) do |example|
+#     with_settings(rest_api_enabled: 1) { example.run }
+#   end
+#   it 'should show project items in correct tree' do
+#     superproject
+#     project_issues
+#     subproject_issues
+#     sub_issues
+#     sub_sub_issues
+#     visit project_easy_wbs_index_path(superproject)
+#     wait_for_ajax
+
+#     expect(page).to have_css('#container')
+#     container=page.find('#container')
+#     #puts evaluate_script('ysy.loader.sourceData;').to_json
+#     expect(container).to have_text(superproject.name)
+#     project_issues.each do |issue|
+#       expect(container).to have_text(issue.subject)
+#     end
+#     expect(container).to have_text(subproject.name)
+#     subproject_issues.each do |issue|
+#       expect(container).not_to have_text(issue.subject)
+#     end
+
+#     node = container.find('span', text: subproject.name).find(:xpath, '..')
+#     node.find('.mapjs-collapsor').click
+#     subproject_issues.each do |issue|
+#       expect(container).to have_text(issue.subject)
+#     end
+#     sub_issues.each do |issue|
+#       expect(container).not_to have_text(issue.subject)
+#     end
+
+#     sleep(0.5)
+#     node = container.find('span', text: subproject_issues[0].subject).find(:xpath, '..')
+#     node.find('.mapjs-collapsor').click
+#     sub_issues.each do |issue|
+#       expect(container).to have_text(issue.subject)
+#     end
+#     sub_sub_issues.each do |issue|
+#       expect(container).not_to have_text(issue.subject)
+#     end
+
+#     sleep(0.5)
+#     node = container.find('span', text: sub_issues[0].subject).find(:xpath, '..')
+#     execute_script("ysy.mapjs.getModel().selectNode(#{node[:id].split('_')[1]})")
+#     node.find('.mapjs-collapsor').click
+#     sub_sub_issues.each do |issue|
+#       expect(container).to have_text(issue.subject)
+#     end
+#   end
+# end
diff --git a/plugins/easy_wbs/README.md b/plugins/easy_wbs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..764745304b6c970aa41b73dd9a63c6654d6b00e1
--- /dev/null
+++ b/plugins/easy_wbs/README.md
@@ -0,0 +1,3 @@
+# Easy WBS
+
+For documentation and requirements, go to https://www.easyredmine.com/redmine-wbs-plugin
diff --git a/plugins/easy_wbs/after_init.rb b/plugins/easy_wbs/after_init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..47d281f9526e98de51a2a5a98040d10ee67b7a85
--- /dev/null
+++ b/plugins/easy_wbs/after_init.rb
@@ -0,0 +1,33 @@
+app_dir = File.join(File.dirname(__FILE__), 'app')
+lib_dir = File.join(File.dirname(__FILE__), 'lib', 'easy_wbs')
+
+# Redmine patches
+patch_path = File.join(lib_dir, 'redmine_patch', '**', '*.rb')
+Dir.glob(patch_path).each do |file|
+  require file
+end
+
+if Redmine::Plugin.installed?(:easy_extensions)
+  ActiveSupport::Dependencies.autoload_paths << File.join(app_dir, 'models', 'easy_queries')
+  EasyQuery.register(EasyWbsEasyIssueQuery)
+end
+
+Redmine::MenuManager.map :project_menu do |menu|
+  menu.push(:easy_wbs, { controller: 'easy_wbs', action: 'index'},
+    param: :project_id,
+    caption: :'easy_wbs.button_project_menu')
+end
+
+Redmine::AccessControl.map do |map|
+  map.project_module :easy_wbs do |pmap|
+    pmap.permission :view_easy_wbs, { easy_wbs: [:index] }, read: true
+    pmap.permission :edit_easy_wbs, { easy_wbs: [:create, :update, :destroy, :update_layout] }
+  end
+end
+
+ActionDispatch::Reloader.to_prepare do
+  require 'easy_wbs/hooks'
+  require 'easy_wbs/easy_wbs'
+
+  RedmineExtensions::EasySettingPresenter.boolean_keys << :easy_wbs_no_sidebar
+end
diff --git a/plugins/easy_wbs/app/controllers/easy_wbs_controller.rb b/plugins/easy_wbs/app/controllers/easy_wbs_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48479a055205bd8ba50cf8a1bf19f89106cf612e
--- /dev/null
+++ b/plugins/easy_wbs/app/controllers/easy_wbs_controller.rb
@@ -0,0 +1,108 @@
+class EasyWbsController < ApplicationController
+  accept_api_auth :index, :update_layout
+  menu_item :easy_wbs
+
+  before_action :check_rest_api_enabled, only: [:index, :update_layout]
+  before_action :find_project_by_project_id, if: proc { params[:project_id].present? }
+  before_action :authorize, if: proc { @project.present? }
+  before_action :authorize_global, if: proc { @project.nil? }
+
+  helper :easy_mindmup
+
+  include_query_helpers
+
+  def index
+    retrieve_query
+
+    respond_to do |format|
+      format.html { render(layout: !request.xhr?) }
+      format.api do
+        load_issues
+        load_projects
+        load_trackers
+        load_users
+        load_versions
+        load_relations
+      end
+    end
+  end
+
+  private
+
+    def check_rest_api_enabled
+      if Setting.rest_api_enabled != '1'
+        render_error message: l('easy_mindmup.errors.no_rest_api')
+        return false
+      end
+    end
+
+    def query_class
+      easy_extensions? ? EasyWbsEasyIssueQuery : EasyWbs::IssueQuery
+    end
+
+    def retrieve_query
+      if params[:query_id].present?
+        cond  = 'project_id IS NULL'
+
+        if @project
+          cond << " OR project_id = #{@project.id}"
+
+          # In Easy Project query can be defined for subprojects
+          if !@project.root? && EasyWbs.easy_extensions?
+            ancestors = @project.ancestors.select(:id).to_sql
+            cond << " OR (is_for_subprojects = #{Project.connection.quoted_true} AND project_id IN (#{ancestors}))"
+          end
+        end
+
+        @query = query_class.where(cond).find_by(id: params[:query_id])
+        raise ActiveRecord::RecordNotFound if @query.nil?
+        raise Unauthorized unless @query.visible?
+
+        @query.project = @project
+        sort_clear
+      else
+        @query = query_class.new(name: '_')
+        @query.project = @project
+        @query.from_params(params)
+      end
+    end
+
+    def load_issues
+      @issues = @query.entities(order: "#{Issue.table_name}.id")
+      @issue_ids = @issues.map(&:id)
+
+      if @issues.blank?
+        return
+      end
+
+      # All ancestors conditions
+      tree_conditions = []
+      @issues.each do |issue|
+        tree_conditions << "(root_id = #{issue.root_id} AND lft < #{issue.lft} AND rgt > #{issue.rgt})"
+      end
+      tree_conditions = tree_conditions.join(' OR ')
+
+      @missing_parent_issues = Issue.where(tree_conditions).where.not(id: @issue_ids)
+    end
+
+    def load_projects
+      @projects = @project.self_and_descendants.where.not(status: [Project::STATUS_CLOSED, Project::STATUS_ARCHIVED])
+    end
+
+    def load_trackers
+      @trackers = Setting.display_subprojects_issues? ? @project.rolled_up_trackers : @project.trackers
+    end
+
+    def load_users
+      @users = @project.assignable_users_including_all_subprojects
+    end
+
+    def load_versions
+      @versions = @projects.flat_map(&:shared_versions).select(&:open?).uniq
+    end
+
+    def load_relations
+      @relations = IssueRelation.where(issue_from_id: @issue_ids, issue_to_id: @issue_ids)
+    end
+
+end
diff --git a/plugins/easy_wbs/app/helpers/easy_wbs_helper.rb b/plugins/easy_wbs/app/helpers/easy_wbs_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..82b81a649db9d72795a4d04b381c4044c3fe223f
--- /dev/null
+++ b/plugins/easy_wbs/app/helpers/easy_wbs_helper.rb
@@ -0,0 +1,59 @@
+module EasyWbsHelper
+
+  TRANSLATION_KEYS = [
+    'field_name',
+    'label_attribute_plural',
+    'label_description',
+    'title_user_detail',
+    'easy_wbs.label_basic_data',
+    'easy_wbs.label_additional_data',
+    'label_comment_plural',
+    'field_attachments',
+    'easy_wbs.label_all',
+    'label_others',
+    'description_notes',
+    'easy_wbs.button_remove_comment',
+    'easy_wbs.button_add_comment',
+    'easy_wbs.text_issue_not_saved',
+    'sidebar_project_members',
+    'label_issue_watchers',
+    'label_issue_automatic_recalculate_attributes',
+    'easy_wbs.button_add_child',
+    'heading_easy_wbs_issues',
+    'label_preview',
+    'title_inline_editable',
+    'easy_wbs.name_an_entity_first'
+  ]
+
+  def api_render_issues(api, issues, filtered_out: false)
+    issues.each do |issue|
+      api.issue do
+        api.id issue.id
+        api.subject issue.subject
+        api.parent_issue_id issue.parent_issue_id
+        api.project_id issue.project_id
+        api.assigned_to_id issue.assigned_to_id
+        api.tracker_id issue.tracker_id
+        api.status_id issue.status_id
+        api.priority_id issue.priority_id
+        api.done_ratio issue.done_ratio
+        api.activity_id issue.try(:activity_id)
+        api.fixed_version_id issue.fixed_version_id
+        api.filtered_out filtered_out
+      end
+    end
+  end
+
+  def wbs_url_to_project(project)
+    if EasyWbs.easy_extensions?
+      url_to_project(project)
+    else
+      project_path(project)
+    end
+  end
+
+  def wbs_translations
+    TRANSLATION_KEYS.map{|k| [k, l(k)] }.to_h
+  end
+
+end
diff --git a/plugins/easy_wbs/app/models/easy_queries/easy_wbs_easy_issue_query.rb b/plugins/easy_wbs/app/models/easy_queries/easy_wbs_easy_issue_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60f320d2460892015e9e790e6fa87a7831ea6000
--- /dev/null
+++ b/plugins/easy_wbs/app/models/easy_queries/easy_wbs_easy_issue_query.rb
@@ -0,0 +1,53 @@
+class EasyWbsEasyIssueQuery < EasyIssueQuery
+
+  def easy_query_entity_controller
+    'easy_wbs'
+  end
+
+  def easy_query_entity_action
+    'index'
+  end
+
+  def columns
+    []
+  end
+
+  def groupable_columns
+    []
+  end
+
+  def project_scope
+    super.where.not(status: [Project::STATUS_CLOSED, Project::STATUS_ARCHIVED])
+  end
+
+  def entity_easy_query_path(options={})
+    if project
+      project_easy_wbs_index_path(project, options)
+    end
+  end
+
+  def query_after_initialize
+    super
+
+    self.display_filter_columns_on_index = false
+    self.display_filter_group_by_on_index = false
+    self.display_filter_sort_on_index = false
+    self.display_filter_settings_on_index = false
+
+    self.display_filter_columns_on_edit = false
+    self.display_filter_group_by_on_edit = false
+    self.display_filter_sort_on_edit = false
+    self.display_filter_settings_on_edit = false
+
+    self.display_show_sum_row = false
+    self.display_load_groups_opened = false
+
+    self.export_formats = {}
+    self.is_tagged = true if new_record?
+
+    # self.display_filter_fullscreen_button =
+    # self.display_save_button =
+    # self.display_project_column_if_project_missing =
+  end
+
+end
diff --git a/plugins/easy_wbs/app/models/easy_wbs/issue_query.rb b/plugins/easy_wbs/app/models/easy_wbs/issue_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62820fd361cc82f214417ff77bfacfe3771dcbdb
--- /dev/null
+++ b/plugins/easy_wbs/app/models/easy_wbs/issue_query.rb
@@ -0,0 +1,51 @@
+module EasyWbs
+  class IssueQuery < ::IssueQuery
+
+    def initialize(*args)
+      super
+      self.filters = { 'status_id' => { operator: 'o', values: ['']} }
+    end
+
+    def to_partial_path
+      'easy_wbs/easy_queries/show'
+    end
+
+    def from_params(params)
+      build_from_params(params)
+    end
+
+    def to_params
+      params = { set_filter: 1, type: self.class.name, f: [], op: {}, v: {} }
+
+      filters.each do |filter_name, opts|
+        params[:f] << filter_name
+        params[:op][filter_name] = opts[:operator]
+        params[:v][filter_name] = opts[:values]
+      end
+
+      params[:c] = column_names
+      params
+    end
+
+    def entity
+      Issue
+    end
+
+    def entity_scope
+      scope = Issue.visible.preload(:project)
+      if Project.column_names.include? 'easy_baseline_for_id'
+        scope = scope.where(Project.table_name => {easy_baseline_for_id: nil})
+      end
+      scope
+    end
+
+    def create_entity_scope(options={})
+      entity_scope.includes(options[:includes]).references(options[:includes]).preload(options[:preload]).where(statement)
+    end
+
+    def entities(options={})
+      create_entity_scope(options).order(options[:order])
+    end
+
+  end
+end
diff --git a/plugins/easy_wbs/app/views/easy_query_settings/by_query/_easy_wbs_easy_issue_query_settings.html.erb b/plugins/easy_wbs/app/views/easy_query_settings/by_query/_easy_wbs_easy_issue_query_settings.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d2f1e083cba8863a49b301a684947599c12e25f1
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_query_settings/by_query/_easy_wbs_easy_issue_query_settings.html.erb
@@ -0,0 +1,5 @@
+<% # A little hack %>
+
+<script>
+  $("#custom_formatting, #filters_settings, .easy_query_additional_options, .show_avatars").remove();
+</script>
diff --git a/plugins/easy_wbs/app/views/easy_wbs/_includes.html.erb b/plugins/easy_wbs/app/views/easy_wbs/_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..efc9f7cc94b515b42b2d5cc69dadfb54155c2a5e
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/_includes.html.erb
@@ -0,0 +1,22 @@
+<% if EasyMindmup.easy_extensions? %>
+  <%= stylesheet_link_tag('easy_wbs', media: 'all') %>
+<% else %>
+  <%= stylesheet_link_tag('generated/easy_wbs', plugin: 'easy_wbs', media: 'all') %>
+<% end %>
+
+<% if EasyMindmup.combine_by_pipeline?(params) %>
+  <%= javascript_include_tag('easy_wbs') %>
+<% else %>
+  <%= javascript_include_tag(
+          :wbs_main,
+          :wbs_context_menu,
+          :wbs_loader,
+          :wbs_node_patch,
+          :wbs_saver,
+          :wbs_modals,
+          :wbs_styles,
+          :wbs_validator,
+          plugin: 'easy_wbs'
+      ) %>
+<% end %>
+<% include_galereya_headers_tags if defined?(include_galereya_headers_tags) %>
diff --git a/plugins/easy_wbs/app/views/easy_wbs/_js_prepare.html.erb b/plugins/easy_wbs/app/views/easy_wbs/_js_prepare.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c4095edecdf12e49c732e90b1fb4212d22fe7d6b
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/_js_prepare.html.erb
@@ -0,0 +1,57 @@
+<script type="text/javascript">
+  window.easyMindMupSetting = $.extend(true, window.easyMindMupSetting, <%= {
+    mindMupId: 'WBS classic',
+    menuDiv: '#wbs_menu',
+    projectID: @project.id,
+    noSave: (params[:nosave] ? true : false),
+    paths: {
+      issuePage: issue_path(':issueID'),
+      projectPage: project_path(':projectID'),
+      issuePOST: issues_path(format: 'json', key: User.current.api_key),
+      issuePUT: issue_path(':issueID', format: 'json', key: User.current.api_key),
+      issueDELETE: issue_path(':issueID', format: 'json', key: User.current.api_key),
+      projectPUT: project_path(':projectID', format: 'json', key: User.current.api_key),
+      newIssuePath: new_issue_path(key: User.current.api_key),
+      editIssuePath: edit_issue_path(':issueID', key: User.current.api_key),
+      updateLayout: project_easy_mindmup_update_layout_path(@project, format: 'json', key: User.current.api_key)
+    },
+    labels: {
+      free: {
+        textNotAvailable: l('easy_wbs.free.text_not_available'),
+        buttonUpgradeHref: l('easy_wbs.free.button_upgrade_href')
+      },
+      errors:{
+        not_subtaskable: l('easy_wbs.errors.not_subtaskable'),
+        cannotBeEmpty: l('activerecord.errors.messages.empty')
+      },
+      types:{
+        project: l(:field_project),
+        issue: l(:field_issue)
+      },
+      modals: {
+        new_issue: l(:label_issue_new),
+        edit_issue: l('easy_wbs.edit_issue')
+      },
+      context: {
+        tracker: l(:field_tracker),
+        priority: l(:field_priority),
+        status: l(:field_status),
+        assignee: l(:field_assigned_to),
+        doneRatio: l(:field_done_ratio)
+      },
+      links: {
+        start_to_start: l(:label_start_to_start),
+        start_to_finish: l(:label_start_to_finish),
+        finish_to_finish: l(:label_finish_to_finish),
+        precedes: l(:label_precedes),
+        relates: l(:label_relates_to),
+        copied_to: l(:label_copied_to),
+        blocks: l(:label_blocks),
+        duplicates: l(:label_duplicates)
+      }
+    },
+    templates: {
+    },
+    translations: wbs_translations
+  }.to_json.html_safe %>);
+</script>
diff --git a/plugins/easy_wbs/app/views/easy_wbs/_test_includes.html.erb b/plugins/easy_wbs/app/views/easy_wbs/_test_includes.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7b1f822fe9035f5012474ad2d5f21ac7bb571924
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/_test_includes.html.erb
@@ -0,0 +1,7 @@
+<%= javascript_include_tag(
+        'tests/encode_layout',
+        'tests/saving_test',
+        'tests/printing',
+        'tests/parse_form',
+        plugin: 'easy_wbs'
+    ) %>
diff --git a/plugins/easy_wbs/app/views/easy_wbs/easy_queries/_show.html.erb b/plugins/easy_wbs/app/views/easy_wbs/easy_queries/_show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0206dada8a2b7268763b5dfcb31c693cb6d1d8af
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/easy_queries/_show.html.erb
@@ -0,0 +1,19 @@
+<div class="content-title"><%= title(easy_query_name) %></div>
+
+<%= form_tag(project_easy_wbs_index_path(@project), method: :get, id: 'query_form') do %>
+  <div id="query_form_with_buttons" class="hide-when-print">
+    <%= hidden_field_tag 'set_filter', '1' %>
+    <div id="query_form_content">
+      <fieldset id="filters" class="collapsible collapsed">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+        <div style="display: none;">
+          <%= render 'queries/filters', query: @query %>
+        </div>
+      </fieldset>
+    </div>
+    <p class="buttons">
+      <%= link_to_function l(:button_apply), '$("#query_form").submit()', class: 'icon icon-checked' %>
+      <%= link_to l(:button_clear), { set_filter: 1, project_id: @project }, class: 'icon icon-reload' %>
+    </p>
+  </div>
+<% end %>
diff --git a/plugins/easy_wbs/app/views/easy_wbs/index.api.rsb b/plugins/easy_wbs/app/views/easy_wbs/index.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..45406029554c0bcc94757e4613570503976e5df9
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/index.api.rsb
@@ -0,0 +1,91 @@
+api.easy_wbs_data do
+
+  api.layout EasySetting.value(:easy_wbs_layout, @project)
+
+  api.array :projects do
+    @projects.each do |project|
+      api.project do
+        api.id project.id
+        api.name project.name
+        api.parent_id project.parent_id
+        api.default_url wbs_url_to_project(project)
+      end
+    end
+  end
+
+  api.array :issues do
+    api_render_issues(api, @issues)
+    api_render_issues(api, @missing_parent_issues, filtered_out: true) if @missing_parent_issues
+  end
+
+  api.array :relations do
+    @relations.each do |rel|
+      api.relation do
+        api.id rel.id
+        api.source_id rel.issue_from_id
+        api.target_id rel.issue_to_id
+        api.type rel.relation_type
+      end
+    end
+  end
+
+  api.array :trackers do
+    @trackers.each do |tracker|
+      api.tracker do
+        api.id tracker.id
+        api.name tracker.name
+        api.subtaskable tracker.core_fields.include?('parent_issue_id')
+      end
+    end
+  end
+
+  api.array :users do
+    @users.each do |user|
+      api.user do
+        api.id user.id
+        api.name user.name
+        api.avatar_url mindmup_avatar_url(user)
+      end
+    end
+  end
+  if defined?(EasyExtensions)
+    api.array :priorities do
+      IssuePriority.active.sorted.each do |priority|
+        api.priority do
+          api.id priority.id
+          api.name priority.name
+          api.scheme priority.easy_color_scheme
+        end
+      end
+    end
+  else
+    api.array :priorities do
+      IssuePriority.active.sorted.each do |priority|
+        api.priority do
+          api.id priority.id
+          api.name priority.name
+          api.is_default priority.is_default
+        end
+      end
+    end
+  end
+
+  api.array :statuses do
+    IssueStatus.sorted.each do |status|
+      api.status do
+        api.id status.id
+        api.name status.name
+      end
+    end
+  end
+
+  api.array :versions do
+    @versions.each do |version|
+      api.version do
+        api.id version.id
+        api.name version.name
+      end
+    end
+  end
+
+end
diff --git a/plugins/easy_wbs/app/views/easy_wbs/index.html.erb b/plugins/easy_wbs/app/views/easy_wbs/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..dd24dabb87b7245b03661476e808ea5c7db3c9a0
--- /dev/null
+++ b/plugins/easy_wbs/app/views/easy_wbs/index.html.erb
@@ -0,0 +1,135 @@
+<%
+  data_path_params = @query.to_params.merge(key: User.current.api_key, format: 'json')
+  data_path = project_easy_wbs_index_path(@project, data_path_params)
+%>
+
+<div id="easy_wbs" class="<%= defined?(EasyExtensions) ? 'easy' : 'redmine' %>
+    wbs clear">
+
+  <%= render @query, easy_query_name: l(:heading_easy_wbs_issues),
+       wrapper_class: '',
+       options: { show_custom_formatting: false, show_free_search: false } %>
+
+  <div id="wbs_cont" class="clear mindmup-cont">
+    <div id="wbs_menu" class="mindmup__menu">
+      <div class="push-left">
+        <div class="mindmup__legend-container">
+          <div class="mindmup__legend-header">
+            <label class=mindmup__legend-label"><%= l(:button_legend, :scope => [:easy_wbs]) %>:
+              <select class="mindmup-color-select" style="width:150px">
+                <option value="tracker" selected><%= l(:field_tracker) %></option>
+                <option value="progress"><%= l(:field_done_ratio) %></option>
+                <option value="priority"><%= l(:field_priority) %></option>
+                <option value="status"><%= l(:field_status) %></option>
+                <option value="assignee"><%= l(:field_assigned_to) %></option>
+                <option value="milestone"><%= l(:field_version) %></option>
+              </select>
+            </label>
+            <div class="mindmup__legend-toggler legend-toggler push-right">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--legend mindmup__legend-trigger"><span class="tip"><%= l(:button_legend, :scope => [:easy_wbs]) %></span></a>
+            </div>
+          </div>
+          <div class="mindmup-legend mindmup__legend-body"></div>
+        </div>
+        <div class="mindmup__menu-group mindmup__menu-group--tooltiped mindmup__menu-item mindmup__menu-group-display">
+          <a href="javascript:void(0)" class="button button-2 easy-mindmup__icon easy-mindmup__icon--display"><span><%= l(:button_display, :scope => [:easy_wbs]) %></span></a>
+          <ul>
+            <li class="show-links-toggler">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--links"><%= l(:button_show_links, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="all-icon-toggler">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--icons"><%= l(:button_all_icons, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="toggleOneSide">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--one_side"><%= l(:button_one_side, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="mindmup-expand-all">
+              <a href="javascript:void(0)" class="mindmup-button-expand-all easy-mindmup__icon easy-mindmup__icon--expand"><%= l(:button_expand_all, :scope => [:easy_wbs]) %></a>
+              <a href="javascript:void(0)" class="mindmup-button-collapse-all easy-mindmup__icon easy-mindmup__icon--collapse"><%= l(:button_collapse_all, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="mindmup__legend-cont-toggler">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--legend_hide"><%= l(:button_legend, :scope => [:easy_mindmup]) %></a>
+            </li>
+          </ul>
+        </div>
+      </div>
+      <div class="mindmup__menu_addons">
+        <div class="mindmup__menu-group mindmup__menu-group--sizing mindmup__menu-item">
+          <ul>
+            <li class="scaleUp">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--zoom_in"></a>
+            </li>
+            <li class="resetView">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--refresh_view"></a>
+            </li>
+            <li class="scaleDown">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--zoom_out"></a>
+            </li>
+          </ul>
+        </div>
+      </div>
+      <div class="push-right">
+        <div class="mindmup__menu-group mindmup__menu-group--tooltiped mindmup__menu-item">
+          <a href="javascript:void(0)" class="button button-2 easy-mindmup__icon easy-mindmup__icon--settings"><span><%= l(:button_actions, :scope => [:easy_wbs]) %></span></a>
+          <ul>
+            <li class="undo mindmup-button-undo">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--undo"><%= l(:button_undo, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="redo mindmup-button-redo">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--redo"><%= l(:button_redo, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="mindmup-button-print">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--print"><%= l(:button_export) %></a>
+            </li>
+            <li class="cut">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--cut"><%= l(:button_cut, :scope => [:easy_wbs]) %></a>
+            </li>
+            <li class="copy">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--copy"><%= l(:button_copy) %></a>
+            </li>
+            <li class="paste">
+              <a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--paste"><%= l(:button_paste, :scope => [:easy_wbs]) %></a>
+            </li>
+          </ul>
+        </div>
+        <div class="mindmup__menu-item save mindmup__menu-save">
+          <a href="javascript:void(0)" class="button button-positive easy-mindmup__icon easy-mindmup__icon--save"><span><%= l(:button_save) %></span></a>
+          <div class="mindmup__menu-save--tooltip"></div>
+        </div>
+      </div>
+    </div>
+
+    <div id="container" class="mindmup-container" style="min-height: 600px;width: 100%;">
+      <div class="link-edit-widget">
+        <div class="link-edit-type-actual"></div>
+      </div>
+      <%= render 'easy_mindmup/hotkeys' %>
+    </div>
+  </div>
+</div>
+
+<% heads_for_wiki_formatter %>
+<%= content_for :header_tags do %>
+  <%= render 'easy_mindmup/includes' %>
+  <%= render 'easy_wbs/includes' %>
+  <%= render 'easy_mindmup/js_prepare' %>
+  <%= render 'easy_mindmup/test_includes' if params[:run_jasmine_tests] || params[:spec] %>
+  <%= render 'easy_wbs/js_prepare' %>
+  <% # helper functions for user testing %>
+  <%= render 'easy_wbs/test_includes' if params[:include_tests] %>
+
+  <script type="application/javascript">
+    window.easyMindMupSetting.paths.data = "<%= data_path.html_safe %>";
+  </script>
+
+  <script type="text/javascript">
+    (function () {
+      var settings = window.easyMindMupSetting;
+      delete window.easyMindMupSetting;
+      $(document).ready(function () {
+        new window.easyMindMupClasses.WbsMain(settings);
+      })
+    })();
+  </script>
+<% end %>
+
diff --git a/plugins/easy_wbs/assets/images/person.gif b/plugins/easy_wbs/assets/images/person.gif
new file mode 100644
index 0000000000000000000000000000000000000000..a0e72aff4e540308d048fd10a07f48a64d32b654
Binary files /dev/null and b/plugins/easy_wbs/assets/images/person.gif differ
diff --git a/plugins/easy_wbs/assets/javascripts/easy_wbs.js b/plugins/easy_wbs/assets/javascripts/easy_wbs.js
new file mode 100644
index 0000000000000000000000000000000000000000..e4cb51845f62ed5a0fb09e4a8d23de3bbd254dd2
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/easy_wbs.js
@@ -0,0 +1,3 @@
+/*
+ * = require_directory .
+ */
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/tests/encode_layout.js b/plugins/easy_wbs/assets/javascripts/tests/encode_layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..6801f410bfe3ab3eb1f3ad33334a3dc5794964ff
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/tests/encode_layout.js
@@ -0,0 +1,5827 @@
+/**
+ * Created by hosekp on 6/12/17.
+ */
+window.easyTests = $.extend(window.easyTests, {
+  encodeLayout: function () {
+    var smallLayout = {
+      "i2280": {"rank": 1},
+      "i2281": {"rank": 2},
+      "i2282": {"rank": 3},
+      "i2279": {"rank": 1},
+      "i2285": {"rank": 1},
+      "i2286": {"rank": 2},
+      "i2287": {"rank": 3},
+      "i2288": {"rank": 4},
+      "i2289": {"rank": 5},
+      "i2284": {"rank": 1},
+      "i2291": {"rank": 1},
+      "i2292": {"rank": 2},
+      "i2293": {"rank": 3},
+      "i2295": {"rank": 1},
+      "i2296": {"rank": 2},
+      "i2294": {"rank": 4},
+      "i2297": {"rank": 5},
+      "i2290": {"rank": 2},
+      "i2299": {"rank": 1},
+      "i2298": {"rank": 3},
+      "i2301": {"rank": 1},
+      "i2302": {"rank": 2},
+      "i2303": {"rank": 3},
+      "i2304": {"rank": 4},
+      "i2300": {"rank": 4},
+      "i2306": {"rank": 1},
+      "i2307": {"rank": 2},
+      "i2305": {"rank": 5},
+      "i2309": {"rank": 1},
+      "i2310": {"rank": 2},
+      "i2308": {"rank": 6},
+      "i2283": {"rank": 2},
+      "i2313": {"rank": 1},
+      "i2314": {"rank": 2},
+      "i2312": {"rank": 1},
+      "i2316": {"rank": 1},
+      "i2315": {"rank": 2},
+      "i2318": {"rank": 1},
+      "i2317": {"rank": 3},
+      "i2320": {"rank": 1},
+      "i2321": {"rank": 2},
+      "i2319": {"rank": 4},
+      "i2323": {"rank": 1},
+      "i2322": {"rank": 5},
+      "i2311": {"rank": 3},
+      "i2325": {"rank": 1},
+      "i2326": {"rank": 2},
+      "i2327": {"rank": 3},
+      "i2328": {"rank": 4},
+      "i2329": {"rank": 5},
+      "i2330": {"rank": 6},
+      "i2331": {"rank": 7},
+      "i2324": {"rank": 4},
+      "i2278": {"rank": 1},
+      "i2335": {"rank": 1},
+      "i2336": {"rank": 2},
+      "i2337": {"rank": 3},
+      "i2338": {"rank": 4},
+      "i2339": {"rank": 5},
+      "i2334": {"rank": 1},
+      "i2341": {"rank": 1},
+      "i2342": {"rank": 2},
+      "i2343": {"rank": 3},
+      "i2344": {"rank": 4},
+      "i2340": {"rank": 2},
+      "i2346": {"rank": 1},
+      "i2347": {"rank": 2},
+      "i2345": {"rank": 3},
+      "i2333": {"rank": 1},
+      "i2350": {"rank": 1},
+      "i2351": {"rank": 2},
+      "i2352": {"rank": 3},
+      "i2349": {"rank": 1},
+      "i2354": {"rank": 1},
+      "i2355": {"rank": 2},
+      "i2353": {"rank": 2},
+      "i2357": {"rank": 1},
+      "i2358": {"rank": 2},
+      "i2356": {"rank": 3},
+      "i2360": {"rank": 1},
+      "i2361": {"rank": 2},
+      "i2359": {"rank": 4},
+      "i2348": {"rank": 2},
+      "i2363": {"rank": 1},
+      "i2364": {"rank": 2},
+      "i2365": {"rank": 3},
+      "i2366": {"rank": 4},
+      "i2367": {"rank": 5},
+      "i2368": {"rank": 6},
+      "i2362": {"rank": 3},
+      "i2332": {"rank": 2},
+      "i2371": {"rank": 1},
+      "i2372": {"rank": 2},
+      "i2373": {"rank": 3},
+      "i2374": {"rank": 4},
+      "i2375": {"rank": 5},
+      "i2376": {"rank": 6},
+      "i2370": {"rank": 1},
+      "i2378": {"rank": 1},
+      "i2379": {"rank": 2},
+      "i2380": {"rank": 3},
+      "i2377": {"rank": 2},
+      "i2382": {"rank": 1},
+      "i2383": {"rank": 2},
+      "i2384": {"rank": 3},
+      "i2381": {"rank": 3},
+      "i2386": {"rank": 1},
+      "i2387": {"rank": 2},
+      "i2388": {"rank": 3},
+      "i2385": {"rank": 4},
+      "i2369": {"rank": 3},
+      "i2391": {"rank": 1},
+      "i2392": {"rank": 2},
+      "i2393": {"rank": 3},
+      "i2390": {"rank": 1},
+      "i2395": {"rank": 1},
+      "i2396": {"rank": 2},
+      "i2397": {"rank": 3},
+      "i2394": {"rank": 2},
+      "i2399": {"rank": 1},
+      "i2400": {"rank": 2},
+      "i2401": {"rank": 3},
+      "i2398": {"rank": 3},
+      "i2389": {"rank": 4},
+      "i2402": {"rank": 5},
+      "p85": {"rank": -6},
+      "i1655": {"rank": 1},
+      "i1656": {"rank": 2},
+      "i1657": {"rank": 3},
+      "i1654": {"rank": 1},
+      "i1660": {"rank": 1},
+      "i1661": {"rank": 2},
+      "i1662": {"rank": 3},
+      "i1663": {"rank": 4},
+      "i1664": {"rank": 5},
+      "i1659": {"rank": 1},
+      "i1666": {"rank": 1},
+      "i1667": {"rank": 2},
+      "i1668": {"rank": 3},
+      "i1670": {"rank": 1},
+      "i1671": {"rank": 2},
+      "i1669": {"rank": 4},
+      "i1672": {"rank": 5},
+      "i1665": {"rank": 2},
+      "i1674": {"rank": 1},
+      "i1673": {"rank": 3},
+      "i1676": {"rank": 1},
+      "i1677": {"rank": 2},
+      "i1678": {"rank": 3},
+      "i1679": {"rank": 4},
+      "i1675": {"rank": 4},
+      "i1681": {"rank": 1},
+      "i1682": {"rank": 2},
+      "i1680": {"rank": 5},
+      "i1684": {"rank": 1},
+      "i1685": {"rank": 2},
+      "i1683": {"rank": 6},
+      "i1658": {"rank": 2},
+      "i1688": {"rank": 1},
+      "i1689": {"rank": 2},
+      "i1687": {"rank": 1},
+      "i1691": {"rank": 1},
+      "i1690": {"rank": 2},
+      "i1693": {"rank": 1},
+      "i1692": {"rank": 3},
+      "i1695": {"rank": 1},
+      "i1696": {"rank": 2},
+      "i1694": {"rank": 4},
+      "i1698": {"rank": 1},
+      "i1697": {"rank": 5},
+      "i1686": {"rank": 3},
+      "i1700": {"rank": 1},
+      "i1701": {"rank": 2},
+      "i1702": {"rank": 3},
+      "i1703": {"rank": 4},
+      "i1704": {"rank": 5},
+      "i1705": {"rank": 6},
+      "i1706": {"rank": 7},
+      "i1699": {"rank": 4},
+      "i1653": {"rank": 1},
+      "i1710": {"rank": 1},
+      "i1711": {"rank": 2},
+      "i1712": {"rank": 3},
+      "i1713": {"rank": 4},
+      "i1714": {"rank": 5},
+      "i1709": {"rank": 1},
+      "i1716": {"rank": 1},
+      "i1717": {"rank": 2},
+      "i1718": {"rank": 3},
+      "i1719": {"rank": 4},
+      "i1715": {"rank": 2},
+      "i1721": {"rank": 1},
+      "i1722": {"rank": 2},
+      "i1720": {"rank": 3},
+      "i1708": {"rank": 1},
+      "i1725": {"rank": 1},
+      "i1726": {"rank": 2},
+      "i1727": {"rank": 3},
+      "i1724": {"rank": 1},
+      "i1729": {"rank": 1},
+      "i1730": {"rank": 2},
+      "i1728": {"rank": 2},
+      "i1732": {"rank": 1},
+      "i1733": {"rank": 2},
+      "i1731": {"rank": 3},
+      "i1735": {"rank": 1},
+      "i1736": {"rank": 2},
+      "i1734": {"rank": 4},
+      "i1723": {"rank": 2},
+      "i1738": {"rank": 1},
+      "i1739": {"rank": 2},
+      "i1740": {"rank": 3},
+      "i1741": {"rank": 4},
+      "i1742": {"rank": 5},
+      "i1743": {"rank": 6},
+      "i1737": {"rank": 3},
+      "i1707": {"rank": 2},
+      "i1746": {"rank": 1},
+      "i1747": {"rank": 2},
+      "i1748": {"rank": 3},
+      "i1749": {"rank": 4},
+      "i1750": {"rank": 5},
+      "i1751": {"rank": 6},
+      "i1745": {"rank": 1},
+      "i1753": {"rank": 1},
+      "i1754": {"rank": 2},
+      "i1755": {"rank": 3},
+      "i1752": {"rank": 2},
+      "i1757": {"rank": 1},
+      "i1758": {"rank": 2},
+      "i1759": {"rank": 3},
+      "i1756": {"rank": 3},
+      "i1761": {"rank": 1},
+      "i1762": {"rank": 2},
+      "i1763": {"rank": 3},
+      "i1760": {"rank": 4},
+      "i1744": {"rank": 3},
+      "i1766": {"rank": 1},
+      "i1767": {"rank": 2},
+      "i1768": {"rank": 3},
+      "i1765": {"rank": 1},
+      "i1770": {"rank": 1},
+      "i1771": {"rank": 2},
+      "i1772": {"rank": 3},
+      "i1769": {"rank": 2},
+      "i1774": {"rank": 1},
+      "i1775": {"rank": 2},
+      "i1776": {"rank": 3},
+      "i1773": {"rank": 3},
+      "i1764": {"rank": 4},
+      "i1777": {"rank": 5},
+      "p80": {"rank": -5},
+      "i2530": {"rank": 1},
+      "i2531": {"rank": 2},
+      "i2532": {"rank": 3},
+      "i2529": {"rank": 1},
+      "i2535": {"rank": 1},
+      "i2536": {"rank": 2},
+      "i2537": {"rank": 3},
+      "i2538": {"rank": 4},
+      "i2539": {"rank": 5},
+      "i2534": {"rank": 1},
+      "i2541": {"rank": 1},
+      "i2542": {"rank": 2},
+      "i2543": {"rank": 3},
+      "i2545": {"rank": 1},
+      "i2546": {"rank": 2},
+      "i2544": {"rank": 4},
+      "i2547": {"rank": 5},
+      "i2540": {"rank": 2},
+      "i2549": {"rank": 1},
+      "i2548": {"rank": 3},
+      "i2551": {"rank": 1},
+      "i2552": {"rank": 2},
+      "i2553": {"rank": 3},
+      "i2554": {"rank": 4},
+      "i2550": {"rank": 4},
+      "i2556": {"rank": 1},
+      "i2557": {"rank": 2},
+      "i2555": {"rank": 5},
+      "i2559": {"rank": 1},
+      "i2560": {"rank": 2},
+      "i2558": {"rank": 6},
+      "i2533": {"rank": 2},
+      "i2563": {"rank": 1},
+      "i2564": {"rank": 2},
+      "i2562": {"rank": 1},
+      "i2566": {"rank": 1},
+      "i2565": {"rank": 2},
+      "i2568": {"rank": 1},
+      "i2567": {"rank": 3},
+      "i2570": {"rank": 1},
+      "i2571": {"rank": 2},
+      "i2569": {"rank": 4},
+      "i2573": {"rank": 1},
+      "i2572": {"rank": 5},
+      "i2561": {"rank": 3},
+      "i2575": {"rank": 1},
+      "i2576": {"rank": 2},
+      "i2577": {"rank": 3},
+      "i2578": {"rank": 4},
+      "i2579": {"rank": 5},
+      "i2580": {"rank": 6},
+      "i2581": {"rank": 7},
+      "i2574": {"rank": 4},
+      "i2528": {"rank": 1},
+      "i2585": {"rank": 1},
+      "i2586": {"rank": 2},
+      "i2587": {"rank": 3},
+      "i2588": {"rank": 4},
+      "i2589": {"rank": 5},
+      "i2584": {"rank": 1},
+      "i2591": {"rank": 1},
+      "i2592": {"rank": 2},
+      "i2593": {"rank": 3},
+      "i2594": {"rank": 4},
+      "i2590": {"rank": 2},
+      "i2596": {"rank": 1},
+      "i2597": {"rank": 2},
+      "i2595": {"rank": 3},
+      "i2583": {"rank": 1},
+      "i2600": {"rank": 1},
+      "i2601": {"rank": 2},
+      "i2602": {"rank": 3},
+      "i2599": {"rank": 1},
+      "i2604": {"rank": 1},
+      "i2605": {"rank": 2},
+      "i2603": {"rank": 2},
+      "i2607": {"rank": 1},
+      "i2608": {"rank": 2},
+      "i2606": {"rank": 3},
+      "i2610": {"rank": 1},
+      "i2611": {"rank": 2},
+      "i2609": {"rank": 4},
+      "i2598": {"rank": 2},
+      "i2613": {"rank": 1},
+      "i2614": {"rank": 2},
+      "i2615": {"rank": 3},
+      "i2616": {"rank": 4},
+      "i2617": {"rank": 5},
+      "i2618": {"rank": 6},
+      "i2612": {"rank": 3},
+      "i2582": {"rank": 2},
+      "i2621": {"rank": 1},
+      "i2622": {"rank": 2},
+      "i2623": {"rank": 3},
+      "i2624": {"rank": 4},
+      "i2625": {"rank": 5},
+      "i2626": {"rank": 6},
+      "i2620": {"rank": 1},
+      "i2628": {"rank": 1},
+      "i2629": {"rank": 2},
+      "i2630": {"rank": 3},
+      "i2627": {"rank": 2},
+      "i2632": {"rank": 1},
+      "i2633": {"rank": 2},
+      "i2634": {"rank": 3},
+      "i2631": {"rank": 3},
+      "i2636": {"rank": 1},
+      "i2637": {"rank": 2},
+      "i2638": {"rank": 3},
+      "i2635": {"rank": 4},
+      "i2619": {"rank": 3},
+      "i2641": {"rank": 1},
+      "i2642": {"rank": 2},
+      "i2643": {"rank": 3},
+      "i2640": {"rank": 1},
+      "i2645": {"rank": 1},
+      "i2646": {"rank": 2},
+      "i2647": {"rank": 3},
+      "i2644": {"rank": 2},
+      "i2649": {"rank": 1},
+      "i2650": {"rank": 2},
+      "i2651": {"rank": 3},
+      "i2648": {"rank": 3},
+      "i2639": {"rank": 4},
+      "i2652": {"rank": 5},
+      "p87": {"rank": -4},
+      "i2905": {"rank": 1},
+      "i2906": {"rank": 2},
+      "i2907": {"rank": 3},
+      "i2904": {"rank": 1},
+      "i2910": {"rank": 1},
+      "i2911": {"rank": 2},
+      "i2912": {"rank": 3},
+      "i2913": {"rank": 4},
+      "i2914": {"rank": 5},
+      "i2909": {"rank": 1},
+      "i2916": {"rank": 1},
+      "i2917": {"rank": 2},
+      "i2918": {"rank": 3},
+      "i2920": {"rank": 1},
+      "i2921": {"rank": 2},
+      "i2919": {"rank": 4},
+      "i2922": {"rank": 5},
+      "i2915": {"rank": 2},
+      "i2924": {"rank": 1},
+      "i2923": {"rank": 3},
+      "i2926": {"rank": 1},
+      "i2927": {"rank": 2},
+      "i2928": {"rank": 3},
+      "i2929": {"rank": 4},
+      "i2925": {"rank": 4},
+      "i2931": {"rank": 1},
+      "i2932": {"rank": 2},
+      "i2930": {"rank": 5},
+      "i2934": {"rank": 1},
+      "i2935": {"rank": 2},
+      "i2933": {"rank": 6},
+      "i2908": {"rank": 2},
+      "i2938": {"rank": 1},
+      "i2939": {"rank": 2},
+      "i2937": {"rank": 1},
+      "i2941": {"rank": 1},
+      "i2940": {"rank": 2},
+      "i2943": {"rank": 1},
+      "i2942": {"rank": 3},
+      "i2945": {"rank": 1},
+      "i2946": {"rank": 2},
+      "i2944": {"rank": 4},
+      "i2948": {"rank": 1},
+      "i2947": {"rank": 5},
+      "i2936": {"rank": 3},
+      "i2950": {"rank": 1},
+      "i2951": {"rank": 2},
+      "i2952": {"rank": 3},
+      "i2953": {"rank": 4},
+      "i2954": {"rank": 5},
+      "i2955": {"rank": 6},
+      "i2956": {"rank": 7},
+      "i2949": {"rank": 4},
+      "i2903": {"rank": 1},
+      "i2960": {"rank": 1},
+      "i2961": {"rank": 2},
+      "i2962": {"rank": 3},
+      "i2963": {"rank": 4},
+      "i2964": {"rank": 5},
+      "i2959": {"rank": 1},
+      "i2966": {"rank": 1},
+      "i2967": {"rank": 2},
+      "i2968": {"rank": 3},
+      "i2969": {"rank": 4},
+      "i2965": {"rank": 2},
+      "i2971": {"rank": 1},
+      "i2972": {"rank": 2},
+      "i2970": {"rank": 3},
+      "i2958": {"rank": 1},
+      "i2975": {"rank": 1},
+      "i2976": {"rank": 2},
+      "i2977": {"rank": 3},
+      "i2974": {"rank": 1},
+      "i2979": {"rank": 1},
+      "i2980": {"rank": 2},
+      "i2978": {"rank": 2},
+      "i2982": {"rank": 1},
+      "i2983": {"rank": 2},
+      "i2981": {"rank": 3},
+      "i2985": {"rank": 1},
+      "i2986": {"rank": 2},
+      "i2984": {"rank": 4},
+      "i2973": {"rank": 2},
+      "i2988": {"rank": 1},
+      "i2989": {"rank": 2},
+      "i2990": {"rank": 3},
+      "i2991": {"rank": 4},
+      "i2992": {"rank": 5},
+      "i2993": {"rank": 6},
+      "i2987": {"rank": 3},
+      "i2957": {"rank": 2},
+      "i2996": {"rank": 1},
+      "i2997": {"rank": 2},
+      "i2998": {"rank": 3},
+      "i2999": {"rank": 4},
+      "i3000": {"rank": 5},
+      "i3001": {"rank": 6},
+      "i2995": {"rank": 1},
+      "i3003": {"rank": 1},
+      "i3004": {"rank": 2},
+      "i3005": {"rank": 3},
+      "i3002": {"rank": 2},
+      "i3007": {"rank": 1},
+      "i3008": {"rank": 2},
+      "i3009": {"rank": 3},
+      "i3006": {"rank": 3},
+      "i3011": {"rank": 1},
+      "i3012": {"rank": 2},
+      "i3013": {"rank": 3},
+      "i3010": {"rank": 4},
+      "i2994": {"rank": 3},
+      "i3016": {"rank": 1},
+      "i3017": {"rank": 2},
+      "i3018": {"rank": 3},
+      "i3015": {"rank": 1},
+      "i3020": {"rank": 1},
+      "i3021": {"rank": 2},
+      "i3022": {"rank": 3},
+      "i3019": {"rank": 2},
+      "i3024": {"rank": 1},
+      "i3025": {"rank": 2},
+      "i3026": {"rank": 3},
+      "i3023": {"rank": 3},
+      "i3014": {"rank": 4},
+      "i3027": {"rank": 5, "position": [808.5, 111.25, 1]},
+      "p90": {"rank": -3},
+      "i1905": {"rank": 1},
+      "i1906": {"rank": 2},
+      "i1907": {"rank": 3},
+      "i1904": {"rank": 1},
+      "i1910": {"rank": 1},
+      "i1911": {"rank": 2},
+      "i1912": {"rank": 3},
+      "i1913": {"rank": 4},
+      "i1914": {"rank": 5},
+      "i1909": {"rank": 1},
+      "i1916": {"rank": 1},
+      "i1917": {"rank": 2},
+      "i1918": {"rank": 3},
+      "i1920": {"rank": 1},
+      "i1921": {"rank": 2},
+      "i1919": {"rank": 4},
+      "i1922": {"rank": 5},
+      "i1915": {"rank": 2},
+      "i1924": {"rank": 1},
+      "i1923": {"rank": 3},
+      "i1926": {"rank": 1},
+      "i1927": {"rank": 2},
+      "i1928": {"rank": 3},
+      "i1929": {"rank": 4},
+      "i1925": {"rank": 4},
+      "i1931": {"rank": 1},
+      "i1932": {"rank": 2},
+      "i1930": {"rank": 5},
+      "i1934": {"rank": 1},
+      "i1935": {"rank": 2},
+      "i1933": {"rank": 6},
+      "i1908": {"rank": 2},
+      "i1938": {"rank": 1},
+      "i1939": {"rank": 2},
+      "i1937": {"rank": 1},
+      "i1941": {"rank": 1},
+      "i1940": {"rank": 2},
+      "i1943": {"rank": 1},
+      "i1942": {"rank": 3},
+      "i1945": {"rank": 1},
+      "i1946": {"rank": 2},
+      "i1944": {"rank": 4},
+      "i1948": {"rank": 1},
+      "i1947": {"rank": 5},
+      "i1936": {"rank": 3},
+      "i1950": {"rank": 1},
+      "i1951": {"rank": 2},
+      "i1952": {"rank": 3},
+      "i1953": {"rank": 4},
+      "i1954": {"rank": 5},
+      "i1955": {"rank": 6},
+      "i1956": {"rank": 7},
+      "i1949": {"rank": 4},
+      "i1903": {"rank": 1},
+      "i1960": {"rank": 1},
+      "i1961": {"rank": 2},
+      "i1962": {"rank": 3},
+      "i1963": {"rank": 4},
+      "i1964": {"rank": 5},
+      "i1959": {"rank": 1},
+      "i1966": {"rank": 1},
+      "i1967": {"rank": 2},
+      "i1968": {"rank": 3},
+      "i1969": {"rank": 4},
+      "i1965": {"rank": 2},
+      "i1971": {"rank": 1},
+      "i1972": {"rank": 2},
+      "i1970": {"rank": 3},
+      "i1958": {"rank": 1},
+      "i1975": {"rank": 1},
+      "i1976": {"rank": 2},
+      "i1977": {"rank": 3},
+      "i1974": {"rank": 1},
+      "i1979": {"rank": 1},
+      "i1980": {"rank": 2},
+      "i1978": {"rank": 2},
+      "i1982": {"rank": 1},
+      "i1983": {"rank": 2},
+      "i1981": {"rank": 3},
+      "i1985": {"rank": 1},
+      "i1986": {"rank": 2},
+      "i1984": {"rank": 4},
+      "i1973": {"rank": 2},
+      "i1988": {"rank": 1},
+      "i1989": {"rank": 2},
+      "i1990": {"rank": 3},
+      "i1991": {"rank": 4},
+      "i1992": {"rank": 5},
+      "i1993": {"rank": 6},
+      "i1987": {"rank": 3},
+      "i1957": {"rank": 2},
+      "i1996": {"rank": 1},
+      "i1997": {"rank": 2},
+      "i1998": {"rank": 3},
+      "i1999": {"rank": 4},
+      "i2000": {"rank": 5},
+      "i2001": {"rank": 6},
+      "i1995": {"rank": 1},
+      "i2003": {"rank": 1},
+      "i2004": {"rank": 2},
+      "i2005": {"rank": 3},
+      "i2002": {"rank": 2},
+      "i2007": {"rank": 1},
+      "i2008": {"rank": 2},
+      "i2009": {"rank": 3},
+      "i2006": {"rank": 3},
+      "i2011": {"rank": 1},
+      "i2012": {"rank": 2},
+      "i2013": {"rank": 3},
+      "i2010": {"rank": 4},
+      "i1994": {"rank": 3},
+      "i2016": {"rank": 1},
+      "i2017": {"rank": 2},
+      "i2018": {"rank": 3},
+      "i2015": {"rank": 1},
+      "i2020": {"rank": 1},
+      "i2021": {"rank": 2},
+      "i2022": {"rank": 3},
+      "i2019": {"rank": 2},
+      "i2024": {"rank": 1},
+      "i2025": {"rank": 2},
+      "i2026": {"rank": 3},
+      "i2023": {"rank": 3},
+      "i2014": {"rank": 4},
+      "i2027": {"rank": 5},
+      "p82": {"rank": -2},
+      "i2780": {"rank": 1},
+      "i2781": {"rank": 2},
+      "i2782": {"rank": 3},
+      "i2779": {"rank": 1},
+      "i2785": {"rank": 1},
+      "i2786": {"rank": 2},
+      "i2787": {"rank": 3},
+      "i2788": {"rank": 4},
+      "i2789": {"rank": 5},
+      "i2784": {"rank": 1},
+      "i2791": {"rank": 1},
+      "i2792": {"rank": 2},
+      "i2793": {"rank": 3},
+      "i2795": {"rank": 1},
+      "i2796": {"rank": 2},
+      "i2794": {"rank": 4},
+      "i2797": {"rank": 5},
+      "i2790": {"rank": 2},
+      "i2799": {"rank": 1},
+      "i2798": {"rank": 3},
+      "i2801": {"rank": 1},
+      "i2802": {"rank": 2},
+      "i2803": {"rank": 3},
+      "i2804": {"rank": 4},
+      "i2800": {"rank": 4},
+      "i2806": {"rank": 1},
+      "i2807": {"rank": 2},
+      "i2805": {"rank": 5},
+      "i2809": {"rank": 1},
+      "i2810": {"rank": 2},
+      "i2808": {"rank": 6},
+      "i2783": {"rank": 2},
+      "i2813": {"rank": 1},
+      "i2814": {"rank": 2},
+      "i2812": {"rank": 1},
+      "i2816": {"rank": 1},
+      "i2815": {"rank": 2},
+      "i2818": {"rank": 1},
+      "i2817": {"rank": 3},
+      "i2820": {"rank": 1},
+      "i2821": {"rank": 2},
+      "i2819": {"rank": 4},
+      "i2823": {"rank": 1},
+      "i2822": {"rank": 5},
+      "i2811": {"rank": 3},
+      "i2825": {"rank": 1},
+      "i2826": {"rank": 2},
+      "i2827": {"rank": 3},
+      "i2828": {"rank": 4},
+      "i2829": {"rank": 5},
+      "i2830": {"rank": 6},
+      "i2831": {"rank": 7},
+      "i2824": {"rank": 4},
+      "i2778": {"rank": 1},
+      "i2835": {"rank": 1},
+      "i2836": {"rank": 2},
+      "i2837": {"rank": 3},
+      "i2838": {"rank": 4},
+      "i2839": {"rank": 5},
+      "i2834": {"rank": 1},
+      "i2841": {"rank": 1},
+      "i2842": {"rank": 2},
+      "i2843": {"rank": 3},
+      "i2844": {"rank": 4},
+      "i2840": {"rank": 2},
+      "i2846": {"rank": 1},
+      "i2847": {"rank": 2},
+      "i2845": {"rank": 3},
+      "i2833": {"rank": 1},
+      "i2850": {"rank": 1},
+      "i2851": {"rank": 2},
+      "i2852": {"rank": 3},
+      "i2849": {"rank": 1},
+      "i2854": {"rank": 1},
+      "i2855": {"rank": 2},
+      "i2853": {"rank": 2},
+      "i2857": {"rank": 1},
+      "i2858": {"rank": 2},
+      "i2856": {"rank": 3},
+      "i2860": {"rank": 1},
+      "i2861": {"rank": 2},
+      "i2859": {"rank": 4},
+      "i2848": {"rank": 2},
+      "i2863": {"rank": 1},
+      "i2864": {"rank": 2},
+      "i2865": {"rank": 3},
+      "i2866": {"rank": 4},
+      "i2867": {"rank": 5},
+      "i2868": {"rank": 6},
+      "i2862": {"rank": 3},
+      "i2832": {"rank": 2},
+      "i2871": {"rank": 1},
+      "i2872": {"rank": 2},
+      "i2873": {"rank": 3},
+      "i2874": {"rank": 4},
+      "i2875": {"rank": 5},
+      "i2876": {"rank": 6},
+      "i2870": {"rank": 1},
+      "i2878": {"rank": 1},
+      "i2879": {"rank": 2},
+      "i2880": {"rank": 3},
+      "i2877": {"rank": 2},
+      "i2882": {"rank": 1},
+      "i2883": {"rank": 2},
+      "i2884": {"rank": 3},
+      "i2881": {"rank": 3},
+      "i2886": {"rank": 1},
+      "i2887": {"rank": 2},
+      "i2888": {"rank": 3},
+      "i2885": {"rank": 4},
+      "i2869": {"rank": 3},
+      "i2891": {"rank": 1},
+      "i2892": {"rank": 2},
+      "i2893": {"rank": 3},
+      "i2890": {"rank": 1},
+      "i2895": {"rank": 1},
+      "i2896": {"rank": 2},
+      "i2897": {"rank": 3},
+      "i2894": {"rank": 2},
+      "i2899": {"rank": 1},
+      "i2900": {"rank": 2},
+      "i2901": {"rank": 3},
+      "i2898": {"rank": 3},
+      "i2889": {"rank": 4},
+      "i2902": {"rank": 5},
+      "p89": {"rank": -1},
+      "i1530": {"rank": 1},
+      "i1531": {"rank": 2},
+      "i1532": {"rank": 3},
+      "i1529": {"rank": 1},
+      "i1535": {"rank": 1},
+      "i1536": {"rank": 2},
+      "i1537": {"rank": 3},
+      "i1538": {"rank": 4},
+      "i1539": {"rank": 5},
+      "i1534": {"rank": 1},
+      "i1541": {"rank": 1},
+      "i1542": {"rank": 2},
+      "i1543": {"rank": 3},
+      "i1545": {"rank": 1},
+      "i1546": {"rank": 2},
+      "i1544": {"rank": 4},
+      "i1547": {"rank": 5},
+      "i1540": {"rank": 2},
+      "i1549": {"rank": 1},
+      "i1548": {"rank": 3},
+      "i1551": {"rank": 1},
+      "i1552": {"rank": 2},
+      "i1553": {"rank": 3},
+      "i1554": {"rank": 4},
+      "i1550": {"rank": 4},
+      "i1556": {"rank": 1},
+      "i1557": {"rank": 2},
+      "i1555": {"rank": 5},
+      "i1559": {"rank": 1},
+      "i1560": {"rank": 2},
+      "i1558": {"rank": 6},
+      "i1533": {"rank": 2},
+      "i1563": {"rank": 1},
+      "i1564": {"rank": 2},
+      "i1562": {"rank": 1},
+      "i1566": {"rank": 1},
+      "i1565": {"rank": 2},
+      "i1568": {"rank": 1},
+      "i1567": {"rank": 3},
+      "i1570": {"rank": 1},
+      "i1571": {"rank": 2},
+      "i1569": {"rank": 4},
+      "i1573": {"rank": 1},
+      "i1572": {"rank": 5},
+      "i1561": {"rank": 3},
+      "i1575": {"rank": 1},
+      "i1576": {"rank": 2},
+      "i1577": {"rank": 3},
+      "i1578": {"rank": 4},
+      "i1579": {"rank": 5},
+      "i1580": {"rank": 6},
+      "i1581": {"rank": 7},
+      "i1574": {"rank": 4},
+      "i1528": {"rank": 1},
+      "i1585": {"rank": 1},
+      "i1586": {"rank": 2},
+      "i1587": {"rank": 3},
+      "i1588": {"rank": 4},
+      "i1589": {"rank": 5},
+      "i1584": {"rank": 1},
+      "i1591": {"rank": 1},
+      "i1592": {"rank": 2},
+      "i1593": {"rank": 3},
+      "i1594": {"rank": 4},
+      "i1590": {"rank": 2},
+      "i1596": {"rank": 1},
+      "i1597": {"rank": 2},
+      "i1595": {"rank": 3},
+      "i1583": {"rank": 1},
+      "i1600": {"rank": 1},
+      "i1601": {"rank": 2},
+      "i1602": {"rank": 3},
+      "i1599": {"rank": 1},
+      "i1604": {"rank": 1},
+      "i1605": {"rank": 2},
+      "i1603": {"rank": 2},
+      "i1607": {"rank": 1},
+      "i1608": {"rank": 2},
+      "i1606": {"rank": 3},
+      "i1610": {"rank": 1},
+      "i1611": {"rank": 2},
+      "i1609": {"rank": 4},
+      "i1598": {"rank": 2},
+      "i1613": {"rank": 1},
+      "i1614": {"rank": 2},
+      "i1615": {"rank": 3},
+      "i1616": {"rank": 4},
+      "i1617": {"rank": 5},
+      "i1618": {"rank": 6},
+      "i1612": {"rank": 3},
+      "i1582": {"rank": 2},
+      "i1621": {"rank": 1},
+      "i1622": {"rank": 2},
+      "i1623": {"rank": 3},
+      "i1624": {"rank": 4},
+      "i1625": {"rank": 5},
+      "i1626": {"rank": 6},
+      "i1620": {"rank": 1},
+      "i1628": {"rank": 1},
+      "i1629": {"rank": 2},
+      "i1630": {"rank": 3},
+      "i1627": {"rank": 2},
+      "i1632": {"rank": 1},
+      "i1633": {"rank": 2},
+      "i1634": {"rank": 3},
+      "i1631": {"rank": 3},
+      "i1636": {"rank": 1},
+      "i1637": {"rank": 2},
+      "i1638": {"rank": 3},
+      "i1635": {"rank": 4},
+      "i1619": {"rank": 3},
+      "i1641": {"rank": 1},
+      "i1642": {"rank": 2},
+      "i1643": {"rank": 3},
+      "i1640": {"rank": 1},
+      "i1645": {"rank": 1},
+      "i1646": {"rank": 2},
+      "i1647": {"rank": 3},
+      "i1644": {"rank": 2},
+      "i1649": {"rank": 1},
+      "i1650": {"rank": 2},
+      "i1651": {"rank": 3},
+      "i1648": {"rank": 3},
+      "i1639": {"rank": 4},
+      "i1652": {"rank": 5, "position": [727.5, 333.5, 1]},
+      "p79": {"rank": 1},
+      "i1405": {"rank": 1},
+      "i1406": {"rank": 2},
+      "i1407": {"rank": 3},
+      "i1404": {"rank": 1},
+      "i1410": {"rank": 1},
+      "i1411": {"rank": 2},
+      "i1412": {"rank": 3},
+      "i1413": {"rank": 4},
+      "i1414": {"rank": 5},
+      "i1409": {"rank": 1},
+      "i1416": {"rank": 1},
+      "i1417": {"rank": 2},
+      "i1418": {"rank": 3},
+      "i1420": {"rank": 1},
+      "i1421": {"rank": 2},
+      "i1419": {"rank": 4},
+      "i1422": {"rank": 5},
+      "i1415": {"rank": 2},
+      "i1424": {"rank": 1},
+      "i1423": {"rank": 3},
+      "i1426": {"rank": 1},
+      "i1427": {"rank": 2},
+      "i1428": {"rank": 3},
+      "i1429": {"rank": 4},
+      "i1425": {"rank": 4},
+      "i1431": {"rank": 1},
+      "i1432": {"rank": 2},
+      "i1430": {"rank": 5},
+      "i1434": {"rank": 1},
+      "i1435": {"rank": 2},
+      "i1433": {"rank": 6},
+      "i1408": {"rank": 2},
+      "i1438": {"rank": 1},
+      "i1439": {"rank": 2},
+      "i1437": {"rank": 1},
+      "i1441": {"rank": 1},
+      "i1440": {"rank": 2},
+      "i1443": {"rank": 1},
+      "i1442": {"rank": 3},
+      "i1445": {"rank": 1},
+      "i1446": {"rank": 2},
+      "i1444": {"rank": 4},
+      "i1448": {"rank": 1},
+      "i1447": {"rank": 5},
+      "i1436": {"rank": 3},
+      "i1450": {"rank": 1},
+      "i1451": {"rank": 2},
+      "i1452": {"rank": 3},
+      "i1453": {"rank": 4},
+      "i1454": {"rank": 5},
+      "i1455": {"rank": 6},
+      "i1456": {"rank": 7},
+      "i1449": {"rank": 4},
+      "i1403": {"rank": 1},
+      "i1460": {"rank": 1},
+      "i1461": {"rank": 2},
+      "i1462": {"rank": 3},
+      "i1463": {"rank": 4},
+      "i1464": {"rank": 5},
+      "i1459": {"rank": 1},
+      "i1466": {"rank": 1},
+      "i1467": {"rank": 2},
+      "i1468": {"rank": 3},
+      "i1469": {"rank": 4},
+      "i1465": {"rank": 2},
+      "i1471": {"rank": 1},
+      "i1472": {"rank": 2},
+      "i1470": {"rank": 3},
+      "i1458": {"rank": 1},
+      "i1475": {"rank": 1},
+      "i1476": {"rank": 2},
+      "i1477": {"rank": 3},
+      "i1474": {"rank": 1},
+      "i1479": {"rank": 1},
+      "i1480": {"rank": 2},
+      "i1478": {"rank": 2},
+      "i1482": {"rank": 1},
+      "i1483": {"rank": 2},
+      "i1481": {"rank": 3},
+      "i1485": {"rank": 1},
+      "i1486": {"rank": 2},
+      "i1484": {"rank": 4},
+      "i1473": {"rank": 2},
+      "i1488": {"rank": 1},
+      "i1489": {"rank": 2},
+      "i1490": {"rank": 3},
+      "i1491": {"rank": 4},
+      "i1492": {"rank": 5},
+      "i1493": {"rank": 6},
+      "i1487": {"rank": 3},
+      "i1457": {"rank": 2},
+      "i1496": {"rank": 1},
+      "i1497": {"rank": 2},
+      "i1498": {"rank": 3},
+      "i1499": {"rank": 4},
+      "i1500": {"rank": 5},
+      "i1501": {"rank": 6},
+      "i1495": {"rank": 1},
+      "i1503": {"rank": 1},
+      "i1504": {"rank": 2},
+      "i1505": {"rank": 3},
+      "i1502": {"rank": 2},
+      "i1507": {"rank": 1},
+      "i1508": {"rank": 2},
+      "i1509": {"rank": 3},
+      "i1506": {"rank": 3},
+      "i1511": {"rank": 1},
+      "i1512": {"rank": 2},
+      "i1513": {"rank": 3},
+      "i1510": {"rank": 4},
+      "i1494": {"rank": 3},
+      "i1516": {"rank": 1},
+      "i1517": {"rank": 2},
+      "i1518": {"rank": 3},
+      "i1515": {"rank": 1},
+      "i1520": {"rank": 1},
+      "i1521": {"rank": 2},
+      "i1522": {"rank": 3},
+      "i1519": {"rank": 2},
+      "i1524": {"rank": 1},
+      "i1525": {"rank": 2},
+      "i1526": {"rank": 3},
+      "i1523": {"rank": 3},
+      "i1514": {"rank": 4},
+      "i1527": {"rank": 5},
+      "p78": {"rank": 2},
+      "i3363": {"rank": 1},
+      "i3364": {"rank": 2},
+      "i3365": {"rank": 3},
+      "i3366": {"rank": 4},
+      "i2155": {"rank": 1},
+      "i2156": {"rank": 2},
+      "i2157": {"rank": 3},
+      "i2154": {"rank": 1},
+      "i2160": {"rank": 1},
+      "i2161": {"rank": 2},
+      "i2162": {"rank": 3},
+      "i2163": {"rank": 4},
+      "i2164": {"rank": 5},
+      "i2159": {"rank": 1},
+      "i2166": {"rank": 1},
+      "i2167": {"rank": 2},
+      "i2168": {"rank": 3},
+      "i2170": {"rank": 1},
+      "i2171": {"rank": 2},
+      "i2169": {"rank": 4},
+      "i2172": {"rank": 5},
+      "i2165": {"rank": 2},
+      "i2174": {"rank": 1},
+      "i2173": {"rank": 3},
+      "i2176": {"rank": 1},
+      "i2177": {"rank": 2},
+      "i2178": {"rank": 3},
+      "i2179": {"rank": 4},
+      "i2175": {"rank": 4},
+      "i2181": {"rank": 1},
+      "i2182": {"rank": 2},
+      "i2180": {"rank": 5},
+      "i2184": {"rank": 1},
+      "i2185": {"rank": 2},
+      "i2183": {"rank": 6},
+      "i2158": {"rank": 2},
+      "i2188": {"rank": 1},
+      "i2189": {"rank": 2},
+      "i2187": {"rank": 1},
+      "i2191": {"rank": 1},
+      "i2190": {"rank": 2},
+      "i2193": {"rank": 1},
+      "i2192": {"rank": 3},
+      "i2195": {"rank": 1},
+      "i2196": {"rank": 2},
+      "i2194": {"rank": 4},
+      "i2198": {"rank": 1},
+      "i2197": {"rank": 5},
+      "i2186": {"rank": 3},
+      "i2200": {"rank": 1},
+      "i2201": {"rank": 2},
+      "i2202": {"rank": 3},
+      "i2203": {"rank": 4},
+      "i2204": {"rank": 5},
+      "i2205": {"rank": 6},
+      "i2206": {"rank": 7},
+      "i2199": {"rank": 4},
+      "i2153": {"rank": 1},
+      "i2210": {"rank": 1},
+      "i2211": {"rank": 2},
+      "i2212": {"rank": 3},
+      "i2213": {"rank": 4},
+      "i2214": {"rank": 5},
+      "i2209": {"rank": 1},
+      "i2216": {"rank": 1},
+      "i2217": {"rank": 2},
+      "i2218": {"rank": 3},
+      "i2219": {"rank": 4},
+      "i2215": {"rank": 2},
+      "i2221": {"rank": 1},
+      "i2222": {"rank": 2},
+      "i2220": {"rank": 3},
+      "i2208": {"rank": 1},
+      "i2225": {"rank": 1},
+      "i2226": {"rank": 2},
+      "i2227": {"rank": 3},
+      "i2224": {"rank": 1},
+      "i2229": {"rank": 1},
+      "i2230": {"rank": 2},
+      "i2228": {"rank": 2},
+      "i2232": {"rank": 1},
+      "i2233": {"rank": 2},
+      "i2231": {"rank": 3},
+      "i2235": {"rank": 1},
+      "i2236": {"rank": 2},
+      "i2234": {"rank": 4},
+      "i2223": {"rank": 2},
+      "i2238": {"rank": 1},
+      "i2239": {"rank": 2},
+      "i2240": {"rank": 3},
+      "i2241": {"rank": 4},
+      "i2242": {"rank": 5},
+      "i2243": {"rank": 6},
+      "i2237": {"rank": 3},
+      "i2207": {"rank": 2},
+      "i2246": {"rank": 1},
+      "i2247": {"rank": 2},
+      "i2248": {"rank": 3},
+      "i2249": {"rank": 4},
+      "i2250": {"rank": 5},
+      "i2251": {"rank": 6},
+      "i2245": {"rank": 1},
+      "i2253": {"rank": 1},
+      "i2254": {"rank": 2},
+      "i2255": {"rank": 3},
+      "i2252": {"rank": 2},
+      "i2257": {"rank": 1},
+      "i2258": {"rank": 2},
+      "i2259": {"rank": 3},
+      "i2256": {"rank": 3},
+      "i2261": {"rank": 1},
+      "i2262": {"rank": 2},
+      "i2263": {"rank": 3},
+      "i2260": {"rank": 4},
+      "i2244": {"rank": 3},
+      "i2266": {"rank": 1},
+      "i2267": {"rank": 2},
+      "i2268": {"rank": 3},
+      "i2265": {"rank": 1},
+      "i2270": {"rank": 1},
+      "i2271": {"rank": 2},
+      "i2272": {"rank": 3},
+      "i2269": {"rank": 2},
+      "i2274": {"rank": 1},
+      "i2275": {"rank": 2},
+      "i2276": {"rank": 3},
+      "i2273": {"rank": 3},
+      "i2264": {"rank": 4},
+      "i2277": {"rank": 5},
+      "p84": {"rank": 3},
+      "i2405": {"rank": 1},
+      "i2406": {"rank": 2},
+      "i2407": {"rank": 3},
+      "i2404": {"rank": 1},
+      "i2410": {"rank": 1},
+      "i2411": {"rank": 2},
+      "i2412": {"rank": 3},
+      "i2413": {"rank": 4},
+      "i2414": {"rank": 5},
+      "i2409": {"rank": 1},
+      "i2416": {"rank": 1},
+      "i2417": {"rank": 2},
+      "i2418": {"rank": 3},
+      "i2420": {"rank": 1},
+      "i2421": {"rank": 2},
+      "i2419": {"rank": 4},
+      "i2422": {"rank": 5},
+      "i2415": {"rank": 2},
+      "i2424": {"rank": 1},
+      "i2423": {"rank": 3},
+      "i2426": {"rank": 1},
+      "i2427": {"rank": 2},
+      "i2428": {"rank": 3},
+      "i2429": {"rank": 4},
+      "i2425": {"rank": 4},
+      "i2431": {"rank": 1},
+      "i2432": {"rank": 2},
+      "i2430": {"rank": 5},
+      "i2434": {"rank": 1},
+      "i2435": {"rank": 2},
+      "i2433": {"rank": 6},
+      "i2408": {"rank": 2},
+      "i2438": {"rank": 1},
+      "i2439": {"rank": 2},
+      "i2437": {"rank": 1},
+      "i2441": {"rank": 1},
+      "i2440": {"rank": 2},
+      "i2443": {"rank": 1},
+      "i2442": {"rank": 3},
+      "i2445": {"rank": 1},
+      "i2446": {"rank": 2},
+      "i2444": {"rank": 4},
+      "i2448": {"rank": 1},
+      "i2447": {"rank": 5},
+      "i2436": {"rank": 3},
+      "i2450": {"rank": 1},
+      "i2451": {"rank": 2},
+      "i2452": {"rank": 3},
+      "i2453": {"rank": 4},
+      "i2454": {"rank": 5},
+      "i2455": {"rank": 6},
+      "i2456": {"rank": 7},
+      "i2449": {"rank": 4},
+      "i2403": {"rank": 1},
+      "i2460": {"rank": 1},
+      "i2461": {"rank": 2},
+      "i2462": {"rank": 3},
+      "i2463": {"rank": 4},
+      "i2464": {"rank": 5},
+      "i2459": {"rank": 1},
+      "i2466": {"rank": 1},
+      "i2467": {"rank": 2},
+      "i2468": {"rank": 3},
+      "i2469": {"rank": 4},
+      "i2465": {"rank": 2},
+      "i2471": {"rank": 1},
+      "i2472": {"rank": 2},
+      "i2470": {"rank": 3},
+      "i2458": {"rank": 1},
+      "i2475": {"rank": 1},
+      "i2476": {"rank": 2},
+      "i2477": {"rank": 3},
+      "i2474": {"rank": 1},
+      "i2479": {"rank": 1},
+      "i2480": {"rank": 2},
+      "i2478": {"rank": 2},
+      "i2482": {"rank": 1},
+      "i2483": {"rank": 2},
+      "i2481": {"rank": 3},
+      "i2485": {"rank": 1},
+      "i2486": {"rank": 2},
+      "i2484": {"rank": 4},
+      "i2473": {"rank": 2},
+      "i2488": {"rank": 1},
+      "i2489": {"rank": 2},
+      "i2490": {"rank": 3},
+      "i2491": {"rank": 4},
+      "i2492": {"rank": 5},
+      "i2493": {"rank": 6},
+      "i2487": {"rank": 3},
+      "i2457": {"rank": 2},
+      "i2496": {"rank": 1},
+      "i2497": {"rank": 2},
+      "i2498": {"rank": 3},
+      "i2499": {"rank": 4},
+      "i2500": {"rank": 5},
+      "i2501": {"rank": 6},
+      "i2495": {"rank": 1},
+      "i2503": {"rank": 1},
+      "i2504": {"rank": 2},
+      "i2505": {"rank": 3},
+      "i2502": {"rank": 2},
+      "i2507": {"rank": 1},
+      "i2508": {"rank": 2},
+      "i2509": {"rank": 3},
+      "i2506": {"rank": 3},
+      "i2511": {"rank": 1},
+      "i2512": {"rank": 2},
+      "i2513": {"rank": 3},
+      "i2510": {"rank": 4},
+      "i2494": {"rank": 3},
+      "i2516": {"rank": 1},
+      "i2517": {"rank": 2},
+      "i2518": {"rank": 3},
+      "i2515": {"rank": 1},
+      "i2514": {"rank": 4},
+      "i2527": {"rank": 5, "position": [291.99426556054686, 101.83236694335938, 1]},
+      "p86": {"rank": 4},
+      "i2030": {"rank": 1},
+      "i2031": {"rank": 2},
+      "i2032": {"rank": 3},
+      "i2029": {"rank": 1},
+      "i2035": {"rank": 1},
+      "i2036": {"rank": 2},
+      "i2037": {"rank": 3},
+      "i2038": {"rank": 4},
+      "i2039": {"rank": 5},
+      "i2034": {"rank": 1},
+      "i2033": {"rank": 2},
+      "i2063": {"rank": 1},
+      "i2064": {"rank": 2},
+      "i2062": {"rank": 1},
+      "i2068": {"rank": 1},
+      "i2067": {"rank": 2},
+      "i2073": {"rank": 1},
+      "i2072": {"rank": 3},
+      "i2061": {"rank": 3},
+      "i2075": {"rank": 1},
+      "i2076": {"rank": 2},
+      "i2077": {"rank": 3},
+      "i2078": {"rank": 4},
+      "i2079": {"rank": 5},
+      "i2080": {"rank": 6},
+      "i2081": {"rank": 7},
+      "i2074": {"rank": 4},
+      "i2524": {"rank": 1},
+      "i2525": {"rank": 2},
+      "i2526": {"rank": 3},
+      "i2523": {"rank": 5},
+      "i2028": {"rank": 1},
+      "i2085": {"rank": 1},
+      "i2086": {"rank": 2},
+      "i2087": {"rank": 3},
+      "i2088": {"rank": 4},
+      "i2089": {"rank": 5},
+      "i2084": {"rank": 1},
+      "i2091": {"rank": 1},
+      "i2092": {"rank": 2},
+      "i2093": {"rank": 3},
+      "i2094": {"rank": 4},
+      "i2090": {"rank": 2},
+      "i2096": {"rank": 1},
+      "i2097": {"rank": 2},
+      "i2095": {"rank": 3},
+      "i2083": {"rank": 1},
+      "i2100": {"rank": 1},
+      "i2101": {"rank": 2},
+      "i2102": {"rank": 3},
+      "i2099": {"rank": 1},
+      "i2104": {"rank": 1},
+      "i2105": {"rank": 2},
+      "i2103": {"rank": 2},
+      "i2107": {"rank": 1},
+      "i2108": {"rank": 2},
+      "i2106": {"rank": 3},
+      "i2110": {"rank": 1},
+      "i2111": {"rank": 2},
+      "i2109": {"rank": 4},
+      "i2098": {"rank": 2},
+      "i2113": {"rank": 1},
+      "i2114": {"rank": 2},
+      "i2115": {"rank": 3},
+      "i2116": {"rank": 4},
+      "i2117": {"rank": 5},
+      "i2118": {"rank": 6},
+      "i2112": {"rank": 3},
+      "i2082": {"rank": 1},
+      "i2121": {"rank": 1},
+      "i2122": {"rank": 2},
+      "i2123": {"rank": 3},
+      "i2124": {"rank": 4},
+      "i2125": {"rank": 5},
+      "i2126": {"rank": 6},
+      "i2120": {"rank": 2},
+      "i2132": {"rank": 1},
+      "i2133": {"rank": 2},
+      "i2134": {"rank": 3},
+      "i2131": {"rank": 3},
+      "i2136": {"rank": 1},
+      "i2137": {"rank": 2},
+      "i2138": {"rank": 3},
+      "i2135": {"rank": 4},
+      "i2119": {"rank": 2},
+      "i2141": {"rank": 1},
+      "i2142": {"rank": 2},
+      "i2143": {"rank": 3},
+      "i2140": {"rank": 1},
+      "i2145": {"rank": 1},
+      "i2146": {"rank": 2},
+      "i2147": {"rank": 3},
+      "i2144": {"rank": 2},
+      "i2149": {"rank": 1},
+      "i2150": {"rank": 2},
+      "i2151": {"rank": 3},
+      "i2148": {"rank": 3},
+      "i2139": {"rank": 3},
+      "i2152": {"rank": 4},
+      "i2520": {"rank": 1},
+      "i2521": {"rank": 2},
+      "i2522": {"rank": 3},
+      "i2519": {"rank": 5},
+      "p83": {"rank": 5},
+      "i1780": {"rank": 1},
+      "i1781": {"rank": 2},
+      "i1782": {"rank": 3},
+      "i1779": {"rank": 1},
+      "i1785": {"rank": 1},
+      "i1786": {"rank": 2},
+      "i1787": {"rank": 3},
+      "i1788": {"rank": 4},
+      "i1789": {"rank": 5},
+      "i1784": {"rank": 1},
+      "i1791": {"rank": 1},
+      "i1792": {"rank": 2},
+      "i1793": {"rank": 3},
+      "i1795": {"rank": 1},
+      "i1796": {"rank": 2},
+      "i1794": {"rank": 4},
+      "i1797": {"rank": 5},
+      "i1790": {"rank": 2},
+      "i1799": {"rank": 1},
+      "i1798": {"rank": 3},
+      "i1801": {"rank": 1},
+      "i1802": {"rank": 2},
+      "i1803": {"rank": 3},
+      "i1804": {"rank": 4},
+      "i1800": {"rank": 4},
+      "i1806": {"rank": 1},
+      "i1807": {"rank": 2},
+      "i1805": {"rank": 5},
+      "i1809": {"rank": 1},
+      "i1810": {"rank": 2},
+      "i1808": {"rank": 6},
+      "i1783": {"rank": 2},
+      "i1813": {"rank": 1},
+      "i1814": {"rank": 2},
+      "i1812": {"rank": 1},
+      "i1816": {"rank": 1},
+      "i1815": {"rank": 2},
+      "i1818": {"rank": 1},
+      "i1817": {"rank": 3},
+      "i1820": {"rank": 1},
+      "i1821": {"rank": 2},
+      "i1819": {"rank": 4},
+      "i1823": {"rank": 1},
+      "i1822": {"rank": 5},
+      "i1811": {"rank": 3},
+      "i1825": {"rank": 1},
+      "i1826": {"rank": 2},
+      "i1827": {"rank": 3},
+      "i1828": {"rank": 4},
+      "i1829": {"rank": 5},
+      "i1830": {"rank": 6},
+      "i1831": {"rank": 7},
+      "i1824": {"rank": 4},
+      "i1778": {"rank": 1},
+      "i1835": {"rank": 1},
+      "i1836": {"rank": 2},
+      "i1837": {"rank": 3},
+      "i1838": {"rank": 4},
+      "i1839": {"rank": 5},
+      "i1834": {"rank": 1},
+      "i1841": {"rank": 1},
+      "i1842": {"rank": 2},
+      "i1843": {"rank": 3},
+      "i1844": {"rank": 4},
+      "i1840": {"rank": 2},
+      "i1846": {"rank": 1},
+      "i1847": {"rank": 2},
+      "i1845": {"rank": 3},
+      "i1833": {"rank": 1},
+      "i1850": {"rank": 1},
+      "i1851": {"rank": 2},
+      "i1852": {"rank": 3},
+      "i1849": {"rank": 1},
+      "i1854": {"rank": 1},
+      "i1855": {"rank": 2},
+      "i1853": {"rank": 2},
+      "i1857": {"rank": 1},
+      "i1858": {"rank": 2},
+      "i1856": {"rank": 3},
+      "i1860": {"rank": 1},
+      "i1861": {"rank": 2},
+      "i1859": {"rank": 4},
+      "i1848": {"rank": 2},
+      "i1863": {"rank": 1},
+      "i1864": {"rank": 2},
+      "i1865": {"rank": 3},
+      "i1866": {"rank": 4},
+      "i1867": {"rank": 5},
+      "i1868": {"rank": 6},
+      "i1862": {"rank": 3},
+      "i1832": {"rank": 2},
+      "i1871": {"rank": 1},
+      "i1872": {"rank": 2},
+      "i1873": {"rank": 3},
+      "i1874": {"rank": 4},
+      "i1875": {"rank": 5},
+      "i1876": {"rank": 6},
+      "i1870": {"rank": 1},
+      "i1878": {"rank": 1},
+      "i1879": {"rank": 2},
+      "i1880": {"rank": 3},
+      "i1877": {"rank": 2},
+      "i1882": {"rank": 1},
+      "i1883": {"rank": 2},
+      "i1884": {"rank": 3},
+      "i1881": {"rank": 3},
+      "i1886": {"rank": 1},
+      "i1887": {"rank": 2},
+      "i1888": {"rank": 3},
+      "i1885": {"rank": 4},
+      "i1869": {"rank": 3},
+      "i1891": {"rank": 1},
+      "i1892": {"rank": 2},
+      "i1893": {"rank": 3},
+      "i1890": {"rank": 1},
+      "i1895": {"rank": 1},
+      "i1896": {"rank": 2},
+      "i1897": {"rank": 3},
+      "i1894": {"rank": 2},
+      "i1899": {"rank": 1},
+      "i1900": {"rank": 2},
+      "i1901": {"rank": 3},
+      "i1898": {"rank": 3},
+      "i1889": {"rank": 4},
+      "i1902": {"rank": 5},
+      "p81": {"rank": 6},
+      "p144": {"rank": 7},
+      "p108": {}
+    };
+    var layout = {
+
+
+
+
+    "i29165":{"rank":"1"},
+    "i31468":{"rank":"2"},
+    "i5672":{"rank":"-8"},
+    "i22466":{"rank":"1"},
+    "i22467":{"rank":"2"},
+    "i22471":{"rank":"3"},
+    "i22498":{"rank":"4"},
+    "i22499":{"rank":"1"},
+    "i22755":{"rank":"2"},
+    "i23301":{"rank":"3"},
+    "i25078":{"rank":"4"},
+    "i26106":{"rank":"5"},
+    "i26324":{"rank":"1"},
+    "i28175":{"rank":"2"},
+    "i26323":{"rank":"6"},
+    "i26699":{"rank":"7"},
+    "i26735":{"rank":"8"},
+    "i26830":{"rank":"9"},
+    "i26927":{"rank":"10"},
+    "i27825":{"rank":"11"},
+    "i27854":{"rank":"12"},
+    "i28165":{"rank":"13"},
+    "i23300":{"rank":"5"},
+    "i22497":{"rank":"1"},
+    "i25071":{"rank":"2"},
+    "i25072":{"rank":"3"},
+    "i25912":{"rank":"4"},
+    "i25993":{"rank":"5"},
+    "i23348":{"rank":"6"},
+    "i25066":{"rank":"1"},
+    "i25067":{"rank":"2"},
+    "i26292":{"rank":"3"},
+    "i23349":{"rank":"7"},
+    "i25127":{"rank":"1"},
+    "i25146":{"rank":"2"},
+    "i26700":{"rank":"3"},
+    "i23350":{"rank":"8"},
+    "i25068":{"rank":"1"},
+    "i25165":{"rank":"2"},
+    "i25910":{"rank":"3"},
+    "i25932":{"rank":"4"},
+    "i26364":{"rank":"5"},
+    "i26366":{"rank":"6"},
+    "i26371":{"rank":"7"},
+    "i27760":{"rank":"8"},
+    "i27795":{"rank":"9"},
+    "i27796":{"rank":"10"},
+    "i28158":{"rank":"11"},
+    "i28917":{"rank":"12"},
+    "i29009":{"rank":"13"},
+    "i29073":{"rank":"14"},
+    "i23351":{"rank":"9"},
+    "i23367":{"rank":"1"},
+    "i23366":{"rank":"10"},
+    "i23615":{"rank":"1"},
+    "i23620":{"rank":"2"},
+    "i23621":{"rank":"3"},
+    "i23622":{"rank":"4"},
+    "i25166":{"rank":"5"},
+    "i25196":{"rank":"6"},
+    "i25281":{"rank":"7"},
+    "i26734":{"rank":"8"},
+    "i27108":{"rank":"9"},
+    "i27797":{"rank":"10"},
+    "i28912":{"rank":"11"},
+    "i23614":{"rank":"11"},
+    "i23623":{"rank":"1"},
+    "i23624":{"rank":"2"},
+    "i25700":{"rank":"3"},
+    "i23616":{"rank":"12"},
+    "i25161":{"rank":"1"},
+    "i25162":{"rank":"2"},
+    "i25163":{"rank":"3"},
+    "i25164":{"rank":"4"},
+    "i25167":{"rank":"5"},
+    "i25169":{"rank":"6"},
+    "i25973":{"rank":"7"},
+    "i25992":{"rank":"8"},
+    "i26351":{"rank":"9"},
+    "i26738":{"rank":"10"},
+    "i23617":{"rank":"13"},
+    "i26331":{"rank":"1"},
+    "i28176":{"rank":"2"},
+    "i23618":{"rank":"14"},
+    "i25154":{"rank":"1"},
+    "i25155":{"rank":"2"},
+    "i25156":{"rank":"3"},
+    "i25157":{"rank":"4"},
+    "i25158":{"rank":"5"},
+    "i25918":{"rank":"6"},
+    "i25971":{"rank":"7"},
+    "i26828":{"rank":"8"},
+    "i23619":{"rank":"15"},
+    "i23828":{"rank":"1"},
+    "i24957":{"rank":"2"},
+    "i25170":{"rank":"3"},
+    "i25390":{"rank":"4"},
+    "i25402":{"rank":"5"},
+    "i25942":{"rank":"6"},
+    "i25984":{"rank":"7"},
+    "i26291":{"rank":"8"},
+    "i26322":{"rank":"9"},
+    "i26325":{"rank":"10"},
+    "i26330":{"rank":"11"},
+    "i27886":{"rank":"12"},
+    "i28157":{"rank":"13"},
+    "i28875":{"rank":"14"},
+    "i23827":{"rank":"16"},
+    "i23876":{"rank":"1"},
+    "i25032":{"rank":"2"},
+    "i27793":{"rank":"3"},
+    "i23866":{"rank":"1"},
+    "i23878":{"rank":"1"},
+    "i24245":{"rank":"2"},
+    "i26220":{"rank":"3"},
+    "i26221":{"rank":"4"},
+    "i27792":{"rank":"5"},
+    "i27826":{"rank":"6"},
+    "i27950":{"rank":"7"},
+    "i28350":{"rank":"8"},
+    "i28896":{"rank":"9"},
+    "i23867":{"rank":"2"},
+    "i23869":{"rank":"3"},
+    "i27895":{"rank":"1"},
+    "i23870":{"rank":"4"},
+    "i23888":{"rank":"5"},
+    "i25120":{"rank":"6"},
+    "i27893":{"rank":"1"},
+    "i27894":{"rank":"2"},
+    "i25313":{"rank":"7"},
+    "i25349":{"rank":"8"},
+    "i27794":{"rank":"9"},
+    "i28159":{"rank":"10"},
+    "i23865":{"rank":"17"},
+    "i23877":{"rank":"18"},
+    "i25168":{"rank":"1"},
+    "i25233":{"rank":"2"},
+    "i25235":{"rank":"3"},
+    "i24976":{"rank":"19"},
+    "i25160":{"rank":"1"},
+    "i25972":{"rank":"2"},
+    "i24977":{"rank":"20"},
+    "i25258":{"rank":"1"},
+    "i26736":{"rank":"2"},
+    "i24978":{"rank":"21"},
+    "i25159":{"rank":"1"},
+    "i24979":{"rank":"22"},
+    "i25034":{"rank":"1"},
+    "i25036":{"rank":"2"},
+    "i25038":{"rank":"3"},
+    "i25039":{"rank":"4"},
+    "i25040":{"rank":"5"},
+    "i25041":{"rank":"6"},
+    "i25042":{"rank":"7"},
+    "i25043":{"rank":"8"},
+    "i25044":{"rank":"9"},
+    "i25045":{"rank":"10"},
+    "i25046":{"rank":"11"},
+    "i25047":{"rank":"12"},
+    "i25048":{"rank":"13"},
+    "i25049":{"rank":"14"},
+    "i25050":{"rank":"15"},
+    "i25052":{"rank":"16"},
+    "i25053":{"rank":"17"},
+    "i25054":{"rank":"18"},
+    "i25056":{"rank":"19"},
+    "i25058":{"rank":"20"},
+    "i25059":{"rank":"21"},
+    "i25282":{"rank":"22"},
+    "i25312":{"rank":"23"},
+    "i26147":{"rank":"24"},
+    "i26224":{"rank":"25"},
+    "i26225":{"rank":"26"},
+    "i27887":{"rank":"27"},
+    "i28895":{"rank":"28"},
+    "i28949":{"rank":"29"},
+    "i25033":{"rank":"23"},
+    "i25035":{"rank":"1"},
+    "i25037":{"rank":"2"},
+    "i25057":{"rank":"3"},
+    "i25060":{"rank":"4"},
+    "i25061":{"rank":"5"},
+    "i25062":{"rank":"6"},
+    "i25063":{"rank":"7"},
+    "i25064":{"rank":"8"},
+    "i25065":{"rank":"9"},
+    "i25051":{"rank":"24"},
+    "i25070":{"rank":"1"},
+    "i25069":{"rank":"25"},
+    "i25915":{"rank":"1"},
+    "i25914":{"rank":"26"},
+    "i25917":{"rank":"1"},
+    "i25916":{"rank":"27"},
+    "i28943":{"rank":"28"},
+    "i28944":{"rank":"29"},
+    "i28950":{"rank":"30"},
+    "i29044":{"rank":"1"},
+    "i29051":{"rank":"2"},
+    "i29052":{"rank":"3"},
+    "i29050":{"rank":"31"},
+    "i29298":{"rank":"32"},
+    "i30050":{"rank":"1"},
+    "i30051":{"rank":"2"},
+    "i30052":{"rank":"3"},
+    "i30053":{"rank":"4"},
+    "i30054":{"rank":"5"},
+    "i30049":{"rank":"33"},
+    "p583":{"rank":"1"},
+    "i19926":{"rank":"1"},
+    "i19937":{"rank":"1"},
+    "i19927":{"rank":"1"},
+    "i19928":{"rank":"2"},
+    "i19929":{"rank":"3"},
+    "i19930":{"rank":"4"},
+    "i19931":{"rank":"5"},
+    "i19932":{"rank":"6"},
+    "i19933":{"rank":"7"},
+    "i19934":{"rank":"8"},
+    "i19935":{"rank":"9"},
+    "i19936":{"rank":"10"},
+    "i19938":{"rank":"2"},
+    "i19925":{"rank":"1"},
+    "p554":{"rank":"2"},
+    "i26414":{"rank":"1"},
+    "i26487":{"rank":"1"},
+    "i26488":{"rank":"2"},
+    "i26489":{"rank":"3"},
+    "i26580":{"rank":"4"},
+    "i26415":{"rank":"2"},
+    "i26416":{"rank":"3"},
+    "i26417":{"rank":"4"},
+    "i26398":{"rank":"1"},
+    "i26401":{"rank":"1"},
+    "i26402":{"rank":"2"},
+    "i26404":{"rank":"3"},
+    "i26407":{"rank":"4"},
+    "i26411":{"rank":"5"},
+    "i26412":{"rank":"6"},
+    "i26422":{"rank":"7"},
+    "i26832":{"rank":"1"},
+    "i26833":{"rank":"2"},
+    "i26525":{"rank":"8"},
+    "i26528":{"rank":"9"},
+    "i26556":{"rank":"10"},
+    "i26399":{"rank":"2"},
+    "i26585":{"rank":"1"},
+    "i26586":{"rank":"2"},
+    "i27746":{"rank":"3"},
+    "i28064":{"rank":"4"},
+    "i26400":{"rank":"3"},
+    "i26503":{"rank":"1"},
+    "i26557":{"rank":"2"},
+    "i26449":{"rank":"4"},
+    "i27726":{"rank":"1"},
+    "i27728":{"rank":"2"},
+    "i27730":{"rank":"3"},
+    "i27725":{"rank":"5"},
+    "p615":{"rank":"3"},
+    "i18940":{"rank":"1"},
+    "i19177":{"rank":"1"},
+    "i19175":{"rank":"1"},
+    "i19172":{"rank":"2"},
+    "i24714":{"rank":"1"},
+    "i24715":{"rank":"2"},
+    "i24716":{"rank":"3"},
+    "i24717":{"rank":"4"},
+    "i24718":{"rank":"5"},
+    "i24719":{"rank":"6"},
+    "i24720":{"rank":"7"},
+    "i24721":{"rank":"8"},
+    "i24722":{"rank":"9"},
+    "i24723":{"rank":"10"},
+    "i24724":{"rank":"11"},
+    "i24725":{"rank":"12"},
+    "i24726":{"rank":"13"},
+    "i24727":{"rank":"14"},
+    "i24728":{"rank":"15"},
+    "i24729":{"rank":"16"},
+    "i24730":{"rank":"17"},
+    "i24731":{"rank":"18"},
+    "i24732":{"rank":"19"},
+    "i24733":{"rank":"20"},
+    "i24734":{"rank":"21"},
+    "i24735":{"rank":"22"},
+    "i24736":{"rank":"23"},
+    "i24737":{"rank":"24"},
+    "i24738":{"rank":"25"},
+    "i24739":{"rank":"26"},
+    "i24740":{"rank":"27"},
+    "i24713":{"rank":"1"},
+    "i24712":{"rank":"3"},
+    "i24741":{"rank":"4"},
+    "i24745":{"rank":"1"},
+    "i24746":{"rank":"2"},
+    "i24747":{"rank":"3"},
+    "i24744":{"rank":"1"},
+    "i24749":{"rank":"1"},
+    "i24750":{"rank":"2"},
+    "i24751":{"rank":"3"},
+    "i24748":{"rank":"2"},
+    "i24753":{"rank":"1"},
+    "i24754":{"rank":"2"},
+    "i24755":{"rank":"3"},
+    "i24752":{"rank":"3"},
+    "i24743":{"rank":"1"},
+    "i24762":{"rank":"1"},
+    "i24763":{"rank":"2"},
+    "i24764":{"rank":"3"},
+    "i24761":{"rank":"1"},
+    "i24766":{"rank":"1"},
+    "i24767":{"rank":"2"},
+    "i24768":{"rank":"3"},
+    "i24765":{"rank":"2"},
+    "i24770":{"rank":"1"},
+    "i24771":{"rank":"2"},
+    "i24772":{"rank":"3"},
+    "i24773":{"rank":"4"},
+    "i24769":{"rank":"3"},
+    "i24776":{"rank":"1"},
+    "i24777":{"rank":"2"},
+    "i24778":{"rank":"3"},
+    "i24775":{"rank":"1"},
+    "i24779":{"rank":"2"},
+    "i24774":{"rank":"4"},
+    "i24760":{"rank":"2"},
+    "i24784":{"rank":"1"},
+    "i24785":{"rank":"2"},
+    "i24783":{"rank":"1"},
+    "i24782":{"rank":"1"},
+    "i24790":{"rank":"1"},
+    "i24791":{"rank":"2"},
+    "i24792":{"rank":"3"},
+    "i24789":{"rank":"2"},
+    "i24794":{"rank":"1"},
+    "i24795":{"rank":"2"},
+    "i24796":{"rank":"3"},
+    "i24793":{"rank":"3"},
+    "i24798":{"rank":"1"},
+    "i24799":{"rank":"2"},
+    "i24800":{"rank":"3"},
+    "i24797":{"rank":"4"},
+    "i24802":{"rank":"1"},
+    "i24803":{"rank":"2"},
+    "i24804":{"rank":"3"},
+    "i24801":{"rank":"5"},
+    "i24806":{"rank":"1"},
+    "i24807":{"rank":"2"},
+    "i24808":{"rank":"3"},
+    "i24805":{"rank":"6"},
+    "i24810":{"rank":"1"},
+    "i24811":{"rank":"2"},
+    "i24812":{"rank":"3"},
+    "i24809":{"rank":"7"},
+    "i24818":{"rank":"1"},
+    "i24819":{"rank":"2"},
+    "i24820":{"rank":"3"},
+    "i24817":{"rank":"8"},
+    "i24826":{"rank":"1"},
+    "i24827":{"rank":"2"},
+    "i24828":{"rank":"3"},
+    "i24825":{"rank":"9"},
+    "i24830":{"rank":"1"},
+    "i24831":{"rank":"2"},
+    "i24832":{"rank":"3"},
+    "i24829":{"rank":"10"},
+    "i24834":{"rank":"1"},
+    "i24835":{"rank":"2"},
+    "i24836":{"rank":"3"},
+    "i24833":{"rank":"11"},
+    "i24781":{"rank":"1"},
+    "i24844":{"rank":"1"},
+    "i24843":{"rank":"1"},
+    "i24846":{"rank":"1"},
+    "i24848":{"rank":"1"},
+    "i24849":{"rank":"2"},
+    "i24850":{"rank":"3"},
+    "i24847":{"rank":"2"},
+    "i24845":{"rank":"2"},
+    "i24852":{"rank":"1"},
+    "i24851":{"rank":"3"},
+    "i24855":{"rank":"4"},
+    "i24864":{"rank":"1"},
+    "i24865":{"rank":"2"},
+    "i24863":{"rank":"5"},
+    "i24869":{"rank":"1"},
+    "i24868":{"rank":"6"},
+    "i24871":{"rank":"1"},
+    "i24870":{"rank":"7"},
+    "i24873":{"rank":"1"},
+    "i24874":{"rank":"2"},
+    "i24875":{"rank":"3"},
+    "i24872":{"rank":"8"},
+    "i24842":{"rank":"2"},
+    "i24780":{"rank":"3"},
+    "i24877":{"rank":"1"},
+    "i24876":{"rank":"4"},
+    "i24890":{"rank":"1"},
+    "i24891":{"rank":"2"},
+    "i24889":{"rank":"1"},
+    "i24893":{"rank":"1"},
+    "i24892":{"rank":"2"},
+    "i24888":{"rank":"1"},
+    "i24878":{"rank":"5"},
+    "i24903":{"rank":"1"},
+    "i26214":{"rank":"1"},
+    "i26216":{"rank":"2"},
+    "i26217":{"rank":"3"},
+    "i26219":{"rank":"1"},
+    "i26218":{"rank":"4"},
+    "i24904":{"rank":"2"},
+    "i24906":{"rank":"1"},
+    "i24907":{"rank":"2"},
+    "i24908":{"rank":"3"},
+    "i24909":{"rank":"4"},
+    "i24910":{"rank":"5"},
+    "i24912":{"rank":"6"},
+    "i24905":{"rank":"3"},
+    "i24913":{"rank":"4"},
+    "i24902":{"rank":"6"},
+    "i24742":{"rank":"5"},
+    "i24914":{"rank":"6"},
+    "i24917":{"rank":"1"},
+    "i24918":{"rank":"2"},
+    "i24916":{"rank":"1"},
+    "i24919":{"rank":"2"},
+    "i24921":{"rank":"1"},
+    "i24922":{"rank":"2"},
+    "i24923":{"rank":"3"},
+    "i24924":{"rank":"4"},
+    "i24920":{"rank":"3"},
+    "i24926":{"rank":"1"},
+    "i24927":{"rank":"2"},
+    "i24925":{"rank":"4"},
+    "i24929":{"rank":"1"},
+    "i24930":{"rank":"2"},
+    "i24928":{"rank":"5"},
+    "i24915":{"rank":"7"},
+    "i24933":{"rank":"1"},
+    "i24932":{"rank":"1"},
+    "i24935":{"rank":"1"},
+    "i24936":{"rank":"2"},
+    "i24934":{"rank":"2"},
+    "i24941":{"rank":"3"},
+    "i24942":{"rank":"4"},
+    "i24931":{"rank":"8"},
+    "i24944":{"rank":"1"},
+    "i24945":{"rank":"2"},
+    "i24946":{"rank":"3"},
+    "i24943":{"rank":"9"},
+    "i24948":{"rank":"1"},
+    "i24949":{"rank":"2"},
+    "i24950":{"rank":"3"},
+    "i24951":{"rank":"4"},
+    "i24952":{"rank":"5"},
+    "i24953":{"rank":"6"},
+    "i24947":{"rank":"10"},
+    "i24954":{"rank":"11"},
+    "i24955":{"rank":"12"},
+    "i24956":{"rank":"13"},
+    "p468":{"rank":"4"},
+    "i29924":{"rank":"1"},
+    "i29925":{"rank":"2"},
+    "i29926":{"rank":"3"},
+    "i29921":{"rank":"1"},
+    "i29928":{"rank":"1"},
+    "i29929":{"rank":"2"},
+    "i29930":{"rank":"3"},
+    "i29931":{"rank":"4"},
+    "i29932":{"rank":"5"},
+    "i29933":{"rank":"6"},
+    "i29934":{"rank":"7"},
+    "i29935":{"rank":"8"},
+    "i29927":{"rank":"2"},
+    "i29937":{"rank":"1"},
+    "i29938":{"rank":"2"},
+    "i29936":{"rank":"3"},
+    "i29940":{"rank":"1"},
+    "i29961":{"rank":"2"},
+    "i29939":{"rank":"4"},
+    "p655":{"rank":"5"},
+    "i23242":{"rank":"1"},
+    "i23243":{"rank":"2"},
+    "i23244":{"rank":"3"},
+    "i23245":{"rank":"4"},
+    "i23246":{"rank":"5"},
+    "i23247":{"rank":"6"},
+    "i23248":{"rank":"7"},
+    "i23249":{"rank":"8"},
+    "i23250":{"rank":"9"},
+    "i23251":{"rank":"10"},
+    "i23252":{"rank":"11"},
+    "i23253":{"rank":"12"},
+    "i23254":{"rank":"13"},
+    "i23255":{"rank":"14"},
+    "i23256":{"rank":"15"},
+    "i23257":{"rank":"16"},
+    "i23258":{"rank":"17"},
+    "i23259":{"rank":"18"},
+    "i23260":{"rank":"19"},
+    "i23261":{"rank":"20"},
+    "i23262":{"rank":"21"},
+    "i23263":{"rank":"22"},
+    "i23264":{"rank":"23"},
+    "i23265":{"rank":"24"},
+    "i23266":{"rank":"25"},
+    "i23267":{"rank":"26"},
+    "i23268":{"rank":"27"},
+    "i23241":{"rank":"1"},
+    "i22186":{"rank":"1"},
+    "i22187":{"rank":"2"},
+    "i22407":{"rank":"1"},
+    "i22408":{"rank":"2"},
+    "i22409":{"rank":"3"},
+    "i22217":{"rank":"1"},
+    "i22411":{"rank":"1"},
+    "i22412":{"rank":"2"},
+    "i22413":{"rank":"3"},
+    "i22218":{"rank":"2"},
+    "i22415":{"rank":"1"},
+    "i22416":{"rank":"2"},
+    "i22417":{"rank":"3"},
+    "i22219":{"rank":"3"},
+    "i22418":{"rank":"1"},
+    "i22419":{"rank":"2"},
+    "i22420":{"rank":"3"},
+    "i22220":{"rank":"4"},
+    "i22216":{"rank":"1"},
+    "i22223":{"rank":"1"},
+    "i22421":{"rank":"2"},
+    "i22422":{"rank":"3"},
+    "i22222":{"rank":"1"},
+    "i22423":{"rank":"1"},
+    "i22424":{"rank":"2"},
+    "i22425":{"rank":"3"},
+    "i22224":{"rank":"2"},
+    "i22426":{"rank":"1"},
+    "i22427":{"rank":"2"},
+    "i22428":{"rank":"3"},
+    "i22429":{"rank":"4"},
+    "i22225":{"rank":"3"},
+    "i22430":{"rank":"1"},
+    "i22431":{"rank":"2"},
+    "i22432":{"rank":"3"},
+    "i22227":{"rank":"1"},
+    "i22565":{"rank":"2"},
+    "i22226":{"rank":"4"},
+    "i22221":{"rank":"2"},
+    "i22611":{"rank":"1"},
+    "i22612":{"rank":"2"},
+    "i22610":{"rank":"1"},
+    "i22614":{"rank":"1"},
+    "i22615":{"rank":"2"},
+    "i22613":{"rank":"2"},
+    "i22579":{"rank":"1"},
+    "i22616":{"rank":"1"},
+    "i22617":{"rank":"2"},
+    "i22618":{"rank":"3"},
+    "i22581":{"rank":"2"},
+    "i22619":{"rank":"1"},
+    "i22620":{"rank":"2"},
+    "i22621":{"rank":"3"},
+    "i22583":{"rank":"3"},
+    "i22624":{"rank":"1"},
+    "i22625":{"rank":"2"},
+    "i22626":{"rank":"3"},
+    "i22584":{"rank":"4"},
+    "i22629":{"rank":"1"},
+    "i22630":{"rank":"2"},
+    "i22631":{"rank":"3"},
+    "i22586":{"rank":"5"},
+    "i22636":{"rank":"1"},
+    "i22637":{"rank":"2"},
+    "i22638":{"rank":"3"},
+    "i22587":{"rank":"6"},
+    "i22643":{"rank":"1"},
+    "i22644":{"rank":"2"},
+    "i22645":{"rank":"3"},
+    "i22588":{"rank":"7"},
+    "i22646":{"rank":"1"},
+    "i22647":{"rank":"2"},
+    "i22648":{"rank":"3"},
+    "i22589":{"rank":"8"},
+    "i22649":{"rank":"1"},
+    "i22650":{"rank":"2"},
+    "i22651":{"rank":"3"},
+    "i22590":{"rank":"9"},
+    "i22652":{"rank":"1"},
+    "i22653":{"rank":"2"},
+    "i22654":{"rank":"3"},
+    "i22591":{"rank":"10"},
+    "i22657":{"rank":"1"},
+    "i22658":{"rank":"2"},
+    "i22659":{"rank":"3"},
+    "i22592":{"rank":"11"},
+    "i22671":{"rank":"1"},
+    "i22672":{"rank":"2"},
+    "i22673":{"rank":"3"},
+    "i22593":{"rank":"12"},
+    "i22661":{"rank":"1"},
+    "i22662":{"rank":"2"},
+    "i22663":{"rank":"3"},
+    "i22660":{"rank":"13"},
+    "i22675":{"rank":"1"},
+    "i22676":{"rank":"2"},
+    "i22677":{"rank":"3"},
+    "i22678":{"rank":"4"},
+    "i22674":{"rank":"14"},
+    "i22577":{"rank":"1"},
+    "i22594":{"rank":"1"},
+    "i22189":{"rank":"1"},
+    "i22595":{"rank":"1"},
+    "i22633":{"rank":"1"},
+    "i22634":{"rank":"2"},
+    "i22635":{"rank":"3"},
+    "i22632":{"rank":"2"},
+    "i22231":{"rank":"2"},
+    "i22596":{"rank":"1"},
+    "i22567":{"rank":"3"},
+    "i22597":{"rank":"1"},
+    "i22568":{"rank":"4"},
+    "i22569":{"rank":"5"},
+    "i22598":{"rank":"1"},
+    "i22599":{"rank":"2"},
+    "i22570":{"rank":"6"},
+    "i22602":{"rank":"1"},
+    "i22571":{"rank":"7"},
+    "i22603":{"rank":"1"},
+    "i22572":{"rank":"8"},
+    "i22605":{"rank":"1"},
+    "i22606":{"rank":"2"},
+    "i22573":{"rank":"9"},
+    "i22607":{"rank":"1"},
+    "i22574":{"rank":"10"},
+    "i22608":{"rank":"1"},
+    "i22575":{"rank":"11"},
+    "i22609":{"rank":"1"},
+    "i22576":{"rank":"12"},
+    "i22640":{"rank":"1"},
+    "i22641":{"rank":"2"},
+    "i22642":{"rank":"3"},
+    "i22639":{"rank":"13"},
+    "i22578":{"rank":"2"},
+    "i22229":{"rank":"3"},
+    "i22688":{"rank":"1"},
+    "i22414":{"rank":"4"},
+    "i22505":{"rank":"1"},
+    "i22506":{"rank":"2"},
+    "i22507":{"rank":"3"},
+    "i22516":{"rank":"4"},
+    "i22228":{"rank":"1"},
+    "i22510":{"rank":"1"},
+    "i22511":{"rank":"2"},
+    "i22518":{"rank":"3"},
+    "i22509":{"rank":"2"},
+    "i22523":{"rank":"1"},
+    "i22524":{"rank":"2"},
+    "i22520":{"rank":"1"},
+    "i22543":{"rank":"1"},
+    "i22544":{"rank":"2"},
+    "i22521":{"rank":"2"},
+    "i22539":{"rank":"1"},
+    "i22540":{"rank":"2"},
+    "i22522":{"rank":"3"},
+    "i22541":{"rank":"1"},
+    "i22542":{"rank":"2"},
+    "i22525":{"rank":"4"},
+    "i22527":{"rank":"5"},
+    "i22519":{"rank":"3"},
+    "i22508":{"rank":"5"},
+    "i22547":{"rank":"1"},
+    "i22548":{"rank":"2"},
+    "i22551":{"rank":"1"},
+    "i22553":{"rank":"2"},
+    "i22554":{"rank":"3"},
+    "i22559":{"rank":"4"},
+    "i22560":{"rank":"5"},
+    "i22561":{"rank":"6"},
+    "i22562":{"rank":"7"},
+    "i22550":{"rank":"3"},
+    "i22564":{"rank":"4"},
+    "i22546":{"rank":"6"},
+    "i22188":{"rank":"3"},
+    "i22549":{"rank":"4"},
+    "i22722":{"rank":"1"},
+    "i22724":{"rank":"2"},
+    "i22696":{"rank":"1"},
+    "i22697":{"rank":"2"},
+    "i22725":{"rank":"1"},
+    "i22726":{"rank":"2"},
+    "i22727":{"rank":"3"},
+    "i22747":{"rank":"4"},
+    "i22698":{"rank":"3"},
+    "i22748":{"rank":"1"},
+    "i22757":{"rank":"2"},
+    "i22719":{"rank":"4"},
+    "i22759":{"rank":"1"},
+    "i22760":{"rank":"2"},
+    "i22758":{"rank":"5"},
+    "i22694":{"rank":"5"},
+    "i22765":{"rank":"1"},
+    "i22764":{"rank":"1"},
+    "i22767":{"rank":"1"},
+    "i22768":{"rank":"2"},
+    "i22766":{"rank":"2"},
+    "i22771":{"rank":"1"},
+    "i22772":{"rank":"2"},
+    "i22773":{"rank":"3"},
+    "i22770":{"rank":"3"},
+    "i22774":{"rank":"4"},
+    "i22775":{"rank":"5"},
+    "i22762":{"rank":"6"},
+    "i22780":{"rank":"1"},
+    "i22781":{"rank":"2"},
+    "i22782":{"rank":"3"},
+    "i27785":{"rank":"4"},
+    "i22779":{"rank":"7"},
+    "i22784":{"rank":"1"},
+    "i22785":{"rank":"2"},
+    "i22786":{"rank":"3"},
+    "i22787":{"rank":"4"},
+    "i22788":{"rank":"5"},
+    "i22789":{"rank":"6"},
+    "i22783":{"rank":"8"},
+    "i22790":{"rank":"9"},
+    "i22791":{"rank":"10"},
+    "i22792":{"rank":"11"},
+    "p576":{"rank":"6"},
+    "i3585":{"rank":"1"},
+    "i3587":{"rank":"2"},
+    "i3589":{"rank":"3"},
+    "i3590":{"rank":"4"},
+    "i3591":{"rank":"5"},
+    "i3593":{"rank":"6"},
+    "i3594":{"rank":"7"},
+    "i3595":{"rank":"8"},
+    "i9547":{"rank":"9"},
+    "i18173":{"rank":"10"},
+    "i3584":{"rank":"1"},
+    "i3597":{"rank":"1"},
+    "i3598":{"rank":"2"},
+    "i3599":{"rank":"3"},
+    "i3596":{"rank":"2"},
+    "i3602":{"rank":"1"},
+    "i3603":{"rank":"2"},
+    "i3604":{"rank":"3"},
+    "i3601":{"rank":"3"},
+    "i3606":{"rank":"1"},
+    "i3607":{"rank":"2"},
+    "i3608":{"rank":"3"},
+    "i3609":{"rank":"4"},
+    "i3605":{"rank":"4"},
+    "i3611":{"rank":"1"},
+    "i3613":{"rank":"2"},
+    "i3618":{"rank":"3"},
+    "i3645":{"rank":"4"},
+    "i3648":{"rank":"5"},
+    "i3653":{"rank":"6"},
+    "i3654":{"rank":"7"},
+    "i3655":{"rank":"8"},
+    "i11635":{"rank":"9"},
+    "i11826":{"rank":"10"},
+    "i11827":{"rank":"11"},
+    "i11828":{"rank":"12"},
+    "i11829":{"rank":"13"},
+    "i11830":{"rank":"14"},
+    "i11831":{"rank":"15"},
+    "i11832":{"rank":"16"},
+    "i11833":{"rank":"17"},
+    "i11834":{"rank":"18"},
+    "i11835":{"rank":"19"},
+    "i11836":{"rank":"20"},
+    "i11837":{"rank":"21"},
+    "i11838":{"rank":"22"},
+    "i11839":{"rank":"23"},
+    "i11840":{"rank":"24"},
+    "i11841":{"rank":"25"},
+    "i11842":{"rank":"26"},
+    "i11843":{"rank":"27"},
+    "i11844":{"rank":"28"},
+    "i11845":{"rank":"29"},
+    "i11846":{"rank":"30"},
+    "i11847":{"rank":"31"},
+    "i11848":{"rank":"32"},
+    "i11849":{"rank":"33"},
+    "i11850":{"rank":"34"},
+    "i11851":{"rank":"35"},
+    "i11852":{"rank":"36"},
+    "i11853":{"rank":"37"},
+    "i11854":{"rank":"38"},
+    "i11855":{"rank":"39"},
+    "i11856":{"rank":"40"},
+    "i11857":{"rank":"41"},
+    "i11858":{"rank":"42"},
+    "i11859":{"rank":"43"},
+    "i11860":{"rank":"44"},
+    "i11861":{"rank":"45"},
+    "i11862":{"rank":"46"},
+    "i19793":{"rank":"47"},
+    "i19794":{"rank":"48"},
+    "i3610":{"rank":"5"},
+    "i3658":{"rank":"1"},
+    "i3659":{"rank":"2"},
+    "i3660":{"rank":"3"},
+    "i3661":{"rank":"4"},
+    "i3662":{"rank":"5"},
+    "i3663":{"rank":"6"},
+    "i4096":{"rank":"7"},
+    "i4749":{"rank":"8"},
+    "i4750":{"rank":"9"},
+    "i4751":{"rank":"10"},
+    "i4752":{"rank":"11"},
+    "i3657":{"rank":"6"},
+    "i3666":{"rank":"1"},
+    "i3669":{"rank":"2"},
+    "i3671":{"rank":"3"},
+    "i3672":{"rank":"4"},
+    "i3674":{"rank":"5"},
+    "i3675":{"rank":"6"},
+    "i3676":{"rank":"7"},
+    "i3677":{"rank":"8"},
+    "i3679":{"rank":"9"},
+    "i3680":{"rank":"10"},
+    "i3681":{"rank":"11"},
+    "i3682":{"rank":"12"},
+    "i3684":{"rank":"13"},
+    "i3685":{"rank":"14"},
+    "i3686":{"rank":"15"},
+    "i3687":{"rank":"16"},
+    "i3688":{"rank":"17"},
+    "i3689":{"rank":"18"},
+    "i3691":{"rank":"19"},
+    "i3693":{"rank":"20"},
+    "i3694":{"rank":"21"},
+    "i3695":{"rank":"22"},
+    "i3696":{"rank":"23"},
+    "i3697":{"rank":"24"},
+    "i3698":{"rank":"25"},
+    "i3700":{"rank":"26"},
+    "i3701":{"rank":"27"},
+    "i3702":{"rank":"28"},
+    "i3703":{"rank":"29"},
+    "i3704":{"rank":"30"},
+    "i3705":{"rank":"31"},
+    "i3706":{"rank":"32"},
+    "i3707":{"rank":"33"},
+    "i3710":{"rank":"34"},
+    "i3711":{"rank":"35"},
+    "i3712":{"rank":"36"},
+    "i3713":{"rank":"37"},
+    "i3714":{"rank":"38"},
+    "i3715":{"rank":"39"},
+    "i3716":{"rank":"40"},
+    "i3718":{"rank":"41"},
+    "i3719":{"rank":"42"},
+    "i3724":{"rank":"43"},
+    "i3726":{"rank":"44"},
+    "i3728":{"rank":"45"},
+    "i3729":{"rank":"46"},
+    "i3730":{"rank":"47"},
+    "i3731":{"rank":"48"},
+    "i3732":{"rank":"49"},
+    "i3733":{"rank":"50"},
+    "i3734":{"rank":"51"},
+    "i3735":{"rank":"52"},
+    "i3736":{"rank":"53"},
+    "i3737":{"rank":"54"},
+    "i3739":{"rank":"55"},
+    "i3740":{"rank":"56"},
+    "i3742":{"rank":"57"},
+    "i3744":{"rank":"58"},
+    "i3746":{"rank":"59"},
+    "i3747":{"rank":"60"},
+    "i3748":{"rank":"61"},
+    "i3750":{"rank":"62"},
+    "i3752":{"rank":"63"},
+    "i3753":{"rank":"64"},
+    "i3754":{"rank":"65"},
+    "i3756":{"rank":"66"},
+    "i3757":{"rank":"67"},
+    "i3758":{"rank":"68"},
+    "i3759":{"rank":"69"},
+    "i3760":{"rank":"70"},
+    "i3762":{"rank":"71"},
+    "i3764":{"rank":"72"},
+    "i3765":{"rank":"73"},
+    "i3766":{"rank":"74"},
+    "i3767":{"rank":"75"},
+    "i3768":{"rank":"76"},
+    "i3769":{"rank":"77"},
+    "i3771":{"rank":"78"},
+    "i3772":{"rank":"79"},
+    "i3773":{"rank":"80"},
+    "i3774":{"rank":"81"},
+    "i3775":{"rank":"82"},
+    "i3776":{"rank":"83"},
+    "i3777":{"rank":"84"},
+    "i3778":{"rank":"85"},
+    "i3780":{"rank":"86"},
+    "i3782":{"rank":"87"},
+    "i3783":{"rank":"88"},
+    "i3784":{"rank":"89"},
+    "i3785":{"rank":"90"},
+    "i3788":{"rank":"91"},
+    "i3789":{"rank":"92"},
+    "i3790":{"rank":"93"},
+    "i3792":{"rank":"94"},
+    "i3793":{"rank":"95"},
+    "i9997":{"rank":"96"},
+    "i10016":{"rank":"97"},
+    "i21223":{"rank":"98"},
+    "i23354":{"rank":"99"},
+    "i26298":{"rank":"100"},
+    "i3664":{"rank":"7"},
+    "i9998":{"rank":"1"},
+    "i9999":{"rank":"2"},
+    "i10000":{"rank":"3"},
+    "i10001":{"rank":"4"},
+    "i10002":{"rank":"5"},
+    "i10003":{"rank":"6"},
+    "i10004":{"rank":"7"},
+    "i10005":{"rank":"8"},
+    "i10006":{"rank":"9"},
+    "i10007":{"rank":"10"},
+    "i10008":{"rank":"11"},
+    "i10009":{"rank":"12"},
+    "i10010":{"rank":"13"},
+    "i10011":{"rank":"14"},
+    "i10012":{"rank":"15"},
+    "i10013":{"rank":"16"},
+    "i10014":{"rank":"17"},
+    "i10015":{"rank":"18"},
+    "i10017":{"rank":"19"},
+    "i10018":{"rank":"20"},
+    "i10019":{"rank":"21"},
+    "i10020":{"rank":"22"},
+    "i10021":{"rank":"23"},
+    "i10022":{"rank":"24"},
+    "i10023":{"rank":"25"},
+    "i10024":{"rank":"26"},
+    "i10025":{"rank":"27"},
+    "i10026":{"rank":"28"},
+    "i10027":{"rank":"29"},
+    "i10028":{"rank":"30"},
+    "i14166":{"rank":"31"},
+    "i14167":{"rank":"32"},
+    "i15672":{"rank":"33"},
+    "i9996":{"rank":"8"},
+    "p321":{"rank":"7"},
+    "i20253":{"rank":"1"},
+    "i20252":{"rank":"8"},
+    "p428":{"rank":"-7"},
+    "i21368":{"rank":"1"},
+    "i21369":{"rank":"1"},
+    "i31897":{"rank":"2"},
+    "i31905":{"rank":"1"},
+    "i31904":{"rank":"3"},
+    "i32316":{"rank":"4"},
+    "p570":{"rank":"1"},
+    "i21834":{"rank":"1"},
+    "i21832":{"rank":"1"},
+    "i32307":{"rank":"1"},
+    "i32309":{"rank":"2"},
+    "i33457":{"rank":"3"},
+    "i32123":{"rank":"2"},
+    "i15004":{"rank":"1"},
+    "i33466":{"rank":"1"},
+    "i18101":{"rank":"2"},
+    "p482":{"rank":"2"},
+    "p520":{"rank":"1"},
+    "i26973":{"rank":"1"},
+    "i13839":{"rank":"1"},
+    "i31032":{"rank":"1"},
+    "i29171":{"rank":"1"},
+    "i31839":{"rank":"1"},
+    "i31108":{"rank":"2"},
+    "i32085":{"rank":"3"},
+    "i32358":{"rank":"4"},
+    "i22212":{"rank":"2"},
+    "i30932":{"rank":"1"},
+    "i25149":{"rank":"3"},
+    "i27930":{"rank":"1"},
+    "i29322":{"rank":"1"},
+    "i29949":{"rank":"2"},
+    "i32147":{"rank":"3"},
+    "i29008":{"rank":"2"},
+    "i31031":{"rank":"3"},
+    "i31450":{"rank":"4"},
+    "i32097":{"rank":"5"},
+    "i32098":{"rank":"6"},
+    "i32099":{"rank":"7"},
+    "i32100":{"rank":"8"},
+    "i32101":{"rank":"9"},
+    "i32306":{"rank":"10"},
+    "i32334":{"rank":"11"},
+    "i26957":{"rank":"4"},
+    "i31399":{"rank":"1"},
+    "i31953":{"rank":"2"},
+    "i31949":{"rank":"5"},
+    "i14221":{"rank":"2"},
+    "p432":{"rank":"3"},
+    "i17788":{"rank":"1"},
+    "i16682":{"rank":"1"},
+    "i17865":{"rank":"2"},
+    "i14986":{"rank":"1"},
+    "i14988":{"rank":"2"},
+    "p479":{"rank":"4"},
+    "i30734":{"rank":"1"},
+    "i31849":{"rank":"1"},
+    "i31850":{"rank":"2"},
+    "i31851":{"rank":"3"},
+    "i31852":{"rank":"4"},
+    "i31842":{"rank":"1"},
+    "i32418":{"rank":"2"},
+    "i31536":{"rank":"2"},
+    "i31537":{"rank":"3"},
+    "p658":{"rank":"5"},
+    "p452":{"rank":"6"},
+    "i23364":{"rank":"1"},
+    "i23700":{"rank":"1"},
+    "i23691":{"rank":"1"},
+    "i23693":{"rank":"2"},
+    "i23763":{"rank":"1"},
+    "i30916":{"rank":"1"},
+    "i30917":{"rank":"2"},
+    "i30919":{"rank":"3"},
+    "i29354":{"rank":"1"},
+    "i31790":{"rank":"2"},
+    "i23766":{"rank":"2"},
+    "i32421":{"rank":"3"},
+    "i32422":{"rank":"4"},
+    "i32423":{"rank":"5"},
+    "i32424":{"rank":"6"},
+    "i32426":{"rank":"1"},
+    "i32427":{"rank":"2"},
+    "i32428":{"rank":"3"},
+    "i32429":{"rank":"4"},
+    "i32430":{"rank":"5"},
+    "i32431":{"rank":"6"},
+    "i32425":{"rank":"7"},
+    "i23694":{"rank":"3"},
+    "i23697":{"rank":"4"},
+    "i30735":{"rank":"5"},
+    "p587":{"rank":"7"},
+    "p592":{"rank":"8"},
+    "i30925":{"rank":"1"},
+    "i30926":{"rank":"2"},
+    "i30927":{"rank":"1"},
+    "i30928":{"rank":"2"},
+    "i32324":{"rank":"1"},
+    "i32323":{"rank":"3"},
+    "i32326":{"rank":"1"},
+    "i32325":{"rank":"4"},
+    "i30922":{"rank":"1"},
+    "i30923":{"rank":"2"},
+    "i30929":{"rank":"1"},
+    "i32072":{"rank":"2"},
+    "i32321":{"rank":"3"},
+    "i32322":{"rank":"4"},
+    "i32395":{"rank":"5"},
+    "i30924":{"rank":"3"},
+    "p668":{"rank":"9"},
+    "i29053":{"rank":"1"},
+    "i29058":{"rank":"1"},
+    "i29059":{"rank":"2"},
+    "i29054":{"rank":"2"},
+    "i29082":{"rank":"1"},
+    "i31948":{"rank":"2"},
+    "i29055":{"rank":"3"},
+    "i29131":{"rank":"1"},
+    "i29056":{"rank":"4"},
+    "i29345":{"rank":"1"},
+    "i29348":{"rank":"1"},
+    "i29350":{"rank":"2"},
+    "i32339":{"rank":"3"},
+    "i29347":{"rank":"2"},
+    "i30005":{"rank":"3"},
+    "i30006":{"rank":"4"},
+    "i29339":{"rank":"5"},
+    "i30736":{"rank":"6"},
+    "p644":{"rank":"10"},
+    "p391":{"rank":"-6"},
+    "i30978":{"rank":"1"},
+    "i30979":{"rank":"2"},
+    "i30976":{"rank":"1"},
+    "i30982":{"rank":"2"},
+    "i30974":{"rank":"1"},
+    "i30984":{"rank":"1"},
+    "i30985":{"rank":"2"},
+    "i30986":{"rank":"3"},
+    "i30987":{"rank":"4"},
+    "i30988":{"rank":"5"},
+    "i30989":{"rank":"6"},
+    "i30990":{"rank":"7"},
+    "i30992":{"rank":"1"},
+    "i30993":{"rank":"2"},
+    "i30991":{"rank":"8"},
+    "i30994":{"rank":"9"},
+    "i30995":{"rank":"10"},
+    "i30983":{"rank":"2"},
+    "i30997":{"rank":"1"},
+    "i30998":{"rank":"2"},
+    "i30999":{"rank":"3"},
+    "i31000":{"rank":"4"},
+    "i30996":{"rank":"3"},
+    "i31005":{"rank":"1"},
+    "i31006":{"rank":"2"},
+    "i31007":{"rank":"3"},
+    "i31004":{"rank":"4"},
+    "p602":{"rank":"1"},
+    "p674":{"rank":"-5"},
+    "i32168":{"rank":"1"},
+    "i32169":{"rank":"2"},
+    "i32165":{"rank":"1"},
+    "i32171":{"rank":"2"},
+    "i32163":{"rank":"1"},
+    "i32173":{"rank":"1"},
+    "i32174":{"rank":"2"},
+    "i32176":{"rank":"3"},
+    "i32177":{"rank":"4"},
+    "i32178":{"rank":"5"},
+    "i32179":{"rank":"6"},
+    "i32181":{"rank":"1"},
+    "i32182":{"rank":"2"},
+    "i32180":{"rank":"7"},
+    "i32184":{"rank":"8"},
+    "i32172":{"rank":"2"},
+    "i32186":{"rank":"1"},
+    "i32187":{"rank":"2"},
+    "i32188":{"rank":"3"},
+    "i32189":{"rank":"4"},
+    "i32185":{"rank":"3"},
+    "i32191":{"rank":"1"},
+    "i32192":{"rank":"2"},
+    "i32190":{"rank":"4"},
+    "i32194":{"rank":"1"},
+    "i32195":{"rank":"2"},
+    "i32196":{"rank":"3"},
+    "i32193":{"rank":"5"},
+    "p663":{"rank":"1"},
+    "p619":{"rank":"2"},
+    "p694":{"rank":"3"},
+    "i28502":{"rank":"1"},
+    "i28503":{"rank":"2"},
+    "i28504":{"rank":"3"},
+    "i28505":{"rank":"4"},
+    "i28501":{"rank":"1"},
+    "i28510":{"rank":"1"},
+    "i28511":{"rank":"2"},
+    "i28512":{"rank":"3"},
+    "i28509":{"rank":"2"},
+    "p620":{"rank":"4"},
+    "i30344":{"rank":"1"},
+    "i30345":{"rank":"2"},
+    "i30346":{"rank":"3"},
+    "i30347":{"rank":"4"},
+    "i30348":{"rank":"5"},
+    "i30349":{"rank":"6"},
+    "i30350":{"rank":"7"},
+    "i30352":{"rank":"1"},
+    "i30353":{"rank":"2"},
+    "i30351":{"rank":"8"},
+    "i30354":{"rank":"9"},
+    "i30355":{"rank":"10"},
+    "i30343":{"rank":"1"},
+    "i30357":{"rank":"1"},
+    "i30358":{"rank":"2"},
+    "i30359":{"rank":"3"},
+    "i30360":{"rank":"4"},
+    "i30356":{"rank":"2"},
+    "i30362":{"rank":"1"},
+    "i30363":{"rank":"2"},
+    "i30361":{"rank":"3"},
+    "i30365":{"rank":"1"},
+    "i30366":{"rank":"2"},
+    "i30367":{"rank":"3"},
+    "i30364":{"rank":"4"},
+    "i30335":{"rank":"1"},
+    "i30337":{"rank":"1"},
+    "i30338":{"rank":"2"},
+    "i30339":{"rank":"3"},
+    "i30340":{"rank":"4"},
+    "i30336":{"rank":"2"},
+    "i30341":{"rank":"3"},
+    "i30342":{"rank":"4"},
+    "i30334":{"rank":"5"},
+    "p614":{"rank":"5"},
+    "p693":{"rank":"6"},
+    "p695":{"rank":"7"},
+    "p609":{"rank":"8"},
+    "p696":{"rank":"9"},
+    "p698":{"rank":"10"},
+    "p691":{"rank":"11"},
+    "i31964":{"rank":"1"},
+    "i31965":{"rank":"2"},
+    "i31967":{"rank":"3"},
+    "i31968":{"rank":"4"},
+    "i31969":{"rank":"5"},
+    "i31970":{"rank":"6"},
+    "i31972":{"rank":"1"},
+    "i31973":{"rank":"2"},
+    "i31971":{"rank":"7"},
+    "i31975":{"rank":"8"},
+    "i31963":{"rank":"1"},
+    "i31977":{"rank":"1"},
+    "i31978":{"rank":"2"},
+    "i31979":{"rank":"3"},
+    "i31980":{"rank":"4"},
+    "i31976":{"rank":"2"},
+    "i31982":{"rank":"1"},
+    "i31983":{"rank":"2"},
+    "i31981":{"rank":"3"},
+    "i31985":{"rank":"1"},
+    "i31986":{"rank":"2"},
+    "i31987":{"rank":"3"},
+    "i31984":{"rank":"4"},
+    "p662":{"rank":"12"},
+    "p697":{"rank":"13"},
+    "i31423":{"rank":"1"},
+    "i31424":{"rank":"2"},
+    "i31425":{"rank":"3"},
+    "i31426":{"rank":"4"},
+    "i31427":{"rank":"5"},
+    "i31428":{"rank":"6"},
+    "i31429":{"rank":"7"},
+    "i31431":{"rank":"1"},
+    "i31432":{"rank":"2"},
+    "i31430":{"rank":"8"},
+    "i31433":{"rank":"9"},
+    "i31434":{"rank":"10"},
+    "i31422":{"rank":"1"},
+    "i31436":{"rank":"1"},
+    "i31437":{"rank":"2"},
+    "i31438":{"rank":"3"},
+    "i31439":{"rank":"4"},
+    "i31435":{"rank":"2"},
+    "i31444":{"rank":"1"},
+    "i31445":{"rank":"2"},
+    "i31446":{"rank":"3"},
+    "i31443":{"rank":"3"},
+    "p664":{"rank":"14"},
+    "p692":{"rank":"15"},
+    "i27375":{"rank":"1"},
+    "i29215":{"rank":"2"},
+    "i29216":{"rank":"3"},
+    "i27353":{"rank":"1"},
+    "i27346":{"rank":"1"},
+    "i28588":{"rank":"1"},
+    "i30828":{"rank":"2"},
+    "i28586":{"rank":"2"},
+    "p632":{"rank":"16"},
+    "i32212":{"rank":"1"},
+    "i32215":{"rank":"1"},
+    "i32216":{"rank":"2"},
+    "i32214":{"rank":"1"},
+    "i32217":{"rank":"2"},
+    "i32219":{"rank":"1"},
+    "i32220":{"rank":"2"},
+    "i32221":{"rank":"3"},
+    "i32218":{"rank":"3"},
+    "i32223":{"rank":"1"},
+    "i32224":{"rank":"2"},
+    "i32225":{"rank":"3"},
+    "i32226":{"rank":"4"},
+    "i32227":{"rank":"5"},
+    "i32228":{"rank":"6"},
+    "i32229":{"rank":"7"},
+    "i32230":{"rank":"8"},
+    "i32231":{"rank":"9"},
+    "i32222":{"rank":"4"},
+    "i32213":{"rank":"2"},
+    "i32232":{"rank":"3"},
+    "i32233":{"rank":"4"},
+    "i32211":{"rank":"1"},
+    "i32235":{"rank":"1"},
+    "i32236":{"rank":"2"},
+    "i32237":{"rank":"3"},
+    "i32238":{"rank":"4"},
+    "i32239":{"rank":"5"},
+    "i32240":{"rank":"6"},
+    "i32241":{"rank":"7"},
+    "i32243":{"rank":"1"},
+    "i32244":{"rank":"2"},
+    "i32242":{"rank":"8"},
+    "i32245":{"rank":"9"},
+    "i32246":{"rank":"10"},
+    "i32234":{"rank":"2"},
+    "i32248":{"rank":"1"},
+    "i32251":{"rank":"1"},
+    "i32253":{"rank":"2"},
+    "i32250":{"rank":"1"},
+    "i32254":{"rank":"2"},
+    "i32256":{"rank":"1"},
+    "i32257":{"rank":"2"},
+    "i32258":{"rank":"3"},
+    "i32255":{"rank":"3"},
+    "i32260":{"rank":"1"},
+    "i32261":{"rank":"2"},
+    "i32262":{"rank":"3"},
+    "i32263":{"rank":"4"},
+    "i32264":{"rank":"5"},
+    "i32265":{"rank":"6"},
+    "i32266":{"rank":"7"},
+    "i32267":{"rank":"8"},
+    "i32268":{"rank":"9"},
+    "i32301":{"rank":"10"},
+    "i32302":{"rank":"11"},
+    "i32259":{"rank":"4"},
+    "i32249":{"rank":"2"},
+    "i32269":{"rank":"3"},
+    "i32270":{"rank":"4"},
+    "i32247":{"rank":"3"},
+    "i32271":{"rank":"1"},
+    "i32272":{"rank":"2"},
+    "i32273":{"rank":"3"},
+    "i32274":{"rank":"4"},
+    "i32252":{"rank":"4"},
+    "i32277":{"rank":"1"},
+    "i32278":{"rank":"2"},
+    "i32279":{"rank":"3"},
+    "i32280":{"rank":"4"},
+    "i32281":{"rank":"5"},
+    "i32282":{"rank":"6"},
+    "i32283":{"rank":"7"},
+    "i32285":{"rank":"1"},
+    "i32286":{"rank":"2"},
+    "i32284":{"rank":"8"},
+    "i32287":{"rank":"9"},
+    "i32288":{"rank":"10"},
+    "i32276":{"rank":"5"},
+    "i32290":{"rank":"1"},
+    "i32291":{"rank":"2"},
+    "i32292":{"rank":"3"},
+    "i32293":{"rank":"4"},
+    "i32289":{"rank":"6"},
+    "i32295":{"rank":"1"},
+    "i32296":{"rank":"2"},
+    "i32294":{"rank":"7"},
+    "i32298":{"rank":"1"},
+    "i32299":{"rank":"2"},
+    "i32300":{"rank":"3"},
+    "i32297":{"rank":"8"},
+    "p682":{"rank":"17"},
+    "p618":{"rank":"-4"},
+    "p666":{"rank":"1"},
+    "p665":{"rank":"1"},
+    "p667":{"rank":"2"},
+    "i6349":{"rank":"3"},
+    "i6352":{"rank":"4"},
+    "i6353":{"rank":"5"},
+    "i6354":{"rank":"6"},
+    "i7295":{"rank":"7"},
+    "i7753":{"rank":"8"},
+    "p366":{"rank":"1"},
+    "i31043":{"rank":"1"},
+    "i31484":{"rank":"2"},
+    "i31660":{"rank":"1"},
+    "i31485":{"rank":"3"},
+    "i31486":{"rank":"4"},
+    "i31487":{"rank":"5"},
+    "i31488":{"rank":"6"},
+    "i31489":{"rank":"7"},
+    "i31543":{"rank":"8"},
+    "i31663":{"rank":"1"},
+    "i31664":{"rank":"2"},
+    "i31665":{"rank":"3"},
+    "i31666":{"rank":"4"},
+    "i31662":{"rank":"9"},
+    "i31885":{"rank":"10"},
+    "p640":{"rank":"2"},
+    "i15863":{"rank":"1"},
+    "i32373":{"rank":"2"},
+    "i32374":{"rank":"3"},
+    "i32387":{"rank":"4"},
+    "i32453":{"rank":"5"},
+    "i32554":{"rank":"6"},
+    "p442":{"rank":"3"},
+    "p673":{"rank":"1"},
+    "i32506":{"rank":"1"},
+    "i33512":{"rank":"2"},
+    "i29237":{"rank":"2"},
+    "i29280":{"rank":"3"},
+    "i29279":{"rank":"1"},
+    "i29294":{"rank":"2"},
+    "i29292":{"rank":"4"},
+    "i31866":{"rank":"1"},
+    "i32088":{"rank":"2"},
+    "i30941":{"rank":"5"},
+    "i31907":{"rank":"1"},
+    "i31753":{"rank":"6"},
+    "i31814":{"rank":"7"},
+    "i31899":{"rank":"1"},
+    "i31901":{"rank":"2"},
+    "i31902":{"rank":"3"},
+    "i31900":{"rank":"8"},
+    "i31903":{"rank":"9"},
+    "i31989":{"rank":"1"},
+    "i31988":{"rank":"10"},
+    "i31992":{"rank":"1"},
+    "i31991":{"rank":"11"},
+    "i32118":{"rank":"1"},
+    "i32119":{"rank":"2"},
+    "i32120":{"rank":"3"},
+    "i32117":{"rank":"12"},
+    "i32124":{"rank":"13"},
+    "i33458":{"rank":"14"},
+    "p635":{"rank":"4"},
+    "i16844":{"rank":"1"},
+    "i32159":{"rank":"2"},
+    "p501":{"rank":"5"},
+    "i9617":{"rank":"1"},
+    "i9620":{"rank":"2"},
+    "i9621":{"rank":"3"},
+    "i9622":{"rank":"4"},
+    "i9819":{"rank":"1"},
+    "i9822":{"rank":"2"},
+    "i9824":{"rank":"3"},
+    "i9827":{"rank":"4"},
+    "i9828":{"rank":"5"},
+    "i9829":{"rank":"6"},
+    "i9830":{"rank":"7"},
+    "i9831":{"rank":"8"},
+    "i9832":{"rank":"9"},
+    "i9833":{"rank":"10"},
+    "i9834":{"rank":"11"},
+    "i9835":{"rank":"12"},
+    "i9836":{"rank":"13"},
+    "i9837":{"rank":"14"},
+    "i9838":{"rank":"15"},
+    "i9839":{"rank":"16"},
+    "i9840":{"rank":"17"},
+    "i9841":{"rank":"18"},
+    "i9842":{"rank":"19"},
+    "i9843":{"rank":"20"},
+    "i9844":{"rank":"21"},
+    "i9845":{"rank":"22"},
+    "i32468":{"rank":"1"},
+    "i9846":{"rank":"23"},
+    "i9847":{"rank":"24"},
+    "i32452":{"rank":"1"},
+    "i9848":{"rank":"25"},
+    "i9849":{"rank":"26"},
+    "i9850":{"rank":"27"},
+    "i9851":{"rank":"28"},
+    "i9852":{"rank":"29"},
+    "i9853":{"rank":"30"},
+    "i9854":{"rank":"31"},
+    "i9855":{"rank":"32"},
+    "i9856":{"rank":"33"},
+    "i9857":{"rank":"34"},
+    "i9858":{"rank":"35"},
+    "i9859":{"rank":"36"},
+    "i9860":{"rank":"37"},
+    "i9861":{"rank":"38"},
+    "i9862":{"rank":"39"},
+    "i9863":{"rank":"40"},
+    "i9864":{"rank":"41"},
+    "i9865":{"rank":"42"},
+    "i9866":{"rank":"43"},
+    "i9867":{"rank":"44"},
+    "i9868":{"rank":"45"},
+    "i9869":{"rank":"46"},
+    "i9870":{"rank":"47"},
+    "i9871":{"rank":"48"},
+    "i9872":{"rank":"49"},
+    "i9873":{"rank":"50"},
+    "i9874":{"rank":"51"},
+    "i9875":{"rank":"52"},
+    "i9876":{"rank":"53"},
+    "i9877":{"rank":"54"},
+    "i9878":{"rank":"55"},
+    "i9879":{"rank":"56"},
+    "i9880":{"rank":"57"},
+    "i9881":{"rank":"58"},
+    "i9882":{"rank":"59"},
+    "i9883":{"rank":"60"},
+    "i9884":{"rank":"61"},
+    "i9885":{"rank":"62"},
+    "i9886":{"rank":"63"},
+    "i9887":{"rank":"64"},
+    "i9888":{"rank":"65"},
+    "i9889":{"rank":"66"},
+    "i9890":{"rank":"67"},
+    "i9891":{"rank":"68"},
+    "i9892":{"rank":"69"},
+    "i9893":{"rank":"70"},
+    "i17567":{"rank":"1"},
+    "i9894":{"rank":"71"},
+    "i9895":{"rank":"72"},
+    "i9896":{"rank":"73"},
+    "i9897":{"rank":"74"},
+    "i9898":{"rank":"75"},
+    "i9899":{"rank":"76"},
+    "i9900":{"rank":"77"},
+    "i9901":{"rank":"78"},
+    "i9902":{"rank":"79"},
+    "i9903":{"rank":"80"},
+    "i9904":{"rank":"81"},
+    "i9905":{"rank":"82"},
+    "i9906":{"rank":"83"},
+    "i16497":{"rank":"1"},
+    "i9907":{"rank":"84"},
+    "i9908":{"rank":"85"},
+    "i9909":{"rank":"86"},
+    "i9910":{"rank":"87"},
+    "i9911":{"rank":"88"},
+    "i9912":{"rank":"89"},
+    "i9913":{"rank":"90"},
+    "i9914":{"rank":"91"},
+    "i9915":{"rank":"92"},
+    "i9916":{"rank":"93"},
+    "i9917":{"rank":"94"},
+    "i9918":{"rank":"95"},
+    "i9919":{"rank":"96"},
+    "i9920":{"rank":"97"},
+    "i9921":{"rank":"98"},
+    "i9922":{"rank":"99"},
+    "i9923":{"rank":"100"},
+    "i9924":{"rank":"101"},
+    "i9925":{"rank":"102"},
+    "i9926":{"rank":"103"},
+    "i9927":{"rank":"104"},
+    "i9928":{"rank":"105"},
+    "i9929":{"rank":"106"},
+    "i9930":{"rank":"107"},
+    "i9931":{"rank":"108"},
+    "i18541":{"rank":"1"},
+    "i19723":{"rank":"2"},
+    "i19725":{"rank":"3"},
+    "i19727":{"rank":"4"},
+    "i9932":{"rank":"109"},
+    "i9933":{"rank":"110"},
+    "i9934":{"rank":"111"},
+    "i9935":{"rank":"112"},
+    "i9936":{"rank":"113"},
+    "i9937":{"rank":"114"},
+    "i9938":{"rank":"115"},
+    "i9939":{"rank":"116"},
+    "i9940":{"rank":"117"},
+    "i9941":{"rank":"118"},
+    "i9942":{"rank":"119"},
+    "i9943":{"rank":"120"},
+    "i9944":{"rank":"121"},
+    "i9945":{"rank":"122"},
+    "i9946":{"rank":"123"},
+    "i9947":{"rank":"124"},
+    "i13091":{"rank":"125"},
+    "i9818":{"rank":"5"},
+    "i10699":{"rank":"1"},
+    "i10310":{"rank":"6"},
+    "i11864":{"rank":"1"},
+    "i11865":{"rank":"2"},
+    "i11866":{"rank":"3"},
+    "i11867":{"rank":"4"},
+    "i11868":{"rank":"5"},
+    "i11869":{"rank":"6"},
+    "i11870":{"rank":"7"},
+    "i11871":{"rank":"8"},
+    "i11872":{"rank":"9"},
+    "i11873":{"rank":"10"},
+    "i11874":{"rank":"11"},
+    "i11875":{"rank":"12"},
+    "i11876":{"rank":"13"},
+    "i11877":{"rank":"14"},
+    "i11878":{"rank":"15"},
+    "i11879":{"rank":"16"},
+    "i11880":{"rank":"17"},
+    "i11881":{"rank":"18"},
+    "i11882":{"rank":"19"},
+    "i11883":{"rank":"20"},
+    "i11884":{"rank":"21"},
+    "i11885":{"rank":"22"},
+    "i11886":{"rank":"23"},
+    "i11887":{"rank":"24"},
+    "i11888":{"rank":"25"},
+    "i11889":{"rank":"26"},
+    "i11890":{"rank":"27"},
+    "i11891":{"rank":"28"},
+    "i11892":{"rank":"29"},
+    "i11893":{"rank":"30"},
+    "i11895":{"rank":"31"},
+    "i11896":{"rank":"32"},
+    "i11897":{"rank":"33"},
+    "i11898":{"rank":"34"},
+    "i11899":{"rank":"35"},
+    "i11900":{"rank":"36"},
+    "i11901":{"rank":"37"},
+    "i11902":{"rank":"38"},
+    "i11903":{"rank":"39"},
+    "i11904":{"rank":"40"},
+    "i11905":{"rank":"41"},
+    "i11906":{"rank":"42"},
+    "i11907":{"rank":"43"},
+    "i11908":{"rank":"44"},
+    "i11909":{"rank":"45"},
+    "i12355":{"rank":"1"},
+    "i11894":{"rank":"46"},
+    "i11863":{"rank":"7"},
+    "i11924":{"rank":"1"},
+    "i11925":{"rank":"2"},
+    "i11926":{"rank":"3"},
+    "i11927":{"rank":"4"},
+    "i11923":{"rank":"8"},
+    "i11929":{"rank":"1"},
+    "i11930":{"rank":"2"},
+    "i11931":{"rank":"3"},
+    "i11928":{"rank":"9"},
+    "i11933":{"rank":"1"},
+    "i11934":{"rank":"2"},
+    "i11935":{"rank":"3"},
+    "i11932":{"rank":"10"},
+    "i11937":{"rank":"1"},
+    "i11938":{"rank":"2"},
+    "i11939":{"rank":"3"},
+    "i11940":{"rank":"4"},
+    "i11941":{"rank":"5"},
+    "i11942":{"rank":"6"},
+    "i11943":{"rank":"7"},
+    "i11944":{"rank":"8"},
+    "i11945":{"rank":"9"},
+    "i12764":{"rank":"10"},
+    "i11936":{"rank":"11"},
+    "i15032":{"rank":"1"},
+    "i15031":{"rank":"12"},
+    "i21266":{"rank":"1"},
+    "i15493":{"rank":"13"},
+    "i22309":{"rank":"1"},
+    "i22308":{"rank":"14"},
+    "i32444":{"rank":"15"},
+    "i32445":{"rank":"16"},
+    "i32450":{"rank":"17"},
+    "i33401":{"rank":"1"},
+    "i33402":{"rank":"2"},
+    "i33454":{"rank":"3"},
+    "i33459":{"rank":"4"},
+    "i33460":{"rank":"5"},
+    "i33465":{"rank":"6"},
+    "i33510":{"rank":"7"},
+    "i33400":{"rank":"18"},
+    "i33456":{"rank":"19"},
+    "i9624":{"rank":"1"},
+    "i32432":{"rank":"1"},
+    "i32433":{"rank":"2"},
+    "i9626":{"rank":"2"},
+    "i9627":{"rank":"3"},
+    "i9618":{"rank":"20"},
+    "p399":{"rank":"6"},
+    "i31081":{"rank":"1"},
+    "i31088":{"rank":"2"},
+    "i31089":{"rank":"3"},
+    "i31090":{"rank":"4"},
+    "i31091":{"rank":"5"},
+    "i31110":{"rank":"6"},
+    "i31111":{"rank":"7"},
+    "i31073":{"rank":"1"},
+    "i31125":{"rank":"1"},
+    "i31126":{"rank":"2"},
+    "i31127":{"rank":"3"},
+    "i31128":{"rank":"4"},
+    "i31129":{"rank":"5"},
+    "i31130":{"rank":"6"},
+    "i31131":{"rank":"7"},
+    "i31118":{"rank":"1"},
+    "i31132":{"rank":"1"},
+    "i31133":{"rank":"2"},
+    "i31134":{"rank":"3"},
+    "i31135":{"rank":"4"},
+    "i31136":{"rank":"5"},
+    "i31137":{"rank":"6"},
+    "i31138":{"rank":"7"},
+    "i31631":{"rank":"8"},
+    "i31119":{"rank":"2"},
+    "i31139":{"rank":"1"},
+    "i31140":{"rank":"2"},
+    "i31141":{"rank":"3"},
+    "i31142":{"rank":"4"},
+    "i31143":{"rank":"5"},
+    "i31144":{"rank":"6"},
+    "i31145":{"rank":"7"},
+    "i31632":{"rank":"8"},
+    "i31120":{"rank":"3"},
+    "i31146":{"rank":"1"},
+    "i31633":{"rank":"2"},
+    "i31634":{"rank":"3"},
+    "i31636":{"rank":"4"},
+    "i31121":{"rank":"4"},
+    "i31116":{"rank":"2"},
+    "i31122":{"rank":"1"},
+    "i31123":{"rank":"2"},
+    "i31124":{"rank":"3"},
+    "i31117":{"rank":"3"},
+    "i31345":{"rank":"1"},
+    "i31343":{"rank":"4"},
+    "i33376":{"rank":"5"},
+    "p642":{"rank":"7"},
+    "p393":{"rank":"-3"},
+    "i21927":{"rank":"1"},
+    "i22736":{"rank":"2"},
+    "i21712":{"rank":"1"},
+    "i21711":{"rank":"1"},
+    "i22750":{"rank":"2"},
+    "i28992":{"rank":"3"},
+    "i22769":{"rank":"1"},
+    "i21715":{"rank":"1"},
+    "i21714":{"rank":"4"},
+    "p573":{"rank":"1"},
+    "i30292":{"rank":"1"},
+    "i30295":{"rank":"2"},
+    "i30330":{"rank":"3"},
+    "i30381":{"rank":"4"},
+    "i30421":{"rank":"5"},
+    "i30457":{"rank":"6"},
+    "i31245":{"rank":"1"},
+    "i30458":{"rank":"7"},
+    "i30459":{"rank":"8"},
+    "i30460":{"rank":"9"},
+    "p659":{"rank":"2"},
+    "i31562":{"rank":"1"},
+    "i31563":{"rank":"2"},
+    "i31576":{"rank":"3"},
+    "i31577":{"rank":"4"},
+    "i28862":{"rank":"1"},
+    "i28888":{"rank":"2"},
+    "i30840":{"rank":"1"},
+    "i30839":{"rank":"1"},
+    "i28916":{"rank":"1"},
+    "i31567":{"rank":"2"},
+    "i31568":{"rank":"3"},
+    "i31569":{"rank":"4"},
+    "i31570":{"rank":"5"},
+    "i31571":{"rank":"6"},
+    "i31572":{"rank":"7"},
+    "i31573":{"rank":"8"},
+    "i31574":{"rank":"9"},
+    "i32476":{"rank":"10"},
+    "i31566":{"rank":"2"},
+    "i28890":{"rank":"3"},
+    "i28332":{"rank":"1"},
+    "i28891":{"rank":"4"},
+    "i28909":{"rank":"1"},
+    "i28892":{"rank":"5"},
+    "i30835":{"rank":"1"},
+    "i31148":{"rank":"2"},
+    "i31198":{"rank":"3"},
+    "i28893":{"rank":"6"},
+    "i28894":{"rank":"7"},
+    "i33387":{"rank":"1"},
+    "i28897":{"rank":"8"},
+    "i29997":{"rank":"1"},
+    "i30370":{"rank":"2"},
+    "i31607":{"rank":"3"},
+    "i32507":{"rank":"4"},
+    "i29334":{"rank":"9"},
+    "i30742":{"rank":"1"},
+    "i30743":{"rank":"2"},
+    "i30744":{"rank":"3"},
+    "i30745":{"rank":"4"},
+    "i31203":{"rank":"5"},
+    "i30739":{"rank":"1"},
+    "i30740":{"rank":"2"},
+    "i30741":{"rank":"3"},
+    "i30738":{"rank":"10"},
+    "i30760":{"rank":"1"},
+    "i30759":{"rank":"11"},
+    "i30830":{"rank":"1"},
+    "i30829":{"rank":"12"},
+    "i29950":{"rank":"1"},
+    "i28889":{"rank":"1"},
+    "i30833":{"rank":"2"},
+    "i30832":{"rank":"13"},
+    "i31335":{"rank":"14"},
+    "i32356":{"rank":"15"},
+    "p636":{"rank":"3"},
+    "i29075":{"rank":"1"},
+    "p420":{"rank":"4"},
+    "i32416":{"rank":"1"},
+    "i30864":{"rank":"1"},
+    "i29089":{"rank":"1"},
+    "i29090":{"rank":"2"},
+    "i29091":{"rank":"3"},
+    "i29321":{"rank":"1"},
+    "i29092":{"rank":"4"},
+    "i32018":{"rank":"1"},
+    "i32467":{"rank":"2"},
+    "i29093":{"rank":"5"},
+    "i29094":{"rank":"6"},
+    "i29095":{"rank":"7"},
+    "i31812":{"rank":"1"},
+    "i32475":{"rank":"2"},
+    "i29096":{"rank":"8"},
+    "i29097":{"rank":"9"},
+    "i29098":{"rank":"10"},
+    "i29297":{"rank":"1"},
+    "i29099":{"rank":"11"},
+    "i29100":{"rank":"12"},
+    "i29101":{"rank":"13"},
+    "i29102":{"rank":"14"},
+    "i29105":{"rank":"15"},
+    "i29181":{"rank":"16"},
+    "i29187":{"rank":"17"},
+    "i30392":{"rank":"18"},
+    "i31411":{"rank":"1"},
+    "i32474":{"rank":"2"},
+    "i30815":{"rank":"19"},
+    "i30854":{"rank":"20"},
+    "i30869":{"rank":"21"},
+    "i31310":{"rank":"22"},
+    "i31822":{"rank":"1"},
+    "i31552":{"rank":"23"},
+    "p645":{"rank":"5"},
+    "i31323":{"rank":"1"},
+    "i31348":{"rank":"2"},
+    "i31385":{"rank":"3"},
+    "i31398":{"rank":"4"},
+    "i31319":{"rank":"1"},
+    "i31362":{"rank":"1"},
+    "i31590":{"rank":"2"},
+    "i31608":{"rank":"3"},
+    "i31609":{"rank":"4"},
+    "i31637":{"rank":"5"},
+    "i31678":{"rank":"6"},
+    "i31320":{"rank":"2"},
+    "i31520":{"rank":"1"},
+    "i31321":{"rank":"3"},
+    "i31525":{"rank":"1"},
+    "i31324":{"rank":"1"},
+    "i31528":{"rank":"1"},
+    "i31325":{"rank":"2"},
+    "i31529":{"rank":"1"},
+    "i31326":{"rank":"3"},
+    "i31350":{"rank":"1"},
+    "i31526":{"rank":"2"},
+    "i31542":{"rank":"3"},
+    "i31327":{"rank":"4"},
+    "i31527":{"rank":"1"},
+    "i31541":{"rank":"2"},
+    "i31328":{"rank":"5"},
+    "i31524":{"rank":"1"},
+    "i31329":{"rank":"6"},
+    "i31349":{"rank":"1"},
+    "i31330":{"rank":"7"},
+    "i31331":{"rank":"8"},
+    "i31322":{"rank":"4"},
+    "i31351":{"rank":"5"},
+    "p671":{"rank":"6"},
+    "i22119":{"rank":"1"},
+    "i28288":{"rank":"2"},
+    "i21295":{"rank":"1"},
+    "i21296":{"rank":"2"},
+    "i21556":{"rank":"1"},
+    "i21969":{"rank":"2"},
+    "i21555":{"rank":"3"},
+    "i21294":{"rank":"1"},
+    "i27104":{"rank":"1"},
+    "i21302":{"rank":"1"},
+    "i21305":{"rank":"2"},
+    "i21301":{"rank":"2"},
+    "i22405":{"rank":"1"},
+    "i21313":{"rank":"1"},
+    "i21315":{"rank":"2"},
+    "i28918":{"rank":"3"},
+    "i21312":{"rank":"3"},
+    "i21814":{"rank":"1"},
+    "i21342":{"rank":"1"},
+    "i21549":{"rank":"2"},
+    "i21552":{"rank":"3"},
+    "i21544":{"rank":"4"},
+    "i28351":{"rank":"5"},
+    "i28353":{"rank":"6"},
+    "i29130":{"rank":"7"},
+    "i30368":{"rank":"8"},
+    "i30819":{"rank":"9"},
+    "p568":{"rank":"7"},
+    "i31594":{"rank":"1"},
+    "i32415":{"rank":"2"},
+    "i32466":{"rank":"3"},
+    "i32469":{"rank":"4"},
+    "i32470":{"rank":"5"},
+    "i30953":{"rank":"1"},
+    "i33452":{"rank":"1"},
+    "i30954":{"rank":"2"},
+    "i33399":{"rank":"1"},
+    "i30955":{"rank":"3"},
+    "i30956":{"rank":"4"},
+    "i31819":{"rank":"1"},
+    "i32107":{"rank":"2"},
+    "i30957":{"rank":"5"},
+    "i32073":{"rank":"1"},
+    "i32074":{"rank":"2"},
+    "i30958":{"rank":"6"},
+    "i30959":{"rank":"7"},
+    "i30965":{"rank":"1"},
+    "i30961":{"rank":"1"},
+    "i30960":{"rank":"8"},
+    "i30966":{"rank":"9"},
+    "i31591":{"rank":"10"},
+    "i31829":{"rank":"11"},
+    "i32477":{"rank":"12"},
+    "p670":{"rank":"8"},
+    "p657":{"rank":"1"},
+    "i28266":{"rank":"1"},
+    "i28342":{"rank":"1"},
+    "i28341":{"rank":"2"},
+    "i28901":{"rank":"1"},
+    "i28903":{"rank":"2"},
+    "i28902":{"rank":"3"},
+    "i28946":{"rank":"4"},
+    "i29308":{"rank":"1"},
+    "i29316":{"rank":"2"},
+    "i30320":{"rank":"3"},
+    "i29302":{"rank":"5"},
+    "i30004":{"rank":"6"},
+    "i30045":{"rank":"1"},
+    "i30046":{"rank":"2"},
+    "i30047":{"rank":"3"},
+    "i30044":{"rank":"1"},
+    "i30043":{"rank":"7"},
+    "i30285":{"rank":"8"},
+    "i30319":{"rank":"9"},
+    "i29047":{"rank":"1"},
+    "i30726":{"rank":"10"},
+    "i27415":{"rank":"1"},
+    "i30818":{"rank":"2"},
+    "i30817":{"rank":"11"},
+    "i30935":{"rank":"12"},
+    "p608":{"rank":"2"},
+    "i26066":{"rank":"1"},
+    "i26067":{"rank":"2"},
+    "i26070":{"rank":"3"},
+    "i26071":{"rank":"4"},
+    "i25997":{"rank":"1"},
+    "i26029":{"rank":"1"},
+    "i26009":{"rank":"1"},
+    "i26030":{"rank":"1"},
+    "i26010":{"rank":"2"},
+    "i26011":{"rank":"3"},
+    "i26012":{"rank":"4"},
+    "i26031":{"rank":"1"},
+    "i26013":{"rank":"5"},
+    "i26032":{"rank":"1"},
+    "i26014":{"rank":"6"},
+    "i26016":{"rank":"7"},
+    "i26069":{"rank":"1"},
+    "i26068":{"rank":"8"},
+    "i25998":{"rank":"2"},
+    "i26033":{"rank":"1"},
+    "i26034":{"rank":"2"},
+    "i26035":{"rank":"3"},
+    "i25999":{"rank":"3"},
+    "i26036":{"rank":"1"},
+    "i26037":{"rank":"2"},
+    "i26038":{"rank":"3"},
+    "i26088":{"rank":"4"},
+    "i26089":{"rank":"5"},
+    "i26000":{"rank":"4"},
+    "i26039":{"rank":"1"},
+    "i26040":{"rank":"2"},
+    "i26041":{"rank":"3"},
+    "i26087":{"rank":"4"},
+    "i26001":{"rank":"5"},
+    "i26042":{"rank":"1"},
+    "i26002":{"rank":"6"},
+    "i26043":{"rank":"1"},
+    "i26003":{"rank":"7"},
+    "i26044":{"rank":"1"},
+    "i26045":{"rank":"2"},
+    "i26004":{"rank":"8"},
+    "i26055":{"rank":"1"},
+    "i26056":{"rank":"2"},
+    "i26046":{"rank":"1"},
+    "i26057":{"rank":"1"},
+    "i26047":{"rank":"2"},
+    "i26005":{"rank":"9"},
+    "i26048":{"rank":"1"},
+    "i26049":{"rank":"2"},
+    "i26006":{"rank":"10"},
+    "i26050":{"rank":"1"},
+    "i26007":{"rank":"11"},
+    "i26051":{"rank":"1"},
+    "i26052":{"rank":"2"},
+    "i26053":{"rank":"3"},
+    "i26093":{"rank":"4"},
+    "i26008":{"rank":"12"},
+    "i26054":{"rank":"1"},
+    "i26015":{"rank":"13"},
+    "i26017":{"rank":"14"},
+    "i26018":{"rank":"15"},
+    "i26058":{"rank":"1"},
+    "i26059":{"rank":"2"},
+    "i26060":{"rank":"3"},
+    "i26075":{"rank":"4"},
+    "i26076":{"rank":"5"},
+    "i26077":{"rank":"6"},
+    "i26078":{"rank":"7"},
+    "i26079":{"rank":"8"},
+    "i26019":{"rank":"16"},
+    "i26020":{"rank":"17"},
+    "i26021":{"rank":"18"},
+    "i26072":{"rank":"1"},
+    "i26073":{"rank":"2"},
+    "i26074":{"rank":"3"},
+    "i26022":{"rank":"19"},
+    "i26080":{"rank":"1"},
+    "i26023":{"rank":"20"},
+    "i26061":{"rank":"1"},
+    "i26062":{"rank":"2"},
+    "i26063":{"rank":"3"},
+    "i26064":{"rank":"4"},
+    "i26065":{"rank":"5"},
+    "i26085":{"rank":"6"},
+    "i26086":{"rank":"7"},
+    "i26024":{"rank":"21"},
+    "i26081":{"rank":"1"},
+    "i26082":{"rank":"2"},
+    "i26083":{"rank":"3"},
+    "i26084":{"rank":"4"},
+    "i26025":{"rank":"22"},
+    "i26092":{"rank":"1"},
+    "i26026":{"rank":"23"},
+    "i26090":{"rank":"1"},
+    "i26027":{"rank":"24"},
+    "i26091":{"rank":"1"},
+    "i26028":{"rank":"25"},
+    "p607":{"rank":"3"},
+    "i27050":{"rank":"1"},
+    "i27051":{"rank":"2"},
+    "i27052":{"rank":"3"},
+    "i27053":{"rank":"4"},
+    "i27049":{"rank":"4"},
+    "i27055":{"rank":"1"},
+    "i27054":{"rank":"5"},
+    "i30048":{"rank":"6"},
+    "p439":{"rank":"9"},
+    "i11703":{"rank":"10"},
+    "p419":{"rank":"-2"},
+    "i30314":{"rank":"1"},
+    "p649":{"rank":"1"},
+    "i18178":{"rank":"1"},
+    "i22193":{"rank":"1"},
+    "i22192":{"rank":"1"},
+    "i21198":{"rank":"2"},
+    "p371":{"rank":"2"},
+    "i31994":{"rank":"1"},
+    "i28224":{"rank":"1"},
+    "i28215":{"rank":"1"},
+    "i24456":{"rank":"2"},
+    "i13676":{"rank":"1"},
+    "p478":{"rank":"3"},
+    "p546":{"rank":"4"},
+    "i25413":{"rank":"1"},
+    "i25442":{"rank":"2"},
+    "i25456":{"rank":"1"},
+    "i25453":{"rank":"1"},
+    "i25444":{"rank":"1"},
+    "i25546":{"rank":"1"},
+    "i25543":{"rank":"1"},
+    "i25481":{"rank":"2"},
+    "i25578":{"rank":"1"},
+    "i25577":{"rank":"3"},
+    "i25443":{"rank":"3"},
+    "i25615":{"rank":"4"},
+    "i25622":{"rank":"1"},
+    "i25623":{"rank":"2"},
+    "i25624":{"rank":"3"},
+    "i25625":{"rank":"4"},
+    "i25621":{"rank":"1"},
+    "i25616":{"rank":"5"},
+    "i25644":{"rank":"6"},
+    "i25649":{"rank":"1"},
+    "i25650":{"rank":"2"},
+    "i25651":{"rank":"3"},
+    "i25652":{"rank":"4"},
+    "i25653":{"rank":"5"},
+    "i25654":{"rank":"6"},
+    "i25648":{"rank":"7"},
+    "i25655":{"rank":"8"},
+    "i25656":{"rank":"9"},
+    "i25657":{"rank":"10"},
+    "i25641":{"rank":"1"},
+    "i25638":{"rank":"1"},
+    "i25632":{"rank":"11"},
+    "p599":{"rank":"5"},
+    "i15869":{"rank":"1"},
+    "i18410":{"rank":"2"},
+    "i15866":{"rank":"1"},
+    "i16286":{"rank":"1"},
+    "i15874":{"rank":"2"},
+    "i15875":{"rank":"3"},
+    "i16086":{"rank":"4"},
+    "i16519":{"rank":"5"},
+    "i17496":{"rank":"6"},
+    "i22388":{"rank":"1"},
+    "i17940":{"rank":"7"},
+    "i18411":{"rank":"8"},
+    "i28346":{"rank":"1"},
+    "i28956":{"rank":"2"},
+    "i18412":{"rank":"9"},
+    "i18559":{"rank":"10"},
+    "i16084":{"rank":"1"},
+    "i23365":{"rank":"1"},
+    "i23370":{"rank":"1"},
+    "i23371":{"rank":"2"},
+    "i23372":{"rank":"3"},
+    "i23373":{"rank":"4"},
+    "i23374":{"rank":"5"},
+    "i23375":{"rank":"6"},
+    "i23376":{"rank":"7"},
+    "i23377":{"rank":"8"},
+    "i23378":{"rank":"9"},
+    "i23379":{"rank":"10"},
+    "i23380":{"rank":"11"},
+    "i23381":{"rank":"12"},
+    "i23382":{"rank":"13"},
+    "i23383":{"rank":"14"},
+    "i23384":{"rank":"15"},
+    "i23385":{"rank":"16"},
+    "i23386":{"rank":"17"},
+    "i23387":{"rank":"18"},
+    "i23388":{"rank":"19"},
+    "i23389":{"rank":"20"},
+    "i23390":{"rank":"21"},
+    "i23391":{"rank":"22"},
+    "i23392":{"rank":"23"},
+    "i23393":{"rank":"24"},
+    "i23394":{"rank":"25"},
+    "i23395":{"rank":"26"},
+    "i23396":{"rank":"27"},
+    "i23369":{"rank":"1"},
+    "i23368":{"rank":"2"},
+    "i23397":{"rank":"3"},
+    "i23401":{"rank":"1"},
+    "i23402":{"rank":"2"},
+    "i23403":{"rank":"3"},
+    "i23400":{"rank":"1"},
+    "i15867":{"rank":"1"},
+    "i23405":{"rank":"2"},
+    "i23406":{"rank":"3"},
+    "i23407":{"rank":"4"},
+    "i23404":{"rank":"2"},
+    "i15879":{"rank":"1"},
+    "i23409":{"rank":"2"},
+    "i23410":{"rank":"3"},
+    "i23411":{"rank":"4"},
+    "i23408":{"rank":"3"},
+    "i15873":{"rank":"1"},
+    "i23413":{"rank":"2"},
+    "i23414":{"rank":"3"},
+    "i23415":{"rank":"4"},
+    "i23412":{"rank":"4"},
+    "i23399":{"rank":"1"},
+    "i23418":{"rank":"1"},
+    "i23419":{"rank":"2"},
+    "i23420":{"rank":"3"},
+    "i23417":{"rank":"1"},
+    "i23422":{"rank":"1"},
+    "i23423":{"rank":"2"},
+    "i23424":{"rank":"3"},
+    "i23421":{"rank":"2"},
+    "i23426":{"rank":"1"},
+    "i23427":{"rank":"2"},
+    "i23428":{"rank":"3"},
+    "i23429":{"rank":"4"},
+    "i23425":{"rank":"3"},
+    "i23432":{"rank":"1"},
+    "i23433":{"rank":"2"},
+    "i23434":{"rank":"3"},
+    "i23431":{"rank":"1"},
+    "i23435":{"rank":"2"},
+    "i23430":{"rank":"4"},
+    "i23416":{"rank":"2"},
+    "i23440":{"rank":"1"},
+    "i23441":{"rank":"2"},
+    "i23439":{"rank":"1"},
+    "i23443":{"rank":"1"},
+    "i23444":{"rank":"2"},
+    "i23442":{"rank":"2"},
+    "i23438":{"rank":"1"},
+    "i23446":{"rank":"1"},
+    "i23447":{"rank":"2"},
+    "i23448":{"rank":"3"},
+    "i23445":{"rank":"2"},
+    "i23450":{"rank":"1"},
+    "i23451":{"rank":"2"},
+    "i23452":{"rank":"3"},
+    "i23449":{"rank":"3"},
+    "i23454":{"rank":"1"},
+    "i23455":{"rank":"2"},
+    "i23456":{"rank":"3"},
+    "i23453":{"rank":"4"},
+    "i23458":{"rank":"1"},
+    "i23459":{"rank":"2"},
+    "i23460":{"rank":"3"},
+    "i23457":{"rank":"5"},
+    "i23462":{"rank":"1"},
+    "i23463":{"rank":"2"},
+    "i23464":{"rank":"3"},
+    "i23461":{"rank":"6"},
+    "i23466":{"rank":"1"},
+    "i23467":{"rank":"2"},
+    "i23468":{"rank":"3"},
+    "i23465":{"rank":"7"},
+    "i23470":{"rank":"1"},
+    "i23471":{"rank":"2"},
+    "i23472":{"rank":"3"},
+    "i23469":{"rank":"8"},
+    "i23474":{"rank":"1"},
+    "i23475":{"rank":"2"},
+    "i23476":{"rank":"3"},
+    "i23473":{"rank":"9"},
+    "i23478":{"rank":"1"},
+    "i23479":{"rank":"2"},
+    "i23480":{"rank":"3"},
+    "i23477":{"rank":"10"},
+    "i23482":{"rank":"1"},
+    "i23483":{"rank":"2"},
+    "i23484":{"rank":"3"},
+    "i23481":{"rank":"11"},
+    "i23486":{"rank":"1"},
+    "i23487":{"rank":"2"},
+    "i23488":{"rank":"3"},
+    "i23485":{"rank":"12"},
+    "i23490":{"rank":"1"},
+    "i23491":{"rank":"2"},
+    "i23492":{"rank":"3"},
+    "i23489":{"rank":"13"},
+    "i23494":{"rank":"1"},
+    "i23495":{"rank":"2"},
+    "i23496":{"rank":"3"},
+    "i23497":{"rank":"4"},
+    "i23493":{"rank":"14"},
+    "i23437":{"rank":"1"},
+    "i21333":{"rank":"1"},
+    "i22387":{"rank":"2"},
+    "i15871":{"rank":"1"},
+    "i23500":{"rank":"1"},
+    "i23499":{"rank":"2"},
+    "i15872":{"rank":"1"},
+    "i23502":{"rank":"2"},
+    "i23504":{"rank":"1"},
+    "i23505":{"rank":"2"},
+    "i23506":{"rank":"3"},
+    "i23503":{"rank":"3"},
+    "i23501":{"rank":"3"},
+    "i23508":{"rank":"1"},
+    "i23507":{"rank":"4"},
+    "i23510":{"rank":"1"},
+    "i23509":{"rank":"5"},
+    "i23511":{"rank":"6"},
+    "i23513":{"rank":"1"},
+    "i23514":{"rank":"2"},
+    "i23512":{"rank":"7"},
+    "i23516":{"rank":"1"},
+    "i23515":{"rank":"8"},
+    "i23518":{"rank":"1"},
+    "i23517":{"rank":"9"},
+    "i23520":{"rank":"1"},
+    "i23521":{"rank":"2"},
+    "i23519":{"rank":"10"},
+    "i23523":{"rank":"1"},
+    "i23522":{"rank":"11"},
+    "i23525":{"rank":"1"},
+    "i23524":{"rank":"12"},
+    "i23527":{"rank":"1"},
+    "i23526":{"rank":"13"},
+    "i23529":{"rank":"1"},
+    "i23530":{"rank":"2"},
+    "i23531":{"rank":"3"},
+    "i23528":{"rank":"14"},
+    "i23498":{"rank":"2"},
+    "i23436":{"rank":"3"},
+    "i16062":{"rank":"1"},
+    "i23533":{"rank":"1"},
+    "i23532":{"rank":"4"},
+    "i23537":{"rank":"1"},
+    "i23538":{"rank":"2"},
+    "i23539":{"rank":"3"},
+    "i23535":{"rank":"1"},
+    "i23541":{"rank":"1"},
+    "i23542":{"rank":"2"},
+    "i23543":{"rank":"3"},
+    "i23540":{"rank":"2"},
+    "i23546":{"rank":"1"},
+    "i23547":{"rank":"2"},
+    "i23545":{"rank":"1"},
+    "i23549":{"rank":"1"},
+    "i23550":{"rank":"2"},
+    "i23548":{"rank":"2"},
+    "i23552":{"rank":"1"},
+    "i23553":{"rank":"2"},
+    "i23551":{"rank":"3"},
+    "i23555":{"rank":"1"},
+    "i23556":{"rank":"2"},
+    "i23554":{"rank":"4"},
+    "i23557":{"rank":"5"},
+    "i23544":{"rank":"3"},
+    "i23534":{"rank":"5"},
+    "i23559":{"rank":"1"},
+    "i23560":{"rank":"2"},
+    "i23562":{"rank":"1"},
+    "i23563":{"rank":"2"},
+    "i23564":{"rank":"3"},
+    "i23565":{"rank":"4"},
+    "i23566":{"rank":"5"},
+    "i23567":{"rank":"6"},
+    "i15870":{"rank":"1"},
+    "i23568":{"rank":"7"},
+    "i23561":{"rank":"3"},
+    "i23569":{"rank":"4"},
+    "i23558":{"rank":"6"},
+    "i23398":{"rank":"4"},
+    "i23570":{"rank":"5"},
+    "i23574":{"rank":"1"},
+    "i23572":{"rank":"1"},
+    "i23575":{"rank":"2"},
+    "i23577":{"rank":"1"},
+    "i23578":{"rank":"2"},
+    "i23579":{"rank":"3"},
+    "i23580":{"rank":"4"},
+    "i23576":{"rank":"3"},
+    "i23582":{"rank":"1"},
+    "i23583":{"rank":"2"},
+    "i23581":{"rank":"4"},
+    "i23585":{"rank":"1"},
+    "i23586":{"rank":"2"},
+    "i23584":{"rank":"5"},
+    "i23571":{"rank":"6"},
+    "i23589":{"rank":"1"},
+    "i23588":{"rank":"1"},
+    "i23591":{"rank":"1"},
+    "i23592":{"rank":"2"},
+    "i23590":{"rank":"2"},
+    "i23594":{"rank":"1"},
+    "i23595":{"rank":"2"},
+    "i23596":{"rank":"3"},
+    "i23593":{"rank":"3"},
+    "i23597":{"rank":"4"},
+    "i23598":{"rank":"5"},
+    "i23587":{"rank":"7"},
+    "i23600":{"rank":"1"},
+    "i23601":{"rank":"2"},
+    "i23602":{"rank":"3"},
+    "i23599":{"rank":"8"},
+    "i23604":{"rank":"1"},
+    "i23605":{"rank":"2"},
+    "i23606":{"rank":"3"},
+    "i23607":{"rank":"4"},
+    "i23608":{"rank":"5"},
+    "i23609":{"rank":"6"},
+    "i23603":{"rank":"9"},
+    "i23610":{"rank":"10"},
+    "i23611":{"rank":"11"},
+    "i23612":{"rank":"12"},
+    "i27819":{"rank":"1"},
+    "i27821":{"rank":"2"},
+    "i27822":{"rank":"3"},
+    "i27823":{"rank":"4"},
+    "i27824":{"rank":"5"},
+    "i27818":{"rank":"13"},
+    "p494":{"rank":"6"},
+    "i27410":{"rank":"1"},
+    "i22667":{"rank":"1"},
+    "i23758":{"rank":"1"},
+    "i23759":{"rank":"2"},
+    "i27413":{"rank":"3"},
+    "p590":{"rank":"7"},
+    "i27857":{"rank":"1"},
+    "i27956":{"rank":"2"},
+    "i27958":{"rank":"1"},
+    "i27959":{"rank":"2"},
+    "i27957":{"rank":"3"},
+    "i26954":{"rank":"1"},
+    "i27981":{"rank":"1"},
+    "i26955":{"rank":"2"},
+    "i27974":{"rank":"1"},
+    "i27976":{"rank":"2"},
+    "i27977":{"rank":"3"},
+    "i27978":{"rank":"4"},
+    "i27807":{"rank":"3"},
+    "i27827":{"rank":"4"},
+    "i28045":{"rank":"1"},
+    "i28046":{"rank":"2"},
+    "i28047":{"rank":"3"},
+    "i28048":{"rank":"4"},
+    "i28049":{"rank":"5"},
+    "i28050":{"rank":"6"},
+    "i28051":{"rank":"7"},
+    "i28052":{"rank":"8"},
+    "i28053":{"rank":"9"},
+    "i28054":{"rank":"10"},
+    "i28055":{"rank":"11"},
+    "i28056":{"rank":"12"},
+    "i27984":{"rank":"1"},
+    "i27985":{"rank":"2"},
+    "i28058":{"rank":"1"},
+    "i28059":{"rank":"2"},
+    "i28060":{"rank":"3"},
+    "i27987":{"rank":"3"},
+    "i28061":{"rank":"1"},
+    "i27992":{"rank":"4"},
+    "i27993":{"rank":"5"},
+    "i27994":{"rank":"6"},
+    "i28062":{"rank":"1"},
+    "i27995":{"rank":"7"},
+    "i27850":{"rank":"1"},
+    "i27936":{"rank":"1"},
+    "i29268":{"rank":"2"},
+    "i29269":{"rank":"3"},
+    "i27851":{"rank":"2"},
+    "i28036":{"rank":"1"},
+    "i28071":{"rank":"2"},
+    "i28166":{"rank":"3"},
+    "i28349":{"rank":"4"},
+    "i27979":{"rank":"3"},
+    "i28044":{"rank":"4"},
+    "i27849":{"rank":"5"},
+    "i27968":{"rank":"1"},
+    "i27973":{"rank":"2"},
+    "i27806":{"rank":"1"},
+    "i27955":{"rank":"6"},
+    "i27980":{"rank":"7"},
+    "i29258":{"rank":"1"},
+    "i29257":{"rank":"8"},
+    "p621":{"rank":"8"},
+    "i27451":{"rank":"1"},
+    "i27453":{"rank":"2"},
+    "i27455":{"rank":"3"},
+    "i27456":{"rank":"4"},
+    "i27457":{"rank":"5"},
+    "i27458":{"rank":"6"},
+    "i27459":{"rank":"7"},
+    "i27462":{"rank":"8"},
+    "i27463":{"rank":"9"},
+    "i27464":{"rank":"10"},
+    "i27465":{"rank":"11"},
+    "i27466":{"rank":"12"},
+    "i27467":{"rank":"13"},
+    "i27469":{"rank":"14"},
+    "i27471":{"rank":"15"},
+    "i27472":{"rank":"16"},
+    "i27473":{"rank":"17"},
+    "i27475":{"rank":"18"},
+    "i27450":{"rank":"1"},
+    "i27449":{"rank":"1"},
+    "i27478":{"rank":"2"},
+    "i27486":{"rank":"1"},
+    "i27488":{"rank":"2"},
+    "i27485":{"rank":"1"},
+    "i27492":{"rank":"1"},
+    "i27489":{"rank":"2"},
+    "i27493":{"rank":"3"},
+    "i27480":{"rank":"1"},
+    "i27497":{"rank":"2"},
+    "i27578":{"rank":"1"},
+    "i27574":{"rank":"1"},
+    "i27518":{"rank":"1"},
+    "i27601":{"rank":"1"},
+    "i27602":{"rank":"2"},
+    "i27600":{"rank":"1"},
+    "i27610":{"rank":"1"},
+    "i27611":{"rank":"2"},
+    "i27612":{"rank":"3"},
+    "i27609":{"rank":"2"},
+    "i27579":{"rank":"2"},
+    "i27517":{"rank":"3"},
+    "i27614":{"rank":"1"},
+    "i27613":{"rank":"4"},
+    "i27479":{"rank":"3"},
+    "i27651":{"rank":"4"},
+    "i27786":{"rank":"1"},
+    "i27680":{"rank":"5"},
+    "i27685":{"rank":"1"},
+    "i27686":{"rank":"2"},
+    "i27687":{"rank":"3"},
+    "i27688":{"rank":"4"},
+    "i27689":{"rank":"5"},
+    "i27684":{"rank":"6"},
+    "i27691":{"rank":"7"},
+    "i27692":{"rank":"8"},
+    "i27693":{"rank":"9"},
+    "i30267":{"rank":"1"},
+    "i30268":{"rank":"2"},
+    "i30265":{"rank":"10"},
+    "p633":{"rank":"9"},
+    "i32834":{"rank":"1"},
+    "i32835":{"rank":"2"},
+    "i32836":{"rank":"3"},
+    "i32837":{"rank":"4"},
+    "i32838":{"rank":"5"},
+    "i32839":{"rank":"6"},
+    "i32840":{"rank":"7"},
+    "i32841":{"rank":"8"},
+    "i32842":{"rank":"9"},
+    "i32843":{"rank":"10"},
+    "i32844":{"rank":"11"},
+    "i32845":{"rank":"12"},
+    "i32846":{"rank":"13"},
+    "i32847":{"rank":"14"},
+    "i32848":{"rank":"15"},
+    "i32849":{"rank":"16"},
+    "i32850":{"rank":"17"},
+    "i32851":{"rank":"18"},
+    "i32852":{"rank":"19"},
+    "i32853":{"rank":"20"},
+    "i32854":{"rank":"21"},
+    "i32855":{"rank":"22"},
+    "i32856":{"rank":"23"},
+    "i32857":{"rank":"24"},
+    "i32858":{"rank":"25"},
+    "i32859":{"rank":"26"},
+    "i32860":{"rank":"27"},
+    "i32833":{"rank":"1"},
+    "i32832":{"rank":"1"},
+    "i32861":{"rank":"2"},
+    "i32865":{"rank":"1"},
+    "i32866":{"rank":"2"},
+    "i32867":{"rank":"3"},
+    "i32864":{"rank":"1"},
+    "i32869":{"rank":"1"},
+    "i32870":{"rank":"2"},
+    "i32871":{"rank":"3"},
+    "i32868":{"rank":"2"},
+    "i32873":{"rank":"1"},
+    "i32874":{"rank":"2"},
+    "i32875":{"rank":"3"},
+    "i32872":{"rank":"3"},
+    "i32877":{"rank":"1"},
+    "i32878":{"rank":"2"},
+    "i32879":{"rank":"3"},
+    "i32876":{"rank":"4"},
+    "i32863":{"rank":"1"},
+    "i32882":{"rank":"1"},
+    "i32883":{"rank":"2"},
+    "i32884":{"rank":"3"},
+    "i32881":{"rank":"1"},
+    "i32886":{"rank":"1"},
+    "i32887":{"rank":"2"},
+    "i32888":{"rank":"3"},
+    "i32885":{"rank":"2"},
+    "i32890":{"rank":"1"},
+    "i32891":{"rank":"2"},
+    "i32892":{"rank":"3"},
+    "i32893":{"rank":"4"},
+    "i32889":{"rank":"3"},
+    "i32896":{"rank":"1"},
+    "i32897":{"rank":"2"},
+    "i32898":{"rank":"3"},
+    "i32895":{"rank":"1"},
+    "i32899":{"rank":"2"},
+    "i32894":{"rank":"4"},
+    "i32880":{"rank":"2"},
+    "i32904":{"rank":"1"},
+    "i32905":{"rank":"2"},
+    "i32903":{"rank":"1"},
+    "i32907":{"rank":"1"},
+    "i32908":{"rank":"2"},
+    "i32906":{"rank":"2"},
+    "i32902":{"rank":"1"},
+    "i32911":{"rank":"1"},
+    "i32912":{"rank":"2"},
+    "i32909":{"rank":"2"},
+    "i32914":{"rank":"1"},
+    "i32915":{"rank":"2"},
+    "i32916":{"rank":"3"},
+    "i32913":{"rank":"3"},
+    "i32918":{"rank":"1"},
+    "i32919":{"rank":"2"},
+    "i32920":{"rank":"3"},
+    "i32917":{"rank":"4"},
+    "i32922":{"rank":"1"},
+    "i32923":{"rank":"2"},
+    "i32924":{"rank":"3"},
+    "i32921":{"rank":"5"},
+    "i32926":{"rank":"1"},
+    "i32927":{"rank":"2"},
+    "i32928":{"rank":"3"},
+    "i32925":{"rank":"6"},
+    "i32930":{"rank":"1"},
+    "i32931":{"rank":"2"},
+    "i32932":{"rank":"3"},
+    "i32929":{"rank":"7"},
+    "i32934":{"rank":"1"},
+    "i32935":{"rank":"2"},
+    "i32936":{"rank":"3"},
+    "i32933":{"rank":"8"},
+    "i32938":{"rank":"1"},
+    "i32939":{"rank":"2"},
+    "i32940":{"rank":"3"},
+    "i32937":{"rank":"9"},
+    "i32942":{"rank":"1"},
+    "i32943":{"rank":"2"},
+    "i32944":{"rank":"3"},
+    "i32941":{"rank":"10"},
+    "i32946":{"rank":"1"},
+    "i32947":{"rank":"2"},
+    "i32948":{"rank":"3"},
+    "i32945":{"rank":"11"},
+    "i32950":{"rank":"1"},
+    "i32951":{"rank":"2"},
+    "i32952":{"rank":"3"},
+    "i32949":{"rank":"12"},
+    "i32954":{"rank":"1"},
+    "i32955":{"rank":"2"},
+    "i32956":{"rank":"3"},
+    "i32953":{"rank":"13"},
+    "i32958":{"rank":"1"},
+    "i32959":{"rank":"2"},
+    "i32960":{"rank":"3"},
+    "i32961":{"rank":"4"},
+    "i32957":{"rank":"14"},
+    "i32901":{"rank":"1"},
+    "i32964":{"rank":"1"},
+    "i32963":{"rank":"1"},
+    "i32966":{"rank":"1"},
+    "i32968":{"rank":"1"},
+    "i32969":{"rank":"2"},
+    "i32970":{"rank":"3"},
+    "i32967":{"rank":"2"},
+    "i32965":{"rank":"2"},
+    "i32972":{"rank":"1"},
+    "i32971":{"rank":"3"},
+    "i32974":{"rank":"1"},
+    "i32973":{"rank":"4"},
+    "i32975":{"rank":"5"},
+    "i32977":{"rank":"1"},
+    "i32978":{"rank":"2"},
+    "i32976":{"rank":"6"},
+    "i32980":{"rank":"1"},
+    "i32979":{"rank":"7"},
+    "i32982":{"rank":"1"},
+    "i32981":{"rank":"8"},
+    "i32984":{"rank":"1"},
+    "i32985":{"rank":"2"},
+    "i32983":{"rank":"9"},
+    "i32987":{"rank":"1"},
+    "i32986":{"rank":"10"},
+    "i32989":{"rank":"1"},
+    "i32988":{"rank":"11"},
+    "i32991":{"rank":"1"},
+    "i32990":{"rank":"12"},
+    "i32993":{"rank":"1"},
+    "i32994":{"rank":"2"},
+    "i32995":{"rank":"3"},
+    "i32992":{"rank":"13"},
+    "i32962":{"rank":"2"},
+    "i32900":{"rank":"3"},
+    "i32997":{"rank":"1"},
+    "i32996":{"rank":"4"},
+    "i33000":{"rank":"1"},
+    "i33001":{"rank":"2"},
+    "i33002":{"rank":"3"},
+    "i33003":{"rank":"4"},
+    "i32999":{"rank":"1"},
+    "i33005":{"rank":"1"},
+    "i33006":{"rank":"2"},
+    "i33007":{"rank":"3"},
+    "i33004":{"rank":"2"},
+    "i33010":{"rank":"1"},
+    "i33011":{"rank":"2"},
+    "i33009":{"rank":"1"},
+    "i33013":{"rank":"1"},
+    "i33014":{"rank":"2"},
+    "i33012":{"rank":"2"},
+    "i33016":{"rank":"1"},
+    "i33017":{"rank":"2"},
+    "i33015":{"rank":"3"},
+    "i33019":{"rank":"1"},
+    "i33020":{"rank":"2"},
+    "i33018":{"rank":"4"},
+    "i33021":{"rank":"5"},
+    "i33008":{"rank":"3"},
+    "i32998":{"rank":"5"},
+    "i33023":{"rank":"1"},
+    "i33024":{"rank":"2"},
+    "i33026":{"rank":"1"},
+    "i33027":{"rank":"2"},
+    "i33028":{"rank":"3"},
+    "i33029":{"rank":"4"},
+    "i33030":{"rank":"5"},
+    "i33031":{"rank":"6"},
+    "i33032":{"rank":"7"},
+    "i33025":{"rank":"3"},
+    "i33033":{"rank":"4"},
+    "i33022":{"rank":"6"},
+    "i32862":{"rank":"3"},
+    "i33034":{"rank":"4"},
+    "i33037":{"rank":"1"},
+    "i33038":{"rank":"2"},
+    "i33036":{"rank":"1"},
+    "i33039":{"rank":"2"},
+    "i33041":{"rank":"1"},
+    "i33042":{"rank":"2"},
+    "i33043":{"rank":"3"},
+    "i33044":{"rank":"4"},
+    "i33040":{"rank":"3"},
+    "i33046":{"rank":"1"},
+    "i33047":{"rank":"2"},
+    "i33045":{"rank":"4"},
+    "i33049":{"rank":"1"},
+    "i33050":{"rank":"2"},
+    "i33048":{"rank":"5"},
+    "i33035":{"rank":"5"},
+    "i33053":{"rank":"1"},
+    "i33052":{"rank":"1"},
+    "i33055":{"rank":"1"},
+    "i33056":{"rank":"2"},
+    "i33054":{"rank":"2"},
+    "i33058":{"rank":"1"},
+    "i33059":{"rank":"2"},
+    "i33060":{"rank":"3"},
+    "i33057":{"rank":"3"},
+    "i33061":{"rank":"4"},
+    "i33062":{"rank":"5"},
+    "i33051":{"rank":"6"},
+    "i33064":{"rank":"1"},
+    "i33065":{"rank":"2"},
+    "i33066":{"rank":"3"},
+    "i33067":{"rank":"4"},
+    "i33063":{"rank":"7"},
+    "i33069":{"rank":"1"},
+    "i33070":{"rank":"2"},
+    "i33071":{"rank":"3"},
+    "i33072":{"rank":"4"},
+    "i33073":{"rank":"5"},
+    "i33074":{"rank":"6"},
+    "i33068":{"rank":"8"},
+    "i33075":{"rank":"9"},
+    "i33076":{"rank":"10"},
+    "i33077":{"rank":"11"},
+    "p688":{"rank":"10"},
+    "i32588":{"rank":"1"},
+    "i32589":{"rank":"2"},
+    "i32590":{"rank":"3"},
+    "i32591":{"rank":"4"},
+    "i32592":{"rank":"5"},
+    "i32593":{"rank":"6"},
+    "i32594":{"rank":"7"},
+    "i32595":{"rank":"8"},
+    "i32596":{"rank":"9"},
+    "i32597":{"rank":"10"},
+    "i32598":{"rank":"11"},
+    "i32599":{"rank":"12"},
+    "i32600":{"rank":"13"},
+    "i32601":{"rank":"14"},
+    "i32602":{"rank":"15"},
+    "i32603":{"rank":"16"},
+    "i32604":{"rank":"17"},
+    "i32605":{"rank":"18"},
+    "i32606":{"rank":"19"},
+    "i32607":{"rank":"20"},
+    "i32608":{"rank":"21"},
+    "i32609":{"rank":"22"},
+    "i32610":{"rank":"23"},
+    "i32611":{"rank":"24"},
+    "i32612":{"rank":"25"},
+    "i32613":{"rank":"26"},
+    "i32614":{"rank":"27"},
+    "i32587":{"rank":"1"},
+    "i32586":{"rank":"1"},
+    "i32615":{"rank":"2"},
+    "i32619":{"rank":"1"},
+    "i32620":{"rank":"2"},
+    "i32621":{"rank":"3"},
+    "i32618":{"rank":"1"},
+    "i32623":{"rank":"1"},
+    "i32624":{"rank":"2"},
+    "i32625":{"rank":"3"},
+    "i32622":{"rank":"2"},
+    "i32627":{"rank":"1"},
+    "i32628":{"rank":"2"},
+    "i32629":{"rank":"3"},
+    "i32626":{"rank":"3"},
+    "i32631":{"rank":"1"},
+    "i32632":{"rank":"2"},
+    "i32633":{"rank":"3"},
+    "i32630":{"rank":"4"},
+    "i32617":{"rank":"1"},
+    "i32636":{"rank":"1"},
+    "i32637":{"rank":"2"},
+    "i32638":{"rank":"3"},
+    "i32635":{"rank":"1"},
+    "i32640":{"rank":"1"},
+    "i32641":{"rank":"2"},
+    "i32642":{"rank":"3"},
+    "i32639":{"rank":"2"},
+    "i32644":{"rank":"1"},
+    "i32645":{"rank":"2"},
+    "i32646":{"rank":"3"},
+    "i32647":{"rank":"4"},
+    "i32643":{"rank":"3"},
+    "i32650":{"rank":"1"},
+    "i32651":{"rank":"2"},
+    "i32652":{"rank":"3"},
+    "i32649":{"rank":"1"},
+    "i32653":{"rank":"2"},
+    "i32648":{"rank":"4"},
+    "i32634":{"rank":"2"},
+    "i32658":{"rank":"1"},
+    "i32659":{"rank":"2"},
+    "i32657":{"rank":"1"},
+    "i32661":{"rank":"1"},
+    "i32662":{"rank":"2"},
+    "i32660":{"rank":"2"},
+    "i32656":{"rank":"1"},
+    "i32665":{"rank":"1"},
+    "i32666":{"rank":"2"},
+    "i32663":{"rank":"2"},
+    "i32668":{"rank":"1"},
+    "i32669":{"rank":"2"},
+    "i32670":{"rank":"3"},
+    "i32667":{"rank":"3"},
+    "i32672":{"rank":"1"},
+    "i32673":{"rank":"2"},
+    "i32674":{"rank":"3"},
+    "i32671":{"rank":"4"},
+    "i32676":{"rank":"1"},
+    "i32677":{"rank":"2"},
+    "i32678":{"rank":"3"},
+    "i32675":{"rank":"5"},
+    "i32680":{"rank":"1"},
+    "i32681":{"rank":"2"},
+    "i32682":{"rank":"3"},
+    "i32679":{"rank":"6"},
+    "i32684":{"rank":"1"},
+    "i32685":{"rank":"2"},
+    "i32686":{"rank":"3"},
+    "i32683":{"rank":"7"},
+    "i32688":{"rank":"1"},
+    "i32689":{"rank":"2"},
+    "i32690":{"rank":"3"},
+    "i32687":{"rank":"8"},
+    "i32692":{"rank":"1"},
+    "i32693":{"rank":"2"},
+    "i32694":{"rank":"3"},
+    "i32691":{"rank":"9"},
+    "i32696":{"rank":"1"},
+    "i32697":{"rank":"2"},
+    "i32698":{"rank":"3"},
+    "i32695":{"rank":"10"},
+    "i32700":{"rank":"1"},
+    "i32701":{"rank":"2"},
+    "i32702":{"rank":"3"},
+    "i32699":{"rank":"11"},
+    "i32704":{"rank":"1"},
+    "i32705":{"rank":"2"},
+    "i32706":{"rank":"3"},
+    "i32703":{"rank":"12"},
+    "i32708":{"rank":"1"},
+    "i32709":{"rank":"2"},
+    "i32710":{"rank":"3"},
+    "i32707":{"rank":"13"},
+    "i32712":{"rank":"1"},
+    "i32713":{"rank":"2"},
+    "i32714":{"rank":"3"},
+    "i32715":{"rank":"4"},
+    "i32711":{"rank":"14"},
+    "i32655":{"rank":"1"},
+    "i32718":{"rank":"1"},
+    "i32717":{"rank":"1"},
+    "i32720":{"rank":"1"},
+    "i32722":{"rank":"1"},
+    "i32723":{"rank":"2"},
+    "i32724":{"rank":"3"},
+    "i32721":{"rank":"2"},
+    "i32719":{"rank":"2"},
+    "i32726":{"rank":"1"},
+    "i32725":{"rank":"3"},
+    "i32728":{"rank":"1"},
+    "i32727":{"rank":"4"},
+    "i32729":{"rank":"5"},
+    "i32731":{"rank":"1"},
+    "i32732":{"rank":"2"},
+    "i32730":{"rank":"6"},
+    "i32734":{"rank":"1"},
+    "i32733":{"rank":"7"},
+    "i32736":{"rank":"1"},
+    "i32735":{"rank":"8"},
+    "i32738":{"rank":"1"},
+    "i32739":{"rank":"2"},
+    "i32737":{"rank":"9"},
+    "i32741":{"rank":"1"},
+    "i32740":{"rank":"10"},
+    "i32743":{"rank":"1"},
+    "i32742":{"rank":"11"},
+    "i32745":{"rank":"1"},
+    "i32744":{"rank":"12"},
+    "i32747":{"rank":"1"},
+    "i32748":{"rank":"2"},
+    "i32749":{"rank":"3"},
+    "i32746":{"rank":"13"},
+    "i32716":{"rank":"2"},
+    "i32654":{"rank":"3"},
+    "i32751":{"rank":"1"},
+    "i32750":{"rank":"4"},
+    "i32754":{"rank":"1"},
+    "i32755":{"rank":"2"},
+    "i32756":{"rank":"3"},
+    "i32757":{"rank":"4"},
+    "i32753":{"rank":"1"},
+    "i32759":{"rank":"1"},
+    "i32760":{"rank":"2"},
+    "i32761":{"rank":"3"},
+    "i32758":{"rank":"2"},
+    "i32764":{"rank":"1"},
+    "i32765":{"rank":"2"},
+    "i32763":{"rank":"1"},
+    "i32767":{"rank":"1"},
+    "i32768":{"rank":"2"},
+    "i32766":{"rank":"2"},
+    "i32770":{"rank":"1"},
+    "i32771":{"rank":"2"},
+    "i32769":{"rank":"3"},
+    "i32773":{"rank":"1"},
+    "i32774":{"rank":"2"},
+    "i32772":{"rank":"4"},
+    "i32775":{"rank":"5"},
+    "i32762":{"rank":"3"},
+    "i32752":{"rank":"5"},
+    "i32777":{"rank":"1"},
+    "i32778":{"rank":"2"},
+    "i32780":{"rank":"1"},
+    "i32781":{"rank":"2"},
+    "i32782":{"rank":"3"},
+    "i32783":{"rank":"4"},
+    "i32784":{"rank":"5"},
+    "i32785":{"rank":"6"},
+    "i32786":{"rank":"7"},
+    "i32779":{"rank":"3"},
+    "i32787":{"rank":"4"},
+    "i32776":{"rank":"6"},
+    "i32616":{"rank":"3"},
+    "i32788":{"rank":"4"},
+    "i32791":{"rank":"1"},
+    "i32792":{"rank":"2"},
+    "i32790":{"rank":"1"},
+    "i32793":{"rank":"2"},
+    "i32795":{"rank":"1"},
+    "i32796":{"rank":"2"},
+    "i32797":{"rank":"3"},
+    "i32798":{"rank":"4"},
+    "i32794":{"rank":"3"},
+    "i32800":{"rank":"1"},
+    "i32801":{"rank":"2"},
+    "i32799":{"rank":"4"},
+    "i32803":{"rank":"1"},
+    "i32804":{"rank":"2"},
+    "i32802":{"rank":"5"},
+    "i32789":{"rank":"5"},
+    "i32807":{"rank":"1"},
+    "i32806":{"rank":"1"},
+    "i32809":{"rank":"1"},
+    "i32810":{"rank":"2"},
+    "i32808":{"rank":"2"},
+    "i32812":{"rank":"1"},
+    "i32813":{"rank":"2"},
+    "i32814":{"rank":"3"},
+    "i32811":{"rank":"3"},
+    "i32815":{"rank":"4"},
+    "i32816":{"rank":"5"},
+    "i32805":{"rank":"6"},
+    "i32818":{"rank":"1"},
+    "i32819":{"rank":"2"},
+    "i32820":{"rank":"3"},
+    "i32821":{"rank":"4"},
+    "i32817":{"rank":"7"},
+    "i32823":{"rank":"1"},
+    "i32824":{"rank":"2"},
+    "i32825":{"rank":"3"},
+    "i32826":{"rank":"4"},
+    "i32827":{"rank":"5"},
+    "i32828":{"rank":"6"},
+    "i32822":{"rank":"8"},
+    "i32829":{"rank":"9"},
+    "i32830":{"rank":"10"},
+    "i32831":{"rank":"11"},
+    "p689":{"rank":"11"},
+    "i33080":{"rank":"1"},
+    "i33081":{"rank":"2"},
+    "i33082":{"rank":"3"},
+    "i33083":{"rank":"4"},
+    "i33084":{"rank":"5"},
+    "i33085":{"rank":"6"},
+    "i33086":{"rank":"7"},
+    "i33087":{"rank":"8"},
+    "i33088":{"rank":"9"},
+    "i33089":{"rank":"10"},
+    "i33090":{"rank":"11"},
+    "i33091":{"rank":"12"},
+    "i33092":{"rank":"13"},
+    "i33093":{"rank":"14"},
+    "i33094":{"rank":"15"},
+    "i33095":{"rank":"16"},
+    "i33096":{"rank":"17"},
+    "i33097":{"rank":"18"},
+    "i33098":{"rank":"19"},
+    "i33099":{"rank":"20"},
+    "i33100":{"rank":"21"},
+    "i33101":{"rank":"22"},
+    "i33102":{"rank":"23"},
+    "i33103":{"rank":"24"},
+    "i33104":{"rank":"25"},
+    "i33105":{"rank":"26"},
+    "i33106":{"rank":"27"},
+    "i33079":{"rank":"1"},
+    "i33078":{"rank":"1"},
+    "i33107":{"rank":"2"},
+    "i33111":{"rank":"1"},
+    "i33112":{"rank":"2"},
+    "i33113":{"rank":"3"},
+    "i33110":{"rank":"1"},
+    "i33115":{"rank":"1"},
+    "i33116":{"rank":"2"},
+    "i33117":{"rank":"3"},
+    "i33114":{"rank":"2"},
+    "i33119":{"rank":"1"},
+    "i33120":{"rank":"2"},
+    "i33121":{"rank":"3"},
+    "i33118":{"rank":"3"},
+    "i33123":{"rank":"1"},
+    "i33124":{"rank":"2"},
+    "i33125":{"rank":"3"},
+    "i33122":{"rank":"4"},
+    "i33109":{"rank":"1"},
+    "i33128":{"rank":"1"},
+    "i33129":{"rank":"2"},
+    "i33130":{"rank":"3"},
+    "i33127":{"rank":"1"},
+    "i33132":{"rank":"1"},
+    "i33133":{"rank":"2"},
+    "i33134":{"rank":"3"},
+    "i33131":{"rank":"2"},
+    "i33136":{"rank":"1"},
+    "i33137":{"rank":"2"},
+    "i33138":{"rank":"3"},
+    "i33139":{"rank":"4"},
+    "i33135":{"rank":"3"},
+    "i33142":{"rank":"1"},
+    "i33143":{"rank":"2"},
+    "i33144":{"rank":"3"},
+    "i33141":{"rank":"1"},
+    "i33145":{"rank":"2"},
+    "i33140":{"rank":"4"},
+    "i33126":{"rank":"2"},
+    "i33150":{"rank":"1"},
+    "i33151":{"rank":"2"},
+    "i33149":{"rank":"1"},
+    "i33153":{"rank":"1"},
+    "i33154":{"rank":"2"},
+    "i33152":{"rank":"2"},
+    "i33148":{"rank":"1"},
+    "i33157":{"rank":"1"},
+    "i33158":{"rank":"2"},
+    "i33155":{"rank":"2"},
+    "i33160":{"rank":"1"},
+    "i33161":{"rank":"2"},
+    "i33162":{"rank":"3"},
+    "i33159":{"rank":"3"},
+    "i33164":{"rank":"1"},
+    "i33165":{"rank":"2"},
+    "i33166":{"rank":"3"},
+    "i33163":{"rank":"4"},
+    "i33168":{"rank":"1"},
+    "i33169":{"rank":"2"},
+    "i33170":{"rank":"3"},
+    "i33167":{"rank":"5"},
+    "i33172":{"rank":"1"},
+    "i33173":{"rank":"2"},
+    "i33174":{"rank":"3"},
+    "i33171":{"rank":"6"},
+    "i33176":{"rank":"1"},
+    "i33177":{"rank":"2"},
+    "i33178":{"rank":"3"},
+    "i33175":{"rank":"7"},
+    "i33180":{"rank":"1"},
+    "i33181":{"rank":"2"},
+    "i33182":{"rank":"3"},
+    "i33179":{"rank":"8"},
+    "i33184":{"rank":"1"},
+    "i33185":{"rank":"2"},
+    "i33186":{"rank":"3"},
+    "i33183":{"rank":"9"},
+    "i33188":{"rank":"1"},
+    "i33189":{"rank":"2"},
+    "i33190":{"rank":"3"},
+    "i33187":{"rank":"10"},
+    "i33192":{"rank":"1"},
+    "i33193":{"rank":"2"},
+    "i33194":{"rank":"3"},
+    "i33191":{"rank":"11"},
+    "i33196":{"rank":"1"},
+    "i33197":{"rank":"2"},
+    "i33198":{"rank":"3"},
+    "i33195":{"rank":"12"},
+    "i33200":{"rank":"1"},
+    "i33201":{"rank":"2"},
+    "i33202":{"rank":"3"},
+    "i33199":{"rank":"13"},
+    "i33204":{"rank":"1"},
+    "i33205":{"rank":"2"},
+    "i33206":{"rank":"3"},
+    "i33207":{"rank":"4"},
+    "i33203":{"rank":"14"},
+    "i33147":{"rank":"1"},
+    "i33210":{"rank":"1"},
+    "i33209":{"rank":"1"},
+    "i33212":{"rank":"1"},
+    "i33214":{"rank":"1"},
+    "i33215":{"rank":"2"},
+    "i33216":{"rank":"3"},
+    "i33213":{"rank":"2"},
+    "i33211":{"rank":"2"},
+    "i33218":{"rank":"1"},
+    "i33217":{"rank":"3"},
+    "i33220":{"rank":"1"},
+    "i33219":{"rank":"4"},
+    "i33221":{"rank":"5"},
+    "i33223":{"rank":"1"},
+    "i33224":{"rank":"2"},
+    "i33222":{"rank":"6"},
+    "i33226":{"rank":"1"},
+    "i33225":{"rank":"7"},
+    "i33228":{"rank":"1"},
+    "i33227":{"rank":"8"},
+    "i33230":{"rank":"1"},
+    "i33231":{"rank":"2"},
+    "i33229":{"rank":"9"},
+    "i33233":{"rank":"1"},
+    "i33232":{"rank":"10"},
+    "i33235":{"rank":"1"},
+    "i33234":{"rank":"11"},
+    "i33237":{"rank":"1"},
+    "i33236":{"rank":"12"},
+    "i33239":{"rank":"1"},
+    "i33240":{"rank":"2"},
+    "i33241":{"rank":"3"},
+    "i33238":{"rank":"13"},
+    "i33208":{"rank":"2"},
+    "i33146":{"rank":"3"},
+    "i33243":{"rank":"1"},
+    "i33242":{"rank":"4"},
+    "i33246":{"rank":"1"},
+    "i33247":{"rank":"2"},
+    "i33248":{"rank":"3"},
+    "i33249":{"rank":"4"},
+    "i33245":{"rank":"1"},
+    "i33251":{"rank":"1"},
+    "i33252":{"rank":"2"},
+    "i33253":{"rank":"3"},
+    "i33250":{"rank":"2"},
+    "i33256":{"rank":"1"},
+    "i33257":{"rank":"2"},
+    "i33255":{"rank":"1"},
+    "i33259":{"rank":"1"},
+    "i33260":{"rank":"2"},
+    "i33258":{"rank":"2"},
+    "i33262":{"rank":"1"},
+    "i33263":{"rank":"2"},
+    "i33261":{"rank":"3"},
+    "i33265":{"rank":"1"},
+    "i33266":{"rank":"2"},
+    "i33264":{"rank":"4"},
+    "i33267":{"rank":"5"},
+    "i33254":{"rank":"3"},
+    "i33244":{"rank":"5"},
+    "i33269":{"rank":"1"},
+    "i33270":{"rank":"2"},
+    "i33272":{"rank":"1"},
+    "i33273":{"rank":"2"},
+    "i33274":{"rank":"3"},
+    "i33275":{"rank":"4"},
+    "i33276":{"rank":"5"},
+    "i33277":{"rank":"6"},
+    "i33278":{"rank":"7"},
+    "i33271":{"rank":"3"},
+    "i33279":{"rank":"4"},
+    "i33268":{"rank":"6"},
+    "i33108":{"rank":"3"},
+    "i33280":{"rank":"4"},
+    "i33283":{"rank":"1"},
+    "i33284":{"rank":"2"},
+    "i33282":{"rank":"1"},
+    "i33285":{"rank":"2"},
+    "i33287":{"rank":"1"},
+    "i33288":{"rank":"2"},
+    "i33289":{"rank":"3"},
+    "i33290":{"rank":"4"},
+    "i33286":{"rank":"3"},
+    "i33292":{"rank":"1"},
+    "i33293":{"rank":"2"},
+    "i33291":{"rank":"4"},
+    "i33295":{"rank":"1"},
+    "i33296":{"rank":"2"},
+    "i33294":{"rank":"5"},
+    "i33281":{"rank":"5"},
+    "i33299":{"rank":"1"},
+    "i33298":{"rank":"1"},
+    "i33301":{"rank":"1"},
+    "i33302":{"rank":"2"},
+    "i33300":{"rank":"2"},
+    "i33304":{"rank":"1"},
+    "i33305":{"rank":"2"},
+    "i33306":{"rank":"3"},
+    "i33303":{"rank":"3"},
+    "i33307":{"rank":"4"},
+    "i33308":{"rank":"5"},
+    "i33297":{"rank":"6"},
+    "i33310":{"rank":"1"},
+    "i33311":{"rank":"2"},
+    "i33312":{"rank":"3"},
+    "i33313":{"rank":"4"},
+    "i33309":{"rank":"7"},
+    "i33315":{"rank":"1"},
+    "i33316":{"rank":"2"},
+    "i33317":{"rank":"3"},
+    "i33318":{"rank":"4"},
+    "i33319":{"rank":"5"},
+    "i33320":{"rank":"6"},
+    "i33314":{"rank":"8"},
+    "i33321":{"rank":"9"},
+    "i33322":{"rank":"10"},
+    "i33323":{"rank":"11"},
+    "p687":{"rank":"12"},
+    "i17511":{"rank":"1"},
+    "i28408":{"rank":"2"},
+    "p519":{"rank":"13"},
+    "p581":{"rank":"14"},
+    "i24225":{"rank":"1"},
+    "i24233":{"rank":"2"},
+    "i24234":{"rank":"3"},
+    "p593":{"rank":"15"},
+    "i30468":{"rank":"1"},
+    "i30469":{"rank":"2"},
+    "i30470":{"rank":"3"},
+    "i30471":{"rank":"4"},
+    "i30472":{"rank":"5"},
+    "i30473":{"rank":"6"},
+    "i30474":{"rank":"7"},
+    "i30475":{"rank":"8"},
+    "i30476":{"rank":"9"},
+    "i30477":{"rank":"10"},
+    "i30478":{"rank":"11"},
+    "i30479":{"rank":"12"},
+    "i30480":{"rank":"13"},
+    "i30481":{"rank":"14"},
+    "i30482":{"rank":"15"},
+    "i30483":{"rank":"16"},
+    "i30484":{"rank":"17"},
+    "i30485":{"rank":"18"},
+    "i30486":{"rank":"19"},
+    "i30487":{"rank":"20"},
+    "i30488":{"rank":"21"},
+    "i30489":{"rank":"22"},
+    "i30490":{"rank":"23"},
+    "i30491":{"rank":"24"},
+    "i30492":{"rank":"25"},
+    "i30493":{"rank":"26"},
+    "i30494":{"rank":"27"},
+    "i30467":{"rank":"1"},
+    "i30466":{"rank":"1"},
+    "i30495":{"rank":"2"},
+    "i30499":{"rank":"1"},
+    "i30500":{"rank":"2"},
+    "i30501":{"rank":"3"},
+    "i30498":{"rank":"1"},
+    "i30503":{"rank":"1"},
+    "i30504":{"rank":"2"},
+    "i30505":{"rank":"3"},
+    "i30502":{"rank":"2"},
+    "i30509":{"rank":"1"},
+    "i30506":{"rank":"3"},
+    "i30511":{"rank":"1"},
+    "i30512":{"rank":"2"},
+    "i30513":{"rank":"3"},
+    "i30510":{"rank":"4"},
+    "i30497":{"rank":"1"},
+    "i30516":{"rank":"1"},
+    "i30517":{"rank":"2"},
+    "i30518":{"rank":"3"},
+    "i30515":{"rank":"1"},
+    "i30520":{"rank":"1"},
+    "i30521":{"rank":"2"},
+    "i30522":{"rank":"3"},
+    "i30519":{"rank":"2"},
+    "i30524":{"rank":"1"},
+    "i30525":{"rank":"2"},
+    "i30526":{"rank":"3"},
+    "i30527":{"rank":"4"},
+    "i30523":{"rank":"3"},
+    "i30530":{"rank":"1"},
+    "i30531":{"rank":"2"},
+    "i30532":{"rank":"3"},
+    "i30529":{"rank":"1"},
+    "i30533":{"rank":"2"},
+    "i30528":{"rank":"4"},
+    "i30514":{"rank":"2"},
+    "i30538":{"rank":"1"},
+    "i30539":{"rank":"2"},
+    "i30537":{"rank":"1"},
+    "i30541":{"rank":"1"},
+    "i30542":{"rank":"2"},
+    "i30540":{"rank":"2"},
+    "i30536":{"rank":"1"},
+    "i30544":{"rank":"1"},
+    "i30545":{"rank":"2"},
+    "i30546":{"rank":"3"},
+    "i30543":{"rank":"2"},
+    "i30548":{"rank":"1"},
+    "i30549":{"rank":"2"},
+    "i30550":{"rank":"3"},
+    "i30547":{"rank":"3"},
+    "i30552":{"rank":"1"},
+    "i30553":{"rank":"2"},
+    "i30554":{"rank":"3"},
+    "i30551":{"rank":"4"},
+    "i30556":{"rank":"1"},
+    "i30557":{"rank":"2"},
+    "i30558":{"rank":"3"},
+    "i30555":{"rank":"5"},
+    "i30560":{"rank":"1"},
+    "i30561":{"rank":"2"},
+    "i30562":{"rank":"3"},
+    "i30559":{"rank":"6"},
+    "i30564":{"rank":"1"},
+    "i30565":{"rank":"2"},
+    "i30566":{"rank":"3"},
+    "i30563":{"rank":"7"},
+    "i30569":{"rank":"1"},
+    "i30570":{"rank":"2"},
+    "i30567":{"rank":"8"},
+    "i30572":{"rank":"1"},
+    "i30573":{"rank":"2"},
+    "i30574":{"rank":"3"},
+    "i30571":{"rank":"9"},
+    "i30576":{"rank":"1"},
+    "i30577":{"rank":"2"},
+    "i30578":{"rank":"3"},
+    "i30575":{"rank":"10"},
+    "i30580":{"rank":"1"},
+    "i30581":{"rank":"2"},
+    "i30582":{"rank":"3"},
+    "i30579":{"rank":"11"},
+    "i30584":{"rank":"1"},
+    "i30585":{"rank":"2"},
+    "i30586":{"rank":"3"},
+    "i30583":{"rank":"12"},
+    "i30588":{"rank":"1"},
+    "i30589":{"rank":"2"},
+    "i30590":{"rank":"3"},
+    "i30587":{"rank":"13"},
+    "i30592":{"rank":"1"},
+    "i30593":{"rank":"2"},
+    "i30594":{"rank":"3"},
+    "i30595":{"rank":"4"},
+    "i30591":{"rank":"14"},
+    "i30535":{"rank":"1"},
+    "i30598":{"rank":"1"},
+    "i30597":{"rank":"1"},
+    "i30600":{"rank":"1"},
+    "i30602":{"rank":"1"},
+    "i30603":{"rank":"2"},
+    "i30604":{"rank":"3"},
+    "i30601":{"rank":"2"},
+    "i30599":{"rank":"2"},
+    "i30606":{"rank":"1"},
+    "i30605":{"rank":"3"},
+    "i30608":{"rank":"1"},
+    "i30607":{"rank":"4"},
+    "i30609":{"rank":"5"},
+    "i30611":{"rank":"1"},
+    "i30612":{"rank":"2"},
+    "i30610":{"rank":"6"},
+    "i30614":{"rank":"1"},
+    "i30613":{"rank":"7"},
+    "i30616":{"rank":"1"},
+    "i30615":{"rank":"8"},
+    "i30618":{"rank":"1"},
+    "i30619":{"rank":"2"},
+    "i30617":{"rank":"9"},
+    "i30621":{"rank":"1"},
+    "i30620":{"rank":"10"},
+    "i30623":{"rank":"1"},
+    "i30622":{"rank":"11"},
+    "i30625":{"rank":"1"},
+    "i30624":{"rank":"12"},
+    "i30627":{"rank":"1"},
+    "i30628":{"rank":"2"},
+    "i30629":{"rank":"3"},
+    "i30626":{"rank":"13"},
+    "i30596":{"rank":"2"},
+    "i30534":{"rank":"3"},
+    "i30631":{"rank":"1"},
+    "i30630":{"rank":"4"},
+    "i30634":{"rank":"1"},
+    "i30635":{"rank":"2"},
+    "i30636":{"rank":"3"},
+    "i30637":{"rank":"4"},
+    "i30633":{"rank":"1"},
+    "i30639":{"rank":"1"},
+    "i30640":{"rank":"2"},
+    "i30641":{"rank":"3"},
+    "i30638":{"rank":"2"},
+    "i30644":{"rank":"1"},
+    "i30645":{"rank":"2"},
+    "i30643":{"rank":"1"},
+    "i30647":{"rank":"1"},
+    "i30648":{"rank":"2"},
+    "i30646":{"rank":"2"},
+    "i30650":{"rank":"1"},
+    "i30651":{"rank":"2"},
+    "i30649":{"rank":"3"},
+    "i30653":{"rank":"1"},
+    "i30654":{"rank":"2"},
+    "i30652":{"rank":"4"},
+    "i30655":{"rank":"5"},
+    "i30642":{"rank":"3"},
+    "i30632":{"rank":"5"},
+    "i30657":{"rank":"1"},
+    "i30658":{"rank":"2"},
+    "i30660":{"rank":"1"},
+    "i30661":{"rank":"2"},
+    "i30662":{"rank":"3"},
+    "i30663":{"rank":"4"},
+    "i30665":{"rank":"5"},
+    "i30659":{"rank":"3"},
+    "i30667":{"rank":"4"},
+    "i30656":{"rank":"6"},
+    "i30496":{"rank":"3"},
+    "i30668":{"rank":"4"},
+    "i30671":{"rank":"1"},
+    "i30672":{"rank":"2"},
+    "i30670":{"rank":"1"},
+    "i30673":{"rank":"2"},
+    "i30675":{"rank":"1"},
+    "i30676":{"rank":"2"},
+    "i30677":{"rank":"3"},
+    "i30678":{"rank":"4"},
+    "i30674":{"rank":"3"},
+    "i30680":{"rank":"1"},
+    "i30681":{"rank":"2"},
+    "i30679":{"rank":"4"},
+    "i30683":{"rank":"1"},
+    "i30684":{"rank":"2"},
+    "i30682":{"rank":"5"},
+    "i30669":{"rank":"5"},
+    "i30687":{"rank":"1"},
+    "i30686":{"rank":"1"},
+    "i30689":{"rank":"1"},
+    "i30690":{"rank":"2"},
+    "i30688":{"rank":"2"},
+    "i30692":{"rank":"1"},
+    "i30693":{"rank":"2"},
+    "i30694":{"rank":"3"},
+    "i30691":{"rank":"3"},
+    "i30695":{"rank":"4"},
+    "i30696":{"rank":"5"},
+    "i30685":{"rank":"6"},
+    "i30698":{"rank":"1"},
+    "i30699":{"rank":"2"},
+    "i30700":{"rank":"3"},
+    "i30701":{"rank":"4"},
+    "i30697":{"rank":"7"},
+    "i30703":{"rank":"1"},
+    "i30704":{"rank":"2"},
+    "i30705":{"rank":"3"},
+    "i30706":{"rank":"4"},
+    "i30707":{"rank":"5"},
+    "i30708":{"rank":"6"},
+    "i30702":{"rank":"8"},
+    "i30709":{"rank":"9"},
+    "i30710":{"rank":"10"},
+    "i30711":{"rank":"11"},
+    "p648":{"rank":"16"},
+    "i28618":{"rank":"1"},
+    "i28619":{"rank":"2"},
+    "i28620":{"rank":"3"},
+    "i28621":{"rank":"4"},
+    "i28622":{"rank":"5"},
+    "i28623":{"rank":"6"},
+    "i28624":{"rank":"7"},
+    "i28625":{"rank":"8"},
+    "i28626":{"rank":"9"},
+    "i28627":{"rank":"10"},
+    "i28628":{"rank":"11"},
+    "i28629":{"rank":"12"},
+    "i28630":{"rank":"13"},
+    "i28631":{"rank":"14"},
+    "i28632":{"rank":"15"},
+    "i28633":{"rank":"16"},
+    "i28634":{"rank":"17"},
+    "i28635":{"rank":"18"},
+    "i28636":{"rank":"19"},
+    "i28637":{"rank":"20"},
+    "i28638":{"rank":"21"},
+    "i28639":{"rank":"22"},
+    "i28640":{"rank":"23"},
+    "i28641":{"rank":"24"},
+    "i28642":{"rank":"25"},
+    "i28643":{"rank":"26"},
+    "i28644":{"rank":"27"},
+    "i28617":{"rank":"1"},
+    "i28616":{"rank":"1"},
+    "i28645":{"rank":"2"},
+    "i28649":{"rank":"1"},
+    "i28650":{"rank":"2"},
+    "i28651":{"rank":"3"},
+    "i28648":{"rank":"1"},
+    "i28653":{"rank":"1"},
+    "i28654":{"rank":"2"},
+    "i28655":{"rank":"3"},
+    "i28652":{"rank":"2"},
+    "i28657":{"rank":"1"},
+    "i28658":{"rank":"2"},
+    "i28659":{"rank":"3"},
+    "i28656":{"rank":"3"},
+    "i28661":{"rank":"1"},
+    "i28662":{"rank":"2"},
+    "i28663":{"rank":"3"},
+    "i28660":{"rank":"4"},
+    "i28647":{"rank":"1"},
+    "i28666":{"rank":"1"},
+    "i28667":{"rank":"2"},
+    "i28668":{"rank":"3"},
+    "i28665":{"rank":"1"},
+    "i28670":{"rank":"1"},
+    "i28671":{"rank":"2"},
+    "i28672":{"rank":"3"},
+    "i28669":{"rank":"2"},
+    "i28674":{"rank":"1"},
+    "i28675":{"rank":"2"},
+    "i28676":{"rank":"3"},
+    "i28677":{"rank":"4"},
+    "i28673":{"rank":"3"},
+    "i28680":{"rank":"1"},
+    "i28681":{"rank":"2"},
+    "i28682":{"rank":"3"},
+    "i28679":{"rank":"1"},
+    "i28683":{"rank":"2"},
+    "i28678":{"rank":"4"},
+    "i28664":{"rank":"2"},
+    "i28688":{"rank":"1"},
+    "i28689":{"rank":"2"},
+    "i28687":{"rank":"1"},
+    "i28691":{"rank":"1"},
+    "i28692":{"rank":"2"},
+    "i28690":{"rank":"2"},
+    "i28686":{"rank":"1"},
+    "i28694":{"rank":"1"},
+    "i28695":{"rank":"2"},
+    "i28696":{"rank":"3"},
+    "i28693":{"rank":"2"},
+    "i28698":{"rank":"1"},
+    "i28699":{"rank":"2"},
+    "i28700":{"rank":"3"},
+    "i28697":{"rank":"3"},
+    "i28702":{"rank":"1"},
+    "i28703":{"rank":"2"},
+    "i28704":{"rank":"3"},
+    "i28701":{"rank":"4"},
+    "i28706":{"rank":"1"},
+    "i28707":{"rank":"2"},
+    "i28708":{"rank":"3"},
+    "i28705":{"rank":"5"},
+    "i28710":{"rank":"1"},
+    "i28711":{"rank":"2"},
+    "i28712":{"rank":"3"},
+    "i28709":{"rank":"6"},
+    "i28714":{"rank":"1"},
+    "i28715":{"rank":"2"},
+    "i28716":{"rank":"3"},
+    "i28713":{"rank":"7"},
+    "i28719":{"rank":"1"},
+    "i28720":{"rank":"2"},
+    "i28717":{"rank":"8"},
+    "i28722":{"rank":"1"},
+    "i28723":{"rank":"2"},
+    "i28724":{"rank":"3"},
+    "i28721":{"rank":"9"},
+    "i28726":{"rank":"1"},
+    "i28727":{"rank":"2"},
+    "i28728":{"rank":"3"},
+    "i28725":{"rank":"10"},
+    "i28730":{"rank":"1"},
+    "i28731":{"rank":"2"},
+    "i28732":{"rank":"3"},
+    "i28729":{"rank":"11"},
+    "i28734":{"rank":"1"},
+    "i28735":{"rank":"2"},
+    "i28736":{"rank":"3"},
+    "i28733":{"rank":"12"},
+    "i28738":{"rank":"1"},
+    "i28739":{"rank":"2"},
+    "i28740":{"rank":"3"},
+    "i28737":{"rank":"13"},
+    "i28742":{"rank":"1"},
+    "i28743":{"rank":"2"},
+    "i28744":{"rank":"3"},
+    "i28745":{"rank":"4"},
+    "i28741":{"rank":"14"},
+    "i28685":{"rank":"1"},
+    "i28748":{"rank":"1"},
+    "i28747":{"rank":"1"},
+    "i28750":{"rank":"1"},
+    "i28752":{"rank":"1"},
+    "i28753":{"rank":"2"},
+    "i28754":{"rank":"3"},
+    "i28751":{"rank":"2"},
+    "i28749":{"rank":"2"},
+    "i28756":{"rank":"1"},
+    "i28755":{"rank":"3"},
+    "i28758":{"rank":"1"},
+    "i28757":{"rank":"4"},
+    "i28759":{"rank":"5"},
+    "i28761":{"rank":"1"},
+    "i28762":{"rank":"2"},
+    "i28760":{"rank":"6"},
+    "i28764":{"rank":"1"},
+    "i28763":{"rank":"7"},
+    "i28766":{"rank":"1"},
+    "i28765":{"rank":"8"},
+    "i28768":{"rank":"1"},
+    "i28769":{"rank":"2"},
+    "i28767":{"rank":"9"},
+    "i28771":{"rank":"1"},
+    "i28770":{"rank":"10"},
+    "i28773":{"rank":"1"},
+    "i28772":{"rank":"11"},
+    "i28775":{"rank":"1"},
+    "i28774":{"rank":"12"},
+    "i28777":{"rank":"1"},
+    "i28778":{"rank":"2"},
+    "i28779":{"rank":"3"},
+    "i28776":{"rank":"13"},
+    "i28746":{"rank":"2"},
+    "i28684":{"rank":"3"},
+    "i28781":{"rank":"1"},
+    "i28780":{"rank":"4"},
+    "i28784":{"rank":"1"},
+    "i28785":{"rank":"2"},
+    "i28786":{"rank":"3"},
+    "i28787":{"rank":"4"},
+    "i28783":{"rank":"1"},
+    "i28789":{"rank":"1"},
+    "i28790":{"rank":"2"},
+    "i28791":{"rank":"3"},
+    "i28788":{"rank":"2"},
+    "i28794":{"rank":"1"},
+    "i28795":{"rank":"2"},
+    "i28793":{"rank":"1"},
+    "i28797":{"rank":"1"},
+    "i28798":{"rank":"2"},
+    "i28796":{"rank":"2"},
+    "i28800":{"rank":"1"},
+    "i28801":{"rank":"2"},
+    "i28799":{"rank":"3"},
+    "i28803":{"rank":"1"},
+    "i28804":{"rank":"2"},
+    "i28802":{"rank":"4"},
+    "i28805":{"rank":"5"},
+    "i28792":{"rank":"3"},
+    "i28782":{"rank":"5"},
+    "i28807":{"rank":"1"},
+    "i28808":{"rank":"2"},
+    "i28810":{"rank":"1"},
+    "i28812":{"rank":"2"},
+    "i28813":{"rank":"3"},
+    "i28815":{"rank":"4"},
+    "i28809":{"rank":"3"},
+    "i28817":{"rank":"4"},
+    "i28806":{"rank":"6"},
+    "i28646":{"rank":"3"},
+    "i28818":{"rank":"4"},
+    "i28821":{"rank":"1"},
+    "i28822":{"rank":"2"},
+    "i28820":{"rank":"1"},
+    "i28823":{"rank":"2"},
+    "i28825":{"rank":"1"},
+    "i28826":{"rank":"2"},
+    "i28827":{"rank":"3"},
+    "i28828":{"rank":"4"},
+    "i28824":{"rank":"3"},
+    "i28830":{"rank":"1"},
+    "i28831":{"rank":"2"},
+    "i28829":{"rank":"4"},
+    "i28833":{"rank":"1"},
+    "i28834":{"rank":"2"},
+    "i28832":{"rank":"5"},
+    "i28819":{"rank":"5"},
+    "i28837":{"rank":"1"},
+    "i28836":{"rank":"1"},
+    "i28839":{"rank":"1"},
+    "i28840":{"rank":"2"},
+    "i28838":{"rank":"2"},
+    "i28842":{"rank":"1"},
+    "i28843":{"rank":"2"},
+    "i28844":{"rank":"3"},
+    "i28841":{"rank":"3"},
+    "i28845":{"rank":"4"},
+    "i28846":{"rank":"5"},
+    "i28835":{"rank":"6"},
+    "i28848":{"rank":"1"},
+    "i28849":{"rank":"2"},
+    "i28850":{"rank":"3"},
+    "i28851":{"rank":"4"},
+    "i28847":{"rank":"7"},
+    "i28853":{"rank":"1"},
+    "i28854":{"rank":"2"},
+    "i28855":{"rank":"3"},
+    "i28856":{"rank":"4"},
+    "i28857":{"rank":"5"},
+    "i28858":{"rank":"6"},
+    "i28852":{"rank":"8"},
+    "i28859":{"rank":"9"},
+    "i28860":{"rank":"10"},
+    "i28861":{"rank":"11"},
+    "p639":{"rank":"17"},
+    "p129":{"rank":"-1"},
+    "p610":{"rank":"1"},
+    "i26848":{"rank":"1"},
+    "i13767":{"rank":"1"},
+    "i14909":{"rank":"2"},
+    "i19995":{"rank":"1"},
+    "i16921":{"rank":"3"},
+    "i17047":{"rank":"4"},
+    "i12332":{"rank":"1"},
+    "i18444":{"rank":"2"},
+    "i17048":{"rank":"5"},
+    "i17052":{"rank":"6"},
+    "p333":{"rank":"1"},
+    "p325":{"rank":"2"},
+    "i13491":{"rank":"1"},
+    "i13534":{"rank":"2"},
+    "i10233":{"rank":"1"},
+    "i13489":{"rank":"1"},
+    "i13535":{"rank":"2"},
+    "i10234":{"rank":"2"},
+    "i22063":{"rank":"1"},
+    "i13487":{"rank":"1"},
+    "i13537":{"rank":"2"},
+    "i10235":{"rank":"3"},
+    "i13485":{"rank":"1"},
+    "i13538":{"rank":"2"},
+    "i10236":{"rank":"4"},
+    "i10232":{"rank":"1"},
+    "i13473":{"rank":"1"},
+    "i13539":{"rank":"2"},
+    "i10248":{"rank":"1"},
+    "i13475":{"rank":"1"},
+    "i13540":{"rank":"2"},
+    "i10249":{"rank":"2"},
+    "i13477":{"rank":"1"},
+    "i13541":{"rank":"2"},
+    "i10250":{"rank":"3"},
+    "i13479":{"rank":"1"},
+    "i13542":{"rank":"2"},
+    "i10251":{"rank":"4"},
+    "i22064":{"rank":"1"},
+    "i13481":{"rank":"1"},
+    "i13543":{"rank":"2"},
+    "i10252":{"rank":"5"},
+    "i22065":{"rank":"1"},
+    "i13483":{"rank":"1"},
+    "i13544":{"rank":"2"},
+    "i10253":{"rank":"6"},
+    "i20749":{"rank":"1"},
+    "i20751":{"rank":"2"},
+    "i21083":{"rank":"3"},
+    "i20767":{"rank":"7"},
+    "i10237":{"rank":"2"},
+    "i10231":{"rank":"1"},
+    "i12674":{"rank":"1"},
+    "i13515":{"rank":"2"},
+    "i14388":{"rank":"3"},
+    "i10247":{"rank":"1"},
+    "i13549":{"rank":"1"},
+    "i13550":{"rank":"2"},
+    "i14389":{"rank":"3"},
+    "i10398":{"rank":"2"},
+    "i10238":{"rank":"2"},
+    "i14456":{"rank":"3"},
+    "i20810":{"rank":"1"},
+    "i20811":{"rank":"2"},
+    "i20812":{"rank":"3"},
+    "i20813":{"rank":"4"},
+    "i20816":{"rank":"5"},
+    "i20817":{"rank":"6"},
+    "i20808":{"rank":"4"},
+    "i20824":{"rank":"1"},
+    "i20825":{"rank":"2"},
+    "i20826":{"rank":"3"},
+    "i20827":{"rank":"4"},
+    "i20828":{"rank":"5"},
+    "i20829":{"rank":"6"},
+    "i20818":{"rank":"5"},
+    "i20830":{"rank":"6"},
+    "i30029":{"rank":"7"},
+    "i30033":{"rank":"8"},
+    "p417":{"rank":"3"},
+    "i29168":{"rank":"1"},
+    "i29170":{"rank":"2"},
+    "i17074":{"rank":"1"},
+    "i17069":{"rank":"1"},
+    "i17748":{"rank":"2"},
+    "i17065":{"rank":"1"},
+    "i28294":{"rank":"2"},
+    "p511":{"rank":"4"},
+    "i19700":{"rank":"1"},
+    "i19701":{"rank":"2"},
+    "i19709":{"rank":"1"},
+    "i19710":{"rank":"2"},
+    "i19711":{"rank":"3"},
+    "i19712":{"rank":"4"},
+    "i19713":{"rank":"5"},
+    "i19714":{"rank":"6"},
+    "i19708":{"rank":"1"},
+    "i19704":{"rank":"3"},
+    "i19716":{"rank":"1"},
+    "i19715":{"rank":"1"},
+    "i19705":{"rank":"4"},
+    "i19918":{"rank":"5"},
+    "i33498":{"rank":"6"},
+    "i33513":{"rank":"7"},
+    "i33514":{"rank":"8"},
+    "i33515":{"rank":"9"},
+    "i33517":{"rank":"1"},
+    "i33518":{"rank":"2"},
+    "i33520":{"rank":"3"},
+    "i33516":{"rank":"10"},
+    "p543":{"rank":"5"},
+    "i24963":{"rank":"1"},
+    "i23324":{"rank":"1"},
+    "i25927":{"rank":"2"},
+    "i30913":{"rank":"3"},
+    "i16842":{"rank":"1"},
+    "i19900":{"rank":"2"},
+    "i25128":{"rank":"1"},
+    "i30912":{"rank":"2"},
+    "i23706":{"rank":"3"},
+    "i24970":{"rank":"2"},
+    "p507":{"rank":"6"},
+    "i24460":{"rank":"1"},
+    "i29080":{"rank":"1"},
+    "i24461":{"rank":"2"},
+    "p594":{"rank":"7"},
+    "i29034":{"rank":"1"},
+    "i29083":{"rank":"1"},
+    "i29084":{"rank":"2"},
+    "i29085":{"rank":"3"},
+    "i29086":{"rank":"4"},
+    "i29087":{"rank":"5"},
+    "i29337":{"rank":"6"},
+    "i29035":{"rank":"2"},
+    "i31521":{"rank":"1"},
+    "i31360":{"rank":"1"},
+    "i31359":{"rank":"3"},
+    "p643":{"rank":"8"},
+    "p539":{"rank":"9"},
+    "i28550":{"rank":"1"},
+    "i28566":{"rank":"1"},
+    "i28560":{"rank":"1"},
+    "i28569":{"rank":"1"},
+    "i28570":{"rank":"2"},
+    "i28571":{"rank":"3"},
+    "i31879":{"rank":"4"},
+    "i28561":{"rank":"2"},
+    "i28573":{"rank":"1"},
+    "i28574":{"rank":"2"},
+    "i28562":{"rank":"3"},
+    "i28572":{"rank":"1"},
+    "i28563":{"rank":"4"},
+    "i28609":{"rank":"5"},
+    "i28551":{"rank":"2"},
+    "i31597":{"rank":"1"},
+    "i31598":{"rank":"2"},
+    "i31596":{"rank":"1"},
+    "i31602":{"rank":"1"},
+    "i31599":{"rank":"2"},
+    "i31603":{"rank":"1"},
+    "i31605":{"rank":"1"},
+    "i31604":{"rank":"2"},
+    "i31600":{"rank":"3"},
+    "i31595":{"rank":"3"},
+    "i31606":{"rank":"4"},
+    "p637":{"rank":"10"},
+    "i31037":{"rank":"1"},
+    "i18050":{"rank":"1"},
+    "i20648":{"rank":"1"},
+    "i28536":{"rank":"1"},
+    "i28537":{"rank":"2"},
+    "i31316":{"rank":"3"},
+    "i31378":{"rank":"4"},
+    "i28534":{"rank":"2"},
+    "i18051":{"rank":"2"},
+    "i26156":{"rank":"3"},
+    "p537":{"rank":"11"},
+    "i23336":{"rank":"1"},
+    "i29243":{"rank":"2"},
+    "i29244":{"rank":"3"},
+    "i29245":{"rank":"4"},
+    "i29241":{"rank":"1"},
+    "i31168":{"rank":"1"},
+    "i28041":{"rank":"1"},
+    "i25108":{"rank":"1"},
+    "i25107":{"rank":"1"},
+    "i25114":{"rank":"1"},
+    "i25113":{"rank":"2"},
+    "i23337":{"rank":"1"},
+    "i29246":{"rank":"2"},
+    "i29242":{"rank":"2"},
+    "i32089":{"rank":"1"},
+    "i32090":{"rank":"2"},
+    "i31047":{"rank":"1"},
+    "i31048":{"rank":"2"},
+    "i31049":{"rank":"3"},
+    "i29249":{"rank":"3"},
+    "i29252":{"rank":"1"},
+    "i29253":{"rank":"2"},
+    "i29254":{"rank":"3"},
+    "i29250":{"rank":"4"},
+    "i29255":{"rank":"1"},
+    "i29251":{"rank":"5"},
+    "p541":{"rank":"12"},
+    "p447":{"rank":"2"},
+    "i32142":{"rank":"1"},
+    "i32143":{"rank":"2"},
+    "i32145":{"rank":"3"},
+    "i32407":{"rank":"4"},
+    "i32408":{"rank":"5"},
+    "i32131":{"rank":"1"},
+    "i32133":{"rank":"2"},
+    "i32134":{"rank":"3"},
+    "i32132":{"rank":"1"},
+    "i32138":{"rank":"1"},
+    "i32139":{"rank":"2"},
+    "i32137":{"rank":"2"},
+    "i32141":{"rank":"1"},
+    "i32140":{"rank":"3"},
+    "p680":{"rank":"1"},
+    "i30382":{"rank":"1"},
+    "i30384":{"rank":"2"},
+    "i30385":{"rank":"3"},
+    "i20396":{"rank":"1"},
+    "i18111":{"rank":"1"},
+    "i29964":{"rank":"1"},
+    "i18115":{"rank":"2"},
+    "i20388":{"rank":"1"},
+    "i18118":{"rank":"3"},
+    "i31534":{"rank":"1"},
+    "i20390":{"rank":"1"},
+    "i18119":{"rank":"4"},
+    "i19866":{"rank":"5"},
+    "i32413":{"rank":"1"},
+    "i30270":{"rank":"6"},
+    "i31174":{"rank":"1"},
+    "i31177":{"rank":"2"},
+    "i31173":{"rank":"1"},
+    "i31172":{"rank":"7"},
+    "i17971":{"rank":"1"},
+    "p535":{"rank":"2"},
+    "p681":{"rank":"3"},
+    "i31238":{"rank":"1"},
+    "i31239":{"rank":"2"},
+    "i31240":{"rank":"3"},
+    "i31237":{"rank":"1"},
+    "i31232":{"rank":"1"},
+    "i31234":{"rank":"2"},
+    "i31158":{"rank":"1"},
+    "i31159":{"rank":"2"},
+    "i31236":{"rank":"3"},
+    "i31241":{"rank":"2"},
+    "i31945":{"rank":"1"},
+    "i31242":{"rank":"3"},
+    "i31243":{"rank":"4"},
+    "i31872":{"rank":"1"},
+    "i31873":{"rank":"2"},
+    "i31874":{"rank":"3"},
+    "i31871":{"rank":"1"},
+    "i31519":{"rank":"1"},
+    "i31875":{"rank":"2"},
+    "i31515":{"rank":"5"},
+    "i31195":{"rank":"1"},
+    "i31209":{"rank":"1"},
+    "i31210":{"rank":"2"},
+    "i31196":{"rank":"2"},
+    "i31211":{"rank":"1"},
+    "i31197":{"rank":"3"},
+    "p672":{"rank":"4"},
+    "i31384":{"rank":"1"},
+    "i32063":{"rank":"2"},
+    "i31295":{"rank":"1"},
+    "i31305":{"rank":"1"},
+    "i31304":{"rank":"2"},
+    "i31308":{"rank":"1"},
+    "i31309":{"rank":"2"},
+    "i31307":{"rank":"3"},
+    "p676":{"rank":"5"},
+    "i32388":{"rank":"1"},
+    "i32389":{"rank":"2"},
+    "i32390":{"rank":"3"},
+    "i26346":{"rank":"1"},
+    "p606":{"rank":"6"},
+    "i33438":{"rank":"1"},
+    "i33439":{"rank":"2"},
+    "i33440":{"rank":"3"},
+    "i33441":{"rank":"4"},
+    "i33442":{"rank":"5"},
+    "i33443":{"rank":"6"},
+    "i33444":{"rank":"7"},
+    "i33445":{"rank":"8"},
+    "i33437":{"rank":"1"},
+    "i33447":{"rank":"1"},
+    "i33448":{"rank":"2"},
+    "i33446":{"rank":"2"},
+    "i33450":{"rank":"1"},
+    "i33451":{"rank":"2"},
+    "i33449":{"rank":"3"},
+    "p719":{"rank":"7"},
+    "i30279":{"rank":"1"},
+    "i32363":{"rank":"2"},
+    "i30280":{"rank":"1"},
+    "i32362":{"rank":"1"},
+    "i32365":{"rank":"2"},
+    "i32366":{"rank":"3"},
+    "i32434":{"rank":"4"},
+    "i32435":{"rank":"5"},
+    "i32436":{"rank":"6"},
+    "i32364":{"rank":"2"},
+    "i30278":{"rank":"1"},
+    "i32368":{"rank":"2"},
+    "i32369":{"rank":"3"},
+    "i32367":{"rank":"3"},
+    "i30274":{"rank":"1"},
+    "i23760":{"rank":"1"},
+    "i30036":{"rank":"1"},
+    "i30037":{"rank":"2"},
+    "i30034":{"rank":"2"},
+    "i30038":{"rank":"1"},
+    "i30039":{"rank":"2"},
+    "i30040":{"rank":"3"},
+    "i30041":{"rank":"4"},
+    "i30042":{"rank":"5"},
+    "i30035":{"rank":"3"},
+    "p591":{"rank":"8"},
+    "i32158":{"rank":"1"},
+    "i32335":{"rank":"2"},
+    "i32336":{"rank":"3"},
+    "i32337":{"rank":"4"},
+    "i32338":{"rank":"5"},
+    "p701":{"rank":"1"},
+    "i31709":{"rank":"1"},
+    "i31711":{"rank":"2"},
+    "i31695":{"rank":"1"},
+    "i31707":{"rank":"1"},
+    "i31708":{"rank":"2"},
+    "i31696":{"rank":"2"},
+    "i31697":{"rank":"1"},
+    "i31688":{"rank":"1"},
+    "i31700":{"rank":"1"},
+    "i31702":{"rank":"2"},
+    "i31703":{"rank":"3"},
+    "i32121":{"rank":"4"},
+    "i32157":{"rank":"5"},
+    "i31699":{"rank":"1"},
+    "i31704":{"rank":"1"},
+    "i31705":{"rank":"2"},
+    "i31706":{"rank":"3"},
+    "i31701":{"rank":"2"},
+    "i31689":{"rank":"2"},
+    "i31736":{"rank":"1"},
+    "i31737":{"rank":"2"},
+    "i31738":{"rank":"3"},
+    "i31739":{"rank":"4"},
+    "i31740":{"rank":"5"},
+    "i31718":{"rank":"1"},
+    "i31741":{"rank":"1"},
+    "i31742":{"rank":"2"},
+    "i31719":{"rank":"2"},
+    "i31690":{"rank":"3"},
+    "i31713":{"rank":"1"},
+    "i31714":{"rank":"2"},
+    "i31715":{"rank":"3"},
+    "i31716":{"rank":"4"},
+    "i31717":{"rank":"5"},
+    "i31712":{"rank":"1"},
+    "i31691":{"rank":"4"},
+    "i31721":{"rank":"1"},
+    "i31722":{"rank":"2"},
+    "i31723":{"rank":"3"},
+    "i31724":{"rank":"4"},
+    "i31725":{"rank":"5"},
+    "i31726":{"rank":"6"},
+    "i31720":{"rank":"1"},
+    "i31692":{"rank":"5"},
+    "i31728":{"rank":"1"},
+    "i31729":{"rank":"2"},
+    "i31730":{"rank":"3"},
+    "i31727":{"rank":"1"},
+    "i31733":{"rank":"1"},
+    "i31734":{"rank":"2"},
+    "i31735":{"rank":"3"},
+    "i31731":{"rank":"2"},
+    "i31693":{"rank":"6"},
+    "i31744":{"rank":"1"},
+    "i31745":{"rank":"2"},
+    "i31746":{"rank":"3"},
+    "i31747":{"rank":"4"},
+    "i31743":{"rank":"1"},
+    "i31694":{"rank":"7"},
+    "i31698":{"rank":"2"},
+    "p677":{"rank":"9"},
+    "i30786":{"rank":"1"},
+    "i30787":{"rank":"2"},
+    "i30776":{"rank":"1"},
+    "i30791":{"rank":"1"},
+    "i30777":{"rank":"2"},
+    "i30770":{"rank":"1"},
+    "i30792":{"rank":"1"},
+    "i30848":{"rank":"2"},
+    "i30779":{"rank":"3"},
+    "i30771":{"rank":"1"},
+    "i30805":{"rank":"2"},
+    "i30845":{"rank":"3"},
+    "i30780":{"rank":"4"},
+    "i30806":{"rank":"1"},
+    "i30849":{"rank":"2"},
+    "i30781":{"rank":"5"},
+    "i30807":{"rank":"1"},
+    "i30846":{"rank":"2"},
+    "i30782":{"rank":"6"},
+    "i30808":{"rank":"1"},
+    "i30850":{"rank":"2"},
+    "i30783":{"rank":"7"},
+    "i30809":{"rank":"1"},
+    "i30847":{"rank":"2"},
+    "i30784":{"rank":"8"},
+    "i30810":{"rank":"1"},
+    "i30851":{"rank":"2"},
+    "i30785":{"rank":"9"},
+    "p661":{"rank":"10"},
+    "p550":{"rank":"3"},
+    "i31915":{"rank":"1"},
+    "i31916":{"rank":"2"},
+    "i31918":{"rank":"1"},
+    "i31924":{"rank":"2"},
+    "i31925":{"rank":"3"},
+    "i31917":{"rank":"3"},
+    "i31920":{"rank":"1"},
+    "i31921":{"rank":"2"},
+    "i31929":{"rank":"3"},
+    "i31930":{"rank":"4"},
+    "i31919":{"rank":"4"},
+    "i31923":{"rank":"5"},
+    "i31934":{"rank":"1"},
+    "i31935":{"rank":"2"},
+    "i31936":{"rank":"3"},
+    "i31937":{"rank":"4"},
+    "i31938":{"rank":"5"},
+    "i31927":{"rank":"1"},
+    "i31926":{"rank":"6"},
+    "i31931":{"rank":"7"},
+    "i31993":{"rank":"1"},
+    "i31932":{"rank":"8"},
+    "i31940":{"rank":"1"},
+    "i31941":{"rank":"2"},
+    "i31942":{"rank":"3"},
+    "i31939":{"rank":"9"},
+    "i31943":{"rank":"10"},
+    "i32013":{"rank":"11"},
+    "p683":{"rank":"1"},
+    "i31996":{"rank":"1"},
+    "i31999":{"rank":"1"},
+    "i32000":{"rank":"2"},
+    "i32001":{"rank":"3"},
+    "i31998":{"rank":"2"},
+    "i32002":{"rank":"3"},
+    "i32081":{"rank":"1"},
+    "i32004":{"rank":"4"},
+    "i32082":{"rank":"1"},
+    "i32005":{"rank":"5"},
+    "i32009":{"rank":"6"},
+    "i32012":{"rank":"7"},
+    "i32014":{"rank":"8"},
+    "p686":{"rank":"2"},
+    "i31776":{"rank":"1"},
+    "i31777":{"rank":"2"},
+    "i31778":{"rank":"3"},
+    "i31779":{"rank":"4"},
+    "i31780":{"rank":"5"},
+    "i31781":{"rank":"6"},
+    "i31782":{"rank":"7"},
+    "i31783":{"rank":"8"},
+    "i31787":{"rank":"1"},
+    "i31765":{"rank":"9"},
+    "p679":{"rank":"3"},
+    "p678":{"rank":"4"},
+    "i32496":{"rank":"1"},
+    "i32497":{"rank":"2"},
+    "i32498":{"rank":"3"},
+    "i32585":{"rank":"4"},
+    "i32495":{"rank":"1"},
+    "p705":{"rank":"1"},
+    "i28203":{"rank":"1"},
+    "i28204":{"rank":"2"},
+    "i32500":{"rank":"1"},
+    "i32501":{"rank":"2"},
+    "i32502":{"rank":"3"},
+    "i32499":{"rank":"3"},
+    "i28193":{"rank":"1"},
+    "i28205":{"rank":"1"},
+    "i28206":{"rank":"2"},
+    "i28194":{"rank":"2"},
+    "i28207":{"rank":"1"},
+    "i28195":{"rank":"3"},
+    "p540":{"rank":"2"},
+    "i29326":{"rank":"1"},
+    "i31224":{"rank":"2"},
+    "i27140":{"rank":"1"},
+    "i25115":{"rank":"1"},
+    "i27816":{"rank":"1"},
+    "i28970":{"rank":"2"},
+    "i27815":{"rank":"2"},
+    "i25200":{"rank":"1"},
+    "i31550":{"rank":"1"},
+    "i31549":{"rank":"1"},
+    "i25201":{"rank":"2"},
+    "i31217":{"rank":"1"},
+    "i31218":{"rank":"2"},
+    "i32020":{"rank":"3"},
+    "i32061":{"rank":"4"},
+    "i31215":{"rank":"3"},
+    "p596":{"rank":"3"},
+    "p714":{"rank":"5"},
+    "p641":{"rank":"6"},
+    "i27947":{"rank":"1"},
+    "i30327":{"rank":"1"},
+    "i28293":{"rank":"2"},
+    "i31188":{"rank":"1"},
+    "i31189":{"rank":"2"},
+    "i31191":{"rank":"3"},
+    "i31187":{"rank":"3"},
+    "i27411":{"rank":"1"},
+    "p631":{"rank":"1"},
+    "i32028":{"rank":"1"},
+    "i32030":{"rank":"2"},
+    "i32031":{"rank":"3"},
+    "i32032":{"rank":"4"},
+    "i32033":{"rank":"5"},
+    "i32060":{"rank":"6"},
+    "i32029":{"rank":"1"},
+    "i32035":{"rank":"1"},
+    "i32036":{"rank":"2"},
+    "i32037":{"rank":"3"},
+    "i32038":{"rank":"4"},
+    "i32034":{"rank":"2"},
+    "i32041":{"rank":"1"},
+    "i32040":{"rank":"1"},
+    "i32039":{"rank":"3"},
+    "i32043":{"rank":"1"},
+    "i32044":{"rank":"2"},
+    "i32045":{"rank":"3"},
+    "i32058":{"rank":"4"},
+    "i32042":{"rank":"4"},
+    "i32047":{"rank":"1"},
+    "i32048":{"rank":"2"},
+    "i32046":{"rank":"5"},
+    "i32049":{"rank":"6"},
+    "i32051":{"rank":"7"},
+    "i32052":{"rank":"8"},
+    "i32054":{"rank":"1"},
+    "i32055":{"rank":"2"},
+    "i32053":{"rank":"9"},
+    "i32056":{"rank":"10"},
+    "i32330":{"rank":"1"},
+    "i32508":{"rank":"2"},
+    "i32329":{"rank":"11"},
+    "i32411":{"rank":"1"},
+    "i32410":{"rank":"12"},
+    "i32027":{"rank":"1"},
+    "i32400":{"rank":"1"},
+    "i32401":{"rank":"2"},
+    "i32402":{"rank":"3"},
+    "i32403":{"rank":"4"},
+    "i32397":{"rank":"2"},
+    "p690":{"rank":"2"},
+    "i11522":{"rank":"1"},
+    "i11520":{"rank":"1"},
+    "i11576":{"rank":"2"},
+    "i11600":{"rank":"3"},
+    "i12263":{"rank":"4"},
+    "i18134":{"rank":"5"},
+    "i11599":{"rank":"2"},
+    "i14056":{"rank":"3"},
+    "i28190":{"rank":"1"},
+    "i8033":{"rank":"1"},
+    "p376":{"rank":"3"},
+    "i23838":{"rank":"1"},
+    "i23839":{"rank":"2"},
+    "i23840":{"rank":"3"},
+    "i23837":{"rank":"1"},
+    "i29166":{"rank":"2"},
+    "i23755":{"rank":"1"},
+    "p589":{"rank":"4"},
+    "p122":{"rank":"7"},
+    "i9120":{"rank":"8"},
+    "i26702":{"rank":"1"},
+    "i22742":{"rank":"9"}};
+    var encoded = easyTests.ysyInstance.storage.extra._encodeLayout(layout);
+    var decoded = easyTests.ysyInstance.storage.extra._decodeLayout(encoded);
+    var keys = Object.getOwnPropertyNames(layout);
+    for(var i=0;i<keys.length;i++){
+      var key = keys[i];
+      if (!_.isEqual(layout[key], decoded[key])) {
+        console.log(key);
+        console.log(layout[key]);
+        console.log(decoded[key]);
+        throw "different";
+      }
+    }
+  },
+  encodeSmallLayout: function () {
+    var layout = {"p25":{},"i2280": {"rank": 1}, "i3027": {"rank": 5, "position": [808.5, 111.25, 1]}};
+    var encoded = easyTests.ysyInstance.storage.extra._encodeLayout(layout);
+    var decoded = easyTests.ysyInstance.storage.extra._decodeLayout(encoded);
+    if (!_.isEqual(layout, decoded)) {
+      console.log(JSON.stringify(layout));
+      console.log(JSON.stringify(decoded));
+      throw "different";
+    }
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/tests/idea_diff.js b/plugins/easy_wbs/assets/javascripts/tests/idea_diff.js
new file mode 100644
index 0000000000000000000000000000000000000000..2570ac4b6954216e12e35cd9945207da92bbdd37
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/tests/idea_diff.js
@@ -0,0 +1,102 @@
+ysy.wbs = ysy.wbs || {};
+ysy.wbs.test = ysy.wbs.test || {};
+ysy.wbs.test.ideaDiff = function () {
+  var idea1 = {id: 0, attr: {data: {id: 0}}};
+  var idea2 = {id: 0, attr: {data: {id: 0}}};
+
+  var recursiveBuilder = function (idea1, idea2, level) {
+    if (Math.random() < 0.5 * (level - 1)) return; // leaves
+    var count = Math.ceil(Math.random() * 7);
+    idea1.ideas = {};
+    idea2.ideas = {};
+    for (var i = 0; i < count; i++) {
+      var id = Math.ceil(Math.random() * 10000);
+      var child1 = {
+        id: id,
+        title: "Task " + id,
+        attr: {
+          data: {
+            id: id
+          }
+        }
+      };
+      var child2 = {
+        id: child1.id,
+        title: "Task " + child1.attr.data.id,
+        attr: {
+          data: {
+            id: child1.attr.data.id
+          }
+        }
+      };
+      var rank = 0;
+      while (rank === 0 || idea1.ideas[rank]) {
+        rank = Math.ceil(Math.random() * count * 2);
+      }
+      idea1.ideas[rank] = child1;
+      rank = 0;
+      while (rank === 0 || idea2.ideas[rank]) {
+        rank = Math.ceil(Math.random() * count * 2);
+      }
+      idea2.ideas[rank] = child2;
+      recursiveBuilder(child1, child2, level + 1);
+    }
+  };
+
+  recursiveBuilder(idea1, idea2, 1);
+  return {idea1: idea1, idea2: idea2};
+  // console.log(JSON.stringify([idea1,idea2]));
+  // console.log(JSON.stringify(ysy.storage.lastState.compareIdea(idea1,"structure",idea2)));
+};
+ysy.wbs.test.scramble = function (idea) {
+  var possibleParents = [];
+  ysy.util.traverse(idea, function (node) {
+    if (_.isEmpty(node.ideas)) return;
+    for (var rank in node.ideas) {
+      if (!node.ideas.hasOwnProperty(rank)) continue;
+      if (!_.isEmpty(node.ideas[rank].ideas)) return;
+    }
+    possibleParents.push(node);
+  });
+  var parent = possibleParents[Math.floor(Math.random() * possibleParents.length)];
+  var childRanks = Object.getOwnPropertyNames(parent.ideas);
+  var childRank = childRanks[Math.floor(Math.random() * childRanks.length)];
+  var child = parent.ideas[childRank];
+  child.scrambled = true;
+  var possibleTargets = [];
+  ysy.util.findWhere(idea, function (node) {
+    if (node === child) return true;
+    if (node === parent) return false;
+    possibleTargets.push(node);
+  });
+  var target = possibleTargets[Math.floor(Math.random() * possibleTargets.length)];
+  if (!target.ideas) target.ideas = {};
+  target.ideas[100000] = child;
+  delete parent.ideas[childRank];
+};
+ysy.wbs.test.changeValue = function (idea) {
+  var possibleNodes = [];
+  ysy.util.traverse(idea, function (node) {
+    if (node.attr.isProject) return;
+    possibleNodes.push(node);
+  });
+  var node = possibleNodes[Math.floor(Math.random() * possibleNodes.length)];
+  // node = idea;
+  node.changed = true;
+  ysy.mapModel.setData(node, {status_id: 5});
+};
+ysy.wbs.test.nonSubtaskable = function () {
+  var task4 = ysy.mapjs.idea.ideas[-1].ideas[1];
+  ysy.mapModel.setData(task4, {tracker_id: 1});
+  ysy.mapModel.setData(task4, {tracker_id: 2});
+  task4.attr.data.tracker_id = 57;
+};
+ysy.wbs.test.diffMessages = function () {
+  var ideas = ysy.wbs.test.ideaDiff();
+  // ysy.wbs.test.scramble(ideas.idea2);
+  ysy.wbs.test.changeValue(ideas.idea2);
+  // ysy.log.debug(JSON.stringify(ideas));
+  var diff = ysy.storage.lastState.compareIdea(ideas.idea2, "server", ideas.idea1);
+  ysy.log.debug(JSON.stringify(diff));
+  ysy.loader.prepareLastStateMessages(diff, ideas.idea1, ideas.idea2);
+};
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/tests/printing.js b/plugins/easy_wbs/assets/javascripts/tests/printing.js
new file mode 100644
index 0000000000000000000000000000000000000000..46b9f731899a44bc6222398119e85999d771399a
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/tests/printing.js
@@ -0,0 +1,14 @@
+/**
+ * Created by hosekp on 11/25/16.
+ */
+
+window.easyTests = $.extend(window.easyTests,{
+  /** @type {MindMup}*/
+  ysyInstance: null,
+  printify:function(){
+    this.ysyInstance.print.beforePrint();
+  },
+  deprintify:function () {
+    this.ysyInstance.print.afterPrint();
+  }
+});
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/tests/saving_test.js b/plugins/easy_wbs/assets/javascripts/tests/saving_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..8cc83a61a1f9f258c1261924af0e6f84596ad557
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/tests/saving_test.js
@@ -0,0 +1,23 @@
+/**
+ * Created by hosekp on 11/15/16.
+ */
+
+window.easyTests = $.extend(window.easyTests,{
+  /** @type {MindMup}*/
+  ysyInstance: null,
+  addTenIssues: function () {
+    var counter = 1;
+    var mapModel = this.ysyInstance.mapModel;
+    for (var i = 0; i < 10; i++) {
+      mapModel.addSubIdea("script", undefined, "Node " + counter++);
+    }
+  },
+  changeStatusOfAll: function (status) {
+    /** @type {MindMup}*/
+    var ysy = this.ysyInstance;
+    ysy.util.traverse(ysy.idea, function (node) {
+      ysy.setData(node,{status_id:status || 2});
+    });
+    ysy.idea.dispatchEvent('changed');
+  }
+});
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_context_menu.js b/plugins/easy_wbs/assets/javascripts/wbs_context_menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..09c18918e6cade93abaad244f838f4947929077d
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_context_menu.js
@@ -0,0 +1,133 @@
+/**
+ * Created by hosekp on 11/28/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * Context menu for WBS
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsContextMenu(ysy) {
+    classes.ContextMenu.call(this, ysy);
+  }
+
+  classes.extendClass(WbsContextMenu, classes.ContextMenu);
+  /**
+   * Prepare Object for Mustache to render context menu
+   * @param {ModelEntity} node
+   * @override
+   * @return {Object}
+   */
+  WbsContextMenu.prototype.getStructure = function (node) {
+    var ysy = this.ysy;
+    var data = ysy.getData(node);
+    var isProject = node.attr.entityType === "project";
+    var isBulkEdit = ysy.mapModel.getActivatedNodeIds().length > 1;
+    var isCollapsed = node.attr.collapsed;
+    var trackers, priorities, statuses, assignees, doneRatio;
+    if (!isProject) {
+      trackers = ysy.dataStorage.get("trackers").map(function (entity) {
+        return {name: entity.name, value: entity.id, previous: data.tracker_id === entity.id}
+      });
+      priorities = ysy.dataStorage.get("priorities").map(function (entity) {
+        return {name: entity.name, value: entity.id, previous: data.priority_id === entity.id};
+      });
+      statuses = ysy.dataStorage.get("statuses").map(function (entity) {
+        return {name: entity.name, value: entity.id, previous: data.status_id === entity.id};
+      });
+      assignees = [{name: "<< nobody >>", value: null, previous: !data.assigned_to_id}]
+          .concat(ysy.dataStorage.get("users").map(function (entity) {
+            return {name: entity.name, value: entity.id, previous: data.assigned_to_id === entity.id}
+          }));
+      doneRatio = [];
+      var dataDoneRatio = data.done_ratio || 0;
+      for (var i = 0; i <= 100; i += 10) {
+        doneRatio.push({name: i + " %", value: i, previous: dataDoneRatio === i})
+      }
+    }
+    var labels = ysy.settings.labels;
+    var ctxLabels = labels.context;
+    var nonEditable = node.attr.nonEditable;
+    return [
+      {
+        name: isCollapsed ? ctxLabels.expand : ctxLabels.collapse,
+        aClassName: 'easy-mindmup__icon easy-mindmup__icon--'+(isCollapsed ? 'expand' : 'collapse') + ' toggleCollapse',
+        skip: _.isEmpty(node.ideas)
+      }, {
+        name: ctxLabels.goto + " " + (isProject ? labels.types.project : labels.types.issue),
+        aClassName: 'easy-mindmup__icon easy-mindmup__icon--follow_url followURL',
+        skip: isBulkEdit
+      }, {
+        name: ctxLabels.rename,
+        aClassName: 'easy-mindmup__icon easy-mindmup__icon--rename editNode',
+        skip: isBulkEdit || !ysy.validator.validate("nodeRename", node)
+      }, {
+        name: ctxLabels.editData,
+        aClassName: 'easy-mindmup__icon easy-mindmup__icon--edit_data editNodeData',
+        skip: isBulkEdit || nonEditable
+      },
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.tracker,
+          key: 'tracker_id',
+          changer: trackers
+        };
+      }, nonEditable || isProject),
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.priority,
+          key: 'priority_id',
+          changer: priorities
+        };
+      }, nonEditable),
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.status,
+          key: 'status_id',
+          changer: statuses
+        };
+      }, nonEditable || isProject),
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.assignee,
+          key: 'assigned_to_id',
+          changer: assignees
+        };
+      }, nonEditable || isProject),
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.doneRatio,
+          key: 'done_ratio',
+          changer: doneRatio
+        };
+      }, nonEditable || isProject),
+      this.prepareOption(function () {
+        return {
+          name: ctxLabels.add,
+          className: 'folder',
+          aClassName: 'easy-mindmup__icon easy-mindmup__icon--add',
+          subMenu: [
+            {
+              name: ctxLabels.addChild,
+              className: 'easy-mindmup__icon easy-mindmup__icon--add addSubIdea'
+            }, {
+              name: ctxLabels.addSibling,
+              className: 'easy-mindmup__icon easy-mindmup__icon--add_sibling addSiblingIdea'
+            }, {
+              name: ctxLabels.addParent,
+              className: 'easy-mindmup__icon easy-mindmup__icon--insert_between insertIntermediate'
+            }
+          ]
+        };
+      }, isBulkEdit),
+      {
+        name: ctxLabels.remove,
+        aClassName: 'easy-mindmup__icon easy-mindmup__icon--remove removeSubIdea',
+        skip: nonEditable
+      }
+    ]
+  };
+  classes.WbsContextMenu = WbsContextMenu;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_loader.js b/plugins/easy_wbs/assets/javascripts/wbs_loader.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b816881907abd974bcce846de10db37ff596e7d
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_loader.js
@@ -0,0 +1,47 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * @extends {Loader}
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsLoader(ysy) {
+    classes.Loader.call(this, ysy);
+  }
+
+  classes.extendClass(WbsLoader, classes.Loader);
+
+  /**
+   *
+   * @param {Object} rawData
+   * @return {Object}
+   */
+  WbsLoader.prototype.extractData = function (rawData) {
+    return rawData["easy_wbs_data"];
+  };
+  WbsLoader.prototype.loadSideData = function (data) {
+    this.ysy.dataStorage.save("trackers", data["trackers"]);
+    this.ysy.dataStorage.save("priorities", data["priorities"]);
+    this.ysy.dataStorage.save("statuses", data["statuses"]);
+    this.ysy.dataStorage.save("users", data["users"]);
+    this.ysy.dataStorage.save("versions", data["versions"]);
+  };
+
+  WbsLoader.prototype.getParentFromSource = function (entity, next) {
+    var entityData = entity.attr.data;
+    if (!next) {
+      if (entityData["parent_issue_id"]) return new classes.ParentPack("issue", entityData["parent_issue_id"]);
+      if (entityData["parent_id"]) return new classes.ParentPack("project", entityData["parent_id"]);
+      if (entityData["project_id"]) return new classes.ParentPack("project", entityData["project_id"]);
+    } else {
+      if (entityData["parent_issue_id"]) return new classes.ParentPack("project", entityData["project_id"]);
+    }
+    return null;
+  };
+
+  classes.WbsLoader = WbsLoader;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_main.js b/plugins/easy_wbs/assets/javascripts/wbs_main.js
new file mode 100644
index 0000000000000000000000000000000000000000..a53194980546411897c335ecf41ddf96a671ebde
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_main.js
@@ -0,0 +1,62 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * @extends {MindMup}
+   * @param {Object} settings
+   * @constructor
+   */
+  function WbsMain(settings) {
+    classes.MindMup.call(this, settings);
+    this.helperInit();
+    this.patch();
+    this.init();
+    // this.settings.noSave = true;
+    if (window.easyTests) {
+      easyTests.ysyInstance = this;
+    }
+  }
+
+  classes.extendClass(WbsMain, classes.MindMup);
+
+
+  WbsMain.prototype.patch = function () {
+    /** @type {WbsLoader} */
+    this.loader = new classes.WbsLoader(this);
+    /** @type {WbsNodePatch} */
+    this.nodePatch = new classes.WbsNodePatch(this);
+    /** @type {WbsSaver} */
+    this.saver = new classes.WbsSaver(this);
+    /** @type {WbsStyles} */
+    this.styles = new classes.WbsStyles(this);
+    /** @type {WbsValidator} */
+    this.validator = new classes.WbsValidator(this);
+    /** @type {WbsContextMenu} */
+    this.contextMenu = new classes.WbsContextMenu(this);
+    /** @type {WbsModals} */
+    this.modals = new classes.WbsModals(this);
+
+    /** @type {WbsMain} */
+    var self = this;
+    /**
+     * Modify StorageExtra._getIdOfIdea(), so it use proper prefixes
+     */
+    this.eventBus.register("BeforeViewClassInit", function () {
+      /** @param {ModelEntity} idea */
+      self.storage.extra._getIdOfIdea = function (idea) {
+        var id = this.ysy.getData(idea).id;
+        if (idea.attr.entityType === "project") {
+          return "p" + id;
+        } else {
+          return "i" + id;
+        }
+      };
+    });
+  };
+  WbsMain.prototype.creatingEntity = "issue";
+
+  classes.WbsMain = WbsMain;
+})();
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_modals.js b/plugins/easy_wbs/assets/javascripts/wbs_modals.js
new file mode 100644
index 0000000000000000000000000000000000000000..9be08d497ef4a40adb3c6f08b6174e7b9588924c
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_modals.js
@@ -0,0 +1,267 @@
+/**
+ * Created by hosekp on 11/15/16.
+ */
+(function () {
+  /**
+   *
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsModals(ysy) {
+    this.ysy = ysy;
+    /** @type {jQuery} */
+    this.$target = null;
+    /** @type {ModelEntity} */
+    this.selectedIdea = null;
+    this._cache = {};
+    this.patch(ysy);
+  }
+
+  /**
+   *
+   * @param {WbsMain} ysy
+   */
+  WbsModals.prototype.patch = function (ysy) {
+    var self = this;
+    ysy.eventBus.register("MapInited", function (mapModel) {
+      ysy.$container.off("click.exclamation").on("click.exclamation", ".mapjs-exclamation", function () {
+        var contextNodeId = mapModel.getSelectedNodeId();
+
+        ysy.eventBus.fireEvent('nodeEditDataRequested', contextNodeId);
+      });
+      ysy.eventBus.register('nodeEditDataRequested', $.proxy(self.onEditRequested, self));
+    });
+  };
+  WbsModals.prototype.onEditRequested = function (nodeId) {
+    /** @type {ModelEntity} */
+    var idea = this.ysy.mapModel.findIdeaById(nodeId);
+    this.selectedIdea = idea;
+    var sendPack = new this.ysy.saver.createSendPack(idea, this.ysy.idea.findParent(idea.id));
+    sendPack.ysy = this.ysy;
+    sendPack.updateNodeData();
+    var startsWith = this.ysy.util.startsWith;
+    var preFill = _.omit(sendPack.node.attr.data, function (value, key) {
+      return _.isFunction(value) || startsWith(key, "_") || key === "journals";
+    });
+    if(preFill.custom_fields){
+      var customValues={};
+      for(var i=0;i<preFill.custom_fields.length;i++){
+        var customField = preFill.custom_fields[i];
+        customValues[customField.id]=customField.value;
+      }
+      delete preFill.custom_fields;
+      preFill.custom_field_values = customValues;
+    }
+    if (idea.attr.entityType === "project") {
+
+    } else {
+      this.$target = this.ysy.util.getModal("form-modal", "90%");
+      if (preFill.id) {
+        this.openEditIssueModal(preFill);
+      } else {
+        this.openNewIssueModal(preFill);
+      }
+    }
+
+  };
+  WbsModals.prototype.submitFunction = function (e) {
+    var $target = this.$target;
+    if (window.fillFormTextAreaFromCKEditor) {
+      $target.find("textarea").each(function () {
+        window.fillFormTextAreaFromCKEditor(this.id);
+      });
+    }
+    var errors = [];
+    var cannotBeEmpty = this.ysy.settings.labels.errors.cannotBeEmpty;
+    $.unique($target.find("label.required, label .required").closest("label")).each(function () {
+      var $label = $(this);
+      var $input = $label.parent().find("#" + $label.attr("for"));
+      if (!$input.length) return;
+      //var $input = $label.next();
+      if (!$input.val()) {
+        var label = $label.text();
+        errors.push(label.substring(0, label.length - 2) + " " + cannotBeEmpty /* + ysy.view.getLabel("addTask", "error_blank")*/);
+      }
+    });
+    if (errors.length) {
+      var errorSpan = $('<span></span>').html(errors.join("<br>"));
+      var closeButton = $('<a href="javascript:void(0)" class="easy-mindmup__icon easy-mindmup__icon--close mindmup_modal__flash_close"></a>').click(function (event) {
+        $(this)
+            .closest('.flash')
+            .fadeOut(500, function () {
+              $(this).remove();
+            })
+      });
+      $target.prepend($('<div class="flash error"></div>').append(errorSpan, closeButton));
+      return false;
+    }
+    var data = $target.parent().find("form").serializeArray();
+
+    var transformed = this.transformData(data);
+    this.updateIssue(this.selectedIdea, transformed);
+    $target.dialog("close");
+    return false;
+  };
+  WbsModals.prototype.openNewIssueModal = function (preFill) {
+    var self = this;
+    var successCallback = function (data) {
+      var $form = $(data).filter("#issue-form");
+      if ($form.length === 0) $form = $(data).find("#issue-form");
+
+      $form.find('input[name="continue"]').remove();
+      $form.find('fieldset[data-toggle="easy_checklist_form_container"]').remove();
+      self.finishModal("new", "issue", $form, preFill);
+    };
+    $.ajax(this.ysy.settings.paths.newIssuePath, {
+      method: "GET",
+      data: {
+        projectID: preFill.project_id,
+        issue: preFill
+      }
+    }).done(successCallback);
+  };
+  WbsModals.prototype.openEditIssueModal = function (preFill) {
+    var self = this;
+    var path = this.ysy.settings.paths.editIssuePath.replace(":issueID", preFill.id);
+    var successCallback = function (data) {
+      var $form = $(data).filter("#issue-form");
+      if ($form.length === 0) $form = $(data).find("#issue-form");
+      if (self.ysy.settings.easyRedmine) {
+        $form.find("#issue_timeentry_fields").parent().remove();
+        $form.find("#edit_issue_notes").hide()
+            .siblings().find(".module-toggle-button .group").removeClass("open");
+        $form.find("#issue_descr_fields").show();
+        $form.find(".issue-edit-hidden-attributes").remove();
+        $form.find(".issue_edit_submit_buttons a").remove();
+      } else {
+        $form.find("#time_entry_hours").closest("fieldset").remove();
+        $form.find("#attachments_fields").closest("fieldset").remove();
+      }
+      $form.find(".issue-attachments-container").remove();
+      self.finishModal("edit", "issue", $form, preFill);
+    };
+    $.ajax(path, {
+      method: "GET",
+      data: {
+        projectID: preFill.project_id,
+        issue: preFill
+      }
+    }).done(successCallback);
+
+  };
+  /**
+   *
+   * @param {String} actionType
+   * @param {String} entityType
+   * @param {jQuery} $form
+   * @param {Object} preFill
+   */
+  WbsModals.prototype.finishModal = function (actionType, entityType, $form, preFill) {
+    var settings = this.ysy.settings;
+    $form.find("#preview").remove();
+    $form.find("h2, h3.title").remove();
+    var $target = this.$target;
+    $target.empty().append($form);
+    if (!settings.easyRedmine) {
+      $form.contents().filter(function () {
+        return this.nodeName === "A" || this.nodeType == 3;
+      }).remove();
+      $('<div class="issue_submit_buttons"></div>').append($form.find('input[type="submit"]')).appendTo($form);
+    }
+    $target.prepend($("<h3 class='title'>" + settings.labels.modals[actionType + "_" + entityType] + "</h3>"));
+    var $buttons = $form.find(".issue_edit_submit_buttons, .issue_submit_buttons");
+    $buttons.find('input[type="submit"]').click($.proxy(this.submitFunction, this));
+    $buttons.append($('<a href="javascript:void(0)" class="wbs-modal-close button">' + settings.labels.buttons.close + '</a>')
+        .click(function () {
+          $target.dialog('close');
+        }));
+    $target.find(".form-actions").hide();
+
+    showModal("form-modal");
+    $target.dialog({
+      maxHeight: window.innerHeight - 100,
+      buttons: [
+        {}
+      ]
+    });
+    $target.parent().find(".ui-dialog-buttonpane").empty().append($('<form id="modal_submit_form">').append($buttons.children()));
+    $target.find("#issue-form").submit($.proxy(this.submitFunction, this))
+        .append($('<input type="hidden" name="version[project_id]" value="' + preFill.project_id + '" />'));
+  };
+  /**
+   * Accepts data from form.serializeArray() and transform it to Object similar to ModelEntityData
+   * @param {Array.<Object.<String,String>>} data
+   * @return {Object}
+   */
+  WbsModals.prototype.transformData = function (data) {
+    //var momentarize=function(date){return moment(date,"YYYY-MM-DD");};
+    //var momentarizeEnd=function(date){var mom=moment(date,"YYYY-MM-DD");mom._isEndDate=true;return mom;};
+    var structured = this.ysy.util.formToJson(data);
+    var transformed = {};
+    var parseInteger = function (number) {
+      if (number === "") return null;
+      return parseInt(number);
+    };
+    var parseDecimal = function (number) {
+      if (number === "") return null;
+      return parseFloat(number);
+    };
+    var functionMap = {
+      // name: nothing,
+      is_private: parseInteger,
+      tracker_id: parseInteger,
+      status_id: parseInteger,
+      // status: nothing,
+      // sharing: nothing,
+      // subject: nothing,
+      // description: nothing,
+      priority_id: parseInteger,
+      project_id: parseInteger,
+      assigned_to_id: parseInteger,
+      fixed_version_id: parseInteger,
+      easy_version_category_id: parseInteger,
+      old_fixed_version_id: parseInteger,
+      parent_issue_id: parseInteger,
+      // start_date: nothing,
+      // due_date: nothing,
+      effective_date: function (value) {
+        transformed.start_date = value;
+        return null;
+      },
+      estimated_hours: parseDecimal,
+      done_ratio: parseInteger,
+      // custom_field_values: nothing,
+      // easy_distributed_tasks: nothing,
+      // easy_repeat_settings: nothing,
+      // easy_repeat_simple_repeat_end_at: nothing,
+      // watcher_user_ids: nothing,
+      // easy_ldap_entity_mapping: nothing,
+      // skip_estimated_hours_validation: nothing,
+      activity_id: parseInteger
+    };
+    var issueKeys = Object.getOwnPropertyNames(structured.issue);
+    for (var i = 0; i < issueKeys.length; i++) {
+      var key = issueKeys[i];
+      if (functionMap.hasOwnProperty(key)) {
+        var parsed = functionMap[key](structured.issue[key], key);
+      } else {
+        parsed = structured.issue[key];
+      }
+      transformed[key] = parsed;
+    }
+    return transformed;
+  };
+  /**
+   *
+   * @param {ModelEntity} idea
+   * @param {Object} transformed
+   */
+  WbsModals.prototype.updateIssue = function (idea, transformed) {
+    this.ysy.setData(idea, transformed);
+    idea.title = idea.attr.data.subject;
+    this.ysy.idea.dispatchEvent("changed");
+  };
+
+
+  window.easyMindMupClasses.WbsModals = WbsModals;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_node_patch.js b/plugins/easy_wbs/assets/javascripts/wbs_node_patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2ec91cca4cf9d560681e0f1f037f7f7f241efcf
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_node_patch.js
@@ -0,0 +1,56 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   *
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsNodePatch(ysy) {
+    classes.NodePatch.call(this, ysy);
+    this.initIcons();
+  }
+
+  classes.extendClass(WbsNodePatch, classes.NodePatch);
+  /**
+   * @param {ModelEntity} nodeContent
+   * @return {String}
+   */
+  WbsNodePatch.prototype.nodeBonusCss = function (nodeContent) {
+    return nodeContent.attr.entityType === "project" ? " mindmup-scheme-project wbs-project" : " wbs-issue";
+  };
+
+  WbsNodePatch.prototype.iconsForEntity = {
+    issue: ["exclamation", "avatar", "progress", "status", "milestone"]
+  };
+  WbsNodePatch.prototype.initIcons = function () {
+    /** @type {WbsMain} */
+    var ysy = this.ysy;
+    this.addIconBuilder("progress", function (nodeContent) {
+      var data = ysy.getData(nodeContent);
+      if (data.done_ratio === undefined) return null;
+      return '<div class="mindmup-node-icon-progress-bar" style="top:' + (100 - data.done_ratio) + '%;height:' + data.done_ratio + '%"></div>';
+    });
+    this.addIconBuilder("status", function (nodeContent) {
+      var data = ysy.getData(nodeContent);
+      var statuses = ysy.dataStorage.get("statuses");
+      if (data.status_id === undefined) return;
+      var status = _.find(statuses, function (status) {
+        return status.id === data.status_id;
+      });
+      if (status) return status.name;
+    });
+    this.addIconBuilder("milestone", function (nodeContent) {
+      var data = ysy.getData(nodeContent);
+      var css = ysy.styles.styles["milestone"].addSchemeClassFromData(data);
+      if (!css) return null;
+      return '<div class="mindmup-node-icon-milestone-shell scheme-by-milestone">\
+            <div class="mindmup-node-icon-milestone-diamond' + css + '"></div></div>'
+    });
+  };
+
+  classes.WbsNodePatch = WbsNodePatch;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_saver.js b/plugins/easy_wbs/assets/javascripts/wbs_saver.js
new file mode 100644
index 0000000000000000000000000000000000000000..96af2c60d419e030ccc19450802cfc34e69f270a
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_saver.js
@@ -0,0 +1,119 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * @extends {Saver}
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsSaver(ysy) {
+    classes.Saver.call(this, ysy);
+  }
+
+  classes.extendClass(WbsSaver, classes.Saver);
+
+  WbsSaver.prototype.layoutKey = "easy_wbs_layout";
+
+  classes.WbsSaver = WbsSaver;
+  //####################################################################################################################
+  /**
+   * @extends {SendPack}
+   * @param {ModelEntity} node
+   * @param {ModelEntity} [parent]
+   * @param {ModelEntity} [project]
+   * @constructor
+   */
+  function WbsSendPack(node, parent, project) {
+    classes.SendPack.call(this, node, parent, project);
+  }
+
+  classes.extendClass(WbsSendPack, classes.SendPack);
+
+  WbsSendPack.prototype.needInclusionCheck = function () {
+    var entityData = this.nodeData;
+    return entityData._old.parent_issue_id !== entityData.parent_issue_id
+        && entityData.parent_issue_id
+        && entityData._old.parent_issue_id
+  };
+  WbsSendPack.prototype.isSafeCheck = function () {
+    var data = this.nodeData;
+    if (this.node.attr.entityType === "project") {
+      return !data._old.hasOwnProperty("parent_id") || data._old.parent_id === data.parent_id;
+    }
+    if (data._old.hasOwnProperty("project_id") && data._old.project_id !== data.project_id) return false;
+    return !data._old.hasOwnProperty("parent_issue_id") || data._old.parent_issue_id === data.parent_issue_id;
+  };
+  WbsSendPack.prototype.updateNodeData = function () {
+    var idea = this.node;
+    var parent = this.parent;
+    var updateObj;
+    if (idea.attr.entityType === "project") {
+      if (parent) {
+        var parentData = this.ysy.getData(parent);
+        updateObj = {parent_id: parentData.id, name: idea.title};
+      } else {
+        updateObj = {name: idea.title};
+      }
+    } else {
+      if (parent) {
+        parentData = this.ysy.getData(parent);
+        if (parent.attr.entityType === "project") {
+          updateObj = {
+            project_id: parentData.id,
+            parent_issue_id: null,
+            subject: idea.title
+          }
+        } else {
+          if (this.project) {
+            var projectId = this.project && this.ysy.getData(this.project).id;
+          }
+          updateObj = {
+            parent_issue_id: parentData.id,
+            subject: idea.title,
+            project_id: projectId || this.ysy.util.getEntityProjectId(parent)
+          };
+        }
+      } else {
+        updateObj = {
+          subject: idea.title
+        };
+      }
+    }
+    if (updateObj) {
+      this.ysy.setData(idea, updateObj);
+    }
+  };
+  WbsSendPack.prototype.updateByPOSTAdditional = function (source) {
+    var keysToTransform = ["tracker", "status", "priority"];
+    var wantedKeys = ["tracker_id", "status_id", "priority_id", "done_ratio"];
+    for (var i = 0; i < keysToTransform.length; i++) {
+      var key = keysToTransform[i];
+      if (_.isObject(source[key])) {
+        source[key + "_id"] = source[key].id;
+        delete source[key];
+      }
+    }
+    $.extend(this.nodeData, _.pick(source, wantedKeys));
+  };
+  WbsSendPack.prototype.getInclusionData = function () {
+    if (this.node.attr.entityType === "issue") {
+      return {id: this.nodeData.id, _old: {parent_issue_id: 5}, parent_issue_id: null};
+    }
+  };
+
+  classes.WbsSendPack = WbsSendPack;
+  /**
+   * @param {ModelEntity} node
+   * @param {ModelEntity} [parent]
+   * @param {ModelEntity} [project]
+   * @return {WbsSendPack}
+   */
+  WbsSaver.prototype.createSendPack = function (node, parent, project) {
+    var pack = new WbsSendPack(node, parent, project);
+    pack.saver = this;
+    return pack;
+  };
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_styles.js b/plugins/easy_wbs/assets/javascripts/wbs_styles.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cd5e1cae9130edab780141699b94ce1f6df529a
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_styles.js
@@ -0,0 +1,134 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  /**
+   * @extends {Styles}
+   * @param {WbsMain} ysy
+   * @constructor
+   */
+  function WbsStyles(ysy) {
+    this.defaultStyle = "tracker";
+    classes.Styles.call(this, ysy);
+    this.createStyles(this.styleSources);
+  }
+
+  classes.extendClass(WbsStyles, classes.Styles);
+
+  WbsStyles.prototype.styleSources = {
+    tracker: {
+      dataArray: "trackers",
+      value: function (data) {
+        if (data.isProject) return "project";
+        return data.tracker_id;
+      }
+    },
+    assignee: {
+      dataArray: "users",
+      value: function (data) {
+        if (data.isProject) return "project";
+        return data.assigned_to_id || 0;
+      },
+      builderType: "assignee",
+      nullAllowed: true,
+      changeObject: classes.Style.oneKeyObjectConstructorBuilder("assigned_to_id")
+    },
+    status: {
+      dataArray: "statuses",
+      value: function (data) {
+        if (data.isProject) return "project";
+        return data.status_id;
+      }
+    },
+    priority: {
+      initAttribute: function (list) {
+        this.colors = {};
+        if (this.ysy.settings.easyRedmine) {
+          for (var i = 0; i < list.length; i++) {
+            var scheme = list[i].scheme;
+            if (!scheme) continue;
+            var schemeSplit = scheme.split("-");
+            if (schemeSplit.length <= 1) continue;
+            var schemeId = parseInt(schemeSplit[schemeSplit.length - 1]);
+            this.colors[list[i].id] = schemeId + 1;
+          }
+        } else {
+          var schemeOffset = 8; // first 8 colors are from easyRedmine
+          scheme = 1 + schemeOffset;
+          var defaultFound = false;
+          for (i = 0; i < list.length; i++) {
+            var isDefault = list[i].is_default;
+            if (isDefault) {
+              this.colors[list[i].id] = 2 + schemeOffset;
+              defaultFound = true;
+              scheme = 3 + schemeOffset;
+              continue;
+            }
+            this.colors[list[i].id] = scheme;
+            if (!defaultFound) {
+              scheme = 2 + schemeOffset;
+            } else {
+              scheme = 4 + schemeOffset;
+            }
+          }
+        }
+        this.data = list;
+      },
+      dataArray: "priorities",
+      value: function (data) {
+        if (data.isProject) return "project";
+        return data.priority_id;
+      }
+    },
+    progress: {
+      init: function () {
+        var colors = {};
+        for (var i = 0; i < 6; i++) {
+          colors[i * 20] = i + 1;
+        }
+        this.colors = colors;
+      },
+      value: function (data) {
+        if (data.isProject) return "project";
+        return Math.round(data.done_ratio / 20.0) * 20.0;
+      },
+      options: function () {
+        return [0, 20, 40, 60, 80, 100];
+      },
+      builderType: "percent",
+      changeObject: classes.Style.oneKeyObjectConstructorBuilder("done_ratio")
+    },
+    milestone: {
+      dataArray: "versions",
+      value: function (data) {
+        if (data.isProject) return;
+        return data.fixed_version_id || 0;
+      },
+      nullAllowed: true,
+      changeObject: classes.Style.oneKeyObjectConstructorBuilder("fixed_version_id")
+    }
+  };
+
+
+  /**
+   *
+   * @param {ModelEntity} node
+   * @return {String}
+   */
+  WbsStyles.prototype.cssClasses = function (node) {
+    var data = this.ysy.getData(node);
+    if (node.attr && node.attr.entityType === "project") return " mindmup-scheme-project";
+    return ""
+        + this.styles["tracker"].addSchemeClassFromData(data)
+        + this.styles["assignee"].addSchemeClassFromData(data)
+        + this.styles["status"].addSchemeClassFromData(data)
+        + this.styles["progress"].addSchemeClassFromData(data)
+        + this.styles["milestone"].addSchemeClassFromData(data)
+        + this.styles["priority"].addSchemeClassFromData(data);
+  };
+
+
+  classes.WbsStyles = WbsStyles;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/javascripts/wbs_validator.js b/plugins/easy_wbs/assets/javascripts/wbs_validator.js
new file mode 100644
index 0000000000000000000000000000000000000000..81579f45c6fd4b1cf19722bcb721c172153e8eae
--- /dev/null
+++ b/plugins/easy_wbs/assets/javascripts/wbs_validator.js
@@ -0,0 +1,30 @@
+/**
+ * Created by hosekp on 11/14/16.
+ */
+(function () {
+  var classes = window.easyMindMupClasses;
+
+  function WbsValidator(ysy) {
+    classes.Validator.call(this,ysy);
+  }
+  classes.extendClass(WbsValidator,classes.Validator);
+
+  WbsValidator.prototype.changeParent = function (child, newParent) {
+    if(child.attr.nonEditable) return false;
+    var childData = this.ysy.getData(child);
+    if (!childData.tracker_id) return true;
+    var tracker = _.find(this.ysy.dataStorage.get("trackers"), function (item) {
+      return item.id === childData.tracker_id;
+    });
+    if (!tracker.subtaskable) {
+      if (newParent.attr && newParent.attr.isProject) return true;
+      showFlashMessage("error", this.ysy.settings.labels.errors.not_subtaskable.replace("%{task_name}", child.title));
+      return false;
+    }
+    return true;
+
+  };
+
+
+  classes.WbsValidator = WbsValidator;
+})();
\ No newline at end of file
diff --git a/plugins/easy_wbs/assets/stylesheets/easy_wbs.css b/plugins/easy_wbs/assets/stylesheets/easy_wbs.css
new file mode 100644
index 0000000000000000000000000000000000000000..f6a0c98f4d0f3db314ab79edf0d34bc29cc19d63
--- /dev/null
+++ b/plugins/easy_wbs/assets/stylesheets/easy_wbs.css
@@ -0,0 +1,10 @@
+/*
+* = require_self
+*/
+.mindmup-sidebar__attachments-thumbnails > img{
+  display: none;
+}
+.mindmup-sidebar__attachments-thumbnails{
+  margin: 0;
+}
+
diff --git a/plugins/easy_wbs/assets/stylesheets/generated/easy_wbs.css b/plugins/easy_wbs/assets/stylesheets/generated/easy_wbs.css
new file mode 100644
index 0000000000000000000000000000000000000000..22174805b0529751542f40216c245f8020e2db24
--- /dev/null
+++ b/plugins/easy_wbs/assets/stylesheets/generated/easy_wbs.css
@@ -0,0 +1 @@
+@charset "UTF-8";#logo-img,.logo{background-image:url(//d1g6a398qq2djm.cloudfront.net/img/logo_32.png);height:40px}#floating-toolbar,.ideaInput{z-index:999;position:absolute}#gplus-share,#logo-img,.logo{background-position:left center;background-repeat:no-repeat}#gplus-share,#toolbarSocial,.menulink{margin-right:20px}#modalDownload .modal-body,.center,.ideaInput{text-align:center}#splittable,.ios-wkwebview{-webkit-tap-highlight-color:transparent}body,html{height:100%}#container,#floating-toolbar,#topbar,.dropdown-menu,.floating{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.logo{width:0;padding-left:40px;padding-top:5px;padding-bottom:5px}#logo-img{width:40px;padding:0;margin-left:50px}@media (max-width:979px){.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;margin-bottom:0}}@media (max-width:767px){.navbar{padding-left:20px;padding-right:20px}body{padding:0}}.span-toolbar{width:120px;cursor:move}.dropdown-menu li a,.share{cursor:pointer}@media (max-width:1024px){.hidden-tablet-landscape{display:none!important}}@media (max-width:768px){.hidden-tablet-portrait{display:none!important}}@media (max-width:320px){.hidden-phone-portrait{display:none!important}}@media (max-height:320px){.form-horizontal .control-group{margin-bottom:5px}.hidden-phone-landscape{display:none}}@media (max-width:400px){.map-changed .changed-phone-hidden{display:none!important}}@media (max-width:1024px){.map-changed .changed-tablet-hidden{display:none!important}}@media screen{#topbar .nav>li>a{font-weight:700}}#floating-toolbar{top:100px;right:10px;border:1px solid #08c;border-radius:9px}.android .visible-touch,.ios .visible-touch{display:block!important}.android .hidden-touch,.ios .hidden-touch,.map-changed .hidden-map-changed,.map-unchanged .visible-map-changed{display:none!important}div.topbar-color-picker{width:127px}.topbar-color-picker .colorPicker-swatch{width:15px;height:15px}.android .colorPicker-swatch,.ios .colorPicker-swatch{width:30px;height:30px}.android .colorPicker-palette,.ios .colorPicker-palette{width:217px;top:55px;left:5px}#toolbarEdit .btn-group{margin-left:0}#toolbarEdit button{margin-top:5px;width:40px;height:32px}#toolbarEdit button.two{width:80px}#toolbarEdit button.three{width:120px}.ideaInput{background-color:#5FBF5F;color:#FFF;font-family:Helvetica,"Arial Unicode MS",sans-serif;font-weight:700;font-size:13px;line-height:13px;padding:0}.ideaInput:focus{outline:0}.menulink{margin-top:10px}#gplus-share{border:0;height:16px;background-image:url(//ssl.gstatic.com/images/icons/gplus-16.png);margin-left:6px;margin-top:2px;padding-right:0}.btn-inline,a.repo{margin-left:5px}#modalDownload .modal-body p{text-align:left}#modalDownload img{max-height:300px;max-width:450px}.share{width:32px;height:32px;display:inline-block;margin:5px}.btn-inline{border:0;background:0 0;margin-right:5px}.colorPicker_hexWrap{display:none}div.colorPicker-picker{display:inline;float:left;background:0 0}.repo,.repo-a{background-image:url(//d1g6a398qq2djm.cloudfront.net/img/logo_16.png)}#modalImport input[type=file]{opacity:0;position:absolute;width:0;height:0}.repo{background-repeat:no-repeat;background-position:left center}.repo-b{background-image:url(//d1g6a398qq2djm.cloudfront.net/img/logo_gold_16.png)}.repo-p{background-image:url(//d1g6a398qq2djm.cloudfront.net/img/logo_gold_private_16.png)}.repo-g{background-image:url(//d1g6a398qq2djm.cloudfront.net/img/google_drive.png)}.repo-menuitem{background-position:14px center;padding-left:35px!important}span.repo-a,span.repo-b,span.repo-g,span.repo-p{padding-left:16px}.visible-collapsed-toolbar{display:none}.collapsed-toolbar .visible-collapsed-toolbar{display:block!important}.collapsed-toolbar span.visible-collapsed-toolbar{display:inline!important}.collapsed-toolbar .hidden-collapsed-toolbar{display:none!important}.visible-row-split{display:none}.row-split .visible-row-split{display:block!important}.row-split span.visible-row-split{display:inline!important}.row-split .hidden-row-split{display:none!important}.visible-column-split{display:none}.column-split .visible-column-split{display:block!important}.column-split span.visible-column-split{display:inline!important}.column-split .hidden-column-split{display:none!important}#listBookmarks a .btn-inline{margin-left:15px}.gecko #listBookmarks a .btn-inline{position:relative;top:-17px;left:15px}.hidden-topbar #topbar .navbar-inner{visibility:hidden}.column-split.hidden-topbar #topbar .navbar-inner,.hidden-topbar #topbar:hover .navbar-inner{visibility:visible}.topbar-autohide{display:inline}.hidden-topbar .topbar-autohide,span.topbar-fixed{display:none}.hidden-topbar span.topbar-fixed{display:inline}body.ipad{margin:1px}body.ios-wkwebview{margin:0;padding:0;width:100%}.image-render-visible{display:none}.image-render .image-render-visible{display:block}.image-render .image-render-hidden,.offline-disabled-hidden{display:none}body.offline-enabled .offline-disabled-hidden{display:block}#attachEditArea{overflow:scroll;outline:0;border:1px solid #ccc;padding:4px;margin:10px;box-sizing:content-box;-webkit-box-shadow:rgba(0,0,0,.0745098) 0 1px 1px 0 inset;box-shadow:rgba(0,0,0,.0745098) 0 1px 1px 0 inset;border-radius:3px;background-color:#fff;display:block;clear:both}.alert-error input,input.noglow{outline:0;box-shadow:none!important;cursor:pointer}#modalAttachmentEditor .editor-topbar{padding-right:10px;padding-left:10px;display:none}#modalAttachmentEditor.mm-editable .editor-topbar{display:block}#modalAttachmentEditor .viewer-topbar{margin-top:5px;margin-bottom:5px;padding-right:10px;padding-left:10px;height:30px}#modalAttachmentEditor.mm-editable .viewer-topbar{display:none}#modalAttachmentEditor button.close{margin-left:5px}#modalAttachmentEditor .editor-topbar>.btn-toolbar{display:inline-block;background-color:#eee;width:100%}#modalAttachmentEditor{position:absolute;height:80%;width:80%;left:10%;top:10%;padding:5px;z-index:9999;background-color:#eee;-moz-border-radius:6px;border-radius:6px;margin:0}@media (max-width:1200px){#modalAttachmentEditor{width:90%;left:5%;top:5%}}#modalAttachmentEditor .dropdown-menu .btn-toolbar{display:block}.show-active,.visible-map-source-a,.visible-map-source-b,.visible-map-source-g,.visible-map-source-n,.visible-map-source-p{display:none}#modalAttachmentEditor .dropdown-menu .btn-toolbar .btn-group{margin-top:5px;margin-left:5px;margin-right:5px}.android #modalAttachmentEditor .btn-toolbar,.ios #modalAttachmentEditor .btn-toolbar{margin-top:5px;margin-bottom:5px;padding-bottom:10px}.android #modalAttachmentEditor,.ios #modalAttachmentEditor{top:0;height:60%}@media (max-height:768px){.android #modalAttachmentEditor,.ios #modalAttachmentEditor{height:37%}}.non-group .btn{margin-left:5px}#linkEditWidget{display:none;z-index:999;position:absolute;padding:10px}.alert a{text-decoration:underline;cursor:pointer}.alert a.btn{text-decoration:none}input.noglow{border:none!important}.map-source-a .visible-map-source-a,.map-source-b .visible-map-source-b,.map-source-g .visible-map-source-g,.map-source-n .visible-map-source-n,.map-source-p .visible-map-source-p{display:inherit}@media (max-width:1200px){.hidden-narrow-screen{display:none}}@media (max-width:1600px){#optionalPane .hidden-narrow-screen{display:none}}.btn-xlarge{padding:18px 28px;font-size:40px;line-height:normal;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;width:250px;height:180px;margin-left:10px;margin-bottom:10px}@media (max-height:320px){.btn-xlarge{padding:9px 14px;font-size:20px;line-height:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;width:230px;height:70px;margin-left:5px;margin-bottom:5px}}@media (max-width:320px){.btn-xlarge{padding:9px 14px;font-size:20px;line-height:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;width:230px;height:70px;margin-left:5px;margin-bottom:5px}#mainMenu>li>a{padding-left:0;padding-right:15px}#logo-img{margin:0;padding-left:5px;padding-right:0}}@media (min-height:640px){#modalKeyActions .modal-body{min-height:400px}}#modalKeyActions .item a span{padding-right:10px}#modalKeyActions a{cursor:pointer;color:#000}#modalKeyActions div.item>a{text-decoration:underline}.social{display:inline-block!important;padding-left:2px!important;padding-right:2px!important;margin-right:2px!important;cursor:pointer}#modalGoldLicense textarea{width:95%;height:200px}#modalIconEdit .file-drop-zone{width:150px;height:150px;border:1px dashed #000;color:gray;font-size:7px;text-align:center}#modalIconEdit .file-drop-zone img{max-width:150px;max-height:150px}#modalIconEdit{z-index:9999}.btn .export{max-width:24px}.btn .landscape{transform:rotate(90deg);-moz-transform:rotate(90deg);-webkit-transform:rotate(90deg)}#modalPDFExport form{margin-top:20px}.alert-error input{background:0 0;margin:0 0 2px;color:#b94a48;border:none!important;width:100%}body .modal.huge{width:90%;height:80%;left:5%;margin-left:auto;margin-right:auto}#measuresSheet table{background-color:#fff;border:1px solid #d4d4d4}#measuresSheet td{text-align:right;width:20%}#measuresSheet th+th{text-align:right}#measuresSheet table thead tr th{border-bottom:3px gray solid!important}#measuresSheet table tfoot tr th{border-top:3px gray solid!important}#measuresSheet table tbody th{width:30%}#measuresSheet form{margin:0!important}#measuresSheet td:focus{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;outline:#5b9dd9 auto}.measures-editor.error{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px red!important;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px red!important;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px red!important;outline:red auto thin!important}#measuresSheet .modal-footer a{margin-right:20px;margin-left:20px;line-height:30px}#optionalPane .input-append.navbar-form button{margin-top:5px!important}#measuresSheet{margin:5px;border-left:1px;user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.table-container{margin-right:5px}.column-split .table-container{margin-bottom:45px}.mm-active .show-active{display:initial}.black{color:#000!important}.activated-scene{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3;outline:#5b9dd9 auto}#storyboard{user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.storyboard-container{padding-left:10px;padding-right:10px}.storyboard-scene{display:inline-block;max-height:120px;max-width:160px;min-height:120px;min-width:160px;overflow:hidden;background-color:#fff;border:1px solid silver;border-radius:4px;color:#000;margin:10px;text-align:center;cursor:move}.ios-icon-label,.ios-modal-title{color:#22AAE0;line-height:1.2;font-family:Helvetica,"Arial Unicode MS",sans-serif;text-align:center}.storyboard-scene-title{display:table-cell;vertical-align:middle;white-space:pre-wrap;font-weight:700}.drag-shadow{opacity:.5;box-shadow:none;outline:0;cursor:move;z-index:10;position:relative}.collaborator-list-container td,.mapjs-node{cursor:pointer}.potential-drop-left{margin-left:0;margin-right:20px}.potential-drop-right{margin-left:20px;margin-right:0}#splittable{width:100%;height:calc(100% - 41px);margin-top:41px}.hidden-topbar:not(.column-split)>#splittable{height:100%;margin-top:0}.ios-wkwebview>#splittable{width:100%;height:calc(100%);margin-top:0}.splittable-optional .navbar{width:100%;margin-bottom:0}.splittable-optional .content{overflow:scroll;height:calc(100% - 41px)}.ios-wkwebview .splittable-optional .content{overflow:scroll;height:100%}.splittable-optional{display:none;outline:#d4d4d4 solid 1px}.column-split .splittable-primary{float:left;height:100%;overflow:scroll;width:50%}.column-split .splittable-optional{float:right;height:100%;width:50%;display:block}.splittable-primary{outline:#d4d4d4 solid 1px;height:100%;width:100%}.row-split .splittable-primary{height:50%;overflow:scroll;width:100%}.row-split .splittable-optional{height:50%;width:100%;display:block}#optionalPane{background-color:#f5f5f5;-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1)}.splittable-optional .content-container{height:100%}.pull-middle{display:table;height:100%}.pull-middle>div{display:table-cell;vertical-align:middle;padding-left:20%;padding-right:20%}.ios-menu-toggle-container{position:absolute;height:90px;width:75px;top:calc(100% - 90px);left:0;overflow:hidden;z-index:999}.ios-corner-icon-container{position:absolute;height:100px;width:75px;top:5;left:-11px;border:1px solid #22AAE0;background-color:#FFF;border-radius:5px;box-shadow:2px 2px 1px rgba(104,104,104,.8)}.ios-modal,.ios-toolbar{background-color:rgba(255,255,255,.85)}.ios-menu-toggle{position:absolute;top:5;left:12;height:73px;width:65px}.ios-toolbar{position:absolute;box-shadow:0 -1px 0 #22AAE0;top:calc(100% - 80px);left:65px;width:calc(100% - 65px);height:80px;z-index:998;overflow:scroll}.ios-icon-label{position:absolute;font-size:8pt;font-weight:400;top:calc(100% - 22px);width:50px;left:5px;height:22px}.ios-toolbar-item{display:inline-block;position:relative;top:0;height:75px;width:60px}.ios-toolbar-icon{position:absolute;left:10px;top:8px;height:40px;width:40px}.ios-modal{z-index:1090;position:absolute;overflow:hidden;left:0;top:0;height:100%;width:100%}.ios-modal-close{position:absolute;top:20;left:calc(100% - 45px);height:40px;width:40px}.ios-modal-title{position:absolute;left:45px;top:30px;height:40px;width:calc(100% - 90px);font-size:12pt;font-weight:700}.ios-modal-content{z-index:1095;position:absolute;top:80px;height:calc(100% - 75px);overflow-x:hidden;overflow-y:auto;background-color:#FFF;border:1px solid #22AAE0;border-radius:5px;box-shadow:2px 2px 1px rgba(104,104,104,.8)}.ios-color-selector{display:inline-block;box-shadow:1px 1px 3px rgba(104,104,104,.7)}.ios-color-palette{display:block;text-align:center}@media (min-width:601px){.ios-modal-content{left:calc(50% - 300px);width:600px}.ios-color-palette{margin:20px 50px}.ios-color-selector{margin:20px;height:80px;width:80px;border-radius:40px}}@media (max-width:600px){.ios-modal-content{left:calc(50% - 150px);width:300px}.ios-color-palette{margin:10px 20px}.ios-color-selector{margin:10px;height:40px;width:40px;border-radius:20px}}.collaborator-list-container{max-height:200px;overflow-y:scroll;padding-top:10px}.mm-icon-gdrive,.mm-icon-gmail{padding:15px 9px;background-position:center center;background-repeat:no-repeat}.collaborator-list-container table{margin-bottom:0}.mm-collaborator{position:absolute;width:32px;height:32px;border:3px solid transparent;border-radius:20px;transform:translate(5px,5px);min-width:30px}.collab-name{overflow-x:hidden;max-width:50px;white-space:nowrap}.collab-name a{line-height:30px}.collab-photo{width:30px}.collab-photo img{width:30px;height:30px;float:left;margin-right:10px;border:2px solid transparent;border-radius:20px}#floating-collaborators{position:absolute;top:100px;left:10px;z-index:999;border:1px solid #08c;border-radius:9px}.visible-collaboration-toolbar{display:none}.map-source-c.collaboration-toolbar .visible-collaboration-toolbar{display:initial!important}.collaboration-toolbar .hidden-collaboration-toolbar{display:none!important}.mm-has-collaborators .hidden-has-collaborators,.visible-has-collaborators{display:none}.mm-has-collaborators .visible-has-collaborators{display:block}.visible-collaboration-mute-speech{display:none}.collaboration-mute-speech .visible-collaboration-mute-speech{display:initial!important}.collaboration-mute-speech .hidden-collaboration-mute-speech{display:none!important}.btn-share{margin-left:10px;margin-right:10px}.mm-icon-gmail{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNDU1NTUgMzMwOTciPg0KIDxnPg0KICA8cGF0aCBmaWxsPSIjRTc1QTREIiBkPSJNNDE2MDggMjUwYzIwMzAsMCAzNjkxLDE2NjEgMzY5MSwzNjkxbDAgMTA3MCAtNTEyOCAzNjYzIC0xNzQ1OCAxMjAyNiAtMTc0NTggLTEyMTQ4IDAgMjQyOTUgLTEzMTQgMGMtMjAzMCwwIC0zNjkxLC0xNjYxIC0zNjkxLC0zNjkxbDAgLTI1MjE0YzAsLTIwMzAgMTY2MSwtMzY5MSAzNjkxLC0zNjkxbDE4NzcyIDEzOTE4IDE4ODk0IC0xMzkxOHoiLz4NCiAgPHBvbHlnb24gZmlsbD0iI0U3RTRENyIgcG9pbnRzPSI1MjU1LDg1NTIgNTI1NSwzMjg0NyA0MDE3MiwzMjg0NyA0MDE3Miw4Njc0IDIyNzEzLDIwNzAwICIvPg0KICA8cG9seWdvbiBmaWxsPSIjQjhCN0FFIiBwb2ludHM9IjUyNTUsMzI4NDcgMjI3MTMsMjA3MDAgMjI2MzQsMjA2NDQgNTI1NSwzMjU4MCAiLz4NCiAgPHBvbHlnb24gZmlsbD0iI0I3QjZBRCIgcG9pbnRzPSI0MDE3Miw4Njc0IDQwMTgyLDMyODQ3IDIyNzEzLDIwNzAwICIvPg0KICA8cGF0aCBmaWxsPSIjQjIzOTJGIiBkPSJNNDUyOTkgNTAxMWw2IDI0MTQ4Yy04OCwyNjg0IC0xMjUxLDM2NDIgLTUxMjMsMzY4N2wtMTAgLTI0MTczIDUxMjggLTM2NjN6Ii8+DQogIDxwb2x5Z29uIGZpbGw9IiNGN0Y1RUQiIHBvaW50cz0iMzk0MSwyNTAgMjI3MTMsMTQxNjggNDE2MDgsMjUwICIvPg0KIDwvZz4NCjwvc3ZnPg0K)}.mm-icon-gdrive{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iNTA5LjA4bW0iIGhlaWdodD0iNDQwLjk3bW0iIHZlcnNpb249IjEuMSIgc3R5bGU9InNoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247IHRleHQtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbjsgaW1hZ2UtcmVuZGVyaW5nOm9wdGltaXplUXVhbGl0eTsgZmlsbC1ydWxlOmV2ZW5vZGQ7IGNsaXAtcnVsZTpldmVub2RkIg0Kdmlld0JveD0iMCAwIDEzMzE1NiAxMTUzNDEiDQogeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPg0KIDxkZWZzPg0KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KICAgPCFbQ0RBVEFbDQogICAgLmZpbDIge2ZpbGw6IzExQTg2MX0NCiAgICAuZmlsMCB7ZmlsbDojMzc3N0UzfQ0KICAgIC5maWwxIHtmaWxsOiNGRkNGNjN9DQogICBdXT4NCiAgPC9zdHlsZT4NCiA8L2RlZnM+DQogPGcgaWQ9IkxpdmVsbG9feDAwMjBfMSI+DQogIDxtZXRhZGF0YSBpZD0iQ29yZWxDb3JwSURfMENvcmVsLUxheWVyIi8+DQogIDxwb2x5Z29uIGNsYXNzPSJmaWwwIiBwb2ludHM9IjIyMTk0LDExNTM0MSA0NDM4NSw3Njg5NCAxMzMxNTYsNzY4OTQgMTEwOTYzLDExNTM0MSAiLz4NCiAgPHBvbHlnb24gY2xhc3M9ImZpbDEiIHBvaW50cz0iODg3NzIsNzY4OTQgMTMzMTU2LDc2ODk0IDg4NzcyLDAgNDQzODUsMCAiLz4NCiAgPHBvbHlnb24gY2xhc3M9ImZpbDIiIHBvaW50cz0iMCw3Njg5NCAyMjE5NCwxMTUzNDEgNjY1NzgsMzg0NDcgNDQzODUsMCAiLz4NCiA8L2c+DQo8L3N2Zz4=);background-size:100% 100%}.mapjs-attachment,.mapjs-hyperlink{background-repeat:no-repeat no-repeat;position:absolute}#collaboratorSpeechBubble{position:absolute;z-index:2;top:100px;left:20px}#collaboratorSpeechBubble img{width:60px;height:60px;border-radius:60px;border:2px solid transparent;box-shadow:0 5px 10px rgba(0,0,0,.2);cursor:pointer}#collaboratorSpeechBubble img:hover{box-shadow:0 5px 10px rgba(0,0,0,.5)}.speech-bubble-inner{max-width:300px;max-height:100px;min-width:150px;overflow:hidden}.speech-bubble-title{max-width:300px;white-space:nowrap;overflow:hidden}.icon-volume-off{margin-left:3px;margin-right:5px}.hidden-topbar #mainMenu .dropdown-menu{margin-top:0}.mapjs-node.activated,.mapjs-node.droppable{margin:-2px;outline:0}.mapjs-node{z-index:3;user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;font-family:Helvetica,"Arial Unicode MS",sans-serif;font-weight:700;font-size:12px}.mapjs-add-link{cursor:crosshair}.mapjs-add-link .mapjs-node{cursor:alias}.mapjs-node span{white-space:pre-wrap;text-align:center;line-height:150%;display:block;max-width:146px;min-height:1.5em;min-width:1em;outline:0;cursor:pointer}.mapjs-node span[contenteditable=true]{user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;cursor:auto}#container,.mindmup-container,.mindmup-noselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.mapjs-node:focus{outline:0}.mapjs-node.selected{outline:0;z-index:4}.mapjs-node.dragging{opacity:.4;z-index:5}.mapjs-node-light{color:#4F4F4F}.mapjs-node-dark{color:#EEE}.mapjs-node-white{color:#000}.mapjs-label{left:-.75em;position:absolute;bottom:-1em;opacity:.8;background-color:#f13333;padding:1px 2px;border:1px solid #fff;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;display:inline-block;font-size:10px;font-weight:700;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);white-space:nowrap;vertical-align:baseline}.mapjs-hyperlink{right:-.75em;bottom:-.75em;background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoNCgkgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQoNCjxwYXRoIHN0eWxlPSJmaWxsOmJsYWNrIiBkPSJNMTU2LjIyNiwxOTkuNjc5YzcuNTQxLTcuNTQsMTUuOTAyLTEzLjc1NywyNC43OTQtMTguNjU5YzQ5LjU1Ni0yNy4zMTgsMTEzLjExNy0xMi43ODgsMTQ0Ljk3LDM1LjUxOA0KDQoJbC0zOC41NDcsMzguNTQ3Yy0xMS4wNTktMjUuMjI3LTM4LjUtMzkuNTY1LTY1LjgxMy0zMy40NTZjLTEwLjI4MiwyLjMtMjAuMDU0LDcuNDI3LTI4LjAzOSwxNS40MTNsLTczLjg5OCw3My44OTYNCg0KCWMtMjIuNDMzLDIyLjQzMy0yMi40MzIsNTguOTM2LDAuMDAyLDgxLjM2OWMyMi40MzMsMjIuNDMzLDU4LjkzNSwyMi40MzMsODEuMzY4LDBsMjIuNzgtMjIuNzc5DQoNCgljMjAuNzEsOC4yMTcsNDIuOTM4LDExLjUwOCw2NC44NjIsOS44NjNsLTUwLjI3OCw1MC4yNzhjLTQzLjEwNSw0My4xMDUtMTEyLjk5MSw0My4xMDUtMTU2LjA5NiwwDQoNCgljLTQzLjEwNS00My4xMDQtNDMuMTA2LTExMi45OTEtMC4wMDEtMTU2LjA5NkwxNTYuMjI2LDE5OS42Nzl6IE0yNzMuNTc0LDgyLjMzbC01MC4yNzgsNTAuMjc4DQoNCgljMjEuOTI4LTEuNjQzLDQ0LjE1MiwxLjY0OCw2NC44NjMsOS44NjVsMjIuNzc5LTIyLjc4YzIyLjQzNC0yMi40MzQsNTguOTM2LTIyLjQzNCw4MS4zNywwYzIyLjQzNCwyMi40MzQsMjIuNDM0LDU4LjkzNiwwLDgxLjM3DQoNCglsLTczLjg5Nyw3My44OTVjLTIyLjUwMSwyMi41MDEtNTkuMDYxLDIyLjMxMS04MS4zNjgsMGMtNS4yMDItNS4yMDEtOS42OTQtMTEuNjc4LTEyLjQ4NC0xOC4wNGwtMzguNTQ2LDM4LjU0Ng0KDQoJYzQuMDQ5LDYuMTQyLDguMjYxLDExLjQ1MywxMy42NjYsMTYuODU4YzEzLjk0OSwxMy45NSwzMS42OTgsMjQuMzM5LDUyLjExNywyOS4yNTFjMjYuNDY2LDYuMzcsNTQuODIzLDIuODM5LDc5LjE4NS0xMC41OTINCg0KCWM4Ljg5Mi00LjkwMywxNy4yNTQtMTEuMTE5LDI0Ljc5NC0xOC42NTlsNzMuODk2LTczLjg5NWM0My4xMDUtNDMuMTA1LDQzLjEwNS0xMTIuOTkxLDAuMDAxLTE1Ni4wOTcNCg0KCUMzODYuNTY2LDM5LjIyNSwzMTYuNjgsMzkuMjI1LDI3My41NzQsODIuMzN6Ii8+DQo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAsLTIwKSIgc3R5bGU9ImZpbGw6IzAwOUFDRCI+DQo8cGF0aCAgZD0iTTE1Ni4yMjYsMTk5LjY3OWM3LjU0MS03LjU0LDE1LjkwMi0xMy43NTcsMjQuNzk0LTE4LjY1OWM0OS41NTYtMjcuMzE4LDExMy4xMTctMTIuNzg4LDE0NC45NywzNS41MTgNCg0KCWwtMzguNTQ3LDM4LjU0N2MtMTEuMDU5LTI1LjIyNy0zOC41LTM5LjU2NS02NS44MTMtMzMuNDU2Yy0xMC4yODIsMi4zLTIwLjA1NCw3LjQyNy0yOC4wMzksMTUuNDEzbC03My44OTgsNzMuODk2DQoNCgljLTIyLjQzMywyMi40MzMtMjIuNDMyLDU4LjkzNiwwLjAwMiw4MS4zNjljMjIuNDMzLDIyLjQzMyw1OC45MzUsMjIuNDMzLDgxLjM2OCwwbDIyLjc4LTIyLjc3OQ0KDQoJYzIwLjcxLDguMjE3LDQyLjkzOCwxMS41MDgsNjQuODYyLDkuODYzbC01MC4yNzgsNTAuMjc4Yy00My4xMDUsNDMuMTA1LTExMi45OTEsNDMuMTA1LTE1Ni4wOTYsMA0KDQoJYy00My4xMDUtNDMuMTA0LTQzLjEwNi0xMTIuOTkxLTAuMDAxLTE1Ni4wOTZMMTU2LjIyNiwxOTkuNjc5eiBNMjczLjU3NCw4Mi4zM2wtNTAuMjc4LDUwLjI3OA0KDQoJYzIxLjkyOC0xLjY0Myw0NC4xNTIsMS42NDgsNjQuODYzLDkuODY1bDIyLjc3OS0yMi43OGMyMi40MzQtMjIuNDM0LDU4LjkzNi0yMi40MzQsODEuMzcsMGMyMi40MzQsMjIuNDM0LDIyLjQzNCw1OC45MzYsMCw4MS4zNw0KDQoJbC03My44OTcsNzMuODk1Yy0yMi41MDEsMjIuNTAxLTU5LjA2MSwyMi4zMTEtODEuMzY4LDBjLTUuMjAyLTUuMjAxLTkuNjk0LTExLjY3OC0xMi40ODQtMTguMDRsLTM4LjU0NiwzOC41NDYNCg0KCWM0LjA0OSw2LjE0Miw4LjI2MSwxMS40NTMsMTMuNjY2LDE2Ljg1OGMxMy45NDksMTMuOTUsMzEuNjk4LDI0LjMzOSw1Mi4xMTcsMjkuMjUxYzI2LjQ2Niw2LjM3LDU0LjgyMywyLjgzOSw3OS4xODUtMTAuNTkyDQoNCgljOC44OTItNC45MDMsMTcuMjU0LTExLjExOSwyNC43OTQtMTguNjU5bDczLjg5Ni03My44OTVjNDMuMTA1LTQzLjEwNSw0My4xMDUtMTEyLjk5MSwwLjAwMS0xNTYuMDk3DQoNCglDMzg2LjU2NiwzOS4yMjUsMzE2LjY4LDM5LjIyNSwyNzMuNTc0LDgyLjMzeiIvPg0KPC9nPg0KDQo8L3N2Zz4NCg0K);width:2em;height:2em;background-size:2em}.mapjs-hyperlink:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoNCgkgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQoNCjxwYXRoIHN0eWxlPSJmaWxsOmJsYWNrIiBkPSJNMTU2LjIyNiwxOTkuNjc5YzcuNTQxLTcuNTQsMTUuOTAyLTEzLjc1NywyNC43OTQtMTguNjU5YzQ5LjU1Ni0yNy4zMTgsMTEzLjExNy0xMi43ODgsMTQ0Ljk3LDM1LjUxOA0KDQoJbC0zOC41NDcsMzguNTQ3Yy0xMS4wNTktMjUuMjI3LTM4LjUtMzkuNTY1LTY1LjgxMy0zMy40NTZjLTEwLjI4MiwyLjMtMjAuMDU0LDcuNDI3LTI4LjAzOSwxNS40MTNsLTczLjg5OCw3My44OTYNCg0KCWMtMjIuNDMzLDIyLjQzMy0yMi40MzIsNTguOTM2LDAuMDAyLDgxLjM2OWMyMi40MzMsMjIuNDMzLDU4LjkzNSwyMi40MzMsODEuMzY4LDBsMjIuNzgtMjIuNzc5DQoNCgljMjAuNzEsOC4yMTcsNDIuOTM4LDExLjUwOCw2NC44NjIsOS44NjNsLTUwLjI3OCw1MC4yNzhjLTQzLjEwNSw0My4xMDUtMTEyLjk5MSw0My4xMDUtMTU2LjA5NiwwDQoNCgljLTQzLjEwNS00My4xMDQtNDMuMTA2LTExMi45OTEtMC4wMDEtMTU2LjA5NkwxNTYuMjI2LDE5OS42Nzl6IE0yNzMuNTc0LDgyLjMzbC01MC4yNzgsNTAuMjc4DQoNCgljMjEuOTI4LTEuNjQzLDQ0LjE1MiwxLjY0OCw2NC44NjMsOS44NjVsMjIuNzc5LTIyLjc4YzIyLjQzNC0yMi40MzQsNTguOTM2LTIyLjQzNCw4MS4zNywwYzIyLjQzNCwyMi40MzQsMjIuNDM0LDU4LjkzNiwwLDgxLjM3DQoNCglsLTczLjg5Nyw3My44OTVjLTIyLjUwMSwyMi41MDEtNTkuMDYxLDIyLjMxMS04MS4zNjgsMGMtNS4yMDItNS4yMDEtOS42OTQtMTEuNjc4LTEyLjQ4NC0xOC4wNGwtMzguNTQ2LDM4LjU0Ng0KDQoJYzQuMDQ5LDYuMTQyLDguMjYxLDExLjQ1MywxMy42NjYsMTYuODU4YzEzLjk0OSwxMy45NSwzMS42OTgsMjQuMzM5LDUyLjExNywyOS4yNTFjMjYuNDY2LDYuMzcsNTQuODIzLDIuODM5LDc5LjE4NS0xMC41OTINCg0KCWM4Ljg5Mi00LjkwMywxNy4yNTQtMTEuMTE5LDI0Ljc5NC0xOC42NTlsNzMuODk2LTczLjg5NWM0My4xMDUtNDMuMTA1LDQzLjEwNS0xMTIuOTkxLDAuMDAxLTE1Ni4wOTcNCg0KCUMzODYuNTY2LDM5LjIyNSwzMTYuNjgsMzkuMjI1LDI3My41NzQsODIuMzN6Ii8+DQo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAsLTIwKSIgc3R5bGU9ImZpbGw6IzAwQkZGRiI+DQo8cGF0aCAgZD0iTTE1Ni4yMjYsMTk5LjY3OWM3LjU0MS03LjU0LDE1LjkwMi0xMy43NTcsMjQuNzk0LTE4LjY1OWM0OS41NTYtMjcuMzE4LDExMy4xMTctMTIuNzg4LDE0NC45NywzNS41MTgNCg0KCWwtMzguNTQ3LDM4LjU0N2MtMTEuMDU5LTI1LjIyNy0zOC41LTM5LjU2NS02NS44MTMtMzMuNDU2Yy0xMC4yODIsMi4zLTIwLjA1NCw3LjQyNy0yOC4wMzksMTUuNDEzbC03My44OTgsNzMuODk2DQoNCgljLTIyLjQzMywyMi40MzMtMjIuNDMyLDU4LjkzNiwwLjAwMiw4MS4zNjljMjIuNDMzLDIyLjQzMyw1OC45MzUsMjIuNDMzLDgxLjM2OCwwbDIyLjc4LTIyLjc3OQ0KDQoJYzIwLjcxLDguMjE3LDQyLjkzOCwxMS41MDgsNjQuODYyLDkuODYzbC01MC4yNzgsNTAuMjc4Yy00My4xMDUsNDMuMTA1LTExMi45OTEsNDMuMTA1LTE1Ni4wOTYsMA0KDQoJYy00My4xMDUtNDMuMTA0LTQzLjEwNi0xMTIuOTkxLTAuMDAxLTE1Ni4wOTZMMTU2LjIyNiwxOTkuNjc5eiBNMjczLjU3NCw4Mi4zM2wtNTAuMjc4LDUwLjI3OA0KDQoJYzIxLjkyOC0xLjY0Myw0NC4xNTIsMS42NDgsNjQuODYzLDkuODY1bDIyLjc3OS0yMi43OGMyMi40MzQtMjIuNDM0LDU4LjkzNi0yMi40MzQsODEuMzcsMGMyMi40MzQsMjIuNDM0LDIyLjQzNCw1OC45MzYsMCw4MS4zNw0KDQoJbC03My44OTcsNzMuODk1Yy0yMi41MDEsMjIuNTAxLTU5LjA2MSwyMi4zMTEtODEuMzY4LDBjLTUuMjAyLTUuMjAxLTkuNjk0LTExLjY3OC0xMi40ODQtMTguMDRsLTM4LjU0NiwzOC41NDYNCg0KCWM0LjA0OSw2LjE0Miw4LjI2MSwxMS40NTMsMTMuNjY2LDE2Ljg1OGMxMy45NDksMTMuOTUsMzEuNjk4LDI0LjMzOSw1Mi4xMTcsMjkuMjUxYzI2LjQ2Niw2LjM3LDU0LjgyMywyLjgzOSw3OS4xODUtMTAuNTkyDQoNCgljOC44OTItNC45MDMsMTcuMjU0LTExLjExOSwyNC43OTQtMTguNjU5bDczLjg5Ni03My44OTVjNDMuMTA1LTQzLjEwNSw0My4xMDUtMTEyLjk5MSwwLjAwMS0xNTYuMDk3DQoNCglDMzg2LjU2NiwzOS4yMjUsMzE2LjY4LDM5LjIyNSwyNzMuNTc0LDgyLjMzeiIvPg0KPC9nPg0KDQo8L3N2Zz4NCg==)}.mapjs-attachment{right:-5px;top:-15px;background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoJIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0eWxlPSJ0cmFuc2Zvcm06cm90YXRlKDEwKSI+DQogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiwtMikgcm90YXRlKDEwKSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MiI+DQogICA8cGF0aCBkPSJNNCwxNiBMNCwxMSBDNCwxIDE1LDEgMTUsMTEgTDE1LDI2IEMxNSwzMSA4LDMxIDgsMjUgTDgsMTAiDQogICAgIHN0eWxlPSJzdHJva2U6YmxhY2siLz4NCiAgIDxwYXRoIGQ9Ik0zLDE2IEwzLDEwIEMzLDAgMTQsMCAxNCwxMCBMMTQsMjUgQzE0LDMwIDcsMzAgNywyNSBMNywxMCINCiAgICAgc3R5bGU9InN0cm9rZTojMDA5QUNEIi8+DQogIDwvZz4NCjwvc3ZnPg0K);width:16px;height:32px;background-size:16px 32px}.mapjs-attachment:hover{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoJIHdpZHRoPSIxNnB4IiBoZWlnaHQ9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHN0eWxlPSJ0cmFuc2Zvcm06cm90YXRlKDEwKSI+DQogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMiwtMikgcm90YXRlKDEwKSIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MiI+DQogICA8cGF0aCBkPSJNNCwxNiBMNCwxMSBDNCwxIDE1LDEgMTUsMTEgTDE1LDI2IEMxNSwzMSA4LDMxIDgsMjUgTDgsMTAiDQogICAgIHN0eWxlPSJzdHJva2U6YmxhY2siLz4NCiAgIDxwYXRoIGQ9Ik0zLDE2IEwzLDEwIEMzLDAgMTQsMCAxNCwxMCBMMTQsMjUgQzE0LDMwIDcsMzAgNywyNSBMNywxMCINCiAgICAgc3R5bGU9InN0cm9rZTojMDBCRkZGIi8+DQogIDwvZz4NCjwvc3ZnPg0K)}.mapjs-draw-container{position:absolute;margin:0;padding:0}.mapjs-draw-container[data-mapjs-role=connector]{z-index:1}.mapjs-draw-container[data-mapjs-role=link]{z-index:2}.mapjs-connector{stroke-width:1px;fill:none}.mapjs-link{stroke-width:1.5px;fill:none}.mapjs-link-hit{stroke:transparent;stroke-width:15;cursor:crosshair}#container{background-color:#FFF;margin:0;padding:0}.mapjs-reorder-bounds{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjExcHgiIGhlaWdodD0iMjAuNjM0cHgiPg0KPHBhdGggZD0iTTAuMTk5LDEwLjc5bDkuNjQ2LDkuNjQ2YzAuMTMsMC4xMzMsMC4zMDIsMC4xOTcsMC40NzEsMC4xOTdjMC4xNzMsMCwwLjM0NC0wLjA2NCwwLjQ3My0wLjE5N2wwLjAxMy0wLjAxMQ0KCUMxMC45MjksMjAuMzA1LDExLDIwLjEzMiwxMSwxOS45NTJ2LTQuNjhjMC0wLjE4Mi0wLjA3LTAuMzQ3LTAuMTk4LTAuNDc1TDYuMzIzLDEwLjMybDQuNDc5LTQuNDgzQzEwLjkyOSw1LjcxNCwxMSw1LjU0MywxMSw1LjM2NA0KCVYwLjY4MmMwLTAuMTc2LTAuMDctMC4zNDUtMC4xOTgtMC40NzRsLTAuMDEzLTAuMDEyQzEwLjY2NywwLjA3MSwxMC40OTcsMCwxMC4zMTYsMEMxMC4xNDMsMCw5Ljk3LDAuMDcxLDkuODQ1LDAuMTk2TDAuMTk5LDkuODQ1DQoJQy0wLjA2NiwxMC4xMDQtMC4wNjYsMTAuNTMyLDAuMTk5LDEwLjc5eiIvPg0KPC9zdmc+);background-height:100%;background-width:100%;height:20px;width:11px;z-index:999;background-repeat:no-repeat}.mapjs-reorder-bounds[mapjs-edge=left]{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgd2lkdGg9IjExcHgiIGhlaWdodD0iMjAuNjM0cHgiPg0KICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTEsIDApIHNjYWxlKC0xLCAxKSI+DQo8cGF0aCBkPSJNMC4xOTksMTAuNzlsOS42NDYsOS42NDZjMC4xMywwLjEzMywwLjMwMiwwLjE5NywwLjQ3MSwwLjE5N2MwLjE3MywwLDAuMzQ0LTAuMDY0LDAuNDczLTAuMTk3bDAuMDEzLTAuMDExDQoJQzEwLjkyOSwyMC4zMDUsMTEsMjAuMTMyLDExLDE5Ljk1MnYtNC42OGMwLTAuMTgyLTAuMDctMC4zNDctMC4xOTgtMC40NzVMNi4zMjMsMTAuMzJsNC40NzktNC40ODNDMTAuOTI5LDUuNzE0LDExLDUuNTQzLDExLDUuMzY0DQoJVjAuNjgyYzAtMC4xNzYtMC4wNy0wLjM0NS0wLjE5OC0wLjQ3NGwtMC4wMTMtMC4wMTJDMTAuNjY3LDAuMDcxLDEwLjQ5NywwLDEwLjMxNiwwQzEwLjE0MywwLDkuOTcsMC4wNzEsOS44NDUsMC4xOTZMMC4xOTksOS44NDUNCglDLTAuMDY2LDEwLjEwNC0wLjA2NiwxMC41MzIsMC4xOTksMTAuNzl6Ii8+DQogIDwvZz4NCjwvc3ZnPg==)}.mindmup_context_menu_item{cursor:pointer}.mindmup-legend-item-cont .gravatar,.mindmup-node-icon-avatar{height:24px;width:24px}.mindmup-legend-item-cont .avatar-container,.mindmup-node-icon-avatar .avatar-container{margin:0}.mindmup-legend-item-cont .gravatar:hover,.mindmup-node-icon-avatar .gravatar:hover{transform:scale(2);position:relative;z-index:5}#context-menu li:not(.folder)>ul{display:none}.controller-easy_wbs .easy-content-page{transform:none!important}.link-edit-widget{display:none;z-index:999;position:absolute;padding:10px;background-color:#fff;border:1px solid #dd3e3a;cursor:default}mindmup-sidebar{display:block}.date-picker__date-wrap .input-append{width:auto;float:right}.date-picker__date-wrap{margin-bottom:5px;overflow:hidden}easy-lookup .easy-lookup-values-wrapper{height:auto}#sidebar{z-index:3}.mindmup-sidebar__closed_button:before{font-family:"Material Icons",sans-serif}.mindmup-sidebar-comments .journal{margin:6px 0 0}.mindmup_hover_menu{-webkit-border-radius:.24rem;-moz-border-radius:.24rem;border-radius:.24rem}.mapjs-collapsor,.mindmup-legend-color-box{-webkit-border-radius:5000px!important;-moz-border-radius:5000px!important;border-radius:5000px!important}.easy .mindmup__menu-group--tooltiped li>.menu-children,.easy .mindmup__menu-group--tooltiped ul,.mapjs-node{-webkit-border-radius:.12rem;-moz-border-radius:.12rem;border-radius:.12rem}.mindmup-node-icon-milestone-shell{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.mindmup-node-icon-progress{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.mindmup__legend-body,.mindmup_hover_menu{-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.mindmup__menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.mindmup__legend-header,.mindmup__menu,.mindmup__node-control{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex}.mindmup__menu,.mindmup__node-control{-webkit-justify-content:space-between;-ms-justify-content:space-between;justify-content:space-between}.mindmup__legend-header>label,.mindmup__menu-group--sizing{-webkit-flex-grow:1;-ms-flex-grow:1;flex-grow:1}.easy .mindmup__menu-group--tooltiped li{font-family:Ubuntu,"Open Sans",sans-serif;font-size:15px;font-weight:400;color:#324164}@media only screen and (max-width:1400px){.easy .mindmup__menu-group--tooltiped li{font-size:12px}}@media only screen and (max-width:1400px) and (min-resolution:100dpi){.easy .mindmup__menu-group--tooltiped li{font-size:15px}}@-webkit-keyframes icon-blink{from{opacity:.25;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}to{opacity:1;-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-ms-transform:scale(1.25);-o-transform:scale(1.25);transform:scale(1.25)}}@-moz-keyframes icon-blink{from{opacity:.25;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}to{opacity:1;-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-ms-transform:scale(1.25);-o-transform:scale(1.25);transform:scale(1.25)}}@-ms-keyframes icon-blink{from{opacity:.25;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}to{opacity:1;-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-ms-transform:scale(1.25);-o-transform:scale(1.25);transform:scale(1.25)}}@keyframes icon-blink{from{opacity:.25;-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}to{opacity:1;-webkit-transform:scale(1.25);-moz-transform:scale(1.25);-ms-transform:scale(1.25);-o-transform:scale(1.25);transform:scale(1.25)}}@-webkit-keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-moz-keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-ms-keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-webkit-keyframes spin-inverse{from{transform:rotate(0)}to{transform:rotate(-360deg)}}@-moz-keyframes spin-inverse{from{transform:rotate(0)}to{transform:rotate(-360deg)}}@-ms-keyframes spin-inverse{from{transform:rotate(0)}to{transform:rotate(-360deg)}}@keyframes spin-inverse{from{transform:rotate(0)}to{transform:rotate(-360deg)}}@-webkit-keyframes translate-x{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-moz-keyframes translate-x{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-ms-keyframes translate-x{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@keyframes translate-x{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes translate-y{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-moz-keyframes translate-y{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-ms-keyframes translate-y{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@keyframes translate-y{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes translate-menu{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-moz-keyframes translate-menu{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-ms-keyframes translate-menu{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@keyframes translate-menu{100%{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes load7{0%,100%,80%{box-shadow:0 2.5em 0 -1.3em}40%{box-shadow:0 2.5em 0 0}}@-moz-keyframes load7{0%,100%,80%{box-shadow:0 2.5em 0 -1.3em}40%{box-shadow:0 2.5em 0 0}}@-ms-keyframes load7{0%,100%,80%{box-shadow:0 2.5em 0 -1.3em}40%{box-shadow:0 2.5em 0 0}}@keyframes load7{0%,100%,80%{box-shadow:0 2.5em 0 -1.3em}40%{box-shadow:0 2.5em 0 0}}@-webkit-keyframes ripple{0%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);opacity:.2}100%{-webkit-transform:scale(3);-moz-transform:scale(3);-ms-transform:scale(3);-o-transform:scale(3);transform:scale(3);opacity:0}}@-moz-keyframes ripple{0%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);opacity:.2}100%{-webkit-transform:scale(3);-moz-transform:scale(3);-ms-transform:scale(3);-o-transform:scale(3);transform:scale(3);opacity:0}}@-ms-keyframes ripple{0%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);opacity:.2}100%{-webkit-transform:scale(3);-moz-transform:scale(3);-ms-transform:scale(3);-o-transform:scale(3);transform:scale(3);opacity:0}}@keyframes ripple{0%{-webkit-transform:scale(0);-moz-transform:scale(0);-ms-transform:scale(0);-o-transform:scale(0);transform:scale(0);opacity:.2}100%{-webkit-transform:scale(3);-moz-transform:scale(3);-ms-transform:scale(3);-o-transform:scale(3);transform:scale(3);opacity:0}}.easy .mindmup__menu-group--tooltiped li a.submenu:after,.easy-mindmup__icon:before,.mapjs-collapsor,.mindmup__legend-toggler.active a:before{speak:none;font-weight:400;font-style:normal;display:inline-block;width:auto;height:auto;background-position:0 0;background-repeat:repeat;background-image:none;vertical-align:baseline;text-decoration:none!important;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga';font-family:'Material Icons',EasyIcons}.easy-mindmup__icon:before,.mapjs-collapsor,.material-icons{font-family:"Material Icons",sans-serif}.easy-mindmup__icon,.mindmup__legend-toggler.active a{position:relative;background-repeat:no-repeat;background-image:none!important}.mindmup-container{background-image:linear-gradient(rgba(217,217,217,.2) 1px,transparent 1px),linear-gradient(90deg,rgba(217,217,217,.2) 1px,transparent 1px);background-size:.8rem .8rem,.8rem .8rem;background-position:-1px -1px,-1px -1px}.easy .mindmup__menu-group--tooltiped li>.menu-children,.easy .mindmup__menu-group--tooltiped ul,.mindmup-sidebar__input__name .baseline,.mindmup__legend-container{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 1px rgba(0,0,0,.1);-moz-box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 1px rgba(0,0,0,.1);box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 1px rgba(0,0,0,.1)}.easy .mindmup__menu-group--tooltiped ul,.mapjs-node.activated,.mapjs-node.droppable,.mapjs-node.selected{-webkit-box-shadow:0 2px 4px rgba(0,0,0,.1),0 2px 2px rgba(0,0,0,.1);-moz-box-shadow:0 2px 4px rgba(0,0,0,.1),0 2px 2px rgba(0,0,0,.1);box-shadow:0 2px 4px rgba(0,0,0,.1),0 2px 2px rgba(0,0,0,.1)}@-webkit-keyframes sidebar-buttons-slide-left{from{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}to{-webkit-transform:translate(-12.2666666667rem,0);-moz-transform:translate(-12.2666666667rem,0);-ms-transform:translate(-12.2666666667rem,0);-o-transform:translate(-12.2666666667rem,0);transform:translate(-12.2666666667rem,0)}}@-moz-keyframes sidebar-buttons-slide-left{from{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}to{-webkit-transform:translate(-12.2666666667rem,0);-moz-transform:translate(-12.2666666667rem,0);-ms-transform:translate(-12.2666666667rem,0);-o-transform:translate(-12.2666666667rem,0);transform:translate(-12.2666666667rem,0)}}@-ms-keyframes sidebar-buttons-slide-left{from{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}to{-webkit-transform:translate(-12.2666666667rem,0);-moz-transform:translate(-12.2666666667rem,0);-ms-transform:translate(-12.2666666667rem,0);-o-transform:translate(-12.2666666667rem,0);transform:translate(-12.2666666667rem,0)}}@keyframes sidebar-buttons-slide-left{from{-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}to{-webkit-transform:translate(-12.2666666667rem,0);-moz-transform:translate(-12.2666666667rem,0);-ms-transform:translate(-12.2666666667rem,0);-o-transform:translate(-12.2666666667rem,0);transform:translate(-12.2666666667rem,0)}}.easy-mindmup__icon.button:before{position:absolute;left:0;width:2.4rem;text-align:center;font-size:1.25em;line-height:1;color:inherit;top:50%;margin-top:-.5em}.easy .mindmup__menu-group--tooltiped li a.submenu:after{margin:0;padding:0;display:block;background:0 0;border:none;min-width:1.6rem;position:absolute;top:0;right:0;bottom:0}.easy .mindmup__menu-group--tooltiped li a.submenu:after span{display:none}.easy .mindmup__menu-group--tooltiped li>.menu-children,.easy .mindmup__menu-group--tooltiped ul{position:absolute;background-color:#fff;color:#324164;padding:.5em;font-size:.89em;line-height:1;margin-top:-.25em;white-space:pre}.easy .mindmup__menu-group--tooltiped li>.menu-children a,.easy .mindmup__menu-group--tooltiped ul a{color:#324164}.easy .mindmup__menu-group--tooltiped .input-append li>.menu-children,.easy .mindmup__menu-group--tooltiped .input-append ul,.input-append .easy .mindmup__menu-group--tooltiped li>.menu-children,.input-append .easy .mindmup__menu-group--tooltiped ul{font-weight:400;margin-top:1px}.easy .mindmup__menu-group--tooltiped li{padding:0 0 0 2.4rem;border:1px solid transparent;border-left:none;border-right:none;position:relative;line-height:1.25}.easy .mindmup__menu-group--tooltiped li:hover{border-color:#f0f8ff!important;background:#f0f8ff!important;z-index:1}.easy .mindmup__menu-group--tooltiped li a{-ms-word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto;padding:.4rem .8rem .4rem 3.2rem;margin-left:-2.4rem;display:block;text-decoration:none}.easy .mindmup__menu-group--tooltiped li a.active{color:#fe7d99;background:0 0;border:none}.easy .mindmup__menu-group--tooltiped li a.active:before{color:#fe7d99}.easy .mindmup__menu-group--tooltiped li a:before{position:absolute;left:0;width:2.4rem;text-align:center;padding-top:1px;color:#324164}.easy .mindmup__menu-group--tooltiped li a.submenu{padding-right:0!important;background:0 0!important;position:relative}.easy .mindmup__menu-group--tooltiped li a.submenu:after{content:"\005d"!important;left:auto;font-size:.6666666667rem;text-align:left;line-height:1.9}.easy .mindmup__menu-group--tooltiped li a~span{font-size:.89em}.easy .mindmup__menu-group--tooltiped{position:relative}.easy .mindmup__menu-group--tooltiped ul{z-index:1500;margin:0;list-style:none;font-size:1.125em;min-width:200px;padding:.4rem 0;white-space:normal!important}.easy .mindmup__menu-group--tooltiped ul:after{content:'';position:absolute;top:0;bottom:0;right:auto;left:2.4rem;border-left:1px solid #e5e5e5;z-index:0}.easy .mindmup__menu-group--tooltiped li>.menu-children{display:none;white-space:normal;top:6px;left:99%;width:150px}.easy .mindmup__menu-group--tooltiped li>.menu-children>li+li{border-top:1px dashed #e5e5e5}.easy .mindmup__menu-group--tooltiped li>.menu-children>li a{text-decoration:none;display:block;padding:.5em}.easy .mindmup__menu-group--tooltiped li:hover>.menu-children,.mindmup__menu-item{display:inline-block}.easy .mindmup__menu-group--tooltiped li>.menu-children>li a:hover{text-decoration:underline}.easy .mindmup__menu-group--tooltiped li>.menu-children>li a:before{color:#01c8a9;text-decoration:none;margin-right:.4rem}.mindmup-cont{margin:0 -1.2rem .4rem;width:auto;overflow:hidden}@media only screen and (max-width:32rem){.mindmup-cont{margin-top:-1.6rem}}.mindmup-container{position:relative;cursor:all-scroll;border-top:none;box-sizing:border-box;background-color:#f9f9f9;margin:0;padding:0;outline:0;overflow-y:hidden!important}.mindmup__menu{user-select:none;position:relative;z-index:1;background-color:#fff;border-bottom:1px solid #e5e5e5;padding:1.6rem}.mindmup__menu_addons{position:absolute;right:0;top:5.6rem;z-index:5}.mindmup__menu-item{text-align:left}.mindmup__menu-item a.active,.mindmup__menu-item a.active:before{color:#d94838!important}@media only screen and (max-width:960px){.mindmup__menu-item>a{padding-right:0}.mindmup__menu-item>a>span{display:none}}@media only screen and (max-width:1400px){.mindmup__menu-item>a.easy-mindmup__icon--display,.mindmup__menu-item>a.easy-mindmup__icon--settings{padding-right:0}.mindmup__menu-item>a.easy-mindmup__icon--display>span,.mindmup__menu-item>a.easy-mindmup__icon--settings>span{display:none}}.mindmup__menu .right-menu{float:right}.mindmup__menu-group ul{margin:0}.mindmup__menu-group--tooltiped>ul{display:none}.mindmup__menu-group--tooltiped:hover>ul{display:block}.mindmup__menu-group--sizing{text-align:center;font-size:1.5em;position:absolute;top:1.6rem;left:-7.6rem;line-height:1.6rem}@media only screen and (max-width:32rem){.mindmup__menu-group--sizing{display:none}}.mindmup__menu-group--sizing a{color:rgba(50,65,100,.5);text-decoration:none}.mindmup__menu-group--sizing li{list-style:none;display:inline-block}@media only screen and (min-width:33rem){.mindmup__menu-group-display{margin-left:18.1333333333rem}}.mindmup__legend-container--hidden+.mindmup__menu-group-display{margin-left:0}.mindmup_hover_menu{display:block;position:absolute;z-index:99;background-color:#fff;min-width:160px;padding:5px 0;margin:2px 0 0;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.mindmup-reload-modal-errors li{color:red}.mindmup-last-modal-diffs li{color:#fa9b3c}.mindmup__button--disabled{opacity:.4}.mindmup_modal__flash_close{position:absolute;right:.8rem;font-size:1.6rem;line-height:1.6rem}.easy-mindmup__icon--add:before,.easy-mindmup__icon--add_sibling:before{content:"\e3ba"}.easy-mindmup__icon--insert_between:before{content:"\e3bb"}.easy-mindmup__icon--remove:before{content:"\e92b"}.easy-mindmup__icon--rename:before{content:"\e22b"}.easy-mindmup__icon--edit_data:before{content:"\e880"}.easy-mindmup__icon--edit:before{content:"\e3c9"}.easy-mindmup__icon--follow_url:before{content:"\e0e2"}.easy-mindmup__icon--zoom_in:before{content:"\e8ff"}.easy-mindmup__icon--zoom_out:before{content:"\e900"}.easy-mindmup__icon--refresh_view:before{content:"\e881"}.easy-mindmup__icon--filter:before{content:"\e152"}.easy-mindmup__icon--cancel:before,.easy-mindmup__icon--cancel_filter:before{content:"\e5c9"}.easy-mindmup__icon--close:before{content:"\e5cd"}.easy-mindmup__icon--display:before{content:"\e417"}.easy-mindmup__icon--links:before{content:"\e157"}.easy-mindmup__icon--icons:before{content:"\e24e"}.easy-mindmup__icon--collapse:before{content:"\e909"}.easy-mindmup__icon--expand:before{content:"\e146"}.easy-mindmup__icon--one_side:before{content:"\e86d"}.easy-mindmup__icon--settings:before{content:"\e8b8"}.easy-mindmup__icon--undo:before{content:"\e166"}.easy-mindmup__icon--redo:before{content:"\e15a"}.easy-mindmup__icon--print:before{content:"\e16b"}.easy-mindmup__icon--cut:before{content:"\e14e"}.easy-mindmup__icon--copy:before{content:"\e14d"}.easy-mindmup__icon--paste:before{content:"\e14f"}.easy-mindmup__icon--save:before{content:"\e161"}.easy-mindmup__icon--legend:before{content:"\e5d2"}.easy-mindmup__icon--legend_hide:before{content:"\e06d"}.easy-mindmup__icon.button{padding-left:2.4rem}.material-icons{font-style:normal}.redmine .button.easy-mindmup__icon:before,.redmine.mindmup__context_menu .easy-mindmup__icon:before{position:absolute;left:0;text-align:center;font-size:1.2em;line-height:1;color:inherit;top:50%;margin-top:-.5em}.redmine .mindmup-cont{margin:0 -10px -10px}.redmine .mindmup-menu{padding:5px 1px 0}.redmine .mindmup-menu .menu-item{padding:8px 10px}.redmine .mindmup__menu{background-color:#FFF;padding:14px}.redmine .mindmup__menu-group--tooltiped>a{padding-top:10px;padding-bottom:10px}.redmine .mindmup__menu-group--tooltiped>ul{position:absolute;background-color:#fff;border:1px solid #dfccaf;color:#42321a;padding:5px 5px 5px 0;margin-top:10px;margin-left:-10px;min-width:150px}.redmine .mindmup__menu-group--tooltiped>ul li{display:block;padding:3px}.redmine .mindmup__menu-group--tooltiped>ul li a{display:block;padding-left:20px}.mindmup__legend-container--hidden,.mindmup__legend-toggler .tip,.redmine .mindmup__menu-group--tooltiped:after{display:none}.redmine .mindmup__menu-group--tooltiped>ul li a:before{position:absolute;left:0;width:20px}.redmine .mindmup__menu-group--tooltiped .icon{background-image:none}.redmine .mindmup__menu-group--sizing a{padding:0}.redmine .mindmup__menu-item{padding-left:15px;padding-right:15px}.redmine .mindmup__menu-save a{padding-left:30px!important}.redmine .mindmup__menu-save a:before{text-align:center;width:36px;position:absolute}.redmine .mindmup__menu_addons{top:25px}.redmine .mindmup__menu_addons ul{padding:0}.redmine .mindmup-legend{margin-top:1px}.redmine .mindmup-legend__filter_cancel_icon{position:absolute}.redmine .mindmup__legend-container{top:0}.redmine .mindmup__legend-toggler{height:25px}.redmine .mindmup__legend-toggler a{line-height:1.5em}.redmine .mindmup__legend-header{padding:8px}.redmine a.button-positive{background-color:#4ebf67;border-radius:2px;border:1.3px solid #3aa051;color:#fff;padding:8px 16px}.redmine .menu-item.active{background-color:#9DB9D5}.redmine .menu-item.active a.button{color:#fff}.redmine .gravatar{max-width:100%;height:auto;border-radius:5000px;overflow:hidden;box-sizing:border-box}.redmine .button-2.active{background-color:transparent!important}.redmine .mindmup_modal__flash_close{color:#800;right:15px;line-height:18px}.mindmup-progress-modal{position:fixed;z-index:10000;width:40%;height:150px;top:150px;left:30%;right:30%;padding-top:30px;background-color:#fff;border:1px solid #d9d9d9;box-shadow:6px 6px 42px 7px rgba(217,217,217,.65)}.mindmup-progress-modal h3{text-align:center}.mindmup-progress-cont{height:8px;width:80%;margin-left:10%;margin-right:10%;border:1px solid #d9d9d9}.mindmup-progress-bar{width:50%;height:100%;background-color:green}.mindmup__legend-container{box-shadow:0 1px 4px rgba(0,0,0,.14),0 2px 2px rgba(0,0,0,.05);background-color:#fff;position:absolute;left:.8rem;top:.8rem;width:17.3333333333rem}@media only screen and (max-width:32rem){.mindmup__legend-container{top:110%}}.mindmup__legend-header{background:#f9f9f9;padding:.8rem}.mindmup__legend-toggler a{color:rgba(50,65,100,.5);font-size:1.5em;line-height:2.4rem}.mindmup__legend-toggler.active a:before{content:"\e5ce"!important}.mindmup__legend-body{overflow-y:auto;padding:.8rem 1.6rem;overflow-x:hidden}.mindmup-legend-color-box{background-color:#E0E0E0;border-color:#E0E0E0;width:1.6rem;height:1.6rem;display:inline-block;border-width:1px;border-style:solid;vertical-align:middle;margin-right:5px}.mindmup-legend-used{margin-top:10px;display:inline-block;color:rgba(66,50,26,.5)}.mindmup-legend-used-toggle{float:right;margin:5px 0}.mindmup-legend-used-all--used,.mindmup-legend-used-used--all{display:none}.mindmup-legend-item-cont{cursor:pointer;margin-top:.4rem}.mindmup-legend-item-cont .avatar-container{float:none}.mindmup-legend .hotkey_link{margin:.8rem -.8rem 0;padding-top:.8rem;border-top:1px solid #e5e5e5}.easy .mindmup-legend .hotkey_link a{color:rgba(50,65,100,.5)}.mindmup-legend__filter_cont{position:absolute;right:.8rem}.mindmup-legend__filter_icon{color:rgba(50,65,100,.4);font-size:2em;padding:5px}.mindmup-legend__filter_cancel_icon{position:absolute;display:none;color:#f33;font-size:2em;padding:0 5px 5px}.mindmup-legend__filter_cont:hover .mindmup-legend__filter_cancel_icon{display:inline}.mindmup-legend-drag-overlay{position:fixed;top:0;left:0;z-index:10000;cursor:move}.mindmup-legend-drag-avatar{position:absolute;z-index:5;height:20px;width:20px;margin-left:-12px;margin-top:-12px;border-width:1px;border-style:solid}.mapjs-connector{stroke:#ccc}.mapjs-draw-container{pointer-events:none;overflow:visible}.mapjs-link-hit{pointer-events:all;fill:none}.mapjs-exclamation{right:-.9em;position:absolute;top:-.9em;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAIWUlEQVR42p2XCWzT1x3Hv3/fTmI7jhPHuUpuxznpAgmES4GGjUFa1LWdWNWBRlXGpHZbN7Wr6DRNazWoqhY2rUDXlkOjbAgqVo5xqEk3Etqy0hyE3OQgd5zDsZP49n+/97e92mlMaZ/0Evkdv/d5v+v9/hzusx1amqXOUMielvD8dhH4YvCBCS7wh+Pg47hmL7hjvQ7nO7sauq33I5f7uvmTy4yZejF3ViaTFiY9WARNWgpiUw0QiSTgXW74PG7wHg/c9nlYBkZgHTNjoncAHq+3Zczt3brtv+09JIf/NgDiqytMbykk4mdyq78HfUkh4PXSsIhNCTcGT3J9fOA/zc3NwmWdgcsyhfHefgy3dcPp499+qL7lZ7TJ+00ApDXlef36vOykgscfofNkdK4UrvYWzLW3wn63Fx7LdED1EAAkGi2UaUsQk50HaXomnKOkjb4eDHbcwYzFNlJZd2sJrXTfD4D0o+XGPtPDG5OTViynm4ng6b+L8Q9PwWebBSRicCLqXPhWniB4n0/QhDg6BvqHNkMcG4upW80Y7x/A6Ih5uLK+JX0hBPeVw8vy7pqqqwxJ5ctIqhTWKxdgvfkZRDKZcDCCBzO1h3piyDjP+8C7PVAXlUBVthqTn32K8bsDGJuYHq2sa3kgFCIUQHyxPO9wmjF7Z+FTT5DFJLBcOIv5thZwUmng8MBKrwc+pxNwOf2/ZXKI5HKSIAlAwA9BzhmdlQvNyjUYr61F/8AQJucd7266fntX0CeCIrl9Bel5K+NiWte8+HNSsxK2miuYbfyCbi4l+4u/xCQ18w471LteQNRjO4Sh+dNHYT38GjiFktaKwtb6CEKVl4+YnAL0Xb6EQYsNn0zN5r/Q0tPOUIMAsqtleY3FP9hs0hcXwzs4DPOZk+DkAbWHNrKxjzzdUNMZNjy6PhcitSYclp3g82sisfK7mB03o7+hEeN2d9uGuualNO0SAB5N1iX/Mit5aPWeX9EOCSaOHAJPKuYk4q+6CYWib2YK+pqusOHx9TkQaeLIDAuAGQTtYSbSVW1B35nTGJx1Yn/PcMqZoYlhJl18alnuS9+pWPaHrI3r4ekbxPTl84LThakzFMA6jYSrbWHD5ioTaUC7KIDgmOQ32uWrMN3Vjf6uO+jx8L994tPWPzIA5aUy483Sx6tN8aZ8zH1cC0dPNzmeZPEoDZgg/uwNgNmcNfKJia1li5ogVAvK5DSINfHouHoFY1JF28ZrzaXsBG1tuXFq9S+egUSlg+Xvf6MU61r8JkHHmrUi9s8nIc4w+pXS2wHLs9sgilEvrrXAPhH5lKZ8HVrf+SsmKHGt+7gxjgEk1JYZx9c+v5sEUOI48japX35PQTylXNWrByEpLBWGPC03YduzGxwloIj7gmbYsBmt+9/EhN7AAPQMQE8AY2uefZoyVzymjx/xxzTHRQagh0f55C7IH/2xMOT84DjsJw6DU0bdG4DMF7uuCrdffwOTKSkMINEPUG4cq9i1AzJdIqwnj4OT3QOAJRiHA/Infwr5w9v8AB+ehPPEIcoDCtoXGYDBq1ZXonnfa5hJz8Da2oYgQN5Yxc4fQaZPge3U++SAsnvehGVAUW4+on73J2Fo/vfPwdfZKmTEyOB+gJiKtWjeuw8zGZkhACtMY8u3bUV0WgbmLv2LjOqODMCa2w2xqRiKF/cKPx37fgNvWzO9JNLIewiAk0gRlV+E5v0HwjSQcKHcVF+ytiwnuWgpPJ0dcI8MRY6CgB8wLfCBt0Aw2b0cN2A6mTYeLg+PtkuXYI5L6NpU17xKCMO/FGe9XFlifD5zZRlkJNtBb4DwsERSJytMXA5/uAoAZDKZ4muhlRlZGLr+CYaGh/GFT/rG7obOV4REVKCOMh0syb65dFMlVBm5cNZfo/wdwQx0E1CalvxwJyQbtwpDnitn4fnHu4AQPYvt4amekUJGL2PrWwcxaUjC7obu0tvWuTYhFVM3XFyR9x9jkSkzpbCQ5Cjgamzw32ihFpj6KQ/Ijl4IG3bt2AxEygNM/elZmGxqQl9TM8wabc+multraWY0KD32+wbt6pdyUs8Zl5cgvqAIGBmBh7ogMBRCALBB+t75cL/8yRYCUC0A8NeLEq0OPjJpy9FjsD6wBHs7Bqovjk7V0QLL/59j6knHS3MPFBp0jywxZiGupBS+3h74pqb8AKGVkNMB0WPbIdqwxc/00Xn4Th8jEyjCYWmtWK0Gp4tH+3tHME1vxx2I//nUjTYqOjCC4HMM/6tD+kPGlYqCmtRkvS4hyYD4BynVjgzDazZ/CRGIZ7hdQjgKjYVfMHeEgIpj6XXUaNB14n3MON2waGMnq/7dtJ5me6nPIqQgQcAXdNQzL1cUnEvVx8XrqKjUl5b6C9/BQeFFC71dWAvREke+w+kT4LXNoevMB5glx5xWx05svNZUTdPsO2ESC0qyYGOZJJ5p4vKqgnNapSIuIUqOuFwj1NnZgN1B3DZKfXbSgid8OwtbBUVBVLTgvJOff47B+uuw6xNhkyunAoezm08gQlG6EGLJ0VLjy1nR8s0amQQqqo6iDYlQpaRBYTBQ3Sjxa4F1dnMKzXmqfKe7OjHT2Q07Pb3zcTr02p0Xtt9of4Xk9S88PBJAEELLwrMyIbbw1zmpr6rFonQl74WU8oOEnBBOl1B6e5kfMAiyv1uphJceJKdKDZuX73u94+6eWrOlhYUb9Wnc54dJqE9EBfwiIVUpT3wuJ6U6XxVdpZKKM0JFBP3O6vL2ttnmrh7oGjw3aHeO0ZA5YO95fMNPs9B5aQCEjAsqeUB1F1gttjDjUGiAnAMzjIX6XOBgdutv9XG6mEZYvqBgx2IFIzuEeSbZB65IN17Y/gfqSW4KhD7b/AAAAABJRU5ErkJggg==);width:2em;height:2em;background-size:2em;background-repeat:no-repeat no-repeat}.mapjs-link{stroke:#4ebf67!important}.mapjs-link[stroke-dasharray*='8']{stroke:#628DB6!important}.mapjs-arrow{fill:#4ebf67!important}.mapjs-node{border:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;background-color:#e5e5e5;color:#324164;margin:0;padding:0}.mapjs-node.activated{background-color:#ccc;border:1px solid transparent}.mapjs-node.activated,.mapjs-node.droppable,.mapjs-node.selected{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,.3);-moz-box-shadow:0 3px 3px 0 rgba(0,0,0,.3);box-shadow:0 3px 3px 0 rgba(0,0,0,.3);margin-left:-2px}.mapjs-node.droppable{border:1px solid #fa9b3c}.mapjs-node span{margin:.6rem 1.2rem;border:1px solid transparent}.mapjs-collapsor{position:absolute;left:auto;top:50%;right:-.9333333333rem;margin-top:-.8rem;background-color:#fff;color:red;line-height:1;font-size:1.6rem;font-weight:400}.mapjs-collapsor:before{content:""}.collapsed .mapjs-collapsor{color:#4ebf67}.collapsed .mapjs-collapsor:before{content:""}.mindmup-node-left .mapjs-collapsor{right:auto;left:-.9333333333rem}.mapjs-node:hover .mapjs-collapsor{font-size:2rem;margin-top:-.9333333333rem;right:-1.1333333333rem}.mindmup-node-left:hover .mapjs-collapsor{left:-1.1333333333rem;right:auto}.mindmup__node--filtered_out{opacity:.2}.mindmup-node-icon{height:1.3333333333rem;display:inline-block;margin-right:.1333333333rem;vertical-align:middle}.mindmup-node-icons{position:absolute;left:-.6666666667rem;top:-1.2rem;white-space:nowrap}.mindmup-node-icons-all{display:inline-block}.mindmup-node-icon-progress{width:.2666666667rem;background-color:#d9d9d9;margin:0 .8rem}.mindmup-node-icon-progress-bar{background-color:#4ebf67;position:relative}.mindmup-node-icon-milestone-shell{vertical-align:middle;display:inline-block}.mindmup-node-icon-milestone-diamond{border-width:1px;border-style:solid;width:8px;height:8px}.mindmup-node-icon-status{font-weight:400;opacity:.5;max-width:2rem;overflow:hidden;font-size:.89em}.mindmup-node-icon-avatar .gravatar{vertical-align:sub}.mindmup-node-icon-is_edited{margin-right:1px;margin-left:-10px;vertical-align:middle;border-radius:5000px;width:1.3333333333rem;height:1.3333333333rem;color:#fff;background-color:#e50026;line-height:1.2;font-size:1.2rem;text-align:center;display:inline-block}.mindmup-node--renaming .mindmup__node-control,.mindmup__print-area .mindmup-node-add-button{display:none}.mindmup-node-icon-is_edited:before{content:"!";font-family:inherit}.mindmup-node-filtered{opacity:.2}.mindmup-node-avatar{position:absolute;top:50%;margin-top:-.7333333333rem}.mindmup-node-left .mindmup-node-avatar{left:auto;right:-2.0666666667rem}.mindmup__node-control{color:#628DB6;background-color:transparent;position:absolute;bottom:100%;height:1rem;width:100%;line-height:1rem;margin-bottom:-.3333333333rem;-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,.1);-moz-box-shadow:0 3px 3px 0 rgba(0,0,0,.1);box-shadow:0 3px 3px 0 rgba(0,0,0,.1)}.mindmup__node-control .easy-mindmup__icon{width:50%;text-align:center;background-color:#f2f2f2}.mindmup__node-control .easy-mindmup__icon--edit{border-right:solid 1px #e5e5e5}.mindmup__node-control .easy-mindmup__icon--add{border-left:solid 1px #e5e5e5}.mindmup__node-control .easy-mindmup__icon:hover{background-color:#e5e5e5}.mindmup-scheme-project{background-color:#628DB6;color:#fff}.mindmup-scheme-project.activated{background-color:#49739c}.scheme-by-priority .scheme-priority-1{background-color:#fad1d1}.scheme-by-priority .scheme-priority-1.activated,.scheme-by-priority .scheme-priority-2{background-color:#f5a3a3}.scheme-by-priority .scheme-priority-2.activated{background-color:#f07575}.scheme-by-priority .scheme-priority-3{background-color:#d1fada}.scheme-by-priority .scheme-priority-3.activated,.scheme-by-priority .scheme-priority-4{background-color:#a3f5b5}.scheme-by-priority .scheme-priority-4.activated{background-color:#75f090}.scheme-by-priority .scheme-priority-5{background-color:#d1e6fa}.scheme-by-priority .scheme-priority-5.activated,.scheme-by-priority .scheme-priority-6{background-color:#a3cdf5}.scheme-by-priority .scheme-priority-6.activated{background-color:#75b4f0}.scheme-by-priority .scheme-priority-7{background-color:#fae6d1}.scheme-by-priority .scheme-priority-7.activated,.scheme-by-priority .scheme-priority-8{background-color:#f5cca3}.scheme-by-priority .scheme-priority-8.activated{background-color:#f0b375}.scheme-by-priority .scheme-priority-9{background-color:#add7f3}.scheme-by-priority .scheme-priority-9.activated{background-color:#81c1ec}.scheme-by-priority .scheme-priority-10{background-color:#e5e5e5}.scheme-by-priority .scheme-priority-10.activated{background-color:#ccc}.scheme-by-priority .scheme-priority-11{background-color:#fcc}.scheme-by-priority .scheme-priority-11.activated{background-color:#f99}.scheme-by-priority .scheme-priority-12{background-color:#ffb4b4}.scheme-by-priority .scheme-priority-12.activated{background-color:#ff8181}.scheme-by-status .scheme-status-1{background-color:#f9f6a8}.scheme-by-status .scheme-status-1.activated{background-color:#f6f178}.scheme-by-status .scheme-status-2{background-color:#fbe3bd}.scheme-by-status .scheme-status-2.activated{background-color:#f8cf8d}.scheme-by-status .scheme-status-3{background-color:#f3cccf}.scheme-by-status .scheme-status-3.activated{background-color:#e9a3a8}.scheme-by-status .scheme-status-4{background-color:#eabdec}.scheme-by-status .scheme-status-4.activated{background-color:#dd95e1}.scheme-by-status .scheme-status-5{background-color:#d0b8ec}.scheme-by-status .scheme-status-5.activated{background-color:#b590e1}.scheme-by-status .scheme-status-6{background-color:#c9c9f9}.scheme-by-status .scheme-status-6.activated{background-color:#9b9bf4}.scheme-by-status .scheme-status-7{background-color:#c9dcff}.scheme-by-status .scheme-status-7.activated{background-color:#96bbff}.scheme-by-status .scheme-status-8{background-color:#d2f4f0}.scheme-by-status .scheme-status-8.activated{background-color:#a9eae2}.scheme-by-status .scheme-status-9{background-color:#d2ecc9}.scheme-by-status .scheme-status-9.activated{background-color:#b3dfa3}.scheme-by-status .scheme-status-10{background-color:#e1ee9e}.scheme-by-status .scheme-status-10.activated{background-color:#d4e673}.scheme-by-status .scheme-status-11{background-color:#dadace}.scheme-by-status .scheme-status-11.activated{background-color:#c4c4b1}.scheme-by-status .scheme-status-12{background-color:#dbd1c7}.scheme-by-status .scheme-status-12.activated{background-color:#c7b8a8}.scheme-by-tracker .scheme-tracker-1{background-color:#f9f6a8}.scheme-by-tracker .scheme-tracker-1.activated{background-color:#f6f178}.scheme-by-tracker .scheme-tracker-2{background-color:#fbe3bd}.scheme-by-tracker .scheme-tracker-2.activated{background-color:#f8cf8d}.scheme-by-tracker .scheme-tracker-3{background-color:#f3cccf}.scheme-by-tracker .scheme-tracker-3.activated{background-color:#e9a3a8}.scheme-by-tracker .scheme-tracker-4{background-color:#eabdec}.scheme-by-tracker .scheme-tracker-4.activated{background-color:#dd95e1}.scheme-by-tracker .scheme-tracker-5{background-color:#d0b8ec}.scheme-by-tracker .scheme-tracker-5.activated{background-color:#b590e1}.scheme-by-tracker .scheme-tracker-6{background-color:#c9c9f9}.scheme-by-tracker .scheme-tracker-6.activated{background-color:#9b9bf4}.scheme-by-tracker .scheme-tracker-7{background-color:#c9dcff}.scheme-by-tracker .scheme-tracker-7.activated{background-color:#96bbff}.scheme-by-tracker .scheme-tracker-8{background-color:#d2f4f0}.scheme-by-tracker .scheme-tracker-8.activated{background-color:#a9eae2}.scheme-by-tracker .scheme-tracker-9{background-color:#d2ecc9}.scheme-by-tracker .scheme-tracker-9.activated{background-color:#b3dfa3}.scheme-by-tracker .scheme-tracker-10{background-color:#e1ee9e}.scheme-by-tracker .scheme-tracker-10.activated{background-color:#d4e673}.scheme-by-tracker .scheme-tracker-11{background-color:#dadace}.scheme-by-tracker .scheme-tracker-11.activated{background-color:#c4c4b1}.scheme-by-tracker .scheme-tracker-12{background-color:#dbd1c7}.scheme-by-tracker .scheme-tracker-12.activated{background-color:#c7b8a8}.scheme-by-assignee .scheme-assignee-1{background-color:#f9f6a8}.scheme-by-assignee .scheme-assignee-1.activated{background-color:#f6f178}.scheme-by-assignee .scheme-assignee-2{background-color:#fbe3bd}.scheme-by-assignee .scheme-assignee-2.activated{background-color:#f8cf8d}.scheme-by-assignee .scheme-assignee-3{background-color:#f3cccf}.scheme-by-assignee .scheme-assignee-3.activated{background-color:#e9a3a8}.scheme-by-assignee .scheme-assignee-4{background-color:#eabdec}.scheme-by-assignee .scheme-assignee-4.activated{background-color:#dd95e1}.scheme-by-assignee .scheme-assignee-5{background-color:#d0b8ec}.scheme-by-assignee .scheme-assignee-5.activated{background-color:#b590e1}.scheme-by-assignee .scheme-assignee-6{background-color:#c9c9f9}.scheme-by-assignee .scheme-assignee-6.activated{background-color:#9b9bf4}.scheme-by-assignee .scheme-assignee-7{background-color:#c9dcff}.scheme-by-assignee .scheme-assignee-7.activated{background-color:#96bbff}.scheme-by-assignee .scheme-assignee-8{background-color:#d2f4f0}.scheme-by-assignee .scheme-assignee-8.activated{background-color:#a9eae2}.scheme-by-assignee .scheme-assignee-9{background-color:#d2ecc9}.scheme-by-assignee .scheme-assignee-9.activated{background-color:#b3dfa3}.scheme-by-assignee .scheme-assignee-10{background-color:#e1ee9e}.scheme-by-assignee .scheme-assignee-10.activated{background-color:#d4e673}.scheme-by-assignee .scheme-assignee-11{background-color:#dadace}.scheme-by-assignee .scheme-assignee-11.activated{background-color:#c4c4b1}.scheme-by-assignee .scheme-assignee-12{background-color:#dbd1c7}.scheme-by-assignee .scheme-assignee-12.activated{background-color:#c7b8a8}.scheme-by-milestone .scheme-milestone-1{background-color:#f9f6a8}.scheme-by-milestone .scheme-milestone-1.activated{background-color:#f6f178}.scheme-by-milestone .scheme-milestone-2{background-color:#fbe3bd}.scheme-by-milestone .scheme-milestone-2.activated{background-color:#f8cf8d}.scheme-by-milestone .scheme-milestone-3{background-color:#f3cccf}.scheme-by-milestone .scheme-milestone-3.activated{background-color:#e9a3a8}.scheme-by-milestone .scheme-milestone-4{background-color:#eabdec}.scheme-by-milestone .scheme-milestone-4.activated{background-color:#dd95e1}.scheme-by-milestone .scheme-milestone-5{background-color:#d0b8ec}.scheme-by-milestone .scheme-milestone-5.activated{background-color:#b590e1}.scheme-by-milestone .scheme-milestone-6{background-color:#c9c9f9}.scheme-by-milestone .scheme-milestone-6.activated{background-color:#9b9bf4}.scheme-by-milestone .scheme-milestone-7{background-color:#c9dcff}.scheme-by-milestone .scheme-milestone-7.activated{background-color:#96bbff}.scheme-by-milestone .scheme-milestone-8{background-color:#d2f4f0}.scheme-by-milestone .scheme-milestone-8.activated{background-color:#a9eae2}.scheme-by-milestone .scheme-milestone-9{background-color:#d2ecc9}.scheme-by-milestone .scheme-milestone-9.activated{background-color:#b3dfa3}.scheme-by-milestone .scheme-milestone-10{background-color:#e1ee9e}.scheme-by-milestone .scheme-milestone-10.activated{background-color:#d4e673}.scheme-by-milestone .scheme-milestone-11{background-color:#dadace}.scheme-by-milestone .scheme-milestone-11.activated{background-color:#c4c4b1}.scheme-by-milestone .scheme-milestone-12{background-color:#dbd1c7}.scheme-by-milestone .scheme-milestone-12.activated{background-color:#c7b8a8}.scheme-by-progress .scheme-progress-1{background-color:#e5e5e5}.scheme-by-progress .scheme-progress-1.activated{background-color:#ccc}.scheme-by-progress .scheme-progress-2{background-color:#edf9f0}.scheme-by-progress .scheme-progress-2.activated{background-color:#c7ecd0}.scheme-by-progress .scheme-progress-3{background-color:#d3efd9}.scheme-by-progress .scheme-progress-3.activated{background-color:#aee1b9}.scheme-by-progress .scheme-progress-4{background-color:#afe2bb}.scheme-by-progress .scheme-progress-4.activated{background-color:#8ad49b}.scheme-by-progress .scheme-progress-5{background-color:#83d295}.scheme-by-progress .scheme-progress-5.activated{background-color:#5ec475}.scheme-by-progress .scheme-progress-6{background-color:#4ebf67}.scheme-by-progress .scheme-progress-6.activated{background-color:#3aa051}@media print{.gravatar{max-width:100%;height:auto;-webkit-border-radius:5000px;-moz-border-radius:5000px;border-radius:5000px}}.mindmup__print-area--compact{position:relative}.mindmup__print-area--stripped{border-left:2px solid #e6e6e6}.mindmup__print-area .mapjs-node{box-sizing:border-box}.mindmup__print-strip{position:relative;border:1px solid #e6e6e6;overflow:hidden;white-space:nowrap;break-inside:avoid;margin-left:-1px;display:inline-block;background-color:#fff;border-left:0}.mindmup-sidebar .mindmup-sidebar__coworkers--no_id,.mindmup-sidebar .mindmup-sidebar__empty-title{background-color:#ffa;border:1px solid #bfb23f;padding:5px;text-align:center}.mindmup-sidebar__empty-title{display:block;margin-top:10px;box-sizing:border-box}.mindmup-sidebar__resize{position:fixed;width:10px;text-align:center;cursor:col-resize;display:block;z-index:101;background-color:silver;box-sizing:border-box;padding-top:8px;font-size:20px;border-radius:5px}.mindmup-sidebar__root{display:block}.mindmup-sidebar__toggler{position:absolute;top:1px;right:1px;padding:0;z-index:1}.mindmup-sidebar__toggler.active{background:#d94838;color:#fff}.mindmup-sidebar__toggler.easy-mindmup__icon{padding-left:15px}.mindmup-sidebar__toggler.easy-mindmup__icon:before{width:auto}.mindmup-sidebar__container{border-left:1px solid #dfccaf;background:#f9f9f9;padding-left:8px;overflow-y:scroll}.mindmup-sidebar__long-text{position:relative;max-height:100px;overflow:hidden;min-height:30px;border:1px solid #dfccaf}.mindmup-sidebar__long-text__curtain{height:100%;position:absolute;width:100%;top:0;left:1px;border-bottom:1px solid #dfccaf;background:-moz-linear-gradient(top,rgba(255,255,255,.01) 0,rgba(255,255,255,.01) 65%,#fff 100%);background:-webkit-linear-gradient(top,rgba(255,255,255,.01) 0,rgba(255,255,255,.01) 65%,#fff 100%);background:linear-gradient(to bottom,rgba(255,255,255,.01) 0,rgba(255,255,255,.01) 65%,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#03ffffff', endColorstr='#ffffff', GradientType=0)}.mindmup-sidebar__long-text__content{padding:3px;min-height:25px;width:100%;box-sizing:border-box;max-height:100px}.mindmup-sidebar__long-text__textarea{height:200px}.mindmup-sidebar__long-text--hidden{display:none}.mindmup-sidebar__attribute{display:block;overflow:hidden;margin-top:3px;position:relative}.mindmup-sidebar__attribute .invalid{border:1px solid red;color:red}.mindmup-sidebar__attribute-form-field{max-width:200px!important;padding:1px 1px 1px 5px!important;line-height:normal;float:right;overflow-x:hidden!important}.mindmup-sidebar__attribute-value{max-width:197px!important;width:100%;padding:1px 1px 1px 5px!important;line-height:normal;float:right;margin-top:6px}.mindmup-sidebar__attribute-label-disabled{color:rgba(0,0,0,.38)}.mindmup-sidebar__attribute-label{display:inline-block;width:100px;margin-top:5px}.mindmup-sidebar__attribute .spaceholder{display:none}.mindmup-sidebar__attribute .top-section{margin-bottom:0}.mindmup-sidebar__attribute-full-screen-icon{display:block;float:right;cursor:pointer;margin-top:6px;position:absolute;top:0;right:0}.mindmup-sidebar__attribute .icon-edit{position:absolute;top:0;right:0;cursor:pointer}.mindmup-sidebar__closed_button{width:200px}.mindmup-sidebar__add-comment-button{margin:3px 0 12px}.mindmup-sidebar__add-comment-button a{width:100%;box-sizing:border-box}.mindmup-sidebar__journal-avatar-container{margin-top:6px}.mindmup-sidebar__journal-header{margin-top:-6px}.mindmup-sidebar__journal-notes{margin-top:0;background:#fff;border:1px solid #dfccaf;margin-bottom:0!important;box-sizing:border-box;padding:3px}.mindmup-sidebar__journal-notes p:last-child{margin-bottom:0}.mindmup-sidebar__journal-full-screen-icon{display:block;float:right;cursor:pointer;margin-top:25px;position:absolute;top:0;right:20px}.mindmup-sidebar__journal-timestamp{font-weight:700;font-size:smaller}.mindmup-sidebar__tab{margin-left:1px;margin-right:1px}.mindmup-sidebar__tab header{min-height:36px!important;padding:0 12px 8px!important}.mindmup-sidebar__tab header.open{min-height:44px}.mindmup-sidebar__tab header:not(:hover){color:inherit!important;background-color:unset!important}.mindmup-sidebar__input__name{color:#000;font-family:"Open Sans",sans-serif;width:100%!important}.mindmup-sidebar__input__name .input{max-width:none;text-align:center;font-weight:700;opacity:1;color:#000!important}.mindmup-sidebar__input__name .bottom-section{display:none!important}.mindmup-sidebar__input__name .baseline{background-color:#fff}.mindmup-sidebar .gravatar{max-height:32px;max-width:32px}.mindmup-sidebar .content-wrapper{margin:0 12px 8px!important}.mindmup-sidebar .material-tab{padding:0!important}@font-face{font-family:"Material Icons";src:local("Material Icons"),local("MaterialIcons-Regular"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.eot?#iefix) format("embedded-opentype"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.woff) format("woff"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.woff2) format("woff2"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.ttf) format("truetype"),url(../../../easy_mindmup/fonts/EasyMaterialIcons-Regular.svg) format("svg");font-weight:400;font-style:normal}.mindmup-sidebar__attachments-thumbnails>img{display:none}.mindmup-sidebar__attachments-thumbnails{margin:0}
\ No newline at end of file
diff --git a/plugins/easy_wbs/config/locales/ar.yml b/plugins/easy_wbs/config/locales/ar.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b93c21a986b87d70b2dfbb219cc684c7721d9891
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/ar.yml
@@ -0,0 +1,2 @@
+---
+ar: 
diff --git a/plugins/easy_wbs/config/locales/cs.yml b/plugins/easy_wbs/config/locales/cs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b949c79269c747be5670063630f1df952c92586f
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/cs.yml
@@ -0,0 +1,170 @@
+---
+cs:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Ostatní akce
+    button_add_child: Přidat dítě
+    button_add_parent: Přidat rodiče
+    button_add_sibling: Přidat sourozence
+    button_all_icons: Ikony
+    button_collapse: Sbalit
+    button_collapse_all: Sbalit vše
+    button_cut: Vyjmout
+    button_display: Zobrazit nastavení
+    button_edit_data: Upravit údaje
+    button_expand: Rozbalit
+    button_expand_all: Rozbalit vše
+    button_expand_collapse: Rozbalit/sbalit
+    button_legend: Legenda
+    button_one_side: Jedna strana
+    button_paste: Vložit
+    button_project_menu: Easy WBS
+    button_redo: Vpřed
+    button_remove_node: Odstranit uzel
+    button_show_links: Zobrazit vazby
+    button_undo: Zpět
+    edit_issue: Upravit úkol
+    error_create: Nelze vytvořit
+    error_delete: Nelze smazat
+    error_update: Nelze aktualizovat
+    errors:
+      not_subtaskable: Úkol "%{task_name}" nemůže být podúkolem kvůli nastavení jeho
+        trackeru
+    free:
+      button_upgrade: Získat plnou verzi
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Obarvení uzlů podle vlastnosti
+      feature_context_menu: Změna vlastností uzlů
+      feature_filtering: Filtrování uzlů podle vlastnosti
+      header_not_available: K dispozici pouze v plné verzi
+      text_not_available: je k dispozici pouze v plné verzi Easy WBS.
+    hotkeys:
+      info_mac_metakey: Na Mac OS X klávesy Ctrl a Cmd mohou být použity pro zkratky
+        níže - některé prohlížeče mohou určitým klávesovým zkratkám bránit. Například
+        pokud Cmd + Space nefunguje v prohlížeči, zkuste Ctrl + Space.
+      keyboard:
+      - title: Manipulace s uzly
+        hotkeys:
+        - hotkey: Enter
+          info: Přidat sourozence
+        - hotkey: Shift+Enter
+          info: Přidat sourozence nad nebo konec řádku (při přejmenování uzlu)
+        - hotkey: Tab nebo Insert
+          info: Přidat dítě
+        - hotkey: Shift+Tab
+          info: Přidat rodiče
+        - hotkey: Mezerník
+          info: Přejmenovat uzel
+        - hotkey: Shift+mezerník
+          info: Upravit data uzlu
+        - hotkey: Backspace nebo Delete
+          info: Odstranit uzel
+        - hotkey: Ctrl+nahoru/dolů
+          info: Posunout uzel nahoru/dolů
+      - title: Úpravy
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Uložit
+        - hotkey: Ctrl+X nebo C
+          info: Vyjmout
+        - hotkey: Ctrl+C nebo Y
+          info: Kopírovat
+        - hotkey: Ctrl+V nebo P
+          info: Vložit
+        - hotkey: U nebo Ctrl+Z
+          info: Zpět
+        - hotkey: R nebo Ctrl+Y nebo Ctrl+Shift+Z
+          info: Vpřed
+      - title: Výběr
+        hotkeys:
+        - hotkey: Å ipky
+          info: Vyberte uzel nahoře/dole/nalevo/napravo od aktuálně vybraného
+        - hotkey: Shift + Å¡ipky
+          info: Přidat uzel nahoru/dolů/nalevo/napravo od aktuálně vybraného (užitečné
+            pro vícenásobný výběr sourozenců)
+        - hotkey: "{"
+          info: Vícenásobný výběr aktuálního uzlu a celého jeho podstromu
+        - hotkey: "["
+          info: Vícenásobný výběr pouze podstromu aktuálního uzlu (nikoli uzlu samého)
+        - hotkey: "="
+          info: Vícenásobný výběr všech sourozenců aktuálního uzlu (které mají stejného
+            rodiče)
+        - hotkey: "."
+          info: Zrušit vícenásobný výběr a znovu vybrat pouze aktuální uzel
+        - hotkey: 1 - 9
+          info: Vybrat všechny uzly na určité úrovni (např. 1 vybere všechny uzly
+            první úrovně)
+      - title: Navigace a obrazovka
+        hotkeys:
+        - hotkey: "/ nebo F"
+          info: Rozbalit nebo sbalit uzel (zobrazit nebo skrýt děti)
+        - hotkey: Ctrl + nebo Z
+          info: Přiblížit
+        - hotkey: Ctrl - nebo Shift Z
+          info: Oddálit
+        - hotkey: Esc, 0, Ctrl+0
+          info: Resetovat zobrazení mapy - vybrat kořenový uzel a umístit ho do středu
+            obrazovky
+      mouse:
+      - action: Přesunutí mapy
+        gesture: klikněte a přetáhněte středový uzel, klikněte a přetáhněte pozadí
+          nebo skrolujte na trackpadu/touchpadu.
+      - action: Vybrat uzel
+        gesture: klikněte nebo klepněte na něj
+      - action: Vybrat více uzlů
+        gesture: Shift+klik
+      - action: Změna pořadí uzlů
+        gesture: přetáhněte uzel mezi svými sourozenci, horizontálně blízko k místu
+          změny pořadí. Při změně pořadí se zobrazí bod s černou šipkou.
+      - action: Ručně umístit uzel
+        gesture: stačí přetáhnout uzel, dokud šipka pro změnu pořadí nezmizí. Chcete-li
+          vynutit ruční postavení, i když se šipka pro změnu pořadí ukazuje, podržte
+          klávesu Shift při tažení. Upozorňujeme, že děti z kořenového uzlu je možné
+          přetáhnout v libovolném směru, ale uzly nižší úrovně mohou být umístěny
+          pouze ve směru svého rodiče ke kořeni.
+      - action: Přejmenování uzlu
+        gesture: Dvojitý klik nebo dvojité ťuknutí
+      - action: Zobrazit kontextové menu s operacemi
+        gesture: 'Klikněte pravým tlačítkem na uzel (myší) nebo poklepejte na pozadí,
+          nebo dlouze stiskněte uzel: zobrazit (na dotykových zařízeních)'
+      - action: Změna rodičovského uzlu
+        gesture: přetáhněte uzel na jiný uzel (kruhové upuštění není povoleno, takže
+          nelze upustit uzel na jednom ze svých dětí nebo potomků)
+      - action: Otevřít úkol nebo projekt v samostatném okně
+        gesture: Alt+klik
+      title_key_shortcuts: Klávesové operace
+      title_mouse_shortcuts: Operace s myší a dotykem
+      title_shortcuts: Zkratky
+    info_all_saved: Vše úspěšně uloženo
+    info_any_failed: 'Některé z neúspěšných požadavků:'
+    info_no_permission: Nemáte potřebná oprávnění k této akci
+    label_color_by: Barva dle
+    label_go_to: Jít do
+    label_or: nebo
+    last_state_modal:
+      label_differencies: Rozdíly na serveru
+      message_changed: mají rozdílné atributy (browser => server) ({{changes}})
+      message_missing: nemá nadřazený úkol "{{from}}"
+      message_moved: je podúkolem "{{to}}", ne "{{from}}"
+      message_present: je přítomen jako podúkol "{{to}}"
+      text_reload_appeal: Přejete si znovu načíst stav ze serveru?
+      title: Poslední klientský stav WBS je odlišný od stavu serveru
+    reload_modal:
+      label_errors: Chyby
+      text_reload_appeal: Chcete ignorovat neuložené položky a znovu načíst data ze
+        serveru?
+      title: WBS se nepodařilo správně uložit
+    stored_modal:
+      text_load_appeal: Chcete jej načíst místo aktuálního stavu ze serveru?
+      title: Byl zjištěn neuložený stav
+    text_issue_not_saved: Úkol není uložen. Nejdříve jej uložte.
+    warning_delete_node: Opravdu chcete odstranit uzel {{name}} a všechny jeho potomky?
+    warning_not_saved: není správně uloženo
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Úkoly
+  label_filter_group_easy_wbs:
+    easy_issue_query: Úkoly
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/da.yml b/plugins/easy_wbs/config/locales/da.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d3dcd59d03737250a404acbf3357fe8585ca3c19
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/da.yml
@@ -0,0 +1,2 @@
+---
+da: 
diff --git a/plugins/easy_wbs/config/locales/de.yml b/plugins/easy_wbs/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c4b6d8b580a16c4be8e051c34ce89f1ed575cd83
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/de.yml
@@ -0,0 +1,181 @@
+---
+de:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Andere Aktionen
+    button_add_child: Untergeordneter Aufgabe hinzufügen
+    button_add_parent: Übergeordnete Aufgabe hinzufügen
+    button_add_sibling: Geschwister Aufgabe hinzufügen
+    button_all_icons: Icons
+    button_collapse: Zusammenklappen
+    button_collapse_all: Alle zusammenklappen
+    button_cut: Ausschneiden
+    button_display: Einstellung anzeigen
+    button_edit_data: Daten bearbeiten
+    button_expand: Aufklappen
+    button_expand_all: Alle aufklappen
+    button_expand_collapse: Aufklappen/Zusammenklappen
+    button_legend: Legende
+    button_one_side: Eine Seite
+    button_paste: Einfügen
+    button_project_menu: Easy WBS
+    button_redo: Wiederherstellen
+    button_remove_node: Knotenpunkt entfernen
+    button_show_links: Links anzeigen
+    button_undo: Rückgängig
+    edit_issue: Die Aufgabe bearbeiten
+    error_create: Konnte nicht erstellt werden
+    error_delete: Konnte nicht gelöscht werden
+    error_update: Konnte nicht aktualisiert werden
+    errors:
+      not_subtaskable: Die Aufgabe "%{task_name}" kann wegen der Einstellungen des
+        Trackers nicht die Teilaufgabe sein
+    free:
+      button_upgrade: Die Vollversion erwerben
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Die Knotenpunkte nach Merkmal färben
+      feature_context_menu: Die Eigenschaften der Knotenpunkte verändern
+      feature_filtering: Knotenpunkte nach Merkmal sortieren
+      header_not_available: Verfügbar nur in der Vollversion
+      text_not_available: verfügbar in der Vollversion der Easy WBS.
+    hotkeys:
+      info_mac_metakey: Auf dem Mac OSX, Ctrl and Cmd tasten können für die unten
+        markierten Abkürzungen statt Ctrl benutzt werden- einige Browser verhindern
+        spezifische Tastenkombinationen. Zum beispiel, falls Cmd+Space funktionier
+        nicht in Ihrem Browser, versuchen Sie Ctrl+Space.
+      keyboard:
+      - title: Knotenpunkt manipulation
+        hotkeys:
+        - hotkey: Enter
+          info: Geschwister Aufgabe einfügen
+        - hotkey: Shift+Enter
+          info: Geschwister Aufgabe einfügen über oder Zeilenumbruch (wenn Knotenpunkt
+            umbenennen)
+        - hotkey: Tab oder Einfg
+          info: Untergeordnete Aufgabe einfügen
+        - hotkey: Shift+Tab
+          info: Übergeordnete Aufgabe einfügen
+        - hotkey: Space
+          info: Knotenpunkt umbenennen
+        - hotkey: Shift+Space
+          info: Daten des Knotenpunkts bearbeiten
+        - hotkey: Backspace oder Entfernen
+          info: Knotenpunkt löschen
+        - hotkey: Ctrl+Up/Down
+          info: Den Knotenpunkt nach oben oder unten verschieben
+      - title: Bearbeitung
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Speichern
+        - hotkey: Ctrl+X oder C
+          info: Ausschneiden
+        - hotkey: Ctrl+C oder Y
+          info: Kopieren
+        - hotkey: Ctrl+V or P
+          info: Einfügen
+        - hotkey: U oder Ctrl+Z
+          info: Rückgängig
+        - hotkey: R oder Ctrl+Y oder Ctrl+Shift+Z
+          info: Wiederholen
+      - title: Auswahl
+        hotkeys:
+        - hotkey: Richtungspfeile
+          info: Knotenpunkt auswählen hoch/runter/rechts/links ausgehend vom aktuell
+            aktiven Knotenpunkt
+        - hotkey: Shift + Richtungspfeile
+          info: Knotenpunkt zur Auswahl hinzufügen hoch/runter/rechts/links (hilfreich
+            für die Mehrfachauswahl der Geschwistern Aufgaben)
+        - hotkey: "{"
+          info: Mehrfachauswahl für den aktuellen Knotenpunkt und kompletten untergeordneten
+            Strukturbaum darunter
+        - hotkey: "["
+          info: Mehrfachauswahl nur für den Strukturbaum unter aktuellem Knotenpunkt
+            (nicht für Knotenpunkt selbst)
+        - hotkey: "="
+          info: Mehrfachauswahl für alle Geschwister Aufgaben vom aktuellen Knotenpunkt
+            (Geschwister mit einer gemeinsamen übergeordneten Aufgabe)
+        - hotkey: "."
+          info: Mehrfachauswahl abbrechen und nur wieder aktuellen Knotenpunkt auswählen
+        - hotkey: 1 - 9
+          info: Alle Knotenpunkte eines bestimmten Levels auswählen (z.B. 1 wählt
+            alle Knotenpunkte auf dem ersten Level aus )
+      - title: Navigation and Bildschirm
+        hotkeys:
+        - hotkey: "/ oder F"
+          info: Knotenpunkt aufklappen oder zusammenklappen (falten oder entfalten
+            untergeordneter Aufgaben)
+        - hotkey: Ctrl + oder Z
+          info: Einzoomen
+        - hotkey: Ctrl - oder Shift Z
+          info: Auszoomen
+        - hotkey: Esc, 0, Ctrl+0
+          info: Die Ansicht der Karte zurücksetzen- Hauptknotenpunkt auswählen und
+            diesen zur Mitte des Bildschirms bringen
+      mouse:
+      - action: Karte verschieben
+        gesture: den Hauptknotenpunkt anklicken und ziehen , Hintergrund anklicken
+          und ziehen oder scrollen mit dem Trackpad/Touchpad.
+      - action: Knotenpunkt auswählen
+        gesture: tippen oder darauf klicken
+      - action: Mehrer Knotenpunkte auswählen
+        gesture: Shift+klick
+      - action: Knotenpunkte neu sortieren
+        gesture: Knotenpunkt zwischen deren Geschwistern ziehen, horizontal nah zu
+          der gewünschten Position. Beim Neusortierung wird der Zeigepunkt zu dem
+          Schwarzen Pfeil.
+      - action: Manuell Knotenpunkt positionieren
+        gesture: Knotenpunkt so lange ziehen bis der Pfeilpunkt für die Neusortierung
+          erscheint. Um manuelle Positionierung zu erzwingen auch mit dem Pfeilpunkt
+          angezeigt wir, Shift halten während ziehen . Bitte bemerken,dass die untergeordnete
+          Aufgaben des Hauptknotenpunkts können in jeder Richtung gezogen werden,
+          aber Knotenpunkte mit niedrigerem Level können nur in die Richtung der Übergeordnete
+          Aufgabe relativ zum Hauptknotenpunkt positioniert werden.
+      - action: Knotenpunkt umbenennen
+        gesture: Doppel-klick oder 2 mal darauf tippen
+      - action: Das Kontextmenü mit den Operationen anzeigen
+        gesture: 'Rechts-klick auf einen Knotenpunkt mit der Maus oder 2 mal auf das
+          Hintergrund tippen, oder lange auf den Knotenpunkt drücken: Anzeigen (Auf
+          Touch-Geräten)'
+      - action: Übergeordnete Aufgabe des Knotenpunkts ändern
+        gesture: Den Knotenpunkt ziehen und auf den anderen Knotenpunkt loslassen
+          (Zyklische Drops (einfügen) sind nicht erlaubt, sprich Sie können nicht
+          einen Knotenpunkt auf die untergeordnete Aufgabe oder auf die Nachfahren
+          ziehen)
+      - action: Aufgabe oder Projekt in separatem Fenster öffnen
+        gesture: Alt+klick
+      title_key_shortcuts: Tastatur-Befehle
+      title_mouse_shortcuts: Maus- und Berührungs- aufgaben
+      title_shortcuts: Abkürzungen
+    info_all_saved: Alles wurde erfolgreich gespeichert
+    info_any_failed: Einige der Anfragen sind fehlgeschlagen
+    info_no_permission: Sie haben nicht die benötigten Berechtigungen um dies durchzuführen
+    label_color_by: Färben nach
+    label_go_to: Springen zu
+    label_or: oder
+    last_state_modal:
+      label_differencies: Unterschiede auf dem Server
+      message_changed: haben unterschiedliche Attribute (Browser => Server) ({{changes}})
+      message_missing: fehlt von der übergeordnete Aufgabe "{{von}}"
+      message_moved: ist untergeordneter von "{{zu}}", nicht"{{von}}"
+      message_present: enthält als untergeordnete Aufgabe von "{{zu}}"
+      text_reload_appeal: Möchten Sie den Status erneut vom Server laden?
+      title: Letzter Client Status von WBS ist unterschiedlich zum Server Status
+    reload_modal:
+      label_errors: Die Fehler
+      text_reload_appeal: Möchten Sie die nicht gespeicherte Elemente ignorieren und
+        die Daten erneut vom Server laden?
+      title: WBS ordnungsgemäßer Speichervorgang ist fehlgeschlagen
+    stored_modal:
+      text_load_appeal: Möchten Sie Laden? Statt des neustens Status vom Server?
+      title: Nicht abgespeicherte Status wurde gefunden
+    warning_delete_node: Möchten Sie wirklich die Node {{name}} und all ihre Nachfahren
+      löschen?
+    warning_not_saved: Es ist nicht ordnungsgemäß abgespeichert
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Die Aufgaben
+  label_filter_group_easy_wbs:
+    easy_issue_query: Die Aufgaben
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/en-AU.yml b/plugins/easy_wbs/config/locales/en-AU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56b80b3c42087a32c3da5d38ddf1bda60788f804
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/en-AU.yml
@@ -0,0 +1,2 @@
+---
+en-AU: 
diff --git a/plugins/easy_wbs/config/locales/en-GB.yml b/plugins/easy_wbs/config/locales/en-GB.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a32c22849c6a55e573aaaa3a41b6c0aca41badc9
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/en-GB.yml
@@ -0,0 +1,2 @@
+---
+en-GB: 
diff --git a/plugins/easy_wbs/config/locales/en-US.yml b/plugins/easy_wbs/config/locales/en-US.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54b3be8e68c04f20ca86f9137ed2c6f00c987b74
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/en-US.yml
@@ -0,0 +1,2 @@
+---
+en-US: 
diff --git a/plugins/easy_wbs/config/locales/en.yml b/plugins/easy_wbs/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..33cc489ec117e2c40239523875014f6e90e1e01e
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/en.yml
@@ -0,0 +1,169 @@
+---
+en:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Other actions
+    button_add_comment: Add comment
+    button_add_child: Add child
+    button_add_parent: Add parent
+    button_add_sibling: Add sibling
+    button_all_icons: Icons
+    button_collapse: Collapse
+    button_collapse_all: Collapse all
+    button_cut: Cut
+    button_display: Display settings
+    button_edit_data: Edit data
+    button_expand: Expand
+    button_expand_all: Expand all
+    button_expand_collapse: Expand/Collapse
+    button_legend: Legend
+    button_one_side: One side
+    button_paste: Paste
+    button_project_menu: Easy WBS
+    button_redo: Redo
+    button_remove_comment: Remove comment
+    button_remove_node: Remove node
+    button_show_links: Show links
+    button_undo: Undo
+    edit_issue: Edit task
+    name_an_entity_first: Name an entity first
+    error_create: could not be created
+    error_delete: could not be deleted
+    error_update: could not be updated
+    errors:
+      not_subtaskable: Task "%{task_name}" cannot be subtask because of the setting
+        of its tracker
+    free:
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      text_not_available: is available only in Full version of Easy WBS.
+    hotkeys:
+      info_mac_metakey: On Mac OSX, Ctrl and Cmd keys can be used for shortcuts marked
+        Ctrl below - some browsers prevent certain key bindings. So, for example,
+        if Cmd+Space does not work in your browser, try Ctrl+Space.
+      keyboard:
+      - title: Node manipulation
+        hotkeys:
+        - hotkey: Enter
+          info: Add Sibling
+        - hotkey: Shift+Enter
+          info: Add Sibling above or line break (when renaming node)
+        - hotkey: Tab or Insert
+          info: Add child
+        - hotkey: Shift+Tab
+          info: Insert parent
+        - hotkey: Space
+          info: Rename node
+        - hotkey: Shift+Space
+          info: Edit node data
+        - hotkey: Backspace or Delete
+          info: Remove node
+        - hotkey: Ctrl+Up/Down
+          info: Move node up/down
+      - title: Editing
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Save
+        - hotkey: Ctrl+X or C
+          info: Cut
+        - hotkey: Ctrl+C or Y
+          info: Copy
+        - hotkey: Ctrl+V or P
+          info: Paste
+        - hotkey: U or Ctrl+Z
+          info: Undo
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: Redo
+      - title: Selection
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: Select the node up/down/left/right of the currently selected one
+        - hotkey: Shift + Arrow keys
+          info: Add node up/down/left/right to selection (useful to multi-select siblings)
+        - hotkey: "{"
+          info: Multi-select the current node and the entire subtree under it
+        - hotkey: "["
+          info: Multi-select only the subtree under the current node (not the node
+            itself)
+        - hotkey: "="
+          info: Multi-select all the siblings of the current node (that have the same
+            parent)
+        - hotkey: "."
+          info: Cancel multi-selection and select only the current node again
+        - hotkey: 1 - 9
+          info: Select all nodes of a particular level (eg 1 selects all first level
+            nodes)
+      - title: Navigation and screen
+        hotkeys:
+        - hotkey: "/ or F"
+          info: Expand or collapse node (fold or unfold children)
+        - hotkey: Ctrl + or Z
+          info: Zoom in
+        - hotkey: Ctrl - or Shift Z
+          info: Zoom out
+        - hotkey: Esc, 0, Ctrl+0
+          info: Reset map view - select root node and bring it to the center of the
+            screen
+      mouse:
+      - action: Move the map
+        gesture: Click and drag the center node, click and drag the background or
+          scroll with trackpad/touchpad.
+      - action: Select a node
+        gesture: Tap or click on it
+      - action: Select multiple nodes
+        gesture: Shift+click
+      - action: Reorder nodes
+        gesture: Drag a node between its siblings, horizontally close to the position
+          of reordering. A black arrow point will show when reordering.
+      - action: Manually position a node
+        gesture: Just drag a node until the arrow point for reordering isn't showing.
+          To force manual position even when the reorder arrow point is showing, hold
+          Shift while dragging. Please note that children of the root node can be
+          pulled in any direction, but lower level nodes can only be positioned in
+          the direction of its parent relative to the root.
+      - action: Rename a node
+        gesture: Double-click or double-tap it
+      - action: Show context menu with operations
+        gesture: 'Right click on a node (by mouse) or double-tap the background, or
+          long press a node: show (on touch devices)'
+      - action: Change node parent
+        gesture: Drag and drop a node on another node (circular drops are not allowed,
+          so you can't drop a node on one of it's children or descendants)
+      - action: Open task or project in separate window
+        gesture: Alt+click
+      title_key_shortcuts: Keyboard operations
+      title_mouse_shortcuts: Mouse and touch operations
+      title_shortcuts: Shortcuts
+    label_additional_data: Additional data
+    label_all: All
+    label_basic_data: Basic data
+    label_color_by: Color by
+    label_go_to: Go to
+    label_or: or
+    last_state_modal:
+      label_differencies: Differencies on server
+      message_changed: have different attributes (browser => server) ({{changes}})
+      message_missing: is missing from parent "{{from}}"
+      message_moved: is child of "{{to}}", not "{{from}}"
+      message_present: is present as child of "{{to}}"
+      text_reload_appeal: Do you want reload state from server?
+      title: Last client state of WBS is different from server state
+    name_an_entity_first: Name an entity first
+    reload_modal:
+      label_errors: Errors
+      text_reload_appeal: Do you want to ignore unsaved items and reload data from
+        server?
+      title: WBS failed to save properly
+    stored_modal:
+      text_load_appeal: Do you want to loaded it? Instead of the fresh state from
+        the server?
+      title: Unsaved state have been found
+    text_issue_not_saved: Issue is not saved. Save it first.
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Tasks
+  label_filter_group_easy_wbs:
+    easy_issue_query: Tasks
+  project_default_page:
+    easy_wbs: Easy WBS
+  project_module_easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/es.yml b/plugins/easy_wbs/config/locales/es.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5bacf9adde8a04cc4cfbc2ec062640e9f16fae90
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/es.yml
@@ -0,0 +1,177 @@
+---
+es:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Otras acciones
+    button_add_child: Añadir hijo
+    button_add_parent: Añadir matriz
+    button_add_sibling: Añadir hermano
+    button_all_icons: Iconos
+    button_collapse: Plegar
+    button_collapse_all: Plegar todo
+    button_cut: Cortar
+    button_display: Ajustes de visualización
+    button_edit_data: Editar datos
+    button_expand: Expandir
+    button_expand_all: Expandir todo
+    button_expand_collapse: Expandir/Plegar
+    button_legend: Leyenda
+    button_one_side: Un lado
+    button_paste: Pegar
+    button_project_menu: Easy WBS
+    button_redo: Volver a hacer
+    button_remove_node: Eliminar nodo
+    button_show_links: Mostrar enlaces
+    button_undo: Deshacer
+    edit_issue: Editar tarea
+    error_create: no se ha podido crear
+    error_delete: no se ha podido borrar
+    error_update: no se ha podido actualizar
+    errors:
+      not_subtaskable: La tarea "%{task_name}" no puede ser una subtarea debido a
+        los ajustes de su rastreador
+    free:
+      button_upgrade: Consigue la versión Completa
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Colorear nodos por propiedad
+      feature_context_menu: Cambiar propiedades del nodo
+      feature_filtering: Filtrar nodos por propiedad
+      header_not_available: Disponible sólo en la versión Completa
+      text_not_available: está disponible en la versión Completa de Easy WBS.
+    hotkeys:
+      info_mac_metakey: En Mac OSX, las teclas Ctrl y Cmd pueden emplearse para atajos
+        marcados en Ctrl bajo - algunos navegadores no permiten ciertos atajos de
+        teclado. Así, por ejemplo, si Cmd+Espacio no funciona en tu navegador, prueba
+        con Ctrl+Espacio
+      keyboard:
+      - title: Manipulación del nódulo
+        hotkeys:
+        - hotkey: Enter
+          info: Añadir hermano
+        - hotkey: Shift+Enter
+          info: Añadir hermano encima o salto de línea (al cambiar nombre de nódulo)
+        - hotkey: Tabulador o Insertar
+          info: Añadir hijo
+        - hotkey: Shift+Tab
+          info: Insertar matriz
+        - hotkey: Espacio
+          info: Cambiar nombre a nódulo
+        - hotkey: Shift+Espacio
+          info: Editar datos de nódulo
+        - hotkey: Retroceso o Borrar
+          info: Eliminar nódulo
+        - hotkey: Ctrl+Arriba/Abajo
+          info: Mover nódulo arriba/abajo
+      - title: Editar
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Guardar
+        - hotkey: Ctrl+X o C
+          info: Cortar
+        - hotkey: Ctrl+C o Y
+          info: Copiar
+        - hotkey: Ctrl+V o P
+          info: Pegar
+        - hotkey: U o Ctrl+Z
+          info: Deshacer
+        - hotkey: R o Ctrl+Y o Ctrl+Shift+Z
+          info: Rehacer
+      - title: Selección
+        hotkeys:
+        - hotkey: Cursores
+          info: Selecciona el nódulo arriba/abajo/izquierda/derecha del seleccionado
+            actual
+        - hotkey: Shift + Cursores
+          info: Añade nódulo arriba/abajo/izquierda/derecha de selección (útil para
+            seleccionar varios hermanos)
+        - hotkey: "{"
+          info: Selección múltiple del nódulo actual y el subárbol bajo él
+        - hotkey: "["
+          info: Selección múltiple sólo del subárbol bajo el nódulo actual (pero no
+            el nódulo)
+        - hotkey: "="
+          info: Selección múltiple de todos los hermanos del módulo actual (que tienen
+            la misma matriz)
+        - hotkey: "."
+          info: Cancelar selección múltiple y seleccionar sólo el nódulo actual de
+            nuevo
+        - hotkey: 1 - 9
+          info: 'Selecciona todos los nódulos de un nivel particular (ej: 1 selecciona
+            todos los nódulos de primer nivel)'
+      - title: Navegación y pantalla
+        hotkeys:
+        - hotkey: "/ o F"
+          info: Expandir u ocultar nódulo (desplegar o contraer hijos)
+        - hotkey: Ctrl + o Z
+          info: Acercar
+        - hotkey: Ctrl - o Shift Z
+          info: Alejar
+        - hotkey: Esc, 0, Ctrl+0
+          info: Restablecer vista de mapa - seleccionar nódulo raíz y llevarlo al
+            centro de la pantalla
+      mouse:
+      - action: Mover el mapa
+        gesture: pulsar y arrastrar el nódulo central, pulsar y arrastrar el fondo
+          o desplazar con trackpad/touchpad.
+      - action: Seleccionar un nódulo
+        gesture: pulsar o hacer clic
+      - action: Seleccionar múltiples nódulos
+        gesture: Shift+clic
+      - action: Reordenar nódulos
+        gesture: arrastrar un nódulo entre sus hermanos, horizontalmente cerca de
+          la posición de reordenamiento. Se mostrará una flecha negra cuando se esté
+          reordenando.
+      - action: Posicionar un nódulo manualmente
+        gesture: arrastra un nódulo hasta que la flecha para reordenar desaparezca.
+          Para forzar la posición manual incluso cuando la flecha de reordenamiento
+          se muestre, pulsa Shift mientras arrastres. Ten en cuenta que los hijos
+          del nódulo raíz pueden moverse en cualquier dirección, mientras que los
+          nódulos de niveles inferiores sólo se podrán colocar en la dirección del
+          matriz relativo a la raíz.
+      - action: Renombrar un nódulo
+        gesture: Hacer doble clic o pulsar dos veces
+      - action: Mostrar menú de contexto con operaciones
+        gesture: 'Hacer clic sobre el botón derecho en el nódulo (con ratón) o pulsar
+          dos veces sobre el fondo, o pulsar un momento sobre el nódulo: mostrar (en
+          dispositivos táctiles)'
+      - action: Cambiar matriz del nódulo
+        gesture: arrastrar y soltar un nódulo en otro nódulo (soltar no está permitirdo,
+          de modo que no puede soltar un nódulo en uno de sus hijos o descendientes)
+      - action: Abrir asunto o proyecto en una ventana separada
+        gesture: Alt+clic
+      title_key_shortcuts: Operaciones de teclado
+      title_mouse_shortcuts: Operaciones por ratón y táctiles
+      title_shortcuts: Atajos
+    info_all_saved: Todo se ha guardado correctamente
+    info_any_failed: 'Algunas de las solicitudes no se han podido realizar:'
+    info_no_permission: No tienes el permiso necesario para realizar esa acción
+    label_color_by: Color por
+    label_go_to: Ir a
+    label_or: o
+    last_state_modal:
+      label_differencies: Diferencias en el servidor
+      message_changed: tiene diferentes atributos (navegador => servidor) ({{changes}})
+      message_missing: ha desaparecido de su matriz "{{from}}"
+      message_moved: es hijo de "{{to}}", no de "{{from}}"
+      message_present: aparece como hijo de "{{to}}"
+      text_reload_appeal: "¿Quieres recargar el estado desde el servidor?"
+      title: El último estado del cliente de WBS es diferente al estado del servidor
+    reload_modal:
+      label_errors: Errores
+      text_reload_appeal: "¿Quieres ignorar los objetos sin guardar y recargar los
+        datos desde el servidor?"
+      title: No se ha podido guardar el WBS correctamente
+    stored_modal:
+      text_load_appeal: "¿Deseas cargarlo en lugar del último estado desde el servidor?"
+      title: Se ha encontrado el estado no guardado
+    warning_delete_node: "¿Seguro que quieres eliminar el nodo {{name}} y todos sus
+      descendientes?"
+    warning_not_saved: no se ha guardado correctamente
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Tareas
+  label_filter_group_easy_wbs:
+    easy_issue_query: Tareas
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/fi.yml b/plugins/easy_wbs/config/locales/fi.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e173d1858f52e3f1aa745d45fd68220187ea3c51
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/fi.yml
@@ -0,0 +1,2 @@
+---
+fi: 
diff --git a/plugins/easy_wbs/config/locales/fr.yml b/plugins/easy_wbs/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9356e01493ef745a5d837a26d2bd395d2ae30491
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/fr.yml
@@ -0,0 +1,182 @@
+---
+fr:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Autres actions
+    button_add_child: Ajouter enfant
+    button_add_parent: Ajouter parent
+    button_add_sibling: Ajouter frère/soeur
+    button_all_icons: Icônes
+    button_collapse: Réduire
+    button_collapse_all: Réduire tout
+    button_cut: Couper
+    button_display: Paramètres d'affichage
+    button_edit_data: Éditer données
+    button_expand: Développer
+    button_expand_all: Développer tout
+    button_expand_collapse: Développer/réduire
+    button_legend: Légende
+    button_one_side: Un côté
+    button_paste: Coller
+    button_project_menu: Easy WBS
+    button_redo: Refaire
+    button_remove_node: Retirer noeud
+    button_show_links: Afficher les liens
+    button_undo: Défaire
+    edit_issue: Éditer tâche
+    error_create: n'a pas pu être créé
+    error_delete: n'a pas pu être supprimé
+    error_update: n'a pas pu être  mis à jour
+    errors:
+      not_subtaskable: La tâche "%{task_name}" ne peut pas être une sous-tâche en
+        raison du réglage de son tracker
+    free:
+      button_upgrade: Obtenir la Version complète
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Nœuds colorants par propriété
+      feature_context_menu: Modification des propriétés du noeud
+      feature_filtering: Nœuds filtrants par propriété
+      header_not_available: Uniquement disponible en Version complète
+      text_not_available: est disponible en version complète d'Easy WBS.
+    hotkeys:
+      info_mac_metakey: Sur Mac OSX , les touches Ctrl et Cmd peuvent être utilisées
+        pour les raccourcis Ctrl marqués ci-dessous - certains navigateurs empêchent
+        certains raccourcis clavier . Ainsi, par exemple, si Cmd + Espace ne fonctionne
+        pas dans votre navigateur , essayez Ctrl + Espace .
+      keyboard:
+      - title: Manipulation du nœud
+        hotkeys:
+        - hotkey: Entrer
+          info: Ajouter frère
+        - hotkey: Shift+Entrée
+          info: Ajouter parent au-dessus ou saut de ligne (lors du renommage du nœud)
+        - hotkey: Tab ou Insérer
+          info: Ajouter enfant
+        - hotkey: Shift+Tab
+          info: Insérer parent
+        - hotkey: Espace
+          info: Renommage du nœud
+        - hotkey: Shift+Espace
+          info: Editer les données nœud
+        - hotkey: Retour arrière or Effacer
+          info: Enlever nœud
+        - hotkey: Ctrl+Haut/Bas
+          info: Déplacer nœud vers le haut/bas
+      - title: 'Édition '
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Sauvegarder
+        - hotkey: Ctrl+X or C
+          info: Couper
+        - hotkey: Ctrl+C or Y
+          info: Copier
+        - hotkey: Ctrl+V or P
+          info: Coller
+        - hotkey: U ou Ctrl+Z
+          info: Défaire
+        - hotkey: R ou Ctrl+Y ou Ctrl+Shift+Z
+          info: Refaire
+      - title: Sélection
+        hotkeys:
+        - hotkey: touche fléchée
+          info: Sélectionnez le noeud haut/bas/gauche/droite de celui sélectionné
+            actuellement
+        - hotkey: Shift + touche fléchée
+          info: Ajouter le nœud haut/bas/gauche/droite à la sélection (utile pour
+            les frères de multi-sélection )
+        - hotkey: "{"
+          info: Multi-sélectionner le nœud actuel et l'arbre entier
+        - hotkey: "["
+          info: Multi-sélectionner uniquement le sous-arbre sous le nœud actuel (pas
+            le nœud lui-même)
+        - hotkey: "="
+          info: Multi-sélectionnez tous les frères du nœud actuel (qui ont le même
+            parent)
+        - hotkey: "."
+          info: Annuler la multi-sélection et sélectionnez uniquement le nœud actuel
+            à nouveau
+        - hotkey: 1 - 9
+          info: Sélectionnez tous les nœuds d'un niveau particulier (par ex. 1 sélectionne
+            tous les nœuds de premier niveau )
+      - title: Navigation et écran
+        hotkeys:
+        - hotkey: "/ ou F"
+          info: Développer ou réduire le nœud (plier ou déplier enfants)
+        - hotkey: Ctrl + ou Z
+          info: Zoom avant
+        - hotkey: Ctrl - ou  Shift Z
+          info: Zoom arrière
+        - hotkey: Esc, 0, Ctrl+0
+          info: Réinitialiser vue de la carte- sélectionnez le nœud racine et l'amener
+            au centre de l'écran
+      mouse:
+      - action: Déplacer la carte
+        gesture: cliquez et faites glisser le nœud central, cliquez et faites glisser
+          l'arrière-plan ou faites défiler avec le trackpad / touchpad .
+      - action: Sélectionnez un nœud
+        gesture: tapez ou cliquez dessus
+      - action: Sélectionnez des nœuds multiples
+        gesture: Shift+clic
+      - action: Réorganiser les nœuds
+        gesture: faire glisser un nœud entre ses frères et sœurs , à l'horizontale
+          près de la position de réordonnancement . Un point de flèche noire montrera
+          quand à lieu le réordonnancement.
+      - action: positionner manuellement un nœud
+        gesture: il suffit de glisser un nœud jusqu'à ce que le point flèche de réordonnancement
+          ne s'affiche plus . Pour forcer la position manuelle, même lorsque le point
+          flèche de réapprovisionnement est affiché, maintenez la touche Maj tout
+          en faisant glisser. S'il vous plaît noter que les enfants du nœud racine
+          peuvent être tirés dans une direction quelconque, mais des nœuds de niveau
+          inférieur ne peuvent pas être placés dans la direction de leur parent par
+          rapport à la racine.
+      - action: Renommer un nœud
+        gesture: Double-cliquez ou double-taper dessus
+      - action: Afficher le menu contextuel avec des opérations
+        gesture: 'Faites un clic droit sur ​​un nœud ( par la souris ) ou double -taper
+          l''arrière-plan , ou appuyez longuement sur un nœud: afficher (sur les appareils
+          tactiles)'
+      - action: Modifier le parent nœud
+        gesture: glisser-déposer un nœud sur un autre nœud ( les gouttes circulaires
+          ne sont pas autorisés , de sorte que vous ne pouvez pas supprimer un nœud
+          sur l'un de ses enfants ou descendants)
+      - action: question ouverte ou projet dans une fenêtre séparée
+        gesture: Alt+clic
+      title_key_shortcuts: Opérations de clavier
+      title_mouse_shortcuts: Souris et opérations tactiles
+      title_shortcuts: Raccourcis
+    info_all_saved: Tout a été sauvegardé avec succès
+    info_any_failed: 'Certaines des demandes  ont échoué:'
+    info_no_permission: Vous ne disposez pas des permissions nécessaires pour agir
+      ainsi
+    label_color_by: Couleur par
+    label_go_to: Aller à
+    label_or: ou
+    last_state_modal:
+      label_differencies: Différences sur le serveur
+      message_changed: dispose de différentes caractéristiques (navigateur =>serveur)
+        ({{changements}})
+      message_missing: est manquant du parent "{{de}}"
+      message_moved: est enfant de "{{à}}", pas"{{de}}"
+      message_present: est présent en tant qu'enfant de "{{à}}"
+      text_reload_appeal: Souhaitez-vous  recharger l'état à partir du serveur?
+      title: Le dernier état du client de WBS est différent  de l'état du serveur
+    reload_modal:
+      label_errors: Erreurs
+      text_reload_appeal: Souhaitez-vous ignorer les éléments non enregistrés et recharger
+        les données à partir du serveur?
+      title: WBS n'a pas pu être sauvegardé correctement
+    stored_modal:
+      text_load_appeal: Voulez-vous le charger? Au lieu du nouvel état à partir du
+        serveur?
+      title: un état non sauvegardé a été trouvé
+    warning_delete_node: Voulez-vous vraiment supprimer le nœud {{nom}} et tous ses
+      descendants?
+    warning_not_saved: n'est pas sauvegardé correctement
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Tâches
+  label_filter_group_easy_wbs:
+    easy_issue_query: Tâches
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/he.yml b/plugins/easy_wbs/config/locales/he.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de35edf7f488f14ca884e77b8d36cf38a66d3e3d
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/he.yml
@@ -0,0 +1,2 @@
+---
+he: 
diff --git a/plugins/easy_wbs/config/locales/hr.yml b/plugins/easy_wbs/config/locales/hr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..662a199e9d139baea3df83993ba938866dc13964
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/hr.yml
@@ -0,0 +1,2 @@
+---
+hr: 
diff --git a/plugins/easy_wbs/config/locales/hu.yml b/plugins/easy_wbs/config/locales/hu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d0309d9b9e29b03f0662943afafb6a7bd055f2c2
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/hu.yml
@@ -0,0 +1,181 @@
+---
+hu:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Egyéb műveletek
+    button_add_comment: Új hozzászólás
+    button_add_child: Alfeladat hozzáadása
+    button_add_parent: Szülő feladat hozzáadása
+    button_add_sibling: Feladat hozzáadása
+    button_all_icons: Ikonok
+    button_collapse: Összezár
+    button_collapse_all: Összes összezárása
+    button_cut: Kivágás
+    button_display: Beállítások megjelenítése
+    button_edit_data: Adat szerkesztése
+    button_expand: Kinyit
+    button_expand_all: Összes kinyitása
+    button_expand_collapse: Kinyit / Összezár
+    button_legend: Jelmagyarázat
+    button_one_side: Egy oldal
+    button_paste: Beillesztés
+    button_project_menu: Easy WBS
+    button_redo: Visszaállít
+    button_remove_comment: Hozzászólás eltávolítása
+    button_remove_node: Ág eltávolítása
+    button_show_links: Kapcsolatok megjelenítése
+    button_undo: Visszavonás
+    edit_issue: Feladat szerkesztése
+    error_create: létrehozás  sikertelen
+    error_delete: nem lehet törölni
+    error_update: Frissítés nem sikerült
+    errors:
+      not_subtaskable: 'Ez a feladat: "%{task_name}" nem lehet alfeladat a típus beállítása
+        miatt'
+    free:
+      button_upgrade: Szerezze be a teljes verziót
+      button_upgrade_href: https://www.easyproject.hu/szoftver
+      feature_coloring: Ágak színezése tulajdonság alapján
+      feature_context_menu: Ág tulajdonságának megváltoztatása
+      feature_filtering: Ágak szűrése tulajdonság alapján
+      header_not_available: Csak teljes verzióban érhető el
+      text_not_available: csak az Easy WBS teljes verziójában elérhető
+    hotkeys:
+      info_mac_metakey: Mac OSX esetén a Ctrl és Cmd billentyűkombináció használható
+        olyan rövidítésekre melyeket Ctrl-er jelölünk- néhány böngésző nem engedélyezi
+        ezeket a kombinációkat. Ezért például, ha a Cmd + Space nem működik a böngészőjében,
+        akkor próbálja meg a Ctrl+ Space kombinációt
+      keyboard:
+      - title: Elemek módosítása
+        hotkeys:
+        - hotkey: Enter
+          info: Feladat hozzáadása
+        - hotkey: Shift+Enter
+          info: Feladat hozzáadása fölé  vagy sortöréssel (elem átnevezésekor)
+        - hotkey: Tab vagy Beillesztés
+          info: alfeladat hozzáadása
+        - hotkey: Shift+Tab
+          info: Szülőfeladat hozzáadása
+        - hotkey: Space
+          info: Elem átnevezése
+        - hotkey: Shift+Space
+          info: Elem adatainak szerkesztése
+        - hotkey: Backspace vagy Törlés
+          info: Elem eltávolítása
+        - hotkey: Ctrl+Fel/Le
+          info: Elem elmozdítása fel/le
+      - title: Szerkesztés
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Mentés
+        - hotkey: Ctrl+X vagy C
+          info: Kivágás
+        - hotkey: Ctrl+C vagy Y
+          info: Másolás
+        - hotkey: Ctrl+V vagy P
+          info: Beillesztés
+        - hotkey: U vagy Ctrl+Z
+          info: Visszaállítás
+        - hotkey: R vagy Ctrl+Y vagy Ctrl+Shift+Z
+          info: Redo
+      - title: Kiválasztás
+        hotkeys:
+        - hotkey: Kurzor billentyűk
+          info: A jelenleg kiválaszott elemhez képest válassza ki a fel/le/bal/jobb
+            irányban lévőt
+        - hotkey: Shift + Kurzor billentyűk
+          info: Elem hozzáadása fel/le/bal/jobb irányban ( hasznos a feladat többszörös
+            kiválasztására)
+        - hotkey: "{"
+          info: Jelenlegi elem és az alatta lévő fastruktúra elemeinek kiválasztása
+        - hotkey: "["
+          info: Jelenlegi elem alatt lévő fastruktúra elemeinek kiválasztása(maga
+            az elem nem kerül kijelölésre)
+        - hotkey: "="
+          info: A jelenlegi elemmel egy szinten lévő elemek együttes kiválasztása
+            (amelyek azonos szülővel rendelkeznek)
+        - hotkey: "."
+          info: Többszörös kiválasztás törlése és válassza ki a jelenlegi elemet ismét
+        - hotkey: 1 - 9
+          info: 'Egy kiválasztott szinten az összes elem kiválasztása (pl. 1: az összes
+            első szintű elem kiválasztása)'
+      - title: Navigáció és  képernyő
+        hotkeys:
+        - hotkey: "/ vagy F"
+          info: Elem kibontása vagy összezárása (kibontja vagy bezárja az alfeladatokat
+            )
+        - hotkey: Ctrl + vagy Z
+          info: Nagyítás
+        - hotkey: Ctrl - vagy Shift Z
+          info: Kicsinyítés
+        - hotkey: Esc, 0, Ctrl+0
+          info: Térkép nézet alaphelyzetbe állítása - Gyökérelem kiválasztása és a
+            képernyő közepére hozása
+      mouse:
+      - action: Térkép áthelyezése
+        gesture: kattintás és  behúzás a központi ágba, kattintás és behúzás a háttér
+          vagy görgetés a touchpaddel.
+      - action: Ág kiválasztása
+        gesture: érintse meg vagy kattintson rá
+      - action: több ág kiválasztása
+        gesture: Shift+kattintás
+      - action: ágak újrhívása
+        gesture: behúzniaz ágat a két testvér közé, vízszintesen zárja be a feladatot
+          a pozíció újrahívásához.  Egy fekete nyíl fog megjelenni az újrahívásnál.
+      - action: Manuálisan állítsa be az ágat
+        gesture: 'csak húzza be az ágta, a nyílig. A kézi pozícionáls kényszerítése
+          amikor az újrahívott nyíl megjelenik, tartsa a  Shift-et ameddig húzza.
+          Figyeljen , hogy az alág bármilyen irányban húzható, de az alacsonyabb szintű
+          ágak a szülő irányába pozícionálhatók. '
+      - action: Ág átnevezése
+        gesture: Dupla kattintás vagy dupla koppintás
+      - action: Kapcsolatban álló menü megjelenítése műveletekkel
+        gesture: 'Jobb klikk az ágra (egérrel) vagy duppla koppintás a háttéren,  vagy
+          hosszan nyomva tartás az ágon: mutatás (érintős eszközön)'
+      - action: Szülő ág megváltoztatása
+        gesture: az ág egy másik ágra húzása (körkörös húzás nem megengedett, tehát
+          egy ág nem húzható rá a gyerek ágra, vagy az egyik alágára)
+      - action: Nyitott feladatok vagy projekt különböző ablakban
+        gesture: Alt+click
+      title_key_shortcuts: Billentyű műveletek
+      title_mouse_shortcuts: Egér műveletek
+      title_shortcuts: Gyorsparancsok
+    info_all_saved: Sikeresen mentve
+    info_any_failed: Kérések egy része sikertelen
+    info_no_permission: Nem rendelkezik a szükséges jogosultsággal a művelet végrehajtásához
+    label_additional_data: További adatok
+    label_all: Mind
+    label_basic_data: Alap adatok
+    label_color_by: Színkódolás szempontja
+    label_go_to: Ugrás feladathoz
+    label_or: vagy
+    last_state_modal:
+      label_differencies: Különbségek a szerveren
+      message_changed: különböző attribútumokkal rendelkezik (böngésző => server ({{változások}})
+      message_missing: hiányzik a szülő feladatból "{{from}}"
+      message_moved: alfeladata "{{to}}", nem "{{from}}"
+      message_present: 'alfeladataként jelenik meg az alábbi feladatnak: "{{to}}"'
+      text_reload_appeal: Szeretné újratölteni az állapotot a szerverről?
+      title: A WBS utolsó kliens állapota különbözik a szerver állapottól
+    name_an_entity_first: Először adjon egy nevet
+    reload_modal:
+      label_errors: Hibák
+      text_reload_appeal: Szeretné figyelmen kívül hagyni a nem mentett elemeket és
+        újratölteni a szerverről?
+      title: Nem sikerült megfelelően menteni a WBS-t
+    stored_modal:
+      text_load_appeal: Szeretné betölteni? A friss állapot helyett a szerverről?
+      title: Nem mentett állapotot talált
+    text_issue_not_saved: Feladat még nincs mentve. Először  mentsen.
+    warning_delete_node: Biztosan törölni akarja a(z) {{name}}  elemet és annak alsóbb
+      szintű elemeit?
+    warning_not_saved: nem megfelelő mentés
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Feladatok
+  label_filter_group_easy_wbs:
+    easy_issue_query: Feladatok
+  project_default_page:
+    easy_wbs: Easy WBS
+  project_module_easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/it.yml b/plugins/easy_wbs/config/locales/it.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6454ea2e1c69cb98ed21f09094267cdd0c25b2f9
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/it.yml
@@ -0,0 +1,175 @@
+---
+it:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Altre azioni
+    button_add_child: Aggiungi attività figlio
+    button_add_parent: Aggiungi attività padre
+    button_add_sibling: Aggiungi attività parallela
+    button_all_icons: Icone
+    button_collapse: Contrai
+    button_collapse_all: Contrai tutto
+    button_cut: Taglia
+    button_display: Mostra impostazioni
+    button_edit_data: Modifica dati
+    button_expand: Espandi
+    button_expand_all: Espandi tutto
+    button_expand_collapse: Espandi/Contrai
+    button_legend: Legenda
+    button_one_side: Da una parte
+    button_paste: Incolla
+    button_project_menu: Easy WBS
+    button_redo: Ripristina
+    button_remove_node: Rimuovi nodo
+    button_show_links: Mostra collegamenti
+    button_undo: Annulla
+    edit_issue: Modifica task
+    error_create: creazione non riuscita
+    error_delete: cancellazione non riuscita
+    error_update: aggiornamento non riuscito
+    errors:
+      not_subtaskable: Il task "%{task_name}" non può avere sottotask a causa delle
+        impostazioni del suo tracker
+    free:
+      button_upgrade: Ottieni la Versione Completa
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Colorazione dei nodi in base alla caratteristica
+      feature_context_menu: Cambiamento delle caratteristiche dei nodi
+      feature_filtering: Filtro dei nodi in base alla caratteristica
+      header_not_available: Disponibile solo nella Versione Completa
+      text_not_available: è disponibile nella Versione Completa di Easy WBS.
+    hotkeys:
+      info_mac_metakey: In caso di sistemi Mac, i tasti Ctrl e Cmd possono essere
+        utilizzati per le scorciatoie in cui si impiega Ctrl. Alcuni browser non permettono
+        alcune combinazioni di tasti. Quindi, se per esempio nel tuo browser Cmd+Spazio
+        non funziona, prova Ctrl+Spazio.
+      keyboard:
+      - title: Node manipulation
+        hotkeys:
+        - hotkey: Enter
+          info: Add Sibling
+        - hotkey: Shift+Enter
+          info: Add Sibling above or line break (when renaming node)
+        - hotkey: Tab or Insert
+          info: Add child
+        - hotkey: Shift+Tab
+          info: Insert parent
+        - hotkey: Space
+          info: Rename node
+        - hotkey: Shift+Space
+          info: Edit node data
+        - hotkey: Backspace or Delete
+          info: Remove node
+        - hotkey: Ctrl+Up/Down
+          info: Move node up/down
+      - title: Editing
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Save
+        - hotkey: Ctrl+X or C
+          info: Cut
+        - hotkey: Ctrl+C or Y
+          info: Copy
+        - hotkey: Ctrl+V or P
+          info: Paste
+        - hotkey: U or Ctrl+Z
+          info: Undo
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: Redo
+      - title: Selection
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: Select the node up/down/left/right of the currently selected one
+        - hotkey: Shift + Arrow keys
+          info: Add node up/down/left/right to selection (useful to multi-select siblings)
+        - hotkey: "{"
+          info: Multi-select the current node and the entire subtree under it
+        - hotkey: "["
+          info: Multi-select only the subtree under the current node (not the node
+            itself)
+        - hotkey: "="
+          info: Multi-select all the siblings of the current node (that have the same
+            parent)
+        - hotkey: "."
+          info: Cancel multi-selection and select only the current node again
+        - hotkey: 1 - 9
+          info: Select all nodes of a particular level (eg 1 selects all first level
+            nodes)
+      - title: Navigation and screen
+        hotkeys:
+        - hotkey: "/ or F"
+          info: Expand or collapse node (fold or unfold children)
+        - hotkey: Ctrl + or Z
+          info: Zoom in
+        - hotkey: Ctrl - or Shift Z
+          info: Zoom out
+        - hotkey: Esc, 0, Ctrl+0
+          info: Reset map view - select root node and bring it to the center of the
+            screen
+      mouse:
+      - action: Muovi la mappa
+        gesture: clicca e trascina il nodo centrale, clicca e trascina lo sfondo o
+          scorri con la rotella/touchpad.
+      - action: Seleziona un nodo
+        gesture: tocca o cliccaci sopra
+      - action: Seleziona nodi multipli
+        gesture: Shift+click
+      - action: Riordina nodi
+        gesture: trascina un nodo tra i suoi fratelli, orizzontalmente vicino alla
+          posizione di riordino. Un puntatore nero a forma di freccia mostrerà dove
+          riordinare.
+      - action: Posizionare manualmente un nodo
+        gesture: semplicemente trascina un nodo finché la freccia che mostra dove
+          riordinare non appare. Per forzare il posizionamento manuale anche quando
+          viene mostrata la freccia nera, tieni premuto Shift mentre trascini. Considera
+          che i figli del nodo radice possono essere spostati in ogni direzione, ma
+          i nodi di livello inferiore possono essere posizionati solo in direzione
+          del proprio padre relativo alla radice.
+      - action: Rinonima un nodo
+        gesture: Fai doppio clic/tocco
+      - action: Mostra il menu contestuale con le operazioni
+        gesture: 'Clicca col destro su un nodo (col mouse) o fai un doppio tocco sullo
+          sfondo, oppure tieni premuto a lungo sul nodo: mostra (su dispositivi touch)'
+      - action: Cambia nodo padre
+        gesture: trascina e rilascia un nodo su un altro nodo (i rilasci circolari
+          non sono permessi, quindi non puoi rilasciare un nodo su uno dei suoi figli
+          o discendenti)
+      - action: Apri questione o progetto in una finestra separata
+        gesture: Alt+click
+      title_key_shortcuts: Operazioni tramite tastiera
+      title_mouse_shortcuts: Operazioni tramite tastiera e tocco
+      title_shortcuts: Scorciatoie
+    info_all_saved: Tutto è stato salvato con successo
+    info_any_failed: 'Alcune richieste non sono andate a buon fine:'
+    info_no_permission: Non hai i permessi necessari per compiere questa operazione
+    label_color_by: Colore per
+    label_go_to: Vai a
+    label_or: oppure
+    last_state_modal:
+      label_differencies: Differenze sul server
+      message_changed: ha degli attributi differenti (browser => server) ({{changes}})
+      message_missing: manca dal padre "{{from}}"
+      message_moved: è figlio di "{{to}}", non di "{{from}}"
+      message_present: è presente come figlio di "{{to}}"
+      text_reload_appeal: Desideri ricaricare lo stato dal server?
+      title: L'ultimo stato WBS del client è diverso da quello del server
+    reload_modal:
+      label_errors: Errori
+      text_reload_appeal: Vuoi ignorare le voci non salvate e ricaricare i dati dal
+        server?
+      title: WBS non salvato correttamente
+    stored_modal:
+      text_load_appeal: Desideri caricarla? Invece di ottenere lo stato più recente
+        dal server?
+      title: Qualcosa risulta priva di salvataggio
+    warning_delete_node: Desideri davvero cancellare il nodo {{name}} e tutti i suoi
+      discendenti?
+    warning_not_saved: non salvato correttamente
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Task
+  label_filter_group_easy_wbs:
+    easy_issue_query: Task
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/ja.yml b/plugins/easy_wbs/config/locales/ja.yml
new file mode 100644
index 0000000000000000000000000000000000000000..82947d50d389e1c298c1bdb5e802c3e5d7d79f40
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/ja.yml
@@ -0,0 +1,161 @@
+---
+ja:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: その他の操作
+    button_add_comment: コメントを追加する
+    button_add_child: 子ノードの追加
+    button_add_parent: 親ノードの追加
+    button_add_sibling: 兄弟ノードの追加
+    button_all_icons: アイコン
+    button_collapse: 折りたたみ
+    button_collapse_all: すべて折りたたみ
+    button_cut: 切り取り
+    button_display: 画面の表示設定
+    button_edit_data: データの編集
+    button_expand: 展開
+    button_expand_all: すべて展開
+    button_expand_collapse: 展開/折りたたみ
+    button_legend: 凡例
+    button_one_side: 片側
+    button_paste: 貼り付け
+    button_project_menu: Easy WBS
+    button_redo: やり直し
+    button_remove_comment: コメントを削除する
+    button_remove_node: ノードを削除
+    button_show_links: リンクを表示
+    button_undo: 元に戻す
+    edit_issue: タスクの編集
+    error_create: 作成できませんでした
+    error_delete: 削除できませんでした
+    error_update: 更新できませんでした
+    errors:
+      not_subtaskable: トラッカーの設定のため、タスク "%{task_name}" サブタスクにすることはできません
+    free:
+      button_upgrade: フルバージョンを入手
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: プロパティ毎にノードを塗り分け
+      feature_context_menu: ノードのプロパティを変更
+      feature_filtering: プロパティ毎にノードをフィルタ
+      header_not_available: フルバージョンでのみ入手可能
+      text_not_available: 'Easy WBSのフルバージョンでのみ利用可能'
+    hotkeys:
+      info_mac_metakey: Mac OS Xの場合、ControlキーとCommandキーは、以下でCtrlと表示しているショートカットに使用可能です。しかしブラウザによっては、無効なキーの組み合わせがあります。そのため、例えばCommand+Spaceが効かない場合は、Control+Spaceを試してください。
+      keyboard:
+      - title: ノード操作
+        hotkeys:
+        - hotkey: Enter
+          info: 兄弟を追加
+        - hotkey: Shift+Enter
+          info: 1つ上に兄弟を追加、または改行(ノード名を変更中の場合)
+        - hotkey: Tab or Insert
+          info: 子を追加
+        - hotkey: Shift+Tab
+          info: 親を追加
+        - hotkey: Space
+          info: ノード名の変更
+        - hotkey: Shift+Space
+          info: ノードの詳細を編集
+        - hotkey: Backspace または Delete
+          info: ノードの削除
+        - hotkey: Ctrl+Up/Down
+          info: ノードを上/下へ移動
+      - title: 編集
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: 保存
+        - hotkey: Ctrl+X または C
+          info: 切り取り
+        - hotkey: Ctrl+C または Y
+          info: コピー
+        - hotkey: Ctrl+V または P
+          info: 貼り付け
+        - hotkey: U または Ctrl+Z
+          info: 元に戻す
+        - hotkey: R または Ctrl+Y または Ctrl+Shift+Z
+          info: やり直し
+      - title: ノード選択
+        hotkeys:
+        - hotkey: 矢印キー
+          info: 現在選択中のノードの上下左右のノードを選択する
+        - hotkey: Shift+矢印キー
+          info: 現在選択しているノードに上下左右のノードを追加(兄弟ノードを複数選択するときに便利です)
+        - hotkey: "{"
+          info: 選択中ノードと、その子すべてを選択
+        - hotkey: "["
+          info: 選択中ノードの、子だけを選択(選択中のノードを含みません)
+        - hotkey: "="
+          info: 選択中ノードの兄弟を、すべて選択(同じ親の下にある兄弟が対象です)
+        - hotkey: "."
+          info: 複数選択を解除し、現在のノードだけを選択
+        - hotkey: 1 - 9
+          info: 指定された階層のノードをすべて選択(1の場合、第一階層のノードがすべて選択されます)
+      - title: ナビゲーションと画面
+        hotkeys:
+        - hotkey: "/ または F"
+          info: ノードの展開/折りたたみ(子ノードをフォルダーに入れる/から出す)
+        - hotkey: Ctrl+(+) または Z
+          info: ズームイン
+        - hotkey: Ctrl+(-) または Shift+Z
+          info: ズームアウト
+        - hotkey: Esc または 0 または Ctrl+0
+          info: マップ表示のリセット - ルートノードを画面中央に移動する
+      mouse:
+      - action: マップを移動
+        gesture: 中央のノードをクリック、背景をクリック・アンド・ドラッグ、トラックパッド/タッチパッドをスクロールする
+      - action: ノード選択
+        gesture: ノードをクリック、またはタップする
+      - action: ノードの複数選択
+        gesture: Shift+クリック
+      - action: ノードの並べ替え
+        gesture: 兄弟ノードを水平方向に並べ替え位置に近いところ迄ドラッグする。並べ替え中には黒い矢印が表示される
+      - action: ノードの位置を手動で調整
+        gesture: 黒い矢印の表示が消えるところまで、ノードをドラッグしてください。並べ替えの黒い矢印が表示されている時に強制的に手動で位置を決めるには、Shiftを押しながらドラッグしてください。注意:第一階層のノードはどの方向にも自由に配置できますが、それより下位階層のノードは、ルートに対して親の向きにしか配置できません。
+      - action: ノード名の変更
+        gesture: ダブルクリックまたはダブルタップ
+      - action: 操作に関するコンテキスト・メニューを表示する
+        gesture: ノードを右クリックする(マウスで)、または背景をダブルタップする、または(タッチパネル上に)表示する
+      - action: ノードの親を変更
+        gesture: ドラッグ&ドロップ(無限ループとなる関係にはできません)
+      - action: タスク/プロジェクトを別ウィンドウで表示
+        gesture: Alt+クリック
+      title_key_shortcuts: キーボード操作
+      title_mouse_shortcuts: マウスとタッチ操作
+      title_shortcuts: ショートカット
+    info_all_saved: すべて正しく保存されました
+    info_any_failed: いくつかの要求に失敗しました:
+    info_no_permission: その操作をする権限がありません
+    label_additional_data: 追加データ
+    label_all: すべて
+    label_basic_data: 基本データ
+    label_color_by: 色の塗り分け
+    label_go_to: '次へ:'
+    label_or: または
+    last_state_modal:
+      label_differencies: サーバとの差異
+      message_changed: はブラウザ上とサーバ上で属性が異なります ({changes})
+      message_missing: は親ノード {from} に存在しません
+      message_moved: は {from} ではなく {to} の子ノードです
+      message_present: は {to} の子ノードです
+      text_reload_appeal: サーバからステータスを再読込みしますか?
+      title: 最新のWBSクライアント・データ・ステータスがサーバー・でーた・ステータスと異なります
+    name_an_entity_first: まずはじめにエンティティーの名前をつける
+    reload_modal:
+      label_errors: エラー
+      text_reload_appeal: 保存されていない変更を破棄して、サーバのデータを再読み込みしますか?
+      title: WBSを正しく保存できませんでした
+    stored_modal:
+      text_load_appeal: サーバーからの最新データの代わりにこれを読み込みますか?
+      title: 保存されていないステータスが見つかりました
+    text_issue_not_saved: 懸案事項が保存されていません。まず保存してください。
+    warning_delete_node: 本当に {{name}} とその子孫チケットを削除してもよろしいですか?
+    warning_not_saved: は正しく保存されません
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: タスク
+  label_filter_group_easy_wbs:
+    easy_issue_query: タスク
+  project_default_page:
+    easy_wbs: Easy WBS
+  project_module_easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/ko.yml b/plugins/easy_wbs/config/locales/ko.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c748d5fb5ff618236d94c7591b11fef7b66e3a8e
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/ko.yml
@@ -0,0 +1,162 @@
+---
+ko:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: 쉬운 WBS
+  easy_wbs:
+    button_actions: 다른 실행
+    button_add_child: 자녀 추가
+    button_add_parent: 부모 추가
+    button_add_sibling: 형제/자매 추가
+    button_all_icons: 아이콘
+    button_collapse: 줄이기
+    button_collapse_all: 모두 줄이기
+    button_cut: 잘라내기
+    button_display: 설정 확인
+    button_edit_data: 데이터 수정
+    button_expand: 넓히기
+    button_expand_all: 모두 늘이기
+    button_expand_collapse: 더보기/줄이기
+    button_legend: 전설
+    button_one_side: 한 쪽
+    button_paste: 붙여넣기
+    button_project_menu: Easy WBS
+    button_redo: 다시하기
+    button_remove_node: 연결고리 제거
+    button_show_links: 링크 보여주기
+    button_undo: 실행취소
+    edit_issue: 작업 수정
+    error_create: 생성될 수 없습니다
+    error_delete: 삭제될 수 없습니다
+    error_update: 업데이트 될 수 없습니다
+    errors:
+      not_subtaskable: 추적기 설정으로 인해 작업 "%{task_name}"은  하위 작업이 될 수 없습니다
+    free:
+      button_upgrade: 전체 버전 받기
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: 속성별로 연결 색 칠하기
+      feature_context_menu: 연결값 변경
+      feature_filtering: 속성별로 연결 필터링
+      header_not_available: 전체 버전에서만 사용할 수 있음
+      text_not_available: Easy WBS의 전체 버전에서만 사용할 수 있음
+    hotkeys:
+      info_mac_metakey: Mac OSX에서는 Ctrl 키와 Cmd 키를 Ctrl 아래에 표시된 단축키에 사용할 수 있습니다. 일부
+        브라우저는 특정 키 조합을 사용하지 못하게 합니다. 예를 들어 Cmd + Space가 브라우저에서 작동하지 않는 경우, Ctrl +
+        Space를 사용하여주세요.
+      keyboard:
+      - title: Node manipulation
+        hotkeys:
+        - hotkey: Enter
+          info: Add Sibling
+        - hotkey: Shift+Enter
+          info: Add Sibling above or line break (when renaming node)
+        - hotkey: Tab or Insert
+          info: Add child
+        - hotkey: Shift+Tab
+          info: Insert parent
+        - hotkey: Space
+          info: Rename node
+        - hotkey: Shift+Space
+          info: Edit node data
+        - hotkey: Backspace or Delete
+          info: Remove node
+        - hotkey: Ctrl+Up/Down
+          info: Move node up/down
+      - title: Editing
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Save
+        - hotkey: Ctrl+X or C
+          info: Cut
+        - hotkey: Ctrl+C or Y
+          info: Copy
+        - hotkey: Ctrl+V or P
+          info: Paste
+        - hotkey: U or Ctrl+Z
+          info: Undo
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: Redo
+      - title: Selection
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: Select the node up/down/left/right of the currently selected one
+        - hotkey: Shift + Arrow keys
+          info: Add node up/down/left/right to selection (useful to multi-select siblings)
+        - hotkey: "{"
+          info: Multi-select the current node and the entire subtree under it
+        - hotkey: "["
+          info: Multi-select only the subtree under the current node (not the node
+            itself)
+        - hotkey: "="
+          info: Multi-select all the siblings of the current node (that have the same
+            parent)
+        - hotkey: "."
+          info: Cancel multi-selection and select only the current node again
+        - hotkey: 1 - 9
+          info: Select all nodes of a particular level (eg 1 selects all first level
+            nodes)
+      - title: Navigation and screen
+        hotkeys:
+        - hotkey: "/ or F"
+          info: Expand or collapse node (fold or unfold children)
+        - hotkey: Ctrl + or Z
+          info: Zoom in
+        - hotkey: Ctrl - or Shift Z
+          info: Zoom out
+        - hotkey: Esc, 0, Ctrl+0
+          info: Reset map view - select root node and bring it to the center of the
+            screen
+      mouse:
+      - action: Move the map
+        gesture: 가운데 노드를 클릭하고 드래그하고 배경을 클릭하여 드래그하거나 트랙 패드 / 터치 패드로 스크롤합니다.
+      - action: 노드 선택
+        gesture: 선택 혹은 클릭하기
+      - action: 다양한 노드 선택
+        gesture: Shift+클릭
+      - action: 노드 재정렬
+        gesture: 형제 파일 간 노드를 드래그하고 재정렬 위치에 가깝게 놓습니다. 재정렬 할 때는 검은 색 화살표 점이 표시됩니다..
+      - action: 직접 노트 위치 수정
+        gesture: 재정렬을 위한 화살표 점이 표시되지 않을 때까지 노드를 드래그하세요. 재정렬 화살표가 표시되어 있지만, 수동으로 위치를
+          지정하려면 Shift 키를 누른 상태에서 드래그하십시오. 루트 노드의 하위 자료는 모든 방향에서 끌어 올 수 있지만, 하위 노드는
+          루트와 관련된 상위 폴더 방향으로 만 이동 할 수 있습니다..
+      - action: 노드 이름 재설정
+        gesture: 더블 클릭 혹은 두번 선택하기
+      - action: 작업이 포함된 메뉴 표시
+        gesture: 노드를 마우스 오른쪽 버튼으로 클릭하거나 배경을 두 번 탭하거나 노드를 길게 누르기 (터치 장치)
+      - action: 노드 상위파일 변경
+        gesture: 다른 노드에 노드를 드래그 앤 드롭하기 (순환 삭제는 허용되지 않으므로 하위 노드 또는 하위 노드 중 한개의 노드를
+          삭제할 수 없습니다.)
+      - action: 작업 실행 혹은 다른 창에서 프로젝트 열기
+        gesture: Alt+클릭
+      title_key_shortcuts: 키보드 활용
+      title_mouse_shortcuts: 마우스 및 터치 활용
+      title_shortcuts: 단축키
+    info_all_saved: 모든 것이 성공적으로 저장되었습니다
+    info_any_failed: '몇몇 요청이 이행되지 않았습니다:'
+    info_no_permission: 이를 실행할 수 있는 허가가 없습니다
+    label_color_by: 으로 칠해진
+    label_go_to: 이동하기
+    label_or: 혹은
+    last_state_modal:
+      label_differencies: 서버의 차이점
+      message_changed: 다른 속성을 가지고 있습니다 (브라우저 => 서버) ({{changes}})
+      message_missing: '"{{from}}" 상위자료로 부터 없어짐'
+      message_moved: '"{{from}}" 가 아닌 "{{to}}" 의 하부내역입니다'
+      message_present: '"{{to}}" 의 하위자료로 있습니다'
+      text_reload_appeal: 서버에서 재로딩을 하시겠습니까?
+      title: WBS의 마지막 고객의 위치는 서버 위치와 다릅니다
+    reload_modal:
+      label_errors: 오류
+      text_reload_appeal: 저장되지 않은 항목들을 무시하고 서버로부터 데이터를 재로딩할까요?
+      title: WBS가 올바르게 저장되지 못했습니다
+    stored_modal:
+      text_load_appeal: 로딩하시겠습니까? 대신에 서버에서 새로운 상태를 로딩하시겠습니까?
+      title: 저장되지 않은 상태를 찾았습니다
+    warning_delete_node: 연결 {{name}}과 모든 하위 자료를 삭제하시겠습니까?
+    warning_not_saved: 올바르게 저장되지 않았습니다
+  heading_easy_wbs_issues: 쉬운 WBS
+  label_filter_group_easy_wbs_easy_issue_query: ìž‘ì—…
+  label_filter_group_easy_wbs:
+    easy_issue_query: ìž‘ì—…
+  project_default_page:
+    easy_wbs: 쉬운 WBS
diff --git a/plugins/easy_wbs/config/locales/mk.yml b/plugins/easy_wbs/config/locales/mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0ae10927d4e23c4b03326d1b5d63b519da6d0cae
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/mk.yml
@@ -0,0 +1,2 @@
+---
+mk: 
diff --git a/plugins/easy_wbs/config/locales/nl.yml b/plugins/easy_wbs/config/locales/nl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5d3da2ca736033b2f769214936ce647dbce713f8
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/nl.yml
@@ -0,0 +1,173 @@
+---
+nl:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Andere acties
+    button_add_child: Voeg kind toe
+    button_add_parent: Voeg ouder toe
+    button_add_sibling: Voeg broer/zus toe
+    button_all_icons: Iconen
+    button_collapse: Inklappen
+    button_collapse_all: Alle inklappen
+    button_cut: Knippen
+    button_display: Weergave instellingen
+    button_edit_data: Bewerk gegevens
+    button_expand: Uitvouwen
+    button_expand_all: Allen uitklappen
+    button_expand_collapse: Uitklappen/Inklappen
+    button_legend: Legenda
+    button_one_side: Een zijde
+    button_paste: Plakken
+    button_project_menu: Easy WBS
+    button_redo: Opnieuw
+    button_remove_node: Verwijder knooppunt
+    button_show_links: Toon links
+    button_undo: Ongedaan maken
+    edit_issue: Bewerk taak
+    error_create: kon niet aangemaakt worden
+    error_delete: kon niet verwijderd worden
+    error_update: kon niet bijgewerkt worden
+    errors:
+      not_subtaskable: Taak "%{task_name}" kan geen subtaak zijn door de instelling
+        van zijn tracker
+    free:
+      button_upgrade: Koop Volledige versie
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Kleur knooppunten op eigenschap
+      feature_context_menu: Wijzig eigenschappen knooppunt
+      feature_filtering: Filter knooppunten op eigenschap
+      header_not_available: Alleen beschikbaar in Volledige versie
+      text_not_available: is beschikbaar in de Volledige versie van Easy WBS.
+    hotkeys:
+      info_mac_metakey: Op Mac OSX kunnen Ctrl en Cmd gebruikt worden voor snelkoppelingen
+        met Ctrl hieronder - sommige browsers blokkeren bepaalde toetscombinaties.
+        Dus, bijvoorbeeld, als Cmd+Spatie niet werkt in uw browser, probeer dan Ctrl+Spatie.
+      keyboard:
+      - title: Knooppunt manipulatie
+        hotkeys:
+        - hotkey: Enter
+          info: Kind Toevoegen
+        - hotkey: Shift+Enter
+          info: Kind Toevoegen boven of breuklijn (bij hernoemen knooppunt)
+        - hotkey: Tab of Insert
+          info: Add child
+        - hotkey: Shift+Tab
+          info: Ouder Invoegen
+        - hotkey: Spatie
+          info: Kind Toevoegen
+        - hotkey: Shift+Spatie
+          info: Knooppunt data bewerken
+        - hotkey: Backspace of Delete
+          info: Verwijder Knooppunt
+        - hotkey: Ctrl+Up/Down
+          info: Verplaats knooppunt omhoog/omlaag
+      - title: Bewerken
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Opslaan
+        - hotkey: Ctrl+X of C
+          info: Knippen
+        - hotkey: Ctrl+C of Y
+          info: Kopieren
+        - hotkey: Ctrl+V of P
+          info: PLakken
+        - hotkey: U of Ctrl+Z
+          info: Ongedaan maken
+        - hotkey: R of Ctrl+Y of Ctrl+Shift+Z
+          info: Redo
+      - title: Selectie
+        hotkeys:
+        - hotkey: Pijltjestoetsen
+          info: Selecteer het knooppunt omhoog/omlaag/links/rechts van de huidige
+            geselecteerde
+        - hotkey: Shift + Pijltjestoetsen
+          info: Voeg knooppunt toe omhoog/omlaag/links/rechts van de selectie (handing
+            voor het selecteren van meerdere siblings)
+        - hotkey: "{"
+          info: Multi-select het huidige knooppunt en de volledige sub-boom eronder
+        - hotkey: "["
+          info: Multi-select alleen de sub-boom onder het huidige knooppunt (niet
+            het knooppunt zelf)
+        - hotkey: "="
+          info: Multi-select alle siblings van het huidige knooppunt (met dezelfde
+            ouder)
+        - hotkey: "."
+          info: Cancel multi-selection and select only the current node again
+        - hotkey: 1 - 9
+          info: Selecteer alle knooppunten op een bepaald niveau (dwz 1 selecteert
+            alle knooppunten van het eerste niveau)
+      - title: Navigatie en scherm
+        hotkeys:
+        - hotkey: "/ of F"
+          info: Knooppunt inklappen of uitklappen (vouw kinderen in of uit)
+        - hotkey: Ctrl + of Z
+          info: Zoom in
+        - hotkey: Ctrl - of Shift Z
+          info: Zoom uit
+        - hotkey: Esc, 0, Ctrl+0
+          info: Reset map weergave - selecteer basis knooppunt en breng het naar het
+            centrum van het scherm
+      mouse:
+      - action: Verplaats de kaart
+        gesture: klik en versleep het centrale knooppunt, klik en versleep de achtergrond
+          of scroll met trackpad/touchpad.
+      - action: Selecteer een knooppunt
+        gesture: tap of click erop
+      - action: Selecteer meerdere knooppunten
+        gesture: Shift+click
+      - action: Herschik knooppunten
+        gesture: versleep een knooppunt tussen zijn siblings, horizontaal dichtbij
+          de positie om te herschikken. Een zwarte pijl is zichtbaar bij het herschikken.
+      - action: Handmatig een knooppunt plaatsen
+        gesture: sleep een knooppunt tot de pijl voor het herschikken niet meer zichtbaar
+          is. Om handmatig te plaatsen  als de pijl zichtbaar is hou Shift ingedrukt
+          bij het slepen. Let op, kinderen van het basisknooppunt kunnen in elke richting
+          getrokken worden, maar knooppunten van een lagen niveau kunnen alleen geplaatst
+          worden in de richting van de ouders relatief aan de basis.
+      - action: Hernoem een knooppunt
+        gesture: Dubbelklik of dubbeltap erop
+      - action: Toon context menu met handelingen
+        gesture: 'Rechtsklikken op knooppunt (met muis) of dubbel-tap de achtergrand,
+          of hou het knooppunt lang ingedrukt: toon (op touch apparaten)'
+      - action: Wijzig knooppunt ouder
+        gesture: sleep en drop een knooppunt op een ander knooppunt (circelvormige
+          drops zijn niet toegestaan, dus een knooppunt kan niet op zijn kinderen
+          of afstammelingen geplaatst worden)
+      - action: Open kwestie of project in apart venster
+        gesture: Alt+click
+      title_key_shortcuts: Toetsenbord bediening
+      title_mouse_shortcuts: Muis en touch bedieningen
+      title_shortcuts: Snelkoppelingen
+    info_all_saved: Alles succesvol opgeslagen
+    info_any_failed: 'Sommige aanvragen zijn mislukt:'
+    info_no_permission: U heeft niet de benodigde toestemming om dit te doen
+    label_color_by: Kleur op
+    label_go_to: Ga naar
+    label_or: of
+    last_state_modal:
+      label_differencies: Verschillen op server
+      message_changed: heeft verschillende attributen (browser => server) ({{changes}})
+      message_missing: mist van ouder "{{from}}"
+      message_moved: is kind van "{{to}}", niet "{{from}}"
+      message_present: is present als kind van "{{to}}"
+      text_reload_appeal: Wilt u de staat opnieuw laden van de server
+      title: Laatste client status van WBS is anders dan de server status
+    reload_modal:
+      label_errors: Fouten
+      text_reload_appeal: Wilt u de niet opgeslagen items negeren en de data van de
+        server opnieuw laden?
+      title: WBS niet correct opgeslagen
+    stored_modal:
+      text_load_appeal: Wilt u het opnieuw laden? In plaats van de verse status van
+        de server?
+      title: Niet opgeslagen staat gevonden
+    warning_delete_node: Wilt u echt{{name}} en al zijn afhankelijkheden verwijderen?
+    warning_not_saved: is niet correct opgeslagen
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Taken
+  label_filter_group_easy_wbs:
+    easy_issue_query: Taken
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/no.yml b/plugins/easy_wbs/config/locales/no.yml
new file mode 100644
index 0000000000000000000000000000000000000000..38901c6667cbd0aed63c1f7b5bcb733803675b36
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/no.yml
@@ -0,0 +1,2 @@
+---
+'no': 
diff --git a/plugins/easy_wbs/config/locales/pl.yml b/plugins/easy_wbs/config/locales/pl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..35d8983c9accd1759c7fe5efbe3f75df0715abd9
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/pl.yml
@@ -0,0 +1,174 @@
+---
+pl:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Inne działania
+    button_add_child: Dodaj dziecko
+    button_add_parent: Dodaj rodzica
+    button_add_sibling: Dodaj rodzeństwo
+    button_all_icons: Ikony
+    button_collapse: Zwiń
+    button_collapse_all: Zwiń wszystko
+    button_cut: Przytnij
+    button_display: Wyświetl ustawienia
+    button_edit_data: Edytuj dane
+    button_expand: Rozwiń
+    button_expand_all: Rozwiń wszystko
+    button_expand_collapse: Rozwiń/Zwiń
+    button_legend: Legenda
+    button_one_side: Jedna strona
+    button_paste: Wklej
+    button_project_menu: Easy WBS
+    button_redo: Popraw
+    button_remove_node: Usuń węzeł
+    button_show_links: Pokaż linki
+    button_undo: Cofnij
+    edit_issue: Edytuj zadanie
+    error_create: nie mógł zostać utworzony
+    error_delete: nie mógł zostać usunięty
+    error_update: nie mógł zostać zaktualizowany
+    errors:
+      not_subtaskable: Zadanie "%{task_name}" nie może być podzadaniem ustawienia
+        swojego narzędzia śledzenia
+    free:
+      button_upgrade: Zdobądź pełną wersję
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Koloruj węzły wg właściwości
+      feature_context_menu: Zmienianie właściwości węzłów
+      feature_filtering: Filtruj węzły wg właściwości
+      header_not_available: Dostępny jedynie w pełnej wersji
+      text_not_available: jest dostępny jedynie w pełnej wersji Easy WBS.
+    hotkeys:
+      info_mac_metakey: Na Mac OSX, klucze Ctrl i Cmd mogą być użyte dla skrótów oznaczonych
+        poniżej Ctrl - niektóre przeglądarki zapobiegają łączeniu niektórych klawiszy.
+        Przykładowo, w ten sposób, jeśli Cmd+Space nie działa na twojej przeglądarce,
+        wypróbuj Ctrl+Space.
+      keyboard:
+      - title: Manipulacja węzłem
+        hotkeys:
+        - hotkey: Enter
+          info: Dodaj rodzeństwo
+        - hotkey: Shift+Enter
+          info: Dodaj rodzeństwo powyżej lub przy końcu linii (podczas zmiany nazwy
+            węzła)
+        - hotkey: Zakładka lub Wstaw
+          info: Dodaj rodzeństwo
+        - hotkey: Shift+Tab
+          info: Wstaw rodzica
+        - hotkey: Spacja
+          info: Zmień nazwę węzła
+        - hotkey: Shift+Space
+          info: Edytuj dane węzła
+        - hotkey: Backspace lub Delete
+          info: Usuń węzeł
+        - hotkey: Ctrl+Up/Down
+          info: Przesuń węzeł w górę/dół
+      - title: Edytowanie
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Zapisz
+        - hotkey: Ctrl+X lub C
+          info: Przytnij
+        - hotkey: Ctrl+C lub Y
+          info: Kopiuj
+        - hotkey: Ctrl+V lub P
+          info: Wklej
+        - hotkey: U lub Ctrl+Z
+          info: Cofnij
+        - hotkey: R lub Ctrl+Y lub Ctrl+Shift+Z
+          info: Napraw
+      - title: Wybór
+        hotkeys:
+        - hotkey: Klawisze strzałek
+          info: Wybierz węzeł w górę/dół/lewo/prawo obecnie wybranego
+        - hotkey: Shift + Klawisze strzałek
+          info: Dodaj węzeł w górę/dół/lewo/prawo do wyboru (użyteczne przy rodzeństwie
+            wielokrotnego wyboru)
+        - hotkey: "{"
+          info: Wielokrotny wybór obecnego węzła i całe pod drzewo pod nim
+        - hotkey: "["
+          info: Wielokrotny wybór jedynie drzewa pod obecnym węzłem (nie samym węzłem)
+        - hotkey: "="
+          info: Wielokrotny wybór całego rodzeństwa obecnego węzła (które posiada
+            tego samego rodzica)
+        - hotkey: "."
+          info: Anuluj wielokrotny wybór i wybierz ponownie jedynie obecny węzeł
+        - hotkey: 1 - 9
+          info: Wybierz wszystkie węzły konkretnego poziomu (np. 1 wybiera wszystkie
+            węzły pierwszego poziomu)
+      - title: Nawigacja i ekran
+        hotkeys:
+        - hotkey: "/ lub F"
+          info: Rozwiń lub zwiń węzeł (złóż lub rozłóż dzieci)
+        - hotkey: Ctrl + lub Z
+          info: Powiększ w
+        - hotkey: Ctrl - lub Shift Z
+          info: Pomniejsz
+        - hotkey: Esc, 0, Ctrl+0
+          info: Resetuj widok mapy – wybierz węzeł główny i przynieś go na środek
+            ekranu
+      mouse:
+      - action: Przesuń mapę
+        gesture: kliknij i przeciągnij środek węzła, kliknij i przeciągnij tło lub
+          przewiń trackpadem/touchpadem.
+      - action: Wybierz węzeł
+        gesture: stuknij lub kliknij go
+      - action: Wybierz wiele węzłów
+        gesture: Shift+click
+      - action: Zmień kolejność węzłów
+        gesture: przeciągnij węzeł pomiędzy jego rodzeństwem, zamknij poziomo do pozycji
+          ponownej zmiany kolejności. Podczas zmieniania kolejności pojawi się czarna
+          strzałka.
+      - action: Ręcznie ustaw pozycję węzła
+        gesture: przeciągnij węzeł dopóki nie pokazuje się punkt strzałki do zmiany
+          kolejności. Aby wymusić ręczną pozycję nawet gdy pokazuje się punkt zmiany
+          kolejności strzałki, przytrzymaj Shift podczas przeciągania. Zauważ, że
+          dzieci głównego węzła mogą zostać przeciągnięte w dowolnym kierunku, jednak
+          węzły niższego poziomu mogą zostać wypozycjonowane w kierunku swojego rodzica
+          do węzła.
+      - action: Zmień nazwę węzła
+        gesture: Dwa razy kliknij lub stuknij to
+      - action: Pokaż menu kontekstowe z operacjami
+        gesture: 'Kliknij prawym przyciskiem myszy na węzeł lub podwójnie stuknij
+          tło, bądź przyciśnij dłużej węzeł: pokaż (na urządzeniach dotykowych)'
+      - action: Zmień rodzica węzła
+        gesture: przeciągnij i opuść węzeł na inny węzeł (okrągłe krople nie są dozwolone,
+          więc nie możesz opuścić kropli na węzeł jednego z jego dzieci lub potomków)
+      - action: Otwórz problem lub projekt w oddzielnym oknie
+        gesture: Alt+click
+      title_key_shortcuts: Operacje klawiatury
+      title_mouse_shortcuts: Operacje myszki i touch pada
+      title_shortcuts: Skróty
+    info_all_saved: Wszystko pomyślnie zapisano
+    info_any_failed: 'Niektóre żądania nie powiodły się:'
+    info_no_permission: Nie masz wymaganych pozwoleń, aby to zrobić
+    label_color_by: Kolor wg
+    label_go_to: Idź do
+    label_or: lub
+    last_state_modal:
+      label_differencies: Różnice na serwerze
+      message_changed: posiada różne atrybuty (przeglądarka => serwer) ({{changes}})
+      message_missing: brakuje rodzica "{{from}}"
+      message_moved: jest dzieckiem "{{to}}", nie "{{from}}"
+      message_present: jest obecnie jako dziecko "{{to}}"
+      text_reload_appeal: Czy chcesz ponownie załadować stan z serwera?
+      title: Ostatni stan klienta WBS jest różny od stanu serwera
+    reload_modal:
+      label_errors: Błędy
+      text_reload_appeal: Czy chcesz zignorować niezapisane pozycje i ponownie załadować
+        dane z serwera?
+      title: Poprawne zapisanie WBS nie powiodło się
+    stored_modal:
+      text_load_appeal: Czy chcesz załadować to zamiast odświeżać stanu z serwera?
+      title: Znaleziono niezapisany stan
+    warning_delete_node: Czy na pewno chcesz usunąć węzeł {{name}} i wszystkich jego
+      potomków?
+    warning_not_saved: nie jest poprawnie zapisany
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Zadania
+  label_filter_group_easy_wbs:
+    easy_issue_query: Zadania
+  project_default_page:
+    easy_wbs: ''
diff --git a/plugins/easy_wbs/config/locales/pt-BR.yml b/plugins/easy_wbs/config/locales/pt-BR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..48634c39656b9b5cb22887f169c96d7b4697bc4c
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/pt-BR.yml
@@ -0,0 +1,174 @@
+---
+pt-BR:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: WBS Easy
+  easy_wbs:
+    button_actions: Outras ações
+    button_add_child: Adicionar criança
+    button_add_parent: Adicionar pai
+    button_add_sibling: Adicionar irmão
+    button_all_icons: Ícones
+    button_collapse: Colapsar
+    button_collapse_all: Colapsar tudo
+    button_cut: Cortar
+    button_display: Configurações de visualização
+    button_edit_data: Editar dados
+    button_expand: Expandir
+    button_expand_all: Expandir tudo
+    button_expand_collapse: Expandir/Colapso
+    button_legend: Legenda
+    button_one_side: Um lado
+    button_paste: Colar
+    button_project_menu: Easy WBS
+    button_redo: Refazer
+    button_remove_node: Remover nódulo
+    button_show_links: Mostrar links
+    button_undo: Desfazer
+    edit_issue: Editar tarefa
+    error_create: não pode ser criado
+    error_delete: não pode ser apagado
+    error_update: não pode ser atualizado
+    errors:
+      not_subtaskable: Tarefa "%{task_name}" não pode ser sub tarefa, devido à configuração
+        do seu rastreador
+    free:
+      button_upgrade: Obter versão completa
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Colorir nódulos por propriedade
+      feature_context_menu: Mudar propriedades de nódulo
+      feature_filtering: Filtrar nódulos por propriedades
+      header_not_available: Disponível apenas na versão completa
+      text_not_available: está disponível na versão completa de Easy WBS.
+    hotkeys:
+      info_mac_metakey: Em Mac OSX, teclas Ctrl e Cmd pode ser usado para atalhos
+        marcados Ctrl em baixo - alguns browsers evitar certas combinações de teclas.
+        Então, por exemplo, se Cmd+Space não funcionar no browser, tente Ctrl+Space.
+      keyboard:
+      - title: Manipulação de nódulos
+        hotkeys:
+        - hotkey: Enter
+          info: Adicionar irmão
+        - hotkey: Shift+Enter
+          info: Adicionar irmão ou mudança de linha (quando renomear nódulo)
+        - hotkey: Guia ou Inserir
+          info: Adicionar criança
+        - hotkey: Shift+Guia
+          info: Inserir pai
+        - hotkey: Espaço
+          info: Renomear nódulo
+        - hotkey: Shift+Espaço
+          info: Editar informação de nódulos
+        - hotkey: Espaço ou Apagar
+          info: Retirar nódulo
+        - hotkey: Ctrl+Seta para Cima/Baixo
+          info: Deslocar nódulo para Cima/Baixo
+      - title: Editando
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Salvar
+        - hotkey: Ctrl+X ou C
+          info: Cortar
+        - hotkey: Ctrl+C ou Y
+          info: Copiar
+        - hotkey: Ctrl+V ou P
+          info: Colar
+        - hotkey: U ou Ctrl+Z
+          info: Desfazer
+        - hotkey: R ou Ctrl+Y ou Ctrl+Shift+Z
+          info: Refazer
+      - title: Seleção
+        hotkeys:
+        - hotkey: Teclas Seta
+          info: Selecionar o nódulo cima/baixo/esquerda/direita do que está atualmente
+            selecionado
+        - hotkey: Shift + Teclas Seta
+          info: Adicionar nódulo cima/baixo/esquerda/direita para seleção (útil para
+            irmãos multi-selecionados)
+        - hotkey: "{"
+          info: 'Multi-selecione o nódulo atual e toda subárvore sob ele '
+        - hotkey: "["
+          info: Multi-selecione somente a  subárvore sob o nódulo atual (e não o próprio
+            nódulo)
+        - hotkey: "="
+          info: Multi-selecione todos os irmãos do nódulo atual (que tem o mesmo pai)
+        - hotkey: "."
+          info: Cancelar multi-seleção e selecionar somente o nódulo atual novamente
+        - hotkey: 1 - 9
+          info: 'Selecionar todos os nódulos de um nível específico (por exemplo:
+            1 seleciona todos os nódulos do primeiro nível)'
+      - title: Navegação e tela
+        hotkeys:
+        - hotkey: "/ ou F"
+          info: Expandir ou contrair nódulo (dobrar ou desdobrar crianças)
+        - hotkey: Ctrl + ou Z
+          info: Fazer zoom
+        - hotkey: Ctrl - ou Shift Z
+          info: Diminuir
+        - hotkey: Esc, 0, Ctrl+0
+          info: Redefinir vista de mapa - selecionar nódulo raíz e trazê-lo para o
+            centro da tela
+      mouse:
+      - action: Deslocar o mapa
+        gesture: clique e arraste o nódulo central, clique e arraste o fundo ou desloque
+          com touchpad/teclado tátil.
+      - action: Selecionar um nódulo
+        gesture: toque ou clique sobre ele
+      - action: Selecionar nódulos múltiplos
+        gesture: Shift+clique
+      - action: Reordenar nódulos
+        gesture: arraste um nódulo entre os da mesma família, na horizontal perto
+          da posição da reordenação. Uma seta preta vai mostrar quando reordenar.
+      - action: Posicione manualmente um nódulo
+        gesture: apenas arraste o nódulo até a ponta da flecha para a reordenação
+          não estiver a mostra. Para forçar manualmente a posição, mesmo quando a
+          ponta da flecha de reordenação esteja a mostra, segure a tecla Shift ao
+          arrastar. Por favor note que filhos do nódulo raiz podem ser arrastados
+          em qualquer direção, mas nódulos de nível inferior só podem ser posicionados
+          na direção do seu pai em relação à raiz.
+      - action: Renomear um nódulo
+        gesture: Clique ou toque duas vezes
+      - action: Mostrar menu de contexto com operações
+        gesture: 'Clique com o botão direito em um nódulo (com o mouse) ou toque duas
+          vezes no fundo, ou pressione por muito tempo um nódulo: mostra (em dispositivos
+          sensíveis ao toque)'
+      - action: Mudar nódulo pai
+        gesture: arraste e solte um nodulo em outro nódulo (movimentos circulares
+          não são permitidos, para que você não possa deixar cair um nódulo em um
+          de seus filhos ou descendentes)
+      - action: Questão em aberto ou projeto em janela separada
+        gesture: Alt+clique
+      title_key_shortcuts: Operações de teclado
+      title_mouse_shortcuts: Operações de mouse e toque
+      title_shortcuts: Atalhos
+    info_all_saved: Tudo foi salvo com êxito
+    info_any_failed: 'Algumas das solicitações falharam:'
+    info_no_permission: Você não tem as permissões necessárias para fazer isso
+    label_color_by: Colorir por
+    label_go_to: Ir a
+    label_or: ou
+    last_state_modal:
+      label_differencies: Diferenças no servidor
+      message_changed: têm diferentes atributos (browser => servidor) ({{changes}})
+      message_missing: está ausente do pai "{{from}}"
+      message_moved: é criança de "{{to}}", não "{{from}}"
+      message_present: no momento é criança de "{{to}}"
+      text_reload_appeal: Você quer recarregar estado desdo servidor?
+      title: Último cliente de WBS é diferente do estado do servidor
+    reload_modal:
+      label_errors: Erros
+      text_reload_appeal: Você quer ignorar itens que não foram salvos e recarregar
+        os dados do servidor?
+      title: WBS não foi salvo corretamente
+    stored_modal:
+      text_load_appeal: Você quer carregá-lo? Em vez do estado recente a partir do
+        servidor?
+      title: Estado  não salvos foram encontrados
+    warning_delete_node: Deseja mesmo apagar o nó {{name}} e todos os seus descendentes?
+    warning_not_saved: não foi salvado corretamente
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Tarefas
+  label_filter_group_easy_wbs:
+    easy_issue_query: Tarefas
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/pt.yml b/plugins/easy_wbs/config/locales/pt.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e40c4851af3aac216594db949527057b830ec1cf
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/pt.yml
@@ -0,0 +1,2 @@
+---
+pt: 
diff --git a/plugins/easy_wbs/config/locales/ro.yml b/plugins/easy_wbs/config/locales/ro.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0df6ab337b3567ead7b98b001c0b5e99e2584d25
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/ro.yml
@@ -0,0 +1,2 @@
+---
+ro: 
diff --git a/plugins/easy_wbs/config/locales/ru.yml b/plugins/easy_wbs/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..00e24ca641e012aee3bb0a97a719586f97ad2668
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/ru.yml
@@ -0,0 +1,172 @@
+---
+ru:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: Другие действия
+    button_add_child: Добавить дочерний элемент
+    button_add_parent: добавить родительский элемент
+    button_add_sibling: Добавить одноуровневый элемент
+    button_all_icons: Icons
+    button_collapse: Свернуть
+    button_collapse_all: Свернуть все
+    button_cut: Вырезать
+    button_display: Настройки
+    button_edit_data: Редактировать данные
+    button_expand: Развернуть
+    button_expand_all: Развернуть все
+    button_expand_collapse: Развернуть/Свернуть
+    button_legend: Легенда
+    button_one_side: одна сторона
+    button_paste: Вставить
+    button_project_menu: Easy WBS
+    button_redo: Переделать
+    button_remove_node: Удалить узел
+    button_show_links: Показать ссылки
+    button_undo: Отмена
+    edit_issue: Редактировать задачу
+    error_create: не может быть создан
+    error_delete: не может быть удален
+    error_update: не может быть обновлен
+    errors:
+      not_subtaskable: Задача "%{task_name}" не может быть подзадачей из-за настройки
+        трекера
+    free:
+      button_upgrade: Получить Полную версию
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: Цвет узла
+      feature_context_menu: Изменение свойств узла
+      feature_filtering: Фильтрация узлов
+      header_not_available: Доступно только в Полной версии
+      text_not_available: доступна в полной версии Easy WBS
+    hotkeys:
+      info_mac_metakey: На Mac OSX, Ctrl и клавиши Cmd могут быть использованы для
+        ярлыков отмеченные Ctrl - иногда браузеры блокируют некоторые комбинации клавиш.
+        Так, например, если Cmd + Space не работает в вашем браузере, попробуйте Ctrl
+        + Space.
+      keyboard:
+      - title: Манипуляция c узлом
+        hotkeys:
+        - hotkey: Enter
+          info: Добавить одноуровневый элемент
+        - hotkey: Shift+Enter
+          info: Добавить одноуровневый элемент выше или разрыв (при переименовании
+            узла)
+        - hotkey: Tab or Insert
+          info: Добавить дочерний элемент
+        - hotkey: Shift+Tab
+          info: Вставить родителя
+        - hotkey: Space
+          info: Переименовать узел
+        - hotkey: Shift+Space
+          info: Редактирование данных узла
+        - hotkey: Backspace or Delete
+          info: Удалить узел
+        - hotkey: Ctrl+Up/Down
+          info: Переместить узел вверх/вниз
+      - title: Редактирование
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: Сохранить
+        - hotkey: Ctrl+X or C
+          info: Вырезать
+        - hotkey: Ctrl+C or Y
+          info: Копировать
+        - hotkey: Ctrl+V or P
+          info: Вставить
+        - hotkey: U or Ctrl+Z
+          info: Отменить
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: Восстановить
+      - title: Выбор
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: 'Выберете узел вверх/вниз/влево/вправо для выбора нужного '
+        - hotkey: Shift + Arrow keys
+          info: 'Добавление узла выше/ниже/слева/справа от выбранного (удобно при
+            выборе нескольких одноуровневых элементов) '
+        - hotkey: "{"
+          info: Выбор текущего узла и всего его поддерева
+        - hotkey: "["
+          info: Мульти-выбор только поддерева текущего узла (не самого узла)
+        - hotkey: "="
+          info: Выбор нескольких или всех одноуровневых элементов текущего узла (у
+            которых один и тот же родитель)
+        - hotkey: "."
+          info: Отмена мульти-выбора и выбор снова текущего узла
+        - hotkey: 1 - 9
+          info: Выбрать все узлы определенного уровня (например, 1, выбор всех узлов
+            первого уровня)
+      - title: 'Навигация и масштабирование '
+        hotkeys:
+        - hotkey: "/ or F"
+          info: Развернуть или свернуть узел (свернуть или развернуть дочерние элементы)
+        - hotkey: Ctrl + or Z
+          info: 'Увеличить масштаб '
+        - hotkey: Ctrl - or Shift Z
+          info: Уменьшить масштаб
+        - hotkey: Esc, 0, Ctrl+0
+          info: 'Сброс настроек карты – выбор корневого узла и перемещение его в центр
+            экрана '
+      mouse:
+      - action: Переместить карту
+        gesture: 'кликните на центральный узел и перетащите его, кликните на фон и
+          переместите его при помощи мышки или на тачпаде. '
+      - action: Выберете узел
+        gesture: коснитесь или кликните по нему
+      - action: 'Выбор нескольких узлов '
+        gesture: Shift+click
+      - action: Перемещение узлов
+        gesture: выберете узел и перемещайте его между одноуровневыми элементами,
+          переместите его ближе к нужной позиции. Черная стрелка поможет вам при смене
+          положения узла.
+      - action: Перемещение узла в ручную
+        gesture: Просто перетаскиваете узел пока не появиться черная стрелка. Для
+          принудительной смены позиции, даже когда появляется черная стрелка нажмите
+          Shift. Обратите внимание, что дочерние элементы основного узла можно перемещать
+          в любом направлении, а более низкого узлы располагаются только в одном направлении
+          относительно своего родителя.
+      - action: 'Переименование узла '
+        gesture: дважды кликните на узел
+      - action: Показать контекстное меню с операциями
+        gesture: Кликните Правой кнопкой мыши по узлу или двойное кликание по фону
+          узла, или долгое нажатие (на сенсорных устройствах)
+      - action: Изменение родителя у узла
+        gesture: притащите узел на другой узел (circular drops are not allowed, so
+          you can't drop a node on one of it's children or descendants)
+      - action: 'Открытие задачи в отдельном окне '
+        gesture: Alt+click
+      title_key_shortcuts: Работа с  клавиатурой
+      title_mouse_shortcuts: Мышь и операции с тачскрином
+      title_shortcuts: Клавиши быстрого доступа
+    info_all_saved: Все успешно сохранено
+    info_any_failed: Некоторые запросы отменены
+    info_no_permission: У вас нет необходимых прав делать это
+    label_color_by: 'Цвет по:'
+    label_go_to: Переход к
+    label_or: или
+    last_state_modal:
+      label_differencies: Различия на сервере
+      message_changed: имеют разные атрибуты (браузер => сервер) ({{changes}}
+      message_missing: отсутствует от родительского "{{from}}"
+      message_moved: является дочерним к "{{to}}", а не "{{from}}"
+      message_present: присутствует как дочерний к "{{to}}"
+      text_reload_appeal: Вы хотите перезагрузить состояние с сервера?
+      title: Последнее состояние клиента  WBS отличается от состояния сервера
+    reload_modal:
+      label_errors: Ошибки
+      text_reload_appeal: Вы хотите игнорировать несохраненные элементы и загрузить
+        данные с сервера?
+      title: WBS не удалось сохранить должным образом
+    stored_modal:
+      text_load_appeal: Хотите загрузить это? Вместо новой версии с сервера?
+      title: Было найдено несохраненное состояние
+    warning_delete_node: Вы действительно хотите удалить узел {{name}} и все его связи?
+    warning_not_saved: не сохранен должным образом
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: Задачи
+  label_filter_group_easy_wbs:
+    easy_issue_query: Задачи
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/locales/sk.yml b/plugins/easy_wbs/config/locales/sk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..354e579f4c849b3d5e6f02dbb1142251de534b0f
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sk.yml
@@ -0,0 +1,2 @@
+---
+sk: 
diff --git a/plugins/easy_wbs/config/locales/sl.yml b/plugins/easy_wbs/config/locales/sl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..31bb26cc3f5eecb9e78c89d8ee46f4f7c6d93bd3
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sl.yml
@@ -0,0 +1,2 @@
+---
+sl: 
diff --git a/plugins/easy_wbs/config/locales/sq.yml b/plugins/easy_wbs/config/locales/sq.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9c092f0a65e3c87d0e93a80f699335ac29e94852
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sq.yml
@@ -0,0 +1,2 @@
+---
+sq: 
diff --git a/plugins/easy_wbs/config/locales/sr-YU.yml b/plugins/easy_wbs/config/locales/sr-YU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..24e179627aca2606c215e215a21b980ce59dfb96
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sr-YU.yml
@@ -0,0 +1,2 @@
+---
+sr-YU: 
diff --git a/plugins/easy_wbs/config/locales/sr.yml b/plugins/easy_wbs/config/locales/sr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..43a10141ba3e960e043e9dda2aa7785a8ef3da4b
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sr.yml
@@ -0,0 +1,2 @@
+---
+sr: 
diff --git a/plugins/easy_wbs/config/locales/sv.yml b/plugins/easy_wbs/config/locales/sv.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed425eabde8cdb1194378aac15a95f05cb156df7
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/sv.yml
@@ -0,0 +1,2 @@
+---
+sv: 
diff --git a/plugins/easy_wbs/config/locales/th.yml b/plugins/easy_wbs/config/locales/th.yml
new file mode 100644
index 0000000000000000000000000000000000000000..35969140c57bbc57ca621c97098aa6ba6fff96dc
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/th.yml
@@ -0,0 +1,2 @@
+---
+th: 
diff --git a/plugins/easy_wbs/config/locales/tr.yml b/plugins/easy_wbs/config/locales/tr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3be79e79e8e62a69dcbe1e94764bb4f52a409ff4
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/tr.yml
@@ -0,0 +1,2 @@
+---
+tr: 
diff --git a/plugins/easy_wbs/config/locales/zh-TW.yml b/plugins/easy_wbs/config/locales/zh-TW.yml
new file mode 100644
index 0000000000000000000000000000000000000000..44cf0f8d49e34cec16d3ed63ef44e1699d8b9b7f
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/zh-TW.yml
@@ -0,0 +1,2 @@
+---
+zh-TW: 
diff --git a/plugins/easy_wbs/config/locales/zh.yml b/plugins/easy_wbs/config/locales/zh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..26224119533369c3b50dff4f100a1e3d883a49b7
--- /dev/null
+++ b/plugins/easy_wbs/config/locales/zh.yml
@@ -0,0 +1,155 @@
+---
+zh:
+  easy_query:
+    name:
+      easy_wbs_easy_issue_query: Easy WBS
+  easy_wbs:
+    button_actions: 其它操作
+    button_add_child: 新增子项
+    button_add_parent: 新增父项
+    button_add_sibling: 新建同级项(同一父项)
+    button_all_icons: 图标
+    button_collapse: 折叠
+    button_collapse_all: 全部折叠
+    button_cut: 剪切
+    button_display: 显示设置
+    button_edit_data: 修改资料
+    button_expand: 展开
+    button_expand_all: 全部展开
+    button_expand_collapse: 展开/ 折叠
+    button_legend: 图例
+    button_one_side: 一侧
+    button_paste: 粘贴
+    button_project_menu: Easy WBS
+    button_redo: 重做
+    button_remove_node: 删除节点
+    button_show_links: 显示连接关系
+    button_undo: 撤消
+    edit_issue: 编辑任务
+    error_create: 无法创建
+    error_delete: 无法删除
+    error_update: 无法更新
+    errors:
+      not_subtaskable: 由于跟踪器的设置, 任务"%{task_name}" 无法为其创建子任务.
+    free:
+      button_upgrade: 获取完全版
+      button_upgrade_href: https://www.easyredmine.com/redmine-wbs-plugin
+      feature_coloring: 按性质着色节点
+      feature_context_menu: 变更节点性质
+      feature_filtering: 按性质分类的过滤器节点
+      header_not_available: 仅在完全版中可用
+      text_not_available: 在Easy WBS完全版中可用
+    hotkeys:
+      info_mac_metakey: 在 Mac OSX系统, Ctrl 和 Cmd 键可以用于被浏览器屏蔽的一些CTRL键组合,  如, 在 Cmd+Space
+        被浏览器禁止时, 使用 Ctrl+Space.
+      keyboard:
+      - title: 结点操作
+        hotkeys:
+        - hotkey: Enter
+          info: 添加同级成员
+        - hotkey: Shift+Enter
+          info: 添加成员或换行(当重命名结点时)
+        - hotkey: Tab or Insert
+          info: 添加子项
+        - hotkey: Shift+Tab
+          info: 插入父项
+        - hotkey: Space
+          info: 重命名结点
+        - hotkey: Shift+Space
+          info: 编辑结点资料
+        - hotkey: Backspace or Delete
+          info: 移除结点
+        - hotkey: Ctrl+Up/Down
+          info: 上下移动结点
+      - title: 编辑
+        hotkeys:
+        - hotkey: Ctrl+S
+          info: 保存
+        - hotkey: Ctrl+X or C
+          info: 剪切
+        - hotkey: Ctrl+C or Y
+          info: 复制
+        - hotkey: Ctrl+V or P
+          info: 粘贴
+        - hotkey: U or Ctrl+Z
+          info: 撤消
+        - hotkey: R or Ctrl+Y or Ctrl+Shift+Z
+          info: 恢复
+      - title: 选择
+        hotkeys:
+        - hotkey: Arrow Keys
+          info: 上下左右移动当前选定的结点
+        - hotkey: Shift + Arrow keys
+          info: 在选定结点的上/下/左/右方添加一个新的结点(选定多个兄弟结点时特别有用)
+        - hotkey: "{"
+          info: 多选 -选定当前结点和其下方整个子树形上的成员
+        - hotkey: "["
+          info: 多选 -选定当前结点下方整个子树形上的成员 (不包括自身)
+        - hotkey: "="
+          info: 多选 -选定当前结点同一父项下所有同级成员
+        - hotkey: "."
+          info: 取消多选, 只选择当前结点自身
+        - hotkey: 1 - 9
+          info: 选择一个特别层级的所有结点(如1 表示选定所有1级的结点)
+      - title: 导航与视图
+        hotkeys:
+        - hotkey: "/ or F"
+          info: 展开或折叠结点(收起或打开其子项)
+        - hotkey: Ctrl + or Z
+          info: 放大
+        - hotkey: Ctrl - or Shift Z
+          info: 缩小
+        - hotkey: Esc, 0, Ctrl+0
+          info: 重置节点地图设置- 选定根结点并让其处于屏幕中心
+      mouse:
+      - action: 移动树形图
+        gesture: " 点并拖动中心结点, 点击并拖动背景或滚动方向条/触摸板."
+      - action: 选择结点
+        gesture: 点击此处
+      - action: 选择多个结点
+        gesture: Shift+click
+      - action: 重排结点
+        gesture: 拖动结点, 将其放在水平方向其重排后的大致位置. 重排时将会出现一个黑色的箭头.
+      - action: 手动放置结点
+        gesture: 拖动结点直到重排时的箭头消失. 如要在箭头未消失时强行放置结点, 须在拖动的同时 按住 Shift. 注意根结点下一级子结点可以在拖向任何方向,
+          但低级的子结点只能在其上级所关联的范围内拖动.
+      - action: 重命名结点
+        gesture: 双击结点
+      - action: 显示带操作的菜单环境
+        gesture: '右击结点 (用鼠标) 或双击背景, 或长按结点: 显示 (在触摸设备上)'
+      - action: 变更父结点
+        gesture: 将结点拖放到另一个结点 (不允许出偱环, 所以不允许将其放到其子结点或继承者上)
+      - action: 在另外的窗口打开项目或问题
+        gesture: Alt+左击
+      title_key_shortcuts: 键盘操作
+      title_mouse_shortcuts: 鼠标操作
+      title_shortcuts: 快捷键及操作一览表
+    info_all_saved: 已全部保存成功
+    info_any_failed: '其中一些请求失败:'
+    info_no_permission: 你没有足够的权限进行此操作
+    label_color_by: 按颜色
+    label_go_to: 转到
+    label_or: 或
+    last_state_modal:
+      label_differencies: 服务器上的差异
+      message_changed: 有不同的属性 (browser => server) ({{changes}})
+      message_missing: 从父项 "{{from}}"中丢失
+      message_moved: 是"{{to}}"的子项, 而不是"{{from}}"
+      message_present: 代表 "{{to}}"的子项
+      text_reload_appeal: 重新从服务器下载国别(状态)?
+      title: WBS中最新的客户国别(状态)与服务器中的不同
+    reload_modal:
+      label_errors: 错误
+      text_reload_appeal: 忽略未保存项目并从服务器重新下载数据?
+      title: WBS 没能完全保存
+    stored_modal:
+      text_load_appeal: 重新下载, 而不是从服务器刷新国别(状态)?
+      title: 不能找到未保存国别(状态)
+    warning_delete_node: 你真的想要删除节点 {{name}} 和这个节点下 全部子项?
+    warning_not_saved: 没有完全保存
+  heading_easy_wbs_issues: Easy WBS
+  label_filter_group_easy_wbs_easy_issue_query: 任务
+  label_filter_group_easy_wbs:
+    easy_issue_query: 任务
+  project_default_page:
+    easy_wbs: Easy WBS
diff --git a/plugins/easy_wbs/config/routes.rb b/plugins/easy_wbs/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e35baee88d9889b16456e1d5c2c1c22ffcbf6687
--- /dev/null
+++ b/plugins/easy_wbs/config/routes.rb
@@ -0,0 +1,6 @@
+# Because of plugin deactivations
+if Redmine::Plugin.installed?(:easy_wbs)
+  resources :projects do
+    get 'easy_wbs', to: 'easy_wbs#index', as: 'easy_wbs_index'
+  end
+end
diff --git a/plugins/easy_wbs/init.rb b/plugins/easy_wbs/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a7a019552869f9aa99fd2b3a026f9445d5640cdd
--- /dev/null
+++ b/plugins/easy_wbs/init.rb
@@ -0,0 +1,19 @@
+Redmine::Plugin.register :easy_wbs do
+  name 'Easy WBS plugin'
+  author 'Easy Software Ltd'
+  description 'new WBS tree hierarchy generator'
+  version '1.9'
+  url 'www.easyredmine.com'
+  author_url 'www.easysoftware.cz'
+
+  requires_redmine_plugin :easy_mindmup, version_or_higher: '1.4'
+
+  if Redmine::Plugin.installed?(:easy_extensions)
+    depends_on [:easy_mindmup]
+  end
+
+end
+
+unless Redmine::Plugin.installed?(:easy_extensions)
+  require_relative 'after_init'
+end
diff --git a/plugins/easy_wbs/lib/easy_wbs/easy_wbs.rb b/plugins/easy_wbs/lib/easy_wbs/easy_wbs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7acb65781eee193c72056670b9f64293930b31c1
--- /dev/null
+++ b/plugins/easy_wbs/lib/easy_wbs/easy_wbs.rb
@@ -0,0 +1,7 @@
+module EasyWbs
+
+  def self.easy_extensions?
+    Redmine::Plugin.installed?(:easy_extensions)
+  end
+
+end
diff --git a/plugins/easy_wbs/lib/easy_wbs/hooks.rb b/plugins/easy_wbs/lib/easy_wbs/hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4265885a6657eb36b7276408e9eac1500b55b718
--- /dev/null
+++ b/plugins/easy_wbs/lib/easy_wbs/hooks.rb
@@ -0,0 +1,9 @@
+module EasyWbs
+  class Hooks < Redmine::Hook::ViewListener
+
+    def helper_options_for_default_project_page(context={})
+      context[:default_pages] << 'easy_wbs' if context[:enabled_modules].include?('easy_wbs')
+    end
+
+  end
+end
diff --git a/plugins/easy_wbs/lib/easy_wbs/redmine_patch/controllers/queries_controller_patch.rb b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/controllers/queries_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c75d86307b20175eeb7296723946bfe67b42071
--- /dev/null
+++ b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/controllers/queries_controller_patch.rb
@@ -0,0 +1,32 @@
+module EasyWbs
+  module QueriesControllerPatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+        if method_defined?(:query_class) || private_method_defined?(:query_class)
+          alias_method_chain :query_class, :easy_wbs
+        end
+      end
+    end
+
+    module InstanceMethods
+
+      # Redmine return only direct sublasses but
+      # Wbs query inherit from IssueQuery
+      def query_class_with_easy_wbs
+        case params[:type]
+        when 'EasyWbs::IssueQuery'
+          EasyWbs::IssueQuery
+        else
+          query_class_without_easy_wbs
+        end
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_controller_patch 'QueriesController', 'EasyWbs::QueriesControllerPatch'
diff --git a/plugins/easy_wbs/lib/easy_wbs/redmine_patch/helpers/application_helper_patch.rb b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/helpers/application_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e0ab00fe10fd6103aff8794c6c456f11e76867ae
--- /dev/null
+++ b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/helpers/application_helper_patch.rb
@@ -0,0 +1,26 @@
+module EasyWbs
+  module ApplicationHelperPatch
+
+    def self.included(base)
+      base.extend(ClassMethods)
+      base.send(:include, InstanceMethods)
+
+      base.class_eval do
+
+        def link_to_project_with_easy_wbs(project, options = {})
+          { controller: 'easy_wbs', action: 'index', project_id: project }
+        end
+
+      end
+    end
+
+    module InstanceMethods
+    end
+
+    module ClassMethods
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_helper_patch 'ApplicationHelper', 'EasyWbs::ApplicationHelperPatch'
diff --git a/plugins/easy_wbs/lib/easy_wbs/redmine_patch/models/project_patch.rb b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/models/project_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86a82e6b3e6ca608e68150c742bb23253d06a1a7
--- /dev/null
+++ b/plugins/easy_wbs/lib/easy_wbs/redmine_patch/models/project_patch.rb
@@ -0,0 +1,36 @@
+module EasyWbs
+  module ProjectPatch
+
+    def self.included(base)
+      base.send(:include, InstanceMethods) unless base.respond_to?(:assignable_users_including_all_subprojects)
+
+      base.class_eval do
+
+
+      end
+
+    end
+
+    module InstanceMethods
+
+      def assignable_users_including_all_subprojects
+        if Setting.display_subprojects_issues?
+          types = ['User']
+          types << 'Group' if Setting.issue_group_assignment?
+
+          @assignable_users_including_all_subprojects ||= Principal.
+            active.
+            joins(:members => :roles).
+            where(:type => types, :roles => {:assignable => true}).
+            where(:members => {:project_id => Project.where("lft >= ? AND rgt <= ?", lft, rgt)}).distinct.sorted
+        else
+          assignable_users
+        end
+      end
+
+    end
+
+  end
+end
+
+RedmineExtensions::PatchManager.register_model_patch 'Project', 'EasyWbs::ProjectPatch'
diff --git a/plugins/easy_wbs/spec/controllers/easy_wbs_controller_spec.rb b/plugins/easy_wbs/spec/controllers/easy_wbs_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1188d7fc39c101e3bf3b4f8a4cca70bdaa30d2ba
--- /dev/null
+++ b/plugins/easy_wbs/spec/controllers/easy_wbs_controller_spec.rb
@@ -0,0 +1,19 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.describe EasyWbsController, logged: :admin do
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  context 'rest api' do
+    let(:project) { FactoryGirl.create(:project, add_modules: ['easy_wbs']) }
+
+    it 'enabled' do
+      get :index, project_id: project
+      expect(response).to be_success
+    end
+  end
+
+end
+
diff --git a/plugins/easy_wbs/spec/features/jasmine_spec.rb b/plugins/easy_wbs/spec/features/jasmine_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2ba6df83d017d513327bf0c0cf0431b82064f8bb
--- /dev/null
+++ b/plugins/easy_wbs/spec/features/jasmine_spec.rb
@@ -0,0 +1,31 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'Jasmine', logged: :admin, js: true, js_wait: true do
+
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 3)
+  }
+  let(:subproject) {
+    FactoryGirl.create(:project, parent_id: superproject.id, number_of_issues: 3)
+  }
+  let(:subissues) {
+    FactoryGirl.create_list(:issue, 3, parent_issue_id: superproject.issues[0].id, project_id: superproject.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) {
+      with_easy_settings(easy_wbs_no_sidebar: 1) { example.run }
+    }
+  end
+
+  describe 'WBS' do
+    it 'should not fail' do
+      visit project_easy_wbs_index_path(superproject, run_jasmine_tests: true)
+      wait_for_ajax
+      expect(page).to have_css('.jasmine-bar')
+      result = page.evaluate_script('jasmine.ysyInstance.tests.parseResult();')
+      expect(result).to eq('success')
+    end
+  end
+
+end
diff --git a/plugins/easy_wbs/spec/features/wbs_legend_spec.rb b/plugins/easy_wbs/spec/features/wbs_legend_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0479c49f1f7763bf87728bbd5a2ba887102e9255
--- /dev/null
+++ b/plugins/easy_wbs/spec/features/wbs_legend_spec.rb
@@ -0,0 +1,59 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'legend', logged: :admin, js: true do
+
+  let(:project_1) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'])
+  }
+  let(:priority_A) {
+    FactoryGirl.create(:issue_priority, name: 'Priority A')
+  }
+  let(:priority_B) {
+    FactoryGirl.create(:issue_priority, name: 'Priority B')
+  }
+  let(:priority_C) {
+    FactoryGirl.create(:issue_priority, name: 'Priority C')
+  }
+  let(:priority_D) {
+    FactoryGirl.create(:issue_priority, name: 'Priority D')
+  }
+  let(:issue_1) {
+    FactoryGirl.create(:issue, project_id: project_1.id, priority_id: priority_A.id)
+  }
+  let(:issue_2) {
+    FactoryGirl.create(:issue, project_id: project_1.id, priority_id: priority_B.id)
+  }
+  let(:issue_3) {
+    FactoryGirl.create(:issue, project_id: project_1.id, priority_id: priority_C.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) {
+      with_easy_settings(easy_wbs_no_sidebar: 1) { example.run }
+    }
+  end
+
+  it 'should show legend filled with priorities' do
+    issue_1
+    issue_2
+    issue_3
+    priority_D
+    visit project_easy_wbs_index_path(project_1)
+    wait_for_ajax
+    expect(page).to have_text(project_1.name)
+    page.find('.mindmup-color-select').first(:option, I18n.t(:field_priority)).select_option
+    legend = page.find('.mindmup-legend')
+    expect(legend).to have_text('Priority A')
+    expect(legend).to have_text('Priority B')
+    expect(legend).to have_text('Priority C')
+
+    # TODO: All priorities all visible
+    #
+    # expect(legend).not_to have_text('Priority D')
+    # within(legend) do
+    #   click_link(I18n.t(:label_more))
+    # end
+
+    expect(legend).to have_text('Priority D')
+  end
+end
diff --git a/plugins/easy_wbs/spec/features/wbs_no_sidebar_spec.rb b/plugins/easy_wbs/spec/features/wbs_no_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e834ada21003f6747d23351d68129b6d4bc65da1
--- /dev/null
+++ b/plugins/easy_wbs/spec/features/wbs_no_sidebar_spec.rb
@@ -0,0 +1,22 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'sidebar', logged: :admin, js: true do
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 3)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) {
+      with_easy_settings(easy_wbs_no_sidebar: 1) { example.run }
+    }
+  end
+
+  it 'should be missing' do
+    visit project_easy_wbs_index_path(superproject)
+    wait_for_ajax
+    within('#container') do
+      expect(page).to have_text(superproject.name)
+    end
+    expect(page).not_to have_css('.mindmup-sidebar__container')
+  end
+end
diff --git a/plugins/easy_wbs/spec/features/wbs_sidebar_spec.rb b/plugins/easy_wbs/spec/features/wbs_sidebar_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..35eedd33f616f7d1c92875ef5ae57231849873aa
--- /dev/null
+++ b/plugins/easy_wbs/spec/features/wbs_sidebar_spec.rb
@@ -0,0 +1,84 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'sidebar', logged: :admin, js: true do
+  let(:priority_A) {
+    FactoryGirl.create(:issue_priority, name: 'Priority A')
+  }
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 0)
+  }
+  let(:superproject_issue) {
+    FactoryGirl.create(:issue, :project_id => superproject.id, priority_id: priority_A.id)
+  }
+  let(:sub_issue) {
+    FactoryGirl.create(:issue, :parent_issue_id => superproject_issue.id, :project_id => superproject.id,
+      description: "sub_issue of #{superproject_issue.subject} in project #{superproject.name}")
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) { example.run }
+  end
+
+  it 'should show sidebar data for superproject_issue' do
+    expect(superproject_issue.priority_id).to eq(priority_A.id)
+    visit project_easy_wbs_index_path(superproject)
+    wait_for_ajax
+
+    menu=page.find('#wbs_menu')
+    sidebar_toggler=page.find('.mindmup-sidebar__toggler')
+    sidebar_toggler.click
+    scale_down=menu.find('.scaleDown')
+    scale_down.click
+    scale_down.click
+    container=page.find('#container')
+    sidebar_toggler.click
+
+    container.find('span', text: superproject_issue.subject).click
+
+    wait_for_ajax
+    within('.mindmup-sidebar__input__name') do
+      expect(find('input').value).to eq(superproject_issue.subject)
+    end
+    within('.mindmup-sidebar__attribute-group') do
+      priority_attribute = page.find('.mindmup-sidebar__attribute-label', text: I18n.t(:field_priority)).first(:xpath, './/..')
+      priority_select = priority_attribute.find('.mindmup-sidebar__attribute-form-field')
+      # priority_option_selector = "option[value='#{priority_select.value}']"
+      # binding.pry
+      expect(priority_select.find("option[value='#{priority_select.value}']")).to have_text(priority_A.name)
+    end
+
+  end
+  it 'should show sidebar data for sub_issue' do
+    sub_issue
+    visit project_easy_wbs_index_path(superproject)
+    wait_for_ajax
+
+    menu=page.find('#wbs_menu')
+    sidebar_toggler=page.find('.mindmup-sidebar__toggler')
+    sidebar_toggler.click
+    scale_down=menu.find('.scaleDown')
+    scale_down.click
+    scale_down.click
+    container=page.find('#container')
+    sidebar_toggler.click
+
+    within('#wbs_menu') do
+      page.find('a', text: I18n.t(:button_display, :scope => [:easy_wbs])).hover
+      click_link(I18n.t(:button_collapse_all, :scope => [:easy_wbs]))
+      click_link(I18n.t(:button_expand_all, :scope => [:easy_wbs]))
+    end
+
+    expect(container).to have_text(sub_issue.subject)
+    container.find('span', text: sub_issue.subject).click
+
+    wait_for_ajax
+
+    within('.mindmup-sidebar__input__name') do
+      expect(find('input').value).to eq(sub_issue.subject)
+    end
+    within('.mindmup-sidebar__attribute-group') do
+      expect(page).to have_text(sub_issue.description)
+    end
+
+  end
+end
diff --git a/plugins/easy_wbs/spec/features/wbs_tree_spec.rb b/plugins/easy_wbs/spec/features/wbs_tree_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27baa2cab75f04da54ef0f0552f0ab53f020e0e7
--- /dev/null
+++ b/plugins/easy_wbs/spec/features/wbs_tree_spec.rb
@@ -0,0 +1,90 @@
+require File.expand_path('../../../../easyproject/easy_plugins/easy_extensions/test/spec/spec_helper', __FILE__)
+
+RSpec.feature 'tree', logged: :admin, js: true do
+  let(:superproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 0)
+  }
+  let(:subproject) {
+    FactoryGirl.create(:project, add_modules: ['easy_wbs'], number_of_issues: 0, parent_id: superproject.id)
+  }
+  let(:project_issues) {
+    FactoryGirl.create_list(:issue, 3, :project_id => superproject.id)
+  }
+  let(:subproject_issues) {
+    FactoryGirl.create_list(:issue, 3, :project_id => subproject.id)
+  }
+  let(:sub_issues) {
+    FactoryGirl.create_list(:issue, 3, :parent_issue_id => subproject_issues[0].id, :project_id => subproject.id)
+  }
+  let(:sub_sub_issues) {
+    FactoryGirl.create_list(:issue, 3, :parent_issue_id => sub_issues[0].id, :project_id => subproject.id)
+  }
+
+  around(:each) do |example|
+    with_settings(rest_api_enabled: 1) {
+      with_easy_settings(easy_wbs_no_sidebar: 1) { example.run }
+    }
+  end
+  it 'should show project items in correct tree' do
+    superproject
+    project_issues
+    subproject_issues
+    sub_issues
+    sub_sub_issues
+    visit project_easy_wbs_index_path(superproject)
+    wait_for_ajax
+
+    def sidebar_toggler
+      page.find('.mindmup-sidebar__toggler')
+    rescue
+      return nil
+    end
+
+    expect(page).to have_css('#container')
+    unless sidebar_toggler.nil?
+      sidebar_toggler.click
+    end
+    menu=page.find('#wbs_menu')
+    scale_down=menu.find('.scaleDown')
+    scale_down.click
+    scale_down.click
+    scale_down.click
+    container=page.find('#container')
+    #puts evaluate_script('ysy.loader.sourceData;').to_json
+    expect(container).to have_text(superproject.name)
+    project_issues.each do |issue|
+      expect(container).to have_text(issue.subject)
+    end
+    expect(container).to have_text(subproject.name)
+    subproject_issues.each do |issue|
+      expect(container).not_to have_text(issue.subject)
+    end
+
+    node = container.find('span', text: subproject.name).find(:xpath, '..')
+    node.find('.mapjs-collapsor').click
+    subproject_issues.each do |issue|
+      expect(container).to have_text(issue.subject)
+    end
+    sub_issues.each do |issue|
+      expect(container).not_to have_text(issue.subject)
+    end
+
+    sleep(0.5)
+    node = container.find('span', text: subproject_issues[0].subject).find(:xpath, '..')
+    node.find('.mapjs-collapsor').click
+    sub_issues.each do |issue|
+      expect(container).to have_text(issue.subject)
+    end
+    sub_sub_issues.each do |issue|
+      expect(container).not_to have_text(issue.subject)
+    end
+
+    sleep(0.5)
+    node = container.find('span', text: sub_issues[0].subject).find(:xpath, '..')
+    execute_script("easyMindMupClasses.MindMup.allMindMups[\"WBS classic\"].mapModel.selectNode(#{node[:id].split('_')[1]})")
+    node.find('.mapjs-collapsor').click
+    sub_sub_issues.each do |issue|
+      expect(container).to have_text(issue.subject)
+    end
+  end
+end
diff --git a/plugins/redmine_agile/.drone.yml b/plugins/redmine_agile/.drone.yml
new file mode 100644
index 0000000000000000000000000000000000000000..19242fcb4ba22aaa6c709ebeea1e13ff36a841da
--- /dev/null
+++ b/plugins/redmine_agile/.drone.yml
@@ -0,0 +1,78 @@
+pipeline:
+  tests:
+    image: redmineup/redmine_agile
+    pull: true
+    commands:
+      - service postgresql start && service mysql start && sleep 5
+      - export PATH=~/.rbenv/shims:$PATH
+      - export CODEPATH=`pwd`
+      - /root/run_for.sh redmine_agile+${LICENSE} ${RUBY_VER} ${DB} ${REDMINE} ${PLUGINS}
+matrix:
+  include:
+    - RUBY_VER: ruby-2.2.6
+      LICENSE: pro
+      DB: mysql
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-2.2.6
+      LICENSE: light
+      DB: mysql
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-2.2.6
+      LICENSE: pro
+      DB: pg
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-2.2.6
+      LICENSE: light
+      DB: pg
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-2.2.6
+      LICENSE: pro
+      DB: mysql
+      REDMINE: redmine-3.0
+    - RUBY_VER: ruby-2.4.1
+      DB: mysql
+      LICENSE: light
+      REDMINE: redmine-3.4
+    - RUBY_VER: ruby-2.4.1
+      DB: pg
+      LICENSE: pro
+      REDMINE: redmine-3.4
+    - RUBY_VER: ruby-2.4.1
+      DB: mysql
+      LICENSE: pro
+      REDMINE: redmine-3.4
+      PLUGINS: redmine_checklists+pro
+      PLUGIN: redmine_agile
+      COVERAGE_EXPORT: 1
+    - RUBY_VER: ruby-1.9.3
+      DB: pg
+      LICENSE: pro
+      REDMINE: redmine-2.6
+    - RUBY_VER: ruby-1.9.3
+      DB: pg
+      LICENSE: pro
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-1.9.3
+      DB: mysql
+      LICENSE: pro
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-1.9.3
+      DB: pg
+      LICENSE: light
+      REDMINE: redmine-3.3
+    - RUBY_VER: ruby-2.4.1
+      DB: mysql
+      LICENSE: pro
+      REDMINE: redmine-trunk
+    - RUBY_VER: ruby-2.4.1
+      DB: mysql
+      LICENSE: light
+      REDMINE: redmine-trunk
+    - RUBY_VER: ruby-2.4.1
+      DB: pg
+      LICENSE: pro
+      REDMINE: redmine-trunk
+    - RUBY_VER: ruby-2.2.6
+      DB: mysql
+      LICENSE: pro
+      REDMINE: redmine-trunk
\ No newline at end of file
diff --git a/plugins/redmine_agile/Gemfile b/plugins/redmine_agile/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..58237bb45bcfe3a8b3d88c7d44d6381ee8fa5289
--- /dev/null
+++ b/plugins/redmine_agile/Gemfile
@@ -0,0 +1 @@
+gem "redmine_crm"
diff --git a/plugins/redmine_agile/README.rdoc b/plugins/redmine_agile/README.rdoc
new file mode 100755
index 0000000000000000000000000000000000000000..320c8ea4fa98e9c8ba55c9a963bdd003ab5c5a6e
--- /dev/null
+++ b/plugins/redmine_agile/README.rdoc
@@ -0,0 +1,19 @@
+= Agile plugin
+
+== Install
+
+* Copy redmine_agile plugin to {RAILS_APP}/plugins on your redmine path
+* Run bundle install --without development test RAILS_ENV=production
+* Run bundle exec rake redmine:plugins NAME=redmine_agile RAILS_ENV=production
+
+== Uninstall
+
+<pre>
+bundle exec rake redmine:plugins NAME=redmine_agile VERSION=0 RAILS_ENV=production
+rm -r plugins/redmine_agile
+</pre>
+
+== Test
+
+bundle exec rake db:drop db:create db:migrate redmine:plugins RAILS_ENV=test
+bundle exec rake test TEST="plugins/redmine_agile/test/**/*_test.rb" RAILS_ENV=test
\ No newline at end of file
diff --git a/plugins/redmine_agile/app/controllers/agile_boards_controller.rb b/plugins/redmine_agile/app/controllers/agile_boards_controller.rb
new file mode 100755
index 0000000000000000000000000000000000000000..8d39544f10dce3673977a7119eaf6e657499fcdd
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_boards_controller.rb
@@ -0,0 +1,184 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileBoardsController < ApplicationController
+  unloadable
+
+  menu_item :agile
+
+  before_action :find_issue, :only => [:update, :issue_tooltip, :inline_comment]
+  before_action :find_optional_project, :only => [:index, :create_issue]
+
+  helper :issues
+  helper :journals
+  helper :projects
+  include ProjectsHelper
+  helper :custom_fields
+  include CustomFieldsHelper
+  helper :issue_relations
+  include IssueRelationsHelper
+  helper :watchers
+  include WatchersHelper
+  helper :attachments
+  include AttachmentsHelper
+  helper :queries
+  include QueriesHelper
+  helper :repositories
+  include RepositoriesHelper
+  helper :sort
+  include SortHelper
+  include IssuesHelper
+  helper :timelog
+  include RedmineAgile::AgileHelper
+
+  def index
+    retrieve_agile_query
+    if @query.valid?
+      @issues = @query.issues
+      @issue_board = @query.issue_board
+      @board_columns = @query.board_statuses
+      @swimlanes = @query.swimlanes
+
+      respond_to do |format|
+        format.html { render :template => 'agile_boards/index', :layout => !request.xhr? }
+        format.js
+      end
+    else
+      respond_to do |format|
+        format.html { render(:template => 'agile_boards/index', :layout => !request.xhr?) }
+        format.js
+      end
+    end
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def update
+    (render_403; return false) unless @issue.editable?
+    retrieve_agile_query_from_session
+    old_status = @issue.status
+    @issue.init_journal(User.current)
+    @issue.safe_attributes = auto_assign_on_move? ? params[:issue].merge(:assigned_to_id => User.current.id) : params[:issue]
+    checking_params = params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params
+    saved = checking_params['issue'] && checking_params['issue'].inject(true) do |total, attribute|
+      if @issue.attributes.include?(attribute.first)
+        total &&= @issue.attributes[attribute.first].to_i == attribute.last.to_i
+      else
+        total &&= true
+      end
+    end
+    call_hook(:controller_agile_boards_update_before_save, { :params => params, :issue => @issue})
+    @update = true
+    if saved && @issue.save
+      call_hook(:controller_agile_boards_update_after_save, { :params => params, :issue => @issue})
+      AgileData.transaction do
+        Issue.eager_load(:agile_data).find(params[:positions].keys).each do |issue|
+          issue.agile_data.position = params[:positions][issue.id.to_s]['position']
+          issue.agile_data.save
+        end
+      end if params[:positions]
+
+      @inline_adding = params[:issue][:notes] || nil
+      if Redmine::VERSION.to_s > '2.4'
+        if current_status = @query.board_statuses.detect{ |st| st == @issue.status }
+          @error_msg =  l(:lable_agile_wip_limit_exceeded) if current_status.over_wp_limit?
+          @wp_class = current_status.wp_class
+        end
+
+        if @old_status = @query.board_statuses.detect{ |st| st == old_status }
+          @wp_class_for_old_status = @old_status.wp_class
+        end
+      end
+
+      respond_to do |format|
+        format.html { render(:partial => 'issue_card', :locals => {:issue => @issue}, :status => :ok, :layout => nil) }
+      end
+    else
+      respond_to do |format|
+        messages = @issue.errors.full_messages
+        messages = [l(:text_agile_move_not_possible)] if messages.empty?
+        format.html {
+          render :json => messages, :status => :fail, :layout => nil
+        }
+      end
+    end
+  end
+  def create_issue
+    if !User.current.allowed_to?(:add_issues, @project) || params[:subject].blank?
+      messages = [l(:notice_not_authorized)]
+      respond_to do |format|
+        format.html {
+          render :json => messages, :status => :fail, :layout => nil
+        }
+      end
+      return
+    end
+    retrieve_agile_query_from_session
+    @update = true
+    @issue = Issue.new(:subject => params[:subject].strip, :project => @project,
+      :tracker => @project.trackers.first, :author => User.current, :status_id => params[:status_id])
+    begin
+      if @issue.save(:validate => false)
+        if Redmine::VERSION.to_s > '2.4'
+          if current_status = @query.board_statuses.detect{ |st| st == @issue.status }
+            @error_msg =  l(:lable_agile_wip_limit_exceeded) if current_status.over_wp_limit?
+            @wp_class = current_status.wp_class
+          end
+        end
+        @not_in_scope = !@query.issues.include?(@issue)
+        respond_to do |format|
+          format.html { render(:partial => 'issue_card', :locals => {:issue => @issue}, :status => :ok, :layout => nil) }
+        end
+      else
+        respond_to do |format|
+          messages = @issue.errors.full_messages
+          messages = [l(:text_agile_move_not_possible)] if messages.empty?
+          format.html {
+            render :json => messages, :status => :fail, :layout => nil
+          }
+        end
+      end
+    rescue
+      respond_to do |format|
+        messages = @issue.errors.full_messages
+        messages = [l(:text_agile_create_issue_error)] if messages.empty?
+        format.html {
+          render :json => messages, :status => :fail, :layout => nil
+        }
+      end
+    end
+  end
+
+  def issue_tooltip
+    render :partial => 'issue_tooltip'
+  end
+
+  def inline_comment
+    render 'inline_comment', :layout => nil
+  end
+
+  private
+
+  def auto_assign_on_move?
+    RedmineAgile.auto_assign_on_move? && @issue.assigned_to.nil? &&
+      !params[:issue].keys.include?('assigned_to_id') &&
+      @issue.status_id != params[:issue]['status_id'].to_i
+  end
+
+end
diff --git a/plugins/redmine_agile/app/controllers/agile_charts_controller.rb b/plugins/redmine_agile/app/controllers/agile_charts_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..563c9ae5a7594f0d189ddf024893eeb0ded58d39
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_charts_controller.rb
@@ -0,0 +1,142 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileChartsController < ApplicationController
+  unloadable
+
+  menu_item :agile
+
+  before_action :find_optional_project, :only => [:show, :render_chart]
+  before_action :find_optional_version, :only => [:render_chart, :select_version_chart]
+
+  helper :issues
+  helper :journals
+  helper :projects
+  include ProjectsHelper
+  helper :custom_fields
+  include CustomFieldsHelper
+  helper :issue_relations
+  include IssueRelationsHelper
+  helper :watchers
+  include WatchersHelper
+  helper :attachments
+  include AttachmentsHelper
+  helper :queries
+  include QueriesHelper
+  helper :repositories
+  include RepositoriesHelper
+  helper :sort
+  include SortHelper
+  include IssuesHelper
+  helper :timelog
+  include RedmineAgile::AgileHelper
+
+  def show
+    retrieve_charts_query
+    @query.date_to ||= Date.today
+    @issues = @query.issues
+    respond_to do |format|
+      format.html
+    end
+  end
+
+  def render_chart
+    if @version
+      @issues = @version.fixed_issues
+      options = {:date_from => @version.start_date,
+                 :date_to => [@version.due_date,
+                              @issues.maximum(:due_date),
+                              @issues.maximum(:updated_on)].compact.max,
+                 :due_date => @version.due_date || @issues.maximum(:due_date) || @issues.maximum(:updated_on)}
+      @chart = params[:chart]
+    else
+      retrieve_charts_query
+      @query.date_to ||= Date.today
+      @issues = Issue.visible.where(@query.statement)
+      options = {:date_from => @query.date_from, :date_to => @query.date_to}
+    end
+    render_data(options)
+  end
+
+  def select_version_chart
+  end
+
+  private
+
+  def render_data(options = {})
+    case @chart
+    when 'lead_time'
+      data = RedmineAgile::LeadTimeChart.data(@issues, options)
+    when 'average_lead_time'
+      data = RedmineAgile::AverageLeadTimeChart.data(@issues, options)
+    when 'burnup'
+      data = RedmineAgile::BurnupChart.data(@issues, options)
+    when 'trackers_cumulative_flow'
+      data = RedmineAgile::TrackersCumulativeFlowChart.data(@issues, options)
+    when 'cumulative_flow'
+      data = RedmineAgile::CumulativeFlowChart.data(@issues, options)
+    when 'issues_velocity'
+      data = RedmineAgile::VelocityChart.data(@issues, options)
+    when 'work_burnup_hours'
+      data = RedmineAgile::WorkBurnupChart.data(@issues, options.merge(:estimated_unit => 'hours'))
+    when 'work_burnup_sp'
+      data = RedmineAgile::WorkBurnupChart.data(@issues, options.merge(:estimated_unit => 'story_points'))
+    when 'work_burndown_hours'
+      data = RedmineAgile::WorkBurndownChart.data(@issues, options.merge(:estimated_unit => 'hours'))
+    when 'work_burndown_sp'
+      data = RedmineAgile::WorkBurndownChart.data(@issues, options.merge(:estimated_unit => 'story_points'))
+    else
+      data = RedmineAgile::BurndownChart.data(@issues, options)
+    end
+    return render :json => data if data
+    raise ActiveRecord::RecordNotFound
+  end
+
+  def find_optional_version
+    @version = Version.find(params[:version_id]) if params[:version_id]
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def retrieve_charts_query
+    if params[:set_filter] || session[:agile_charts_query].nil? || session[:agile_charts_query][:project_id] != (@project ? @project.id : nil)
+      # Give it a name, required to be valid
+      @query = AgileChartsQuery.new(:name => "_")
+      @query.project = @project
+      @query.build_from_params(params)
+      session[:agile_charts_query] = {:project_id => @query.project_id,
+                                      :filters => @query.filters,
+                                      :group_by => @query.group_by,
+                                      :column_names => @query.column_names,
+                                      :date_from => @query.date_from,
+                                      :date_to => @query.date_to}
+    else
+      # retrieve from session
+      @query = AgileChartsQuery.new(:name => "_",
+        :filters => session[:agile_charts_query][:filters] || session[:agile_query][:filters],
+        :group_by => session[:agile_charts_query][:group_by],
+        :column_names => session[:agile_charts_query][:column_names],
+        :date_from => session[:agile_charts_query][:date_from],
+        :date_to => session[:agile_charts_query][:date_to]
+        )
+      @query.project = @project
+    end
+    @chart = params[:chart] || "issues_burndown"
+  end
+end
diff --git a/plugins/redmine_agile/app/controllers/agile_colors_controller.rb b/plugins/redmine_agile/app/controllers/agile_colors_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0b6f5ff68078942ec8a5ff31f625c49cd86543a4
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_colors_controller.rb
@@ -0,0 +1,50 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileColorsController < ApplicationController
+  unloadable
+
+  layout 'admin'
+
+  before_action :require_admin
+  before_action :find_coloreds, :only => [:index, :update]
+
+  def index
+  end
+
+  def update
+    @colored_class.transaction do
+      params[:coloreds].each do |colored|
+        @colored_class.update(colored[:id], :color => colored[:color])
+      end
+      flash[:notice] = l(:notice_successful_update)
+    end
+    redirect_to :action => :index, :object_type => params[:object_type]
+  end
+
+  private
+
+  def find_coloreds
+    klass = Object.const_get(params[:object_type].camelcase) rescue nil
+    @colored_class = klass
+    @coloreds = klass.sorted if klass && klass.new.respond_to?('color')
+    render_404 unless @coloreds.present?
+  end
+
+end
diff --git a/plugins/redmine_agile/app/controllers/agile_journal_details_controller.rb b/plugins/redmine_agile/app/controllers/agile_journal_details_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8eaf878d66e076879ee1750fc273ea541b4df42d
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_journal_details_controller.rb
@@ -0,0 +1,66 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileJournalDetailsController < ApplicationController
+  unloadable
+
+  before_action :find_issue
+
+  helper :issues
+  helper :agile_support
+
+  def done_ratio
+    @done_ratios = @issue.journals.map(&:details).flatten.select {|detail| 'done_ratio' == detail.prop_key }.sort_by {|a| a.journal.created_on }
+    @done_ratios.unshift(JournalDetail.new(:property => 'attr', :prop_key => 'done_ratio', :value => history_initial_value(@done_ratios) || @issue.done_ratio,
+                                           :journal => Journal.new(:user => @issue.author, :created_on => @issue.created_on)))
+  end
+
+  def status
+    @statuses = @issue.journals.map(&:details).flatten.select {|detail| 'status_id' == detail.prop_key }.sort_by {|a| a.journal.created_on }
+    @statuses.unshift(JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => history_initial_value(@statuses) || @issue.status.id,
+                                        :journal => Journal.new(:user => @issue.author, :created_on => @issue.created_on)))
+  end
+
+  def assignee
+    @assignees = @issue.journals.map(&:details).flatten.select {|detail| 'assigned_to_id' == detail.prop_key }.sort_by {|a| a.journal.created_on }
+    @assignees.unshift(JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :value => history_initial_value(@assignees) || @issue.assigned_to_id,
+                                         :journal => Journal.new(:user => @issue.author, :created_on => @issue.created_on)))
+  end
+
+  def edit
+  end
+
+  def new
+  end
+
+  private
+
+  def find_issue
+    @issue = Issue.eager_load(:journals => :details).find(params[:issue_id])
+    raise Unauthorized unless @issue.visible?
+    @project = @issue.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def history_initial_value(journals)
+    return nil unless journals.present?
+    journals.first.old_value
+  end
+end
diff --git a/plugins/redmine_agile/app/controllers/agile_queries_controller.rb b/plugins/redmine_agile/app/controllers/agile_queries_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab63020cb53e39fd03254f750f85cdd29fe0838a
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_queries_controller.rb
@@ -0,0 +1,124 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileQueriesController < ApplicationController
+  unloadable
+
+  menu_item :agile
+
+  before_action :find_query, :except => [:new, :create, :index]
+  before_action :find_optional_project, :only => [:new, :create]
+
+  include QueriesHelper
+  helper :queries
+  helper :agile_boards
+
+  def index
+    @limit = per_page_option
+    @query_count = AgileQuery.visible.count
+    @query_pages = Paginator.new @query_count, @limit, params['page']
+    @queries = AgileQuery.visible.
+                    order("#{Query.table_name}.name").
+                    limit(@limit).
+                    offset(@offset).
+                    all
+  end
+
+  def new
+    @query = AgileQuery.new
+    @query.user = User.current
+    @query.project = @project
+    @query.visibility = AgileQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+    @query.build_from_params(params)
+  end
+
+  def create
+    @query = AgileQuery.new
+    @query.user = User.current
+    @query.project = params[:query_is_for_all] ? nil : @project
+    @query.build_from_params(params)
+    @query.name = params[:query] && params[:query][:name]
+    if User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+      @query.visibility = (params[:query] && params[:query][:visibility]) || AgileQuery::VISIBILITY_PRIVATE
+      @query.role_ids = params[:query] && params[:query][:role_ids] if Redmine::VERSION.to_s > '2.4'
+    else
+      @query.visibility = AgileQuery::VISIBILITY_PRIVATE
+    end
+    @query.column_names = nil if params[:default_columns]
+
+    if @query.save
+      flash[:notice] = l(:notice_successful_create)
+      redirect_to_agile_board(:query_id => @query)
+    else
+      render :action => 'new', :layout => !request.xhr?
+    end
+  end
+
+  def edit
+  end
+
+  def update
+    @query.project = nil if params[:query_is_for_all]
+    @query.build_from_params(params)
+    @query.name = params[:query] && params[:query][:name]
+    if User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
+      @query.visibility = (params[:query] && params[:query][:visibility]) || AgileQuery::VISIBILITY_PRIVATE
+      @query.role_ids = params[:query] && params[:query][:role_ids] if Redmine::VERSION.to_s > '2.4'
+    else
+      @query.visibility = AgileQuery::VISIBILITY_PRIVATE
+    end
+    @query.column_names = nil if params[:default_columns]
+
+    if @query.save
+      flash[:notice] = l(:notice_successful_update)
+      redirect_to_agile_board(:query_id => @query)
+    else
+      render :action => 'edit'
+    end
+  end
+
+  def destroy
+    @query.destroy
+    redirect_to_agile_board(:set_filter => 1)
+  end
+
+private
+  def find_query
+    @query = AgileQuery.find(params[:id])
+    @project = @query.project
+    render_403 unless @query.editable_by?(User.current)
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def find_optional_project
+    @project = Project.find(params[:project_id]) if params[:project_id]
+    render_403 unless User.current.allowed_to?(:add_agile_queries, @project, :global => true)
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def redirect_to_agile_board(options)
+    if @project
+      redirect_to agile_board_path(options.merge(:project_id => @project))
+    else
+      redirect_to agile_board_path(options)
+    end
+  end
+end
diff --git a/plugins/redmine_agile/app/controllers/agile_versions_controller.rb b/plugins/redmine_agile/app/controllers/agile_versions_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..611b72fa0f131cbea3e68b00fde3e5bac1f1c74b
--- /dev/null
+++ b/plugins/redmine_agile/app/controllers/agile_versions_controller.rb
@@ -0,0 +1,85 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileVersionsController < ApplicationController
+  unloadable
+
+  menu_item :agile
+
+  before_action :find_project_by_project_id, :only => [:index, :autocomplete, :load]
+  before_action :find_version, :only => [:load]
+  before_action :authorize, :except => [:autocomplete, :load]
+  before_action :find_no_version_issues, :only => [:index, :autocomplete]
+
+  include QueriesHelper
+  helper :queries
+  include RedmineAgile::AgileHelper
+
+  def index
+    retrieve_versions_query
+    if @query.valid?
+      @backlog_version = @query.backlog_version
+      @backlog_version_issues =  @query.backlog_version_issues
+
+      @current_version = @query.current_version
+      @current_version_issues = @query.current_version_issues
+    end
+    respond_to do |format|
+      format.html
+      format.js
+    end
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def autocomplete
+    render :layout => false
+  end
+
+  def load
+    retrieve_versions_query
+    @version_issues = @query.version_issues(@version)
+    @version_type = params[:version_type]
+    @other_version_type = @version_type == "backlog" ? "current" : "backlog"
+    @other_version_id = params[:other_version_id]
+    respond_to do |format|
+      format.js
+    end
+  end
+
+  private
+
+  def find_version
+    @version = Version.visible.find(params[:version_id])
+    @project ||= @version.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def find_no_version_issues
+    retrieve_versions_query
+    if @query.valid?
+      scope = @query.no_version_issues(params)
+      @issue_count = scope.count
+      @issue_pages = Redmine::Pagination::Paginator.new @issue_count, 20, params['page']
+      @version_issues = scope.offset(@issue_pages.offset).limit(@issue_pages.per_page).all
+    end
+  end
+
+end
diff --git a/plugins/redmine_agile/app/helpers/agile_boards_helper.rb b/plugins/redmine_agile/app/helpers/agile_boards_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b05cf675c7053e24fd03c6149503d0e4bacdbc90
--- /dev/null
+++ b/plugins/redmine_agile/app/helpers/agile_boards_helper.rb
@@ -0,0 +1,244 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module AgileBoardsHelper
+  def agile_color_class(issue, options={})
+    if options[:color_base]
+      color = case options[:color_base]
+      when AgileColor::COLOR_GROUPS[:issue]
+        issue.color
+      when AgileColor::COLOR_GROUPS[:tracker]
+        issue.tracker.color
+      when AgileColor::COLOR_GROUPS[:priority]
+        issue.priority.color
+      when AgileColor::COLOR_GROUPS[:spent_time]
+        AgileColor.for_spent_time(issue.estimated_hours, issue.spent_hours)
+      when AgileColor::COLOR_GROUPS[:project]
+        issue.project.color
+      end
+    else
+      color = if RedmineAgile.tracker_colors?
+        issue.tracker.color
+      elsif RedmineAgile.issue_colors?
+        issue.color
+      elsif RedmineAgile.priority_colors?
+        issue.priority.color
+      elsif RedmineAgile.spent_time_colors?
+        AgileColor.for_spent_time(issue.estimated_hours, issue.spent_hours)
+      end
+    end
+    "#{RedmineAgile.color_prefix}-#{color}" if color && RedmineAgile.use_colors?
+          end
+
+  def agile_user_color(user, options={})
+    return if Redmine::VERSION.to_s < '2.4'
+    user_color = user.color rescue nil
+    user_color ||= AgileColor.for_user(user.login)
+    if options[:color_base]
+      "border-left: 5px solid #{user_color}".html_safe if options[:color_base] ==  AgileColor::COLOR_GROUPS[:user]
+    elsif RedmineAgile.user_color?
+      "border-left: 5px solid #{user_color}".html_safe
+    end
+  end
+
+  def header_th(name, rowspan = 1, colspan = 1, leaf = nil)
+    th_attributes = {}
+    if leaf
+      # th_attributes[:style] = ""
+      th_attributes[:style] = "border-bottom: 4px solid; border-bottom-color: #{color_by_name(leaf.name)};" if RedmineAgile.status_colors?
+      th_attributes[:"data-column-id"] = leaf.id
+      issue_count = leaf.instance_variable_get("@issue_count") || 0
+      if Redmine::VERSION.to_s > '2.4'
+        wp_count = leaf.instance_variable_get("@wp_max")
+        unless wp_count.blank?
+          th_attributes[:class] = leaf.wp_class
+          issue_count_tag = content_tag(:span, issue_count, :class => leaf.wp_class)
+          count_tag = " (#{content_tag(:span, issue_count_tag + "/#{wp_count}", :class => 'count')})".html_safe
+        else
+          count_tag = " (#{content_tag(:span, issue_count.to_i, :class => 'count')})".html_safe
+        end
+      else
+        count_tag = " (#{content_tag(:span, issue_count.to_i, :class => 'count')})".html_safe
+      end
+            
+
+      # estimated hours total
+      story_points_count = leaf.instance_variable_get("@story_points") || 0
+      hours_count = leaf.instance_variable_get("@estimated_hours_sum") || 0
+      if story_points_count > 0
+        hours_tag = " #{content_tag(:span, (story_points_count).to_s + 'sp',
+          :class => 'hours', :title => l(:field_estimated_hours))}".html_safe
+      else
+        hours_tag = " #{content_tag(:span, ("%.2fh" % hours_count.to_f).to_s,
+        :class => 'hours', :title => l(:field_estimated_hours))}".html_safe if hours_count > 0
+      end
+    end
+    th_attributes[:rowspan] = rowspan if rowspan > 1
+    th_attributes[:colspan] = colspan if colspan > 1
+    content_tag :th, h(name) + count_tag + hours_tag, th_attributes
+  end
+
+  def render_board_headers(columns)
+    tree = HeaderTree.new
+
+    columns.map do |column|
+      path = column.name.split(':').map(&:strip)
+      tree.put path, column
+    end
+
+    # puts tree
+
+    maxdepth = tree.depth
+
+    ret = tree.render
+
+    ret[1..-1].map do |row|
+      row.map do |th_params|
+        header_th *th_params
+      end
+    end.map{|x| "<tr>#{x.join('')}</tr>" }.join.html_safe
+          end
+
+  def color_by_name(name)
+    "##{"%06x" % (name.unpack('H*').first.hex % 0xffffff)}"
+  end
+  def format_swimlane_object(object, html=true)
+    case object.class.name
+    when 'Array'
+      object.map {|o| format_swimlane_object(o, html)}.join(', ').html_safe
+    when 'Time'
+      format_time(object)
+    when 'Date'
+      format_date(object)
+    when 'Fixnum'
+      object.to_s
+    when 'Float'
+      sprintf "%.2f", object
+    when 'User'
+      html ? link_to_user(object) : object.to_s
+    when 'Project'
+      html ? link_to_project(object) : object.to_s
+    when 'Version'
+      html ? link_to(object.name, version_path(object)) : object.to_s
+    when 'TrueClass'
+      l(:general_text_Yes)
+    when 'FalseClass'
+      l(:general_text_No)
+    when 'Issue'
+      object.visible? && html ? link_to_issue(object) : "##{object.id}"
+    else
+      html ? h(object) : object.to_s
+    end
+  end
+
+  def render_board_fields_selection(query)
+    query.available_inline_columns.reject(&:frozen?).reject{ |c| c.name == :story_points && !RedmineAgile.use_story_points? }.map do |column|
+      label_tag('', check_box_tag('c[]', column.name, query.columns.include?(column)) + column.caption, :class => "floating" )
+    end.join(" ").html_safe
+  end
+
+  def render_board_fields_status(query)
+    available_statuses = Redmine::VERSION.to_s >= '3.4' && @project ? @project.rolled_up_statuses : IssueStatus.sorted
+    current_statuses = query.options[:f_status] || IssueStatus.where(:is_closed => false).pluck(:id).map(&:to_s)
+    wp = query.options[:wp] || {}
+    status_tags = available_statuses.map do |status|
+      content_tag(:span,
+        label_tag('', check_box_tag('f_status[]', status.id, current_statuses.include?(status.id.to_s)
+        ) + content_tag(:span, status.to_s), :title => status.to_s) + text_field_tag("wp[#{status.id}]", wp[status.id.to_s],
+          :size => 5, :class => 'wp_input', :placeholder => "WIP",
+          :title => l(:label_agile_wip_limit)), :class => 'floating'
+      )
+                end.join(' ').html_safe
+    hidden_field_tag('f[]', 'status_id').html_safe +
+      hidden_field_tag('op[status_id]', "=").html_safe +
+      status_tags
+  end
+
+  def render_issue_card_hours(query, issue)
+    hours = []
+    hours << "%.2f" % issue.total_spent_hours.to_f if query.has_column_name?(:spent_hours) && issue.total_spent_hours > 0
+    hours << "%.2f" % issue.estimated_hours.to_f if query.has_column_name?(:estimated_hours) && issue.estimated_hours
+    hours = [hours.join('/') + "h"] unless hours.blank?
+    hours << "#{issue.story_points}sp" if query.has_column_name?(:story_points) && issue.story_points
+
+    content_tag(:span, "(#{hours.join('/')})", :class => 'hours') unless hours.blank?
+  end
+
+  def agile_progress_bar(pcts, options={})
+    pcts = [pcts, pcts] unless pcts.is_a?(Array)
+    pcts = pcts.collect(&:round)
+    pcts[1] = pcts[1] - pcts[0]
+    pcts << (100 - pcts[1] - pcts[0])
+    width = options[:width] || '100px;'
+    legend = options[:legend] || ''
+    content_tag('table',
+      content_tag('tr',
+        (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
+        (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
+        (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) +
+        (legend ? content_tag('td', content_tag('p', legend, :class => 'percent'), :class => 'legend') : ''.html_safe)
+      ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe
+  end
+
+  def issue_children(issue)
+    return unless issue.children.any?
+    content_tag :ul do
+      issue.children.select{ |x| x.visible? }.each do |child|
+        id = if @query.has_column_name?(:tracker) || @query.has_column_name?(:id) then "##{child.id}:&nbsp;" else '' end
+        concat "<li class='#{'task-closed' if child.closed?}'><a href='#{issue_path(child)}'>#{id}#{child.subject}</a></li>#{issue_children(child)}".html_safe
+      end
+    end
+  end
+
+  def time_in_state(distance=nil)
+    return "" if !distance || !(distance.is_a? Time)
+    distance = Time.now - distance
+    hours = distance/(3600)
+    return "#{I18n.t('datetime.distance_in_words.x_hours', :count => hours.to_i)}" if hours < 24
+    "#{I18n.t('datetime.distance_in_words.x_days', :count => (hours/24).to_i)}"
+  end
+
+  def class_for_closed_issue(issue)
+    return '' if !RedmineAgile.hide_closed_issues_data?
+    return 'closed-issue' if issue.closed?
+    ''
+  end
+
+  def init_agile_tooltip_info(options={})
+    js_code = "function callGetToolTipInfo()
+      {
+        var url = '#{issue_tooltip_url}';
+        agileBoard.getToolTipInfo(this, url);
+      }
+      $('.tooltip').mouseenter(callGetToolTipInfo);
+    "
+    return js_code.html_safe if options[:only_code]
+    javascript_tag(js_code)
+  end
+
+  def show_checklist?(issue)
+    RedmineAgile.use_checklist? && issue.checklists.any? && User.current.allowed_to?(:view_checklists, issue.project)
+  rescue
+    false
+  end
+
+
+end
diff --git a/plugins/redmine_agile/app/helpers/agile_charts_helper.rb b/plugins/redmine_agile/app/helpers/agile_charts_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b813026013945e29968b4c3a73a84d8cfebf5127
--- /dev/null
+++ b/plugins/redmine_agile/app/helpers/agile_charts_helper.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module AgileChartsHelper
+  def render_agile_charts_breadcrumb
+    links = []
+    links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil})
+    links << link_to(h(@project), {:project_id => @project, :issue_id => nil}) if @project
+    if @version
+      if @version.visible?
+        links << link_to(@version.name, version_path(@version))
+      else
+        links << @version.name
+      end
+    end
+    breadcrumb links
+  end
+
+end
diff --git a/plugins/redmine_agile/app/helpers/agile_support_helper.rb b/plugins/redmine_agile/app/helpers/agile_support_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a18d86dc20360a6522b682f36b8c84793a12f504
--- /dev/null
+++ b/plugins/redmine_agile/app/helpers/agile_support_helper.rb
@@ -0,0 +1,40 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module AgileSupportHelper
+  # Returns a h2 tag and sets the html title with the given arguments
+  def title(*args)
+    strings = args.map do |arg|
+      if arg.is_a?(Array) && arg.size >= 2
+        link_to(*arg)
+      else
+        h(arg.to_s)
+      end
+    end
+    html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
+    content_tag('h2', strings.join(' &#187; ').html_safe)
+  end
+
+  def event_duration(event, next_event)
+    end_time = next_event ? next_event.journal.created_on : Time.now
+    distance_of_time_in_words(end_time, event.journal.created_on).html_safe
+  end
+end
diff --git a/plugins/redmine_agile/app/helpers/agile_versions_helper.rb b/plugins/redmine_agile/app/helpers/agile_versions_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b306e6f92b397a3a28a7ca755c34a947f6f9276
--- /dev/null
+++ b/plugins/redmine_agile/app/helpers/agile_versions_helper.rb
@@ -0,0 +1,54 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module AgileVersionsHelper
+  def version_select_tag(version, option={})
+    return "" if version.blank?
+    version_id =  version.is_a?(Version) && version.id || version
+    other_version_id = option[:other_version].is_a?(Version) && option[:other_version].id || option[:other_version]
+    select_tag('version_id',
+      options_for_select(versions_collection_for_select,
+          {:selected => version_id, :disabled => other_version_id}),
+      :data => {:remote => true,
+                :method => 'get',
+                :url => load_agile_versions_path(:version_type => option[:version_type],
+                                                 :other_version_id => other_version_id,
+                                                 :project_id => @project)}) +
+    content_tag(:span, '', :class => "hours header-hours #{option[:version_type]}-hours")
+  end
+
+  def versions_collection_for_select
+    @project.shared_versions.open.map{|version| [format_version_name(version), version.id.to_s]}
+  end
+
+  def estimated_hours(issue)
+    "%.2fh" % issue.estimated_hours.to_f
+  end
+
+  def estimated_value(issue)
+    return (issue.story_points || 0) if RedmineAgile.use_story_points?
+    issue.estimated_hours.to_f || 0
+  end
+
+  def estimated_unit
+    RedmineAgile.use_story_points? ? 'sp' : 'h'
+  end
+end
diff --git a/plugins/redmine_agile/app/models/agile_charts_query.rb b/plugins/redmine_agile/app/models/agile_charts_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85fce06d0a89a7b2a466c6153546af2fedc8f55c
--- /dev/null
+++ b/plugins/redmine_agile/app/models/agile_charts_query.rb
@@ -0,0 +1,192 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileChartsQuery < AgileQuery
+  unloadable
+
+  validate :validate_query_dates
+
+  def initialize(attributes=nil, *args)
+    super attributes
+    self.filters.delete('status_id')
+  end
+
+  self.operators_by_filter_type[:chart_period] = [ "><", "w", "lw", "l2w", "m", "lm", "y"]
+
+  def initialize_available_filters
+    principals = []
+    subprojects = []
+    versions = []
+    categories = []
+    issue_custom_fields = []
+
+    # add_available_filter "chart_period", :type => :chart_period, :name => l(:label_agile_chart_period)
+
+    if project
+      principals += project.principals.sort
+      unless project.leaf?
+        subprojects = project.descendants.visible.all
+        principals += Principal.member_of(subprojects)
+      end
+      versions = project.shared_versions.all
+      categories = project.issue_categories.all
+      issue_custom_fields = project.all_issue_custom_fields
+    else
+      if all_projects.any?
+        principals += Principal.member_of(all_projects)
+      end
+      versions = Version.visible.where(:sharing => 'system').all
+      issue_custom_fields = IssueCustomField.where(:is_for_all => true)
+    end
+    principals.uniq!
+    principals.sort!
+    users = principals.select {|p| p.is_a?(User)}
+
+    if project.nil?
+      project_values = []
+      if User.current.logged? && User.current.memberships.any?
+        project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
+      end
+      project_values += all_projects_values
+      add_available_filter("project_id",
+        :type => :list, :values => project_values
+      ) unless project_values.empty?
+    end
+
+    add_available_filter "tracker_id",
+      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter "priority_id",
+      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
+
+    author_values = []
+    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    author_values += users.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("author_id",
+      :type => :list, :values => author_values
+    ) unless author_values.empty?
+
+    assigned_to_values = []
+    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    assigned_to_values += (Setting.issue_group_assignment? ?
+                              principals : users).collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("assigned_to_id",
+      :type => :list_optional, :values => assigned_to_values
+    ) unless assigned_to_values.empty?
+
+    if versions.any?
+      add_available_filter "fixed_version_id",
+        :type => :list_optional,
+        :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
+    end
+
+    if categories.any?
+      add_available_filter "category_id",
+        :type => :list_optional,
+        :values => categories.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_available_filter "subject", :type => :text
+    add_available_filter "created_on", :type => :date_past
+    add_available_filter "updated_on", :type => :date_past
+    add_available_filter "closed_on", :type => :date_past
+    add_available_filter "start_date", :type => :date
+    add_available_filter "due_date", :type => :date
+    add_available_filter "estimated_hours", :type => :float
+    add_available_filter "done_ratio", :type => :integer
+
+    if subprojects.any?
+      add_available_filter "subproject_id",
+        :type => :list_subprojects,
+        :values => subprojects.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_custom_fields_filters(issue_custom_fields)
+
+    add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
+
+    Tracker.disabled_core_fields(trackers).each {|field|
+      delete_available_filter field
+    }
+  end
+
+  def default_columns_names
+    @default_columns_names = [:id, :subject, :estimated_hours, :spent_hours, :done_ratio, :assigned_to]
+  end
+
+  def sql_for_chart_period_field(field, operator, value)
+    "1=1"
+  end
+
+  def date_from
+    @date_from
+  end
+
+  def date_from=(arg)
+    @date_from = Date.parse(arg.to_s) rescue nil
+  end
+
+  def date_to
+    @date_to
+  end
+
+  def date_to=(arg)
+    @date_to = Date.parse(arg.to_s) rescue nil
+  end
+
+  def build_from_params(params)
+    if params[:fields] || params[:f]
+      self.filters = {}
+      add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
+    else
+      available_filters.keys.each do |field|
+        add_short_filter(field, params[field]) if params[field]
+      end
+    end
+    self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
+    self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
+
+    self.date_from = params[:date_from] || (params[:query] && params[:query][:date_from])
+    self.date_to = params[:date_to] || (params[:query] && params[:query][:date_to])
+    self
+  end
+
+private
+
+  def issue_scope
+    Issue.visible.
+      eager_load(:status,
+                 :project,
+                 :assigned_to,
+                 :tracker,
+                 :priority,
+                 :category,
+                 :fixed_version,
+                 :agile_data).
+      where(statement)
+  end
+
+  def validate_query_dates
+    if (self.date_from && self.date_to && self.date_from >= self.date_to) ||
+       (self.date_from && self.date_to.blank?)
+      m = l(:label_agile_chart_dates) + " " + l(:invalid, :scope => 'activerecord.errors.messages')
+      errors.add(:base, m)
+    end
+  end
+
+end
diff --git a/plugins/redmine_agile/app/models/agile_color.rb b/plugins/redmine_agile/app/models/agile_color.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1bfeb467fe6f798ef4828f2afed712a12af3d7de
--- /dev/null
+++ b/plugins/redmine_agile/app/models/agile_color.rb
@@ -0,0 +1,68 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileColor < ActiveRecord::Base
+  unloadable
+  include Redmine::SafeAttributes
+
+  COLOR_GROUPS = {
+    :issue => 'issue',
+    :priority => 'priority',
+    :tracker => 'tracker',
+    :spent_time => 'spent_time',
+    :user => 'user',
+    :project => 'project'
+  }
+
+  AGILE_COLORS = {
+    :green  => 'green',
+    :blue => 'blue',
+    :turquoise  => 'turquoise',
+    :light_green  => 'lightgreen',
+    :yellow => 'yellow',
+    :orange => 'orange',
+    :red  => 'red',
+    :purple => 'purple',
+    :gray => 'gray'
+  }
+
+  belongs_to :container, :polymorphic => true
+
+  attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
+  safe_attributes 'color'
+
+  def self.for_user(user_name)
+    '0xffffff'.html_safe if !user_name
+    "##{"%06x" % (user_name.unpack('H*').first.hex % 0xffffff)}".html_safe
+  end
+
+  def self.for_spent_time(est_time = nil, spent_time = nil)
+    return AGILE_COLORS[:gray] if !est_time || !spent_time || est_time.to_f.zero? || (spent_time.to_f.zero? && est_time.to_f.zero?)
+    percent = ((spent_time / est_time.to_f) * 100).to_i
+    if percent <= 80
+      return AGILE_COLORS[:green]
+    elsif percent > 80 && (spent_time < est_time)
+      return AGILE_COLORS[:yellow]
+    elsif (spent_time >= est_time) && (spent_time < est_time * 2)
+      return AGILE_COLORS[:red]
+    elsif spent_time >= est_time * 2
+      return AGILE_COLORS[:purple]
+    end
+  end
+end
diff --git a/plugins/redmine_agile/app/models/agile_data.rb b/plugins/redmine_agile/app/models/agile_data.rb
new file mode 100755
index 0000000000000000000000000000000000000000..f5685f4c9158874bd12dceba79c50763b701a0d1
--- /dev/null
+++ b/plugins/redmine_agile/app/models/agile_data.rb
@@ -0,0 +1,24 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileData < ActiveRecord::Base
+  unloadable
+  belongs_to :issue
+  validates :story_points, :numericality => {:only_integer => true, :greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
+end
diff --git a/plugins/redmine_agile/app/models/agile_query.rb b/plugins/redmine_agile/app/models/agile_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f465bc9d0f7b35756f70b9ea625a6aad1232940e
--- /dev/null
+++ b/plugins/redmine_agile/app/models/agile_query.rb
@@ -0,0 +1,735 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileQuery < Query
+  unloadable
+  include Redmine::SafeAttributes
+
+  attr_reader :truncated
+
+  self.queried_class = Issue
+  self.view_permission = :view_issues if Redmine::VERSION.to_s >= '3.4'
+
+  self.available_columns = [
+    QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => :label_agile_issue_id),
+    QueryColumn.new(:project, :groupable => "#{Issue.table_name}.project_id", :sortable => "#{Project.table_name}.id"),
+    QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
+    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
+    QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
+    QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
+    QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("users")}, :groupable => true),
+    QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => "#{Issue.table_name}.category_id"),
+    QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => "#{Issue.table_name}.fixed_version_id"),
+    QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
+    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
+    QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on"),
+    QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on"),
+    QueryColumn.new(:thumbnails, :caption => :label_agile_board_thumbnails),
+    QueryColumn.new(:description),
+    QueryColumn.new(:sub_issues, :caption => :label_agile_sub_issues),
+    QueryColumn.new(:day_in_state, :caption => :label_agile_day_in_state),
+    QueryColumn.new(:parent, :groupable => "#{Issue.table_name}.parent_id", :sortable => "#{AgileData.table_name}.position", :caption => :field_parent_issue),
+    QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => "#{Issue.table_name}.assigned_to_id"),
+    QueryColumn.new(:relations, :caption => :label_related_issues),
+    QueryColumn.new(:last_comment, :caption => :label_agile_last_comment),
+    QueryColumn.new(:story_points, :caption => :label_agile_story_points)
+  ]
+
+  if RedmineAgile.use_checklist?
+    self.available_columns << QueryColumn.new(:checklists, :caption => :label_checklist_plural)
+  end
+  before_save :set_default_when_appropriate
+
+  scope :visible, lambda {|*args|
+    user = args.shift || User.current
+    base = Project.allowed_to_condition(user, :view_issues, *args)
+    scope = eager_load(:project).where("#{table_name}.project_id IS NULL OR (#{base})")
+
+    if user.admin?
+      scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
+    elsif user.memberships.any?
+      scope.where("#{table_name}.visibility = ?" +
+        " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
+          "SELECT DISTINCT q.id FROM #{table_name} q" +
+          " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
+          " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
+          " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
+          " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
+        " OR #{table_name}.user_id = ?",
+        VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
+    elsif user.logged?
+      scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
+    else
+      scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
+    end
+  }
+
+  def initialize(attributes=nil, *args)
+    super attributes
+    unless Redmine::VERSION.to_s > '2.4'
+      self.filters ||= { 'status_id' => {:operator => "*", :values => [""]} }
+    end
+    self.filters ||= { }
+    @truncated = false
+  end
+
+  def card_columns
+    self.inline_columns.select{|c| !%w(day_in_state tracker thumbnails description assigned_to done_ratio spent_hours estimated_hours project id sub_issues checklists last_comment story_points).include?(c.name.to_s)}
+  end
+
+  def visible?(user=User.current)
+    return true if user.admin?
+    return false unless project.nil? || user.allowed_to?(:view_issues, project)
+    case visibility
+    when VISIBILITY_PUBLIC
+      true
+    when VISIBILITY_ROLES
+      if project
+        (user.roles_for_project(project) & roles).any?
+      else
+        Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
+      end
+    else
+      user == self.user
+    end
+  end
+
+  def is_private?
+    visibility == VISIBILITY_PRIVATE
+  end
+
+  def is_public?
+    !is_private?
+  end
+
+  def color_base
+    options[:color_base] || RedmineAgile.color_base
+  end
+
+  def color_base=(value)
+    options[:color_base] = value
+  end
+  def is_default?
+    !!options[:is_default]
+  end
+
+  def is_default=(value)
+    options[:is_default] = !!value
+  end
+
+  def set_as_default
+    AgileQuery.where(:project_id => self.project_id).where(:visibility => self.visibility).where("#{AgileQuery.table_name}.id <> ?", self.id).each do |query|
+      query.is_default = false
+      query.save
+    end if self.is_default?
+  end
+
+  def self.default_query(project=nil)
+    board_scope = AgileQuery.visible
+    board_scope = board_scope.where(:project_id => project)
+    default_query = board_scope.where("#{table_name}.visibility = ? AND #{table_name}.user_id = ?", VISIBILITY_PRIVATE, User.current).detect{|q| q.is_default?}
+    default_query ||= board_scope.eager_load(:user => :memberships).where("#{table_name}.visibility = ?", VISIBILITY_ROLES).detect{|q| q.is_default?}
+    default_query ||= board_scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC).detect{|q| q.is_default?}
+    default_query
+  end
+
+  def build_from_params(params)
+    if params[:fields] || params[:f]
+      self.filters = {}
+      add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
+    else
+      available_filters.keys.each do |field|
+        add_short_filter(field, params[field]) if params[field]
+      end
+    end
+    self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
+    self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
+    self.color_base = params[:color_base] || (params[:query] && params[:query][:color_base])
+    self.is_default = params[:is_default] || (params[:query] && params[:query][:is_default])
+    self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
+    if params[:f_status] || params[:wp]
+      self.options = options.merge({ :f_status => params[:f_status], :wp => params[:wp] })
+    end
+    self
+  end
+
+  # Builds a new query from the given params and attributes
+  def self.build_from_params(params, attributes={})
+    new(attributes).build_from_params(params)
+  end
+
+  def initialize_available_filters
+    principals = []
+    subprojects = []
+    versions = []
+    categories = []
+    issue_custom_fields = []
+
+    if project
+      principals += project.principals.sort
+      unless project.leaf?
+        subprojects = project.descendants.visible.all
+        principals += Principal.member_of(subprojects)
+      end
+      versions = project.shared_versions.all
+      categories = project.issue_categories.all
+      issue_custom_fields = project.all_issue_custom_fields
+    else
+      if all_projects.any?
+        principals += Principal.member_of(all_projects)
+      end
+      versions = Version.visible.where(:sharing => 'system').all
+      issue_custom_fields = IssueCustomField.where(:is_for_all => true)
+    end
+    principals.uniq!
+    principals.sort!
+    users = principals.select {|p| p.is_a?(User)}
+
+    unless Redmine::VERSION.to_s > '2.4'
+      add_available_filter "status_id",
+        :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    if project.nil?
+      project_values = []
+      if User.current.logged? && User.current.memberships.any?
+        project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
+      end
+      project_values += all_projects_values
+      add_available_filter("project_id",
+        :type => :list, :values => project_values
+      ) unless project_values.empty?
+    end
+
+    add_available_filter "tracker_id",
+      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter "priority_id",
+      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
+
+    author_values = []
+    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    author_values += users.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("author_id",
+      :type => :list, :values => author_values
+    ) unless author_values.empty?
+
+    assigned_to_values = []
+    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    assigned_to_values += (Setting.issue_group_assignment? ?
+                              principals : users).collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("assigned_to_id",
+      :type => :list_optional, :values => assigned_to_values
+    ) unless assigned_to_values.empty?
+
+    group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
+    add_available_filter("member_of_group",
+      :type => :list_optional, :values => group_values
+    ) unless group_values.empty?
+
+    role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
+    add_available_filter("assigned_to_role",
+      :type => :list_optional, :values => role_values
+    ) unless role_values.empty?
+
+    if versions.any?
+      fixed_versions = []
+      fixed_versions << ["<< #{l(:label_current_version)} >>", 'current_version']
+      versions.sort.each{ |s| fixed_versions << ["#{s.project.name} - #{s.name}", s.id.to_s] }
+      add_available_filter "fixed_version_id",
+        :type => :list_optional,
+        :values => fixed_versions
+    end
+
+    if categories.any?
+      add_available_filter "category_id",
+        :type => :list_optional,
+        :values => categories.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_available_filter "subject", :type => :text
+    add_available_filter "created_on", :type => :date_past
+    add_available_filter "updated_on", :type => :date_past
+    add_available_filter "closed_on", :type => :date_past
+    add_available_filter "start_date", :type => :date
+    add_available_filter "due_date", :type => :date
+    add_available_filter "estimated_hours", :type => :float
+    add_available_filter "done_ratio", :type => :integer
+    add_available_filter "parent_issue_id", :type => :relation
+    add_available_filter "has_sub_issues", :type => :list,
+      :values => [ l(:general_text_yes), l(:general_text_no)],
+      :label => :label_agile_has_sub_issues
+    add_available_filter "version_status", :type => :list,
+      :name => l("label_attribute_of_fixed_version", :name => 'status'),
+      :values => Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]}
+    add_available_filter "parent_issue_tracker_id", :type => :list,
+      :label => :label_agile_parent_issue_tracker_id,
+      :values => Tracker.pluck(:name)
+
+    if subprojects.any?
+      add_available_filter "subproject_id",
+        :type => :list_subprojects,
+        :values => subprojects.collect{|s| [s.name, s.id.to_s] }
+    end
+
+
+    add_custom_fields_filters(issue_custom_fields)
+
+    add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
+
+    IssueRelation::TYPES.each do |relation_type, options|
+      add_available_filter relation_type, :type => :relation, :label => options[:name]
+    end
+
+    Tracker.disabled_core_fields(trackers).each {|field|
+      delete_available_filter field
+    }
+
+    add_available_filter "issue_id", :type => :integer, :label => :label_issue
+    add_available_filter 'description', :type => :text
+
+    if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
+      User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
+      add_available_filter 'is_private', type: :list,
+                           values: [[l(:general_text_yes), '1'], [l(:general_text_no), '0']]
+    end
+
+    if User.current.logged?
+      add_available_filter 'watcher_id', type: :list, values: [["<< #{l(:label_me)} >>", 'me']]
+    end
+  end
+
+  def available_columns
+    return @available_columns if @available_columns
+    @available_columns = self.class.available_columns.dup
+    @available_columns += (project ? project.all_issue_custom_fields : IssueCustomField).visible.collect { |cf| QueryCustomFieldColumn.new(cf) }
+
+    if User.current.allowed_to?(:view_time_entries, project, :global => true)
+      index = nil
+      @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
+      index = (index ? index + 1 : -1)
+      # insert the column after estimated_hours or at the end
+      @available_columns.insert index, QueryColumn.new(:spent_hours,
+        :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
+        :default_order => 'desc',
+        :caption => :label_spent_time
+      )
+    end
+
+    if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
+      User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
+      @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
+    end
+
+    disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
+    @available_columns.reject! {|column|
+      disabled_fields.include?(column.name.to_s)
+    }
+
+    @available_columns.reject! {|column| column.name == :done_ratio} unless Issue.use_field_for_done_ratio?
+
+    @available_columns
+  end
+
+  def editable_by?(user)
+    return false unless user
+    # Admin can edit them all and regular users can edit their private queries
+    return true if user.admin? || (is_private? && user_id == user.id)
+    # Members can not edit public queries that are for all project (only admin is allowed to)
+    is_public? && !@is_for_all && user.allowed_to?(:manage_public_agile_queries, project, global: true)
+  end
+
+  def default_columns_names
+    @default_columns_names = RedmineAgile.default_columns.map(&:to_sym)
+  end
+
+  def has_column_name?(name)
+    columns.detect{|c| c.name == name}
+  end
+
+  def groupable_columns
+    available_columns.select {|c| c.groupable && !c.is_a?(QueryCustomFieldColumn)}
+  end
+
+  def sql_for_issue_id_field(field, operator, value)
+    if operator == "="
+      # accepts a comma separated list of ids
+      ids = value.first.to_s.scan(/\d+/).map(&:to_i)
+      if ids.present?
+        "#{Issue.table_name}.id IN (#{ids.join(",")})"
+      else
+        "1=0"
+      end
+    else
+      sql_for_field("id", operator, value, Issue.table_name, "id")
+    end
+  end
+
+  def sql_for_watcher_id_field(field, operator, value)
+    db_table = Watcher.table_name
+    "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
+      sql_for_field(field, '=', value, db_table, 'user_id') + ')'
+  end
+
+  def sql_for_version_status_field(field, operator, value)
+     sql_for_field(field, operator, value, Version.table_name, "status")
+  end
+
+  def sql_for_has_sub_issues_field(field, operator, value)
+    cond = ''
+    cond = 'NOT' if operator == '=' && value.include?(I18n.t(:general_text_no))
+    cond = 'NOT' if operator == '!' && value.include?(I18n.t(:general_text_yes))
+    "( #{cond} EXISTS ( SELECT * FROM #{Issue.table_name} AS subissues WHERE subissues.parent_id = issues.id ) )"
+  end
+
+  def sql_for_parent_issue_id_field(field, operator, value, options={})
+    value = value.first.split(",") if value.is_a? Array
+    value = value.split(",") if value.is_a? String
+    sql = case operator
+      when "*", "!*", "=", "!"
+        sql_for_field(field, operator, value, queried_table_name, "parent_id")
+      when "=p", "=!p", "!p"
+        op = (operator == "!p" ? 'NOT IN' : 'IN')
+        comp = (operator == "=!p" ? '<>' : '=')
+        "#{Issue.table_name}.parent_id #{op} (SELECT DISTINCT #{Issue.table_name}.id FROM #{Issue.table_name} WHERE #{Issue.table_name}.project_id #{comp} #{value.first.to_i})"
+      end
+    "(#{sql})"
+  end
+
+  def sql_for_parent_issue_tracker_id_field(field, operator, value)
+    cond = if operator == '=' then '' else 'NOT' end
+    selected_trackers_ids = Tracker.where(:name => value).pluck(:id).join(',')
+    "( EXISTS (SELECT * FROM #{Issue.table_name} AS parents WHERE parents.tracker_id #{cond} IN (#{selected_trackers_ids}) AND parents.id = issues.parent_id ) )"
+  end
+
+  def sql_for_member_of_group_field(field, operator, value)
+    if operator == '*' # Any group
+      groups = Group.all
+      operator = '=' # Override the operator since we want to find by assigned_to
+    elsif operator == "!*"
+      groups = Group.all
+      operator = '!' # Override the operator since we want to find by assigned_to
+    else
+      groups = Group.where(:id => value).all
+    end
+    groups ||= []
+
+    members_of_groups = groups.inject([]) {|user_ids, group|
+      user_ids + group.user_ids + [group.id]
+    }.uniq.compact.sort.collect(&:to_s)
+
+    '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
+  end
+
+  def sql_for_assigned_to_role_field(field, operator, value)
+    case operator
+    when "*", "!*" # Member / Not member
+      sw = operator == "!*" ? 'NOT' : ''
+      nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
+      "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
+        " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
+    when "=", "!"
+      role_cond = value.any? ?
+        "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
+        "1=0"
+
+      sw = operator == "!" ? 'NOT' : ''
+      nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
+      "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
+        " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
+    end
+  end
+
+  def condition_for_status
+    if Redmine::VERSION.to_s > '2.4'
+      return {:status_id => options[:f_status] || IssueStatus.where(:is_closed => false)}
+    end
+    '1=1'
+  end
+
+  def issues(options={})
+    order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
+    scope = issue_scope.
+      joins(:status).
+      eager_load((options[:include] || []).uniq).
+      where(options[:conditions]).
+      order(order_option).
+      joins(joins_for_order_statement(order_option.join(','))).
+      limit(options[:limit]).
+      offset(options[:offset])
+
+    scope = scope.preload(:custom_values)
+    if has_column?(:author)
+      scope = scope.preload(:author)
+    end
+    if color_base == AgileColor::COLOR_GROUPS[:issue]
+      scope = scope.preload(:agile_color)
+    end
+
+    if color_base == AgileColor::COLOR_GROUPS[:priority]
+      scope = scope.preload(:priority => :agile_color)
+    end
+
+    if color_base == AgileColor::COLOR_GROUPS[:tracker]
+      scope = scope.preload(:tracker => :agile_color)
+    end
+
+    if has_column_name?(:checklists)
+      scope = scope.preload(:checklists)
+    end
+
+    if order_option.detect {|x| x.match("agile_data.position")}
+      scope = scope.sorted_by_rank
+    end
+
+    if has_column_name?(:last_comment)
+      journal_comment = Journal.joins(:issue).where("#{Journal.table_name}.notes <> ''").
+        where(:issues => {:id => issues_ids(scope)}).order("#{Journal.table_name}.id ASC")
+      @last_comments = {}
+
+      journal_comment.each do |lc|
+        @last_comments[lc.journalized_id] = lc
+      end
+    end
+
+    if has_column_name?(:day_in_state)
+      @journals_for_state = Journal.joins(:details).where(
+        :journals => {
+          :journalized_id => issues_ids(scope),
+          :journalized_type => "Issue"
+        },
+        :journal_details => {:prop_key => 'status_id'}).order("created_on DESC")
+    end
+
+
+    scope
+    rescue ::ActiveRecord::StatementInvalid => e
+      raise StatementInvalid.new(e.message)
+  end
+
+  def issues_ids(scope)
+    @issues_ids ||= scope.map(&:id)
+  end
+
+  def journals_for_state
+    @journals_for_state
+  end
+
+  def issue_last_comment(issue, options = {})
+    return unless has_column_name?(:last_comment) || options[:inline_adding]
+    return issue.last_comment unless @last_comments
+    @last_comments[issue.id]
+  end
+
+  def board_statuses
+    if Redmine::VERSION.to_s > '2.4'
+      statuses =
+        if Redmine::VERSION.to_s >= '3.4' && project
+          project.rolled_up_statuses
+        else
+          IssueStatus.where(:id => Tracker.eager_load(:issues => [:status, :project, :fixed_version]).where(statement).map(&:issue_statuses).flatten.uniq.map(&:id))
+        end
+      status_filter_values = (options[:f_status] if options)
+      if status_filter_values
+        result_statuses = statuses.where(:id => status_filter_values)
+      else
+        result_statuses = statuses.where(:is_closed => false)
+      end
+      result_statuses.sorted.map do |s|
+        s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i
+        if has_column_name?(:estimated_hours)
+          s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f
+        end
+        if has_column_name?(:story_points)
+          s.instance_variable_set "@story_points", self.issue_count_by_story_points[s.id].to_i
+        end
+        if options && options[:wp]
+          wp_string = options[:wp][s.id.to_s]
+          if /(\d+)-?(\d*)/i =~ wp_string
+            s.instance_variable_set("@wp_max", $2.blank? ? $1.to_i : $2.to_i)
+            s.instance_variable_set("@wp_min", $1.to_i) if  !$1.blank? && !$2.blank?
+          end
+        end
+
+        def s.over_wp_limit?
+          return false if @wp_max.blank?
+          @wp_max.to_i < @issue_count
+        end
+
+        def s.under_wp_limit?
+          return false if @wp_min.blank?
+          @wp_min.to_i > @issue_count
+        end
+
+        def s.wp_class
+          return 'over_wp_limit' if over_wp_limit?
+          'under_wp_limit' if under_wp_limit?
+        end
+        s
+      end
+    else
+      status_filter_operator = filters.fetch("status_id", {}).fetch(:operator, nil)
+      status_filter_values = filters.fetch("status_id", {}).fetch(:values, [])
+      statuses = IssueStatus.where(:id => Tracker.eager_load(:issues => [:status, :project, :fixed_version]).where(statement).map(&:issue_statuses).flatten.uniq.map(&:id))
+      result_statuses = case status_filter_operator
+      when "o"
+        statuses.where(:is_closed => false).sorted
+      when "c"
+        statuses.where(:is_closed => true).sorted
+      when "="
+        statuses.where(:id => status_filter_values).sorted
+      when "!"
+        statuses.where("#{IssueStatus.table_name}.id NOT IN (" + status_filter_values.map{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")").sorted
+      else
+        statuses.sorted
+      end
+      result_statuses.map do |s|
+        s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i
+        if has_column_name?(:estimated_hours)
+          s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f
+        end
+        s
+      end
+      s
+    end
+  end
+  def swimlanes
+    return [] unless self.grouped?
+    lane_ids = issue_scope.group(self.group_by_column.groupable).count.keys
+    lanes = Issue.reflect_on_association(self.group_by_column.name).klass.where(:id => lane_ids).reorder(group_by_sort_order)
+    lanes = lanes.eager_load(:agile_data) if self.group_by_column.name == :parent
+    lanes = lanes.to_a
+    lanes << nil if lane_ids.include?(nil)
+    lanes
+  end
+
+  def issue_count_by_swimlane
+    @issue_count_by_swimlane ||= issue_scope.group("#{Issue.table_name}.#{self.group_by_column.name}_id").count if self.grouped?
+  end
+
+  def issue_count_by_status
+    @issue_count_by_status ||= issue_scope.group("#{Issue.table_name}.status_id").count
+  end
+
+  def issue_count_by_estimated_hours
+    @issue_count_by_estimated_hours ||= issue_scope.group("#{Issue.table_name}.status_id").sum("estimated_hours")
+  end
+
+  def issue_count_by_story_points
+    @issue_count_by_story_points ||= issue_scope.group("#{Issue.table_name}.status_id").sum("#{AgileData.table_name}.story_points")
+  end
+
+  def issue_board(options={})
+    @truncated = RedmineAgile.board_items_limit <= issue_scope.count
+    all_issues = self.issues.limit(RedmineAgile.board_items_limit).sorted_by_rank
+    grouped_issues = grouped? ? all_issues.group_by{|i| [i.status_id, i.send("#{self.group_by_column.name}_id")]} : all_issues.group_by{|i| [i.status_id]}
+    grouped_issues.values.each{|x|x.sort!{|a,b| a.position.to_i <=> b.position.to_i}} if grouped?
+    grouped_issues
+          end
+
+  # Function for filter with relations
+
+  def sql_for_relations(field, operator, value, options={})
+    relation_options = IssueRelation::TYPES[field]
+    return relation_options unless relation_options
+
+    relation_type = field
+    join_column, target_join_column = "issue_from_id", "issue_to_id"
+    if relation_options[:reverse] || options[:reverse]
+      relation_type = relation_options[:reverse] || relation_type
+      join_column, target_join_column = target_join_column, join_column
+    end
+
+    sql = case operator
+      when "*", "!*"
+        op = (operator == "*" ? 'IN' : 'NOT IN')
+        "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')"
+      when "=", "!"
+        op = (operator == "=" ? 'IN' : 'NOT IN')
+        "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
+      when "=p", "=!p", "!p"
+        op = (operator == "!p" ? 'NOT IN' : 'IN')
+        comp = (operator == "=!p" ? '<>' : '=')
+        "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
+      when "*o", "!o"
+        op = (operator == "!o" ? 'NOT IN' : 'IN')
+        "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
+      end
+
+    if relation_options[:sym] == field && !options[:reverse]
+      sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
+      sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
+    end
+    "(#{sql})"
+  end
+
+  IssueRelation::TYPES.keys.each do |relation_type|
+    alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
+  end
+
+  def draw_relations
+    r = options[:draw_relations]
+    r.nil? || r == '1'
+  end
+
+  def draw_relations=(arg)
+    options[:draw_relations] = (arg == '0' ? '0' : nil)
+  end
+
+  def statement
+    if values_for('fixed_version_id') == ['current_version'] && project
+      version = current_version
+      # substitute id for current version
+      filters['fixed_version_id'][:values] = [version.id.to_s] if version
+    end
+    clauses = super
+    if version
+      # return string for correct value in a select on a form
+      filters['fixed_version_id'][:values] = ['current_version']
+    end
+    clauses
+  end
+
+private
+  def issue_scope
+    Issue.visible.
+      eager_load(:status,
+                 :project,
+                 :assigned_to,
+                 :tracker,
+                 :priority,
+                 :category,
+                 :fixed_version,
+                 :agile_data).
+      where(statement).
+      where(condition_for_status)
+  end
+
+  def current_version
+    return @current_version if @current_version
+    versions = project.shared_versions.open.where("LOWER(#{Version.table_name}.name) NOT LIKE LOWER(?)", 'backlog')
+    versions -= versions.select(&:completed?).reverse
+    @current_version = versions.to_a.uniq.sort.first
+  end
+  def set_default_when_appropriate
+    if options[:is_default]
+      set_as_default
+    end
+  end
+
+end
diff --git a/plugins/redmine_agile/app/models/agile_versions_query.rb b/plugins/redmine_agile/app/models/agile_versions_query.rb
new file mode 100644
index 0000000000000000000000000000000000000000..53c18b90b371931cf169e5353fcf196bfcb8b5b3
--- /dev/null
+++ b/plugins/redmine_agile/app/models/agile_versions_query.rb
@@ -0,0 +1,150 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+
+class AgileVersionsQuery < Query
+  unloadable
+
+  self.queried_class = Issue
+  self.view_permission = :view_issues if Redmine::VERSION.to_s >= '3.4'
+
+  self.available_columns = [
+    QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
+    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
+    QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
+    QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("users")}, :groupable => true),
+    QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => "#{Issue.table_name}.category_id"),
+    QueryColumn.new(:status, :groupable => true, :caption => :field_invoice_status),
+    QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => "#{Issue.table_name}.assigned_to_id")  
+  ]
+
+  def initialize(attributes=nil, *args)
+    super attributes
+    self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
+  end
+
+  def initialize_available_filters
+    principals = []
+    categories = []
+    issue_custom_fields = []
+
+    if project
+      principals += project.principals.sort
+      unless project.leaf?
+        subprojects = project.descendants.visible.all
+        principals += Principal.member_of(subprojects)
+      end
+      categories = project.issue_categories.all
+      issue_custom_fields = project.all_issue_custom_fields
+    else
+      if all_projects.any?
+        principals += Principal.member_of(all_projects)
+      end
+      issue_custom_fields = IssueCustomField.where(:is_for_all => true)
+    end
+    principals.uniq!
+    principals.sort!
+    users = principals.select {|p| p.is_a?(User)}
+
+    add_available_filter "tracker_id",
+      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter "priority_id",
+      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
+
+    author_values = []
+    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    author_values += users.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("author_id",
+      :type => :list, :values => author_values
+    ) unless author_values.empty?
+
+    assigned_to_values = []
+    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    assigned_to_values += (Setting.issue_group_assignment? ?
+                              principals : users).collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("assigned_to_id",
+      :type => :list_optional, :values => assigned_to_values
+    ) unless assigned_to_values.empty?
+
+    if categories.any?
+      add_available_filter "category_id",
+        :type => :list_optional,
+        :values => categories.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_available_filter "status_id",
+      :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
+
+    add_available_filter "estimated_hours", :type => :float
+    add_custom_fields_filters(issue_custom_fields)
+
+    add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
+  end
+
+  def backlog_version
+    @backlog_version = project.shared_versions.open.where("LOWER(#{Version.table_name}.name) LIKE LOWER(?)", "backlog").first ||
+        project.shared_versions.open.where(:effective_date => nil).first ||
+        project.shared_versions.open.order("effective_date ASC").first
+  end
+
+  def backlog_version_issues
+    return [] if backlog_version.blank?
+    backlog_version.fixed_issues.visible.joins(query_includes).where(statement).sorted_by_rank
+          end
+
+  def current_version
+    @current_version = Version.open.
+        where(:project_id => project).
+        where("#{Version.table_name}.id <> ?", self.backlog_version).
+        order("effective_date DESC").first
+  end
+
+  def current_version_issues
+    return [] if current_version.blank?
+    current_version.fixed_issues.visible.joins(query_includes).where(statement).sorted_by_rank
+          end
+
+  def no_version_issues(params={})
+    q = (params[:q] || params[:term]).to_s.strip
+    scope = Issue.visible.joins(query_includes)
+            if project
+      project_ids = [project.id]
+      project_ids += project.descendants.collect(&:id) if Setting.display_subprojects_issues?
+      scope = scope.where(:project_id => project_ids)
+    end
+    scope = scope.where(statement).where(:fixed_version_id => nil).sorted_by_rank
+            if q.present?
+      if q.match(/^#?(\d+)\z/)
+        scope = scope.where("(#{Issue.table_name}.id = ?) OR (LOWER(#{Issue.table_name}.subject) LIKE LOWER(?))", $1.to_i,"%#{q}%")
+      else
+        scope = scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%")
+      end
+    end
+    scope
+  end
+
+  def version_issues(version)
+    version.fixed_issues.visible.joins(query_includes).where(statement).sorted_by_rank
+          end
+  private
+
+  def query_includes
+    [:project]
+  end
+end
diff --git a/plugins/redmine_agile/app/views/agile_boards/_add_issue_card.html.erb b/plugins/redmine_agile/app/views/agile_boards/_add_issue_card.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bb7b80e6526c71969537fd046e3e60f9247804c5
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_add_issue_card.html.erb
@@ -0,0 +1,6 @@
+
+<% if User.current.allowed_to?(:add_issues, @project, :global => true) && @project && RedmineAgile.allow_create_card? && @project.trackers.any? %>
+  <div class="add-issue">
+    <input class="new-card__input" placeholder='<%= l(:label_agile_add_new_issue) %>' >
+  </div>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_board.html.erb b/plugins/redmine_agile/app/views/agile_boards/_board.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..ea98ab8a1ab8c8d2c2122a98b334838c2dd69dac
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_board.html.erb
@@ -0,0 +1,51 @@
+<%= form_tag({}) do -%>
+  <%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params) %>
+  <%= hidden_field_tag 'project_id', @project.id if @project %>
+  <% board_statuses = IssueStatus.sorted %>
+  <div class="agile-board autoscroll">
+    <div class="flash error" style="display: none;" id="agile-board-errors">
+    </div>
+    <table class="list issues-board <%= 'status-colors' if RedmineAgile.status_colors? %> <%= 'minimize-closed' if RedmineAgile.minimize_closed? %>">
+    <div class='lock'> </div>
+      <thead>
+      <%= render_board_headers(@board_columns) %>
+      </thead>
+      <% if @swimlanes.size > 0 %>
+        <% @swimlanes.each do |swimlane| %>
+          <tr class="group open swimlane" data-id="<%= swimlane && swimlane.id || swimlane %>">
+            <td colspan="<%= @board_columns.size %>">
+              <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
+              <%= swimlane.blank? ? l(:label_none) : format_swimlane_object(swimlane) %><span class="count"><%= @query.issue_count_by_swimlane[swimlane && swimlane.id || swimlane] %></span>
+              <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
+                                   "toggleAllRowGroups(this)", :class => 'toggle-all') %>
+            </td>
+          </tr>
+
+          <tr style="text-align: center;white-space: nowrap;" class="swimlane issue <%= cycle('odd', 'even') %>" data-id="<%= swimlane && swimlane.id || swimlane %>" data-field="<%= @query.grouped? && "#{@query.group_by_column.name}_id" %>">
+          <% @board_columns.each do |column| %>
+            <td class="issue-status-col <%= 'closed' if column.is_closed? %> <%= column.wp_class if column.respond_to?(:wp_class) %>" data-id="<%= column.id %>">
+            <% @issue_board[[column.id, swimlane && swimlane.id || swimlane]].each do |issue| %>
+              <%= render :partial => 'issue_card', :locals => {:issue => issue} %>
+            <% end if  @issue_board[[column.id, swimlane && swimlane.id || swimlane]] %>
+            </td>
+            <% end %>
+          </tr>
+        <% end %>
+      <% else %>
+          <tr style="text-align: center;white-space: nowrap;" class="issue <%= cycle('odd', 'even') %>">
+          <% @board_columns.each do |column| %>
+            <td class="issue-status-col <%= 'closed' if column.is_closed? %> <%= 'collapse' if RedmineAgile.hide_closed_issues_data? %> <%= column.wp_class if column.respond_to?(:wp_class) %>" data-id="<%= column.id %>">
+              <% @issue_board[[column.id]].each do |issue| %>
+                <%= render :partial => 'issue_card', :locals => {:issue => issue} %>
+              <% end if @issue_board[[column.id]] %>
+              <%= render(:partial => 'add_issue_card') unless column.is_closed? %>
+            </td>
+
+            <% end %>
+          </tr>
+      <% end %>
+
+    </table>
+</div>
+
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_index.html.erb b/plugins/redmine_agile/app/views/agile_boards/_index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3bd1aa046b7582e0050b459a58b1f8fccba3a07f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_index.html.erb
@@ -0,0 +1,120 @@
+<div class="contextual">
+<% if !@query.new_record? && @query.editable_by?(User.current) %>
+  <%= link_to l(:button_edit), edit_agile_query_path(@query), :class => 'icon icon-edit' %>
+  <%= delete_link agile_query_path(@query) %>
+<% end %>
+</div>
+
+<% html_title(@query.new_record? ? l(:label_agile_board) : @query.name) %>
+<h2>
+  <%= @query.new_record? ? l(:label_agile_board) : h(@query.name) %>
+  <span class="live_search">
+    <%= text_field_tag(:search, '', :id => 'agile_live_search', :class => 'live_search_field', :placeholder => l(:label_cards_search)) %>
+  </span>
+</h2>
+
+<%= form_tag({ :controller => 'agile_boards', :action => 'index', :project_id => @project },
+            :method => :get, :id => 'query_form', :onsubmit => 'DisableNullFields()') do %>
+  <div id="query_form_with_buttons" class="hide-when-print">
+    <%= hidden_field_tag 'set_filter', '1' %>
+    <div id="query_form_content">
+      <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+        <div style="<%= @query.new_record? ? "" : "display: none;" %>">
+          <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
+        </div>
+      </fieldset>
+      <fieldset id="options" class="collapsible collapsed">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
+        <div style="display: none;">
+          <table class="options">
+            <% if Redmine::VERSION.to_s > '2.4' %>
+              <tr>
+                <td>
+                  <%= l(:label_agile_board_columns) %>
+                </td>
+                <td class="card-fields">
+                  <%= render_board_fields_status(@query) %>
+                </td>
+              </tr>
+            <% end %>
+            <tr>
+              <td><%= l(:label_agile_fields) %></td>
+              <td class="card-fields">
+                <%= render_board_fields_selection(@query) %>
+              </td>
+            </tr>
+            <tr>
+              <td><label for='group_by'><%= l(:label_agile_swimlanes) %></label></td>
+              <td><%= select_tag('group_by',
+                                 options_for_select(
+                                   [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
+                                   @query.group_by)
+                         ) %></td>
+            </tr>
+            <% if RedmineAgile.use_colors? && @query.respond_to?(:color_base) %>
+              <tr>
+                <td><label for='color_base'><%= l(:label_agile_color_based_on) %></label></td>
+                <td><%= select_tag 'color_base', options_card_colors_for_select(@query.color_base) %></td>
+              </tr>
+            <% end %>
+          </table>
+        </div>
+      </fieldset>
+    </div>
+    <p class="buttons">
+    <span class="contextual">
+      <%= link_to_function l(:label_agile_fullscreen), '$("html").toggleClass("agile-board-fullscreen"); saveFullScreenState()', :class => 'icon icon-fullscreen' %>
+    </span>
+    <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
+    <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload'  %>
+    <% if @query.new_record? && User.current.allowed_to?(:add_agile_queries, @project, :global => true) %>
+        <%= link_to_function l(:button_save),
+                             "$('#query_form').attr('action', '#{ @project ? new_project_agile_query_path(@project) : new_agile_query_path }').submit()",
+                             :class => 'icon icon-save' %>
+    <% end %>
+    </p>
+
+  </div>
+<% end %>
+
+<%= error_messages_for 'query' %>
+<% if @query.valid? %>
+  <% if @issues.empty? || @board_columns.empty? %>
+    <p class="nodata"><%= l(:label_no_data) %></p>
+  <% else %>
+    <% if @query.truncated %>
+      <p class="warning"><%= l(:label_agile_board_truncated, :max => RedmineAgile.board_items_limit) %></p>
+    <% end %>
+    <%= render :partial => 'board' %>
+  <% end %>
+<% end %>
+
+<% content_for :sidebar do %>
+  <%= render :partial => 'issues_links' %>
+  <% if @project && @project.assignable_users.any? %>
+    <%= render :partial => 'members' %>
+  <% end %>
+  <%= render :partial => 'agile_charts/agile_charts' %>
+<% end %>
+
+<% html_title l(:label_agile_board) %>
+<%= javascript_tag "agileContextMenuInit('#{ url_for(issues_context_menu_path) }')" %>
+<% content_for :header_tags do %>
+  <%= javascript_include_tag "redmine_agile", :plugin => 'redmine_agile' %>
+  <%= javascript_include_tag "jquery.ui.touch-punch.js", :plugin => 'redmine_agile' %>
+  <%= javascript_include_tag "visibility.min.js", :plugin => 'redmine_agile' %>
+  <%= javascript_include_tag "redmine_agile_context_menu", :plugin => 'redmine_agile' %>
+  <%= stylesheet_link_tag 'context_menu' %>
+  <%= stylesheet_link_tag "redmine_agile.css", :plugin => "redmine_agile", :media => "print" %>
+<% end %>
+<% if User.current.allowed_to?(:edit_issues, @project, :global => true) %>
+  <script type="text/javascript">
+    var agileBoard = new AgileBoard({
+      project_id: '<%= @project && @project.id %>',
+      update_agile_board_path: '<%= escape_javascript update_agile_board_path %>',
+      issues_path: '<%= escape_javascript issues_path %>',
+      create_issue_path: '<%= escape_javascript(agile_create_issue_path(:project_id => @project)) if @project %>'
+    });
+  </script>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_issue_card.html.erb b/plugins/redmine_agile/app/views/agile_boards/_issue_card.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..a674c5cf0c1b4eed29c370d6cfdda40ed8d5b184
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_issue_card.html.erb
@@ -0,0 +1,158 @@
+<div class="issue-card hascontextmenu <%= agile_color_class(issue, :color_base => @query.respond_to?(:color_base) && @query.color_base) %> <%= class_for_closed_issue(issue) %>" data-id="<%= issue.id %>" style="<%= agile_user_color(issue.assigned_to, :color_base => @query.respond_to?(:color_base) && @query.color_base) if issue.assigned_to %>">
+    <% if issue.closed? && RedmineAgile.hide_closed_issues_data? %>
+      <span class="fields">
+         <div class="tooltip">
+           <p class="issue-id <%= 'without-tracker' if @query.has_column_name?(:tracker).blank? %>">
+               <strong><%= link_to "#{'#' + issue.id.to_s}", issue_path(issue) %></strong>
+            </p>
+            <span class="tip">
+            </span>
+          </div>
+      </span>
+    <% else %>
+      <span class="fields">
+          <% if User.current.allowed_to?(:edit_issues, @project, :global => true) && RedmineAgile.allow_inline_comments?  %>
+            <div class="quick-edit-card">
+              <%# link_to image_tag("/images/edit.png"), edit_issue_path(issue), :class => "", :style => "margin-right: 3px;", :title => l(:button_edit) %>
+              <%= link_to image_tag("/images/comment.png", :alt => "Edit"), "#", :onclick => "showInlineComment(this, '#{j(agile_inline_comment_path(:id => issue))}'); return false;", :title => l(:label_comment_add), :class => 'add-comment' %>
+            </div>
+          <% end %>
+          <% if @query.has_column_name?(:project) %>
+            <p class="project">
+              <%= issue.project.name %>
+            </p>
+          <% end %>
+          <p class="issue-id <%= 'without-tracker' if @query.has_column_name?(:tracker).blank? %>">
+            <%= check_box_tag("ids[]", issue.id, false, :id => nil, :class => 'checkbox') %>
+            <% if @query.has_column_name?(:tracker) %>
+              <strong><%= issue_heading(issue) %></strong>
+            <% end %>
+            <%= render_issue_card_hours(@query, issue) %>
+          </p>
+          <p class="name" ><%= link_to "#{'#' + issue.id.to_s + ': ' if @query.has_column_name?(:id) && @query.has_column_name?(:tracker).blank?}#{issue.subject.truncate(100)}", issue_path(issue) %></p>
+          <p class="attributes">
+            <% @query.card_columns.select{|c| !c.value(issue).blank? }.each do |column| %>
+              <b><%= column.caption %></b>: <%= column_content(column, issue) %> <br>
+            <% end %>
+          </p>
+
+          <% if @query.has_column_name?(:day_in_state) %>
+            <p class="attributes">
+                <b><%= " #{I18n.t('label_agile_day_in_state')}: " %></b>
+                <% find_change_state = false %>
+                <% if @query.journals_for_state %>
+                  <% @query.journals_for_state.each do |journal| %>
+                    <% if journal.journalized_id == issue.id %>
+                      <%= time_in_state(journal.created_on) %>
+                      <% find_change_state = true %>
+                      <% break %>
+                    <% end %>
+                  <% end %>
+                <% end %>
+
+                <% if !find_change_state %>
+                  <%= time_in_state(issue.day_in_state) %>
+                <% end %>
+            </p>
+          <% end %>
+
+          <% if @query.has_column_name?(:description) && !issue.description.blank? %>
+            <em class="info description">
+              <%= issue.description.truncate(200) %>
+            </em>
+          <% end %>
+          <% if @query.has_column_name?(:sub_issues) && issue.sub_issues.any? %>
+            <div class='sub-issues'>
+              <%= issue_children(issue) %>
+            </div>
+          <% end %>
+          <% if @query.has_column_name?(:thumbnails)  %>
+            <% image = issue.attachments.select(&:thumbnailable?).last %>
+            <% if image %>
+            <div class="thumbnail" style="background-image: url('<%= thumbnail_path(image, :size => 250) %>')">
+            </div>
+            <% end %>
+          <% end %>
+          <!-- Check list -->
+          <% if @query.has_column_name?(:checklists) && show_checklist?(issue) %>
+            <div class="checklist" id="checklist_<%= issue.id %>">
+              <ul id="checklist_items">
+                <% issue.checklists.each do |checklist_item| %>
+                  <%= render :partial => 'checklists/checklist_item', :object => checklist_item %>
+                <% end %>
+              </ul>
+              <script type="text/javascript">
+                $("#checklist_<%= issue.id %>").checklist();
+              </script>
+            </div>
+          <% end %>
+          <% if @query.has_column_name?(:assigned_to) %>
+            <p class="info assigned-user" style="<%= 'display: none;' unless issue.assigned_to %>">
+              <span class="user"><%= avatar(issue.assigned_to, :size => "14").to_s.html_safe + " " + link_to_user(issue.assigned_to) if issue.assigned_to %></span>
+            </p>
+          <% end %>
+
+          <% if @query.has_column_name?(:done_ratio) %>
+            <%= progress_bar(issue.done_ratio, :width => '100%') %>
+          <% end %>
+
+          <% if (last_comment = @query.issue_last_comment(issue, :inline_adding => @inline_adding)) %>
+            <em class="info description last_comment" title='<%= last_comment.user.to_s + " " + format_date(last_comment.created_on) %>'>
+              <span class="icon icon-comment last-comment">
+                <%= last_comment.notes.truncate(100) %>
+              </span>
+            </em>
+          <% end %>
+          <% if User.current.allowed_to?(:edit_issues, @project, :global => true) && RedmineAgile.allow_inline_comments? %>
+            <div class="quick-comment">
+            </div>
+          <% end %>
+      </span>
+    <% end %>
+</div>
+<% if User.current.allowed_to?(:edit_issues, @project, :global => true) && @update %>
+  <script type="text/javascript">
+
+    document.onkeydown = function(evt) {
+        evt = evt || window.event;
+        if (evt.keyCode == 27) {
+            $('html.agile-board-fullscreen').removeClass('agile-board-fullscreen');
+            $(".issue-card").addClass("hascontextmenu");
+            saveFullScreenState();
+        }
+    };
+
+    $("table.issues-board thead").html("<%=escape_javascript render_board_headers(@query.board_statuses) %>");
+  </script>
+
+  <% if @error_msg %>
+    <script type="text/javascript">
+      setErrorMessage("<%= @error_msg%>", 'warning');
+    </script>
+  <% end %>
+
+  <% if @update %>
+    <script type="text/javascript">
+      $("table.issues-board thead").html("<%=escape_javascript render_board_headers(@query.board_statuses) %>");
+    </script>
+    <% if Redmine::VERSION.to_s > '2.4' %>
+      <script type="text/javascript">
+        agileBoard.initDroppable();
+      </script>
+      <script type="text/javascript">
+        $("td.issue-status-col[data-id=<%= issue.status.id %>]").removeClass("over_wp_limit under_wp_limit");
+        $("td.issue-status-col[data-id=<%= issue.status.id %>]").addClass("<%= @wp_class %>");
+        <% if @old_status %>
+          $("td.issue-status-col[data-id=<%= @old_status.id %>]").removeClass("over_wp_limit under_wp_limit");
+          $("td.issue-status-col[data-id=<%= @old_status.id %>]").addClass("<%= @wp_class_for_old_status %>");
+        <% end %>
+      </script>
+    <% end %>
+    <% if @not_in_scope %>
+      <script type="text/javascript">
+        $(".issue-card[data-id='<%= issue.id %>']").toggle( "highlight" );
+      </script>
+    <% end %>
+  <% end %>
+  <%= init_agile_tooltip_info %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_issue_tooltip.html.erb b/plugins/redmine_agile/app/views/agile_boards/_issue_tooltip.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6228beee7f81a662efb9b7180b9e8a6e8ac00574
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_issue_tooltip.html.erb
@@ -0,0 +1 @@
+<%= render_issue_tooltip @issue %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_issues_links.html.erb b/plugins/redmine_agile/app/views/agile_boards/_issues_links.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..60ac827be4a2f63fcf5cf7ca365262b3d08c6eb5
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_issues_links.html.erb
@@ -0,0 +1,21 @@
+<h3><%= l(:label_issue_plural) %></h3>
+
+  <ul>
+  <li><%= link_to l(:label_issue_view_all), _project_issues_path(@project, :set_filter => 1) %></li>
+  <% if @project %>
+  <li><%= link_to l(:field_summary), project_issues_report_path(@project) %></li>
+  <% end %>
+
+  <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %>
+  <li><%= link_to l(:label_calendar), _project_calendar_path(@project) %></li>
+  <% end %>
+  <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %>
+  <li><%= link_to l(:label_gantt), _project_gantt_path(@project) %></li>
+  <% end %>
+  <% if User.current.allowed_to?(:view_agile_queries, @project) %>
+  <li><%= link_to l(:label_agile_board), {:controller => "agile_boards", :action => "index", :project_id => @project} %></li>
+  <% end %>
+  <% if User.current.allowed_to?(:manage_agile_verions, @project) %>
+  <li><%= link_to l(:label_agile_version_planning), {:controller => "agile_versions", :action => "index", :project_id => @project} %></li>
+  <% end %>
+  </ul>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_issues_sidebar.html.erb b/plugins/redmine_agile/app/views/agile_boards/_issues_sidebar.html.erb
new file mode 100755
index 0000000000000000000000000000000000000000..77c8cc8a49f359b9da53d6cfbedeced1b667983a
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_issues_sidebar.html.erb
@@ -0,0 +1,8 @@
+<% if User.current.allowed_to?(:view_agile_queries, @project, :global => true) %>
+  <%= link_to l(:label_agile_board), {:controller => "agile_boards", :action => "index", :project_id => @project} %>
+  <br>
+<% end %>
+<% if User.current.allowed_to?(:manage_agile_verions, @project) && @project.versions.open.any? %>
+  <%= link_to l(:label_agile_version_planning), {:controller => "agile_versions", :action => "index", :project_id => @project} %>
+  <br>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/_members.html.erb b/plugins/redmine_agile/app/views/agile_boards/_members.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..ffd2096b7c7b1c8a424350f5708572d498e44101
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/_members.html.erb
@@ -0,0 +1,8 @@
+<div class="project-members">
+  <h3><%=l(:label_member_plural)%></h3>
+    <% @project.assignable_users.each do |user| %>
+      <span class="assignable-user" data-id="<%= user.id %>"><%= avatar(user.is_a?(User) ? user : '<gravatar>', :size => "14", :style => agile_user_color(user, :color_base => @query.respond_to?(:color_base) && @query.color_base) ).to_s.html_safe + " " + link_to_user(user) %>
+      <br>
+      </span>
+    <% end %>
+</div>
diff --git a/plugins/redmine_agile/app/views/agile_boards/index.html.erb b/plugins/redmine_agile/app/views/agile_boards/index.html.erb
new file mode 100755
index 0000000000000000000000000000000000000000..269b466ceb9cdba7d5071b76ac789a6ba7ccd08f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/index.html.erb
@@ -0,0 +1,52 @@
+<%= render 'index' %>
+<%= javascript_tag do %>
+  var fullScreenState = localStorage.getItem('full-screen-board');
+  if (fullScreenState == "true") {
+    $("html").toggleClass("agile-board-fullscreen");
+  }
+
+  function updateBoard() {
+    if ( $('html').hasClass('agile-board-fullscreen') ) {
+      if ( $('.ui-sortable-helper').length == 0  &&
+        $('textarea:focus').length == 0 && $('.lock:visible').size() == 0 )
+      {
+        var lastScrollPosition = $('.agile-board.autoscroll').scrollTop();
+        $('.lock').show();
+        $.ajax(location.href, {
+          dataType: 'script',
+          contentType: 'text/javascript',
+          success: function(){
+            $(".agile-board.autoscroll").scrollTop(lastScrollPosition);
+          },
+          error: function(){
+            $(".agile-board.autoscroll").scrollTop(lastScrollPosition);
+          },
+          complete: function(){
+            $('.lock').hide();
+          }
+        });
+      }
+    };
+  }
+
+
+  Visibility.every(20 * 1000, function () {
+    updateBoard();
+  });
+
+  Visibility.change(function (e, state) {
+    if (state == 'visible') {
+      updateBoard();
+    }
+  });
+
+  function checkBoardVisibility() {
+    if (Visibility.state() != 'visible') {
+      updateBoard();
+    }
+  }
+
+  setInterval(checkBoardVisibility, 1200000);
+<% end %>
+<%= init_agile_tooltip_info %>
+<%= call_hook(:view_agile_board_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/index.js.erb b/plugins/redmine_agile/app/views/agile_boards/index.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1d3ad17da17ee836e53037c88a38f1c934678f2d
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/index.js.erb
@@ -0,0 +1,3 @@
+$('#content').html('<%= j(render('index')) %>')
+$('table.issues-board').StickyHeader();
+<%= init_agile_tooltip_info(:only_code => true) %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/inline_comment.html.erb b/plugins/redmine_agile/app/views/agile_boards/inline_comment.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..62349b239fed1fb288dcbef68a1d79ed088f0a92
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/inline_comment.html.erb
@@ -0,0 +1,3 @@
+<%= text_area_tag "issue[notes]", "", :cols => 60, :rows => 5, :class => 'wiki-edit', :no_label => true %>
+<button type="button" onclick="agileBoard.saveInlineComment(this, '<%= update_agile_board_path(:id => @issue) %>'); return false;">  <%= l(:button_submit) %> </button>
+<%= link_to l(:button_cancel), "#", :onclick => "cancelInlineComment(this);return false;" %>
diff --git a/plugins/redmine_agile/app/views/agile_boards/update.js.erb b/plugins/redmine_agile/app/views/agile_boards/update.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..30694140ad0446956e747aa77e1818f8fb9542b2
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_boards/update.js.erb
@@ -0,0 +1,6 @@
+decHtmlNumber('th[data-column-id="<%= params[:old_status_id] %>"] span.count');
+incHtmlNumber('th[data-column-id="<%= params[:new_status_id] %>"] span.count');
+decHtmlNumber('tr.group.swimlane[data-id="<%= params[:old_swimlane_id] %>"] td span.count');
+incHtmlNumber('tr.group.swimlane[data-id="<%= params[:new_swimlane_id] %>"] td span.count');
+
+//$('.issue-card[data-id="<%= @issue.id %>"]').html("<%= render :partial => 'issue_card', :locals => {:issue => @issue} %>");
diff --git a/plugins/redmine_agile/app/views/agile_charts/_agile_charts.html.erb b/plugins/redmine_agile/app/views/agile_charts/_agile_charts.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..9621af22cd934ec274aad9692c14666382fa5cdb
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_charts/_agile_charts.html.erb
@@ -0,0 +1,13 @@
+<% if User.current.allowed_to?(:view_agile_charts, @project, :global => true) %>
+<h3><%= l(:label_agile_chart_plural) %></h3>
+<ul>
+  <li><%= link_to l(:label_agile_charts_issues_burndown), {:controller => "agile_charts", :action => "show", :chart => "issues_burndown", :project_id => @project}, :class => "#{'selected' if @chart == 'issues_burndown'}" %></li>
+  <li><%= link_to l(:label_agile_charts_burnup), {:controller => "agile_charts", :action => "show", :chart => "burnup", :project_id => @project}, :class => "#{'selected' if @chart == 'burnup'}" %></li>
+  <li><%= link_to l(:label_agile_charts_issues_velocity), {:controller => "agile_charts", :action => "show", :chart => "issues_velocity", :project_id => @project}, :class => "#{'selected' if @chart == 'issues_velocity'}" %></li>
+  <li><%= link_to l(:label_agile_charts_lead_time), {:controller => "agile_charts", :action => "show", :chart => "lead_time", :project_id => @project}, :class => "#{'selected' if @chart == 'lead_time'}" %></li>
+  <li><%= link_to l(:label_agile_charts_trackers_cumulative_flow), {:controller => "agile_charts", :action => "show", :chart => "trackers_cumulative_flow", :project_id => @project}, :class => "#{'selected' if @chart == 'trackers_cumulative_flow'}" %></li>
+</ul>
+<% end %>
+<% if User.current.allowed_to?(:view_agile_queries, @project, :global => true) %>
+  <%= render_sidebar_agile_queries %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_charts/_chart.html.erb b/plugins/redmine_agile/app/views/agile_charts/_chart.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6a30b07d6d7684ca881926097baa87bd3b9822f6
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_charts/_chart.html.erb
@@ -0,0 +1,67 @@
+<% if issues_scope.empty? %>
+  <p class="nodata"><%= l(:label_no_data) %></p>
+<% elsif %w(work_burndown cumulative_flow hours_velocity).include?(chart) && issues_scope.count > RedmineAgile.time_reports_items_limit %>
+  <p class="nodata"><%= l(:label_agile_too_many_items, :max => RedmineAgile.time_reports_items_limit) %></p>
+<% else %>
+  <div class="agile-chart-container">
+    <canvas id="agile-chart"></canvas>
+  </div>
+<% end %>
+
+<%= javascript_tag do %>
+  $(document).ready(function(){
+
+    if (document.getElementById("agile-chart")) {
+      $.getJSON(<%= raw url_for(:controller => 'agile_charts', :action => 'render_chart', :project_id => @project,
+                                :chart => chart, :version_id => @version).to_json %>, function(data){
+
+        Chart.defaults.global.defaultFontColor = 'black';
+        Chart.defaults.global.defaultFontFamily = '"Arial", sans-serif';
+        Chart.defaults.global.defaultFontStyle = 'normal';
+
+        var chartData = {
+          labels: data['labels'],
+          datasets: data['datasets'],
+          stacked: data['stacked']
+        };
+
+        new Chart(document.getElementById("agile-chart").getContext("2d"), {
+          type: 'bar',
+          data: chartData,
+          options: {
+            maintainAspectRatio: false,
+            legend: { position: 'right' },
+            title: {
+              display: true,
+              fontSize: 16,
+              fontStyle: 'normal',
+              text: data['title']
+            },
+            elements: {
+              line: {
+                  tension: 0, // disables bezier curves
+              }
+            },
+            scales: {
+              yAxes: [{
+                stacked: data['stacked'],
+                scaleLabel: {
+                  display: true,
+                  fontColor: 'rgba(255, 0, 0 ,1)',
+                  fontSize: 14,
+                  labelString: data['y_title']
+                }
+              }],
+              xAxes: [{
+                  ticks: {
+                      autoSkip: true,
+                      maxRotation: 0
+                  }
+              }]
+            }
+          }
+        });
+      });
+    }
+  });
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_charts/_versions_show.html.erb b/plugins/redmine_agile/app/views/agile_charts/_versions_show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..16f2117162ec706cfa640ce0f7134ff310a3143f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_charts/_versions_show.html.erb
@@ -0,0 +1,17 @@
+<% if @issues.any? && User.current.allowed_to?(:view_agile_charts, @project) %>
+  <fieldset>
+    <legend>
+    <%= l(:label_agile_chart) %>
+    <%= select_tag('chart', options_charts_for_select(params[:chart] || RedmineAgile.default_chart),
+                       :id => 'chart_by_select',
+                       :data => {:remote => true, :method => 'get', :url => agile_charts_select_version_chart_path(:version_id => @version)}) %>
+    </legend>
+    <div id='agile_chart'>
+      <%= render_agile_chart(params[:chart] || RedmineAgile.default_chart, @version.fixed_issues) %>
+    </div>
+  </fieldset>
+<% end %>
+
+<% content_for :header_tags do %>
+  <%= chartjs_assets %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_charts/select_version_chart.js.erb b/plugins/redmine_agile/app/views/agile_charts/select_version_chart.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e9d4c38e431118d32b3607c4e144b9d7652c28d5
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_charts/select_version_chart.js.erb
@@ -0,0 +1,5 @@
+$('#agile_chart').html('<%= escape_javascript(render_agile_chart(params[:chart], @version.fixed_issues)) %>');
+
+if ("replaceState" in window.history) {
+  window.history.replaceState(null, document.title, "?chart=<%= params[:chart] %>");
+}
diff --git a/plugins/redmine_agile/app/views/agile_charts/show.html.erb b/plugins/redmine_agile/app/views/agile_charts/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f9d080435fce009a63c42837ab8b4b7ef154c0a6
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_charts/show.html.erb
@@ -0,0 +1,56 @@
+<%= render_agile_charts_breadcrumb %>
+
+<h2><%= @query.new_record? ? l(:label_agile_chart_plural) : h(@query.name) %></h2>
+<% html_title(@query.new_record? ? l(:label_agile_chart_plural) : @query.name) %>
+
+
+<%= form_tag({ :controller => 'agile_charts', :action => 'show', :project_id => @project },
+            :method => :get, :id => 'query_form') do %>
+  <div id="query_form_with_buttons" class="hide-when-print">
+    <%= hidden_field_tag 'set_filter', '1' %>
+    <div id="query_form_content">
+      <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+        <div style="<%= @query.new_record? ? "" : "display: none;" %>">
+          <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
+        </div>
+      </fieldset>
+      <fieldset class="collapsible collapsed">
+        <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
+        <div>
+          <table>
+            <tr>
+              <td><label for='chart'><%= l(:label_agile_chart) %></label></td>
+              <td><%= select_tag('chart', options_charts_for_select(@chart)) %></td>
+            </tr>
+            <tr>
+              <td><%= l(:label_agile_date_from) %></td>
+              <td>
+                <%= text_field_tag 'date_from', @query.date_from, :size => 10 %><%= calendar_for('date_from') %>
+                <%= l(:label_agile_date_to) %>
+                <%= text_field_tag 'date_to', @query.date_to, :size => 10 %><%= calendar_for('date_to') %>
+              </td>
+            </tr>
+          </table>
+        </div>
+      </fieldset>
+    </div>
+    <p class="buttons">
+      <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
+      <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload'  %>
+    </p>
+  </div>
+<% end %>
+
+<%= error_messages_for 'query' %>
+
+<%= render_agile_chart(@chart, @issues) if @query.valid? %>
+
+<% content_for :header_tags do %>
+  <%= chartjs_assets %>
+<% end %>
+
+<% content_for :sidebar do %>
+  <%= render :partial => 'agile_boards/issues_links' %>
+  <%= render :partial => 'agile_charts/agile_charts' %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_colors/index.html.erb b/plugins/redmine_agile/app/views/agile_colors/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..8440f02c0cca8679ebcb84310f46bed794bdfe96
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_colors/index.html.erb
@@ -0,0 +1,29 @@
+<h2><%=l(:label_agile_color) %> <%= select_tag "object_type", options_for_select([[l(:label_agile_tracker_colors), "tracker"], [l(:label_agile_issue_priority_colors), "issue_priority"]],  params[:object_type]), :onchange => "location = this.options[this.options.selectedIndex].value" %></h2>
+
+<%= form_tag(update_agile_colors_path(:object_type => params[:object_type]), :method => :put) do %>
+  <% if @coloreds.any? %>
+    <table class="list"><thead>
+    <tr>
+        <th><%= l(:field_name) %></th>
+    </tr></thead>
+    <% @coloreds.each do |colored| %>
+      <tr class="<%= cycle('odd', 'even') %>">
+          <td class="name">
+            <%= hidden_field_tag 'coloreds[][id]', colored.id %>
+            <%= select_tag 'coloreds[][color]', options_for_select(AgileColor::AGILE_COLORS, colored.color), :include_blank => true, :id => "color_select_#{colored.id}", :class => "agile-color" %>  <%= colored %>
+          </td>
+      </tr>
+    <% end %>
+    </table>
+    <%= javascript_tag "$('.agile-color').simplecolorpicker({picker: true});"%>
+
+  <% end %>
+  <%= submit_tag l(:button_save) %>
+<% end %>
+
+<% content_for :header_tags do %>
+  <%= javascript_include_tag 'jquery.simplecolorpicker.js', :plugin => "redmine_agile" %>
+  <%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_agile' %>
+<% end %>
+
+<% html_title(l(:enumeration_issue_priorities)) -%>
diff --git a/plugins/redmine_agile/app/views/agile_journal_details/assignee.html.erb b/plugins/redmine_agile/app/views/agile_journal_details/assignee.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..9b583997d7d1c5dba6b7f50f091d31667557544b
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_journal_details/assignee.html.erb
@@ -0,0 +1,27 @@
+<%= title [issue_heading(@issue) , issue_path(@issue)],  l(:field_assigned_to) %>
+
+<% html_title(l(:field_assigned_to)) %>
+
+<% if @assignees.any? %>
+  <table class="list"><thead>
+  <tr>
+      <th>#</th>
+      <th><%= l(:field_created_on) %></th>
+      <th><%= l(:field_assigned_to) %></th>
+      <th><%= l(:field_duration) %></th>
+      <th><%= l(:field_author) %></th>
+  </tr></thead>
+  <% @assignees.each_with_index do |status, index| %>
+  <% assignee = User.where(:id => status.value).first %>
+  <tr class="<%= cycle('odd', 'even') %>">
+      <td class="index"><%= index + 1 %></td>
+      <td class="name"><%= format_time(status.journal.created_on) %></td>
+      <td class="name"><%= avatar(assignee, :size => "14").to_s.html_safe + " " + link_to_user(assignee) %></td>
+      <td><%= event_duration(status, @assignees[index + 1]) %></td>
+      <td><%= link_to_user status.journal.user %></td>
+  </tr>
+  <% end %>
+  </table>
+<% else %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_journal_details/done_ratio.html.erb b/plugins/redmine_agile/app/views/agile_journal_details/done_ratio.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bcf9e453157192f2c8485b715726ef619d0f6c95
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_journal_details/done_ratio.html.erb
@@ -0,0 +1,28 @@
+<%= title [issue_heading(@issue) , issue_path(@issue)],  l(:field_done_ratio) %>
+
+<% html_title(l(:field_done_ratio)) %>
+
+<% if @done_ratios.any? %>
+  <table class="list"><thead>
+  <tr>
+      <th>#</th>
+      <th><%= l(:field_created_on) %></th>
+      <th><%= l(:field_done_ratio) %></th>
+      <th><%= l(:label_agile_charts_number_of_hours) %></th>
+      <th><%= l(:field_duration) %></th>
+      <th><%= l(:field_author) %></th>
+  </tr></thead>
+  <% @done_ratios.each_with_index do |done_ratio, index| %>
+  <tr class="<%= cycle('odd', 'even') %>">
+      <td class="index"><%= index + 1 %></td>
+      <td class="name"><%= format_time(done_ratio.journal.created_on) %></td>
+      <td><%= progress_bar(done_ratio.value.to_f, :width => '80px', :legend => "#{"%.2f" %done_ratio.value.to_f}%") %></td>
+      <td><%= "%.2f" % (@issue.estimated_hours.to_f * (100 - done_ratio.value.to_f) / 100.0) %></td>
+      <td><%= event_duration(done_ratio, @done_ratios[index + 1]) %></td>
+      <td><%= done_ratio.journal.user.name %></td>
+  </tr>
+  <% end %>
+  </table>
+<% else %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_journal_details/edit.html.erb b/plugins/redmine_agile/app/views/agile_journal_details/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d72d1ce590a941143802cb5f6f67178e8d9a8905
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_journal_details/edit.html.erb
@@ -0,0 +1 @@
+<h2>AgileDoneRatioFlowsController#edit</h2>
diff --git a/plugins/redmine_agile/app/views/agile_journal_details/new.html.erb b/plugins/redmine_agile/app/views/agile_journal_details/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..00643dd3ce4a962c13662b03822949c85d83f1d4
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_journal_details/new.html.erb
@@ -0,0 +1 @@
+<h2>AgileDoneRatioFlowsController#new</h2>
diff --git a/plugins/redmine_agile/app/views/agile_journal_details/status.html.erb b/plugins/redmine_agile/app/views/agile_journal_details/status.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..90f6d076fd792fce327a46d69ca223ddf70d3cd1
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_journal_details/status.html.erb
@@ -0,0 +1,27 @@
+<%= title [issue_heading(@issue) , issue_path(@issue)],  l(:label_issue_status) %>
+
+<% html_title(l(:label_issue_status_plural)) %>
+
+<% if @statuses.any? %>
+  <table class="list"><thead>
+  <tr>
+      <th>#</th>
+      <th><%= l(:field_created_on) %></th>
+      <th><%= l(:field_status) %></th>
+      <th><%= l(:field_duration) %></th>
+      <th><%= l(:field_author) %></th>
+  </tr></thead>
+  <% @statuses.each_with_index do |status, index| %>
+  <% issue_status = IssueStatus.where(:id => status.value).first %>
+  <tr class="<%= cycle('odd', 'even') %>">
+      <td class="index"><%= index + 1 %></td>
+      <td class="name"><%= format_time(status.journal.created_on) %></td>
+      <td><%= issue_status.name %></td>
+      <td><%= event_duration(status, @statuses[index + 1]) %></td>
+      <td><%= status.journal.user.name %></td>
+  </tr>
+  <% end %>
+  </table>
+<% else %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/_columns.html.erb b/plugins/redmine_agile/app/views/agile_queries/_columns.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..117acd847cd3e0b08c0728c65d1637271d6251f9
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/_columns.html.erb
@@ -0,0 +1,38 @@
+<table class="query-columns">
+  <tr>
+    <td style="padding-left:0">
+      <%= label_tag "available_columns", l(:description_available_columns) %>
+      <br />
+      <%= select_tag 'available_columns',
+              options_for_select(query_available_inline_columns_options(query)),
+              :multiple => true, :size => 10, :style => "width:150px",
+              :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %>
+    </td>
+    <td class="buttons">
+      <input type="button" value="&#8594;"
+       onclick="moveOptions(this.form.available_columns, this.form.selected_columns);" /><br />
+      <input type="button" value="&#8592;"
+       onclick="moveOptions(this.form.selected_columns, this.form.available_columns);" />
+    </td>
+    <td>
+      <%= label_tag "selected_columns", l(:description_selected_columns) %>
+      <br />
+      <%= select_tag tag_name,
+              options_for_select(query_selected_inline_columns_options(query)),
+              :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px",
+              :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);" %>
+    </td>
+    <td class="buttons">
+      <input type="button" value="&#8593;" onclick="moveOptionUp(this.form.selected_columns);" /><br />
+      <input type="button" value="&#8595;" onclick="moveOptionDown(this.form.selected_columns);" />
+    </td>
+  </tr>
+</table>
+
+<%= javascript_tag do %>
+$(document).ready(function(){
+  $('.query-columns').closest('form').submit(function(){
+    $('#selected_columns option').prop('selected', true);
+  });
+});
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/_filters.html.erb b/plugins/redmine_agile/app/views/agile_queries/_filters.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f12d2b091cc981f0122e6e4b110068e00c31f065
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/_filters.html.erb
@@ -0,0 +1,28 @@
+<%= javascript_tag do %>
+var operatorLabels = <%= raw_json Query.operators_labels %>;
+var operatorByType = <%= raw_json Query.operators_by_filter_type %>;
+var availableFilters = <%= raw_json query.available_filters_as_json %>;
+var labelDayPlural = <%= raw_json l(:label_day_plural) %>;
+var allProjects = <%= raw_json query.all_projects_values %>;
+$(document).ready(function(){
+  initFilters();
+  <% query.filters.each do |field, options| %>
+  addFilter("<%= field %>", <%= raw_json query.operator_for(field) %>, <%= raw_json query.values_for(field) %>);
+  <% end %>
+});
+<% end %>
+
+<table style="width:100%">
+<tr>
+<td>
+<table id="filters-table">
+</table>
+</td>
+<td class="add-filter" style="width: initial;">
+<%= label_tag('add_filter_select', l(:label_filter_add)) %>
+<%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %>
+</td>
+</tr>
+</table>
+<%= hidden_field_tag 'f[]', '' %>
+<% include_calendar_headers_tags %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/_form.html.erb b/plugins/redmine_agile/app/views/agile_queries/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..455ace044f15dc6bee98421d384d4246f5816bdc
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/_form.html.erb
@@ -0,0 +1,80 @@
+<%= error_messages_for 'query' %>
+
+<div class="box">
+<div class="tabular">
+  <%= hidden_field_tag 'gantt', '1' if params[:gantt] %>
+
+  <p><label for="query_name"><%=l(:field_name)%></label>
+  <%= text_field 'query', 'name', :size => 80 %></p>
+
+  <% if User.current.admin? || User.current.allowed_to?(:manage_public_agile_queries, @project) %>
+    <% if Redmine::VERSION.to_s < "2.4" %>
+      <p><label for="query_is_public"><%=l(:field_is_public)%></label>
+      <%= check_box 'query', 'is_public',
+            :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %></p>
+    <% else %>
+      <p><label><%=l(:field_visible)%></label>
+        <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PRIVATE %> <%= l(:label_visibility_private) %></label>
+        <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label>
+        <% Role.givable.sorted.each do |role| %>
+          <label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label>
+        <% end %>
+        <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PUBLIC %> <%= l(:label_visibility_public) %></label>
+        <%= hidden_field_tag 'query[role_ids][]', '' %>
+      </p>
+    <% end %>
+  <% end %>
+
+  <p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label>
+  <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?,
+        :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %></p>
+  <% if Redmine::VERSION.to_s > "2.4" %>
+    <p>
+      <label>
+        <%= l(:label_agile_default_board) %>
+      </label>
+      <%= check_box_tag 'query[is_default]', 1, @query.is_default? %>
+    </p>
+  <% end %>
+  <fieldset><legend><%= l(:label_options) %></legend>
+    <p><label for="query_default_columns"><%=l(:label_agile_board_default_fields)%></label>
+    <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
+          :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %></p>
+
+    <% if RedmineAgile.use_colors? && @query.respond_to?(:color_base) %>
+      <p><label for='color_base'><%= l(:label_agile_color_based_on) %></label>
+      <%= select 'query', 'color_base', options_for_select(options_card_colors_for_select(@query.color_base)) %></p>
+    <% end %>
+    <p><label for="query_group_by"><%= l(:label_agile_swimlanes) %></label>
+      <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>
+    </p>
+  </fieldset>
+</div>
+
+<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
+<%= render :partial => 'agile_queries/filters', :locals => {:query => query}%>
+</fieldset>
+
+<% if Redmine::VERSION.to_s > '2.4' %>
+  <%= content_tag 'fieldset', :class => "card-fields" do %>
+  <legend><%= l(:label_agile_board_columns) %></legend>
+  <%= render_board_fields_status(query) %>
+  <% end %>
+<% end %>
+
+
+<%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil), :class => "card-fields" do %>
+<legend><%= l(:label_agile_fields) %></legend>
+<%= render_board_fields_selection(query) %>
+<% end %>
+
+</div>
+
+<%= javascript_tag do %>
+$(document).ready(function(){
+  $("input[name='query[visibility]']").change(function(){
+    var checked = $('#query_visibility_1').is(':checked');
+    $("input[name='query[role_ids][]'][type=checkbox]").attr('disabled', !checked);
+  }).trigger('change');
+});
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/edit.html.erb b/plugins/redmine_agile/app/views/agile_queries/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d2b31118145e6efd2b533bd638a00f468dbf0e7d
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/edit.html.erb
@@ -0,0 +1,6 @@
+<h2><%= l(:label_agile_board_edit) %></h2>
+
+<%= form_tag(agile_query_path(@query), :method => :put) do %>
+  <%= render :partial => 'form', :locals => {:query => @query} %>
+  <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/index.html.erb b/plugins/redmine_agile/app/views/agile_queries/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e545c5e6d697effbd2aab552f1df3ba8cce1e3df
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/index.html.erb
@@ -0,0 +1,25 @@
+<div class="contextual">
+<%= link_to l(:label_query_new), new_agile_query_path(:project_id => @project), :class => 'icon icon-add' if User.current.allowed_to?(:add_agile_queries, nil, :global => true)%>
+</div>
+
+<h2><%= l(:label_agile_board_plural) %></h2>
+
+<% if @queries.empty? %>
+  <p><i><%=l(:label_no_data)%></i></p>
+<% else %>
+  <table class="list">
+  <% @queries.each do |query| %>
+    <tr class="<%= cycle('odd', 'even') %>">
+      <td class="name">
+        <%= link_to h(query.name), :controller => 'agile_boards', :action => 'index', :project_id => query.project_id, :query_id => query %>
+      </td>
+      <td class="buttons">
+        <% if query.editable_by?(User.current) %>
+        <%= link_to l(:button_edit), edit_agile_query_path(query), :class => 'icon icon-edit' %>
+        <%= delete_link agile_query_path(query) %>
+      <% end %>
+      </td>
+    </tr>
+  <% end %>
+  </table>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_queries/new.html.erb b/plugins/redmine_agile/app/views/agile_queries/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..870ed12bef4e8022b7088a4d7b6d163ef0d6d1aa
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_queries/new.html.erb
@@ -0,0 +1,6 @@
+<h2><%= l(:label_agile_board_new) %></h2>
+
+<%= form_tag(@project ? project_agile_queries_path(@project) : agile_queries_path) do %>
+  <%= render :partial => 'form', :locals => {:query => @query} %>
+  <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_versions/_board.html.erb b/plugins/redmine_agile/app/views/agile_versions/_board.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e62b5d1db775a6691f3a17b738f41fb3948edb5f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_versions/_board.html.erb
@@ -0,0 +1,43 @@
+<%= form_tag({}) do -%>
+  <%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params) %>
+  <%= hidden_field_tag 'project_id', @project.id if @project %>
+<div class="autoscroll">
+    <div class="flash error" style="display: none;" id="agile-board-errors">
+    </div>
+    <table class="list  versions-planning-board">
+      <thead>
+      <tr>
+        <th style="width: 33%; text-align: left;">
+          <%= text_field_tag "search", '', :placeholder => l(:label_agile_no_version_issues) %>
+          <%= javascript_tag "observeIssueSearchfield('search', '#{escape_javascript autocomplete_agile_versions_path(:project_id => @project)}');" %>
+        </th>
+        <th style="width: 33%; text-align: left;" id="backlog_version_header" data-estimated-unit="<%= estimated_unit%>">
+          <%= version_select_tag(@backlog_version, :version_type => "backlog", :other_version => @current_version) %>
+        </th>
+        <th style="width: 33%; text-align: left;" id="current_version_header" data-estimated-unit="<%= estimated_unit%>">
+          <%= version_select_tag(@current_version, :version_type => "current", :other_version => @backlog_version) %>
+        </th>
+      </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td id="no_version" class="issue-version-col" data-id="">
+          <%= render :partial => 'version_issues', :object => @version_issues  %>
+          </td>
+          <td id="backlog_version" class="<%= 'issue-version-col' if @backlog_version %>" data-id="<%= @backlog_version && @backlog_version.id %>">
+            <%= render :partial => 'version_issues', :object => @backlog_version_issues if @backlog_version %>
+          </td>
+          <td id="current_version" class="<%= 'issue-version-col' if @current_version %>" data-id="<%= @current_version && @current_version.id %>">
+            <%= render :partial => 'version_issues', :object => @current_version_issues if @current_version %>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+</div>
+
+<% end %>
+
+<%= javascript_tag do %>
+  recalculateHours();
+  $(document).ajaxSuccess(recalculateHours);
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_versions/_version_issues.html.erb b/plugins/redmine_agile/app/views/agile_versions/_version_issues.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..278ce44f4f3ddb94c226175ec12a05420488f12e
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_versions/_version_issues.html.erb
@@ -0,0 +1,22 @@
+<% if version_issues.any? %>
+  <% version_issues.each do |issue| %>
+    <% issue_estmated_value = estimated_value(issue) %>
+    <div class="issue-card hascontextmenu" data-id="<%= issue.id %>" data-estimated-hours="<%= issue_estmated_value %>">
+      <% if issue_estmated_value > 0 %>
+        <span class="hours"><%= ("%.2f" % issue_estmated_value) + estimated_unit %></span>
+      <% end %>
+      <%= check_box_tag("ids[]", issue.id, false, :id => nil, :class => 'checkbox') %>
+      <%= link_to_issue(issue, :project => (@project != issue.project)) %>
+    </div>
+  <% end %>
+
+  <% if version_issues.first.fixed_version_id.blank? %>
+    <div class="pagination-wrapper">
+    <span class="pagination">
+        <% pagination_links_full(@issue_pages, @issue_count, :per_page_links => false)  do |text, parameters, options| %>
+          <%= link_to text, autocomplete_agile_versions_path(parameters.merge(:q => params[:q], :project_id => @project)), :remote => true %>
+        <% end %>
+    </span>
+    </div>
+  <% end %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_versions/autocomplete.js.erb b/plugins/redmine_agile/app/views/agile_versions/autocomplete.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3c6c1acd39d842bdf8aca84229b5b6158b2db93d
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_versions/autocomplete.js.erb
@@ -0,0 +1 @@
+$('#no_version').html('<%= escape_javascript(render :partial => "version_issues", :object => @version_issues) %>')
diff --git a/plugins/redmine_agile/app/views/agile_versions/index.html.erb b/plugins/redmine_agile/app/views/agile_versions/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..318d2060e14fb36052ade707d37e985d3a77b63f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_versions/index.html.erb
@@ -0,0 +1,56 @@
+<h2><%= l(:label_agile_version_planning) %></h2>
+<div class="contextual">
+<% if !@query.new_record? && @query.editable_by?(User.current) %>
+  <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %>
+  <%= delete_link query_path(@query) %>
+<% end %>
+</div>
+
+<%= form_tag({ :controller => 'agile_versions', :action => 'index', :project_id => @project },
+            :method => :get, :id => 'query_form') do %>
+  <div id="query_form_with_buttons" class="hide-when-print">
+    <%= hidden_field_tag 'set_filter', '1' %>
+    <div id="query_form_content">
+    <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
+      <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+      <div style="<%= @query.new_record? ? "" : "display: none;" %>">
+        <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
+      </div>
+    </fieldset>
+    </div>
+    <p class="buttons">
+    <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
+    <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload'  %>
+    </p>
+  </div>
+<% end %>
+
+<% if User.current.allowed_to?(:edit_issues, @project) %>
+  <%= javascript_tag do %>
+    new PlanningBoard().init({
+      project_id: '<%= @project && @project.id %>',
+      update_agile_board_path: '<%= escape_javascript update_agile_board_path %>',
+      issues_path: '<%= escape_javascript issues_path %>'
+    });
+  <% end %>
+<% end %>
+
+<% if @project.shared_versions.empty? %>
+  <p class="nodata"><%= l(:label_no_data) %></p>
+<% else %>
+  <%= render :partial => 'board' %>
+<% end %>
+
+<% content_for :sidebar do %>
+  <%= render :partial => 'agile_boards/issues_links' %>
+  <%= render :partial => 'agile_charts/agile_charts' %>
+<% end %>
+
+<% html_title l(:label_agile_version_planning) %>
+<%= javascript_tag "agileContextMenuInit('#{ url_for(issues_context_menu_path) }')" %>
+<% content_for :header_tags do %>
+  <%= javascript_include_tag "redmine_agile", :plugin => 'redmine_agile' %>
+  <%= javascript_include_tag "jquery.ui.touch-punch.js", :plugin => 'redmine_agile' %>
+  <%= javascript_include_tag "redmine_agile_context_menu", :plugin => 'redmine_agile' %>
+  <%= stylesheet_link_tag 'context_menu' %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/agile_versions/load.js.erb b/plugins/redmine_agile/app/views/agile_versions/load.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..496b46571f0ba4b50504f2ac7c95332b6c0fe160
--- /dev/null
+++ b/plugins/redmine_agile/app/views/agile_versions/load.js.erb
@@ -0,0 +1,5 @@
+$('#<%= @version_type %>_version').html('<%= escape_javascript(render :partial => "version_issues", :object => @version_issues) %>');
+$('#<%= @version_type %>_version').attr('data-id', '<%= @version.id %>');
+
+$('#<%= @version_type %>_version_header').html('<%= escape_javascript(version_select_tag(@version, :version_type => @version_type, :other_version => @other_version_id)) %>');
+$('#<%= @other_version_type %>_version_header').html('<%= escape_javascript(version_select_tag(@other_version_id, :version_type => @other_version_type, :other_version => @version)) %>');
diff --git a/plugins/redmine_agile/app/views/context_menus/_agile_colors.html.erb b/plugins/redmine_agile/app/views/context_menus/_agile_colors.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c719d738a32730027c9c39a3e413fba077ae878d
--- /dev/null
+++ b/plugins/redmine_agile/app/views/context_menus/_agile_colors.html.erb
@@ -0,0 +1,11 @@
+<% if @safe_attributes.include?('agile_color_attributes') %>
+<li class="folder">
+  <a href="#" class="submenu"><%= l(:label_agile_color) %></a>
+  <ul>
+  <% AgileColor::AGILE_COLORS.each do |k, color| %>
+      <li><%= context_menu_link "<span style=\"background-color: #{color}; width: 8em; height: 1.2em; display: block;border: 1px solid #222;\"></span>".html_safe, bulk_update_issues_path(:ids => @issue_ids, :issue => {:agile_color_attributes => {:color => color}}, :back_url => @back), :method => :post,
+                                    :selected => (@issue && @issue.color == color), :disabled => (!@can[:edit]) %></li>
+  <% end -%>
+  </ul>
+</li>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/issues/_agile_data_fields.html.erb b/plugins/redmine_agile/app/views/issues/_agile_data_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d32aa6eae5b268c439ff786e0886cfb60b2f3df9
--- /dev/null
+++ b/plugins/redmine_agile/app/views/issues/_agile_data_fields.html.erb
@@ -0,0 +1,9 @@
+<div class="attributes">
+	<div class="splitcontent">
+		<%= render :partial => 'issue_color_form', :locals => { :form => form } %>
+		<%= render :partial => 'issue_story_points_form', :locals => { :form => form } %>
+	</div>
+</div>
+<% content_for :header_tags do %>
+	<%= javascript_include_tag "redmine_agile", :plugin => 'redmine_agile' %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/issues/_issue_color.html.erb b/plugins/redmine_agile/app/views/issues/_issue_color.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..467ad9b03398e7045c40b97a5433dc44c3f503b9
--- /dev/null
+++ b/plugins/redmine_agile/app/views/issues/_issue_color.html.erb
@@ -0,0 +1,11 @@
+<% if User.current.allowed_to?(:view_agile_queries, @project) && @issue.color && RedmineAgile.issue_colors? %>
+
+<%= issue_fields_rows do |rows|
+  rows.left l(:label_agile_color), "<span class=\"simplecolorpicker button\" style=\"background-color: #{@issue.color};\"></span>".html_safe
+end %>
+
+  <% content_for :header_tags do %>
+    <%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_agile' %>
+  <% end %>
+
+<% end %>
diff --git a/plugins/redmine_agile/app/views/issues/_issue_color_form.html.erb b/plugins/redmine_agile/app/views/issues/_issue_color_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..2cc4f251151f289534e9aa9ed15216fe42f95013
--- /dev/null
+++ b/plugins/redmine_agile/app/views/issues/_issue_color_form.html.erb
@@ -0,0 +1,14 @@
+<div class="splitcontentleft">
+  <% if @issue.safe_attribute? 'agile_color_attributes' -%>
+    <%= form.fields_for :agile_color do |f| %>
+      <p><%= f.select :color, options_for_select(AgileColor::AGILE_COLORS, @issue.color), {:label => l(:label_agile_color), :include_blank => true} %></p>
+    <% end %>
+
+    <%= javascript_tag "$('#issue_agile_color_attributes_color').simplecolorpicker({picker: true});"%>
+    <% content_for :header_tags do %>
+      <%= javascript_include_tag 'jquery.simplecolorpicker.js', :plugin => "redmine_agile" %>
+      <%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_agile' %>
+    <% end %>
+
+  <% end %>
+</div>
diff --git a/plugins/redmine_agile/app/views/issues/_issue_story_points.html.erb b/plugins/redmine_agile/app/views/issues/_issue_story_points.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..bfc90dd8f35fbcddeb9feace556e98fccac88d1f
--- /dev/null
+++ b/plugins/redmine_agile/app/views/issues/_issue_story_points.html.erb
@@ -0,0 +1,7 @@
+<% if @issue.project.module_enabled?('agile') && RedmineAgile.use_story_points? && RedmineAgile.use_story_points_for?(@issue.tracker) %>
+  <%= issue_fields_rows do |rows|
+    rows.left l(:label_agile_story_points), @issue.story_points
+  end if @issue.story_points %>
+<% end %>
+
+<%= javascript_tag "linkableAttributeFields();" %>
diff --git a/plugins/redmine_agile/app/views/issues/_issue_story_points_form.html.erb b/plugins/redmine_agile/app/views/issues/_issue_story_points_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..ab0f94d7b95a36d5a6025a3b5be0e1abd8e1489d
--- /dev/null
+++ b/plugins/redmine_agile/app/views/issues/_issue_story_points_form.html.erb
@@ -0,0 +1,14 @@
+<div class="splitcontentright">
+  <% if @issue.project.module_enabled?('agile') && RedmineAgile.use_story_points? && RedmineAgile.use_story_points_for?(@issue.tracker) %>
+    <%= form.fields_for :agile_data do |f| %>
+      <p>
+      <% if RedmineAgile.sp_values.any? %>
+        <%= f.select :story_points, RedmineAgile.sp_values.unshift(@issue.story_points).uniq, 
+          :include_blank => @issue.story_points, :selected => @issue.story_points %>
+      <% else %>
+        <%= f.text_field :story_points, :size => 3, :required => @issue.required_attribute?('agile_data_attributes') %>
+      <% end %>
+      </p>
+    <% end %>
+  <% end %>
+</div>
diff --git a/plugins/redmine_agile/app/views/projects/_project_color_form.html.erb b/plugins/redmine_agile/app/views/projects/_project_color_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5b5d6580e77de6fe1037fe66295ebc55a182fa60
--- /dev/null
+++ b/plugins/redmine_agile/app/views/projects/_project_color_form.html.erb
@@ -0,0 +1,11 @@
+<% if @project.safe_attribute? 'agile_color_attributes' -%>
+  <%= form.fields_for :agile_color do |fr| %>
+    <p><%= fr.select :color, options_for_select(AgileColor::AGILE_COLORS, @project.color), {:label => l(:label_agile_color), :include_blank => true} %></p>
+  <% end %>
+  
+  <%= javascript_tag "$('#project_agile_color_attributes_color').simplecolorpicker({picker: true});"%>
+  <% content_for :header_tags do %>
+    <%= javascript_include_tag 'jquery.simplecolorpicker.js', :plugin => "redmine_agile" %>
+    <%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_agile' %>
+  <% end %>
+<% end %>
diff --git a/plugins/redmine_agile/app/views/settings/agile/_general.html.erb b/plugins/redmine_agile/app/views/settings/agile/_general.html.erb
new file mode 100755
index 0000000000000000000000000000000000000000..5373c1c3e6645acf9156fb03241df1a9058eade4
--- /dev/null
+++ b/plugins/redmine_agile/app/views/settings/agile/_general.html.erb
@@ -0,0 +1,79 @@
+
+<p>
+  <label><%= l(:label_agile_board_items_limit) %></label>
+  <%= text_field_tag 'settings[board_items_limit]', RedmineAgile.board_items_limit, :size => 3 %>
+</p>
+<p>
+  <label for="settings_status_colors"><%= l(:label_agile_status_colors) %></label>
+  <%= hidden_field_tag 'settings[status_colors]', 0, :id => nil %>
+  <%= check_box_tag 'settings[status_colors]', 1, RedmineAgile.status_colors? %>
+</p>
+
+<p>
+  <label><%= l(:label_agile_color_based_on) %></label>
+  <%= select_tag 'settings[color_on]', options_card_colors_for_select(@settings["color_on"]) %> <%= link_to l(:label_agile_manage_colors), agile_colors_path("tracker") %>
+</p>
+
+<p>
+  <label><%= l(:label_agile_board_default_fields) %></label>
+  <%= select_tag 'settings[default_columns]', options_for_select(AgileQuery.new(:column_names => RedmineAgile.default_columns).available_columns.collect{|column| [column.caption, column.name]}, RedmineAgile.default_columns), :multiple => true, :include_blank => true, :size => 10, :style => "width:150px" %>
+</p>
+
+<p>
+  <label><%= l(:label_agile_esitmate_units) %></label>
+  <%= select_tag 'settings[estimate_units]', options_for_select(RedmineAgile::ESTIMATE_UNITS.map{ |eu| [l("label_agile_#{eu}"), eu] }, 
+    RedmineAgile.estimate_units), :onchange => "toggleTrackerForSP(this); return false" %>
+  <script type="text/javascript">
+    function toggleTrackerForSP (node) {
+      if ($(node).val() == 'story_points')
+        $('#trackers_for_sp').show();
+      else
+        $('#trackers_for_sp').hide();
+    }
+  </script>
+</p>
+<div id="trackers_for_sp" style="display:<%= RedmineAgile.use_story_points? ? 'block' : 'none' %>">
+  <p>
+    <label><%= l(:label_agile_trackers_for_sp) %></label>
+    <%= select_tag 'settings[trackers_for_sp]', options_for_select(Tracker.all.map{|tr| [tr.name, tr.id]}, RedmineAgile.trackers_for_sp), :prompt => "All", :style => "width:150px" %>
+  </p>
+  <p>
+    <label><%= l(:label_agile_sp_values) %></label>
+    <%= text_field_tag 'settings[sp_values]', RedmineAgile.sp_values.join(", ") %>
+  </p>
+</div>
+<p>
+  <label><%= l(:label_agile_default_chart) %></label>
+  <%= select_tag 'settings[default_chart]', options_charts_for_select(RedmineAgile.default_chart) %>
+</p>
+
+<p>
+  <label for="settings_exclude_weekends"><%= l(:label_agile_exclude_weekends) %></label>
+  <%= hidden_field_tag 'settings[exclude_weekends]', 0, :id => nil %>
+  <%= check_box_tag 'settings[exclude_weekends]', 1, RedmineAgile.exclude_weekends? %>
+</p>
+
+<p>
+  <label><%= l(:label_agile_time_reports_items_limit) %></label>
+  <%= text_field_tag 'settings[time_reports_items_limit]', RedmineAgile.time_reports_items_limit, :size => 3 %>
+</p>
+<p>
+  <label for="settings_hide_closed_issues_data"><%= l(:label_agile_hide_closed_issues_data) %></label>
+  <%= check_box_tag 'settings[hide_closed_issues_data]', 1, RedmineAgile.hide_closed_issues_data? %>
+</p>
+<p>
+  <label><%= l(:label_agile_allow_create_cards) %></label>
+  <%= hidden_field_tag 'settings[allow_create_card]', 0, :id => nil %>
+  <%= check_box_tag 'settings[allow_create_card]', 1, RedmineAgile.allow_create_card? %>
+</p>
+<p>
+  <label for="settings_auto_assign_on_move"><%= l(:label_agile_auto_assign_on_move) %></label>
+  <%= hidden_field_tag 'settings[auto_assign_on_move]', 0, :id => nil %>
+  <%= check_box_tag 'settings[auto_assign_on_move]', 1, RedmineAgile.auto_assign_on_move? %>
+</p>
+
+<p>
+  <label for="settings_allow_inline_comments"><%= l(:label_agile_inline_comment) %></label>
+  <%= hidden_field_tag 'settings[allow_inline_comments]', 0, :id => nil %>
+  <%= check_box_tag 'settings[allow_inline_comments]', 1, RedmineAgile.allow_inline_comments? %>
+</p>
diff --git a/plugins/redmine_agile/app/views/users/_user_color_form.html.erb b/plugins/redmine_agile/app/views/users/_user_color_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d2ae41c507d62c4abe05a8e44fe63ff2b3774772
--- /dev/null
+++ b/plugins/redmine_agile/app/views/users/_user_color_form.html.erb
@@ -0,0 +1,11 @@
+<% if @user.safe_attribute? 'agile_color_attributes' -%>
+    <%= form.fields_for :agile_color do |fr| %>
+        <p><%= fr.select :color, options_for_select(AgileColor::AGILE_COLORS, @user.color), {:label => l(:label_agile_color), :include_blank => true} %></p>
+    <% end %>
+
+    <%= javascript_tag "$('#user_agile_color_attributes_color').simplecolorpicker({picker: true});"%>
+    <% content_for :header_tags do %>
+        <%= javascript_include_tag 'jquery.simplecolorpicker.js', :plugin => "redmine_agile" %>
+        <%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_agile' %>
+    <% end %>
+<% end %>
diff --git a/plugins/redmine_agile/assets/images/agile.png b/plugins/redmine_agile/assets/images/agile.png
new file mode 100644
index 0000000000000000000000000000000000000000..e9604d3df4a7db1e14a320dec490d5ec594747ba
Binary files /dev/null and b/plugins/redmine_agile/assets/images/agile.png differ
diff --git a/plugins/redmine_agile/assets/images/fullscreen.png b/plugins/redmine_agile/assets/images/fullscreen.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e9bc42bec16e3077a9680e7af0f90395bfeb60c
Binary files /dev/null and b/plugins/redmine_agile/assets/images/fullscreen.png differ
diff --git a/plugins/redmine_agile/assets/images/pro_version_agile.png b/plugins/redmine_agile/assets/images/pro_version_agile.png
new file mode 100644
index 0000000000000000000000000000000000000000..5288542f4b91df719ddb392848fe95a2211083d8
Binary files /dev/null and b/plugins/redmine_agile/assets/images/pro_version_agile.png differ
diff --git a/plugins/redmine_agile/assets/javascripts/jquery.simplecolorpicker.js b/plugins/redmine_agile/assets/javascripts/jquery.simplecolorpicker.js
new file mode 100755
index 0000000000000000000000000000000000000000..9cfc45654e10acd1b05c4655f6d7c4468d8c498b
--- /dev/null
+++ b/plugins/redmine_agile/assets/javascripts/jquery.simplecolorpicker.js
@@ -0,0 +1,235 @@
+/*
+ * Very simple jQuery Color Picker
+ * https://github.com/tkrotoff/jquery-simplecolorpicker
+ *
+ * Copyright (C) 2012-2013 Tanguy Krotoff <tkrotoff@gmail.com>
+ *
+ * Licensed under the MIT license
+ */
+
+(function($) {
+  'use strict';
+
+  /**
+   * Constructor.
+   */
+  var SimpleColorPicker = function(select, options) {
+    this.init('simplecolorpicker', select, options);
+  };
+
+  /**
+   * SimpleColorPicker class.
+   */
+  SimpleColorPicker.prototype = {
+    constructor: SimpleColorPicker,
+
+    init: function(type, select, options) {
+      var self = this;
+
+      self.type = type;
+
+      self.$select = $(select);
+      self.$select.hide();
+
+      self.options = $.extend({}, $.fn.simplecolorpicker.defaults, options);
+
+      self.$colorList = null;
+
+      if (self.options.picker === true) {
+        var selectText = self.$select.find('> option:selected').text();
+        self.$icon = $('<span class="simplecolorpicker button"'
+                     + ' title="' + selectText + '"'
+                     + ' style="background-color: ' + self.$select.val() + ';"'
+                     + ' role="button" tabindex="0">'
+                     + '</span>').insertAfter(self.$select);
+        self.$icon.on('click.' + self.type, $.proxy(self.showPicker, self));
+
+        self.$picker = $('<span class="simplecolorpicker picker ' + self.options.theme + '"></span>').appendTo(document.body);
+        self.$colorList = self.$picker;
+
+        // Hide picker when clicking outside
+        $(document).on('mousedown.' + self.type, $.proxy(self.hidePicker, self));
+        self.$picker.on('mousedown.' + self.type, $.proxy(self.mousedown, self));
+      } else {
+        self.$inline = $('<span class="simplecolorpicker inline ' + self.options.theme + '"></span>').insertAfter(self.$select);
+        self.$colorList = self.$inline;
+      }
+
+      // Build the list of colors
+      // <span class="color selected" title="Green" style="background-color: #7bd148;" role="button"></span>
+      self.$select.find('> option').each(function() {
+        var $option = $(this);
+        var color = $option.val();
+
+        var isSelected = $option.is(':selected');
+        var isDisabled = $option.is(':disabled');
+
+        var selected = '';
+        if (isSelected === true) {
+          selected = ' data-selected';
+        }
+
+        var disabled = '';
+        if (isDisabled === true) {
+          disabled = ' data-disabled';
+        }
+
+        var title = '';
+        if (isDisabled === false) {
+          title = ' title="' + $option.text() + '"';
+        }
+
+        var role = '';
+        if (isDisabled === false) {
+          role = ' role="button" tabindex="0"';
+        }
+
+        var $colorSpan = $('<span class="color"'
+                         + title
+                         + ' style="background-color: ' + color + ';"'
+                         + ' data-color="' + color + '"'
+                         + selected
+                         + disabled
+                         + role + '>'
+                         + '</span>');
+
+        self.$colorList.append($colorSpan);
+        $colorSpan.on('click.' + self.type, $.proxy(self.colorSpanClicked, self));
+
+        var $next = $option.next();
+        if ($next.is('optgroup') === true) {
+          // Vertical break, like hr
+          self.$colorList.append('<span class="vr"></span>');
+        }
+      });
+    },
+
+    /**
+     * Changes the selected color.
+     *
+     * @param color the hexadecimal color to select, ex: '#fbd75b'
+     */
+    selectColor: function(color) {
+      var self = this;
+
+      var $colorSpan = self.$colorList.find('> span.color').filter(function() {
+        return $(this).data('color').toLowerCase() === color.toLowerCase();
+      });
+
+      if ($colorSpan.length > 0) {
+        self.selectColorSpan($colorSpan);
+      } else {
+        console.error("The given color '" + color + "' could not be found");
+      }
+    },
+
+    showPicker: function() {
+      var pos = this.$icon.offset();
+      this.$picker.css({
+        // Remove some pixels to align the picker icon with the icons inside the dropdown
+        left: pos.left - 1,
+        top: pos.top - 4//+ this.$icon.outerHeight()
+      });
+
+      this.$picker.show(this.options.pickerDelay);
+    },
+
+    hidePicker: function() {
+      this.$picker.hide(this.options.pickerDelay);
+    },
+
+    /**
+     * Selects the given span inside $colorList.
+     *
+     * The given span becomes the selected one.
+     * It also changes the HTML select value, this will emit the 'change' event.
+     */
+    selectColorSpan: function($colorSpan) {
+      var color = $colorSpan.data('color');
+      var title = $colorSpan.prop('title');
+
+      // Mark this span as the selected one
+      $colorSpan.siblings().removeAttr('data-selected');
+      $colorSpan.attr('data-selected', '');
+
+      if (this.options.picker === true) {
+        this.$icon.css('background-color', color);
+        this.$icon.prop('title', title);
+        this.hidePicker();
+      }
+
+      // Change HTML select value
+      this.$select.val(color);
+    },
+
+    /**
+     * The user clicked on a color inside $colorList.
+     */
+    colorSpanClicked: function(e) {
+      // When a color is clicked, make it the new selected one (unless disabled)
+      if ($(e.target).is('[data-disabled]') === false) {
+        this.selectColorSpan($(e.target));
+        this.$select.trigger('change');
+      }
+    },
+
+    /**
+     * Prevents the mousedown event from "eating" the click event.
+     */
+    mousedown: function(e) {
+      e.stopPropagation();
+      e.preventDefault();
+    },
+
+    destroy: function() {
+      if (this.options.picker === true) {
+        this.$icon.off('.' + this.type);
+        this.$icon.remove();
+        $(document).off('.' + this.type);
+      }
+
+      this.$colorList.off('.' + this.type);
+      this.$colorList.remove();
+
+      this.$select.removeData(this.type);
+      this.$select.show();
+    }
+  };
+
+  /**
+   * Plugin definition.
+   * How to use: $('#id').simplecolorpicker()
+   */
+  $.fn.simplecolorpicker = function(option) {
+    var args = $.makeArray(arguments);
+    args.shift();
+
+    // For HTML element passed to the plugin
+    return this.each(function() {
+      var $this = $(this),
+        data = $this.data('simplecolorpicker'),
+        options = typeof option === 'object' && option;
+      if (data === undefined) {
+        $this.data('simplecolorpicker', (data = new SimpleColorPicker(this, options)));
+      }
+      if (typeof option === 'string') {
+        data[option].apply(data, args);
+      }
+    });
+  };
+
+  /**
+   * Default options.
+   */
+  $.fn.simplecolorpicker.defaults = {
+    // No theme by default
+    theme: '',
+
+    // Show the picker or make it inline
+    picker: false,
+
+    // Animation delay in milliseconds
+    pickerDelay: 0
+  };
+
+})(jQuery);
diff --git a/plugins/redmine_agile/assets/javascripts/jquery.ui.touch-punch.js b/plugins/redmine_agile/assets/javascripts/jquery.ui.touch-punch.js
new file mode 100644
index 0000000000000000000000000000000000000000..16ce41d1edf13ba003315af035e2a5180b7e964d
--- /dev/null
+++ b/plugins/redmine_agile/assets/javascripts/jquery.ui.touch-punch.js
@@ -0,0 +1,180 @@
+/*!
+ * jQuery UI Touch Punch 0.2.3
+ *
+ * Copyright 2011–2014, Dave Furfero
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Depends:
+ *  jquery.ui.widget.js
+ *  jquery.ui.mouse.js
+ */
+(function ($) {
+
+  // Detect touch support
+  $.support.touch = 'ontouchend' in document;
+
+  // Ignore browsers without touch support
+  if (!$.support.touch) {
+    return;
+  }
+
+  var mouseProto = $.ui.mouse.prototype,
+      _mouseInit = mouseProto._mouseInit,
+      _mouseDestroy = mouseProto._mouseDestroy,
+      touchHandled;
+
+  /**
+   * Simulate a mouse event based on a corresponding touch event
+   * @param {Object} event A touch event
+   * @param {String} simulatedType The corresponding mouse event
+   */
+  function simulateMouseEvent (event, simulatedType) {
+
+    // Ignore multi-touch events
+    if (event.originalEvent.touches.length > 1) {
+      return;
+    }
+
+    event.preventDefault();
+
+    var touch = event.originalEvent.changedTouches[0],
+        simulatedEvent = document.createEvent('MouseEvents');
+    
+    // Initialize the simulated mouse event using the touch event's coordinates
+    simulatedEvent.initMouseEvent(
+      simulatedType,    // type
+      true,             // bubbles                    
+      true,             // cancelable                 
+      window,           // view                       
+      1,                // detail                     
+      touch.screenX,    // screenX                    
+      touch.screenY,    // screenY                    
+      touch.clientX,    // clientX                    
+      touch.clientY,    // clientY                    
+      false,            // ctrlKey                    
+      false,            // altKey                     
+      false,            // shiftKey                   
+      false,            // metaKey                    
+      0,                // button                     
+      null              // relatedTarget              
+    );
+
+    // Dispatch the simulated event to the target element
+    event.target.dispatchEvent(simulatedEvent);
+  }
+
+  /**
+   * Handle the jQuery UI widget's touchstart events
+   * @param {Object} event The widget element's touchstart event
+   */
+  mouseProto._touchStart = function (event) {
+
+    var self = this;
+
+    // Ignore the event if another widget is already being handled
+    if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
+      return;
+    }
+
+    // Set the flag to prevent other widgets from inheriting the touch event
+    touchHandled = true;
+
+    // Track movement to determine if interaction was a click
+    self._touchMoved = false;
+
+    // Simulate the mouseover event
+    simulateMouseEvent(event, 'mouseover');
+
+    // Simulate the mousemove event
+    simulateMouseEvent(event, 'mousemove');
+
+    // Simulate the mousedown event
+    simulateMouseEvent(event, 'mousedown');
+  };
+
+  /**
+   * Handle the jQuery UI widget's touchmove events
+   * @param {Object} event The document's touchmove event
+   */
+  mouseProto._touchMove = function (event) {
+
+    // Ignore event if not handled
+    if (!touchHandled) {
+      return;
+    }
+
+    // Interaction was not a click
+    this._touchMoved = true;
+
+    // Simulate the mousemove event
+    simulateMouseEvent(event, 'mousemove');
+  };
+
+  /**
+   * Handle the jQuery UI widget's touchend events
+   * @param {Object} event The document's touchend event
+   */
+  mouseProto._touchEnd = function (event) {
+
+    // Ignore event if not handled
+    if (!touchHandled) {
+      return;
+    }
+
+    // Simulate the mouseup event
+    simulateMouseEvent(event, 'mouseup');
+
+    // Simulate the mouseout event
+    simulateMouseEvent(event, 'mouseout');
+
+    // If the touch interaction did not move, it should trigger a click
+    if (!this._touchMoved) {
+
+      // Simulate the click event
+      simulateMouseEvent(event, 'click');
+    }
+
+    // Unset the flag to allow other widgets to inherit the touch event
+    touchHandled = false;
+  };
+
+  /**
+   * A duck punch of the $.ui.mouse _mouseInit method to support touch events.
+   * This method extends the widget with bound touch event handlers that
+   * translate touch events to mouse events and pass them to the widget's
+   * original mouse event handling methods.
+   */
+  mouseProto._mouseInit = function () {
+    
+    var self = this;
+
+    // Delegate the touch handlers to the widget's element
+    self.element.bind({
+      touchstart: $.proxy(self, '_touchStart'),
+      touchmove: $.proxy(self, '_touchMove'),
+      touchend: $.proxy(self, '_touchEnd')
+    });
+
+    // Call the original $.ui.mouse init method
+    _mouseInit.call(self);
+  };
+
+  /**
+   * Remove the touch event handlers
+   */
+  mouseProto._mouseDestroy = function () {
+    
+    var self = this;
+
+    // Delegate the touch handlers to the widget's element
+    self.element.unbind({
+      touchstart: $.proxy(self, '_touchStart'),
+      touchmove: $.proxy(self, '_touchMove'),
+      touchend: $.proxy(self, '_touchEnd')
+    });
+
+    // Call the original $.ui.mouse destroy method
+    _mouseDestroy.call(self);
+  };
+
+})(jQuery);
\ No newline at end of file
diff --git a/plugins/redmine_agile/assets/javascripts/redmine_agile.js b/plugins/redmine_agile/assets/javascripts/redmine_agile.js
new file mode 100755
index 0000000000000000000000000000000000000000..9f86d08b7c30084ed49cfb89aee4ebe16125aacf
--- /dev/null
+++ b/plugins/redmine_agile/assets/javascripts/redmine_agile.js
@@ -0,0 +1,681 @@
+(function() {
+  // var AgileBoard = function() {};
+  var PlanningBoard = function() {};
+
+  PlanningBoard.prototype = {
+
+    init: function(routes) {
+      var self = this;
+      self.routes = routes;
+
+      $(function() {
+        self.initSortable();
+      });
+    },
+
+    // If there are no changes
+    backSortable: function($oldColumn) {
+      $oldColumn.sortable('cancel');
+    },
+
+    successSortable: function($oldColumn, $column) {
+      clearErrorMessage();
+      var r = new RegExp(/\d+/)
+      var ids = [];
+
+      ids.push({
+        column: $column,
+        id: $column.data('id'),
+        to: true
+      });
+      ids.push({
+        column: $oldColumn,
+        id: $oldColumn.data('id'),
+        from: true
+      });
+
+      for (var i = 0; i < ids.length; i++) {
+        var current = ids[i];
+        var headerSelector = '.version-planning-board thead tr th[data-column-id="' + current.id + '"]';
+        var $columnHeader = $(headerSelector);
+        var columnText = $columnHeader.text();
+        var currentIssuesAmount = ~~columnText.match(r);
+        currentIssuesAmount = (current.from) ? currentIssuesAmount - 1 : currentIssuesAmount + 1;
+        $columnHeader.text(columnText.replace(r, currentIssuesAmount));
+      }
+    },
+
+    errorSortable: function($oldColumn, responseText) {
+      var alertMessage = parseErrorResponse(responseText);
+      if (alertMessage) {
+        setErrorMessage(alertMessage);
+      };
+    },
+
+    initSortable: function() {
+      var self = this;
+      var $issuesCols = $(".issue-version-col");
+
+      $issuesCols.sortable({
+        connectWith: ".issue-version-col",
+        start: function(event, ui) {
+          var $item = $(ui.item);
+          $item.attr('oldColumnId', $item.parent().data('id'));
+          $item.attr('oldPosition', $item.index());
+        },
+        stop: function(event, ui) {
+          var $item = $(ui.item);
+          var sender = ui.sender;
+          var $column = $item.parents('.issue-version-col');
+          var issue_id = $item.data('id');
+          var version_id = $column.attr("data-id");
+          var order = $column.sortable('serialize');
+          var positions = {};
+          var oldId = $item.attr('oldColumnId');
+          var $oldColumn = $('.ui-sortable[data-id="' + oldId + '"]');
+
+          if(!self.hasChange($item)){
+            self.backSortable($column);
+            return;
+          }
+
+          $column.find('.issue-card').each(function(i, e) {
+            var $e = $(e);
+            positions[$e.data('id')] = { position: $e.index() };
+          });
+
+          $.ajax({
+            url: self.routes.update_agile_board_path,
+            type: 'PUT',
+            data: {
+              issue: {
+                fixed_version_id: version_id
+              },
+              positions: positions,
+              id: issue_id
+            },
+            success: function(data, status, xhr) {
+              self.successSortable($oldColumn, $column);
+            },
+            error: function(xhr, status, error) {
+              self.errorSortable($oldColumn, xhr.responseText);
+            }
+          });
+        }
+      }).disableSelection();
+
+      $issuesCols.sortable( "option", "cancel", "div.pagination-wrapper" );
+
+    },
+
+    hasChange: function($item){
+      var column = $item.parents('.issue-version-col');
+      return $item.attr('oldColumnId') != column.data('id') || // Checks a version change
+             $item.attr('oldPosition') != $item.index();
+    },
+
+  }
+
+  function AgileBoard(routes){
+
+    // ----- estimated hours ------
+    this.recalculateEstimateHours = function(oldStatusId, newStatusId, value){
+      oldStatusElement = $('th[data-column-id="' + oldStatusId + '"]');
+      newStatusElement = $('th[data-column-id="' + newStatusId + '"]');
+      oldStatusElement.each(function(i, elem){
+        changeHtmlNumber(elem, -value);
+      });
+      newStatusElement.each(function(i, elem){
+        changeHtmlNumber(elem, value);
+      });
+    };
+
+    this.successSortable = function(oldStatusId, newStatusId, oldSwimLaneId, newSwimLaneId) {
+      clearErrorMessage();
+    };
+
+    // If there are no changes
+    this.backSortable = function($oldColumn) {
+      $oldColumn.sortable('cancel');
+    };
+
+    this.errorSortable = function($oldColumn, responseText) {
+      var alertMessage = parseErrorResponse(responseText);
+      if (alertMessage) {
+        setErrorMessage(alertMessage);
+      }
+    };
+
+    this.initSortable = function() {
+      var self = this;
+      var $issuesCols = $(".issue-status-col");
+
+      $issuesCols.sortable({
+        items: '.issue-card',
+        connectWith: ".issue-status-col",
+        start: function(event, ui) {
+          var $item = $(ui.item);
+          $item.attr('oldColumnId', $item.parent().data('id'));
+          $item.attr('oldSwimLaneId', $item.parents('tr.swimlane').data('id'));
+          $item.attr('oldSwimLaneField', $item.parents('tr.swimlane').attr('data-field'));
+          $item.attr('oldPosition', $item.index());
+        },
+        stop: function(event, ui) {
+          var that = this;
+          var $item = $(ui.item);
+          var sender = ui.sender;
+          var $column = $item.parents('.issue-status-col');
+          var $swimlane = $item.parents('tr.swimlane');
+          var issue_id = $item.data('id');
+          var newStatusId = $column.data("id");
+          var order = $column.sortable('serialize');
+          var swimLaneId = $swimlane.data('id')
+          var swimLaneField = $swimlane.attr('data-field');
+          var positions = {};
+          var oldStatusId = $item.attr('oldColumnId');
+          var oldSwimLaneId = $item.attr('oldSwimLaneId');
+          var oldSwimLaneField = $item.attr('oldSwimLaneField');
+          var $oldColumn = $('.ui-sortable[data-id="' + oldStatusId + '"]');
+
+          if(!self.hasChange($item)){
+            self.backSortable($column);
+            return;
+          }
+          $('.lock').show();
+          if ($column.hasClass("closed")){
+            $item.addClass("float-left")
+          }
+          else{
+            $item.removeClass("closed-issue");
+            $item.removeClass("float-left")
+          }
+
+          $column.find('.issue-card').each(function(i, e) {
+            var $e = $(e);
+            positions[$e.data('id')] = { position: $e.index() };
+          });
+
+          var params = {
+              issue: {
+                status_id: newStatusId
+              },
+              positions: positions,
+              id: issue_id
+            }
+          params['issue'][swimLaneField] = swimLaneId;
+
+          $.ajax({
+            url: self.routes.update_agile_board_path,
+            type: 'PUT',
+            data: params,
+            success: function(data, status, xhr) {
+              self.successSortable(oldStatusId, newStatusId, oldSwimLaneId, swimLaneId);
+              $($item).replaceWith(data);
+              estimatedHours = $($item).find("span.hours");
+              if(estimatedHours.size() > 0){
+                hours = $(estimatedHours).html().replace(/(\(|\)|h)?/g, '');
+                // self.recalculateEstimateHours(oldStatusId, newStatusId, hours);
+              }
+            },
+            error: function(xhr, status, error) {
+              self.errorSortable($oldColumn, xhr.responseText);
+              $(that).sortable( "cancel" );
+            },
+            complete: function(){
+              $('.lock').hide();
+            }
+          });
+        }
+      });
+
+    };
+
+    this.initDraggable = function() {
+      if ($("#group_by").val() != "assigned_to"){
+        $(".assignable-user").draggable({
+                helper: "clone",
+                start: function startDraggable(event, ui) {
+                  $(ui.helper).addClass("draggable-active")
+                }
+              });
+      }
+    };
+
+    this.hasChange = function($item){
+      var column = $item.parents('.issue-status-col');
+      var swimlane = $item.parents('tr.swimlane');
+      return $item.attr('oldColumnId') != column.data('id') || // Checks the status change
+             $item.attr('oldSwimLaneId') != swimlane.data('id') ||
+             $item.attr('oldPosition') != $item.index();
+    };
+
+    this.initDroppable = function() {
+      var self = this;
+
+      $(".issue-card").droppable({
+        activeClass: 'droppable-active',
+        hoverClass: 'droppable-hover',
+        accept: '.assignable-user',
+        tolerance: 'pointer',
+        drop: function(event, ui) {
+          var $self = $(this);
+          $('.lock').show();
+          $.ajax({
+            url: self.routes.update_agile_board_path,
+            type: "PUT",
+            dataType: "html",
+            data: {
+              issue: {
+                assigned_to_id: ui.draggable.data("id")
+              },
+              id: $self.data("id")
+            },
+            success: function(data, status, xhr){
+              $self.replaceWith(data);
+            },
+            error:function(xhr, status, error) {
+              var alertMessage = parseErrorResponse(xhr.responseText);
+              if (alertMessage) {
+                setErrorMessage(alertMessage);
+                $self.find("p.assigned-user").remove();
+              }
+            },
+            complete: function(){
+              $('.lock').hide();
+            }
+          });
+          $self.find("p.info").show();
+          $self.find("p.info").html(ui.draggable.clone());
+        }
+      });
+    };
+
+    this.getToolTipInfo = function(node, url){
+      var issue_id = $(node).parents(".issue-card").data("id");
+      var tip = $(node).children(".tip");
+      if( $(tip).html() && $.trim($(tip).html()) != "")
+        return;
+      $.ajax({
+          url: url,
+          type: "get",
+          dataType: "html",
+          data: {
+            id: issue_id
+          },
+          success: function(data, status, xhr){
+            $(tip).html(data);
+          },
+          error:function(xhr, status, error) {
+            $(tip).html(error);
+          }
+      });
+    }
+
+    this.saveInlineComment = function(node, url){
+      var node = node;
+      var comment = $(node).siblings("textarea").val();
+      if ($.trim(comment) === "") return false;
+      $(node).prop('disabled', true);
+      $('.lock').show();
+      var card = $(node).parents(".issue-card");
+      $.ajax({
+        url: url,
+        type: "PUT",
+        dataType: "html",
+        data: { issue: { notes: comment } },
+        success: function(data, status, xhr){
+          $(card).replaceWith(data);
+        },
+        error: function(xhr, status, error){
+          var alertMessage = parseErrorResponse(xhr.responseText);
+          if (alertMessage) {
+            setErrorMessage(alertMessage);
+          }
+        },
+        complete: function(xhr, status){
+          $(node).prop('disabled', false);
+          $('.lock').hide();
+        }
+      });
+    }
+
+    this.createIssue = function(url){
+      $('.add-issue').click(function(){
+        $(this).children('.new-card__input').focus();
+      });
+      $('.new-card__input').keyup(function(evt){
+        var node = this;
+        evt = evt || window.event;
+        subject = $.trim($(node).val());
+        if (evt.keyCode == 13 && subject.length != 0) {
+          $.ajax({
+            url: url,
+            type: "POST",
+            data: {
+              subject: subject,
+              status_id: $(node).parents('td').data('id')
+            },
+            dataType: "html",
+            success: function(data, status, xhr){
+              $(node).parent().before(data);
+              $(node).val('');
+            },
+            error:function(xhr, status, error) {
+              var alertMessage = parseErrorResponse(xhr.responseText);
+              if (alertMessage) {
+                setErrorMessage(alertMessage);
+              }
+            }
+          });
+        }
+      });
+    }
+
+    this.routes = routes;
+
+    this.initSortable();
+    this.initDraggable();
+    this.initDroppable();
+    this.createIssue(routes.create_issue_path);
+  }
+
+  window.AgileBoard = AgileBoard;
+  window.PlanningBoard = PlanningBoard;
+
+  $.fn.StickyHeader = function() {
+    return this.each(function() {
+    var
+      $this = $(this),
+      $body = $('body'),
+      $html = $body.parent(),
+      $hideButton = $body.find('#hideSidebarButton'),
+      $fullScreenButton = $body.find('.icon-fullscreen'),
+      $containerFixed,
+      $tableFixed,
+      $tableRows,
+      $tableFixedRows,
+      containerWidth,
+      offset,
+      tableHeight,
+      tableHeadHeight,
+      tableOffsetTop,
+      tableOffsetBottom,
+      tmp;
+
+      function init() {
+          $this.wrap('<div class="container-fixed" />');
+          $tableFixed = $this.clone();
+          $containerFixed = $this.parents('.container-fixed');
+          $tableFixed
+              .find('tbody')
+              .remove()
+              .end()
+              .addClass('sticky')
+              .insertBefore($this)
+              .hide();
+      }
+
+      function resizeFixed() {
+          containerWidth = $containerFixed.width();
+          tableHeadHeight = $this.find("thead").height() + 3;
+          $tableRows = $this.find('thead th');
+          $tableFixedRows = $tableFixed.find('th');
+
+          $tableFixed.css({'width': containerWidth});
+
+          $tableRows.each(function(i) {
+              tmp = jQuery(this).width();
+              jQuery($tableFixedRows[i]).css('width', tmp);
+          });
+      }
+
+      function scrollFixed() {
+          tableHeight = $this.height();
+          tableHeadHeight = $this.find("thead").height();
+          offset = $(window).scrollTop();
+          tableOffsetTop = $this.offset().top;
+          tableOffsetBottom = tableOffsetTop + tableHeight - tableHeadHeight;
+
+          resizeFixed();
+
+          // The first breakpoint to add responsiveness is 899px
+          var headerHeight = $(window).width() < 900 ? $('#header').height() : 0;
+          var tablePositionTop= tableOffsetTop - headerHeight;
+          var tablePositionBottom= tableOffsetBottom - headerHeight;
+
+          if (offset < tablePositionTop|| offset > tablePositionBottom) {
+              $tableFixed.css('display', 'none');
+          } else if (offset >= tablePositionTop && offset <= tablePositionBottom) {
+              $tableFixed.css('display', 'table');
+              // Fix for chrome not redrawing header
+              $tableFixed.css('z-index', '100');
+          }
+      }
+
+
+      function bindScroll() {
+          if ($html.hasClass('agile-board-fullscreen')) {
+              scrollFixed();
+              $('div.agile-board.autoscroll').scroll(scrollFixed);
+              $(window).unbind('scroll');
+          } else {
+              $(window).scroll(scrollFixed);
+              $('div.agile-board.autoscroll').unbind('scroll');
+              $tableFixed.hide();
+          }
+      }
+
+      $hideButton.click(function() {
+          resizeFixed();
+      });
+
+      $fullScreenButton.click(function() {
+        bindScroll();
+      });
+
+      $(window).resize(resizeFixed);
+
+      $(window).keyup(function(evt){
+          if (evt.keyCode == 27) {
+              $('html.agile-board-fullscreen').removeClass('agile-board-fullscreen');
+              $(".issue-card").addClass("hascontextmenu");
+              bindScroll();
+              saveFullScreenState();
+          }
+        }
+      );
+
+      init();
+      bindScroll();
+
+    });
+  };
+})();
+
+function parseErrorResponse(responseText){
+  try {
+    var errors = JSON.parse(responseText);
+  } catch(e) {
+
+  };
+
+  var alertMessage = '';
+
+  if (errors && errors.length > 0) {
+    for (var i = 0; i < errors.length; i++) {
+      alertMessage += errors[i] + '\n';
+    }
+  }
+  return alertMessage;
+}
+
+function setErrorMessage(message, flashClass) {
+  flashClass = flashClass || "error"
+  $('div#agile-board-errors').addClass("flash " + flashClass);
+  $('div#agile-board-errors').html(message).show();
+  setTimeout(clearErrorMessage,3000);
+}
+
+function clearErrorMessage() {
+  $('div#agile-board-errors').removeClass();
+  $('div#agile-board-errors').html('').hide();
+}
+
+
+function incHtmlNumber(element) {
+  $(element).html(~~$(element).html() + 1);
+}
+
+function decHtmlNumber(element) {
+  $(element).html(~~$(element).html() - 1);
+}
+
+function changeHtmlNumber(element, number){
+  elementWithHours = $(element).find("span.hours");
+  if (elementWithHours.size() > 0){
+    old_value = $(elementWithHours).html().replace(/(\(|\)|h)/);
+    new_value = parseFloat(old_value)+ parseFloat(number);
+    if (new_value > 0)
+      $(elementWithHours).html(new_value.toFixed(2) + "h");
+    else
+      $(elementWithHours).remove();
+  }
+  else{
+    new_value = number;
+    $(element).append("<span class='hours'>" + new_value + "h</span>");
+  }
+}
+
+
+function observeIssueSearchfield(fieldId, url) {
+  $('#'+fieldId).each(function() {
+    var $this = $(this);
+    $this.addClass('autocomplete');
+    $this.attr('data-value-was', $this.val());
+    var check = function() {
+      var val = $this.val();
+      if ($this.attr('data-value-was') != val){
+        $this.attr('data-value-was', val);
+        $.ajax({
+          url: url,
+          type: 'get',
+          data: {q: $this.val()},
+          beforeSend: function(){ $this.addClass('ajax-loading'); },
+          complete: function(){ $this.removeClass('ajax-loading'); }
+        });
+      }
+    };
+    var reset = function() {
+      if (timer) {
+        clearInterval(timer);
+        timer = setInterval(check, 300);
+      }
+    };
+    var timer = setInterval(check, 300);
+    $this.bind('keyup click mousemove', reset);
+  });
+}
+
+function recalculateHours() {
+  var backlogSum = 0;
+  var unit = $("#backlog_version_header").data('estimated-unit');
+
+  $('.versions-planning-board td:nth-child(2) .issue-card').each(function(i, elem){
+    hours = parseFloat($(elem).data('estimated-hours'));
+    backlogSum += hours;
+  })
+  $('.versions-planning-board .backlog-hours').text('(' + backlogSum.toFixed(2) + unit +')');
+
+  var currentSum = 0;
+  $('.versions-planning-board td:nth-child(3) .issue-card').each(function(i, elem){
+    hours = parseFloat($(elem).data('estimated-hours'));
+    currentSum += hours;
+  })
+  $('.versions-planning-board .current-hours').text('(' + currentSum.toFixed(2) + unit + ')');
+}
+
+function showInlineCommentNode(quick_comment){
+  if(quick_comment){
+    $(quick_comment).siblings(".last_comment").hide();
+    $(quick_comment).show();
+    $(quick_comment).children("textarea").focus();
+  }
+}
+
+function showInlineComment(node, url){
+  $(node).parent().toggleClass('hidden');
+  var quick_comment = $(node).parents(".fields").children(".quick-comment");
+  if ( $.trim($(quick_comment).html()) != '' ){
+    showInlineCommentNode(quick_comment);
+  }
+  else{
+    $.ajax({
+        url: url,
+        type: "get",
+        dataType: "html",
+        success: function(data, status, xhr){
+          $(quick_comment).html(data);
+          showInlineCommentNode(quick_comment);
+        },
+        error:function(xhr, status, error) {
+          var alertMessage = parseErrorResponse(xhr.responseText);
+          if (alertMessage) {
+            setErrorMessage(alertMessage);
+          }
+        }
+    })
+  };
+}
+
+function cancelInlineComment(node){
+  $(node).parent().hide();
+  $(node).parent().siblings(".last_comment").show();
+  $(node).parent().siblings('.quick-edit-card').toggleClass('hidden');
+  $(node).parent().html('');
+  return false;
+}
+
+function saveFullScreenState() {
+  state = $('html').hasClass('agile-board-fullscreen');
+  localStorage.setItem('full-screen-board', state);
+};
+
+$(document).ready(function(){
+  $('table.issues-board').StickyHeader();
+  $('div#agile-board-errors').click(function(){
+    $(this).animate({top: -$(this).outerHeight()}, 500);
+  });
+
+  $("#agile_live_search").keyup(function() {
+    var cards = $(".issues-board").find(".issue-card");
+    var searchTerm = this.value;
+    cards.removeClass("filtered");
+    cards.filter(function() {
+      return $(this).find(".name").text().toLowerCase().indexOf(searchTerm) === -1;
+    }).addClass("filtered");
+  });
+});
+
+function DisableNullFields() {
+  $('input').each(function(i) {
+    var $input = $(this);
+    if ($input.val() == '')
+      $input.attr('disabled', 'disabled');
+    }
+  );
+};
+
+function linkGenerator(path, text) {
+  return '<a href="' + window.location.origin + window.location.pathname + path + ' ">' + text + '</a>'
+};
+
+function linkableAttributeFields() {
+  var status_label = $('.status.attribute .label')
+  status_label.html(linkGenerator('/status', status_label.html()));
+
+  var assigned_label = $('.assigned-to.attribute .label')
+  assigned_label.html(linkGenerator('/assignee', assigned_label.html()));
+
+  var progress_label = $('.progress.attribute .label')
+  progress_label.html(linkGenerator('/done_ratio', progress_label.html()));
+};
diff --git a/plugins/redmine_agile/assets/javascripts/redmine_agile_context_menu.js b/plugins/redmine_agile/assets/javascripts/redmine_agile_context_menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d757e6fab6d9a00cc4ecbb1c374bfa4d96f6e44
--- /dev/null
+++ b/plugins/redmine_agile/assets/javascripts/redmine_agile_context_menu.js
@@ -0,0 +1,222 @@
+var agileContextMenuObserving;
+var agileContextMenuUrl;
+
+function agileContextMenuRightClick(event) {
+  var target = $(event.target);
+  if (target.is('a')) {return;}
+  var tr = target.parents('div').first();
+  if (!tr.hasClass('hascontextmenu')) {return;}
+  event.preventDefault();
+  if (!agileContextMenuIsSelected(tr)) {
+    agileContextMenuUnselectAll();
+    agileContextMenuAddSelection(tr);
+    agileContextMenuSetLastSelected(tr);
+  }
+  agileContextMenuShow(event);
+}
+
+function agileContextMenuClick(event) {
+  var target = $(event.target);
+  var lastSelected;
+
+  if (target.is('a') && target.hasClass('submenu')) {
+    event.preventDefault();
+    return;
+  }
+  agileContextMenuHide();
+  if (target.is('a') || target.is('img')) { return; }
+  if (event.which == 1 || (navigator.appVersion.match(/\bMSIE\b/))) {
+    var tr = target.parents('div').first();
+    if (tr.length && tr.hasClass('hascontextmenu')) {
+      // a row was clicked, check if the click was on checkbox
+      if (target.is('input.checkbox')) {
+        // a checkbox may be clicked
+        if (target.attr('checked')) {
+          tr.addClass('context-menu-selection');
+        } else {
+          tr.removeClass('context-menu-selection');
+        }
+      } else {
+        if (event.ctrlKey || event.metaKey) {
+          agileContextMenuToggleSelection(tr);
+        } else if (event.shiftKey) {
+          lastSelected = agileContextMenuLastSelected();
+          if (lastSelected.length) {
+            var toggling = false;
+            $('.hascontextmenu').each(function(){
+              if (toggling || $(this).is(tr)) {
+                agileContextMenuAddSelection($(this));
+              }
+              if ($(this).is(tr) || $(this).is(lastSelected)) {
+                toggling = !toggling;
+              }
+            });
+          } else {
+            agileContextMenuAddSelection(tr);
+          }
+        } else {
+          agileContextMenuUnselectAll();
+          agileContextMenuAddSelection(tr);
+        }
+        agileContextMenuSetLastSelected(tr);
+      }
+    } else {
+      // click is outside the rows
+      if (target.is('a') && (target.hasClass('disabled') || target.hasClass('submenu'))) {
+        event.preventDefault();
+      } else {
+        agileContextMenuUnselectAll();
+      }
+    }
+  }
+}
+
+function agileContextMenuCreate() {
+  if ($('#context-menu').length < 1) {
+    var menu = document.createElement("div");
+    menu.setAttribute("id", "context-menu");
+    menu.setAttribute("style", "display:none;");
+    document.getElementById("content").appendChild(menu);
+  }
+}
+
+function agileContextMenuShow(event) {
+  var mouse_x = event.pageX;
+  var mouse_y = event.pageY;
+  var render_x = mouse_x;
+  var render_y = mouse_y;
+  var dims;
+  var menu_width;
+  var menu_height;
+  var window_width;
+  var window_height;
+  var max_width;
+  var max_height;
+
+  $('#context-menu').css('left', (render_x + 'px'));
+  $('#context-menu').css('top', (render_y + 'px'));
+  $('#context-menu').html('');
+
+  $.ajax({
+    url: agileContextMenuUrl,
+    data: $(event.target).parents('form').first().serialize(),
+    success: function(data, textStatus, jqXHR) {
+      $('#context-menu').html(data);
+      menu_width = $('#context-menu').width();
+      menu_height = $('#context-menu').height();
+      max_width = mouse_x + 2*menu_width;
+      max_height = mouse_y + menu_height;
+
+      var ws = window_size();
+      window_width = ws.width;
+      window_height = ws.height;
+
+      /* display the menu above and/or to the left of the click if needed */
+      if (max_width > window_width) {
+       render_x -= menu_width;
+       $('#context-menu').addClass('reverse-x');
+      } else {
+       $('#context-menu').removeClass('reverse-x');
+      }
+      if (max_height > window_height) {
+       render_y -= menu_height;
+       $('#context-menu').addClass('reverse-y');
+      } else {
+       $('#context-menu').removeClass('reverse-y');
+      }
+      if (render_x <= 0) render_x = 1;
+      if (render_y <= 0) render_y = 1;
+      $('#context-menu').css('left', (render_x + 'px'));
+      $('#context-menu').css('top', (render_y + 'px'));
+      $('#context-menu').show();
+
+      //if (window.parseStylesheets) { window.parseStylesheets(); } // IE
+
+    }
+  });
+}
+
+function agileContextMenuSetLastSelected(tr) {
+  $('.cm-last').removeClass('cm-last');
+  tr.addClass('cm-last');
+}
+
+function agileContextMenuLastSelected() {
+  return $('.cm-last').first();
+}
+
+function agileContextMenuUnselectAll() {
+  $('.hascontextmenu').each(function(){
+    agileContextMenuRemoveSelection($(this));
+  });
+  $('.cm-last').removeClass('cm-last');
+}
+
+function agileContextMenuHide() {
+  $('#context-menu').hide();
+}
+
+function agileContextMenuToggleSelection(tr) {
+  if (agileContextMenuIsSelected(tr)) {
+    agileContextMenuRemoveSelection(tr);
+  } else {
+    agileContextMenuAddSelection(tr);
+  }
+}
+
+function agileContextMenuAddSelection(tr) {
+  tr.addClass('context-menu-selection');
+  agileContextMenuCheckSelectionBox(tr, true);
+  agileContextMenuClearDocumentSelection();
+}
+
+function agileContextMenuRemoveSelection(tr) {
+  tr.removeClass('context-menu-selection');
+  agileContextMenuCheckSelectionBox(tr, false);
+}
+
+function agileContextMenuIsSelected(tr) {
+  return tr.hasClass('context-menu-selection');
+}
+
+function agileContextMenuCheckSelectionBox(tr, checked) {
+  tr.find('input.checkbox[type=checkbox]').prop('checked', checked);
+}
+
+function agileContextMenuClearDocumentSelection() {
+  // TODO
+  if (document.selection) {
+    document.selection.empty(); // IE
+  } else {
+    window.getSelection().removeAllRanges();
+  }
+}
+
+function agileContextMenuInit(url) {
+  agileContextMenuUrl = url;
+  agileContextMenuCreate();
+  agileContextMenuUnselectAll();
+
+  if (!agileContextMenuObserving) {
+    $(document).click(agileContextMenuClick);
+    $(document).contextmenu(agileContextMenuRightClick);
+    agileContextMenuObserving = true;
+  }
+}
+
+
+function window_size() {
+  var w;
+  var h;
+  if (window.innerWidth) {
+    w = window.innerWidth;
+    h = window.innerHeight;
+  } else if (document.documentElement) {
+    w = document.documentElement.clientWidth;
+    h = document.documentElement.clientHeight;
+  } else {
+    w = document.body.clientWidth;
+    h = document.body.clientHeight;
+  }
+  return {width: w, height: h};
+}
diff --git a/plugins/redmine_agile/assets/javascripts/visibility.min.js b/plugins/redmine_agile/assets/javascripts/visibility.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..3733ccd08ac83894c50b7b7b4a7376ee1a0b6cfe
--- /dev/null
+++ b/plugins/redmine_agile/assets/javascripts/visibility.min.js
@@ -0,0 +1 @@
+!function(e){"use strict";var i=-1,t={onVisible:function(e){var i=t.isSupported();if(!i||!t.hidden())return e(),i;var n=t.change(function(){t.hidden()||(t.unbind(n),e())});return n},change:function(e){if(!t.isSupported())return!1;i+=1;var n=i;return t._callbacks[n]=e,t._listen(),n},unbind:function(e){delete t._callbacks[e]},afterPrerendering:function(e){var i=t.isSupported(),n="prerender";if(!i||n!=t.state())return e(),i;var r=t.change(function(i,d){n!=d&&(t.unbind(r),e())});return r},hidden:function(){return!(!t._doc.hidden&&!t._doc.webkitHidden)},state:function(){return t._doc.visibilityState||t._doc.webkitVisibilityState||"visible"},isSupported:function(){return!(!t._doc.visibilityState&&!t._doc.webkitVisibilityState)},_doc:document||{},_callbacks:{},_change:function(e){var i=t.state();for(var n in t._callbacks)t._callbacks[n].call(t._doc,e,i)},_listen:function(){if(!t._init){var e="visibilitychange";t._doc.webkitVisibilityState&&(e="webkit"+e);var i=function(){t._change.apply(t,arguments)};t._doc.addEventListener?t._doc.addEventListener(e,i):t._doc.attachEvent(e,i),t._init=!0}}};"undefined"!=typeof module&&module.exports?module.exports=t:e.Visibility=t}(this),function(e){"use strict";var i=-1,t=function(t){return t.every=function(e,n,r){t._time(),r||(r=n,n=null),i+=1;var d=i;return t._timers[d]={visible:e,hidden:n,callback:r},t._run(d,!1),t.isSupported()&&t._listen(),d},t.stop=function(e){return t._timers[e]?(t._stop(e),delete t._timers[e],!0):!1},t._timers={},t._time=function(){t._timed||(t._timed=!0,t._wasHidden=t.hidden(),t.change(function(){t._stopRun(),t._wasHidden=t.hidden()}))},t._run=function(i,n){var r,d=t._timers[i];if(t.hidden()){if(null===d.hidden)return;r=d.hidden}else r=d.visible;var a=function(){d.last=new Date,d.callback.call(e)};if(n){var o=new Date,u=o-d.last;r>u?d.delay=setTimeout(function(){a(),d.id=setInterval(a,r)},r-u):(a(),d.id=setInterval(a,r))}else d.id=setInterval(a,r)},t._stop=function(e){var i=t._timers[e];clearInterval(i.id),clearTimeout(i.delay),delete i.id,delete i.delay},t._stopRun=function(){var e=t.hidden(),i=t._wasHidden;if(e&&!i||!e&&i)for(var n in t._timers)t._stop(n),t._run(n,!e)},t};"undefined"!=typeof module&&module.exports?module.exports=t(require("./visibility.core")):t(e.Visibility)}(window);
\ No newline at end of file
diff --git a/plugins/redmine_agile/assets/stylesheets/jquery.simplecolorpicker.css b/plugins/redmine_agile/assets/stylesheets/jquery.simplecolorpicker.css
new file mode 100755
index 0000000000000000000000000000000000000000..234017d9e44e74b3f0fc8bcf0941643078d97074
--- /dev/null
+++ b/plugins/redmine_agile/assets/stylesheets/jquery.simplecolorpicker.css
@@ -0,0 +1,88 @@
+/*
+ * Very simple jQuery Color Picker
+ * https://github.com/tkrotoff/jquery-simplecolorpicker
+ *
+ * Copyright (C) 2012-2013 Tanguy Krotoff <tkrotoff@gmail.com>
+ *
+ * Licensed under the MIT license
+ */
+
+/**
+ * Inspired by Bootstrap Twitter.
+ * See https://github.com/twbs/bootstrap/blob/master/less/navbar.less
+ * See https://github.com/twbs/bootstrap/blob/master/less/dropdowns.less
+ */
+
+.simplecolorpicker.picker {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1051; /* Above Bootstrap modal (@zindex-modal = 1050) */
+  display: none;
+  float: left;
+
+  min-width: 160px;
+  max-width: 283px; /* @popover-max-width = 276px + 7 */
+
+  padding: 5px 0 0 5px;
+  margin: 2px 0 0;
+  list-style: none;
+  background-color: #fff; /* @dropdown-bg */
+
+  border: 1px solid #ccc;
+}
+
+.simplecolorpicker.inline {
+  display: inline-block;
+}
+
+.simplecolorpicker span {
+  margin: 0 5px 5px 0;
+}
+
+.simplecolorpicker.button,
+.simplecolorpicker span.color {
+  display: inline-block;
+  outline: none;
+  cursor: pointer;
+  border: 1px solid transparent;
+}
+
+.simplecolorpicker.button {
+  border: 1px solid #DDD;
+}
+
+.simplecolorpicker.button:after,
+.simplecolorpicker span.color:after {
+  content: '\00a0\00a0\00a0\00a0'; /* Spaces */
+}
+
+.simplecolorpicker span.color[data-disabled]:hover {
+  cursor: not-allowed;
+  border: 1px solid transparent;
+}
+
+.simplecolorpicker span.color:hover,
+.simplecolorpicker span.color[data-selected],
+.simplecolorpicker span.color[data-selected]:hover {
+  border: 1px solid #222; /* @gray-dark */
+}
+.simplecolorpicker span.color[data-selected]:after {
+  color: #fff;
+}
+
+/* Vertical separator, replaces optgroup. */
+.simplecolorpicker span.vr {
+  border-left: 1px solid #222; /* @gray-dark */
+}
+
+.simplecolorpicker span.color[data-selected]:after {
+  /*font-family: 'FontAwesome';*/
+  -webkit-font-smoothing: antialiased;
+
+  content: '\2714'; /* Ok/check mark */
+
+  margin-right: 2px;
+  margin-left: 2px;
+}
+
diff --git a/plugins/redmine_agile/assets/stylesheets/redmine_agile.css b/plugins/redmine_agile/assets/stylesheets/redmine_agile.css
new file mode 100755
index 0000000000000000000000000000000000000000..e3192063abd16a1f19c2a6dd3447274290224351
--- /dev/null
+++ b/plugins/redmine_agile/assets/stylesheets/redmine_agile.css
@@ -0,0 +1,487 @@
+/**********************************************************************/
+/* ICONS
+/**********************************************************************/
+#admin-menu a.agile { background-image: url(../images/agile.png);}
+.icon-fullscreen { background-image:  url(../images/fullscreen.png); }
+
+/**********************************************************************/
+/* FULLSCREEN
+/**********************************************************************/
+html.agile-board-fullscreen {
+  overflow: hidden;
+}
+
+html.agile-board-fullscreen div.agile-board {
+  position: fixed;
+  top: 0px;
+  left: 0px;
+  right: 0px;
+  bottom: 0px;
+  z-index: 20;
+  background: white;
+  overflow-y:scroll;
+}
+
+html.agile-board-fullscreen table.list.issues-board {
+  min-height: 100%;
+  top: 0px;
+  left: 0px;
+  right: 0px;
+}
+
+html.agile-board-fullscreen table.list.issues-board.sticky { min-height: auto; }
+
+html.agile-board-fullscreen .icon-fullscreen {
+  position: fixed;
+  right: 5px;
+  top: 5px;
+  z-index: 21;
+  text-indent: -9999px;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+div.agile-board.autoscroll {
+  overflow: visible;
+  position: relative;
+}
+
+/**********************************************************************/
+/* BOARD SETTINGS
+/**********************************************************************/
+table.options tr > td {
+  white-space: nowrap;
+}
+
+.card-fields .floating {
+  text-align: left;
+  width: 200px;
+  float: left;
+  overflow: hidden;
+  word-wrap: break-word;
+}
+
+.card-fields .floating label span {
+  width: 110px;
+  text-overflow: ellipsis;
+  display: inline-block;
+  white-space: nowrap;
+  overflow: hidden;
+  vertical-align: middle;
+}
+
+.card-fields .floating .wp_input{
+  width: 50px;
+  padding: 1px 5px !important;
+}
+
+/**********************************************************************/
+/* ISSUES SIDEBAR
+/**********************************************************************/
+#sidebar ul {
+  margin: 0;
+  padding: 0;
+}
+
+#sidebar ul li {
+  list-style-type: none;
+  margin: 0px 2px 0px 0px;
+  padding: 0px 0px 0px 0px;
+}
+
+/**********************************************************************/
+/* PLANNING BOARD
+/**********************************************************************/
+table.versions-planning-board {
+  border-spacing: 10px;
+  width: 100%;
+}
+
+table.versions-planning-board td.issue-version-col {
+  vertical-align: top;
+  height: 400px;
+}
+
+table.list.versions-planning-board input#search.autocomplete {width: 80%;}
+
+table.list.versions-planning-board tbody tr,
+table.list.versions-planning-board tbody tr:hover {background-color: white;}
+table.list.versions-planning-board .header-hours {
+  float: right;
+  vertical-align: baseline;
+  height: 26px;
+  line-height: 26px;
+}
+
+/**********************************************************************/
+/* AGILE BOARD
+/**********************************************************************/
+table.list.issues-board {table-layout: fixed;}
+table.list.issues-board th {overflow: hidden; text-overflow: ellipsis;}
+
+.agile-board table.list.issues-board tbody tr,
+.agile-board table.list.issues-board tbody tr:hover {background-color: white;}
+
+.assignable-user.draggable-active {
+  padding: 5px;
+  border: 1px solid #D5D5D5;
+  background-color: #ffffdd;
+}
+
+table.issues-board td.issue-status-col.closed {background-color: #FAFAFA;}
+
+table.issues-board tr.group.swimlane {height: 30px;}
+table.issues-board tr.group.swimlane td {border-top: 0px;border-bottom: 1px solid #ccc;}
+
+table.issues-board tbody td,
+table.issues-board tbody tr.issue:hover td {border: 0px; vertical-align: top;}
+
+table.issues-board.minimize-closed td.issue-status-col.closed .issue-card {
+  width: 10px;
+  float: left;
+}
+table.issues-board.minimize-closed td.issue-status-col.closed .issue-card span.fields {display: none;}
+
+/**********************************************************************/
+/* ISSUE CARD
+/**********************************************************************/
+.issue-card {
+  padding: 5px;
+  border: solid 1px #d5d5d5;
+  background-color: #ffffdd;
+  margin: 5px;
+  word-wrap: break-word;
+  text-align: left;
+  white-space: normal;
+  cursor: pointer;
+  position: relative;
+}
+
+.issues-board td.issue-status-col.closed .issue-card.closed-issue {float: left;}
+.issues-board td.issue-status-col.closed.collapse .issue-card.float-left {float: left; position: relative;}
+.issue-card.closed-issue {white-space: nowrap; display: inline-block;}
+.issue-card .tip{ position: fixed; white-space: normal; }
+
+.issue-card:not(.context-menu-selection) .attributes,
+.issue-card:not(.context-menu-selection) span.hours  {color: #888;}
+
+.issue-card .attributes,
+.issue-card span.hours {font-size: 90%;}
+
+.issue-card span.hours {float: right;}
+
+.issue-card .thumbnail {
+  height: 145px;
+  background-size: auto;
+  background-position: center;
+  background-repeat: no-repeat;
+  margin: 5px;
+}
+
+.issue-card .checkbox {display: none;}
+.issue-card .avatar {float: left; margin-right: 5px;}
+.issue-card p.name {font-weight: bold;}
+.issue-card p.project {
+  border: 1px solid #d5d5d5;
+  padding: 5px;
+  margin-bottom: 5px;
+  text-align: center;
+  background-color: white;
+}
+.issue-card .info {
+  border-top: 1px solid #d5d5d5;
+  padding-top: 5px;
+  margin-top: 5px;
+}
+.issue-card table.progress {
+  float: none;
+  margin-top: 5px;
+  width: 100%;
+}
+
+.issue-card table.progress td {
+  height: 5px;
+  padding: 0px;
+}
+
+.issue-card li.task-closed {
+  text-decoration: line-through;
+  color: #999;
+}
+
+.issue-card div.sub-issues {
+  border-top: 1px solid #d5d5d5;
+  padding-top: 5px;
+  margin-top: 5px;
+  font-size: 90%;
+}
+
+.issue-card div.sub-issues ul {
+  margin: 1px 0px 5px 0px;
+  padding-left: 30px;
+}
+
+.issue-card .issue-id.without-tracker {
+  float: right;
+}
+
+.issue-card.context-menu-selection p.project {
+  color: black;
+}
+
+.issue-card.context-menu-selection .attributes,
+.issue-card.context-menu-selection em.info {
+  color: white;
+}
+
+.issues-board td.issue-status-col.closed .issue-card {background-color: #EDEDED;}
+
+.issue-card.ui-sortable-helper {
+  -moz-transform: rotate(5deg); /* Для Firefox */
+  -ms-transform: rotate(5deg); /* Для IE */
+  -webkit-transform: rotate(5deg); /* Для Safari, Chrome, iOS */
+  -o-transform: rotate(5deg); /* Для Opera */
+  transform: rotate(5deg);
+  box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
+  -webkit-box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
+  cursor: -moz-grabbing;
+  cursor: -webkit-grabbing;
+  cursor: grabbing;
+}
+
+
+.issue-card.br-red {border-left: 5px solid red;}
+.issue-card.br-green {border-left: 5px solid green;}
+.issue-card.br-blue {border-left: 5px solid blue;}
+.issue-card.br-turquoise {border-left: 5px solid turquoise;}
+.issue-card.br-lightgreen {border-left: 5px solid lightgreen;}
+.issue-card.br-yellow {border-left: 5px solid yellow;}
+.issue-card.br-orange {border-left: 5px solid orange;}
+.issue-card.br-purple {border-left: 5px solid purple;}
+.issue-card.br-gray {border-left: 5px solid gray;}
+
+table.list.issues tr.issue.br-red, .issue-card.br-red {border-left: 5px solid red;}
+table.list.issues tr.issue.br-green, .issue-card.br-green {border-left: 5px solid green;}
+table.list.issues tr.issue.br-blue, .issue-card.br-blue {border-left: 5px solid blue;}
+table.list.issues tr.issue.br-turquoise, .issue-card.br-turquoise {border-left: 5px solid turquoise;}
+table.list.issues tr.issue.br-lightgreen, .issue-card.br-lightgreen {border-left: 5px solid lightgreen;}
+table.list.issues tr.issue.br-yellow, .issue-card.br-yellow {border-left: 5px solid yellow;}
+table.list.issues tr.issue.br-orange, .issue-card.br-orange {border-left: 5px solid orange;}
+table.list.issues tr.issue.br-purple, .issue-card.br-purple {border-left: 5px solid purple;}
+table.list.issues tr.issue.br-gray, .issue-card.br-gray {border-left: 5px solid gray;}
+
+.issue-card.bk-red {background-color: #FFE2E3; border-color: rgb(255, 201, 201);}
+.issue-card.bk-green {background-color: #DFFFCC; border-color: rgb(190, 239, 190);}
+.issue-card.bk-blue {background-color: #DCE7FF; border-color: rgb(189, 189, 233);}
+.issue-card.bk-turquoise {background-color: #C4FDFF; border-color: rgb(151, 222, 214);}
+.issue-card.bk-lightgreen {background-color: #D2FFEF; border-color: rgb(184, 228, 226);}
+.issue-card.bk-yellow {background-color: #FFFD9C; border-color: rgb(234, 234, 94);}
+.issue-card.bk-orange {background-color: #FFDBBA; border-color: rgb(255, 202, 105);}
+.issue-card.bk-purple {background-color: #EFDFFC; border-color: rgb(233, 186, 233);}
+.issue-card.bk-gray {background-color: #e1e1e1; border-color: rgb(198, 198, 198);}
+
+div.issue.details.br-red div.subject div > h3::before, div.issue.details.bk-red div.subject div > h3::before, a.issue.bk-red::before, a.issue.br-red::before {content: "\25CF  "; color: red;}
+div.issue.details.br-green div.subject div > h3::before, div.issue.details.bk-green div.subject div > h3::before, a.issue.bk-green::before, a.issue.br-green::before {content: "\25CF  "; color: green;}
+div.issue.details.br-blue div.subject div > h3::before, div.issue.details.bk-blue div.subject div > h3::before, a.issue.bk-blue::before, a.issue.br-blue::before {content: "\25CF  "; color: blue;}
+div.issue.details.br-turquoise div.subject div > h3::before, div.issue.details.bk-turquoise div.subject div > h3::before, a.issue.bk-turquoise::before, a.issue.br-turquoise::before {content: "\25CF  "; color: turquoise;}
+div.issue.details.br-lightgreen div.subject div > h3::before, div.issue.details.bk-lightgreen div.subject div > h3::before, a.issue.bk-lightgreen::before, a.issue.br-lightgreen::before {content: "\25CF  "; color: lightgreen;}
+div.issue.details.br-yellow div.subject div > h3::before, div.issue.details.bk-yellow div.subject div > h3::before, a.issue.bk-yellow::before, a.issue.br-yellow::before {content: "\25CF  "; color: yellow;}
+div.issue.details.br-orange div.subject div > h3::before, div.issue.details.bk-orange div.subject div > h3::before, a.issue.bk-orange::before, a.issue.br-orange::before {content: "\25CF  "; color: orange;}
+div.issue.details.br-purple div.subject div > h3::before, div.issue.details.bk-purple div.subject div > h3::before, a.issue.bk-purple::before, a.issue.br-purple::before {content: "\25CF  "; color: purple;}
+div.issue.details.br-gray div.subject div > h3::before, div.issue.details.bk-gray div.subject div > h3::before, a.issue.bk-gray::before, a.issue.br-gray::before {content: "\25CF  "; color: gray;}
+
+table.list.issues tr.issue:not(.context-menu-selection).bk-red td.id a {background-color: #FFE2E3; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-green td.id a {background-color: #DFFFCC; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-blue td.id a {background-color: #DCE7FF; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-turquoise td.id a {background-color: #C4FDFF; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-lightgreen td.id a {background-color: #D2FFEF; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-yellow td.id a {background-color: #FFFD9C; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-orange td.id a {background-color: #FFDBBA; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-purple td.id a {background-color: #EFDFFC; padding: 2px; border: 1px solid #DDD;}
+table.list.issues tr.issue:not(.context-menu-selection).bk-gray td.id a {background-color: #e1e1e1; padding: 2px; border: 1px solid #DDD;}
+
+
+/**********************************************************************/
+/* ISSUE CARD CHECKLIST
+/**********************************************************************/
+
+.issue-card div.checklist {
+  border-top: 1px solid #d5d5d5;
+  padding-top: 5px;
+  margin-top: 5px;
+  font-size: 90%;
+}
+
+.issue-card div.checklist ul {
+  list-style: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+.issue-card div.checklist input[type=checkbox] {
+  margin: 0px;
+}
+
+/**********************************************************************/
+/* ISSUE CARD QUICKEDIT
+/**********************************************************************/
+
+.issue-card div.quick-edit-card {
+  position: absolute;
+  right: 2px;
+  bottom: 2px;
+  border: 1px solid #ddd;
+  background-color: white;
+  height: 17px;
+  padding: 1px 1px 0px 1px;
+  margin: 0px;
+  display: none;
+}
+
+.issue-card div.quick-edit-card a {
+  float: left;
+}
+
+.issue-card:hover div.quick-edit-card.hidden{
+  display: none;
+}
+.issue-card:hover div.quick-edit-card {
+  display: block;
+  cursor: default;
+  opacity: 0.5;
+}
+
+.issue-card:hover div.quick-edit-card:hover { opacity: 1;}
+
+.issue-card .quick-comment {
+  display: none;
+  margin-top: 5px;
+  padding-right: 5px;
+}
+
+.issue-card .quick-comment textarea{
+  width: 100%;
+}
+
+.issue-card .last-comment{
+  font-style: italic;
+}
+
+.issues-board .add-issue{
+  background: transparent;
+  border: 1px dashed rgba(48, 59, 77, 0.3);
+  padding: 5px;
+  margin: 5px;
+}
+
+.issues-board .add-issue .new-card__input{
+  outline: none;
+  width: 100%;
+  border: 0px;
+  background: transparent;
+  position: relative;
+  text-align: center;
+}
+
+/**********************************************************************/
+/* ISSUES BOARD
+/**********************************************************************/
+
+.issues-board td.issue-status-col.droppable-hover, .issues-board td.issue-status-col .issue-card.droppable-hover {
+  border-style: dotted;
+  background-color: #E9F8FD;
+}
+
+table.list thead tr th span.hours {color: #888; float: right; font-size: 90%; font-weight: normal;}
+table.issues-board thead th {white-space: normal;}
+/*
+tr.issue.br-red td.id::before,
+tr.issue.br-green td.id::before,
+tr.issue.br-blue td.id::before,
+tr.issue.br-turquoise td.id::before,
+tr.issue.br-lightgreen td.id::before,
+tr.issue.br-yellow td.id::before,
+tr.issue.br-orange td.id::before,
+tr.issue.br-purple td.id::before,
+tr.issue.br-gray td.id::before,
+*/
+
+.wp_input {
+  margin-left: 2px;
+}
+
+table.issues-board thead th span.count span.under_wp_limit {color: #008000;}
+table.issues-board thead th span.count span.over_wp_limit {color: #A50000;}
+
+table.issues-board thead th.under_wp_limit {background-color: #D4F1D4;}
+table.issues-board thead th.over_wp_limit {background-color: #FFE9E9;}
+
+table.issues-board tbody td.under_wp_limit {background-color: #EFFFEF;}
+table.issues-board tbody td.over_wp_limit {background-color: #FFF6F6;}
+
+.add-issue{
+  background: transparent;
+  border: 1px dashed rgba(48, 59, 77, 0.3);
+  margin: 5px;
+}
+
+.add-issue .new-card__input{
+  outline: none;
+  width: 100%;
+  border: 0px;
+  background: transparent;
+  position: relative;
+  text-align: center;
+}
+table.issues-board tbody td.over_wp_limit {background-color: #FFF6F6;}
+
+.lock {
+  display: none;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  z-index: 9999;
+  opacity: 0.9;
+}
+
+.agile-board-fullscreen .lock{
+  position: fixed;
+}
+
+
+.agile-chart-container {
+  margin: auto;
+  height: 400px;
+  max-width: 800px;
+}
+
+.issue-card.filtered {
+  opacity: 0.2;
+}
+
+
+table.list.issues-board.sticky {
+  display: table;
+  top: 0px;
+  position: fixed;
+}
+
+@media screen and (max-width: 899px) {
+  html.agile-board-fullscreen div.agile-board {
+    padding: 64px 0 0; /* padding-top equals header height */
+  }
+
+  html.agile-board-fullscreen .icon-fullscreen {
+    top: 64px; /* top equals header height */
+  }
+
+  html.agile-board-fullscreen table.list.issues-board.sticky { top: 64px; }
+  table.list.issues-board.sticky { top: 64px; }
+
+  html.agile-board-fullscreen #wrapper > div.flyout-menu.js-flyout-menu { z-index: 22; }
+}
+
+html {
+  overflow-y: inherit !important;
+}
diff --git a/plugins/redmine_agile/config/locales/de.yml b/plugins/redmine_agile/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e576b63ddf2c09131b06a23d8be9e94041cc2876
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/de.yml
@@ -0,0 +1,125 @@
+# German strings go here for Rails i18n
+de:
+  label_agile: Agiles Projektmanagement
+  label_agile_board: Agiles Taskboard
+  label_agile_board_plural: Agile Taskboards
+  label_agile_board_thumbnails: Vorschaubilder
+  label_agile_board_more_issues: Mehr Aufgaben
+  error_agile_status_transition: Aufgabenstatus konnte nicht geändert werden.
+  label_agile_board_new: Neues Taskboard
+  label_agile_board_edit: Taskboard bearbeiten
+  label_agile_my_boards: Meine Taskboards
+  label_agile_issue_id: Aufgaben-Nummer
+
+  permission_manage_public_agile_queries: Taskboards verwalten
+  permission_add_agile_queries: Taskboards hinzufügen
+  permission_view_agile_queries: Taskboards ansehen
+
+  #1.1.0
+  label_agile_board_default_fields: Standardfelder für Taskboard-Karten
+  label_agile_charts_issues_burndown: Burndown nach Aufgaben
+  label_agile_charts_work_burndown:  Burndown nach erfassten Zeiten
+  label_agile_charts_number_of_hours: Stunden
+  label_agile_charts_number_of_issues: Anzahl Aufgaben
+  label_agile_charts_cumulative_flow: Cumulative Flow
+  label_agile_charts_trackers_cumulative_flow: Cumulative Flow nach Tracker
+  label_agile_charts_issues_velocity: Velocity
+  label_agile_charts_lead_time: Lead Time
+  label_agile_charts_average_lead_time: Durchschnittliche Lead Time
+  label_agile_chart_plural: Agile Diagramme
+  permission_view_agile_charts: Agile Diagramme ansehen
+  label_agile_ideal_work_remaining: Soll
+  label_agile_actual_work_remaining: Ist
+  label_agile_chart: Diagramm
+  label_agile_date_from: Von
+  label_agile_date_to: Bis
+  label_agile_chart_dates: Intervall für das Diagramm
+  label_agile_weighed_ideal_work_remaining: Gewichtetes Soll
+  label_agile_status_colors: Status-Farben
+  label_agile_charts_burnup: Burnup nach Aufgaben
+  label_agile_charts_number_of_days: Anzahl Tage
+  label_agile_too_many_items: "Das Diagramm konnte nicht generiert werden, da es die maximale Anzahl an darstellbaren Aufgaben (%{max}) überschreiten würde."
+  label_agile_time_reports_items_limit: Maximale Anzahl an Aufgaben in Diagrammen
+  label_agile_total_work_remaining: Gesamt
+  label_agile_default_chart: Standarddiagramm für Versions
+
+  #1.1.1
+  label_agile_charts_average_velocity: Durchschnittliche Velocity
+  label_agile_charts_avarate_number_of_issues: Durchschnittliche Aufgabenanzahl
+  label_agile_charts_avarate_number_of_hours: Durchschnittliche Stundenanzahl
+
+  #1.2.0
+  label_agile_color: Farben
+  permission_manage_agile_verions: Versionplanung verwalten
+  label_agile_version_planning: Versionplanung
+  error_agile_version_transition: Version der Aufgabe konnte nicht verändert werden.
+  label_agile_tracker_colors: Farben für Tracker
+  label_agile_issue_priority_colors: Farben für Aufgabenprioritäten
+  label_agile_color_based_on: Taskboard-Karten einfärben basierend auf
+  label_agile_color_no_colors: Keine Farben
+  label_agile_manage_colors: Farben verwalten
+  label_agile_fullscreen: Vollbild
+  label_agile_no_version_issues: Aufgaben ohne Version
+  label_agile_charts_work_burnup: Burnup nach erfassten Zeiten
+  label_agile_completed: Abgeschlossen
+  label_agile_exclude_weekends: Wochenenden von Soll-Zeiten ausnehmen
+  label_agile_board_truncated: "Das Taskboard wurde abgeschnitten, da es die maximale Anzahl an darstellbaren Aufgaben (%{max}) überschreitet."
+  label_agile_board_items_limit: Aufgabenlimit für Taskboards
+  label_agile_swimlanes: Swimlanes
+  label_agile_minimize_closed: Geschlossene Aufgaben minimieren
+  label_agile_fields: Felder für Taskboard-Karten
+  label_agile_default_board: Standard-Taskboard
+
+  #1.3.6
+  text_agile_move_not_possible: Diese Änderung ist nicht möglich
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: Ãœbergeordneter Tracker
+  label_agile_sub_issues: Unteraufgaben
+  label_agile_color_green: Grün
+  label_agile_color_blue: Blau
+  label_agile_color_turquoise: Türkis
+  label_agile_color_lightgreen: Hellgrün
+  label_agile_color_yellow: Gelb
+  label_agile_color_orange: Orange
+  label_agile_color_red: Rot
+  label_agile_color_purple: Lila
+  label_agile_color_gray: Grau
+  label_agile_has_sub_issues: Hat Unteraufgaben
+  label_agile_light_free_version: Agile Light freie Version
+  label_agile_link_to_pro: Upgrade auf PRO
+  label_agile_link_to_pro_demo: PRO version live demo
+  label_agile_link_to_more_plugins: Entdecken Sie mehr RedmineUP plugins
+  label_agile_button_agree: Einverstanden
+  label_agile_license: RedmineUP Lizenz
+  label_agile_saving_boards: Boards speichern
+  label_agile_horizontal_swim_lines: Horizontale Swimlanes
+  label_agile_board_sub_columns: Board-Unterspalten
+  label_agile_additional_agile_charts: Weitere Agile-Charts
+  label_agile_coloured_issue_cards: Farbige Karten
+  label_agile_4_more_features: 4 weitere Funktionen...
+  label_agile_upgrade_to_pro: Upgrade auf die PRO-Version um die Funktionen zu nutzen
+
+  label_agile_day_in_state: Zeit seit letzter Status-Änderung
+
+  #1.3.10
+  label_agile_hide_closed_issues_data: Daten von geschlossenen Aufgaben ausblenden
+  project_module_agile: Agile
+
+  #1.3.13
+  label_agile_last_comment: Letzter Kommentar
+  label_agile_esitmate_units: Einheit für Schätzungen
+  label_agile_trackers_for_sp: Tracker für Story Points
+  label_agile_story_points: Story Points
+  field_story_points: Story Points
+  label_agile_charts_number_of_story_points: Anzahl an Story Points
+  label_agile_board_columns: Taskboard Spalten
+  lable_agile_wip_limit_exceeded: Work-in-progress Grenze überschritten
+  label_agile_wip_limit: Work-in-progress Grenze
+  label_agile_add_new_issue: + NEUE AUFGABE
+  label_agile_allow_create_cards: Karte für neue Aufgabe
+  label_agile_auto_assign_on_move: Selbst zuweisen bei Taskboard-Karten-Bewegung
+  text_agile_create_issue_error: Die Aufgabe konnte nicht erstellt werden
+  label_agile_inline_comment: Inline-Kommentar
+  label_agile_hours: Stunden
+  field_color: Farbe
diff --git a/plugins/redmine_agile/config/locales/en.yml b/plugins/redmine_agile/config/locales/en.yml
new file mode 100755
index 0000000000000000000000000000000000000000..f74ba5cfe91bcbb6f7d22076561935e58cd3d955
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/en.yml
@@ -0,0 +1,137 @@
+# English strings go here for Rails i18n
+en:
+  label_agile: Agile
+  label_agile_board: Agile board
+  label_agile_board_plural: Agile boards
+  label_agile_board_thumbnails: Thumbnails
+  label_agile_board_more_issues: More issues
+  error_agile_status_transition: Can’t change issue status
+  label_agile_board_new: New agile board
+  label_agile_board_edit: Edit agile board
+  label_agile_my_boards: My agile boards
+  label_agile_issue_id: Issue ID
+
+  permission_manage_public_agile_queries: Manage public agile boards
+  permission_add_agile_queries: Add agile boards
+  permission_view_agile_queries: View agile boards
+
+  #1.1.0
+  label_agile_board_default_fields: Default card fields
+  label_agile_charts_issues_burndown: Issues burndown
+  label_agile_charts_work_burndown_hours: Hours burndown
+  label_agile_charts_work_burndown_sp: Story points burndown
+  label_agile_charts_number_of_hours: Number of hours
+  label_agile_charts_number_of_issues: Number of issues
+  label_agile_charts_cumulative_flow: Cumulative flow
+  label_agile_charts_trackers_cumulative_flow: Trackers cumulative flow
+  label_agile_charts_issues_velocity: Velocity
+  label_agile_charts_lead_time: Lead time
+  label_agile_charts_average_lead_time: Average lead time
+  label_agile_chart_plural: Agile charts
+  permission_view_agile_charts: View agile charts
+  label_agile_ideal_work_remaining: Ideal
+  label_agile_actual_work_remaining: Actual
+  label_agile_chart: Chart
+  label_agile_date_from: From
+  label_agile_date_to: To
+  label_agile_chart_dates: Chart intervals
+  label_agile_weighed_ideal_work_remaining: Weighted ideal
+  label_agile_status_colors: Status colors
+  label_agile_charts_burnup: Issues burnup
+  label_agile_charts_number_of_days: Number of days
+  label_agile_too_many_items: "The chart can't be created because it exceeds the maximum number of items that can be displayed (%{max})"
+  label_agile_time_reports_items_limit: Time entries based charts issues limit
+  label_agile_total_work_remaining: Total
+  label_agile_default_chart: Version default chart
+
+  #1.1.1
+  label_agile_charts_average_velocity: Average velocity
+  label_agile_charts_avarate_number_of_issues: Average number of issues
+  label_agile_charts_avarate_number_of_hours: Average number of hours
+
+  #1.2.0
+  label_agile_color: Color
+  permission_manage_agile_verions: Manage version planning
+  label_agile_version_planning: Version planning
+  error_agile_version_transition: Can’t change issue version
+  label_agile_tracker_colors: Tracker colors
+  label_agile_issue_priority_colors: Issue priority colors
+  label_agile_color_based_on: Colored by
+  label_agile_color_no_colors: No colors
+  label_agile_manage_colors: Manage colors
+  label_agile_fullscreen: Full screen
+  label_agile_no_version_issues: Issues without version
+  label_agile_charts_work_burnup_hours: Hours burnup
+  label_agile_charts_work_burnup_sp: Story points burnup
+  label_agile_completed: Completed
+  label_agile_exclude_weekends: Exclude weekends from ideal effort
+  label_agile_board_truncated: "The board was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
+  label_agile_board_items_limit: Agile board items limit
+  label_agile_swimlanes: Swim lanes
+  label_agile_minimize_closed: Minimize closed issues
+  label_agile_fields: Card fields
+  label_agile_default_board: Default board
+
+  #1.3.6
+  text_agile_move_not_possible: That move is not possible
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: Parent tracker
+  label_agile_sub_issues: Sub issues
+  label_agile_color_green: Green
+  label_agile_color_blue: Blue
+  label_agile_color_turquoise: Turquoise
+  label_agile_color_lightgreen: Light green
+  label_agile_color_yellow: Yellow
+  label_agile_color_orange: Orange
+  label_agile_color_red: Red
+  label_agile_color_purple: Purple
+  label_agile_color_gray: Gray
+  label_agile_has_sub_issues: Has sub issues
+  label_agile_light_free_version: Agile Light free version
+  label_agile_link_to_pro: Upgrade to PRO
+  label_agile_link_to_pro_demo: PRO version live demo
+  label_agile_link_to_more_plugins: Find more RedmineUP plugins
+  label_agile_button_agree: Agree
+  label_agile_license: RedmineUP License
+  label_agile_saving_boards: Saving board
+  label_agile_horizontal_swim_lines: Horizontal swim lines
+  label_agile_board_sub_columns: Board sub-columns
+  label_agile_additional_agile_charts: Additional agile charts
+  label_agile_coloured_issue_cards: Coloured issue cards
+  label_agile_4_more_features: 4 more features...
+  label_agile_upgrade_to_pro: Upgrade to PRO version to use this features
+
+  label_agile_day_in_state:  In status
+
+  #1.3.10
+  label_agile_hide_closed_issues_data: Hide closed issues data
+  project_module_agile: Agile
+
+  #1.3.13
+  label_agile_last_comment: Last comment
+
+  #1.4.0
+  label_agile_esitmate_units: Estimate units
+  label_agile_trackers_for_sp: Trackers for story points
+  label_agile_story_points: Story points
+  field_story_points: Story points
+  label_agile_charts_number_of_story_points: Number of story points
+  label_agile_board_columns: Board columns
+  lable_agile_wip_limit_exceeded: Work-in-progress limit exceeded
+  label_agile_wip_limit: Work-in-progress limit
+  label_agile_add_new_issue: '+ ADD NEW ISSUE'
+  label_agile_allow_create_cards: Card creation
+  label_agile_auto_assign_on_move: Auto assign on move
+  text_agile_create_issue_error: An error occurred while creating the task
+  label_agile_inline_comment: Inline comment
+  label_agile_hours: Hours
+  field_color: Color
+
+  field_duration: Duration
+
+  field_closed_on_trendline: Closed trendline
+  field_created_on_trendline: Created trendline
+
+  label_agile_sp_values: Story points values
+  label_cards_search: Search by subject
diff --git a/plugins/redmine_agile/config/locales/es.yml b/plugins/redmine_agile/config/locales/es.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7b6cec1368735d387554b1017b40a2ff0fa6e0b0
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/es.yml
@@ -0,0 +1,50 @@
+# Spanish strings go here for Rails i18n
+es:
+  label_agile: Ágil
+  label_agile_board: Tablero ágil
+  label_agile_board_plural: Tableros ágiles
+  label_agile_board_thumbnails: Thumbnails
+  label_agile_board_issues_per_column: Peticiones por columna
+  label_agile_board_more_issues: Más peticiones
+  error_agile_status_transition: No se puede cambiar el estado de la petición
+  label_agile_board_new: Nuevo tablero ágil
+  label_agile_board_edit: Editar tablero ágil
+  label_agile_my_boards: Mis tableros ágiles
+  label_agile_issue_id: ID de Petición
+
+  permission_manage_public_agile_queries: Administrar tableros ágiles públicos
+  permission_add_agile_queries: Agregar tableros ágiles
+  permission_view_agile_queries: Ver tableros ágiles
+
+  #1.1.0
+  label_agile_board_default_fields: Columnas por defecto
+  label_agile_charts_issues_burndown: Peticiones burndown
+  label_agile_charts_work_burndown: Trabajo burndown
+  label_agile_charts_number_of_hours: Número de horas
+  label_agile_charts_number_of_issues: Número de peticiones
+  label_agile_charts_cumulative_flow: Flujo acumulado
+  label_agile_charts_trackers_cumulative_flow: Seguimiento de flujo acumulado
+  label_agile_charts_issues_velocity: Velocidad
+  label_agile_charts_lead_time: Tiempo de ejecución
+  label_agile_charts_average_lead_time: Tiempo de ejecución promedio
+  label_agile_chart_plural: Gráficos ágiles
+  permission_view_agile_charts: Ver gráficos ágiles
+  label_agile_ideal_work_remaining: Ideal
+  label_agile_actual_work_remaining: Actual
+  label_agile_chart: Gráfico
+  label_agile_date_from: Desde
+  label_agile_date_to: Hasta
+  label_agile_chart_dates: Intervalos gráficos
+  label_agile_weighed_ideal_work_remaining: Weighed ideal
+  label_agile_status_colors: Color de estados
+  label_agile_charts_burnup: "Peticiones burnup"
+  label_agile_charts_number_of_days: Número de días
+  label_agile_too_many_items: "El gráfico no puede ser creado por exceder el número máximo de items que pueden ser mostrados (%{max})"
+  label_agile_time_reports_items_limit: Tiempo de entradas basado en gráfico de peticiones límite
+  label_agile_total_work_remaining: Total
+  label_agile_default_chart: Versión gráfica por defecto
+
+  #1.1.1
+  label_agile_charts_average_velocity: Velocidad promedio
+  label_agile_charts_avarate_number_of_issues: Número de peticiones promedio
+  label_agile_charts_avarate_number_of_hours: Número de horas promedio
\ No newline at end of file
diff --git a/plugins/redmine_agile/config/locales/fr.yml b/plugins/redmine_agile/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..205a99d4f9250b8da0e582202bcd637d08ceecca
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/fr.yml
@@ -0,0 +1,106 @@
+# French strings go here for Rails i18n
+fr:
+  label_agile: Agile
+  label_agile_board: Tableau Agile
+  label_agile_board_plural: Tableaux Agile
+  label_agile_board_thumbnails: Miniatures
+  label_agile_board_more_issues: Plus de demandes
+  error_agile_status_transition: Impossible de modifier les statuts des demandes
+  label_agile_board_new: Nouveau tableau Agile
+  label_agile_board_edit: Modifier le tableau Agile
+  label_agile_my_boards: Mes tableaux Agiles
+  label_agile_issue_id: ID de demande
+
+  permission_manage_public_agile_queries: Gérer les tableaux Agile publics
+  permission_add_agile_queries: Ajouter des tableaux Agile
+  permission_view_agile_queries: Afficher des tableaux Agile
+
+  #1.1.0
+  label_agile_board_default_fields: Champs par défaut des cartes
+  label_agile_charts_issues_burndown: Burndown des demandes
+  label_agile_charts_work_burndown: Burndown du temps de travail
+  label_agile_charts_number_of_hours: Nombre d'heures
+  label_agile_charts_number_of_issues: Nombre de demandes
+  label_agile_charts_cumulative_flow: Flux cumulé
+  label_agile_charts_trackers_cumulative_flow: Flux cumulé des trackers
+  label_agile_charts_issues_velocity: Vélocité
+  label_agile_charts_lead_time: Délais
+  label_agile_charts_average_lead_time: Délai moyen de livraison
+  label_agile_chart_plural: Graphiques Agiles
+  permission_view_agile_charts: Afficher les graphique Agiles
+  label_agile_ideal_work_remaining: Idéal
+  label_agile_actual_work_remaining: Réel
+  label_agile_chart: Graphique
+  label_agile_date_from: De
+  label_agile_date_to: À
+  label_agile_chart_dates: Intervalles du graphique
+  label_agile_weighed_ideal_work_remaining: Idéal pondéré
+  label_agile_status_colors: Couleurs des statuts
+  label_agile_charts_burnup: Burnup des demandes
+  label_agile_charts_number_of_days: Nombre de jours
+  label_agile_too_many_items: "Le graphique ne peut pas être créé car il dépasse le nombre maximal d'éléments pouvant être affichés (%{max})"
+  label_agile_time_reports_items_limit: Limite du nombre de demandes pour les graphiques temporels
+  label_agile_total_work_remaining: Total
+  label_agile_default_chart: Graphique de versions
+
+  #1.1.1
+  label_agile_charts_average_velocity: Vélocité moyenne
+  label_agile_charts_avarate_number_of_issues: Nombre moyen de demandes
+  label_agile_charts_avarate_number_of_hours: nombre moyen d'heures
+
+  #1.2.0
+  label_agile_color: Couleur
+  permission_manage_agile_verions: Gérer la planification de versions
+  label_agile_version_planning: Planification de versions
+  error_agile_version_transition: Impossible de modifier la version de la demande
+  label_agile_tracker_colors: Couleurs des trackers
+  label_agile_issue_priority_colors: Couleurs des priorités de demandes
+  label_agile_color_based_on: Coloré par
+  label_agile_color_no_colors: Pas de couleurs
+  label_agile_manage_colors: Gérer les couleurs
+  label_agile_fullscreen: Plein écran
+  label_agile_no_version_issues: Demandes sans version
+  label_agile_charts_work_burnup: Burnup du temps de travail
+  label_agile_completed: Terminé
+  label_agile_exclude_weekends: Exclure les week-ends de l'effort idéal
+  label_agile_board_truncated: "Le tableau ne peut pas être créé car il dépasse le nombre maximal d'éléments pouvant être affichés (%{max})"
+  label_agile_board_items_limit: Limite du nombre d'élément du tableau Agile
+  label_agile_swimlanes: Lignes
+  label_agile_minimize_closed: Réduire les demandes fermées
+  label_agile_fields: Champs des cartes
+  label_agile_default_board: Tableau par défaut
+
+  #1.3.6
+  text_agile_move_not_possible: Ce déplacement n'est pas possible
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: Tracker parent
+  label_agile_sub_issues: Sous-demandes
+  label_agile_color_green: Vert
+  label_agile_color_blue: Bleu
+  label_agile_color_turquoise: Turquoise
+  label_agile_color_lightgreen: Vert clair
+  label_agile_color_yellow: Jaune
+  label_agile_color_orange: Orange
+  label_agile_color_red: Rouge
+  label_agile_color_purple: Violet
+  label_agile_color_gray: Gris
+  label_agile_has_sub_issues: A des sous-demandes
+  label_agile_light_free_version: Agile Version gratuite
+  label_agile_link_to_pro: Mettre à jour en version PRO
+  label_agile_link_to_pro_demo: Démo live de la version PRO
+  label_agile_link_to_more_plugins: Voir plus de plugins RedmineUP
+  label_agile_button_agree: D'accord
+  label_agile_license: Licence RedmineUP
+  label_agile_saving_boards: Enregistrement des tableaux
+  label_agile_horizontal_swim_lines: Lignes en horizontal
+  label_agile_board_sub_columns: Sous-colonnes du tableau
+  label_agile_additional_agile_charts: Graphiques Agiles supplémentaires
+  label_agile_coloured_issue_cards: Cartes colorées
+  label_agile_4_more_features: 4 nouveautés supplémentaires...
+  label_agile_upgrade_to_pro: Passez en version PRO pour bénéficier de ces nouveautés
+
+  label_agile_day_in_state:  Dans le statut
+
+  #1.3.10
+  project_module_agile: Agile
\ No newline at end of file
diff --git a/plugins/redmine_agile/config/locales/ko.yml b/plugins/redmine_agile/config/locales/ko.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5e58dcbf3d4a72b43b20e7342702b4523fc5352c
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/ko.yml
@@ -0,0 +1,70 @@
+# Translation by Ki Won Kim (http://x10.iptime.org/redmine, http://xyz37.blog.me, xyz37@naver.com)
+ko:
+  label_agile: "애자일"
+  label_agile_board: "애자일 보드"
+  label_agile_board_plural: "애자일 보드"
+  label_agile_board_thumbnails: "축소판 그림"
+  label_agile_board_more_issues: "더 많은 일감"
+  error_agile_status_transition: "일감 상태를 변경할 수 없습니다."
+  label_agile_board_new: "신규 애자일 보드"
+  label_agile_board_edit: "애자일 보드 수정"
+  label_agile_my_boards: "내 애자일 보드"
+  label_agile_issue_id: "일감 번호"
+
+  permission_manage_public_agile_queries: "공용 애자일 보드 관리"
+  permission_add_agile_queries: "애자일 보드 추가"
+  permission_view_agile_queries: "애자일 보드 보기"
+
+  #1.1.0
+  label_agile_board_default_fields: "기본 카드 필드"
+  label_agile_charts_issues_burndown: "일감 Burn Down"
+  label_agile_charts_work_burndown: "ìž‘ì—… Burn Down"
+  label_agile_charts_number_of_hours: "시간 수"
+  label_agile_charts_number_of_issues: "일감 수"
+  label_agile_charts_cumulative_flow: "누적 흐름"
+  label_agile_charts_trackers_cumulative_flow: "일감 유형 누적 흐름"
+  label_agile_charts_issues_velocity: "속도"
+  label_agile_charts_lead_time: "리드 타임"
+  label_agile_charts_average_lead_time: "평균 리드 타임"
+  label_agile_chart_plural: "애자일 차트"
+  permission_view_agile_charts: "애자일 차트 보기"
+  label_agile_ideal_work_remaining: "이상적인"
+  label_agile_actual_work_remaining: "실제"
+  label_agile_chart: "차트"
+  label_agile_date_from: "부터"
+  label_agile_date_to: "까지"
+  label_agile_chart_dates: "차트 간격"
+  label_agile_weighed_ideal_work_remaining: "무게 이상"
+  label_agile_status_colors: "상태 색상"
+  label_agile_charts_burnup: "일감 Burn Up"
+  label_agile_charts_number_of_days: "일 수"
+  label_agile_too_many_items: "차트가 표시 (% {max}) 될 수 있는 항목의 최대 수를 초과 하므로 만들 수 없습니다."
+  label_agile_time_reports_items_limit: "시간 항목에 기반한 차트 일감 제한"
+  label_agile_total_work_remaining: "ì „ì²´"
+  label_agile_default_chart: "버전 기본 차트"
+
+  #1.1.1
+  label_agile_charts_average_velocity: "평균 속도"
+  label_agile_charts_avarate_number_of_issues: "평균 일감 수"
+  label_agile_charts_avarate_number_of_hours: "평균 시간 수"
+
+  #1.2.0
+  label_agile_color: "색상"
+  permission_manage_agile_verions: "버전 계획 관리"
+  label_agile_version_planning: "버전 계획"
+  error_agile_version_transition: "일감 버전을 변경할 수 없습니다."
+  label_agile_tracker_colors: "일감 유형 색상"
+  label_agile_issue_priority_colors: "일감 우선 순위 색상"
+  label_agile_color_based_on: "색상의"
+  label_agile_color_no_colors: "색상 없음"
+  label_agile_manage_colors: "색상 관리"
+  label_agile_fullscreen: "전체 화면"
+  label_agile_no_version_issues: "버전없는 일감들"
+  label_agile_charts_work_burnup: "ìž‘ì—… Burn Up"
+  label_agile_completed: "완료됨"
+  label_agile_exclude_weekends: "이상적인 노력에서 주말 제외"
+  label_agile_board_truncated: "보드가 표시 (% {max}) 될 수 있는 항목의 최대 수를 초과 하므로 잘렸습니다."
+  label_agile_board_items_limit: "애자일 보드 항목 제한"
+  label_agile_swimlanes: "Swim lanes"
+  label_agile_minimize_closed: "완료된 일감 최소화"
+  label_agile_fields: "카드 필드"
\ No newline at end of file
diff --git a/plugins/redmine_agile/config/locales/pt-BR.yml b/plugins/redmine_agile/config/locales/pt-BR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..22a783fa72bf49ccf3ffd44c50881e8ed311744d
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/pt-BR.yml
@@ -0,0 +1,70 @@
+# English strings go here for Rails i18n
+pt-BR:
+  label_agile: Agile
+  label_agile_board: Agile board
+  label_agile_board_plural: Agile boards
+  label_agile_board_thumbnails: Thumbnails
+  label_agile_board_more_issues: Mais Tarefas
+  error_agile_status_transition: Não é possível alterar o status da tarefa
+  label_agile_board_new: Novo agile board
+  label_agile_board_edit: Edit agile board
+  label_agile_my_boards: My agile boards
+  label_agile_issue_id: Número da Tarefa
+
+  permission_manage_public_agile_queries: Manage public agile boards
+  permission_add_agile_queries: Add agile boards
+  permission_view_agile_queries: View agile boards
+
+  #1.1.0
+  label_agile_board_default_fields: Default card fields
+  label_agile_charts_issues_burndown: Tarefas burndown
+  label_agile_charts_work_burndown: Trabalho burndown
+  label_agile_charts_number_of_hours: Número de horas
+  label_agile_charts_number_of_issues: Número de tarefas
+  label_agile_charts_cumulative_flow: Afluência 
+  label_agile_charts_trackers_cumulative_flow: Afluência de tarefas
+  label_agile_charts_issues_velocity: Velocidade
+  label_agile_charts_lead_time: Tempo de espera
+  label_agile_charts_average_lead_time: Tempo de espera médio
+  label_agile_chart_plural: Agile charts
+  permission_view_agile_charts: View agile charts
+  label_agile_ideal_work_remaining: Ideal
+  label_agile_actual_work_remaining: Real
+  label_agile_chart: Gráfico
+  label_agile_date_from: De
+  label_agile_date_to: Para
+  label_agile_chart_dates: Chart intervals
+  label_agile_weighed_ideal_work_remaining: Média ideal
+  label_agile_status_colors: Status colors
+  label_agile_charts_burnup: Tarefas burnup
+  label_agile_charts_number_of_days: Número de dias
+  label_agile_too_many_items: "O gráfico não pode ser criado porque excede o número máximo de itens que podem ser exibidos (%{max})"
+  label_agile_time_reports_items_limit: Time entries based charts issues limit
+  label_agile_total_work_remaining: Total
+  label_agile_default_chart: Version default chart
+
+  #1.1.1
+  label_agile_charts_average_velocity: Velocidade média
+  label_agile_charts_avarate_number_of_issues: Número médio de tarefas
+  label_agile_charts_avarate_number_of_hours: Número médio de horas
+
+  #1.2.0
+  label_agile_color: Color
+  permission_manage_agile_verions: Manage version planning
+  label_agile_version_planning: Planejamento da versão
+  error_agile_version_transition: Can’t change issue version
+  label_agile_tracker_colors: Tracker colors
+  label_agile_issue_priority_colors: Issue priority colors
+  label_agile_color_based_on: Colored by
+  label_agile_color_no_colors: No colors
+  label_agile_manage_colors: Manage colors
+  label_agile_fullscreen: Fullscreen
+  label_agile_no_version_issues: Issues without version
+  label_agile_charts_work_burnup: Work burnup
+  label_agile_completed: Concluído
+  label_agile_exclude_weekends: Exclude weekends from ideal effort
+  label_agile_board_truncated: "The board was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
+  label_agile_board_items_limit: Agile board items limit
+  label_agile_swimlanes: Swim lanes
+  label_agile_minimize_closed: Minimize closed issues
+  label_agile_fields: Card fields
\ No newline at end of file
diff --git a/plugins/redmine_agile/config/locales/ru.yml b/plugins/redmine_agile/config/locales/ru.yml
new file mode 100755
index 0000000000000000000000000000000000000000..5a28e54f4920e2fe83b2e0eef7c586fdde12202c
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/ru.yml
@@ -0,0 +1,132 @@
+# Russian strings go here for Rails i18n
+ru:
+  label_agile: Agile
+  label_agile_board: Доска задач
+  label_agile_board_plural: Доски задач
+  label_agile_board_thumbnails: Превью
+  label_agile_board_issues_per_column: Кол-во задач в колонке
+  label_agile_board_more_issues: Еще задачи
+  error_agile_status_transition: Невозможно изменить статус
+  label_agile_board_new: Новая доска
+  label_agile_board_edit: Редактировать доску
+  label_agile_my_boards: Мои доски
+  label_agile_issue_id: Номер задачи
+
+  permission_manage_public_agile_queries: Управление публичными досками задач
+  permission_add_agile_queries: Добавление досок задач
+  permission_view_agile_queries: Просмотр досок задач
+
+  #1.1.0
+  label_agile_board_default_fields: Поля по умолчанию
+  label_agile_charts_issues_burndown: Сгорание задач
+  label_agile_charts_work_burndown_hours: Сгорание времени задач (burndown)
+  label_agile_charts_work_burndown_sp: Сгорание story points (burndown)
+  label_agile_charts_number_of_hours: Кол-во часов
+  label_agile_charts_number_of_issues: Кол-во задач
+  label_agile_charts_cumulative_flow: Накопительный поток
+  label_agile_charts_trackers_cumulative_flow: Накопительный поток треккеров
+  label_agile_charts_issues_velocity: Скорость закрытия задач
+  label_agile_charts_lead_time: Цикл задачи
+  label_agile_charts_average_lead_time: Средний цикл задачи
+  label_agile_chart_plural: Диаграммы
+  permission_view_agile_charts: Просмотр диаграмм
+  label_agile_ideal_work_remaining: Идеально
+  label_agile_actual_work_remaining: Действительно
+  label_agile_chart: Диаграмма
+  label_agile_date_from: С
+  label_agile_date_to: По
+  label_agile_chart_dates: Интервалы диаграммы
+  label_agile_weighed_ideal_work_remaining: Взвещенно
+  label_agile_status_colors: Цвета статусов
+  label_agile_charts_burnup: Выполнение задач
+  label_agile_charts_number_of_days: Кол-во дней
+  label_agile_too_many_items: "Диаграмма не может быть создана потому что превышено максимальное кол-во задач (%{max})"
+  label_agile_time_reports_items_limit: Максимальное кол-во задач отображаемых на диаграммах
+  label_agile_total_work_remaining: Всего
+  label_agile_default_chart: Диаграмма для версии
+
+  #1.1.1
+  label_agile_charts_average_velocity: Средняя скорость закрытия задач
+  label_agile_charts_avarate_number_of_issues: Среднее кол-во задач
+  label_agile_charts_avarate_number_of_hours: Среднее кол-во часов
+
+  #1.2.0
+  label_agile_color: Цвет
+  permission_manage_agile_verions: Планирование версий
+  label_agile_version_planning: Планирование версий
+  error_agile_version_transition: Невозможно изменить версию задачи
+  label_agile_tracker_colors: Цвета треккеров
+  label_agile_issue_priority_colors: Цвета приоритетов задачи
+  label_agile_color_based_on: Цвета на основании
+  label_agile_color_no_colors: Без цвета
+  label_agile_manage_colors: Настроить цвета
+  label_agile_fullscreen: Во весь экран
+  label_agile_no_version_issues: Задачи без версии
+  label_agile_charts_work_burnup_hours: Выполнение времени задач (burnup)
+  label_agile_charts_work_burnup_sp: Выполнение story points (burnup)
+  label_agile_completed: Выполнено
+  label_agile_exclude_weekends: Исключать выходные для диаграммы сгорания
+  label_agile_board_truncated: "Доска была усечена, поскольку превышено максимальное кол-во элементов, которые могут отображаться (%{max})"
+  label_agile_board_items_limit: Максимальное кол-во задач отображаемых на Доске
+  label_agile_swimlanes: Группировать по
+  label_agile_minimize_closed: Минимизировать закрытые задачи
+  label_agile_fields: Поля
+  label_agile_default_board: По умолчанию
+
+  #1.3.6
+  text_agile_move_not_possible: Данное передвижение невозможно
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: Треккер родителя
+  label_agile_sub_issues: Подзадачи
+  label_agile_color_green: Зеленый
+  label_agile_color_blue: Синий
+  label_agile_color_turquoise: Бирюзовый
+  label_agile_color_lightgreen: Светло зеленый
+  label_agile_color_yellow: Желтый
+  label_agile_color_orange: Оранжевый
+  label_agile_color_red: Красный
+  label_agile_color_purple: Фиолетовый
+  label_agile_color_gray: Серый
+  label_agile_has_sub_issues: Подзадачи
+  label_agile_light_free_version: Agile бесплатная версия
+  label_agile_link_to_pro: Обновление до PRO
+  label_agile_link_to_pro_demo: Онлайн демо PRO
+  label_agile_link_to_more_plugins: Другие плагины RedmineUP
+  label_agile_button_agree: Согласен
+  label_agile_license: Лицензионное соглашение RedmineUP
+  label_agile_saving_boards: Сохранить настройку доски
+  label_agile_horizontal_swim_lines: Группировки карточек
+  label_agile_board_sub_columns: Доп. колонки
+  label_agile_additional_agile_charts: Доп. диаграммы
+  label_agile_coloured_issue_cards: Цветовая индикация карточек
+  label_agile_4_more_features: И еще другие 4 функции...
+  label_agile_upgrade_to_pro: 'Обновитесь до PRO версии чтобы:'
+
+  #1.3.10
+  label_agile_hide_closed_issues_data: Скрывать данные закрытых задач
+  label_agile_day_in_state: В статусе
+  project_module_agile: Agile
+
+  #1.3.13
+  label_agile_last_comment: Последний комментарий
+
+  #1.4.0
+  label_agile_esitmate_units: Использовать для оценки
+  label_agile_trackers_for_sp: Трекеры для Story points
+  label_agile_story_points: Story points
+  field_story_points: Story points
+  label_agile_charts_number_of_story_points: Кол-во story points
+  label_agile_board_columns: Столбцы доски
+  lable_agile_wip_limit_exceeded: Превышен лимит по задачам в данном столбце
+  label_agile_wip_limit: Ограничение по кол-ву задач в столбце
+  label_agile_add_new_issue: '+ НОВАЯ ЗАДАЧА'
+  label_agile_allow_create_cards: Создавать новые задачи на доске
+  label_agile_auto_assign_on_move: Автоназначение при перетаскивании
+  text_agile_create_issue_error: Произошла ошибка при создании задачи
+  label_agile_inline_comment: Комментирование с доски
+  label_agile_hours: Часы
+  field_color: Цвет
+
+  field_duration: Продолжительность
+  label_cards_search: Поиск по теме
diff --git a/plugins/redmine_agile/config/locales/zh-TW.yml b/plugins/redmine_agile/config/locales/zh-TW.yml
new file mode 100644
index 0000000000000000000000000000000000000000..821c70b32e149a7e665badeaf9c555633026fc9c
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/zh-TW.yml
@@ -0,0 +1,92 @@
+# encoding: utf-8
+# Simplified Chinese strings go here for Rails i18n
+# Author: zhoutt
+# Based on file: en.yml
+
+'zh-TW':
+  label_agile: 敏捷
+  label_agile_board: 敏捷看板
+  label_agile_board_plural: 敏捷看板
+  label_agile_board_thumbnails: 縮略圖
+  label_agile_board_more_issues: 更多問題
+  error_agile_status_transition: 不能更改問題狀態
+  label_agile_board_new: 新敏捷看板
+  label_agile_board_edit: 編輯敏捷看板
+  label_agile_my_boards: 我的敏捷看板
+  label_agile_issue_id: 問題ID
+
+  permission_manage_public_agile_queries: 管理公共敏捷看板
+  permission_add_agile_queries: 新建敏捷看板
+  permission_view_agile_queries: 瀏覽敏捷看板
+
+  #1.1.0
+  label_agile_board_default_fields: 默認卡片區域
+  label_agile_charts_issues_burndown: 問題燃燒圖
+  label_agile_charts_work_burndown: 工作燃燒圖
+  label_agile_charts_number_of_hours: 時長數
+  label_agile_charts_number_of_issues: 問題數
+  label_agile_charts_cumulative_flow: 累積流量
+  label_agile_charts_trackers_cumulative_flow: 追蹤累流量
+  label_agile_charts_issues_velocity: 速度
+  label_agile_charts_lead_time: 交付周期
+  label_agile_charts_average_lead_time: 平均交付周期
+  label_agile_chart_plural: 敏捷圖標
+  permission_view_agile_charts: 瀏覽敏捷圖標
+  label_agile_ideal_work_remaining: 理想
+  label_agile_actual_work_remaining: 實際
+  label_agile_chart: 圖表
+  label_agile_date_from: 自
+  label_agile_date_to: 至
+  label_agile_chart_dates: 圖標間隔
+  label_agile_weighed_ideal_work_remaining: 理想權衡
+  label_agile_status_colors: 狀態顏色
+  label_agile_charts_burnup: 問題燃燒圖
+  label_agile_charts_number_of_days: 天數
+  label_agile_too_many_items: "該圖表因超出可顯示的最大項目數而不能被創建(%{max})"
+  label_agile_time_reports_items_limit: 時間條目基于圖表的問題限制
+  label_agile_total_work_remaining: 總計
+  label_agile_default_chart: 版本默認圖表
+
+  #1.1.1
+  label_agile_charts_average_velocity: 平均速率
+  label_agile_charts_avarate_number_of_issues: 平均問題數
+  label_agile_charts_avarate_number_of_hours: 平均時長
+
+  #1.2.0
+  label_agile_color: 顏色
+  permission_manage_agile_verions: 管理版本規劃
+  label_agile_version_planning: 版本規劃
+  error_agile_version_transition: 不能更改問題版本
+  label_agile_tracker_colors: 跟蹤器顏色
+  label_agile_issue_priority_colors: 問題優先級顏色
+  label_agile_color_based_on: 顏色
+  label_agile_color_no_colors: 無顏色
+  label_agile_manage_colors: 管理顏色
+  label_agile_fullscreen: 全屏
+  label_agile_no_version_issues: 無版本項目
+  label_agile_charts_work_burnup: 工作燃燒
+  label_agile_completed: 完成
+  label_agile_exclude_weekends: 從理想工作中排除周末時間
+  label_agile_board_truncated: "看板被縮短是由于超出最大顯示項目數 (%{max})"
+  label_agile_board_items_limit: 敏捷看板項目限制
+  label_agile_swimlanes: 泳道
+  label_agile_minimize_closed: 最小化關閉的問題
+  label_agile_fields: 卡片區域
+  label_agile_default_board: 默認看板
+
+  #1.3.6
+  text_agile_move_not_possible: 該步驟不可行
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: 本追蹤
+  label_agile_sub_issues: 子問題
+  label_agile_color_green: 綠色
+  label_agile_color_blue: 藍色
+  label_agile_color_turquoise: 藍綠色
+  label_agile_color_lightgreen: 淺綠色
+  label_agile_color_yellow: 黃色
+  label_agile_color_orange: 橙色
+  label_agile_color_red: 紅色
+  label_agile_color_purple: 紫色
+  label_agile_color_gray: 灰色
+  label_agile_has_sub_issues: 子問題
diff --git a/plugins/redmine_agile/config/locales/zh.yml b/plugins/redmine_agile/config/locales/zh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff313fc52f97445a0decb26dab113a8bd0c6037f
--- /dev/null
+++ b/plugins/redmine_agile/config/locales/zh.yml
@@ -0,0 +1,133 @@
+# encoding: utf-8
+# Simplified Chinese strings go here for Rails i18n
+# Author: zhoutt
+# Based on file: en.yml
+
+zh:
+  label_agile: 敏捷
+  label_agile_board: 敏捷看板
+  label_agile_board_plural: 敏捷看板
+  label_agile_board_thumbnails: 缩略图
+  label_agile_board_more_issues: 更多问题
+  error_agile_status_transition: 不能更改问题状态
+  label_agile_board_new: 新敏捷看板
+  label_agile_board_edit: 编辑敏捷看板
+  label_agile_my_boards: 我的敏捷看板
+  label_agile_issue_id: 问题ID
+
+  permission_manage_public_agile_queries: 管理公共敏捷看板
+  permission_add_agile_queries: 新建敏捷看板
+  permission_view_agile_queries: 浏览敏捷看板
+
+  #1.1.0
+  label_agile_board_default_fields: 默认卡片区域
+  label_agile_charts_issues_burndown: 问题燃烧图
+  label_agile_charts_work_burndown_hours: 小时燃尽图
+  label_agile_charts_work_burndown_sp: 故事点燃烧图
+  label_agile_charts_number_of_hours: 时长数
+  label_agile_charts_number_of_issues: 问题数
+  label_agile_charts_cumulative_flow: 累积流量
+  label_agile_charts_trackers_cumulative_flow: 追踪累流量
+  label_agile_charts_issues_velocity: 速度
+  label_agile_charts_lead_time: 交付周期
+  label_agile_charts_average_lead_time: 平均交付周期
+  label_agile_chart_plural: 敏捷图标
+  permission_view_agile_charts: 浏览敏捷图标
+  label_agile_ideal_work_remaining: 理想
+  label_agile_actual_work_remaining: 实际
+  label_agile_chart: 图表
+  label_agile_date_from: 自
+  label_agile_date_to: 至
+  label_agile_chart_dates: 图标间隔
+  label_agile_weighed_ideal_work_remaining: 理想权衡
+  label_agile_status_colors: 状态颜色
+  label_agile_charts_burnup: 问题燃烧图
+  label_agile_charts_number_of_days: 天数
+  label_agile_too_many_items: "该图表因超出可显示的最大项目数而不能被创建(%{max})"
+  label_agile_time_reports_items_limit: 时间条目基于图表的问题限制
+  label_agile_total_work_remaining: 总计
+  label_agile_default_chart: 版本默认图表
+
+  #1.1.1
+  label_agile_charts_average_velocity: 平均速率
+  label_agile_charts_avarate_number_of_issues: 平均问题数
+  label_agile_charts_avarate_number_of_hours: 平均时长
+
+  #1.2.0
+  label_agile_color: 颜色
+  permission_manage_agile_verions: 管理版本规划
+  label_agile_version_planning: 版本规划
+  error_agile_version_transition: 不能更改问题版本
+  label_agile_tracker_colors: 跟踪器颜色
+  label_agile_issue_priority_colors: 问题优先级颜色
+  label_agile_color_based_on: 颜色
+  label_agile_color_no_colors: 无颜色
+  label_agile_manage_colors: 管理颜色
+  label_agile_fullscreen: 全屏
+  label_agile_no_version_issues: 无版本项目
+  label_agile_charts_work_burnup_hours: 小时燃耗
+  label_agile_charts_work_burnup_sp: 故事点燃耗
+  label_agile_completed: 完成
+  label_agile_exclude_weekends: 从理想工作中排除周末时间
+  label_agile_board_truncated: "看板被缩短是由于超出最大显示项目数 (%{max})"
+  label_agile_board_items_limit: 敏捷看板项目限制
+  label_agile_swimlanes: 泳道
+  label_agile_minimize_closed: 最小化关闭的问题
+  label_agile_fields: 卡片区域
+  label_agile_default_board: 默认看板
+
+  #1.3.6
+  text_agile_move_not_possible: 该步骤不可行
+
+  #1.3.8
+  label_agile_parent_issue_tracker_id: 本追踪
+  label_agile_sub_issues: 子问题
+  label_agile_color_green: 绿色
+  label_agile_color_blue: 蓝色
+  label_agile_color_turquoise: 蓝绿色
+  label_agile_color_lightgreen: 浅绿色
+  label_agile_color_yellow: 黄色
+  label_agile_color_orange: 橙色
+  label_agile_color_red: 红色
+  label_agile_color_purple: 紫色
+  label_agile_color_gray: 灰色
+  label_agile_has_sub_issues: 子问题
+  label_agile_light_free_version: 敏捷免费版
+  label_agile_link_to_pro: 升级到PRO版
+  label_agile_link_to_pro_demo: PRO版在线演示
+  label_agile_link_to_more_plugins: 查找更多RedmineUP插件
+  label_agile_button_agree: 同意
+  label_agile_license: RedmineUP 协议
+  label_agile_saving_boards: 保存白板
+  label_agile_horizontal_swim_lines: 水平泳道线
+  label_agile_board_sub_columns: 白板子列
+  label_agile_additional_agile_charts: 额外的敏捷图表
+  label_agile_coloured_issue_cards: 彩色的问题卡
+  label_agile_4_more_features: 敏捷4更多功能...
+  label_agile_upgrade_to_pro: 更新到PRO版使用这个功能
+
+  label_agile_day_in_state:  状态
+
+  #1.3.10
+  label_agile_hide_closed_issues_data: 隐藏关闭的问题数据
+  project_module_agile: 敏捷
+
+  #1.3.13
+  label_agile_last_comment: 最后的评论
+
+  #1.4.0
+  label_agile_esitmate_units: 单元评估
+  label_agile_trackers_for_sp: 追踪故事点
+  label_agile_story_points: 故事要点
+  field_story_points: 故事要点
+  label_agile_charts_number_of_story_points: 故事要点数量
+  label_agile_board_columns: 白板列
+  lable_agile_wip_limit_exceeded: 进展中的工作超出限制
+  label_agile_wip_limit: 进展中的工作限制
+  label_agile_add_new_issue: '+ 添加新问题'
+  label_agile_allow_create_cards: 卡片创建
+  label_agile_auto_assign_on_move: 移动自动分配
+  text_agile_create_issue_error: 在创建任务时发生了一个错误
+  label_agile_inline_comment: 内联注释
+  label_agile_hours: 小时
+  field_color: 颜色
diff --git a/plugins/redmine_agile/config/routes.rb b/plugins/redmine_agile/config/routes.rb
new file mode 100755
index 0000000000000000000000000000000000000000..2a340d47f5fe03218677869db997df90d529080f
--- /dev/null
+++ b/plugins/redmine_agile/config/routes.rb
@@ -0,0 +1,56 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+# Plugin's routes
+# See: http://guides.rubyonrails.org/routing.html
+
+resources :projects do
+  resources :agile_queries, :only => [:new, :create]
+  resources :agile_versions, :only => [:index] do
+    post :index, :on => :collection
+  end
+end
+
+resources :agile_versions, :only => [:update, :show] do
+  collection do
+    get 'load'
+    get 'autocomplete'
+  end
+end
+
+resources :issues do
+  get "done_ratio", :to => "agile_journal_details#done_ratio"
+  get "status", :to => "agile_journal_details#status"
+  get "assignee", :to => "agile_journal_details#assignee"
+end
+
+resources :agile_queries
+get '/agile_colors/:object_type', :to => "agile_colors#index", :as => "agile_colors"
+put '/agile_colors/:object_type', :to => "agile_colors#update", :as => "update_agile_colors"
+
+get '/projects/:project_id/agile/charts', :to => "agile_charts#show", :as => "project_agile_charts"
+get '/agile/charts/', :to => "agile_charts#show", :as => "agile_charts"
+get '/agile/charts/render_chart', :to => "agile_charts#render_chart"
+get '/agile/charts/select_version_chart', :to => "agile_charts#select_version_chart"
+get '/projects/:project_id/agile/board', :to => 'agile_boards#index'
+get '/agile/board', :to => 'agile_boards#index'
+put '/agile/board', :to => 'agile_boards#update', :as => 'update_agile_board'
+get '/agile/issue_tooltip', :to => 'agile_boards#issue_tooltip', :as => 'issue_tooltip'
+get '/agile/inline_comment', :to => 'agile_boards#inline_comment', :as => 'agile_inline_comment'
+post 'projects/:project_id/agile/create_issue', :to => 'agile_boards#create_issue', :as => 'agile_create_issue'
diff --git a/plugins/redmine_agile/db/migrate/001_create_issue_status_orders.rb b/plugins/redmine_agile/db/migrate/001_create_issue_status_orders.rb
new file mode 100755
index 0000000000000000000000000000000000000000..24edba793d628a364c630abb89c1d811e3d3c416
--- /dev/null
+++ b/plugins/redmine_agile/db/migrate/001_create_issue_status_orders.rb
@@ -0,0 +1,30 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateIssueStatusOrders < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def change
+    create_table :issue_status_orders do |t|
+      t.integer :issue_id
+      t.integer :position
+    end
+
+    add_index :issue_status_orders, :issue_id
+    add_index :issue_status_orders, :position
+  end
+end
diff --git a/plugins/redmine_agile/db/migrate/002_create_agile_colors.rb b/plugins/redmine_agile/db/migrate/002_create_agile_colors.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c835f2b6dc4a568b01051f58905dff64aaf8fa25
--- /dev/null
+++ b/plugins/redmine_agile/db/migrate/002_create_agile_colors.rb
@@ -0,0 +1,30 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateAgileColors < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def change
+    create_table :agile_colors do |t|
+      t.references :container, :polymorphic => true
+      t.string :color
+    end
+
+    add_index :agile_colors, :container_id
+    add_index :agile_colors, :container_type
+  end
+end
diff --git a/plugins/redmine_agile/db/migrate/003_rename_issue_status_orders.rb b/plugins/redmine_agile/db/migrate/003_rename_issue_status_orders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..751c90ffeef2149efba0c0b6ca9f8823e980927e
--- /dev/null
+++ b/plugins/redmine_agile/db/migrate/003_rename_issue_status_orders.rb
@@ -0,0 +1,40 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class RenameIssueStatusOrders < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def up
+    remove_index :issue_status_orders, :issue_id
+    remove_index :issue_status_orders, :position
+
+    rename_table :issue_status_orders, :agile_ranks
+
+    add_index :agile_ranks, :issue_id
+    add_index :agile_ranks, :position
+  end
+
+  def down
+    remove_index :agile_ranks, :issue_id
+    remove_index :agile_ranks, :position
+
+    rename_table :agile_ranks, :issue_status_orders
+
+    add_index :issue_status_orders, :issue_id
+    add_index :issue_status_orders, :position
+  end
+end
diff --git a/plugins/redmine_agile/db/migrate/004_rename_agile_ranks.rb b/plugins/redmine_agile/db/migrate/004_rename_agile_ranks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b8cddf89728e3d72a8dcc6f57e2fc0b71e357ac
--- /dev/null
+++ b/plugins/redmine_agile/db/migrate/004_rename_agile_ranks.rb
@@ -0,0 +1,40 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class RenameAgileRanks < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def up
+    remove_index :agile_ranks, :issue_id if index_exists? :agile_ranks, :issue_id
+    remove_index :agile_ranks, :position if index_exists? :agile_ranks, :position
+
+    rename_table :agile_ranks, :agile_data
+
+    add_index :agile_data, :issue_id
+    add_index :agile_data, :position
+  end
+
+  def down
+    remove_index :agile_data, :issue_id
+    remove_index :agile_data, :position
+
+    rename_table :agile_data, :agile_ranks
+
+    add_index :agile_ranks, :issue_id
+    add_index :agile_ranks, :position
+  end
+end
diff --git a/plugins/redmine_agile/db/migrate/005_add_story_points_to_agile_ranks.rb b/plugins/redmine_agile/db/migrate/005_add_story_points_to_agile_ranks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cece10b445c00baee6f4263d8ad15bdacdd0c787
--- /dev/null
+++ b/plugins/redmine_agile/db/migrate/005_add_story_points_to_agile_ranks.rb
@@ -0,0 +1,24 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AddStoryPointsToAgileRanks < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def change
+    add_column :agile_data, :story_points, :integer
+  end
+end
diff --git a/plugins/redmine_agile/doc/CHANGELOG b/plugins/redmine_agile/doc/CHANGELOG
new file mode 100755
index 0000000000000000000000000000000000000000..d76945074620f3b8b89c2df0997a2430ee1fc391
--- /dev/null
+++ b/plugins/redmine_agile/doc/CHANGELOG
@@ -0,0 +1,225 @@
+== Redmine Agile plugin changelog
+
+Redmine Agile plugin - Agile board plugin for redmine
+Copyright (C) 2011-2018 RedmineUP
+http://www.redmineup.com/
+
+== 2018-09-25 v1.4.7
+
+* Added missing filters: Issue, Description, Private, Watcher
+* Added preloading of colors for issues
+* Fixed tags render on a card
+* Fixed public board permission bug
+* Fixed agile board adaptability
+* Fixed fullscreen link displaying
+* Fixed filtering by Company custom field
+
+== 2018-03-20 v1.4.6
+
+* Redmine 4 support
+* Quick seach on the board
+* Issue tags field on cards
+* Story Points available values setting
+* Fixed story point showing bug
+* Fixed makeup on issue edit form
+* Fixed light version dropable bug
+* Fixed custom fields bug with charts
+
+== 2017-09-06 v1.4.5
+
+* Agile charts moved to Chartjs
+* Fixed current version filter value
+* Fixed issue sorting bug with active story points
+
+== 2017-07-06 v1.4.4
+
+* Redmine 3.4 support 
+* Color attribute for users
+* Agile board <Current version> query filter
+* Added initional state for status and assignee history 
+* Chinese translation update
+* Fixed checklist items order
+
+== 2017-03-20 v1.4.3
+
+* Added assignee, status and % done history urls
+* Created and Updated dates card fields
+* Fixed version from future bug
+* Fixed Agile board z-index bug
+* Fixed blocked query filter error
+* Fixed bug with zero divide for spent time color
+* Added trendline first point
+
+== 2016-11-16 v1.4.2
+
+* Trendlines for lead time and velocity charts
+* Current active version filter
+* Chinese translation (Zuofeng Zhang)
+* Issue order for swimlanes
+* Fixed bug with session storage overload
+* Story points chart data calculation bug fixed
+* Firefox and IE bug with on board comments fixed
+
+== 2016-06-20 v1.4.1
+
+* Agile board header height fixed for IE
+* Auto assign user on move card
+* Burndown/burnup charts fixes for story points and hours
+* Context menu for fullscreen board
+* Separate charts for SP and hours
+* Lock board on autorefresh
+
+== 2016-02-09 v1.4.0
+
+* Story points estimation
+* Card assignment error messages
+* Cleanup WIP limits option styles
+* Highligh inline created cards
+* Inline comments board lock
+* Fixed version planning access right
+* Fixed unicode letters in locale files
+* Fixed performance issues with board rendering
+
+== 2016-01-20 v1.3.13
+
+* Inline issue creation
+* Work-in-progress limits
+* Colored by project
+* Adding notes inline
+* Checklist on card view
+* Last comment card field
+* French locale by Olivier Houdas
+* Fixed bug with sorting cards in redmine < 3
+
+== 2015-11-03 v1.3.12
+
+* Estimated time sum in column header
+* Issue relations filter
+* Fixed bug with Mysql migration
+* Fixed bug with global board cards view
+* Fixed issue context menu bug
+
+== 2015-08-18 v1.3.11
+
+* Setting for closed cards view
+* Colorized by assignee
+
+== 2015-08-06 v1.3.10
+
+* Fixed bug with sticky headers after fullscreen auto update
+* Colored by spent time
+* Fixed bug with version planner unassigned issues
+* Added translation for module
+* Days in state card field
+
+== 2015-05-20 v1.3.9
+
+* Chinese translation (zhoutt)
+* Fixed print media styles for board
+* Parent task filter allowed commas
+* Version planner filters
+
+== 2015-03-06 v1.3.8
+
+* Redmine 3.0 support fixes
+* Autoupdate board on fullscreen view
+* N+1 agile color fixes
+* New filter Parent issue tracker
+* Total hours on version planning columns
+* Sub issues card field
+* Issue history for color changes
+
+== 2015-02-10 v1.3.6
+
+* Sticky headers in fullscreen mode (Dariusz Kowalski)
+* Ajax error messages
+* Fixed bug in version planner with wrong column ID
+* Fixed 404 when enter hit in version planning form
+* Portuguese (Brazil) translation (Marcelo de A. Fernandes)
+
+== 2014-10-28 v1.3.5
+
+* Default boards for projects roles and users
+* Sub-projects issues in version planner
+
+== 2014-10-16 v1.3.4
+
+* Added filters for Assignee's role and Assignee's group
+* Shared versions support
+* Assignee history log
+
+== 2014-09-28 v1.3.3
+
+* German translation (Jan Schulz-Hofen)
+* Assignable users on agile board sidebar
+* Fixed bug with saving boards for non admin roles for Redmine 2.3
+* Fixed bug with authors swimlanes
+* Fixed bug in swimlanes with empty versions list
+
+== 2014-06-27 v1.3.2
+
+* Default desc sorting for priority swimlanes
+* Rank sorting for parent task swimlanes
+* Koren translation (김기원)
+* Fixed XSS Vulnerability (Felix Schäfer)
+* Fixed Issue status count starting with empty not updated (Felix Schäfer)
+* Fixed bug with "Is not" status filter
+
+== 2014-05-05 v1.3.1
+
+* Status Sub-Columns with colon delimiter
+* Fixes for SQL Server
+
+== 2014-04-24 v1.3.0
+
+* Swim lanes
+* Weekends for burnup and burndown charts
+* Burnup charts cleanup
+* New board card fields setting
+
+== 2014-04-18 v1.2.0
+
+* Colors
+* Version planning
+* Touch devices support
+* Fullscreen mode
+* Spanish translation by (Leandro Russo)
+* Created/Closed chart renamed to Issues burnup
+* Work burnup chart
+* First period effort for budrndown charst
+* Fixes in cumulative flow chart
+* Fixes with load_more duplications
+
+== 2014-04-09 v1.1.2
+
+* Compatibility fixes
+
+== 2014-04-09 v1.1.1
+
+* New scale divisions for charts
+* Fixes for work burndown with subtasks
+* Fixed Gantt and Caledar issues
+
+== 2014-04-07 v1.1.0
+
+* Save and manage agile boards
+* Select issue card fields
+* Permissions for agile boards
+* Default issue card columns setting
+* Issue card context menu
+* Project column
+* Issue done ratio and status flows
+* Charts (Work burndown chart, Issues burndown chart, Cumulative flow chart, Trackers cumulative flow chart, Velocity chart, Lead time chart, Average lead time chart, Created/Closed chart)
+* Fixed bug with load more filters
+
+== 2014-03-18 v1.0.1
+
+* Sorting issues inside status
+* Issues lazy loading
+
+== 2014-02-28 v1.0.0
+
+* Initial release
+* Draggable issues
+* Draggable assignees
+* Issue description and attached image preview
diff --git a/plugins/redmine_agile/doc/COPYING b/plugins/redmine_agile/doc/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..63e41a44cffe44662260b7b902e4b065b201b515
--- /dev/null
+++ b/plugins/redmine_agile/doc/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
\ No newline at end of file
diff --git a/plugins/redmine_agile/doc/LICENSE b/plugins/redmine_agile/doc/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..e2e50b5e93e1f520b12a10aba06bcd201e0b07c9
--- /dev/null
+++ b/plugins/redmine_agile/doc/LICENSE
@@ -0,0 +1,26 @@
+LICENSING
+
+RedmineUP Licencing
+
+This End User License Agreement is a binding legal agreement between you and RedmineUP. Purchase, installation or use of RedmineUP Extensions provided on redmineup.com signifies that you have read, understood, and agreed to be bound by the terms outlined below.
+
+RedmineUP GPL Licencing
+
+All Redmine Extensions produced by RedmineUP are released under the GNU General Public License, version 2 (http://www.gnu.org/licenses/gpl-2.0.html). Specifically, the Ruby code portions are distributed under the GPL license. If not otherwise stated, all images, manuals, cascading style sheets, and included JavaScript are NOT GPL, and are released under the RedmineUP Proprietary Use License v1.0 (See below) unless specifically authorized by RedmineUP. Elements of the extensions released under this proprietary license may not be redistributed or repackaged for use other than those allowed by the Terms of Service.
+
+RedmineUP Proprietary Use License (v1.0)
+
+The RedmineUP Proprietary Use License covers any images, cascading stylesheets, manuals and JavaScript files in any extensions produced and/or distributed by redmineup.com. These files are copyrighted by redmineup.com (RedmineUP) and cannot be redistributed in any form without prior consent from redmineup.com (RedmineUP)
+
+Usage Terms
+
+You are allowed to use the Extensions on one or many "production" domains, depending on the type of your license
+You are allowed to make any changes to the code, however modified code will not be supported by us.
+
+Modification Of Extensions Produced By RedmineUP.
+
+You are authorized to make any modification(s) to RedmineUP extension Ruby code. However, if you change any Ruby code and it breaks functionality, support may not be available to you.
+
+In accordance with the RedmineUP Proprietary Use License v1.0, you may not release any proprietary files (modified or otherwise) under the GPL license. The terms of this license and the GPL v2 prohibit the removal of the copyright information from any file.
+
+Please contact us if you have any requirements that are not covered by these terms.
\ No newline at end of file
diff --git a/plugins/redmine_agile/init.rb b/plugins/redmine_agile/init.rb
new file mode 100755
index 0000000000000000000000000000000000000000..53e8ad8a4fac18f8e85754c0c18bc0d3815cd2a2
--- /dev/null
+++ b/plugins/redmine_agile/init.rb
@@ -0,0 +1,60 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+requires_redmine_crm :version_or_higher => '0.0.32' rescue raise "\n\033[31mRedmine requires newer redmine_crm gem version.\nPlease update with 'bundle update redmine_crm'.\033[0m"
+
+require 'redmine'
+
+AGILE_VERSION_NUMBER = '1.4.7'
+AGILE_VERSION_TYPE = 'PRO version'
+
+Redmine::Plugin.register :redmine_agile do
+  name "Redmine Agile plugin (#{AGILE_VERSION_TYPE})"
+  author 'RedmineUP'
+  description 'Scrum and Agile project management plugin for redmine'
+  version AGILE_VERSION_NUMBER
+  url 'http://redmineup.com/pages/plugins/agile'
+  author_url 'mailto:support@redmineup.com'
+
+  requires_redmine :version_or_higher => '2.6'
+
+  settings :default => { 'default_columns' => %w(tracker assigned_to) },
+           :partial => 'settings/agile/general'
+
+  menu :application_menu, :agile,
+       { :controller => 'agile_boards', :action => 'index' },
+       :caption => :label_agile,
+       :if => Proc.new { User.current.allowed_to?(:view_agile_queries, nil, :global => true) }
+  menu :project_menu, :agile, {:controller => 'agile_boards', :action => 'index' },
+                              :caption => :label_agile,
+                              :after => :gantt,
+                              :param => :project_id
+
+  menu :admin_menu, :agile, {:controller => 'settings', :action => 'plugin', :id => "redmine_agile"}, :caption => :label_agile, :html => {:class => 'icon'}
+
+  project_module :agile do
+    permission :manage_public_agile_queries, {:agile_queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
+    permission :manage_agile_verions, {:agile_versions => [:index, :update]}
+    permission :add_agile_queries, {:agile_queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
+    permission :view_agile_queries, {:agile_boards => [:index, :create_issue], :agile_queries => :index}, :read => true
+    permission :view_agile_charts, {:agile_charts => [:show, :render_chart, :select_version_chart]}, :read => true
+  end
+end
+
+require 'redmine_agile'
diff --git a/plugins/redmine_agile/lib/acts_as_colored/init.rb b/plugins/redmine_agile/lib/acts_as_colored/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3ff28d4c93cdf9cb8e8698be155ae145a7490f0e
--- /dev/null
+++ b/plugins/redmine_agile/lib/acts_as_colored/init.rb
@@ -0,0 +1,21 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.dirname(__FILE__) + '/lib/acts_as_colored'
+ActiveRecord::Base.send(:include, RedmineAgile::Acts::Colored)
diff --git a/plugins/redmine_agile/lib/acts_as_colored/lib/acts_as_colored.rb b/plugins/redmine_agile/lib/acts_as_colored/lib/acts_as_colored.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8701bbd5ad418c9eafe60e72fe8437e292d7b34
--- /dev/null
+++ b/plugins/redmine_agile/lib/acts_as_colored/lib/acts_as_colored.rb
@@ -0,0 +1,69 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Acts
+    module Colored
+      def self.included(base)
+        base.extend ClassMethods
+      end
+
+      module ClassMethods
+        def acts_as_colored(_options = {})
+          return if included_modules.include?(RedmineAgile::Acts::Colored::InstanceMethods)
+          send :include, RedmineAgile::Acts::Colored::InstanceMethods
+
+          class_eval do
+            has_one :agile_color, :as => :container, :dependent => :destroy
+            delegate :color, :to => :agile_color, :allow_nil => true
+
+            accepts_nested_attributes_for :agile_color, :reject_if => :reject_color, :allow_destroy => true
+
+            alias_method :agile_color_without_default, :agile_color
+            alias_method :agile_color, :agile_color_with_default
+          end
+        end
+      end
+
+      module InstanceMethods
+        def self.included(base)
+          base.extend ClassMethods
+        end
+
+        def reject_color(attributes)
+          exists = attributes['id'].present?
+          empty = attributes[:color].blank?
+          attributes[:_destroy] = 1 if exists && empty
+          !exists && empty
+        end
+
+        def color=(value)
+          agile_color.color = value
+        end
+
+        def agile_color_with_default
+          agile_color_without_default || build_agile_color
+        end
+
+        module ClassMethods
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile.rb b/plugins/redmine_agile/lib/redmine_agile.rb
new file mode 100755
index 0000000000000000000000000000000000000000..300f6a06e64cc469341815c46f9c306128a60462
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile.rb
@@ -0,0 +1,161 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require 'redmine_agile/patches/compatibility/application_controller_patch' if Rails::VERSION::MAJOR < 4
+require 'acts_as_colored/init'
+
+require 'redmine_agile/hooks/views_layouts_hook'
+require 'redmine_agile/hooks/views_issues_hook'
+require 'redmine_agile/hooks/views_versions_hook'
+require 'redmine_agile/hooks/controller_issue_hook'
+require 'redmine_agile/hooks/helper_issues_hook'
+require 'redmine_agile/patches/issue_patch'
+
+require 'redmine_agile/patches/compatibility_patch'
+
+require 'redmine_agile/helpers/agile_helper'
+
+require 'redmine_agile/charts/agile_chart'
+require 'redmine_agile/charts/burndown_chart'
+require 'redmine_agile/charts/work_burndown_chart'
+require 'redmine_agile/charts/velocity_chart'
+require 'redmine_agile/charts/cumulative_flow_chart'
+require 'redmine_agile/charts/trackers_cumulative_flow_chart'
+require 'redmine_agile/charts/burnup_chart'
+require 'redmine_agile/charts/work_burnup_chart'
+require 'redmine_agile/charts/lead_time_chart'
+require 'redmine_agile/charts/average_lead_time_chart'
+
+require 'redmine_agile/patches/issue_priority_patch'
+require 'redmine_agile/patches/issue_query_patch'
+require 'redmine_agile/patches/tracker_patch'
+require 'redmine_agile/patches/project_patch'
+require 'redmine_agile/hooks/views_context_menus_hook'
+require 'redmine_agile/hooks/views_projects_form_hook'
+
+require 'redmine_agile/utils/header_tree'
+
+require 'redmine_agile/patches/user_patch'
+require 'redmine_agile/hooks/views_users_form_hook'
+require 'redmine_agile/patches/queries_controller_patch' if Redmine::VERSION.to_s >= '3.4'
+
+module RedmineAgile
+
+  ISSUES_PER_COLUMN = 10
+  TIME_REPORTS_ITEMS = 1000
+  BOARD_ITEMS = 500
+  ESTIMATE_UNITS = ['hours', 'story_points']
+  COLOR_BASE = ['issue', 'tracker', 'priority', 'spent_time', 'user', 'project']
+
+  class << self
+    def time_reports_items_limit
+      by_settigns = Setting.plugin_redmine_agile['time_reports_items_limit'].to_i
+      by_settigns > 0 ? by_settigns : TIME_REPORTS_ITEMS
+    end
+
+    def board_items_limit
+      by_settigns = Setting.plugin_redmine_agile['board_items_limit'].to_i
+      by_settigns > 0 ? by_settigns : BOARD_ITEMS
+    end
+
+    def issues_per_column
+      by_settigns = Setting.plugin_redmine_agile['issues_per_column'].to_i
+      by_settigns > 0 ? by_settigns : ISSUES_PER_COLUMN
+    end
+
+    def default_columns
+      Setting.plugin_redmine_agile['default_columns'].to_a
+    end
+
+    def default_chart
+      Setting.plugin_redmine_agile['default_chart'] || 'issues_burndown'
+    end
+
+    def estimate_units
+      Setting.plugin_redmine_agile['estimate_units'] || 'hours'
+    end
+
+    def use_story_points?
+      estimate_units == 'story_points'
+    end
+
+    def trackers_for_sp
+      Setting.plugin_redmine_agile['trackers_for_sp']
+    end
+
+    def use_story_points_for?(tracker)
+      return true if trackers_for_sp.blank?
+      tracker = tracker.is_a?(Tracker) ? tracker.id.to_s : tracker
+      trackers_for_sp == tracker
+    end
+
+    def use_colors?
+      COLOR_BASE.include?(color_base)
+                end
+
+    def color_base
+      Setting.plugin_redmine_agile['color_on'] || 'none'
+                end
+
+    def minimize_closed?
+      Setting.plugin_redmine_agile['minimize_closed'].to_i > 0
+    end
+
+    def exclude_weekends?
+      Setting.plugin_redmine_agile['exclude_weekends'].to_i > 0
+    end
+
+    def auto_assign_on_move?
+      Setting.plugin_redmine_agile['auto_assign_on_move'].to_i > 0
+    end
+    def color_prefix
+      'bk'
+    end
+
+    COLOR_BASE.each do |cb|
+      define_method :"#{cb}_colors?" do
+        color_base == cb
+      end
+    end
+
+    def status_colors?
+      Setting.plugin_redmine_agile['status_colors'].to_i > 0
+                end
+
+    def hide_closed_issues_data?
+      Setting.plugin_redmine_agile['hide_closed_issues_data'].to_i > 0
+    end
+
+    def use_checklist?
+      @@chcklist_plugin_installed ||= (Redmine::Plugin.installed?(:redmine_checklists))
+    end
+
+    def allow_create_card?
+      Setting.plugin_redmine_agile['allow_create_card'].to_i > 0
+          end
+
+    def allow_inline_comments?
+      Setting.plugin_redmine_agile['allow_inline_comments'].to_i > 0
+    end
+    def sp_values
+      Setting.plugin_redmine_agile['sp_values'].to_s.split(',').map{|x| x.strip.to_i}.uniq.delete_if{|x| x == 0}
+    end
+  end
+
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/agile_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/agile_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a8df261c6972268c2b50fe06b69019b5afd9fa64
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/agile_chart.rb
@@ -0,0 +1,252 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class AgileChart
+    include Redmine::I18n
+    include Redmine::Utils::DateCalculation
+
+    attr_reader :line_colors
+
+    def initialize(data_scope, options={})
+      @data_scope = data_scope
+      @data_from ||= options[:data_from]
+      @data_to ||= options[:data_to]
+      @period_count, @scale_division = chart_periods
+      @step_x_labels = @period_count > 18 ? @period_count / 12 + 1 : 1
+      @fields = chart_fields_by_period
+      @weekend_periods = weekend_periods
+      @estimated_unit = options[:estimated_unit] || 'hours'
+      @line_colors = {}
+    end
+
+    def data
+      { :title => '', :y_title => '', :labels => [], :datasets => [] }
+    end
+
+    def self.data(data_scope, options = {})
+      new(data_scope, options).data
+    end
+
+    protected
+
+    def current_date_period
+      date_period = (@date_to <= Date.today ? @period_count : (@period_count - (@date_to - Date.today).to_i / @scale_division - 1) + 1).round
+      @current_date_period ||= date_period > 0 ? date_period : 0
+    end
+
+    def due_date_period
+      due_date = (@due_date && @due_date > @date_from) ? @due_date : @date_from
+      @due_date_period ||= (@due_date ? @period_count - (@date_to - due_date).to_i / @scale_division - 1 : @period_count - 1) + 1
+    end
+
+    def date_short_period?
+      (@date_to - @date_from).to_i <= 31
+    end
+
+    def date_effort(issues, effort_date)
+      cumulative_left = 0
+      total_left = 0
+      total_done = 0
+      issues.each do |issue|
+        done_ratio_details = issue.journals.map(&:details).flatten.select { |detail| 'done_ratio' == detail.prop_key }
+        details_today_or_earlier = done_ratio_details.select { |a| a.journal.created_on.localtime.to_date <= effort_date }
+
+        last_done_ratio_change = details_today_or_earlier.sort_by { |a| a.journal.created_on }.last
+        ratio = if issue.closed? && issue.closed_on.localtime.to_date <= effort_date
+                  100
+                elsif last_done_ratio_change
+                  last_done_ratio_change.value
+                elsif (done_ratio_details.size > 0) || (issue.closed? && issue.closed_on > effort_date)
+                  0
+                else
+                  issue.done_ratio.to_i
+                end
+
+        if @estimated_unit == 'hours'
+          cumulative_left += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
+          total_left += (issue.estimated_hours.to_f * (100 - ratio.to_f) / 100.0)
+          total_done += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
+        else
+          cumulative_left += (issue.story_points.to_f * ratio.to_f / 100.0)
+          total_left += (issue.story_points.to_f * (100 - ratio.to_f) / 100.0)
+          total_done += (issue.story_points.to_f * ratio.to_f / 100.0)
+        end
+      end
+      [total_left, cumulative_left, total_done]
+    end
+
+    def use_subissue_done_ratio
+      !Setting.respond_to?(:parent_issue_done_ratio) || Setting.parent_issue_done_ratio == 'derived' || Setting.parent_issue_done_ratio.nil?
+    end
+
+    private
+
+    def scope_by_created_date
+      @data_scope.
+        where("#{Issue.table_name}.created_on >= ?", @date_from).
+        where("#{Issue.table_name}.created_on < ?", @date_to.to_date + 1).
+        where("#{Issue.table_name}.created_on IS NOT NULL").
+        group("#{Issue.table_name}.created_on").
+        count
+    end
+
+    def scope_by_closed_date
+      @data_scope.
+        open(false).
+        where("#{Issue.table_name}.closed_on >= ?", @date_from).
+        where("#{Issue.table_name}.closed_on < ?", @date_to.to_date + 1).
+        where("#{Issue.table_name}.closed_on IS NOT NULL").
+        group("#{Issue.table_name}.closed_on").
+        count
+    end
+
+    # options
+    # color    - Line color in RGB format (e.g '255,255,255') (random)
+    # fill     - Fille background under line (false)
+    # dashed   - Draw dached line (solid)
+    # nopoints - Doesn't show points on line (false)
+
+    def dataset(dataset_data, label, options = {})
+      color = options[:color] || [rand(255), rand(255), rand(255)].join(',')
+      dataset_color = "rgba(#{color}, 1)"
+      {
+        :type => (options[:type] || 'line'),
+        :data => dataset_data,
+        :label => label,
+        :fill => (options[:fill] || false),
+        :backgroundColor => "rgba(#{color}, 0.2)",
+        :borderColor => dataset_color,
+        :borderDash => (options[:dashed] ? [5, 5] : []),
+        :borderWidth => (options[:dashed] ? 1.5 : 2),
+        :pointRadius => (options[:nopoints] ? 0 : 3),
+        :pointBackgroundColor => dataset_color
+      }
+    end
+
+    def chart_periods
+      raise "Dates can't be blank" if [@date_to, @date_from].any?(&:blank?)
+      period_count = (@date_to.to_date + 1 - @date_from.to_date).to_i
+      scale_division = period_count > 31 ? period_count / 31.0 : 1
+
+      [(period_count / scale_division).round, scale_division]
+    end
+
+    def issues_count_by_period(issues_scope)
+      data = [0] * @period_count
+      issues_scope.each do |c|
+        next if c.first.localtime.to_date > @date_to.to_date
+        period_num = ((@date_to.to_date - c.first.localtime.to_date).to_i / @scale_division).to_i
+        data[period_num] += c.last unless data[period_num].blank?
+      end
+      data.reverse
+    end
+
+    def issues_avg_count_by_period(issues_scope)
+      count_by_date = {}
+      issues_scope.each {|x, y| count_by_date[x.localtime.to_date] = count_by_date[x.localtime.to_date].to_i + y }
+      data = [0] * @period_count
+      count_by_date.each do |x, y|
+        next if x.to_date > @date_to.to_date
+        period_num = ((@date_to.to_date - x.to_date).to_i / @scale_division).to_i
+        if data[period_num]
+          data[period_num] = y unless data[period_num].to_i > 0
+          data[period_num] = (data[period_num] + y) / 2.0
+        end
+      end
+      data.reverse
+    end
+
+    def chart_fields_by_period
+      chart_dates_by_period.map do |d|
+        if @scale_division >= 365
+          d.year
+        elsif @scale_division >= 13
+          month_abbr_name(d.at_beginning_of_week.to_time.month) + ' ' + d.at_beginning_of_week.to_time.year.to_s
+        elsif @scale_division >= 7
+          d.at_beginning_of_week.to_time.day.to_s + ' ' + month_name(d.at_beginning_of_week.to_time.month)
+        else
+          d.to_time.day.to_s + ' ' + month_name(d.to_time.month)
+        end
+      end
+    end
+
+    def weekend_periods
+      periods = []
+      @period_count.times do |m|
+        period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
+        periods << @period_count - m - 1 if non_working_week_days.include?(period_date.cwday)
+      end
+      periods.compact
+    end
+
+    def chart_data_pairs(chart_data)
+      chart_data.inject([]) { |accum, value| accum << value }
+      data_pairs = []
+      for i in 0..chart_data.count - 1
+        data_pairs << [chart_dates_by_period[i], chart_data[i]]
+      end
+      data_pairs
+    end
+
+    def chart_dates_by_period
+      @chart_dates_by_period ||= @period_count.times.inject([]) do |accum, m|
+        period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
+        accum << if m == 0 || m == @period_count - 1
+                   period_date.to_date
+                 elsif @scale_division >= 13
+                   period_date.at_beginning_of_week.to_date
+                 elsif @scale_division >= 7
+                   period_date.at_beginning_of_week.to_date
+                 else
+                   period_date.to_date
+                 end
+      end.reverse
+    end
+
+    def month_abbr_name(month)
+      l('date.abbr_month_names')[month]
+    end
+
+    def trendline(y_values)
+      size = y_values.size
+      x_values = (1..size).to_a
+      sum_x = 0
+      sum_y = 0
+      sum_xx = 0
+      sum_xy = 0
+      y_values.zip(x_values).each do |y, x|
+        sum_xy += x * y
+        sum_xx += x * x
+        sum_x  += x
+        sum_y  += y
+      end
+
+      slope = 1.0 * ((size * sum_xy) - (sum_x * sum_y)) / ((size * sum_xx) - (sum_x * sum_x))
+      intercept = 1.0 * (sum_y - (slope * sum_x)) / size
+
+      line_values = x_values.map { |x| predict(x, slope, intercept) }
+      line_values.select { |val| val >= 0 }
+    end
+
+    def predict(x, slope, intercept)
+      slope * x + intercept
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/average_lead_time_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/average_lead_time_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a49e1605f6e82d133ec83ae39c3ede2a33552377
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/average_lead_time_chart.rb
@@ -0,0 +1,67 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class AverageLeadTimeChart < LeadTimeChart
+    def initialize(data_scope, options = {})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = options[:date_to] || Date.today
+      @average_lead_time = !!options[:average_lead_time]
+      super data_scope, options
+      @line_colors = { :closed => '247,175,125' }
+    end
+
+    def data
+      chart_data = average_lead_time_data
+      datasets = []
+      datasets << dataset(chart_data, l(:field_closed_on), :color => line_colors[:closed]) if chart_data.any?
+
+      {
+        :title    => l(:label_agile_charts_lead_time),
+        :y_title  => l(:label_agile_charts_number_of_days),
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+
+    private
+
+    def average_lead_time_data
+      lead_time_by_date = closed_issues.map { |c| { :closed_on => c.closed_on, :lead_time => (c.closed_on.to_time - c.created_on.to_time).to_f / (60 * 60 * 24) } }
+      lead_time_by_period = [0] * @period_count
+      lead_time_by_date.each do |c|
+        next if c[:closed_on].to_date > @date_to.to_date
+        period_num = (@date_to.to_date - c[:closed_on].to_date).to_i / @scale_division
+        if lead_time_by_period[period_num]
+          lead_time_by_period[period_num] = c[:lead_time] unless lead_time_by_period[period_num].to_i > 0
+          lead_time_by_period[period_num] = ((lead_time_by_period[period_num].to_f + c[:lead_time]).to_f / 2).round(2)
+        end
+      end
+      lead_time_by_period.reverse!
+
+      prev_lead_time = lead_time_by_period[0]
+      lead_time_by_period.each_with_index do |c, index|
+        lead_time_by_period[index] = c == 0 ? prev_lead_time : ((c + prev_lead_time).to_f / 2).round(2)
+        prev_lead_time = lead_time_by_period[index]
+      end
+
+      lead_time_by_period
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/burndown_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/burndown_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5d23a67b44d189d8496a6e334efdd35fd4b6b9f3
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/burndown_chart.rb
@@ -0,0 +1,99 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class BurndownChart < AgileChart
+    attr_accessor :burndown_data, :cumulative_burndown_data
+
+    def initialize(data_scope, options={})
+      @date_from = options[:date_from] && options[:date_from].to_date ||
+                   [data_scope.minimum("#{Issue.table_name}.created_on"),
+                    data_scope.minimum("#{Issue.table_name}.start_date")].compact.map(&:to_date).min
+      @date_to = options[:date_to] && options[:date_to].to_date ||
+                 [options[:due_date],
+                  data_scope.maximum("#{Issue.table_name}.updated_on")].compact.map(&:to_date).max
+      @due_date = options[:due_date].to_date if options[:due_date]
+      @show_ideal_effort = options[:date_from] && options[:date_to]
+
+      super data_scope, options
+
+      @fields = [''] + @fields
+      @y_title = l(:label_agile_charts_number_of_issues)
+      @graph_title = l(:label_agile_charts_issues_burndown)
+      @line_colors = { :work => '80,122,170', :ideal => '102,102,102', :total => '80,122,170' }
+    end
+
+    def data
+      return false unless calculate_burndown_data.any?
+
+      datasets = [dataset(@burndown_data, l(:label_agile_actual_work_remaining), :fill => true, :color => line_colors[:work])]
+      if @show_ideal_effort
+        datasets << dataset(ideal_effort(@cumulative_burndown_data.first), l(:label_agile_ideal_work_remaining),
+                            :color => line_colors[:ideal], :dashed => true, :nopoints => true)
+      end
+      if @show_ideal_effort && (@cumulative_burndown_data != @burndown_data)
+        datasets << dataset(@cumulative_burndown_data, l(:label_agile_total_work_remaining),
+                            :color => line_colors[:total], :dashed => true)
+      end
+
+      {
+        :title    => @graph_title,
+        :y_title  => @y_title,
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+
+    protected
+
+    def ideal_effort(start_remaining)
+      data = [0] * (due_date_period - 1)
+      active_periods = (RedmineAgile.exclude_weekends? && date_short_period?) ? due_date_period - @weekend_periods.select { |p| p < due_date_period }.count : due_date_period
+      avg_remaining_velocity = start_remaining.to_f / active_periods.to_f
+      sum = start_remaining.to_f
+      data[0] = sum
+      (1..due_date_period - 1).each do |i|
+        sum -= avg_remaining_velocity unless (RedmineAgile.exclude_weekends? && date_short_period?) && @weekend_periods.include?(i - 1)
+        data[i] = (sum * 100).round / 100.0
+      end
+      data[due_date_period] = 0
+      data
+    end
+
+    def calculate_burndown_data
+      created_by_period = issues_count_by_period(scope_by_created_date)
+      closed_by_period = issues_count_by_period(scope_by_closed_date)
+
+      total_issues = @data_scope.count
+      total_issues_before = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).count
+      total_closed_before = @data_scope.open(false).where("#{Issue.table_name}.closed_on < ?", @date_from).count
+
+      sum = total_issues_before
+      cumulative_created_by_period = created_by_period.first(current_date_period).map { |x| sum += x }
+      sum = total_closed_before
+      cumulative_closed_by_period = closed_by_period.first(current_date_period).map { |x| sum += x }
+
+      burndown_by_period = [0] * (current_date_period)
+      cumulative_created_by_period.each_with_index { |e, i| burndown_by_period[i] = e - cumulative_closed_by_period[i] }
+      first_day_open_issues = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from + 1).count - total_closed_before
+      @cumulative_burndown_data = [total_issues - total_closed_before] + cumulative_closed_by_period.map { |c| total_issues - c }
+      @burndown_data = [first_day_open_issues] + burndown_by_period
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/burnup_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/burnup_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31a03b105a565c06dea6269afc594448deff9047
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/burnup_chart.rb
@@ -0,0 +1,81 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class BurnupChart < AgileChart
+    def initialize(data_scope, options = {})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = (options[:date_to] || Date.today).to_date
+      @due_date = options[:due_date].to_date if options[:due_date]
+
+      super data_scope, options
+
+      @fields = [''] + @fields
+      @y_title = l(:label_agile_charts_number_of_issues)
+      @graph_title = l(:label_agile_charts_burnup)
+      @line_colors = { :created => '102,102,102', :closed => '80,122,170', :ideal => '102,102,102' }
+    end
+
+    def data
+      return false unless calculate_data.any?
+
+      datasets = [
+        dataset(@cumulative_data, l(:field_created_on),:fill => true, :color => line_colors[:created]),
+        dataset(@data, l(:field_closed_on), :fill => true, :color => line_colors[:closed]),
+        dataset(ideal_effort(@data.first, @cumulative_data.last), l(:label_agile_ideal_work_remaining), :color => line_colors[:ideal], :dashed => true, :nopoints => true)
+      ]
+
+      {
+        :title    => @graph_title,
+        :y_title  => @y_title,
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+
+    protected
+
+    def ideal_effort(start_data, end_data)
+      data = [0] * (due_date_period - 1)
+      active_periods = (RedmineAgile.exclude_weekends? && date_short_period?) ? due_date_period - @weekend_periods.select { |p| p < due_date_period }.count : due_date_period
+      avg_remaining_velocity = (end_data - start_data).to_f / active_periods.to_f
+      sum = start_data.to_f
+      data[0] = sum
+      (1..due_date_period - 1).each do |i|
+        sum += avg_remaining_velocity unless (RedmineAgile.exclude_weekends? && date_short_period?) && @weekend_periods.include?(i - 1)
+        data[i] = (sum * 100).round / 100.0
+      end
+      data[due_date_period] = end_data
+      data
+    end
+
+    def calculate_data
+      created_by_period = issues_count_by_period(scope_by_created_date)
+      closed_by_period = issues_count_by_period(scope_by_closed_date)
+
+      total_issues = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).count
+      total_closed = @data_scope.open(false).where("#{Issue.table_name}.closed_on < ?", @date_from).count
+
+      sum = total_issues
+      @cumulative_data = [total_issues] + created_by_period.first(current_date_period).map { |x| sum += x }
+      sum = total_closed
+      @data = [total_closed] + closed_by_period.first(current_date_period).map { |x| sum += x }
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/cumulative_flow_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/cumulative_flow_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3029f46e11b1cb45be153d2cd18fc587dfd3797a
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/cumulative_flow_chart.rb
@@ -0,0 +1,72 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class CumulativeFlowChart < AgileChart
+    def initialize(data_scope, options = {})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = options[:date_to] || Date.today
+      super data_scope, options
+      @line_colors = { 0 => '255,204,0', 1 => '0,153,0', 2 => '80,122,170', 3 => '102,102,102', 4 => '154,167,208', 5 => '224,112,235',
+                       6 => '235,17,142', 7 => '167,40,6', 8 => '108,97,58', 9 => '33,147,155', 10 => '43,237,59' }
+    end
+
+    def data
+      datasets = []
+      all_issues = @data_scope.eager_load(:journals => { :details => :journal })
+      data = chart_dates_by_period.map do |date|
+        issues = all_issues.select { |issue| issue.created_on.localtime.to_date <= date }
+        issues.inject({}) do |accum, issue|
+          status_details = issue.journals.map(&:details).flatten.select { |detail| 'status_id' == detail.prop_key }.sort_by { |a| a.journal.created_on }
+          details_today_or_earlier = status_details.select { |a| a.journal.created_on.to_date <= date }
+          last_status_change = details_today_or_earlier.last
+
+          status = if last_status_change
+                     last_status_change.value.to_i
+                   elsif status_details.size > 0
+                     status_details.first.old_value.to_i
+                   else
+                     issue.status_id
+                   end
+
+          accum[status] = accum[status].to_i + 1
+          accum
+        end
+      end
+
+      IssueStatus.where(:id => data.map(&:keys).flatten.uniq).sorted.reverse.each_with_index do |status, index|
+        datasets << dataset(data.map { |d| d[status.id].to_i }, status.name, :color => line_colors[index], :fill => true, :nopoints => true) unless data.empty?
+      end
+
+      {
+        :title    => l(:label_agile_charts_cumulative_flow),
+        :y_title  => l(:label_agile_charts_number_of_issues),
+        :labels   => @fields,
+        :stacked  => true,
+        :datasets => datasets
+      }
+    end
+
+    private
+
+    def available_statuses
+      @available_statuses ||= IssueStatus.find(@data_scope.group("#{Issue.table_name}.status_id").count.keys).map
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/lead_time_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/lead_time_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dc7bc7212cf87aec797295156606f6e3507e4cf0
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/lead_time_chart.rb
@@ -0,0 +1,77 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class LeadTimeChart < AgileChart
+    def initialize(data_scope, options = {})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = options[:date_to] || Date.today
+      @average_lead_time = !!options[:average_lead_time]
+      super data_scope, options
+      @line_colors = { :closed => '102,102,102' }
+    end
+
+    def data
+      chart_data = lead_time_data
+      datasets = []
+      if chart_data.any?
+        datasets << dataset(chart_data, l(:field_closed_on), :type => 'bar', :fill => true, :color => line_colors[:closed])
+        datasets << dataset(trendline(chart_data), l(:field_closed_on_trendline), :nopoints => true, :dashed => true, :color => line_colors[:closed])
+      end
+      {
+        :title    => l(:label_agile_charts_lead_time),
+        :y_title  => l(:label_agile_charts_number_of_days),
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+
+    private
+
+    def lead_time_data
+      lead_time_by_date = closed_issues.map { |c| { :closed_on => c.closed_on.localtime, :lead_time => (c.closed_on.to_time.localtime - c.created_on.localtime.to_time).to_f / (60 * 60 * 24) } }
+      lead_time_arr_by_period = {}
+      lead_time_by_date.each do |c|
+        next if c[:closed_on].to_date > @date_to.to_date
+        period_num = ((@date_to.to_date - c[:closed_on].localtime.to_date).to_i / @scale_division).to_i
+        lead_time_arr_by_period[period_num] = [] if lead_time_arr_by_period[period_num].blank?
+        lead_time_arr_by_period[period_num] << c[:lead_time]
+      end
+
+      lead_time_by_period = [0] * @period_count
+      (0..@period_count - 1).each do |period_num|
+        next if lead_time_arr_by_period[period_num].blank?
+        arr = lead_time_arr_by_period[period_num]
+        len = arr.length
+        half_len = len / 2
+        sorted = arr.sort
+        median = len % 2 == 1 ? sorted[half_len] : (sorted[half_len - 1] + sorted[half_len]).to_f / 2
+        lead_time_by_period[period_num] = median.round(2)
+      end
+      lead_time_by_period.reverse!
+    end
+
+    def closed_issues
+      @closed_issues ||= @data_scope.open(false).where("#{Issue.table_name}.closed_on IS NOT NULL").
+                         where("#{Issue.table_name}.closed_on >= ?", @date_from).
+                         where("#{Issue.table_name}.closed_on < ?", @date_to + 1).
+                         where("#{Issue.table_name}.created_on IS NOT NULL")
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/trackers_cumulative_flow_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/trackers_cumulative_flow_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2b26388aabe91f6147889441a29455b12b615c67
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/trackers_cumulative_flow_chart.rb
@@ -0,0 +1,56 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class TrackersCumulativeFlowChart < AgileChart
+    def initialize(data_scope, options={})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = options[:date_to] || Date.today
+      super data_scope, options
+      @line_colors = { 0 => '0,153,0', 1 => '80,122,170', 2 => '102,102,102', 3 => '255,204,0', 4 => '154,167,208', 5 => '224,112,235',
+                       6 => '235,17,142', 7 => '167,40,6', 8 => '108,97,58', 9 => '33,147,155', 10 => '43,237,59' }
+    end
+
+    def data
+      datasets = []
+      Tracker.where(:id => @data_scope.group("#{Issue.table_name}.tracker_id").count.keys).sorted.reverse.each_with_index do |tracker, index|
+        created_by_date = @data_scope.where(:tracker_id => tracker.id).
+                          where("#{Issue.table_name}.created_on >= ?", @date_from).
+                          where("#{Issue.table_name}.created_on <= ?", @date_to).
+                          where("#{Issue.table_name}.created_on IS NOT NULL").
+                          group("#{Issue.table_name}.created_on").
+                          count
+        created_by_period = issues_count_by_period(created_by_date)
+        total_issues = @data_scope.where(:tracker_id => tracker.id).
+                       where("#{Issue.table_name}.created_on < ?", @date_from).count
+        cumulative_created_by_period = created_by_period.map { |x| total_issues += x }
+
+        datasets << dataset(cumulative_created_by_period, tracker.name, :color => line_colors[index], :fill => true, :nopoints => true) if created_by_period.any?
+      end
+
+      {
+        :title    => l(:label_agile_charts_cumulative_flow),
+        :y_title  => l(:label_agile_charts_number_of_issues),
+        :stacked  => true,
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/velocity_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/velocity_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f21688b6665b43a84231d020814475509608fcab
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/velocity_chart.rb
@@ -0,0 +1,60 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class VelocityChart < AgileChart
+    def initialize(data_scope, options = {})
+      @date_from = (options[:date_from] || data_scope.minimum("#{Issue.table_name}.created_on")).to_date
+      @date_to = options[:date_to] || Date.today
+      super data_scope, options
+      @line_colors = { :closed => '102,102,102', :created => '80,122,170' }
+    end
+
+    def data
+      created_by_period = issues_avg_count_by_period(scope_by_created_date)
+      closed_by_period = issues_avg_count_by_period(scope_by_closed_date)
+
+      if @scale_division > 1
+        y_title = l(:label_agile_charts_avarate_number_of_issues)
+        graph_title = l(:label_agile_charts_average_velocity)
+      else
+        y_title = l(:label_agile_charts_number_of_issues)
+        graph_title = l(:label_agile_charts_issues_velocity)
+      end
+
+      datasets = []
+      if closed_by_period.any?
+        datasets << dataset(closed_by_period, l(:field_closed_on), :type => 'bar', :fill => true, :color => line_colors[:closed])
+        datasets << dataset(trendline(closed_by_period), l(:field_closed_on_trendline), :nopoints => true, :dashed => true, :color => line_colors[:closed])
+      end
+
+      if created_by_period.any?
+        datasets << dataset(created_by_period, l(:field_created_on), :type => 'bar', :fill => true, :color => line_colors[:created])
+        datasets << dataset(trendline(created_by_period), l(:field_created_on_trendline), :nopoints => true, :dashed => true, :color => line_colors[:created])
+      end
+
+      {
+        :title    => graph_title,
+        :y_title  => y_title,
+        :labels   => @fields,
+        :datasets => datasets
+      }
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/work_burndown_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/work_burndown_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27408d4c87695651cbbcf03faf0f2f2eba957cbc
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/work_burndown_chart.rb
@@ -0,0 +1,68 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class WorkBurndownChart < BurndownChart
+    def initialize(data_scope, options = {})
+      super data_scope, options
+      if @estimated_unit == 'hours'
+        @y_title = l(:label_agile_charts_number_of_hours)
+        @graph_title = l(:label_agile_charts_work_burndown_hours)
+      else
+        @y_title = l(:label_agile_charts_number_of_story_points)
+        @graph_title = l(:label_agile_charts_work_burndown_sp)
+      end
+
+      @line_colors = { :work => '0,153,0', :ideal => '102,102,102', :total => '0,153,0' }
+    end
+
+    protected
+
+    def calculate_burndown_data
+      data_scope = @data_scope
+      data_scope = data_scope.where("#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1") if use_subissue_done_ratio && @estimated_unit == 'hours'
+
+      if @estimated_unit == 'hours'
+        all_issues = data_scope.where("#{Issue.table_name}.estimated_hours IS NOT NULL").
+                     eager_load([:journals, :status, { :journals => { :details => :journal } }])
+        cumulative_total_hours = data_scope.sum("#{Issue.table_name}.estimated_hours").to_f
+      else
+        all_issues = data_scope.where("#{AgileData.table_name}.story_points IS NOT NULL").
+                     joins(:agile_data).eager_load([:journals, :status, { :journals => { :details => :journal } }])
+        cumulative_total_hours = data_scope.joins(:agile_data).sum("#{AgileData.table_name}.story_points").to_f
+      end
+
+      data = chart_dates_by_period.select { |d| d <= Date.today }.map do |date|
+        issues = all_issues.select { |issue| issue.created_on.localtime.to_date <= date }
+        total_hours_left, cumulative_total_hours_left = date_effort(issues, date)
+        [total_hours_left, cumulative_total_hours - cumulative_total_hours_left]
+      end
+      data = first_period_effort(all_issues, chart_dates_by_period.first, cumulative_total_hours) + data
+      @burndown_data, @cumulative_burndown_data = data.transpose
+    end
+
+    private
+
+    def first_period_effort(issues_scope, start_date, cumulative_total_hours)
+      issues = issues_scope.select { |issue| issue.created_on.localtime.to_date <= start_date }
+      total_left, cumulative_left = date_effort(issues, start_date - 1)
+      [[total_left, cumulative_total_hours - cumulative_left]]
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/charts/work_burnup_chart.rb b/plugins/redmine_agile/lib/redmine_agile/charts/work_burnup_chart.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b72262f4e516a1b08007199ababacc9b2c4472c6
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/charts/work_burnup_chart.rb
@@ -0,0 +1,112 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  class WorkBurnupChart < BurnupChart
+    def initialize(data_scope, options = {})
+      super data_scope, options
+      if @estimated_unit == 'hours'
+        @y_title = l(:label_agile_charts_number_of_hours)
+        @graph_title = l(:label_agile_charts_work_burnup_hours)
+      else
+        @y_title = l(:label_agile_charts_number_of_story_points)
+        @graph_title = l(:label_agile_charts_work_burnup_sp)
+      end
+      @line_colors = { :created => '102,102,102', :closed => '0,153,0', :ideal => '102,102,102' }
+    end
+
+    protected
+
+    def calculate_data
+      data_scope = @data_scope
+      data_scope = data_scope.where("#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1") if use_subissue_done_ratio && @estimated_unit == 'hours'
+      if @estimated_unit == 'hours'
+        all_issues = data_scope.where("#{Issue.table_name}.estimated_hours IS NOT NULL").
+                     eager_load([:journals, :status, { :journals => { :details => :journal } }])
+        @cumulative_data = cumulative_hours_by_period(data_scope)
+      else
+        all_issues = data_scope.where("#{AgileData.table_name}.story_points IS NOT NULL").
+                     joins(:agile_data).eager_load([:journals, :status, { :journals => { :details => :journal } }])
+        @cumulative_data = cumulative_story_points_by_period(data_scope)
+      end
+
+      data = chart_dates_by_period.select { |d| d <= Date.today }.map do |date|
+        issues = all_issues.select do |issue|
+          issue.created_on.localtime.to_date <= date
+        end
+        cumulative_total_hours_left, total_hours_done = date_effort(issues, date)[1..2]
+        total_hours_done
+      end
+      @data = [first_period_effort(all_issues, chart_dates_by_period.first)[0][2]] + data
+      @cumulative_data = @cumulative_data.first(@data.count) if @cumulative_data.count > @data.count
+      @data
+    end
+
+    private
+
+    def cumulative_hours_by_period(data_scope)
+      data = [0] * chart_dates_by_period.count
+      data_scope.
+        where("#{Issue.table_name}.created_on >= ?", @date_from).
+        where("#{Issue.table_name}.created_on < ?", @date_to.to_date + 1).
+        where("#{Issue.table_name}.created_on IS NOT NULL").
+        group("#{Issue.table_name}.created_on").
+        sum(:estimated_hours).each do |c|
+          next if c.first.localtime.to_date > @date_to.to_date
+          period_num = ((@date_to.to_date - c.first.localtime.to_date).to_i / @scale_division).to_i
+          data[period_num] += c.last unless data[period_num].blank?
+        end
+
+      total_estimated_hours = data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).sum(:estimated_hours)
+      first_date_estimated_hours = data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).sum(:estimated_hours)
+      ([first_date_estimated_hours] + data.reverse.map { |x| total_estimated_hours += x })
+    end
+
+    def cumulative_story_points_by_period(data_scope)
+      data = [0] * @period_count
+      data_scope.
+        where("#{Issue.table_name}.created_on >= ?", @date_from).
+        where("#{Issue.table_name}.created_on < ?", @date_to.to_date + 1).
+        where("#{Issue.table_name}.created_on IS NOT NULL").
+        group("#{Issue.table_name}.created_on").
+        joins(:agile_data).
+        sum("#{AgileData.table_name}.story_points").each do |c|
+          next if c.first.localtime.to_date > @date_to.to_date
+          period_num = ((@date_to.to_date - c.first.localtime.to_date).to_i / @scale_division).to_i
+          data[period_num] += c.last.to_i unless data[period_num].blank?
+        end
+
+      total_estimated_hours = data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).
+                              joins(:agile_data).
+                              sum("#{AgileData.table_name}.story_points").to_i
+      first_date_estimated_hours = data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).
+                                   joins(:agile_data).
+                                   sum("#{AgileData.table_name}.story_points").to_i
+      [first_date_estimated_hours] +  data.reverse.map { |x| total_estimated_hours += x }
+    end
+
+    def first_period_effort(issues_scope, start_date)
+      issues = issues_scope.select do |issue|
+        issue.created_on.localtime.to_date <= start_date
+      end
+      total_left, cumulative_left, total_done = date_effort(issues, start_date - 1)
+      [[total_left, cumulative_left, total_done]]
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/helpers/agile_helper.rb b/plugins/redmine_agile/lib/redmine_agile/helpers/agile_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b77fbd040a5215ef28f9f60eaa9f193907d3a6e
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/helpers/agile_helper.rb
@@ -0,0 +1,184 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module AgileHelper
+
+    def retrieve_agile_query_from_session
+      if session[:agile_query]
+        if session[:agile_query][:id]
+          @query = AgileQuery.find_by_id(session[:agile_query][:id])
+          return unless @query
+        else
+          @query = AgileQuery.new(get_query_attributes_from_session)
+        end
+        if session[:agile_query].has_key?(:project_id)
+          @query.project_id = session[:agile_query][:project_id]
+        else
+          @query.project = @project
+        end
+        @query
+      else
+        @query = AgileQuery.new(:name => "_")
+      end
+    end
+
+    def retrieve_agile_query
+      if !params[:query_id].blank?
+        cond = "project_id IS NULL"
+        cond << " OR project_id = #{@project.id}" if @project
+        @query = AgileQuery.where(cond).find(params[:query_id])
+        raise ::Unauthorized unless @query.visible?
+        @query.project = @project
+        session[:agile_query] = {:id => @query.id, :project_id => @query.project_id}
+        sort_clear
+      elsif api_request? || params[:set_filter] || session[:agile_query].nil? || session[:agile_query][:project_id] != (@project ? @project.id : nil)
+        @query = AgileQuery.default_query(@project) || AgileQuery.default_query unless params[:set_filter]
+        unless @query
+          @query = AgileQuery.new(:name => "_", :project => @project)
+          @query.build_from_params(params)
+        else
+          @query.project = @project if @project
+        end
+        save_qeury_attribures_to_session(@query)
+      else
+        # retrieve from session
+        @query = nil
+        if session[:agile_query] && !session[:agile_query][:id] && !params[:project_id]
+          @query = AgileQuery.new(get_query_attributes_from_session)
+        end
+        @query ||= AgileQuery.default_query(@project) || AgileQuery.default_query
+
+        @query ||= AgileQuery.find_by_id(session[:agile_query][:id]) if session[:agile_query][:id]
+        @query ||= AgileQuery.new(get_query_attributes_from_session)
+        @query.project = @project
+        save_qeury_attribures_to_session(@query)
+      end
+    end
+
+    def retrieve_versions_query
+      if api_request? || params[:set_filter] || session[:versions_query].nil? || session[:versions_query][:project_id] != (@project ? @project.id : nil)
+        @query = AgileVersionsQuery.new(:name => "_")
+        @query.project = @project if @project
+        @query.build_from_params(params)
+
+        session[:versions_query] = {:project_id => @query.project_id,
+                                 :filters => @query.filters}
+      else
+        @query = AgileVersionsQuery.new(:name => "_", :filters => session[:versions_query][:filters])
+        @query.project = @project if @project
+      end
+                            end
+    def agile_query_links(title, queries)
+      return '' if queries.empty?
+      # links to #index on issues/show
+      url_params = {:controller => 'agile_boards', :action => 'index', :project_id => @project}
+
+      content_tag('h3', title) + "\n" +
+        content_tag('ul',
+          queries.collect {|query|
+              css = 'query'
+              css << ' selected' if query == @query
+              content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
+            }.join("\n").html_safe,
+          :class => 'queries'
+        ) + "\n"
+    end
+
+
+    def sidebar_agile_queries
+      unless @sidebar_agile_queries
+        @sidebar_agile_queries = AgileQuery.visible.
+          order("#{Query.table_name}.name ASC").
+          # Project specific queries and global queries
+          where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
+          all
+      end
+      @sidebar_agile_queries
+    end
+
+    def render_sidebar_agile_queries
+      out = ''.html_safe
+      out << agile_query_links(l(:label_agile_my_boards), sidebar_agile_queries.select {|q| !q.is_public?})
+      out << agile_query_links(l(:label_agile_board_plural), sidebar_agile_queries.reject {|q| !q.is_public?})
+      out
+    end
+    def options_card_colors_for_select(selected, options={})
+      color_base = [[l(:label_agile_color_no_colors), "none"],
+        [l(:label_issue), "issue"],
+        [l(:label_tracker), "tracker"],
+        [l(:field_priority), "priority"],
+        [l(:label_spent_time), "spent_time"],
+        [l(:field_assigned_to), "user"]]
+      if (@project && @project.children.any?) || !@project
+        color_base << [l(:field_project), 'project']
+      end
+      options_for_select(color_base.compact,
+        selected)
+    end
+
+    def options_charts_for_select(selected, options={})
+      options_for_select([[l(:label_agile_charts_issues_burndown), "issues_burndown"],
+        [l(:label_agile_charts_work_burndown_sp), "work_burndown_sp"],
+        [l(:label_agile_charts_work_burndown_hours), "work_burndown_hours"],
+        [l(:label_agile_charts_burnup), "burnup"],
+        [l(:label_agile_charts_work_burnup_sp), "work_burnup_sp"],
+        [l(:label_agile_charts_work_burnup_hours), "work_burnup_hours"],
+        [l(:label_agile_charts_cumulative_flow), "cumulative_flow"],
+        [l(:label_agile_charts_issues_velocity), "issues_velocity"],
+        [l(:label_agile_charts_lead_time), "lead_time"],
+        [l(:label_agile_charts_average_lead_time), "average_lead_time"],
+        [l(:label_agile_charts_trackers_cumulative_flow), "trackers_cumulative_flow"],
+        nil].compact,
+        selected)
+    end
+
+    def render_agile_chart(chart_name, issues_scope)
+      render :partial => "agile_charts/chart", :locals => {:chart => chart_name, :issues_scope => issues_scope}
+    end
+
+    private
+
+    def get_query_attributes_from_session
+      attributes = {
+        :name => "_",
+        :filters => session[:agile_query][:filters],
+        :group_by => session[:agile_query][:group_by],
+        :column_names => session[:agile_query][:column_names],
+        :color_base => session[:agile_query][:color_base]
+      }
+      (attributes[:options] = session[:agile_query][:options] || {}) if Redmine::VERSION.to_s > '2.4'
+      attributes
+    end
+
+    def save_qeury_attribures_to_session(query)
+      session[:agile_query] = {:project_id => query.project_id,
+                                 :filters => query.filters,
+                                 :group_by => query.group_by,
+                                 :color_base => (query.respond_to?(:color_base) && query.color_base),
+                                 :column_names => query.column_names}
+      (session[:agile_query][:options] = query.options) if Redmine::VERSION.to_s > '2.4'
+    end
+
+  end
+end
+
+ActionView::Base.send :include, RedmineAgile::AgileHelper
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/controller_issue_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/controller_issue_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1905c47fe3709be328f5939b3b76fad8190a742f
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/controller_issue_hook.rb
@@ -0,0 +1,49 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ControllerIssueHook < Redmine::Hook::ViewListener
+
+      def controller_issues_edit_before_save(context={})
+        return false unless context[:issue].project.module_enabled?(:agile)
+        # return false unless context[:issue].color
+        old_value = Issue.find(context[:issue].id)
+        old_issue_color = old_value.color.to_s
+        new_issue_color = context[:issue].color.to_s
+
+        if new_issue_color && !((new_issue_color == old_issue_color) || context[:issue].current_journal.blank?)
+          context[:issue].current_journal.details << JournalDetail.new(:property => 'attr',
+                                                                       :prop_key => 'color',
+                                                                       :old_value => old_issue_color,
+                                                                       :value => new_issue_color)
+        end
+        # save changes for story points to journal
+        old_sp = old_value.story_points
+        new_sp = context[:issue].story_points
+        if !((new_sp == old_sp) || context[:issue].current_journal.blank?)
+          context[:issue].current_journal.details << JournalDetail.new(:property => 'attr',
+          :prop_key => 'story_points',
+          :old_value => old_sp,
+          :value => new_sp)
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/helper_issues_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/helper_issues_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6057ba706074fe2f90c90d29ba85af62a77474da
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/helper_issues_hook.rb
@@ -0,0 +1,34 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class HelperIssuesHook < Redmine::Hook::ViewListener
+
+      def helper_issues_show_detail_after_setting(context={})
+        if context[:detail].prop_key == 'color'
+          detail = context[:detail]
+          context[:detail].value = detail.value.blank? ? nil : l(("label_agile_color_" + detail.value.to_s).to_sym)
+          context[:detail].old_value = detail.old_value.blank? ? nil : l(("label_agile_color_" + detail.old_value.to_s).to_sym)
+        end
+      end
+
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_context_menus_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_context_menus_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b701bd481de3b3fdd9bfee8ae106be525ec80841
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_context_menus_hook.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsContextMenuesHook < Redmine::Hook::ViewListener
+      render_on :view_issues_context_menu_end, :partial => "context_menus/agile_colors"
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_issues_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_issues_hook.rb
new file mode 100755
index 0000000000000000000000000000000000000000..dab96ad67400fb1860b71c709904096ed1f55d3c
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_issues_hook.rb
@@ -0,0 +1,35 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsIssuesHook < Redmine::Hook::ViewListener
+      def view_issues_sidebar_issues_bottom(context={})
+        context[:controller].send(:render_to_string, {
+          :partial => 'agile_boards/issues_sidebar',
+          :locals => context }) +
+        context[:controller].send(:render_to_string, {
+          :partial => "agile_charts/agile_charts",
+          :locals => context })
+      end
+      render_on :view_issues_form_details_bottom, :partial => "issues/agile_data_fields"
+      render_on :view_issues_show_details_bottom, :partial => "issues/issue_story_points"
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_layouts_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_layouts_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ac141b2d5c92e95bdf97eb809f1c613fd61cd97
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_layouts_hook.rb
@@ -0,0 +1,28 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsLayoutsHook < Redmine::Hook::ViewListener
+      def view_layouts_base_html_head(context={})
+        return stylesheet_link_tag(:redmine_agile, :plugin => 'redmine_agile') 
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_projects_form_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_projects_form_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94864cebf4d026ad538e590acf90d9cc19c0f2e1
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_projects_form_hook.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsProjectsForm < Redmine::Hook::ViewListener
+      render_on :view_projects_form, :partial => "projects/project_color_form"
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_users_form_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_users_form_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..42d228c6c4acebe293415b5f776f361e83662df3
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_users_form_hook.rb
@@ -0,0 +1,27 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsUsersHook < Redmine::Hook::ViewListener
+      preferences_hook = Redmine::VERSION.to_s > '2.5' ? :view_users_form_preferences : :view_users_form
+      render_on preferences_hook, :partial => 'users/user_color_form'
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/hooks/views_versions_hook.rb b/plugins/redmine_agile/lib/redmine_agile/hooks/views_versions_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..deae5deaad538aacefb0ff3534df579af7af1bb7
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/hooks/views_versions_hook.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Hooks
+    class ViewsVersionsHook < Redmine::Hook::ViewListener
+      render_on :view_versions_show_bottom, :partial => "agile_charts/versions_show"
+    end
+  end
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/compatibility/application_controller_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/compatibility/application_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..633e1a400b24032bda401f35d83c2feb97fd2cc3
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/compatibility/application_controller_patch.rb
@@ -0,0 +1,41 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+    module ApplicationControllerPatch
+      def self.included(base) # :nodoc:
+        base.extend(ClassMethods)
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+        end
+      end
+
+      module ClassMethods
+        def before_action(*filters, &block)
+          before_filter(*filters, &block)
+        end
+      end
+    end
+  end
+end
+
+unless ApplicationController.included_modules.include?(RedmineAgile::Patches::ApplicationControllerPatch)
+  ApplicationController.send(:include, RedmineAgile::Patches::ApplicationControllerPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/compatibility_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/compatibility_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4b67d3d35010e6331144e78fc609bcbae382dc8
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/compatibility_patch.rb
@@ -0,0 +1,351 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+class AgileQuery < Query
+  unloadable
+
+  VISIBILITY_PRIVATE = 0
+  VISIBILITY_ROLES   = 1
+  VISIBILITY_PUBLIC  = 2
+
+  attr_reader :truncated
+
+  self.queried_class = Issue
+
+  self.available_columns = [
+    QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => :label_agile_issue_id),
+    QueryColumn.new(:project, :groupable => "#{Issue.table_name}.project_id"),
+    QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
+    QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
+    QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
+    QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
+    QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("users")}, :groupable => true),
+    QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => "#{Issue.table_name}.category_id"),
+    QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
+    QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
+    QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
+    QueryColumn.new(:thumbnails, :caption => :label_agile_board_thumbnails),
+    QueryColumn.new(:description),
+    QueryColumn.new(:parent, :groupable => "#{Issue.table_name}.parent_id", :caption => :field_parent_issue),
+    QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => "#{Issue.table_name}.assigned_to_id")
+  ]
+
+  scope :visible, lambda {|*args|
+    user = args.shift || User.current
+    base = Project.allowed_to_condition(user, :view_issues, *args)
+    user_id = user.logged? ? user.id : 0
+
+    eager_load(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
+  }
+
+  def initialize(attributes=nil, *args)
+    attributes.delete(:color_base) if attributes
+    super attributes
+    self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
+    @truncated = false
+  end
+
+  def card_columns
+    self.inline_columns.select{|c| !%w(tracker thumbnails description assigned_to done_ratio spent_hours estimated_hours project id day_in_state last_comment story_points).include?(c.name.to_s)}
+  end
+
+  def visible?(user=User.current)
+    (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
+  end
+
+  def is_private?
+    visibility == VISIBILITY_PRIVATE
+  end
+
+  def is_public?
+    !is_private?
+  end
+
+  def self.default_query(project=nil)
+    false
+  end
+
+  def visibility=(value)
+    self.is_public = value == VISIBILITY_PUBLIC
+  end
+
+  def visibility
+    self.is_public ? VISIBILITY_PUBLIC : VISIBILITY_PRIVATE
+  end
+
+  def build_from_params(params)
+    if params[:fields] || params[:f]
+      self.filters = {}
+      add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
+    else
+      available_filters.keys.each do |field|
+        add_short_filter(field, params[field]) if params[field]
+      end
+    end
+    self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
+    self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
+    self
+  end
+
+  # Builds a new query from the given params and attributes
+  def self.build_from_params(params, attributes={})
+    new(attributes).build_from_params(params)
+  end
+
+  def initialize_available_filters
+    principals = []
+    subprojects = []
+    versions = []
+    categories = []
+    issue_custom_fields = []
+
+    if project
+      principals += project.principals.sort
+      unless project.leaf?
+        subprojects = project.descendants.visible.all
+        principals += Principal.member_of(subprojects)
+      end
+      versions = project.shared_versions.all
+      categories = project.issue_categories.all
+      issue_custom_fields = project.all_issue_custom_fields
+    else
+      if all_projects.any?
+        principals += Principal.member_of(all_projects)
+      end
+      versions = Version.visible.where(:sharing => 'system').all
+      issue_custom_fields = IssueCustomField.where(:is_for_all => true)
+    end
+    principals.uniq!
+    principals.sort!
+    users = principals.select {|p| p.is_a?(User)}
+
+    add_available_filter "status_id",
+      :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
+
+    if project.nil?
+      project_values = []
+      if User.current.logged? && User.current.memberships.any?
+        project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
+      end
+      project_values += all_projects_values
+      add_available_filter("project_id",
+        :type => :list, :values => project_values
+      ) unless project_values.empty?
+    end
+
+    add_available_filter "tracker_id",
+      :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter "priority_id",
+      :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
+
+    author_values = []
+    author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    author_values += users.collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("author_id",
+      :type => :list, :values => author_values
+    ) unless author_values.empty?
+
+    assigned_to_values = []
+    assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
+    assigned_to_values += (Setting.issue_group_assignment? ?
+                              principals : users).collect{|s| [s.name, s.id.to_s] }
+    add_available_filter("assigned_to_id",
+      :type => :list_optional, :values => assigned_to_values
+    ) unless assigned_to_values.empty?
+
+    if versions.any?
+      add_available_filter "fixed_version_id",
+        :type => :list_optional,
+        :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
+    end
+
+    if categories.any?
+      add_available_filter "category_id",
+        :type => :list_optional,
+        :values => categories.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_available_filter "subject", :type => :text
+    add_available_filter "created_on", :type => :date_past
+    add_available_filter "updated_on", :type => :date_past
+    add_available_filter "closed_on", :type => :date_past
+    add_available_filter "start_date", :type => :date
+    add_available_filter "due_date", :type => :date
+    add_available_filter "estimated_hours", :type => :float
+    add_available_filter "done_ratio", :type => :integer
+
+    if subprojects.any?
+      add_available_filter "subproject_id",
+        :type => :list_subprojects,
+        :values => subprojects.collect{|s| [s.name, s.id.to_s] }
+    end
+
+    add_custom_fields_filters(issue_custom_fields)
+
+    add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
+
+    Tracker.disabled_core_fields(trackers).each {|field|
+      delete_available_filter field
+    }
+  end
+
+  def available_columns
+    return @available_columns if @available_columns
+    @available_columns = self.class.available_columns.dup
+    @available_columns += (project ?
+                            project.all_issue_custom_fields :
+                            IssueCustomField.all
+                           ).collect {|cf| QueryCustomFieldColumn.new(cf) }
+
+    if User.current.allowed_to?(:view_time_entries, project, :global => true)
+      index = nil
+      @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
+      index = (index ? index + 1 : -1)
+      # insert the column after estimated_hours or at the end
+      @available_columns.insert index, QueryColumn.new(:spent_hours,
+        :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
+        :default_order => 'desc',
+        :caption => :label_spent_time
+      )
+    end
+
+    disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
+    @available_columns.reject! {|column|
+      disabled_fields.include?(column.name.to_s)
+    }
+
+    @available_columns.reject! {|column| column.name == :done_ratio} unless Issue.use_field_for_done_ratio?
+
+    @available_columns
+  end
+
+  def editable_by?(user)
+    return false unless user
+    # Admin can edit them all and regular users can edit their private queries
+    return true if user.admin? || (is_private? && self.user_id == user.id)
+    # Members can not edit public queries that are for all project (only admin is allowed to)
+    is_public? && !@is_for_all && user.allowed_to?(:manage_public_agile_queries, project, global: true)
+  end
+
+  def default_columns_names
+    @default_columns_names = RedmineAgile.default_columns.map(&:to_sym)
+  end
+
+  def has_column_name?(name)
+    columns.detect{|c| c.name == name}
+  end
+
+  def groupable_columns
+    available_columns.select {|c| c.groupable && !c.is_a?(QueryCustomFieldColumn)}
+  end
+
+  def issues(options={})
+    order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
+
+    scope = issue_scope.
+      joins(:status, :project).
+      includes((options[:include] || []).uniq).
+      where(options[:conditions]).
+      order(order_option).
+      joins(joins_for_order_statement(order_option.join(','))).
+      limit(options[:limit]).
+      offset(options[:offset])
+
+    scope = scope.preload(:custom_values)
+    if has_column?(:author)
+      scope = scope.preload(:author)
+    end
+
+    # if has_column?(:spent_hours)
+    #   Issue.load_visible_spent_hours(issues)
+    # end
+
+    scope
+  rescue ::ActiveRecord::StatementInvalid => e
+    raise StatementInvalid.new(e.message)
+  end
+
+  # for compatibility with upper versions
+  def issue_last_comment(issue_id, options = {})
+    nil
+  end
+
+  def journals_for_state
+    nil
+  end
+
+  def board_statuses
+    status_filter_operator = filters.fetch("status_id", {}).fetch(:operator, nil)
+    status_filter_values = filters.fetch("status_id", {}).fetch(:values, [])
+    statuses = IssueStatus.where(:id => Tracker.includes(:issues => [:status, :project, :fixed_version]).where(statement).map(&:issue_statuses).flatten.uniq.map(&:id))
+    result_statuses = case status_filter_operator
+    when "o"
+      statuses.where(:is_closed => false).sorted
+    when "c"
+      statuses.where(:is_closed => true).sorted
+    when "="
+      statuses.where(:id => status_filter_values).sorted
+    when "!"
+      statuses.where("#{IssueStatus.table_name}.id NOT IN (" + status_filter_values.map{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")").sorted
+    else
+      statuses.sorted
+    end
+    result_statuses.map do |s|
+      s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id]
+      s
+    end
+  end
+  def swimlanes
+    return [] unless self.grouped?
+    lane_ids = issue_scope.group(self.group_by_column.groupable).count.keys
+    lanes = Issue.reflect_on_association(self.group_by_column.name).klass.where(:id => lane_ids).reorder(group_by_sort_order)
+    lanes << nil if lane_ids.include?(nil)
+    lanes
+  end
+
+  def issue_count_by_swimlane
+    @issue_count_by_swimlane ||= issue_scope.group("#{Issue.table_name}.#{self.group_by_column.name}_id").count if self.grouped?
+  end
+
+  def issue_count_by_status
+    @issue_count_by_status ||= issue_scope.group("#{Issue.table_name}.status_id").count
+  end
+
+  def issue_board(options={})
+    @truncated = RedmineAgile.board_items_limit <= issue_scope.count
+    all_issues = self.issues.limit(RedmineAgile.board_items_limit).sorted_by_rank
+    grouped_issues = grouped? ? all_issues.group_by{|i| [i.status_id, i.send("#{self.group_by_column.name}_id")]} : all_issues.group_by{|i| [i.status_id]}
+    grouped_issues.values.each{|x|x.sort!{|a,b| a.position.to_i <=> b.position.to_i}} if grouped?
+    grouped_issues
+          end
+
+private
+  def issue_scope
+    Issue.visible.
+      includes(:status,
+               :project,
+               :assigned_to,
+               :tracker,
+               :priority,
+               :category,
+               :fixed_version).
+      where(statement)
+  end
+
+end if Redmine::VERSION.to_s < '2.4'
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/issue_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/issue_patch.rb
new file mode 100755
index 0000000000000000000000000000000000000000..3161f866202b45d1d0286cf4bc0f7b332f88578f
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/issue_patch.rb
@@ -0,0 +1,91 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'issue'
+require_dependency 'agile_data'
+
+module RedmineAgile
+  module Patches
+
+    module IssuePatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+          has_one :agile_data, :dependent => :destroy
+          delegate :position, :to => :agile_data, :allow_nil => true
+          scope :sorted_by_rank, lambda { eager_load(:agile_data).
+                                          order("COALESCE(#{AgileData.table_name}.position, 999999)") }
+          alias_method :css_classes_without_agile, :css_classes
+          alias_method :css_classes, :css_classes_with_agile
+
+          acts_as_colored
+
+          safe_attributes 'agile_color_attributes',
+            :if => lambda { |issue, user| user.allowed_to?(:edit_issues, issue.project) && user.allowed_to?(:view_agile_queries, issue.project) && RedmineAgile.issue_colors? }
+          safe_attributes 'agile_data_attributes', :if => lambda { |issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) && RedmineAgile.use_story_points? }
+          accepts_nested_attributes_for :agile_data, :allow_destroy => true
+
+          alias_method :agile_data_without_default, :agile_data
+          alias_method :agile_data, :agile_data_with_default
+        end
+      end
+
+      module InstanceMethods
+        def agile_data_with_default
+          agile_data_without_default || build_agile_data
+        end
+
+        def day_in_state
+          change_time = journals.joins(:details).where(:journals => { :journalized_id => id, :journalized_type => 'Issue' },
+                                                       :journal_details => { :prop_key => 'status_id' }).order('created_on DESC').first
+          change_time.created_on
+        rescue
+          created_on
+        end
+
+        def last_comment
+          journals.where("notes <> ''").order("#{Journal.table_name}.id ASC").last
+        end
+
+        def story_points
+          @story_points ||= agile_data.story_points
+        end
+        def css_classes_with_agile(user = User.current)
+          s = if Redmine::VERSION.to_s < '2.4'
+                css_classes_without_agile
+              else
+                css_classes_without_agile(user)
+              end
+          s << " #{RedmineAgile.color_prefix}-#{color}" if RedmineAgile.issue_colors? && color
+          s
+        end
+
+        def sub_issues
+          descendants
+        end
+      end
+    end
+
+  end
+end
+
+unless Issue.included_modules.include?(RedmineAgile::Patches::IssuePatch)
+  Issue.send(:include, RedmineAgile::Patches::IssuePatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/issue_priority_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/issue_priority_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ffcc76f214e3f7a2715c5ab3a2193ccb8af1b90
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/issue_priority_patch.rb
@@ -0,0 +1,36 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+
+    module IssuePriorityPatch
+      def self.included(base)
+        base.class_eval do
+          acts_as_colored
+        end
+      end
+    end
+
+  end
+end
+
+unless IssuePriority.included_modules.include?(RedmineAgile::Patches::IssuePriorityPatch)
+  IssuePriority.send(:include, RedmineAgile::Patches::IssuePriorityPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/issue_query_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/issue_query_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6fe8c905fd1cc791c3f8400982c24d687a6d77cd
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/issue_query_patch.rb
@@ -0,0 +1,62 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'issue'
+
+module RedmineAgile
+  module Patches
+
+    module IssueQueryPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          alias_method :issues_without_agile, :issues
+          alias_method :issues, :issues_with_agile
+          alias_method :issue_ids_without_agile, :issue_ids
+          alias_method :issue_ids, :issue_ids_with_agile
+
+          available_columns << QueryColumn.new(:story_points, :caption => :label_agile_story_points, :sortable => "#{AgileData.table_name}.story_points")
+        end
+      end
+
+      module InstanceMethods
+        def issues_with_agile(options = {})
+          options[:include] = (options[:include] || []) | [:agile_data]
+          issues = issues_without_agile(options)
+          if RedmineAgile.color_base == AgileColor::COLOR_GROUPS[:issue]
+            agile_colors = AgileColor.where(container_id: issues, container_type: 'Issue').group_by { |ac| ac[:container_id] }
+            issues.each { |issue| issue.color = agile_colors[issue.id].try(:first).try(:color) }
+          end
+          issues
+        end
+
+        def issue_ids_with_agile(options = {})
+          options[:include] = (options[:include] || []) | [:agile_data]
+          options[:include] = (options[:include] || []) | [:agile_color] if RedmineAgile.color_base == AgileColor::COLOR_GROUPS[:issue]
+          issue_ids_without_agile(options)
+        end
+      end
+    end
+
+  end
+end
+
+unless IssueQuery.included_modules.include?(RedmineAgile::Patches::IssueQueryPatch)
+  IssueQuery.send(:include, RedmineAgile::Patches::IssueQueryPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/project_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/project_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d03c32805e1371dd81479fd439d175541efb813f
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/project_patch.rb
@@ -0,0 +1,39 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+
+    module ProjectPatch
+      def self.included(base)
+        base.class_eval do
+          unloadable
+          acts_as_colored
+          safe_attributes 'agile_color_attributes',
+            :if => lambda { |project, user| user.allowed_to?(:edit_project, project) && user.allowed_to?(:view_agile_queries, project) && RedmineAgile.use_colors? }
+        end
+      end
+    end
+
+  end
+end
+
+unless Project.included_modules.include?(RedmineAgile::Patches::ProjectPatch)
+  Project.send(:include, RedmineAgile::Patches::ProjectPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/queries_controller_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/queries_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d60e5042f17cf444652388f61111ea2019aa818d
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/queries_controller_patch.rb
@@ -0,0 +1,43 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+    module QueriesControllerPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          alias_method :query_class_without_agile, :query_class
+          alias_method :query_class, :query_class_with_agile
+        end
+      end
+
+      module InstanceMethods
+        def query_class_with_agile
+          return AgileChartsQuery if params[:type] == 'AgileChartsQuery'
+          query_class_without_agile
+        end
+      end
+    end
+  end
+end
+
+unless QueriesController.included_modules.include?(RedmineAgile::Patches::QueriesControllerPatch)
+  QueriesController.send(:include, RedmineAgile::Patches::QueriesControllerPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/tracker_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/tracker_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e326771ddf4bce4a0f0d381700ebd146cd67879
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/tracker_patch.rb
@@ -0,0 +1,36 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+
+    module TrackerPatch
+      def self.included(base)
+        base.class_eval do
+          acts_as_colored
+        end
+      end
+    end
+
+  end
+end
+
+unless Tracker.included_modules.include?(RedmineAgile::Patches::TrackerPatch)
+  Tracker.send(:include, RedmineAgile::Patches::TrackerPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/patches/user_patch.rb b/plugins/redmine_agile/lib/redmine_agile/patches/user_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8422507f2445fbb9fb9d3a8749760ed9dc82fbdd
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/patches/user_patch.rb
@@ -0,0 +1,39 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module Patches
+
+    module UserPatch
+      def self.included(base)
+        base.class_eval do
+          unloadable
+          acts_as_colored
+          safe_attributes 'agile_color_attributes',
+            :if => lambda { |user, current_user| (current_user.admin? || (user.new_record? && current_user.anonymous? && Setting.self_registration?)) && RedmineAgile.use_colors? }
+        end
+      end
+    end
+
+  end
+end
+
+unless User.included_modules.include?(RedmineAgile::Patches::UserPatch)
+  User.send(:include, RedmineAgile::Patches::UserPatch)
+end
diff --git a/plugins/redmine_agile/lib/redmine_agile/utils/header_tree.rb b/plugins/redmine_agile/lib/redmine_agile/utils/header_tree.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eee599e57001d62422bbe26fd4faaccc548c2440
--- /dev/null
+++ b/plugins/redmine_agile/lib/redmine_agile/utils/header_tree.rb
@@ -0,0 +1,118 @@
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineAgile
+  module HeaderTree
+    class HeaderTree
+      class HeaderTreeNode
+        attr_accessor :leaf
+
+        def initialize(name = nil, level = 0)
+          @name = name
+          @level = level
+          @children = ActiveSupport::OrderedHash.new
+        end
+
+        def has_child?(name, has_leaf)
+          @children.keys.last == [name, has_leaf, @children.keys.size - 1]
+        end
+
+        def get_child(name, has_leaf)
+          k = @children.keys.select{ |x| x.first(2) == [name, has_leaf] }.last
+          @children[k]
+        end
+
+        def add_child(name, has_leaf)
+          @children[[name, has_leaf, @children.keys.size]] = HeaderTreeNode.new(name, @level + 1)
+        end
+
+        def branch_width
+          if @leaf
+            1
+          else
+            @children.values.map(&:branch_width).sum
+          end
+        end
+
+        def depth
+          if @leaf
+            1
+          else
+            1 + @children.values.map(&:depth).max
+          end
+        end
+
+        def render(levels)
+          height = @children.values.any? ? 1 : levels.size - @level
+          levels[@level] ||= []
+          levels[@level] << [@name, height, branch_width, @leaf]
+          @children.values.each do |child|
+            child.render(levels)
+          end
+          levels
+        end
+
+        def to_s
+          "#{"\t" * @level}#{@name || 'ROOT'} depth: #{depth}, width: #{branch_width}, level: #{@level}, leaf: #{!!@leaf}\n" +
+            @children.values.map(&:to_s).join
+        end
+      end
+
+      def initialize
+        @root = HeaderTreeNode.new
+      end
+
+      def put(path, leaf, node = nil)
+        node_to_put = node || @root
+        child_node_name = path.first
+        path_left = path[1..-1]
+        has_leaf = path_left.empty?
+        child =
+          if node_to_put.has_child? child_node_name, has_leaf
+            node_to_put.get_child(child_node_name, has_leaf)
+          else
+            node_to_put.add_child(child_node_name, has_leaf)
+          end
+        if has_leaf
+          child.leaf = leaf
+        else
+          put path_left, leaf, child
+        end
+      end
+
+      def depth
+        @root.depth - 1 # Because root itself is not treat as node
+      end
+
+      def render
+        maxdepth = depth
+        levels = Array.new maxdepth + 1
+        @root.render(levels)
+      end
+
+      def to_s
+        @root.to_s
+      end
+    end
+  end
+end
+
+unless AgileBoardsHelper.included_modules.include?(RedmineAgile::HeaderTree)
+  AgileBoardsHelper.send(:include, RedmineAgile::HeaderTree)
+end
diff --git a/plugins/redmine_agile/test/fixtures/queries.yml b/plugins/redmine_agile/test/fixtures/queries.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e33abb9f78a52e0b62312783257883196c79152
--- /dev/null
+++ b/plugins/redmine_agile/test/fixtures/queries.yml
@@ -0,0 +1,32 @@
+agile_query_101:
+  id: 101
+  type: AgileQuery
+  project_id: 1
+  <% if Redmine::VERSION.to_s < '2.4' %>
+  is_public: false
+  <% else %>
+  visibility: 2
+  <% end %>
+  name: Board for specific project
+  filters: |
+    ---
+    tracker_id:
+      :values:
+      - "3"
+      :operator: "="
+  user_id: 1
+  column_names: []
+
+agile_query_102:
+  id: 102
+  type: AgileQuery
+  project_id: nil
+  <% if Redmine::VERSION.to_s < '2.4' %>
+  is_public: true
+  <% else %>
+  visibility: 2
+  <% end %>
+  name: Board for all projects
+  filters: {}
+  user_id: 1
+  column_names: []
diff --git a/plugins/redmine_agile/test/functional/agile_board_controller_test.rb b/plugins/redmine_agile/test/functional/agile_board_controller_test.rb
new file mode 100755
index 0000000000000000000000000000000000000000..094e6359b27f0f25b9409428f37cc02da96ca38d
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/agile_board_controller_test.rb
@@ -0,0 +1,963 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+module RedmineAgile
+  module AgileBoardsControllerTest
+    module SpecificTestCase
+      def test_get_index_with_filter_issue_id
+        compatible_request :get, :index, {
+          project_id: 'ecookbook',
+          set_filter: '1',
+          f: %w(issue_id),
+          op: { 'issue_id' => '<=' },
+          v: { 'issue_id' => ['2'] },
+          c: %w(tracker assigned_to)
+        }
+
+        assert_response :success
+        assert_equal [1, 2], agile_issues_in_list.map(&:id).sort
+      end
+
+      def test_get_index_with_filter_is_private
+        compatible_request :get, :index, {
+          project_id: 'ecookbook',
+          set_filter: '1',
+          f: %w(is_private),
+          op: { 'is_private' => '=' },
+          v: { 'is_private' => ['1'] },
+          c: %w(tracker assigned_to)
+        }
+
+        assert_response :success
+        assert_equal [14], agile_issues_in_list.map(&:id)
+      end
+      def test_get_index_with_filter_description
+        compatible_request :get, :index, {
+          project_id: 'ecookbook',
+          set_filter: '1',
+          f: %w(description),
+          op: { 'description' => '=' },
+          v: { 'description' => ['Unable to print recipes'] },
+          c: %w(tracker assigned_to)
+        }
+
+        assert_response :success
+        assert_equal [1], agile_issues_in_list.map(&:id)
+      end
+
+      def test_get_index_with_filter_watcher_id
+        issues(:issues_001).set_watcher(users(:users_002))
+
+        compatible_request :get, :index, {
+          project_id: 'ecookbook',
+          set_filter: '1',
+          f: %w(watcher_id),
+          op: { 'watcher_id' => '=' },
+          v: { 'watcher_id' => ['2'] },
+          c: %w(tracker assigned_to)
+        }
+
+        assert_response :success
+        assert_equal [1], agile_issues_in_list.map(&:id)
+      end
+    end
+  end
+end
+
+
+class AgileBoardsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :issue_relations,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  fixtures :email_addresses if Redmine::VERSION.to_s > '3.0'
+
+  include(RedmineAgile::AgileBoardsControllerTest::SpecificTestCase) if Redmine::VERSION.to_s > '2.4'
+
+  def setup
+    @project_1 = Project.find(1)
+    @project_2 = Project.find(5)
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    EnabledModule.create(:project => @project_2, :name => 'agile')
+    @request.session[:user_id] = 1
+  end
+
+  def test_get_index
+    # global board
+    compatible_request :get, :index
+    assert_response :success
+    assert_equal Issue.open.map(&:id).sort, agile_issues_in_list.map(&:id).sort
+    assert_select '.issue-card', Issue.open.count
+  end
+
+  def test_get_index_with_project
+    compatible_request :get, :index, :project_id => @project_1
+    issues = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a,
+                         :status_id => IssueStatus.where(:is_closed => false))
+    assert_equal issues.map(&:id).sort, agile_issues_in_list.map(&:id).sort
+    assert_select '.issue-card', issues.count
+    assert_select '.issue-card span.fields p.issue-id strong', issues.count
+    assert_select '.issue-card span.fields p.name a', issues.count
+  end
+
+  def test_get_index_truncated
+    with_agile_settings 'board_items_limit' => 1 do
+      compatible_request :get, :index, agile_query_params
+      assert_response :success
+      assert_select 'div#content p.warning', 1
+      assert_select 'td.issue-status-col .issue-card', 1
+    end
+  end
+
+  def test_limit_for_truncated
+    expected_issues = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a,
+                                  :status_id => IssueStatus.where(:is_closed => false))
+    with_agile_settings 'board_items_limit' => (expected_issues.count + 1) do
+      compatible_request :get, :index, agile_query_params.merge(:f_status => IssueStatus.where(:is_closed => false).pluck(:id))
+      assert_response :success
+      assert_select 'div#content p.warning', 0
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_filters
+    if Redmine::VERSION.to_s > '2.4'
+      compatible_request :get, :index, agile_query_params.merge(:f_status => IssueStatus.where('id != 1').pluck(:id))
+    else
+      compatible_request :get, :index, agile_query_params.merge(:op => { :status_id => '!' }, :v => { :status_id => ['1'] })
+    end
+    assert_response :success
+    expected_issues = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a,
+                                  :status_id => IssueStatus.where('id != 1'))
+    assert_equal expected_issues.map(&:id).sort, agile_issues_in_list.map(&:id).sort
+  end
+
+  def test_get_index_with_filter_on_assignee_role
+    assignee_params = { :set_filter => '1',
+                        :f => ['assigned_to_role', ''],
+                        :op => { :assigned_to_role => '=' },
+                        :v => { 'assigned_to_role' => ['2'] },
+                        :c => ['assigned_to'],
+                        :project_id => 'ecookbook' }
+    compatible_request :get, :index, assignee_params
+    assert_response :success
+    members = Member.joins(:roles).where(:project_id => [@project_1]).where('member_roles.role_id = 2').map(&:user_id).uniq
+    expected_issues = Issue.where(:project_id => [@project_1]).where(:assigned_to_id => members)
+    assert_equal expected_issues.map(&:id).sort, agile_issues_in_list.map(&:id).sort
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def create_subissue
+    @issue1 = Issue.find(1)
+    @subissue = Issue.create!(
+      :subject         => 'Sub issue',
+      :project         => @issue1.project,
+      :tracker         => @issue1.tracker,
+      :author          => @issue1.author,
+      :parent_issue_id => @issue1.id,
+      :fixed_version   => Version.last
+    )
+  end
+
+  def test_get_index_with_filter_on_parent_tracker
+    create_subissue
+    compatible_request :get, :index, agile_query_params.merge(
+      :op => { :parent_issue_tracker_id => '=' },
+      :v => { :parent_issue_tracker_id => [Tracker.find(1).name] },
+      :f => [:parent_issue_tracker_id],
+      :project_id => Project.order(:id).first.id
+    )
+    assert_response :success
+    assert_equal [@subissue.id], agile_issues_in_list.map(&:id)
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_filter_on_two_parent_id
+    create_subissue
+    issue2 = Issue.generate!
+    child2 = Issue.generate!(:parent_issue_id => issue2.id)
+
+    compatible_request :get, :index, agile_query_params.merge(
+      :op => { :parent_issue_id => '=' },
+      :v => { :parent_issue_id => ["#{@issue1.id}, #{issue2.id}"] },
+      :f => [:parent_issue_id],
+      :project_id => Project.order(:id).first.id
+    )
+    assert_response :success
+    assert_equal [@subissue.id, child2.id].sort, agile_issues_in_list.map(&:id).sort
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_filter_on_parent_tracker_inversed
+    create_subissue
+    compatible_request :get, :index, agile_query_params.merge(
+      :op => { :parent_issue_tracker_id => '!' },
+      :v => { :parent_issue_tracker_id => [Tracker.find(1).name] },
+      :f => [:parent_issue_tracker_id],
+      :project_id => Project.order(:id).first.id
+    )
+    assert_response :success
+    assert_not_include @subissue.id, agile_issues_in_list.map(&:id)
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_filter_on_has_subissues
+    create_subissue
+    compatible_request :get, :index, agile_query_params.merge(
+      :op => { :has_sub_issues => '=' },
+      :v => { :has_sub_issues => ['yes'] },
+      :f => [:has_sub_issues],
+      :project_id => Project.order(:id).first.id
+    )
+    assert_response :success
+    assert_equal [@issue1.id], agile_issues_in_list.map(&:id)
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_filter_and_field_time_in_state
+    create_subissue
+    columns_group_by = AgileQuery.new.groupable_columns
+    columns_group_by.each do |col|
+      compatible_request :get, :index, agile_query_params.merge(
+        :project_id => Project.order(:id).first.id,
+        :group_by => col.name.to_s
+      )
+      assert_response :success, "Error with group by #{col.name}"
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_put_update_status
+    status_id = 1
+    first_issue_id = 1
+    second_issue_id = 3
+    first_pos = 1
+    second_pos = 2
+    positions = { first_issue_id.to_s => { 'position' => first_pos }, second_issue_id.to_s => { 'position' => second_pos } }
+    compatible_xhr_request :put, :update, :id => first_issue_id, :issue => { :status_id => status_id }, :positions => positions
+    assert_response :success
+    assert_equal status_id, Issue.find(first_issue_id).status_id
+    assert_equal first_pos, Issue.find(first_issue_id).agile_data.position
+    assert_equal second_pos, Issue.find(second_issue_id).agile_data.position
+    # check js code for update board header
+    assert_match '$("table.issues-board thead").html(', @response.body
+  end
+
+  def test_put_update_version
+    fixed_version_id = 3
+    first_issue_id = 1
+    second_issue_id = 3
+    first_pos = 1
+    second_pos = 2
+    positions = { first_issue_id.to_s => { 'position' => first_pos }, second_issue_id.to_s => { 'position' => second_pos } }
+    compatible_xhr_request :put, :update, :id => first_issue_id, :issue => { :fixed_version_id => fixed_version_id }, :positions => positions
+    assert_response :success
+    assert_equal fixed_version_id, Issue.find(first_issue_id).fixed_version_id
+    assert_equal first_pos, Issue.find(first_issue_id).agile_data.position
+    assert_equal second_pos, Issue.find(second_issue_id).agile_data.position
+  end
+
+  def test_put_update_assigned
+    assigned_to_id = 3
+    issue_id = 1
+    compatible_xhr_request :put, :update, :id => issue_id, :issue => { :assigned_to_id => assigned_to_id }
+    assert_response :success
+    assert_equal assigned_to_id, Issue.find(issue_id).assigned_to_id
+  end
+
+  def test_get_index_with_all_fields
+    compatible_request :get, :index, agile_query_params.merge(:f => AgileQuery.available_columns.map(&:name))
+    assert_response :success
+  end
+  def test_get_index_with_colors
+    with_agile_settings 'color_on' => 'issue' do
+      issue = Issue.find(1)
+      issue.agile_color.color = 'red'
+      issue.save
+      compatible_request :get, :index, agile_query_params.merge(:color_base => 'issue')
+      assert_response :success
+      assert_select 'td.issue-status-col .issue-card.bk-red', 1
+    end
+  end
+
+  def test_get_index_with_colors_for_spent_time
+    with_agile_settings 'color_on' => 'spent_time' do
+      issue = Issue.find(1)
+      est_time = issue.estimated_hours
+      spent_time = issue.spent_hours
+      compatible_request :get, :index, agile_query_params.merge(:color_base => 'spent_time')
+      assert_response :success
+      assert_select "td.issue-status-col .issue-card.bk-#{AgileColor.for_spent_time(est_time, spent_time)}"
+    end
+  end
+
+  def test_get_index_with_colors_for_project
+    with_agile_settings 'color_on' => 'project' do
+      @project_1.color = AgileColor::AGILE_COLORS[:red]
+      @project_1.save
+      compatible_request :get, :index, :color_base => 'project'
+      assert_response :success
+      assert_select 'td.issue-status-col .issue-card.bk-red', @project_1.issues.open.count
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_colors_for_user
+    with_settings :gravatar_enabled => '1' do
+      with_agile_settings 'color_on' => 'user' do
+        issue = Issue.find(2)
+        user = issue.assigned_to
+        user.color = AgileColor::AGILE_COLORS[:red]
+        user.save
+        user_color = user.reload.color
+        compatible_request :get, :index, agile_query_params.merge(:color_base => 'user')
+        assert_response :success
+        assert_select 'td.issue-status-col .issue-card[data-id="2"]'
+        assert_select "img.gravatar[style='#{"border-left: 5px solid #{user_color}".html_safe}']"
+        assert response.body.include? user_color
+      end
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_colors_for_user_with_allow_issue_assignment_to_groups_settings
+    with_settings :gravatar_enabled => '1', :issue_group_assignment => '1' do
+      with_agile_settings 'color_on' => 'user' do
+        issue = Issue.find(2)
+        user = issue.assigned_to
+        user.color = AgileColor::AGILE_COLORS[:red]
+        user.save
+        user_color = user.reload.color
+        compatible_request :get, :index, agile_query_params.merge(:color_base => 'user')
+        assert_response :success
+        assert_select 'td.issue-status-col .issue-card[data-id="2"]'
+        assert_select "img.gravatar[style='#{"border-left: 5px solid #{user_color}".html_safe}']"
+        assert response.body.include? user_color
+      end
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_colors_by_user
+    with_settings :gravatar_enabled => '1' do
+      with_agile_settings 'color_on' => 'user' do
+        issue = Issue.find(2)
+        user_color = AgileColor.for_user(issue.assigned_to.login)
+        compatible_request :get, :index, agile_query_params.merge(:color_base => 'user')
+        assert_response :success
+        assert_select 'td.issue-status-col .issue-card[data-id="2"]'
+        assert_select "img.gravatar[style='#{"border-left: 5px solid #{user_color}".html_safe}']"
+        assert response.body.include? user_color
+      end
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_none_colors_by_user
+    with_settings :gravatar_enabled => '1' do
+      with_agile_settings 'color_on' => 'user' do
+        issue = Issue.find(2)
+        user_color = AgileColor.for_user(issue.assigned_to.login)
+        compatible_request :get, :index, agile_query_params.merge(:color_base => 'none')
+        assert_response :success
+        assert_select 'td.issue-status-col .issue-card[data-id="2"]'
+        assert_select "img.gravatar[style='#{"border-left: 5px solid #{user_color}".html_safe}']", false
+        assert !(response.body.include? user_color), 'Found color for user'
+      end
+    end
+  end
+
+  def test_get_index_with_swimlanes
+    compatible_request :get, :index, agile_query_params.merge(:group_by => 'priority')
+    assert_response :success
+    assert_select 'tr.group.open.swimlane[data-id="4"] td', /Low/
+    assert_select 'tr.group.open.swimlane[data-id="4"] td span.count', { :text => '5' }
+
+    AgileQuery.available_columns.select(&:groupable).each do |column|
+      compatible_request :get, :index, agile_query_params.merge(:group_by => column.name.to_s)
+      assert_response :success
+    end
+  end
+
+  def test_count_for_swimlanes
+    issues_count = Issue.where(:project_id => [@project_1.id] + @project_1.children.pluck(:id)).open.group('project_id').count
+    compatible_request :get, :index, agile_query_params.merge(:group_by => 'project')
+    assert_response :success
+    issues_count.each do |c|
+      assert_select "tr.group.open.swimlane[data-id='#{c[0]}'] td span.count", :text => c[1].to_s
+    end
+  end
+
+  def test_get_index_with_sub_issues
+    issue1 = Issue.find(1)
+    issue2 = Issue.create!(
+      :subject         => 'Sub issue',
+      :project         => issue1.project,
+      :tracker         => issue1.tracker,
+      :author          => issue1.author,
+      :parent_issue_id => issue1.id,
+      :fixed_version   => Version.last
+    )
+
+    compatible_request :get, :index, agile_query_params.merge(:c => ['sub_issues'])
+    assert_response :success
+
+    assert_select 'div.sub-issues', 1
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_proper_columns_for_default_board
+    # In Redmine 2.3 Query model doesn't have options attribute
+    # So it can't be default and so 'default' behaviour cannot be broken
+    # As it does not work at all
+    if AgileQuery.new.has_attribute?(:options)
+      AgileQuery.destroy_all
+      project1 = Project.create!(:name => 'project1', :identifier => 'project1')
+      board_params = {
+        :project => project1,
+        :name => 'board1',
+        :column_names => [:id, :author, :description],
+        :options => { :is_default => true },
+        :visibility => 2
+      }
+
+      AgileQuery.create! board_params
+      EnabledModule.create(:project => project1, :name => 'agile')
+      compatible_request :get, :index, :project_id => 'project1'
+      assert_response :success
+    end
+  end
+
+  def test_proper_columns_for_default_board_with_update
+    if AgileQuery.new.has_attribute?(:options)
+      board_params = {
+        :project => @project_1,
+        :name => 'board_for_project_1',
+        :column_names => [:id, :author, :description, :project, :tracker, :assignee],
+        :options => { :is_default => true },
+        :visibility => 2
+      }
+
+      board = AgileQuery.create! board_params
+      issues = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a).open(true)
+      compatible_request :get, :index, :project_id => @project_1, :query_id => board.id
+      assert_select '.issue-card span.fields p.issue-id strong', issues.count
+      assert_select '.issue-card span.fields p.project', issues.count
+      assert_select '.issue-card span.fields p.name', issues.count
+      assert_select '.issue-card span.fields p.attributes a', issues.count
+      compatible_request :put, :update, :issue => { :status_id => (issues.first.status_id + 1) }, :id => issues.first.id
+      # update action return html for one card but with same fields
+      assert_select '.issue-card span.fields p.issue-id strong', 1
+      assert_select '.issue-card span.fields p.project', 1
+      assert_select '.issue-card span.fields p.name', 1
+      assert_select '.issue-card span.fields p.attributes a', 1
+    end
+  end
+
+  def test_short_card_for_closed_issue
+    with_agile_settings 'hide_closed_issues_data' => '1' do
+      closed_status = IssueStatus.where(:is_closed => true).pluck(:id)
+      closed_issues = Issue.where(:status_id => closed_status)
+      project = closed_issues.first.project
+
+      # get :index, agile_query_params.merge(:f_status => closed_status)
+      if Redmine::VERSION.to_s > '2.4'
+        compatible_request :get, :index, agile_query_params.merge(:f_status => closed_status)
+      else
+        compatible_request :get, :index, agile_query_params.merge('f' => [''])
+      end
+
+      assert_response :success
+      assert_select '.closed-issue', project.issues.where(:status_id => IssueStatus.where(:is_closed => true)).count
+    end
+  end
+
+  def test_get_tooltip_for_issue
+    issue = Issue.where(:status_id => IssueStatus.where(:is_closed => true)).first
+    compatible_request :get, :issue_tooltip, :id => issue.id
+    assert_response :success
+    assert_select 'a.issue', 1
+    assert_select 'strong', 6
+    assert_match issue.status.name, @response.body
+  end
+
+  def test_empty_node_for_tooltip
+    with_agile_settings 'hide_closed_issues_data' => '1' do
+      closed_status = IssueStatus.where(:is_closed => true).pluck(:id)
+      if Redmine::VERSION.to_s > '2.4'
+        compatible_request :get, :index, agile_query_params.merge(:f_status => closed_status)
+      else
+        compatible_request :get, :index, agile_query_params.merge('f' => [''])
+      end
+      assert_select 'span.tip', { :text => '' }
+    end
+  end
+
+  def test_setting_for_closed_issues
+    with_agile_settings 'hide_closed_issues_data' => '0' do
+      closed_issues = Issue.where(:status_id => IssueStatus.where(:is_closed => true))
+      project = closed_issues.first.project
+      compatible_request :get, :index, agile_query_params.merge('f' => [''])
+      assert_response :success
+      assert_select '.closed-issue', 0
+    end
+  end
+
+  def test_index_with_js_format
+    with_agile_settings 'hide_closed_issues_data' => '1' do
+      closed_issues = Issue.where(:status_id => IssueStatus.where(:is_closed => true))
+      project = closed_issues.first.project
+      compatible_xhr_request :get, :index, agile_query_params.merge('f' => [''], :format => :js)
+      assert_response :success
+      assert_match "$('.tooltip').mouseenter(callGetToolTipInfo)", @response.body
+    end
+  end
+
+  def test_get_index_with_day_in_state_and_parent_group
+    compatible_request :get, :index, agile_query_params.merge(:c => ['day_in_state'], :group_by => 'parent')
+    assert_response :success
+  end
+
+  def test_assinged_to_and_in_state_in_index
+    issue1 = Issue.find(1)
+    issue2 = Issue.create!(
+      :subject         => 'Test assigned_to with day in state',
+      :project         => issue1.project,
+      :tracker         => issue1.tracker,
+      :author          => issue1.author,
+      :fixed_version   => Version.last
+    )
+    compatible_request :get, :index, agile_query_params.merge(:c => ['day_in_state'], :group_by => 'parent')
+    assigned_to_id = 3
+    issue_id = issue2.id
+    compatible_xhr_request :put, :update, :id => issue_id, :issue => { :assigned_to_id => assigned_to_id }
+    assert_response :success
+    issue2.reload
+    assert_equal assigned_to_id, issue2.assigned_to_id
+  end
+
+  def test_index_with_relations_relates
+    create_issue_relation
+    compatible_request :get, :index, agile_query_params.merge(:f_status => IssueStatus.pluck(:id), :op => { :status_id => '*', :relates => '*' }, :f => ['relates'])
+    assert_response :success
+    assert_equal [1, 7, 8], agile_issues_in_list.map(&:id).sort
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_index_with_relations_blocked
+    create_issue_relation
+
+    compatible_request :get, :index, agile_query_params.merge(:f_status => IssueStatus.pluck(:id), :op => { :status_id => '*', :blocked => '*' }, :f => ['blocked'])
+    assert_response :success
+    assert_equal [2, 11], agile_issues_in_list.map(&:id).sort
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_list_of_attributes_on_update_status
+    params = agile_query_params.merge(:c => ['project', 'day_in_state'], :group_by => 'parent')
+    params.delete(:project_id)
+    compatible_request :get, :index, params
+    assert_response :success
+    assert_select 'p.project'
+    compatible_request :put, :update, :issue => { :status_id => 2 }, :id => 1
+    assert_select 'p.project', { :text => @project_1.to_s }
+  end
+
+  def test_day_in_state_when_change_status
+    @request.session[:agile_query] = {}
+    @request.session[:agile_query][:column_names] = ['project', 'day_in_state']
+    issue = Issue.find(1)
+    compatible_request :put, :update, :issue => { :status_id => 2 }, :id => 1
+    issue.reload
+    assert_response :success
+    assert_equal 2, issue.status_id
+    assert_select 'p.attributes', /0.hours/
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_global_query_and_project_board
+    query = AgileQuery.create(:name => 'global', :column_names => ['project'], :visibility => 2, :options => { :is_default => true })
+    compatible_request :get, :index, :project_id => 1
+    assert_select 'p.project', { :count => 0, :text => Project.find(2).to_s }
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_total_estimated_hours_for_status
+    [1, 2, 3].each do |id|
+      issue = Issue.find(id)
+      issue.estimated_hours = 10
+      issue.save
+    end
+    compatible_request :get, :index, agile_query_params.merge(:c => ['estimated_hours'])
+    assert_response :success
+    assert_select 'thead tr th span.hours', { :count => 1, :text => '20.00h' } # status_id == 1
+    assert_select 'thead tr th span.hours', { :count => 1, :text => '10.00h' } # status_id == 2
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_get_index_with_checklist
+    # global board
+    issue1 = issue_with_checklist
+
+    compatible_request :get, :index, agile_query_params.merge(:c => ['checklists'], :project_id => nil)
+    assert_response :success
+    # check checklist nodes
+    assert_select "#checklist_#{issue1.id}"
+    issue1.checklists.each do |checklist|
+      assert_select "#checklist_item_#{checklist.id}", :text => checklist.subject
+    end
+  end if RedmineAgile.use_checklist?
+
+  def test_get_index_without_checklist
+    issue1 = issue_with_checklist
+    compatible_request :get, :index, :c => []
+    assert_response :success
+    # check checklist nodes
+    assert_select "#checklist_#{issue1.id}", :count => 0
+    issue1.checklists.each do |checklist|
+      assert_select "#checklist_item_#{checklist.id}", :count => 0
+    end
+  end if RedmineAgile.use_checklist?
+
+  def test_get_index_with_project_with_checklist
+    issue1 = issue_with_checklist
+    compatible_request :get, :index, agile_query_params.merge(:c => ['checklists'], :project_id => issue1.project)
+    assert_response :success
+    # check checklist nodes
+    assert_select "#checklist_#{issue1.id}"
+    issue1.checklists.each do |checklist|
+      assert_select "#checklist_item_#{checklist.id}", :text => checklist.subject
+    end
+  end if RedmineAgile.use_checklist?
+
+  def test_get_index_with_project_without_checklist
+    issue1 = issue_with_checklist
+    compatible_request :get, :index, agile_query_params.merge(:project_id => issue1.project, :c => [])
+    assert_response :success
+    # check checklist nodes
+    assert_select "#checklist_#{issue1.id}", :count => 0
+    issue1.checklists.each do |checklist|
+      assert_select "#checklist_item_#{checklist.id}", :count => 0
+    end
+  end if RedmineAgile.use_checklist?
+
+  def test_get_index_check_quick_edit_without_permission
+    role = Role.find(2)
+    role.permissions << :veiw_issues
+    role.permissions << :view_agile_queries
+    role.permissions.delete(:edit_issues)
+    role.save
+    @request.session[:user_id] = 3
+
+    compatible_request :get, :index, :project_id => @project_1
+    issues = Issue.where(:project_id => [@project_1], :status_id => IssueStatus.where(:is_closed => false))
+    assert_response :success
+    assert_select '.issue-card', agile_issues_in_list.count
+    assert_select '.issue-card .quick-edit-card a', :count => 0
+  end
+
+  def test_last_comment_on_issue_cart
+    issue = @project_1.issues.open.first
+    text_comment = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus in blandit ex. Donec vitae urna quis tortor tempor mollis.'
+    comment = Journal.new(:notes => text_comment, :user_id => 1)
+    issue.journals << comment
+    params = agile_query_params.merge(:c => ['project', 'last_comment'], :group_by => 'parent')
+    compatible_request :get, :index, params
+    assert_response :success
+    assert_select 'span.last-comment', :text => text_comment.truncate(100)
+    compatible_request :put, :update, :issue => { :status_id => 2 }, :id => issue.id
+    assert_select 'span.last-comment', :text => text_comment.truncate(100)
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_show_sp_value_on_issue_cart
+    with_agile_settings 'estimate_units' => 'story_points' do
+      issues = @project_1.issues.open.first(3)
+      issues.each do |issue|
+        issue.agile_data.story_points = issue.id * 10
+        issue.save
+      end
+      params = agile_query_params.merge(:c => ['story_points'])
+      compatible_request :get, :index, params
+      IssueStatus.where(:id => @project_1.issues.open.joins(:status).pluck(:status_id).uniq).each do |status|
+        sp_sum = @project_1.issues.eager_load(:agile_data).where(:status_id => status.id).sum("#{AgileData.table_name}.story_points")
+        next unless sp_sum.to_i > 0
+        assert_select "th[data-column-id='#{status.id}'] span.hours", :text =>"#{sp_sum}sp"
+      end
+      issues.each do |issue|
+        assert_select ".issue-card[data-id='#{issue.id}'] span.fields p.issue-id span.hours", :text =>"(#{issue.story_points}sp)"
+      end
+      # check story_points in available_columns for query
+      assert_select 'input[value="story_points"]'
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_show_sp_with_estimated_hours_on_issue_cart
+    with_agile_settings 'estimate_units' => 'story_points' do
+      issues = @project_1.issues.open.first(3)
+      issues.each do |issue|
+        issue.agile_data.story_points = issue.id * 10
+        issue.estimated_hours = issue.id * 2
+        issue.save
+      end
+      params = agile_query_params.merge(:c => ['story_points', 'estimated_hours'])
+      compatible_request :get, :index, params
+      # in a header show only story_points!
+      IssueStatus.where(:id => @project_1.issues.open.joins(:status).pluck(:status_id).uniq).each do |status|
+        sp_sum = @project_1.issues.eager_load(:agile_data).where(:status_id => status.id).sum("#{AgileData.table_name}.story_points")
+        next unless sp_sum.to_i > 0
+        assert_select "th[data-column-id='#{status.id}'] span.hours", :text =>"#{sp_sum}sp"
+      end
+      # in a card show and estimated hours and story points
+      issues.each do |issue|
+        assert_select ".issue-card[data-id='#{issue.id}'] span.fields p.issue-id span.hours",
+                      :text => "(#{"%.2fh" % issue.estimated_hours.to_f}/#{issue.story_points}sp)"
+      end
+    end if Redmine::VERSION.to_s > '2.4'
+  end
+
+  def test_quick_add_comment_button
+    with_agile_settings 'allow_inline_comments' => 1 do
+      compatible_request :get, :index, agile_query_params
+      assert_response :success
+      assert_select '.quick-edit-card img[alt="Edit"]'
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_quick_add_comment_form
+    compatible_request :get, :inline_comment, :id => @project_1.issues.open.first
+    assert_response :success
+    assert_select 'textarea'
+    assert_select 'button'
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_quick_add_comment_update
+    issue = @project_1.issues.open.first
+    compatible_request :put, :update, :issue => { :notes => 'new comment!!!' }, :id => issue
+    assert_response :success
+    assert_select '.last_comment', :text => 'new comment!!!'
+  end if Redmine::VERSION.to_s > '2.4'
+  def test_wp_max_count_and_style
+    statuses = IssueStatus.pluck(:id)
+    compatible_request :get, :index, 'set_filter' => '1', :project_id => @project_1, :f_status => statuses, :wp => statuses.inject({}){ |r,s| r.merge!(s.to_s => '5') }
+    issues_for_status = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a).group(:status_id).count
+    issues_for_status.each do |status_id, issues_count|
+      if issues_count > 5
+        assert_select "th.over_wp_limit[data-column-id='#{status_id}']"
+        assert_select "th.over_wp_limit[data-column-id='#{status_id}'] span.count span.over_wp_limit", :text => issues_count.to_s
+      end
+      assert_select "th[data-column-id='#{status_id}'] span.count", :text => "#{issues_count}/5"
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_wp_min_count_and_style
+    statuses = IssueStatus.pluck(:id)
+    issues_for_status = Issue.where(:project_id => [@project_1] + Project.where(:parent_id => @project_1.id).to_a).group(:status_id).count
+    wp_limits = issues_for_status.inject({}) do |h, (k, v)|
+      h.merge!(k.to_s => "#{v.to_i + 1}-100")
+    end
+
+    compatible_request :get, :index, 'set_filter' => '1', :project_id => @project_1, :f_status => statuses, :wp => wp_limits
+    issues_for_status.each do | status_id, issues_count|
+      if issues_count > 0
+        assert_select "th.under_wp_limit[data-column-id='#{status_id}']"
+        assert_select "th.under_wp_limit[data-column-id='#{status_id}'] span.count span.under_wp_limit", :text => issues_count.to_s
+      end
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_card_for_new_issue
+    with_agile_settings 'allow_create_card' => 1 do
+      statuses = IssueStatus.all
+      compatible_request :get, :index, 'set_filter' => '1', :project_id => @project_1, :f_status => statuses.map(&:id)
+      assert_select '.add-issue input.new-card__input', IssueStatus.where(:is_closed => false).count
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_not_show_card_for_new_issue_without_permission
+    with_agile_settings 'allow_create_card' => 1 do
+      role = Role.find(2)
+      role.permissions << :veiw_issues
+      role.permissions << :view_agile_queries
+      role.permissions.delete(:add_issues)
+      role.save
+      @request.session[:user_id] = 3
+      statuses = IssueStatus.all
+      compatible_request :get, :index, 'set_filter' => '1', :project_id => @project_1, :f_status => statuses.map(&:id)
+      assert_select '.add-issue input.new-card__input', 0
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_create_issue_from_board
+    with_agile_settings 'allow_create_card' => 1 do
+      assert_difference 'Issue.count' do
+        compatible_request :post, :create_issue, :project_id => @project_1, :subject => 'new issue from board', :status_id => 1
+      end
+      assert_response :success
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_create_issue_from_board_with_empty_subject
+    with_agile_settings 'allow_create_card' => 1 do
+      assert_no_difference 'Issue.count' do
+        compatible_request :post, :create_issue, :project_id => @project_1, :subject => '', :status_id => 1
+      end
+      assert_response 500
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_create_issue_from_board_when_subject_consists_of_only_spaces
+    with_agile_settings "allow_create_card" => 1 do
+      assert_no_difference 'Issue.count' do
+        compatible_request :post, :create_issue, :project_id => @project_1, :subject => '   ', :status_id => 1
+      end
+      assert_response 500
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_create_issue_from_board_without_permission
+    with_agile_settings 'allow_create_card' => 1 do
+      role = Role.find(2)
+      role.permissions << :veiw_issues
+      role.permissions << :view_agile_queries
+      role.permissions.delete(:add_issues)
+      role.save
+      @request.session[:user_id] = 3
+      assert_no_difference 'Issue.count' do
+        compatible_request :post, :create_issue, :project_id => @project_1, :subject => 'new issue from board', :status_id => 1
+      end
+      assert_response 500
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_on_auto_assign_on_move
+    with_agile_settings 'auto_assign_on_move' => '1' do
+      @request.session[:user_id] = 2
+      issue = Issue.find(1)
+
+      user = issue.project.users.first
+      @request.session[:user_id] = user.id
+
+      assert_nil issue.assigned_to
+      compatible_request :put, :update, :issue => { :status_id => 2 }, :id => 1
+      issue.reload
+      assert_response :success
+      assert_equal 2, issue.status_id
+      assert_equal user, issue.assigned_to
+    end
+  end
+
+  def test_off_auto_assign_on_move
+    with_agile_settings 'auto_assign_on_move' => '0' do
+      issue = Issue.find(1)
+
+      user = issue.project.users.first
+      @request.session[:user_id] = user.id
+
+      assert_nil issue.assigned_to
+      compatible_request :put, :update, :issue => { :status_id => 2 }, :id => 1
+      issue.reload
+      assert_response :success
+      assert_equal 2, issue.status_id
+      assert_nil issue.assigned_to
+    end
+  end
+
+  def test_off_auto_assign_on_move_by_sorting
+    with_agile_settings 'auto_assign_on_move' => '1' do
+      @request.session[:user_id] = 2
+      issue = Issue.find(1)
+
+      user = issue.project.users.first
+      @request.session[:user_id] = user.id
+
+      assert_nil issue.assigned_to
+      compatible_request :put, :update, :issue => { :status_id => issue.status_id }, :id => 1
+      issue.reload
+      assert_response :success
+      assert_nil issue.assigned_to
+    end
+  end
+
+  def test_option_for_select_current_version
+    @request.session[:user_id] = 1
+    compatible_request :get, :index, :project_id => @project_1
+    assert_response :success
+    assert_match 'current_version', response.body
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_filter_by_current_version
+    @request.session[:user_id] = 1
+    compatible_request :get, :index, :project_id => @project_1, 'v' => { 'fixed_version_id' => ['current_version'] },
+                                                                'set_filter' => '1', 'f' => ['fixed_version_id'],
+                                                                'op' => { 'fixed_version_id' => '=' }
+    assert_response :success
+    current_version = @project_1.shared_versions.order(:effective_date).first
+    issue = @project_1.issues.first
+    issue.fixed_version = current_version
+    issue.save
+    assert agile_issues_in_list.all? { |i| i.fixed_version == current_version }
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_filter_by_current_version_closed
+    @request.session[:user_id] = 1
+    current_version = @project_1.shared_versions.order(:effective_date).first
+    current_version.status = 'closed'
+    current_version.save
+
+    compatible_request :get, :index, :project_id => @project_1, 'v' => { 'fixed_version_id' => ['current_version'] },
+                                                                'set_filter' => '1', 'f' => ['fixed_version_id'],
+                                                                'op' => { 'fixed_version_id' => '=' }
+    assert_response :success
+    issue = @project_1.issues.first
+    issue.fixed_version = current_version
+    issue.save
+    assert agile_issues_in_list.all?{ |i| i.fixed_version != current_version }
+    # check set filter for current version
+    assert_match 'addFilter("fixed_version_id", "=", ["current_version"])', response.body
+  end if Redmine::VERSION.to_s > '2.4'
+
+  private
+
+  def agile_query_params
+    { :set_filter => '1', :f => ['status_id', ''], :op => { :status_id => 'o' }, :c => ['tracker', 'assigned_to'], :project_id => 'ecookbook' }
+  end
+
+  def create_issue_relation
+    IssueRelation.delete_all
+    IssueRelation.create!(:relation_type => 'relates', :issue_from => Issue.find(1), :issue_to => Issue.find(7))
+    IssueRelation.create!(:relation_type => 'relates', :issue_from => Issue.find(8), :issue_to => Issue.find(1))
+    IssueRelation.create!(:relation_type => 'blocks', :issue_from => Issue.find(1), :issue_to => Issue.find(11))
+    IssueRelation.create!(:relation_type => 'blocks', :issue_from => Issue.find(12), :issue_to => Issue.find(2))
+  end
+
+  def issue_with_checklist
+    issue1 = Issue.find(1)
+    chk1 = issue1.checklists.create(:subject => 'TEST1', :position => 1)
+    chk2 = issue1.checklists.create(:subject => 'TEST2', :position => 2)
+    issue1
+  end if RedmineAgile.use_checklist?
+
+end
diff --git a/plugins/redmine_agile/test/functional/agile_charts_controller_test.rb b/plugins/redmine_agile/test/functional/agile_charts_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..02b51f7a9e97a759d7b484f0d9c9242819838b34
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/agile_charts_controller_test.rb
@@ -0,0 +1,150 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileChartsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  def test_get_show
+    @request.session[:user_id] = 1
+    compatible_request :get, :show
+    assert_response :success
+    assert_select 'canvas#agile-chart', 1
+  end
+
+  def test_get_render_chart_issues_burndown_with_version
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'issues_burndown', :version_id => 2
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_issues_burndown
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'issues_burndown'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_work_burndown_sp
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'work_burndown_sp'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_work_burndown_hours
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'work_burndown_hours'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_burnup
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'burnup'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_work_burnup_sp
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'work_burnup_sp'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_work_burnup_hours
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'work_burnup_hours'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_trackers_cumulative_flow
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'trackers_cumulative_flow'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_cumulative_flow
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'cumulative_flow'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_issues_velocity
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'issues_velocity'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_lead_time
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'lead_time'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_get_render_chart_average_lead_time
+    @request.session[:user_id] = 1
+    compatible_request :get, :render_chart, :chart => 'average_lead_time'
+    assert_response :success
+    assert_equal 'application/json', @response.content_type
+  end
+
+  def test_issues_burndown_chart_when_first_issue_later_then_due_date
+    @project_1 = Project.find(1)
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    @request.session[:user_id] = 1
+    new_version = Version.create!(:name => 'Some new vesion', :effective_date => (Date.today - 10.days), :project_id => @project_1.id)
+    issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_issues_burndown_chart_when_first_issue_later_then_due_date', :author_id => 2, :start_date => Date.today)
+    new_version.fixed_issues << issue
+    compatible_request :get, :render_chart, :chart => 'issues_burndown', :project_id => 1, :version_id => new_version.id
+    assert_response :success
+  end
+end
diff --git a/plugins/redmine_agile/test/functional/agile_journal_details_controller_test.rb b/plugins/redmine_agile/test/functional/agile_journal_details_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d63aa5c35b692d410ba5325cf9be5b08092de479
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/agile_journal_details_controller_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileJournalDetailsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :issue_relations,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  fixtures :email_addresses if Redmine::VERSION.to_s > '3.0'
+
+  def setup
+    @project = Project.find(1)
+    EnabledModule.create(:project => @project, :name => 'agile')
+    @request.session[:user_id] = 1
+  end
+
+  def test_get_done_ratio
+    compatible_request :get, :done_ratio, :issue_id => 1
+    assert_response :success
+    assert_match /% Done/, @response.body
+    assert_match /Bug #1/, @response.body
+    assert_select 'table.progress', 2
+  end
+
+  def test_get_status
+    compatible_request :get, :status, :issue_id => 1
+    assert_response :success
+    assert_match /Issue statuses/, @response.body
+    assert_match /Bug #1/, @response.body
+    assert_select '.list td.name', 2
+  end
+
+  def test_get_done_assignee
+    compatible_request :get, :assignee, :issue_id => 1
+    assert_response :success
+    assert_match /Assignee/, @response.body
+    assert_match /Bug #1/, @response.body
+    assert_select '.list td a.user', 1
+  end
+end
diff --git a/plugins/redmine_agile/test/functional/agile_queries_controller_test.rb b/plugins/redmine_agile/test/functional/agile_queries_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..37edd27b184d4d2b91f532657a5ef958b0dfbec9
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/agile_queries_controller_test.rb
@@ -0,0 +1,134 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+class AgileQueriesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineAgile::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_agile).directory + '/test/fixtures/', [:queries])
+
+  def setup
+    RedmineAgile::TestCase.prepare
+  end
+
+  def test_get_index
+    @request.session[:user_id] = 1
+    compatible_request :get, :index
+    assert_response :success
+    assert_match /Agile boards/, @response.body
+  end
+
+  def test_get_new
+    @request.session[:user_id] = 1
+    compatible_request :get, :new
+    assert_response :success
+    assert_match /New agile board/, @response.body
+  end
+
+  def test_get_edit
+    @request.session[:user_id] = 1
+    compatible_request :get, :edit, :id => create_agile_query.id
+    assert_response :success
+    assert_match /Edit agile board/, @response.body
+  end
+
+  def test_get_edit_for_public_board
+    @request.session[:user_id] = 2
+    compatible_request :get, :edit, id: AgileQuery.find(102).id # Public board query
+    assert_response :success
+    assert_match /Edit agile board/, @response.body
+  end
+
+  def test_post_create
+    @request.session[:user_id] = 1
+    params = { :query => { :name => 'Test', :group_by => '' },
+               :query_is_for_all => '1', :default_columns => '1', :f => ['status_id', ''],
+               :op => { 'status_id' => 'o' }, :c => ['tracker', 'assigned_to'] }
+    if Redmine::VERSION.to_s < '2.4'
+      params[:query][:is_public] = true
+    else
+      params[:query][:visibility] = '0'
+    end
+    assert_difference 'AgileQuery.count' do
+      compatible_request :post, :create, params
+      assert_response :redirect
+    end
+  end
+
+  def test_put_update
+    @request.session[:user_id] = 1
+    params = { :query => { :name => 'Test changed', :group_by => ''}, :id => create_agile_query.id}
+    Redmine::VERSION.to_s < '2.4' ? params[:query][:is_public] = true : params[:query][:visibility] = '0'
+    compatible_request :put, :update, params
+    assert_response :redirect
+  end
+  def test_save_wip_options
+    @request.session[:user_id] = 1
+    wp_params = { '1' => '5', '2' => '5', '3' => '5', '4' => '5', '5' => '', '6' => '' }
+    params = { :query => { :name => 'Test', :group_by => '', :is_default => '1' },
+               :query_is_for_all => '1',
+               :default_columns => '1',
+               :f_status => ['1', '2', '3', '4'],
+               :wp => wp_params,
+               :op => { 'status_id' => 'o' },
+               :c => ['tracker', 'assigned_to'] }
+    compatible_request :post, :create, params
+    query = Query.last
+    assert_equal true, query.is_default?
+    assert_equal wp_params, query.options[:wp]
+    assert_equal ['1', '2', '3', '4'], query.options[:f_status]
+  end if Redmine::VERSION.to_s > '2.4'
+
+private
+
+  def create_agile_query
+    query = AgileQuery.new(:name => 'Board for specific project',
+                           :user_id => 1,
+                           :project_id => 1,
+                           :filters => { :tracker_id => { :values => ['3'], :operator => '=' } })
+    Redmine::VERSION.to_s < '2.4' ? query.is_public = false : query.visibility = 2
+    query.save
+    query
+  end
+
+end
diff --git a/plugins/redmine_agile/test/functional/agile_versions_controller_test.rb b/plugins/redmine_agile/test/functional/agile_versions_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c193859714ce128845d4d43551e20fc2545544d
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/agile_versions_controller_test.rb
@@ -0,0 +1,124 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileVersionsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  def setup
+    RedmineAgile.create_issues
+
+    @project_1 = Project.find(1)
+    @project_2 = Project.find(2)
+    @project_3 = Project.find(5)
+
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    EnabledModule.create(:project => @project_2, :name => 'agile')
+    EnabledModule.create(:project => @project_3, :name => 'agile')
+
+    @request.session[:user_id] = 1
+  end
+
+  def test_get_index
+    compatible_request :get, :index, :project_id => @project_1
+    assert_response :success
+    assert_match /Version planning/, @response.body
+  end
+
+  def test_get_load
+    compatible_xhr_request :get, :load, :version_type => 'backlog', :version_id => '3', :project_id => 'ecookbook'
+    assert_response :success
+  end
+
+  def test_get_autocomplete_id
+    compatible_xhr_request :get, :autocomplete, :project_id => 'ecookbook', :q => '#3'
+    assert_response :success
+    assert_match "Error 281",  @response.body
+  end
+
+  def test_get_autocomplete_text
+    compatible_xhr_request :get, :autocomplete, :project_id => 'ecookbook', :q => 'error'
+    assert_response :success
+    assert_match "Error 281",  @response.body
+  end
+  def test_get_index_with_filter
+    compatible_request :get, :index, version_params
+
+    assert_response :success
+
+    assert_match /Issue 108/, @response.body
+    assert_match /Blaaa/, @response.body
+    assert_match /Issue 105/, @response.body
+    assert_match /Issue 106/, @response.body
+
+    assert_no_match /(Issue 100)|(Issue 101)|(Issue 102)|(Issue 103)/, @response.body
+  end
+
+  def test_get_index_with_filter_and_search_query
+    compatible_request :get, :index, version_params.merge({ :q => 'Blaaa' })
+
+    assert_no_match /Issue 108/,  @response.body
+    assert_match /Blaaa/,  @response.body
+  end
+
+  def test_get_index_with_sp
+    with_agile_settings "estimate_units" => "story_points" do
+      compatible_request :get, :index, :project_id => @project_2
+      assert_response :success
+      assert_select '.issue-card[data-id="100"] span.hours', :text => '10.00sp'
+    end
+  end
+
+  private
+
+  def version_params
+    {
+      :f =>['status_id'],
+      :op => { 'status_id' => '=' },
+      :v => { 'status_id' => ['5'] },
+      :project_id => @project_2
+    }
+  end
+
+end
diff --git a/plugins/redmine_agile/test/functional/issues_controller_test.rb b/plugins/redmine_agile/test/functional/issues_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ea9766f0a5ef2946e02e5f6d8889410490ff904
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/issues_controller_test.rb
@@ -0,0 +1,163 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class IssuesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  def setup
+    @project_1 = Project.find(1)
+    @project_2 = Project.find(5)
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    EnabledModule.create(:project => @project_2, :name => 'agile')
+    @request.session[:user_id] = 1
+  end
+  def test_get_index_with_colors
+    with_agile_settings "color_on" => "issue" do
+      issue = Issue.find(1)
+      issue.color = AgileColor::AGILE_COLORS[:red]
+      issue.save
+      compatible_request :get, :index
+      assert_response :success
+      assert_select 'tr#issue-1.issue.bk-red', 1
+    end
+  end
+
+  def test_post_issue_journal_color
+    with_agile_settings 'color_on' => 'issue' do
+      compatible_request :put, :update, :id => 1, :issue => { :agile_color_attributes => { :color => AgileColor::AGILE_COLORS[:red] } }
+      issue = Issue.find(1)
+      details = issue.journals.order(:id).last.details.last
+      assert issue.color
+      assert_equal 'color', details.prop_key
+      assert_equal AgileColor::AGILE_COLORS[:red], details.value
+    end
+  end
+
+  def test_new_issue_with_sp_value
+    with_agile_settings 'estimate_units' => 'story_points' do
+      compatible_request :get, :new, :project_id => 1
+      assert_response :success
+      assert_select 'input#issue_agile_data_attributes_story_points'
+    end
+  end
+
+  def test_new_issue_without_sp_value
+    with_agile_settings 'estimate_units' => 'hours' do
+      compatible_request :get, :new, :project_id => 1
+      assert_response :success
+      assert_select 'input#issue_agile_data_attributes_story_points', :count => 0
+    end
+  end
+
+  def test_create_issue_with_sp_value
+    with_agile_settings 'estimate_units' => 'story_points' do
+      assert_difference 'Issue.count' do
+        compatible_request :post, :create, :project_id => 1, :issue => {
+          :subject => 'issue with sp',
+          :tracker_id => 3,
+          :status_id => 1,
+          :priority_id => IssuePriority.first.id,
+          :agile_data_attributes => { :story_points => 50 }
+        }
+      end
+      issue = Issue.last
+      assert_equal 'issue with sp', issue.subject
+      assert_equal 50, issue.story_points
+    end
+  end
+
+  def test_post_issue_journal_story_points
+    with_agile_settings 'estimate_units' => 'story_points' do
+      compatible_request :put, :update, :id => 1, :issue => { :agile_data_attributes => { :story_points => 100 } }
+      issue = Issue.find(1)
+      assert_equal 100, issue.story_points
+      sp_history = JournalDetail.where(:property => 'attr', :prop_key => 'story_points', :journal_id => issue.journals).last
+      assert sp_history
+      assert_equal 100, sp_history.value.to_i
+    end
+  end
+
+  def test_show_issue_with_story_points
+    with_agile_settings 'estimate_units' => 'story_points' do
+      compatible_request :get, :show, :id => 1
+      assert_response :success
+      assert_select '.attributes', :text => /Story points/, :count => 1
+    end
+  end
+
+  def test_show_issue_with_order_by_story_points
+    session[:issue_query] = { :project_id => Issue.find(1).project_id,
+                              :filters => { 'status_id' => { :operator => 'o', :values => [''] } },
+                              :group_by => '',
+                              :column_names => [:tracker, :status, :story_points],
+                              :totalable_names => [],
+                              :sort => [['story_points', 'asc'], ['id', 'desc']]
+                            }
+    with_agile_settings 'estimate_units' => 'story_points' do
+      compatible_request :get, :show, :id => 1
+      assert_response :success
+      assert_select '.attributes', :text => /Story points/, :count => 1
+    end
+  ensure
+    session[:issue_query] = {}
+  end
+  def test_show_issue_form_with_story_points_select
+    with_agile_settings('sp_values' => [1,2,3],
+      'estimate_units' => 'story_points') do
+        compatible_request :get, :new, :project_id => 1
+        assert_response :success
+        assert_select 'select#issue_agile_data_attributes_story_points'
+    end
+  end
+
+  def test_dont_show_story_points_select_when_no_sp_values
+    with_agile_settings('sp_values' => [],
+      'estimate_units' => 'story_points') do
+        compatible_request :get, :new, :project_id => 1
+        assert_response :success
+        assert_select 'select#issue_agile_data_attributes_story_points', :count => 0
+    end
+  end
+end
diff --git a/plugins/redmine_agile/test/functional/projects_controller_test.rb b/plugins/redmine_agile/test/functional/projects_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..da7e3f232c0526b9d71f606b04bf9ccf33d7b282
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/projects_controller_test.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ProjectsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles
+
+
+  def setup
+    @project_1 = Project.find(1)
+    @project_2 = Project.find(5)
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    EnabledModule.create(:project => @project_2, :name => 'agile')
+    @request.session[:user_id] = 1
+  end
+  def test_get_index_with_colors
+    with_agile_settings 'color_on' => 'project' do
+      compatible_request :get, :settings, :id => @project_1
+      assert_response :success
+      assert_select '#project_agile_color_attributes_color', 1
+    end
+  end
+
+
+  def test_save_project_with_color
+    with_agile_settings 'color_on' => 'project' do
+      compatible_request :post, :update, :id => @project_1, :project => { :name => 'Test changed name',
+        :agile_color_attributes => { :color => AgileColor::AGILE_COLORS[:red] } }
+      @project_1.reload
+      assert_equal 'Test changed name', @project_1.name
+      assert_equal AgileColor::AGILE_COLORS[:red], @project_1.color
+    end
+  end
+end
diff --git a/plugins/redmine_agile/test/functional/users_controller_test.rb b/plugins/redmine_agile/test/functional/users_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..213f427aec1f6c95bddfea34304cdfb863069576
--- /dev/null
+++ b/plugins/redmine_agile/test/functional/users_controller_test.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class UsersControllerTest < ActionController::TestCase
+  fixtures :users,
+           :roles,
+           :members,
+           :member_roles
+
+  fixtures :email_addresses if Redmine::VERSION.to_s > '3.0'
+
+  def setup
+    @user = User.find(1)
+    @request.session[:user_id] = @user.id
+  end
+  def test_get_index_with_colors
+    with_agile_settings 'color_on' => 'user' do
+      compatible_request :get, :edit, :id => @user.id
+      assert_response :success
+      assert_select '#user_agile_color_attributes_color', 1
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+
+  def test_save_user_with_color
+    with_agile_settings 'color_on' => 'user' do
+      compatible_request :post, :update, :id => @user.id,
+        :user => { :agile_color_attributes => { :color => AgileColor::AGILE_COLORS[:red] } }
+      assert_equal AgileColor::AGILE_COLORS[:red], @user.reload.color
+    end
+  end if Redmine::VERSION.to_s > '2.4'
+end
diff --git a/plugins/redmine_agile/test/integration/common_views_test.rb b/plugins/redmine_agile/test/integration/common_views_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c595266352d962043802e15043f3a48a3d861185
--- /dev/null
+++ b/plugins/redmine_agile/test/integration/common_views_test.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+
+class RedmineAgile::CommonViewsTest < ActionDispatch::IntegrationTest
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  def setup
+    @project_1 = Project.find(1)
+    EnabledModule.create(:project => @project_1, :name => 'agile')
+    EnabledModule.create(:project => @project_1, :name => 'gantt')
+    EnabledModule.create(:project => @project_1, :name => 'calendar')
+  end
+
+  test 'View issues' do
+    log_user('admin', 'admin')
+    compatible_request :get, '/issues'
+    assert_response :success
+  end
+
+  test 'View Gantt chart' do
+    log_user('admin', 'admin')
+    compatible_request :get, '/projects/ecookbook/issues/gantt'
+    assert_response :success
+  end
+
+  test 'View Calendar' do
+    log_user('admin', 'admin')
+    compatible_request :get, '/projects/ecookbook/issues/calendar'
+    assert_response :success
+  end
+
+  test 'View agile settings' do
+    log_user('admin', 'admin')
+    compatible_request :get, '/settings/plugin/redmine_agile'
+    assert_response :success
+  end
+
+  test 'View version' do
+    log_user('admin', 'admin')
+    compatible_request :get, '/versions/2'
+    assert_response :success
+  end
+
+end
diff --git a/plugins/redmine_agile/test/test_helper.rb b/plugins/redmine_agile/test/test_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..efa1661bab4a2cf0fd794145cf4a08407c2f64a2
--- /dev/null
+++ b/plugins/redmine_agile/test/test_helper.rb
@@ -0,0 +1,362 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+# Load the Redmine helper
+require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
+
+def with_agile_settings(options, &block)
+  saved_settings = options.keys.inject({}) do |h, k|
+    h[k] = case Setting.plugin_redmine_agile[k]
+      when Symbol, false, true, nil
+        Setting.plugin_redmine_agile[k]
+      else
+        Setting.plugin_redmine_agile[k].dup
+      end
+    h
+  end
+  options.each {|k, v| Setting.plugin_redmine_agile[k] = v}
+  yield
+ensure
+  saved_settings.each {|k, v| Setting.plugin_redmine_agile[k] = v} if saved_settings
+end
+
+def log_user(login, password)
+  User.anonymous
+  compatible_request :get, '/logout'
+  compatible_request :get, '/login'
+  assert_nil session[:user_id]
+  assert_response :success
+  compatible_request :post, '/login', :username => login, :password => password
+  assert_equal login, User.find(session[:user_id]).login
+end
+
+def wait_for_ajax
+  counter = 0
+  while page.execute_script('return $.active').to_i > 0
+    counter += 1
+    sleep(0.1)
+    raise 'AJAX request took longer than 5 seconds.' if counter >= 50
+  end
+end
+
+def credentials(user, password=nil)
+  {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
+end
+
+module RedmineAgile
+  module TestHelper
+    def compatible_request(type, action, parameters = {})
+      return send(type, action, parameters) if Redmine::VERSION.to_s < '3.5' && Redmine::VERSION::BRANCH == 'stable'
+      send(type, action, :params => parameters)
+    end
+
+    def compatible_xhr_request(type, action, parameters = {})
+      return xhr type, action, parameters if Redmine::VERSION.to_s < '3.5' && Redmine::VERSION::BRANCH == 'stable'
+      send(type, action, :params => parameters, :xhr => true)
+    end
+
+    def agile_issues_in_list
+      ids = css_select('p.issue-id input').map { |tag| tag.to_s.to_s[/.*?value=\"(\d+)".*?/, 1] }.map(&:to_i)
+      Issue.where(:id => ids).sort_by { |issue| ids.index(issue.id) }
+    end
+  end
+
+  module Demo
+    # create_issue(10.days.ago, 4.days.ago, version)
+    # 100.times{|i| create_issue(100.days.ago + i, 90.days.ago + i, version)}
+    class << self
+      def create_issue(from, to, options={})
+        version = options[:version] || Version.last
+        tracker = options[:tracker] || version.project.trackers.select{|t| t.issue_statuses.any?}.sample
+        return false unless tracker
+        status = options[:status] || tracker.issue_statuses.sample
+        issue = Issue.create(:subject => "New issue #{rand(1000)}",
+                          :fixed_version => version,
+                          :project => version.project,
+                          :tracker => tracker,
+                          :start_date => from,
+                          :created_on => from,
+                          :updated_on => to,
+                          :due_date => to,
+                          :closed_on => (status.is_closed? ? to : nil),
+                          :status => status,
+                          :author => version.project.members.map(&:user).flatten.sample)
+      end
+
+      def change_issue_done_ratio(issue, change_date, value)
+        # journal = issue.init_journal(User.current)
+        issue.done_ratio = value
+        issue.save
+        issue.update_attributes(:updated_on => change_date)
+        Journal.last.update_attributes(:created_on => change_date)
+      end
+
+      def close_issue(issue, closed_on)
+        journal = issue.init_journal(User.current)
+        done_status = IssueStatus.where(:name => "Closed", :is_closed => true).first
+        in_status = IssueStatus.where(:name => "In progress", :is_closed => false)
+        days_count = (issue.due_date - issue.created_on).to_i
+        change_date = issue.created_on
+        change_date = change_date + rand((issue.due_date - days_count).to_i)
+        issue.update_attributes(:status_id => in_status.id, :updated_on => change_date)
+        journal.update_attributes(:created_on => change_date)
+        change_date = change_date + rand((issue.due_date - days_count).to_i)
+        issue.update_attributes(:status_id => done_status.id, :updated_on => change_date)
+        journal.update_attributes(:created_on => change_date)
+        issue
+      end
+
+      def create_version_issue(from, to, version, done_ratio=[])
+        to = from + 1 if to < from
+        issue = create_issue(from, to, :version => version, :status => IssueStatus.where(:name => "New", :is_closed => false).first)
+        days_count = (to - from).to_i
+        if done_ratio.empty?
+          change_issue_done_ratio(issue, from + rand(days_count), rand(100))
+          change_issue_done_ratio(issue.reload, to.to_time - 2.hours , 100)
+        else
+          change_date = from
+          done_ratio.each do |ratio|
+            change_date = change_date + rand((to - change_date).to_i)
+            change_issue_done_ratio(issue, change_date, ratio)
+          end
+        end
+      end
+
+      # create_version_issue("2012-03-10".to_date, "2012-03-14".to_date, version)
+      # create_version_issue("2012-03-10".to_date, "2012-03-14".to_date, version, done_ratio=[30, 100])
+
+    end
+  end
+
+  def self.create_issues
+  [ # BackLog
+    {
+    :project_id => 2,
+    :priority_id => 1,
+    :subject => "Issue 100",
+    :id => 100,
+    :fixed_version_id => 7,
+    :category_id => 1,
+    :description => "Unable to print recipes",
+    :tracker_id => 1,
+    :assigned_to_id => 1,
+    :author_id => 1,
+    :status_id => 4,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 3,
+    :agile_data_attributes => { :story_points => 10 }
+    },
+    {
+    :project_id => 2,
+    :priority_id => 1,
+    :subject => "Issue 101",
+    :id => 101,
+    :fixed_version_id => 7,
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => 3,
+    :author_id => 3,
+    :status_id => 3,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 1,
+    },
+    {
+    :project_id => 2,
+    :priority_id => 3,
+    :subject => "Issue 102",
+    :id => 102,
+    :fixed_version_id => 7,
+    :category_id => 1,
+    :description => "Unable to print recipes",
+    :tracker_id => 3,
+    :assigned_to_id => 2,
+    :author_id => 3,
+    :status_id => 2,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 12
+    },
+    {
+    :project_id => 2,
+    :priority_id => 3,
+    :subject => "Issue 103",
+    :id => 103,
+    :fixed_version_id => 7,
+    :category_id => 1,
+    :description => "Unable to print recipes",
+    :tracker_id => 1,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 1,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 55,
+    },
+    # Current Version
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "Issue 104",
+    :id => 104,
+    :fixed_version_id => 5,
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => nil,
+    :author_id => 1,
+    :status_id => 4,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 3,
+    },
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "Issue 105",
+    :id => 105,
+    :fixed_version_id => 5,
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 5,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 4,
+    },
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "Issue 106",
+    :id => 106,
+    :fixed_version_id => 5,
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 5,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 8,
+    },
+    # No Version
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "Blaa",
+    :id => 107,
+    # fixed_version_id =>
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 4,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 3,
+    },
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "No Version Issue 108",
+    :id => 108,
+    # fixed_version_id =>
+    :category_id => 3,
+    :description => "No Version Issue 108",
+    :tracker_id => 2,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 5,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 4
+    },
+    {
+    :project_id => 2,
+    :priority_id => 2,
+    :subject => "Blaaa",
+    :id => 109,
+    # fixed_version_id =>
+    :category_id => 3,
+    :description => "Unable to print recipes",
+    :tracker_id => 2,
+    :assigned_to_id => 2,
+    :author_id => 1,
+    :status_id => 5,
+    :start_date => 1.day.ago.to_date.to_s(:db),
+    :due_date => 10.day.from_now.to_date.to_s(:db),
+    :root_id => 1,
+    :lock_version => 3,
+    :estimated_hours => 8
+    }].each do |issue|
+      i = Issue.new(issue)
+      i.id = issue[:id]
+      i.save
+    end
+
+  end
+
+end
+
+class RedmineAgile::TestCase
+  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+    if ActiveRecord::VERSION::MAJOR >= 4
+      ActiveRecord::FixtureSet.create_fixtures(fixtures_directory, table_names, class_names = {})
+    else
+      ActiveRecord::Fixtures.create_fixtures(fixtures_directory, table_names, class_names = {})
+    end
+  end
+
+  def self.prepare
+    Role.find(1, 2, 3, 4).each do |r|
+      r.permissions << :manage_public_agile_queries
+      r.permissions << :add_agile_queries
+      r.permissions << :view_agile_queries
+      r.save
+    end
+  end
+end
+
+include RedmineAgile::TestHelper
diff --git a/plugins/redmine_agile/test/ui/agile_board_ui_test.rb b/plugins/redmine_agile/test/ui/agile_board_ui_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c99997a4b41f4b9653d0a97625ef5a9a01d308d
--- /dev/null
+++ b/plugins/redmine_agile/test/ui/agile_board_ui_test.rb
@@ -0,0 +1,99 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+if ENV["UI_TESTS"]
+
+  require File.expand_path('../../../../../test/ui/base', __FILE__)
+
+  class Redmine::UiTest::AgileBoardUiTest < Redmine::UiTest::Base
+    fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
+             :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
+             :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
+             :watchers, :journals, :journal_details
+
+    def setup
+      project = Project.find(1)
+      EnabledModule.create(:project => project, :name => 'agile')
+      Issue.find_each do |issue|
+        issue.agile_data.position = issue.id
+        issue.agile_data.save!
+      end
+    end
+
+    def test_move_issue_to_resolved_column
+      log_user('admin', 'admin')
+      visit '/projects/ecookbook/agile/board'
+
+      first_issue = Issue.first
+      issue = first(:css, '.issue-card[data-id="1"]')
+      feedback_col = first(:css, '.issue-status-col[data-id="3"]')
+      assert_equal 'New', first_issue.status.name
+      issue.drag_to(feedback_col)
+      wait_for_ajax
+      assert_equal 'Resolved', first_issue.reload.status.name
+    end
+
+    def test_reorder_issue_on_board
+      log_user('admin', 'admin')
+      visit '/projects/ecookbook/agile/board'
+
+      first_issue = all(:css, '.issue-status-col[data-id="1"] .issue-card').first
+      last_issue = all(:css, '.issue-status-col[data-id="1"] .issue-card').last
+      issue = Issue.find(last_issue['data-id'].to_i)
+      assert_not_equal 0, issue.reload.agile_data.position
+      last_issue.drag_to(first_issue)
+      wait_for_ajax
+      assert_equal 0, issue.reload.agile_data.position
+    end
+
+    def test_assign_member_on_issue
+      log_user('admin', 'admin')
+      visit '/projects/ecookbook/agile/board'
+
+      first_issue = all(:css, '.issue-status-col[data-id="1"] .issue-card').first
+      last_user = all(:css, '.assignable-user').last
+      issue = Issue.find(first_issue['data-id'].to_i)
+      user = User.find(last_user['data-id'].to_i)
+      assert_nil issue.reload.assigned_to
+      last_user.drag_to(first_issue)
+      wait_for_ajax
+      assert_equal user, issue.reload.assigned_to
+    end
+
+    def test_add_comment_to_issue
+      with_agile_settings 'allow_inline_comments' => 1 do
+        log_user('admin', 'admin')
+        visit '/projects/ecookbook/agile/board'
+
+        first_issue = all(:css, '.issue-status-col[data-id="1"] .issue-card').first
+        issue = Issue.find(first_issue['data-id'].to_i)
+        page.driver.browser.mouse.move_to(first_issue.native)
+        all(:css, '.issue-status-col[data-id="1"] .issue-card .quick-edit-card a').first.click
+        wait_for_ajax
+        fill_in 'issue[notes]', :with => 'Test quick comment'
+        click_button('Submit')
+        wait_for_ajax
+        assert_equal 'Test quick comment', issue.journals.last.notes
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_agile/test/unit/agile_color_test.rb b/plugins/redmine_agile/test/unit/agile_color_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..346697750b9d58d9a5b84c194708a0c7815cde4a
--- /dev/null
+++ b/plugins/redmine_agile/test/unit/agile_color_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileColorTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  # Replace this with your real tests.
+  def test_save_color
+    issue = Issue.find(1)
+    assert_nil issue.color
+    issue.agile_color.color = AgileColor::AGILE_COLORS[:red]
+    assert issue.save
+    issue.reload
+    assert_equal AgileColor::AGILE_COLORS[:red], issue.color
+  end
+
+  def test_delete_color
+    issue = Issue.find(1)
+    assert_nil issue.color
+    issue.agile_color.color = AgileColor::AGILE_COLORS[:red]
+    assert issue.save
+    issue.reload
+    color = issue.agile_color
+    assert issue.destroy
+    assert !AgileColor.exists?(color.id)
+  end
+
+  def test_color_for_spent_time
+    assert_equal 'gray', AgileColor.for_spent_time(nil, nil)
+    assert_equal 'gray', AgileColor.for_spent_time(nil, 10)
+    assert_equal 'green', AgileColor.for_spent_time(20, 10)
+    assert_equal 'yellow', AgileColor.for_spent_time(20, 19)
+    assert_equal 'red', AgileColor.for_spent_time(20, 30)
+    assert_equal 'purple', AgileColor.for_spent_time(20, 42)
+    assert_equal 'gray', AgileColor.for_spent_time(0.0, 0.0)
+    assert_equal 'gray', AgileColor.for_spent_time(0.0, 8.8)
+  end
+end
diff --git a/plugins/redmine_agile/test/unit/agile_data_test.rb b/plugins/redmine_agile/test/unit/agile_data_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cbec7368361e452262e542c7505f9ea5234b36b7
--- /dev/null
+++ b/plugins/redmine_agile/test/unit/agile_data_test.rb
@@ -0,0 +1,67 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileDataTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  # Replace this with your real tests.
+  def test_save_color
+    issue = Issue.find(1)
+    issue.agile_data.position = 1
+    assert issue.save
+    issue.reload
+    assert_equal 1, issue.agile_data.position
+  end
+
+  def test_delete_color
+    issue = Issue.find(1)
+    issue.agile_data.position = 1
+    assert issue.save
+    issue.reload
+    agile_data = issue.agile_data
+    assert issue.destroy
+    assert !AgileData.exists?(agile_data.id)
+  end
+end
diff --git a/plugins/redmine_agile/test/unit/agile_versions_query_test.rb b/plugins/redmine_agile/test/unit/agile_versions_query_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c473fcca78b806a3759eea86874ae7aa51d4c009
--- /dev/null
+++ b/plugins/redmine_agile/test/unit/agile_versions_query_test.rb
@@ -0,0 +1,138 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AgileVersionsQueryTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issue_statuses,
+           :issues,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  def filter_fields
+    ['assigned_to_id', 'tracker_id', 'status_id', 'author_id', 'category_id'] # estimated_hours
+  end
+
+  def setup
+    super
+    RedmineAgile.create_issues
+    @query = AgileVersionsQuery.new(:name => '_')
+            @query.project = Project.find(2)
+    @backlog_version = Version.find(7)
+    @current_version = Version.find(5)
+    User.current = User.find(1) # because issues selected according permissions
+  end
+
+  def test_backlog_version
+    assert_equal @backlog_version, @query.backlog_version
+  end
+
+  def test_current_version
+    assert_equal @current_version, @query.current_version
+  end
+
+  def test_backlog_issues
+    assert_equal [100, 101, 102, 103], @query.backlog_version_issues.map(&:id).sort
+  end
+
+  def test_current_issues
+    assert_equal [104], @query.current_version_issues.map(&:id).sort
+  end
+  def test_current_version_issues
+    assert_equal 1, @query.current_version_issues.count
+
+    @query.build_from_params(:f => ['status_id'], :o => ['status_id' => '*'], :v => ['status_id' => IssueStatus.all.map(&:id)])
+    assert_equal [104, 105, 106], @query.current_version_issues.map(&:id).sort
+  end
+
+  def test_filters_backlog_issues_in
+    filter_fields.each do |filter|
+      m = "Error in the #{filter} filter"
+      hash = {
+        :f =>[filter],
+        :op => { filter => '='},
+        :v => { filter => ['1', '3'] } }
+
+      @query.build_from_params(hash)
+      assert_equal [1, 3], @query.backlog_version_issues.collect { |issue| issue.send(filter.to_sym).to_i }.uniq.sort, m
+    end
+  end
+
+  def test_filters_backlog_issues_not_in
+    filter_fields.each do |filter|
+      m = "Error in the #{filter} filter"
+      hash = {
+        :f => [filter],
+        :op => { filter => '!' },
+        :v => { filter => ['1', '3'] } }
+
+      @query.build_from_params(hash)
+      issues = @query.backlog_version_issues.collect { |issue| issue.send(filter.to_sym) }.uniq.sort
+      assert_equal [], [1, 3] & issues, m
+    end
+  end
+
+  def test_with_few_filters
+    hash = {
+        :f => ['assigned_to_id', 'priority_id', 'tracker_id', 'estimated_hours'],
+        :op => { 'assigned_to_id' => '*', 'priority_id' => '!', 'tracker_id' => '=', 'estimated_hours' => '><' },
+        :v => { 'priority_id' => ['3'], 'tracker_id' => ['1', '2', '3'], 'estimated_hours' => ['2', '7'] } }
+    @query.build_from_params(hash)
+    assert_equal [100], @query.backlog_version_issues.map(&:id)
+    assert_equal [105], @query.current_version_issues.map(&:id).sort
+  end
+
+  def test_no_assigned_to
+    hash = {
+        :f => ['assigned_to_id'], :op => { 'assigned_to_id' => '!*' }
+    }
+    @query.build_from_params(hash)
+    assert_equal [104], @query.current_version_issues.map(&:id).sort
+  end
+
+  def test_no_version_issues
+    hash = {
+        :f =>['tracker_id'],
+        :op => { 'tracker_id' => '=' },
+        :v => { 'tracker_id' => ['1', '2', '3'] } }
+    @query.build_from_params(hash)
+    assert_equal [107, 109], @query.no_version_issues(:q => 'bla').map(&:id).sort
+    assert_equal [109], @query.no_version_issues(:q => '#109').map(&:id)
+  end
+end
diff --git a/plugins/redmine_agile/test/unit/helpers/agile_boards_helper_test.rb b/plugins/redmine_agile/test/unit/helpers/agile_boards_helper_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3dc35ff53f8204ccc09e444f9674a13ace9fd0f5
--- /dev/null
+++ b/plugins/redmine_agile/test/unit/helpers/agile_boards_helper_test.rb
@@ -0,0 +1,188 @@
+# encoding: utf-8
+#
+# This file is a part of Redmin Agile (redmine_agile) plugin,
+# Agile board plugin for redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_agile is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_agile is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_agile.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../../test_helper', __FILE__)
+
+class AgileBoardsHelperTest < ActiveSupport::TestCase
+  fixtures :projects, :trackers, :issue_statuses, :issues, :issue_categories
+
+  include ApplicationHelper
+  include AgileBoardsHelper
+  include CustomFieldsHelper
+  include ActionView::Helpers::TagHelper
+  include Redmine::I18n
+  include ERB::Util
+
+  def setup
+    super
+    set_language_if_valid('en')
+    User.current = nil
+    EnabledModule.create(:project => Project.find(1), :name => 'issue_tracking') if RedmineAgile.use_checklist?
+  end
+
+  def test_render_board_headers_flat
+    columns = [
+      status_1 = IssueStatus.create(:name => "ToDo"),
+      status_2 = IssueStatus.create(:name => "Doing"),
+      status_3 = IssueStatus.create(:name => "Done"),
+    ]
+
+    html = %{
+      <tr><th data-column-id="#{status_1.id}">ToDo (<span class="count">0</span>)</th><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_rowspan
+    # | One  | Two    | Three |
+    # |      | Doing  | Done  |
+    columns = [
+      status_1 = IssueStatus.create(:name => "One"),
+      status_2 = IssueStatus.create(:name => "Two:Doing"),
+      status_3 = IssueStatus.create(:name => "Three:Done"),
+    ]
+
+    html = %{
+       <tr><th data-column-id="#{status_1.id}" rowspan="2">One (<span class="count">0</span>)</th><th>Two</th><th>Three</th></tr><tr><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_colspan
+    # | One  |       Two      |
+    # |      | Doing  | Done  |
+    columns = [
+      status_1 = IssueStatus.create(:name => "One"),
+      status_2 = IssueStatus.create(:name => "Two:Doing"),
+      status_3 = IssueStatus.create(:name => "Two:Done"),
+    ]
+
+    html = %{
+       <tr><th data-column-id="#{status_1.id}" rowspan="2">One (<span class="count">0</span>)</th><th colspan="2">Two</th></tr><tr><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_colspan_first
+    # |      One      | Two  |
+    # | ToDo  | Doing | Done |
+    columns = [
+      status_1 = IssueStatus.create(:name => "One:ToDo"),
+      status_2 = IssueStatus.create(:name => "One:Doing"),
+      status_3 = IssueStatus.create(:name => "Two:Done"),
+    ]
+
+    html = %{
+       <tr><th colspan="2">One</th><th>Two</th></tr><tr><th data-column-id="#{status_1.id}">ToDo (<span class="count">0</span>)</th><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_three_levels
+    # |         Mega One     |            |
+    # |      One      | Two  |  Mega Two  |
+    # | ToDo  | Doing | Done |            |
+    columns = [
+      status_1 = IssueStatus.create(:name => "Mega One:One:ToDo"),
+      status_2 = IssueStatus.create(:name => "Mega One:One:Doing"),
+      status_3 = IssueStatus.create(:name => "Mega One:Two:Done"),
+      status_4 = IssueStatus.create(:name => "Mega Two"),
+    ]
+
+    html = %{
+       <tr><th colspan="3">Mega One</th><th data-column-id="#{status_4.id}" rowspan="3">Mega Two (<span class="count">0</span>)</th></tr><tr><th colspan="2">One</th><th>Two</th></tr><tr><th data-column-id="#{status_1.id}">ToDo (<span class="count">0</span>)</th><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_same_name_levels
+    # |         Mega One     |            | Mega Two |
+    # |      One      | Two  |  Mega Two  |    One   |
+    # | ToDo  | Doing | Done |            |          |
+    columns = [
+      status_1 = IssueStatus.create(:name => "Mega One:One:ToDo"),
+      status_2 = IssueStatus.create(:name => "Mega One:One:Doing"),
+      status_3 = IssueStatus.create(:name => "Mega One:Two:Done"),
+      status_4 = IssueStatus.create(:name => "Mega Two"),
+      status_5 = IssueStatus.create(:name => "Mega Two:One"),
+    ]
+
+    html = %{
+       <tr><th colspan="3">Mega One</th><th data-column-id="#{status_4.id}" rowspan="3">Mega Two (<span class="count">0</span>)</th><th>Mega Two</th></tr><tr><th colspan="2">One</th><th>Two</th><th data-column-id="#{status_5.id}" rowspan="2">One (<span class="count">0</span>)</th></tr><tr><th data-column-id="#{status_1.id}">ToDo (<span class="count">0</span>)</th><th data-column-id="#{status_2.id}">Doing (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_render_board_headers_keep_order
+    # | Dev   |  Testing     | Dev  |
+    # | ToDo  |              | Done |
+    columns = [
+      status_1 = IssueStatus.create(:name => "Dev:ToDo"),
+      status_2 = IssueStatus.create(:name => "Testing"),
+      status_3 = IssueStatus.create(:name => "Dev:Done")
+    ]
+
+    html = %{
+       <tr><th>Dev</th><th data-column-id="#{status_2.id}" rowspan="2">Testing (<span class="count">0</span>)</th><th>Dev</th></tr><tr><th data-column-id="#{status_1.id}">ToDo (<span class="count">0</span>)</th><th data-column-id="#{status_3.id}">Done (<span class="count">0</span>)</th></tr>
+    }.strip.gsub(/\r/, "")
+
+    headers = render_board_headers(columns)
+
+    assert_equal html, headers
+  end
+
+  def test_time_in_state
+    hour10 = Time.now - 10.hours
+    assert_equal "#{I18n.t('datetime.distance_in_words.x_hours', :count => 10)}", time_in_state(hour10)
+    one_day = Time.now - 25.hours
+    assert_equal "#{I18n.t('datetime.distance_in_words.x_days', :count => 1)}", time_in_state(one_day)
+
+    assert_equal "", time_in_state(nil)
+    assert_equal "", time_in_state("string")
+  end
+
+  def test_show_checklist
+    issue = Issue.first
+    issue.checklists.create(subject: 'TEST1', position: 1)
+    User.current = User.find(1)
+
+    assert show_checklist?(issue), 'Not allowed show checklist for first issue'
+    assert !show_checklist?(Issue.find(3)), 'Allowed show checklist for issue without checklist'
+  end if RedmineAgile.use_checklist?
+end
diff --git a/plugins/redmine_checklists/Gemfile b/plugins/redmine_checklists/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..c6b5df6354d30f2138757b8909c303344505b357
--- /dev/null
+++ b/plugins/redmine_checklists/Gemfile
@@ -0,0 +1 @@
+gem 'redmine_crm'
diff --git a/plugins/redmine_checklists/app/controllers/checklist_template_categories_controller.rb b/plugins/redmine_checklists/app/controllers/checklist_template_categories_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03893bc382d2d0240c2e91e94f487fca48392ac7
--- /dev/null
+++ b/plugins/redmine_checklists/app/controllers/checklist_template_categories_controller.rb
@@ -0,0 +1,72 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ChecklistTemplateCategoriesController < ApplicationController
+  unloadable
+
+  before_action :find_category, :only => [:destroy, :update, :edit]
+
+  def create
+    @category = ChecklistTemplateCategory.new
+    @category.safe_attributes = params[:category]
+    if @category.save
+      flash[:notice] = l(:notice_successful_create)
+      redirect_to_list
+    else
+      render :action => 'new'
+    end
+  end
+
+  def destroy
+    @category.destroy
+    redirect_to_list
+  rescue
+    flash[:error] = l(:label_finance_can_not_delete_category)
+    redirect_to_list
+  end
+
+  def update
+    @category.safe_attributes = params[:category]
+    @category.insert_at(@category.position) if @category.position_changed?
+    if @category.save
+      respond_to do |format|
+        format.html do
+          flash[:notice] = l(:notice_successful_update)
+          redirect_to_list
+        end
+        format.js { head 200 }
+      end
+    else
+      respond_to do |format|
+        format.html { render :action => 'edit' }
+        format.js { head 422 }
+      end
+    end
+  end
+
+private
+
+  def find_category
+    @category = ChecklistTemplateCategory.find(params[:id])
+  end
+
+  def redirect_to_list
+    redirect_to :action =>"plugin", :id => "redmine_checklists", :controller => "settings", :tab => 'checklist_template_categories'
+  end
+end
diff --git a/plugins/redmine_checklists/app/controllers/checklist_templates_controller.rb b/plugins/redmine_checklists/app/controllers/checklist_templates_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..651d907948ee401f7a5d07dda62d15a1c9eb50a1
--- /dev/null
+++ b/plugins/redmine_checklists/app/controllers/checklist_templates_controller.rb
@@ -0,0 +1,94 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ChecklistTemplatesController < ApplicationController
+  unloadable
+
+  before_action :find_checklist_template, :except => [:new, :create, :index]
+  before_action :find_optional_project, :only => [:new, :create, :add, :destroy, :edit, :update]
+  before_action :require_admin, :only => [:index]
+
+  def new
+    @checklist_template = ChecklistTemplate.new
+    @checklist_template.user = User.current
+    @checklist_template.project = @project
+    @checklist_template.is_public = false unless User.current.allowed_to?(:manage_checklist_templates, @project) || User.current.admin?
+  end
+
+  def create
+    @checklist_template = ChecklistTemplate.new
+    @checklist_template.safe_attributes = params[:checklist_template]
+    @checklist_template.user = User.current
+    @checklist_template.project = params[:checklist_template_is_for_all] && User.current.admin? ? nil : @project
+    @checklist_template.is_public = false unless User.current.allowed_to?(:manage_checklist_templates, @project) || User.current.admin?
+
+    if @checklist_template.save
+      flash[:notice] = l(:notice_successful_create)
+      redirect_to_project_or_global
+    else
+      render :action => 'new', :layout => !request.xhr?
+    end
+  end
+
+  def edit
+  end
+
+  def update
+    @checklist_template.safe_attributes = params[:checklist_template]
+    @checklist_template.project = nil if params[:checklist_template_is_for_all]
+    @checklist_template.project = @project if params[:checklist_template][:is_public] == '1' && !User.current.admin?
+    @checklist_template.project = (params[:checklist_template_is_for_all] && User.current.admin?) ? nil : @project
+    @checklist_template.is_public = false unless User.current.allowed_to?(:manage_checklist_templates, @project) || User.current.admin?
+
+    if @checklist_template.save
+      flash[:notice] = l(:notice_successful_update)
+      redirect_to_project_or_global
+    else
+      render :action => 'edit'
+    end
+  end
+
+  def destroy
+    @checklist_template.destroy
+    redirect_to_project_or_global
+  end
+
+private
+  def redirect_to_project_or_global
+    redirect_to @project ? settings_project_path(@project, :tab => 'checklist_template') : {:action => "plugin", :id => "redmine_checklists", :controller => "settings", :tab => 'checklist_templates'}
+  end
+
+  def find_issue
+    @issue = Issue.find(params[:issue_id])
+    @project = @issue.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def find_checklist_template
+    template_scope = ChecklistTemplate.where(:id => params[:id].to_i)
+    template_scope = template_scope.where('user_id = ? OR is_public = ?', User.current.id, true) unless User.current.admin?
+    @checklist_template = template_scope.first
+    raise ActiveRecord::RecordNotFound unless @checklist_template.present?
+    @project = @checklist_template.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+end
diff --git a/plugins/redmine_checklists/app/controllers/checklists_controller.rb b/plugins/redmine_checklists/app/controllers/checklists_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5cf9545da3ea4da5275cde58d1d7853256dc8686
--- /dev/null
+++ b/plugins/redmine_checklists/app/controllers/checklists_controller.rb
@@ -0,0 +1,124 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ChecklistsController < ApplicationController
+  unloadable
+
+  before_action :find_checklist_item, :except => [:index, :create]
+  before_action :find_issue_by_id, :only => [:index, :create]
+  before_action :authorize, :except => [:done]
+  helper :issues
+
+  accept_api_auth :index, :update, :destroy, :create, :show
+
+  def index
+    @checklists = @issue.checklists
+    respond_to do |format|
+      format.api
+    end
+  end
+
+  def show
+    respond_to do |format|
+      format.api
+    end
+  end
+
+  def destroy
+    @checklist_item.destroy
+    respond_to do |format|
+      format.api { render_api_ok }
+    end
+  end
+
+  def create
+    @checklist_item = Checklist.new
+    @checklist_item.safe_attributes = params[:checklist]
+    @checklist_item.issue = @issue
+    respond_to do |format|
+      format.api {
+        if @checklist_item.save
+          render :action => 'show', :status => :created, :location => checklist_url(@checklist_item)
+        else
+          render_validation_errors(@checklist_item)
+        end
+      }
+    end
+  end
+
+  def update
+    @checklist_item.safe_attributes = params[:checklist]
+    respond_to do |format|
+      format.api {
+        if @checklist_item.save
+          render_api_ok
+        else
+          render_validation_errors(@checklist_item)
+        end
+      }
+    end
+  end
+
+  def done
+    (render_403; return false) unless User.current.allowed_to?(:done_checklists, @checklist_item.issue.project)
+    old_checklist_items = @checklist_item.issue.checklists.to_a
+    @checklist_item.is_done = params[:is_done] == 'true'
+
+    if @checklist_item.save
+      journal = Journal.new(:journalized => @checklist_item.issue, :user => User.current)
+      checklist_items = Checklist.where(:issue_id => @checklist_item.issue.id).to_a
+      detail = JournalDetail.new( :property => 'attr',
+                                  :prop_key => 'checklist',
+                                  :old_value => old_checklist_items.to_json.to_s,
+                                  :value => checklist_items.to_json.to_s,
+                                  :journal => journal
+                                  )
+      if JournalChecklistHistory.can_fixup?(detail)
+        JournalChecklistHistory.fixup(detail)
+      else
+        journal.details << detail
+      end
+      journal.save unless journal.destroyed?
+      if (Setting.issue_done_ratio == "issue_field") && RedmineChecklists.settings["issue_done_ratio"].to_i > 0
+        Checklist.recalc_issue_done_ratio(@checklist_item.issue.id)
+        @checklist_item.issue.reload
+      end
+    end
+    respond_to do |format|
+      format.js
+      format.html { redirect_to :back }
+    end
+  end
+
+  private
+
+  def find_issue_by_id
+    @issue = Issue.find(params[:issue_id])
+    @project = @issue.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def find_checklist_item
+    @checklist_item = Checklist.find(params[:id])
+    @project = @checklist_item.issue.project
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+end
diff --git a/plugins/redmine_checklists/app/helpers/checklists_helper.rb b/plugins/redmine_checklists/app/helpers/checklists_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e1459dbd4450a784ec6c009486495bfc6d7cffd8
--- /dev/null
+++ b/plugins/redmine_checklists/app/helpers/checklists_helper.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module ChecklistsHelper
+
+  def link_to_remove_checklist_fields(name, f, options={})
+    f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", options)
+  end
+
+  def new_object(f, association)
+    @new_object ||= f.object.class.reflect_on_association(association).klass.new
+  end
+
+  def fields(f, association)
+    @fields ||= f.fields_for(association, new_object(f, association), :child_index => "new_#{association}") do |builder|
+      render(association.to_s.singularize + "_fields", :f => builder)
+    end
+  end
+
+  def new_or_show(f)
+    if f.object.new_record?
+      if f.object.subject.present?
+        "show"
+      else
+        "new"
+      end
+    else
+      "show"
+    end
+  end
+
+  def done_css(f)
+    if f.object.is_done
+      "is-done-checklist-item"
+    else
+      ""
+    end
+  end
+  def template_options_for_select(project = nil, tracker = nil)
+    scoped = ChecklistTemplate.visible
+    scoped = scoped.in_project_and_global(project) if project.present?
+    scoped = scoped.for_tracker_and_global(tracker) if tracker.present?
+    templates = scoped.eager_load(:category).to_a
+    without_category = templates.select{ |x| x.category.nil? }.map{ |x| [x.name, x.id, {'data-template-items' => x.template_items}] }
+    with_category = templates.select{ |x| x.category }
+    options_for_select(
+      [[l(:label_select_template), '']] + without_category
+    ) +
+    grouped_options_for_select(
+        with_category.group_by{ |x| x.category.try(:name) }.
+        map{ |k,v| [ k, v.map{ |x| [ x.name, x.id, {'data-template-items' => x.template_items} ] } ] }
+    )
+  end
+
+end
diff --git a/plugins/redmine_checklists/app/models/checklist.rb b/plugins/redmine_checklists/app/models/checklist.rb
new file mode 100644
index 0000000000000000000000000000000000000000..763a2d223c6b2cbeb9a133af01691564516fdd3a
--- /dev/null
+++ b/plugins/redmine_checklists/app/models/checklist.rb
@@ -0,0 +1,91 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class Checklist < ActiveRecord::Base
+  unloadable
+  include Redmine::SafeAttributes
+  belongs_to :issue
+  belongs_to :author, :class_name => "User", :foreign_key => "author_id"
+
+  attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
+
+  acts_as_event :datetime => :created_at,
+                :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue_id}},
+                :type => 'issue issue-closed',
+                :title => Proc.new {|o| o.subject },
+                :description => Proc.new {|o| "#{l(:field_issue)}:  #{o.issue.subject}" }
+
+
+  if ActiveRecord::VERSION::MAJOR >= 4
+    acts_as_activity_provider :type => "checklists",
+                              :permission => :view_checklists,
+                              :scope => preload({:issue => :project})
+    acts_as_searchable :columns => ["#{table_name}.subject"],
+                       :scope => lambda { includes([:issue => :project]).order("#{table_name}.id") },
+                       :project_key => "#{Issue.table_name}.project_id"
+
+  else
+    acts_as_activity_provider :type => "checklists",
+                              :permission => :view_checklists,
+                              :find_options => {:issue => :project}
+    acts_as_searchable :columns => ["#{table_name}.subject"],
+                       :include => [:issue => :project],
+                       :project_key => "#{Issue.table_name}.project_id",
+                       :order_column => "#{table_name}.id"
+  end
+
+  rcrm_acts_as_list
+
+  validates_presence_of :subject
+  validates_length_of :subject, :maximum => 512
+  validates_presence_of :position
+  validates_numericality_of :position
+
+  def self.recalc_issue_done_ratio(issue_id)
+    issue = Issue.find(issue_id)
+    return false if (Setting.issue_done_ratio != "issue_field") || RedmineChecklists.settings["issue_done_ratio"].to_i < 1 || issue.checklists.empty?
+    done_checklist = issue.checklists.map{|c| c.is_done ? 1 : 0}
+    done_ratio = (done_checklist.count(1) * 10) / done_checklist.count * 10
+    issue.update_attribute(:done_ratio, done_ratio)
+  end
+
+  def self.old_format?(detail)
+    (detail.old_value.is_a?(String) && detail.old_value.match(/^\[[ |x]\] .+$/).present?) ||
+      (detail.value.is_a?(String) && detail.value.match(/^\[[ |x]\] .+$/).present?)
+  end
+
+  safe_attributes 'subject', 'position', 'issue_id', 'is_done'
+
+  def editable_by?(usr = User.current)
+    usr && (usr.allowed_to?(:edit_checklists, project) || (author == usr && usr.allowed_to?(:edit_own_checklists, project)))
+  end
+
+  def project
+    issue.project if issue
+  end
+
+  def info
+    "[#{is_done ? 'x' : ' '}] #{subject.strip}"
+  end
+
+  def add_to_list_bottom
+    return unless issue.checklists.select(&:persisted?).map(&:position).include?(self[position_column])
+    self[position_column] = bottom_position_in_list.to_i + 1
+  end
+end
diff --git a/plugins/redmine_checklists/app/models/checklist_template.rb b/plugins/redmine_checklists/app/models/checklist_template.rb
new file mode 100644
index 0000000000000000000000000000000000000000..873ab641e83fbffb0d6916149b8ee8eaff664500
--- /dev/null
+++ b/plugins/redmine_checklists/app/models/checklist_template.rb
@@ -0,0 +1,62 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ChecklistTemplate < ActiveRecord::Base
+  unloadable
+  include Redmine::SafeAttributes
+  belongs_to :project
+  belongs_to :tracker
+  belongs_to :user
+  belongs_to :category, :class_name => "ChecklistTemplateCategory", :foreign_key => "category_id"
+
+  validates_presence_of :name, :template_items
+  validates_length_of :name, :maximum => 255
+
+  attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
+  safe_attributes 'name', 'template_items', 'project', 'user', 'category_id', 'is_public', 'is_default', 'tracker_id'
+
+  scope :visible, lambda {|*args|
+    user = args.shift || User.current
+    base = Project.allowed_to_condition(user, :manage_checklist_templates, *args)
+    user_id = user.logged? ? user.id : 0
+
+    eager_load(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
+  }
+
+  scope :in_project_and_global, lambda {|project|
+    where("#{table_name}.project_id IS NULL OR #{table_name}.project_id = 0 OR #{table_name}.project_id = ?", project)
+  }
+
+  scope :for_tracker_and_global, lambda { |tracker|
+    where("#{table_name}.tracker_id IS NULL OR #{table_name}.tracker_id = 0 OR #{table_name}.tracker_id = ?", tracker)
+  }
+
+  scope :for_tracker_id, lambda { |tracker_id| where(:tracker_id => tracker_id) }
+
+  scope :default, lambda { where(:is_default => true) }
+
+  def to_s
+    name
+  end
+
+  def checklists
+    template_items.split("\r\n").map { |subject| Checklist.new(:subject => subject) }
+  end
+
+end
diff --git a/plugins/redmine_checklists/app/models/checklist_template_category.rb b/plugins/redmine_checklists/app/models/checklist_template_category.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1059afa1599c7e2705f24f293d59874ef9f6944d
--- /dev/null
+++ b/plugins/redmine_checklists/app/models/checklist_template_category.rb
@@ -0,0 +1,33 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ChecklistTemplateCategory < ActiveRecord::Base
+  unloadable
+  include Redmine::SafeAttributes
+
+  attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
+  scope :ordered, lambda { order(:position) }
+  safe_attributes 'name', 'position'
+
+  rcrm_acts_as_list
+
+  def to_s
+    name
+  end
+end
diff --git a/plugins/redmine_checklists/app/models/journal_checklist_history.rb b/plugins/redmine_checklists/app/models/journal_checklist_history.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ca66b3390f6bcabcaa6c4fe2fecf0053d13d358c
--- /dev/null
+++ b/plugins/redmine_checklists/app/models/journal_checklist_history.rb
@@ -0,0 +1,148 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class JournalChecklistHistory
+  def self.can_fixup?(journal_details)
+    unless journal_details.journal
+      return false
+    end
+    issue = journal_details.journal.journalized
+    unless issue.is_a?(Issue)
+      return false
+    end
+    prev_journal_scope = issue.journals.order('id DESC')
+    prev_journal_scope = prev_journal_scope.where('id <> ?', journal_details.journal_id) if journal_details.journal_id
+    prev_journal = prev_journal_scope.first
+    unless prev_journal
+      return false
+    end
+
+    return false if Time.zone.now > prev_journal.created_on + 1.minute
+
+    prev_journal.details.all?{ |x| x.prop_key == 'checklist'} &&
+      journal_details.journal.details.all?{ |x| x.prop_key == 'checklist'} &&
+      journal_details.journal.notes.blank? &&
+      prev_journal.notes.blank? &&
+      prev_journal.details.select{ |x| x.prop_key == 'checklist' }.size == 1
+  end
+
+  def self.fixup(journal_details)
+    issue = journal_details.journal.journalized
+    prev_journal_scope = issue.journals.order('id DESC')
+    prev_journal_scope = prev_journal_scope.where('id <> ?', journal_details.journal_id) if journal_details.journal_id
+    prev_journal = prev_journal_scope.first
+    checklist_details = prev_journal.details.find{ |x| x.prop_key == 'checklist'}
+    if new(checklist_details.old_value, journal_details.value).empty_diff?
+      prev_journal.destroy
+      journal_details.journal.send(:send_checklist_notification)
+    else
+      checklist_details.update_attribute(:value, journal_details.value)
+      journal_details.journal.destroy unless journal_details.journal.new_record? && journal_details.journal.details.any?{ |x| x.prop_key != 'checklist'}
+      prev_journal.send(:send_checklist_notification)
+    end
+  end
+
+  def initialize(was, become)
+    @was = force_object(was)
+    @become = force_object(become)
+    @was_ids = @was.map(&:id)
+    @become_ids = @become.map(&:id)
+    @added_ids = @become_ids - @was_ids
+    @removed_ids = @was_ids - @become_ids
+    @both_ids = @become_ids & @was_ids
+  end
+
+  def diff
+    {
+      :added => @become.select{ |x| @added_ids.include? x.id },
+      :removed => @was.select{ |x| @removed_ids.include? x.id },
+      :renamed => renamed,
+      :undone => undone,
+      :done => done
+    }
+  end
+
+  def empty_diff?
+    diff.all?{ |_,v| v.empty? }
+  end
+
+  def journal_details(opts = {})
+    JournalDetail.new(opts.merge({
+        :property  => 'attr',
+        :prop_key  => 'checklist',
+        :old_value => @was.map(&:to_h).to_json,
+        :value     => @become.map(&:to_h).to_json
+      }))
+  end
+
+  private
+
+  def undone
+    @both_ids.map do |id|
+      was_is_done = was_by_id(id).is_done
+      become_is_done = become_by_id(id).is_done
+      if was_is_done != become_is_done && was_is_done
+        become_by_id(id)
+      else
+        nil
+      end
+    end.compact
+  end
+
+  def done
+    @both_ids.map do |id|
+      was_is_done = was_by_id(id).is_done
+      become_is_done = become_by_id(id).is_done
+      if was_is_done != become_is_done && become_is_done
+        become_by_id(id)
+      else
+        nil
+      end
+    end.compact
+  end
+  def renamed
+    Hash[@both_ids.map do |id|
+      was_subject = was_by_id(id).subject
+      become_subject = become_by_id(id).subject
+      if was_subject != become_subject
+        [was_subject, become_subject]
+      else
+        nil
+      end
+    end.compact]
+  end
+
+  def was_by_id(id)
+    @was.find{ |x| x.id == id }
+  end
+
+  def become_by_id(id)
+    @become.find{ |x| x.id == id }
+  end
+
+  def force_object(unk)
+    if unk.is_a?(String)
+      json = JSON.parse(unk)
+      json = [json] unless json.is_a?(Array)
+      json.map{ |x| OpenStruct2.new(x.has_key?('checklist') ? x['checklist'] : x) }
+    else
+      unk.map{ |x| OpenStruct2.new(x.attributes) }
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/app/views/checklist_template_categories/_form.html.erb b/plugins/redmine_checklists/app/views/checklist_template_categories/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1ae541822f1fdc645ac9516bc9d5975cc8e66cab
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_template_categories/_form.html.erb
@@ -0,0 +1,6 @@
+<%= error_messages_for 'category' %>
+
+<div class="box tabular">
+<p><label for="category_name"><%=l(:field_name)%><span class="required"> *</span></label>
+<%= text_field 'category', 'name'  %></p>
+</div>
diff --git a/plugins/redmine_checklists/app/views/checklist_template_categories/edit.html.erb b/plugins/redmine_checklists/app/views/checklist_template_categories/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d8b7f0f64019d1ee462b970ad9efbf62e32ead6b
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_template_categories/edit.html.erb
@@ -0,0 +1,6 @@
+<h2><%= link_to l(:label_checklist_template_category_plural), :action =>"plugin", :id => "redmine_checklists", :controller => "settings", :tab => 'checklist_template_categories' %> &#187; <%= @category.name %></h2>
+
+<%= form_tag({:action => 'update', :id => @category}, :class => "tabular edit_checklist_template_category", :method => :put) do %>
+  <%= render :partial => 'form' %>
+  <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/checklist_template_categories/new.html.erb b/plugins/redmine_checklists/app/views/checklist_template_categories/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0a30b9d4194b2afe3965d0ca83e7a6d96c1da2fe
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_template_categories/new.html.erb
@@ -0,0 +1,6 @@
+<h2><%= link_to l(:label_checklist_template_category_plural), :action =>"plugin", :id => "redmine_checklists", :controller => "settings", :tab => 'checklist_template_categories' %> &#187; <%=l(:label_checklist_template_category_new)%></h2>
+
+<%= form_tag({:action => 'create'}, :class => "tabular new_checklist_template_category") do %>
+  <%= render :partial => 'form' %>
+  <%= submit_tag l(:button_create) %>
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/checklist_templates/_form.html.erb b/plugins/redmine_checklists/app/views/checklist_templates/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..dc1e998acb82b3e4115ff1bd66d0d172a05a3681
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_templates/_form.html.erb
@@ -0,0 +1,59 @@
+<%= error_messages_for 'checklist_template' %>
+
+<div class="box tabular">
+  <p><%= f.text_field :name, :size => 80, :required => true %></p>
+  <% if ChecklistTemplateCategory.any? %>
+    <p>
+      <%= f.select :category_id, options_for_select([['', nil]] + ChecklistTemplateCategory.all.map{ |x| [x.name, x.id]}, f.object.category_id) %>
+    </p>
+  <% end %>
+
+  <% if User.current.admin? || User.current.allowed_to?(:manage_checklist_templates, @project) %>
+    <p><label><%=l(:field_visible)%></label>
+      <label class="block">
+        <%= f.radio_button :is_public, 0, :checked => !@checklist_template.is_public?,
+                           :onchange => (User.current.admin? ? nil : '$("#checklist_template_is_for_all").removeAttr("disabled");') %>
+        <%= l(:label_visibility_private) %>
+      </label>
+      <label class="block">
+        <%= f.radio_button :is_public, 1, :checked => @checklist_template.is_public?,
+                           :onchange => (User.current.admin? ? nil : '$("#checklist_template_is_for_all").removeAttr("checked"); $("#checklist_template_is_for_all").attr("disabled", true);') %>
+        <%= l(:label_visibility_public) %>
+      </label>
+    </p>
+  <% end %>
+
+  <p><label for="checklist_template_is_for_all"><%=l(:field_is_for_all)%></label>
+  <%= check_box_tag 'checklist_template_is_for_all', 1, @checklist_template.project.nil?,
+        :disabled => (!@checklist_template.new_record? && (@checklist_template.project.nil? || (@checklist_template.is_public? && !User.current.admin?)) || @project.nil? ) %></p>
+
+  <p id='is_default_block'><%= f.check_box :is_default, :label => l(:label_checklist_is_default) %></p>
+
+  <p>
+    <%= f.select :tracker_id, options_from_collection_for_select(@project ? @project.trackers : Tracker.all, :id, :name, f.object.tracker_id), :include_blank => true, :label => l(:label_tracker) %>
+  </p>
+
+  <p><%= f.text_area :template_items, :required => true, :rows => 5 %></p>
+  <p>
+    <em class='info'>
+      <%= l(:label_checklists_description) %>
+    </em>
+  </p>
+</div>
+
+<%= javascript_tag do %>
+  function isDefaultToggle(element){
+    if (element.prop('checked') == true) {
+      $('#is_default_block').hide();
+      $('#checklist_template_is_default').prop('checked', false);
+    } else {
+      $('#is_default_block').show();
+    }
+  }
+
+  $('#checklist_template_is_for_all').change(function(){
+    isDefaultToggle($(this));
+  });
+
+  isDefaultToggle($('#checklist_template_is_for_all'));
+<% end%>
diff --git a/plugins/redmine_checklists/app/views/checklist_templates/edit.html.erb b/plugins/redmine_checklists/app/views/checklist_templates/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..a7e2e7aa2920bdddbc9fae053c29044919da743c
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_templates/edit.html.erb
@@ -0,0 +1,6 @@
+<h2><%=l(:label_checklist_template)%></h2>
+
+<%= labelled_form_for :checklist_template, @checklist_template, :url => { :action => 'update', :project_id => @project } do |f| %>
+<%= render :partial => 'checklist_templates/form', :locals => { :f => f } %>
+<%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/checklist_templates/new.html.erb b/plugins/redmine_checklists/app/views/checklist_templates/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6c646f9e68af848798da2d7ea3dcc9283756abcc
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklist_templates/new.html.erb
@@ -0,0 +1,6 @@
+<h2><%=l(:label_checklist_new_checklist_template)%></h2>
+
+<%= labelled_form_for :checklist_template, @checklist_template, :url => { :action => 'create', :project_id => @project } do |f| %>
+<%= render :partial => 'checklist_templates/form', :locals => { :f => f } %>
+<%= submit_tag l(:button_create) %>
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/checklists/_checklist_item.html.erb b/plugins/redmine_checklists/app/views/checklists/_checklist_item.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..19d094ab9d86cd7a4a7f769e596a57c2b13e0601
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklists/_checklist_item.html.erb
@@ -0,0 +1,8 @@
+<li id="checklist_item_<%= checklist_item.id %>" <%= "class=is-done-checklist-item" if checklist_item.is_done %> >
+  <%= check_box_tag 'checklist_item', "", checklist_item.is_done,
+                    :disabled => !User.current.allowed_to?(:done_checklists, checklist_item.issue.project) && !User.current.allowed_to?(:edit_checklists, checklist_item.issue.project),
+                    :data_url => url_for( {:controller => "checklists", :action => "done", :id => checklist_item.id} ), :class => 'checklist-checkbox'
+  %>
+  <%= textilizable(checklist_item, :subject).gsub(/<\/?(p|h\d+|li|ul)>/, '').strip.html_safe %>
+
+</li>
diff --git a/plugins/redmine_checklists/app/views/checklists/done.js.erb b/plugins/redmine_checklists/app/views/checklists/done.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..9fc91bdaae3242bd04e532cb84dabb2139de08f8
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklists/done.js.erb
@@ -0,0 +1,4 @@
+$("#checklist_item_<%= @checklist_item.id %>").toggleClass('is-done-checklist-item');
+$('#checklist_form .checklist-item#<%= @checklist_item.id %> input[type=checkbox]').trigger('click');
+$('.issue .attributes table.progress').parent().html('<%= j(progress_bar(@checklist_item.issue.done_ratio, :width => '80px', :legend => "#{@checklist_item.issue.done_ratio}%")) %>');
+$('#issue_done_ratio').val('<%= @checklist_item.issue.done_ratio %>');
diff --git a/plugins/redmine_checklists/app/views/checklists/index.api.rsb b/plugins/redmine_checklists/app/views/checklists/index.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..4d1411afe7c0e07df263aab26d4403ad7f9fd135
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklists/index.api.rsb
@@ -0,0 +1,14 @@
+api.array :checklists, api_meta(:total_count => @checklists.size) do
+  @checklists.each do |checklist|
+    api.checklist do
+      api.id         checklist.id
+      api.issue_id   checklist.issue_id
+      api.subject    checklist.subject
+      api.is_done    checklist.is_done
+      api.position   checklist.position
+
+      api.created_at checklist.created_at
+      api.updated_at checklist.updated_at
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/app/views/checklists/show.api.rsb b/plugins/redmine_checklists/app/views/checklists/show.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..083b463647bebaed62bf7f2e8448aa700f3b6010
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/checklists/show.api.rsb
@@ -0,0 +1,10 @@
+api.checklist do
+  api.id         @checklist_item.id
+  api.issue_id   @checklist_item.issue_id
+  api.subject    @checklist_item.subject
+  api.is_done    @checklist_item.is_done
+  api.position   @checklist_item.position
+
+  api.created_at @checklist_item.created_at
+  api.updated_at @checklist_item.updated_at
+end
diff --git a/plugins/redmine_checklists/app/views/issues/_checklist.html.erb b/plugins/redmine_checklists/app/views/issues/_checklist.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..edf30eec8c54c81b69df0997ece7ef42236aa963
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/issues/_checklist.html.erb
@@ -0,0 +1,16 @@
+<%  if !@issue.blank? &&  @issue.checklists.any? && User.current.allowed_to?(:view_checklists, @project) %>
+<hr />
+<div id="checklist">
+
+  <p><strong><%=l(:label_checklist_plural)%></strong></p>
+
+  <ul id="checklist_items">
+  <% @issue.checklists.each do |checklist_item| %>
+    <%= render :partial => 'checklists/checklist_item', :object => checklist_item %>
+
+  <% end %>
+  </ul>
+</div>
+
+
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/issues/_checklist_fields.html.erb b/plugins/redmine_checklists/app/views/issues/_checklist_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..92dd2208df8c9ff59188b96b4da75e95c67dd26e
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/issues/_checklist_fields.html.erb
@@ -0,0 +1,21 @@
+<span class="checklist-item <%= new_or_show(f) %>" id = "<%=f.object.id%>">
+  <span class = "checklist-show-only checklist-checkbox"><%= f.check_box :is_done %></span>
+  <span class = "checklist-show checklist-subject <%= done_css(f) %>">
+    <%= f.object.subject %>
+  </span>
+  <span class = "checklist-edit checklist-new checklist-edit-box">
+    <%= text_field_tag nil, f.object.subject, :class => 'edit-box'%>
+    <%= f.hidden_field :subject, :class => 'checklist-subject-hidden' %>
+  </span>
+  <span class= "checklist-edit-only checklist-edit-save-button"><%= submit_tag l(:button_save), :type => "button", :class => "item item-save small"%> </span>
+  <span class= "checklist-edit-only checklist-edit-reset-button"><% concat l(:button_cancel)
+  %> </span>
+  <span class = "checklist-show-only checklist-remove"><%= link_to_remove_checklist_fields "", f,
+                                                                            :class => "icon icon-del"  %></span>
+  <%= f.hidden_field :position, :class => 'checklist-item-position' %>
+  <%= f.hidden_field :id, :class => 'checklist-item-id' %>
+  <span class = "icon icon-add checklist-new-only save-new-by-button"></span>
+
+ <br>
+</span>
+
diff --git a/plugins/redmine_checklists/app/views/issues/_checklist_form.html.erb b/plugins/redmine_checklists/app/views/issues/_checklist_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e7f85462571b65172ca9170df62b074bf781b694
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/issues/_checklist_form.html.erb
@@ -0,0 +1,26 @@
+<% if User.current.allowed_to?(:edit_checklists, @project, :global => true) %>
+  <div class="tabular">
+    <p id="checklist_form">
+      <label><%=l(:label_checklist_plural)%></label>
+      <% @issue.checklists.build if @issue.checklists.blank? || @issue.checklists.last.subject.present? %>
+      <%= fields_for :issue, issue do |f| -%>
+        <%= hidden_field_tag 'issue[checklist_template_id]', params[:issue][:checklist_template_id] if params[:issue] %>
+        <span id="checklist_form_items" data-checklist-fields='<%= fields(f, :checklists) %>'>
+          <%= f.fields_for :checklists do |builder| %>
+            <%= render :partial => 'checklist_fields', :locals => {:f => builder, :checklist => @checklist} %>
+          <% end %>
+          <%= render 'checklist_templates' if ChecklistTemplate.visible.in_project_and_global(@project).for_tracker_and_global(@issue.tracker).any? %>
+        </span>
+      <% end %>
+    </p>
+  </div>
+<% end %>
+
+<%= javascript_tag do %>
+  <% unless User.current.allowed_to?(:done_checklists, @project) %>
+    $("#checklist_items input").attr("disabled", true);
+  <% end %>
+
+  $("span#checklist_form_items").checklist();
+  $("#checklist_items").checklist();
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/issues/_checklist_templates.html.erb b/plugins/redmine_checklists/app/views/issues/_checklist_templates.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..fe355087373c8092252bc2b437e6b8389cb10a43
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/issues/_checklist_templates.html.erb
@@ -0,0 +1,4 @@
+<span class='search_for_watchers template-wrapper'>
+  <%= link_to l(:label_add_checklists_from_template), 'javascript:void(0)', :id => 'template-link' %>
+</span>
+<%= select :checklist, :template, template_options_for_select(@project, @issue.tracker), {}, :style => 'display: none' %>
diff --git a/plugins/redmine_checklists/app/views/projects/settings/_checklist_templates.html.erb b/plugins/redmine_checklists/app/views/projects/settings/_checklist_templates.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..19ba0c04868b21d9d46c2f7a1d5071bdcccf65fe
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/projects/settings/_checklist_templates.html.erb
@@ -0,0 +1,34 @@
+<% @checklist_templates = ChecklistTemplate.visible.in_project_and_global(@project).order("#{ChecklistTemplate.table_name}.name") %>
+<% if @checklist_templates %>
+  <table class="list">
+    <thead>
+      <tr>
+        <th><%= l(:field_name) %></th>
+        <th><%= "#{l(:field_visible)} #{l(:label_visibility_public)}" %></th>
+        <th><%= l(:field_is_for_all) %></th>
+        <th><%= l(:field_is_for_tracker) %></th>
+        <th></th>
+        </tr>
+    </thead>
+    <tbody>
+      <% @checklist_templates.each do |checklist_template| %>
+        <tr class="checklist-template <%= cycle 'odd', 'even' %>">
+          <td class="name"><%= checklist_template.name  %></td>
+          <td class="tick"><%= checked_image checklist_template.is_public? %></td>
+          <td class="tick"><%= checked_image checklist_template.project.blank? %></td>
+          <td><%= checklist_template.tracker  %></td>
+          <td class="buttons">
+            <% if User.current.admin? || User.current == checklist_template.user || User.current.allowed_to?(:manage_checklist_templates, checklist_template.project) %>
+              <%= link_to l(:button_edit), edit_project_checklist_template_path(@project, checklist_template), :class => 'icon icon-edit' %>
+              <%= delete_link checklist_template_path(checklist_template, :project_id => @project) %>
+            <% end %>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% else %>
+  <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
+
+<p><%= link_to l(:label_checklist_new_checklist_template), new_project_checklist_template_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_checklist_templates, @project) %></p>
diff --git a/plugins/redmine_checklists/app/views/settings/checklists/_checklists.html.erb b/plugins/redmine_checklists/app/views/settings/checklists/_checklists.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f59936bfeb1c9ab8eed95cd3c55a15ea44db3809
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/settings/checklists/_checklists.html.erb
@@ -0,0 +1,14 @@
+<% checklist_tabs = [
+  {:name => 'general', :partial => 'settings/checklists/general', :label => :label_general}]
+  checklist_tabs.push({:name => 'checklist_templates',
+                       :partial => 'settings/checklists/templates',
+                       :label => :label_checklist_templates})
+  checklist_tabs.push({:name => 'checklist_template_categories',
+                       :partial => 'settings/checklists/template_categories',
+                       :label => :label_checklist_template_category_plural})
+
+ %>
+
+<%= render_tabs checklist_tabs %>
+
+<% html_title(l(:label_settings), l(:label_checklists)) -%>
diff --git a/plugins/redmine_checklists/app/views/settings/checklists/_general.html.erb b/plugins/redmine_checklists/app/views/settings/checklists/_general.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..af667550d985d1ad416c9dd934012b8ee1fb2e55
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/settings/checklists/_general.html.erb
@@ -0,0 +1,13 @@
+<% if Setting.issue_done_ratio == "issue_field" %>
+<p>
+  <label for="settings_issue_done_ratio"><%= l(:label_checklist_done_ratio) %></label>
+  <%= hidden_field_tag 'settings[issue_done_ratio]', 0, :id => nil %>
+  <%= check_box_tag 'settings[issue_done_ratio]', 1, @settings["issue_done_ratio"].to_i > 0 %>
+</p>
+<% end %>
+
+<p>
+  <label for="settings_block_issue_closing"><%= l(:label_checklist_block_issue_closing) %></label>
+  <%= hidden_field_tag 'settings[block_issue_closing]', 0, :id => nil %>
+  <%= check_box_tag 'settings[block_issue_closing]', 1, @settings["block_issue_closing"].to_i > 0 %>
+</p>
diff --git a/plugins/redmine_checklists/app/views/settings/checklists/_template_categories.html.erb b/plugins/redmine_checklists/app/views/settings/checklists/_template_categories.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5853c1c444bce98102e3b14b08f1b6f3f671bce4
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/settings/checklists/_template_categories.html.erb
@@ -0,0 +1,29 @@
+<div class="contextual">
+<%= link_to l(:label_checklist_template_category_new), {:controller => "checklist_template_categories", :action => 'new'}, :class => 'icon icon-add' %>
+</div>
+
+<h3><%=l(:label_checklist_template_category_plural)%></h3>
+
+<table class="list">
+  <thead><tr>
+  <th><%=l(:field_checklist_template_category)%></th>
+  <th><%=l(:button_sort)%></th>
+  <th></th>
+  </tr></thead>
+  <tbody>
+    <% for category in ChecklistTemplateCategory.ordered  %>
+      <tr class="<%= cycle("odd", "even") %>">
+      <td class="name"><%= category.name %></td>
+      <td align="center" style="width:15%;"><%= stocked_reorder_link(category, 'category', {:controller => "checklist_template_categories", :action => 'update', :id => category}, :put) %></td>
+      <td class="buttons">
+        <%= link_to l(:button_edit), edit_checklist_template_category_path(category), :class => 'icon icon-edit' %>
+        <%= delete_link checklist_template_category_path(category) %>
+      </td>
+      </tr>
+    <% end %>
+  </tbody>
+</table>
+
+<%= javascript_tag do %>
+  $(function() { $("table.list tbody").positionedItems(); });
+<% end %>
diff --git a/plugins/redmine_checklists/app/views/settings/checklists/_templates.html.erb b/plugins/redmine_checklists/app/views/settings/checklists/_templates.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..8a1b487038ddf5a9e842c28d5d00cbd0366eba92
--- /dev/null
+++ b/plugins/redmine_checklists/app/views/settings/checklists/_templates.html.erb
@@ -0,0 +1,44 @@
+<div class='contextual'>
+  <%= link_to l(:label_checklist_new_checklist_template), new_checklist_template_path, :class => 'icon icon-add' if User.current.allowed_to?(:manage_checklist_templates, nil, { :global => true }) %>
+</div>
+
+<h3><%=l(:label_checklist_templates)%></h3>
+
+<% if ChecklistTemplate.any? %>
+  <table class="list">
+    <thead><tr>
+    <th><%= l(:field_name) %></th>
+    <th><%= "#{l(:field_visible)} #{l(:label_visibility_public)}" %></th>
+    <th><%= l(:field_project) %></th>
+    <th></th>
+    </tr></thead>
+    <tbody>
+    <% previous_group = false %>
+    <% ChecklistTemplate.eager_load(:category).each do |checklist_template| %>
+    <% if checklist_template.category != previous_group %>
+      <% reset_cycle %>
+      <% unless checklist_template.category.blank? %>
+      <tr class="group open">
+        <td colspan="4">
+          <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
+          <%= checklist_template.category.name %>
+        </td>
+      </tr>
+      <% end %>
+      <% previous_group = checklist_template.category %>
+    <% end %>
+    <tr class="checklist-template <%= cycle 'odd', 'even' %>">
+      <td class="name"><%= checklist_template.name  %></td>
+      <td class="tick"><%= checked_image checklist_template.is_public? %></td>
+      <td class="project"><%= checklist_template.project ? checklist_template.project.name : l(:field_is_for_all) %></td>
+      <td class="buttons">
+        <%= link_to l(:button_edit), edit_checklist_template_path(checklist_template), :class => 'icon icon-edit' %>
+        <%= delete_link checklist_template_path(checklist_template, :project_id => checklist_template.project) %>
+      </td>
+    </tr>
+    <% end %>
+    </tbody>
+  </table>
+<% else %>
+  <p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
diff --git a/plugins/redmine_checklists/assets/javascripts/checklists.js b/plugins/redmine_checklists/assets/javascripts/checklists.js
new file mode 100644
index 0000000000000000000000000000000000000000..1bdb224833cc58ec465cc3fe9d63277dd444651d
--- /dev/null
+++ b/plugins/redmine_checklists/assets/javascripts/checklists.js
@@ -0,0 +1,358 @@
+if(typeof(String.prototype.trim) === "undefined")
+{
+    String.prototype.trim = function()
+    {
+        return String(this).replace(/^\s+|\s+$/g, '');
+    };
+}
+
+/*! jQuery klass v0.2a - Jean-Louis Grall - MIT license - http://code.google.com/p/jquery-klass-plugin */
+
+( function( $, undefined ) {
+
+
+// Function: $.klass( [SuperKlass,] props )
+// Creates and returns a new class.
+// Usages:  MyKlass = $.klass( { init: function() { ... } } )
+//      MyKlass = $.klass( SuperKlass, { } )
+// Arguments:
+//    SuperKlass  (optional) The super class that the new class will extend.
+//    props   Set of methods and other class properties.
+// Special props names:
+//    init    The constructor. If omitted, an implicit init will be created.
+//          Thus all classes have an init method.
+//    _klass    Set of class methods (static methods). They will be added directly to the class.
+// Notes:
+//  - $.klass is the implicit super class, not Object
+    var $klass = $.klass = function( _super, fields ) { // The class factory. It is also the invisible "super class" of all classes. Methods added to its prototype will be available to all classes.
+
+            // If no _super:
+            if ( !fields ) {
+                fields = _super;
+                _super = undefined;
+            }
+
+            var
+            // init is our future class and constructor
+            // If no init is provided, make one (Implicit constructor)
+                klass = fields.init || ( fields.init = function() {
+                    // Automatically calls the superconstructor if there is one.
+                    _super && _super.prototype.init.apply( this, arguments );
+                } ),
+
+            // Used to make the new klass extends its super class
+                protoChainingProxy = function() { },
+
+            // klass.prototype
+                proto,
+
+            // index in loop
+                name;
+
+            // Prepare prototype chaining to the super class
+            // If no super class, use $.klass as implicit super class
+            protoChainingProxy.prototype = (_super || $klass).prototype;
+            // Make the [[prototype]]'s chain from klass to it's super class
+            proto = klass.prototype = new protoChainingProxy; // At the end we have: klass.prototype.[[prototype]] = protoChainingProxy.prototype = _super.prototype. Here the "new" operator creates the new object with the right prototype chain, but doesn't call the constructor because there is no "()". See also: http://brokenliving.blogspot.com/2009/09/simple-javascript-inheritance.html
+            // Now we have: klass.prototype.[[prototype]] = protoChainingProxy.prototype = _super.prototype
+
+            // Accessor for super klass ( can be undefined )
+            klass._super = _super;
+
+            // Add each function to the prototype of the new class (they are our new class methods):
+            for ( name in fields ) {
+                // Add the static variables to the new class:
+                if ( name === "_klass" ) $.extend( klass, fields[name] );
+                // Each new method keeps a reference to its name and its class, allowing us to find its super method dynamically at runtime:
+                else $.isFunction( proto[ name ] = fields[name] ) && ( fields[name]._klass = { klass: klass, name: name } );
+            }
+
+            // Sets the constructor for instanciated objects
+            proto.constructor = klass;
+
+            return klass;
+        },
+        Array_slice = [].slice;
+
+
+    /* $.klass.prototype */
+// Properties assigned to it are available from any instance of a class made by $.klass
+
+// Function: this._super( [ methodName,] arguments, args... )
+// Calls a super method. Finds the super method dynamically.
+// Usages:  this._super( arguments, arg1, arg2, arg3, ... )
+//      this._super( "methodName", arguments, arg1, arg2, arg3, ... )
+// Arguments:
+//    methodName  (optional) Name of the super method.
+//          By default, use the name of the calling method.
+//    arguments You must give the arguments object here.
+//    args...   List of arguments for the super method.
+// Note:
+//  - Super methods are found dynamically by the function in the super class using the method's name.
+    $klass.prototype._super = function( arg0, arg1 ) {
+        var arg0IsArguments = arg0.callee,
+            _klass = ( arg0IsArguments ? arg0 : arg1 ).callee._klass,
+            name = arg0IsArguments ? _klass.name : arg0,
+            superMethod = _klass.klass._super.prototype[ name ];
+        return superMethod.apply( this, Array_slice.call( arguments, 1 + ( !arg0IsArguments ) ) );
+    };
+
+})( jQuery );
+
+var Redmine = Redmine || {};
+
+Redmine.Checklist = $.klass({
+
+  preventEvent: function(event) {
+    if (event.preventDefault)
+      event.preventDefault()
+    else
+      event.returnValue = false
+  },
+
+  addChecklistFields: function(templateDiv) {
+    var new_id = new Date().getTime();
+    var regexp = new RegExp("new_checklist", "g");
+    if (templateDiv) {
+      appended = $(this.content.replace(regexp, new_id)).insertBefore(templateDiv)
+    } else {
+      appended = $(this.content.replace(regexp, new_id)).appendTo(this.root)
+    }
+    appended.find('.edit-box').focus()
+  },
+
+  findSpan: function(event) {
+    return $(event.target).closest('.checklist-item')
+  },
+
+  findSpanBefore: function(elem) {
+    return elem.prevAll('span.checklist-item.new')
+  },
+
+  transformItem: function(event, elem, valueToSet) {
+    var checklistItem;
+    if (event) {
+      checklistItem = this.findSpan(event)
+    } else {
+      checklistItem = this.findSpanBefore(elem)
+    }
+    var val;
+    if (valueToSet) {
+      val = valueToSet
+      checklistItem.find('input.edit-box').val(val)
+    } else {
+      val = checklistItem.find('input.edit-box').val()
+    }
+    checklistItem.find('.checklist-subject').text(val)
+    checklistItem.find('.checklist-subject-hidden').val(val)
+    checklistItem.removeClass('edit')
+    checklistItem.removeClass('new')
+    checklistItem.addClass('show')
+  },
+
+  resetItem: function(item) {
+    item.find('input.edit-box').val(item.find('checklist-subject-hidden').val() )
+    item.removeClass('edit')
+    item.addClass('show')
+  },
+
+  addChecklistItem: function(event) {
+    this.preventEvent(event)
+    this.transformItem(event)
+    if ($('.template-wrapper').length)
+      this.addChecklistFields($('.template-wrapper'))
+    else
+      this.addChecklistFields()
+  },
+
+  canSave: function(span) {
+    return (!span.hasClass('invalid')) && (span.find('input.edit-box').val().length > 0)
+  },
+
+  onEnterInNewChecklistItemForm: function() {
+    this.root.on('keydown', 'input.edit-box', $.proxy(function(event) {
+      if (event.which == 13) {
+        this.preventEvent(event)
+        span = this.findSpan(event)
+        if (this.canSave(span))
+        {
+          if (span.hasClass('edit'))
+            this.transformItem(event)
+          else
+            this.addChecklistItem(event)
+        }
+      }
+    }, this))
+  },
+
+  onClickPlusInNewChecklistItem: function() {
+    this.root.on('click', '.save-new-by-button', $.proxy(function(event){
+      span = this.findSpan(event)
+      if (this.canSave(span))
+        this.addChecklistItem(event)
+    }, this))
+  },
+
+  onIssueFormSubmitRemoveEmptyChecklistItems: function() {
+    $('body').on('submit', '#issue-form', function(){
+      $('.checklist-subject-hidden').each(function(i, elem) {
+        if ($(elem).val() == "")
+        {
+          $(elem).closest('.checklist-item').remove()
+        }
+      })
+    })
+  },
+
+  onChecklistRemove: function() {
+    this.root.on('click', '.checklist-remove a', $.proxy(function(event){
+      this.preventEvent(event);
+      var itemToRemove = this.findSpan(event);
+      var checkbox = itemToRemove.find(".checklist-remove input[type=hidden]");
+
+      if (checkbox.val() === "false") {
+        checkbox.val("1");
+        itemToRemove.fadeOut(200);
+      }
+    }, this));
+  },
+
+  makeChecklistsSortable: function() {
+    $('#checklist_form_items').sortable({
+      revert: true,
+      items: '.checklist-item.show',
+      helper: "clone",
+      stop: function (event, ui) {
+        if (ui.item.hasClass("edited-now")) {
+          $( this ).sortable( "cancel" );
+        }
+        if (ui.item.hasClass("edit-active")) {
+          $( this ).sortable( "cancel" );
+        }
+        $(".checklist-item").each(function(index, element){
+          $(element).children('.checklist-item-position').val(index);
+        });
+      }
+    });
+  },
+
+  makeChecklistsEditable: function() {
+    this.root.on('click', '.checklist-subject', $.proxy(function(event) {
+      $('.checklist-item').each($.proxy(function(i, elem) {
+        if ($(elem).hasClass('edit'))
+          this.resetItem($(elem))
+      }, this))
+
+      span = this.findSpan(event)
+      span.addClass('edit')
+      span.removeClass('show')
+      span.find('.edit-box').val(span.find('.checklist-subject-hidden').val())
+      span.find('.edit-box').focus()
+    }, this));
+    this.root.on('click', '.checklist-edit-save-button', $.proxy(function(event){
+      this.transformItem(event)
+    }, this))
+    this.root.on('click', '.checklist-edit-reset-button', $.proxy(function(event){
+      this.resetItem(this.findSpan(event))
+    }, this))
+  },
+
+  onCheckboxChanged: function() {
+    this.root.on('change', 'input[type=checkbox]', $.proxy(function(event){
+      checkbox = $(event.target)
+      subj = this.findSpan(event).find('.checklist-subject')
+      if (checkbox.is(':checked'))
+        subj.addClass('is-done-checklist-item')
+      else
+        subj.removeClass('is-done-checklist-item')
+    }, this))
+  },
+
+  onChangeCheckbox: function(){
+    this.root.on('change', 'input.checklist-checkbox', $.proxy(function(event){
+      checkbox = $(event.target)
+      url = checkbox.attr('data_url')
+      $.ajax({type: "PUT", url: url, data: { is_done: checkbox.prop('checked') }, dataType: 'script'})
+    }, this))
+  },
+
+  enableUniquenessValidation: function() {
+    this.root.on('keyup', 'input.edit-box', $.proxy(function(event) {
+      value = $(event.target).val()
+      span = this.findSpan(event)
+      span.removeClass('invalid')
+      $('.checklist-item').each(function(i, elem) {
+        e = $(elem)
+        if (!e.is('.edit') && !e.is('.new'))
+        {
+          if ( (value == e.find('.edit-box').val()) )
+          {
+            span.addClass('invalid')
+          }
+        }
+      })
+    }, this))
+  },
+
+  hasAlreadyChecklistWithName: function(value) {
+    var ret = false;
+    $('.checklist-show.checklist-subject').each(function(i, elem) {
+      e = $(elem)
+      if (value == e.text().trim())
+      {
+        ret = true;
+      }
+    })
+    return ret;
+  },
+
+  assignTemplateSelectedEvent: function() {
+    var item;
+    this.root.on('change', '#checklist_template', $.proxy(function(){
+      value = $('#checklist_template').val()
+      selected = $('#checklist_template option[value='+value+']').data('template-items')
+      items = selected.split(/\n/)
+      for(i = 0; i<items.length; i++)
+      {
+        item = items[i]
+        if (!this.hasAlreadyChecklistWithName(item))
+        {
+          this.transformItem(null, $('#checklist_template'), item)
+          this.addChecklistFields($('#template-link').closest('span'))
+        }
+      }
+      $('#checklist_template').val('')
+      $('#template-link').show()
+      $('#checklist_template').hide()
+
+    }, this))
+  },
+
+  clickSelectTemplateLink: function() {
+    this.root.on('click', '#template-link', function(){
+      $('#template-link').hide()
+      $('#checklist_template').show()
+    })
+  },
+
+  init: function(element) {
+    this.root = element
+    this.content = element.data('checklist-fields')
+    this.onEnterInNewChecklistItemForm()
+    this.onClickPlusInNewChecklistItem()
+    this.onIssueFormSubmitRemoveEmptyChecklistItems()
+    this.onChecklistRemove()
+    this.makeChecklistsSortable()
+    this.makeChecklistsEditable()
+    this.onCheckboxChanged()
+    this.onChangeCheckbox()
+    this.enableUniquenessValidation()
+    this.assignTemplateSelectedEvent()
+    this.clickSelectTemplateLink()
+  }
+
+})
+
+$.fn.checklist = function(element){
+  new Redmine.Checklist(this);
+}
diff --git a/plugins/redmine_checklists/assets/stylesheets/checklists.css b/plugins/redmine_checklists/assets/stylesheets/checklists.css
new file mode 100644
index 0000000000000000000000000000000000000000..030318f48a228184295a7ef4bfdfe8638d36afae
--- /dev/null
+++ b/plugins/redmine_checklists/assets/stylesheets/checklists.css
@@ -0,0 +1,77 @@
+
+div#checklist ul {
+	list-style: none;
+	padding-left: 0px;
+	margin-bottom: 0px;
+}
+
+div#checklist li {
+  padding-bottom: 10px;
+  margin-left: 10px;
+}
+
+#checklist li:hover a.delete {opacity: 1;}
+
+#checklist a.delete {opacity: 0.4;}
+
+span.checklist-item {
+  display: block;
+  margin-bottom: 5px;
+}
+
+span.checklist-subject.is-done-checklist-item, span.checklist-item.is-done-checklist-item, #checklist_items li.is-done-checklist-item  {
+  text-decoration: line-through;
+  color: #999;
+}
+
+span.checklist-remove { margin-left: 2px; opacity: 0.4;}
+
+span.checklist-remove:hover  {opacity: 1;}
+
+span.checklist-edit-box input {
+    margin-right: 6px;
+    width: 40%;
+    -moz-transition: top 0.2s;
+    -o-transition: top 0.2s;
+    -webkit-transition: top 0.2s;
+    transition: top 0.2s;
+}
+
+.invalid span.checklist-edit-box input {
+  border-color: #b94a48;
+  color: #b94a48;
+}
+
+.invalid span.checklist-edit-box input:focus {
+  border-color: #953b39;
+  color: #b94a48;
+}
+
+.invalid .checklist-edit-save-button {
+  display: none;
+}
+
+span.checklist-edit-reset-button {
+    cursor: pointer;
+    color: #2996CC;
+}
+
+span.checklist-subject {cursor: pointer;}
+
+span.checklist-item.show .checklist-edit,
+span.checklist-item.show .checklist-edit-only,
+span.checklist-item.show .checklist-new,
+span.checklist-item.show .checklist-new-only,
+span.checklist-item.edit .checklist-show,
+span.checklist-item.edit .checklist-new-only,
+span.checklist-item.new .checklist-edit-only,
+span.checklist-item.new .checklist-show-or-edit,
+span.checklist-item.new .checklist-show-only,
+span.checklist-item.edit .checklist-show-only {
+  display: none;
+}
+
+div#checklist ol {
+  display: inline-block;
+  padding-left: 0;
+}
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/bg.yml b/plugins/redmine_checklists/config/locales/bg.yml
new file mode 100644
index 0000000000000000000000000000000000000000..888a23b58fb55df514cb532adb33ecc235b8ae58
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/bg.yml
@@ -0,0 +1,9 @@
+# encoding: utf-8
+bg:
+  label_checklist_plural: Чеклист
+  field_checklist: Чеклист
+  label_checklist_save_log: Съхраняване на записи в историята на дейността
+  label_checklist_done_ratio: Преизчисляване на процента готовност
+  permission_view_checklists: Разглеждане на чеклисти
+  permission_done_checklists: Изпълнение на чеклисти
+  permission_edit_checklists: Редактиране на чеклисти
diff --git a/plugins/redmine_checklists/config/locales/de.yml b/plugins/redmine_checklists/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a90806672d31ab083a1c23999c7bea3ef6fbae1
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/de.yml
@@ -0,0 +1,29 @@
+# encoding: utf-8
+# German strings go here for Rails i18n
+de:
+  label_checklist_plural: Checkliste
+  field_checklist: Checkliste
+  label_checklist_save_log: "Speichere Änderungen in der Ticket-Historie"
+  label_checklist_done_ratio: Aktualisiere den Ticket-Fortschritt
+  permission_view_checklists: Checkliste anzeigen
+  permission_done_checklists: Checkliste anwenden
+  permission_edit_checklists: Checkliste bearbeiten
+  label_checklist_template_category_plural: Kategorien
+  label_checklist_template_category_new: Neue Kategorie
+  field_checklist_template_category: Kategorie
+  label_checklist_templates: Checklisten-Vorlagen
+  label_checklist_new_checklist_template: Neue Checklisten-Vorlage
+  field_template_items: "Einträge"
+  label_checklist_template: Checklisten-Vorlage
+  label_add_checklists_from_template: "Aus Vorlage wählen"
+  label_select_template: "-- Vorlage auswählen --"
+  label_checklist_category_not_specified: "-- Nicht spezifiziert --"
+  label_checklists_description: 'Mehrere Einträge erlaubt (ein Eintrag pro Zeile)'
+  label_checklist_item: Checklisten-Eintrag
+  label_checklist_deleted: gelöscht
+  label_checklist_changed_from: geändert von
+  label_checklist_changed_to: zu
+  label_checklist_added: hinzugefügt
+  label_checklist_done: als Erledigt markiert
+  label_checklist_undone: als Nicht Erledigt markiert
+  label_checklist_updated: Checklisten-Eintrag editiert
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/en.yml b/plugins/redmine_checklists/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4052f03c7e209538bd5ab7fed4216e4dd16b8bb2
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/en.yml
@@ -0,0 +1,36 @@
+# English strings go here for Rails i18n
+en:
+  label_checklist_plural: Checklist
+  field_checklist: Checklist
+  label_checklist_save_log: Save changes to issue log
+  label_checklist_done_ratio: Set issue done ratio
+  permission_view_checklists: View checklist
+  permission_done_checklists: Done checklist items
+  permission_edit_checklists: Edit checklist items
+  label_checklist_template_category_plural: Template categories
+  label_checklist_template_category_new: New category
+  field_checklist_template_category: Category
+  label_checklist_templates: Checklist templates
+  label_checklist_new_checklist_template: New checklist template
+  field_template_items: Template items
+  label_checklist_template: Checklist template
+  label_add_checklists_from_template: Add from template
+  label_select_template: "-- Select template --"
+  label_checklist_category_not_specified: "-- Not specified --"
+  label_checklists_description: 'Multiple values allowed (one line for each value)'
+  label_checklist_item: Checklist item
+  label_checklist_deleted: deleted
+  label_checklist_changed_from: changed from
+  label_checklist_changed_to: to
+  label_checklist_added: added
+  label_checklist_done: set to Done
+  label_checklist_undone: set to Not done
+  label_checklist_updated: Checklist item edited
+  label_checklist_status: Checklist status
+  label_checklist_status_done: Done
+  label_checklist_status_undone: Undone
+  label_checklist_status: Checklist status
+  label_checklist_is_default: Default
+  field_is_for_tracker: Tracker
+  label_checklists_must_be_completed: All checklists of an issue must be done before closing
+  label_checklist_block_issue_closing: Block issue closing
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/es.yml b/plugins/redmine_checklists/config/locales/es.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d129956b5205e5ccee96c8921b0a0ac88a289f4f
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/es.yml
@@ -0,0 +1,28 @@
+# Spanish strings go here for Rails i18n
+es:
+  label_checklist_plural: Checklist
+  field_checklist: Checklist
+  label_checklist_save_log: Salvar cambios al log de la petición
+  label_checklist_done_ratio: Setear porcentaje de estado de petición
+  permission_view_checklists: Ver checklist
+  permission_done_checklists: Cerrar items de checklist
+  permission_edit_checklists: Editar iterms de checklist
+  label_checklist_template_category_plural: Categorías de plantillas
+  label_checklist_template_category_new: Nueva categoría
+  field_checklist_template_category: Categoría
+  label_checklist_templates: Plantillas de checklists
+  label_checklist_new_checklist_template: Nueva plantilla de checklist
+  field_template_items: Items de la plantilla
+  label_checklist_template: Plantilla de checklist
+  label_add_checklists_from_template: Añadir desde plantilla
+  label_select_template: "-- Seleccionar plantilla --"
+  label_checklist_category_not_specified: "-- No especificada --"
+  label_checklists_description: 'Se permiten múltiples valores (un valor por línea)'
+  label_checklist_item: Item de checklist
+  label_checklist_deleted: borrado
+  label_checklist_changed_from: cambiado de
+  label_checklist_changed_to: a
+  label_checklist_added: añadido
+  label_checklist_done: marcado como Hecho
+  label_checklist_undone: marcado como No hecho
+  label_checklist_updated: Item de checklist modificado
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/fr.yml b/plugins/redmine_checklists/config/locales/fr.yml
new file mode 100755
index 0000000000000000000000000000000000000000..6a1ed72cf3ef47b9ace9b8f657ed55ec1fd13c70
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/fr.yml
@@ -0,0 +1,4 @@
+# encoding: utf-8
+fr:
+  label_checklist_plural: Liste de Tâches
+  field_checklist: Tâche
diff --git a/plugins/redmine_checklists/config/locales/ja.yml b/plugins/redmine_checklists/config/locales/ja.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c81518dad43ce4a547aa95d9dcc78241ea6aaa6c
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/ja.yml
@@ -0,0 +1,9 @@
+# encoding: utf-8
+ja:
+  label_checklist_plural: "チェックリスト" # Checklist
+  field_checklist: "チェックリスト" # Checklist
+  label_checklist_save_log: "変更をチケットに記録する" # Save changes to issue log
+  label_checklist_done_ratio: "チェック完了率" # Set issue done ratio
+  permission_view_checklists: "チェックリストの参照" # View checklist
+  permission_done_checklists: "チェックリストへ済印" # Done checklist items
+  permission_edit_checklists: "チェックリストの編集" # Edit checklist items
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/ko.yml b/plugins/redmine_checklists/config/locales/ko.yml
new file mode 100644
index 0000000000000000000000000000000000000000..24ede9a962c73794332d851887b12239aa923fbd
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/ko.yml
@@ -0,0 +1,9 @@
+# Translation by Ki Won Kim (http://x10.iptime.org/redmine, http://xyz37.blog.me, xyz37@naver.com)
+ko:
+  label_checklist_plural: "체크리스트"
+  field_checklist: "체크리스트"
+  label_checklist_save_log: "일감 로그에 변경사항을 저장합니다."
+  label_checklist_done_ratio: "완료 비율 일감 설정"
+  permission_view_checklists: "체크리스트 보기"
+  permission_done_checklists: "체크리스트 완료"
+  permission_edit_checklists: "체크리스트 수정"
diff --git a/plugins/redmine_checklists/config/locales/pl.yml b/plugins/redmine_checklists/config/locales/pl.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d91b76f290b769594015e6bd13dbc24da3a01828
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/pl.yml
@@ -0,0 +1,28 @@
+# English strings go here for Rails i18n
+pl:
+  label_checklist_plural: Lista
+  field_checklist: Checklista
+  label_checklist_save_log: Zapisuj zmiany w historii
+  label_checklist_done_ratio: Ustawia wskaźnik wykonania dla zgłoszeń
+  permission_view_checklists: Zobacz listÄ™
+  permission_done_checklists: Ustaw jako ukończone elementy checklisty
+  permission_edit_checklists: Edytuj elementy checklisty
+  label_checklist_template_category_plural: Kategorie szablonów
+  label_checklist_template_category_new: Nowa kategoria
+  field_checklist_template_category: Kategoria
+  label_checklist_templates: Szablony checklist
+  label_checklist_new_checklist_template: New checklist template
+  field_template_items: Elementy szablonu
+  label_checklist_template: Szablon checklisty
+  label_add_checklists_from_template: Dodaj z szablonu
+  label_select_template: "-- Wybierz szablon --"
+  label_checklist_category_not_specified: "-- Nie wybrano --"
+  label_checklists_description: 'Dozwolone wiele wartości (po jednej linii dla każdej wartości)'
+  label_checklist_item: Element checklisty
+  label_checklist_deleted: usunięty
+  label_checklist_changed_from: zmieniony z
+  label_checklist_changed_to: na
+  label_checklist_added: dodany
+  label_checklist_done: ustaw jako Wykonane
+  label_checklist_undone: ustaw jako Niewykonane
+  label_checklist_updated: Zmieniono element checklisty
diff --git a/plugins/redmine_checklists/config/locales/pt-BR.yml b/plugins/redmine_checklists/config/locales/pt-BR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0991c43f23f94768871d92bcf2a1f6d00413a141
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/pt-BR.yml
@@ -0,0 +1,9 @@
+#Portuguese Brazilian strings go here for Rails i18n
+pt-BR:
+  label_checklist_plural: Checklist
+  field_checklist: Checklist
+  label_checklist_save_log: Salvar alterações nos logs das tarefas
+  label_checklist_done_ratio: Definir % terminando quando desmarcar o item
+  permission_view_checklists: Ver checklist
+  permission_done_checklists: Remover itens do checklist
+  permission_edit_checklists: Alterar itens do checklist
diff --git a/plugins/redmine_checklists/config/locales/ru.yml b/plugins/redmine_checklists/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e3d5d6b7cf85041594ff16b9e1aadd8f138b65f
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/ru.yml
@@ -0,0 +1,33 @@
+# encoding: utf-8
+ru:
+  label_checklist_plural: Чеклист
+  field_checklist: Чеклист
+  label_checklist_save_log: Сохранять изменения в истории
+  label_checklist_done_ratio: Рассчитывать готовность задачи
+  permission_view_checklists: Просматривать чеклисты
+  permission_done_checklists: Выполнять чеклисты
+  permission_edit_checklists: Редактировать чеклисты
+  label_checklist_template_category_plural: Категории шаблонов чеклистов
+  label_checklist_template_category_new: Новая категория шаблонов чеклистов
+  field_checklist_template_category: Категория
+  label_checklist_templates: Шаблоны чеклистов
+  label_checklist_new_checklist_template: Новый шаблон чеклистов
+  field_template_items: Элементы шаблона
+  label_checklist_template: Шаблон чеклистов
+  label_add_checklists_from_template: Добавить из шаблона
+  label_select_template: "-- Выберите шаблон --"
+  label_checklist_category_not_specified: "-- Без категории --"
+  label_checklists_description: 'Для ввода нескольких значений вводите по одному на строку'
+  label_checklist_item: Пункт чеклиста
+  label_checklist_deleted: удалён
+  label_checklist_changed_from: изменён с
+  label_checklist_changed_to: на
+  label_checklist_added: добавлен
+  label_checklist_done: выполнен
+  label_checklist_undone: не выполнен
+  label_checklist_updated: Пункт чеклиста изменен
+  label_checklist_status: Статус пункта чеклиста
+  label_checklist_status_done: Выполнен
+  label_checklist_status_undone: Не выполнен
+  label_checklist_is_default: По умолчанию
+  field_is_for_tracker: Трекер
diff --git a/plugins/redmine_checklists/config/locales/sk.yml b/plugins/redmine_checklists/config/locales/sk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1fe026c5847fa76ff1d92fbb33adc262503d61f6
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/sk.yml
@@ -0,0 +1,10 @@
+# encoding: utf-8
+# Slovak strings go here for Rails i18n
+sk:
+  label_checklist_plural: Checklist
+  field_checklist: Checklist
+  label_checklist_save_log: Uložiť zmeny do histórie úlohy
+  label_checklist_done_ratio: Nastaviť úlohu ako kompletnú
+  permission_view_checklists: Zobraziť checklist
+  permission_done_checklists: Splnené checklist položky
+  permission_edit_checklists: Upraviť checklist položky
diff --git a/plugins/redmine_checklists/config/locales/sv.yml b/plugins/redmine_checklists/config/locales/sv.yml
new file mode 100644
index 0000000000000000000000000000000000000000..797b1e3207397f8a00309983d4e65d72f2192264
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/sv.yml
@@ -0,0 +1,11 @@
+# Swedish strings go here for Rails i18n
+# Translation by Khedron Wilk (khedron.wilk@gmail.com)
+# Created 2014 Dec 14 Based on RedmineUP Checklist plugin (Light) ver 3.0.2
+sv:
+  label_checklist_plural: Checklista
+  field_checklist: Checklista
+  label_checklist_save_log: Spara ändringar i ärendeloggen
+  label_checklist_done_ratio: Använd checklista för att beräkna % klart
+  permission_view_checklists: Visa checklistor
+  permission_done_checklists: Klara checklistepunkter
+  permission_edit_checklists: Redigera checklistepunkter
diff --git a/plugins/redmine_checklists/config/locales/uk.yml b/plugins/redmine_checklists/config/locales/uk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5c039450ae66fa7677a95e2fee9f2f39df77a239
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/uk.yml
@@ -0,0 +1,9 @@
+# encoding: utf-8
+uk:
+  label_checklist_plural: Чекліст
+  field_checklist: Чекліст
+  label_checklist_save_log: Зберегти зміни в історії
+  label_checklist_done_ratio: Розрахувати готовність завдання
+  permission_view_checklists: Переглядати чеклісти
+  permission_done_checklists: Виконувати чеклісти
+  permission_edit_checklists: Редагувати чеклісти
\ No newline at end of file
diff --git a/plugins/redmine_checklists/config/locales/zh.yml b/plugins/redmine_checklists/config/locales/zh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d5fb51032658b6bb0c88777921185b217e3362b9
--- /dev/null
+++ b/plugins/redmine_checklists/config/locales/zh.yml
@@ -0,0 +1,32 @@
+# encoding: utf-8
+# Simplified Chinese strings go here for Rails i18n
+# Author: Steven.W
+# Based on file: en.yml
+
+zh:
+  label_checklist_plural: 检查列表
+  field_checklist: 检查列表
+  label_checklist_save_log: 保存检查列表变更到问题日志
+  label_checklist_done_ratio: 设置列表完成比率
+  permission_view_checklists: 查看检查列表
+  permission_done_checklists: 完成检查项
+  permission_edit_checklists: 编辑检查项
+  label_checklist_template_category_plural: 模板类别
+  label_checklist_template_category_new: 新类别
+  field_checklist_template_category: 类别
+  label_checklist_templates: 检查列表模板
+  label_checklist_new_checklist_template: 新检查列表模板
+  field_template_items: 模板项
+  label_checklist_template: 检查列表模板
+  label_add_checklists_from_template: 从模板新建
+  label_select_template: "-- 选择模板 --"
+  label_checklist_category_not_specified: "-- 未指定 --"
+  label_checklists_description: '允许多重值 (每一行的值)'
+  label_checklist_item: 检查列表项
+  label_checklist_deleted: 删除
+  label_checklist_changed_from: 改变自
+  label_checklist_changed_to: 到
+  label_checklist_added: 添加
+  label_checklist_done: 设置完成
+  label_checklist_undone: 设置未完成
+  label_checklist_updated: 检查列表项编辑
diff --git a/plugins/redmine_checklists/config/routes.rb b/plugins/redmine_checklists/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98d51b4c7164fd6d1304315d10fa651a0bf40206
--- /dev/null
+++ b/plugins/redmine_checklists/config/routes.rb
@@ -0,0 +1,35 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+resources :issues do
+  resources :checklists, :only => [:index, :create]
+end
+
+resources :checklists, :only => [:destroy, :update, :show] do
+  member do
+    put :done
+  end
+end
+resources :projects do
+  resources :checklist_templates, :only => [:new, :create, :update, :edit]
+end
+
+resources :checklist_templates
+
+resources :checklist_template_categories, :only => [:edit, :update, :new, :create, :destroy]
diff --git a/plugins/redmine_checklists/db/migrate/001_create_checklists.rb b/plugins/redmine_checklists/db/migrate/001_create_checklists.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f5a56c3230e95cfe25110d4ecbdf4db21728b3d7
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/001_create_checklists.rb
@@ -0,0 +1,38 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateChecklists < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+
+  def self.up
+    if ActiveRecord::Base.connection.table_exists? :issue_checklists
+      rename_table :issue_checklists, :checklists
+    else
+      create_table :checklists do |t|
+        t.boolean :is_done, :default => false
+        t.string :subject
+        t.integer :position, :default => 1
+        t.references :issue, :null => false
+      end
+    end
+  end
+
+  def self.down
+    drop_table :checklists
+  end
+end
diff --git a/plugins/redmine_checklists/db/migrate/002_add_time_stamps_to_checklists.rb b/plugins/redmine_checklists/db/migrate/002_add_time_stamps_to_checklists.rb
new file mode 100644
index 0000000000000000000000000000000000000000..db3f1a49096e556063bbd5bf7e614befac68a868
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/002_add_time_stamps_to_checklists.rb
@@ -0,0 +1,25 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class AddTimeStampsToChecklists < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def change
+    add_column :checklists, :created_at, :timestamp
+    add_column :checklists, :updated_at, :timestamp
+  end
+end
diff --git a/plugins/redmine_checklists/db/migrate/003_create_checklist_template_category.rb b/plugins/redmine_checklists/db/migrate/003_create_checklist_template_category.rb
new file mode 100644
index 0000000000000000000000000000000000000000..700930edf0c3d7d417aa1fbc3013d3647a5fe193
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/003_create_checklist_template_category.rb
@@ -0,0 +1,32 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateChecklistTemplateCategory < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+
+  def self.up
+    create_table :checklist_template_categories do |t|
+      t.string :name
+      t.integer :position, :default => 1
+    end
+  end
+
+  def self.down
+    drop_table :checklist_template_categories
+  end
+end
diff --git a/plugins/redmine_checklists/db/migrate/004_create_checklist_templates.rb b/plugins/redmine_checklists/db/migrate/004_create_checklist_templates.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89b7a0774ec0d9ffbcaf7357d7f6a7e8f74ce13e
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/004_create_checklist_templates.rb
@@ -0,0 +1,36 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateChecklistTemplates < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+
+  def self.up
+    create_table :checklist_templates do |t|
+      t.string :name
+      t.references :project
+      t.references :category
+      t.references :user
+      t.boolean :is_public
+      t.text :template_items
+    end
+  end
+
+  def self.down
+    drop_table :checklist_templates
+  end
+end
diff --git a/plugins/redmine_checklists/db/migrate/005_modify_checklist_subject_length.rb b/plugins/redmine_checklists/db/migrate/005_modify_checklist_subject_length.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7707d7c5b3d6d5fe54cf94bdcbf5fd123577ae56
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/005_modify_checklist_subject_length.rb
@@ -0,0 +1,28 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class ModifyChecklistSubjectLength < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def self.up
+    change_column :checklists, :subject, :string, :limit => 512
+  end
+
+  def self.down
+    change_column :checklists, :subject, :string, :limit => 256
+  end
+end
diff --git a/plugins/redmine_checklists/db/migrate/006_add_fields_to_checklist_template.rb b/plugins/redmine_checklists/db/migrate/006_add_fields_to_checklist_template.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f5a75fae46452b7081d5fce804560aed3534f041
--- /dev/null
+++ b/plugins/redmine_checklists/db/migrate/006_add_fields_to_checklist_template.rb
@@ -0,0 +1,31 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class AddFieldsToChecklistTemplate < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def self.up
+    add_column :checklist_templates, :is_default, :boolean, :default => false
+    add_column :checklist_templates, :tracker_id, :integer
+    add_index :checklist_templates, :tracker_id
+  end
+
+  def self.down
+    remove_column :checklist_templates, :is_default
+    remove_column :checklist_templates, :tracker_id
+  end
+end
diff --git a/plugins/redmine_checklists/doc/CHANGELOG b/plugins/redmine_checklists/doc/CHANGELOG
new file mode 100644
index 0000000000000000000000000000000000000000..c86364d19f90e327e84ca6730b7c219e78c85799
--- /dev/null
+++ b/plugins/redmine_checklists/doc/CHANGELOG
@@ -0,0 +1,103 @@
+== Redmine Checklists plugin changelog
+
+Redmine Checklists plugin - managing issue checklists plugin for Redmine
+Copyright (C) 2011-2018 RedmineUP
+http://www.redmineup.com/
+
+== 2018-03-23 v3.1.11
+
+* Rails 4 support
+* Setting for block issues with undone checklists
+* Fixed bug with default template
+* Fixed email notification bug
+
+== 2017-10-12 v3.1.10
+
+* Fixed email notification bug
+* Fixed empty project issues bug
+
+== 2017-09-23 v3.1.9
+
+* Fixed bug with creating issues without checklist
+
+== 2017-09-21 v3.1.8
+
+* Default templates bug fixed
+
+== 2017-08-30 v3.1.7
+
+* Assigned tracker for checklist template
+* Fixed bug with coping project and issue with subissues
+* Fixed settings saving bug
+* 512 characters in checklist subjects (Ondřej Kudlík)
+* Fixed bug for markdown
+
+== 2017-07-07 v3.1.6
+
+* Redmine 3.4 support
+* New checklists filters for issues table
+* Save log by default 
+* Chinese translation update
+* Polish translation update
+* Fixed bug with template editing 
+
+== 2016-08-15 v3.1.5
+
+* Fixed bug with project tabs
+
+== 2016-08-10 v3.1.4
+
+* Fixed empty history items for issues with checklists
+* Fixed XSS in journal rendering
+* Fixed error with template search in MSSQL
+* Banner plugin compatibility
+* Fixed for-all-project template option
+* Validation for subject length
+* Hungarian translation by Peter Tabajdi
+* Spanish translation update by Luis Blasco
+* Merge checklists history if less than minute interval changes
+
+== 2015-09-25 v3.1.3
+
+* Bug with attach files history fixed
+* Bug with old checkboxes fixed
+
+== 2015-09-25 v3.1.2
+
+* Issue history details
+* Templates permissions fixes
+
+== 2015-04-08 v3.1.0
+
+* Checklist templates
+
+== 2015-03-06 v3.0.4
+
+* Redmine 3.0 compatibility fixes
+* Fixed checklist styles on read only issue mode
+
+== 2015-02-23 v3.0.3
+
+* Swedish translation update (Khedron Wilk)
+* Portuguese Brazilian translation (Leandro Gehlen)
+* Redmine 3.0 (Rails4) support
+* Copying checklists with project copying (Andrew Reshetov)
+* Fixed bug with unable editing checklist after tracker or status changed
+
+== 2014-12-09 v3.0.2
+
+* Fixed bug wuth empty subject with CKEditor plugin
+
+== 2014-09-15 v3.0.1
+
+* Bulgarian translation (Иван Ценов)
+* Fixed bug with IE browser
+
+== 2014-09-10 v3.0.0
+
+* Editing checklist items
+* REST API for index, create, destroy, update, show
+* Change issues progress in real time
+* Sync checked items with show and edit forms
+* Fixed bug with rejecting new checklist items on update issue status
+* Japan translation (Yukio KANEDA)
diff --git a/plugins/redmine_checklists/doc/COPYING b/plugins/redmine_checklists/doc/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..63e41a44cffe44662260b7b902e4b065b201b515
--- /dev/null
+++ b/plugins/redmine_checklists/doc/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
\ No newline at end of file
diff --git a/plugins/redmine_checklists/doc/LICENSE b/plugins/redmine_checklists/doc/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..e2e50b5e93e1f520b12a10aba06bcd201e0b07c9
--- /dev/null
+++ b/plugins/redmine_checklists/doc/LICENSE
@@ -0,0 +1,26 @@
+LICENSING
+
+RedmineUP Licencing
+
+This End User License Agreement is a binding legal agreement between you and RedmineUP. Purchase, installation or use of RedmineUP Extensions provided on redmineup.com signifies that you have read, understood, and agreed to be bound by the terms outlined below.
+
+RedmineUP GPL Licencing
+
+All Redmine Extensions produced by RedmineUP are released under the GNU General Public License, version 2 (http://www.gnu.org/licenses/gpl-2.0.html). Specifically, the Ruby code portions are distributed under the GPL license. If not otherwise stated, all images, manuals, cascading style sheets, and included JavaScript are NOT GPL, and are released under the RedmineUP Proprietary Use License v1.0 (See below) unless specifically authorized by RedmineUP. Elements of the extensions released under this proprietary license may not be redistributed or repackaged for use other than those allowed by the Terms of Service.
+
+RedmineUP Proprietary Use License (v1.0)
+
+The RedmineUP Proprietary Use License covers any images, cascading stylesheets, manuals and JavaScript files in any extensions produced and/or distributed by redmineup.com. These files are copyrighted by redmineup.com (RedmineUP) and cannot be redistributed in any form without prior consent from redmineup.com (RedmineUP)
+
+Usage Terms
+
+You are allowed to use the Extensions on one or many "production" domains, depending on the type of your license
+You are allowed to make any changes to the code, however modified code will not be supported by us.
+
+Modification Of Extensions Produced By RedmineUP.
+
+You are authorized to make any modification(s) to RedmineUP extension Ruby code. However, if you change any Ruby code and it breaks functionality, support may not be available to you.
+
+In accordance with the RedmineUP Proprietary Use License v1.0, you may not release any proprietary files (modified or otherwise) under the GPL license. The terms of this license and the GPL v2 prohibit the removal of the copyright information from any file.
+
+Please contact us if you have any requirements that are not covered by these terms.
\ No newline at end of file
diff --git a/plugins/redmine_checklists/init.rb b/plugins/redmine_checklists/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..174d9d75606090d2bea82d898f23ca52ed462e5e
--- /dev/null
+++ b/plugins/redmine_checklists/init.rb
@@ -0,0 +1,54 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require 'redmine'
+require 'redmine_checklists/redmine_checklists'
+
+CHECKLISTS_VERSION_NUMBER = '3.1.11'.freeze
+CHECKLISTS_VERSION_TYPE = 'PRO version'.freeze
+
+
+Redmine::Plugin.register :redmine_checklists do
+  name "Redmine Checklists plugin (#{CHECKLISTS_VERSION_TYPE})"
+  author 'RedmineUP'
+  description 'This is a issue checklist plugin for Redmine'
+  version CHECKLISTS_VERSION_NUMBER
+  url 'https://www.redmineup.com/pages/plugins/checklists'
+  author_url 'mailto:support@redmineup.com'
+
+  requires_redmine :version_or_higher => '2.3'
+
+  settings :default => {
+    :save_log => true,
+    :issue_done_ratio => false
+  }, :partial => 'settings/checklists/checklists'
+
+  Redmine::AccessControl.map do |map|
+    map.project_module :issue_tracking do |map|
+      map.permission :view_checklists, { :checklists => [:show, :index] }
+      map.permission :done_checklists, { :checklists => :done }
+      map.permission :edit_checklists, { :checklists => [:done, :create, :destroy, :update] }
+      map.permission :manage_checklist_templates, { :checklist_templates => [:new, :create, :destroy, :edit, :update] }
+    end
+  end
+
+  Redmine::Search.map do |search|
+    # search.register :checklists
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/hooks/controller_issues_hook.rb b/plugins/redmine_checklists/lib/redmine_checklists/hooks/controller_issues_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef97797a5ce30812fa2ee4879cf657e1255d1ceb
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/hooks/controller_issues_hook.rb
@@ -0,0 +1,43 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Hooks
+    class ControllerIssuesHook < Redmine::Hook::ViewListener
+      def controller_issues_edit_after_save(context = {})
+        old_checklists = context[:issue].old_checklists
+        new_checklists = context[:issue].checklists.to_json
+        journal = context[:journal]
+        details = JournalChecklistHistory.new(old_checklists, new_checklists).journal_details
+        if JournalChecklistHistory.can_fixup?(details)
+          JournalChecklistHistory.fixup(details)
+        elsif details.old_value != details.value
+          journal.details << details
+          journal.save
+        else
+          journal.save
+        end
+
+        if (Setting.issue_done_ratio == "issue_field") && RedmineChecklists.settings["issue_done_ratio"].to_i > 0
+          Checklist.recalc_issue_done_ratio(context[:issue].id)
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_issues_hook.rb b/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_issues_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3923d25d1aceec4b4b5c1ffd382b1e8399ecf672
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_issues_hook.rb
@@ -0,0 +1,27 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Hooks
+    class ViewsIssuesHook < Redmine::Hook::ViewListener
+      render_on :view_issues_show_description_bottom, :partial => "issues/checklist"
+      render_on :view_issues_form_details_bottom, :partial => "issues/checklist_form"
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_layouts_hook.rb b/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_layouts_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7631101ede7fdac70e940e637d403c1529aba159
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/hooks/views_layouts_hook.rb
@@ -0,0 +1,29 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Hooks
+    class ViewsLayoutsHook < Redmine::Hook::ViewListener
+      def view_layouts_base_html_head(context={})
+        return javascript_include_tag(:checklists, :plugin => 'redmine_checklists') +
+          stylesheet_link_tag(:checklists, :plugin => 'redmine_checklists')
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/add_helpers_for_checklists_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/add_helpers_for_checklists_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..750e2d03646489f4b2749686f7b03ae1103d407d
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/add_helpers_for_checklists_patch.rb
@@ -0,0 +1,33 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Patches
+
+    module AddHelpersForChecklistPatch
+      def self.apply(controller)
+        controller.send(:helper, 'checklists')
+      end
+    end
+  end
+end
+
+[IssuesController].each do |controller|
+  RedmineChecklists::Patches::AddHelpersForChecklistPatch.apply(controller)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/2.1/redmine_api_test_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/2.1/redmine_api_test_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d7ad48cd567202578463ebf13e4ee13bacb1ba3d
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/2.1/redmine_api_test_patch.rb
@@ -0,0 +1,275 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module Redmine
+  module ApiTest
+    # Base class for API tests
+    class Base < ActionDispatch::IntegrationTest
+      # Test that a request allows the three types of API authentication
+      #
+      # * HTTP Basic with username and password
+      # * HTTP Basic with an api key for the username
+      # * Key based with the key=X parameter
+      #
+      # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
+      # @param [String] url the request url
+      # @param [optional, Hash] parameters additional request parameters
+      # @param [optional, Hash] options additional options
+      # @option options [Symbol] :success_code Successful response code (:success)
+      # @option options [Symbol] :failure_code Failure response code (:unauthorized)
+      def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
+        should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
+        should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
+        should_allow_key_based_auth(http_method, url, parameters, options)
+      end
+
+      # Test that a request allows the username and password for HTTP BASIC
+      #
+      # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
+      # @param [String] url the request url
+      # @param [optional, Hash] parameters additional request parameters
+      # @param [optional, Hash] options additional options
+      # @option options [Symbol] :success_code Successful response code (:success)
+      # @option options [Symbol] :failure_code Failure response code (:unauthorized)
+      def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
+        success_code = options[:success_code] || :success
+        failure_code = options[:failure_code] || :unauthorized
+
+        context "should allow http basic auth using a username and password for #{http_method} #{url}" do
+          context "with a valid HTTP authentication" do
+            setup do
+              @user = User.generate! do |user|
+                user.admin = true
+                user.password = 'my_password'
+              end
+              send(http_method, url, parameters, credentials(@user.login, 'my_password'))
+            end
+
+            should_respond_with success_code
+            should_respond_with_content_type_based_on_url(url)
+            should "login as the user" do
+              assert_equal @user, User.current
+            end
+          end
+
+          context "with an invalid HTTP authentication" do
+            setup do
+              @user = User.generate!
+              send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
+            end
+
+            should_respond_with failure_code
+            should_respond_with_content_type_based_on_url(url)
+            should "not login as the user" do
+              assert_equal User.anonymous, User.current
+            end
+          end
+
+          context "without credentials" do
+            setup do
+              send(http_method, url, parameters)
+            end
+
+            should_respond_with failure_code
+            should_respond_with_content_type_based_on_url(url)
+            should "include_www_authenticate_header" do
+              assert @controller.response.headers.has_key?('WWW-Authenticate')
+            end
+          end
+        end
+      end
+
+      # Test that a request allows the API key with HTTP BASIC
+      #
+      # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
+      # @param [String] url the request url
+      # @param [optional, Hash] parameters additional request parameters
+      # @param [optional, Hash] options additional options
+      # @option options [Symbol] :success_code Successful response code (:success)
+      # @option options [Symbol] :failure_code Failure response code (:unauthorized)
+      def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
+        success_code = options[:success_code] || :success
+        failure_code = options[:failure_code] || :unauthorized
+
+        context "should allow http basic auth with a key for #{http_method} #{url}" do
+          context "with a valid HTTP authentication using the API token" do
+            setup do
+              @user = User.generate! do |user|
+                user.admin = true
+              end
+              @token = Token.create!(:user => @user, :action => 'api')
+              send(http_method, url, parameters, credentials(@token.value, 'X'))
+            end
+            should_respond_with success_code
+            should_respond_with_content_type_based_on_url(url)
+            should_be_a_valid_response_string_based_on_url(url)
+            should "login as the user" do
+              assert_equal @user, User.current
+            end
+          end
+
+          context "with an invalid HTTP authentication" do
+            setup do
+              @user = User.generate!
+              @token = Token.create!(:user => @user, :action => 'feeds')
+              send(http_method, url, parameters, credentials(@token.value, 'X'))
+            end
+            should_respond_with failure_code
+            should_respond_with_content_type_based_on_url(url)
+            should "not login as the user" do
+              assert_equal User.anonymous, User.current
+            end
+          end
+        end
+      end
+
+      # Test that a request allows full key authentication
+      #
+      # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
+      # @param [String] url the request url, without the key=ZXY parameter
+      # @param [optional, Hash] parameters additional request parameters
+      # @param [optional, Hash] options additional options
+      # @option options [Symbol] :success_code Successful response code (:success)
+      # @option options [Symbol] :failure_code Failure response code (:unauthorized)
+      def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
+        success_code = options[:success_code] || :success
+        failure_code = options[:failure_code] || :unauthorized
+
+        context "should allow key based auth using key=X for #{http_method} #{url}" do
+          context "with a valid api token" do
+            setup do
+              @user = User.generate! do |user|
+                user.admin = true
+              end
+              @token = Token.create!(:user => @user, :action => 'api')
+              # Simple url parse to add on ?key= or &key=
+              request_url = if url.match(/\?/)
+                              url + "&key=#{@token.value}"
+                            else
+                              url + "?key=#{@token.value}"
+                            end
+              send(http_method, request_url, parameters)
+            end
+            should_respond_with success_code
+            should_respond_with_content_type_based_on_url(url)
+            should_be_a_valid_response_string_based_on_url(url)
+            should "login as the user" do
+              assert_equal @user, User.current
+            end
+          end
+
+          context "with an invalid api token" do
+            setup do
+              @user = User.generate! do |user|
+                user.admin = true
+              end
+              @token = Token.create!(:user => @user, :action => 'feeds')
+              # Simple url parse to add on ?key= or &key=
+              request_url = if url.match(/\?/)
+                              url + "&key=#{@token.value}"
+                            else
+                              url + "?key=#{@token.value}"
+                            end
+              send(http_method, request_url, parameters)
+            end
+            should_respond_with failure_code
+            should_respond_with_content_type_based_on_url(url)
+            should "not login as the user" do
+              assert_equal User.anonymous, User.current
+            end
+          end
+        end
+
+        context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
+          setup do
+            @user = User.generate! do |user|
+              user.admin = true
+            end
+            @token = Token.create!(:user => @user, :action => 'api')
+            send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
+          end
+          should_respond_with success_code
+          should_respond_with_content_type_based_on_url(url)
+          should_be_a_valid_response_string_based_on_url(url)
+          should "login as the user" do
+            assert_equal @user, User.current
+          end
+        end
+      end
+
+      # Uses should_respond_with_content_type based on what's in the url:
+      #
+      # '/project/issues.xml' => should_respond_with_content_type :xml
+      # '/project/issues.json' => should_respond_with_content_type :json
+      #
+      # @param [String] url Request
+      def self.should_respond_with_content_type_based_on_url(url)
+        case
+        when url.match(/xml/i)
+          should "respond with XML" do
+            assert_equal 'application/xml', @response.content_type
+          end
+        when url.match(/json/i)
+          should "respond with JSON" do
+            assert_equal 'application/json', @response.content_type
+          end
+        else
+          raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
+        end
+      end
+
+      # Uses the url to assert which format the response should be in
+      #
+      # '/project/issues.xml' => should_be_a_valid_xml_string
+      # '/project/issues.json' => should_be_a_valid_json_string
+      #
+      # @param [String] url Request
+      def self.should_be_a_valid_response_string_based_on_url(url)
+        case
+        when url.match(/xml/i)
+          should_be_a_valid_xml_string
+        when url.match(/json/i)
+          should_be_a_valid_json_string
+        else
+          raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
+        end
+      end
+
+      # Checks that the response is a valid JSON string
+      def self.should_be_a_valid_json_string
+        should "be a valid JSON string (or empty)" do
+          assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
+        end
+      end
+
+      # Checks that the response is a valid XML string
+      def self.should_be_a_valid_xml_string
+        should "be a valid XML string" do
+          assert REXML::Document.new(response.body)
+        end
+      end
+
+      def self.should_respond_with(status)
+        should "respond with #{status}" do
+          assert_response status
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_controller_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..22a5595b77f02bf1507b54d5c7c9548e0a32e331
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_controller_patch.rb
@@ -0,0 +1,41 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Patches
+    module ApplicationControllerPatch
+      def self.included(base) # :nodoc:
+        base.extend(ClassMethods)
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+        end
+      end
+
+      module ClassMethods
+        def before_action(*filters, &block)
+          before_filter(*filters, &block)
+        end
+      end
+    end
+  end
+end
+
+unless ApplicationController.included_modules.include?(RedmineChecklists::Patches::ApplicationControllerPatch)
+  ApplicationController.send(:include, RedmineChecklists::Patches::ApplicationControllerPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_helper_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a627386b299d4cd03dcc93a1cb8f4af8f2aa9215
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/application_helper_patch.rb
@@ -0,0 +1,38 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Patches
+    module ApplicationHelperPatch
+      def self.included(base) # :nodoc:
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+
+          def stocked_reorder_link(object, name = nil, url = {}, method = :post)
+            Redmine::VERSION.to_s > '3.3' ? reorder_handle(object, :param => name) : reorder_links(name, url, method)
+          end
+        end
+      end
+    end
+  end
+end
+
+unless ApplicationHelper.included_modules.include?(RedmineChecklists::Patches::ApplicationHelperPatch)
+  ApplicationHelper.send(:include, RedmineChecklists::Patches::ApplicationHelperPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/journal_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/journal_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4e48af9a1c8da20852f93e12a803e11d7813d153
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/journal_patch.rb
@@ -0,0 +1,75 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+
+require_dependency 'journal'
+
+module RedmineChecklists
+  module Patches
+    module JournalPatch
+
+      def self.included(base) # :nodoc:
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          after_create :send_checklist_notification
+        end
+      end
+
+      module InstanceMethods
+        def send_checklist_notification
+          detail = detail_for_attribute('checklist')
+          checklist_email_nootification(self).deliver if !Setting.notified_events.include?('issue_updated') &&
+                                                         Setting.notified_events.include?('checklist_updated') &&
+                                                         detail.present? &&
+                                                         !JournalChecklistHistory.new(detail.old_value, detail.value).empty_diff?
+        end
+
+        if Redmine::VERSION.to_s < '2.6'
+          def send_notification
+            if notify? &&
+                (Setting.notified_events.include?('issue_updated') ||
+                  (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
+                  (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
+                  (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?)
+                )
+              checklist_email_nootification(self).deliver
+            end
+          end
+
+          def detail_for_attribute(attribute)
+            details.detect { |detail| detail.prop_key == attribute }
+          end
+        end
+
+        def checklist_email_nootification(journal)
+          if Redmine::VERSION.to_s < '2.4'
+            Mailer.issue_edit(journal)
+          else
+            Mailer.issue_edit(journal, journal.notified_users, journal.notified_watchers - journal.notified_users)
+          end
+        end
+      end
+
+    end
+  end
+end
+
+unless Journal.included_modules.include?(RedmineChecklists::Patches::JournalPatch)
+  Journal.send(:include, RedmineChecklists::Patches::JournalPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/open_struct_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/open_struct_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d0d27e10a10c790e19cdf472ee888a26aed6863e
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility/open_struct_patch.rb
@@ -0,0 +1,37 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+class OpenStruct2 < OpenStruct
+  undef id if defined?(id)
+
+  def to_h
+    json
+  end
+
+  def [](key)
+    json[key.to_s]
+  end
+
+  def json
+    return @json if @json
+    @json = JSON.parse(to_json)
+    @json = @json['table'] if @json.has_key?('table')
+    @json
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e1460ebbf0f9686b9c4f6fd606ba399cd509401
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/compatibility_patch.rb
@@ -0,0 +1,22 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+if Redmine::VERSION.to_s < '2.3'
+  Dir[File.dirname(__FILE__) + '/compatibility/2.1/*.rb'].each { |f| require f }
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab57df57ef926c66599ba855925d46bc79cc464c
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_patch.rb
@@ -0,0 +1,81 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'issue'
+
+module RedmineChecklists
+  module Patches
+    module IssuePatch
+      def self.included(base) # :nodoc:
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+          attr_accessor :old_checklists
+          attr_accessor :removed_checklist_ids
+          attr_reader :copied_from
+
+          alias_method :copy_without_checklist, :copy
+          alias_method :copy, :copy_with_checklist
+          after_save :copy_subtask_checklists
+
+          if ActiveRecord::VERSION::MAJOR >= 4
+            has_many :checklists, lambda { order("#{Checklist.table_name}.position") }, :class_name => 'Checklist', :dependent => :destroy, :inverse_of => :issue
+          else
+            has_many :checklists, :class_name => 'Checklist', :dependent => :destroy, :inverse_of => :issue, :order => 'position'
+          end
+
+          accepts_nested_attributes_for :checklists, :allow_destroy => true, :reject_if => proc { |attrs| attrs['subject'].blank? }
+
+          validate :block_issue_closing_if_checklists_unclosed
+
+          safe_attributes 'checklists_attributes',
+            :if => lambda { |issue, user| (user.allowed_to?(:done_checklists, issue.project) || user.allowed_to?(:edit_checklists, issue.project)) }
+
+          def copy_checklists(arg)
+            issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
+            issue.checklists.each{ |checklist| Checklist.create(checklist.attributes.except('id', 'issue_id').merge(:issue => self)) } if issue
+          end
+
+          def block_issue_closing_if_checklists_unclosed
+            if RedmineChecklistSetting.block_issue_closing? && checklists.any? && status.is_closed?
+              errors.add(:checklists, l(:label_checklists_must_be_completed)) unless (checklists - checklists.where(:id => removed_checklist_ids)).all?(&:is_done)
+            end
+          end
+        end
+      end
+
+      module InstanceMethods
+        def copy_subtask_checklists
+          return if !copy? || parent_id.nil? || checklists.any?
+          copy_checklists(@copied_from)
+        end
+
+        def copy_with_checklist(attributes = nil, copy_options = {})
+          copy = copy_without_checklist(attributes, copy_options)
+          copy.copy_checklists(self)
+          copy
+        end
+      end
+    end
+  end
+end
+
+unless Issue.included_modules.include?(RedmineChecklists::Patches::IssuePatch)
+  Issue.send(:include, RedmineChecklists::Patches::IssuePatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_query_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_query_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d6b2de3c44586e3f73b0ea24a600454075dfb079
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/issue_query_patch.rb
@@ -0,0 +1,90 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'query'
+
+module RedmineChecklists
+  module Patches
+    module IssueQueryPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+          alias_method :available_filters_without_checklists, :available_filters
+          alias_method :available_filters, :available_filters_with_checklists
+        end
+      end
+
+      module InstanceMethods
+
+        def available_filters_with_checklists
+          if @available_filters.blank?
+            add_available_filter('checklists_status', :type => :list, :name => l(:label_checklist_status),
+                                             :values => [[l(:label_checklist_status_done), '1'], [l(:label_checklist_status_undone), '0']]) unless available_filters_without_checklists.key?('checklists_status') && !User.current.allowed_to?(:view_checklists, project, :global => true)
+
+            add_available_filter('checklists_item', :type => :string, :name => l(:label_checklist_item)) unless available_filters_without_checklists.key?('checklists_item') && !User.current.allowed_to?(:view_checklists, project, :global => true)
+          else
+            available_filters_without_checklists
+          end
+          @available_filters
+        end
+
+        def sql_for_checklists_status_field(_field, operator, value)
+          case operator
+          when '='
+            compare = '='
+          when '!'
+            compare = '!='
+          end
+          condition =
+            if value.size > 1
+              '1=1'
+            else
+              "is_done #{compare} #{value.join == '1' ? self.class.connection.quoted_true : self.class.connection.quoted_false}"
+            end
+          issue_ids = "SELECT DISTINCT(#{Checklist.table_name}.issue_id) FROM #{Checklist.table_name} WHERE #{condition}"
+          "(#{Issue.table_name}.id IN (#{issue_ids}))"
+        end
+
+        def sql_for_checklists_item_field(_field, operator, value)
+          case operator
+          when '=', '!'
+            condition = "#{Checklist.table_name}.subject = ?"
+          when '~', '!~'
+            condition = "LOWER(#{Checklist.table_name}.subject) LIKE LOWER(?)"
+            value = "%#{value.join}%"
+          when '*', '!*'
+            condition = '1=1'
+          end
+          issue_ids = Checklist.where(condition, value).pluck(:issue_id).uniq
+          if ['!', '!~'].include?(operator)
+            all_issue_ids = Checklist.pluck(:issue_id).uniq
+            issue_ids = all_issue_ids - issue_ids
+          end
+          return '1=0' if issue_ids.empty?
+          "(#{Issue.table_name}.id #{'NOT' if operator == '!*'} IN (#{issue_ids.join(',')}))"
+        end
+      end
+    end
+  end
+end
+
+unless IssueQuery.included_modules.include?(RedmineChecklists::Patches::IssueQueryPatch)
+  IssueQuery.send(:include, RedmineChecklists::Patches::IssueQueryPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_controller_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..af244e65f743e923e33028caa2c91aa6047aa2be
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_controller_patch.rb
@@ -0,0 +1,113 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Patches
+
+    module IssuesControllerPatch
+      def self.included(base) # :nodoc:
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+
+          alias_method :build_new_issue_from_params_without_checklist, :build_new_issue_from_params
+          alias_method :build_new_issue_from_params, :build_new_issue_from_params_with_checklist
+          before_action :save_before_state, :only => [:update]
+        end
+      end
+
+      module InstanceMethods
+        def build_new_issue_from_params_with_checklist
+          if params[:id].blank?
+            if params[:copy_from].blank?
+              fill_default_checklist
+            else
+              fill_checklist_attributes
+            end
+          end
+          build_new_issue_from_params_without_checklist
+        end
+
+        def save_before_state
+          @issue.old_checklists = @issue.checklists.to_json
+          checklists_params = params[:issue].present? && params[:issue][:checklists_attributes].present? ? params[:issue][:checklists_attributes] : {}
+          @issue.removed_checklist_ids =
+            if checklists_params.present?
+              checklists_params = checklists_params.respond_to?(:to_unsafe_hash) ? checklists_params.to_unsafe_hash : checklists_params
+              checklists_params.map { |_k, v| v['id'].to_i if ['1', 'true'].include?(v['_destroy']) }.compact
+            else
+              []
+            end
+        end
+
+        def fill_checklist_attributes
+          return unless params[:issue].blank?
+          begin
+            @copy_from = Issue.visible.find(params[:copy_from])
+            add_checklists_to_params(@copy_from.checklists)
+          rescue ActiveRecord::RecordNotFound
+            render_404
+            return
+          end
+        end
+        def fill_default_checklist
+          return if custom_checklists_included?(params[:issue])
+          params[:issue] ||= {}
+          tracker_id = params[:issue].try(:[], :tracker_id) || issue_project.trackers.first.try(:id)
+          default_template = issue_project.default_checklist_template(tracker_id)
+          return params[:issue][:checklists_attributes] = {} if default_template.nil?
+          params[:issue][:checklist_template_id] = default_template.id
+          add_checklists_to_params(default_template.checklists)
+        end
+
+        def add_checklists_to_params(checklists)
+          params[:issue].blank? ? params[:issue] = { :checklists_attributes => {} } : params[:issue][:checklists_attributes] = {}
+          checklists.each_with_index do |checklist_item, index|
+            params[:issue][:checklists_attributes][index.to_s] = { :is_done => checklist_item.is_done,
+                                                                   :subject => checklist_item.subject,
+                                                                   :position => checklist_item.position }
+          end
+        end
+        def custom_checklists_included?(issue_attr)
+          return if issue_attr.blank? || issue_attr[:checklists_attributes].blank? || checklist_subjects(issue_attr[:checklists_attributes]).empty?
+          default_template = issue_project.checklist_templates.visible.where(:id => issue_attr[:checklist_template_id]).first
+          return true unless default_template
+          checklist_subjects(issue_attr[:checklists_attributes]) != default_template.checklists.map(&:subject)
+        end
+
+        def checklist_subjects(attrs)
+          if attrs.is_a?(Array) # JSON/XML
+            attrs.map { |checklist_attr| checklist_attr[:subject] }.compact
+          else
+            attrs = attrs.permit!.to_h if Rails::VERSION::MAJOR >= 5
+            attrs.map { |_k, v| v[:subject] if v[:subject].present? }.compact
+          end
+        end
+
+        def issue_project
+          @project || Project.where(:id => Issue.new.allowed_target_projects.map(&:id)).order(:id).first
+        end
+      end
+    end
+  end
+end
+
+unless IssuesController.included_modules.include?(RedmineChecklists::Patches::IssuesControllerPatch)
+  IssuesController.send(:include, RedmineChecklists::Patches::IssuesControllerPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_helper_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f010f4e57f964a6ed2f064a1d419ae701207d5ad
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/issues_helper_patch.rb
@@ -0,0 +1,111 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  module Patches
+    module IssuesHelperPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+
+        base.class_eval do
+          unloadable
+
+          alias_method :details_to_strings_without_checklists, :details_to_strings
+          alias_method :details_to_strings, :details_to_strings_with_checklists
+          if Redmine::VERSION.to_s >= '2.2' && Redmine::VERSION.to_s <= '2.4'
+            alias_method :render_email_issue_attributes_without_checklists, :render_email_issue_attributes
+            alias_method :render_email_issue_attributes, :render_email_issue_attributes_with_checklists
+          end
+        end
+      end
+
+
+      module InstanceMethods
+
+        def render_email_issue_attributes_with_checklists(issue, html = false)
+          journal = issue.journals.order(:id).last
+          return render_email_issue_attributes_without_checklists(issue, html) unless journal
+          details = journal.details
+          return render_email_issue_attributes_without_checklists(issue, html) unless details
+          checklist_details = details.select{ |x| x.prop_key == 'checklist'}
+          return render_email_issue_attributes_without_checklists(issue, html) unless checklist_details.any?
+          return render_email_issue_attributes_without_checklists(issue, html) + details_to_strings_with_checklists(checklist_details, !html).join(html ? "<br/>".html_safe : "\n")
+        end
+
+        def details_to_strings_with_checklists(details, no_html = false, options = {})
+          details_checklist, details_other = details.partition{ |x| x.prop_key == 'checklist' }
+          details_checklist.map do |detail|
+            result = []
+            diff = Hash.new([])
+
+            if Checklist.old_format?(detail)
+              result << "<b>#{l(:label_checklist_item)}</b> #{l(:label_checklist_changed_from)} #{detail.old_value} #{l(:label_checklist_changed_to)} #{detail.value}"
+            else
+              diff = JournalChecklistHistory.new(detail.old_value, detail.value).diff
+            end
+            if diff[:removed].any?
+              diff[:removed].each do |item|
+                result << "<b>#{ERB::Util.h l(:label_checklist_item)}</b> #{ERB::Util.h l(:label_checklist_deleted)} (<strike><i>#{ERB::Util.h item[:subject]}</i></strike>)"
+              end
+            end
+
+            if diff[:renamed].any?
+              diff[:renamed].each do |was, became|
+                result << "<b>#{ERB::Util.h l(:label_checklist_item)}</b> #{ERB::Util.h l(:label_checklist_changed_from)} <i>#{ERB::Util.h was}</i> #{ERB::Util.h l(:label_checklist_changed_to)} <i>#{ERB::Util.h  became}</i>"
+              end
+            end
+
+            if diff[:added].any?
+              diff[:added].each do |item|
+                result << "<b>#{ERB::Util.h l(:label_checklist_item)}</b> <input type='checkbox' #{item.is_done ? 'checked' : '' } disabled> <i>#{ERB::Util.h item[:subject]}</i> #{ERB::Util.h l(:label_checklist_added)}"
+              end
+            end
+
+            if diff[:done].any?
+              diff[:done].each do |item|
+                result << "<b>#{ERB::Util.h l(:label_checklist_item)}</b> <input type='checkbox' #{item.is_done ? 'checked' : '' } disabled> <i>#{ERB::Util.h item[:subject]}</i> #{ERB::Util.h l(:label_checklist_done)}"
+              end
+            end
+
+            if diff[:undone].any?
+              diff[:undone].each do |item|
+                result << "<b>#{ERB::Util.h l(:label_checklist_item)}</b> <input type='checkbox' #{item.is_done ? 'checked' : '' } disabled> <i>#{ERB::Util.h item[:subject]}</i> #{ERB::Util.h l(:label_checklist_undone)}"
+              end
+            end
+
+            result = result.join('</li><li>').html_safe
+            result = nil if result.blank?
+            if result && no_html
+              result = result.gsub /<\/li><li>/, "\n"
+              result = result.gsub /<input type='checkbox'[^c^>]*checked[^>]*>/, '[x]'
+              result = result.gsub /<input type='checkbox'[^c^>]*>/, '[ ]'
+              result = result.gsub /<[^>]*>/, ''
+              result = CGI.unescapeHTML(result)
+            end
+            result
+          end.compact + details_to_strings_without_checklists(details_other, no_html, options)
+        end
+      end
+    end
+  end
+end
+
+unless IssuesHelper.included_modules.include?(RedmineChecklists::Patches::IssuesHelperPatch)
+  IssuesHelper.send(:include, RedmineChecklists::Patches::IssuesHelperPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/notifiable_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/notifiable_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..911a3fe25c2358c117df6b62031c7574d613c70b
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/notifiable_patch.rb
@@ -0,0 +1,49 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+
+module RedmineChecklists
+  module Patches
+    module NotifiablePatch
+      def self.included(base)
+        base.extend(ClassMethods)
+        base.class_eval do
+          unloadable
+          class << self
+            alias_method :all_without_checklists, :all
+            alias_method :all, :all_with_checklists
+          end
+        end
+      end
+
+      module ClassMethods
+        def all_with_checklists
+          notifications = all_without_checklists
+          last_issue_child_index = notifications.find_index(notifications.select{ |element| element.parent == 'issue_updated' }.last)
+          notifications.insert(last_issue_child_index + 1, Redmine::Notifiable.new('checklist_updated', 'issue_updated'))
+          notifications
+        end
+      end
+    end
+  end
+end
+
+unless Redmine::Notifiable.included_modules.include?(RedmineChecklists::Patches::NotifiablePatch)
+  Redmine::Notifiable.send(:include, RedmineChecklists::Patches::NotifiablePatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/project_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/project_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c374f34b870022861c352033b93aa4c92fa56e9
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/project_patch.rb
@@ -0,0 +1,53 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'project'
+
+module RedmineChecklists
+  module Patches
+    module ProjectPatch
+      def self.included(base) # :nodoc:
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable # Send unloadable so it will not be unloaded in development
+          alias_method :copy_issues_without_checklist, :copy_issues
+          alias_method :copy_issues, :copy_issues_with_checklist
+          has_many :checklist_templates
+        end
+      end
+
+      module InstanceMethods
+        def default_checklist_template(tracker_id = nil)
+          default_templates = checklist_templates.visible.default
+          default_by_tracker = default_templates.for_tracker_id(tracker_id).first
+          default_by_tracker || default_templates.for_tracker_id(nil).first
+        end
+
+        def copy_issues_with_checklist(project)
+          copy_issues_without_checklist(project)
+          issues.each{ |issue| issue.copy_checklists(issue.copied_from)}
+        end
+      end
+    end
+  end
+end
+
+unless Project.included_modules.include?(RedmineChecklists::Patches::ProjectPatch)
+  Project.send(:include, RedmineChecklists::Patches::ProjectPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/patches/projects_helper_patch.rb b/plugins/redmine_checklists/lib/redmine_checklists/patches/projects_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51b6fe2fb7cb9c170116a8f3bde3f49b480e44a3
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/patches/projects_helper_patch.rb
@@ -0,0 +1,52 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+
+module RedmineChecklists
+  module Patches
+    module ProjectsHelperPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+
+        base.class_eval do
+          unloadable
+
+          alias_method :project_settings_tabs_without_checklists, :project_settings_tabs
+          alias_method :project_settings_tabs, :project_settings_tabs_with_checklists
+        end
+      end
+
+      module InstanceMethods
+        def project_settings_tabs_with_checklists
+          tabs = project_settings_tabs_without_checklists
+          tab = { :name => 'checklist_template',
+                  :action => :manage_checklist_templates,
+                  :partial => 'projects/settings/checklist_templates',
+                  :label => :label_checklist_templates }
+          tabs << tab if User.current.allowed_to?(:edit_issues, @project) && User.current.allowed_to?(tab[:action], @project)
+          tabs
+        end
+      end
+    end
+  end
+end
+
+unless ProjectsHelper.included_modules.include?(RedmineChecklists::Patches::ProjectsHelperPatch)
+  ProjectsHelper.send(:include, RedmineChecklists::Patches::ProjectsHelperPatch)
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklist_setting.rb b/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklist_setting.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a8761003f5d40877eb4a17d89e87860897cfd66e
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklist_setting.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineChecklists
+  class RedmineChecklistSetting
+    def self.block_issue_closing?
+      Setting.plugin_redmine_checklists['block_issue_closing'].to_i > 0
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklists.rb b/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklists.rb
new file mode 100644
index 0000000000000000000000000000000000000000..966a59b8cc2b4314a92ba3793e48912254116b69
--- /dev/null
+++ b/plugins/redmine_checklists/lib/redmine_checklists/redmine_checklists.rb
@@ -0,0 +1,43 @@
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+Rails.configuration.to_prepare do
+  require 'redmine_checklists/patches/compatibility/application_helper_patch'
+  require 'redmine_checklists/patches/compatibility/application_controller_patch' if Rails::VERSION::MAJOR < 4
+
+  require 'redmine_checklists/hooks/views_issues_hook'
+  require 'redmine_checklists/hooks/views_layouts_hook'
+  require 'redmine_checklists/hooks/controller_issues_hook'
+
+  require 'redmine_checklists/patches/issue_patch'
+  require 'redmine_checklists/patches/project_patch'
+  require 'redmine_checklists/patches/issues_controller_patch'
+  require 'redmine_checklists/patches/add_helpers_for_checklists_patch'
+  require 'redmine_checklists/patches/compatibility_patch'
+  require 'redmine_checklists/patches/projects_helper_patch'
+  require 'redmine_checklists/patches/notifiable_patch'
+  require 'redmine_checklists/patches/issue_query_patch'
+  require 'redmine_checklists/patches/issues_helper_patch'
+  require 'redmine_checklists/patches/compatibility/open_struct_patch'
+  require 'redmine_checklists/patches/compatibility/journal_patch'
+end
+
+module RedmineChecklists
+  def self.settings() Setting[:plugin_redmine_checklists].blank? ? {} : Setting[:plugin_redmine_checklists] end
+end
diff --git a/plugins/redmine_checklists/scripts/run_local.sh b/plugins/redmine_checklists/scripts/run_local.sh
new file mode 100755
index 0000000000000000000000000000000000000000..dc77740d3f4d53101067e1d97b1fbcb155153615
--- /dev/null
+++ b/plugins/redmine_checklists/scripts/run_local.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+RUBY=$1
+DB=$2
+REDMINE=$3
+
+docker run -t -i -v `pwd`:/var/www/$RUBY/$DB/$REDMINE/plugins/redmine_checklists \
+  --env RUBY=$1 \
+  --env DB=$2 \
+  --env REDMINE=$3 \
+  --env PLUGIN=redmine_checklists \
+  redmineup/redmine_checklists \
+  /root/run_local.sh
diff --git a/plugins/redmine_checklists/test/fixtures/checklists.yml b/plugins/redmine_checklists/test/fixtures/checklists.yml
new file mode 100644
index 0000000000000000000000000000000000000000..573b24aeed8e79c98a2e995856c06be8c9cd3379
--- /dev/null
+++ b/plugins/redmine_checklists/test/fixtures/checklists.yml
@@ -0,0 +1,16 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  is_done: false
+  subject: First todo
+  issue_id: 1
+two:
+  id: 2
+  is_done: true
+  subject: Second todo
+  issue_id: 1
+three:
+  id: 3
+  is_done: true
+  subject: Third todo
+  issue_id: 2
diff --git a/plugins/redmine_checklists/test/functional/checklist_template_categories_controller_test.rb b/plugins/redmine_checklists/test/functional/checklist_template_categories_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6c242b21736dc3490f88dd9b6a2186279dd417b7
--- /dev/null
+++ b/plugins/redmine_checklists/test/functional/checklist_template_categories_controller_test.rb
@@ -0,0 +1,92 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+class ChecklistTemplateCategoriesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    Project.find(1).enable_module!(:checklists)
+    Project.find(1).enable_module!(:issue_tracking)
+    User.current = nil
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.find(1)
+    @checklist_1 = Checklist.find(1)
+  end
+
+  test 'should show new form' do
+    @request.session[:user_id] = 1
+    compatible_request :get, :new
+    assert_select 'form.new_checklist_template_category div.box.tabular'
+  end
+
+  test 'creates new checklist template category' do
+    @request.session[:user_id] = 1
+    compatible_request :post, :create, :checklist_template_category => { :name => 'test1' }
+    assert_equal 1, ChecklistTemplateCategory.count
+  end
+
+  test 'should show edit form' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplateCategory.create!(:name => 'category1')
+    compatible_request :get, :edit, :id => @template.to_param
+    assert_select 'form.edit_checklist_template_category div.box.tabular'
+  end
+
+  test 'should update checklist template category' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplateCategory.create!(:name => 'category1')
+    compatible_request :put, :update, :id => @template.to_param, :category => { :name => 'category2' }
+    assert_equal 'category2', ChecklistTemplateCategory.last.name
+  end
+
+  test 'should delete checklist template' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplateCategory.create!(:name => 'category1')
+    compatible_request :delete, :destroy, :id => @template.to_param
+    assert_equal 0, ChecklistTemplateCategory.count
+  end
+end
diff --git a/plugins/redmine_checklists/test/functional/checklist_templates_controller_test.rb b/plugins/redmine_checklists/test/functional/checklist_templates_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..73a637430f53a399c3ab8c476a034c821339671e
--- /dev/null
+++ b/plugins/redmine_checklists/test/functional/checklist_templates_controller_test.rb
@@ -0,0 +1,135 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ChecklistTemplatesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    Project.find(1).enable_module!(:checklists)
+    Project.find(1).enable_module!(:issue_tracking)
+    User.current = nil
+    @second_user = User.find(2)
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.find(1)
+    @checklist_1 = Checklist.find(1)
+    MemberRole.create(:member_id => 1, :role_id => 2)
+  end
+
+  test 'should show new form' do
+    @request.session[:user_id] = 1
+    compatible_request :get, :new
+    assert_select 'form.new_checklist_template div.box.tabular'
+  end
+
+  test 'creates new checklist template' do
+    @request.session[:user_id] = 1
+    compatible_request :post, :create, :checklist_template => { :name => 'test1', :template_items => 'item1 item2' }, :checklist_template_is_for_all => true
+    assert_equal 'test1', ChecklistTemplate.last.name
+    assert_equal 1, ChecklistTemplate.last.user_id
+  end
+
+  test 'user with right can create template' do
+    @request.session[:user_id] = @second_user.id
+    compatible_request :post, :create, :checklist_template => { :name => 'user_test', :template_items => 'item1 item2' }, :project_id => @project_1
+    assert_equal 'user_test', ChecklistTemplate.last.name
+    assert_equal 2, ChecklistTemplate.last.user_id
+    assert_equal @project_1, ChecklistTemplate.last.project
+  end
+
+  test 'user cant create public template for all projects' do
+    @request.session[:user_id] = @second_user.id
+    compatible_request :post, :create, :checklist_template => { :name => 'public_template', :template_items => 'item1 item2', :is_public => '1' },
+                                       :checklist_template_is_for_all => '1',
+                                       :project_id => @project_1
+    assert_equal 'public_template', ChecklistTemplate.last.name
+    assert_equal 2, ChecklistTemplate.last.user_id
+    assert_equal true, ChecklistTemplate.last.is_public
+    assert_equal @project_1, ChecklistTemplate.last.project
+  end
+
+  test 'nobody cant edit user template if it not public, except admins' do
+    @request.session[:user_id] = @second_user.id
+    compatible_request :post, :create, :checklist_template => { :name => 'not_public_template', :template_items => 'item1 item2', :is_public => '0' },
+                                       :checklist_template_is_for_all => '1',
+                                       :project_id => @project_1
+    assert_equal 'not_public_template', ChecklistTemplate.last.name
+    @request.session[:user_id] = 3
+    compatible_request :get, :edit, :project_id => @project_1, :id => ChecklistTemplate.last.id
+    assert_response :missing
+  end
+
+  test 'should show edit form' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplate.create!(:name => 'template1', :template_items => 'item1 item2', :is_public => true)
+    compatible_request :get, :edit, :id => @template.to_param
+    assert_select 'form.edit_checklist_template div.box.tabular'
+  end
+
+  test 'should update checklist template' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplate.create!(:name => 'template1', :template_items => 'item1 item2', :is_public => true)
+    compatible_request :put, :update, :id => @template.to_param, :checklist_template => { :name => 'test2' }
+    assert_equal 'test2', ChecklistTemplate.last.name
+  end
+
+  test 'should delete checklist template' do
+    @request.session[:user_id] = 1
+    @template = ChecklistTemplate.create!(:name => 'template1', :template_items => 'item1 item2', :is_public => true)
+    compatible_request :delete, :destroy, :id => @template.to_param
+    assert_equal 0, ChecklistTemplate.count
+  end
+
+  def test_should_created_default_template_for_tracker
+    @request.session[:user_id] = 1
+    compatible_request :post, :create, :checklist_template => { :name => 'default', :template_items => 'default1 default2', :is_default => 1, :tracker_id => 1 }
+    assert_equal 'default', ChecklistTemplate.last.name
+    assert_equal 1, ChecklistTemplate.last.user_id
+    assert_equal true, ChecklistTemplate.last.is_default?
+    assert_equal Tracker.find(1), ChecklistTemplate.last.tracker
+  end
+end
diff --git a/plugins/redmine_checklists/test/functional/checklists_controller_test.rb b/plugins/redmine_checklists/test/functional/checklists_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3ad4aaf5841d2e0d9b32a65bb7454c8d8250fc92
--- /dev/null
+++ b/plugins/redmine_checklists/test/functional/checklists_controller_test.rb
@@ -0,0 +1,104 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+class ChecklistsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    Project.find(1).enable_module!(:checklists)
+    User.current = nil
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.find(1)
+    @checklist_1 = Checklist.find(1)
+  end
+
+  test "should post done" do
+    # log_user('admin', 'admin')
+    @request.session[:user_id] = 1
+
+    compatible_xhr_request :put, :done, :is_done => 'true', :id => '1'
+    assert_response :success, 'Post done not working'
+    assert_equal true, Checklist.find(1).is_done, 'Post done not working'
+  end
+  test 'sends email about checklists' do
+    @request.session[:user_id] = 1
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    EmailAddress.create!(:user_id => 2, :address =>  'test@example.com') if Redmine::VERSION.to_s >= '3.0'
+    compatible_xhr_request :put, :done, :is_done => 'true', :id => '1'
+    assert ActionMailer::Base.deliveries.last
+    email = ActionMailer::Base.deliveries.last
+    assert_include 'Checklist item [x] First todo set to Done', email.text_part.body.to_s
+    # Test changes fixup
+    compatible_xhr_request :put, :done, :is_done => 'false', :id => '2'
+    email = ActionMailer::Base.deliveries.last
+    assert_include 'Checklist item [x] First todo set to Done', email.text_part.body.to_s
+  end
+
+  test "should not post done by deny user" do
+    # log_user('admin', 'admin')
+    @request.session[:user_id] = 5
+
+    compatible_xhr_request :put, :done, :is_done => true, :id => "1"
+    assert_response 403, "Post done accessible for all"
+  end
+
+  test "should view issue with checklist" do
+    # log_user('admin', 'admin')
+    @request.session[:user_id] = 1
+    @controller = IssuesController.new
+    compatible_request :get, :show, :id => @issue_1.id
+    assert_select 'ul#checklist_items li#checklist_item_1', @checklist_1.subject, "Issue won't view for admin"
+  end
+
+  test "should not view issue with checklist if deny" do
+    # log_user('anonymous', '')
+    @request.session[:user_id] = 5
+    @controller = IssuesController.new
+    compatible_request :get, :show, :id => @issue_1.id
+    assert_select 'ul#checklist_items', false, "Issue view for anonymous"
+  end
+end
diff --git a/plugins/redmine_checklists/test/functional/issues_controller_test.rb b/plugins/redmine_checklists/test/functional/issues_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3cf565a765f7a13ed751d98b6158b624cd44bf5a
--- /dev/null
+++ b/plugins/redmine_checklists/test/functional/issues_controller_test.rb
@@ -0,0 +1,370 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+# Re-raise errors caught by the controller.
+# class HelpdeskMailerController; def rescue_action(e) raise e end; end
+
+class IssuesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    @request.session[:user_id] = 1
+  end
+  def test_show_index_with_checklists_filter
+    compatible_request :get, :index, :project_id => 1, :set_filter => '1', :f => ['checklists_status', ''], :op => { :checklists_status => '=' }, :v => { :checklists_status => ['1'] }
+    assert_response :success
+    assert_equal issues_in_list.map(&:id), [2, 1]
+
+    compatible_request :get, :index, :project_id => 1, :set_filter => '1', :f => ['checklists_status', ''], :op => { :checklists_status => '=' }, :v => { :checklists_status => ['0'] }
+    assert_response :success
+    assert_equal issues_in_list.map(&:id), [1]
+
+    compatible_request :get, :index, :project_id => 1, :set_filter => '1', :f => ['checklists_item', ''], :op => { :checklists_item => '~' }, :v => { :checklists_item => ['todo'] }
+    assert_response :success
+    assert_equal issues_in_list.map(&:id), [2, 1]
+
+    compatible_request :get, :index, :project_id => 1, :set_filter => '1', :f => ['checklists_item', ''], :op => { :checklists_item => '~' }, :v => { :checklists_item => ['Third'] }
+    assert_response :success
+    assert_equal issues_in_list.map(&:id), [2]
+
+    compatible_request :get, :index, :project_id => 1, :set_filter => '1', :f => ['checklists_item', ''], :op => { :checklists_item => '!*' }
+    assert_response :success
+    assert_equal issues_in_list.map(&:id).include?(1), false
+    assert_equal issues_in_list.map(&:id).include?(2), false
+  end
+
+  def test_new_issue_without_project
+    compatible_request :get, :new
+    assert_response :success
+  end if Redmine::VERSION.to_s > '3.0'
+
+  def test_get_show_issue
+    issue = Issue.find(1)
+    assert_not_nil issue.checklists.first
+    compatible_request(:get, :show, :id => 1)
+    assert_response :success
+    assert_select "ul#checklist_items li#checklist_item_1", /First todo/
+    assert_select "ul#checklist_items li#checklist_item_1 input[checked=?]", "checked", { :count => 0 }
+    assert_select "ul#checklist_items li#checklist_item_2 input[checked=?]", "checked"
+  end
+
+  def test_get_edit_issue
+    compatible_request :get, :edit, :id => 1
+    assert_response :success
+  end
+
+  def test_get_copy_issue
+    compatible_request :get, :new, :project_id => 1, :copy_from => 1
+    assert_response :success
+    assert_select "span#checklist_form_items span.checklist-subject", { :count => 3 }
+    assert_select "span#checklist_form_items span.checklist-edit input[value=?]", "First todo"
+  end
+
+  def test_put_update_form
+    parameters = {:tracker_id => 2,
+                  :checklists_attributes => {
+                    "0" => {"is_done"=>"0", "subject"=>"FirstChecklist"},
+                    "1" => {"is_done"=>"0", "subject"=>"Second"}}}
+
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    if Redmine::VERSION.to_s > '2.3' && Redmine::VERSION.to_s < '3.0'
+      compatible_xhr_request :put, :update_form, :issue => parameters, :project_id => issue.project
+    else
+      compatible_xhr_request :put, :new, :issue => parameters, :project_id => issue.project
+    end
+    assert_response :success
+    assert_equal 'text/javascript', response.content_type
+    assert_match 'FirstChecklist', response.body
+  end
+  def test_update_sends_email
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    parameters = { :checklists_attributes => { '0' => { 'is_done' => '0', 'subject' => 'Third' },
+                                               '1' => { 'is_done' => '1', 'subject' => 'Fourth' },
+                                               '2' => { 'id' => 2, '_destroy' => '1', 'subject' => 'Second todo' }
+                    } }
+
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    EmailAddress.create!(:user_id => 2, :address => 'test@example.com') if Redmine::VERSION.to_s >= '3.0'
+
+    compatible_xhr_request :put, :update, :issue => parameters, :project_id => issue.project, :id => issue.to_param
+    assert ActionMailer::Base.deliveries.last
+    email = ActionMailer::Base.deliveries.last
+    assert_include 'Checklist item [ ] Third added', email.text_part.body.to_s
+    assert_include 'Checklist item [x] Fourth added', email.text_part.body.to_s
+    assert_include 'Checklist item deleted (Second todo)', email.text_part.body.to_s
+  end
+
+  def test_update_send_notification_email
+    old_events = Setting.notified_events
+    Setting.notified_events = ['checklist_updated']
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    parameters = { 'checklists_attributes' => { '0' => { 'is_done' => '0', 'subject' => 'Third' },
+                                                '1' => { 'is_done' => '1', 'subject' => 'Fourth' },
+                                                '2' => { 'id' => '2', '_destroy' => '1', 'subject' => 'Second todo' } } }
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    EmailAddress.create!(:user_id => 2, :address => 'test@example.com', :is_default => true) if Redmine::VERSION.to_s >= '3.0'
+
+    compatible_xhr_request :put, :update, :issue => parameters, :project_id => issue.project, :id => issue.to_param
+    assert ActionMailer::Base.deliveries.last
+    email = ActionMailer::Base.deliveries.last
+    assert_include 'Checklist item [ ] Third added', email.text_part.body.to_s
+  ensure
+    Setting.notified_events = old_events
+  end
+
+  def test_update_status_with_checklist_destroy
+    issue = Issue.find(1)
+    initial_status = issue.status_id
+    closed_status = IssueStatus.where(:is_closed => true).first
+    with_checklists_settings('block_issue_closing' => '1') do
+      parameters = { :status_id => closed_status.id,
+                     :checklists_attributes => { '0' => { 'id' => 1, 'is_done' => '1', 'subject' => 'First todo' },
+                                                 '1' => { 'id' => 2, '_destroy' => '1', 'subject' => 'Second todo' } } }
+
+      compatible_xhr_request :put, :update, :issue => parameters, :project_id => issue.project, :id => issue.to_param
+      issue.reload
+      assert_equal issue.status, closed_status
+      assert_equal issue.checklists.count, 1
+    end
+  ensure
+    issue.update_attributes(:status_id => initial_status)
+  end
+
+  def test_added_attachment_shows_in_log_once
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    set_tmp_attachments_directory
+    parameters = { :tracker_id => 2,
+                   :checklists_attributes => {
+                     '0' => { 'is_done' => '0', 'subject' => 'First' },
+                     '1' => { 'is_done' => '0', 'subject' => 'Second' } } }
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    compatible_request :post, :update, :issue => parameters,
+                                       :attachments => { '1' => { 'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file' } },
+                                       :project_id => issue.project,
+                                       :id => issue.to_param
+    assert_response :redirect
+    assert_equal 1, Journal.last.details.where(:property => 'attachment').count
+  end
+  def test_update_with_delete_write_to_journal
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    EmailAddress.create!(:user_id => 2, :address => 'test@example.com') if Redmine::VERSION.to_s >= '3.0'
+
+    # Create new checklist
+    compatible_xhr_request :put, :update,
+                           :issue => { :notes => 'fix me',
+                                       :checklists_attributes => { '0' => { 'is_done' => '0', 'subject' => 'Five' } } },
+                           :project_id => issue.project,
+                           :id => issue.to_param
+    assert_response :redirect
+    issue.reload
+    # Delete new checklist
+    compatible_xhr_request :put, :update,
+                           :issue => { :checklists_attributes => { '0' => { 'id' => issue.checklists.max.id, '_destroy' => '1', 'subject' => 'First todo' } } },
+                           :project_id => issue.project,
+                           :id => issue.to_param
+    assert_response :redirect
+
+    compatible_request :get, :show, :id => issue.id
+    assert_response :success
+    assert_select "#change-#{issue.journals.last.id} .details li", 'Checklist item deleted (Five)'
+  end
+
+  def test_history_dont_show_old_format_checklists
+    Setting[:plugin_redmine_checklists] = { :save_log => 1, :issue_done_ratio => 0 }
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    issue.journals.create!(:user_id => 1)
+    issue.journals.last.details.create!(:property =>  'attr',
+                                        :prop_key =>  'checklist',
+                                        :old_value => '[ ] TEST',
+                                        :value =>     '[x] TEST')
+
+    compatible_request :post, :show, :id => issue.id
+    assert_response :success
+    last_journal = issue.journals.last
+    assert_equal last_journal.details.size, 1
+    assert_equal last_journal.details.first.prop_key, 'checklist'
+    assert_select "#change-#{last_journal.id} .details li", 'Checklist item changed from [ ] TEST to [x] TEST'
+  end
+
+  def test_empty_update_dont_write_to_journal
+    @request.session[:user_id] = 1
+    issue = Issue.find(1)
+    journals_before = issue.journals.count
+    compatible_request :post, :update, :issue => {}, :id => issue.to_param, :project_id => issue.project
+    assert_response :redirect
+    assert_equal journals_before, issue.reload.journals.count
+  end
+
+  def test_create_issue_without_checklists
+    @request.session[:user_id] = 1
+    assert_difference 'Issue.count' do
+      compatible_request :post, :create, :project_id => 1, :issue => { :tracker_id => 3,
+                                                                       :status_id => 2,
+                                                                       :subject => 'NEW issue without checklists',
+                                                                       :description => 'This is the description'
+                                                                     }
+    end
+    assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
+
+    issue = Issue.find_by_subject('NEW issue without checklists')
+    assert_not_nil issue
+  end
+
+  def test_create_issue_using_json
+    old_value = Setting.rest_api_enabled
+    Setting.rest_api_enabled = '1'
+    @request.session[:user_id] = 1
+    assert_difference 'Issue.count' do
+      compatible_request :post, :create, :format => :json, :project_id => 1, :issue => { :tracker_id => 3,
+                                                                                         :status_id => 2,
+                                                                                         :subject => 'NEW JSON issue',
+                                                                                         :description => 'This is the description',
+                                                                                         :checklists_attributes => [{ :is_done => 0, :subject => 'JSON checklist' }]
+                                                                                       },
+                                                                             :key => User.find(1).api_key
+    end
+    assert_response :created
+
+    issue = Issue.find_by_subject('NEW JSON issue')
+    assert_not_nil issue
+    assert_equal 1, issue.checklists.count
+  ensure
+    Setting.rest_api_enabled = old_value
+  end
+  def test_save_order_position
+    parameters = { :checklists_attributes => { '0' => { 'id' => 1, 'is_done' => '0', 'subject' => 'First todo', 'position' => '0' },
+                                               '1' => { 'is_done' => '1', 'subject' => 'New checklist', 'position' => '1' },
+                                               '2' => { 'id' => 2, 'is_done' => '0', 'subject' => 'Second todo', 'position' => '2' } } }
+
+    issue = Issue.find(1)
+    @request.session[:user_id] = 1
+    compatible_xhr_request :put, :update, :issue => parameters, :project_id => issue.project, :id => issue.to_param
+    assert_equal 0, Checklist.find(1).position
+    assert_equal 2, Checklist.find(2).position
+    assert_equal 1, Checklist.last.position
+  end
+
+  def test_add_default_project_template_to_issue
+    @request.session[:user_id] = 1
+    @project = Project.find(1)
+    @template = ChecklistTemplate.create!(:name => 'Default', :template_items => 'def 1', :is_default => true, :user => User.find(1), :project => @project)
+    compatible_request :get, :new, :project_id => @project.id
+    assert_response :success
+    assert_select 'span.checklist-subject', 'def 1'
+  ensure
+    @template.destroy
+  end
+
+  def test_add_default_tracker_project_template_to_issue
+    @request.session[:user_id] = 1
+    @project = Project.find(1)
+    @tracker = @project.trackers.first
+    @p_template = ChecklistTemplate.create!(:name => 'Default P', :template_items => 'project 1', :is_default => true, :user => User.find(1), :project => @project)
+    @t_template = ChecklistTemplate.create!(:name => 'Default T', :template_items => 'tracker 1', :is_default => true,
+                                            :tracker_id => @tracker.id, :user => User.find(1), :project => @project)
+    compatible_request :get, :new, :project_id => @project.id
+    assert_response :success
+    assert_select 'span.checklist-subject', 'tracker 1'
+  ensure
+    @p_template.destroy
+    @t_template.destroy
+  end
+
+  def test_apply_default_tracker_template_on_tracker_change
+    @request.session[:user_id] = 1
+    @project = Project.find(1)
+    @tracker = @project.trackers.first
+    @t_template = ChecklistTemplate.create!(:name => 'Default T', :template_items => 'tracker-1', :is_default => true,
+                                            :tracker_id => @tracker.id, :user => User.find(1), :project => @project)
+
+    parameters = { :tracker_id => @tracker.id + 1, :checklists_attributes => { '0' => { 'is_done' => '0', 'subject' => '', '_destroy' => 'false', 'position' => '1', 'id' => '' } } }
+
+    # Tracker without default list
+    compatible_xhr_request :post, :new, :issue => parameters, :project_id => @project
+    assert_response :success
+    assert_no_match %r{tracker-1}, response.body
+
+    # Tracker with default list
+    compatible_xhr_request :post, :new, :issue => parameters.merge(:tracker_id => @tracker.id), :project_id => @project
+    assert_response :success
+    assert_match 'tracker-1', response.body
+
+    # Tracker with custom checklist
+    compatible_xhr_request :post, :new, :issue => { :tracker_id => @tracker.id, :checklists_attributes => { '0' => { 'is_done' => '0', 'subject' => 'CUSTOM', '_destroy' => 'false', 'position' => '1', 'id' => '' } } }, :project_id => @project
+    assert_response :success
+    assert_match 'CUSTOM', response.body
+  ensure
+    @t_template.destroy
+  end
+
+  def test_copy_subtask_with_checklits
+    parent = Issue.find(1)
+    child  = Issue.find(2)
+    child.parent_issue_id = parent.id
+    child.fixed_version_id = nil
+    child.save
+
+    check_attrs = { :checklists_attributes => { '0' => { 'is_done' => '0', 'subject' => 'First todo', '_destroy' => 'false', 'position' => '1', 'id' => '' },
+                                                '1' => { 'is_done' => '1', 'subject' => 'Second todo', '_destroy' => 'false', 'position' => '2', 'id' => '' } } }
+    compatible_request :post, :create, :copy_from => parent.id, :link_copy => 1, :copy_subtasks => 1, :issue => parent.attributes.except(:created_on, :updated_on).merge(check_attrs)
+    parent_copy = parent.reload.relations.first.issue_to
+    assert_not_nil parent_copy
+    assert_equal 2, parent_copy.checklists.count
+    child_copy = parent_copy.children.first
+    assert_not_nil child_copy
+    assert_equal 1, child_copy.checklists.count
+  ensure
+    Issue.find(2).update_attributes(:parent_id => nil, :fixed_version_id => 2)
+  end
+end
diff --git a/plugins/redmine_checklists/test/integration/api_test/checklists_test.rb b/plugins/redmine_checklists/test/integration/api_test/checklists_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..613b7b57b737faba0018fd2605b54e374525d242
--- /dev/null
+++ b/plugins/redmine_checklists/test/integration/api_test/checklists_test.rb
@@ -0,0 +1,111 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../../test_helper', __FILE__)
+
+class Redmine::ApiTest::ChecklistsTest < Redmine::ApiTest::Base
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    Setting.rest_api_enabled = '1'
+  end
+
+  def test_get_checklists_xml
+    compatible_api_request :get, '/issues/1/checklists.xml', {}, credentials('admin')
+
+    assert_select 'checklists[type=array]' do
+      assert_select 'checklist' do
+        assert_select 'id', :text => '1'
+        assert_select 'subject', :text => 'First todo'
+      end
+    end
+  end
+
+  def test_get_checklists_1_xml
+    compatible_api_request :get, '/checklists/1.xml', {}, credentials('admin')
+
+    assert_select 'checklist' do
+      assert_select 'id', :text => '1'
+      assert_select 'subject', :text => 'First todo'
+    end
+  end
+
+  def test_post_checklists_xml
+    parameters = { :checklist => { :issue_id => 1,
+                                   :subject => 'api_test_001',
+                                   :is_done => true } }
+    assert_difference('Checklist.count') do
+      compatible_api_request :post, '/issues/1/checklists.xml', parameters, credentials('admin')
+    end
+
+    checklist = Checklist.order('id DESC').first
+    assert_equal parameters[:checklist][:subject], checklist.subject
+
+    assert_response :created
+    assert_equal 'application/xml', @response.content_type
+    assert_select 'checklist id', :text => checklist.id.to_s
+  end
+
+  def test_put_checklists_1_xml
+    parameters = { :checklist => { :subject => 'Item_UPDATED' } }
+
+    assert_no_difference('Checklist.count') do
+      compatible_api_request :put, '/checklists/1.xml', parameters, credentials('admin')
+    end
+
+    checklist = Checklist.find(1)
+    assert_equal parameters[:checklist][:subject], checklist.subject
+  end
+
+  def test_delete_1_xml
+    assert_difference 'Checklist.count', -1 do
+      compatible_api_request :delete, '/checklists/1.xml', {}, credentials('admin')
+    end
+
+    assert_response :ok
+    assert_equal '', @response.body
+    assert_nil Checklist.find_by_id(1)
+  end
+end
diff --git a/plugins/redmine_checklists/test/integration/common_issue_test.rb b/plugins/redmine_checklists/test/integration/common_issue_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c43526af82ef58e06e8a9ce555448b63bfc003f4
--- /dev/null
+++ b/plugins/redmine_checklists/test/integration/common_issue_test.rb
@@ -0,0 +1,79 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+class CommonIssueTest < RedmineChecklists::IntegrationTest
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project_1   = Project.find(1)
+    @issue_1     = Issue.find(1)
+    @checklist_1 = Checklist.find(1)
+  end
+  def test_checklist_settings
+    log_user('admin', 'admin')
+    ChecklistTemplate.create!(:name => 'template1', :template_items => 'item1 item2', :is_public => true, :project => @project_1)
+    ChecklistTemplate.create!(:name => 'template2', :template_items => 'item1 item2', :is_public => true)
+    compatible_request :get, '/settings/plugin/redmine_checklists'
+    assert_response :success
+    assert_select 'tr.checklist-template', 2
+  end
+
+  def test_checklist_project_settings
+    log_user('admin', 'admin')
+    ChecklistTemplate.create!(:name => 'template1', :template_items => 'item1 item2', :is_public => true, :project => @project_1)
+    ChecklistTemplate.create!(:name => 'template2', :template_items => 'item1 item2', :is_public => true)
+    compatible_request :get, '/projects/ecookbook/settings/checklist_template'
+    assert_response :success
+    assert_select 'tr.checklist-template', 2
+  end
+
+  def test_global_search_with_checklist
+    log_user('admin', 'admin')
+    compatible_request :get, '/search?q=First'
+    assert_response :success
+  end
+end
diff --git a/plugins/redmine_checklists/test/test_helper.rb b/plugins/redmine_checklists/test/test_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c457c3cbe1aa062658b902023527dbebd4501507
--- /dev/null
+++ b/plugins/redmine_checklists/test/test_helper.rb
@@ -0,0 +1,89 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
+
+module RedmineChecklists
+  module TestHelper
+    def compatible_request(type, action, parameters = {})
+      return send(type, action, :params => parameters) if Rails.version >= '5.1'
+      send(type, action, parameters)
+    end
+
+    def compatible_xhr_request(type, action, parameters = {})
+      return send(type, action, :params => parameters, :xhr => true) if Rails.version >= '5.1'
+      xhr type, action, parameters
+    end
+
+    def compatible_api_request(type, action, parameters = {}, headers = {})
+      return send(type, action, :params => parameters, :headers => headers) if Redmine::VERSION.to_s >= '3.4'
+      send(type, action, parameters, headers)
+    end
+
+    def issues_in_list
+      ids = css_select('tr.issue td.id a').map { |tag| tag.to_s.gsub(/<.*?>/, '') }.map(&:to_i)
+      Issue.where(:id => ids).sort_by { |issue| ids.index(issue.id) }
+    end
+
+    def with_checklists_settings(options, &block)
+      Setting.plugin_redmine_checklists.stubs(:[]).returns(nil)
+      options.each { |k, v| Setting.plugin_redmine_checklists.stubs(:[]).with(k).returns(v) }
+      yield
+    ensure
+      options.each { |_k, _v| Setting.plugin_redmine_checklists.unstub(:[]) }
+    end
+  end
+end
+
+include RedmineChecklists::TestHelper
+
+if ActiveRecord::VERSION::MAJOR >= 4
+  class RedmineChecklists::IntegrationTest < Redmine::IntegrationTest; end
+else
+  class RedmineChecklists::IntegrationTest < ActionController::IntegrationTest; end
+end
+
+class RedmineChecklists::TestCase
+  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+    if ActiveRecord::VERSION::MAJOR >= 4
+      ActiveRecord::FixtureSet.create_fixtures(fixtures_directory, table_names, class_names = {})
+    else
+      ActiveRecord::Fixtures.create_fixtures(fixtures_directory, table_names, class_names = {})
+    end
+  end
+
+  def self.prepare
+    Role.find(1, 2, 3, 4).each do |r|
+      r.permissions << :edit_checklists
+      r.save
+    end
+
+    Role.find(3, 4).each do |r|
+      r.permissions << :done_checklists
+      r.save
+    end
+
+    Role.find([2]).each do |r|
+      r.permissions << :manage_checklist_templates
+      r.save
+    end
+  end
+end
diff --git a/plugins/redmine_checklists/test/unit/checklist_template_category_test.rb b/plugins/redmine_checklists/test/unit/checklist_template_category_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..17ab54be3c9bf0f06fdc28a966bb9f91f9febdd4
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/checklist_template_category_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ChecklistTemplateCategoryTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 1,
+                            :status_id => 1, :priority => IssuePriority.first,
+                            :subject => 'Invoice Issue 1')
+    @checklist_1 = Checklist.create(:subject => 'TEST1', :position => 1, :issue => @issue_1)
+  end
+
+  test "should exist" do
+    assert ChecklistTemplateCategory, "Checklist template category is not defined"
+  end
+
+  test "has ordered scope" do
+    assert ChecklistTemplateCategory.ordered, "Ordered scope not found for checklist template category model"
+  end
+
+  test "should act as list" do
+    cat1 = ChecklistTemplateCategory.create!(:name => 'Category 1', :position => 1)
+    cat2 = ChecklistTemplateCategory.create!(:name => 'Category 2', :position => 2)
+    assert_equal 'Category 2', ChecklistTemplateCategory.ordered.last.name
+
+    cat2.move_higher
+    assert_equal ChecklistTemplateCategory.ordered.last.name, 'Category 1'
+
+    cat2.move_to_bottom
+    assert_equal ChecklistTemplateCategory.ordered.last.name, 'Category 2'
+  end
+end
diff --git a/plugins/redmine_checklists/test/unit/checklist_template_test.rb b/plugins/redmine_checklists/test/unit/checklist_template_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d26101b71312f5a0c1e9d69bfbd0f21315992825
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/checklist_template_test.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ChecklistTemplateTest < ActiveSupport::TestCase
+
+  def test_save_with_category
+    ch_temp_cat = ChecklistTemplateCategory.create(:name => 'Category 1', :position => 1)
+    check_list_template = ChecklistTemplate.new(:name => 'name', :category_id => ch_temp_cat.id, :template_items => 's')
+    check_list_template.save
+    assert_equal ch_temp_cat.id, check_list_template.reload.category.id
+  end
+end
diff --git a/plugins/redmine_checklists/test/unit/checklist_test.rb b/plugins/redmine_checklists/test/unit/checklist_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..75d7c921db74b73d4bd844c4f620a7c9cadf08f0
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/checklist_test.rb
@@ -0,0 +1,89 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ChecklistTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 1,
+                            :status_id => 1, :priority => IssuePriority.first,
+                            :subject => 'Invoice Issue 1')
+    @checklist_1 = Checklist.create(:subject => 'TEST1', :position => 1, :issue => @issue_1)
+  end
+
+  test "should save checklist" do
+    assert @checklist_1.save, "Checklist save error"
+  end
+
+  test "should not save checklist without subject" do
+    @checklist_1.subject = nil
+    assert !@checklist_1.save, "Checklist save with nil subject"
+  end
+
+  test "should not save checklist without position" do
+    @checklist_1.position = nil
+    assert !@checklist_1.save, "Checklist save with nil position"
+  end
+
+  test "should not save checklist with non integer position" do
+    @checklist_1.position = "string"
+    assert !@checklist_1.save, "Checklist save with non ingeger position"
+  end
+
+  test "should return project info" do
+    assert_equal @project_1, @checklist_1.project, "Helper project broken"
+  end
+
+  test "should return info about checklist" do
+    assert_equal "[ ] #{@checklist_1.subject}", @checklist_1.info, "Helper info broken"
+    @checklist_1.is_done = 1
+    assert_equal "[x] #{@checklist_1.subject}", @checklist_1.info, "Helper info broken"
+  end
+
+end
diff --git a/plugins/redmine_checklists/test/unit/issue_test.rb b/plugins/redmine_checklists/test/unit/issue_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cbf31d8cf8bc1384cdc5c0008e69719c124e28b5
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/issue_test.rb
@@ -0,0 +1,85 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+include RedmineChecklists::TestHelper
+
+class IssueTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project = Project.find(1)
+    @issue = Issue.create(:project => @project, :tracker_id => 1, :author_id => 1,
+                          :status_id => 1, :priority => IssuePriority.first,
+                          :subject => 'TestIssue')
+    @checklist_1 = Checklist.create(:subject => 'TEST1', :position => 1, :issue => @issue)
+    @checklist_2 = Checklist.create(:subject => 'TEST2', :position => 2, :issue => @issue, :is_done => true)
+    @issue.reload
+  end
+
+  def test_issue_shouldnt_close_when_it_has_unfinished_checklists
+    with_checklists_settings('block_issue_closing' => '1') do
+      @issue.status_id = 5
+      assert !@issue.valid?
+    end
+  end
+
+  def test_validation_should_be_ignored_if_setting_disabled
+    with_checklists_settings('block_issue_closing' => '0') do
+      @issue.status_id = 5
+      assert @issue.valid?
+    end
+  end
+
+  def test_issue_should_close_when_all_checklists_finished
+    with_checklists_settings('block_issue_closing' => '1') do
+      @checklist_1.update_attributes(:is_done => true)
+      assert @issue.valid?
+    end
+  ensure
+    @checklist_1.update_attributes(:is_done => false)
+  end
+end
diff --git a/plugins/redmine_checklists/test/unit/journal_checklist_history_test.rb b/plugins/redmine_checklists/test/unit/journal_checklist_history_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e505afa770468678b6379d55735cda5aad00e24
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/journal_checklist_history_test.rb
@@ -0,0 +1,251 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class JournalChecklistHistoryTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/', [:checklists])
+
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.create(:project => @project_1, :tracker_id => 1, :author_id => 1,
+                            :status_id => 1, :priority => IssuePriority.first,
+                            :subject => 'TestIssue')
+
+    @checklist_1 = Checklist.create(:subject => 'TEST1', :position => 1, :issue => @issue_1)
+    @checklist_2 = Checklist.create(:subject => 'TEST2', :position => 2, :issue => @issue_1, :is_done => true)
+    @checklist_3 = Checklist.create(:subject => 'TEST3', :position => 3, :issue => @issue_1, :is_done => true)
+  end
+
+  test 'JournalChecklistHistory exists' do
+    assert JournalChecklistHistory
+  end
+
+  test 'JournalChecklistHistory can discover that checklist has been added' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+    checklist_set_2 = [@checklist_1, @checklist_2, @checklist_3]
+    diff = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).diff
+
+    assert(diff[:added])
+    assert_equal(@checklist_3.id, diff[:added].map(&:to_h)[0]['id'])
+  end
+
+  test 'JournalChecklistHistory can discover that checklist has been removed' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+    checklist_set_2 = [@checklist_1, @checklist_2, @checklist_3]
+
+    diff = JournalChecklistHistory.new(checklist_set_2, checklist_set_1).diff
+
+    assert(diff[:removed])
+    assert_equal(@checklist_3.id, diff[:removed].map(&:to_h)[0]['id'])
+  end
+
+  test 'JournalChecklistHistory can discover that checklist has been renamed' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.subject = 'TEST2_CHANGED'
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_2_dup.id = @checklist_2.id
+
+    checklist_set_2 = [@checklist_1, @checklist_2_dup]
+
+    diff = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).diff
+
+    assert(diff[:renamed])
+    assert_equal({ @checklist_2.subject => @checklist_2_dup.subject }, diff[:renamed])
+  end
+
+  test 'is able to work with JSON representations' do
+    checklist_set_1 = [@checklist_1, @checklist_2].to_json
+    checklist_set_2 = [@checklist_1, @checklist_2, @checklist_3].to_json
+
+    diff = JournalChecklistHistory.new(checklist_set_2, checklist_set_1).diff
+
+    assert(diff[:removed])
+    # In fact diff can contain OpenStruct instances instead of Checklists
+    # But they should have same attributes
+    assert_equal(@checklist_3.id, diff[:removed].map(&:to_h)[0]['id'])
+    assert_equal(@checklist_3.subject, diff[:removed].map(&:to_h)[0]['subject'])
+    assert_equal(@checklist_3.is_done, diff[:removed].map(&:to_h)[0]['is_done'])
+  end
+
+  test 'JournalChecklistHistory able to create JournalDetail' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.subject = 'TEST2_CHANGED'
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_2_dup.id = @checklist_2.id
+    checklist_set_2 = [@checklist_2_dup, @checklist_3]
+    details = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal)
+    assert_equal details.prop_key, 'checklist'
+    assert_equal JSON.parse(details.old_value)[1]['subject'], 'TEST2'
+    assert_equal JSON.parse(details.value)[0]['subject'], 'TEST2_CHANGED'
+  end
+
+  test 'can define that previous Journal contains only checklist changes' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.subject = 'TEST2_CHANGED'
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_2_dup.id = @checklist_2.id
+    checklist_set_2 = [@checklist_2_dup, @checklist_3]
+    @issue_1.init_journal(User.find(1))
+    @old_journal = @issue_1.current_journal
+    @issue_1.current_journal.save!
+    JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal).save!
+    assert_equal(1, @issue_1.journals.size)
+
+    @issue_1 = Issue.find(@issue_1.id)
+    @issue_1.init_journal(User.find(1))
+    @issue_1.current_journal.save!
+    journal_detail = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal)
+    assert(JournalChecklistHistory.can_fixup?(journal_detail))
+
+    JournalDetail.new({
+      :property  => 'attr',
+      :prop_key  => 'color',
+      :old_value => 'blue',
+      :value     => 'red',
+      :journal   => @old_journal
+    }).save!
+
+    assert(!JournalChecklistHistory.can_fixup?(journal_detail))
+  end
+
+  test 'is able to fixup JournalDetail if previous Journal contains only checklist changes' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.subject = 'TEST2_CHANGED'
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_2_dup.id = @checklist_2.id
+    checklist_set_2 = [@checklist_2_dup, @checklist_3]
+    @issue_1.init_journal(User.find(1))
+    @old_journal = @issue_1.current_journal
+    @issue_1.current_journal.save!
+    JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal).save!
+    assert_equal(1, @issue_1.journals.size)
+
+    @issue_1 = Issue.find(@issue_1.id)
+    @issue_1.init_journal(User.find(1))
+    @issue_1.current_journal.save!
+    checklist_set_2 = [@checklist_1, @checklist_3]
+    journal_detail = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal)
+    JournalChecklistHistory.fixup(journal_detail)
+    diff = JournalChecklistHistory.new(JournalDetail.last.old_value, JournalDetail.last.value).diff
+
+    assert_equal(@checklist_3.id, diff[:added].map(&:to_h)[0]['id'])
+    assert_equal(@checklist_2.id, diff[:removed].map(&:to_h)[0]['id'])
+    assert_equal(0, diff[:renamed].size)
+  end
+
+  test 'JournalChecklistHistory detects is_done change to undone' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.is_done = false
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_2_dup.id = @checklist_2.id
+    checklist_set_2 = [@checklist_1, @checklist_2_dup]
+    diff = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).diff
+
+    assert(diff[:undone])
+    assert_equal(@checklist_2_dup.id, diff[:undone].map(&:to_h)[0]['id'])
+  end
+
+  test 'JournalChecklistHistory detects is_done change to done' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_1_dup = @checklist_1.dup
+    @checklist_1_dup.is_done = true
+    # dup is redefined in ActiveRecord so it nullifies id in return value
+    # But we are imitating situation when user has modified same instance
+    @checklist_1_dup.id = @checklist_1.id
+    checklist_set_2 = [@checklist_1_dup, @checklist_2]
+    diff = JournalChecklistHistory.new(checklist_set_1, checklist_set_2).diff
+
+    assert(diff[:done])
+    assert_equal(@checklist_1_dup.id, diff[:done].map(&:to_h)[0]['id'])
+  end
+
+  test 'it not fixup after one minute details' do
+    checklist_set_1 = [@checklist_1, @checklist_2]
+
+    @checklist_2_dup = @checklist_2.dup
+    @checklist_2_dup.is_done = false
+    @checklist_2_dup.id = @checklist_2.id
+    checklist_set_2 = [@checklist_2_dup]
+
+    @issue_1.init_journal(User.find(1))
+    @old_journal = @issue_1.current_journal
+    @issue_1.current_journal.save!
+    JournalChecklistHistory.new(checklist_set_1, checklist_set_2).journal_details(:journal => @issue_1.current_journal).save!
+    assert_equal(1, @issue_1.journals.size)
+
+    # Try fixup for fresh data
+    journal = Journal.new(:journalized => @issue_1, :user => User.find(1))
+    detail = JournalDetail.new(:property  => 'attr',
+                               :prop_key  => 'checklist',
+                               :old_value => checklist_set_1.to_json.to_s,
+                               :value     => checklist_set_2.to_json.to_s,
+                               :journal   => journal
+                              )
+    assert_equal true, JournalChecklistHistory.can_fixup?(detail)
+
+    # Try fixup old data
+    @issue_1.journals.last.update_attribute(:created_on, Time.zone.now - 10.minutes)
+    assert_equal false, JournalChecklistHistory.can_fixup?(detail)
+  end
+end
diff --git a/plugins/redmine_checklists/test/unit/project_test.rb b/plugins/redmine_checklists/test/unit/project_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..baf52b12366d5a85a94dbe1ab7b409fe1c656fb6
--- /dev/null
+++ b/plugins/redmine_checklists/test/unit/project_test.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Checklists (redmine_checklists) plugin,
+# issue checklists management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_checklists is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_checklists is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_checklists.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class ProjectTest < ActiveSupport::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers,
+           :time_entries,
+           :journals,
+           :journal_details,
+           :queries
+
+  RedmineChecklists::TestCase.create_fixtures(Redmine::Plugin.find(:redmine_checklists).directory + '/test/fixtures/',
+                                         [:checklists])
+  def setup
+    RedmineChecklists::TestCase.prepare
+    Setting.default_language = 'en'
+    @project_1 = Project.find(1)
+    @issue_1 = Issue.create(:project => @project_1, :tracker_id => 1, :author_id => 1,
+                            :status_id => 1, :priority => IssuePriority.first,
+                            :subject => 'TestIssue')
+    @checklist_1 = Checklist.create(:subject => 'TEST1', :position => 1, :issue => @issue_1)
+    @checklist_1 = Checklist.create(:subject => 'TEST2', :position => 2, :issue => @issue_1, :is_done => true)
+  end
+
+  test 'should copy checklists' do
+    project_copy = Project.copy_from(Project.find(1))
+    project_copy.name = 'Test name'
+    project_copy.identifier = Project.next_identifier
+    project_copy.copy(Project.find(1))
+
+    checklists_copies = project_copy.issues.where(:subject => 'TestIssue').first.checklists
+    assert_equal(checklists_copies.count, 2)
+    assert_equal(checklists_copies.where(:subject => 'TEST1').first.is_done, false)
+    assert_equal(checklists_copies.where(:subject => 'TEST2').first.is_done, true)
+  end
+end
diff --git a/plugins/redmine_omniauth_client/Gemfile b/plugins/redmine_omniauth_client/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..7723190ba693fce3b6250584458f41a0fd0f99a2
--- /dev/null
+++ b/plugins/redmine_omniauth_client/Gemfile
@@ -0,0 +1,4 @@
+source 'http://rubygems.org'
+
+gem 'oauth2'
+gem 'json'
diff --git a/plugins/redmine_omniauth_client/Gemfile.lock b/plugins/redmine_omniauth_client/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..a495cd56eef0d5dd6ac9013878e83f78a9161c52
--- /dev/null
+++ b/plugins/redmine_omniauth_client/Gemfile.lock
@@ -0,0 +1,22 @@
+GEM
+  remote: http://rubygems.org/
+  specs:
+    addressable (2.3.2)
+    faraday (0.7.6)
+      addressable (~> 2.2)
+      multipart-post (~> 1.1)
+      rack (~> 1.1)
+    json (1.7.5)
+    multi_json (1.3.6)
+    multipart-post (1.1.5)
+    oauth2 (0.5.2)
+      faraday (~> 0.7)
+      multi_json (~> 1.0)
+    rack (1.4.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  json
+  oauth2
diff --git a/plugins/redmine_omniauth_client/README.md b/plugins/redmine_omniauth_client/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b5715b83f3c4b3e6a46efda608d2d205f3077d36
--- /dev/null
+++ b/plugins/redmine_omniauth_client/README.md
@@ -0,0 +1,51 @@
+## Redmine omniauth ROR
+
+This plugin is used to authenticate Redmine users using an OAuth2 provider.
+
+### Installation:
+
+Download the plugin and install required gems:
+
+```console
+cd /path/to/redmine/plugins
+git clone https://github.com/arlin2050/redmine_omniauth_client.git
+cd /path/to/redmine
+bundle install
+```
+
+Restart the app
+```console
+touch /path/to/redmine/tmp/restart.txt
+```
+
+### Configuration
+
+* Login as a user with administrative privileges. 
+* In top menu select "Administration".
+* Click "Plugins"
+* In plugins list, click "Configure" in the row for "Redmine OAuth Client plugin"
+* Configure all fields.
+* Check the box near "Enable OAuth authentication"
+* Click Apply. 
+ 
+Users can now to use their account to log into your instance of Redmine.
+
+Additionaly
+* Setup value Autologin in Settings on tab Authentification
+
+### Authentication Workflow
+
+1. An unauthenticated user requests the URL to your Redmine instance.
+2. User clicks the "Login via App" button.
+3. The plugin redirects them to a sign in page if they are not already signed into their account.
+4. App redirects user back to Redmine, where the OAuth plugin's controller takes over.
+
+One of the following cases will occur:
+
+1. If self-registration is disabled (Under Administration > Settings > Authentication) but force_account_creation option is checked, the account is created and user is redirected to 'my/page'.
+2. Otherwise, self-registration method is used to register users.
+
+### TODO
+
+* Access webservices in other formats than json.
+* Fonctionnal tests
\ No newline at end of file
diff --git a/plugins/redmine_omniauth_client/app/controllers/redmine_oauth_controller.rb b/plugins/redmine_omniauth_client/app/controllers/redmine_oauth_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ccd8827d17906fce36657363b4e1dc670366c48e
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/controllers/redmine_oauth_controller.rb
@@ -0,0 +1,236 @@
+require 'account_controller'
+require 'json'
+require 'jwt'
+
+class RedmineOauthController < AccountController
+  def oauth
+    if Setting.plugin_redmine_omniauth_client['oauth_authentification']
+      session[:back_url] = params[:back_url]
+      redirect_to oauth_client.auth_code.authorize_url(:redirect_uri => oauth_callback_url)
+    else
+      password_authentication
+    end
+  end
+
+  def oauth_callback
+    if params[:error]
+      flash[:error] = l(:notice_access_denied, :app => settings['app_name'])
+      redirect_to signin_path
+    else
+      begin
+        token = oauth_client.auth_code.get_token(params[:code],:mode => :body, :redirect_uri => oauth_callback_url)
+        info = JWT.decode(token.token, nil, false)[0]
+      rescue
+        flash[:error] = (t 'notices.unable_to_authenticate')
+      end
+
+      #result = token.get(settings['site_url'] + settings['ws_url'])
+      #info = JSON.parse(result.body)
+
+      if info && info[settings['field_username']]
+        try_to_login info
+      else
+        flash[:error] = l(:notice_unable_to_obtain_app_credentials, :app => settings['app_name'])
+        redirect_to signin_path
+      end
+    end
+  end
+
+  def try_to_login(info)
+   params[:back_url] = session[:back_url]
+   session.delete(:back_url)
+   begin
+     user = User.where(sso_u_id: info[settings['sso_u_id']]).first
+     #File.open("ssouid_user.txt", 'w') { |file| file.write(user) }
+   rescue
+   end
+   if user.nil?
+      user = User.find_by_login(info[settings['field_username']])
+      #File.open("username_user.txt", 'w') { |file| file.write(user) }
+      #File.open("sso.txt", 'w') { |file| file.write(info[settings['sso_u_id']]) }
+
+      #user.sso_u_id = info[settings['sso_u_id']]
+      #user.save
+      if user.nil?
+        # Create on the fly
+        user = User.new
+        user.sso_u_id = info[settings['sso_u_id']]
+        user.firstname = info[settings['field_firstname']]
+        user.lastname = info[settings['field_lastname']]
+	user.mail =  info[settings['field_email']]
+	if info.key?('email_contact')
+		if info['email_contact'] != ""
+			user.mail = info['email_contact']
+		end
+	end
+        user.login = info[settings['field_username']]
+        user.random_password
+        user.register
+
+        # Use registration if defined
+        case Setting.self_registration
+        when '1'
+          register_by_email_activation(user) do
+            onthefly_creation_failed(user)
+          end
+        when '2'
+          register_manually_by_administrator(user) do
+            onthefly_creation_failed(user)
+          end
+        when '3'
+          register_automatically(user) do
+            onthefly_creation_failed(user)
+          end
+        else
+          if settings['force_account_creation']
+            register_automatically(user) do
+              onthefly_creation_failed(user)
+            end
+          else
+            flash[:error] = l(:unable_create_account)
+            redirect_to signin_path
+          end
+        end
+      else
+        user.sso_u_id = info[settings['sso_u_id']]
+        user.save
+      end
+   else
+     user.firstname = info[settings['field_firstname']]
+     user.lastname = info[settings['field_lastname']]
+     user.mail = info[settings['field_email']]
+     if info.key?('email_contact')
+    	if info['email_contact'] != ""
+		user.mail = info['email_contact']
+	end
+     end
+     user.login = info[settings['field_username']]
+     user.save
+
+      # Existing record
+      if user.active?
+        successful_authentication(user)
+      else
+        # Redmine 2.4 adds an argument to account_pending
+        if Redmine::VERSION::MAJOR > 2 or
+          (Redmine::VERSION::MAJOR == 2 and Redmine::VERSION::MINOR >= 4)
+          account_pending(user)
+        else
+          account_pending
+        end
+      end
+    end
+#########################
+    #File.open("ssouidUSER.txt", 'w') { |file| file.write(user.sso_u_id) }
+    begin
+      # Odstraní uživatele ze všech skupin, případně jen ze skupin párovaných k SSO.
+      if @settings['remove_from_groups']
+        if @settings['remove_from_all_groups']
+          user.groups.delete(Group.all)
+        # Nebo pouze ze spárovaných skupin
+        else
+          GroupMapping.all.each do |group_mapping|
+            group = Group.find(group_mapping.group_id)
+            user.groups.delete(group) if user.groups.include? group
+          end
+        end
+      end
+
+      resources = info[settings['field_resources']]
+      field_groups = settings['field_groups'].split(",")
+      field_groups.each do |field_group|
+          field_group = field_group.strip
+          field_group_spl = field_group.split(":")
+          field_group = field_group_spl[0]
+          if field_group_spl.length > 1
+              field_group_prefix = field_group_spl[1]
+          else
+              field_group_prefix = ""
+          end
+
+          groups = resources[field_group]
+          roles = groups[settings['field_roles']]
+
+          unless roles == nil || roles == []
+            # Prochází každou rolí v SSO acces tokenu
+            roles.each do |sso_group|
+              # možná mají role nějaký prefix
+              sso_group = field_group_prefix + sso_group
+              # Vyhledá mapování skupin na skupiny RM
+              mappings = GroupMapping.where(variable: sso_group)
+              # Pokud najde, přiřadí uživatele do příslušných skupin
+              unless mappings.to_a == nil || mappings.to_a == []
+                mappings.each do |mapping|
+                  @group = Group.find(mapping.group_id)
+                  unless @group.users.include? user
+                    @group.users << user
+                  end
+                end
+              # Pokud nenajde, hledá skupinu přímo odpovídající jménem skupině v SSO
+              else
+                @group = Group.find_by(lastname: sso_group)
+                # Pokud nenajde, vytvoří skupinu
+                if @group == nil || @group == []
+                  @group = Group.new({:lastname => sso_group})
+                  #@group.name= sso_group
+                  @group.save
+                end
+                # Vytváří mapování na novou, či nemapovanou skupinu a přiřadí uživatele do skupiny
+                #File.open("mapping_" + sso_group + ".txt", 'w') { |file| file.write(@group) }
+                unless @group.users.include? user
+                  @group.users << user
+                end
+                new_mapping = GroupMapping.new({:variable => sso_group, :group_id => @group.get_id})
+                new_mapping.save
+              end
+            end
+          end
+      end       # field_group
+   rescue
+      logger.error  "Error. Group assignemnt failed."
+   end
+  end
+
+  def oauth_client
+    @client ||= OAuth2::Client.new(settings['client_id'], settings['client_secret'],
+      :site => settings['site_url'],
+      :authorize_url => settings['auth_url'],
+      :token_url => settings['token_url'])
+  end
+
+  def settings
+    @settings ||= Setting.plugin_redmine_omniauth_client
+  end
+
+############################
+
+  def group_mapping
+    @groups = GroupMapping.all
+  end
+
+  def edit
+    #mapping = GroupMapping.find_by(variable: params[:variable])
+    begin
+      mapping = GroupMapping.find_by(variable: params[:variable], :group_id => params[:group_id])
+      if mapping == nil
+        new_mapping = GroupMapping.new({:variable => params[:variable], :group_id => params[:group_id]})
+        new_mapping.save
+      else
+        mapping.update({:variable => params[:variable], :group_id => params[:group_id]})
+    #  mapping.destroy
+      end
+      flash[:notice] = (t 'notices.saved')
+    rescue
+      flash[:warning] = (t 'notices.notSaved')
+    end
+    redirect_to :action => 'group_mapping'
+  end
+
+  def delete
+    unless GroupMapping.find_by(variable: params[:variable], :group_id => params[:group_id]) == nil
+      mapping = GroupMapping.find_by(variable: params[:variable], :group_id => params[:group_id])
+      mapping.destroy
+    end
+    redirect_to :action => 'group_mapping'
+  end
+end
diff --git a/plugins/redmine_omniauth_client/app/helpers/redmine_omniauth_helper.rb b/plugins/redmine_omniauth_client/app/helpers/redmine_omniauth_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e1d24455f65bf3be7622f065738e099df26d4c73
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/helpers/redmine_omniauth_helper.rb
@@ -0,0 +1,2 @@
+module RedmineOmniauthHelper
+end
diff --git a/plugins/redmine_omniauth_client/app/models/group_mapping.rb b/plugins/redmine_omniauth_client/app/models/group_mapping.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9afd2166ccf14fe3b27615461599060963d0b931
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/models/group_mapping.rb
@@ -0,0 +1,2 @@
+class GroupMapping < ActiveRecord::Base
+end
diff --git a/plugins/redmine_omniauth_client/app/views/hooks/_view_account_login_top.html.erb b/plugins/redmine_omniauth_client/app/views/hooks/_view_account_login_top.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0b5334c81b99e7bc48768d312e415b1d5be86052
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/views/hooks/_view_account_login_top.html.erb
@@ -0,0 +1,11 @@
+<%= stylesheet_link_tag 'buttons', :plugin => 'redmine_omniauth_client' %>
+
+<% if Setting.plugin_redmine_omniauth_client['oauth_authentification'] %>
+  <%= link_to oauth_client_path(:back_url => back_url), :class=>"no-pointer" do %>
+    <h2 style="width:450px; margin: 10px auto -25px auto; max-width: 20em; margin: 1rem auto; text-align: center; padding: 1rem 0; border: 1px rgb(204, 204, 204) solid; cursor: pointer; background: #efefef;"><%= t 'oauth.login_via_sso' %></h2>
+    <br/>
+    <br/>
+  <% end %>
+<% end %>
+
+
diff --git a/plugins/redmine_omniauth_client/app/views/redmine_oauth/group_mapping.html.erb b/plugins/redmine_omniauth_client/app/views/redmine_oauth/group_mapping.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b7c74c1e7c49dd19aa506c8e816a84517af1ad62
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/views/redmine_oauth/group_mapping.html.erb
@@ -0,0 +1,49 @@
+<h2><%=t 'oauth.mapping_headline'%></h2>
+ 
+<table>
+  <%= form_tag({:controller => 'redmine_oauth', :action => 'edit'}, method: "post") do %>
+    <tr>
+      <td>
+        <%= text_field_tag :variable %>
+      </td>
+      <td>
+        <%= select_tag :group_id, options_from_collection_for_select(Group.all - GroupBuiltin.all, :id, :name) %>
+      </td>
+      <td>
+        <%= submit_tag(t 'oauth.save') %>
+      </td>
+    </tr>
+  <% end %>
+</table>
+
+<hr />
+
+<h3><%=t 'oauth.saved_settings'%></h3>
+
+<table style="border: 1px solid black; padding: 1px">
+  <% @groups.each do |group| %>
+    <%= form_tag({:controller => 'redmine_oauth', :action => 'edit'}, method: "post") do %>
+      <tr>
+        <td style="border: 1px solid black">
+          <b><%= group.variable %></b>
+          <%= text_field_tag :variable, group.variable, hidden: true %>
+        </td>
+        <td style="border: 1px solid black">
+          <%= select_tag :group_id, options_from_collection_for_select(Group.all - GroupBuiltin.all, :id, :name, group.group_id) %>
+        </td>
+        <td style="border: 1px solid black">
+          <%= submit_tag(t 'oauth.edit') %>
+        <% end %>
+      </td>
+      <td style="border: 1px solid black">
+        <%= form_tag({:controller => 'redmine_oauth', :action => 'delete'}, method: "post") do %>
+          <%= text_field_tag :variable, group.variable, hidden: true %>
+          <%= text_field_tag :group_id, group.group_id, hidden: true %>
+          <%= submit_tag(t 'oauth.delete') %>
+        <% end %>
+      </td>
+    </tr>
+  <% end %>
+</table>
+
+<%#= link_to "Back", :back %>
diff --git a/plugins/redmine_omniauth_client/app/views/settings/_client_settings.html.erb b/plugins/redmine_omniauth_client/app/views/settings/_client_settings.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..73dc86902d7c0f9c0fd445bb1b41fdc12b487b92
--- /dev/null
+++ b/plugins/redmine_omniauth_client/app/views/settings/_client_settings.html.erb
@@ -0,0 +1,81 @@
+<p>
+  <label><%=t 'oauth.app_name'%></label>
+  <%= text_field_tag 'settings[app_name]', @settings['app_name'] %>
+</p>
+<p>
+  <label><%=t 'oauth.site_url'%></label>
+  <%= text_field_tag 'settings[site_url]', @settings['site_url'] %>
+</p>
+<p>
+  <label><%=t 'oauth.auth_url'%></label>
+  <%= text_field_tag 'settings[auth_url]', @settings['auth_url'] %>
+</p>
+<p>
+  <label><%=t 'oauth.token_url'%></label>
+  <%= text_field_tag 'settings[token_url]', @settings['token_url'] %>
+</p>
+<p>
+  <label><%=t 'oauth.ws_url'%></label>
+  <%= text_field_tag 'settings[ws_url]', @settings['ws_url'] %>
+</p>
+<p>
+  <label><%=t 'oauth.client_id'%></label>
+  <%= text_field_tag 'settings[client_id]', @settings['client_id'] %>
+</p>
+<p>
+  <label><%=t 'oauth.client_secret'%></label>
+  <%= text_field_tag 'settings[client_secret]', @settings['client_secret'] %>
+</p>
+<p>
+  <label><%=t 'oauth.sso_u_id'%></label>
+  <%= text_field_tag 'settings[sso_u_id]', @settings['sso_u_id'] %>
+</p>
+<p>
+  <label><%=t 'oauth.username_f'%></label>
+  <%= text_field_tag 'settings[field_username]', @settings['field_username'] %>
+</p>
+<p>
+  <label><%=t 'oauth.mail_f'%></label>
+  <%= text_field_tag 'settings[field_email]', @settings['field_email'] %>
+</p>
+<p>
+  <label><%=t 'oauth.firstname_f'%></label>
+  <%= text_field_tag 'settings[field_firstname]', @settings['field_firstname'] %>
+</p>
+<p>
+  <label><%=t 'oauth.lastname_f'%></label>
+  <%= text_field_tag 'settings[field_lastname]', @settings['field_lastname'] %>
+</p>
+<p>
+  <label><%=t 'oauth.resourecs_f'%></label>
+  <%= text_field_tag 'settings[field_resources]', @settings['field_resources'] %>
+</p>
+<p>
+  <label><%=t 'oauth.groups_f'%></label>
+  <%= text_field_tag 'settings[field_groups]', @settings['field_groups'] %>
+</p>
+<p>
+  <label><%=t 'oauth.roles_f'%></label>
+  <%= text_field_tag 'settings[field_roles]', @settings['field_roles'] %>
+</p>
+<p>
+ <%= link_to (t 'oauth.mapping_headline'), group_mapping_path %>
+ </p>
+<p>
+  <label><%=t 'oauth.remove_from_groups'%> </label>
+  <%= check_box_tag "settings[remove_from_groups]", true, @settings['remove_from_groups'], :onclick =>
+      "if (this.checked) { $('#settings_remove_from_all_groups').removeAttr('disabled'); } else { $('#settings_remove_from_all_groups').attr('disabled', true); }" %>
+</p>
+<p>
+  <label><%=t 'oauth.remove_from_all_groups'%></label>
+  <%= check_box_tag "settings[remove_from_all_groups]", true, @settings['remove_from_all_groups'], disabled: true %>
+</p>
+<p>
+  <label><%=t 'oauth.force_acc_creation'%> </label>
+  <%= check_box_tag "settings[force_account_creation]", true, @settings['force_account_creation'] %>
+</p>
+<p>
+  <label><%=t 'oauth.enable_oauth'%></label>
+  <%= check_box_tag "settings[oauth_authentification]", true, @settings['oauth_authentification'] %>
+</p>
+
diff --git a/plugins/redmine_omniauth_client/assets/images/bt_connect_portail.png b/plugins/redmine_omniauth_client/assets/images/bt_connect_portail.png
new file mode 100644
index 0000000000000000000000000000000000000000..f61c0a7be2f4d96fce00739e9f8399fcbe0269ad
Binary files /dev/null and b/plugins/redmine_omniauth_client/assets/images/bt_connect_portail.png differ
diff --git a/plugins/redmine_omniauth_client/assets/images/bt_connect_ror.png b/plugins/redmine_omniauth_client/assets/images/bt_connect_ror.png
new file mode 100644
index 0000000000000000000000000000000000000000..161ec0549b34fad4e08a4f82e7d5454428d77b3b
Binary files /dev/null and b/plugins/redmine_omniauth_client/assets/images/bt_connect_ror.png differ
diff --git a/plugins/redmine_omniauth_client/assets/images/keycloak_plugin.png b/plugins/redmine_omniauth_client/assets/images/keycloak_plugin.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d92a7af42fcfccfc354886671439b4ccbcec6c8
Binary files /dev/null and b/plugins/redmine_omniauth_client/assets/images/keycloak_plugin.png differ
diff --git a/plugins/redmine_omniauth_client/assets/images/keycloak_plugin_small.png b/plugins/redmine_omniauth_client/assets/images/keycloak_plugin_small.png
new file mode 100644
index 0000000000000000000000000000000000000000..249f0bdba4b18aa0be367e5740df93da272fe325
Binary files /dev/null and b/plugins/redmine_omniauth_client/assets/images/keycloak_plugin_small.png differ
diff --git a/plugins/redmine_omniauth_client/assets/stylesheets/buttons.css b/plugins/redmine_omniauth_client/assets/stylesheets/buttons.css
new file mode 100644
index 0000000000000000000000000000000000000000..865524f591d133950ca51026741f247f9c78ee79
--- /dev/null
+++ b/plugins/redmine_omniauth_client/assets/stylesheets/buttons.css
@@ -0,0 +1,60 @@
+.button-login {
+  display: inline-block;
+  border: none;
+  border-radius: 2px;
+  margin-top: 40px;
+  width: 360px;
+  height: 75px;
+  padding: 0;
+  width: 100%;
+  background-color: #fff;
+  cursor: pointer;
+}
+
+.button-login-text {
+  margin-left: auto;
+  margin-right: auto;
+  background: url(../images/keycloak_plugin_small.png) !important;
+  width: 360px;
+  height: 75px;
+  /* margin-left: -129px; */
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  /* background-repeat: no-repeat; */
+  /* overflow: hidden; */
+  /* z-index: 100; */
+  text-align: center;
+}
+
+.or-container {
+  display:block;
+  padding: 0;
+  margin: 0;
+  min-width: 900px;
+  padding-top: 40px;
+}
+
+.or-container hr {
+  width: 454px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.or-container p {
+  width: 454px;
+  margin-left: auto;
+  margin-right: auto;
+  text-align: center;
+  margin-top: -15px;
+}
+
+.or-container span {
+  background-color: #fff;
+  padding: 5px;
+  font-size: medium;
+}
+
+.no-pointer {
+  cursor: default;
+}
\ No newline at end of file
diff --git a/plugins/redmine_omniauth_client/config/locales/cs.yml b/plugins/redmine_omniauth_client/config/locales/cs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b3408c4ab8851ea478f6eee1d72295dec1dfc8a0
--- /dev/null
+++ b/plugins/redmine_omniauth_client/config/locales/cs.yml
@@ -0,0 +1,37 @@
+# Czech strings go here for Rails i18n
+cs:
+  oauth:
+    login_via_sso: 'Přihlásit se pirátskou identitou'
+    login_via_rm: 'Přihlásit se heslem z Redmine'
+
+    mapping_headline: 'Mapování skupin'
+    save: 'Uložit'
+    delete: 'Smazat'
+    edit: 'Upravit'
+    saved_settings: 'Uložené mapování'
+
+    app_name: 'Název aplikace:'
+    site_url: 'URL stránky:'
+    auth_url: 'URL autorizace:'
+    token_url: 'URL tokenu:'
+    ws_url: 'URL webové služby:'
+    client_id: 'ID klienta:'
+    client_secret: 'Tajný klíč klienta:'
+    sso_u_id: 'SSO id uživatele:'
+    username_f: 'Pole uživatelského jména:'
+    mail_f: 'Pole emailu:'
+    firstname_f: 'Pole křestního jména:'
+    lastname_f: 'Pole příjmení:'
+    resourecs_f: 'Pole zdrojů skupin:'
+    groups_f: 'Pole redmine skupin:'
+    roles_f: 'Pole rolí:'
+    remove_from_groups: 'Odebrat uživatele ze skupiny v Redmine při jeho odebrání ze skupiny v SSO:'
+    remove_from_all_groups: 'Odstranit úplně ze všech skupin, nikoliv pouze z těch párovaných na SSO:'
+    force_acc_creation: 'Vynutit založení účtu:'
+    enable_oauth: 'Povolit OAuth autentizaci:'
+
+  notices:
+    saved: 'Nastavení uloženo.'
+    notSaved: 'Chyba! Nastavení nebylo uloženo.'
+    groups_failed: 'Chyba! Selhalo přiřazení skupin.'
+    unable_to_authenticate: 'Chyba! Přihlášení selhalo. Kontaktujte prosím správce serveru'
diff --git a/plugins/redmine_omniauth_client/config/locales/en.yml b/plugins/redmine_omniauth_client/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3fd90952fabfe1fba16d1d9d0bee6d00baa3846a
--- /dev/null
+++ b/plugins/redmine_omniauth_client/config/locales/en.yml
@@ -0,0 +1,41 @@
+en:
+  notice_unable_to_obtain_app_credentials: "Unable to obtain credentials from %{app}."
+  notice_domain_not_allowed: "You can not login using %{domain} domain."
+  notice_access_denied: "You must allow to use you %{app} credentials to enter this site."
+  login_via_app: ""
+  unable_create_account: "Unable to create account."
+
+  oauth:
+    login_via_sso: 'Login via SSO'
+
+    mapping_headline: 'Groups mapping'
+    save: 'Save'
+    delete: 'Delete'
+    edit: 'Edit'
+    saved_settings: 'Saved mapping'
+
+    app_name: 'Application Name:'
+    site_url: 'Site URL:'
+    auth_url: 'Authorization URL:'
+    token_url: 'Token URL:'
+    ws_url: 'Webservice URL:'
+    client_id: 'Client ID:'
+    client_secret: 'Client Secret:'
+    sso_u_id: 'SSO user id:'
+    username_f: 'Username field:'
+    mail_f: 'Mail field:'
+    firstname_f: 'First name field:'
+    lastname_f: 'Last name field:'
+    resourecs_f: 'Group resources field:'
+    groups_f: 'Redmine groups field:'
+    roles_f: 'Roles field:'
+    remove_from_groups: 'Remove users from redmine groups with removing from SSO group:'
+    remove_from_all_groups: 'Remove user from all groups (including SSO-non-mapped):'
+    force_acc_creation: 'Force account creation:'
+    enable_oauth: 'Enable OAuth authentification:'
+
+  notices:
+    saved: 'Settings saved.'
+    notSaved: 'Error! Settings not saved.'
+    groups_failed: 'Error! Groups assignement failed.'
+    unable_to_authenticate: 'Error! Authentication failed.'
diff --git a/plugins/redmine_omniauth_client/config/locales/fr.yml b/plugins/redmine_omniauth_client/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cf559dbeb6f14c223da63b36da11aa730fe0a802
--- /dev/null
+++ b/plugins/redmine_omniauth_client/config/locales/fr.yml
@@ -0,0 +1,6 @@
+fr:
+  notice_unable_to_obtain_app_credentials: "Impossible d'obtenir les droits d'authentification sur le %{app}."
+  notice_domain_not_allowed: "Vous ne pouvez pas vous identifier sur le domaine %{domain}."
+  notice_access_denied: "Vous devez autoriser l'utilisation de vos données provenant du %{app}."
+  login_via_app: ""
+  unable_create_account: "Votre compte n'a pas pu être créé."
diff --git a/plugins/redmine_omniauth_client/config/locales/ru.yml b/plugins/redmine_omniauth_client/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0144c48460a9bfef60d6f894566cfa0e3534f131
--- /dev/null
+++ b/plugins/redmine_omniauth_client/config/locales/ru.yml
@@ -0,0 +1,5 @@
+ru:
+  notice_unable_to_obtain_app_credentials: "Не удалось получить данные от %{app}."
+  notice_domain_not_allowed: "Вы не можете войти в систему при помощи домена %{domain}."
+  notice_access_denied: "Для корректного входа необходимо разрешить приложению доступ к аккаунту."
+  login_via_app: "Войти с %{app}"
diff --git a/plugins/redmine_omniauth_client/config/routes.rb b/plugins/redmine_omniauth_client/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3d34c40a1cd24ca907771281a887a33f406bb345
--- /dev/null
+++ b/plugins/redmine_omniauth_client/config/routes.rb
@@ -0,0 +1,5 @@
+get 'oauth_client', :to => 'redmine_oauth#oauth'
+get 'oauth_client_callback', :to => 'redmine_oauth#oauth_callback', :as => 'oauth_callback'
+get 'group_mapping', :to => 'redmine_oauth#group_mapping'
+post 'post/redmine_oauth#edit', :to => 'redmine_oauth#edit'
+post 'post/redmine_oauth#delete', :to => 'redmine_oauth#delete'
\ No newline at end of file
diff --git a/plugins/redmine_omniauth_client/db/migrate/001_create_group_mappings.rb b/plugins/redmine_omniauth_client/db/migrate/001_create_group_mappings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3a652374a8a2b437aea1877b35d0b05f8c166b34
--- /dev/null
+++ b/plugins/redmine_omniauth_client/db/migrate/001_create_group_mappings.rb
@@ -0,0 +1,8 @@
+class CreateGroupMappings < ActiveRecord::Migration
+  def change
+    create_table :group_mappings do |t|
+      t.string :variable
+      t.integer :group_id
+    end
+  end
+end
diff --git a/plugins/redmine_omniauth_client/db/migrate/002_update_users_ssouid.rb b/plugins/redmine_omniauth_client/db/migrate/002_update_users_ssouid.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9efa7f1fa51d350aa329fe851a17891da98b2a81
--- /dev/null
+++ b/plugins/redmine_omniauth_client/db/migrate/002_update_users_ssouid.rb
@@ -0,0 +1,13 @@
+class UpdateUsersSsouid < ActiveRecord::Migration
+  def self.up
+    change_table :users do |t|
+      t.column :sso_u_id, :string
+    end
+  end
+
+  def self.down
+    change_table :users do |t|
+      t.remove :sso_u_id
+    end
+  end
+end
\ No newline at end of file
diff --git a/plugins/redmine_omniauth_client/init.rb b/plugins/redmine_omniauth_client/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..293026b4e66fd1cde024459f28cc7bd0e0b468b1
--- /dev/null
+++ b/plugins/redmine_omniauth_client/init.rb
@@ -0,0 +1,31 @@
+require 'redmine'
+#require 'openssl'
+require_dependency 'redmine_omniauth_client/hooks'
+require_dependency 'group_patch'
+Group.send(:include, GroupPatch)
+
+Redmine::Plugin.register :redmine_omniauth_client do
+  name 'Redmine OAuth Client plugin'
+  author 'Andre Cardoso <acardoso@orupaca.fr>'
+  description 'This is a plugin for Redmine registration through OAuth 2.0 protocole.'
+  version '0.0.1'
+  url 'https://github.com/arlin2050/redmine_omniauth_client.git'
+  
+  #OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
+
+  settings :default => {
+    :app_name => "Application name",
+    :site_url => "http://example.net",
+    :auth_url => "/oauth/v2/auth",
+    :token_url => "/oauth/v2/token",
+    :ws_url => "/current.json",
+    :client_id => "",
+    :client_secret => "",
+    :field_username => "",
+    :field_email => "",
+    :field_firstname => "",
+    :field_lastname => "",
+    :force_account_creation => true,
+    :oauth_autentification => false,
+  }, :partial => 'settings/client_settings'
+end
diff --git a/plugins/redmine_omniauth_client/lib/group_patch.rb b/plugins/redmine_omniauth_client/lib/group_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a4223fe0a67d76e0e110078092c378b9d9456e56
--- /dev/null
+++ b/plugins/redmine_omniauth_client/lib/group_patch.rb
@@ -0,0 +1,15 @@
+module GroupPatch
+  def self.included(base)
+    base.extend(ClassMethods)
+    base.send(:include, InstanceMethods)
+  end
+
+  module InstanceMethods
+    def get_id
+      return self.id
+    end
+  end
+  
+  module ClassMethods
+  end
+end
\ No newline at end of file
diff --git a/plugins/redmine_omniauth_client/lib/redmine_omniauth_client/hooks.rb b/plugins/redmine_omniauth_client/lib/redmine_omniauth_client/hooks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a3a1fddf3131b055ed2224f59977ef45bee24507
--- /dev/null
+++ b/plugins/redmine_omniauth_client/lib/redmine_omniauth_client/hooks.rb
@@ -0,0 +1,9 @@
+module RedmineOmniauthRor
+  class Hooks < Redmine::Hook::ViewListener
+    def view_account_login_top(context = {})
+      context[:controller].send(:render_to_string, {
+        :partial => "hooks/view_account_login_top",
+        :locals => context})
+    end
+  end
+end
diff --git a/plugins/redmine_omniauth_client/test/functional/redmine_oauth_controller_test.rb b/plugins/redmine_omniauth_client/test/functional/redmine_oauth_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9d5dc38922dc7f49035b3596ce07b3c6c2a1377b
--- /dev/null
+++ b/plugins/redmine_omniauth_client/test/functional/redmine_oauth_controller_test.rb
@@ -0,0 +1,116 @@
+require File.expand_path('../../test_helper', __FILE__)
+
+class RedmineOauthControllerTest < ActionController::TestCase
+  include Helpers::MailHelper
+  def setup
+    @default_user_credentials = { :firstname => 'Cool',
+                                  :lastname => 'User',
+                                  :mail => 'user@somedomain.com'}
+    @default_response_body = {:verified_email => true,
+                              :name => 'Cool User',
+                              :given_name => 'Cool',
+                              :family_name => 'User',
+                              :email => 'user@somedomain.com'}
+    User.current = nil
+    Setting.openid = '1'
+    OAuth2::AccessToken.any_instance.stubs(:get => OAuth2::Response.new(nil))
+    OAuth2::Client.any_instance.stubs(:get_token => OAuth2::AccessToken.new('code', 'redirect_uri'))
+  end
+
+  #creates a new user with the credentials listed in the options and fills in the missing data by default data
+  def new_user options = {}
+    User.where(@default_user_credentials.merge(options)).delete_all
+    user = User.new @default_user_credentials.merge(options)
+    user.login = options[:login] || 'cool_user'
+    user
+  end
+
+  #creates a new user with the credentials listed in the options and fills in the missing data by default data
+  def set_response_body_stub options = {}
+    OAuth2::Response.any_instance.stubs(:body => @default_response_body.merge(options).to_json)
+  end
+
+  def test_oauth_client_with_enabled_oauth_authentification
+    Setting.plugin_redmine_omniauth_client[:oauth_authentification] = nil
+    get :oauth_client
+    assert_response 404
+  end
+
+  def test_oauth_client_callback_for_existing_non_active_user
+    Setting.self_registration = '2'
+    user = new_user :status => User::STATUS_REGISTERED
+    assert user.save
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to signin_path
+  end
+
+  def test_oauth_client_callback_for_existing_active_user
+    user = new_user
+    user.activate
+    assert user.save
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :controller => 'my', :action => 'page'
+  end
+
+  def test_oauth_client_callback_for_new_user_with_valid_credentials_and_sefregistration_enabled
+    Setting.self_registration = '3'
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :controller => 'my', :action  => 'account'
+    user = User.find_by_mail(@default_response_body[:email])
+    assert_equal user.mail, @default_response_body[:email]
+    assert_equal user.login, parse_email(@default_response_body[:email])[:login]
+  end
+
+  def test_oauth_client_callback_for_new_user_with_valid_credentials_and_sefregistration_disabled
+    Setting.self_registration = '2'
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to signin_path
+  end
+
+  def test_oauth_client_callback_with_new_user_with_invalid_oauth_provider
+    Setting.self_registration = '3'
+    set_response_body_stub :verified_email => false
+    get :oauth_client_callback
+    assert_redirected_to signin_path
+  end
+
+  def test_oauth_client_callback_with_new_user_created_with_email_activation_should_have_a_token
+    Setting.self_registration = '1'
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :signin
+    user = User.find_by_mail(@default_user_credentials[:mail])
+    assert user
+    token = Token.find_by_user_id_and_action(user.id, 'register')
+    assert token
+  end
+
+  def test_oauth_client_callback_with_new_user_created_with_manual_activation
+    Setting.self_registration = '2'
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :signin
+    user = User.find_by_mail(@default_user_credentials[:mail])
+    assert user
+    assert_equal User::STATUS_REGISTERED, user.status
+  end
+
+  def test_oauth_client_callback_with_not_allowed_email_domain
+    Setting.plugin_redmine_omniauth_client[:allowed_domains] = "twinslash.com"
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :signin
+  end
+
+  def test_oauth_client_callback_with_allowed_email_domain
+    Setting.self_registration = '3'
+    Setting.plugin_redmine_omniauth_client[:allowed_domains] = parse_email(@default_response_body[:email])[:domain]
+    set_response_body_stub
+    get :oauth_client_callback
+    assert_redirected_to :controller => 'my', :action => 'account'
+  end
+end
diff --git a/plugins/redmine_omniauth_client/test/test_helper.rb b/plugins/redmine_omniauth_client/test/test_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..54685d33ca58645c65e0566b7d3a93d166f71f44
--- /dev/null
+++ b/plugins/redmine_omniauth_client/test/test_helper.rb
@@ -0,0 +1,2 @@
+# Load the Redmine helper
+require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
diff --git a/plugins/redmine_omniauth_client/test/unit/group_mapping_test.rb b/plugins/redmine_omniauth_client/test/unit/group_mapping_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f03a143de836512587ed7bceda1461a45055a4aa
--- /dev/null
+++ b/plugins/redmine_omniauth_client/test/unit/group_mapping_test.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../../test_helper', __FILE__)
+
+class GroupMappingTest < ActiveSupport::TestCase
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/.hgignore b/plugins/redmine_work_time/.hgignore
new file mode 100644
index 0000000000000000000000000000000000000000..ca4b11cee3c8b897f86c6eabe03726adbe8bdfd4
--- /dev/null
+++ b/plugins/redmine_work_time/.hgignore
@@ -0,0 +1,3 @@
+syntax: regexp
+.svn
+.git
diff --git a/plugins/redmine_work_time/GPL.txt b/plugins/redmine_work_time/GPL.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d511905c1647a1e311e8b20d5930a37a9c2531cd
--- /dev/null
+++ b/plugins/redmine_work_time/GPL.txt
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/plugins/redmine_work_time/README.md b/plugins/redmine_work_time/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1f372513674f8ec9b1b0da1a268d5b20c5a247b6
--- /dev/null
+++ b/plugins/redmine_work_time/README.md
@@ -0,0 +1,17 @@
+WorkTime is a plugin of Redmine to view and update Spent time by each user.
+
+### Installation notes ###
+
+0. Setup Redmine
+1. Download redmine_work_time-*.zip from https://bitbucket.org/tkusukawa/redmine_work_time/downloads
+2. Expand the plugin into the plugins directory
+3. Migrate plugin: rake redmine:plugins:migrate RAILS_ENV=production
+4. Restart Redmine
+5. Enable the module on the project setting page.
+6. Check the permissions on the Roles and permissions(Administration)
+
+### Links ###
+
+* http://www.redmine.org/plugins/redmine_work_time
+* https://bitbucket.org/tkusukawa/redmine_work_time
+* http://www.r-labs.org/projects/worktime/
\ No newline at end of file
diff --git a/plugins/redmine_work_time/README.rdoc b/plugins/redmine_work_time/README.rdoc
new file mode 100644
index 0000000000000000000000000000000000000000..91fe638b3d917f9d5d9395a4d9b6f978982100a3
--- /dev/null
+++ b/plugins/redmine_work_time/README.rdoc
@@ -0,0 +1,3 @@
+= work_time
+
+Description goes here
diff --git a/plugins/redmine_work_time/app/controllers/work_time_controller.rb b/plugins/redmine_work_time/app/controllers/work_time_controller.rb
new file mode 100755
index 0000000000000000000000000000000000000000..3a407d1f40874de2ada3e90324a344a9602f9476
--- /dev/null
+++ b/plugins/redmine_work_time/app/controllers/work_time_controller.rb
@@ -0,0 +1,1428 @@
+class WorkTimeController < ApplicationController
+  unloadable
+  #  before_filter :find_project, :authorize
+  accept_api_auth :relay_total
+
+  helper :custom_fields
+  include CustomFieldsHelper
+
+  NO_ORDER = -1
+
+  def index
+    @message = ""
+    require_login || return
+    @project = nil
+    prepare_values
+    ticket_pos
+    prj_pos
+    ticket_del
+    hour_update
+    make_pack
+    update_daily_memo(params[:memo]) if params.key?(:memo)
+    set_holiday
+    @custom_fields = TimeEntryCustomField.all
+    @link_params.merge!(:action=>"index")
+    if !params.key?(:user) then
+      redirect_to @link_params
+    else
+      render "show"
+    end
+  end
+
+  def show
+    @message = ""
+    require_login || return
+    find_project
+    authorize
+    prepare_values
+    if @this_user.nil? || !@this_user.allowed_to?(:view_work_time_tab, @project)
+      @link_params.merge!(:action=>"relay_total")
+      redirect_to @link_params
+      return
+    end
+    ticket_pos
+    prj_pos
+    ticket_del
+    hour_update
+    make_pack
+    member_add_del_check
+    update_daily_memo(params[:memo]) if params.key?(:memo)
+    set_holiday
+    @custom_fields = TimeEntryCustomField.all
+    @link_params.merge!(:action=>"show")
+    if !params.key?(:user) then
+      redirect_to @link_params
+    end
+  end
+
+  def member_monthly_data
+    require_login || return
+    if params.key?(:id) then
+      find_project
+    end
+    prepare_values
+    make_pack
+
+    csv_data = %Q|"user","date","project","ticket","spent time"\n|
+
+    (@first_date..@last_date).each do |date|
+      @month_pack[:odr_prjs].each do |prj_pack|
+        next if prj_pack[:count_issues] == 0
+        prj_pack[:odr_issues].each do |issue_pack|
+          next if issue_pack[:count_hours] == 0
+          issue = issue_pack[:issue]
+          if issue_pack[:total_by_day][date] then
+            csv_data << %Q|"#{@this_user}","#{date}","#{issue.project}","##{issue.id} #{issue.subject}",#{issue_pack[:total_by_day][date]}\n|
+          end
+        end
+      end
+      if @month_pack[:other_by_day].has_key?(date) then
+        csv_data << %Q|"#{@this_user}","#{date}","PRIVATE","PRIVATE",#{@month_pack[:other_by_day][date]}\n|
+      end
+    end
+    send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"member_monthly.csv"
+  end
+
+  def total
+    @message = ""
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+    @link_params.merge!(:action=>"total")
+  end
+
+  def total_data
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+    
+    csv_data = %Q|"user","relayed project","relayed ticket","project","ticket","spent time"\n|
+    #-------------------------------------- メンバーのループ
+    @members.each do |mem_info|
+      user = mem_info[1]
+
+      #-------------------------------------- プロジェクトのループ
+      prjs = WtProjectOrders.where("uid=-1").
+          order("dsp_pos").
+          all
+      prjs.each do |po|
+        dsp_prj = po.dsp_prj
+        dsp_pos = po.dsp_pos
+        next unless @prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
+        next unless @prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
+        next if @prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
+        prj =Project.find_by_id(dsp_prj)
+        
+        #-------------------------------------- チケットのループ
+        tickets = WtTicketRelay.order("position").all
+        tickets.each do |tic|
+          issue_id = tic.issue_id
+          next unless @issue_cost.key?(issue_id) # 値の無いチケットはパス
+          next unless @issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
+          next if @issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
+          next unless @issue_cost[issue_id].key?(user.id) # 値の無いチケットはパス
+          next if @issue_cost[issue_id][user.id] == 0 # 値の無いチケットはパス
+
+          issue = Issue.find_by_id(issue_id)
+          next if issue.nil? # チケットが削除されていたらパス
+          next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
+
+          parent_issue = Issue.find_by_id(@issue_parent[issue_id])
+          next if parent_issue.nil? # チケットが削除されていたらパス
+
+          csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
+          csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}",#{@issue_cost[issue_id][user.id]}\n|
+        end
+      end
+      if @issue_cost.has_key?(-1) && @issue_cost[-1].has_key?(user.id) then
+        csv_data << %Q|"#{user}","private","private","private","private",#{@issue_cost[-1][user.id]}\n|
+      end
+    end
+    send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_raw.csv"
+  end
+
+  def total_data_with_act
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+
+    csv_data = %Q|"user","relayed project","relayed ticket","project","ticket","activity","spent time"\n|
+    @issue_act_cost.each do |issue_id, user_act_cost|
+      if issue_id >0
+        issue = Issue.find_by_id(issue_id)
+        next if issue.nil? # チケットが削除されていたらパス
+
+        parent_issue = Issue.find_by_id(@issue_parent[issue_id])
+        next if parent_issue.nil? # チケットが削除されていたらパス
+
+        prj = issue.project
+
+        user_act_cost.each do |user_id, act_cost|
+          user = User.find_by_id(user_id)
+          act_cost.each do |act_id, cost|
+            act = TimeEntryActivity.find_by_id(act_id)
+            unless act.nil?
+              csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
+              csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}","#{act.name}",|
+              csv_data << %Q|#{cost}\n|
+            else # can not find activity
+              csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
+              csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}","nil",|
+              csv_data << %Q|#{cost}\n|
+            end
+          end
+        end
+      else # 表示権限の無い工数があった場合
+        user_act_cost.each do |user_id, act_cost|
+          user = User.find_by_id(user_id)
+          act_cost.each do |act_id, cost|
+            csv_data << %Q|"#{user}","private","private","private","private","private",|
+            csv_data << %Q|#{cost}\n|
+          end
+        end
+      end
+    end
+    send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_raw_with_act.csv"
+  end
+
+  def edit_relay
+    @message = ""
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+    @link_params.merge!(:action=>"edit_relay")
+  end
+
+  def relay_total
+    @message = ""
+    find_project || return
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+    respond_to do |format|
+	format.html {
+	    @link_params.merge!(:action=>"relay_total")
+	}
+	format.api {}
+    end
+  end
+
+  def relay_total_data
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+    
+    csv_data = %Q|"user","project","ticket","spent time"\n|
+    #-------------------------------------- メンバーのループ
+    @members.each do |mem_info|
+      user = mem_info[1]
+
+      #-------------------------------------- プロジェクトのループ
+      prjs = WtProjectOrders.where("uid=-1").
+          order("dsp_pos").
+          all
+      prjs.each do |po|
+        dsp_prj = po.dsp_prj
+        dsp_pos = po.dsp_pos
+        next unless @r_prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
+        next unless @r_prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
+        next if @r_prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
+        prj =Project.find_by_id(dsp_prj)
+        
+        #-------------------------------------- チケットのループ
+        tickets = WtTicketRelay.order("position").all
+        tickets.each do |tic|
+          issue_id = tic.issue_id
+          next unless @r_issue_cost.key?(issue_id) # 値の無いチケットはパス
+          next unless @r_issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
+          next if @r_issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
+          next unless @r_issue_cost[issue_id].key?(user.id) # 値の無いチケットはパス
+          next if @r_issue_cost[issue_id][user.id] == 0 # 値の無いチケットはパス
+
+          issue = Issue.find_by_id(issue_id)
+          next if issue.nil? # チケットが削除されていたらパス
+          next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
+
+          csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",#{@r_issue_cost[issue_id][user.id]}\n|
+        end
+      end
+      if @r_issue_cost.has_key?(-1) && @r_issue_cost[-1].has_key?(user.id) then
+        csv_data << %Q|"#{user}","private","private",#{@r_issue_cost[-1][user.id]}\n|
+      end
+    end
+    send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report.csv"
+  end
+
+  def relay_total_data_with_act
+    find_project
+    authorize
+    prepare_values
+    change_member_position
+    change_ticket_position
+    change_project_position
+    member_add_del_check
+    calc_total
+
+    csv_data = %Q|"user","project","ticket","activity","spent time"\n|
+    @r_issue_act_cost.each do |issue_id, user_act_cost|
+      if issue_id >0
+        issue = Issue.find_by_id(issue_id)
+        next if issue.nil?
+        prj = issue.project
+
+        user_act_cost.each do |user_id, act_cost|
+          user = User.find_by_id(user_id)
+          act_cost.each do |act_id, cost|
+            act = TimeEntryActivity.find_by_id(act_id)
+            unless act.nil?
+              csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",|
+              csv_data << %Q|"#{act.name}",#{cost}\n|
+            else # can not find activity
+              csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",|
+              csv_data << %Q|"nil",#{cost}\n|
+            end
+          end
+        end
+      else # 表示権限の無い工数があった場合
+        user_act_cost.each do |user_id, act_cost|
+          user = User.find_by_id(user_id)
+          act_cost.each do |act_id, cost|
+            csv_data << %Q|"#{user}","private","private",|
+            csv_data << %Q|"private",#{cost}\n|
+          end
+        end
+      end
+    end
+    send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_with_act.csv"
+  end
+
+  def ajax_relay
+    if !params.key?(:issue_id)
+      render :layout=>false, :text=>'ERROR: no issue_id'
+      return
+    end
+    @issue_id = params[:issue_id].to_i
+
+    find_project
+    @message = ''
+    @parent_disp = ''
+    @relay_modified = false
+
+    if params.key?(:parent_id)
+      @parent_id = params[:parent_id].to_i
+      if @parent_id >= 0
+        update_relay @issue_id, @parent_id
+      else
+        # parent_id == -1 by set_ticket_relay_by_issue_relation
+        redmine_parent_id = Issue.find_by_id(@issue_id).parent_id
+        if redmine_parent_id && redmine_parent_id >= 1 # has parent
+          update_relay @issue_id, redmine_parent_id
+        end
+      end
+    end
+    relay = WtTicketRelay.where(["issue_id=:i",{:i=>@issue_id}]).first
+    @parent_id = relay.parent
+
+    if @parent_id != 0 && !((parent = Issue.find_by_id(@parent_id)).nil?) then
+      @parent_disp = parent.closed? ? '<del>'+parent.to_s+'</del>' : parent.to_s
+    end
+    render :layout=>false
+  end
+
+  def update_relay(issue_id, parent_id)
+    if !User.current.allowed_to?(:edit_work_time_total, @project)
+      @message ||= ''
+      @message += l(:wt_no_permission)
+      return
+    end
+
+    # loop relay check
+    route = ''
+    search_id = parent_id
+    while search_id != 0 do
+      route += "->#{search_id}"
+      if search_id == issue_id
+        @message ||= ''
+        @message += l(:wt_loop_relay)+route
+        return
+      end
+      relay = WtTicketRelay.where(["issue_id=:i",{:i=>search_id}]).first
+      break if !relay
+      search_id = relay.parent
+    end
+
+    relay = WtTicketRelay.where(["issue_id=:i",{:i=>issue_id}]).first
+    if relay then
+      relay.parent = parent_id
+      relay.save
+      @relay_modified = true
+    else
+      @message ||= ''
+      @message += "Internal Error: no WtTicketRelay for ##{issue_id}"
+    end
+  end
+
+  def ajax_relay_input # チケット選択の内容を返すアクション
+    @issue_id = params[:issue_id]
+    @projects = Project.joins("INNER JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=-1").
+        select("projects.*, wt_project_orders.dsp_pos as pos").
+        order("pos").
+        all
+    render(:layout=>false)
+  end
+
+  def ajax_relay_input_select # チケット選択ウィンドウにAjaxで挿入(Update)される内容を返すアクション
+    @issue_id = params[:issue_id]
+    @issues = Issue.includes(:assigned_to).
+        where(["project_id=:p",{:p=>params[:prj]}]).
+        order("id DESC").
+        all
+    render(:layout=>false)
+  end
+
+  def ajax_add_tickets_input
+    prepare_values
+    @select_projects = Project.
+        joins("LEFT JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=#{User.current.id}").
+        select("projects.*, coalesce(wt_project_orders.dsp_pos,100000) as pos").
+        order("pos,name").
+        all
+    render(:layout=>false)
+  end
+
+  def ajax_add_tickets_input_select # 複数チケット選択ウィンドウにAjaxで挿入(Update)される内容を返すアクション
+    prepare_values
+    @issues = Issue.
+        includes(:assigned_to).
+        where(["project_id=:p",{:p=>params[:prj]}]).
+        order("id DESC").
+        all
+
+    render(:layout=>false)
+  end
+
+  def ajax_add_tickets_insert # 日毎工数に挿入するAjaxアクション
+    prepare_values
+
+    uid = params[:user]
+    @add_issue_id = params[:add_issue]
+    @add_count = params[:count]
+    if @this_uid==@crnt_uid then
+      add_issue = Issue.find_by_id(@add_issue_id)
+      @add_issue_children_cnt = Issue.where(["parent_id = ?", add_issue.id.to_s]).count
+      if add_issue && add_issue.visible? then
+        prj = add_issue.project
+        if User.current.allowed_to?(:log_time, prj) then
+          if add_issue.closed? then
+            @issueHtml = "<del>"+add_issue.to_s+"</del>"
+          else
+            @issueHtml = add_issue.to_s
+          end
+
+          @activities = []
+          @activity_default = nil
+          prj.activities.each do |act|
+            @activities.push([act.name, act.id])
+            @activity_default = act.id if act.is_default
+          end
+
+          @custom_fields = TimeEntryCustomField.all
+          @custom_fields.each do |cf|
+            def cf.custom_field
+              return self
+            end
+            def cf.value
+              return self.default_value
+            end
+            def cf.true?
+              return self.default_value
+            end
+          end
+
+          @add_issue = add_issue
+
+          unless UserIssueMonth.exists?(["uid=:u and issue=:i",{:u=>uid, :i=>@add_issue_id}]) then
+            # 既存のレコードが存在していなければ追加
+            UserIssueMonth.create(:uid=>uid, :issue=>@add_issue_id,
+              :odr => UserIssueMonth.where(["uid = ?", uid]).count + 1
+            )
+          end
+        end
+      end
+    end
+
+    render(:layout=>false)
+  end
+
+  def ajax_memo_edit # 日毎のメモ入力フォームを出力するAjaxアクション
+    render(:layout=>false)
+  end
+
+  def ajax_done_ratio_input # 進捗%更新ポップアップ
+    prepare_values
+    issue_id = params[:issue_id]
+    @issue = Issue.find_by_id(issue_id)
+    if @issue.nil? || @issue.closed? || !@issue.visible? then
+      @issueHtml = "<del>"+@issue.to_s+"</del>"
+    else
+      @issueHtml = @issue.to_s
+    end
+    render(:layout=>false)
+  end
+
+  def ajax_done_ratio_update
+    prepare_values
+    issue_id = params[:issue_id]
+    done_ratio = params[:done_ratio]
+    @issue = Issue.find_by_id(issue_id)
+    if User.current.allowed_to?(:edit_issues, @issue.project) then
+      @issue.init_journal(User.current)
+      @issue.done_ratio = done_ratio
+      @issue.save
+    end
+    render(:layout=>false)
+  end
+
+  def register_project_settings
+    @message = ""
+    require_login || return
+    find_project
+    authorize
+    @settings = Setting.plugin_redmine_work_time
+    @settings = Hash.new unless @settings.is_a?(Hash)
+    @settings['account_start_days'] = Hash.new unless @settings['account_start_days'].is_a?(Hash)
+    @settings['account_start_days'][@project.id.to_s] = params['account_start_day']
+    Setting.plugin_redmine_work_time = @settings
+    redirect_to :controller => 'projects',
+                :action => 'settings', :id => @project, :tab => 'work_time'
+  end
+
+private
+  def find_project
+    # Redmine Pluginとして必要らしいので@projectを設定
+    @project = Project.find(params[:id])
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+
+  def prepare_values
+    # ************************************* 値の準備
+    @crnt_uid = User.current.id
+    @this_uid = (params.key?(:user) && User.current.allowed_to?(:view_work_time_other_member, @project)) ? params[:user].to_i : @crnt_uid
+    @this_user = User.find_by_id(@this_uid)
+
+    if @project &&
+      Setting.plugin_redmine_work_time.is_a?(Hash) &&
+      Setting.plugin_redmine_work_time['account_start_days'].is_a?(Hash) &&
+      Setting.plugin_redmine_work_time['account_start_days'].has_key?(@project.id.to_s)
+        @account_start_day = Setting.plugin_redmine_work_time['account_start_days'][@project.id.to_s].to_i
+    else
+      @account_start_day = 1
+    end
+
+    @today = Date.today
+    year = params.key?(:year) ? params[:year].to_i : @today.year
+    month = params.key?(:month) ? params[:month].to_i : @today.month
+    day = params.key?(:day) ? params[:day].to_i : @today.day
+    @this_date = Date.new(year, month, day)
+    display_date = @this_date
+    display_date <<= 1 if day < @account_start_day
+    @display_year = display_date.year
+    @display_month = display_date.month
+
+    @last_month = @this_date << 1
+    @next_month = @this_date >> 1
+
+    @restrict_project = (params.key?(:prj) && params[:prj].to_i > 0) ? params[:prj].to_i : false
+
+    @first_date = Date.new(@display_year, @display_month, @account_start_day)
+    @last_date = (@first_date >> 1) - 1
+
+    @month_names = l(:wt_month_names).split(',')
+    @wday_name = l(:wt_week_day_names).split(',')
+    @wday_color = ["#faa", "#eee", "#eee", "#eee", "#eee", "#eee", "#aaf"]
+
+    @link_params = {:controller=>"work_time", :id=>@project,
+                    :year=>year, :month=>month, :day=>day,
+                    :user=>@this_uid, :prj=>@restrict_project}
+    @is_registerd_backlog = false
+    begin
+      Redmine::Plugin.find :redmine_backlogs
+      @is_registerd_backlog = true
+    rescue Exception => exception
+    end
+  end
+
+  def ticket_pos
+    return if @this_uid != @crnt_uid
+
+    # 重複削除と順序の正規化
+    if order_normalization(UserIssueMonth, :issue, :order=>"odr", :conditions=>["uid=:u",{:u=>@this_uid}]) then
+      @message ||= ''
+      #@message += '<div style="background:#faa;">Warning: normalize UserIssueMonth</div>'
+      return
+    end
+
+    # 表示チケット順序変更求処理
+    if params.key?("ticket_pos") && params[:ticket_pos] =~ /^(.*)_(.*)$/ then
+      tid = $1.to_i
+      dst = $2.to_i
+      src = UserIssueMonth.where(["uid=:u and issue=:i", {:u=>@this_uid,:i=>tid}]).first
+      if src then # ポジション変更の場合
+        if src.odr > dst then # チケットを前にもっていく場合
+          tgts = UserIssueMonth.
+              where(["uid=:u and odr>=:o1 and odr<:o2", {:u=>src.uid, :o1=>dst, :o2=>src.odr}]).
+              all
+          tgts.each do |tgt|
+            tgt.odr += 1; tgt.save# 順位をひとつずつ後へ
+          end
+          src.odr = dst; src.save
+        else # チケットを後に持っていく場合
+          tgts = UserIssueMonth.
+              where(["uid=:u and odr<=:o1 and odr>:o2",{:u=>src.uid, :o1=>dst, :o2=>src.odr}]).
+              all
+          tgts.each do |tgt|
+            tgt.odr -= 1; tgt.save# 順位をひとつずつ後へ
+          end
+          src.odr = dst; src.save
+        end
+      else
+        # 新規のポジションの場合
+        tgts = UserIssueMonth.
+            where(["uid=:u and odr>=:o1", {:u=>@this_uid, :o1=>dst}]).
+            all
+        tgts.each do |tgt|
+          tgt.odr += 1; tgt.save# 順位をひとつずつ後へ
+        end
+        UserIssueMonth.create(:uid=>@this_uid, :issue=>tid, :odr=>dst) # 追加
+      end
+    end
+  end
+
+  def prj_pos
+    return if @this_uid != @crnt_uid
+
+    # 重複削除と順序の正規化
+    if order_normalization(WtProjectOrders, :dsp_prj, :order=>"dsp_pos", :conditions=>["uid=:u",{:u=>@this_uid}]) then
+      @message ||= ''
+      #@message += '<div style="background:#faa;">Warning: normalize WtProjectOrders</div>'
+      return
+    end
+
+    # 表示プロジェクト順序変更求処理
+    if params.key?("prj_pos") && params[:prj_pos] =~ /^(.*)_(.*)$/ then
+      tid = $1.to_i
+      dst = $2.to_i
+      src = WtProjectOrders.
+          where(["uid=:u and dsp_prj=:d",{:u=>@this_uid, :d=>tid}]).
+          first
+
+      if src then # ポジション変更の場合
+        if src.dsp_pos > dst then # チケットを前にもっていく場合
+          tgts = WtProjectOrders.
+              where(["uid=:u and dsp_pos>=:o1 and dsp_pos<:o2",{:u=>@this_uid, :o1=>dst, :o2=>src.dsp_pos}]).
+              all
+          tgts.each do |tgt|
+            tgt.dsp_pos += 1; tgt.save# 順位をひとつずつ後へ
+          end
+          src.dsp_pos = dst; src.save
+        else # チケットを後に持っていく場合
+          tgts = WtProjectOrders.
+              where(["uid=:u and dsp_pos<=:o1 and dsp_pos>:o2",{:u=>@this_uid, :o1=>dst, :o2=>src.dsp_pos}]).
+              all
+          tgts.each do |tgt|
+            tgt.dsp_pos -= 1; tgt.save# 順位をひとつずつ後へ
+          end
+          src.dsp_pos = dst; src.save
+        end
+      else
+        # 新規のポジションの場合
+          tgts = WtProjectOrders.
+              where(["uid=:u and dsp_pos>=:o1",{:u=>@this_uid, :o1=>dst}]).
+              all
+          tgts.each do |tgt|
+            tgt.dsp_pos += 1; tgt.save# 順位をひとつずつ後へ
+          end
+          WtProjectOrders.create(:uid=>@this_uid, :dsp_prj=>tid, :dsp_pos=>dst)
+      end
+    end
+  end
+
+  def ticket_del # チケット削除処理
+    if params.key?("ticket_del") then
+      if params["ticket_del"]=="closed" then # 終了チケット全削除の場合
+          issues = Issue.
+              joins("INNER JOIN user_issue_months ON user_issue_months.issue=issues.id").
+              where(["user_issue_months.uid=:u",{:u=>@this_uid}]).
+              all
+          issues.each do |issue|
+            if issue.closed? then
+              tgt = UserIssueMonth.
+                  where(["uid=:u and issue=:i",{:u=>@this_uid,:i=>issue.id}]).first
+              tgt.destroy
+            end
+          end
+          return
+      end
+
+      # チケット番号指定の削除の場合
+      src = UserIssueMonth.
+          where(["uid=:u and issue=:i",{:u=>@this_uid,:i=>params["ticket_del"]}]).
+          first
+      if src && src.uid == @crnt_uid then
+          tgts = UserIssueMonth.
+              where(["uid=:u and odr>:o",{:u=>src.uid, :o=>src.odr}]).
+              all
+          tgts.each do |tgt|
+            tgt.odr -= 1; tgt.save# 当該チケット表示より後ろの全チケットの順位をアップ
+          end
+          src.destroy# 当該チケット表示を削除
+      end
+    end
+  end
+
+  def hour_update # *********************************** 工数更新要求の処理
+    by_other = false
+    if @this_uid != @crnt_uid
+      if User.current.allowed_to?(:edit_work_time_other_member, @project)
+        by_other = true
+      else
+        return
+      end
+    end
+
+    # 新規工数の登録
+    if params["new_time_entry"] then
+      params["new_time_entry"].each do |issue_id, valss|
+        issue = Issue.find_by_id(issue_id)
+        next if issue.nil? || !issue.visible?
+        next if !User.current.allowed_to?(:log_time, issue.project)
+        valss.each do |count, vals|
+          tm_vals = vals.slice! "remaining_hours", "status_id"
+          tm_vals.merge!(params["new_time_entry_#{issue_id}_#{count}"]) if params.has_key?("new_time_entry_#{issue_id}_#{count}")
+          next if tm_vals["hours"].blank? && vals["remaining_hours"].blank? && vals["status_id"].blank?
+          if tm_vals["hours"].present? then
+            if !tm_vals[:activity_id] then
+              append_error_message_html(@message, 'Error: Issue'+issue_id+': No Activities!')
+              next
+            end
+            if by_other
+              append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
+              append_text += " add time entry of ##{issue.id.to_s}: #{tm_vals[:hours].to_f}h"
+              update_daily_memo(append_text, true)
+            end
+            new_entry = TimeEntry.new(:project => issue.project, :issue => issue, :user => @this_user, :spent_on => @this_date)
+            new_entry.safe_attributes = tm_vals
+            new_entry.save
+            append_error_message_html(@message, hour_update_check_error(new_entry, issue_id))
+          end
+          if vals["remaining_hours"].present? || vals["status_id"].present? then
+            append_error_message_html(@message, issue_update_to_remain_and_more(issue_id, vals))
+          end
+        end
+      end
+    end
+
+    # 既存工数の更新
+    if params["time_entry"] then
+      params["time_entry"].each do |id, vals|
+        tm = TimeEntry.find_by_id(id)
+        issue_id = tm.issue.id
+        tm_vals = vals.slice! "remaining_hours", "status_id"
+        tm_vals.merge!(params["time_entry_"+id.to_s]) if params.has_key?("time_entry_"+id.to_s)
+        if tm_vals["hours"].blank? then
+          # 工数指定が空文字の場合は工数項目を削除
+          if by_other
+            append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
+            append_text += " delete time entry of ##{issue_id.to_s}: -#{tm.hours.to_f}h-"
+            update_daily_memo(append_text, true)
+          end
+          tm.destroy
+        else
+          if by_other && tm_vals.key?(:hours) && tm.hours.to_f != tm_vals[:hours].to_f
+            append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
+            append_text += " update time entry of ##{issue_id.to_s}: -#{tm.hours.to_f}h- #{tm_vals[:hours].to_f}h"
+            update_daily_memo(append_text, true)
+          end
+          tm.safe_attributes = tm_vals
+          tm.save
+          append_error_message_html(@message, hour_update_check_error(tm, issue_id))
+        end
+        if vals["remaining_hours"].present? || vals["status_id"].present? then
+          append_error_message_html(@message, issue_update_to_remain_and_more(issue_id, vals))
+        end
+      end
+    end
+  end
+
+  def issue_update_to_remain_and_more(issue_id, vals)
+    issue = Issue.find_by_id(issue_id)
+    return 'Error: Issue'+issue_id+': Private!' if issue.nil? || !issue.visible?
+    return if vals["remaining_hours"].blank? && vals["status_id"].blank?
+    journal = issue.init_journal(User.current)
+    # update "0.0" is changed
+    vals["remaining_hours"] = 0 if vals["remaining_hours"] == "0.0"
+    if vals['status_id'] =~ /^M+(.*)$/
+      vals['status_id'] = $1.to_i
+    else
+      vals.delete 'status_id'
+    end
+    issue.safe_attributes = vals
+    return if !issue.changed?
+    issue.save
+    hour_update_check_error(issue, issue_id)
+  end
+
+  def append_error_message_html(html, msg)
+    @message ||= ''
+    @message += '<div style="background:#faa;">' + msg + '</div><br>' if !msg.blank?
+  end
+
+  def hour_update_check_error(obj, issue_id)
+    return "" if obj.errors.empty?
+    str = l("field_issue")+"#"+issue_id.to_s+"<br>"
+    fm = obj.errors.full_messages
+    fm.each do |msg|
+        str += msg+"<br>"
+    end
+    str.html_safe
+  end
+
+  def member_add_del_check
+    # プロジェクトのメンバーを取得
+    mem = Member.where(["project_id=:prj", {:prj=>@project.id}]).all
+    mem_by_uid = {}
+    mem.each do |m|
+      next if m.nil? || m.user.nil? || ! m.user.allowed_to?(:view_work_time_tab, @project)
+      mem_by_uid[m.user_id] = m
+    end
+
+    # メンバーの順序を取得
+    odr = WtMemberOrder.where(["prj_id=:p", {:p=>@project.id}]).order("position").all
+
+    # 当月のユーザ毎の工数入力数を取得
+    entry_count = TimeEntry.
+        where(["spent_on>=:first_date and spent_on<=:last_date",
+               {:first_date=>@first_date, :last_date=>@last_date}]).
+        select("user_id, count(hours)as cnt").
+        group("user_id").
+        all
+    cnt_by_uid = {}
+    entry_count.each do |ec|
+      cnt_by_uid[ec.user_id] = ec.cnt
+    end
+
+    @members = []
+    pos = 1
+    # 順序情報にあってメンバーに無いものをチェック
+    odr.each do |o|
+      if mem_by_uid.has_key?(o.user_id) then
+        user=mem_by_uid[o.user_id].user
+        if ! user.nil? then
+          # 順位の確認と修正
+          if o.position != pos then
+            o.position=pos
+            o.save
+          end
+          # 表示メンバーに追加
+          if user.active? || cnt_by_uid.has_key?(user.id) then
+            @members.push([pos, user])
+          end
+          pos += 1
+          # 順序情報に存在したメンバーを削っていく
+          mem_by_uid.delete(o.user_id)
+          next
+        end
+      end
+      # メンバーに無い順序情報は削除する
+      o.destroy
+    end
+
+    # 残ったメンバーを順序情報に加える
+    mem_by_uid.each do |k,v|
+      user = v.user
+      next if user.nil?
+      n = WtMemberOrder.new(:user_id=>user.id,
+                              :position=>pos,
+                              :prj_id=>@project.id)
+      n.save
+      if user.active? || cnt_by_uid.has_key?(user.id) then
+        @members.push([pos, user])
+      end
+      pos += 1
+    end
+    
+  end
+
+  def update_daily_memo(text, append = false) # 日ごとメモの更新
+    year = params[:year] || return
+    month = params[:month] || return
+    day = params[:day] || return
+    user_id = params[:user] || return
+
+    # ユーザと日付で既存のメモを検索
+    date = Date.new(year.to_i,month.to_i,day.to_i)
+    memo = WtDailyMemo.where(["day=:d and user_id=:u",{:d=>date,:u=>user_id}]).first
+
+    if memo then
+      # 既存のメモがあれば
+      text = memo.description + text if append
+      memo.description = text
+      memo.updated_on = Time.now
+      memo.save # æ›´æ–°
+    else
+      # 既存のメモがなければ新規作成
+      now = Time.now
+      WtDailyMemo.create(:user_id=>user_id,
+                         :day=>date,
+                         :created_on=>now,
+                         :updated_on=>now,
+                         :description=>text)
+    end
+  end
+
+  ################################ 休日設定
+  def set_holiday
+    user_id = params["user"] || return
+    if set_date = params['set_holiday'] then
+      WtHolidays.create(:holiday=>set_date, :created_on=>Time.now, :created_by=>user_id)
+    end
+    if del_date = params['del_holiday'] then
+      holidays = WtHolidays.where(["holiday=:h and deleted_on is null",{:h=>del_date}]).all
+      holidays.each do |h|
+        h.deleted_on = Time.now
+        h.deleted_by = user_id
+        h.save
+      end
+    end
+  end
+
+  def change_member_position
+    ################################### メンバー順序変更処理
+    if params.key?("member_pos") && params[:member_pos]=~/^(.*)_(.*)$/ then
+      if User.current.allowed_to?(:edit_work_time_total, @project) then
+        uid = $1.to_i
+        dst = $2.to_i
+        mem = WtMemberOrder.where(["prj_id=:p and user_id=:u",{:p=>@project.id, :u=>uid}]).first
+        if mem then
+          if mem.position > dst then # メンバーを前に持っていく場合
+            tgts = WtMemberOrder.
+                where(["prj_id=:p and position>=:p1 and position<:p2",{:p=>@project.id, :p1=>dst, :p2=>mem.position}]).
+                all
+            tgts.each do |mv|
+              mv.position+=1; mv.save # 順位を一つずつ後へ
+            end
+            mem.position=dst; mem.save
+          end
+          if mem.position < dst then # メンバーを後に持っていく場合
+            tgts = WtMemberOrder.
+                where(["prj_id=:p and position<=:p1 and position>:p2",{:p=>@project.id, :p1=>dst, :p2=>mem.position}]).
+                all
+            tgts.each do |mv|
+              mv.position-=1; mv.save # 順位を一つずつ前へ
+            end
+            mem.position=dst; mem.save
+          end
+        end
+      else
+        @message ||= ''
+        @message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
+        return
+      end
+    end
+  end
+
+  def change_ticket_position
+    # 重複削除と順序の正規化
+    if order_normalization(WtTicketRelay, :issue_id, :order=>"position") then
+      @message ||= ''
+      #@message += '<div style="background:#faa;">Warning: normalize WtTicketRelay</div>'
+      return
+    end
+
+    ################################### チケット表示順序変更処理
+    if params.key?("ticket_pos") && params[:ticket_pos]=~/^(.*)_(.*)$/ then
+      if User.current.allowed_to?(:edit_work_time_total, @project) then
+        issue_id = $1.to_i
+        dst = $2.to_i
+        relay = WtTicketRelay.where(["issue_id=:i",{:i=>issue_id}]).first
+        if relay then
+          if relay.position > dst then # 前に持っていく場合
+            tgts = WtTicketRelay.
+                where(["position>=:p1 and position<:p2",{:p1=>dst, :p2=>relay.position}]).
+                all
+            tgts.each do |mv|
+              mv.position+=1; mv.save # 順位を一つずつ後へ
+            end
+            relay.position=dst; relay.save
+          end
+          if relay.position < dst then # 後に持っていく場合
+            tgts = WtTicketRelay.
+                where(["position<=:p1 and position>:p2",{:p1=>dst, :p2=>relay.position}]).
+                all
+            tgts.each do |mv|
+              mv.position-=1; mv.save # 順位を一つずつ前へ
+            end
+            relay.position=dst; relay.save
+          end
+        end
+      else
+        @message ||= ''
+        @message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
+        return
+      end
+    end
+  end
+
+
+  def change_project_position
+    # 重複削除と順序の正規化
+    if order_normalization(WtProjectOrders, :dsp_prj, :order=>"dsp_pos", :conditions=>"uid=-1") then
+      @message ||= ''
+      #@message += '<div style="background:#faa;">Warning: normalize WtProjectOrders</div>'
+      return
+    end
+
+    ################################### プロジェクト表示順序変更処理
+    return if !params.key?("prj_pos") # 位置変更パラメータが無ければパス
+    return if !(params[:prj_pos]=~/^(.*)_(.*)$/) # パラメータの形式が正しくなければパス
+    dsp_prj = $1.to_i
+    dst = $2.to_i
+
+    if !User.current.allowed_to?(:edit_work_time_total, @project) then
+       # 権限が無ければパス
+      @message ||= ''
+      @message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
+      return
+    end
+
+    po = WtProjectOrders.where(["uid=-1 and dsp_prj=:d",{:d=>dsp_prj}]).first
+    return if po == nil # 対象の表示プロジェクトが無ければパス
+
+    if po.dsp_pos > dst then # 前に持っていく場合
+      tgts = WtProjectOrders.where(["uid=-1 and dsp_pos>=:o1 and dsp_pos<:o2",{:o1=>dst, :o2=>po.dsp_pos}]).all
+      tgts.each do |mv|
+        mv.dsp_pos+=1; mv.save # 順位を一つずつ後へ
+      end
+      po.dsp_pos=dst; po.save
+    end
+
+    if po.dsp_pos < dst then # 後に持っていく場合
+      tgts = WtProjectOrders.where(["uid=-1 and dsp_pos<=:o1 and dsp_pos>:o2",{:o1=>dst, :o2=>po.dsp_pos}]).all
+      tgts.each do |mv|
+        mv.dsp_pos-=1; mv.save # 順位を一つずつ前へ
+      end
+      po.dsp_pos=dst; po.save
+    end
+  end
+
+  def calc_total
+    ################################################  合計集計計算ループ ########
+    @total_cost = 0
+    @member_cost = Hash.new
+    WtMemberOrder.where(["prj_id=:p",{:p=>@project.id}]).all.each do |i|
+      @member_cost[i.user_id] = 0
+    end
+    @issue_parent = Hash.new # clear cash
+
+    @issue_cost = Hash.new
+    @r_issue_cost = Hash.new
+
+    @prj_cost = Hash.new
+    @r_prj_cost = Hash.new
+
+    @issue_act_cost = Hash.new
+    @r_issue_act_cost = Hash.new
+
+    relay = Hash.new
+    WtTicketRelay.all.each do |i|
+      relay[i.issue_id] = i.parent
+    end
+
+    #当月の時間記録を抽出
+    TimeEntry.
+        where(["spent_on>=:t1 and spent_on<=:t2 and hours>0",{:t1 => @first_date, :t2 => @last_date}]).
+        all.
+        each do |time_entry|
+      iid = time_entry.issue_id
+      uid = time_entry.user_id
+      cost = time_entry.hours
+      act = time_entry.activity_id
+      # 本プロジェクトのユーザの工数でなければパス
+      next unless @member_cost.key?(uid)
+
+      issue = Issue.find_by_id(iid)
+      next if issue.nil? # チケットが削除されていたらパス
+      pid = issue.project_id
+      # プロジェクト限定の対象でなければパス
+      next if @restrict_project && pid != @restrict_project
+
+      @total_cost += cost
+      @member_cost[uid] += cost
+
+      parent_iid = get_parent_issue(relay, iid)
+      if !Issue.find_by_id(iid) || !Issue.find_by_id(iid).visible?
+        # 表示権限の無い工数があった場合
+        iid = -1 # private
+        pid = -1 # private
+        act = -1 # private
+      end
+      @issue_cost[iid] ||= Hash.new
+      @issue_cost[iid][-1] ||= 0
+      @issue_cost[iid][-1] += cost
+      @issue_cost[iid][uid] ||= 0
+      @issue_cost[iid][uid] += cost
+
+      @prj_cost[pid] ||= Hash.new
+      @prj_cost[pid][-1] ||= 0
+      @prj_cost[pid][-1] += cost
+      @prj_cost[pid][uid] ||= 0
+      @prj_cost[pid][uid] += cost
+
+      @issue_act_cost[iid] ||= Hash.new
+      @issue_act_cost[iid][uid] ||= Hash.new
+      @issue_act_cost[iid][uid][act] ||= 0
+      @issue_act_cost[iid][uid][act] += cost
+
+      parent_issue = Issue.find_by_id(parent_iid)
+      if parent_issue && parent_issue.visible?
+        parent_pid = parent_issue.project_id
+      else
+        parent_iid = -1
+        parent_pid = -1
+      end
+
+      @r_issue_cost[parent_iid] ||= Hash.new
+      @r_issue_cost[parent_iid][-1] ||= 0
+      @r_issue_cost[parent_iid][-1] += cost
+      @r_issue_cost[parent_iid][uid] ||= 0
+      @r_issue_cost[parent_iid][uid] += cost
+
+      @r_prj_cost[parent_pid] ||= Hash.new
+      @r_prj_cost[parent_pid][-1] ||= 0
+      @r_prj_cost[parent_pid][-1] += cost
+      @r_prj_cost[parent_pid][uid] ||= 0
+      @r_prj_cost[parent_pid][uid] += cost
+
+      @r_issue_act_cost[parent_iid] ||= Hash.new
+      @r_issue_act_cost[parent_iid][uid] ||= Hash.new
+      @r_issue_act_cost[parent_iid][uid][act] ||= 0
+      @r_issue_act_cost[parent_iid][uid][act] += cost
+    end
+  end
+
+  def get_parent_issue(relay, iid)
+    @issue_parent ||= Hash.new
+    return @issue_parent[iid] if @issue_parent.has_key?(iid)
+    issue = Issue.find_by_id(iid)
+    return 0 if issue.nil? # issueが削除されていたらそこまで
+    @issue_cost[iid] ||= Hash.new
+
+    if relay.has_key?(iid)
+      parent_id = relay[iid]
+      if parent_id != 0 && parent_id != iid
+        parent_id = get_parent_issue(relay, parent_id)
+      end
+      parent_id = iid if parent_id == 0
+    else
+      # 関連が登録されていない場合は登録する
+      WtTicketRelay.create(:issue_id=>iid, :position=>relay.size, :parent=>0)
+      parent_id = iid
+    end
+
+    # iid に対する初めての処理
+    pid = issue.project_id
+    unless @prj_cost.has_key?(pid)
+      check = WtProjectOrders.where(["uid=-1 and dsp_prj=:p",{:p=>pid}]).all
+      if check.size == 0
+        WtProjectOrders.create(:uid=>-1, :dsp_prj=>pid, :dsp_pos=>@prj_cost.size)
+      end
+    end
+
+    @issue_parent[iid] = parent_id # return
+  end
+
+  def make_pack
+    # 月間工数表のデータを作成
+    @month_pack = {:ref_prjs=>{}, :odr_prjs=>[],
+                   :total=>0, :total_by_day=>{},
+                   :other=>0, :other_by_day=>{},
+                   :count_prjs=>0, :count_issues=>0}
+    @month_pack[:total_by_day].default = 0
+
+    # 日毎工数のデータを作成
+    @day_pack = {:ref_prjs=>{}, :odr_prjs=>[],
+                 :total=>0, :total_by_day=>{},
+                 :other=>0, :other_by_day=>{},
+                 :count_prjs=>0, :count_issues=>0}
+    @day_pack[:total_by_day].default = 0
+
+    # プロジェクト順の表示データを作成
+    dsp_prjs = Project.joins("INNER JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id").
+        where(["wt_project_orders.uid=:u",{:u=>@this_uid}]).
+        select("projects.*, wt_project_orders.dsp_pos as dsp_pos").
+        order("wt_project_orders.dsp_pos").
+        all
+    dsp_prjs.each do |prj|
+      next if @restrict_project && @restrict_project!=prj.id
+      make_pack_prj(@month_pack, prj, prj.dsp_pos)
+      make_pack_prj(@day_pack, prj, prj.dsp_pos)
+    end
+    @prj_odr_max = dsp_prjs.length
+
+    # チケット順の表示データを作成
+    dsp_issues = Issue.joins("INNER JOIN user_issue_months ON user_issue_months.issue=issues.id").
+        where(["user_issue_months.uid=:u",{:u=>@this_uid}]).
+        order("user_issue_months.odr").
+        select("issues.*, user_issue_months.odr").
+        all
+    dsp_issues.each do |issue|
+      next if @restrict_project && @restrict_project!=issue.project.id
+      month_prj_pack = make_pack_prj(@month_pack, issue.project)
+      make_pack_issue(month_prj_pack, issue, issue.odr)
+      day_prj_pack = make_pack_prj(@day_pack, issue.project)
+      make_pack_issue(day_prj_pack, issue, issue.odr)
+    end
+    @issue_odr_max = dsp_issues.length
+
+    # 月内の工数を集計
+    hours = TimeEntry.
+        includes(:issue).
+        where(["user_id=:uid and spent_on>=:day1 and spent_on<=:day2",
+               {:uid => @this_uid, :day1 => @first_date, :day2 => @last_date}]).
+        all
+    hours.each do |hour|
+      next if @restrict_project && @restrict_project!=hour.project.id
+      work_time = hour.hours
+      if hour.issue && hour.issue.visible? then
+        # 表示項目に工数のプロジェクトがあるかチェック→なければ項目追加
+        prj_pack = make_pack_prj(@month_pack, hour.project)
+
+        # 表示項目に工数のチケットがあるかチェック→なければ項目追加
+        issue_pack = make_pack_issue(prj_pack, hour.issue)
+
+        issue_pack[:count_hours] += 1
+
+        # 合計時間の計算
+        @month_pack[:total] += work_time
+        prj_pack[:total] += work_time
+        issue_pack[:total] += work_time
+
+        # 日毎の合計時間の計算
+        date = hour.spent_on
+        @month_pack[:total_by_day][date] += work_time
+        prj_pack[:total_by_day][date] += work_time
+        issue_pack[:total_by_day][date] += work_time
+
+        if date==@this_date then # 表示日の工数であれば項目追加
+          # 表示項目に工数のプロジェクトがあるかチェック→なければ項目追加
+          day_prj_pack = make_pack_prj(@day_pack, hour.project)
+
+          # 表示項目に工数のチケットがあるかチェック→なければ項目追加
+          day_issue_pack = make_pack_issue(day_prj_pack, hour.issue, NO_ORDER)
+
+          day_issue_pack[:each_entries][hour.id] = hour # 工数エントリを追加
+          day_issue_pack[:total] += work_time
+          day_prj_pack[:total] += work_time
+          @day_pack[:total] += work_time
+        end
+      else
+        # 合計時間の計算
+        @month_pack[:total] += work_time
+        @month_pack[:other] += work_time
+
+        # 日毎の合計時間の計算
+        date = hour.spent_on
+        @month_pack[:total_by_day][date] ||= 0
+        @month_pack[:total_by_day][date] += work_time
+        @month_pack[:other_by_day][date] ||= 0
+        @month_pack[:other_by_day][date] += work_time
+
+        if date==@this_date then # 表示日の工数であれば項目追加
+          @day_pack[:total] += work_time
+          @day_pack[:other] += work_time
+        end
+      end
+    end
+
+    # この日のチケット作成を洗い出す
+    next_date = @this_date+1
+    t1 = Time.local(@this_date.year, @this_date.month, @this_date.day)
+    t2 = Time.local(next_date.year, next_date.month, next_date.day)
+    isu = Issue.arel_table
+    jnl = Journal.arel_table
+    union_sql = Issue.where((isu[:author_id].eq(@this_uid))
+      .and(  isu[:created_on].gteq(t1))
+      .and(  isu[:created_on].lt(t2)))
+    .union(
+      Issue.joins(isu.join(jnl).on(isu[:id].eq(jnl[:journalized_id])).join_sources.first)
+      .where(jnl[:journalized_type].eq('Issue')
+        .and(  jnl[:user_id].eq(@this_uid))
+        .and(  jnl[:created_on].gteq(t1))
+        .and(  jnl[:created_on].lt(t2))
+      ).uniq
+    )
+    issues = Issue.from("#{union_sql.to_sql} issues" )
+
+    issues.each do |issue|
+      next if @restrict_project && @restrict_project!=issue.project.id
+      next if !@this_user.allowed_to?(:log_time, issue.project)
+      next if !issue.visible?
+      prj_pack = make_pack_prj(@day_pack, issue.project)
+      issue_pack = make_pack_issue(prj_pack, issue)
+      if issue_pack[:css_classes] == 'wt_iss_overdue'
+        issue_pack[:css_classes] = 'wt_iss_overdue_worked'
+      else
+        issue_pack[:css_classes] = 'wt_iss_worked'
+      end
+    end
+    issues = Issue.
+        joins("INNER JOIN issue_statuses ist on ist.id = issues.status_id ").
+        joins("LEFT JOIN groups_users on issues.assigned_to_id = group_id").
+        where(["1 = 1 and
+                (  (issues.assigned_to_id = :u or groups_users.user_id = :u) and
+                   issues.start_date < :t2 and
+                   ist.is_closed = :closed
+                )", {:u => @this_uid, :t2 => t2, :closed => false}]).
+        all
+    issues.each do |issue|
+      next if @restrict_project && @restrict_project!=issue.project.id
+      next if !@this_user.allowed_to?(:log_time, issue.project)
+      next if !issue.visible?
+      prj_pack = make_pack_prj(@day_pack, issue.project)
+      issue_pack = make_pack_issue(prj_pack, issue)
+      if issue_pack[:css_classes] == 'wt_iss_default'
+        issue_pack[:css_classes] = 'wt_iss_assigned'
+      elsif issue_pack[:css_classes] == 'wt_iss_worked'
+        issue_pack[:css_classes] = 'wt_iss_assigned_worked'
+      elsif issue_pack[:css_classes] == 'wt_iss_overdue'
+        issue_pack[:css_classes] = 'wt_iss_assigned_overdue'
+      elsif issue_pack[:css_classes] == 'wt_iss_overdue_worked'
+        issue_pack[:css_classes] = 'wt_iss_assigned_overdue_worked'
+      end
+    end
+
+    # 月間工数表から工数が無かった項目の削除と項目数のカウント
+    @month_pack[:count_issues] = 0
+    @month_pack[:odr_prjs].each do |prj_pack|
+      prj_pack[:odr_issues].each do |issue_pack|
+        if issue_pack[:count_hours]==0 then
+          prj_pack[:count_issues] -= 1
+        end
+      end
+
+      if prj_pack[:count_issues]==0 then
+        @month_pack[:count_prjs] -= 1
+      else
+        @month_pack[:count_issues] += prj_pack[:count_issues]
+      end
+    end
+  end
+
+  def make_pack_prj(pack, new_prj, odr=NO_ORDER)
+      # 表示項目に当該プロジェクトがあるかチェック→なければ項目追加
+      unless pack[:ref_prjs].has_key?(new_prj.id) then
+        prj_pack = {:odr=>odr, :prj=>new_prj,
+                    :total=>0, :total_by_day=>{},
+                    :ref_issues=>{}, :odr_issues=>[], :count_issues=>0}
+        pack[:ref_prjs][new_prj.id] = prj_pack
+        pack[:odr_prjs].push prj_pack
+        pack[:count_prjs] += 1
+        prj_pack[:total_by_day].default = 0
+      end
+      pack[:ref_prjs][new_prj.id]
+  end
+
+  def make_pack_issue(prj_pack, new_issue, odr=NO_ORDER)
+      id = new_issue.nil? ? -1 : new_issue.id
+      # 表示項目に当該チケットがあるかチェック→なければ項目追加
+      unless prj_pack[:ref_issues].has_key?(id) then
+        issue_pack = {:odr=>odr, :issue=>new_issue,
+                      :total=>0, :total_by_day=>{},
+                      :count_hours=>0, :each_entries=>{},
+                      :cnt_childrens=>0}
+        issue_pack[:total_by_day].default = 0
+        if !new_issue.due_date.nil? && new_issue.due_date < @this_date.to_datetime
+          issue_pack[:css_classes] = 'wt_iss_overdue'
+        else
+          issue_pack[:css_classes] = 'wt_iss_default'
+        end
+        prj_pack[:ref_issues][id] = issue_pack
+        prj_pack[:odr_issues].push issue_pack
+        prj_pack[:count_issues] += 1
+        cnt_childrens = Issue.where(["parent_id = ?", new_issue.id.to_s]).count
+        issue_pack[:cnt_childrens] = cnt_childrens
+      end
+      prj_pack[:ref_issues][id]
+  end
+
+  def sum_or_nil(v1, v2)
+    if v2.blank?
+      v1
+    else
+      if v1.blank?
+        v2
+      else
+        v1 + v2
+      end
+    end
+  end
+
+  # 重複削除と順序の正規化
+  def order_normalization(table, key_column, find_params)
+    raise "need table" unless table
+    order = find_params[:order]
+    raise "need :order" unless order
+    update = false
+
+    tgts = table.
+        where(find_params[:conditions]).
+        order(order).
+        all
+    keys = []
+    tgts.each do |tgt|
+      if keys.include?(tgt[key_column]) then
+        tgt.destroy
+        update = true
+      else
+        keys.push(tgt[key_column])
+        if tgt[order] != keys.length then
+          tgt[order] = keys.length
+          tgt.save
+          update = true
+        end
+      end
+    end
+    update
+  end
+
+end
diff --git a/plugins/redmine_work_time/app/helpers/work_time_helper.rb b/plugins/redmine_work_time/app/helpers/work_time_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2813e65a4d2ef24b21f4d08424b565a753c3c5e
--- /dev/null
+++ b/plugins/redmine_work_time/app/helpers/work_time_helper.rb
@@ -0,0 +1,27 @@
+module WorkTimeHelper
+  def print_issue_cost(issue)
+    return "" unless issue
+    issue_cost_est = issue.estimated_hours
+    return "" unless issue_cost_est
+    issue_cost = TimeEntry.where(:issue_id => issue.id).sum(:hours).to_f
+    return sprintf("(%1.1f/%1.1f)",issue_cost,issue_cost_est)
+  end
+
+  def print_issue_cost_rate(issue)
+    return "" unless issue
+    issue_cost_est = issue.estimated_hours
+    return "" unless issue_cost_est
+    issue_cost = TimeEntry.where(:issue_id => issue.id).sum(:hours).to_f
+    return sprintf("%1.0f",issue_cost/issue_cost_est*100)
+  end
+
+  def wk_pretty_issue_name(issue, issue_id = issue.id)
+    if issue.nil? || !issue.visible?
+      content_tag :del, issue_id
+    elsif issue.closed?
+      content_tag :del, issue.to_s
+    else
+      issue.to_s
+    end
+  end
+end
diff --git a/plugins/redmine_work_time/app/models/user_issue_month.rb b/plugins/redmine_work_time/app/models/user_issue_month.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b9788f7b728ace03e8436d14baf7cd93ca2675f1
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/user_issue_month.rb
@@ -0,0 +1,3 @@
+class UserIssueMonth < ActiveRecord::Base
+  attr_accessible :uid, :issue, :odr
+end
diff --git a/plugins/redmine_work_time/app/models/wt_daily_memo.rb b/plugins/redmine_work_time/app/models/wt_daily_memo.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f44b38e774de8032861978fd7089a1c5832a0e3a
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/wt_daily_memo.rb
@@ -0,0 +1,3 @@
+class WtDailyMemo < ActiveRecord::Base
+  attr_accessible :user_id, :day, :created_on, :updated_on, :description
+end
diff --git a/plugins/redmine_work_time/app/models/wt_holidays.rb b/plugins/redmine_work_time/app/models/wt_holidays.rb
new file mode 100644
index 0000000000000000000000000000000000000000..99ac609087ad3ba594162c79fd7af3c07c9f071f
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/wt_holidays.rb
@@ -0,0 +1,3 @@
+class WtHolidays < ActiveRecord::Base
+  attr_accessible :holiday, :created_on, :created_by
+end
diff --git a/plugins/redmine_work_time/app/models/wt_member_order.rb b/plugins/redmine_work_time/app/models/wt_member_order.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7de57bb23f1f712cd0345f0b3d0f2a0bdc37c785
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/wt_member_order.rb
@@ -0,0 +1,3 @@
+class WtMemberOrder < ActiveRecord::Base
+  attr_accessible :user_id, :position, :prj_id
+end
diff --git a/plugins/redmine_work_time/app/models/wt_project_orders.rb b/plugins/redmine_work_time/app/models/wt_project_orders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84312c01f8ba182e2adac76b528cfa4852a792b5
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/wt_project_orders.rb
@@ -0,0 +1,3 @@
+class WtProjectOrders < ActiveRecord::Base
+  attr_accessible :uid, :dsp_prj, :dsp_pos
+end
diff --git a/plugins/redmine_work_time/app/models/wt_ticket_relay.rb b/plugins/redmine_work_time/app/models/wt_ticket_relay.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e150b445c78cbe2aebdd7e88ee90a1d62f13d1ad
--- /dev/null
+++ b/plugins/redmine_work_time/app/models/wt_ticket_relay.rb
@@ -0,0 +1,3 @@
+class WtTicketRelay < ActiveRecord::Base
+  attr_accessible :issue_id, :position, :parent
+end
diff --git a/plugins/redmine_work_time/app/views/settings/_work_time_project_settings.html.erb b/plugins/redmine_work_time/app/views/settings/_work_time_project_settings.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..68abdec4cded94452bbee5efe6415b800c6f26af
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/settings/_work_time_project_settings.html.erb
@@ -0,0 +1,19 @@
+<%
+  settings = Setting.plugin_redmine_work_time
+  if settings.is_a?(Hash) &&
+      settings['account_start_days'].is_a?(Hash) &&
+      settings['account_start_days'].has_key?(@project.id.to_s)
+    day = settings['account_start_days'][@project.id.to_s]
+  else
+    day = 1
+  end
+%>
+<%= form_tag(:controller=>"work_time", :id=>@project, :action => "register_project_settings") do %>
+    <p>
+      <%=content_tag(:label, l(:wt_account_start_day))%>
+      <%=number_field_tag 'account_start_day', day , in: 1..31 %>
+    </p>
+    <%= submit_tag l(:wt_update) %>
+<%end%>
+
+
diff --git a/plugins/redmine_work_time/app/views/settings/_work_time_settings.html.erb b/plugins/redmine_work_time/app/views/settings/_work_time_settings.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e3b13477b641d7e441684bb95bc812d0cd0519ab
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/settings/_work_time_settings.html.erb
@@ -0,0 +1,11 @@
+<%
+  @settings = Hash.new unless @settings.is_a?(Hash)
+%>
+<p>
+  <%= label_tag :settings_show_account_menu, 'show account menu' %>
+  <%= check_box_tag 'settings[show_account_menu]', true, @settings['show_account_menu'] %>
+</p>
+
+<% (@settings['account_start_days'] || {}).each do |k,v| %>
+    <%= hidden_field_tag "settings[account_start_days][#{k}]", v %>
+<% end %>
diff --git a/plugins/redmine_work_time/app/views/work_time/_select_project.html.erb b/plugins/redmine_work_time/app/views/work_time/_select_project.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..16a749879ab16dee848659828642f56f3e153bcf
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/_select_project.html.erb
@@ -0,0 +1,21 @@
+<%#*************************************************** プロジェクトセレクタ %>
+<%= @restrict_project ? Project.find(@restrict_project).name : ""%>
+
+<select onchange="if (this.value != '') {window.location = this.value;}">
+  <option selected="selectes"><%=l(:wt_select_project)%></option>
+  <option value="<%= url_for(@link_params.merge(:prj=>false));%>">---</option>
+<%
+prjs = Project.
+        joins("LEFT JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=#{User.current.id}").
+        select("projects.*, coalesce(wt_project_orders.dsp_pos,100000) as pos").
+        order("pos,name").
+        all
+
+Project.project_tree(prjs) do |prj, level|
+  next unless prj.active?
+  next if !prj.visible?
+  name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ': '').html_safe
+%>
+  <option value="<%= url_for(@link_params.merge(:prj=>prj.id));%>"> <%=name_prefix + prj.name%> </option>
+<%end%>
+</select>
diff --git a/plugins/redmine_work_time/app/views/work_time/_select_user.html.erb b/plugins/redmine_work_time/app/views/work_time/_select_user.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5e72a9bbc95fb6b3eaf387e003bfbdbd0377742c
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/_select_user.html.erb
@@ -0,0 +1,20 @@
+<%#**************************************************** ユーザセレクタ %>
+<%if @project then%>
+<%=l(:wt_select_user)%>
+<select onchange="if (this.value != '') {window.location = this.value;}">
+  <option value="">---</option>
+<%
+members_name_id = Array.new
+@members.each {|mem| members_name_id << [mem[1].to_s, mem[1].id]}
+members_name_id.sort!
+members_name_id.each do |member_name_id|
+  sel = (member_name_id[1] == @this_uid) ? 'selected' : ''
+%>
+  <option value="<%= url_for(link_params.merge(:user=>member_name_id[1]));%>" <%=sel%>>
+    <%=member_name_id[0]%>
+  </option>
+<%
+end
+%>
+</select>
+<%end%>
diff --git a/plugins/redmine_work_time/app/views/work_time/_user_day_table.html.erb b/plugins/redmine_work_time/app/views/work_time/_user_day_table.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b3b86c59e121a3033f3be48b2d4dd560933968c6
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/_user_day_table.html.erb
@@ -0,0 +1,282 @@
+<table border="0" id="time_input_table">
+  <tr style="background:#fff;font-size:14px">
+    <td>&nbsp;</td>
+    <td><%=l(:work_time)%></td>
+    <% if @is_registerd_backlog then %>
+    <td><%=l(:field_remaining_hours)%></td>
+    <% end %>
+    <td><%=l(:field_activity)%></td>
+    <td><%=l(:field_status)%></td>
+    <td><%=l(:field_comments)%></td>
+    <% @custom_fields.each do |cf| %>
+       <td><%= cf.name %></td>
+    <% end %>
+  </tr>
+<%
+@day_pack[:odr_prjs].each do |prj_pack|
+  next if prj_pack[:ref_issues].length==0
+  prj = prj_pack[:prj]
+  next if !prj.visible?
+  dsp_prj = prj.id
+  dsp_pos = prj_pack[:odr]
+
+  activities = []
+  activity_default = nil
+  prj.activities.each do |act|
+    activities.push([act.name, act.id])
+    activity_default = act.id if act.is_default
+  end
+%>
+<tr style="background:#000;color:#fff;">
+  <td>
+      <%if @this_uid==@crnt_uid then%>
+
+        <a href="#"
+          style="color:#8ff;"
+          onclick="prj_pos('<%=url_for(@link_params) %>',
+                           <%=dsp_prj%>,
+                           <%=dsp_pos%>,
+                           <%=@prj_odr_max+((dsp_pos.to_i<0)?1:0)%>);
+                   return false;">
+          <%= (dsp_pos.to_i<0) ? "*" : dsp_pos %>:
+        </a>
+     <%end%>
+        <%=prj.name%>
+  </td>
+  <td><%=sprintf("%1.2f", prj_pack[:total])%></td>
+  <% if @is_registerd_backlog then %>
+  <td>&nbsp;</td>
+  <% end %>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <% @custom_fields.each do |cf| %>
+     <td>&nbsp;</td>
+  <% end %>
+</tr>
+<%
+prj_pack[:odr_issues].each do |issue_pack|
+  issue = issue_pack[:issue]
+  issue_id = issue.nil? ? -1 : issue.id
+  issue_odr = issue_pack[:odr].to_i
+#  issue_class = issue_pack[:worked] ? "#cfc" : "#fff"
+  issue_css_classes = issue_pack[:css_classes]
+  issue = Issue.find_by_id(issue_id)
+  issue_html = wk_pretty_issue_name issue, issue_id
+
+  if issue_pack[:each_entries].length==0 then
+%>
+<!-- 工数エントリなし -->
+<tr id="time_entry_pos<%=issue_id%>_0">
+  <td class="<%=issue_css_classes%>">
+  <div style="position:relative;width:300px;">
+    <%if @this_uid==@crnt_uid then%>
+    <a href="#"
+      onclick="ticket_pos('<%=url_for(@link_params) %>',
+                          <%=issue_id%>,
+                          <%=issue_odr%>,
+                          <%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
+               return false;">
+      <%= (issue_odr.to_i<0) ? "*" : issue_odr %>
+    </a>
+    <%end%>
+
+    <%= link_to(issue_html,
+                {:controller=>"issues", :action=>"show", :id=>issue_id},
+                :popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
+    <%= print_issue_cost(issue) %>
+    <%if User.current.allowed_to?(:edit_issues, issue.project)%>
+        <a name="<%='done_ratio'+issue.id.to_s%>"
+           class="wt_done_ratio"
+           data-issue="<%= issue_id %>"
+           href="#"
+           onclick="input_done_ratio(
+                   '<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
+                   <%=issue_id%>);
+                   return false;">
+          [<%=issue.done_ratio%>&#37;]
+        </a>
+    <%else%>
+        [<%=issue.done_ratio%>&#37;]
+    <%end%>
+    <%if @this_uid==@crnt_uid then%>
+    &nbsp;&nbsp;&nbsp;
+    <a style="position:absolute;bottom:1px;right:10px;"
+       href="#"
+       onclick="dup_ticket(
+                    '<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
+                    'time_entry_pos<%=issue_id%>_0',
+                    <%=issue_id%>);
+                return false;">
+      +
+    </a>
+      <%if issue_odr>0 then%>
+      <a style="position:absolute;bottom:1px;right:3px;" href="<%=url_for(@link_params.merge(:ticket_del=>issue_id))%>">x</a>
+      <%end%>
+    <%end%>
+  </div>
+  </td>
+  <td>
+    <%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][hours]", "", :size=>5, :oninput => "sumDayTimes()") %>
+  </td>
+  <% if @is_registerd_backlog then %>
+  <td>
+    <% if issue_pack[:cnt_childrens] != 0 || issue.closed? then %>
+      <%= issue[:remaining_hours] %>
+    <% else %>
+      <%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][remaining_hours]", issue[:remaining_hours], :size=>5, :oninput => "sumDayTimes()") %>
+    <% end %>
+  </td>
+  <% end %>
+  <td>
+    <%= select_tag "new_time_entry["+issue_id.to_s+"][0][activity_id]", options_for_select(activities,activity_default) %>
+  </td>
+  <td>
+    <% if issue.new_statuses_allowed_to(User.current).length == 0 then %>
+      <%= issue.status %>
+    <% else %>
+      <% select_name="new_time_entry["+issue_id.to_s+"][0][status_id]" %>
+      <%= select_tag select_name,
+                     options_for_select(issue.new_statuses_allowed_to(User.current).collect {|p| [p.name, p.id]},
+                                        issue.status_id),
+                     :onchange => "statusUpdateOnDailyTable('#{select_name}')",
+                     :required => true %>
+    <% end %>
+  </td>
+  <td>
+    <%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][comments]", "", :size=>80)%>
+  </td>
+  <%if @custom_fields.length != 0
+      dummy_hour = TimeEntry.new(:project => issue.project, :issue => issue, :user => @this_user, :spent_on => @this_date)
+      dummy_hour.custom_field_values.each do |cfv|
+  %>
+     <td><%= custom_field_tag "new_time_entry_"+issue_id.to_s+"_0", cfv %></td>
+      <%end
+     end%>
+</tr>
+<%
+  else
+    issue_pack[:each_entries].each do |hour_id, hour|
+%>
+<!-- 工数エントリあり -->
+<tr id="time_entry_pos<%=hour_id%>">
+  <td class="<%=issue_css_classes%>">
+  <div style="position:relative;width:300px;">
+  <%if !issue.nil? then%>
+    <%if @this_uid==@crnt_uid then%>
+    <a href="#"
+      onclick="ticket_pos('<%=url_for(@link_params) %>',
+                          <%=issue_id%>,
+                          <%=issue_odr%>,
+                          <%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
+               return false;">
+      <%= (issue_odr.to_i<0) ? "*" : issue_odr %>
+    </a>
+    <%end%>
+    <%= link_to(issue_html,
+                {:controller=>"issues", :action=>"show", :id=>issue_id},
+                :popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
+
+    <%= print_issue_cost(issue) %>
+    <%if User.current.allowed_to?(:edit_issues, issue.project)%>
+            <a name="<%='done_ratio'+issue_id.to_s%>"
+               class="wt_done_ratio"
+               data-issue="<%= issue_id %>"
+               href="#"
+               onclick="input_done_ratio(
+                       '<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
+                       <%=issue_id%>);
+                       return false;">
+              [<%=issue.done_ratio%>&#37;]
+            </a>
+    <%else%>
+            [<%=issue.done_ratio%>&#37;]
+    <%end%>
+    <%if @this_uid==@crnt_uid then%>
+    &nbsp;&nbsp;&nbsp;
+
+    <a style="position:absolute;bottom:1px;right:10px;"
+       href="#"
+       onclick="dup_ticket(
+                    '<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
+                    'time_entry_pos<%=hour_id%>',
+                    <%=issue_id%>);
+                return false;">
+      +
+    </a>
+      <%if issue_odr>0 then%>
+        <a style="position:absolute;bottom:1px;right:3px;" href="<%=url_for(@link_params.merge(:ticket_del=>issue_id))%>">x</a>
+      <%end%>
+    <%end%>
+  <%end%>
+  </div>
+  </td>
+  <td>
+    <%= text_field_tag("time_entry["+hour_id.to_s+"][hours]", sprintf("%1.2f", hour.hours), :size=>5, :oninput => "sumDayTimes()") %>
+  </td>
+  <% if @is_registerd_backlog then %>
+  <td>
+    <% if issue_pack[:cnt_childrens] != 0 || hour.issue.closed? then %>
+      <%= hour.issue[:remaining_hours] %>
+    <% else %>
+      <%= text_field_tag("time_entry["+hour_id.to_s+"][remaining_hours]", hour.issue[:remaining_hours], :size=>5, :oninput => "sumDayTimes()") %>
+    <% end %>
+  </td>
+  <% end %>
+  <td>
+    <%= select_tag "time_entry["+hour_id.to_s+"][activity_id]", options_for_select(activities, hour.activity_id), :required => true %>
+  </td>
+  <td>
+    <% if issue.new_statuses_allowed_to(User.current).length == 0 then %>
+      <%= issue.status %>
+    <% else %>
+      <% select_name="time_entry["+hour_id.to_s+"][status_id]" %>
+      <%= select_tag select_name,
+                     options_for_select(issue.new_statuses_allowed_to(User.current).collect {|p| [p.name, p.id]},
+                                        issue.status_id),
+                     :onchange => "statusUpdateOnDailyTable('#{select_name}')",
+                     :required => true %>
+    <% end %>
+  </td>
+  <td>
+    <%= text_field_tag("time_entry["+hour_id.to_s+"][comments]", hour.comments, :size=>80)%>
+  </td>
+  <% hour.custom_field_values.each do |cfv| %>
+     <td><%= custom_field_tag "time_entry_"+hour_id.to_s, cfv %></td>
+  <% end %>
+</tr>
+<%
+   end
+  end
+ end
+end
+%>
+<% if @day_pack[:other]!=0 then %>
+<tr style="background:#000;color:#fff;">
+  <td>private</td>
+  <td><%=sprintf("%1.2f", @day_pack[:other])%></td>
+  <% if @is_registerd_backlog then %>
+      <td><%=sprintf("%1.2f", @day_pack[:other_remain]) if @day_pack[:other_remain]%></td>
+  <% end %>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <% @custom_fields.each do |cf| %>
+     <td>&nbsp;</td>
+  <% end %>
+</tr>
+<% end %>
+<tr align="center" id="time_input_table_bottom" style="background:#ddd;">
+  <td>&nbsp;</td>
+  <td><span id='currentTotal' title='<%=l(:wt_saved_value)%> <%=sprintf("%1.2f", @day_pack[:total])%>'><%=sprintf("%1.2f", @day_pack[:total])%></span></td>
+  <% if @is_registerd_backlog then %>
+  <td>&nbsp;</td>
+  <% end %>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
+  <% @custom_fields.each do |cf| %>
+     <td>&nbsp;</td>
+  <% end %>
+</tr>
+</table>
diff --git a/plugins/redmine_work_time/app/views/work_time/_user_month_table.html.erb b/plugins/redmine_work_time/app/views/work_time/_user_month_table.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..974f4df662c8d34f7235e8fa54d2ff7b6be9aa62
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/_user_month_table.html.erb
@@ -0,0 +1,260 @@
+<%
+win_w = 35    # 小窓の幅
+win_h = 31    # 小窓の高さ
+win_hs = 20   # 小さい小窓の高さ
+
+box_w = 300
+box_h = win_hs*3+win_h*@month_pack[:count_issues]+win_hs*@month_pack[:count_prjs]+20
+if @month_pack[:other]!=0 then
+  box_h += win_hs
+end
+%>
+
+<h2><%=l(:wt_monthly_report)%> (<%=@this_user%>)</h2>
+<table width="100%" style="border:0;border-collapse:collapse;">
+<tr>
+  <!-- ************************************************ 左側を表示 -->
+  <td style="width:<%=box_w%>px;padding:0px;">
+    <div style="background: #ccc;">
+    <div style="position:relative;height:<%=box_h%>px;overflow:auto;">
+
+      <div style="position:absolute;top:1px;left:1px;">
+      <div style="background: #fff;font-size:1.5em;">
+      <div style="text-align:center;position:relative;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_hs*2-1%>px;">
+        <%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
+      </div>
+      </div>
+      <div style="text-align:right;position:absolute;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_hs*2-1%>px;">
+        <%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
+      </div>
+      <div style="text-align:left;position:absolute;top:1px;left:1px;">
+        <%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
+      </div>
+      </div>
+<%
+ofst_t = win_hs*3-5
+
+@month_pack[:odr_prjs].each do |prj_pack|
+  next if prj_pack[:count_issues] == 0
+  prj = prj_pack[:prj]
+  dsp_pos = prj_pack[:odr]
+  prj_ofst_t = ofst_t
+
+  ofst_t += win_hs
+  prj_pack[:odr_issues].each do |issue_pack|
+    next if issue_pack[:count_hours] == 0
+    issue = issue_pack[:issue]
+    issue_id = issue.nil? ? -1 : issue.id
+    issue_odr = issue_pack[:odr]
+
+%>
+    <div style="position:absolute;top:<%=ofst_t%>px;left:1px;">
+      <div style="background: #fff;font-size:0.8em;">
+        <div style="position:relative;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_h-1%>px;">
+          <%if !issue.nil? then%>
+            <%if @this_uid==@crnt_uid then%>
+              <a href="#"
+                onclick="JavaScript:
+                  ticket_pos(
+                    '<%=url_for(@link_params) %>',
+                    <%=issue_id%>,
+                    <%=issue_odr%>,
+                    <%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
+                  return false;">
+                <%= (issue_odr.to_i<0) ? "*" : issue_odr %>
+              </a>
+            <%end%>
+            <%= link_to(wk_pretty_issue_name(issue, issue_id), {:controller=>"issues", :action=>"show", :id=>issue_id}, :popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
+            <%= print_issue_cost(issue) %>
+          <%end%>
+          <div style="position:absolute;top:15px;right:15px;">
+            <%=sprintf("%1.2f", issue_pack[:total])%>
+          </div>
+        </div>
+      </div>
+    </div>
+<%
+  ofst_t += win_h
+ end
+%>
+  <div style="position:absolute;top:<%=prj_ofst_t%>px;left:2px;">
+    <div style="background:#000;color:#fff;font-size:1.0em;">
+      <div style="position:relative;top:1px;left:1px;width:<%=box_w-3%>px;height:<%=win_hs-1%>px;">
+<%if @this_uid==@crnt_uid then%>
+        <a href="#"
+          style="color:#8ff;"
+          onclick="JavaScript:
+            prj_pos(
+              '<%=url_for(@link_params) %>',
+              <%=prj.id%>,
+              <%=dsp_pos%>,
+              <%=@prj_odr_max+((dsp_pos.to_i<0)?1:0)%>);
+            return false;">
+          <%= (dsp_pos.to_i<0) ? "*" : dsp_pos %>:
+        </a>
+<%end%>
+        <%= prj.name%>
+          <div style="position:absolute;top:1px;right:10px;">
+            <%=sprintf("%1.2f",prj_pack[:total])%>
+          </div>
+      </div>
+    </div>
+  </div>
+<%
+end
+%>
+    <%if @month_pack[:other]!=0 then%>
+      <div style="position:absolute;top:<%=ofst_t%>px;left:2px;">
+        <div style="background:#000;color:#fff;font-size:1.0em;">
+          <div style="position:relative;top:1px;left:1px;width:<%=box_w-4%>px;height:<%=win_hs-2%>px;">
+            private
+              <div style="position:absolute;top:1px;right:10px;">
+                <%=sprintf("%1.2f", @month_pack[:other])%>
+              </div>
+          </div>
+        </div>
+      </div>
+    <%end%>
+      <div style="position:absolute;bottom:10px;right:10px;">
+        <%=sprintf("%1.2f", @month_pack[:total])%>
+      </div>
+      <div style="position:absolute;top:<%=win_hs*2%>px;right:10px;">
+        <%=sprintf("%1.2f", @month_pack[:total])%>
+      </div>
+    </div>
+
+    </div>
+  </td>
+
+  <!-- ************************************************ 右側を表示 -->
+  <td>
+    <div style="background: #ccc;">
+    <div style="position:relative;height:<%=box_h%>px;overflow:auto;" id="day_scroll">
+
+<% # チケット毎の合計時間を計算&表示
+day_pos = 0
+(@first_date..@last_date).each do |date|
+  day_pos += 1
+  month = date.month
+  day = date.day
+  wday = date.wday
+  ofst_l = win_w*(day_pos-1)
+  holidayEntry = WtHolidays.
+          where(["holiday=:h and deleted_on is null",{:h=>date}]).
+          all
+  if holidayEntry.size == 0 then
+    bcolor = @wday_color[wday]
+  else
+    bcolor = "#fbc"
+  end
+  if day==@this_date.day then
+    bcolor_day = "#cfc"
+    today_id = 'id=day_scroll_today'
+  else
+    bcolor_day = bcolor
+    today_id = ''
+  end
+%>
+    <div style="position:absolute;top:1px;left:<%=ofst_l%>px;" <%=today_id%> >
+      <div style="background:<%=bcolor_day%>;">
+        <div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
+          <%= link_to(day, @link_params.merge(:day=>day, :month=>month)) %>
+        </div>
+      </div>
+    </div>
+    <div style="position:absolute;top:<%=win_hs+1%>px;left:<%=ofst_l%>px;">
+      <div style="background:<%=bcolor%>;">
+        <div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
+          <%= @wday_name[wday]%>
+        </div>
+      </div>
+    </div>
+<%
+ofst_t = win_hs*3-5
+day_cost = 0
+
+@month_pack[:odr_prjs].each do |prj_pack|
+  next if prj_pack[:count_issues] == 0
+  dsp_prj = prj_pack[:prj].id
+  dsp_pos = prj_pack[:odr]
+  prj_ofst_t = ofst_t
+
+  ofst_t += win_hs
+  prj_pack[:odr_issues].each do |issue_pack|
+    next if issue_pack[:count_hours] == 0
+    issue = issue_pack[:issue]
+    issue_id = issue.nil? ? -1 : issue.id
+    issue_odr = issue_pack[:odr]
+%>
+    <div style="position:absolute;top:<%=ofst_t%>px;left:<%=ofst_l%>px;">
+      <div style="background:<%=bcolor%>;font-size:0.8em;">
+        <div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-1%>px;height:<%=win_h-1%>px;">
+<%
+  if issue_pack[:total_by_day].has_key?(date) then
+%>
+          <%=sprintf("%1.2f", issue_pack[:total_by_day][date])%>
+<%
+   end
+%>
+        </div>
+      </div>
+    </div>
+<%
+  ofst_t += win_h
+ end
+%>
+  <div style="position:absolute;top:<%=prj_ofst_t%>px;left:<%=ofst_l%>px;">
+    <div style="background:#000;color:#fff;font-size:0.8em;">
+      <div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
+<%if prj_pack[:total_by_day].has_key?(date) then%>
+        <%=sprintf("%1.2f", prj_pack[:total_by_day][date])%>
+<%end%>
+      </div>
+    </div>
+  </div>
+
+<%
+end
+%>
+<!--日毎の合計時間表示-->
+    <div style="position:absolute;top:<%=win_hs*2%>px;left:<%=ofst_l%>px;">
+      <div style="font-size:0.8em;">
+        <div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-2%>px;height:<%=win_hs-2%>px;">
+<%if @month_pack[:total_by_day].has_key?(date) then%>
+          <%= sprintf("%1.2f", @month_pack[:total_by_day][date])%>
+<%end%>
+        </div>
+      </div>
+    </div>
+<!--otherの時間表示-->
+<%if @month_pack[:other]!=0 then%>
+  <div style="position:absolute;top:<%=ofst_t%>px;left:<%=ofst_l%>px;">
+    <div style="background:#000;color:#fff;font-size:0.8em;">
+      <div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-2%>px;height:<%=win_hs-2%>px;">
+<%if @month_pack[:other_by_day].has_key?(date) then%>
+          <%= sprintf("%1.2f", @month_pack[:other_by_day][date])%>
+<%end%>
+      </div>
+    </div>
+  </div>
+<%end%>
+
+<%end%>
+    </div>
+    </div>
+  </td>
+</tr>
+</table>
+<input type="button"
+       onclick="location.href='<%=url_for(@link_params.merge(:action=>"member_monthly_data"))%>'"
+       value="<%=l(:wt_data_download)%>"
+/>
+<br/>
+
+<script type="text/javascript">
+<!--
+  var scr = document.getElementById("day_scroll");
+  var tgt = document.getElementById("day_scroll_today");
+  scr.scrollLeft = tgt.offsetLeft - scr.offsetLeft;
+-->
+</script>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5c19f89faf1a383a28153c4fc964115bab898765
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input.html.erb
@@ -0,0 +1,51 @@
+<div class="wt_add_ticket_block">
+<h2><%=l(:wt_add_ticket)%></h2>
+<select onchange="if (this.value != '') {
+        jQuery.ajax({
+                url: this.value,
+                data: {asynchronous: true, method: 'get'},
+                success: function (response) {
+                        jQuery('#input_tickets').replaceWith(response);}
+                })
+        return false;}">
+  <option value=''><%=l(:wt_select_project)%></option>
+  <option disabled="disabled">---</option>
+  <%
+     Project.project_tree(@select_projects) do |prj, level|
+       next unless prj.active?
+       next if !User.current.allowed_to?(:log_time, prj)
+       members = Member.
+               where(["user_id=:u and project_id=:p",{:u=>User.current.id, :p=>prj.id}]).
+               all
+       next if members.size==0
+       name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ': '').html_safe
+  %>
+      <option value="<%= url_for(:action=>"ajax_add_tickets_input_select", :prj=>prj.id) %>">
+        <%= name_prefix + prj.name %>
+      </option>
+  <%
+     end
+  %>
+</select>
+<div id="input_tickets" style="display: inline-block;_display: inline;">
+  <%=l(:wt_input_ticket_numbers)%>
+  <form action="">
+    <input type="text"
+           id="input_ids"
+           size=32
+           onKeyPress="if(checkEnter(event)) {
+                   tickets_inputed('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');
+                   return false;
+                   }
+                   return event;"
+    />
+    <input type="button"
+           value="<%=l(:button_apply)%>"
+           onclick="tickets_inputed('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
+    />
+  </form>
+</div>
+</div>
+<script>
+    $('#input_ids').focus();
+</script>
\ No newline at end of file
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input_select.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input_select.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6891d1d92302ce71cd732386017f10994ad7ca20
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_input_select.html.erb
@@ -0,0 +1,71 @@
+<div id="input_tickets">
+<form style="font-size:14px;" action="">
+<%
+  opt = {
+    :action => 'ajax_add_tickets_input_select',
+    :prj => params[:prj],
+    :all => params[:all] || '0',
+    :other => params[:other] || '0'
+  }
+
+  opt[:all] = opt[:all] == '0' ? '1' : '0' if params.key?(:all_toggle)
+  opt[:other] = opt[:other] == '0' ? '1' : '0' if params.key?(:other_toggle)
+
+  url1 = url_for(opt.merge({:all_toggle => ''}))
+  url2 = url_for(opt.merge({:other_toggle => ''}) )
+
+  link_label1 = opt[:all] == '0' ? l(:wt_opt_disp_ticket_with_closed) : l(:wt_opt_disp_ticket_opened_only)
+  link_label2 = opt[:other] == '0' ? l(:wt_opt_disp_ticket_with_other_member) : l(:wt_opt_disp_ticket_mine_only)
+%>
+<a href="#"
+  onclick="jQuery.ajax({
+          url: '<%=url1%>',
+          data: {asynchronous: true, method: 'get'},
+          success: function (response) {
+                  jQuery('#input_tickets').replaceWith(response);}
+          });
+          return false;">
+    <%= link_label1 %>
+</a>
+<a href="#"
+  onclick="jQuery.ajax({
+          url: '<%=url2%>',
+          data: {asynchronous: true, method: 'get'},
+          success: function (response) {
+                  jQuery('#input_tickets').replaceWith(response);}
+          });return false;">
+    <%= link_label2 %>
+</a>
+<br/>
+  <input type="button"
+         onclick="tickets_checked('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
+         value="<%=l(:wt_apply_checked)%>"/>
+<br/>
+<div id="tickets_check">
+<%
+@issues.each do |issue|
+  next if issue.nil? || !issue.visible? || (opt[:other] == '0' && issue.assigned_to_id != User.current.id) || (issue.closed? && opt[:all] == '0')
+%>
+      <input type="checkbox"
+             name="ticket_select_check"
+             value="<%=issue.id%>"
+      />
+      <input type="button"
+             onclick="tickets_selected('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>', '<%=issue.id%>');"
+             value="<%=l(:button_apply)%>"
+      />
+(<%= link_to_user(issue.assigned_to || '-') %>): <%= wk_pretty_issue_name issue %>
+<a href="#"
+  onclick="window.open('<%=url_for(:controller=>"issues", :action=>"show", :id=>issue.id)%>');">
+  &#63;
+</a>
+<br/>
+<%end%>
+</div>
+</form>
+  <input type="button"
+         onclick="tickets_checked('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
+         value="<%=l(:wt_apply_checked)%>"/>
+<br/>
+<br/>
+</div>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_insert.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_insert.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6955beb768729037efa0ec735aff8e4a0485bed8
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_add_tickets_insert.html.erb
@@ -0,0 +1,72 @@
+<%if @add_issue then%>
+<tr style="background:#ddf;" id="time_entry_pos<%=@add_issue_id%>_<%=@add_count%>">
+  <td>
+  <div style="position:relative;width:300px;">
+    <%=@add_issue.project.name%>
+    <br/>
+    <%= link_to(@issueHtml.html_safe, {:controller=>"issues", :action=>"show", :id=>@add_issue.id},
+                :popup=>true, :class=>'wt_iss_link', :'data-issue'=>@add_issue.id) %>
+    <%= print_issue_cost(@add_issue) %>
+    <%if User.current.allowed_to?(:edit_issues, @add_issue.project)%>
+        <a name="<%='done_ratio'+@add_issue.id.to_s%>"
+           class="wt_done_ratio"
+           data-issue="<%= @add_issue.id %>"
+           href="#"
+           onclick="JavaScript:
+                   input_done_ratio(
+                           '<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
+                           <%=@add_issue.id%>);
+                   return false;">
+          [<%=@add_issue.done_ratio%>&#37;]
+        </a>
+    <%else%>
+        [<%=@add_issue.done_ratio%>&#37;]
+    <%end%>
+
+    <a style="position:absolute;bottom:1px;right:10px;"
+       href="#"
+       onclick="JavaScript:
+         dup_ticket(
+           '<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
+           'time_entry_pos<%=@add_issue_id%>_<%=@add_count%>',
+           <%=@add_issue.id%>);
+         return false;">
+      +
+    </a>
+  </div>
+  </td>
+  <td>
+    <%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][hours]", "", :size=>5, :oninput => "sumDayTimes()") %>
+  </td>
+  <% if @is_registerd_backlog then %>
+  <td>
+    <% if @add_issue_children_cnt != 0 || @add_issue.closed? then %>
+      <%= @add_issue.remaining_hours %>
+    <% else %>
+        <%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][remaining_hours]", @add_issue.remaining_hours, :size=>5, :oninput => "sumDayTimes()") %>
+    <% end %>
+  </td>
+  <% end %>
+  <td>
+    <%= select_tag "new_time_entry["+@add_issue_id+"]["+@add_count+"][activity_id]", options_for_select(@activities,@activity_default), :required => true %>
+  </td>
+  <td>
+  <%
+    if @add_issue.new_statuses_allowed_to(User.current).length == 0
+  %>
+     <%= @add_issue.status %>
+  <% else %>
+    <%= select_tag "new_time_entry["+@add_issue_id.to_s+"][0][status_id]", options_for_select(@add_issue.new_statuses_allowed_to(User.current), @add_issue.status), :required => true %>
+  <% end %>
+  </td>
+  <td>
+    <%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][comments]", "", :size=>80)%>
+  </td>
+  <%if @custom_fields.length != 0
+      dummy_hour = TimeEntry.new(:project => @add_issue.project, :issue => @add_issue, :user => @this_user, :spent_on => @this_date)
+      dummy_hour.custom_field_values.each do |cfv|
+  %>
+     <td><%= custom_field_tag "new_time_entry_"+@add_issue_id+"_"+@add_count, cfv %></td>
+      <%end end%>
+</tr>
+<%end%>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_input.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_input.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..3b6741f5d57332417830f32313b899794c7f3c1e
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_input.html.erb
@@ -0,0 +1,31 @@
+<div name="<%='done_ratio'+@issue.id.to_s%>">
+<% if @issue.estimated_hours then %>
+    <%= print_issue_cost(@issue) %>
+    &#61;
+    <%= print_issue_cost_rate(@issue) %>
+    &#37;
+    <br/>
+<% end %>
+<form action="">
+  <%= @issue.done_ratio %>
+  &#37;
+  &rarr;
+  <input type="text"
+         id="<%='input_ratio'+@issue.id.to_s%>"
+         size="4"
+         onKeyPress="if(checkEnter(event)) {
+                         update_done_ratio('<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_update"))%>', <%=@issue.id.to_s%>);
+                         return false;
+                 }
+                 return event; "
+         value="<%= print_issue_cost_rate(@issue) %>"/>
+  &#37;
+  <input type="button"
+         value="<%=l(:button_apply)%>"
+         onclick="update_done_ratio('<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_update"))%>',
+                 <%=@issue.id.to_s%>);" />
+</form>
+</div>
+<script>
+    $('#<%='input_ratio'+@issue.id.to_s%>').focus();
+</script>
\ No newline at end of file
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_update.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_update.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..830bdd7629201a62ce98251ba2a0b67e7febe15a
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_done_ratio_update.html.erb
@@ -0,0 +1,15 @@
+<%#
+# チケットの進捗%をAjaxUpdateする
+%>
+<%if User.current.allowed_to?(:edit_issues, @issue.project)%>
+    <a name="<%='done_ratio'+@issue.id.to_s%>"
+       href="#"
+       onclick="input_done_ratio(
+               '<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
+               <%=@issue.id.to_s%>);
+               return false;">
+      [<%=@issue.done_ratio%>&#37;]
+    </a>
+<%else%>
+    [<%=@issue.done_ratio%>&#37;]
+<%end%>
\ No newline at end of file
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_memo_edit.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_memo_edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f4484b981e94cda67f7eec7b5b34453863b090e9
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_memo_edit.html.erb
@@ -0,0 +1,17 @@
+<%
+year = params["year"]
+month = params["month"]
+day = params["day"]
+user_id = params["user"]
+date = Date.new(year.to_i,month.to_i,day.to_i)
+wday_name = l(:wt_week_day_names).split(',')
+memo=WtDailyMemo.where(["day=:d and user_id=:u",{:d=>date,:u=>user_id}]).first
+if memo then
+%>
+<textarea id="work_time_memo" name="memo" rows="15" cols="100"><%=memo.description -%></textarea>
+<%else%>
+<textarea id="work_time_memo" name="memo" rows="15" cols="100"></textarea>
+<%end%>
+<br/>
+<%= submit_tag l(:wt_update) %>
+<%= wikitoolbar_for 'work_time_memo' %>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_relay.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_relay.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e80f354733ff0d9463b0151740122de947958cff
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_relay.html.erb
@@ -0,0 +1,51 @@
+<% if @message != '' %>
+    <div class="wt_error">
+      <%=@message.html_safe%>
+    </div>
+<% end %>
+
+<div class="<%=@relay_modified ? 'wt_modified' : ''%>">
+  =>
+  <%=@parent_disp.html_safe%>
+  <%
+     if User.current.allowed_to?(:edit_work_time_total, @project)
+       if @parent_disp != ''
+  %>
+          <a href="#"
+             onclick="jQuery.ajax({
+                     url: '<%=url_for(:action=>"ajax_relay_input", :issue_id=>@issue_id.to_s)%>',
+                     data: {asynchronous: true, method: 'get'},
+                     success: function (response) {
+                             jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
+                             }
+                     });
+                     return false;">
+            [<%=l(:button_change)%>]
+          </a>
+          <a href="#"
+             onclick="jQuery.ajax({
+                     url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id, :parent_id=>0)%>',
+                     data:{asynchronous:true, method:'get'},
+                     success: function(response) {
+                             jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
+                     });
+                     return false;">
+            [<%=l(:button_delete)%>]
+          </a>
+      <%else%>
+          <a href="#"
+             onclick="jQuery.ajax({
+                     url: '<%=url_for(:action=>"ajax_relay_input", :issue_id=>@issue_id.to_s)%>',
+                     data: {asynchronous: true, method: 'get'},
+                     success: function (response) {
+                             jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
+                             }
+                     });
+                     return false;">
+            [<%=l(:button_add)%>]
+          </a>
+      <%
+         end
+         end
+      %>
+</div>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_relay_input.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_relay_input.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..036077bd1833d8d0e024c4d609484876196e962d
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_relay_input.html.erb
@@ -0,0 +1,70 @@
+<select onchange="JavaScript:if (this.value != '') {
+        jQuery.ajax({
+                url:this.value,
+                data:{asynchronous:true, method:'get'},
+                success:function(response){
+                        jQuery('#input_area_<%=@issue_id%>').replaceWith(response);
+                        }
+                });
+        return false;}">
+  <option value=''><%=l(:wt_select_project)%></option>
+  <option disabled="disabled">---</option>
+  <%
+     @projects.each do |prj|
+       next unless prj.active?
+       next if !prj.visible?
+  %>
+      <option value="<%= url_for(:action=>"ajax_relay_input_select", :prj=>prj.id, :issue_id=>@issue_id) %>">
+        <%= (prj.pos.to_i < 100000) ? prj.pos : '*' %>: <%= prj.name %>
+      </option>
+  <%
+     end
+  %>
+</select>
+<div id="input_area_<%=@issue_id%>" style="display: inline-block;_display: inline;">
+  <%=l(:wt_input_ticket_numbers)%>
+  <form action="">
+    <input type="text"
+           id="input_id_<%=@issue_id%>"
+           size="8"
+           onKeyPress="if(checkEnter(event)) {
+                   jQuery.ajax({
+                           url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id)%>' +
+                                   '&parent_id=' + $('#input_id_<%=@issue_id%>').val(),
+                           data:{asynchronous:true, method:'get'},
+                           success: function(response) {
+                                   jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
+                           });
+                   return false;
+                   }
+                   return event;"
+    />
+    <input type="button"
+           value="<%=l(:button_apply)%>"
+           onclick="jQuery.ajax({
+                   url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id)%>' +
+                           '&parent_id=' + $('#input_id_<%=@issue_id%>').val(),
+                   data:{asynchronous:true, method:'get'},
+                   success: function(response) {
+                           jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
+                   });
+                   return false;"
+    />
+  </form>
+</div>
+
+<a href="#"
+   onclick="jQuery.ajax({
+           url: '<%=url_for(:action=>"ajax_relay", :issue_id=>@issue_id.to_s)%>',
+           data: {asynchronous: true, method: 'get'},
+           success: function (response) {
+                   jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
+                   }
+           });
+           return false;">
+  <%=l(:button_cancel)%>
+</a>
+
+<script>
+    $('#input_id_<%=@issue_id%>').focus();
+</script>
diff --git a/plugins/redmine_work_time/app/views/work_time/ajax_relay_input_select.html.erb b/plugins/redmine_work_time/app/views/work_time/ajax_relay_input_select.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..559b72a776ca84abc0f6103393eb612c63c6b403
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/ajax_relay_input_select.html.erb
@@ -0,0 +1,51 @@
+<div id="input_area_<%=@issue_id%>">
+  <%
+  opt = {
+    :action => 'ajax_relay_input_select',
+    :issue_id => @issue_id,
+    :prj => params[:prj],
+    :all => params[:all] || '0'
+  }
+
+  opt[:all] = opt[:all] == '0' ? '1' : '0' if params.key?(:all_toggle)
+
+  url1 = url_for(opt.merge({:all_toggle => ''}))
+
+  link_label1 = opt[:all] == '0' ? l(:wt_opt_disp_ticket_with_closed) : l(:wt_opt_disp_ticket_opened_only)
+%>
+<a href="#"
+   onclick="jQuery.ajax({
+           url: '<%=url1%>',
+           data: {asynchronous: true, method: 'get'},
+           success: function (response) {
+                   jQuery('#input_area_<%=@issue_id%>').html(response);}
+           });JavaScript:
+           return false;">
+  <%= link_label1 %>
+</a>
+<br/>
+<%
+@issues.each do |issue|
+  next if issue.nil? || !issue.visible? || (issue.closed? && opt[:all] == '0')
+%>
+<div style="font-size:14px;">
+<input type="button"
+       onclick="jQuery.ajax({
+               url:'<%=url_for(:action=>'ajax_relay', :issue_id=>@issue_id, :parent_id=>issue.id)%>',
+               data:{asynchronous:true, method:'get'},
+               success: function(response) {
+                       jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
+               });
+       "
+       value="<%=l(:button_apply)%>"
+/>
+(<%= link_to_user(issue.assigned_to || '-') %>): <%= wk_pretty_issue_name issue %>
+<a href="#"
+  onclick="window.open('<%=url_for(:controller=>"issues", :action=>"show", :id=>issue.id)%>');
+          return false;">
+  &#63;
+</a>
+<br/>
+</div>
+<%end%>
+</div>
diff --git a/plugins/redmine_work_time/app/views/work_time/edit_relay.html.erb b/plugins/redmine_work_time/app/views/work_time/edit_relay.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..aabf91fc14a4da17cb89068f634bd7f2a6674810
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/edit_relay.html.erb
@@ -0,0 +1,174 @@
+<%=javascript_include_tag 'work_time', :plugin=>'redmine_work_time' %>
+<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
+
+<%= @message.html_safe %>
+<div style="text-align:right;">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
+[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
+</div>
+<%
+###################################################### チケット関係テーブルの表示
+%>
+<br/>
+<h2>
+  <%=l(:wt_edit_relay)%>
+  <%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
+  <%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
+  <%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
+</h2>
+
+<table border="1" id="relay_table">
+  <%
+     prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
+     tickets = WtTicketRelay.order("position").all
+     tic_max = tickets.size
+
+     prjs.each do |po|
+       dsp_prj = po.dsp_prj
+       dsp_pos = po.dsp_pos
+       next if (!@prj_cost.has_key?(dsp_prj) || @prj_cost[dsp_prj][-1]==0.0) && (!@r_prj_cost.has_key?(dsp_prj) || @r_prj_cost[dsp_prj][-1]==0.0)
+       prj =Project.find_by_id(dsp_prj)
+  %>
+      <tr style="background:#000;color:#fff;">
+        <td>
+          <%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+              <a href="#"
+                 style="color:#8ff;"
+                 onclick="JavaScript:
+                         prj_pos(
+                                 '<%=url_for(@link_params)%>',
+                                 <%=dsp_prj%>,
+                                 <%=dsp_pos%>,
+                                 <%=prjs.size%>);
+                         return false;">
+                <%=dsp_pos%>:
+              </a>
+          <%end%>
+          <%=prj.name%>
+        </td>
+        <td style="text-align:right;">
+          <%if @prj_cost.has_key?(dsp_prj) && @prj_cost[dsp_prj][-1]!=0%>
+              <%=sprintf('%1.2f', @prj_cost[dsp_prj][-1])%>
+          <%else%>
+              &nbsp;
+          <%end%>
+        </td>
+        <td style="text-align:right;"><b>
+          <%if @r_prj_cost.has_key?(dsp_prj) && @r_prj_cost[dsp_prj][-1]!=0%>
+              <%=sprintf('%1.2f', @r_prj_cost[dsp_prj][-1]) %>
+          <%else%>
+              &nbsp;
+          <%end%>
+        </b></td>
+        <td>&nbsp;</td>
+      </tr>
+      <%
+         tic_num = 0
+         tickets.each do |tic|
+           issue_id = tic.issue_id
+           tic_num += 1
+           if tic.position != tic_num then # 番号が間違っていたらつけなおし
+             tic.position = tic_num; tic.save
+           end
+           # 当該チケットに集計が無かったらスキップ
+           next unless @issue_cost.has_key?(issue_id) || @r_issue_cost.has_key?(issue_id)
+           issue = Issue.find_by_id(issue_id)
+           parent_id = ""
+           next if issue.nil? # チケットが削除されていたらパス
+           next if issue.project_id != dsp_prj
+
+           if tic.parent && tic.parent != 0 then
+             parent = Issue.find_by_id(tic.parent)
+             if parent.nil? then
+               parentHtml = "<del>Issue:"+tic.parent.to_s+"</del>"
+             else
+               parent_id = parent.id
+               parentHtml = parent.closed? ? "<del>"+parent.to_s+"</del>" : parent.to_s
+             end
+           else
+             parentHtml = ""
+           end
+
+           if @issue_cost.key?(issue_id) &&
+                   @issue_cost[issue_id].key?(-1) &&
+                   @issue_cost[issue_id][-1]!=0 then
+             cost=sprintf("%.2f",@issue_cost[issue_id][-1])
+           else
+             cost="&nbsp"
+           end
+
+           if @r_issue_cost.key?(issue_id) &&
+                   @r_issue_cost[issue_id].key?(-1) &&
+                   @r_issue_cost[issue_id][-1]!=0 then
+             r_cost=sprintf("%.2f",@r_issue_cost[issue_id][-1])
+           else
+             r_cost="&nbsp"
+           end
+
+      %>
+          <tr>
+            <td>
+              <%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+                  <a href="#"
+                     onclick="JavaScript:
+                             ticket_pos('<%=url_for(@link_params)%>',
+                                     <%=issue_id%>,
+                                     <%=tic.position%>,
+                                     <%=tic_max%>);
+                             return false;">
+                    <%=tic.position%>
+                  </a>
+              <%end%>
+              <%= link_to(wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
+              <%= print_issue_cost(issue) %>
+            </td>
+            <td style="text-align:right;"><%=cost.html_safe%></td>
+            <td style="text-align:right;"><%=r_cost.html_safe%></td>
+            <td id="ticket_relay_<%=issue_id%>"></td>
+            <script>
+                jQuery.ajax({
+                    url: '<%=url_for(:action=>"ajax_relay", :issue_id=>issue_id.to_s)%>',
+                    data: {asynchronous: true, method: 'get'},
+                    success: function (response) {
+                        jQuery('#ticket_relay_<%=issue_id.to_s%>').html(response);
+                    }
+                });
+            </script>
+          </tr>
+      <%
+         end
+         end
+      %>
+  <% if @prj_cost.has_key?(-1) || @r_prj_cost.has_key?(-1) then%>
+      <tr style="background:#000;color:#fff;">
+        <td>private</td>
+        <td style="text-align:right;">
+          <%if @prj_cost.has_key?(-1) then%>
+              <%=sprintf('%1.2f', @prj_cost[-1][-1])%>
+          <%else%>
+              &nbsp;
+          <%end%>
+        </td>
+        <td style="text-align:right;">
+          <%if @r_prj_cost.has_key?(-1) then%>
+              <b><%=sprintf('%1.2f', @r_prj_cost[-1][-1]) if @r_prj_cost[-1][-1]%></b>
+          <%else%>
+              &nbsp;
+          <%end%>
+        </td>
+        <td>&nbsp;</td>
+      </tr>
+  <% end %>
+
+</table>
+<%= submit_tag(l('wt_bulkupdate_relations'),
+              {:type => 'button',
+               :onclick => "set_ticket_relay_by_issue_relation('#{url_for(:action=>'ajax_relay', :parent_id=>-1)}')".html_safe}) %>
+<div style="text-align:right;">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
+[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
+</div>
diff --git a/plugins/redmine_work_time/app/views/work_time/relay_total.api.rsb b/plugins/redmine_work_time/app/views/work_time/relay_total.api.rsb
new file mode 100644
index 0000000000000000000000000000000000000000..7636aa37e8474edb7e364752bea01dd0ef1b9f19
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/relay_total.api.rsb
@@ -0,0 +1,27 @@
+api.total_relay do
+  api.total_cost @total_cost
+  api.array :member_cost do
+    @members.each do |mem_info|
+      if @member_cost.key?(mem_info[1].id) && @member_cost[mem_info[1].id] > 0 then
+        api.member_cost do
+          api.user(:id => mem_info[1].id, :firstname => mem_info[1].firstname, :lastname => mem_info[1].lastname) unless mem_info[1].nil?
+          api.cost @member_cost[mem_info[1].id]
+        end
+      end
+    end
+  end
+  api.array :issues_cost do
+    @r_issue_cost.each do |issue,cost|
+      iss = Issue.find_by_id(issue)
+      api.issue_cost do
+        api.issue do
+	  api.id iss.id unless iss.nil?
+	  api.tracker(:id => iss.tracker.id, :name => iss.tracker.name) unless iss.tracker.nil?
+          api.subject iss.subject
+        end
+        api.cost cost[-1]
+      end
+    end
+  end
+end
+     
diff --git a/plugins/redmine_work_time/app/views/work_time/relay_total.html.erb b/plugins/redmine_work_time/app/views/work_time/relay_total.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..8411776d61cc5f0265c2f6cc5063a91ba9b1fb84
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/relay_total.html.erb
@@ -0,0 +1,206 @@
+<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
+<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
+
+<%= @message.html_safe %>
+<div style="text-align:right;">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
+[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
+</div>
+
+<h2>
+  <%=l(:wt_relay_total)%>
+  <%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
+  <%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
+  <%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
+</h2>
+
+<%
+if !User.current.allowed_to?(:view_work_time_other_member, @project) then
+  return
+end
+############################################## 集計表 #####################
+%>
+<table border="1">
+<tr valign="top">
+<td>
+</td>
+<td style="background:#ddd;"></td>
+<%
+max = @members.size
+@members.each do |mem_info|
+  pos = mem_info[0]
+  user = mem_info[1]
+%>
+<td align="center">
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+<a href="#"
+  onclick="JavaScript:
+    member_pos(
+      '<%=url_for(@link_params)%>',
+      <%=user.id%>,
+      <%=pos%>,
+      <%=max%>);
+    return false;">
+  <%=pos%>
+</a>
+<%end%>
+<div style="top:2px;width:56px;font-size:10px;">
+<%= link_to(user, @link_params.merge(:action=>"show", :user=>user.id))%>
+</div>
+</td>
+<%
+end
+%>
+</tr>
+
+<tr>
+<td style="background:#ddd;">
+</td>
+<td style="background:#ddd;text-align:right;">
+<%=sprintf('%1.2f', @total_cost)%>
+</td>
+<%
+#---------------------------------------各人の合計を表示
+@members.each do |mem_info|
+  user=mem_info[1]
+%>
+<td style="background:#ddd;text-align:right;">
+<%= (@member_cost.key?(user.id) && @member_cost[user.id] !=0 ) ? sprintf('%1.2f', @member_cost[user.id]) : " "%>
+</td>
+<%
+end
+%>
+</tr>
+
+<%
+prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
+
+tickets = WtTicketRelay.order("position").all
+tic_max = tickets.size
+
+#-------------------------------------- チケット行のループ
+prjs.each do |po|
+ dsp_prj = po.dsp_prj
+ dsp_pos = po.dsp_pos
+ next unless @r_prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
+ next unless @r_prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
+ next if @r_prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
+ prj =Project.find(dsp_prj)
+%>
+<tr style="background:#000;color:#fff;">
+ <td>
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+    <a href="#"
+      style="color:#8ff;"
+      onclick="JavaScript:
+        prj_pos(
+          '<%=url_for(@link_params)%>',
+          <%=dsp_prj%>,
+          <%=dsp_pos%>,
+          <%=prjs.size%>);
+        return false;">
+      <%=dsp_pos%>:
+    </a>
+<%end%>
+   <%=prj.name%>
+ </td>
+ <td style="text-align:right;"> <%=sprintf('%1.2f', (@r_prj_cost[dsp_prj])[-1])%> </td>
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+  uid=user.id
+%>
+  <td style="text-align:right;">
+    <%= @r_prj_cost[dsp_prj].key?(uid) ? sprintf('%1.2f', (@r_prj_cost[dsp_prj])[uid]) : " "%>
+  </td>
+<%
+end
+%>
+</tr>
+<%
+tickets.each do |tic|
+  issue_id = tic.issue_id
+  next unless @r_issue_cost.key?(issue_id) # 値の無いチケットはパス
+  next unless @r_issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
+  next if @r_issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
+
+  issue = Issue.find_by_id(issue_id)
+  next if issue.nil? # チケットが削除されていたらパス
+  next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
+
+%>
+<tr>
+<td>
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+    <a href="#"
+      onclick="JavaScript:
+        ticket_pos(
+          '<%=url_for(@link_params)%>',
+          <%=issue_id%>,
+          <%=tic.position%>,
+          <%=tic_max%>);
+        return false;">
+      <%=tic.position%>
+    </a>
+<%end%>
+  <%= link_to( wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
+  <%= print_issue_cost(issue) %>
+</td>
+
+<td style="background:#ddd;text-align:right;">
+<%=sprintf('%1.2f', (@r_issue_cost[issue_id])[-1])%>
+</td>
+
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+%>
+<td style="text-align:right;">
+<% if @r_issue_cost.key?(issue_id) && @r_issue_cost[issue_id].key?(user.id) then %>
+<%= sprintf('%1.2f', (@r_issue_cost[issue_id])[user.id]) %>
+<%else%>
+&nbsp;
+<%end%>
+</td>
+<%
+end
+%>
+
+</tr>
+<%
+ end
+end
+if @r_prj_cost.has_key?(-1) then
+%>
+<tr style="background:#000;color:#fff;">
+ <td>private</td>
+ <td style="text-align:right;"> <%=sprintf('%1.2f', (@r_prj_cost[-1])[-1])%> </td>
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+  uid=user.id
+%>
+  <td style="text-align:right;">
+    <%= @r_prj_cost[-1].key?(uid) ? sprintf('%1.2f', (@r_prj_cost[-1])[uid]) : " "%>
+  </td>
+<%end%>
+</tr>
+<%end%>
+</table>
+<input type="button"
+       onclick="location.href='<%=url_for(@link_params.merge(:action=>"relay_total_data"))%>'"
+       value="<%=l(:wt_data_download)%>"
+/>
+<input type="button"
+       onclick="location.href='<%=url_for(@link_params.merge(:action=>"relay_total_data_with_act"))%>'"
+       value="<%=l(:wt_data_download_with_act)%>"
+/>
+
+<div style="text-align:right;">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
+[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
+</div>
diff --git a/plugins/redmine_work_time/app/views/work_time/show.html.erb b/plugins/redmine_work_time/app/views/work_time/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..145de77e7879a8c9f1a529d9df206271caa8888b
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/show.html.erb
@@ -0,0 +1,143 @@
+<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
+<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
+
+<%= @message.html_safe %>
+<div class="contextual">
+<%= render :partial=>'select_project' %>
+<%if User.current.allowed_to?(:view_work_time_other_member, @project) then%>
+  <%= render :partial=>'select_user', :locals=>{:link_params=>@link_params} %>
+  <%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>
+<%end%>
+  <%if User.current.admin then%>
+      <%if WtHolidays.where(["holiday=:h and deleted_on is null",{:h=>@this_date}]).all.size == 0 then %>
+          <input type="button"
+                 onclick="location.href='<%=url_for(@link_params.merge(:set_holiday=>@this_date))%>'"
+                 value="<%=l(:wt_set_holiday)%>"
+          />
+      <%else%>
+          <input type="button"
+                 onclick="location.href='<%=url_for(@link_params.merge(:del_holiday=>@this_date))%>'"
+                 value="<%=l(:wt_del_holiday)%>"
+          />
+      <%end%>
+  <%end%>
+</div>
+
+<%= render :partial=>'user_month_table' %>
+
+<br/>
+<hr/>
+
+<%= form_tag @link_params do %>
+    <h2>
+      <%=l(:wt_daily_report)%>
+      <%=@this_date.strftime("%Y-%m-%d")%>
+    </h2>
+
+    <%= hidden_field_tag('year', @this_date.year) %>
+    <%= hidden_field_tag('month', @this_date.month) %>
+    <%= hidden_field_tag('day', @this_date.day) %>
+    <%= hidden_field_tag('user', @this_uid) %>
+    <%= hidden_field_tag('prj', @restrict_project) %>
+
+    <table style="background: #eeeeee;"><tr><td>
+      <table style="display:inline-block; border:0;">
+        <tr>
+          <td><%=l(:wt_legend)%>:</td>
+          <td class='wt_iss_default' width='100px' align='center'><%=l(:wt_style_default)%></td>
+          <td class='wt_iss_assigned' width='100px' align='center'><%=l(:wt_style_assigned)%></td>
+          <td class='wt_iss_worked' width='100px' align='center'><%=l(:wt_style_worked)%></td>
+          <td class='wt_iss_assigned_worked' width='100px' align='center'><%=l(:wt_style__assigned_worked)%></td>
+          <td class='wt_iss_overdue' width='100px' align='center'><%=l(:wt_style_overdue)%>
+          <td class='wt_iss_assigned_overdue' width='100px' align='center'><%=l(:wt_style_assigned_overdue)%>
+          </td><td class='wt_iss_overdue_worked' width='100px' align='center'><%=l(:wt_style_overdue_worked)%></td>
+          <td class='wt_iss_assigned_overdue_worked' width='100px' align='center'><%=l(:wt_style_assigned_overdue_worked)%></td>
+        </tr>
+      </table>
+    </td></tr></table>
+    <%= render :partial=>'user_day_table' %>
+
+<%if @this_uid==@crnt_uid || User.current.allowed_to?(:edit_work_time_other_member, @project) %>
+        <%= submit_tag l(:wt_update) %>
+        &#58;
+        <input type="button"
+               onclick="location.href='<%=url_for(@link_params.merge(:ticket_del=>"closed"))%>'"
+               value="<%=l(:wt_delete_closed_tickets)%>"
+        />
+        <div id="add_ticket_area" style="display: inline-block;_display: inline;">
+          <input type="button"
+               onclick="jQuery.ajax({
+                       url: '<%=url_for(:action=>"ajax_add_tickets_input")%>',
+                       data:{asynchronous:true, method:'get'},
+                       success: function(response){
+                               jQuery('#add_ticket_area').replaceWith(response);
+                               }
+                       });
+                       return false;"
+               value="<%=l(:wt_add_ticket)%>"
+          />
+        </div>
+<%end%>
+
+  <%#------------------------- Legend for the daily table%>
+    <br/>
+    <br/>
+    <hr/>
+
+<h2 id="memo">
+  <%=link_to(@this_date.strftime("%Y-%m-%d")+'('+@wday_name[@this_date.wday]+') '+@this_user.to_s,
+    @link_params.merge(:anchor=>"memo"))
+  %>
+</h2>
+
+<%#------------------------------------------- Wiki表示#%>
+<div id="memo-wiki">
+<%
+memo=WtDailyMemo.where(["day=:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).first
+if memo then # この日のメモがある場合
+%>
+<div class="wiki wt_memo_wiki_block">
+  <%=textilizable(memo.description) %>
+  <div style="text-align:right;">
+    (update:<%=memo.updated_on.localtime.strftime("%Y-%m-%d %H:%M")%>)
+  </div>
+</div>
+<%end%>
+
+<%
+memo=WtDailyMemo.where(["day<:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).order("day DESC").first
+if memo then
+d = memo.day
+%>
+[<%=link_to(l(:wt_pre_memo), @link_params.merge(:anchor=>"memo", :day=>d.day, :month=>d.month, :year=>d.year))%>]
+<%end%>
+
+<%if @this_uid==@crnt_uid then%>
+[<a href="#"
+    onclick="jQuery.ajax({
+            url: '<%=url_for(@link_params.merge(:action=>"ajax_memo_edit"))%>',
+            data:{asynchronous:true, method:'get'},
+            success: function(response){
+                    jQuery('#memo-wiki').html(response);
+                    }
+            });
+            return false;">
+  <%=l(:wt_edit_memo)%>
+</a>]
+<%end%>
+
+<%
+memo=WtDailyMemo.where(["day>:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).order("day").first
+if memo then
+d = memo.day
+%>
+[<%= link_to(l(:wt_next_memo), @link_params.merge(:anchor=>"memo", :day=>d.day, :month=>d.month, :year=>d.year))%>]
+<%end%>
+</div>
+<br/>
+
+<%if User.current.allowed_to?(:view_work_time_other_member, @project) then%>
+  <%= render :partial=>'select_user', :locals=>{:link_params=>@link_params.merge(:anchor=>"memo")} %>
+<%end%>
+
+<%end%>
diff --git a/plugins/redmine_work_time/app/views/work_time/total.html.erb b/plugins/redmine_work_time/app/views/work_time/total.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c1378e0cb7678e63f365e48c69f42142acb7ff85
--- /dev/null
+++ b/plugins/redmine_work_time/app/views/work_time/total.html.erb
@@ -0,0 +1,206 @@
+<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
+<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
+
+<%= @message.html_safe %>
+<div style="text-align:right;">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
+[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
+</div>
+
+<h2>
+  <%=l(:wt_raw_total)%>
+  <%=link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
+  <%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
+  <%=link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
+</h2>
+
+<%
+if !User.current.allowed_to?(:view_work_time_other_member, @project) then
+  return
+end
+############################################## 集計表 #####################
+%>
+<table border="1">
+<tr valign="top">
+<td>
+</td>
+<td style="background:#ddd;"></td>
+<%
+max = @members.size
+@members.each do |mem_info|
+  pos=mem_info[0]
+  user=mem_info[1]
+%>
+<td align="center">
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+<a href="#"
+  onclick="JavaScript:
+    member_pos(
+      '<%=url_for(@link_params)%>',
+      <%=user.id%>,
+      <%=pos%>,
+      <%=max%>);
+    return false;">
+  <%=pos%>
+</a>
+<%end%>
+<div style="top:2px;width:56px;font-size:10px;">
+<%= link_to(user, @link_params.merge(:action=>"show", :user=>user.id))%>
+</div>
+</td>
+<%
+end
+%>
+</tr>
+
+<tr>
+<td style="background:#ddd;">
+</td>
+<td style="background:#ddd;text-align:right;">
+<%=sprintf('%1.2f', @total_cost)%>
+</td>
+<%
+#---------------------------------------各人の合計を表示
+@members.each do |mem_info|
+  user=mem_info[1]
+%>
+<td style="background:#ddd;text-align:right;">
+<%= (@member_cost.key?(user.id) && @member_cost[user.id] !=0 ) ? sprintf('%1.2f', @member_cost[user.id]) : " "%>
+</td>
+<%
+end
+%>
+</tr>
+
+<%
+prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
+
+tickets = WtTicketRelay.order("position").all
+tic_max = tickets.size
+
+#-------------------------------------- チケット行のループ
+prjs.each do |po|
+ dsp_prj = po.dsp_prj
+ dsp_pos = po.dsp_pos
+ next unless @prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
+ next unless @prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
+ next if @prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
+ prj =Project.find(dsp_prj)
+%>
+<tr style="background:#000;color:#fff;">
+ <td>
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+    <a href="#"
+      style="color:#8ff;"
+      onclick="JavaScript:
+        prj_pos(
+          '<%=url_for(@link_params)%>',
+          <%=dsp_prj%>,
+          <%=dsp_pos%>,
+          <%=prjs.size%>);
+        return false;">
+      <%=dsp_pos%>:
+    </a>
+<%end%>
+   <%=prj.name%>
+ </td>
+ <td style="text-align:right;"> <%=sprintf('%1.2f', (@prj_cost[dsp_prj])[-1])%> </td>
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+  uid=user.id
+%>
+  <td style="text-align:right;">
+    <%= @prj_cost[dsp_prj].key?(uid) ? sprintf('%1.2f', (@prj_cost[dsp_prj])[uid]) : " "%>
+  </td>
+<%
+end
+%>
+</tr>
+<%
+tickets.each do |tic|
+  issue_id = tic.issue_id
+  next unless @issue_cost.key?(issue_id) # 値の無いチケットはパス
+  next unless @issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
+  next if @issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
+
+  issue = Issue.find_by_id(issue_id)
+  next if issue.nil? # チケットが削除されていたらパス
+  next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
+
+%>
+<tr>
+<td>
+<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
+    <a href="#"
+      onclick="JavaScript:
+        ticket_pos(
+          '<%=url_for(@link_params)%>',
+          <%=issue_id%>,
+          <%=tic.position%>,
+          <%=tic_max%>);
+        return false;">
+      <%=tic.position%>
+    </a>
+<%end%>
+  <%= link_to(wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
+  <%= print_issue_cost(issue) %>
+</td>
+
+<td style="background:#ddd;text-align:right;">
+<%=sprintf('%1.2f', (@issue_cost[issue_id])[-1])%>
+</td>
+
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+%>
+<td style="text-align:right;">
+<% if @issue_cost.key?(issue_id) && @issue_cost[issue_id].key?(user.id) then %>
+<%= sprintf('%1.2f', (@issue_cost[issue_id])[user.id]) %>
+<%else%>
+&nbsp;
+<%end%>
+</td>
+<%
+end
+%>
+
+</tr>
+<%
+  end
+end
+if @prj_cost.has_key?(-1) then
+%>
+<tr style="background:#000;color:#fff;">
+ <td>private</td>
+ <td style="text-align:right;"> <%=sprintf('%1.2f', (@prj_cost[-1])[-1])%> </td>
+<%
+@members.each do |mem_info|
+  user=mem_info[1]
+  uid=user.id
+%>
+  <td style="text-align:right;">
+    <%= @prj_cost[-1].key?(uid) ? sprintf('%1.2f', (@prj_cost[-1])[uid]) : " "%>
+  </td>
+<%end%>
+</tr>
+<%end%>
+</table>
+<input type="button"
+       onclick="location.href='<%=url_for(@link_params.merge(:action=>"total_data"))%>'"
+       value="<%=l(:wt_data_download)%>"
+/>
+<input type="button"
+       onclick="location.href='<%=url_for(@link_params.merge(:action=>"total_data_with_act"))%>'"
+       value="<%=l(:wt_data_download_with_act)%>"
+/>
+
+<div align="right">
+<%= render :partial=>'select_project' %> <br/>
+[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
+[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
+[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
+</div>
diff --git a/plugins/redmine_work_time/assets/javascripts/work_time.js b/plugins/redmine_work_time/assets/javascripts/work_time.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f39ce160a4bfe174a7b663510baa7906c096714
--- /dev/null
+++ b/plugins/redmine_work_time/assets/javascripts/work_time.js
@@ -0,0 +1,172 @@
+function ticket_pos(url, issue, pos, max)
+{
+  var new_pos = prompt("Destination No.", pos);
+  if(new_pos != null) {
+    if((new_pos>=1) && (new_pos<=max))
+      location.replace(url+"&ticket_pos="+issue+"_"+new_pos);
+    else 
+      alert("Out of range!");
+  }
+}
+
+function prj_pos(url, prj, pos, max)
+{
+  var new_pos = prompt("Destination No.", pos);
+  if(new_pos != null) {
+    if((new_pos>=1) && (new_pos<=max))
+      location.replace(url+"&prj_pos="+prj+"_"+new_pos);
+    else 
+      alert("Out of range!");
+  }
+}
+
+function member_pos(url, user_id, pos, max)
+{
+  var new_pos = prompt("Distination No.", pos);
+  if(new_pos != null) {
+    if((new_pos>=1) && (new_pos<=max))
+      location.replace(url+"&member_pos="+user_id+"_"+new_pos);
+    else 
+      alert("Out of rnage!");
+  }
+}
+
+function set_ticket_relay_by_issue_relation(ajax_url) {
+  $('[id^=ticket_relay_]').each(function(){
+    var issue_id = $(this).attr('id').replace(/.*([^_]+)$/, "$1");
+    jQuery.ajax({
+      url: ajax_url + '&issue_id=' + issue_id,
+      data:{asynchronous:true, method:'get'},
+      success: function(response) {
+        jQuery('#ticket_relay_'+issue_id).html(response);
+      }
+    });
+  })
+}
+
+function input_done_ratio(ajax_url, issue_id) {
+  jQuery.ajax({
+    url: ajax_url + "&issue_id=" + issue_id,
+    data: {asynchronous: true, method: 'get'},
+    success: function (response) {
+      jQuery('[name="done_ratio'+ issue_id+'"]:first').replaceWith(response);
+    }
+  });
+}
+
+function update_done_ratio(ajax_url, issue_id) {
+  var done_ratio = $('#input_ratio'+issue_id).val();
+  jQuery.ajax({
+    url:ajax_url+"&issue_id="+issue_id+"&done_ratio="+done_ratio,
+    data:{asynchronous:true, method:'get'},
+    success:function(response){
+      jQuery('[name="done_ratio'+ issue_id+'"]').replaceWith(response);
+    }
+  });
+}
+
+function checkEnter(e)
+{
+  if (!e) var e = window.event;
+  if(e.keyCode == 13)
+    return true;
+  else
+    return false;
+}
+
+//------------------------------------------------- for show.html.erb
+var add_ticket_count = 1;
+
+function dup_ticket(ajax_url, insert_pos, id)
+{
+  jQuery.ajax({
+    url:ajax_url+"&add_issue="+id+"&count="+add_ticket_count,
+    data:{asynchronous:true, method:'get'},
+    success:function(response){
+      jQuery('#'+insert_pos).after(response);
+    }
+  });
+  add_ticket_count ++;
+}
+
+function tickets_insert(ajax_url, tickets)
+{
+  for(i=0; i<tickets.length;i++) {
+    jQuery.ajax({
+      url:ajax_url+"&add_issue="+tickets[i]+"&count="+add_ticket_count,
+      data:{asynchronous:true, method:'get'},
+      success:function(response){
+        jQuery('#time_input_table_bottom').before(response);
+      }
+    });
+    add_ticket_count ++;
+  }
+}
+
+function tickets_inputed(ajax_url)
+{
+  var vals = document.getElementById("input_ids").value;
+  var tickets = vals.split(',');
+  tickets_insert(ajax_url, tickets);
+}
+
+function tickets_selected(ajax_url, issue_id)
+{
+  var tickets = [issue_id];
+  tickets_insert(ajax_url, tickets);
+}
+
+function tickets_checked(ajax_url)
+{
+  var $checked = $('[name="ticket_select_check"]:checked');
+  var tickets = $checked.map(function(i,e){return $(this).val()});
+  tickets_insert(ajax_url, tickets);
+}
+
+function statusUpdateOnDailyTable(name) {
+  obj = document.getElementsByName(name)[0];
+  obj.style.backgroundColor = '#cfc';
+  index = obj.selectedIndex;
+  v = obj.options[index].value;
+  obj.options[index].value = 'M'+v;
+}
+
+//------------- for user_day_table.html.erb
+function sumDayTimes() {
+  var total=0;
+  var dayInputs;
+  
+  // List all Input elemnets of the page
+  dayInputs = document.getElementsByTagName("input");
+  for (var i=0; i<dayInputs.length; i++) {
+    // Consider only those with an id containing the strings 'time_entry' and 'hours'
+    if ((dayInputs[i].id.indexOf("time_entry") >= 0) && (dayInputs[i].id.indexOf("hours") >= 0)) {
+      var val = dayInputs[i].value;
+      if (val) {
+        var vals = val.match(/^([\d\.]+)$/);
+        if (vals) {
+          // add the number to the total if it is a valid number
+          total = total + parseFloat(vals[1]);
+        }
+        else {
+          vals = val.match(/^(\d+)m$/);
+          if(vals) {
+            total = total + parseFloat(vals[1])/60;
+          }
+          else {
+            vals = val.match(/^(\d+):(\d+)$/);
+            if(vals) {
+              total = total + parseFloat(vals[1]) + parseFloat(vals[2])/60;
+            }
+          }
+        }
+      }
+    }
+  }
+  // Set the total value to the new number, changing the style to indicate 
+  // it is not saved, and adding the saved value as a flyover indication
+  var originalValue;
+  document.getElementById("currentTotal").innerHTML = total.toFixed(2);
+  document.getElementById("currentTotal").style = 'color:#FF0000;';
+  return true;
+}
diff --git a/plugins/redmine_work_time/assets/stylesheets/work_time.css b/plugins/redmine_work_time/assets/stylesheets/work_time.css
new file mode 100644
index 0000000000000000000000000000000000000000..7e8599f550be811026c7ccc802718258e26a6f8f
--- /dev/null
+++ b/plugins/redmine_work_time/assets/stylesheets/work_time.css
@@ -0,0 +1,51 @@
+h1 {
+  margin-top: 0;
+  margin-bottom: 0
+}
+
+h2 {
+  margin-top: 0;
+  margin-bottom: 0
+}
+
+div.wt_modified {
+  background: #cfc;
+}
+
+div.wt_error {
+  background: #faa;
+}
+
+div.wt_memo_wiki_block {
+  background: #ffb;
+}
+div.wt_add_ticket_block {
+  background: #ddd;
+  border: 1px solid black;
+}
+
+/* use day table */
+td.wt_iss_default {
+  background: #fff;
+}
+td.wt_iss_worked {
+  background: #cfc;
+}
+td.wt_iss_overdue {
+  background: #ffc;
+}
+td.wt_iss_overdue_worked {
+  background: #ccc;
+}
+td.wt_iss_assigned {
+  background: #cff;
+}
+td.wt_iss_assigned_worked {
+  background: #ccf;
+}
+td.wt_iss_assigned_overdue {
+  background: #fcc;
+}
+td.wt_iss_assigned_overdue_worked {
+  background: #fcf;
+}
diff --git a/plugins/redmine_work_time/config/locales/ca.yml b/plugins/redmine_work_time/config/locales/ca.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4188bd529cac8794365b36f8647ce1f8d740093d
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/ca.yml
@@ -0,0 +1,48 @@
+ca:
+  work_time: "WorkTime"
+  wt_update: "Update"
+  wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
+  wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
+  wt_monthly_report: "Monthly Report"
+  wt_daily_report: "Daily Report"
+  wt_each_member_report: "Member Report"
+  wt_raw_total: "Monthly Report(not relayed)"
+  wt_relay_total: "Monthly Report(relayed)"
+  wt_edit_relay: "Ticket Relay Editor"
+  wt_ticket: "Ticket"
+  wt_add_ticket: "Add ticket"
+  wt_no_permission: "No permission"
+  wt_loop_relay: "Ticket relation looped"
+  wt_set_holiday: "set holiday on this date"
+  wt_del_holiday: "del holiday on this date"
+  wt_apply_checked: "apply all checked"
+  wt_select_project: "Restrict project..."
+  wt_select_user: "Select user..."
+  wt_edit_memo: "Edit memo"
+  wt_pre_memo: "pre. memo"
+  wt_next_memo: "next memo"
+  wt_data_list: "Data list"
+  wt_input_ticket_numbers: "or input ticket numbers"
+  wt_delete_closed_tickets: "delete closed tickets"
+  wt_data_download: "data download"
+  wt_data_download_with_act: "data download(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/de.yml b/plugins/redmine_work_time/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..acdbb8a0ce692ef9a76fb205b0d761bfba8bd378
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/de.yml
@@ -0,0 +1,48 @@
+de:
+  work_time: "Zeiterfassung"
+  wt_update: "Aktualisieren"
+  wt_month_names: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez"
+  wt_week_day_names: "So,Mo,Di,Mi,Do,Fr,Sa"
+  wt_monthly_report: "Monatsbericht"
+  wt_daily_report: "Tagesbericht"
+  wt_each_member_report: "Bericht pro Benutzer"
+  wt_raw_total: "Monatsbericht (ohne Fortschreibung)"
+  wt_edit_relay: "Aufwände fortschreiben"
+  wt_relay_total: "Monatsbericht"
+  wt_ticket: "Ticket"
+  wt_add_ticket: "Ticket hinzufügen"
+  wt_no_permission: "Keine Berechtigung"
+  wt_loop_relay: "Zyklus bei der Aufwandsfortschreibung!"
+  wt_set_holiday: "Tag zum Urlaubs-/Feiertag machen"
+  wt_del_holiday: "Tag zum Arbeitstag machen"
+  wt_apply_checked: "Alle ausgewählten Einträge übernehmen"
+  wt_select_project: "Projektauswahl"
+  wt_select_user: "Benutzerauswahl"
+  wt_edit_memo: "Memo ändern"
+  wt_pre_memo: "Vorheriges Memo"
+  wt_next_memo: "Nächstes Memo"
+  wt_data_list: "Datenliste"
+  wt_input_ticket_numbers: "oder Ticket-Nr. direkt eingeben"
+  wt_delete_closed_tickets: "delete closed tickets"
+  wt_data_download: "Daten laden"
+  wt_data_download_with_act: "Daten laden(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/en.yml b/plugins/redmine_work_time/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..020b5e622ba5fd991de40252e0a5ffe4eb039107
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/en.yml
@@ -0,0 +1,48 @@
+en:
+  work_time: "WorkTime"
+  wt_update: "Update"
+  wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
+  wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
+  wt_monthly_report: "Monthly Report"
+  wt_daily_report: "Daily Report"
+  wt_each_member_report: "Member Report"
+  wt_raw_total: "Monthly Report(not relayed)"
+  wt_relay_total: "Monthly Report(relayed)"
+  wt_edit_relay: "Ticket Relay Editor"
+  wt_ticket: "Ticket"
+  wt_add_ticket: "Add ticket"
+  wt_no_permission: "No permission"
+  wt_loop_relay: "Ticket relation looped"
+  wt_set_holiday: "set holiday on this date"
+  wt_del_holiday: "del holiday on this date"
+  wt_apply_checked: "apply all checked"
+  wt_select_project: "Restrict project..."
+  wt_select_user: "Select user..."
+  wt_edit_memo: "Edit memo"
+  wt_pre_memo: "pre. memo"
+  wt_next_memo: "next memo"
+  wt_data_list: "Data list"
+  wt_input_ticket_numbers: "or input ticket numbers"
+  wt_delete_closed_tickets: "delete closed tickets"
+  wt_data_download: "data download"
+  wt_data_download_with_act: "data download(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/es.yml b/plugins/redmine_work_time/config/locales/es.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff94584ebe7f35090975645d7ff208c351f037a0
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/es.yml
@@ -0,0 +1,48 @@
+es:
+  work_time: "Agenda"
+  wt_update: "Actualizar"
+  wt_month_names: "Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic"
+  wt_week_day_names: "Dom,Lun,Mar,Mie,Jue,Vie,Sab"
+  wt_monthly_report: "Informe mensual"
+  wt_daily_report: "Informe diario"
+  wt_each_member_report: "Informe de miembro"
+  wt_raw_total: "Informe mensual(no se retransmite)"
+  wt_edit_relay: "Editor transmisión de peticiones"
+  wt_relay_total: "Informe mensual de peticiones"
+  wt_ticket: "Petición"
+  wt_add_ticket: "Añade petición"
+  wt_no_permission: "Sin permiso"
+  wt_loop_relay: "Transmisión de petición en bucle"
+  wt_set_holiday: "Establece vacaciones en esta fecha"
+  wt_del_holiday: "Elimina vacaciones en esta fecha"
+  wt_apply_checked: "Aplica a todos los seleccionados"
+  wt_select_project: "Seleccione proyecto..."
+  wt_select_user: "Seleccione usuario..."
+  wt_edit_memo: "Edita memorandum"
+  wt_pre_memo: "Anterior memorandum"
+  wt_next_memo: "Siguiente memorandum"
+  wt_data_list: "Lista de datos"
+  wt_input_ticket_numbers: "o introduzca numero de petición"
+  wt_delete_closed_tickets: "delete closed tickets"
+  wt_data_download: "data download"
+  wt_data_download_with_act: "data download(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/fr.yml b/plugins/redmine_work_time/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..538f5f2305f8a13aec492ad833c55a14e2d19637
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/fr.yml
@@ -0,0 +1,48 @@
+fr:
+  work_time: "Temps de travail"
+  wt_update: "Mettre à jour"
+  wt_month_names: "Jan,Fév,Mar,Avr,Mai,Juin,Juil,Août,Sep,Oct,Nov,Déc"
+  wt_week_day_names: "Dim,Lun,Mar,Mer,Jeu,Ven,Sam"
+  wt_monthly_report: "Rapport mensuel"
+  wt_daily_report: "Rapport journalier"
+  wt_each_member_report: "Rapport utilisateur"
+  wt_raw_total: "Rapport mensuel(non relayé)"
+  wt_relay_total: "Rapport mensuel (relayé)"
+  wt_edit_relay: "Ticket Relay Editor"
+  wt_ticket: "Demande"
+  wt_add_ticket: "Ajouter une demande"
+  wt_no_permission: "Vous n'avez pas les droits"
+  wt_loop_relay: "Boucle infinie sur une demande"
+  wt_set_holiday: "Placer cette date en jour férié"
+  wt_del_holiday: "Retirer cette date des jours fériés"
+  wt_apply_checked: "Appliquer pour toutes les cases cochées"
+  wt_select_project: "Restreindre à un projet"
+  wt_select_user: "Choisir un utilisateur"
+  wt_edit_memo: "Éditer la note"
+  wt_pre_memo: "Note précédente"
+  wt_next_memo: "Note suivante"
+  wt_data_list: "Liste brute"
+  wt_input_ticket_numbers: "ou entrez le numéro de la demande"
+  wt_delete_closed_tickets: "Supprimer les demandes fermées"
+  wt_data_download: "Téléchargement des données"
+  wt_data_download_with_act: "Téléchargement des données(each activity)"
+  wt_opt_disp_ticket_with_closed: "[afficher les demandes fermés]"
+  wt_opt_disp_ticket_opened_only: "[n'afficher que les demandes ouvertes]"
+  wt_opt_disp_ticket_with_other_member: "[afficher les demandes des autres membres]"
+  wt_opt_disp_ticket_mine_only: "[n'afficher que mes demandes]"
+  wt_bulkupdate_relations: "mise à jour en bloc du parent"
+  permission_view_work_time_tab: "Afficher l'onglet Temps de travail"
+  permission_view_work_time_other_member: "Afficher les temps des autres membres"
+  permission_edit_work_time_total: "Modifier les totaux de temps de travail"
+  permission_edit_work_time_other_member: "Modifier les temps des autres membres"
+  wt_saved_value: "Temps sauvegardé : "
+  wt_legend: "Légende"
+  wt_style_default: "Par défaut"
+  wt_style_assigned: "Affecté"
+  wt_style_worked: "Travail en cours"
+  wt_style__assigned_worked: "Affecté et travail en cours"
+  wt_style_overdue: "En retard"
+  wt_style_assigned_overdue: "Affecté et en retard"
+  wt_style_overdue_worked: "En retard et travail en cours"
+  wt_style_assigned_overdue_worked: "Affecté, en retard et travail en cours"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/it.yml b/plugins/redmine_work_time/config/locales/it.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a1a1b13473708dcc9911828316331c945570414e
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/it.yml
@@ -0,0 +1,48 @@
+it:
+  work_time: "Tempo di lavoro"
+  wt_update: "Aggiorna"
+  wt_month_names: "Gen,Feb,Mar,Apr,Mag,Giu,Lug,Ago,Set,Ott,Nov,Dic"
+  wt_week_day_names: "Dom,Lun,Mar,Mer,Gio,Ven,Sab"
+  wt_monthly_report: "Report Mensile"
+  wt_daily_report: "Report Giornaliero"
+  wt_each_member_report: "Report Utente"
+  wt_raw_total: "Report Mensile (not relayed)"
+  wt_edit_relay: "Editor segnalazioni trasmesse"
+  wt_relay_total: "Report Mensile Segnalazioni Relayed"
+  wt_ticket: "Segnalazione"
+  wt_add_ticket: "Aggiungi segnalazione"
+  wt_no_permission: "Non autorizzato"
+  wt_loop_relay: "Ticket relation looped"
+  wt_set_holiday: "Imposta giorno come festivo"
+  wt_del_holiday: "Canc giorno come festivo"
+  wt_apply_checked: "Applica a selezionati"
+  wt_select_project: "Seleziona progetto..."
+  wt_select_user: "Seleziona utente..."
+  wt_edit_memo: "Modifica nota"
+  wt_pre_memo: "Nota precedente"
+  wt_next_memo: "Prossima nota"
+  wt_data_list: "Lista Dati"
+  wt_input_ticket_numbers: "o inserisci i numeri delle segnalazioni"
+  wt_delete_closed_tickets: "Elimina segnalazioni chiuse"
+  wt_data_download: "download di dati"
+  wt_data_download_with_act: "download di dati(each activity)"
+  wt_opt_disp_ticket_with_closed: "[mostra segnalazioni chiuse]"
+  wt_opt_disp_ticket_opened_only: "[mostra solo segnalazioni aperte]"
+  wt_opt_disp_ticket_with_other_member: "[mostra segnalazioni di altri membri]"
+  wt_opt_disp_ticket_mine_only: "[mostra solo le mie segnalazioni]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/ja.yml b/plugins/redmine_work_time/config/locales/ja.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cfff85ebd0d5b50e035039b06edcf4d96128f85e
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/ja.yml
@@ -0,0 +1,48 @@
+ja:
+  work_time: "工数"
+  wt_update: "æ›´æ–°"
+  wt_month_names: "1,2,3,4,5,6,7,8,9,10,11,12"
+  wt_week_day_names: "日,月,火,水,木,金,土"
+  wt_monthly_report: "月間工数表"
+  wt_daily_report: "日毎工数"
+  wt_each_member_report: "個人工数集計"
+  wt_raw_total: "月間集計(付替なし)"
+  wt_relay_total: "月間集計(付替あり)"
+  wt_edit_relay: "チケット付替設定"
+  wt_ticket: "チケット"
+  wt_add_ticket: "チケット追加"
+  wt_no_permission: "権限がありません"
+  wt_loop_relay: "チケット関係がループしています"
+  wt_set_holiday: "この日を休日に設定"
+  wt_del_holiday: "この日の休日を解除"
+  wt_apply_checked: "チェックを適用"
+  wt_select_project: "プロジェクト限定"
+  wt_select_user: "ユーザを選択"
+  wt_edit_memo: "メモを編集"
+  wt_pre_memo: "前のメモ"
+  wt_next_memo: "次のメモ"
+  wt_data_list: "データリスト"
+  wt_input_ticket_numbers: "またはチケット番号を入力"
+  wt_delete_closed_tickets: "終了チケット削除"
+  wt_data_download: "データダウンロード"
+  wt_data_download_with_act: "活動別データダウンロード"
+  wt_opt_disp_ticket_with_closed: "[終了したチケットも表示]"
+  wt_opt_disp_ticket_opened_only: "[オープン中のチケットのみ表示]"
+  wt_opt_disp_ticket_with_other_member: "[他のメンバーのチケットも表示]"
+  wt_opt_disp_ticket_mine_only: "[自分のチケットのみ表示]"
+  wt_bulkupdate_relations: "親チケットへ一括更新"
+  permission_view_work_time_tab: "工数入力集計対象者"
+  permission_view_work_time_other_member: "他のメンバーの工数の表示"
+  permission_edit_work_time_total: "工数集計の設定"
+  permission_edit_work_time_other_member: "他のメンバーの工数の入力"
+  wt_saved_value: "保存時間: "
+  wt_legend: "チケット表示凡例"
+  wt_style_default: "通常表示"
+  wt_style_assigned: "担当期間"
+  wt_style_worked: "書込"
+  wt_style__assigned_worked: "担当期間,書込"
+  wt_style_overdue: "期限超過"
+  wt_style_assigned_overdue: "担当,期限超過"
+  wt_style_overdue_worked: "期限超過,書込"
+  wt_style_assigned_overdue_worked: "担当,超過,書込"
+  wt_account_start_day: "集計開始日"
diff --git a/plugins/redmine_work_time/config/locales/ko.yml b/plugins/redmine_work_time/config/locales/ko.yml
new file mode 100644
index 0000000000000000000000000000000000000000..896436af100ebacc9831645a915c66b59467a68a
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/ko.yml
@@ -0,0 +1,49 @@
+# Translation by Ki Won Kim (http://xyz37.blog.me, xyz37@naver.com)
+ko:
+  work_time: "작업 시간"
+  wt_update: "수정"
+  wt_month_names: "1ì›”,2ì›”,3ì›”,4ì›”,5ì›”,6ì›”,7ì›”,8ì›”,9ì›”,10ì›”,11ì›”,12ì›”"
+  wt_week_day_names: "일,월,화,수,목,금,토"
+  wt_monthly_report: "월간 보고"
+  wt_daily_report: "일간 보고"
+  wt_each_member_report: "사용자 별 보고"
+  wt_raw_total: "월간 보고(연동 없음)"
+  wt_edit_relay: "티켓 연동 편집기"
+  wt_relay_total: "연동된 티켓 월간 보고"
+  wt_ticket: "티켓"
+  wt_add_ticket: "티켓 추가"
+  wt_no_permission: "권한 없음"
+  wt_loop_relay: "티켓 관계가 반복(Looping) 됩니다."
+  wt_set_holiday: "이 날짜를 휴일로 지정"
+  wt_del_holiday: "이 날짜를 휴일에서 제거"
+  wt_apply_checked: "선택 항목 적용"
+  wt_select_project: "프로젝트 선택..."
+  wt_select_user: "사용자 선택..."
+  wt_edit_memo: "메모 수정"
+  wt_pre_memo: "이전 메모"
+  wt_next_memo: "다음 메모"
+  wt_data_list: "데이터 목록"
+  wt_input_ticket_numbers: "또는 티켓 번호를 입력하세요."
+  wt_delete_closed_tickets: "완료된 티켓 삭제"
+  wt_data_download: "데이터 다운로드"
+  wt_data_download_with_act: "데이터 다운로드(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/no.yml b/plugins/redmine_work_time/config/locales/no.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6ba420064df245c6fc7b1ec21cb22d0338202b63
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/no.yml
@@ -0,0 +1,48 @@
+"no":
+  work_time: "Arbeidstimer"
+  wt_update: "Oppdater"
+  wt_month_names: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des"
+  wt_week_day_names: "Søn,Man,Tir,Ons,Tor,Fre,Lør"
+  wt_monthly_report: "MÃ¥nedsrapport"
+  wt_daily_report: "Dagsrapport"
+  wt_each_member_report: "Medlemsrapport"
+  wt_raw_total: "MÃ¥nedsrapport (ikke samlet)"
+  wt_relay_total: "MÃ¥nedsrapport (samlet)"
+  wt_edit_relay: "Samle poster"
+  wt_ticket: "Poster"
+  wt_add_ticket: "Legg til post"
+  wt_no_permission: "Ikke tilgang"
+  wt_loop_relay: "Samlekobling i løkke"
+  wt_set_holiday: "merk dato som feriedag"
+  wt_del_holiday: "fjern feriedagmerking på dat"
+  wt_apply_checked: "Legg til valgte poster"
+  wt_select_project: "Begrens prosjekt..."
+  wt_select_user: "Velg bruker..."
+  wt_edit_memo: "Endre notat"
+  wt_pre_memo: "forr. notat"
+  wt_next_memo: "neste notat"
+  wt_data_list: "Data list"
+  wt_input_ticket_numbers: "eller legg inn saksnummer"
+  wt_delete_closed_tickets: "Slett lukkede poster"
+  wt_data_download: "Last ned data"
+  wt_data_download_with_act: "Last ned data(each activity)"
+  wt_opt_disp_ticket_with_closed: "[vis lukkede saker]"
+  wt_opt_disp_ticket_opened_only: "[vis bare åpne saker]"
+  wt_opt_disp_ticket_with_other_member: "[vis andre brukeres saker]"
+  wt_opt_disp_ticket_mine_only: "[vis bare mine saker]"
+  wt_bulkupdate_relations: "Samle alle til sin forelder"
+  permission_view_work_time_tab: "Vise arbeidstime-fane"
+  permission_view_work_time_other_member: "Vise andres arbeidstimer"
+  permission_edit_work_time_total: "Endre oppsummerte arbeidstimer"
+  permission_edit_work_time_other_member: "Endre andres arbeidstimer"
+  wt_saved_value: "Lagret timer: "
+  wt_legend: "Nøkkel"
+  wt_style_default: "Forvalgt"
+  wt_style_assigned: "Tildelt"
+  wt_style_worked: "Jobbet med"
+  wt_style__assigned_worked: "Tildelt og jobbet med"
+  wt_style_overdue: "Over tidsfrist"
+  wt_style_assigned_overdue: "Tildelt og over tidsfrist"
+  wt_style_overdue_worked: "Over tidsfrist og jobbet med"
+  wt_style_assigned_overdue_worked: "Tildelt, over tidsfrist og jobbet med"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/po.yml b/plugins/redmine_work_time/config/locales/po.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0fab351d1fccb6471ef12f5b8136b03207a8139c
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/po.yml
@@ -0,0 +1,48 @@
+po:
+  work_time: "WorkTime"
+  wt_update: "Update"
+  wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
+  wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
+  wt_monthly_report: "Monthly Report"
+  wt_daily_report: "Daily Report"
+  wt_each_member_report: "Member Report"
+  wt_raw_total: "Monthly Report(not relayed)"
+  wt_relay_total: "Rapport mensuel (relayed)"
+  wt_edit_relay: "Ticket Relay Editor"
+  wt_ticket: "Ticket"
+  wt_add_ticket: "Add ticket"
+  wt_no_permission: "No permission"
+  wt_loop_relay: "Ticket relation looped"
+  wt_set_holiday: "set holiday on this date"
+  wt_del_holiday: "del holiday on this date"
+  wt_apply_checked: "apply all checked"
+  wt_select_project: "Restrict project..."
+  wt_select_user: "Select user..."
+  wt_edit_memo: "Edit memo"
+  wt_pre_memo: "pre. memo"
+  wt_next_memo: "next memo"
+  wt_data_list: "Data list"
+  wt_input_ticket_numbers: "or input ticket numbers"
+  wt_delete_closed_tickets: "delete closed tickets"
+  wt_data_download: "data download"
+  wt_data_download_with_act: "data download(each activity)"
+  wt_opt_disp_ticket_with_closed: "[display closed ticket]"
+  wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
+  wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
+  wt_opt_disp_ticket_mine_only: "[display my ticket only]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "View Work time tab"
+  permission_view_work_time_other_member: "View other members' work time"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Edit other members' work time"
+  wt_saved_value: "Saved timed: "
+  wt_legend: "Legend"
+  wt_style_default: "By default"
+  wt_style_assigned: "Assigned"
+  wt_style_worked: "Worked on"
+  wt_style__assigned_worked: "Assigned and worked on"
+  wt_style_overdue: "Overdue"
+  wt_style_assigned_overdue: "Assigned and overdue"
+  wt_style_overdue_worked: "Overdue and worked on"
+  wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/pt-BR.yml b/plugins/redmine_work_time/config/locales/pt-BR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b097f3de677d4e7c0c833872b683021d39a41b8c
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/pt-BR.yml
@@ -0,0 +1,49 @@
+pt-BR:
+  work_time: "Agenda"
+  wt_update: "Atualização"
+  wt_month_names: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez"
+  wt_week_day_names: "Dom,Seg,Ter,Qua,Qui,Sex,Sab"
+  wt_monthly_report: "Relatório Mensal"
+  wt_daily_report: "Relatório Diário"
+  wt_each_member_report: "Relatório Membro"
+  wt_raw_total: "Relatório Mensal(não reenviado)"
+  wt_edit_relay: "Editor de etarefas enviada"
+  wt_relay_total: "Relatório mensal de tarefas reenviadas"
+  wt_ticket: "Tarefa"
+  wt_add_ticket: "Nova tarefa"
+  wt_no_permission: "Sem permissão"
+  wt_no_permission_del: "Não é possível excluir porque há pedidos de horas-homem"
+  wt_loop_relay: "Ticket relation looped"
+  wt_set_holiday: "define feriado nesta data"
+  wt_del_holiday: "define data normal"
+  wt_apply_checked: "Aplicar selecionados"
+  wt_select_project: "Selecione projeto..."
+  wt_select_user: "Selecione usuário..."
+  wt_edit_memo: "Edita anotação"
+  wt_pre_memo: "prev anotação"
+  wt_next_memo: "prox anotação"
+  wt_data_list: "Lista de data"
+  wt_input_ticket_numbers: "ou inclua o número da tarefa"
+  wt_delete_closed_tickets: "apagar tarefas fechadas"
+  wt_data_download: "download de dados"
+  wt_data_download_with_act: "download de dados(each activity)"
+  wt_opt_disp_ticket_with_closed: "[exibe tarefa fechada]"
+  wt_opt_disp_ticket_opened_only: "[exibe somente tafera aberta]"
+  wt_opt_disp_ticket_with_other_member: "[exibe tarefa de outros membros]"
+  wt_opt_disp_ticket_mine_only: "[existe somente minha tarefa]"
+  wt_bulkupdate_relations: "bulkupdate to parent"
+  permission_view_work_time_tab: "Ver a tab tempo de trabalho"
+  permission_view_work_time_other_member: "Ver o tempo de trabalho de outros membros"
+  permission_edit_work_time_total: "Edit work time totals"
+  permission_edit_work_time_other_member: "Editar o tempo de trabalho de outros membros"
+  wt_saved_value: "Tempo salvo: "
+  wt_legend: "Legenda"
+  wt_style_default: "Por padrão"
+  wt_style_assigned: "Atribuído"
+  wt_style_worked: "Trabalhando em"
+  wt_style__assigned_worked: "Atrasado e trabalhando em"
+  wt_style_overdue: "Atrasada"
+  wt_style_assigned_overdue: "Atribuído e em atraso"
+  wt_style_overdue_worked: "Atrasada e trabalhado"
+  wt_style_assigned_overdue_worked: "Atribuída, atrasada e trabalhando em"
+  wt_account_start_day: "Empresa começar dia"
diff --git a/plugins/redmine_work_time/config/locales/ru.yml b/plugins/redmine_work_time/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..35dbb1f8bc43afca432c5caf2667b24131ef6989
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/ru.yml
@@ -0,0 +1,48 @@
+ru:
+  work_time: "Рабочий отчёт"
+  wt_update: "Обновить"
+  wt_month_names: "Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек"
+  wt_week_day_names: "Вск,Пон,Втр,Срд,Чет,Пят,Суб"
+  wt_monthly_report: "Ежемесячный отчёт"
+  wt_daily_report: "Ежедневный отчёт"
+  wt_each_member_report: "Отчёт по пользователям"
+  wt_raw_total: "Отчёт за месяц (не связанный)"
+  wt_relay_total: "Отчёт за месяц (связанный)"
+  wt_edit_relay: "Редактор связей задач"
+  wt_ticket: "Задача"
+  wt_add_ticket: "Добавить задачу"
+  wt_no_permission: "Ограничение прав"
+  wt_loop_relay: "Взаимосвязь задач"
+  wt_set_holiday: "установить выходной на дату"
+  wt_del_holiday: "удалить выходной на дату"
+  wt_apply_checked: "применить все отмеченные"
+  wt_select_project: "Отфильтровать по проектам..."
+  wt_select_user: "Выбор пользователя..."
+  wt_edit_memo: "Редактировать примечание"
+  wt_pre_memo: "предыдущее примечание"
+  wt_next_memo: "следующе примечание"
+  wt_data_list: "Список дат"
+  wt_input_ticket_numbers: "или введите номера задач (через запятую)"
+  wt_delete_closed_tickets: "удалить закрытые задачи"
+  wt_data_download: "экспорт данных"
+  wt_data_download_with_act: "экспорт данных(each activity)"
+  wt_opt_disp_ticket_with_closed: "[отобразить закрытые задачи]"
+  wt_opt_disp_ticket_opened_only: "[отобразить только открытые задачи]"
+  wt_opt_disp_ticket_with_other_member: "[отобразить задачи других участников]"
+  wt_opt_disp_ticket_mine_only: "[отобразить только мои задачи]"
+  wt_bulkupdate_relations: "массовое обновление связанных"
+  permission_view_work_time_tab: "Просмотр закладки рабочего времени"
+  permission_view_work_time_other_member: "Просмотр рабочего времени других участников"
+  permission_edit_work_time_total: "Редактирование рабочего времени"
+  permission_edit_work_time_other_member: "Редактирование рабочего времени других участников"
+  wt_saved_value: "Сохранённое время: "
+  wt_legend: "Соответствие цветов"
+  wt_style_default: "По умолчанию"
+  wt_style_assigned: "Назначенные"
+  wt_style_worked: "В работе"
+  wt_style__assigned_worked: "Назначенные и в работе"
+  wt_style_overdue: "Просроченные"
+  wt_style_assigned_overdue: "Назначенные и просроченные"
+  wt_style_overdue_worked: "Просроченные и в работе"
+  wt_style_assigned_overdue_worked: "Назначенные, в работе и просроченные"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/tr.yml b/plugins/redmine_work_time/config/locales/tr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bff0ae8834033ccbba5c674080bec46db5ce24ef
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/tr.yml
@@ -0,0 +1,48 @@
+tr:
+  work_time: "ÇalışmaZamanı"
+  wt_update: "Güncelle"
+  wt_month_names: "Oca,Åžub,Mar,Nis,May,Haz,Tem,AÄŸu,Eyl,Eki,Kas,Ara"
+  wt_week_day_names: "Paz,Pzt,Sal,Çar,Per,Cum,Cmt"
+  wt_monthly_report: "Aylık Rapor"
+  wt_daily_report: "Günlük Rapor"
+  wt_each_member_report: "Kullanıcı Raporu"
+  wt_raw_total: "Aylık Rapor(aktarımsız)"
+  wt_edit_relay: "İş Aktarım Düzenleyici"
+  wt_relay_total: "Aktarılmış İş Aylık Rapor"
+  wt_ticket: "Ä°ÅŸ"
+  wt_add_ticket: "Ä°ÅŸ ekle"
+  wt_no_permission: "Yetki yok"
+  wt_loop_relay: "İş ilişkileri dögülü"
+  wt_set_holiday: "bu tarihe tatil koy"
+  wt_del_holiday: "bu tarihteki tatili sil"
+  wt_apply_checked: "seçililere uygula"
+  wt_select_project: "Birim seç..."
+  wt_select_user: "Kullanıcı seç..."
+  wt_edit_memo: "Not düzenle"
+  wt_pre_memo: "önceki not"
+  wt_next_memo: "sonraki not"
+  wt_data_list: "Veri Liste"
+  wt_input_ticket_numbers: "veya iş numarası gir"
+  wt_delete_closed_tickets: "kapalı işleri sil"
+  wt_data_download: "veri indir"
+  wt_data_download_with_act: "veri indir(each activity)"
+  wt_opt_disp_ticket_with_closed: "[kapalı işleri göster]"
+  wt_opt_disp_ticket_opened_only: "[sadece açık işleri göster]"
+  wt_opt_disp_ticket_with_other_member: "[başkalarının işlerini göster]"
+  wt_opt_disp_ticket_mine_only: "[sadece kendi işlerimi göster]"
+  wt_bulkupdate_relations: "üst işi toplu güncelle"
+  permission_view_work_time_tab: "ÇalışmaZamanı sekmesini gör"
+  permission_view_work_time_other_member: "Başka kişilerin çalışma zamanını gör"
+  permission_edit_work_time_total: "Çalışma zamanı toplamlarını düzenle"
+  permission_edit_work_time_other_member: "Başka kiçinin çalışma zamanını düzenle"
+  wt_saved_value: "Saklanmış değer: "
+  wt_legend: "Ä°ÅŸaretler"
+  wt_style_default: "Öntanımlı"
+  wt_style_assigned: "Atanmış"
+  wt_style_worked: "Çalışılmış"
+  wt_style__assigned_worked: "Atanmış ve çalışılmış"
+  wt_style_overdue: "Zamanı geçmiş"
+  wt_style_assigned_overdue: "Atanmış ve zamanı geçmiş"
+  wt_style_overdue_worked: "Zamanı geçmiş ve çalışılmış"
+  wt_style_assigned_overdue_worked: "Atanmış, zamanı geçmiş, çalışılmış"
+  wt_account_start_day: "Company start day"
diff --git a/plugins/redmine_work_time/config/locales/zh.yml b/plugins/redmine_work_time/config/locales/zh.yml
new file mode 100755
index 0000000000000000000000000000000000000000..6fc4f9c76239423cf2662fdd2b769ce46a51e313
--- /dev/null
+++ b/plugins/redmine_work_time/config/locales/zh.yml
@@ -0,0 +1,48 @@
+zh:
+  work_time: "工时日志"
+  wt_update: "æ›´æ–°"
+  wt_month_names: "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月"
+  wt_week_day_names: "周日,周一,周二,周三,周四,周五,周六"
+  wt_monthly_report: "月度报告"
+  wt_daily_report: "日报告"
+  wt_each_member_report: "成员报告"
+  wt_raw_total: "月度报告(not relayed)"
+  wt_relay_total: "月度报告(relayed)"
+  wt_edit_relay: "工作 Relay 编辑器"
+  wt_ticket: "工作"
+  wt_add_ticket: "新增工作"
+  wt_no_permission: "无权限"
+  wt_loop_relay: "工作关系循环"
+  wt_set_holiday: "设置当前日期为假期"
+  wt_del_holiday: "设置当期日期为工作日"
+  wt_apply_checked: "应用所有已选项"
+  wt_select_project: "选择项目..."
+  wt_select_user: "选择用户..."
+  wt_edit_memo: "编辑备忘录"
+  wt_pre_memo: "前一备忘录"
+  wt_next_memo: "后一备忘录"
+  wt_data_list: "数据清单"
+  wt_input_ticket_numbers: "请输入编号"
+  wt_delete_closed_tickets: "删除已关闭的工作"
+  wt_data_download: "数据下载"
+  wt_data_download_with_act: "数据下载(按活动)"
+  wt_opt_disp_ticket_with_closed: "[显示已关闭的工作]"
+  wt_opt_disp_ticket_opened_only: "[只显示打开的工作]"
+  wt_opt_disp_ticket_with_other_member: "[显示其他成员的工作]"
+  wt_opt_disp_ticket_mine_only: "[只显示我的工作]"
+  wt_bulkupdate_relations: "批量更新到上一级"
+  permission_view_work_time_tab: "查看工时日志"
+  permission_view_work_time_other_member: "查看其他成员的工时日志"
+  permission_edit_work_time_total: "编辑工时日志总计"
+  permission_edit_work_time_other_member: "编辑其他成员的工时日志"
+  wt_saved_value: "已节省: "
+  wt_legend: "图例"
+  wt_style_default: "按默认"
+  wt_style_assigned: "已分配"
+  wt_style_worked: "已处理"
+  wt_style__assigned_worked: "已分配并已耗费"
+  wt_style_overdue: "逾期"
+  wt_style_assigned_overdue: "已分配并已逾期"
+  wt_style_overdue_worked: "已逾期并已耗费"
+  wt_style_assigned_overdue_worked: "已分配、已逾期并已耗费"
+  wt_account_start_day: "统计开始日"
diff --git a/plugins/redmine_work_time/config/routes.rb b/plugins/redmine_work_time/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ba5af123dc3ca3a4207d8aebfcbfc3c279347b7
--- /dev/null
+++ b/plugins/redmine_work_time/config/routes.rb
@@ -0,0 +1,4 @@
+RedmineApp::Application.routes.draw do
+  match 'work_time/:action', :to => 'work_time#index', :via => [:get, :post]
+  match 'work_time/:action/:id', :to => 'work_time#show', :via => [:get, :post]
+end
diff --git a/plugins/redmine_work_time/db/migrate/001_create_user_issue_months.rb b/plugins/redmine_work_time/db/migrate/001_create_user_issue_months.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19780cb0e4dc2b88853b687aca4adc123346f1c8
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/001_create_user_issue_months.rb
@@ -0,0 +1,14 @@
+class CreateUserIssueMonths < ActiveRecord::Migration
+  def self.up
+    create_table :user_issue_months do |t|
+      t.column :uid, :integer
+      t.column :issue, :integer
+      t.column :month, :string
+      t.column :odr, :integer
+    end
+  end
+
+  def self.down
+    drop_table :user_issue_months
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/002_create_wt_member_orders.rb b/plugins/redmine_work_time/db/migrate/002_create_wt_member_orders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a4884c80a558db50bdb964d70638a52d6ebc7eb
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/002_create_wt_member_orders.rb
@@ -0,0 +1,12 @@
+class CreateWtMemberOrders < ActiveRecord::Migration
+  def self.up
+    create_table :wt_member_orders do |t|
+      t.column :user_id, :integer
+      t.column :position, :integer
+    end
+  end
+
+  def self.down
+    drop_table :wt_member_orders
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/003_create_wt_ticket_relays.rb b/plugins/redmine_work_time/db/migrate/003_create_wt_ticket_relays.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84099ad339525287c131b08c4e43d412ce463198
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/003_create_wt_ticket_relays.rb
@@ -0,0 +1,13 @@
+class CreateWtTicketRelays < ActiveRecord::Migration
+  def self.up
+    create_table :wt_ticket_relays do |t|
+      t.column :issue_id, :integer
+      t.column :position, :integer
+      t.column :parent, :integer
+    end
+  end
+
+  def self.down
+    drop_table :wt_ticket_relays
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/004_add_prj_to_mem_odr.rb b/plugins/redmine_work_time/db/migrate/004_add_prj_to_mem_odr.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb14beeb258ce96bbf5f6cdd9ee034f11e58b3d3
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/004_add_prj_to_mem_odr.rb
@@ -0,0 +1,9 @@
+class AddPrjToMemOdr < ActiveRecord::Migration
+  def self.up
+    add_column :wt_member_orders, :prj_id, :integer, :default => nil
+  end
+
+  def self.down
+    remove_column :wt_member_orders, :prj_id
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/005_create_wt_daily_memos.rb b/plugins/redmine_work_time/db/migrate/005_create_wt_daily_memos.rb
new file mode 100644
index 0000000000000000000000000000000000000000..db53882fe42fbc6b1bae594656d0ce54f05d0cce
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/005_create_wt_daily_memos.rb
@@ -0,0 +1,15 @@
+class CreateWtDailyMemos < ActiveRecord::Migration
+  def self.up
+    create_table :wt_daily_memos do |t|
+      t.column :day, :date
+      t.column :user_id, :integer
+      t.column :created_on, :timestamp
+      t.column :updated_on, :timestamp
+      t.column :description, :text
+    end
+  end
+
+  def self.down
+    drop_table :wt_daily_memos
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/006_create_wt_project_orders.rb b/plugins/redmine_work_time/db/migrate/006_create_wt_project_orders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fe65846eacf90f6251a5d5d8dae4c725260c6568
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/006_create_wt_project_orders.rb
@@ -0,0 +1,14 @@
+class CreateWtProjectOrders < ActiveRecord::Migration
+  def self.up
+    create_table :wt_project_orders do |t|
+      t.column :prj, :integer
+      t.column :uid, :integer
+      t.column :dsp_prj, :integer
+      t.column :dsp_pos, :integer
+    end
+  end
+
+  def self.down
+    drop_table :wt_project_orders
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/007_create_wt_holidays.rb b/plugins/redmine_work_time/db/migrate/007_create_wt_holidays.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a9537f990955bd4bf06ecb04f02b7525ed9a83b4
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/007_create_wt_holidays.rb
@@ -0,0 +1,15 @@
+class CreateWtHolidays < ActiveRecord::Migration
+  def self.up
+    create_table :wt_holidays do |t|
+      t.column :holiday, :date
+      t.column :created_on, :datetime
+      t.column :created_by, :integer
+      t.column :deleted_on, :datetime
+      t.column :deleted_by, :integer
+    end
+  end
+
+  def self.down
+    drop_table :wt_holidays
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/008_remove_month_from_user_issue_month.rb b/plugins/redmine_work_time/db/migrate/008_remove_month_from_user_issue_month.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33559ce7767069c072262be586256fdd4e70d45b
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/008_remove_month_from_user_issue_month.rb
@@ -0,0 +1,9 @@
+class RemoveMonthFromUserIssueMonth < ActiveRecord::Migration
+  def self.up
+    remove_column :user_issue_months, :month
+  end
+
+  def self.down
+    add_column :user_issue_months, :month, :string, :default => nil
+  end
+end
diff --git a/plugins/redmine_work_time/db/migrate/009_remove_prj_from_wt_project_orders.rb b/plugins/redmine_work_time/db/migrate/009_remove_prj_from_wt_project_orders.rb
new file mode 100644
index 0000000000000000000000000000000000000000..318bffcca698efe252ba36b272885a15b5d6fcfd
--- /dev/null
+++ b/plugins/redmine_work_time/db/migrate/009_remove_prj_from_wt_project_orders.rb
@@ -0,0 +1,9 @@
+class RemovePrjFromWtProjectOrders < ActiveRecord::Migration
+  def self.up
+    remove_column :wt_project_orders, :prj
+  end
+
+  def self.down
+    add_column :wt_project_orders, :prj, :integer, :default => nil
+  end
+end
diff --git a/plugins/redmine_work_time/init.rb b/plugins/redmine_work_time/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5d1caf665dcbbf82247adf5aab95742e1e9e0d45
--- /dev/null
+++ b/plugins/redmine_work_time/init.rb
@@ -0,0 +1,47 @@
+require 'redmine'
+
+Redmine::Plugin.register :redmine_work_time do
+  name 'Redmine Work Time plugin'
+  author 'Tomohisa Kusukawa'
+  description 'A plugin to view and update TimeEntry by each user'
+  version '0.3.4'
+  url 'http://www.redmine.org/plugins/redmine_work_time'
+  author_url 'http://about.me/tkusukawa'
+  
+  project_module :work_time do
+    permission :view_work_time_tab, {:work_time =>
+            [:show,:member_monthly_data,
+             :total,:total_data,:edit_relay,:relay_total,:relay_total_data,
+             :total_data_with_act, :relay_total_data_with_act,
+             :register_project_settings,
+            ]}
+    permission :view_work_time_other_member, {:work_time =>
+            [:show,:member_monthly_data,
+             :total,:total_data,:edit_relay,:relay_total,:relay_total_data,
+             :total_data_with_act, :relay_total_data_with_act,
+             :register_project_settings,
+            ]}
+    permission :edit_work_time_total, {}
+    permission :edit_work_time_other_member, {}
+  end
+
+  menu :account_menu, :work_time,
+    {:controller => 'work_time', :action => 'index'},
+    :before => :my_account,
+    :caption => :work_time,
+    :if => Proc.new{User.current.logged? && Setting.plugin_redmine_work_time['show_account_menu']}
+
+  menu :project_menu, :work_time,
+    {:controller => 'work_time', :action => 'show'}, :caption => :work_time,
+    :after => :gantt
+
+  settings :default => {'account_start_days' => {}, 'show_account_menu' => 'true'},
+           :partial => 'settings/work_time_settings'
+
+  Rails.configuration.to_prepare do
+    require_dependency 'projects_helper'
+    unless ProjectsHelper.included_modules.include? WorkTimeProjectsHelperPatch
+      ProjectsHelper.send(:include, WorkTimeProjectsHelperPatch)
+    end
+  end
+end
diff --git a/plugins/redmine_work_time/lib/work_time_projects_helper_patch.rb b/plugins/redmine_work_time/lib/work_time_projects_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6157b27eb04de0d785b8dbf69f7d19f4c54a764b
--- /dev/null
+++ b/plugins/redmine_work_time/lib/work_time_projects_helper_patch.rb
@@ -0,0 +1,22 @@
+require_dependency 'projects_helper'
+
+module WorkTimeProjectsHelperPatch
+  def self.included base # :nodoc:
+    base.send :include, ProjectsHelperMethodsWorkTime
+    base.class_eval do
+      alias_method_chain :project_settings_tabs, :work_time
+    end
+  end
+end
+
+module ProjectsHelperMethodsWorkTime
+  def project_settings_tabs_with_work_time
+    tabs = project_settings_tabs_without_work_time
+    action = {:name => 'work_time',
+      :controller => 'work_time',
+      :action => :show, 
+      :partial => 'settings/work_time_project_settings', :label => :work_time}
+    tabs << action if User.current.allowed_to?(:edit_work_time_total, @project)
+    tabs
+  end
+end
diff --git a/plugins/redmine_work_time/memo.txt b/plugins/redmine_work_time/memo.txt
new file mode 100644
index 0000000000000000000000000000000000000000..86799ae050a67316592129d191464697c09ff16b
--- /dev/null
+++ b/plugins/redmine_work_time/memo.txt
@@ -0,0 +1,121 @@
+Redmineプラグイン(WorkTime) 作成覚書
+
+cd $RAILS_ROOT
+
+***プラグインの雛形生成***
+# ruby script/generate redmine_plugin work_time
+
+***バージョン管理に登録***
+# cd vendor/plugins/redmine_work_time
+# hg init
+# hg commit -A
+
+***init.rbの編集***
+# vi init.rb
+
+# cat init.rb
+require 'redmine'
+
+Redmine::Plugin.register :redmine_work_time do
+  name 'Redmine Work Time plugin'
+  author 'Tomohisa Kusukawa'
+  description 'A plugin to view and update TimeEntry by each user'
+  version '0.0.1'
+
+  project_module :work_time do
+    permission :view_work_time_tab, {:work_time => [:show]}, :public => true
+  end
+
+  menu :project_menu, :work_time, {:controller => 'work_time', :action => 'show'}, :caption => :work_time
+end
+
+*** モデル(DBテーブル)の作成 ***
+
+# ruby script/generate redmine_plugin_model work_time user_issue_month uid:integer issue:integer month:string odr:integer
+      exists  app/models/
+      create  test/unit/
+      create  test/fixtures/
+      create  app/models/user_issue_month.rb
+      create  test/unit/user_issue_month_test.rb
+      create  test/fixtures/user_issue_months.yml
+      exists  db/migrate
+      create  db/migrate/20090104004624_create_user_issue_months.rb
+# mv db/migrate/20090104004624_create_user_issue_months.rb db/migrate/001_create_user_issue_months.rb
+
+# ruby script/generate redmine_plugin_model work_time wt_member_order user_id:integer position:integer
+      exists  app/models/
+      exists  test/unit/
+      exists  test/fixtures/
+      create  app/models/wt_member_order.rb
+      create  test/unit/wt_member_order_test.rb
+      create  test/fixtures/wt_member_orders.yml
+      exists  db/migrate
+      create  db/migrate/20090131105945_create_wt_member_orders.rb
+# mv db/migrate/20090131105945_create_wt_member_orders.rb db/migrate/002_create_wt_member_orders.rb
+
+# ruby script/generate redmine_plugin_model work_time wt_ticket_relay issue_id:integer position:integer parent:integer
+      exists  app/models/
+      exists  test/unit/
+      exists  test/fixtures/
+      create  app/models/wt_ticket_relay.rb
+      create  test/unit/wt_ticket_relay_test.rb
+      create  test/fixtures/wt_ticket_relays.yml
+      exists  db/migrate
+      create  db/migrate/20090131110021_create_wt_ticket_relays.rb
+# mv db/migrate/20090131110021_create_wt_ticket_relays.rb db/migrate/003_create_wt_ticket_relays.rb
+
+# vi db/migrate/004_add_prj_to_mem_odr.rb
+
+# ruby script/generate redmine_plugin_model work_time wt_daily_memo day:date user_id:integer created_on:timestamp updated_on:timestamp description:text
+      exists  app/models/
+      exists  test/unit/
+      exists  test/fixtures/
+      create  app/models/wt_daily_memo.rb
+      create  test/unit/wt_daily_memo_test.rb
+      create  test/fixtures/wt_daily_memos.yml
+      exists  db/migrate
+      create  db/migrate/20090221151021_create_wt_daily_memos.rb
+# mv db/migrate/20090221151021_create_wt_daily_memos.rb db/migrate/005_create_wt_daily_memos.rb
+
+# rake db:migrate_plugins RAILS_ENV=production
+
+*** コントローラの作成 ***
+
+# ruby script/generate redmine_plugin_controller work_time work_time show
+      exists  app/controllers/
+      exists  app/helpers/
+      create  app/views/work_time
+      create  test/functional/
+      create  app/controllers/work_time_controller.rb
+      create  test/functional/work_time_controller_test.rb
+      create  app/helpers/work_time_helper.rb
+      create  app/views/work_time/show.html.erb
+
+# cd $RAILS_ROOT/vendor/plugins/redmine_work_time/app/controllers/
+# vi work_time_controller.rb
+
+*** ビューの編集 ***
+
+# cd $RAILS_ROOT/vendor/plugins/redmine_work_time/app/view/work_time/
+# vi show.html.erb
+
+######################################################################### version 0.0.33
+ユーザーの月毎表示におけるチケットトータル工数の表示を左側に変更
+
+######################################################################### version 0.0.34
+ユーザ毎の工数表示をプロジェクト毎にまとめて表示するように変更
+# cd $RAILS_ROOT
+# ruby script/generate redmine_plugin_model work_time wt_project_orders prj:integer uid:integer dsp_prj:integer dsp_pos:integer
+      exists  app/models/
+      exists  test/unit/
+      exists  test/fixtures/
+      create  app/models/wt_project_orders.rb
+      create  test/unit/wt_project_orders_test.rb
+      create  test/fixtures/wt_project_orders.yml
+      exists  db/migrate
+      create  db/migrate/20090531095136_create_wt_project_orders.rb
+# cd vendor/plugins/redmine_work_time/db/migrate/
+# mv 20090531095136_create_wt_project_orders.rb 006_create_wt_project_orders.rb
+# cd $RAILS_ROOT
+# rake db:migrate_plugins RAILS_ENV=production
+
diff --git a/plugins/redmine_work_time/test/fixtures/user_issue_months.yml b/plugins/redmine_work_time/test/fixtures/user_issue_months.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1ce8467a098298f6b04a7ab1391b418ae4e659c1
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/user_issue_months.yml
@@ -0,0 +1,13 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  uid: 1
+  issue: 1
+  month: MyString
+  odr: 1
+two:
+  id: 2
+  uid: 1
+  issue: 1
+  month: MyString
+  odr: 1
diff --git a/plugins/redmine_work_time/test/fixtures/wt_daily_memos.yml b/plugins/redmine_work_time/test/fixtures/wt_daily_memos.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f27ad512b70025fc08534f6f5c142e6baff94b11
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/wt_daily_memos.yml
@@ -0,0 +1,15 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  day: 2009-02-22
+  user_id: 1
+  created_on: 2009-02-22 00:10:21
+  updated_on: 2009-02-22 00:10:21
+  description: MyText
+two:
+  id: 2
+  day: 2009-02-22
+  user_id: 1
+  created_on: 2009-02-22 00:10:21
+  updated_on: 2009-02-22 00:10:21
+  description: MyText
diff --git a/plugins/redmine_work_time/test/fixtures/wt_holidays.yml b/plugins/redmine_work_time/test/fixtures/wt_holidays.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c21da4de5e883dfa40dbbee6601f7eab497182e6
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/wt_holidays.yml
@@ -0,0 +1,15 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  holiday: 2009-09-12
+  created_on: 2009-09-12 18:48:19
+  created_by: 1
+  deleted_on: 2009-09-12 18:48:19
+  deleted_by: 1
+two:
+  id: 2
+  holiday: 2009-09-12
+  created_on: 2009-09-12 18:48:19
+  created_by: 1
+  deleted_on: 2009-09-12 18:48:19
+  deleted_by: 1
diff --git a/plugins/redmine_work_time/test/fixtures/wt_member_orders.yml b/plugins/redmine_work_time/test/fixtures/wt_member_orders.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c3f6c51561b385f424767e9f30cf62cdbfb757a8
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/wt_member_orders.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  user_id: 1
+  position: 1
+  prj_id: 1
+two:
+  id: 2
+  user_id: 1
+  position: 1
+  prj_id: 1
diff --git a/plugins/redmine_work_time/test/fixtures/wt_project_orders.yml b/plugins/redmine_work_time/test/fixtures/wt_project_orders.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c3a3c4615273b2c90f8e2d9bdee41bc6b9c444e
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/wt_project_orders.yml
@@ -0,0 +1,13 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  prj: 1
+  uid: 1
+  dsp_prj: 1
+  dsp_pos: 1
+two:
+  id: 2
+  prj: 1
+  uid: 1
+  dsp_prj: 1
+  dsp_pos: 1
diff --git a/plugins/redmine_work_time/test/fixtures/wt_ticket_relays.yml b/plugins/redmine_work_time/test/fixtures/wt_ticket_relays.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d9a2cb5ea8c483f1a56d1c16df804c0e9a9d887b
--- /dev/null
+++ b/plugins/redmine_work_time/test/fixtures/wt_ticket_relays.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  issue_id: 1
+  position: 1
+  parent: 1
+two:
+  id: 2
+  issue_id: 1
+  position: 1
+  parent: 1
diff --git a/plugins/redmine_work_time/test/functional/work_time_controller_test.rb b/plugins/redmine_work_time/test/functional/work_time_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f773c200ba5cc1a25e3ab04db2d8a9deae9962a3
--- /dev/null
+++ b/plugins/redmine_work_time/test/functional/work_time_controller_test.rb
@@ -0,0 +1,8 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WorkTimeControllerTest < ActionController::TestCase
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/test_helper.rb b/plugins/redmine_work_time/test/test_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd1ed0c5d90a72c90a28caf5d7aad1792a953356
--- /dev/null
+++ b/plugins/redmine_work_time/test/test_helper.rb
@@ -0,0 +1,5 @@
+# Load the normal Rails helper
+require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
+
+# Ensure that we are using the temporary fixture path
+Engines::Testing.set_fixture_path
diff --git a/plugins/redmine_work_time/test/unit/user_issue_month_test.rb b/plugins/redmine_work_time/test/unit/user_issue_month_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7bbd4b07e99b3d6e62537c467ebeff674b0b39c8
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/user_issue_month_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserIssueMonthTest < Test::Unit::TestCase
+  fixtures :user_issue_months
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/unit/wt_daily_memo_test.rb b/plugins/redmine_work_time/test/unit/wt_daily_memo_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..18d0a21e053a71dd83da90043eb84935c0031404
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/wt_daily_memo_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WtDailyMemoTest < Test::Unit::TestCase
+  fixtures :wt_daily_memos
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/unit/wt_holidays_test.rb b/plugins/redmine_work_time/test/unit/wt_holidays_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..869ce810eed28f936516d7b9ff74d739865696d1
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/wt_holidays_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WtHolidaysTest < Test::Unit::TestCase
+  fixtures :wt_holidays
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/unit/wt_member_order_test.rb b/plugins/redmine_work_time/test/unit/wt_member_order_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7487d8ed61c2f8daf976fc9279c2d9305d68b70e
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/wt_member_order_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WtMemberOrderTest < Test::Unit::TestCase
+  fixtures :wt_member_orders
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/unit/wt_project_orders_test.rb b/plugins/redmine_work_time/test/unit/wt_project_orders_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..660c622ce754a99b25d17132f204f3141fa4dff7
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/wt_project_orders_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WtProjectOrdersTest < Test::Unit::TestCase
+  fixtures :wt_project_orders
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmine_work_time/test/unit/wt_ticket_relay_test.rb b/plugins/redmine_work_time/test/unit/wt_ticket_relay_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ff70cc5059ada24c1c084863788f497d4c39a40f
--- /dev/null
+++ b/plugins/redmine_work_time/test/unit/wt_ticket_relay_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class WtTicketRelayTest < Test::Unit::TestCase
+  fixtures :wt_ticket_relays
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end
diff --git a/plugins/redmineup_tags/Gemfile b/plugins/redmineup_tags/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..c6b5df6354d30f2138757b8909c303344505b357
--- /dev/null
+++ b/plugins/redmineup_tags/Gemfile
@@ -0,0 +1 @@
+gem 'redmine_crm'
diff --git a/plugins/redmineup_tags/Gemfile.lock b/plugins/redmineup_tags/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..a328ca35e3e5755d50ac098e1c8ad880ac1321b3
--- /dev/null
+++ b/plugins/redmineup_tags/Gemfile.lock
@@ -0,0 +1,108 @@
+GEM
+  specs:
+    actionmailer (4.2.8)
+      actionpack (= 4.2.8)
+      actionview (= 4.2.8)
+      activejob (= 4.2.8)
+      mail (~> 2.5, >= 2.5.4)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+    actionpack (4.2.8)
+      actionview (= 4.2.8)
+      activesupport (= 4.2.8)
+      rack (~> 1.6)
+      rack-test (~> 0.6.2)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.2)
+    actionview (4.2.8)
+      activesupport (= 4.2.8)
+      builder (~> 3.1)
+      erubis (~> 2.7.0)
+      rails-dom-testing (~> 1.0, >= 1.0.5)
+      rails-html-sanitizer (~> 1.0, >= 1.0.3)
+    activejob (4.2.8)
+      activesupport (= 4.2.8)
+      globalid (>= 0.3.0)
+    activemodel (4.2.8)
+      activesupport (= 4.2.8)
+      builder (~> 3.1)
+    activerecord (4.2.8)
+      activemodel (= 4.2.8)
+      activesupport (= 4.2.8)
+      arel (~> 6.0)
+    activesupport (4.2.8)
+      i18n (~> 0.7)
+      minitest (~> 5.1)
+      thread_safe (~> 0.3, >= 0.3.4)
+      tzinfo (~> 1.1)
+    arel (6.0.4)
+    builder (3.2.3)
+    concurrent-ruby (1.1.3)
+    crass (1.0.4)
+    erubis (2.7.0)
+    globalid (0.4.1)
+      activesupport (>= 4.2.0)
+    i18n (0.7.0)
+    liquid (2.6.3)
+    loofah (2.2.3)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.5.9)
+    mail (2.6.6)
+      mime-types (>= 1.16, < 4)
+    mime-types (3.2.2)
+      mime-types-data (~> 3.2015)
+    mime-types-data (3.2018.0812)
+    mini_portile2 (2.3.0)
+    minitest (5.11.3)
+    nokogiri (1.8.5)
+      mini_portile2 (~> 2.3.0)
+    rack (1.6.11)
+    rack-test (0.6.3)
+      rack (>= 1.0)
+    rails (4.2.8)
+      actionmailer (= 4.2.8)
+      actionpack (= 4.2.8)
+      actionview (= 4.2.8)
+      activejob (= 4.2.8)
+      activemodel (= 4.2.8)
+      activerecord (= 4.2.8)
+      activesupport (= 4.2.8)
+      bundler (>= 1.3.0, < 2.0)
+      railties (= 4.2.8)
+      sprockets-rails
+    rails-deprecated_sanitizer (1.0.3)
+      activesupport (>= 4.2.0.alpha)
+    rails-dom-testing (1.0.9)
+      activesupport (>= 4.2.0, < 5.0)
+      nokogiri (~> 1.6)
+      rails-deprecated_sanitizer (>= 1.0.1)
+    rails-html-sanitizer (1.0.4)
+      loofah (~> 2.2, >= 2.2.2)
+    railties (4.2.8)
+      actionpack (= 4.2.8)
+      activesupport (= 4.2.8)
+      rake (>= 0.8.7)
+      thor (>= 0.18.1, < 2.0)
+    rake (12.3.1)
+    redmine_crm (0.0.42)
+      liquid (< 2.6.4)
+      rails
+    sprockets (3.7.2)
+      concurrent-ruby (~> 1.0)
+      rack (> 1, < 3)
+    sprockets-rails (3.2.1)
+      actionpack (>= 4.0)
+      activesupport (>= 4.0)
+      sprockets (>= 3.0.0)
+    thor (0.20.3)
+    thread_safe (0.3.6)
+    tzinfo (1.2.5)
+      thread_safe (~> 0.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  redmine_crm
+
+BUNDLED WITH
+   1.17.1
diff --git a/plugins/redmineup_tags/app/controllers/issue_tags_controller.rb b/plugins/redmineup_tags/app/controllers/issue_tags_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ed8cc7dd76375a0a630ba5a6b7f92a5a55a2eb2
--- /dev/null
+++ b/plugins/redmineup_tags/app/controllers/issue_tags_controller.rb
@@ -0,0 +1,58 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+class IssueTagsController < ApplicationController
+  unloadable
+
+  before_action :find_issues, :only => [:edit, :update]
+
+  def edit
+    return unless User.current.allowed_to?(:edit_tags, @projects.first)
+    @issue_ids = params[:ids]
+    @is_bulk_editing = @issue_ids.size > 1
+    @issue_tags = @is_bulk_editing ? [] : @issues.first.tag_list
+    @most_used_tags = Issue.all_tags(:sort_by => 'count', :order => 'DESC').limit(10)
+  end
+
+  def update
+    if User.current.allowed_to?(:edit_tags, @projects.first)
+      tags = params[:issue] && params[:issue][:tag_list] ? params[:issue][:tag_list].reject(&:empty?) : []
+
+      unless User.current.allowed_to?(:create_tags, @projects.first) || Issue.allowed_tags?(tags)
+        flash[:error] = t(:notice_failed_to_add_tags)
+        return
+      end
+
+      Issue.transaction do
+        @issues.each do |issue|
+          issue.tag_list = tags
+          issue.save!
+        end
+      end
+      flash[:notice] = t(:notice_tags_added)
+    else
+      flash[:error] = t(:notice_failed_to_add_tags)
+    end
+  rescue Exception => e
+    puts e
+    flash[:error] = t(:notice_failed_to_add_tags)
+  ensure
+    redirect_to_referer_or { render :text => 'Tags updated.', :layout => true }
+  end
+end
diff --git a/plugins/redmineup_tags/app/controllers/tags_controller.rb b/plugins/redmineup_tags/app/controllers/tags_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..485fd05d9d099a42d6346705cb4c0917ad1aa3ac
--- /dev/null
+++ b/plugins/redmineup_tags/app/controllers/tags_controller.rb
@@ -0,0 +1,89 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+class TagsController < ApplicationController
+  unloadable
+  before_action :require_admin
+  before_action :find_tag, :only => [:edit, :update]
+  before_action :bulk_find_tags, :only => [:context_menu, :merge, :destroy]
+
+  helper :issues_tags
+
+  def edit
+  end
+
+  def destroy
+    @tags.each do |tag|
+      begin
+        tag.reload.destroy
+      rescue ::ActiveRecord::RecordNotFound 
+      end
+    end
+
+    redirect_back_or_default(:controller => 'settings', :action => 'plugin', :id => 'redmineup_tags', :tab => 'manage_tags')
+  end
+
+  def update
+    @tag.name = params[:tag][:name] if params[:tag]
+    if @tag.save
+      flash[:notice] = l(:notice_successful_update)
+      respond_to do |format|
+        format.html { redirect_to :controller => 'settings', :action => 'plugin', :id => 'redmineup_tags', :tab => 'manage_tags' }
+        format.xml  {}
+      end
+    else
+      respond_to do |format|
+        format.html { render :action => 'edit' }
+      end
+    end
+  end
+
+  def context_menu
+    @tag = @tags.first if (@tags.size == 1)
+    @back = back_url
+    render :layout => false
+  end
+
+  def merge
+    if request.post? && params[:tag] && params[:tag][:name]
+      RedmineCrm::Tagging.transaction do
+        tag = RedmineCrm::Tag.find_by_name(params[:tag][:name]) || RedmineCrm::Tag.create(params[:tag])
+        RedmineCrm::Tagging.where(:tag_id => @tags.map(&:id)).update_all(:tag_id => tag.id)
+        @tags.select{|t| t.id != tag.id}.each{|t| t.destroy }
+        redirect_to :controller => 'settings', :action => 'plugin', :id => 'redmineup_tags', :tab => 'manage_tags'
+      end
+    end
+  end
+
+  private
+
+  def bulk_find_tags
+    @tags = RedmineCrm::Tag.joins("JOIN #{RedmineCrm::Tagging.table_name} ON #{RedmineCrm::Tagging.table_name}.tag_id = #{RedmineCrm::Tag.table_name}.id ").
+            select("#{RedmineCrm::Tag.table_name}.*, COUNT(DISTINCT #{RedmineCrm::Tagging.table_name}.taggable_id) AS count").
+            where(:id => params[:id] ? [params[:id]] : params[:ids]).
+            group("#{RedmineCrm::Tag.table_name}.id, #{RedmineCrm::Tag.table_name}.name")
+    raise ActiveRecord::RecordNotFound if @tags.empty?
+  end
+
+  def find_tag
+    @tag = RedmineCrm::Tag.find(params[:id])
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+end
diff --git a/plugins/redmineup_tags/app/helpers/issues_tags_helper.rb b/plugins/redmineup_tags/app/helpers/issues_tags_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83f526ed99a3d5bf80c95fc33007f7f7d7c4dae3
--- /dev/null
+++ b/plugins/redmineup_tags/app/helpers/issues_tags_helper.rb
@@ -0,0 +1,43 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module IssuesTagsHelper
+  def sidebar_tags
+    unless @sidebar_tags
+      @sidebar_tags = []
+      if :none != RedmineTags.settings['issues_sidebar'].to_sym
+        @sidebar_tags = Issue.available_tags({
+          :project => @project,
+          :open_only => (RedmineTags.settings['issues_open_only'].to_i == 1)
+        })
+      end
+    end
+    @sidebar_tags.to_a
+  end
+
+  def render_sidebar_tags
+    render_tags_list(sidebar_tags, {
+      :show_count => (RedmineTags.settings['issues_show_count'].to_i == 1),
+      :open_only => (RedmineTags.settings['issues_open_only'].to_i == 1),
+      :style => RedmineTags.settings['issues_sidebar'].to_sym
+    })
+  end
+end
diff --git a/plugins/redmineup_tags/app/helpers/tags_helper.rb b/plugins/redmineup_tags/app/helpers/tags_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fe09a64f8473bf7ae625261ab42ba20860822b9e
--- /dev/null
+++ b/plugins/redmineup_tags/app/helpers/tags_helper.rb
@@ -0,0 +1,132 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require 'digest/md5'
+
+module TagsHelper
+  include RedmineCrm::TagsHelper
+
+  def render_issue_tag_link(tag, options = {})
+    filters = [[:issue_tags, '=', tag.name]]
+    filters << [:status_id, 'o'] if options[:open_only]
+    if options[:use_search]
+      content =  link_to(tag, {:controller => "search", :action => "index", :id => @project, :q => tag.name, :wiki_pages => true, :issues => true})
+    else
+      content = link_to_issue_filter tag.name, filters, :project_id => @project
+    end
+    if options[:show_count]
+      content << content_tag('span', "(#{tag.count})", :class => 'tag-count')
+    end
+
+    style = RedmineTags.settings['issues_use_colors'].to_i > 0 ? {:class => "tag-label-color", :style => "background-color: #{tag_color(tag)}"} : {:class => "tag-label"}
+    content_tag('span', content, style)
+  end
+
+  def tag_color(tag)
+    tag_name = tag.respond_to?(:name) ? tag.name : tag
+    "##{Digest::MD5.hexdigest(tag_name)[0..5]}"
+  end
+
+  def render_tags_list(tags, options = {})
+    unless tags.nil? or tags.empty?
+      content, style = '', options.delete(:style)
+
+      tags = tags.all.to_a if tags.respond_to?(:all)
+
+      case sorting = "#{RedmineTags.settings['issues_sort_by']}:#{RedmineTags.settings['issues_sort_order']}"
+        when "name:asc";    tags.sort! { |a,b| a.name <=> b.name }
+        when "name:desc";   tags.sort! { |a,b| b.name <=> a.name }
+        when "count:asc";   tags.sort! { |a,b| a.count <=> b.count }
+        when "count:desc";  tags.sort! { |a,b| b.count <=> a.count }
+        # Unknown sorting option. Fallback to default one
+        else
+          logger.warn "[redmine_tags] Unknown sorting option: <#{sorting}>"
+          tags.sort! { |a,b| a.name <=> b.name }
+      end
+
+      if :list == style
+        list_el, item_el = 'ul', 'li'
+      elsif :simple_cloud == style
+        list_el, item_el = 'div', 'span'
+      elsif :cloud == style
+        list_el, item_el = 'div', 'span'
+      else
+        raise "Unknown list style"
+      end
+
+      content = content.html_safe
+      if :list == style && RedmineTags.settings['issues_sort_by'] == 'name'
+        tags.group_by { |tag| tag.name.downcase.first }.each do |letter, grouped_tags|
+          content << content_tag(item_el, letter.upcase, :class => 'letter', :style => '')
+          add_tags(style, grouped_tags, content, item_el, options)
+        end
+      else
+        add_tags(style, tags, content, item_el, options)
+      end
+
+      content_tag(list_el, content, :class => 'tags-cloud', :style => (:simple_cloud == style ? "text-align: left;" : ""))
+    end
+  end
+
+def link_to_issue_filter(title, filters, options = {})
+    options.merge! link_to_issue_filter_options(filters)
+    link_to title, options
+  end
+
+
+  # returns hash suitable for passing it to the <tt>to_link</tt>
+  # === parameters
+  # * <i>filters</i> = array of arrays. each child array is an array of strings:
+  #                    name, operator and value
+  # === example
+  # link_to 'foobar', link_to_issue_filter_options [[ :tags, '~', 'foobar' ]]
+  #
+  # filters = [[ :tags, '~', 'bazbaz' ], [:status_id, 'o']]
+  # link_to 'bazbaz', link_to_issue_filter_options filters
+  def link_to_issue_filter_options(filters)
+    options = {
+      :controller => 'issues',
+      :action => 'index',
+      :set_filter => 1,
+      :fields => [],
+      :values => {},
+      :operators => {}
+    }
+
+    filters.each do |f|
+      name, operator, value = f
+      options[:fields].push(name)
+      options[:operators][name] = operator
+      options[:values][name] = [value]
+    end
+
+    options
+  end
+
+  private
+
+  def add_tags(style, tags, content, item_el, options)
+    tag_cloud tags, (1..8).to_a do |tag, weight|
+      content << ' '.html_safe + content_tag(item_el, render_issue_tag_link(tag, options), :class => "tag-nube-#{weight}", :style => (:simple_cloud == style ? "font-size: 1em;" : "")) + " ".html_safe
+    end
+  end
+
+end
diff --git a/plugins/redmineup_tags/app/views/auto_completes/_redmine_tags.erb b/plugins/redmineup_tags/app/views/auto_completes/_redmine_tags.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6de3634c0ae22c3bc33c4ece9aa8d4be2869460a
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/auto_completes/_redmine_tags.erb
@@ -0,0 +1,6 @@
+<%= raw @redmine_tags.map { |redmine_tag| {
+        'id' => redmine_tag.name,
+        'text' => redmine_tag.name
+      }
+    }.to_json
+%>
diff --git a/plugins/redmineup_tags/app/views/auto_completes/_tag_list.html.erb b/plugins/redmineup_tags/app/views/auto_completes/_tag_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..262cb2f75020ea2c5f9642c04a9e9762eca1dcef
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/auto_completes/_tag_list.html.erb
@@ -0,0 +1,4 @@
+<%= raw @tags.collect {|tag| 
+       tag.name
+    }.to_json
+%>
diff --git a/plugins/redmineup_tags/app/views/auto_completes/_tag_list.json.erb b/plugins/redmineup_tags/app/views/auto_completes/_tag_list.json.erb
new file mode 100644
index 0000000000000000000000000000000000000000..262cb2f75020ea2c5f9642c04a9e9762eca1dcef
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/auto_completes/_tag_list.json.erb
@@ -0,0 +1,4 @@
+<%= raw @tags.collect {|tag| 
+       tag.name
+    }.to_json
+%>
diff --git a/plugins/redmineup_tags/app/views/context_menus/_issues_tags.html.erb b/plugins/redmineup_tags/app/views/context_menus/_issues_tags.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..53ac38646d872bb21be2fd6d83bb88dbad0926a3
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/context_menus/_issues_tags.html.erb
@@ -0,0 +1,11 @@
+<% if User.current.allowed_to?(:edit_tags, @project) %>
+  <li class="folder">
+    <a href="#" class="submenu"><%= l(:tags) %></a>
+    <ul>
+      <li><%= context_menu_link l(:button_add),
+                                edit_issue_tags_path(:ids => @issue_ids),
+                                :remote => true,
+                                :class => 'icon-add' %></li>
+    </ul>
+  </li>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/issue_tags/_edit_modal.html.erb b/plugins/redmineup_tags/app/views/issue_tags/_edit_modal.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..12c77a52d342d2931b3e000654d259450c3ccb2d
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/issue_tags/_edit_modal.html.erb
@@ -0,0 +1,41 @@
+<h3 class="title"><%= l(:label_add_tags) %></h3>
+
+<% if @is_bulk_editing %>
+  <h3><%= l(:label_bulk_edit_selected_issues) %></h3>
+  <ul>
+    <% @issues.each do |issue| %>
+        <%= content_tag 'li', link_to_issue(issue) %>
+    <% end %>
+  </ul>
+<% else %>
+    <h3><%= content_tag 'span', link_to_issue(@issues.first) %></h3>
+<% end %>
+
+
+<%= form_tag(update_issue_tags_path(:ids => @issue_ids),
+             :method => :post,
+             :id => 'edit-issue-tags-form') do %>
+
+    <fieldset class="box">
+      <legend><%= l(:tags) %></legend>
+      <div id="issue_tags">
+        <%= select2_tag 'issue[tag_list]',
+                        options_for_select(@issue_tags.map{ |tag| [tag, tag] }, @issue_tags),
+                        :multiple => true,
+                        :style => 'width: 100%;',
+                        :url => auto_complete_redmine_tags_path,
+                        :placeholder => @is_bulk_editing ? t(:label_no_change_option) : '+ add tag',
+                        :tags => User.current.allowed_to?(:create_tags, @project) %>
+      </div>
+
+      <p class="most_used_tags">
+        <%= safe_join(@most_used_tags.collect { |t| content_tag('span', t.name, :class => 'most_used_tag') }, ', ') %>
+      </p>
+      <%= javascript_tag "var mostUsedTags = #{@most_used_tags.map(&:name)}" %>
+    </fieldset>
+
+    <div class="buttons">
+      <%= submit_tag l(:button_add), :name => nil %>
+      <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
+    </div>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/issue_tags/edit.js.erb b/plugins/redmineup_tags/app/views/issue_tags/edit.js.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b2ae12774cb1d7f9c5c2ef064f3385fe001b13a4
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/issue_tags/edit.js.erb
@@ -0,0 +1,2 @@
+$('#ajax-modal').html('<%= escape_javascript(render :partial => 'issue_tags/edit_modal') %>');
+showModal('ajax-modal', '600px');
diff --git a/plugins/redmineup_tags/app/views/issues/._tags_sidebar.html.erb.swp b/plugins/redmineup_tags/app/views/issues/._tags_sidebar.html.erb.swp
new file mode 100644
index 0000000000000000000000000000000000000000..11569ee85b5237b593006f876dc13fe98a470e1e
Binary files /dev/null and b/plugins/redmineup_tags/app/views/issues/._tags_sidebar.html.erb.swp differ
diff --git a/plugins/redmineup_tags/app/views/issues/_tags.html.erb b/plugins/redmineup_tags/app/views/issues/_tags.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..79751339132d61a7a2e14f47bf3aec6a0f8245ac
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/issues/_tags.html.erb
@@ -0,0 +1,5 @@
+<% if issue.tag_list.present? %>
+  <%= issue_fields_rows do |rows|
+    rows.left l(:tags), safe_join(issue.tag_counts.collect{ |t| render_issue_tag_link(t, :show_count => false, :open_only => RedmineTags.settings['issues_open_only'].to_i > 0 ) }, RedmineTags.settings['issues_use_colors'].to_i > 0 ? ' ' : ', ')
+  end %>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/issues/_tags_form.html.erb b/plugins/redmineup_tags/app/views/issues/_tags_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..83730d6499ceb2706145e32ad2d70a1d6bcd6d1f
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/issues/_tags_form.html.erb
@@ -0,0 +1,23 @@
+<% if User.current.allowed_to?(:edit_tags, @project) %>
+  <div class="<%= 'splitcontentleft' if defined? form %>">
+    <p id="issue_tags">
+      <label><%= l(:tags) %></label>
+      <% text_field_options = {:label => :tags, :size => 60, :class => 'hol'} %>
+      <% tags = defined?(form) ? form.object.tag_list : [] %>
+      <% if defined? form || defined? issues %>
+        <%= select2_tag 'issue[tag_list]',
+                        options_for_select(tags.map{ |tag| [tag, tag] }, tags),
+                        :multiple => true,
+                        :include_hidden => (params[:action] != 'bulk_edit'),
+                        :style => 'width: 100%;',
+                        :url => auto_complete_redmine_tags_path,
+                        :placeholder => params[:action] == 'bulk_edit' ? t(:label_no_change_option) : '+ add tag',
+                        :tags => User.current.allowed_to?(:create_tags, @project) %>
+
+      <% else %>
+        <%= label_tag :tags, nil, :for => :issue_tag_list %>
+        <%= text_field_tag 'issue[tag_list]', "", :class => text_field_options[:class], :size => text_field_options[:size], :id => :issue_tag_list %>
+      <% end %>
+    </p>
+  </div>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/issues/_tags_sidebar.html.erb b/plugins/redmineup_tags/app/views/issues/_tags_sidebar.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..7d20add69dfc9803132b1e5ed8be4bd0098acb3c
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/issues/_tags_sidebar.html.erb
@@ -0,0 +1,8 @@
+<% if defined? sidebar_tags -%>
+<% unless sidebar_tags.empty? -%>
+<div class="sidebar-tags">
+  <h3><%= l(:tags) %></h3>
+  <%= render_sidebar_tags %>
+</div>
+<% end -%>
+<% end -%>
diff --git a/plugins/redmineup_tags/app/views/tags/_additional_assets.html.erb b/plugins/redmineup_tags/app/views/tags/_additional_assets.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..8b941e6495167f1865e34ad6fc220e0db2a3ca1f
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/_additional_assets.html.erb
@@ -0,0 +1,5 @@
+<% content_for :header_tags do %>
+  <%= stylesheet_link_tag(:redmine_tags, plugin: 'redmineup_tags') %>
+  <%= javascript_include_tag(:redmine_tags, plugin: 'redmineup_tags') %>
+  <%= select2_assets %>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/tags/_general.html.erb b/plugins/redmineup_tags/app/views/tags/_general.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..8f3c36e883867f27d242ff89d89182a7d3e7dcf4
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/_general.html.erb
@@ -0,0 +1,25 @@
+<p>
+  <%= label_tag 'settings[issues_sidebar]', l(:issues_sidebar) %>
+  <%= select_tag 'settings[issues_sidebar]', options_for_select(%w(none list cloud simple_cloud).collect{|v| [l("issue_tags_sidebar_#{v}"), v]}, @settings['issues_sidebar']) %>
+</p>
+<p>
+  <%= label_tag 'settings[issues_show_count]', l(:issues_show_count) %>
+  <%= check_box_tag 'settings[issues_show_count]', 1, 1 == @settings['issues_show_count'].to_i %>
+</p>
+<p>
+  <%= label_tag 'settings[issues_open_only]', l(:issues_open_only) %>
+  <%= check_box_tag 'settings[issues_open_only]', 1, 1 == @settings['issues_open_only'].to_i %>
+</p>
+<p>
+  <%= label_tag 'settings[issues_sort_by]', l(:issues_sort_by) %>
+  <%= select_tag 'settings[issues_sort_by]', options_for_select(%w(name count).collect{|v| [l("issues_sort_by_#{v}"), v]}, @settings['issues_sort_by']) %>
+  <%= select_tag 'settings[issues_sort_order]', options_for_select(%w(asc desc).collect{|v| [l("issues_sort_order_#{v}"), v]}, @settings['issues_sort_order']) %>
+</p>
+<p>
+  <%= label_tag 'settings[tags_suggestion_order]', l(:tags_suggestion_order) %>
+  <%= select_tag 'settings[tags_suggestion_order]', options_for_select(%w(name last_created most_used).collect{|v| [l("tags_order_by_#{v}"), v]}, @settings['tags_suggestion_order']) %>
+</p>
+<p>
+  <%= label_tag 'settings[issues_use_colors]', l(:issues_use_colors) %>
+  <%= check_box_tag 'settings[issues_use_colors]', 1, 1 == @settings['issues_use_colors'].to_i %>
+</p>
diff --git a/plugins/redmineup_tags/app/views/tags/_manage_tags.html.erb b/plugins/redmineup_tags/app/views/tags/_manage_tags.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..d6d84ec64004a8f1a8e0748e77387a26f4afadab
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/_manage_tags.html.erb
@@ -0,0 +1,53 @@
+<% tags = Issue.all_tag_counts(:order => :name) %>
+
+<% unless tags.to_a.empty? %>
+<table class  = "list issues">
+  <thead>
+    <tr>
+      <th class="checkbox hide-when-print">
+        <%= link_to image_tag('toggle_check.png'), {},
+                              :onclick => 'toggleIssuesSelection(this); return false;',
+                              :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
+      </th>
+      <th><%= l(:field_name) %></th>
+      <th align="center" style="width:10%;"> </th>
+    </tr>
+  </thead>
+  <tbody>
+    <% tags.each do |tag| %>
+      <tr id="<%= tag.id %>" class="<%= cycle("odd", "even") %> hascontextmenu ">
+        <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", tag.id, false, :id => nil) %></td>
+        <td><%= render_issue_tag_link(tag) %></td>
+        <td class="buttons">
+          <%= link_to l(:button_edit), edit_tag_path(tag),
+                                         :class => 'icon icon-edit' %>
+
+          <%= link_to l(:button_delete), tags_path(:ids => tag),
+                                         :method => :delete,
+                                         :confirm => l(:text_are_you_sure),
+                                         :class => 'icon icon-del' %>
+          </td>
+
+      </tr>
+
+    <% end %>
+  </tbody>
+</table>
+<% if Redmine::VERSION.to_s < '3.4' && Redmine::VERSION::BRANCH == 'stable' %>
+  <%= context_menu tags_context_menu_path %>
+<% else %>
+  <% content_for :header_tags do
+    javascript_include_tag('context_menu') + stylesheet_link_tag('context_menu')
+  end %>
+
+  <%= javascript_tag do %>
+    $(document).ready(function(){
+      $('#settings form').data('cmUrl', '<%= tags_context_menu_path %>')
+    })
+  <% end %>
+<% end %>
+<% else %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% end %>
+
+
diff --git a/plugins/redmineup_tags/app/views/tags/_select2_transformation_rules.html.erb b/plugins/redmineup_tags/app/views/tags/_select2_transformation_rules.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0c83768492caeb82f528f91d542b9b33384de5b4
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/_select2_transformation_rules.html.erb
@@ -0,0 +1 @@
+<%= transform_to_select2('issue_tags', url: auto_complete_redmine_tags_url) %>
diff --git a/plugins/redmineup_tags/app/views/tags/_settings.html.erb b/plugins/redmineup_tags/app/views/tags/_settings.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..543f4e4cce2277b0ab227eb1ea30b815bf421855
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/_settings.html.erb
@@ -0,0 +1,8 @@
+<% tags_tabs = [{:name => 'general', :partial => 'tags/general', :label => :label_general},
+                {:name => 'manage_tags', :partial => 'tags/manage_tags', :label => :issue_tags_manage_tags}
+        ]   %>   
+
+
+<%= render_tabs tags_tabs %>
+
+<% html_title(l(:label_settings), l(:tags)) -%>  
diff --git a/plugins/redmineup_tags/app/views/tags/context_menu.html.erb b/plugins/redmineup_tags/app/views/tags/context_menu.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..dec5445c111ea10b5f8da81022cab2608d45f761
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/context_menu.html.erb
@@ -0,0 +1,12 @@
+<ul>
+  <% if @tag -%>
+    <li><%= link_to l(:button_edit), edit_tag_path(@tag),
+            :class => 'icon-edit' %></li>
+  <% else %>
+    <li><%= link_to l(:issue_tags_button_merge), merge_tags_path(:ids => @tags.collect(&:id)),
+            :class => 'icon-tags-merge' %></li>
+  <% end %>
+
+    <li><%= link_to l(:button_delete), tags_path(:ids => @tags.collect(&:id), :back_url => @back),
+                            :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon-del' %></li>
+</ul>
diff --git a/plugins/redmineup_tags/app/views/tags/edit.html.erb b/plugins/redmineup_tags/app/views/tags/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..a2e9745507dc8c926e1064ed191277cb844f2e25
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/edit.html.erb
@@ -0,0 +1,14 @@
+<h2><%= link_to l(:setting_issue_tags), :controller => 'settings', :action => 'plugin', :id => 'redmine_tags', :tab => 'manage_tags' %> &#187; <%=h (l(:issue_tags_tag) + ": " + @tag.name) %></h2>
+
+<%= form_tag(tag_path(@tag, :tab => 'tags'), :class => "tabular", :method => :put) do %>
+  <%= error_messages_for 'tag' %>
+<div class="box">
+<!--[form:optvalue]-->
+<p><label for="tag_name"><%=l(:field_name)%></label>
+<%= text_field 'tag', 'name'  %></p>
+
+
+</div>
+
+  <%= submit_tag l(:button_save) %>
+<% end %>
diff --git a/plugins/redmineup_tags/app/views/tags/merge.html.erb b/plugins/redmineup_tags/app/views/tags/merge.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..76ac2ac754917c90206af0032e7943f5b5cf41c7
--- /dev/null
+++ b/plugins/redmineup_tags/app/views/tags/merge.html.erb
@@ -0,0 +1,26 @@
+<h2>
+  <%= link_to l(:setting_issue_tags), :controller => 'settings', :action => 'plugin', :id => 'redmineup_tags', :tab => 'manage_tags' %> &#187; <%= l(:issue_tags_label_merge) %>
+</h2>
+
+<%= form_tag(merge_tags_path(:ids => @tags.map(&:id)), :class => "tabular") do %>
+  <%= error_messages_for 'tag' %>
+  <%= render_tags_list(@tags, {
+      :show_count => true,
+      :open_only => false,
+      :style => :simple_cloud
+    }) %>
+
+
+<div class="box">
+
+
+<!--[form:optvalue]-->
+<p><label for="tag_name"><%=l(:field_name)%></label>
+<%= text_field 'tag', 'name', :value => @tags.first.name  %></p>
+
+
+</div>
+
+  <%= submit_tag l(:button_save) %>
+<% end %>
+
diff --git a/plugins/redmineup_tags/assets/images/tag-icon.png b/plugins/redmineup_tags/assets/images/tag-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..9757fc6ed6597438eb8e5a70a1ab2402cdebd5d1
Binary files /dev/null and b/plugins/redmineup_tags/assets/images/tag-icon.png differ
diff --git a/plugins/redmineup_tags/assets/javascripts/redmine_tags.js b/plugins/redmineup_tags/assets/javascripts/redmine_tags.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bb289171ce1c445d9acccc3e3f9e00afe517de7
--- /dev/null
+++ b/plugins/redmineup_tags/assets/javascripts/redmine_tags.js
@@ -0,0 +1,19 @@
+$(function () {
+    $('body').on('click', '.most_used_tags .most_used_tag', function (e) {
+        var $tagsSelect = $('select#issue_tag_list');
+        var tag = e.target.innerText;
+        if ($tagsSelect.find("option[value='" + tag + "']").length === 0) {
+            var newOption = new Option(tag, tag, true, true);
+            $tagsSelect.append(newOption).trigger("change");
+        }
+
+        mostUsedTags = $.grep(mostUsedTags, function(t) { return t != tag; });
+        var tagsHtml = mostUsedTags.map(function(tag) {
+            return '<span class="most_used_tag">' + tag + '</span>';
+        }).join(', ');
+
+        var $mostUsedTagsContainer = $(e.target).parent('.most_used_tags');
+        $mostUsedTagsContainer.empty();
+        $mostUsedTagsContainer.append(tagsHtml);
+    });
+});
diff --git a/plugins/redmineup_tags/assets/stylesheets/redmine_tags.css b/plugins/redmineup_tags/assets/stylesheets/redmine_tags.css
new file mode 100644
index 0000000000000000000000000000000000000000..9efaaf03192c5919a252093dc7a8db40e147c27b
--- /dev/null
+++ b/plugins/redmineup_tags/assets/stylesheets/redmine_tags.css
@@ -0,0 +1,43 @@
+#admin-menu a.tags { background-image: url(../images/tag-icon.png);}
+
+#sidebar ul.tags-cloud { list-style: none; padding: 0px; }
+#sidebar ul.tags-cloud li { margin: .25em 0px; }
+
+div.tags-cloud { text-align: center; }
+.tag-label-color {
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 3px;
+  padding: 2px 4px;
+  display: inline-block;
+  font-size: 10px;
+  margin: 0px 0px 5px 2px;
+  color: white;
+}
+
+table.list tr.issue .tag-label-color a, .tag-label-color a { color: white; }
+
+tr.issue td.tags,
+table.list tr td.tags {text-align: left; white-space: normal;}
+
+div.tags-cloud .tag-nube-1 { font-size: .8em; }
+div.tags-cloud .tag-nube-2 { font-size: .9em; }
+div.tags-cloud .tag-nube-3 { font-size: 1em; }
+div.tags-cloud .tag-nube-4 { font-size: 1.1em; }
+div.tags-cloud .tag-nube-5 { font-size: 1.2em; }
+div.tags-cloud .tag-nube-6 { font-size: 1.3em; }
+div.tags-cloud .tag-nube-7 { font-size: 1.4em; }
+div.tags-cloud .tag-nube-8 { font-size: 1.5em; }
+
+.tag-label .tag-count,
+.tag-label-color .tag-count { font-size: .75em; margin-left: .5em; }
+
+
+#sidebar ul.tags-cloud li.letter { border-bottom: 1px dotted; margin: 10px 30px 10px 0px; font-weight: bold; }
+
+#tr_tags .select2-selection { background: initial; }
+#tr_tags .select2-selection__rendered { padding-left: 8px; }
+
+.icon-tags { background-image: url(../images/tag-icon.png);}
+
+.most_used_tag { cursor: pointer; color: #169; }
+.most_used_tag:hover { text-decoration: underline;}
diff --git a/plugins/redmineup_tags/config/locales/bg.yml b/plugins/redmineup_tags/config/locales/bg.yml
new file mode 100644
index 0000000000000000000000000000000000000000..af3700e3066033a556e0c5f76061639548e1f6f6
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/bg.yml
@@ -0,0 +1,43 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Bulgarian translation for redmine_tags
+# by Ivan Cenov (jwalker_@Skype) i_cenov@botevgrad.com
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+bg:
+  tags: Маркери
+  field_tags: Маркери
+  field_tag_list: Маркери
+  setting_issue_tags: Маркери на задачите
+  issues_sidebar: Показване на страничния панел като
+  issues_show_count: Показване на броя на задачите
+  issues_open_only: Само отворените задачи
+  issues_sort_by: Подреждане на маркерите по
+  issues_use_colors: Използване на цветове
+
+  issue_tags_sidebar_none: Да не се показват
+  issue_tags_sidebar_list: Списък
+  issue_tags_sidebar_cloud: Облак
+  issue_tags_sidebar_simple_cloud: Опростен облак
+
+  issues_sort_by_name: Наименование
+  issues_sort_by_count: Количество задачи
+  issues_sort_order_asc: Нарастване
+  issues_sort_order_desc: Намаляване
+
+  auto_complete_new_tag: Добавяне на нов...
diff --git a/plugins/redmineup_tags/config/locales/de.yml b/plugins/redmineup_tags/config/locales/de.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e369536f93c78f43148e2df320c9f35fa1c8e922
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/de.yml
@@ -0,0 +1,43 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# German translation for redmine_tags
+# by: Terence Miller AKA cforce, <cforce(at)gmx.de>,
+#     Jörg Jans
+#
+# Copyright (c) 2010 Terence Miller AKA cforce
+# Copyright (c) 2010 Jörg Jans
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+de:
+  tags: Tags
+  field_tags: Tags
+  field_tag_list: Tags
+  setting_issue_tags: Ticket Tags
+  issues_sidebar: Zeige die Tags auf der Sidebar
+  issues_show_count: Zeige die Ticketanzahl an
+  issues_open_only: Zeige nur noch offene Tickets
+  issues_sort_by: Sortiere Tags nach
+
+  issue_tags_sidebar_none: Keine
+  issue_tags_sidebar_list: Liste
+  issue_tags_sidebar_cloud: Cloud
+
+  issues_sort_by_name: Name
+  issues_sort_by_count: Anzahl tickets
+  issues_sort_order_asc: Aufsteigend
+  issues_sort_order_desc: Absteigend
+
+  auto_complete_new_tag: Hinzufügen...
diff --git a/plugins/redmineup_tags/config/locales/en.yml b/plugins/redmineup_tags/config/locales/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..48263772efeeff17fcf0c4ec3977887ece25bd26
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/en.yml
@@ -0,0 +1,57 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# English translation for redmine_tags
+# by Aleksey V Zapparov AKA ixti
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+en:
+  tags: Tags
+  field_tags: Tags
+  field_tag_list: Tags
+  label_add_tags: Add tags
+  notice_tags_added: Tags added
+  notice_failed_to_add_tags: Failed to add tags
+  setting_issue_tags: Issues Tags
+  issues_sidebar: Display tags on sidebar as
+  issues_show_count: Display amount of issues
+  issues_open_only: Display open issues only
+  issues_sort_by: Sort tags by
+  issues_use_colors: Use colors
+
+  issue_tags_sidebar_none: None
+  issue_tags_sidebar_list: List
+  issue_tags_sidebar_cloud: Cloud
+  issue_tags_sidebar_simple_cloud: Simple cloud
+
+  issues_sort_by_name: Name
+  issues_sort_by_count: Issues amount
+  issues_sort_order_asc: Ascending
+  issues_sort_order_desc: Descending
+
+  auto_complete_new_tag: Add new...
+
+  issue_tags_label_add_tag: + add tag
+  issue_tags_manage_tags: Manage tags
+  issue_tags_tag: Tag
+  issue_tags_button_merge: Merge
+  issue_tags_label_merge: Merge selected tags
+
+  tags_suggestion_order: Suggestion order
+  tags_order_by_name: Name
+  tags_order_by_last_created: Last created
+  tags_order_by_most_used: Most used
diff --git a/plugins/redmineup_tags/config/locales/fr.yml b/plugins/redmineup_tags/config/locales/fr.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3476420066fde48fdec55d6666b8194395efc42d
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/fr.yml
@@ -0,0 +1,41 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# French translation for redmine_tags
+# by Stephane HANNEQUIN, <stephane.hannequin(at)aster-ingenierie.com>
+#
+# Copyright (c) 2010 Stephane HANNEQUIN
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+fr:
+  tags: Tags
+  field_tags: Tags
+  field_tag_list: Tags
+  setting_issue_tags: Tags des demandes
+  issues_sidebar: Afficher les Tags comme
+  issues_show_count: Afficher le nombre de demande
+  issues_open_only: N'afficher que les demandes ouvertes
+  issues_sort_by: (en) Sort tags by
+  
+  issue_tags_sidebar_none: Ne pas afficher
+  issue_tags_sidebar_list: Liste
+  issue_tags_sidebar_cloud: Nuage
+
+  issues_sort_by_name: (en) Name
+  issues_sort_by_count: (en) Issues amount
+  issues_sort_order_asc: (en) Ascending
+  issues_sort_order_desc: (en) Descending
+
+  auto_complete_new_tag: Nouveau...
diff --git a/plugins/redmineup_tags/config/locales/ru.yml b/plugins/redmineup_tags/config/locales/ru.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2ae790e5a735351c72bf2542a7a4064e670bef87
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/ru.yml
@@ -0,0 +1,52 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Russian translation for redmine_tags
+# by Aleksey V Zapparov AKA ixti
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+ru:
+  tags: Метки
+  field_tags: Метки
+  field_tag_list: Метки
+  label_add_tags: Добавить теги
+  notice_tags_added: Теги добавлены
+  notice_failed_to_add_tags: Не удалось добавить теги
+  setting_issue_tags: Метки задач
+  issues_sidebar: Боковую панель как
+  issues_show_count: Показать кол-во задач
+  issues_open_only: Только открытые задачи
+  issues_sort_by: Упорядочить метки по
+  issues_use_colors: Использовать цвета
+  
+  issue_tags_sidebar_none: Не показывать
+  issue_tags_sidebar_list: Список
+  issue_tags_sidebar_cloud: Облако
+  issue_tags_sidebar_simple_cloud: Простое облако
+  
+  issues_sort_by_name: Названию
+  issues_sort_by_count: Кол-ву задач
+  issues_sort_order_asc: по возрастанию
+  issues_sort_order_desc: по убыванию
+
+  auto_complete_new_tag: Добавить...
+  issue_tags_label_add_tag: + добавить метку
+
+  tags_suggestion_order: Порядок поиска
+  tags_order_by_name: По названию
+  tags_order_by_last_created: Сначала новые
+  tags_order_by_most_used: Часто используемые
diff --git a/plugins/redmineup_tags/config/locales/zh-TW.yml b/plugins/redmineup_tags/config/locales/zh-TW.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc26fbcf6f5aa1112515439b8dba95f7528242c8
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/zh-TW.yml
@@ -0,0 +1,47 @@
+# This file is a part of redmine_tags
+# redmine plugin, that adds tagging support.
+#
+# Tranditional Chinese translation for redmine_tags
+# by Timothy Lee (https://github.com/ctiml)
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+zh-TW:
+  tags: 標籤
+  field_tags: 標籤
+  field_tag_list: 標籤
+  setting_issue_tags: 問題標籤
+  issues_sidebar: 在側邊欄顯示標籤
+  issues_show_count: 顯示問計數
+  issues_open_only: 僅顯示進行中的問題
+  issues_sort_by: 標籤依哪種方式排序
+
+  issue_tags_sidebar_none: ç„¡
+  issue_tags_sidebar_list: 列表顯示
+  issue_tags_sidebar_cloud: 標籤雲
+
+  issues_sort_by_name: 名稱名称
+  issues_sort_by_count: 問題計數
+  issues_sort_order_asc: 順序
+  issues_sort_order_desc: 倒序
+
+  auto_complete_new_tag: 增加新標籤 ...
+
+  issue_tags_label_add_tag: + 新增
+  issue_tags_manage_tags: 管理標籤
+  issue_tags_tag: 標籤
+  issue_tags_button_merge: 合併
+  issue_tags_label_merge: 合併選擇的標籤
diff --git a/plugins/redmineup_tags/config/locales/zh.yml b/plugins/redmineup_tags/config/locales/zh.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e830c16bbf73be14c2bdf08f64c2d9f56d22494
--- /dev/null
+++ b/plugins/redmineup_tags/config/locales/zh.yml
@@ -0,0 +1,41 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Simplified Chinese translation for redmine_tags
+# by archonwang (https://github.com/archonwang)
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+zh:
+  tags: 标签
+  field_tags: 标签
+  field_tag_list: 标签
+  setting_issue_tags: 问题标签
+  issues_sidebar: 在侧边栏显示标签
+  issues_show_count: 显示问题计数
+  issues_open_only: 仅显示打开的问题
+  issues_sort_by: 标签按何种方式排序
+
+  issue_tags_sidebar_none: æ— 
+  issue_tags_sidebar_list: 列表显示
+  issue_tags_sidebar_cloud: 云显示
+
+  issues_sort_by_name: 名称
+  issues_sort_by_count: 问题计数
+  issues_sort_order_asc: 顺序
+  issues_sort_order_desc: 倒序
+
+  auto_complete_new_tag: 添加新标签 ... ...
diff --git a/plugins/redmineup_tags/config/routes.rb b/plugins/redmineup_tags/config/routes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f8d54e8dcf26a3cae6f6414b8435a237b7ce5a57
--- /dev/null
+++ b/plugins/redmineup_tags/config/routes.rb
@@ -0,0 +1,36 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+RedmineApp::Application.routes.draw do
+  match '/issue_tags/auto_complete/:project_id', :to => 'auto_completes#issue_tags', :via => :get, :as => 'auto_complete_issue_tags'
+  match '/wiki_tags/auto_complete/:project_id', :to => 'auto_completes#wiki_tags', :via => :get, :as => 'auto_complete_wiki_tags'
+  match 'auto_completes/redmine_tags' => 'auto_completes#redmine_tags', :via => :get, :as => 'auto_complete_redmine_tags'
+  match '/tags/context_menu', :to => 'tags#context_menu', :as => 'tags_context_menu', :via => [:get, :post]
+  match '/tags', :controller => 'tags', :action => 'destroy', :via => :delete
+end
+
+resources :tags, :only => [:edit, :update] do
+  collection do
+    post :merge
+    get :context_menu, :merge
+  end
+end
+
+get :edit_issue_tags, :to => 'issue_tags#edit'
+post :update_issue_tags, :to => 'issue_tags#update'
diff --git a/plugins/redmineup_tags/db/migrate/001_add_tags_and_taggings.rb b/plugins/redmineup_tags/db/migrate/001_add_tags_and_taggings.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8da3ff6c41075853239ba0a3d388e193627ec042
--- /dev/null
+++ b/plugins/redmineup_tags/db/migrate/001_add_tags_and_taggings.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+class AddTagsAndTaggings < (Rails.version < '5.1') ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+  def up
+  end
+
+  def down
+  end
+end
diff --git a/plugins/redmineup_tags/db/migrate/002_create_tags.rb b/plugins/redmineup_tags/db/migrate/002_create_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..db116354c09b9bd2cd5835f4f1fed2030895f855
--- /dev/null
+++ b/plugins/redmineup_tags/db/migrate/002_create_tags.rb
@@ -0,0 +1,28 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+class CreateTags < (Rails.version < '5.1') ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
+
+  def self.up
+    ActiveRecord::Base.create_taggable_table
+  end
+
+  def self.down
+  end
+end
diff --git a/plugins/redmineup_tags/doc/CHANGELOG b/plugins/redmineup_tags/doc/CHANGELOG
new file mode 100755
index 0000000000000000000000000000000000000000..b1a81c0da390bd96730885b6cd4ad3f1091c7411
--- /dev/null
+++ b/plugins/redmineup_tags/doc/CHANGELOG
@@ -0,0 +1,35 @@
+== Redmine Tags plugin changelog
+
+Redmine Tags plugin - Tags board plugin for redmine
+Copyright (C) 2011-2018 RedmineUP
+http://www.redmineup.com/
+
+== 2018-09-25 v2.0.4
+
+* Fixed bug with Agile board view
+
+== 2018-08-10 v2.0.3
+
+* Fixed issues export to CSV, PDF tags field
+* select2 behaviour fixes
+
+== 2018-03-21 v2.0.2
+
+* Redmine 4 support
+* Adding tags from issue's context menu
+* Added new permissions for Add and Create new tags in a project
+* Suggestion order setting for tags autocomplete (Name, Last created, Most used)
+* Prevent adding commas
+* Fixed issues list tags column formating
+
+== 2017-09-21 v2.0.1
+
+* Fixed issue import error
+
+== 2017-07-24 v2.0.0
+
+* select2 control
+* redmine_crm gem for tags
+* Admin settings link
+* Alphabet sorting view for list
+* Merging tags fixes
\ No newline at end of file
diff --git a/plugins/redmineup_tags/doc/COPYING b/plugins/redmineup_tags/doc/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..63e41a44cffe44662260b7b902e4b065b201b515
--- /dev/null
+++ b/plugins/redmineup_tags/doc/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
\ No newline at end of file
diff --git a/plugins/redmineup_tags/doc/LICENSE b/plugins/redmineup_tags/doc/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..e2e50b5e93e1f520b12a10aba06bcd201e0b07c9
--- /dev/null
+++ b/plugins/redmineup_tags/doc/LICENSE
@@ -0,0 +1,26 @@
+LICENSING
+
+RedmineUP Licencing
+
+This End User License Agreement is a binding legal agreement between you and RedmineUP. Purchase, installation or use of RedmineUP Extensions provided on redmineup.com signifies that you have read, understood, and agreed to be bound by the terms outlined below.
+
+RedmineUP GPL Licencing
+
+All Redmine Extensions produced by RedmineUP are released under the GNU General Public License, version 2 (http://www.gnu.org/licenses/gpl-2.0.html). Specifically, the Ruby code portions are distributed under the GPL license. If not otherwise stated, all images, manuals, cascading style sheets, and included JavaScript are NOT GPL, and are released under the RedmineUP Proprietary Use License v1.0 (See below) unless specifically authorized by RedmineUP. Elements of the extensions released under this proprietary license may not be redistributed or repackaged for use other than those allowed by the Terms of Service.
+
+RedmineUP Proprietary Use License (v1.0)
+
+The RedmineUP Proprietary Use License covers any images, cascading stylesheets, manuals and JavaScript files in any extensions produced and/or distributed by redmineup.com. These files are copyrighted by redmineup.com (RedmineUP) and cannot be redistributed in any form without prior consent from redmineup.com (RedmineUP)
+
+Usage Terms
+
+You are allowed to use the Extensions on one or many "production" domains, depending on the type of your license
+You are allowed to make any changes to the code, however modified code will not be supported by us.
+
+Modification Of Extensions Produced By RedmineUP.
+
+You are authorized to make any modification(s) to RedmineUP extension Ruby code. However, if you change any Ruby code and it breaks functionality, support may not be available to you.
+
+In accordance with the RedmineUP Proprietary Use License v1.0, you may not release any proprietary files (modified or otherwise) under the GPL license. The terms of this license and the GPL v2 prohibit the removal of the copyright information from any file.
+
+Please contact us if you have any requirements that are not covered by these terms.
\ No newline at end of file
diff --git a/plugins/redmineup_tags/init.rb b/plugins/redmineup_tags/init.rb
new file mode 100644
index 0000000000000000000000000000000000000000..be594ed1b85cd96266e789773087337d11f7a82e
--- /dev/null
+++ b/plugins/redmineup_tags/init.rb
@@ -0,0 +1,75 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+requires_redmine_crm :version_or_higher => '0.0.38' rescue raise "\n\033[31mRedmine requires newer redmine_crm gem version.\nPlease update with 'bundle update redmine_crm'.\033[0m"
+
+require 'redmine'
+require 'redmine_tags'
+
+TAGS_VERSION_NUMBER = '2.0.4'
+TAGS_VERSION_TYPE = "Light version"
+
+Redmine::Plugin.register :redmineup_tags do
+  name "Redmine Tags plugin (#{TAGS_VERSION_TYPE})"
+  author 'RedmineUP'
+  description 'Redmine issues tagging support'
+  version TAGS_VERSION_NUMBER
+  url 'https://www.redmineup.com/pages/tags'
+  author_url 'mailto:support@redmineup.com'
+
+  requires_redmine :version_or_higher => '2.4'
+
+  settings :default => {
+    :issues_sidebar => 'none',
+    :issues_show_count => 0,
+    :issues_open_only => 0,
+    :issues_sort_by => 'name',
+    :issues_sort_order => 'asc',
+    :tags_suggestion_order => 'name',
+  }, :partial => 'tags/settings'
+
+  menu :admin_menu, :tags, {:controller => 'settings', :action => 'plugin', :id => "redmineup_tags"}, :caption => :tags, :html => {:class => 'icon'}
+
+  project_module :issue_tracking do
+    permission :create_tags, {}
+    permission :edit_tags, {}
+  end
+end
+
+(Rails.version < '5.1' ? ActionDispatch::Callbacks : ActiveSupport::Reloader).to_prepare do
+  unless Issue.included_modules.include?(RedmineTags::Patches::IssuePatch)
+    Issue.send(:include, RedmineTags::Patches::IssuePatch)
+  end
+
+  [IssuesController, CalendarsController, GanttsController, SettingsController].each do |controller|
+    RedmineTags::Patches::AddHelpersForIssueTagsPatch.apply(controller)
+  end
+  RedmineTags::Patches::AddHelpersForIssueTagsPatch.apply(ImportsController) if Redmine::VERSION.to_s > '3.2'
+
+  base = ActiveSupport::Dependencies::search_for_file('issue_queries_helper') ? IssueQueriesHelper : QueriesHelper
+  unless base.included_modules.include?(RedmineTags::Patches::QueriesHelperPatch)
+    base.send(:include, RedmineTags::Patches::QueriesHelperPatch)
+  end
+end
+
+require 'redmine_tags/hooks/model_issue_hook'
+require 'redmine_tags/hooks/views_issues_hook'
+require 'redmine_tags/hooks/views_layouts_hook'
+require 'redmine_tags/hooks/views_context_menus_hook'
+require 'redmine_tags/patches/agile_query_patch' if Redmine::Plugin.installed?(:redmine_agile) && Redmine::Plugin.find(:redmine_agile).version >= '1.4.3'
diff --git a/plugins/redmineup_tags/lib/query_tags_column.rb b/plugins/redmineup_tags/lib/query_tags_column.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95ca6160159093ee00d62df6cb447e4f708fd69c
--- /dev/null
+++ b/plugins/redmineup_tags/lib/query_tags_column.rb
@@ -0,0 +1,24 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'query'
+
+class QueryTagsColumn < QueryColumn
+  def css_classes; 'tags' end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags.rb b/plugins/redmineup_tags/lib/redmine_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1eacd1fd1d61a235bb93dac645de43aaab581423
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags.rb
@@ -0,0 +1,27 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require 'redmine_tags/patches/auto_completes_controller_patch'
+require 'redmine_tags/patches/action_controller_patch'
+require 'redmine_tags/patches/issue_query_patch'
+require 'query_tags_column'
+
+module RedmineTags
+  def self.settings() Setting[:plugin_redmineup_tags].stringify_keys end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/hooks/model_issue_hook.rb b/plugins/redmineup_tags/lib/redmine_tags/hooks/model_issue_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..371b7d204db40498de54db6657d94b7f3c77f5d7
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/hooks/model_issue_hook.rb
@@ -0,0 +1,47 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Hooks
+    class ModelIssueHook < Redmine::Hook::ViewListener
+      def controller_issues_edit_before_save(context = {})
+        tags_log context
+      end
+
+      def controller_issues_bulk_edit_before_save(context = {})
+        tags_log context
+      end
+
+      def tags_log(context, create_journal = true)
+        issue = context[:issue]
+        params = context[:params]
+        if params && params[:issue] && !params[:issue][:tag_list].nil?
+          old_tags = Issue.find(issue.id).tag_list.to_s
+          new_tags = issue.tag_list.to_s
+          if create_journal && !(old_tags == new_tags || issue.current_journal.blank?)
+            issue.current_journal.details << JournalDetail.new(:property => 'attr',
+                                                               :prop_key => 'tag_list',
+                                                               :old_value => old_tags,
+                                                               :value => new_tags)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/hooks/views_context_menus_hook.rb b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_context_menus_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33f41b092a30d3dffb427b8a3294263eef8f830e
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_context_menus_hook.rb
@@ -0,0 +1,26 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Hooks
+    class ViewsContextMenuesHook < Redmine::Hook::ViewListener
+      render_on :view_issues_context_menu_end, :partial => 'context_menus/issues_tags'
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/hooks/views_issues_hook.rb b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_issues_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7fa1fec7abde1da7884411bb6097565df383b07b
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_issues_hook.rb
@@ -0,0 +1,30 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Hooks
+    class ViewsIssuesHook < Redmine::Hook::ViewListener
+      render_on :view_issues_show_details_bottom, :partial => 'issues/tags'
+      render_on :view_issues_form_details_bottom, :partial => 'issues/tags_form'
+      render_on :view_issues_sidebar_planning_bottom, :partial => 'issues/tags_sidebar'
+      render_on :view_issues_bulk_edit_details_bottom, :partial => 'issues/tags_form'
+    end
+  end
+end
+
diff --git a/plugins/redmineup_tags/lib/redmine_tags/hooks/views_layouts_hook.rb b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_layouts_hook.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b8d413bf8b084d892b66b2958f9125b1597c06a2
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/hooks/views_layouts_hook.rb
@@ -0,0 +1,27 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Hooks
+    class ViewsLayoutsHook < Redmine::Hook::ViewListener
+      render_on :view_layouts_base_html_head, partial: 'tags/additional_assets'
+      render_on :view_layouts_base_body_bottom, partial: 'tags/select2_transformation_rules'
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/action_controller_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/action_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3d01e8c3b0f7d911851e8b44f6613306fcceace1
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/action_controller_patch.rb
@@ -0,0 +1,49 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Patches
+    module ActionControllerPatch
+      def self.included(base)
+        base.extend(ClassMethods) if Rails::VERSION::MAJOR < 4
+
+        base.class_eval do
+        end
+      end
+
+      module ClassMethods
+        def before_action(*filters, &block)
+          before_filter(*filters, &block)
+        end
+
+        def after_action(*filters, &block)
+          after_filter(*filters, &block)
+        end
+
+        def skip_before_action(*filters)
+          skip_before_filter(*filters)
+        end
+      end
+    end
+  end
+end
+
+unless ActionController::Base.included_modules.include?(RedmineTags::Patches::ActionControllerPatch)
+  ActionController::Base.send(:include, RedmineTags::Patches::ActionControllerPatch)
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/add_helpers_for_issue_tags_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/add_helpers_for_issue_tags_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d3a5dc8498c109ed6b8bfe677cbbdd86baf49506
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/add_helpers_for_issue_tags_patch.rb
@@ -0,0 +1,29 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  module Patches
+    module AddHelpersForIssueTagsPatch
+      def self.apply(controller)
+        controller.send(:helper, 'tags')
+        controller.send(:helper, 'issues_tags')
+      end
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/agile_query_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/agile_query_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5bc69bc58f29e0c0e38696fa15ec29e2e20ed701
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/agile_query_patch.rb
@@ -0,0 +1,69 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'query'
+
+module RedmineTags
+  module Patches
+    module AgileQueryPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+
+          alias_method :available_filters_without_redmine_tags, :available_filters
+          alias_method :available_filters, :available_filters_with_redmine_tags
+
+          add_available_column QueryTagsColumn.new(:tags_relations, caption: :tags)
+        end
+      end
+
+      module InstanceMethods
+        def sql_for_issue_tags_field(_field, operator, value)
+          case operator
+          when '=', '!'
+            issues = Issue.tagged_with(value.clone)
+          when '!*'
+            issues = Issue.joins(:tags).uniq
+          else
+            issues = Issue.tagged_with(RedmineCrm::Tag.all.map(&:to_s), :any => true)
+          end
+
+          compare   = operator.include?('!') ? 'NOT IN' : 'IN'
+          ids_list  = issues.collect(&:id).push(0).join(',')
+          "( #{Issue.table_name}.id #{compare} (#{ids_list}) ) "
+        end
+
+        def available_filters_with_redmine_tags
+          available_filters_without_redmine_tags
+          selected_tags = []
+          if filters['issue_tags'].present?
+            selected_tags = Issue.all_tags(:project => project, :open_only => RedmineTags.settings['issues_open_only'].to_i == 1).
+                                  where(:name => filters['issue_tags'][:values]).map { |c| [c.name, c.name] }
+          end
+          add_available_filter('issue_tags', type: :issue_tags, name: l(:tags), values: selected_tags)
+        end
+      end
+    end
+  end
+end
+
+unless AgileQuery.included_modules.include?(RedmineTags::Patches::AgileQueryPatch)
+  AgileQuery.send(:include, RedmineTags::Patches::AgileQueryPatch)
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3a9e993d07163d2ee7ce95405eb98dd19798619f
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb
@@ -0,0 +1,56 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'auto_completes_controller'
+
+module RedmineContacts
+  module Patches
+    module AutoCompletesControllerPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+
+        base.class_eval do
+        end
+      end
+
+      module InstanceMethods
+        SORTING_FIELDS = {
+          'name' => 'name',
+          'last_created' => 'created_at',
+          'most_used' => 'count'
+        }
+
+        def redmine_tags
+          suggestion_order = RedmineTags.settings['tags_suggestion_order'] || 'name'
+          options = {
+            :name_like => (params[:q] || params[:term]).to_s.strip,
+            :sort_by => SORTING_FIELDS[suggestion_order],
+            :order => (suggestion_order == 'name' ? 'ASC' : 'DESC')
+          }
+          @redmine_tags = Issue.all_tags(options).limit(params[:limit] || 10)
+          render :layout => false, :partial => 'redmine_tags'
+        end
+      end
+    end
+  end
+end
+
+unless AutoCompletesController.included_modules.include?(RedmineContacts::Patches::AutoCompletesControllerPatch)
+  AutoCompletesController.send(:include, RedmineContacts::Patches::AutoCompletesControllerPatch)
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/issue_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/issue_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f5d8696723a769df72047501ad5b6dd83ee2237
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/issue_patch.rb
@@ -0,0 +1,115 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'issue'
+
+module RedmineTags
+  module Patches
+    module IssuePatch
+
+      def self.included(base)
+        base.extend(ClassMethods)
+
+        base.class_eval do
+          include InstanceMethods
+
+          unloadable
+          rcrm_acts_as_taggable
+
+          alias_method :safe_attributes_without_safe_tags=, :safe_attributes=
+          alias_method :safe_attributes=, :safe_attributes_with_safe_tags=
+
+          class << self
+            alias_method :available_tags_without_redmine_tags, :available_tags
+            alias_method :available_tags, :available_tags_with_redmine_tags
+          end
+
+          scope :on_project, lambda { |project|
+            project = project.id if project.is_a? Project
+            { :conditions => ["#{Project.table_name}.id=?", project] }
+          }
+        end
+      end
+
+      # Class used to represent the tags relations of an issue
+      class TagsRelations < IssueRelation::Relations
+        def to_s(*args)
+          map(&:name).join(', ')
+        end
+      end
+
+      module ClassMethods
+        def available_tags_with_redmine_tags(options = {})
+          scope = available_tags_without_redmine_tags(options)
+          return scope unless options[:open_only]
+          scope.joins("JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id").
+                where("#{IssueStatus.table_name}.is_closed = ?", false)
+        end
+
+        def all_tags(options = {})
+          scope = RedmineCrm::Tag.where({})
+          scope = scope.where("LOWER(#{RedmineCrm::Tag.table_name}.name) LIKE LOWER(?)", "%#{options[:name_like]}%") if options[:name_like]
+          join = []
+          join << "JOIN #{RedmineCrm::Tagging.table_name} ON #{RedmineCrm::Tagging.table_name}.tag_id = #{RedmineCrm::Tag.table_name}.id "
+          join << "JOIN #{Issue.table_name} ON #{Issue.table_name}.id = #{RedmineCrm::Tagging.table_name}.taggable_id
+            AND #{RedmineCrm::Tagging.table_name}.taggable_type = '#{Issue.name}' "
+          scope = scope.joins(join.join(' '))
+
+          columns = [
+            "#{RedmineCrm::Tag.table_name}.*",
+            "COUNT(DISTINCT #{RedmineCrm::Tagging.table_name}.taggable_id) AS count"
+          ]
+          if options[:sort_by] == 'created_at'
+            columns << "MIN(#{RedmineCrm::Tagging.table_name}.created_at) AS created_at"
+          end
+          scope = scope.select(columns.join(', '))
+
+          scope = scope.group("#{RedmineCrm::Tag.table_name}.id, #{RedmineCrm::Tag.table_name}.name ")
+          scope = scope.having('COUNT(*) > 0')
+
+          column = options[:sort_by] || "#{RedmineCrm::Tag.table_name}.name"
+          order = options[:order] || 'ASC'
+          scope.order("#{column} #{order}")
+        end
+
+        def allowed_tags?(tags)
+          allowed_tags = all_tags.map(&:name)
+          tags.all? { |tag| allowed_tags.include?(tag) }
+        end
+      end
+
+      module InstanceMethods
+        def safe_attributes_with_safe_tags=(attrs, user=User.current)
+          self.safe_attributes_without_safe_tags = attrs
+          if attrs && attrs[:tag_list] && user.allowed_to?(:edit_tags, self.project)
+            tags = attrs[:tag_list].reject(&:empty?)
+            if user.allowed_to?(:create_tags, self.project) || Issue.allowed_tags?(tags)
+              self.tag_list = tags
+            end
+          end
+        end
+
+        def tags_relations
+          TagsRelations.new(self, tags.to_a)
+        end
+      end
+
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/issue_query_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/issue_query_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b9c1f117df60739647c2904f118ecd67486f2ff
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/issue_query_patch.rb
@@ -0,0 +1,87 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'issue_query'
+
+module RedmineTags
+  module Patches
+    module IssueQueryPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+
+        base.class_eval do
+          unloadable
+
+          alias_method :statement_without_redmine_tags, :statement
+          alias_method :statement, :statement_with_redmine_tags
+
+          alias_method :available_filters_without_redmine_tags, :available_filters
+          alias_method :available_filters, :available_filters_with_redmine_tags
+
+          add_available_column QueryTagsColumn.new(:tags_relations, caption: :tags)
+        end
+      end
+
+      module InstanceMethods
+        def statement_with_redmine_tags
+          filter  = filters.delete 'issue_tags'
+          clauses = statement_without_redmine_tags || ''
+
+          if filter
+            filters['issue_tags'] = filter
+
+            issues = Issue.where({})
+
+            op = operator_for('issue_tags')
+            case op
+            when '=', '!'
+              issues = issues.tagged_with(values_for('issue_tags').clone)
+            when '!*'
+              issues = issues.joins(:tags).uniq
+            else
+              issues = issues.tagged_with(RedmineCrm::Tag.all.map(&:to_s), :any => true)
+            end
+
+            compare   = op.include?('!') ? 'NOT IN' : 'IN'
+            ids_list  = issues.collect(&:id).push(0).join(',')
+
+            clauses << ' AND ' unless clauses.empty?
+            clauses << "( #{Issue.table_name}.id #{compare} (#{ids_list}) ) "
+          end
+
+          clauses
+        end
+
+        def available_filters_with_redmine_tags
+          available_filters_without_redmine_tags
+          selected_tags = []
+          if filters['issue_tags'].present?
+            selected_tags = Issue.all_tags(:project => project, :open_only => RedmineTags.settings['issues_open_only'].to_i == 1).
+                                  where(:name => filters['issue_tags'][:values]).map { |c| [c.name, c.name] }
+          end
+          add_available_filter('issue_tags', type: :issue_tags, name: l(:tags), values: selected_tags)
+        end
+      end
+    end
+  end
+end
+
+unless IssueQuery.included_modules.include?(RedmineTags::Patches::IssueQueryPatch)
+  IssueQuery.send(:include, RedmineTags::Patches::IssueQueryPatch)
+end
diff --git a/plugins/redmineup_tags/lib/redmine_tags/patches/queries_helper_patch.rb b/plugins/redmineup_tags/lib/redmine_tags/patches/queries_helper_patch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c76bb3dbe3a4f8c713881ef04c3127b93999dc58
--- /dev/null
+++ b/plugins/redmineup_tags/lib/redmine_tags/patches/queries_helper_patch.rb
@@ -0,0 +1,51 @@
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require_dependency 'queries_helper'
+if ActiveSupport::Dependencies::search_for_file('issue_queries_helper')
+  require_dependency 'issue_queries_query'
+end
+
+module RedmineTags
+  module Patches
+    module QueriesHelperPatch
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+
+        base.class_eval do
+          unloadable
+          alias_method :column_value_without_tags, :column_value
+          alias_method :column_value, :column_value_with_tags
+        end
+      end
+
+      module InstanceMethods
+        include TagsHelper
+
+        def column_value_with_tags(column, list_object, value)
+          if column.name == :tags_relations && list_object.is_a?(Issue)
+            [value].flatten.collect{ |t| render_issue_tag_link(t) }.join(RedmineTags.settings['issues_use_colors'].to_i > 0 ? ' ' : ', ').html_safe
+          else
+            column_value_without_tags(column, list_object, value)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/redmineup_tags/test/fixtures/taggings.yml b/plugins/redmineup_tags/test/fixtures/taggings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..28c2b1648b627ae90570c8055bf5c603e3c570ea
--- /dev/null
+++ b/plugins/redmineup_tags/test/fixtures/taggings.yml
@@ -0,0 +1,68 @@
+# project_1:
+#   issue_1:
+#     tags: [second, third]
+#   issue_2:
+#     tags: [second, third]
+#   issue_8:
+#     tags: [first, second]
+#
+# project_3:
+#   issue_5:
+#     tags: [second, third]
+
+---
+# Relation with closed issue from project with id 1
+tagging_1:
+  tag_id: 1
+  taggable_id: 8
+  taggable_type: Issue
+  created_at: "2017-12-04 00:0:00"
+
+# Relation with opened issue from project with id 1
+tagging_2:
+  tag_id: 2
+  taggable_id: 1
+  taggable_type: Issue
+  created_at: "2017-12-04 01:00:00"
+
+# Relation with opened issue from project with id 1
+tagging_3:
+  tag_id: 2
+  taggable_id: 2
+  taggable_type: Issue
+  created_at: "2017-12-04 02:00:00"
+
+# Relation with closed issue from project with id 1
+tagging_4:
+  tag_id: 2
+  taggable_id: 8
+  taggable_type: Issue
+  created_at: "2017-12-04 03:00:00"
+
+# Relation with opened issue from project with id 3
+tagging_5:
+  tag_id: 2
+  taggable_id: 5
+  taggable_type: Issue
+  created_at: "2017-12-04 04:00:00"
+
+# Relation with opened issue from project with id 1
+tagging_6:
+  tag_id: 3
+  taggable_id: 1
+  taggable_type: Issue
+  created_at: "2017-12-04 05:00:00"
+
+# Relation with opened issue from project with id 1
+tagging_7:
+  tag_id: 3
+  taggable_id: 2
+  taggable_type: Issue
+  created_at: "2017-12-04 06:00:00"
+
+# Relation with closed issue from project with id 3
+tagging_8:
+  tag_id: 3
+  taggable_id: 5
+  taggable_type: Issue
+  created_at: "2017-12-04 07:00:00"
\ No newline at end of file
diff --git a/plugins/redmineup_tags/test/fixtures/tags.yml b/plugins/redmineup_tags/test/fixtures/tags.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7e4e6c16120969c83bc7c367fa42290923763c4c
--- /dev/null
+++ b/plugins/redmineup_tags/test/fixtures/tags.yml
@@ -0,0 +1,12 @@
+---
+tag_1:
+  id: 1
+  name: first
+
+tag_2:
+  id: 2
+  name: second
+
+tag_3:
+  id: 3
+  name: third
diff --git a/plugins/redmineup_tags/test/functional/auto_completes_controller_test.rb b/plugins/redmineup_tags/test/functional/auto_completes_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ae343d6399347af205ce9950edfa14e905929e6
--- /dev/null
+++ b/plugins/redmineup_tags/test/functional/auto_completes_controller_test.rb
@@ -0,0 +1,109 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class AutoCompletesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers
+
+
+  def setup
+    @tag = RedmineCrm::Tag.create(:name => 'Test_tag')
+    @request.session[:user_id] = 1
+  end
+
+  def test_redmine_tags_should_not_be_case_sensitive
+    issue = Issue.find(1)
+    issue.tags << @tag
+    compatible_request :get, :redmine_tags, :project_id => 'ecookbook', :q => 'te'
+    assert_response :success
+    redmine_tags = ActiveSupport::JSON.decode(response.body).map { |item| item['id'] }
+    assert_not_nil redmine_tags
+    assert_equal [@tag.name], redmine_tags
+  end
+
+  def test_contacts_should_return_json
+    issue = Issue.find(1)
+    issue.tags << @tag
+    compatible_request :get, :redmine_tags, :project_id => 'ecookbook', :q => 'te'
+    assert_response :success
+    json = ActiveSupport::JSON.decode(response.body)
+    assert_kind_of Array, json
+    parsed_tag = json.last
+    assert_kind_of Hash, parsed_tag
+    assert_equal @tag.name, parsed_tag['id']
+    assert_equal @tag.name, parsed_tag['text']
+  end
+
+  def test_suggestion_order_default
+    with_settings :plugin_redmineup_tags => Setting.available_settings['plugin_redmineup_tags']['default'] do
+      compatible_request :get, :redmine_tags, :project_id => 'ecookbook'
+    end
+    assert_response :success
+    tags = ActiveSupport::JSON.decode(response.body).map { |item| item['id'] }
+    assert_equal %w(first second third), tags
+  end
+
+  def test_suggestion_order_name
+    with_settings :plugin_redmineup_tags => { :tags_suggestion_order => 'name' } do
+      compatible_request :get, :redmine_tags, :project_id => 'ecookbook'
+    end
+    assert_response :success
+    tags = ActiveSupport::JSON.decode(response.body).map { |item| item['id'] }
+    assert_equal %w(first second third), tags
+  end
+
+  def test_suggestion_order_most_used
+    with_settings :plugin_redmineup_tags => { :tags_suggestion_order => 'most_used' } do
+      compatible_request :get, :redmine_tags, :project_id => 'ecookbook'
+    end
+    assert_response :success
+    tags = ActiveSupport::JSON.decode(response.body).map { |item| item['id'] }
+    assert_equal %w(second third first), tags
+  end
+
+  def test_suggestion_order_last_created
+    with_settings :plugin_redmineup_tags => { :tags_suggestion_order => 'last_created' } do
+      compatible_request :get, :redmine_tags, :project_id => 'ecookbook'
+    end
+    assert_response :success
+    tags = ActiveSupport::JSON.decode(response.body).map { |item| item['id'] }
+    assert_equal %w(third second first), tags
+  end
+end
diff --git a/plugins/redmineup_tags/test/functional/issue_tags_controller_test.rb b/plugins/redmineup_tags/test/functional/issue_tags_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..39df2fa5c2f7ac16894d3dd60dab0fe7fe2113cb
--- /dev/null
+++ b/plugins/redmineup_tags/test/functional/issue_tags_controller_test.rb
@@ -0,0 +1,224 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class IssueTagsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers
+
+
+  def setup
+    @request.env['HTTP_REFERER'] = '/update_issue_tags'
+    @request.session[:user_id] = 2
+    @project_1 = projects(:projects_001)
+    @issue_1 = issues(:issues_001)
+    @issue_2 = issues(:issues_002)
+    @issue_8 = issues(:issues_008)
+    @issues = [@issue_1, @issue_2, @issue_8]
+    @ids = [1, 2, 8]
+    @most_used_tags = %w(second third first)
+    @role = roles(:roles_001) # Manager role
+    @role.add_permission! :edit_tags
+  end
+
+  def test_should_get_edit_when_one_issue_chose
+    compatible_xhr_request :get, :edit, :ids => [1]
+    assert_response :success
+    assert_equal 'text/javascript', response.content_type
+
+    html_form = response.body[/<form.+form>/].delete('\\')
+
+    assert_select_in html_form, 'select#issue_tag_list', 1 do
+      assert_select 'option[selected="selected"]', 2
+      assert_select 'option[selected="selected"]', :text => 'second', :count => 1
+      assert_select 'option[selected="selected"]', :text => 'third', :count => 1
+    end
+
+    assert_select_in html_form, '.most_used_tags', :text => /.+second.+third.+first.+/, :count => 1 do
+      assert_select '.most_used_tag', 3
+      @most_used_tags.each { |tag| assert_select '.most_used_tag', :text => tag, :count => 1 }
+    end
+  end
+
+  def test_should_get_edit_when_several_issues_chose
+    compatible_xhr_request :get, :edit, :ids => @ids
+    assert_response :success
+    assert_equal 'text/javascript', response.content_type
+
+    html_form = response.body[/<form.+form>/].delete('\\')
+
+    assert_select_in html_form, 'select#issue_tag_list', 1 do
+      assert_select 'option[selected="selected"]', 0
+    end
+
+    assert_select_in html_form, '.most_used_tags', :text => /.+second.+third.+first.+/, :count => 1 do
+      assert_select '.most_used_tag', 3
+      @most_used_tags.each { |tag| assert_select '.most_used_tag', :text => tag, :count => 1 }
+    end
+  end
+
+  def test_should_get_not_found_when_no_ids
+    compatible_xhr_request :get, :edit, :ids => []
+    assert_response :missing
+    compatible_request :post, :update, :ids => [], :issue => {:tag_list => []}
+    assert_response :missing
+  end
+
+  def test_should_change_issue_tags_empty_tags
+    compatible_request :post, :update, :ids => [1], :issue => {:tag_list => ['', '', '']}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal [], @issue_1.tag_list
+  end
+
+  def test_should_change_issue_tags_no_tags
+    compatible_request :post, :update, :ids => [1], :issue => {:tag_list => []}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal [], @issue_1.tag_list
+  end
+
+  def test_should_change_issue_tags_one_tag
+    compatible_request :post, :update, :ids => [1], :issue => {:tag_list => %w(first)}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal %w(first), @issue_1.tag_list
+  end
+
+  def test_should_change_issue_tags_several_tags
+    compatible_request :post, :update, :ids => [1], :issue => {:tag_list => %w(first second third)}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal %w(first second third), @issue_1.tag_list.sort
+  end
+
+  def test_should_bulk_change_issue_tags_no_tags
+    compatible_request :post, :update, :ids => @ids, :issue => {:tag_list => []}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    @issues.each { |issue| assert_equal [], issue.tag_list }
+  end
+
+  def test_should_bulk_change_issue_tags_one_tag
+    compatible_request :post, :update, :ids => @ids, :issue => {:tag_list => %w(first)}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    @issues.each { |issue| assert_equal %w(first), issue.tag_list }
+  end
+
+  def test_should_bulk_change_issue_tags_several_tags
+    compatible_request :post, :update, :ids => @ids, :issue => {:tag_list => %w(first second third)}
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    @issues.each { |issue| assert_equal %w(first second third), issue.tag_list.sort }
+  end
+
+  def test_edit_tags_permission
+    tag = 'first'
+    assert_not_equal Issue.find(1).tag_list, [tag]
+    assert Issue.all_tags.map(&:name).include?(tag)
+    compatible_request :post, :update, :ids => [1], :issue => { :tag_list => [tag] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal Issue.find(1).tag_list, [tag]
+
+    @role.remove_permission! :edit_tags
+    tag2 = 'second'
+
+    assert Issue.all_tags.map(&:name).include?(tag2)
+    compatible_request :post, :update, :ids => [1], :issue => { :tag_list => [tag2] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_failed_to_add_tags), flash[:error]
+    assert_equal Issue.find(1).tag_list, [tag]
+  end
+
+  def test_bulk_edit_tags_permission
+    tag = 'first'
+    assert Issue.all_tags.map(&:name).include?(tag)
+    compatible_request :post, :update, :ids => [1, 2], :issue => { :tag_list => [tag] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal Issue.find(1).tag_list, [tag]
+    assert_equal Issue.find(2).tag_list, [tag]
+
+    @role.remove_permission! :edit_tags
+    tag2 = 'second'
+
+    assert Issue.all_tags.map(&:name).include?(tag2)
+    compatible_request :post, :update, :ids => [1, 2], :issue => { :tag_list => [tag2] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_failed_to_add_tags), flash[:error]
+    assert_equal Issue.find(1).tag_list, [tag]
+    assert_equal Issue.find(2).tag_list, [tag]
+  end
+
+  def test_create_tags_permission
+    @role.add_permission! :create_tags
+    new_tag = 'enable_create_tags_permission'
+
+    assert_not_equal Issue.find(1).tag_list, [new_tag]
+    assert !Issue.all_tags.map(&:name).include?(new_tag)
+    compatible_request :post, :update, :ids => [1], :issue => { :tag_list => [new_tag] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_tags_added), flash[:notice]
+    assert_equal Issue.find(1).tag_list, [new_tag]
+
+    @role.remove_permission! :create_tags
+    new_tag2 = 'disable_create_tags_permission'
+
+    assert !Issue.all_tags.map(&:name).include?(new_tag2)
+    compatible_request :post, :update, :ids => [1], :issue => { :tag_list => [new_tag2] }
+    assert_response :redirect
+    assert_redirected_to :action => 'update'
+    assert_equal I18n.t(:notice_failed_to_add_tags), flash[:error]
+    assert_equal Issue.find(1).tag_list, [new_tag]
+  end
+end
diff --git a/plugins/redmineup_tags/test/functional/issues_controller_test.rb b/plugins/redmineup_tags/test/functional/issues_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31bbd886cbc8c94a4867674990522caa3784bb8e
--- /dev/null
+++ b/plugins/redmineup_tags/test/functional/issues_controller_test.rb
@@ -0,0 +1,234 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class IssuesControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers
+
+  def setup
+    @tag = RedmineCrm::Tag.create(:name => 'test_tag')
+    @last_tag = RedmineCrm::Tag.create(:name => 'last_tag')
+    @request.session[:user_id] = 1
+  end
+
+  def test_get_index_with_tags
+    issue = Issue.find(2)
+    issue.tags << @tag
+    compatible_request(
+      :get,
+      :index,
+      :f => ['status_id', 'issue_tags',''],
+      :op => { :status_id => 'o', :issue_tags => '=' },
+      :v =>  { :issue_tags => ['test_tag'] },
+      :c => ['status', 'priority', 'subject', 'tags_relations'],
+      :project_id => 'ecookbook'
+    )
+    assert_response :success
+    assert_select 'table.list.issues tr.issue', 1
+    assert_select 'table.list.issues tr.issue td.subject', issue.subject
+    assert_select 'table.list.issues tr.issue td.tags a', 'test_tag'
+  ensure
+    issue.tags = []
+  end
+
+  def test_get_index_with_sidebar_tags_in_list_by_count
+    issue1 = Issue.find(1)
+    issue1.tags << @tag
+    issue2 = Issue.find(2)
+    issue2.tags << @tag
+    issue2.tags << @last_tag
+    RedmineTags.stubs(:settings).returns({ 'issues_sidebar' => 'list',
+                                           'issues_show_count' => '1',
+                                           'issues_sort_by' => 'count',
+                                           'issues_sort_order' => 'desc' })
+
+    compatible_request :get, :index, :project_id => 'ecookbook'
+    assert_response :success
+    assert_select '.tag-label', 'test_tag(2)'
+  ensure
+    issue1.tags = []
+    issue2.tags = []
+    RedmineTags.unstub(:settings)
+  end
+
+  def test_get_index_with_sidebar_tags_in_cloud_by_count
+    issue1 = Issue.find(1)
+    issue1.tags << @last_tag
+
+    issue2 = Issue.find(2)
+    issue2.tags << @tag
+    issue2.tags << @last_tag
+
+    RedmineTags.stubs(:settings).returns({ 'issues_sidebar' => 'cloud',
+                                           'issues_show_count' => '1',
+                                           'issues_sort_by' => 'count',
+                                           'issues_sort_order' => 'desc' })
+    compatible_request :get, :index, :project_id => 'ecookbook'
+    assert_response :success
+    assert_select '.tag-label', 'last_tag(2)'
+  ensure
+    issue1.tags = []
+    issue2.tags = []
+    RedmineTags.unstub(:settings)
+  end
+
+  def test_should_not_update_without_tag_list
+    tags = %w(second third)
+    assert_equal tags, Issue.find(1).tag_list.sort
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1 }
+    assert_response :redirect
+    assert_equal tags, Issue.find(1).tag_list.sort
+  end
+
+  def test_should_update_with_empty_string_tags
+    assert_equal %w(second third), Issue.find(1).tag_list.sort
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :tag_list => ['', ''] }
+    assert_response :redirect
+    assert_equal [], Issue.find(1).tag_list
+  end
+
+  def test_should_update_with_new_tags
+    assert_equal %w(second third), Issue.find(1).tag_list.sort
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :tag_list => %w(new_tag1 new_tag2) }
+    assert_response :redirect
+    assert_equal %w(new_tag1 new_tag2), Issue.find(1).tag_list.sort
+  end
+
+  def test_should_update_issue_and_tags
+    assert_equal %w(second third), Issue.find(1).tag_list.sort
+    compatible_request :post, :update, :id => 1, :issue => {
+      :project_id => 1,
+      :description => 'Test should update issue and tags',
+      :tag_list => %w(new_tag1 new_tag2)
+    }
+    assert_response :redirect
+    issue = Issue.find(1)
+    assert_equal 'Test should update issue and tags', issue.description
+    assert_equal %w(new_tag1 new_tag2), issue.tag_list.sort
+  end
+
+  def test_post_bulk_edit_without_tag_list
+    issue1 = Issue.find(1)
+    issue1.tags = [@tag]
+
+    issue2 = Issue.find(2)
+    issue2.tags = [@last_tag]
+
+    compatible_request :post, :bulk_update, :ids => [1, 2], :issue => { :project_id => '', :tracker_id => '' }
+    assert_response :redirect
+    assert_equal [@tag.name], Issue.find(1).tag_list
+    assert_equal [@last_tag.name], Issue.find(2).tag_list
+  ensure
+    issue1.tags = []
+    issue2.tags = []
+    RedmineTags.unstub(:settings)
+  end
+
+  def test_post_bulk_edit_with_empty_string_tags
+    (1..2).each { |i| assert_equal %w(second third), Issue.find(i).tag_list.sort }
+    compatible_request :post, :bulk_update, :ids => [1, 2], :issue => { :project_id => '', :tracker_id => '', :tag_list => ['', ''] }
+    assert_response :redirect
+    (1..2).each { |i| assert_equal [], Issue.find(i).tag_list }
+  end
+
+  def test_post_bulk_edit_with_changed_tags
+    issue1 = Issue.find(1)
+    issue1.tags << @tag
+
+    issue2 = Issue.find(2)
+    issue2.tags << @last_tag
+
+    compatible_request :post, :bulk_update, :ids => [1, 2], :issue => { :project_id => '', :tracker_id => '', :tag_list => ['bulk_tag'] }
+    assert_response :redirect
+    assert_equal ['bulk_tag'], Issue.find(1).tag_list
+    assert_equal ['bulk_tag'], Issue.find(2).tag_list
+  ensure
+    issue1.tags = []
+    issue2.tags = []
+    RedmineTags.unstub(:settings)
+  end
+
+
+  def test_edit_tags_permission
+    # User(id: 2) has role Manager in Project(id: 1) and Project(id: 1) contains Issue(id: 1)
+    @request.session[:user_id] = 2
+    manager_role = Role.find(1)
+    manager_role.add_permission! :edit_tags
+    tag = 'first'
+
+    assert_not_equal Issue.find(1).tag_list, [tag]
+    assert Issue.all_tags.map(&:name).include?(tag)
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :tag_list => [tag] }
+    assert_response :redirect
+    assert_equal Issue.find(1).tag_list, [tag]
+
+    manager_role.remove_permission! :edit_tags
+    tag2 = 'second'
+
+    assert Issue.all_tags.map(&:name).include?(tag2)
+    assert_equal Issue.find(1).description, 'Unable to print recipes'
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :description => 'New description', :tag_list => [tag2] }
+    assert_response :redirect
+    issue = Issue.find(1)
+    assert_equal 'New description', issue.description
+    assert_equal [tag], issue.tag_list
+  end
+
+  def test_create_tags_permission
+    @request.session[:user_id] = 2
+    manager_role = Role.find(1)
+    manager_role.add_permission! :edit_tags, :create_tags
+    new_tag = 'enable_create_tags_permission'
+
+    assert_not_equal Issue.find(1).tag_list, [new_tag]
+    assert !Issue.all_tags.map(&:name).include?(new_tag) # The project should not contain the new tag
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :tag_list => [new_tag] }
+    assert_response :redirect
+    assert_equal [new_tag], Issue.find(1).tag_list
+
+    manager_role.remove_permission! :create_tags
+    new_tag2 = 'disable_create_tags_permission'
+
+    assert !Issue.all_tags.map(&:name).include?(new_tag2)
+    compatible_request :post, :update, :id => 1, :issue => { :project_id => 1, :tag_list => [new_tag2] }
+    assert_response :redirect
+    assert_equal Issue.find(1).tag_list, [new_tag]
+  end
+end
diff --git a/plugins/redmineup_tags/test/functional/tags_controller_test.rb b/plugins/redmineup_tags/test/functional/tags_controller_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e415b144c1f3f1112d5ff676f9964cde068a29d
--- /dev/null
+++ b/plugins/redmineup_tags/test/functional/tags_controller_test.rb
@@ -0,0 +1,103 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class TagsControllerTest < ActionController::TestCase
+  fixtures :projects,
+           :users,
+           :roles,
+           :members,
+           :member_roles,
+           :issues,
+           :issue_statuses,
+           :versions,
+           :trackers,
+           :projects_trackers,
+           :issue_categories,
+           :enabled_modules,
+           :enumerations,
+           :attachments,
+           :workflows,
+           :custom_fields,
+           :custom_values,
+           :custom_fields_projects,
+           :custom_fields_trackers
+
+
+  def setup
+    # run as the admin
+    @request.session[:user_id] = 1
+
+    @project_a = Project.generate!
+    @project_b = Project.generate!
+
+    add_issue @project_a, %w{a1 a2}, false
+    add_issue @project_a, %w{a2 a3}, false
+    add_issue @project_a, %w{a4 a5}, true
+    add_issue @project_b, %w{b6 b7}, true
+    add_issue @project_b, %w{b8 b9}, false
+  end
+
+  def test_should_get_edit
+    tag = RedmineCrm::Tag.find_by_name('a1')
+    compatible_request :get, :edit, :id => tag.id
+    assert_response :success
+    assert_select "input#tag_name[value='#{tag.name}']", 1
+  end
+
+  def test_should_put_update
+    tag1 = RedmineCrm::Tag.find_by_name('a1')
+    new_name = 'updated main'
+    compatible_request :put, :update, :id => tag1.id, :tag => { :name => new_name }
+    assert_redirected_to :controller => 'settings', :action => 'plugin', :id => 'redmineup_tags', :tab => 'manage_tags'
+    tag1.reload
+    assert_equal new_name, tag1.name
+  end
+
+  test "should delete destroy" do
+    tag1 = RedmineCrm::Tag.find_by_name("a1")
+    assert_difference 'RedmineCrm::Tag.count', -1 do
+      compatible_request :post, :destroy, :ids => tag1.id
+      assert_response 302
+    end
+  end
+
+  test "should post merge" do
+    tag1 = RedmineCrm::Tag.find_by_name("a1")
+    tag2 = RedmineCrm::Tag.find_by_name("b8")
+    assert_difference 'RedmineCrm::Tag.count', -1 do
+      compatible_request :post, :merge, :ids => [tag1.id, tag2.id], :tag => {:name => "a1"}
+      assert_redirected_to :controller => 'settings', :action => 'plugin', :id => "redmineup_tags", :tab => "manage_tags"
+    end
+    assert_equal 0, Issue.tagged_with("b8").count
+    assert_equal 2, Issue.tagged_with("a1").count
+  end
+
+  private
+
+  def add_issue(project, tags, closed)
+    issue = Issue.generate!(:project_id => project.id)
+    issue.tag_list = tags
+    issue.status = IssueStatus.where(:is_closed => true).first if closed
+    issue.save
+  end
+end
diff --git a/plugins/redmineup_tags/test/test_helper.rb b/plugins/redmineup_tags/test/test_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..78d94c89d83b5bd2c702b3aea2f4742fa7ae599b
--- /dev/null
+++ b/plugins/redmineup_tags/test/test_helper.rb
@@ -0,0 +1,52 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
+
+
+def plugin_fixtures(*fixtures)
+  fixtures_directory = "#{File.dirname(__FILE__)}/fixtures/"
+  fixture_names =
+    if fixtures.first == :all
+      Dir["#{fixtures_directory}/**/*.{yml}"].map do |file_path|
+        file_path[(fixtures_directory.size + 1)..-5]
+      end
+    else
+      fixtures.flatten.map { |n| n.to_s }
+    end
+
+  if ActiveRecord::VERSION::MAJOR >= 4
+    ActiveRecord::FixtureSet.create_fixtures fixtures_directory, fixture_names
+  else
+    ActiveRecord::Fixtures.create_fixtures fixtures_directory, fixture_names
+  end
+end
+
+plugin_fixtures :all
+
+
+def compatible_request(type, action, parameters = {})
+  Rails.version < '5.1' ? send(type, action, parameters) : send(type, action, :params => parameters)
+end
+
+def compatible_xhr_request(type, action, parameters = {})
+  Rails.version < '5.1' ? xhr(type, action, parameters) : send(type, action, :params => parameters, :xhr => true)
+end
diff --git a/plugins/redmineup_tags/test/unit/issue_test.rb b/plugins/redmineup_tags/test/unit/issue_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0be3dc2c73e3ca7a030c4b7fd32832687e25c252
--- /dev/null
+++ b/plugins/redmineup_tags/test/unit/issue_test.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+#
+# This file is a part of Redmine Tags (redmine_tags) plugin,
+# customer relationship management plugin for Redmine
+#
+# Copyright (C) 2011-2018 RedmineUP
+# http://www.redmineup.com/
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+require File.expand_path('../../test_helper', __FILE__)
+
+class RedmineTags::Patches::IssueTest < ActiveSupport::TestCase
+  fixtures :users, :projects, :issues, :issue_statuses, :enumerations, :trackers
+
+  def setup
+    # run as the admin
+    User.stubs(:current).returns(users(:users_001))
+
+    @project_a = Project.find 1
+    @project_b = Project.find 3
+  end
+
+  test "patch was applied" do
+    assert_respond_to Issue, :available_tags, 'Issue has available_tags getter'
+    assert_respond_to Issue.new, :tags, 'Issue instance has tags getter'
+    assert_respond_to Issue.new, :tags=, 'Issue instance has tags setter'
+    assert_respond_to Issue.new, :tag_list=, 'Issue instance has tag_list setter'
+  end
+
+  test "available tags should return list of distinct tags" do
+    assert_equal 3, Issue.available_tags.to_a.size
+  end
+
+  test "available tags should allow list tags of open issues only" do
+    assert_equal 2, Issue.available_tags(:open_only => true).to_a.size
+  end
+
+  test "available tags should allow list tags of specific project only" do
+    assert_equal 3, Issue.available_tags(:project => @project_a).to_a.size
+    assert_equal 2, Issue.available_tags(:project => @project_b).to_a.size
+
+    assert_equal 2, Issue.available_tags(:open_only => true, :project => @project_a).to_a.size
+    assert_equal 2, Issue.available_tags(:open_only => true, :project => @project_b).to_a.size
+  end
+
+  test "available tags should allow list tags found by name" do
+    assert_equal 2, Issue.available_tags(:name_like => 'i').to_a.size
+    assert_equal 1, Issue.available_tags(:name_like => 'rd').to_a.size
+    assert_equal 2, Issue.available_tags(:name_like => 's').to_a.size
+    assert_equal 1, Issue.available_tags(:name_like => 'e').to_a.size
+
+    assert_equal 1, Issue.available_tags(:name_like => 'f', :project => @project_a).to_a.size
+    assert_equal 0, Issue.available_tags(:name_like => 'b', :project => @project_a).to_a.size
+    assert_equal 1, Issue.available_tags(:name_like => 'sec', :open_only => true, :project => @project_a).to_a.size
+    assert_equal 0, Issue.available_tags(:name_like => 'fir', :open_only => true, :project => @project_a).to_a.size
+  end
+
+  test 'Issue.all_tags should return all tags kind of Issue' do
+    tags = Issue.all_tags.map &:name
+    assert_equal %w(first second third), tags
+  end
+end